Skip to content
Closed
21 changes: 20 additions & 1 deletion packages/acp-bridge/src/bridge.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2300,7 +2300,9 @@ describe('createAcpSessionBridge', () => {
availableSkills: [],
});
await expect(
bridge.getSessionTasksStatus(session.sessionId),
bridge.getSessionTasksStatus(session.sessionId, {
includeWorkflows: true,
}),
).resolves.toMatchObject({
sessionId: session.sessionId,
tasks: [],
Expand All @@ -2324,6 +2326,10 @@ describe('createAcpSessionBridge', () => {
'qwen/status/session/tasks',
'qwen/status/session/lsp',
]);
expect(handles[0]?.agent.extMethodCalls[2]?.params).toMatchObject({
sessionId: session.sessionId,
includeWorkflows: true,
});

await bridge.shutdown();
});
Expand Down Expand Up @@ -15272,6 +15278,19 @@ describe('createAcpSessionBridge', () => {
{ clientId: 'client-not-issued' },
),
).rejects.toBeInstanceOf(InvalidClientIdError);
await expect(
bridge.cancelSessionTask(session.sessionId, 'task-1', 'workflow', {
clientId: 'client-not-issued',
}),
).rejects.toBeInstanceOf(InvalidClientIdError);
await expect(
bridge.controlSessionWorkflowTask(
session.sessionId,
'task-1',
'rerun',
{ clientId: 'client-not-issued' },
),
).rejects.toBeInstanceOf(InvalidClientIdError);
await bridge.shutdown();
});

Expand Down
23 changes: 21 additions & 2 deletions packages/acp-bridge/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import {
type ServeSessionContextStatus,
type ServeSessionLspStatus,
type ServeSessionTasksStatus,
type ServeSessionWorkflowTaskStatus,
type ServeWorkspaceMcpResourcesStatus,
type ServeWorkspaceMcpStatus,
type ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -8867,10 +8868,11 @@ export function createAcpSessionBridge(opts: BridgeOptions): AcpSessionBridge {
);
},

async getSessionTasksStatus(sessionId) {
async getSessionTasksStatus(sessionId, opts) {
return requestSessionStatus<ServeSessionTasksStatus>(
sessionId,
SERVE_STATUS_EXT_METHODS.sessionTasks,
{ includeWorkflows: opts?.includeWorkflows === true },
);
},

Expand All @@ -8885,14 +8887,31 @@ export function createAcpSessionBridge(opts: BridgeOptions): AcpSessionBridge {
return requestSessionTranscriptPage(req);
},

async cancelSessionTask(sessionId, taskId, taskKind) {
async cancelSessionTask(sessionId, taskId, taskKind, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(sessionId);
resolveTrustedClientId(entry, context?.clientId);
return requestSessionStatus<{ cancelled: boolean }>(
sessionId,
SERVE_CONTROL_EXT_METHODS.sessionTaskCancel,
{ taskId, taskKind },
);
},

async controlSessionWorkflowTask(sessionId, taskId, action, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(sessionId);
resolveTrustedClientId(entry, context?.clientId);
return requestSessionStatus<{
changed: boolean;
status?: ServeSessionWorkflowTaskStatus['status'];
taskId?: string;
}>(sessionId, SERVE_CONTROL_EXT_METHODS.sessionWorkflowTaskAction, {
taskId,
action,
});
},

async clearSessionGoal(sessionId) {
return requestSessionStatus<{ cleared: boolean; condition?: string }>(
sessionId,
Expand Down
27 changes: 25 additions & 2 deletions packages/acp-bridge/src/bridgeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
ServeSessionLspStatus,
ServeSessionSupportedCommandsStatus,
ServeSessionTasksStatus,
ServeSessionWorkflowTaskStatus,
ServeWorkspaceExtensionsStatus,
ServeWorkspaceHooksStatus,
ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -1447,7 +1448,10 @@ export interface AcpSessionBridge {
): Promise<ServeSessionSupportedCommandsStatus>;

/** Read the live background task snapshot for a live session. */
getSessionTasksStatus(sessionId: string): Promise<ServeSessionTasksStatus>;
getSessionTasksStatus(
sessionId: string,
opts?: { includeWorkflows?: boolean },
): Promise<ServeSessionTasksStatus>;

/** Read sanitized LSP server status for a live session. */
getSessionLspStatus(sessionId: string): Promise<ServeSessionLspStatus>;
Expand All @@ -1465,9 +1469,28 @@ export interface AcpSessionBridge {
cancelSessionTask(
sessionId: string,
taskId: string,
taskKind: 'agent' | 'shell' | 'monitor',
taskKind: 'agent' | 'shell' | 'monitor' | 'workflow',
context?: BridgeClientRequestContext,
): Promise<{ cancelled: boolean }>;

/** Control a run, delete history, or start a saved workflow definition. */
controlSessionWorkflowTask(
sessionId: string,
taskId: string,
action:
| 'pause'
| 'resume'
| 'retry'
| 'rerun'
| 'delete-history'
| 'run-saved',
context?: BridgeClientRequestContext,
): Promise<{
changed: boolean;
status?: ServeSessionWorkflowTaskStatus['status'];
taskId?: string;
}>;

/** Clear an active goal in a live session without cancelling the running prompt. */
clearSessionGoal(
sessionId: string,
Expand Down
124 changes: 123 additions & 1 deletion packages/acp-bridge/src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ export const SERVE_CONTROL_EXT_METHODS = {
workspaceMemoryDream: 'qwen/control/workspace/memory/dream',
// Runtime MCP server mutation ext-methods
sessionTaskCancel: 'qwen/control/session/task/cancel',
sessionWorkflowTaskAction: 'qwen/control/session/task/workflow-action',
sessionGoalClear: 'qwen/control/session/goal/clear',
/**
* Read a live session's `/goal` state. The active goal lives only in the
Expand Down Expand Up @@ -601,6 +602,13 @@ export interface ServeSessionSupportedCommandsStatus {
sessionId: string;
availableCommands: AvailableCommand[];
availableSkills: string[];
/** Whether the Workflow tool and its Web Shell surfaces are enabled. */
workflowsEnabled?: boolean;
/** Reusable workflow definitions visible to this session. */
savedWorkflows?: Array<{
name: string;
source: 'project' | 'user';
}>;
}

export interface ServeLspServerStatus {
Expand Down Expand Up @@ -712,10 +720,124 @@ export interface ServeSessionMonitorTaskStatus {
toolUseId?: string;
}

export interface ServeWorkflowPhaseVisit {
id: string;
index: number;
title: string;
startedAt: number;
endedAt?: number;
}

export type ServeWorkflowDispatchStatus =
| 'queued'
| 'running'
| 'completed'
| 'failed'
| 'cancelled'
| 'cached';

export interface ServeWorkflowDispatchStatusEntry {
id: string;
phaseVisitId: string | null;
label: string;
prompt: string;
subagentId?: string;
status: ServeWorkflowDispatchStatus;
dependsOn: string[];
queuedAt: number;
startedAt?: number;
endedAt?: number;
error?: string;
}

export interface ServeWorkflowApprovalStatusEntry {
approvalId: string;
subagentId: string;
name: string;
description: string;
at: number;
}

interface ServeWorkflowEventBase {
id: string;
at: number;
}

export type ServeWorkflowEvent =
| (ServeWorkflowEventBase & {
type: 'phase-started';
phaseVisitId: string;
title: string;
})
| (ServeWorkflowEventBase & {
type: 'phase-completed';
phaseVisitId: string;
})
| (ServeWorkflowEventBase & {
type:
| 'dispatch-queued'
| 'dispatch-started'
| 'dispatch-completed'
| 'dispatch-cancelled'
| 'dispatch-cached';
dispatchId: string;
})
| (ServeWorkflowEventBase & {
type: 'dispatch-failed';
dispatchId: string;
error: string;
})
| (ServeWorkflowEventBase & { type: 'log'; message: string })
| (ServeWorkflowEventBase & {
type: 'approval-requested' | 'approval-settled';
name: string;
dispatchId?: string;
})
| (ServeWorkflowEventBase & {
type: 'workflow-completed' | 'workflow-cancelled';
})
| (ServeWorkflowEventBase & {
type: 'workflow-failed';
error: string;
});

export interface ServeSessionWorkflowTaskStatus {
kind: 'workflow';
id: string;
/** Tool call in the parent session that launched this workflow. */
toolUseId?: string;
/** Restored from the project snapshot store; controls are read-only. */
isHistorical?: boolean;
sourceRunId?: string;
startMode?: 'retry' | 'rerun';
label: string;
description: string;
status: ServeSessionTaskLifecycleStatus | 'pausing';
startTime: number;
endTime?: number;
runtimeMs: number;
outputFile?: string;
isBackgrounded: boolean;
currentPhase: string | null;
phaseVisits: ServeWorkflowPhaseVisit[];
dispatches: ServeWorkflowDispatchStatusEntry[];
agentsDispatched: number;
agentsCompleted: number;
tokensSpent: number;
tokenBudgetTotal: number | null;
recentLogs: string[];
/** Ordered runtime facts; absent for snapshots created before event tracing. */
events?: ServeWorkflowEvent[];
pendingApprovalCount: number;
pendingApprovals?: ServeWorkflowApprovalStatusEntry[];
error?: string;
}

export type ServeSessionTaskStatus =
| ServeSessionAgentTaskStatus
| ServeSessionShellTaskStatus
| ServeSessionMonitorTaskStatus;
| ServeSessionMonitorTaskStatus
| ServeSessionWorkflowTaskStatus;

export interface ServeSessionTasksStatus {
v: typeof STATUS_SCHEMA_VERSION;
Expand Down
Loading
Loading