@chengchenccc/oh-my-agent 0.1.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +120 -0
- package/dist/cli/initial-input.d.ts +32 -0
- package/dist/cli/initial-input.d.ts.map +1 -0
- package/dist/cli/initial-input.js +131 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/core/__fixtures__/echo-model.d.ts +6 -0
- package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
- package/dist/core/__fixtures__/echo-model.js +10 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
- package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
- package/dist/core/agent-runtime.d.ts +21 -0
- package/dist/core/agent-runtime.d.ts.map +1 -0
- package/dist/core/agent-runtime.js +14 -0
- package/dist/core/memory/autonomous-memory.d.ts +21 -0
- package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
- package/dist/core/memory/autonomous-memory.js +202 -0
- package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
- package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
- package/dist/core/persistence/in-memory-session-store.js +109 -0
- package/dist/core/persistence/session-store.contract.d.ts +5 -0
- package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
- package/dist/core/persistence/session-store.contract.js +183 -0
- package/dist/core/persistence/session-store.d.ts +33 -0
- package/dist/core/persistence/session-store.d.ts.map +1 -0
- package/dist/core/persistence/session-store.js +22 -0
- package/dist/core/persistence/session-tree.d.ts +49 -0
- package/dist/core/persistence/session-tree.d.ts.map +1 -0
- package/dist/core/persistence/session-tree.js +1 -0
- package/dist/core/plugins/plugin-code.d.ts +13 -0
- package/dist/core/plugins/plugin-code.d.ts.map +1 -0
- package/dist/core/plugins/plugin-code.js +62 -0
- package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
- package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
- package/dist/core/plugins/plugin-marketplace.js +340 -0
- package/dist/core/plugins/plugin-resolve.d.ts +32 -0
- package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
- package/dist/core/plugins/plugin-resolve.js +101 -0
- package/dist/core/plugins/plugin-trust.d.ts +24 -0
- package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugins/plugin-trust.js +89 -0
- package/dist/core/runtime/agent-event.d.ts +98 -0
- package/dist/core/runtime/agent-event.d.ts.map +1 -0
- package/dist/core/runtime/agent-event.js +1 -0
- package/dist/core/runtime/agent-loop.d.ts +142 -0
- package/dist/core/runtime/agent-loop.d.ts.map +1 -0
- package/dist/core/runtime/agent-loop.js +1008 -0
- package/dist/core/runtime/approval.d.ts +23 -0
- package/dist/core/runtime/approval.d.ts.map +1 -0
- package/dist/core/runtime/approval.js +25 -0
- package/dist/core/runtime/compaction.d.ts +22 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -0
- package/dist/core/runtime/compaction.js +90 -0
- package/dist/core/runtime/context-estimate.d.ts +42 -0
- package/dist/core/runtime/context-estimate.d.ts.map +1 -0
- package/dist/core/runtime/context-estimate.js +34 -0
- package/dist/core/runtime/create-runtime.d.ts +81 -0
- package/dist/core/runtime/create-runtime.d.ts.map +1 -0
- package/dist/core/runtime/create-runtime.js +250 -0
- package/dist/core/runtime/fake-provider.d.ts +3 -0
- package/dist/core/runtime/fake-provider.d.ts.map +1 -0
- package/dist/core/runtime/fake-provider.js +87 -0
- package/dist/core/runtime/logger.d.ts +13 -0
- package/dist/core/runtime/logger.d.ts.map +1 -0
- package/dist/core/runtime/logger.js +38 -0
- package/dist/core/runtime/loop-input.d.ts +39 -0
- package/dist/core/runtime/loop-input.d.ts.map +1 -0
- package/dist/core/runtime/loop-input.js +38 -0
- package/dist/core/runtime/message-cache.d.ts +16 -0
- package/dist/core/runtime/message-cache.d.ts.map +1 -0
- package/dist/core/runtime/message-cache.js +23 -0
- package/dist/core/runtime/model-catalog.d.ts +10 -0
- package/dist/core/runtime/model-catalog.d.ts.map +1 -0
- package/dist/core/runtime/model-catalog.js +14 -0
- package/dist/core/runtime/plugin-runtime.d.ts +41 -0
- package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
- package/dist/core/runtime/plugin-runtime.js +1 -0
- package/dist/core/runtime/plugin.d.ts +86 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -0
- package/dist/core/runtime/plugin.js +23 -0
- package/dist/core/runtime/process-tree.d.ts +12 -0
- package/dist/core/runtime/process-tree.d.ts.map +1 -0
- package/dist/core/runtime/process-tree.js +40 -0
- package/dist/core/runtime/prompt.d.ts +23 -0
- package/dist/core/runtime/prompt.d.ts.map +1 -0
- package/dist/core/runtime/prompt.js +29 -0
- package/dist/core/runtime/prompts.d.ts +16 -0
- package/dist/core/runtime/prompts.d.ts.map +1 -0
- package/dist/core/runtime/prompts.js +36 -0
- package/dist/core/runtime/retry.d.ts +20 -0
- package/dist/core/runtime/retry.d.ts.map +1 -0
- package/dist/core/runtime/retry.js +60 -0
- package/dist/core/runtime/run-runtime.d.ts +93 -0
- package/dist/core/runtime/run-runtime.d.ts.map +1 -0
- package/dist/core/runtime/run-runtime.js +664 -0
- package/dist/core/runtime/runtime-catalog.d.ts +12 -0
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/core/runtime/runtime-catalog.js +89 -0
- package/dist/core/runtime/stream-rules.d.ts +9 -0
- package/dist/core/runtime/stream-rules.d.ts.map +1 -0
- package/dist/core/runtime/stream-rules.js +44 -0
- package/dist/core/runtime/title.d.ts +7 -0
- package/dist/core/runtime/title.d.ts.map +1 -0
- package/dist/core/runtime/title.js +114 -0
- package/dist/core/runtime/todo.d.ts +16 -0
- package/dist/core/runtime/todo.d.ts.map +1 -0
- package/dist/core/runtime/todo.js +40 -0
- package/dist/core/runtime/tool-filter.d.ts +12 -0
- package/dist/core/runtime/tool-filter.d.ts.map +1 -0
- package/dist/core/runtime/tool-filter.js +23 -0
- package/dist/core/runtime/tool-pruning.d.ts +29 -0
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
- package/dist/core/runtime/tool-pruning.js +105 -0
- package/dist/core/session/input-history.d.ts +8 -0
- package/dist/core/session/input-history.d.ts.map +1 -0
- package/dist/core/session/input-history.js +41 -0
- package/dist/core/session/session-file.d.ts +84 -0
- package/dist/core/session/session-file.d.ts.map +1 -0
- package/dist/core/session/session-file.js +392 -0
- package/dist/core/session/session-loop.d.ts +33 -0
- package/dist/core/session/session-loop.d.ts.map +1 -0
- package/dist/core/session/session-loop.js +30 -0
- package/dist/core/settings/project-settings.d.ts +46 -0
- package/dist/core/settings/project-settings.d.ts.map +1 -0
- package/dist/core/settings/project-settings.js +85 -0
- package/dist/core/settings/workspace-context.d.ts +10 -0
- package/dist/core/settings/workspace-context.d.ts.map +1 -0
- package/dist/core/settings/workspace-context.js +48 -0
- package/dist/core/tools/agent-fs-like.d.ts +15 -0
- package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
- package/dist/core/tools/agent-fs-like.js +4 -0
- package/dist/core/tools/ask-question.d.ts +5 -0
- package/dist/core/tools/ask-question.d.ts.map +1 -0
- package/dist/core/tools/ask-question.js +51 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +109 -0
- package/dist/core/tools/eval.d.ts +10 -0
- package/dist/core/tools/eval.d.ts.map +1 -0
- package/dist/core/tools/eval.js +83 -0
- package/dist/core/tools/file-tools.d.ts +18 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -0
- package/dist/core/tools/file-tools.js +249 -0
- package/dist/core/tools/glob.d.ts +5 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +54 -0
- package/dist/core/tools/grep.d.ts +5 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +61 -0
- package/dist/core/tools/index.d.ts +14 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +14 -0
- package/dist/core/tools/ls-tree.d.ts +10 -0
- package/dist/core/tools/ls-tree.d.ts.map +1 -0
- package/dist/core/tools/ls-tree.js +177 -0
- package/dist/core/tools/mcp-mount.d.ts +63 -0
- package/dist/core/tools/mcp-mount.d.ts.map +1 -0
- package/dist/core/tools/mcp-mount.js +287 -0
- package/dist/core/tools/read-image.d.ts +6 -0
- package/dist/core/tools/read-image.d.ts.map +1 -0
- package/dist/core/tools/read-image.js +98 -0
- package/dist/core/tools/skill.d.ts +11 -0
- package/dist/core/tools/skill.d.ts.map +1 -0
- package/dist/core/tools/skill.js +65 -0
- package/dist/core/tools/skills.d.ts +10 -0
- package/dist/core/tools/skills.d.ts.map +1 -0
- package/dist/core/tools/skills.js +116 -0
- package/dist/core/tools/todo-store.d.ts +8 -0
- package/dist/core/tools/todo-store.d.ts.map +1 -0
- package/dist/core/tools/todo-store.js +100 -0
- package/dist/core/tools/todo.d.ts +8 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +91 -0
- package/dist/core/tools/url-guard.d.ts +12 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -0
- package/dist/core/tools/url-guard.js +58 -0
- package/dist/core/tools/web-ports-std.d.ts +6 -0
- package/dist/core/tools/web-ports-std.d.ts.map +1 -0
- package/dist/core/tools/web-ports-std.js +145 -0
- package/dist/core/tools/web-ports.d.ts +56 -0
- package/dist/core/tools/web-ports.d.ts.map +1 -0
- package/dist/core/tools/web-ports.js +62 -0
- package/dist/core/tools/workspace-sandbox.d.ts +20 -0
- package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
- package/dist/core/tools/workspace-sandbox.js +84 -0
- package/dist/core/workflow/subagent-registry.d.ts +40 -0
- package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
- package/dist/core/workflow/subagent-registry.js +118 -0
- package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
- package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
- package/dist/core/workflow/workflow-evaluator.js +21 -0
- package/dist/core/workflow/workflow-executor.d.ts +108 -0
- package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
- package/dist/core/workflow/workflow-executor.js +549 -0
- package/dist/core/workflow/workflow-tools.d.ts +50 -0
- package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow/workflow-tools.js +217 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +97 -0
- package/dist/modes/json-mode.d.ts +5 -0
- package/dist/modes/json-mode.d.ts.map +1 -0
- package/dist/modes/json-mode.js +46 -0
- package/dist/modes/print-mode.d.ts +20 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +82 -0
- package/dist/modes/rpc/jsonl.d.ts +23 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +88 -0
- package/dist/modes/rpc/rpc-mode.d.ts +18 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +326 -0
- package/dist/modes/tui/settings-overlay.d.ts +23 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
- package/dist/modes/tui/settings-overlay.js +136 -0
- package/dist/modes/tui/tui-commands-session.d.ts +3 -0
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands-session.js +191 -0
- package/dist/modes/tui/tui-commands.d.ts +52 -0
- package/dist/modes/tui/tui-commands.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands.js +373 -0
- package/dist/modes/tui/tui-components.d.ts +81 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -0
- package/dist/modes/tui/tui-components.js +248 -0
- package/dist/modes/tui/tui-format.d.ts +65 -0
- package/dist/modes/tui/tui-format.d.ts.map +1 -0
- package/dist/modes/tui/tui-format.js +246 -0
- package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
- package/dist/modes/tui/tui-frame-provider.js +31 -0
- package/dist/modes/tui/tui-interactive.d.ts +16 -0
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
- package/dist/modes/tui/tui-interactive.js +123 -0
- package/dist/modes/tui/tui-io.d.ts +12 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -0
- package/dist/modes/tui/tui-io.js +620 -0
- package/dist/modes/tui/tui-mode.d.ts +120 -0
- package/dist/modes/tui/tui-mode.d.ts.map +1 -0
- package/dist/modes/tui/tui-mode.js +391 -0
- package/dist/modes/tui/tui-render.d.ts +54 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-render.js +399 -0
- package/dist/modes/tui/tui-slash.d.ts +12 -0
- package/dist/modes/tui/tui-slash.d.ts.map +1 -0
- package/dist/modes/tui/tui-slash.js +64 -0
- package/dist/modes/tui/tui-tool-render.d.ts +6 -0
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-tool-render.js +79 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
- package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
- package/dist/modes/tui/view-state.d.ts +67 -0
- package/dist/modes/tui/view-state.d.ts.map +1 -0
- package/dist/modes/tui/view-state.js +353 -0
- package/dist/prompts/agents/subagent.md +26 -0
- package/dist/prompts/system/base.md +20 -0
- package/dist/prompts/system/memory.md +23 -0
- package/dist/prompts/system/safety.md +12 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/mapping.d.ts +25 -0
- package/dist/protocol/mapping.d.ts.map +1 -0
- package/dist/protocol/mapping.js +115 -0
- package/dist/protocol/transport.d.ts +1396 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Usage } from "@chengchenccc/agent-contract";
|
|
2
|
+
import type { AIMessageChunk, JsonSchema, Message } from "@chengchenccc/message";
|
|
3
|
+
import { type ContextBudget, type ContextSummarizer, type OmaLoopEvent, type PluginTool } from "../agent-runtime.js";
|
|
4
|
+
export interface WorkflowAgentSpec {
|
|
5
|
+
readonly prompt: string;
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
readonly schema?: Readonly<Record<string, unknown>>;
|
|
8
|
+
/** 3.4: registry-provided role prompt (`.oma/agents/*.md` body or a
|
|
9
|
+
* builtin role); the generic subagent tail is appended by the executor. */
|
|
10
|
+
readonly systemPrompt?: string;
|
|
11
|
+
/** 3.4: tool name allowlist — a subset of the executor's file tools.
|
|
12
|
+
* `explore` uses read/grep/glob/tree/read_image; undefined = all. */
|
|
13
|
+
readonly toolNames?: readonly string[];
|
|
14
|
+
/** 3.4: per-call model override (provider/model id, resolved like the
|
|
15
|
+
* run's own model). Defaults to the run model. */
|
|
16
|
+
readonly modelId?: string;
|
|
17
|
+
/** 3.4 Phase 2: resume an existing subagent handle with a follow-up
|
|
18
|
+
* prompt (its spec snapshot is pinned at first dispatch). */
|
|
19
|
+
readonly resumeHandle?: string;
|
|
20
|
+
/** 3.4 Phase 3: fire-and-forget — returns immediately with the handle in
|
|
21
|
+
* `running` state; the result lands on the handle (subagent_output). */
|
|
22
|
+
readonly background?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkflowAgentResult {
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly text: string;
|
|
27
|
+
readonly output?: unknown;
|
|
28
|
+
readonly ok: boolean;
|
|
29
|
+
readonly error?: string;
|
|
30
|
+
readonly usage?: Usage;
|
|
31
|
+
/** A3: full text spilled to this workspace-relative path (read it back
|
|
32
|
+
* with the read tool instead of carrying it inline). */
|
|
33
|
+
readonly resultPath?: string;
|
|
34
|
+
/** A4: workspace-relative paths the subagent wrote/edited (write/edit
|
|
35
|
+
* tool calls), so the parent has a visible handoff surface. */
|
|
36
|
+
readonly artifacts?: readonly string[];
|
|
37
|
+
/** 3.4 Phase 2: subagent handle — pass it back via `resumeHandle` to
|
|
38
|
+
* continue the same session with a follow-up prompt. */
|
|
39
|
+
readonly handle?: string;
|
|
40
|
+
/** 3.4 Phase 3: present on background dispatch acknowledgements
|
|
41
|
+
* (`running`) and stored on the handle after completion. */
|
|
42
|
+
readonly status?: "running" | "completed" | "failed" | "stopped";
|
|
43
|
+
}
|
|
44
|
+
export interface WorkflowRunResult {
|
|
45
|
+
readonly items: readonly WorkflowAgentResult[];
|
|
46
|
+
readonly totalTokens: number;
|
|
47
|
+
readonly ok: boolean;
|
|
48
|
+
}
|
|
49
|
+
/** Same shape as the session's modelStream option: the subagent session calls
|
|
50
|
+
* it with its own messages/signal/tools. The `modelId` override comes from
|
|
51
|
+
* the role definition (3.4) and must resolve in the runtime catalog;
|
|
52
|
+
* `responseFormat` (F5) asks the provider for schema-conformant JSON. */
|
|
53
|
+
export type SubagentModelStream = (messages: readonly Message[], signal?: AbortSignal, tools?: readonly PluginTool[], modelId?: string, responseFormat?: JsonSchema) => AsyncIterable<AIMessageChunk>;
|
|
54
|
+
export interface WorkflowExecutorOptions {
|
|
55
|
+
/** Build the subagent model stream (same model + reasoning as the run,
|
|
56
|
+
* unless the role pins a `modelId` override). */
|
|
57
|
+
readonly makeSubagentStream: (sessionId: string, modelId?: string, responseFormat?: JsonSchema) => SubagentModelStream;
|
|
58
|
+
readonly modelId: string;
|
|
59
|
+
readonly summarize: ContextSummarizer;
|
|
60
|
+
readonly contextBudget: ContextBudget;
|
|
61
|
+
/** File tools only (no workflow/product tools - recursion + clobber guards). */
|
|
62
|
+
readonly tools: readonly PluginTool[];
|
|
63
|
+
readonly workspaceRoot: string;
|
|
64
|
+
readonly workspaceAccess: "read_only" | "read_write";
|
|
65
|
+
readonly maxConcurrent: number;
|
|
66
|
+
readonly maxTotal: number;
|
|
67
|
+
readonly emit: (event: OmaLoopEvent) => void;
|
|
68
|
+
/** Optional product budget gate: consulted BEFORE each spawn. */
|
|
69
|
+
readonly budgetGate?: () => {
|
|
70
|
+
allowed: boolean;
|
|
71
|
+
reason?: string;
|
|
72
|
+
};
|
|
73
|
+
/** Optional wall-clock deadline per subagent (B7). Undefined = no
|
|
74
|
+
* deadline (the model call keeps its own per-call timeout). */
|
|
75
|
+
readonly perAgentTimeoutMs?: number;
|
|
76
|
+
}
|
|
77
|
+
export interface WorkflowExecutor {
|
|
78
|
+
runSubagent(input: {
|
|
79
|
+
workflowId: string;
|
|
80
|
+
agentId: string;
|
|
81
|
+
} & WorkflowAgentSpec, signal?: AbortSignal): Promise<WorkflowAgentResult>;
|
|
82
|
+
runWorkflow(input: {
|
|
83
|
+
workflowId: string;
|
|
84
|
+
label: string;
|
|
85
|
+
items: readonly WorkflowAgentSpec[];
|
|
86
|
+
signal?: AbortSignal;
|
|
87
|
+
}): Promise<WorkflowRunResult>;
|
|
88
|
+
/** 3.4 Phase 3 control plane. */
|
|
89
|
+
listSubagents(): Array<{
|
|
90
|
+
handle: string;
|
|
91
|
+
label: string;
|
|
92
|
+
status: string;
|
|
93
|
+
usage?: Usage;
|
|
94
|
+
}>;
|
|
95
|
+
getSubagentOutput(handle: string): {
|
|
96
|
+
handle: string;
|
|
97
|
+
status: string;
|
|
98
|
+
result?: WorkflowAgentResult;
|
|
99
|
+
};
|
|
100
|
+
stopSubagent(handle: string): {
|
|
101
|
+
ok: boolean;
|
|
102
|
+
error?: string;
|
|
103
|
+
};
|
|
104
|
+
/** Stop every live subagent (run teardown cascade). */
|
|
105
|
+
abortAllSubagents(): void;
|
|
106
|
+
}
|
|
107
|
+
export declare function createWorkflowExecutor(opts: WorkflowExecutorOptions): WorkflowExecutor;
|
|
108
|
+
//# sourceMappingURL=workflow-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-executor.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/workflow-executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import subagentPrompt from "../../prompts/agents/subagent.md" with { type: "text" };
|
|
4
|
+
import { createInMemorySessionStore, createOmaSession, } from "../agent-runtime.js";
|
|
5
|
+
const SUBAGENT_SYSTEM_PROMPT = subagentPrompt.trim();
|
|
6
|
+
/** A2: minimal JSON-Schema subset validator for model-supplied output
|
|
7
|
+
* schemas — covers type / properties / required / enum / items (the exact
|
|
8
|
+
* Loop triage shape, loop-step.ts renderTriageWorkflow). Unknown keywords
|
|
9
|
+
* pass through; this is a guard, not a full validator.
|
|
10
|
+
* Upgrade path: replace with provider-native structured output
|
|
11
|
+
* (ChatModel.stream responseFormat) once that lands; the validator then
|
|
12
|
+
* stays as a backstop and the schema-correction retry is kept. */
|
|
13
|
+
function matchesSchemaType(type, value) {
|
|
14
|
+
switch (type) {
|
|
15
|
+
case "string":
|
|
16
|
+
return typeof value === "string";
|
|
17
|
+
case "number":
|
|
18
|
+
return typeof value === "number";
|
|
19
|
+
case "integer":
|
|
20
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
21
|
+
case "boolean":
|
|
22
|
+
return typeof value === "boolean";
|
|
23
|
+
case "null":
|
|
24
|
+
return value === null;
|
|
25
|
+
case "object":
|
|
26
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
27
|
+
case "array":
|
|
28
|
+
return Array.isArray(value);
|
|
29
|
+
default:
|
|
30
|
+
return true; // unknown types never reject
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Returns a human-readable violation, or undefined when the value
|
|
34
|
+
* conforms to the supported subset. */
|
|
35
|
+
function validateJsonSchema(value, schema) {
|
|
36
|
+
const types = (Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : []);
|
|
37
|
+
if (types.length > 0 && !types.some((t) => matchesSchemaType(t, value))) {
|
|
38
|
+
return `expected ${types.join("|")}, got ${value === null ? "null" : typeof value}`;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
41
|
+
return `value not in enum (${JSON.stringify(schema.enum).slice(0, 120)})`;
|
|
42
|
+
}
|
|
43
|
+
if (matchesSchemaType("object", value)) {
|
|
44
|
+
const record = value;
|
|
45
|
+
for (const key of schema.required ?? []) {
|
|
46
|
+
if (!(key in record))
|
|
47
|
+
return `missing required property "${key}"`;
|
|
48
|
+
}
|
|
49
|
+
const props = schema.properties;
|
|
50
|
+
if (props) {
|
|
51
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
52
|
+
if (key in record) {
|
|
53
|
+
const err = validateJsonSchema(record[key], propSchema);
|
|
54
|
+
if (err)
|
|
55
|
+
return `${key}: ${err}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(value) && schema.items) {
|
|
62
|
+
for (let i = 0; i < value.length; i++) {
|
|
63
|
+
const err = validateJsonSchema(value[i], schema.items);
|
|
64
|
+
if (err)
|
|
65
|
+
return `[${i}]: ${err}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
/** A3 fan-in size guard: per-item inline ceiling, total inline budget, and
|
|
71
|
+
* the excerpt length kept in the tool result when a text is spilled. */
|
|
72
|
+
const MAX_INLINE_ITEM_CHARS = 2000;
|
|
73
|
+
const MAX_TOTAL_INLINE_CHARS = 16_000;
|
|
74
|
+
const EXCERPT_CHARS = 400;
|
|
75
|
+
/** Parse the loop's final text against the optional schema. Returns the
|
|
76
|
+
* parsed output (validated) plus a violation message when it fails. */
|
|
77
|
+
function parseAndValidate(result, schema) {
|
|
78
|
+
const text = (result.messages?.at(-1)?.text ?? "").trim();
|
|
79
|
+
if (!schema || !text)
|
|
80
|
+
return { text };
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(text);
|
|
83
|
+
const schemaError = validateJsonSchema(parsed, schema);
|
|
84
|
+
if (schemaError)
|
|
85
|
+
return { text, parseError: `schema validation failed: ${schemaError}` };
|
|
86
|
+
return { text, output: parsed };
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return { text, parseError: `schema output is not valid JSON: ${text.slice(0, 120)}` };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function createWorkflowExecutor(opts) {
|
|
93
|
+
let totalSpawned = 0;
|
|
94
|
+
let current = 0;
|
|
95
|
+
const waiters = [];
|
|
96
|
+
/** 3.4 Phase 2: live subagent handles — session + pinned spec snapshot.
|
|
97
|
+
* Per-run executor instance, so the table dies with the run. */
|
|
98
|
+
const subagentHandles = new Map();
|
|
99
|
+
async function acquire(signal) {
|
|
100
|
+
if (current < opts.maxConcurrent) {
|
|
101
|
+
current++;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (signal?.aborted)
|
|
105
|
+
throw new Error("workflow aborted while queued");
|
|
106
|
+
await new Promise((resolve, reject) => {
|
|
107
|
+
const onAbort = () => {
|
|
108
|
+
const idx = waiters.indexOf(fire);
|
|
109
|
+
if (idx >= 0)
|
|
110
|
+
waiters.splice(idx, 1);
|
|
111
|
+
reject(new Error("workflow aborted while queued"));
|
|
112
|
+
};
|
|
113
|
+
const fire = () => {
|
|
114
|
+
signal?.removeEventListener("abort", onAbort);
|
|
115
|
+
resolve();
|
|
116
|
+
};
|
|
117
|
+
waiters.push(fire);
|
|
118
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
119
|
+
// Cover the abort-between-check-and-listen race.
|
|
120
|
+
if (signal?.aborted)
|
|
121
|
+
onAbort();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function release() {
|
|
125
|
+
const next = waiters.shift();
|
|
126
|
+
if (next)
|
|
127
|
+
next();
|
|
128
|
+
else
|
|
129
|
+
current--;
|
|
130
|
+
}
|
|
131
|
+
class WorkflowGateError extends Error {
|
|
132
|
+
}
|
|
133
|
+
function gate() {
|
|
134
|
+
if (totalSpawned >= opts.maxTotal) {
|
|
135
|
+
throw new WorkflowGateError(`workflow exceeds the ${opts.maxTotal}-agent cap`);
|
|
136
|
+
}
|
|
137
|
+
if (opts.budgetGate) {
|
|
138
|
+
const decision = opts.budgetGate();
|
|
139
|
+
if (!decision.allowed) {
|
|
140
|
+
throw new WorkflowGateError(decision.reason ?? "workflow budget exhausted");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
totalSpawned++;
|
|
144
|
+
}
|
|
145
|
+
async function runSubagent(input, signal) {
|
|
146
|
+
// Phase 2 resume: reuse a live handle's session + pinned spec snapshot
|
|
147
|
+
// (later registry edits never mutate an existing handle's definition).
|
|
148
|
+
const existing = input.resumeHandle ? subagentHandles.get(input.resumeHandle) : null;
|
|
149
|
+
if (input.resumeHandle && !existing) {
|
|
150
|
+
const active = [...subagentHandles.keys()].join(", ");
|
|
151
|
+
return {
|
|
152
|
+
label: input.label ?? input.resumeHandle,
|
|
153
|
+
text: "",
|
|
154
|
+
ok: false,
|
|
155
|
+
error: `unknown subagent handle "${input.resumeHandle}" (active: ${active || "none"})`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const spec = existing?.spec ?? input;
|
|
159
|
+
const workflowId = existing?.workflowId ?? input.workflowId;
|
|
160
|
+
const agentId = existing?.agentId ?? input.agentId;
|
|
161
|
+
const sessionId = existing?.sessionId ?? `wf:${workflowId}:${agentId}`;
|
|
162
|
+
await acquire(signal);
|
|
163
|
+
// gate() may throw (cap/budget): it runs inside the try so the acquired
|
|
164
|
+
// concurrency slot is ALWAYS released - a leak here would deadlock every
|
|
165
|
+
// later acquire once all slots are gone.
|
|
166
|
+
try {
|
|
167
|
+
gate();
|
|
168
|
+
const label = input.label ?? agentId;
|
|
169
|
+
const agentSignal = opts.perAgentTimeoutMs !== undefined
|
|
170
|
+
? signal
|
|
171
|
+
? AbortSignal.any([signal, AbortSignal.timeout(opts.perAgentTimeoutMs)])
|
|
172
|
+
: AbortSignal.timeout(opts.perAgentTimeoutMs)
|
|
173
|
+
: signal;
|
|
174
|
+
opts.emit({
|
|
175
|
+
type: "workflow_agent_started",
|
|
176
|
+
workflowId,
|
|
177
|
+
agentId,
|
|
178
|
+
label,
|
|
179
|
+
});
|
|
180
|
+
// Role body + the generic subagent tail (constraints live once).
|
|
181
|
+
const systemPrompt = spec.systemPrompt
|
|
182
|
+
? `${spec.systemPrompt}\n\n${SUBAGENT_SYSTEM_PROMPT}`
|
|
183
|
+
: SUBAGENT_SYSTEM_PROMPT;
|
|
184
|
+
const modelId = spec.modelId ?? opts.modelId;
|
|
185
|
+
const subagentTools = spec.toolNames
|
|
186
|
+
? opts.tools.filter((t) => spec.toolNames.includes(t.name))
|
|
187
|
+
: opts.tools;
|
|
188
|
+
const store = existing?.store ?? createInMemorySessionStore();
|
|
189
|
+
if (!existing) {
|
|
190
|
+
// The loop opens the session record on startLoop: seed it first
|
|
191
|
+
// (same contract as the run runtime's create-runtime.ts).
|
|
192
|
+
await store.create({
|
|
193
|
+
sessionId,
|
|
194
|
+
backendKind: "oma",
|
|
195
|
+
workspaceRoot: opts.workspaceRoot,
|
|
196
|
+
leafEntryId: null,
|
|
197
|
+
createdAt: Date.now(),
|
|
198
|
+
updatedAt: Date.now(),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const session = existing?.session ??
|
|
202
|
+
createOmaSession({
|
|
203
|
+
sessionId,
|
|
204
|
+
store,
|
|
205
|
+
plugins: [{ name: "subagent-tools", tools: subagentTools }],
|
|
206
|
+
maxSteps: 8,
|
|
207
|
+
maxForceContinues: 2,
|
|
208
|
+
// Transient model failures (429/timeout) retry via the loop's
|
|
209
|
+
// default retryStream policy (maxAttempts 3).
|
|
210
|
+
modelStream: opts.makeSubagentStream(sessionId, modelId, spec.schema ? { name: "result", schema: spec.schema, strict: true } : undefined),
|
|
211
|
+
summarize: opts.summarize,
|
|
212
|
+
contextBudget: opts.contextBudget,
|
|
213
|
+
});
|
|
214
|
+
const handle = existing ? input.resumeHandle : `sub-${crypto.randomUUID()}`;
|
|
215
|
+
if (!existing) {
|
|
216
|
+
subagentHandles.set(handle, {
|
|
217
|
+
session,
|
|
218
|
+
store,
|
|
219
|
+
sessionId,
|
|
220
|
+
workflowId,
|
|
221
|
+
agentId,
|
|
222
|
+
spec,
|
|
223
|
+
status: "running",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
const onAbort = () => session.stop();
|
|
227
|
+
agentSignal?.addEventListener("abort", onAbort, { once: true });
|
|
228
|
+
const loopInput = {
|
|
229
|
+
run: {
|
|
230
|
+
runId: sessionId,
|
|
231
|
+
model: { backendKind: "oma", modelId },
|
|
232
|
+
systemPrompt,
|
|
233
|
+
configRevision: 0,
|
|
234
|
+
},
|
|
235
|
+
workspace: { root: opts.workspaceRoot, access: opts.workspaceAccess },
|
|
236
|
+
};
|
|
237
|
+
// startFollowUp is the resume primitive: same session, no Meta re-send.
|
|
238
|
+
const launch = (history, inputMsg) => existing
|
|
239
|
+
? session.startFollowUp({ ...loopInput, history: [], input: inputMsg })
|
|
240
|
+
: session.startLoop({ ...loopInput, history, input: inputMsg });
|
|
241
|
+
const finish = async () => {
|
|
242
|
+
// Never launch into an already-aborted signal: an abort that landed
|
|
243
|
+
// before the model stream registered its listener would otherwise
|
|
244
|
+
// leave the loop awaiting an abort event that will never fire.
|
|
245
|
+
if (agentSignal?.aborted) {
|
|
246
|
+
return {
|
|
247
|
+
label,
|
|
248
|
+
text: "",
|
|
249
|
+
ok: false,
|
|
250
|
+
error: agentSignal.reason instanceof Error
|
|
251
|
+
? agentSignal.reason.message
|
|
252
|
+
: "workflow agent aborted",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
let result;
|
|
256
|
+
try {
|
|
257
|
+
result = await launch([], {
|
|
258
|
+
inputId: agentId,
|
|
259
|
+
// Same envelope as oh-my-pi's subagent-user-prompt template.
|
|
260
|
+
message: { role: "user", text: `Complete assignment thoroughly:\n\n${spec.prompt}` },
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
finally {
|
|
264
|
+
agentSignal?.removeEventListener("abort", onAbort);
|
|
265
|
+
}
|
|
266
|
+
// A4: collect the subagent's write/edit artifacts BEFORE the store
|
|
267
|
+
// closes (advisory — failures never fail the run).
|
|
268
|
+
const artifactPaths = new Set();
|
|
269
|
+
try {
|
|
270
|
+
const branch = await store.readBranch(sessionId);
|
|
271
|
+
for (const entry of branch) {
|
|
272
|
+
if (entry.type !== "message")
|
|
273
|
+
continue;
|
|
274
|
+
for (const block of entry.message.blocks ?? []) {
|
|
275
|
+
if (block.type !== "tool_use" || (block.name !== "write" && block.name !== "edit")) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const p = block.input?.path;
|
|
279
|
+
if (typeof p === "string" && p.length > 0)
|
|
280
|
+
artifactPaths.add(p);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
/* best-effort */
|
|
286
|
+
}
|
|
287
|
+
const artifacts = [...artifactPaths];
|
|
288
|
+
// Resume keeps the store alive for the handle; only a fresh dispatch
|
|
289
|
+
// closes it.
|
|
290
|
+
if (!existing) {
|
|
291
|
+
void store.close().catch((err) => {
|
|
292
|
+
console.error(`[workflow] subagent store close failed for ${sessionId}:`, err);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
let { text, output, parseError } = parseAndValidate(result, spec.schema);
|
|
296
|
+
if (parseError && !agentSignal?.aborted && result.status === "completed") {
|
|
297
|
+
// A2: one schema-correction turn — the same session re-runs with the
|
|
298
|
+
// produced messages as history plus an explicit fix instruction. A
|
|
299
|
+
// second violation is terminal.
|
|
300
|
+
const correction = `Your previous final message did not match the required output schema. ` +
|
|
301
|
+
`Return ONLY the corrected JSON. Error: ${parseError}`;
|
|
302
|
+
result = await launch((result.messages ?? []).map((message, i) => ({
|
|
303
|
+
// Synthetic identity: the in-memory subagent store never
|
|
304
|
+
// persists canonical messages, so the id is only for the loop's
|
|
305
|
+
// internal append bookkeeping.
|
|
306
|
+
productEntryId: `sub:${agentId}:${i}`,
|
|
307
|
+
message,
|
|
308
|
+
})), {
|
|
309
|
+
inputId: `${agentId}:schema-fix`,
|
|
310
|
+
message: { role: "user", text: correction },
|
|
311
|
+
});
|
|
312
|
+
({ text, output, parseError } = parseAndValidate(result, spec.schema));
|
|
313
|
+
}
|
|
314
|
+
const loopError = result.status !== "completed"
|
|
315
|
+
? (result.error ?? `subagent loop ${result.status}`)
|
|
316
|
+
: undefined;
|
|
317
|
+
const error = agentSignal?.aborted
|
|
318
|
+
? agentSignal.reason instanceof Error
|
|
319
|
+
? agentSignal.reason.message
|
|
320
|
+
: "workflow agent timed out"
|
|
321
|
+
: (loopError ?? parseError);
|
|
322
|
+
const agentResult = {
|
|
323
|
+
label,
|
|
324
|
+
text,
|
|
325
|
+
ok: result.status === "completed" && !error,
|
|
326
|
+
...(output !== undefined ? { output } : {}),
|
|
327
|
+
...(error ? { error } : {}),
|
|
328
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
329
|
+
...(artifacts.length > 0 ? { artifacts } : {}),
|
|
330
|
+
// Phase 2: the handle is minted on first dispatch; resume calls the
|
|
331
|
+
// same handle back (no new handle on follow-ups).
|
|
332
|
+
...(existing ? {} : { handle }),
|
|
333
|
+
};
|
|
334
|
+
// A1/F2: best-effort per-subagent state dump — the audit trail
|
|
335
|
+
// survives a crash/abort that the in-memory store cannot. Includes
|
|
336
|
+
// the pinned spec snapshot so a verdict dispute can be traced to
|
|
337
|
+
// exactly what the subagent was asked to do.
|
|
338
|
+
const safeName = (s) => /^[A-Za-z0-9-]+$/.test(s);
|
|
339
|
+
if (opts.workspaceAccess === "read_write" && safeName(workflowId) && safeName(agentId)) {
|
|
340
|
+
try {
|
|
341
|
+
const rel = `.oma/workflow/${workflowId}/${agentId}.session.json`;
|
|
342
|
+
const abs = join(opts.workspaceRoot, rel);
|
|
343
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
344
|
+
writeFileSync(abs, JSON.stringify({
|
|
345
|
+
workflowId,
|
|
346
|
+
agentId,
|
|
347
|
+
label,
|
|
348
|
+
ok: agentResult.ok,
|
|
349
|
+
...(agentResult.error ? { error: agentResult.error } : {}),
|
|
350
|
+
status: result.status,
|
|
351
|
+
spec: {
|
|
352
|
+
prompt: spec.prompt,
|
|
353
|
+
...(spec.label ? { label: spec.label } : {}),
|
|
354
|
+
...(spec.systemPrompt ? { systemPrompt: spec.systemPrompt } : {}),
|
|
355
|
+
...(spec.toolNames ? { toolNames: spec.toolNames } : {}),
|
|
356
|
+
...(spec.modelId ? { modelId: spec.modelId } : {}),
|
|
357
|
+
...(spec.schema ? { schema: spec.schema } : {}),
|
|
358
|
+
},
|
|
359
|
+
...(result.usage ? { usage: result.usage } : {}),
|
|
360
|
+
...(artifacts.length > 0 ? { artifacts } : {}),
|
|
361
|
+
messages: result.messages ?? [],
|
|
362
|
+
updatedAt: Date.now(),
|
|
363
|
+
}, null, 2));
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
console.error(`[workflow] state dump failed for ${input.agentId}:`, err);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else if (opts.workspaceAccess !== "read_write") {
|
|
370
|
+
console.warn(`[workflow] read_only workspace: subagent session not dumped for ${agentId}`);
|
|
371
|
+
}
|
|
372
|
+
opts.emit({
|
|
373
|
+
type: "workflow_agent_completed",
|
|
374
|
+
workflowId,
|
|
375
|
+
agentId,
|
|
376
|
+
label,
|
|
377
|
+
ok: agentResult.ok,
|
|
378
|
+
...(agentResult.error ? { error: agentResult.error } : {}),
|
|
379
|
+
...(agentResult.usage ? { usage: agentResult.usage } : {}),
|
|
380
|
+
});
|
|
381
|
+
return agentResult;
|
|
382
|
+
};
|
|
383
|
+
// 3.4 Phase 3: fire-and-forget. Acknowledge immediately with the
|
|
384
|
+
// handle; the result lands on the handle for subagent_output.
|
|
385
|
+
if (spec.background) {
|
|
386
|
+
const entry = subagentHandles.get(handle);
|
|
387
|
+
if (entry)
|
|
388
|
+
entry.status = "running";
|
|
389
|
+
void finish()
|
|
390
|
+
.then((agentResult) => {
|
|
391
|
+
const e = subagentHandles.get(handle);
|
|
392
|
+
if (!e)
|
|
393
|
+
return;
|
|
394
|
+
if (e.stopRequested) {
|
|
395
|
+
e.status = "stopped";
|
|
396
|
+
e.result = { ...agentResult, ok: false, error: "stopped", status: "stopped" };
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
e.status = agentResult.ok ? "completed" : "failed";
|
|
400
|
+
e.result = { ...agentResult, status: e.status };
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
.catch((err) => {
|
|
404
|
+
const e = subagentHandles.get(handle);
|
|
405
|
+
if (e) {
|
|
406
|
+
e.status = e.stopRequested ? "stopped" : "failed";
|
|
407
|
+
e.result = {
|
|
408
|
+
label,
|
|
409
|
+
text: "",
|
|
410
|
+
ok: false,
|
|
411
|
+
error: err instanceof Error ? err.message : String(err),
|
|
412
|
+
status: e.status,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
return { label, text: "", ok: true, handle, status: "running" };
|
|
417
|
+
}
|
|
418
|
+
return finish();
|
|
419
|
+
}
|
|
420
|
+
catch (err) {
|
|
421
|
+
// Gate failures (cap/budget) are WORKFLOW-level: propagate so the
|
|
422
|
+
// whole fan-out rejects instead of degrading to a failed agent row.
|
|
423
|
+
if (err instanceof WorkflowGateError)
|
|
424
|
+
throw err;
|
|
425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
426
|
+
const label = input.label ?? agentId;
|
|
427
|
+
opts.emit({
|
|
428
|
+
type: "workflow_agent_completed",
|
|
429
|
+
workflowId,
|
|
430
|
+
agentId,
|
|
431
|
+
label,
|
|
432
|
+
ok: false,
|
|
433
|
+
error: message,
|
|
434
|
+
});
|
|
435
|
+
return { label, text: "", ok: false, error: message };
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
release();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/** A3: keep fan-in results small enough to re-inject into the main loop.
|
|
442
|
+
* Long item texts spill to `.oma/workflow/<wfId>/<agentId>.result.md` (the
|
|
443
|
+
* main session reads them back with the read tool); read_only workspaces
|
|
444
|
+
* degrade to inline truncation. The total-inline budget forces spill
|
|
445
|
+
* even when no single item exceeds the per-item ceiling. */
|
|
446
|
+
function spillResults(results, workflowId) {
|
|
447
|
+
const total = results.reduce((acc, r) => acc + r.text.length, 0);
|
|
448
|
+
const forceSpill = total > MAX_TOTAL_INLINE_CHARS;
|
|
449
|
+
return results.map((r, i) => {
|
|
450
|
+
if (!forceSpill && r.text.length <= MAX_INLINE_ITEM_CHARS)
|
|
451
|
+
return r;
|
|
452
|
+
const excerpt = r.text.slice(0, EXCERPT_CHARS);
|
|
453
|
+
if (opts.workspaceAccess !== "read_write") {
|
|
454
|
+
return { ...r, text: `${excerpt}…[truncated]` };
|
|
455
|
+
}
|
|
456
|
+
const rel = `.oma/workflow/${workflowId}/a${i}.result.md`;
|
|
457
|
+
const abs = join(opts.workspaceRoot, rel);
|
|
458
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
459
|
+
writeFileSync(abs, r.text);
|
|
460
|
+
return { ...r, text: excerpt, resultPath: rel };
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
async function runWorkflow(input) {
|
|
464
|
+
opts.emit({
|
|
465
|
+
type: "workflow_started",
|
|
466
|
+
workflowId: input.workflowId,
|
|
467
|
+
label: input.label,
|
|
468
|
+
agentCount: input.items.length,
|
|
469
|
+
});
|
|
470
|
+
// Own controller: a gate failure (cap/budget) or abort must stop
|
|
471
|
+
// in-flight siblings (B1) instead of orphaning them while Promise.all
|
|
472
|
+
// rejects and no terminal event is emitted.
|
|
473
|
+
const controller = new AbortController();
|
|
474
|
+
const combined = input.signal
|
|
475
|
+
? AbortSignal.any([input.signal, controller.signal])
|
|
476
|
+
: controller.signal;
|
|
477
|
+
try {
|
|
478
|
+
const rawResults = await Promise.all(input.items.map((item, i) => runSubagent({ workflowId: input.workflowId, agentId: `a${i}`, ...item }, combined)));
|
|
479
|
+
if (input.signal?.aborted)
|
|
480
|
+
throw new Error("workflow aborted");
|
|
481
|
+
const results = spillResults(rawResults, input.workflowId);
|
|
482
|
+
const totalTokens = results.reduce((acc, r) => acc +
|
|
483
|
+
(r.usage?.inputTokens ?? 0) +
|
|
484
|
+
(r.usage?.outputTokens ?? 0) +
|
|
485
|
+
(r.usage?.cacheReadTokens ?? 0) +
|
|
486
|
+
(r.usage?.cacheWriteTokens ?? 0), 0);
|
|
487
|
+
const ok = results.every((r) => r.ok);
|
|
488
|
+
opts.emit({
|
|
489
|
+
type: "workflow_completed",
|
|
490
|
+
workflowId: input.workflowId,
|
|
491
|
+
ok,
|
|
492
|
+
agentCount: input.items.length,
|
|
493
|
+
totalTokens,
|
|
494
|
+
});
|
|
495
|
+
return { items: results, totalTokens, ok };
|
|
496
|
+
}
|
|
497
|
+
catch (err) {
|
|
498
|
+
controller.abort();
|
|
499
|
+
opts.emit({
|
|
500
|
+
type: "workflow_failed",
|
|
501
|
+
workflowId: input.workflowId,
|
|
502
|
+
error: err instanceof Error ? err.message : String(err),
|
|
503
|
+
});
|
|
504
|
+
throw err;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
// 3.4 Phase 3 control plane.
|
|
508
|
+
function listSubagents() {
|
|
509
|
+
return [...subagentHandles.entries()].map(([handle, e]) => ({
|
|
510
|
+
handle,
|
|
511
|
+
label: e.spec.label ?? e.agentId,
|
|
512
|
+
status: e.status,
|
|
513
|
+
...(e.result?.usage ? { usage: e.result.usage } : {}),
|
|
514
|
+
}));
|
|
515
|
+
}
|
|
516
|
+
function getSubagentOutput(handle) {
|
|
517
|
+
const e = subagentHandles.get(handle);
|
|
518
|
+
if (!e)
|
|
519
|
+
return { handle, status: "unknown" };
|
|
520
|
+
if (e.result) {
|
|
521
|
+
// A3 size guard applies to fetched results too.
|
|
522
|
+
const [spilled] = spillResults([e.result], e.workflowId);
|
|
523
|
+
return { handle, status: e.status, result: spilled };
|
|
524
|
+
}
|
|
525
|
+
return { handle, status: e.status };
|
|
526
|
+
}
|
|
527
|
+
function stopSubagent(handle) {
|
|
528
|
+
const e = subagentHandles.get(handle);
|
|
529
|
+
if (!e)
|
|
530
|
+
return { ok: false, error: `unknown subagent handle "${handle}"` };
|
|
531
|
+
e.stopRequested = true;
|
|
532
|
+
e.session.stop();
|
|
533
|
+
return { ok: true };
|
|
534
|
+
}
|
|
535
|
+
function abortAllSubagents() {
|
|
536
|
+
for (const e of subagentHandles.values()) {
|
|
537
|
+
e.stopRequested = true;
|
|
538
|
+
e.session.stop();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
runSubagent,
|
|
543
|
+
runWorkflow,
|
|
544
|
+
listSubagents,
|
|
545
|
+
getSubagentOutput,
|
|
546
|
+
stopSubagent,
|
|
547
|
+
abortAllSubagents,
|
|
548
|
+
};
|
|
549
|
+
}
|