@xrkseek/harness 0.4.8 → 0.4.9
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/node_modules/@xrkseek/cordis/LICENSE +21 -0
- package/node_modules/@xrkseek/cordis/README.md +14 -0
- package/node_modules/@xrkseek/cordis/bin.js +16 -0
- package/node_modules/@xrkseek/cordis/lib/index.js +1828 -0
- package/node_modules/@xrkseek/cordis/lib/types/context.d.ts +101 -0
- package/node_modules/@xrkseek/cordis/lib/types/context.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/events.d.ts +240 -0
- package/node_modules/@xrkseek/cordis/lib/types/events.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/fiber.d.ts +202 -0
- package/node_modules/@xrkseek/cordis/lib/types/fiber.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/index.d.ts +15 -0
- package/node_modules/@xrkseek/cordis/lib/types/index.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/logger.d.ts +105 -0
- package/node_modules/@xrkseek/cordis/lib/types/logger.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/reflect.d.ts +186 -0
- package/node_modules/@xrkseek/cordis/lib/types/reflect.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/registry.d.ts +201 -0
- package/node_modules/@xrkseek/cordis/lib/types/registry.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/service.d.ts +55 -0
- package/node_modules/@xrkseek/cordis/lib/types/service.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/lib/types/utils.d.ts +62 -0
- package/node_modules/@xrkseek/cordis/lib/types/utils.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/package.json +49 -0
- package/node_modules/@xrkseek/cordis/src/context.d.ts +101 -0
- package/node_modules/@xrkseek/cordis/src/context.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/context.js +99 -0
- package/node_modules/@xrkseek/cordis/src/context.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/context.ts +146 -0
- package/node_modules/@xrkseek/cordis/src/events.d.ts +240 -0
- package/node_modules/@xrkseek/cordis/src/events.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/events.js +199 -0
- package/node_modules/@xrkseek/cordis/src/events.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/events.ts +352 -0
- package/node_modules/@xrkseek/cordis/src/fiber.d.ts +202 -0
- package/node_modules/@xrkseek/cordis/src/fiber.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/fiber.js +677 -0
- package/node_modules/@xrkseek/cordis/src/fiber.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/fiber.ts +754 -0
- package/node_modules/@xrkseek/cordis/src/index.d.ts +15 -0
- package/node_modules/@xrkseek/cordis/src/index.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/index.js +15 -0
- package/node_modules/@xrkseek/cordis/src/index.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/index.ts +14 -0
- package/node_modules/@xrkseek/cordis/src/logger.d.ts +105 -0
- package/node_modules/@xrkseek/cordis/src/logger.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/logger.js +193 -0
- package/node_modules/@xrkseek/cordis/src/logger.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/logger.ts +270 -0
- package/node_modules/@xrkseek/cordis/src/reflect.d.ts +186 -0
- package/node_modules/@xrkseek/cordis/src/reflect.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/reflect.js +311 -0
- package/node_modules/@xrkseek/cordis/src/reflect.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/reflect.ts +418 -0
- package/node_modules/@xrkseek/cordis/src/registry.d.ts +201 -0
- package/node_modules/@xrkseek/cordis/src/registry.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/registry.js +218 -0
- package/node_modules/@xrkseek/cordis/src/registry.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/registry.ts +337 -0
- package/node_modules/@xrkseek/cordis/src/service.d.ts +55 -0
- package/node_modules/@xrkseek/cordis/src/service.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/service.js +113 -0
- package/node_modules/@xrkseek/cordis/src/service.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/service.ts +115 -0
- package/node_modules/@xrkseek/cordis/src/utils.d.ts +62 -0
- package/node_modules/@xrkseek/cordis/src/utils.d.ts.map +1 -0
- package/node_modules/@xrkseek/cordis/src/utils.js +271 -0
- package/node_modules/@xrkseek/cordis/src/utils.js.map +1 -0
- package/node_modules/@xrkseek/cordis/src/utils.ts +287 -0
- package/node_modules/@xrkseek/core-agent-loop/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/core-agent-loop/dist/index.js +39 -68
- package/node_modules/@xrkseek/core-agent-loop/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/core-session/dist/compaction.d.ts +4 -3
- package/node_modules/@xrkseek/core-session/dist/compaction.d.ts.map +1 -1
- package/node_modules/@xrkseek/core-session/dist/compaction.js.map +1 -1
- package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.d.ts.map +1 -1
- package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.js +2 -1
- package/node_modules/@xrkseek/core-tools/dist/subagent-prompt.js.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/README.md +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/catalog.d.ts +16 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/catalog.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/catalog.js +152 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/catalog.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/deepinfra-http.d.ts +9 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/deepinfra-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/deepinfra-http.js +21 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/deepinfra-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/fal-http.d.ts +16 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/fal-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/fal-http.js +189 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/fal-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/index.d.ts +10 -5
- package/node_modules/@xrkseek/exec-image-gen/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/index.js +174 -18
- package/node_modules/@xrkseek/exec-image-gen/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/krea-http.d.ts +16 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/krea-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/krea-http.js +144 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/krea-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/meta-ai-http.d.ts +9 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/meta-ai-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/meta-ai-http.js +21 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/meta-ai-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/openai-http.d.ts +4 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/openai-http.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/openai-http.js +7 -5
- package/node_modules/@xrkseek/exec-image-gen/dist/openai-http.js.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/openrouter-http.d.ts +14 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/openrouter-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/openrouter-http.js +117 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/openrouter-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/tools.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/tools.js +43 -6
- package/node_modules/@xrkseek/exec-image-gen/dist/tools.js.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/types.d.ts +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/types.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/types.js.map +1 -1
- package/node_modules/@xrkseek/exec-image-gen/dist/xai-http.d.ts +16 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/xai-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/xai-http.js +137 -0
- package/node_modules/@xrkseek/exec-image-gen/dist/xai-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-memory/dist/consolidate-pipeline.d.ts +52 -0
- package/node_modules/@xrkseek/exec-memory/dist/consolidate-pipeline.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-memory/dist/consolidate-pipeline.js +216 -0
- package/node_modules/@xrkseek/exec-memory/dist/consolidate-pipeline.js.map +1 -0
- package/node_modules/@xrkseek/exec-memory/dist/index.d.ts +1 -0
- package/node_modules/@xrkseek/exec-memory/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-memory/dist/index.js +1 -0
- package/node_modules/@xrkseek/exec-memory/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/exec-memory/dist/tools.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-memory/dist/tools.js +20 -7
- package/node_modules/@xrkseek/exec-memory/dist/tools.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/README.md +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/catalog.d.ts +14 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/catalog.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/catalog.js +99 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/catalog.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/deepinfra-http.d.ts +9 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/deepinfra-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/deepinfra-http.js +17 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/deepinfra-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/fal-http.d.ts +14 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/fal-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/fal-http.js +342 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/fal-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/index.d.ts +8 -6
- package/node_modules/@xrkseek/exec-video-gen/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/index.js +122 -19
- package/node_modules/@xrkseek/exec-video-gen/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/openai-http.d.ts +4 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/openai-http.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/openai-http.js +21 -15
- package/node_modules/@xrkseek/exec-video-gen/dist/openai-http.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/openrouter-http.d.ts +9 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/openrouter-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/openrouter-http.js +18 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/openrouter-http.js.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/tools.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/tools.js +26 -6
- package/node_modules/@xrkseek/exec-video-gen/dist/tools.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/types.d.ts +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/types.d.ts.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/types.js.map +1 -1
- package/node_modules/@xrkseek/exec-video-gen/dist/xai-http.d.ts +18 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/xai-http.d.ts.map +1 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/xai-http.js +263 -0
- package/node_modules/@xrkseek/exec-video-gen/dist/xai-http.js.map +1 -0
- package/node_modules/@xrkseek/preset-harness/dist/preset.d.ts.map +1 -1
- package/node_modules/@xrkseek/preset-harness/dist/preset.js +12 -3
- package/node_modules/@xrkseek/preset-harness/dist/preset.js.map +1 -1
- package/node_modules/@xrkseek/preset-harness/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@xrkseek/preset-minimal/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@xrkseek/preset-server/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@xrkseek/server-face/dist/agent-team-graph.d.ts +31 -0
- package/node_modules/@xrkseek/server-face/dist/agent-team-graph.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/agent-team-graph.js +125 -1
- package/node_modules/@xrkseek/server-face/dist/agent-team-graph.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/context.d.ts +19 -1
- package/node_modules/@xrkseek/server-face/dist/context.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/index.d.ts +3 -3
- package/node_modules/@xrkseek/server-face/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/index.js +1 -1
- package/node_modules/@xrkseek/server-face/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/mcp-oauth.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/mcp-oauth.js +25 -3
- package/node_modules/@xrkseek/server-face/dist/mcp-oauth.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/ralph-tool.d.ts +2 -0
- package/node_modules/@xrkseek/server-face/dist/ralph-tool.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/ralph-tool.js +11 -5
- package/node_modules/@xrkseek/server-face/dist/ralph-tool.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/runtime.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/runtime.js +1 -0
- package/node_modules/@xrkseek/server-face/dist/runtime.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/session-status.d.ts +16 -0
- package/node_modules/@xrkseek/server-face/dist/session-status.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/session-status.js +44 -5
- package/node_modules/@xrkseek/server-face/dist/session-status.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-credentials.d.ts +9 -0
- package/node_modules/@xrkseek/server-face/dist/settings-credentials.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-credentials.js +50 -0
- package/node_modules/@xrkseek/server-face/dist/settings-credentials.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-document.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-document.js +40 -0
- package/node_modules/@xrkseek/server-face/dist/settings-document.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-schemas.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/settings-schemas.js +20 -4
- package/node_modules/@xrkseek/server-face/dist/settings-schemas.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/subagent-tools.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/subagent-tools.js +191 -0
- package/node_modules/@xrkseek/server-face/dist/subagent-tools.js.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/wire/rpc-error.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-face/dist/wire/rpc-error.js +8 -0
- package/node_modules/@xrkseek/server-face/dist/wire/rpc-error.js.map +1 -1
- package/node_modules/@xrkseek/server-host/dist/index.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-host/dist/index.js +135 -24
- package/node_modules/@xrkseek/server-host/dist/index.js.map +1 -1
- package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.js +6 -0
- package/node_modules/@xrkseek/server-host/dist/sidebar-face-bridge.js.map +1 -1
- package/node_modules/@xrkseek/server-host/dist/workflow-engine-mount.d.ts +32 -0
- package/node_modules/@xrkseek/server-host/dist/workflow-engine-mount.d.ts.map +1 -0
- package/node_modules/@xrkseek/server-host/dist/workflow-engine-mount.js +134 -0
- package/node_modules/@xrkseek/server-host/dist/workflow-engine-mount.js.map +1 -0
- package/node_modules/@xrkseek/server-host/package.json +2 -0
- package/node_modules/@xrkseek/server-http/dist/sidebar/sidebar-adapter.d.ts.map +1 -1
- package/node_modules/@xrkseek/server-http/dist/sidebar/sidebar-adapter.js +20 -0
- package/node_modules/@xrkseek/server-http/dist/sidebar/sidebar-adapter.js.map +1 -1
- package/node_modules/@xrkseek/server-http/dist/sidebar/sidebar-face-bridge.d.ts +10 -2
- package/node_modules/@xrkseek/server-http/dist/sidebar/sidebar-face-bridge.d.ts.map +1 -1
- package/node_modules/@xrkseek/xrk-workflow/README.i18n.yaml +6 -0
- package/node_modules/@xrkseek/xrk-workflow/README.md +73 -0
- package/node_modules/@xrkseek/xrk-workflow/README.zh.md +72 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/index.d.ts +177 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/index.js +730 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/invariant.d.ts +13 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/invariant.js +118 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/runtime-types.d.ts +45 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/runtime-types.js +9 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/types.d.ts +118 -0
- package/node_modules/@xrkseek/xrk-workflow/lib/types/types.js +16 -0
- package/node_modules/@xrkseek/xrk-workflow/package.json +53 -0
- package/node_modules/@xrkseek/xrk-workflow/src/index.ts +947 -0
- package/package.json +5 -1
|
@@ -0,0 +1,947 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Service Definition for the workflow capability seam. Service Providers execute orchestration scripts;
|
|
3
|
+
* observe-only lifecycle events never expose run control.
|
|
4
|
+
* @module @xrkseek/xrk-workflow
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Context, Service } from '@xrkseek/cordis'
|
|
8
|
+
import { HarnessError } from '@xrkseek/xrk-llm/src/error.ts'
|
|
9
|
+
import { SessionId } from '@xrkseek/xrk-session/types'
|
|
10
|
+
import {
|
|
11
|
+
WorkflowRunId,
|
|
12
|
+
type WorkflowAgentEndInfo,
|
|
13
|
+
type WorkflowAgentInfo,
|
|
14
|
+
type WorkflowResult,
|
|
15
|
+
type WorkflowResultInfo,
|
|
16
|
+
type WorkflowRunInfo,
|
|
17
|
+
} from './types.ts'
|
|
18
|
+
import type { WorkflowRun, WorkflowStartRequest } from './runtime-types.ts'
|
|
19
|
+
|
|
20
|
+
export { WorkflowRunId } from './types.ts'
|
|
21
|
+
export type {
|
|
22
|
+
WorkflowAgentEndInfo,
|
|
23
|
+
WorkflowAgentInfo,
|
|
24
|
+
WorkflowAgentOutcome,
|
|
25
|
+
WorkflowMeta,
|
|
26
|
+
WorkflowPhase,
|
|
27
|
+
WorkflowResult,
|
|
28
|
+
WorkflowResultInfo,
|
|
29
|
+
WorkflowRunInfo,
|
|
30
|
+
WorkflowStopReason,
|
|
31
|
+
} from './types.ts'
|
|
32
|
+
export type { WorkflowRun, WorkflowStartRequest } from './runtime-types.ts'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Nested `await tools.name(args)` bridge (Code Mode shape). Host mounts a
|
|
36
|
+
* registry-backed bridge; Isolating posts RPC across the worker boundary.
|
|
37
|
+
*/
|
|
38
|
+
export interface WorkflowToolCallResult {
|
|
39
|
+
readonly content: string
|
|
40
|
+
readonly isError?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface WorkflowToolBridge {
|
|
44
|
+
/** Names exposed on the `tools` Proxy (exclude recursive orchestrators). */
|
|
45
|
+
readonly listNames: () => readonly string[]
|
|
46
|
+
readonly call: (
|
|
47
|
+
name: string,
|
|
48
|
+
args: unknown,
|
|
49
|
+
signal?: AbortSignal,
|
|
50
|
+
) => Promise<WorkflowToolCallResult>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Build the script-facing `tools` Proxy (same contract as Code Mode). */
|
|
54
|
+
export function buildWorkflowToolsProxy(
|
|
55
|
+
bridge: WorkflowToolBridge | undefined,
|
|
56
|
+
signal?: AbortSignal,
|
|
57
|
+
): Record<string, (args?: unknown) => Promise<string>> {
|
|
58
|
+
const listNames = (): readonly string[] => bridge?.listNames() ?? []
|
|
59
|
+
const callTool = (name: string) => async (args?: unknown): Promise<string> => {
|
|
60
|
+
if (!bridge) throw new Error('tools bridge not mounted')
|
|
61
|
+
if (!listNames().includes(name)) throw new Error(`unknown tool: ${name}`)
|
|
62
|
+
const out = await bridge.call(name, args ?? {}, signal)
|
|
63
|
+
if (out.isError) throw new Error(`tools.${name} failed: ${out.content}`)
|
|
64
|
+
return out.content
|
|
65
|
+
}
|
|
66
|
+
return new Proxy({}, {
|
|
67
|
+
get(_target, prop) {
|
|
68
|
+
if (typeof prop !== 'string' || prop === 'then') return undefined
|
|
69
|
+
return callTool(prop)
|
|
70
|
+
},
|
|
71
|
+
ownKeys() {
|
|
72
|
+
return [...listNames()]
|
|
73
|
+
},
|
|
74
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
75
|
+
if (typeof prop === 'string' && listNames().includes(prop)) {
|
|
76
|
+
return {
|
|
77
|
+
enumerable: true,
|
|
78
|
+
configurable: true,
|
|
79
|
+
writable: false,
|
|
80
|
+
value: callTool(prop),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return undefined
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
declare module '@xrkseek/cordis' {
|
|
89
|
+
interface Context {
|
|
90
|
+
workflowEngine: WorkflowEngine
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface Events {
|
|
94
|
+
/**
|
|
95
|
+
* A workflow run started — the script's meta block validated, the body
|
|
96
|
+
* about to execute. Paired with {@link Events['workflow/end']}.
|
|
97
|
+
* @param info - the run's identity snapshot (id + meta).
|
|
98
|
+
* @mode emit
|
|
99
|
+
*/
|
|
100
|
+
'workflow/start'(info: WorkflowRunInfo): void
|
|
101
|
+
/**
|
|
102
|
+
* The script entered a phase (a `phase(title)` call) — progress grouping
|
|
103
|
+
* for observers; no execution semantics.
|
|
104
|
+
* @param info - the run's identity snapshot.
|
|
105
|
+
* @param title - the phase title, verbatim.
|
|
106
|
+
* @mode emit
|
|
107
|
+
*/
|
|
108
|
+
'workflow/phase'(info: WorkflowRunInfo, title: string): void
|
|
109
|
+
/**
|
|
110
|
+
* The script emitted a narration line (a `log(message)` call).
|
|
111
|
+
* @param info - the run's identity snapshot.
|
|
112
|
+
* @param message - the logged message, verbatim.
|
|
113
|
+
* @mode emit
|
|
114
|
+
*/
|
|
115
|
+
'workflow/log'(info: WorkflowRunInfo, message: string): void
|
|
116
|
+
/**
|
|
117
|
+
* One `agent()` call established a published child run. Paired with
|
|
118
|
+
* {@link Events['workflow/agent-end']} by `agent.seq`. A call that never
|
|
119
|
+
* receives a published run from the provider emits neither
|
|
120
|
+
* event in this pair.
|
|
121
|
+
* @param info - the run's identity snapshot.
|
|
122
|
+
* @param agent - the call's sequence number, label, phase, and child id.
|
|
123
|
+
* @mode emit
|
|
124
|
+
*/
|
|
125
|
+
'workflow/agent-start'(info: WorkflowRunInfo, agent: WorkflowAgentInfo): void
|
|
126
|
+
/**
|
|
127
|
+
* One `agent()` call settled (clean result, child failure, or run
|
|
128
|
+
* cancellation). Paired with {@link Events['workflow/agent-start']} by
|
|
129
|
+
* `agent.seq`, exactly once per started call on every stop path — on an
|
|
130
|
+
* engine termination path (a worker killed past its grace) the end is
|
|
131
|
+
* engine-synthesized with outcome `'cancelled'`.
|
|
132
|
+
* @param info - the run's identity snapshot.
|
|
133
|
+
* @param agent - the call identity plus its outcome.
|
|
134
|
+
* @mode emit
|
|
135
|
+
*/
|
|
136
|
+
'workflow/agent-end'(info: WorkflowRunInfo, agent: WorkflowAgentEndInfo): void
|
|
137
|
+
/**
|
|
138
|
+
* A workflow run settled (any stop reason). Fired when
|
|
139
|
+
* {@link WorkflowRun.result} resolves. Paired with
|
|
140
|
+
* {@link Events['workflow/start']}.
|
|
141
|
+
* @param info - the run's identity snapshot.
|
|
142
|
+
* @param result - the outcome data (stop reason, error, agent count) —
|
|
143
|
+
* deliberately WITHOUT the result value (see {@link WorkflowResultInfo}).
|
|
144
|
+
* @mode emit
|
|
145
|
+
*/
|
|
146
|
+
'workflow/end'(info: WorkflowRunInfo, result: WorkflowResultInfo): void
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** The full set of `workflow/*` event names {@link WorkflowEngine.emitWorkflowEvent} dispatches. */
|
|
151
|
+
export type WorkflowEventName =
|
|
152
|
+
| 'workflow/start'
|
|
153
|
+
| 'workflow/phase'
|
|
154
|
+
| 'workflow/log'
|
|
155
|
+
| 'workflow/agent-start'
|
|
156
|
+
| 'workflow/agent-end'
|
|
157
|
+
| 'workflow/end'
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Machine-routable fatal workflow failures: parse/meta/argument/schema errors,
|
|
161
|
+
* resource caps, subagent infrastructure failures, unserializable boundary
|
|
162
|
+
* values, and cancellation. An ordinary child failure resolves its item to
|
|
163
|
+
* `null` and is not one of these fatal codes.
|
|
164
|
+
*/
|
|
165
|
+
export type WorkflowErrorCode =
|
|
166
|
+
| 'SCRIPT_PARSE'
|
|
167
|
+
| 'META_INVALID'
|
|
168
|
+
| 'INVALID_ARGUMENT'
|
|
169
|
+
| 'UNSUPPORTED_OPTION'
|
|
170
|
+
| 'UNSUPPORTED_SCHEMA'
|
|
171
|
+
| 'AGENT_CAP'
|
|
172
|
+
| 'ITEM_CAP'
|
|
173
|
+
| 'AGENT_START'
|
|
174
|
+
| 'AGENT_RESULT'
|
|
175
|
+
| 'RESULT_UNSERIALIZABLE'
|
|
176
|
+
| 'CANCELLED'
|
|
177
|
+
| 'WORKER_EXIT'
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Typed error for workflow-seam failures. Extends {@link HarnessError}, so the
|
|
181
|
+
* `code` is machine-routable taxonomy. `fatal` drives the combinator
|
|
182
|
+
* discipline: `parallel()`/`pipeline()` re-throw a fatal error (a typo'd
|
|
183
|
+
* option or a tripped cap must kill the script loudly), and reserve the
|
|
184
|
+
* per-item `null` for child-run failures and ordinary in-stage script errors.
|
|
185
|
+
* Every {@link WorkflowErrorCode} is fatal; the flag exists so the
|
|
186
|
+
* distinction is explicit at every catch site rather than implied.
|
|
187
|
+
*/
|
|
188
|
+
export class WorkflowError extends HarnessError {
|
|
189
|
+
/** Whether combinators must propagate this error instead of nulling the item. */
|
|
190
|
+
readonly fatal: boolean
|
|
191
|
+
|
|
192
|
+
constructor(message: string, code: WorkflowErrorCode, options?: ErrorOptions & { fatal?: boolean }) {
|
|
193
|
+
super(message, code, options)
|
|
194
|
+
this.name = 'WorkflowError'
|
|
195
|
+
this.fatal = options?.fatal ?? true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Whether combinators must re-throw `error` instead of mapping the item to `null`.
|
|
201
|
+
* @param error - any thrown value; fatality is host `instanceof` (unforgeable from a script realm).
|
|
202
|
+
* @returns true iff `error` is a {@link WorkflowError} whose `fatal` flag is set.
|
|
203
|
+
*/
|
|
204
|
+
export function isFatalWorkflowError(error: unknown): boolean {
|
|
205
|
+
return error instanceof WorkflowError && error.fatal
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Workflow Service Definition contract. Invalid requests throw before publication; a live
|
|
210
|
+
* run is holder-owned, its result never rejects, cancellation and disposal are
|
|
211
|
+
* bounded, and disposal waits for child cleanup within that bound. Lifecycle
|
|
212
|
+
* listener failures are contained, and `workflow/end` fires exactly once as the
|
|
213
|
+
* result settles.
|
|
214
|
+
*/
|
|
215
|
+
export abstract class WorkflowEngine extends Service {
|
|
216
|
+
constructor(ctx: Context) {
|
|
217
|
+
super(ctx, 'workflowEngine')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Parse and execute a workflow script.
|
|
222
|
+
* @param request - the script, its `args`, the parent agent, and an
|
|
223
|
+
* optional cancel signal.
|
|
224
|
+
* @returns the live run; its `result` resolves when the script settles.
|
|
225
|
+
*/
|
|
226
|
+
abstract start(request: WorkflowStartRequest): WorkflowRun
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Emit a lifecycle event while containing and logging each listener failure.
|
|
230
|
+
* @param name - the `workflow/*` event to dispatch.
|
|
231
|
+
* @param args - the event's payload, matching its declared signature.
|
|
232
|
+
*/
|
|
233
|
+
protected emitWorkflowEvent(name: WorkflowEventName, ...args: unknown[]): void {
|
|
234
|
+
for (const callback of this.ctx.events.dispatch('emit', [name, ...args])) {
|
|
235
|
+
try {
|
|
236
|
+
const returned: unknown = (callback as (...payload: unknown[]) => unknown)(...args)
|
|
237
|
+
void Promise.resolve(returned).catch((error: unknown) => {
|
|
238
|
+
this.ctx.logger.warn(`workflow: ${name} listener rejected: ${renderListenerError(error)}`)
|
|
239
|
+
})
|
|
240
|
+
} catch (error: unknown) {
|
|
241
|
+
this.ctx.logger.warn(`workflow: ${name} listener threw: ${renderListenerError(error)}`)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Render any thrown value without violating listener containment.
|
|
249
|
+
* @param error - any thrown value.
|
|
250
|
+
* @returns `String(error)`, or a fixed label when even coercion throws.
|
|
251
|
+
*/
|
|
252
|
+
function renderListenerError(error: unknown): string {
|
|
253
|
+
try {
|
|
254
|
+
return String(error)
|
|
255
|
+
} catch {
|
|
256
|
+
// String coercion itself may throw.
|
|
257
|
+
return '[unrenderable thrown value]'
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* In-process Provider: AsyncFunction body with `phase` / `log` / `agent`.
|
|
263
|
+
* `agent()` returns `null` unless `createAgent` is supplied (Cordis compositions
|
|
264
|
+
* that wire subagents). Product Face boot uses the Face-native `ralph` tool;
|
|
265
|
+
* this Provider fills the abstract `ctx.workflowEngine` hole for Cordis mounts.
|
|
266
|
+
*/
|
|
267
|
+
export class InProcessWorkflowEngine extends WorkflowEngine {
|
|
268
|
+
private readonly createAgent:
|
|
269
|
+
| ((
|
|
270
|
+
request: WorkflowStartRequest,
|
|
271
|
+
call: { label: string; prompt: string; phase?: string },
|
|
272
|
+
) => Promise<unknown>)
|
|
273
|
+
| undefined
|
|
274
|
+
private readonly toolBridge: WorkflowToolBridge | undefined
|
|
275
|
+
|
|
276
|
+
constructor(
|
|
277
|
+
ctx: Context,
|
|
278
|
+
options?: {
|
|
279
|
+
readonly createAgent?: InProcessWorkflowEngine['createAgent']
|
|
280
|
+
readonly toolBridge?: WorkflowToolBridge
|
|
281
|
+
},
|
|
282
|
+
) {
|
|
283
|
+
super(ctx)
|
|
284
|
+
this.createAgent = options?.createAgent
|
|
285
|
+
this.toolBridge = options?.toolBridge
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
start(request: WorkflowStartRequest): WorkflowRun {
|
|
289
|
+
const meta = request.meta
|
|
290
|
+
if (!meta || typeof meta !== 'object') {
|
|
291
|
+
throw new WorkflowError('workflow meta must be an object', 'META_INVALID')
|
|
292
|
+
}
|
|
293
|
+
if (typeof meta.name !== 'string' || !meta.name.trim()) {
|
|
294
|
+
throw new WorkflowError('workflow meta.name is required', 'META_INVALID')
|
|
295
|
+
}
|
|
296
|
+
if (typeof meta.description !== 'string' || !meta.description.trim()) {
|
|
297
|
+
throw new WorkflowError('workflow meta.description is required', 'META_INVALID')
|
|
298
|
+
}
|
|
299
|
+
if (typeof request.script !== 'string' || !request.script.trim()) {
|
|
300
|
+
throw new WorkflowError('workflow script must be a non-empty string', 'SCRIPT_PARSE')
|
|
301
|
+
}
|
|
302
|
+
const id = WorkflowRunId(`wf_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`)
|
|
303
|
+
let cancelled = false
|
|
304
|
+
let cancelReason = 'cancelled'
|
|
305
|
+
let agentsStarted = 0
|
|
306
|
+
let currentPhase: string | undefined
|
|
307
|
+
const ac = new AbortController()
|
|
308
|
+
const onAbort = () => {
|
|
309
|
+
cancelled = true
|
|
310
|
+
cancelReason = 'parent signal aborted'
|
|
311
|
+
ac.abort()
|
|
312
|
+
}
|
|
313
|
+
if (request.signal) {
|
|
314
|
+
if (request.signal.aborted) onAbort()
|
|
315
|
+
else request.signal.addEventListener('abort', onAbort, { once: true })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const info = { id, meta: request.meta }
|
|
319
|
+
this.emitWorkflowEvent('workflow/start', info)
|
|
320
|
+
|
|
321
|
+
const result = (async (): Promise<WorkflowResult> => {
|
|
322
|
+
try {
|
|
323
|
+
if (cancelled) {
|
|
324
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
325
|
+
}
|
|
326
|
+
const phase = (title: string) => {
|
|
327
|
+
currentPhase = String(title ?? '')
|
|
328
|
+
this.emitWorkflowEvent('workflow/phase', info, currentPhase)
|
|
329
|
+
}
|
|
330
|
+
const log = (message: string) => {
|
|
331
|
+
this.emitWorkflowEvent('workflow/log', info, String(message ?? ''))
|
|
332
|
+
}
|
|
333
|
+
const agent = async (opts: {
|
|
334
|
+
label?: string
|
|
335
|
+
prompt?: string
|
|
336
|
+
phase?: string
|
|
337
|
+
} = {}) => {
|
|
338
|
+
if (cancelled || ac.signal.aborted) {
|
|
339
|
+
throw new WorkflowError(cancelReason, 'CANCELLED')
|
|
340
|
+
}
|
|
341
|
+
const cap = request.maxTotalAgents
|
|
342
|
+
if (typeof cap === 'number' && agentsStarted >= cap) {
|
|
343
|
+
throw new WorkflowError(`workflow maxTotalAgents ${cap} reached`, 'AGENT_CAP')
|
|
344
|
+
}
|
|
345
|
+
agentsStarted += 1
|
|
346
|
+
const seq = agentsStarted
|
|
347
|
+
const label = String(opts.label ?? opts.prompt ?? `agent-${seq}`).slice(0, 120)
|
|
348
|
+
const phaseTitle = opts.phase ?? currentPhase
|
|
349
|
+
const childId = SessionId(`wf-child-${String(id)}-${seq}`)
|
|
350
|
+
this.emitWorkflowEvent('workflow/agent-start', info, {
|
|
351
|
+
seq,
|
|
352
|
+
label,
|
|
353
|
+
...(phaseTitle ? { phase: phaseTitle } : {}),
|
|
354
|
+
childId,
|
|
355
|
+
})
|
|
356
|
+
let outcome: 'completed' | 'failed' | 'cancelled' = 'failed'
|
|
357
|
+
let value: unknown = null
|
|
358
|
+
try {
|
|
359
|
+
if (this.createAgent) {
|
|
360
|
+
value = await this.createAgent(request, {
|
|
361
|
+
label,
|
|
362
|
+
prompt: String(opts.prompt ?? ''),
|
|
363
|
+
...(phaseTitle ? { phase: phaseTitle } : {}),
|
|
364
|
+
})
|
|
365
|
+
outcome = cancelled ? 'cancelled' : 'completed'
|
|
366
|
+
}
|
|
367
|
+
} catch {
|
|
368
|
+
outcome = cancelled ? 'cancelled' : 'failed'
|
|
369
|
+
value = null
|
|
370
|
+
}
|
|
371
|
+
this.emitWorkflowEvent('workflow/agent-end', info, {
|
|
372
|
+
seq,
|
|
373
|
+
label,
|
|
374
|
+
...(phaseTitle ? { phase: phaseTitle } : {}),
|
|
375
|
+
childId,
|
|
376
|
+
outcome,
|
|
377
|
+
})
|
|
378
|
+
return value
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor as new (
|
|
382
|
+
...args: string[]
|
|
383
|
+
) => (...args: unknown[]) => Promise<unknown>
|
|
384
|
+
const tools = buildWorkflowToolsProxy(this.toolBridge, ac.signal)
|
|
385
|
+
const fn = new AsyncFunction(
|
|
386
|
+
'args',
|
|
387
|
+
'phase',
|
|
388
|
+
'log',
|
|
389
|
+
'agent',
|
|
390
|
+
'tools',
|
|
391
|
+
`"use strict";\n${request.script}`,
|
|
392
|
+
)
|
|
393
|
+
const value = await fn(request.args, phase, log, agent, tools)
|
|
394
|
+
if (cancelled || ac.signal.aborted) {
|
|
395
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
value: value === undefined ? null : value,
|
|
399
|
+
stopReason: 'completed',
|
|
400
|
+
agentsStarted,
|
|
401
|
+
}
|
|
402
|
+
} catch (err) {
|
|
403
|
+
if (cancelled || ac.signal.aborted) {
|
|
404
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
405
|
+
}
|
|
406
|
+
return {
|
|
407
|
+
value: null,
|
|
408
|
+
stopReason: 'error',
|
|
409
|
+
error: err instanceof Error ? err.message : String(err),
|
|
410
|
+
agentsStarted,
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
})().then((settled) => {
|
|
414
|
+
this.emitWorkflowEvent('workflow/end', info, {
|
|
415
|
+
stopReason: settled.stopReason,
|
|
416
|
+
agentsStarted: settled.agentsStarted,
|
|
417
|
+
...(settled.error ? { error: settled.error } : {}),
|
|
418
|
+
})
|
|
419
|
+
return settled
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
return {
|
|
423
|
+
id,
|
|
424
|
+
meta: request.meta,
|
|
425
|
+
result,
|
|
426
|
+
cancel(reason?: string) {
|
|
427
|
+
cancelled = true
|
|
428
|
+
cancelReason = reason?.trim() || 'cancelled'
|
|
429
|
+
ac.abort()
|
|
430
|
+
},
|
|
431
|
+
async dispose() {
|
|
432
|
+
cancelled = true
|
|
433
|
+
ac.abort()
|
|
434
|
+
await result.catch(() => undefined)
|
|
435
|
+
},
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Worker-thread Provider: script body runs in an isolated `worker_threads`
|
|
442
|
+
* Worker (eval). `agent()` posts an RPC to the parent thread, which may run
|
|
443
|
+
* an optional {@link IsolatingWorkflowEngine} `createAgent` bridge (same
|
|
444
|
+
* contract as {@link InProcessWorkflowEngine}) and return a structured-cloneable
|
|
445
|
+
* value. Without `createAgent`, `agent()` still settles `null`.
|
|
446
|
+
* `await tools.name(args)` uses an optional {@link WorkflowToolBridge}
|
|
447
|
+
* (Code Mode subset — nested Host tools via parentPort RPC). Full sandboxed
|
|
448
|
+
* Node PTC process confinement is not claimed.
|
|
449
|
+
*/
|
|
450
|
+
export class IsolatingWorkflowEngine extends WorkflowEngine {
|
|
451
|
+
private readonly createAgent:
|
|
452
|
+
| ((
|
|
453
|
+
request: WorkflowStartRequest,
|
|
454
|
+
call: { label: string; prompt: string; phase?: string },
|
|
455
|
+
) => Promise<unknown>)
|
|
456
|
+
| undefined
|
|
457
|
+
private readonly toolBridge: WorkflowToolBridge | undefined
|
|
458
|
+
|
|
459
|
+
constructor(
|
|
460
|
+
ctx: Context,
|
|
461
|
+
options?: {
|
|
462
|
+
readonly createAgent?: IsolatingWorkflowEngine['createAgent']
|
|
463
|
+
readonly toolBridge?: WorkflowToolBridge
|
|
464
|
+
},
|
|
465
|
+
) {
|
|
466
|
+
super(ctx)
|
|
467
|
+
this.createAgent = options?.createAgent
|
|
468
|
+
this.toolBridge = options?.toolBridge
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
start(request: WorkflowStartRequest): WorkflowRun {
|
|
472
|
+
const meta = request.meta
|
|
473
|
+
if (!meta || typeof meta !== 'object') {
|
|
474
|
+
throw new WorkflowError('workflow meta must be an object', 'META_INVALID')
|
|
475
|
+
}
|
|
476
|
+
if (typeof meta.name !== 'string' || !meta.name.trim()) {
|
|
477
|
+
throw new WorkflowError('workflow meta.name is required', 'META_INVALID')
|
|
478
|
+
}
|
|
479
|
+
if (typeof meta.description !== 'string' || !meta.description.trim()) {
|
|
480
|
+
throw new WorkflowError('workflow meta.description is required', 'META_INVALID')
|
|
481
|
+
}
|
|
482
|
+
if (typeof request.script !== 'string' || !request.script.trim()) {
|
|
483
|
+
throw new WorkflowError('workflow script must be a non-empty string', 'SCRIPT_PARSE')
|
|
484
|
+
}
|
|
485
|
+
const id = WorkflowRunId(`wf_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`)
|
|
486
|
+
let cancelled = false
|
|
487
|
+
let cancelReason = 'cancelled'
|
|
488
|
+
let agentsStarted = 0
|
|
489
|
+
const ac = new AbortController()
|
|
490
|
+
const onAbort = () => {
|
|
491
|
+
cancelled = true
|
|
492
|
+
cancelReason = 'parent signal aborted'
|
|
493
|
+
ac.abort()
|
|
494
|
+
}
|
|
495
|
+
if (request.signal) {
|
|
496
|
+
if (request.signal.aborted) onAbort()
|
|
497
|
+
else request.signal.addEventListener('abort', onAbort, { once: true })
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const info = { id, meta: request.meta }
|
|
501
|
+
this.emitWorkflowEvent('workflow/start', info)
|
|
502
|
+
|
|
503
|
+
const result = (async (): Promise<WorkflowResult> => {
|
|
504
|
+
if (cancelled) {
|
|
505
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
const settled = await runIsolatingWorkerScript({
|
|
509
|
+
script: request.script,
|
|
510
|
+
args: request.args,
|
|
511
|
+
signal: ac.signal,
|
|
512
|
+
onPhase: (title) => {
|
|
513
|
+
this.emitWorkflowEvent('workflow/phase', info, title)
|
|
514
|
+
},
|
|
515
|
+
onLog: (message) => {
|
|
516
|
+
this.emitWorkflowEvent('workflow/log', info, message)
|
|
517
|
+
},
|
|
518
|
+
onAgentStart: (agent) => {
|
|
519
|
+
agentsStarted += 1
|
|
520
|
+
this.emitWorkflowEvent('workflow/agent-start', info, agent)
|
|
521
|
+
},
|
|
522
|
+
onAgentEnd: (agent) => {
|
|
523
|
+
this.emitWorkflowEvent('workflow/agent-end', info, agent)
|
|
524
|
+
},
|
|
525
|
+
runId: String(id),
|
|
526
|
+
toolNames: this.toolBridge?.listNames() ?? [],
|
|
527
|
+
...(typeof request.maxTotalAgents === 'number'
|
|
528
|
+
? { maxTotalAgents: request.maxTotalAgents }
|
|
529
|
+
: {}),
|
|
530
|
+
...(this.createAgent
|
|
531
|
+
? {
|
|
532
|
+
createAgent: async (call: {
|
|
533
|
+
label: string
|
|
534
|
+
prompt: string
|
|
535
|
+
phase?: string
|
|
536
|
+
}) => {
|
|
537
|
+
if (cancelled || ac.signal.aborted) {
|
|
538
|
+
throw new WorkflowError(cancelReason, 'CANCELLED')
|
|
539
|
+
}
|
|
540
|
+
return this.createAgent!(request, call)
|
|
541
|
+
},
|
|
542
|
+
}
|
|
543
|
+
: {}),
|
|
544
|
+
...(this.toolBridge ? { toolBridge: this.toolBridge } : {}),
|
|
545
|
+
})
|
|
546
|
+
if (cancelled || ac.signal.aborted) {
|
|
547
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
548
|
+
}
|
|
549
|
+
agentsStarted = Math.max(agentsStarted, settled.agentsStarted)
|
|
550
|
+
return settled
|
|
551
|
+
} catch (err) {
|
|
552
|
+
if (cancelled || ac.signal.aborted) {
|
|
553
|
+
return { value: null, stopReason: 'cancelled', error: cancelReason, agentsStarted }
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
value: null,
|
|
557
|
+
stopReason: 'error',
|
|
558
|
+
error: err instanceof Error ? err.message : String(err),
|
|
559
|
+
agentsStarted,
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
})().then((settled) => {
|
|
563
|
+
this.emitWorkflowEvent('workflow/end', info, {
|
|
564
|
+
stopReason: settled.stopReason,
|
|
565
|
+
agentsStarted: settled.agentsStarted,
|
|
566
|
+
...(settled.error ? { error: settled.error } : {}),
|
|
567
|
+
})
|
|
568
|
+
return settled
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
id,
|
|
573
|
+
meta: request.meta,
|
|
574
|
+
result,
|
|
575
|
+
cancel(reason?: string) {
|
|
576
|
+
cancelled = true
|
|
577
|
+
cancelReason = reason?.trim() || 'cancelled'
|
|
578
|
+
ac.abort()
|
|
579
|
+
},
|
|
580
|
+
async dispose() {
|
|
581
|
+
cancelled = true
|
|
582
|
+
ac.abort()
|
|
583
|
+
await result.catch(() => undefined)
|
|
584
|
+
},
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/** Best-effort clone so worker_threads can postMessage the agent result. */
|
|
590
|
+
function cloneForWorker(value: unknown): unknown {
|
|
591
|
+
if (value === undefined) return null
|
|
592
|
+
try {
|
|
593
|
+
return structuredClone(value)
|
|
594
|
+
} catch {
|
|
595
|
+
try {
|
|
596
|
+
return JSON.parse(JSON.stringify(value)) as unknown
|
|
597
|
+
} catch {
|
|
598
|
+
return null
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
async function runIsolatingWorkerScript(input: {
|
|
604
|
+
readonly script: string
|
|
605
|
+
readonly args: unknown
|
|
606
|
+
readonly signal?: AbortSignal
|
|
607
|
+
readonly onPhase: (title: string) => void
|
|
608
|
+
readonly onLog: (message: string) => void
|
|
609
|
+
readonly onAgentStart: (agent: WorkflowAgentInfo) => void
|
|
610
|
+
readonly onAgentEnd: (agent: WorkflowAgentEndInfo) => void
|
|
611
|
+
readonly runId: string
|
|
612
|
+
readonly maxTotalAgents?: number
|
|
613
|
+
readonly toolNames?: readonly string[]
|
|
614
|
+
readonly createAgent?: (call: {
|
|
615
|
+
label: string
|
|
616
|
+
prompt: string
|
|
617
|
+
phase?: string
|
|
618
|
+
}) => Promise<unknown>
|
|
619
|
+
readonly toolBridge?: WorkflowToolBridge
|
|
620
|
+
}): Promise<WorkflowResult> {
|
|
621
|
+
const { Worker } = await import('node:worker_threads')
|
|
622
|
+
const workerSource = `
|
|
623
|
+
const { parentPort, workerData } = require('node:worker_threads');
|
|
624
|
+
let agentsStarted = 0;
|
|
625
|
+
let callSeq = 0;
|
|
626
|
+
const pending = new Map();
|
|
627
|
+
parentPort.on('message', (msg) => {
|
|
628
|
+
if (!msg || (msg.type !== 'agent-result' && msg.type !== 'tool-result')) return;
|
|
629
|
+
const wait = pending.get(msg.callId);
|
|
630
|
+
if (!wait) return;
|
|
631
|
+
pending.delete(msg.callId);
|
|
632
|
+
if (msg.ok) wait.resolve(msg.value === undefined ? null : msg.value);
|
|
633
|
+
else wait.reject(new Error(String(msg.error || 'bridge failed')));
|
|
634
|
+
});
|
|
635
|
+
const phase = (title) => {
|
|
636
|
+
parentPort.postMessage({ type: 'phase', title: String(title ?? '') });
|
|
637
|
+
};
|
|
638
|
+
const log = (message) => {
|
|
639
|
+
parentPort.postMessage({ type: 'log', message: String(message ?? '') });
|
|
640
|
+
};
|
|
641
|
+
const agent = async (opts = {}) => {
|
|
642
|
+
const cap = workerData.maxTotalAgents;
|
|
643
|
+
if (typeof cap === 'number' && agentsStarted >= cap) {
|
|
644
|
+
throw new Error('workflow maxTotalAgents ' + cap + ' reached');
|
|
645
|
+
}
|
|
646
|
+
agentsStarted += 1;
|
|
647
|
+
const seq = agentsStarted;
|
|
648
|
+
const label = String(opts.label ?? opts.prompt ?? ('agent-' + seq)).slice(0, 120);
|
|
649
|
+
const phaseTitle = opts.phase;
|
|
650
|
+
const childId = 'wf-child-' + workerData.runId + '-' + seq;
|
|
651
|
+
const callId = 'c' + (++callSeq);
|
|
652
|
+
parentPort.postMessage({
|
|
653
|
+
type: 'agent-start',
|
|
654
|
+
seq, label, phase: phaseTitle || undefined, childId,
|
|
655
|
+
});
|
|
656
|
+
parentPort.postMessage({
|
|
657
|
+
type: 'agent-call',
|
|
658
|
+
callId, seq, label,
|
|
659
|
+
prompt: String(opts.prompt ?? ''),
|
|
660
|
+
phase: phaseTitle || undefined,
|
|
661
|
+
childId,
|
|
662
|
+
});
|
|
663
|
+
let value = null;
|
|
664
|
+
let outcome = 'completed';
|
|
665
|
+
try {
|
|
666
|
+
value = await new Promise((resolve, reject) => {
|
|
667
|
+
pending.set(callId, { resolve, reject });
|
|
668
|
+
});
|
|
669
|
+
} catch (err) {
|
|
670
|
+
outcome = 'failed';
|
|
671
|
+
parentPort.postMessage({
|
|
672
|
+
type: 'agent-end',
|
|
673
|
+
seq, label, phase: phaseTitle || undefined, childId, outcome,
|
|
674
|
+
});
|
|
675
|
+
throw err;
|
|
676
|
+
}
|
|
677
|
+
parentPort.postMessage({
|
|
678
|
+
type: 'agent-end',
|
|
679
|
+
seq, label, phase: phaseTitle || undefined, childId, outcome,
|
|
680
|
+
});
|
|
681
|
+
return value;
|
|
682
|
+
};
|
|
683
|
+
const toolNames = Array.isArray(workerData.toolNames) ? workerData.toolNames : [];
|
|
684
|
+
const callTool = (name) => async (args) => {
|
|
685
|
+
const callId = 't' + (++callSeq);
|
|
686
|
+
parentPort.postMessage({
|
|
687
|
+
type: 'tool-call',
|
|
688
|
+
callId,
|
|
689
|
+
name: String(name),
|
|
690
|
+
args: args === undefined ? {} : args,
|
|
691
|
+
});
|
|
692
|
+
const out = await new Promise((resolve, reject) => {
|
|
693
|
+
pending.set(callId, { resolve, reject });
|
|
694
|
+
});
|
|
695
|
+
if (out && out.isError) {
|
|
696
|
+
throw new Error('tools.' + name + ' failed: ' + String(out.content || ''));
|
|
697
|
+
}
|
|
698
|
+
return out && typeof out.content === 'string' ? out.content : String(out && out.content != null ? out.content : '');
|
|
699
|
+
};
|
|
700
|
+
const tools = new Proxy({}, {
|
|
701
|
+
get(_target, prop) {
|
|
702
|
+
if (typeof prop !== 'string' || prop === 'then') return undefined;
|
|
703
|
+
return callTool(prop);
|
|
704
|
+
},
|
|
705
|
+
ownKeys() { return toolNames.slice(); },
|
|
706
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
707
|
+
if (typeof prop === 'string' && toolNames.includes(prop)) {
|
|
708
|
+
return { enumerable: true, configurable: true, writable: false, value: callTool(prop) };
|
|
709
|
+
}
|
|
710
|
+
return undefined;
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;
|
|
714
|
+
try {
|
|
715
|
+
const fn = new AsyncFunction('args', 'phase', 'log', 'agent', 'tools',
|
|
716
|
+
'"use strict";\\n' + workerData.script);
|
|
717
|
+
Promise.resolve(fn(workerData.args, phase, log, agent, tools)).then((value) => {
|
|
718
|
+
parentPort.postMessage({
|
|
719
|
+
type: 'done',
|
|
720
|
+
ok: true,
|
|
721
|
+
value: value === undefined ? null : value,
|
|
722
|
+
agentsStarted,
|
|
723
|
+
});
|
|
724
|
+
}).catch((err) => {
|
|
725
|
+
parentPort.postMessage({
|
|
726
|
+
type: 'done',
|
|
727
|
+
ok: false,
|
|
728
|
+
error: String(err && err.message ? err.message : err),
|
|
729
|
+
agentsStarted,
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
} catch (err) {
|
|
733
|
+
parentPort.postMessage({
|
|
734
|
+
type: 'done',
|
|
735
|
+
ok: false,
|
|
736
|
+
error: String(err && err.message ? err.message : err),
|
|
737
|
+
agentsStarted: 0,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
`
|
|
741
|
+
|
|
742
|
+
return new Promise<WorkflowResult>((resolve, reject) => {
|
|
743
|
+
const worker = new Worker(workerSource, {
|
|
744
|
+
eval: true,
|
|
745
|
+
workerData: {
|
|
746
|
+
script: input.script,
|
|
747
|
+
args: input.args ?? null,
|
|
748
|
+
runId: input.runId,
|
|
749
|
+
toolNames: [...(input.toolNames ?? [])],
|
|
750
|
+
...(typeof input.maxTotalAgents === 'number'
|
|
751
|
+
? { maxTotalAgents: input.maxTotalAgents }
|
|
752
|
+
: {}),
|
|
753
|
+
},
|
|
754
|
+
resourceLimits: { maxOldGenerationSizeMb: 128 },
|
|
755
|
+
})
|
|
756
|
+
let settled = false
|
|
757
|
+
let agentsStartedSeen = 0
|
|
758
|
+
const finish = (result: WorkflowResult) => {
|
|
759
|
+
if (settled) return
|
|
760
|
+
settled = true
|
|
761
|
+
input.signal?.removeEventListener('abort', onAbort)
|
|
762
|
+
void worker.terminate().catch(() => undefined)
|
|
763
|
+
resolve(result)
|
|
764
|
+
}
|
|
765
|
+
const onAbort = () => {
|
|
766
|
+
void worker.terminate().catch(() => undefined)
|
|
767
|
+
finish({
|
|
768
|
+
value: null,
|
|
769
|
+
stopReason: 'cancelled',
|
|
770
|
+
error: 'parent signal aborted',
|
|
771
|
+
agentsStarted: agentsStartedSeen,
|
|
772
|
+
})
|
|
773
|
+
}
|
|
774
|
+
if (input.signal) {
|
|
775
|
+
if (input.signal.aborted) {
|
|
776
|
+
onAbort()
|
|
777
|
+
return
|
|
778
|
+
}
|
|
779
|
+
input.signal.addEventListener('abort', onAbort, { once: true })
|
|
780
|
+
}
|
|
781
|
+
worker.on('message', (msg: {
|
|
782
|
+
type?: string
|
|
783
|
+
title?: string
|
|
784
|
+
message?: string
|
|
785
|
+
seq?: number
|
|
786
|
+
label?: string
|
|
787
|
+
phase?: string
|
|
788
|
+
childId?: string
|
|
789
|
+
callId?: string
|
|
790
|
+
prompt?: string
|
|
791
|
+
name?: string
|
|
792
|
+
args?: unknown
|
|
793
|
+
outcome?: 'completed' | 'failed' | 'cancelled'
|
|
794
|
+
ok?: boolean
|
|
795
|
+
value?: unknown
|
|
796
|
+
error?: string
|
|
797
|
+
agentsStarted?: number
|
|
798
|
+
}) => {
|
|
799
|
+
if (!msg || typeof msg !== 'object') return
|
|
800
|
+
if (msg.type === 'phase' && typeof msg.title === 'string') {
|
|
801
|
+
input.onPhase(msg.title)
|
|
802
|
+
return
|
|
803
|
+
}
|
|
804
|
+
if (msg.type === 'log' && typeof msg.message === 'string') {
|
|
805
|
+
input.onLog(msg.message)
|
|
806
|
+
return
|
|
807
|
+
}
|
|
808
|
+
if (msg.type === 'agent-start' && typeof msg.seq === 'number') {
|
|
809
|
+
agentsStartedSeen = Math.max(agentsStartedSeen, msg.seq)
|
|
810
|
+
input.onAgentStart({
|
|
811
|
+
seq: msg.seq,
|
|
812
|
+
label: String(msg.label ?? ''),
|
|
813
|
+
...(msg.phase ? { phase: msg.phase } : {}),
|
|
814
|
+
childId: SessionId(String(msg.childId ?? `wf-child-${msg.seq}`)),
|
|
815
|
+
})
|
|
816
|
+
return
|
|
817
|
+
}
|
|
818
|
+
if (msg.type === 'agent-call' && typeof msg.callId === 'string') {
|
|
819
|
+
const callId = msg.callId
|
|
820
|
+
const label = String(msg.label ?? '')
|
|
821
|
+
const prompt = String(msg.prompt ?? '')
|
|
822
|
+
const phaseTitle = msg.phase
|
|
823
|
+
void (async () => {
|
|
824
|
+
try {
|
|
825
|
+
let value: unknown = null
|
|
826
|
+
if (input.createAgent) {
|
|
827
|
+
value = await input.createAgent({
|
|
828
|
+
label,
|
|
829
|
+
prompt,
|
|
830
|
+
...(phaseTitle ? { phase: String(phaseTitle) } : {}),
|
|
831
|
+
})
|
|
832
|
+
}
|
|
833
|
+
worker.postMessage({
|
|
834
|
+
type: 'agent-result',
|
|
835
|
+
callId,
|
|
836
|
+
ok: true,
|
|
837
|
+
value: cloneForWorker(value),
|
|
838
|
+
})
|
|
839
|
+
} catch (err) {
|
|
840
|
+
worker.postMessage({
|
|
841
|
+
type: 'agent-result',
|
|
842
|
+
callId,
|
|
843
|
+
ok: false,
|
|
844
|
+
error: err instanceof Error ? err.message : String(err),
|
|
845
|
+
})
|
|
846
|
+
}
|
|
847
|
+
})()
|
|
848
|
+
return
|
|
849
|
+
}
|
|
850
|
+
if (msg.type === 'tool-call' && typeof msg.callId === 'string' && typeof msg.name === 'string') {
|
|
851
|
+
const callId = msg.callId
|
|
852
|
+
const name = msg.name
|
|
853
|
+
void (async () => {
|
|
854
|
+
try {
|
|
855
|
+
if (!input.toolBridge) {
|
|
856
|
+
worker.postMessage({
|
|
857
|
+
type: 'tool-result',
|
|
858
|
+
callId,
|
|
859
|
+
ok: false,
|
|
860
|
+
error: 'tools bridge not mounted',
|
|
861
|
+
})
|
|
862
|
+
return
|
|
863
|
+
}
|
|
864
|
+
const names = input.toolBridge.listNames()
|
|
865
|
+
if (!names.includes(name)) {
|
|
866
|
+
worker.postMessage({
|
|
867
|
+
type: 'tool-result',
|
|
868
|
+
callId,
|
|
869
|
+
ok: false,
|
|
870
|
+
error: `unknown tool: ${name}`,
|
|
871
|
+
})
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
const out = await input.toolBridge.call(name, msg.args ?? {}, input.signal)
|
|
875
|
+
worker.postMessage({
|
|
876
|
+
type: 'tool-result',
|
|
877
|
+
callId,
|
|
878
|
+
ok: true,
|
|
879
|
+
value: cloneForWorker({
|
|
880
|
+
content: out.content,
|
|
881
|
+
...(out.isError ? { isError: true } : {}),
|
|
882
|
+
}),
|
|
883
|
+
})
|
|
884
|
+
} catch (err) {
|
|
885
|
+
worker.postMessage({
|
|
886
|
+
type: 'tool-result',
|
|
887
|
+
callId,
|
|
888
|
+
ok: false,
|
|
889
|
+
error: err instanceof Error ? err.message : String(err),
|
|
890
|
+
})
|
|
891
|
+
}
|
|
892
|
+
})()
|
|
893
|
+
return
|
|
894
|
+
}
|
|
895
|
+
if (msg.type === 'agent-end' && typeof msg.seq === 'number') {
|
|
896
|
+
input.onAgentEnd({
|
|
897
|
+
seq: msg.seq,
|
|
898
|
+
label: String(msg.label ?? ''),
|
|
899
|
+
...(msg.phase ? { phase: msg.phase } : {}),
|
|
900
|
+
childId: SessionId(String(msg.childId ?? `wf-child-${msg.seq}`)),
|
|
901
|
+
outcome: msg.outcome ?? 'completed',
|
|
902
|
+
})
|
|
903
|
+
return
|
|
904
|
+
}
|
|
905
|
+
if (msg.type === 'done') {
|
|
906
|
+
const agentsStarted =
|
|
907
|
+
typeof msg.agentsStarted === 'number' ? msg.agentsStarted : agentsStartedSeen
|
|
908
|
+
if (msg.ok) {
|
|
909
|
+
finish({
|
|
910
|
+
value: msg.value ?? null,
|
|
911
|
+
stopReason: 'completed',
|
|
912
|
+
agentsStarted,
|
|
913
|
+
})
|
|
914
|
+
} else {
|
|
915
|
+
finish({
|
|
916
|
+
value: null,
|
|
917
|
+
stopReason: 'error',
|
|
918
|
+
error: msg.error || 'worker failed',
|
|
919
|
+
agentsStarted,
|
|
920
|
+
})
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
})
|
|
924
|
+
worker.on('error', (err) => {
|
|
925
|
+
finish({
|
|
926
|
+
value: null,
|
|
927
|
+
stopReason: 'error',
|
|
928
|
+
error: err instanceof Error ? err.message : String(err),
|
|
929
|
+
agentsStarted: agentsStartedSeen,
|
|
930
|
+
})
|
|
931
|
+
})
|
|
932
|
+
worker.on('exit', (code) => {
|
|
933
|
+
if (settled) return
|
|
934
|
+
if (code === 0) {
|
|
935
|
+
finish({
|
|
936
|
+
value: null,
|
|
937
|
+
stopReason: 'completed',
|
|
938
|
+
agentsStarted: agentsStartedSeen,
|
|
939
|
+
})
|
|
940
|
+
} else {
|
|
941
|
+
reject(new WorkflowError(`isolating worker exited ${code}`, 'WORKER_EXIT'))
|
|
942
|
+
}
|
|
943
|
+
})
|
|
944
|
+
})
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
export default WorkflowEngine
|