Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions packages/acp-bridge/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
createIdleWorkspaceSkillsStatus,
mapDomainErrorToErrorKind,
type ServePreflightCell,
type ServeSessionStatsStatus,
type ServeSessionTasksStatus,
type ServeStatusCell,
} from './status.js';
Expand Down Expand Up @@ -589,6 +590,19 @@ function hasControlCharacter(value: string): boolean {

const DEFAULT_INIT_TIMEOUT_MS = 10_000;
const PERSIST_TIMEOUT_MS = 5_000;
/**
* #4282 fold-in 2 (gpt-5.5 CV2). Bridge-race deadline for the
* `workspace/mcp/:server/restart` ACP extMethod. The MCP manager's
* per-server discovery deadline can be up to 5 minutes
* (`McpClientManager.MAX_DISCOVERY_TIMEOUT_MS`), so reusing
* `initTimeoutMs` (10s) here produced a guaranteed false-timeout for
* any stdio MCP server slower than 10s while the ACP child kept
* reconnecting in the background. The bridge race is purely a safety
* net against a completely wedged ACP channel; it should be at least
* as long as the slowest legitimate per-server discovery.
*/
const MCP_RESTART_TIMEOUT_MS = 300_000;
const MCP_OAUTH_TIMEOUT_MS = 600_000;
/**
* Backstop timeout for `qwen/control/session/recap`. The underlying
* side-query is single-attempt with `maxOutputTokens: 300`, so a
Expand Down Expand Up @@ -3174,6 +3188,13 @@ export function createHttpAcpBridge(opts: BridgeOptions): HttpAcpBridge {
);
},

async getSessionStatsStatus(sessionId) {
return requestSessionStatus<ServeSessionStatsStatus>(
sessionId,
SERVE_STATUS_EXT_METHODS.sessionStats,
);
},

async setSessionModel(sessionId, req, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(sessionId);
Expand Down Expand Up @@ -3823,6 +3844,67 @@ export function createHttpAcpBridge(opts: BridgeOptions): HttpAcpBridge {
return response;
},

async manageMcpServer(serverName, action, originatorClientId) {
const info = liveChannelInfo();
if (!info) {
throw new SessionNotFoundError(`mcp:${serverName}`);
}
const timeout =
action === 'authenticate'
? MCP_OAUTH_TIMEOUT_MS
: MCP_RESTART_TIMEOUT_MS;
const response = (await Promise.race([
withTimeout(
info.connection.extMethod(
SERVE_CONTROL_EXT_METHODS.workspaceMcpManage,
{ serverName, action, originatorClientId },
),
timeout,
SERVE_CONTROL_EXT_METHODS.workspaceMcpManage,
),
getChannelClosedReject(info),
])) as {
serverName: string;
action: 'enable' | 'disable' | 'authenticate' | 'clear-auth';
ok: true;
changed?: boolean;
messages?: string[];
authUrl?: string;
};
broadcastWorkspaceEvent({
type: 'mcp_server_changed',
data: {
serverName: response.serverName,
action: response.action,
originatorClientId,
},
...(originatorClientId ? { originatorClientId } : {}),
});
return response;
},

async generateWorkspaceAgent(description, _originatorClientId) {
const info = liveChannelInfo();
if (!info) {
throw new SessionNotFoundError('agents:generate');
}
return (await Promise.race([
withTimeout(
info.connection.extMethod(
SERVE_CONTROL_EXT_METHODS.workspaceAgentGenerate,
{ description },
),
MCP_RESTART_TIMEOUT_MS,
SERVE_CONTROL_EXT_METHODS.workspaceAgentGenerate,
),
getChannelClosedReject(info),
])) as {
name: string;
description: string;
systemPrompt: string;
};
},

async addRuntimeMcpServer(name, config, originatorClientId) {
// T2.8 (#4514). Round-trip the runtime-add ext-method through the
// live ACP child and broadcast an `mcp_server_added` event on
Expand Down
26 changes: 26 additions & 0 deletions packages/acp-bridge/src/bridgeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type {
ServeWorkspaceSkillsStatus,
ServeWorkspaceToolsStatus,
ServeSessionContextUsageStatus,
ServeSessionStatsStatus,
} from './status.js';

export interface BridgeSpawnRequest {
Expand Down Expand Up @@ -340,6 +341,9 @@ export interface HttpAcpBridge {
/** Read the live background task snapshot for a live session. */
getSessionTasksStatus(sessionId: string): Promise<ServeSessionTasksStatus>;

/** Read structured session usage stats (tokens, tools, files). */
getSessionStatsStatus(sessionId: string): Promise<ServeSessionStatsStatus>;

/**
* Switch the active model service for a session. Throws
* `SessionNotFoundError` for unknown ids.
Expand Down Expand Up @@ -523,6 +527,28 @@ export interface HttpAcpBridge {
}
>;

manageMcpServer(
serverName: string,
action: 'enable' | 'disable' | 'authenticate' | 'clear-auth',
Comment thread
ytahdn marked this conversation as resolved.
originatorClientId: string | undefined,
): Promise<{
serverName: string;
action: 'enable' | 'disable' | 'authenticate' | 'clear-auth';
ok: true;
changed?: boolean;
messages?: string[];
authUrl?: string;
}>;

generateWorkspaceAgent(
description: string,
originatorClientId: string | undefined,
): Promise<{
name: string;
description: string;
systemPrompt: string;
}>;

/**
* Tear down a session — kill the child, drop from maps, publish
* `session_died`. Idempotent on already-dead sessions.
Expand Down
74 changes: 73 additions & 1 deletion packages/acp-bridge/src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export const SERVE_STATUS_EXT_METHODS = {
sessionContextUsage: 'qwen/status/session/context_usage',
sessionSupportedCommands: 'qwen/status/session/supported_commands',
sessionTasks: 'qwen/status/session/tasks',
sessionStats: 'qwen/status/session/stats',
} as const;

/**
Expand All @@ -122,6 +123,8 @@ export const SERVE_CONTROL_EXT_METHODS = {
sessionBtw: 'qwen/control/session/btw',
sessionShellHistory: 'qwen/control/session/shell_history',
workspaceMcpRestart: 'qwen/control/workspace/mcp/restart',
workspaceMcpManage: 'qwen/control/workspace/mcp/manage',
workspaceAgentGenerate: 'qwen/control/workspace/agents/generate',
// T2.8 (#4514): runtime MCP server mutation ext-methods
workspaceMcpRuntimeAdd: 'qwen/control/workspace/mcp/runtime-add',
workspaceMcpRuntimeRemove: 'qwen/control/workspace/mcp/runtime-remove',
Expand Down Expand Up @@ -167,6 +170,15 @@ export interface ServeWorkspaceMcpServerStatus extends ServeStatusCell {
mcpStatus?: ServeMcpServerRuntimeStatus;
transport: ServeMcpTransport;
disabled: boolean;
hasOAuthTokens?: boolean;
source?: 'user' | 'project' | 'extension';
config?: {
command?: string;
args?: string[];
httpUrl?: string;
url?: string;
cwd?: string;
};
description?: string;
extensionName?: string;
/**
Expand Down Expand Up @@ -321,6 +333,8 @@ export interface ServeWorkspaceSkillsStatus {
export interface ServeWorkspaceProviderCurrent {
authType?: string;
modelId?: string;
baseUrl?: string;
fastModelId?: string;
}

export interface ServeWorkspaceProviderModel {
Expand All @@ -329,6 +343,14 @@ export interface ServeWorkspaceProviderModel {
name: string;
description?: string | null;
contextLimit?: number;
modalities?: {
image?: boolean;
pdf?: boolean;
audio?: boolean;
video?: boolean;
};
baseUrl?: string;
envKey?: string;
isCurrent: boolean;
isRuntime: boolean;
}
Expand Down Expand Up @@ -494,6 +516,55 @@ export interface ServeSessionTasksStatus {
tasks: ServeSessionTaskStatus[];
}

export interface ServeSessionStatsModelMetrics {
api: {
totalRequests: number;
totalErrors: number;
totalLatencyMs: number;
};
tokens: {
prompt: number;
candidates: number;
total: number;
cached: number;
thoughts: number;
};
}

export interface ServeSessionStatsToolByName {
count: number;
success: number;
fail: number;
durationMs: number;
decisions: {
accept: number;
reject: number;
modify: number;
auto_accept: number;
};
}

export interface ServeSessionStatsStatus {
v: typeof STATUS_SCHEMA_VERSION;
sessionId: string;
workspaceCwd: string;
sessionStartTimeMs: number;
durationMs: number;
promptCount: number;
models: Record<string, ServeSessionStatsModelMetrics>;
tools: {
totalCalls: number;
totalSuccess: number;
totalFail: number;
totalDurationMs: number;
byName: Record<string, ServeSessionStatsToolByName>;
};
files: {
totalLinesAdded: number;
totalLinesRemoved: number;
};
}

/**
* Issue #4175 PR 16: workspace memory + agents read surfaces.
*
Expand Down Expand Up @@ -690,7 +761,8 @@ export type ServeEnvKind =
| 'platform'
| 'sandbox'
| 'proxy'
| 'env_var';
| 'env_var'
| 'memory';

export interface ServeEnvCell extends ServeStatusCell {
kind: ServeEnvKind;
Expand Down
Loading