Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
0187849
feat(web-shell): visualize workflow runs
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
fefccfb
Merge remote-tracking branch 'origin/codex/issue-9033-workflow-daemon…
qqqys Aug 23, 2026
5774f61
fix(web-shell): harden workflow history UX
qqqys Aug 23, 2026
fdadec7
fix(serve): start controlled workflows in background
qqqys Aug 23, 2026
95e8df7
Merge remote-tracking branch 'origin/codex/issue-9033-workflow-daemon…
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
c82ade0
merge: prepare final Workflow Web Shell demo
qqqys Aug 25, 2026
52206ee
feat(web-shell): add workflow creation entry
qqqys Aug 25, 2026
ad9bf4e
style(web-shell): flatten the workflow execution graph
Aug 25, 2026
f6b1830
Merge remote-tracking branch 'origin/codex/issue-8941-workflow-web-sh…
Aug 25, 2026
750029e
feat(web-shell): open a saved workflow's definition from the Saved tab
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
ed400eb
Merge branch 'main' into codex/issue-8941-workflow-web-shell
wenshao 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
8a7a63f
Merge branch 'feat/workflow-daemon-api' into feat/workflow-web-shell
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
24 changes: 23 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 @@ -4265,6 +4271,9 @@ describe('createAcpSessionBridge', () => {
await expect(bridge.getSessionLspStatus('missing')).rejects.toBeInstanceOf(
SessionNotFoundError,
);
await expect(
bridge.getSessionSavedWorkflow('missing', 'deep-review'),
).rejects.toBeInstanceOf(SessionNotFoundError);
});

it('reuses an echoed daemon-issued client id on attach', async () => {
Expand Down Expand Up @@ -23073,6 +23082,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
32 changes: 30 additions & 2 deletions packages/acp-bridge/src/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ import {
type ServeSessionStatsStatus,
type ServeSessionContextStatus,
type ServeSessionLspStatus,
type ServeSessionSavedWorkflowStatus,
type ServeSessionTasksStatus,
type ServeSessionWorkflowTaskStatus,
type ServeWorkspaceMcpResourcesStatus,
type ServeWorkspaceMcpStatus,
type ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -10919,10 +10921,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 @@ -10933,18 +10936,43 @@ export function createAcpSessionBridge(opts: BridgeOptions): AcpSessionBridge {
);
},

async getSessionSavedWorkflow(sessionId, name) {

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.

[Suggestion] R1-26: The bridge-level happy path of getSessionSavedWorkflow — forwarding { name } as the ext-method params — is untested; only the SessionNotFoundError rejection is covered (unlike the sibling includeWorkflows param, which got an assertion). A regression renaming the forwarded key ships green (measured: {workflowName} mutation → 804/804); the child would then reject every read with "Invalid or missing name" and the Runs page would show every saved definition as unreadable.

Witness:

[probe] renamed the forwarded key to {workflowName: name} → bridge.test.ts
804/804 green.

Extend the extMethodCalls-asserting bridge test: attach a session, call getSessionSavedWorkflow(id, 'deep-review'), assert the recorded params are {sessionId, name:'deep-review'} — red the moment the name stops reaching the child as name.

中文说明 getSessionSavedWorkflow 的桥接层正常路径(转发 {name})没有测试:把键名改错的回归全绿(已实测),子进程会拒绝所有读取,Runs 页全部显示定义不可读。请在 extMethodCalls 断言中补充该参数检查。

— glm-5.3 via Qwen Code /review (v0.22.2)

return requestSessionStatus<ServeSessionSavedWorkflowStatus>(
sessionId,
SERVE_STATUS_EXT_METHODS.sessionSavedWorkflow,
{ name },
);
},

async getSessionTranscriptPage(req) {
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
49 changes: 44 additions & 5 deletions packages/acp-bridge/src/bridgeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@ import type {
ServeSessionContextStatus,
ServeSessionHooksStatus,
ServeSessionLspStatus,
ServeSessionSavedWorkflowStatus,
ServeSessionSupportedCommandsStatus,
ServeSessionTasksStatus,
ServeSessionWorkflowTaskStatus,
ServeWorkspaceExtensionsStatus,
ServeWorkspaceHooksStatus,
ServeWorkspaceMcpToolsStatus,
Expand Down Expand Up @@ -312,11 +314,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 +333,7 @@ export const ACTIVE_WORK_HOLD_CATEGORIES: readonly ActiveWorkHoldCategory[] = [
'agent',
'notification',
'shell',
'workflow',
];

export interface ActiveWorkHeartbeatCapabilityV1 {
Expand Down Expand Up @@ -1777,11 +1784,24 @@ 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>;

/**
* Read one saved workflow definition visible to a live session. The
* envelope's `workflow` is null when the name is unknown or Workflow
* controls are unavailable.
*/
getSessionSavedWorkflow(
sessionId: string,
name: string,
): Promise<ServeSessionSavedWorkflowStatus>;

/**
* Read a page of persisted transcript replay events through the ACP child.
* This is workspace-scoped and read-only: implementations must not attach a
Expand All @@ -1795,9 +1815,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
Loading
Loading