Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
40 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
4183ee7
fix(serve): refuse to report a workflow history deletion whose regist…
qqqys Aug 28, 2026
6bd83e7
Merge remote-tracking branch 'upstream/main' into codex/issue-9033-wo…
qqqys Aug 28, 2026
4c68068
docs(serve): fix a stale method reference in the workflow history com…
qqqys Aug 28, 2026
9d19374
Merge remote-tracking branch 'upstream/main' into feat/workflow-daemo…
qqqys Aug 29, 2026
7a88723
fix: report workflow lifecycle changes
qqqys Aug 29, 2026
0653b42
fix(core): repair the workflow test doubles and converge every eviction
qqqys Aug 29, 2026
c1c0072
fix(cli): drop the duplicated mock keys and stop misdirecting a start…
qqqys Aug 29, 2026
dbf7cb1
fix(cli): preserve starting workflow guidance
qqqys Aug 30, 2026
26abdbe
test(cli): pin background work hint guards
qqqys Aug 30, 2026
2e56793
Merge remote-tracking branch 'upstream/main' into feat/workflow-daemo…
qqqys Aug 30, 2026
ac988b8
fix(serve): redact untrusted standalone workflow replay
qqqys Aug 30, 2026
dce0277
test(web-shell): fix daemon SDK mock path
qqqys Aug 30, 2026
1bd5b3d
Merge remote-tracking branch 'upstream/main' into feat/workflow-daemo…
qqqys Aug 30, 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 @@ -3161,7 +3161,9 @@ describe('createAcpSessionBridge', () => {
availableSkills: [],
});
await expect(
bridge.getSessionTasksStatus(session.sessionId),
bridge.getSessionTasksStatus(session.sessionId, {
includeWorkflows: true,
}),
).resolves.toMatchObject({
sessionId: session.sessionId,
tasks: [],
Expand All @@ -3185,6 +3187,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 @@ -23073,6 +23079,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 @@ -80,6 +80,7 @@ import {
type ServeSessionContextStatus,
type ServeSessionLspStatus,
type ServeSessionTasksStatus,
type ServeSessionWorkflowTaskStatus,
type ServeWorkspaceMcpResourcesStatus,
type ServeWorkspaceMcpStatus,
type ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -10919,10 +10920,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 @@ -10937,14 +10939,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 controlSessionGoal(sessionId, request, context) {
const entry = byId.get(sessionId);
if (!entry) throw new SessionNotFoundError(sessionId);
Expand Down
38 changes: 33 additions & 5 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 @@ -312,11 +313,15 @@ export const ACTIVE_WORK_MAX_SESSION_HOLDS = 1024;
export const WORKTREE_MCP_DEFER_META_KEY = 'qwen.session.deferMcpDiscovery';

/**
* Work categories a child reports holds for. Monitors, workflows, and cron
* remain outside `activeWork`'s declared scope. The category travels on every
* Work categories a child reports holds for. Monitors and cron remain outside
* `activeWork`'s declared scope. The category travels on every
* hold so peers can negotiate coverage explicitly when the scope widens.
*/
export type ActiveWorkHoldCategory = 'agent' | 'notification' | 'shell';
export type ActiveWorkHoldCategory =
| 'agent'
| 'notification'
| 'shell'
| 'workflow';

/** Categories understood by active-work v1 before category negotiation was
* added to the daemon's initialize request. */
Expand All @@ -327,6 +332,7 @@ export const ACTIVE_WORK_HOLD_CATEGORIES: readonly ActiveWorkHoldCategory[] = [
'agent',
'notification',
'shell',
'workflow',
];

export interface ActiveWorkHeartbeatCapabilityV1 {
Expand Down Expand Up @@ -1780,7 +1786,10 @@ export interface AcpSessionBridge extends WorkspaceEventBridge {
): 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 @@ -1798,9 +1807,28 @@ export interface AcpSessionBridge extends WorkspaceEventBridge {
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 @@ -176,6 +176,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 @@ -615,6 +616,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 @@ -726,10 +734,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