Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9bab3c3
feat(serve): expose workflow tasks and controls
qqqys Aug 20, 2026
75f2975
Merge upstream/main into codex/issue-9033-workflow-daemon-api
qqqys Aug 20, 2026
80f94d6
Merge upstream/main into codex/issue-9033-workflow-daemon-api
qqqys Aug 23, 2026
bea0746
fix(serve): align workflow capability gating
qqqys Aug 23, 2026
6febdb2
fix(cli): reject live workflow history deletion
qqqys Aug 23, 2026
fdadec7
fix(serve): start controlled workflows in background
qqqys Aug 23, 2026
135bf45
fix(serve): address workflow control review blockers
qqqys Aug 23, 2026
4ab652a
fix(serve): make workflow history deletion race-safe
qqqys Aug 23, 2026
e139338
chore(serve): resolve merge conflicts with main
qqqys Aug 24, 2026
3497d0d
fix(serve): enforce workspace trust on the daemon Workflow surfaces (…
Aug 25, 2026
32d8934
Merge branch 'main' into codex/issue-9033-workflow-daemon-api
qwen-code-dev-bot Aug 25, 2026
c25a5f9
fix(serve): close cross-session workflow deletion races and untrusted…
qwen-code-dev-bot Aug 25, 2026
2ac1f68
Merge branch 'main' into codex/issue-9033-workflow-daemon-api
qqqys Aug 26, 2026
15847f9
Merge branch 'main' into codex/issue-9033-workflow-daemon-api
qqqys Aug 26, 2026
e80f785
Merge remote-tracking branch 'upstream/main' into tmp-sync-9546
qqqys Aug 26, 2026
eeca4d7
Merge upstream/main into codex/issue-9033-workflow-daemon-api
qqqys Aug 26, 2026
f8506d3
fix(serve): enforce workflow trust gates
qqqys Aug 26, 2026
b799ea6
fix(cli): close the four cross-session workflow-history consistency h…
qqqys Aug 27, 2026
3d6b3bc
Merge remote-tracking branch 'upstream/main' into HEAD
qqqys Aug 27, 2026
f42b285
fix(core): drop the duplicated telemetry-swap mock property
qqqys Aug 27, 2026
0b5d23c
fix(workflows): guard pending run lifecycle
qqqys Aug 27, 2026
6c79490
fix(workflows): let a starting run be cancelled, and stop two slow leaks
qqqys Aug 27, 2026
f119607
fix(core): close the round-5 review findings on workflow task controls
qqqys Aug 28, 2026
c58b3fd
Merge remote-tracking branch 'upstream/main' into codex/issue-9033-wo…
qqqys Aug 28, 2026
2c2c232
Merge branch 'main' into codex/issue-9033-workflow-daemon-api
qqqys Aug 28, 2026
4b441a9
Merge branch 'main' into codex/issue-9033-workflow-daemon-api
qqqys Aug 28, 2026
8bdd956
fix(serve): keep workflow retry and the workflowsEnabled flag consist…
qqqys Aug 28, 2026
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
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 @@ -2740,7 +2740,9 @@ describe('createAcpSessionBridge', () => {
availableSkills: [],
});
await expect(
bridge.getSessionTasksStatus(session.sessionId),
bridge.getSessionTasksStatus(session.sessionId, {
includeWorkflows: true,
}),
).resolves.toMatchObject({
sessionId: session.sessionId,
tasks: [],
Expand All @@ -2764,6 +2766,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 @@ -21997,6 +22003,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 @@ -79,6 +79,7 @@ import {
type ServeSessionContextStatus,
type ServeSessionLspStatus,
type ServeSessionTasksStatus,
type ServeSessionWorkflowTaskStatus,
type ServeWorkspaceMcpResourcesStatus,
type ServeWorkspaceMcpStatus,
type ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -10382,10 +10383,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 @@ -10400,14 +10402,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,
});
},
Comment on lines +10963 to +10965

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred this round: Critical-only mode is active and the batch was capped at the six Critical findings (the cross-session deletion/resurrection cluster and the untrusted-workspace includeWorkflows leak), each landed with regression tests and mutation-probed witnesses. This Suggestion remains queued for a follow-up round. The controlSessionWorkflowTask success-path payload assertion was not added this round.

本轮顺延:当前处于 Critical-only 模式,批次上限为 6 个 Critical 发现(跨会话删除/复活问题簇与不可信工作区的 includeWorkflows 泄露),均已随回归测试和变异探针见证落库。该建议保持排队,留待后续轮次处理。


async controlSessionGoal(sessionId, request, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(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 @@ -46,6 +46,7 @@ import type {
ServeSessionLspStatus,
ServeSessionSupportedCommandsStatus,
ServeSessionTasksStatus,
ServeSessionWorkflowTaskStatus,
ServeWorkspaceExtensionsStatus,
ServeWorkspaceHooksStatus,
ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -1687,7 +1688,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 @@ -1705,9 +1709,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',
sessionGoalControl: 'qwen/control/session/goal/control',
sessionGoalClear: 'qwen/control/session/goal/clear',
/**
Expand Down Expand Up @@ -604,6 +605,13 @@ export interface ServeSessionSupportedCommandsStatus {
sessionId: string;
availableCommands: AvailableCommand[];
availableSkills: string[];
/** Whether Workflow is available for this session. */
workflowsEnabled?: boolean;
/** Reusable workflow definitions visible to this session. */
savedWorkflows?: Array<{
name: string;
source: 'project' | 'user';
}>;
}

export interface ServeLspServerStatus {
Expand Down Expand Up @@ -715,10 +723,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