feat(workflows): dynamic workflow orchestration end to end

A workflow is a JS script the model writes in the moment and hands to the
Workflow tool, which runs it in a locked-down `node:vm` and orchestrates
subagents through `agent()`/`parallel()`/`pipeline()`/`phase()`. Saving one
as a `/name` command is the secondary path; the inline script is the point.

Runtime: cross-realm value marshalling so a script cannot reach the host
`Function`, determinism guards on `Date.now()`/`Math.random()` (they would
make a resume replay diverge), a FIFO concurrency gate, and a journal that
lets an interrupted run resume from its longest unchanged prefix.

Desktop: the run shows up as a `workflow` section in the existing activity
panel — phases as headings, their agents beneath. A workflow agent is an
ordinary subagent run by the same runner, so its row opens the existing
subagent page rather than a parallel viewer of its own; that needed a
`by-agent` lookup, because these agents have no parent `Agent` tool call to
key off. Finished runs are rebuilt from the per-agent sidecars when a
session is reopened, since the live progress stream does not outlive the
process.
This commit is contained in:
程序员阿江(Relakkes)
2026-08-10 00:17:03 +08:00
parent 0f3a517085
commit 3ce7c67822
100 changed files with 10676 additions and 296 deletions
+66 -33
View File
@@ -1,34 +1,67 @@
// @generated stub from scan-missing-imports
// 该文件自动生成,对应 ant-internal 的 feature() gated 模块。
// 所有外部 build 的代码路径在 DCE 后都不会真的执行这里的代码,这只是
// bun build resolver 的占位符。
const __target = function noop() {}
const __handler: ProxyHandler<any> = {
get(_t, prop) {
if (prop === '__esModule') return true
if (prop === 'default') return new Proxy(__target, __handler)
if (prop === Symbol.toPrimitive) return () => undefined
if (prop === Symbol.iterator) return function* () {}
if (prop === Symbol.asyncIterator) return async function* () {}
if (prop === 'then') return undefined
return new Proxy(__target, __handler)
},
apply() {
return new Proxy(__target, __handler)
},
construct() {
return new Proxy(__target, __handler)
},
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'
import type { Command } from '../../types/command.js'
import { loadWorkflows } from '../../utils/workflows/discovery.js'
import { areWorkflowsEnabled } from '../../utils/workflows/enabled.js'
import type { WorkflowDefinition } from '../../utils/workflows/types.js'
import { WORKFLOW_TOOL_NAME } from './constants.js'
/**
* Turn every discovered workflow into a `/<name>` command.
*
* The command is a prompt, not a direct tool call: whatever the user typed
* after the name has to become the `args` value, and only the model can decide
* whether "issues 1024, 1025" is a list of numbers or a sentence. The prompt
* pins everything else so the model's only job is that conversion.
*/
export async function getWorkflowCommands(cwd?: string): Promise<Command[]> {
if (!areWorkflowsEnabled()) return []
const workflows = await loadWorkflows(cwd)
return workflows.map(workflow => createWorkflowCommand(workflow))
}
export function createWorkflowCommand(workflow: WorkflowDefinition): Command {
return {
type: 'prompt',
name: workflow.name,
description: workflow.description,
progressMessage: `running the ${workflow.name} workflow`,
contentLength: workflow.description.length + 200,
argNames: ['args'],
source: workflowCommandSource(workflow),
isEnabled: () => areWorkflowsEnabled(),
userFacingName: () => workflow.name,
async getPromptForCommand(args: string): Promise<ContentBlockParam[]> {
const argsLine =
args.trim() === ''
? 'Pass no `args`.'
: `Convert the following invocation text into the \`args\` value and pass it: ${JSON.stringify(args.trim())}. ` +
'If it is a list of items, pass a real JSON array, not a string.'
return [
{
type: 'text',
text:
`Run the saved workflow "${workflow.name}" by calling ${WORKFLOW_TOOL_NAME} exactly once with ` +
`{ name: "${workflow.name}" }. ${argsLine}\n` +
'Do not write a new script and do not do the work yourself — the saved workflow already ' +
'contains the orchestration. After the tool returns, end your turn; the result arrives as a ' +
'task notification.',
},
]
},
} as Command
}
function workflowCommandSource(
workflow: WorkflowDefinition,
): 'builtin' | 'userSettings' | 'projectSettings' | 'plugin' {
switch (workflow.source) {
case 'built-in':
return 'builtin'
case 'plugin':
return 'plugin'
case 'projectSettings':
return 'projectSettings'
default:
return 'userSettings'
}
}
const stub: any = new Proxy(__target, __handler)
export default stub
export const __stubMissing = true
// 兼容常见的命名导出 —— 没列在这里的也会通过 default Proxy 兜底
export const createCachedMCState = stub
export const isCachedMicrocompactEnabled = stub
export const isModelSupportedForCacheEditing = stub
export const getCachedMCConfig = stub
export const markToolsSentToAPI = stub
export const resetCachedMCState = stub
export const checkProtectedNamespace = stub
export const getCoordinatorUserContext = stub