@meiyukichan/dsh-sandore 0.1.0-rc.6
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/LICENSE +201 -0
- package/cordis.patch.yml +39 -0
- package/dsh.plugin.json +16 -0
- package/lib/client/CommandEditor.d.ts +19 -0
- package/lib/client/DagEditor.d.ts +22 -0
- package/lib/client/DefinitionDetail.d.ts +13 -0
- package/lib/client/DefinitionList.d.ts +19 -0
- package/lib/client/DiscussionInput.d.ts +63 -0
- package/lib/client/DragHandle.d.ts +16 -0
- package/lib/client/EditorPane.d.ts +13 -0
- package/lib/client/FlowEditor.d.ts +31 -0
- package/lib/client/InstanceList.d.ts +24 -0
- package/lib/client/NewInstanceDialog.d.ts +19 -0
- package/lib/client/PreviewPanel.d.ts +13 -0
- package/lib/client/RunView.d.ts +79 -0
- package/lib/client/StepChatPanel.d.ts +34 -0
- package/lib/client/TreeNav.d.ts +27 -0
- package/lib/client/UnifiedTimeline.d.ts +11 -0
- package/lib/client/WorkflowButton.d.ts +11 -0
- package/lib/client/WorkflowList.d.ts +18 -0
- package/lib/client/WorkflowOverlay.d.ts +6 -0
- package/lib/client/chat/AssistantMarkdown.d.ts +22 -0
- package/lib/client/chat/CompactionItem.d.ts +24 -0
- package/lib/client/chat/ContextBody.d.ts +136 -0
- package/lib/client/chat/ContextInjectionRow.d.ts +25 -0
- package/lib/client/chat/GenericCommandCard.d.ts +10 -0
- package/lib/client/chat/MessageIconActions.d.ts +41 -0
- package/lib/client/chat/MessageItem.d.ts +65 -0
- package/lib/client/chat/ReasoningRow.d.ts +13 -0
- package/lib/client/chat/TurnTailNodeView.d.ts +29 -0
- package/lib/client/chat/UserMessageRow.d.ts +18 -0
- package/lib/client/chat/attachment/ImageLightbox.d.ts +26 -0
- package/lib/client/chat/attachment/MessageImage.d.ts +56 -0
- package/lib/client/chat/attachment/image-attachment.d.ts +41 -0
- package/lib/client/chat/attachment/image-labels.d.ts +21 -0
- package/lib/client/chat/message-chrome.d.ts +51 -0
- package/lib/client/chat/reference/ReferenceIcon.d.ts +20 -0
- package/lib/client/chat/turn-assistant.d.ts +12 -0
- package/lib/client/chat/turn-metrics.d.ts +42 -0
- package/lib/client/chat/use-calendar-day.d.ts +5 -0
- package/lib/client/chat/use-throttled-visual-update.d.ts +7 -0
- package/lib/client/chat-flow.d.ts +110 -0
- package/lib/client/composer-selects.d.ts +17 -0
- package/lib/client/dag-editor-model.d.ts +141 -0
- package/lib/client/describe-error.d.ts +9 -0
- package/lib/client/index.d.ts +6 -0
- package/lib/client/locales.d.ts +777 -0
- package/lib/client/model-faces.d.ts +156 -0
- package/lib/client/open-discussion.d.ts +24 -0
- package/lib/client/panel-model.d.ts +34 -0
- package/lib/client/remote.d.ts +34 -0
- package/lib/client/run-view-model.d.ts +304 -0
- package/lib/client/sandore-save.d.ts +41 -0
- package/lib/client/sandore-sse.d.ts +15 -0
- package/lib/client/step-chat-retry.d.ts +44 -0
- package/lib/client/tool/GenericToolCard.d.ts +28 -0
- package/lib/client/tool/ToolRow.d.ts +95 -0
- package/lib/client/tool/diff-card-model.d.ts +63 -0
- package/lib/client/tool/read-card-model.d.ts +67 -0
- package/lib/client/tool/search-card-model.d.ts +94 -0
- package/lib/client/tool/terminal-card-model.d.ts +86 -0
- package/lib/client/tool/tool-call-model.d.ts +74 -0
- package/lib/client/tool/web-card-model.d.ts +44 -0
- package/lib/client/use-focus-refresh.d.ts +1 -0
- package/lib/client/workflow-face.d.ts +68 -0
- package/lib/client/workflow-store.d.ts +80 -0
- package/lib/client/workspace-path.d.ts +15 -0
- package/lib/client.js +38389 -0
- package/lib/index.js +8854 -0
- package/lib/microflow/branch-evaluator.d.ts +290 -0
- package/lib/microflow/concurrency-limiter.d.ts +39 -0
- package/lib/microflow/errors.d.ts +39 -0
- package/lib/microflow/file-utils.d.ts +210 -0
- package/lib/microflow/finalize-manager.d.ts +414 -0
- package/lib/microflow/fork-session.d.ts +151 -0
- package/lib/microflow/index.d.ts +17 -0
- package/lib/microflow/internal.d.ts +56 -0
- package/lib/microflow/loop-manager.d.ts +127 -0
- package/lib/microflow/rules-resolver.d.ts +28 -0
- package/lib/microflow/stage-executor.d.ts +362 -0
- package/lib/microflow/step-navigator.d.ts +108 -0
- package/lib/microflow/step-runner.d.ts +190 -0
- package/lib/microflow/subagents.d.ts +380 -0
- package/lib/tool/index.d.ts +65 -0
- package/lib/typert.host.d.ts +16 -0
- package/lib/typert.host.js +25 -0
- package/lib/typert.remote-client.d.ts +114 -0
- package/lib/typert.remote-client.js +399 -0
- package/lib/workflow/ask-tool.d.ts +78 -0
- package/lib/workflow/dag-navigator.d.ts +136 -0
- package/lib/workflow/domain.d.ts +304 -0
- package/lib/workflow/engine-surface.d.ts +70 -0
- package/lib/workflow/errors.d.ts +13 -0
- package/lib/workflow/host-agent.d.ts +53 -0
- package/lib/workflow/index.d.ts +23 -0
- package/lib/workflow/interaction-manager.d.ts +170 -0
- package/lib/workflow/lifecycle-manager.d.ts +111 -0
- package/lib/workflow/persistence.d.ts +35 -0
- package/lib/workflow/plugin.d.ts +27 -0
- package/lib/workflow/reactive-state.d.ts +27 -0
- package/lib/workflow/recovery-manager.d.ts +158 -0
- package/lib/workflow/remote.d.ts +139 -0
- package/lib/workflow/schema.d.ts +1917 -0
- package/lib/workflow/seam.d.ts +254 -0
- package/lib/workflow/shell.d.ts +127 -0
- package/lib/workflow/sse.d.ts +16 -0
- package/lib/workflow/typert/typert.host.d.ts +16 -0
- package/lib/workflow/typert/typert.remote-client.d.ts +114 -0
- package/lib/workflow/types.d.ts +187 -0
- package/lib/workflow/wire.d.ts +256 -0
- package/lib/workflow/workflow-engine.d.ts +373 -0
- package/lib/workflow/yaml-error.d.ts +22 -0
- package/lib/workflow/yaml-parser.d.ts +23 -0
- package/package.json +140 -0
- package/packages/client/CommandEditor.tsx +73 -0
- package/packages/client/DagEditor.module.css +711 -0
- package/packages/client/DagEditor.tsx +1203 -0
- package/packages/client/DefinitionDetail.module.css +110 -0
- package/packages/client/DefinitionDetail.tsx +126 -0
- package/packages/client/DefinitionList.tsx +173 -0
- package/packages/client/DiscussionInput.tsx +763 -0
- package/packages/client/DragHandle.tsx +92 -0
- package/packages/client/EditorPane.tsx +70 -0
- package/packages/client/FlowEditor.tsx +227 -0
- package/packages/client/InstanceList.tsx +250 -0
- package/packages/client/NewInstanceDialog.module.css +244 -0
- package/packages/client/NewInstanceDialog.tsx +478 -0
- package/packages/client/PreviewPanel.tsx +55 -0
- package/packages/client/RunView.module.css +246 -0
- package/packages/client/RunView.tsx +314 -0
- package/packages/client/StepChatPanel.tsx +486 -0
- package/packages/client/TreeNav.tsx +294 -0
- package/packages/client/UnifiedTimeline.tsx +484 -0
- package/packages/client/WorkflowButton.module.css +72 -0
- package/packages/client/WorkflowButton.tsx +50 -0
- package/packages/client/WorkflowList.tsx +158 -0
- package/packages/client/WorkflowOverlay.module.css +390 -0
- package/packages/client/WorkflowOverlay.tsx +136 -0
- package/packages/client/WorkflowPanel.module.css +247 -0
- package/packages/client/chat/AssistantMarkdown.module.css +38 -0
- package/packages/client/chat/AssistantMarkdown.tsx +120 -0
- package/packages/client/chat/ChatView.module.css +198 -0
- package/packages/client/chat/CompactionItem.tsx +85 -0
- package/packages/client/chat/ContextBody.module.css +162 -0
- package/packages/client/chat/ContextBody.tsx +598 -0
- package/packages/client/chat/ContextInjectionRow.module.css +63 -0
- package/packages/client/chat/ContextInjectionRow.tsx +78 -0
- package/packages/client/chat/GenericCommandCard.module.css +87 -0
- package/packages/client/chat/GenericCommandCard.tsx +76 -0
- package/packages/client/chat/MessageIconActions.module.css +84 -0
- package/packages/client/chat/MessageIconActions.tsx +147 -0
- package/packages/client/chat/MessageItem.module.css +293 -0
- package/packages/client/chat/MessageItem.tsx +384 -0
- package/packages/client/chat/ReasoningRow.module.css +82 -0
- package/packages/client/chat/ReasoningRow.tsx +70 -0
- package/packages/client/chat/TurnTailNodeView.module.css +10 -0
- package/packages/client/chat/TurnTailNodeView.tsx +63 -0
- package/packages/client/chat/UserMessageRow.module.css +75 -0
- package/packages/client/chat/UserMessageRow.tsx +92 -0
- package/packages/client/chat/accessibility.module.css +9 -0
- package/packages/client/chat/attachment/ImageLightbox.module.css +45 -0
- package/packages/client/chat/attachment/ImageLightbox.tsx +70 -0
- package/packages/client/chat/attachment/MessageImage.module.css +70 -0
- package/packages/client/chat/attachment/MessageImage.tsx +136 -0
- package/packages/client/chat/attachment/image-attachment.ts +98 -0
- package/packages/client/chat/attachment/image-labels.ts +34 -0
- package/packages/client/chat/message-chrome.ts +96 -0
- package/packages/client/chat/reference/ReferenceIcon.tsx +40 -0
- package/packages/client/chat/turn-assistant.ts +15 -0
- package/packages/client/chat/turn-metrics.ts +102 -0
- package/packages/client/chat/use-calendar-day.ts +27 -0
- package/packages/client/chat/use-throttled-visual-update.ts +44 -0
- package/packages/client/chat-flow.spec.ts +357 -0
- package/packages/client/chat-flow.ts +278 -0
- package/packages/client/composer-selects.module.css +376 -0
- package/packages/client/composer-selects.tsx +531 -0
- package/packages/client/dag-editor-model.spec.ts +173 -0
- package/packages/client/dag-editor-model.ts +359 -0
- package/packages/client/describe-error.spec.ts +18 -0
- package/packages/client/describe-error.ts +12 -0
- package/packages/client/index.ts +144 -0
- package/packages/client/input.module.css +854 -0
- package/packages/client/is-conditional-check.spec.ts +34 -0
- package/packages/client/locales.ts +789 -0
- package/packages/client/model-faces.ts +225 -0
- package/packages/client/open-discussion.spec.ts +39 -0
- package/packages/client/open-discussion.ts +32 -0
- package/packages/client/panel-model.spec.ts +152 -0
- package/packages/client/panel-model.ts +72 -0
- package/packages/client/preview.module.css +95 -0
- package/packages/client/react-dom.d.ts +11 -0
- package/packages/client/reactflow-theme.css +91 -0
- package/packages/client/remote.spec.ts +41 -0
- package/packages/client/remote.ts +46 -0
- package/packages/client/run-view-model.spec.ts +641 -0
- package/packages/client/run-view-model.ts +636 -0
- package/packages/client/sandore-save.spec.ts +146 -0
- package/packages/client/sandore-save.ts +97 -0
- package/packages/client/sandore-sse.ts +59 -0
- package/packages/client/step-chat-retry.spec.ts +70 -0
- package/packages/client/step-chat-retry.ts +65 -0
- package/packages/client/timeline.module.css +503 -0
- package/packages/client/tool/GenericToolCard.tsx +99 -0
- package/packages/client/tool/ToolRow.module.css +308 -0
- package/packages/client/tool/ToolRow.tsx +328 -0
- package/packages/client/tool/diff-card-model.ts +103 -0
- package/packages/client/tool/read-card-model.ts +86 -0
- package/packages/client/tool/search-card-model.ts +166 -0
- package/packages/client/tool/terminal-card-model.ts +225 -0
- package/packages/client/tool/tool-call-model.ts +267 -0
- package/packages/client/tool/web-card-model.ts +80 -0
- package/packages/client/tree-nav.module.css +282 -0
- package/packages/client/use-focus-refresh.ts +23 -0
- package/packages/client/workflow-face.ts +72 -0
- package/packages/client/workflow-store.spec.ts +49 -0
- package/packages/client/workflow-store.ts +161 -0
- package/packages/client/workspace-path.ts +27 -0
- package/packages/css-modules.d.ts +6 -0
- package/packages/microflow/branch-evaluator.spec.ts +745 -0
- package/packages/microflow/branch-evaluator.ts +1037 -0
- package/packages/microflow/concurrency-limiter.ts +70 -0
- package/packages/microflow/errors.ts +64 -0
- package/packages/microflow/file-utils.spec.ts +604 -0
- package/packages/microflow/file-utils.ts +521 -0
- package/packages/microflow/finalize-manager.spec.ts +1977 -0
- package/packages/microflow/finalize-manager.ts +1464 -0
- package/packages/microflow/fork-session.spec.ts +184 -0
- package/packages/microflow/fork-session.ts +316 -0
- package/packages/microflow/index.ts +17 -0
- package/packages/microflow/internal.ts +166 -0
- package/packages/microflow/loop-manager.spec.ts +294 -0
- package/packages/microflow/loop-manager.ts +255 -0
- package/packages/microflow/rules-resolver.spec.ts +147 -0
- package/packages/microflow/rules-resolver.ts +58 -0
- package/packages/microflow/stage-executor.spec.ts +1577 -0
- package/packages/microflow/stage-executor.ts +1478 -0
- package/packages/microflow/step-navigator.spec.ts +301 -0
- package/packages/microflow/step-navigator.ts +300 -0
- package/packages/microflow/step-runner.spec.ts +1132 -0
- package/packages/microflow/step-runner.ts +752 -0
- package/packages/microflow/subagents.spec.ts +215 -0
- package/packages/microflow/subagents.ts +422 -0
- package/packages/tests/fixtures/test-workflow.yaml +128 -0
- package/packages/tests/mocks/agents.ts +206 -0
- package/packages/tests/mocks/llm.ts +46 -0
- package/packages/tests/mocks/session-title.ts +31 -0
- package/packages/tests/mocks/sessions.ts +131 -0
- package/packages/tests/mocks/shell.ts +60 -0
- package/packages/tests/mocks/storage-domain.ts +101 -0
- package/packages/tests/mocks/subagents.ts +197 -0
- package/packages/tests/mocks/subprocess.ts +59 -0
- package/packages/tests/mocks/user-questions.ts +33 -0
- package/packages/tests/mocks/workspace.ts +52 -0
- package/packages/tool/index.spec.ts +287 -0
- package/packages/tool/index.ts +348 -0
- package/packages/workflow/ask-tool.ts +173 -0
- package/packages/workflow/dag-navigator.spec.ts +236 -0
- package/packages/workflow/dag-navigator.ts +425 -0
- package/packages/workflow/domain.ts +26 -0
- package/packages/workflow/engine-surface.ts +71 -0
- package/packages/workflow/errors.ts +13 -0
- package/packages/workflow/host-agent.spec.ts +92 -0
- package/packages/workflow/host-agent.ts +62 -0
- package/packages/workflow/index.ts +25 -0
- package/packages/workflow/interaction-manager.spec.ts +390 -0
- package/packages/workflow/interaction-manager.ts +397 -0
- package/packages/workflow/lifecycle-manager.spec.ts +683 -0
- package/packages/workflow/lifecycle-manager.ts +288 -0
- package/packages/workflow/persistence.spec.ts +109 -0
- package/packages/workflow/persistence.ts +64 -0
- package/packages/workflow/plugin.ts +39 -0
- package/packages/workflow/reactive-state.spec.ts +69 -0
- package/packages/workflow/reactive-state.ts +52 -0
- package/packages/workflow/recovery-manager.spec.ts +606 -0
- package/packages/workflow/recovery-manager.ts +471 -0
- package/packages/workflow/remote.spec.ts +1045 -0
- package/packages/workflow/remote.ts +479 -0
- package/packages/workflow/schema.spec.ts +303 -0
- package/packages/workflow/schema.ts +562 -0
- package/packages/workflow/seam.ts +293 -0
- package/packages/workflow/shell.ts +142 -0
- package/packages/workflow/sse.ts +110 -0
- package/packages/workflow/typert/typert.host.d.ts +16 -0
- package/packages/workflow/typert/typert.host.js +25 -0
- package/packages/workflow/typert/typert.remote-client.d.ts +114 -0
- package/packages/workflow/typert/typert.remote-client.js +399 -0
- package/packages/workflow/types.ts +258 -0
- package/packages/workflow/wire.ts +267 -0
- package/packages/workflow/workflow-engine-e2e.spec.ts +262 -0
- package/packages/workflow/workflow-engine-real-e2e.spec.ts +922 -0
- package/packages/workflow/workflow-engine.spec.ts +4389 -0
- package/packages/workflow/workflow-engine.ts +2133 -0
- package/packages/workflow/yaml-error.ts +29 -0
- package/packages/workflow/yaml-parser.spec.ts +757 -0
- package/packages/workflow/yaml-parser.ts +287 -0
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工作流 DAG 导航器(Stage 级编排)。
|
|
3
|
+
*
|
|
4
|
+
* **包职责边界(用户原则)**:workflow 只管 Stage——Stage→Stage 推进 + Stage 级编排 +
|
|
5
|
+
* 引擎 + Host RPC。本导航器持有 Stage 节点索引 `stageIndex`(Stage 节点图)、
|
|
6
|
+
* `stageContracts`(已完成 Stage 契约)、当前 Stage 指针,承担 Stage 级路由:
|
|
7
|
+
* {@link WorkflowDagNavigator.tryAdvanceStage} 基于 Stage `branch.condition` 评估选
|
|
8
|
+
* 下一 Stage(StageContract-based,**非 fork-based**)。
|
|
9
|
+
*
|
|
10
|
+
* **Step 级路由已迁 microflow**:`resolveNextNodes`(循环/条件/并行分支算下一批 Step)
|
|
11
|
+
* + 循环计数 + 判断 + 收尾全归 microflow 的 `StepNavigator`。一个 Stage 内部执行
|
|
12
|
+
* (含 Step 路由)自包含在 microflow 完成,才到本导航器 `tryAdvanceStage` 推进下一
|
|
13
|
+
* Stage。
|
|
14
|
+
*
|
|
15
|
+
* **Stage 级条件分支**:`WorkflowStage.branches` 可带 `condition`(CommandConfig:
|
|
16
|
+
* AI/shell)。`tryAdvanceStage` 在当前 Stage 完成后,按定义序评估 Stage 分支
|
|
17
|
+
* `condition`:shell 条件本地逐条执行(`matchesExpectation(label)`);AI 条件一次
|
|
18
|
+
* 批量调用(`ctx.llm.stream` 一次性会话 + StageContract 作 context,对齐 Step 级
|
|
19
|
+
* branch-evaluate-batch),返回满足条件的 branchId 数组。无 `condition` 分支为默认
|
|
20
|
+
* (始终走,按位置参与竞争);无命中按定义数组顺序兜底。Stage 级条件评估是 workflow
|
|
21
|
+
* 级、基于 StageContract——Stage 无单一 step 会话可 fork(fork 是 Step 级),Stage
|
|
22
|
+
* 条件基于聚合 StageContract 判断。
|
|
23
|
+
* @module dsh-sandore/workflow/src/dag-navigator
|
|
24
|
+
*/
|
|
25
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
26
|
+
import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
27
|
+
import type { GenerateOptions, Message, StreamChunk } from '@deepseek-ai/dsh-llm';
|
|
28
|
+
|
|
29
|
+
import { extractShouldExecuteBatch, matchesExpectation } from '../microflow/branch-evaluator';
|
|
30
|
+
import type { BranchEvaluatorConfig } from '../microflow/branch-evaluator';
|
|
31
|
+
import { describeError, escapeXml, resolveProviderModel, textSummary } from '../microflow/internal';
|
|
32
|
+
|
|
33
|
+
import type { CommandConfig, DagBranch, WorkflowDefinition, WorkflowStage, WorkflowTaskState } from './schema.ts';
|
|
34
|
+
import type { ShellExecRequest, ShellRunResult } from './shell.ts';
|
|
35
|
+
import type { StageContract } from './types.ts';
|
|
36
|
+
|
|
37
|
+
/** 已保存 Stage 契约的运行时形状(与快照 `stageContracts` 值严格一致)。 */
|
|
38
|
+
type StageContractValue = WorkflowTaskState['stageContracts'][string];
|
|
39
|
+
|
|
40
|
+
/** AI 条件评估命令的 value 取值域(provider/model 三层回退)。 */
|
|
41
|
+
type AiConditionValue = { readonly promptText: string; readonly providerId?: string; readonly modelId?: string };
|
|
42
|
+
/** shell 条件评估命令的 value。 */
|
|
43
|
+
type ShellConditionValue = { readonly command: string };
|
|
44
|
+
|
|
45
|
+
/** Stage 节点视图(出边统一在 branches;Stage 级路由唯一目标单元)。 */
|
|
46
|
+
export interface StageNodeRef {
|
|
47
|
+
readonly stage: WorkflowStage;
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly branches?: DagBranch[] | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 导航器初始化播种(崩溃恢复/重试续跑时从快照重建执行位置)。 */
|
|
54
|
+
export interface DagNavigatorSeed {
|
|
55
|
+
/** 起始 Stage(从快照 `currentStageId` 播种,缺省为定义首个 Stage)。 */
|
|
56
|
+
readonly currentStageId?: string;
|
|
57
|
+
/** 已完成 Stage 契约(从快照 `stageContracts` 播种)。 */
|
|
58
|
+
readonly stageContracts?: Readonly<Record<string, StageContractValue>>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Stage 级条件分支评估提示词主头部(时序开场:当前 Stage 已执行完成,无角色声明)。 */
|
|
62
|
+
const STAGE_BRANCH_PROMPT_HEADER =
|
|
63
|
+
'当前 Stage 已执行完成。请根据当前 Stage 的完成契约,判断下列分支中哪些满足其执行条件。';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 工作流 DAG 导航器(Stage 级)。
|
|
67
|
+
*
|
|
68
|
+
* 保存 `stageIndex`(stageId → Stage 节点视图)、`stageContracts`(已完成 Stage 契约)、
|
|
69
|
+
* 当前 Stage 指针。`tryAdvanceStage` 基于 StageContract 评估 Stage `branch.condition`
|
|
70
|
+
* 选下一 Stage。Step 级路由/循环已不在本导航器(归 microflow `StepNavigator`)。
|
|
71
|
+
*/
|
|
72
|
+
export class WorkflowDagNavigator {
|
|
73
|
+
private readonly stageIndex = new Map<string, StageNodeRef>();
|
|
74
|
+
private readonly stageSet: ReadonlySet<string>;
|
|
75
|
+
private readonly stageContracts = new Map<string, StageContractValue>();
|
|
76
|
+
private readonly stageOrder: readonly string[];
|
|
77
|
+
private currentStageId: string;
|
|
78
|
+
private readonly ctx: Context;
|
|
79
|
+
private readonly config: BranchEvaluatorConfig;
|
|
80
|
+
private readonly cwd: string;
|
|
81
|
+
|
|
82
|
+
constructor(
|
|
83
|
+
definition: WorkflowDefinition,
|
|
84
|
+
ctx: Context,
|
|
85
|
+
config: BranchEvaluatorConfig,
|
|
86
|
+
cwd: string,
|
|
87
|
+
seed?: DagNavigatorSeed,
|
|
88
|
+
) {
|
|
89
|
+
this.ctx = ctx;
|
|
90
|
+
this.config = config;
|
|
91
|
+
this.cwd = cwd;
|
|
92
|
+
this.buildStageIndex(definition);
|
|
93
|
+
this.stageSet = new Set(definition.stages.map(stage => stage.id));
|
|
94
|
+
this.stageOrder = definition.stages.map(stage => stage.id);
|
|
95
|
+
// 从快照播种当前 Stage(续跑入口);缺省回退定义首个 Stage。
|
|
96
|
+
this.currentStageId = seed?.currentStageId ?? this.stageOrder[0] ?? '';
|
|
97
|
+
if (seed?.stageContracts !== undefined) {
|
|
98
|
+
for (const [id, contract] of Object.entries(seed.stageContracts)) {
|
|
99
|
+
this.stageContracts.set(id, contract);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** 当前 Stage ID(无 Stage 时为空字符串)。 */
|
|
105
|
+
get currentNodeId(): string {
|
|
106
|
+
return this.currentStageId;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 已完成 Stage 的契约(stageId → StageContract)只读快照。 */
|
|
110
|
+
getStageContracts(): ReadonlyMap<string, StageContractValue> {
|
|
111
|
+
return new Map(this.stageContracts);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 查询单个 Stage 的契约。 */
|
|
115
|
+
getStageContract(stageId: string): StageContractValue | undefined {
|
|
116
|
+
return this.stageContracts.get(stageId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 聚合前置 Stage 的契约产物(供后续 Stage 引用)。 */
|
|
120
|
+
getContractArtifacts(): Record<string, string[]> {
|
|
121
|
+
const merged: Record<string, string[]> = {};
|
|
122
|
+
for (const contract of this.stageContracts.values()) {
|
|
123
|
+
for (const [name, files] of Object.entries(contract.contractArtifacts)) {
|
|
124
|
+
merged[name] = [...(merged[name] ?? []), ...files];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return merged;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** 保存 Stage 完成契约。 */
|
|
131
|
+
setStageContract(stageId: string, contract: StageContract): void {
|
|
132
|
+
this.stageContracts.set(stageId, contract);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** 当前 Stage 是否已全部完成(以已存契约的 completed 状态判定)。 */
|
|
136
|
+
isStageAllDone(stageId: string): boolean {
|
|
137
|
+
return this.stageContracts.get(stageId)?.status === 'completed';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 推进到下一 Stage(Stage 级条件分支批量评估)。
|
|
142
|
+
*
|
|
143
|
+
* 仅在当前 Stage 已完成(`isStageAllDone`)时推进。对当前 Stage 的 `branch`(Stage 级
|
|
144
|
+
* 出边)按定义序分组评估:
|
|
145
|
+
* ① 目标非 Stage 节点 → 跳过(Stage 路由只跟随 Stage 目标);
|
|
146
|
+
* ② 无 `condition`(默认分支)→ 恒通过(首个无 condition 位置);
|
|
147
|
+
* ③ shell 条件 → 本地逐条执行(命令各异不可合并),`matchesExpectation(label)` 判定;
|
|
148
|
+
* ④ AI 条件 → **一次批量调用**(`ctx.llm.stream` 一次性会话,StageContract 摘要 +
|
|
149
|
+
* 分支执行条件列表作 context,对齐 Step 级 branch-evaluate-batch),返回命中的
|
|
150
|
+
* branchId 数组。
|
|
151
|
+
* 定义序首个命中者胜出(默认分支按位置参与竞争);无命中按定义数组顺序推断下一
|
|
152
|
+
* Stage。无下一 Stage 返回 false。
|
|
153
|
+
*
|
|
154
|
+
* Stage 级条件评估是 workflow 级、基于 StageContract——**不是 fork-based**(Stage 无
|
|
155
|
+
* 单一 step 会话可 fork;Stage 条件基于聚合 StageContract 判断)。
|
|
156
|
+
*/
|
|
157
|
+
async tryAdvanceStage(signal?: AbortSignal): Promise<boolean> {
|
|
158
|
+
if (!this.isStageAllDone(this.currentStageId)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
const current = this.stageIndex.get(this.currentStageId);
|
|
162
|
+
if (current === undefined) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
const contract = this.getStageContract(this.currentStageId);
|
|
166
|
+
const branches = current.branches ?? [];
|
|
167
|
+
// 定义序分组:默认分支(无 condition,恒通过)、shell 条件(本地逐条)、AI 条件(批量一次)。
|
|
168
|
+
const defaultIndexes: number[] = [];
|
|
169
|
+
const shellCandidates: Array<{ branch: DagBranch; index: number }> = [];
|
|
170
|
+
const aiCandidates: Array<{ branch: DagBranch; index: number }> = [];
|
|
171
|
+
branches.forEach((branch, index) => {
|
|
172
|
+
// Stage 路由只跟随 Stage 目标;非 Stage 目标(理论上 Stage 分支只指 Stage)跳过。
|
|
173
|
+
if (!this.stageSet.has(branch.next)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (branch.condition === undefined) {
|
|
177
|
+
defaultIndexes.push(index);
|
|
178
|
+
} else if (branch.condition.type === 'shell') {
|
|
179
|
+
shellCandidates.push({ branch, index });
|
|
180
|
+
} else {
|
|
181
|
+
aiCandidates.push({ branch, index });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
// 命中集合(定义序 index):首个默认分支恒通过;shell 逐条评估;AI 条件一次批量调用。
|
|
185
|
+
const passed = new Set<number>();
|
|
186
|
+
if (defaultIndexes.length > 0) {
|
|
187
|
+
passed.add(defaultIndexes[0]!);
|
|
188
|
+
}
|
|
189
|
+
for (const { branch, index } of shellCandidates) {
|
|
190
|
+
if (await this.evaluateStageShellBranch(branch, contract, signal)) {
|
|
191
|
+
passed.add(index);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (aiCandidates.length > 0) {
|
|
195
|
+
const passedIds = await this.evaluateStageAiBranchesBatch(
|
|
196
|
+
aiCandidates.map(candidate => candidate.branch),
|
|
197
|
+
contract,
|
|
198
|
+
signal,
|
|
199
|
+
);
|
|
200
|
+
for (const { branch, index } of aiCandidates) {
|
|
201
|
+
if (passedIds.includes(branch.next)) {
|
|
202
|
+
passed.add(index);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// 定义序首个命中(默认分支按位置参与竞争,与逐条语义一致)。
|
|
207
|
+
let nextId: string | undefined;
|
|
208
|
+
for (let index = 0; index < branches.length; index += 1) {
|
|
209
|
+
const branch = branches[index]!;
|
|
210
|
+
if (!this.stageSet.has(branch.next)) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (passed.has(index)) {
|
|
214
|
+
nextId = branch.next;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (nextId === undefined) {
|
|
219
|
+
const index = this.stageOrder.indexOf(current.id);
|
|
220
|
+
nextId = index >= 0 ? this.stageOrder[index + 1] : undefined;
|
|
221
|
+
}
|
|
222
|
+
if (nextId === undefined || !this.stageSet.has(nextId)) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
this.currentStageId = nextId;
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 评估单个 Stage shell 条件分支(StageContract-based)。
|
|
231
|
+
*
|
|
232
|
+
* shell:经 `ctx.shell` 执行命令(StageContract 摘要作 stdin + 透传变量 env),成功取
|
|
233
|
+
* stdout,`matchesExpectation(label)` 判定;失败(非零退出/超时/中止)降级为条件不通过。
|
|
234
|
+
*/
|
|
235
|
+
private async evaluateStageShellBranch(
|
|
236
|
+
branch: DagBranch,
|
|
237
|
+
contract: StageContractValue | undefined,
|
|
238
|
+
signal: AbortSignal | undefined,
|
|
239
|
+
): Promise<boolean> {
|
|
240
|
+
const condition = branch.condition!;
|
|
241
|
+
const contractSummary = contract === undefined ? '(无已完成的 Stage 契约)' : this.summarizeStageContract(contract);
|
|
242
|
+
try {
|
|
243
|
+
const output = await this.runStageShell(condition as Extract<CommandConfig, { type: 'shell' }>, contractSummary, signal);
|
|
244
|
+
return matchesExpectation(output.trim(), branch.label);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
// 评估失败(shell 非零退出/超时):降级为条件不通过(走兜底)。
|
|
247
|
+
this.ctx.logger.warn(`sandore: stage shell branch condition evaluation failed: ${describeError(error)}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Stage AI 条件分支批量评估入口:一次调用评估所有 AI 条件分支(对齐 Step 级
|
|
254
|
+
* branch-evaluate-batch)。会话失败/解析失败整批降级(warn 一次 + 空数组 → 条件不通过)。
|
|
255
|
+
*/
|
|
256
|
+
private async evaluateStageAiBranchesBatch(
|
|
257
|
+
branches: readonly DagBranch[],
|
|
258
|
+
contract: StageContractValue | undefined,
|
|
259
|
+
signal: AbortSignal | undefined,
|
|
260
|
+
): Promise<readonly string[]> {
|
|
261
|
+
const contractSummary = contract === undefined ? '(无已完成的 Stage 契约)' : this.summarizeStageContract(contract);
|
|
262
|
+
try {
|
|
263
|
+
return await this.runStageAiBatch(branches, contractSummary, signal);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
this.ctx.logger.warn(`sandore: stage AI branch conditions evaluation failed: ${describeError(error)}`);
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Stage shell 条件:经 `ctx.shell` 执行命令,StageContract 摘要作 stdin。 */
|
|
271
|
+
private async runStageShell(
|
|
272
|
+
condition: Extract<CommandConfig, { type: 'shell' }>,
|
|
273
|
+
contractSummary: string,
|
|
274
|
+
signal: AbortSignal | undefined,
|
|
275
|
+
): Promise<string> {
|
|
276
|
+
const request: ShellExecRequest = {
|
|
277
|
+
command: condition.value.command,
|
|
278
|
+
timeoutMs: this.config.defaultTimeoutMs,
|
|
279
|
+
workdir: this.cwd,
|
|
280
|
+
stdin: contractSummary,
|
|
281
|
+
...(signal === undefined ? {} : { signal }),
|
|
282
|
+
};
|
|
283
|
+
const spec = this.ctx.shell.resolve(request);
|
|
284
|
+
const result = await this.ctx.shell.run(spec);
|
|
285
|
+
return this.toStdout(result);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** shell 结果 → stdout;非零退出/超时/中止抛错(由 evaluateStageShellBranch 捕获降级)。 */
|
|
289
|
+
private toStdout(result: ShellRunResult): string {
|
|
290
|
+
if (result.exitCode === 0 && !result.timedOut && !result.aborted) {
|
|
291
|
+
return result.stdout.text;
|
|
292
|
+
}
|
|
293
|
+
const stderr = result.stderr.text.trim();
|
|
294
|
+
const stderrSummary = stderr === '' ? '(no stderr)' : stderr;
|
|
295
|
+
if (result.timedOut) {
|
|
296
|
+
throw new Error(`stage shell condition timed out: ${stderrSummary}`);
|
|
297
|
+
}
|
|
298
|
+
const reason = result.aborted
|
|
299
|
+
? 'aborted'
|
|
300
|
+
: `exited with code ${result.exitCode === null ? 'signal' : String(result.exitCode)}`;
|
|
301
|
+
throw new Error(`stage shell condition ${reason}: ${stderrSummary}`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Stage AI 条件批量评估:`ctx.llm.stream` 一次性会话(一次调用评估所有 AI 条件分支)。
|
|
306
|
+
*
|
|
307
|
+
* 不 fork(Stage 无单一 step 会话可 fork)——一次性会话,StageContract 摘要 + 分支
|
|
308
|
+
* 执行条件列表拼进提示词作 context。provider/model 三层回退(首个 AI 分支条件定义 →
|
|
309
|
+
* 引擎配置 → ctx.llm 目录兜底,对齐 Step 级批量评估)。输出聚合后提取命中的
|
|
310
|
+
* branchId 数组;解析失败(undefined)→ 空数组(整批条件不通过,走兜底)。
|
|
311
|
+
*/
|
|
312
|
+
private async runStageAiBatch(
|
|
313
|
+
branches: readonly DagBranch[],
|
|
314
|
+
contractSummary: string,
|
|
315
|
+
signal: AbortSignal | undefined,
|
|
316
|
+
): Promise<readonly string[]> {
|
|
317
|
+
const value = (branches[0]!.condition!.value as AiConditionValue);
|
|
318
|
+
const route = await resolveProviderModel(value, this.config, this.ctx.llm);
|
|
319
|
+
const promptText = this.buildStageBatchBranchPrompt(branches, contractSummary);
|
|
320
|
+
const message = createUserMessage({
|
|
321
|
+
source: { kind: 'user' },
|
|
322
|
+
content: [{ type: 'text', text: promptText }],
|
|
323
|
+
});
|
|
324
|
+
const messages: Message[] = [message];
|
|
325
|
+
const generateOptions: GenerateOptions = {
|
|
326
|
+
provider: route.provider,
|
|
327
|
+
model: route.model,
|
|
328
|
+
messages,
|
|
329
|
+
...(signal === undefined ? {} : { signal }),
|
|
330
|
+
};
|
|
331
|
+
const assembler = new BlockAssembler();
|
|
332
|
+
const stream = this.ctx.llm.stream(generateOptions);
|
|
333
|
+
for await (const chunk of stream as AsyncIterable<StreamChunk>) {
|
|
334
|
+
assembler.push(chunk);
|
|
335
|
+
}
|
|
336
|
+
const output = textSummary(assembler.blocks());
|
|
337
|
+
return extractShouldExecuteBatch(output) ?? [];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 构造 Stage 分支批量评估提示词(v6:时序开场 + 四点 + 参考信息 + 分支执行条件 +
|
|
342
|
+
* 返回格式)。
|
|
343
|
+
*
|
|
344
|
+
* 批量评估所有 AI 条件分支(一次 llm 调用);shell 条件不经此(本地执行)。分支执行
|
|
345
|
+
* 条件列表(branchId + executeCondition)以 JSON 给出,executeCondition 取自各分支 AI
|
|
346
|
+
* 条件命令的 promptText;StageContract 摘要经 escapeXml 转义后以 `<ReferenceInfo>` 注入
|
|
347
|
+
* (必注入——一次性会话无 fork 历史,摘要是唯一判断依据)。无红线节:一次性会话
|
|
348
|
+
* (`llm.stream`)无工具,禁止类约束为空话(同 Phase A 原则)。
|
|
349
|
+
*/
|
|
350
|
+
private buildStageBatchBranchPrompt(branches: readonly DagBranch[], contractSummary: string): string {
|
|
351
|
+
const sections: string[] = [STAGE_BRANCH_PROMPT_HEADER];
|
|
352
|
+
sections.push('');
|
|
353
|
+
sections.push('必须先明确以下四点,再开始执行:');
|
|
354
|
+
sections.push('- `## 你的任务` —— 本次 Stage 路由判断要达成的**唯一目标**。');
|
|
355
|
+
sections.push('- `## 分支执行条件` —— 需要判断的分支及其执行条件。');
|
|
356
|
+
sections.push('- `## 参考信息` —— 当前 Stage 的完成契约,按需查阅。');
|
|
357
|
+
sections.push('- `## 返回格式` —— 必须返回的 JSON 格式。');
|
|
358
|
+
sections.push('');
|
|
359
|
+
sections.push('## 你的任务');
|
|
360
|
+
sections.push('根据 `## 分支执行条件` 与 `## 参考信息` 中当前 Stage 的完成契约,判断哪些分支满足其执行条件。**唯一目标**:找出所有满足执行条件的分支,返回其 branchId 列表。');
|
|
361
|
+
sections.push('');
|
|
362
|
+
sections.push('## 参考信息');
|
|
363
|
+
sections.push('<ReferenceInfo>');
|
|
364
|
+
sections.push(' <Reference title="当前 Stage 完成契约">');
|
|
365
|
+
sections.push(escapeXml(contractSummary));
|
|
366
|
+
sections.push(' </Reference>');
|
|
367
|
+
sections.push('</ReferenceInfo>');
|
|
368
|
+
sections.push('');
|
|
369
|
+
sections.push('## 分支执行条件');
|
|
370
|
+
sections.push('```json');
|
|
371
|
+
sections.push(JSON.stringify(branches.map(branch => ({
|
|
372
|
+
branchId: branch.next,
|
|
373
|
+
executeCondition: (branch.condition!.value as AiConditionValue).promptText,
|
|
374
|
+
})), null, 2));
|
|
375
|
+
sections.push('```');
|
|
376
|
+
sections.push('');
|
|
377
|
+
sections.push('## 返回格式');
|
|
378
|
+
sections.push('请仅返回 JSON,格式如下:');
|
|
379
|
+
sections.push('```json');
|
|
380
|
+
sections.push('{"shouldExecute": ["branchId1", "branchId2"]}');
|
|
381
|
+
sections.push('```');
|
|
382
|
+
sections.push('其中,`shouldExecute` 为满足其 `executeCondition` 条件的分支的 `branchId` 列表;没有任何分支满足时返回空数组 `[]`。');
|
|
383
|
+
return sections.join('\n');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 汇总 Stage 契约为紧凑文本(供 AI context / shell stdin)。
|
|
388
|
+
*
|
|
389
|
+
* 含 Stage 名/状态/产物(stepId → returnPrompt 摘要)/ 透传变量。避免全量 JSON 转储。
|
|
390
|
+
*/
|
|
391
|
+
private summarizeStageContract(contract: StageContractValue): string {
|
|
392
|
+
const lines: string[] = [];
|
|
393
|
+
lines.push(`Stage: ${contract.stageName}(${contract.stageId})`);
|
|
394
|
+
lines.push(`状态: ${contract.status}`);
|
|
395
|
+
if (contract.errorDetail !== undefined) {
|
|
396
|
+
lines.push(`错误: ${contract.errorDetail}`);
|
|
397
|
+
}
|
|
398
|
+
if (contract.artifacts.length > 0) {
|
|
399
|
+
lines.push('产物:');
|
|
400
|
+
for (const artifact of contract.artifacts) {
|
|
401
|
+
const prompt = artifact.returnPrompt === undefined ? '(无文本摘要)' : artifact.returnPrompt;
|
|
402
|
+
lines.push(`- step ${artifact.stepId} → ${artifact.outputToId}: ${prompt}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const vars = Object.entries(contract.passThroughVars);
|
|
406
|
+
if (vars.length > 0) {
|
|
407
|
+
lines.push('透传变量:');
|
|
408
|
+
for (const [name, value] of vars) {
|
|
409
|
+
lines.push(`- ${name}: ${value.value}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return lines.join('\n');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
private buildStageIndex(definition: WorkflowDefinition): void {
|
|
416
|
+
for (const stage of definition.stages) {
|
|
417
|
+
this.stageIndex.set(stage.id, {
|
|
418
|
+
stage,
|
|
419
|
+
id: stage.id,
|
|
420
|
+
name: stage.name,
|
|
421
|
+
branches: stage.branches,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sandore 存储域声明(Node 侧)。
|
|
3
|
+
*
|
|
4
|
+
* `sandoreDomainSpec` 依赖 `@deepseek-ai/dsh-storage-domain`(Node 侧 cordis 插件),
|
|
5
|
+
* 故从浏览器安全的 `schema.ts` 拆出至此,使定义层 schema 可作为 `./schema` 子路径
|
|
6
|
+
* 在浏览器侧安全导入。定义/Job/状态三表的 value schema 仍来自 `./schema.ts`(纯 zod)。
|
|
7
|
+
* @module dsh-sandore/workflow/src/domain
|
|
8
|
+
*/
|
|
9
|
+
import { defineDomain, domainTable } from '@deepseek-ai/dsh-storage-domain'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
WorkflowDefinitionRecordSchema,
|
|
13
|
+
WorkflowTaskStateSchema,
|
|
14
|
+
type WorkflowDefinitionRecord,
|
|
15
|
+
type WorkflowTaskState,
|
|
16
|
+
} from './schema.ts'
|
|
17
|
+
|
|
18
|
+
/** sandore 存储域声明:两表 `definitions`/`state`,Zod 校验每一条记录(去 job 概念)。 */
|
|
19
|
+
export const sandoreDomainSpec = defineDomain({
|
|
20
|
+
name: 'sandore',
|
|
21
|
+
version: 1,
|
|
22
|
+
tables: {
|
|
23
|
+
definitions: domainTable<string, WorkflowDefinitionRecord>(WorkflowDefinitionRecordSchema),
|
|
24
|
+
state: domainTable<string, WorkflowTaskState>(WorkflowTaskStateSchema),
|
|
25
|
+
},
|
|
26
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sandore 工作流引擎的实例级生命周期结构面(纯 type-only,无运行时依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 仅声明 `SandoreEngine` 抽象类暴露给宿主/工具的实例级操作签名,作为 seam 抽象类与
|
|
5
|
+
* tool 包结构面之间的「单一事实来源」,避免两份独立声明静默漂移。本文件不含任何
|
|
6
|
+
* `declare module` 模块增强与运行时代码,可被 tool 包以 `import type` 安全引用。
|
|
7
|
+
* @module dsh-sandore/workflow/src/engine-surface
|
|
8
|
+
*/
|
|
9
|
+
import type { AskUserQuestionAnswer, DagInstanceState, DagRun, DagStartRequest } from './types.ts';
|
|
10
|
+
import type { WorkflowTaskState } from './schema.ts';
|
|
11
|
+
import type { RecoverOrphanedInstancesResult, RevertBoundaryOption } from './wire.ts';
|
|
12
|
+
|
|
13
|
+
/** 工作流引擎实例级操作结构面(seam `SandoreEngine` 与 tool 包共享)。 */
|
|
14
|
+
export interface SandoreEngineSurface {
|
|
15
|
+
/** 启动一个新的工作流实例。 */
|
|
16
|
+
startInstance(request: DagStartRequest): Promise<DagRun>;
|
|
17
|
+
/** 恢复一个已存在的工作流实例。 */
|
|
18
|
+
recoverInstance(instanceId: string): Promise<DagRun>;
|
|
19
|
+
/** 批量恢复孤儿实例(进程重启后内存未命中但快照非终态),返回 { recovered, errors }。 */
|
|
20
|
+
recoverOrphanedInstances(): Promise<RecoverOrphanedInstancesResult>;
|
|
21
|
+
/** 中止一个运行中的工作流实例。 */
|
|
22
|
+
abortInstance(instanceId: string): Promise<void>;
|
|
23
|
+
/** 回复一个待处理问答请求。 */
|
|
24
|
+
replyQuestion(instanceId: string, requestId: string, answers: AskUserQuestionAnswer): Promise<void>;
|
|
25
|
+
/** 拒绝一个待处理问答请求(标对应 Step failed,缺陷 26)。 */
|
|
26
|
+
rejectQuestion(instanceId: string, requestId: string): Promise<void>;
|
|
27
|
+
/** 回复一个待处理权限请求。 */
|
|
28
|
+
replyPermission(instanceId: string, requestId: string, reply: 'once' | 'always' | 'reject'): Promise<void>;
|
|
29
|
+
/** 暂停单个 Step。 */
|
|
30
|
+
pauseStep(instanceId: string, stepId: string): Promise<void>;
|
|
31
|
+
/** 恢复单个 Step(可选附带恢复提示词)。 */
|
|
32
|
+
resumeStep(instanceId: string, stepId: string, resumePrompt?: string): Promise<void>;
|
|
33
|
+
/** 暂停实例内所有 Step。 */
|
|
34
|
+
pauseAllSteps(instanceId: string): Promise<void>;
|
|
35
|
+
/** 恢复实例内所有 Step(可选附带恢复提示词)。 */
|
|
36
|
+
resumeAllSteps(instanceId: string, resumePrompt?: string): Promise<void>;
|
|
37
|
+
/** 重试单个 Step(revert 模式可选 `revertBoundary` 指定更早闭合 turn/end)。 */
|
|
38
|
+
retryStep(instanceId: string, stepId: string, mode: 'continue' | 'revert' | 'fork', message: string, revertBoundary?: number): Promise<void>;
|
|
39
|
+
/** 列出某 Step 子代理会话中可用的闭合 turn 回退点候选(revert 回退点选择数据源)。 */
|
|
40
|
+
listRevertBoundaries(instanceId: string, stepId: string): RevertBoundaryOption[];
|
|
41
|
+
/**
|
|
42
|
+
* 向运行中实例的指定 Step 子代理追加一条用户 @mention 消息(缺陷 31)。
|
|
43
|
+
*
|
|
44
|
+
* 复用 DSH 宿主 `ctx.subagents.followup`(向子代理会话追加消息并取回新回复):
|
|
45
|
+
* requireActive + 终态守卫 → 按 stepId 反查最后活跃段 sessionMeta.childId →
|
|
46
|
+
* synthetic===false 才 followup(synthetic 步骤抛 INVALID_OPERATION——shell
|
|
47
|
+
* 合成 session 无真实子代理可发消息)→ 追加 userMessages `{kind:'message',
|
|
48
|
+
* target:stepId}`(不复用 'input' 以免污染 originalInput 语义)→ persistState →
|
|
49
|
+
* 双发 `sandore/step-message`。@mention 由 UI 端 @Step 选择器产出 stepId,
|
|
50
|
+
* 引擎端不做文本解析。
|
|
51
|
+
*/
|
|
52
|
+
sendStepMessage(instanceId: string, stepId: string, message: string): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* 运行中切换实例权限模式(缺陷 29)。
|
|
55
|
+
*
|
|
56
|
+
* 对宿主 session + hostAgent 重新应用 sandbox 模式 + 审批策略(复用
|
|
57
|
+
* `applyPermission`,DSH 折叠读取,下次受限调用即生效);终态实例拒绝。
|
|
58
|
+
* 仅作用于宿主会话与切换后新委派的 Step;已运行 Step 子代理保持其委派时
|
|
59
|
+
* 的权限快照(架构差异,非缺陷——见子代理快照契约测试)。
|
|
60
|
+
*/
|
|
61
|
+
setPermissionMode(instanceId: string, mode: 'read-only' | 'workspace-write' | 'danger-full-access'): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* 读运行中实例的内存 state(收尾阶段中间态立即可见;终态清表或未在内存返回 undefined)。
|
|
64
|
+
*
|
|
65
|
+
* 用于 `getInstanceState` RPC 的内存优先路径:引擎 `active.state` 引用在 commitFinalize
|
|
66
|
+
* 写入 currentPhase 时同步更新,事件发出时已可从内存读到中间态。
|
|
67
|
+
*/
|
|
68
|
+
getInstanceState(instanceId: string): WorkflowTaskState | undefined;
|
|
69
|
+
/** 查询实例的当前状态快照。 */
|
|
70
|
+
getState(instanceId: string): DagInstanceState;
|
|
71
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sandore 工作流引擎的类型化错误(re-export)。
|
|
3
|
+
*
|
|
4
|
+
* `SandoreError`/`SandoreErrorCode`/`isFatalSandoreError` 的实现已下沉至
|
|
5
|
+
* microflow 包(`../microflow/errors.ts`),此处仅
|
|
6
|
+
* re-export 以保持 workflow 包对外 API 不变。依赖方向统一为 workflow → microflow,
|
|
7
|
+
* microflow 不再跨包导入 workflow 的运行时值。
|
|
8
|
+
* @module dsh-sandore/workflow/src/errors
|
|
9
|
+
*/
|
|
10
|
+
export { SandoreError, isFatalSandoreError } from '../microflow/errors';
|
|
11
|
+
export type { SandoreErrorCode } from '../microflow/errors';
|
|
12
|
+
export { WorkflowYamlError } from './yaml-error.ts';
|
|
13
|
+
export type { WorkflowYamlErrorCode } from './yaml-error.ts';
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host-agent.ts(createHostAgentSetup 工厂)的单元测试(§3.0:宿主主代理 = 纯 DSH
|
|
3
|
+
* 默认主代理):setup 唯一动作是挂默认 agent preset(不注册任何 sandore_* 工具、
|
|
4
|
+
* 不注入编排器角色提示词)、presets 服务缺失时跳过 mount、create/resume 共用同一
|
|
5
|
+
* 工厂,以及宿主主代理创建的无自动 prompt 与 setup 抛错回滚语义。
|
|
6
|
+
* @module dsh-sandore/workflow/src/host-agent.spec
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import type { AgentSetupContext } from '../microflow/subagents';
|
|
11
|
+
import { FakeAgentRegistry } from '../tests/mocks/agents.ts';
|
|
12
|
+
import { FakeSessionStore } from '../tests/mocks/sessions.ts';
|
|
13
|
+
import { createHostAgentSetup } from './host-agent.ts';
|
|
14
|
+
|
|
15
|
+
/** 构造最小 agentCtx(setup 不再注册工具/提示词,只需满足 AgentSetupContext 结构)。 */
|
|
16
|
+
function makeRecordingContext(): { agentCtx: AgentSetupContext } {
|
|
17
|
+
const agentCtx: AgentSetupContext = {
|
|
18
|
+
tools: { register: () => {} },
|
|
19
|
+
systemPrompt: { section: () => {} },
|
|
20
|
+
};
|
|
21
|
+
return { agentCtx };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe('createHostAgentSetup (§3.0 pure default preset)', () => {
|
|
25
|
+
it('mounts the default agent preset via the injected presets seam and registers no sandore tools', async () => {
|
|
26
|
+
const mount = vi.fn(async () => ({ id: 'default' }));
|
|
27
|
+
const presets = { mount, resolve: vi.fn(async () => ({ id: 'default' })) };
|
|
28
|
+
const setup = createHostAgentSetup({ instanceId: 'inst-1', presets });
|
|
29
|
+
const { agentCtx } = makeRecordingContext();
|
|
30
|
+
|
|
31
|
+
await setup(agentCtx);
|
|
32
|
+
|
|
33
|
+
// 唯一动作:经注入的 presets 服务挂默认 preset(无 id = 默认),不注册任何工具。
|
|
34
|
+
expect(mount).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(mount).toHaveBeenCalledWith(agentCtx);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('skips mount when the presets seam is absent (no preset deployment)', async () => {
|
|
39
|
+
const setup = createHostAgentSetup({ instanceId: 'inst-1' });
|
|
40
|
+
const { agentCtx } = makeRecordingContext();
|
|
41
|
+
|
|
42
|
+
await setup(agentCtx);
|
|
43
|
+
|
|
44
|
+
// presets 缺失:跳过 mount 不抛错。
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('produces identical behavior for create and resume (same params → same closure)', async () => {
|
|
48
|
+
const mount = vi.fn(async () => ({ id: 'default' }));
|
|
49
|
+
const presets = { mount, resolve: vi.fn(async () => ({ id: 'default' })) };
|
|
50
|
+
const createSetup = createHostAgentSetup({ instanceId: 'inst-1', presets });
|
|
51
|
+
const resumeSetup = createHostAgentSetup({ instanceId: 'inst-1', presets });
|
|
52
|
+
|
|
53
|
+
const createCtx = makeRecordingContext();
|
|
54
|
+
const resumeCtx = makeRecordingContext();
|
|
55
|
+
await createSetup(createCtx.agentCtx);
|
|
56
|
+
await resumeSetup(resumeCtx.agentCtx);
|
|
57
|
+
|
|
58
|
+
expect(mount).toHaveBeenCalledTimes(2);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('host agent creation', () => {
|
|
63
|
+
it('mints an idle agent (no auto prompt) with meta.cwd and runs setup exactly once', async () => {
|
|
64
|
+
const sessions = new FakeSessionStore();
|
|
65
|
+
const registry = new FakeAgentRegistry(sessions);
|
|
66
|
+
const presets = { mount: vi.fn(async () => ({ id: 'default' })), resolve: vi.fn(async () => ({ id: 'default' })) };
|
|
67
|
+
const setup = createHostAgentSetup({ instanceId: 'inst-1', presets });
|
|
68
|
+
|
|
69
|
+
const handle = await registry.create({ sessionId: 'inst-1', meta: { cwd: '/srv/app' }, setup });
|
|
70
|
+
|
|
71
|
+
expect(handle.agent.id).toBe('inst-1');
|
|
72
|
+
expect(handle.agent.status).toBe('idle');
|
|
73
|
+
expect(handle.agent.inbox.hasPending).toBe(false);
|
|
74
|
+
expect(registry.createCwds).toEqual(['/srv/app']);
|
|
75
|
+
expect(registry.setupCount).toBe(1);
|
|
76
|
+
// 宿主 Session 已发布进 session store(引擎读回用于事件双发)。
|
|
77
|
+
expect(sessions.get('inst-1')).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rejects create when setup throws and leaves no agent or session registered', async () => {
|
|
81
|
+
const sessions = new FakeSessionStore();
|
|
82
|
+
const registry = new FakeAgentRegistry(sessions);
|
|
83
|
+
registry.setupError = new Error('setup boom');
|
|
84
|
+
|
|
85
|
+
const presets = { mount: vi.fn(async () => ({ id: 'default' })), resolve: vi.fn(async () => ({ id: 'default' })) };
|
|
86
|
+
const setup = createHostAgentSetup({ instanceId: 'inst-1', presets });
|
|
87
|
+
await expect(registry.create({ sessionId: 'inst-1', setup })).rejects.toThrow('setup boom');
|
|
88
|
+
|
|
89
|
+
expect(registry.get('inst-1')).toBeUndefined();
|
|
90
|
+
expect(sessions.get('inst-1')).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|