@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,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 恢复协调器(重试三模式 + 崩溃恢复的纯协调器)。
|
|
3
|
+
*
|
|
4
|
+
* `RecoveryManager` 是「纯协调器」:不持有引擎状态,仅经注入的选项/回调访问宿主
|
|
5
|
+
* 主代理、`subagents.followup`/`agents.create`、`sessions.fork`/`sessions.get`、
|
|
6
|
+
* `readState`/`commitState`(不可变重建 + 持久化)与 `emitStepRetried`(事件双发)。
|
|
7
|
+
* 它负责重试/恢复的「决策与准备」:前置校验、状态清理、`RetryRecord` 历史、子代理
|
|
8
|
+
* 会话准备(continue 同会话 / revert 安全边界 fork / fork 全量 fork),随后把真正的
|
|
9
|
+
* 执行(whenIdle + finalAssistantOutput + finalize + DAG 推进)交给引擎注入的
|
|
10
|
+
* `resumeExecution` 续跑。
|
|
11
|
+
*
|
|
12
|
+
* 重试三模式:`continue` 同会话 `followup`(sessionId 不变);`revert` 计算最后一个
|
|
13
|
+
* 闭合 turn 的末事件 seq 作安全边界(或用用户传入的 `revertBoundary` 选更早闭合 turn)
|
|
14
|
+
* → `sessions.fork(childId, boundary)` 取前缀 → `agents.create({ seed: forked.events,
|
|
15
|
+
* sessionId: 新合成 childId })`(新 session id,`parentSessionId` 记旧值);`fork` 省略
|
|
16
|
+
* boundary 保留全量历史(尾部未闭合时自动回退到最后一个闭合 turn/end 防 OPEN_TURN)
|
|
17
|
+
* → `agents.create({ seed })` 新 agent 续跑。三模式共用重试续跑循环:置 `running` →
|
|
18
|
+
* 清残留字段 → `RetryRecord` 入 `retryHistory` → 发射 `sandore/step-retried` →
|
|
19
|
+
* followup/续跑 → `resumeExecution`。仅 `failed` Step 可重试(completed 实例/Step
|
|
20
|
+
* 返回无害 no-op),执行中抛 `INVALID_OPERATION`。
|
|
21
|
+
*
|
|
22
|
+
* 崩溃恢复:`recover()` 按快照 `executedSteps` 分类处理——合成 session(shell)
|
|
23
|
+
* 标 `failed`、仅对第一个 `running`/`interacting` 子代理 Step `followup` 冷恢复(与引擎
|
|
24
|
+
* 单步续跑收口一致)、`paused` 交由引擎从 `pausedChildIds` 重建登记表等待手动 resume。
|
|
25
|
+
* @module dsh-sandore/workflow/src/recovery-manager
|
|
26
|
+
*/
|
|
27
|
+
import type { ContentBlock } from '@deepseek-ai/dsh-llm';
|
|
28
|
+
import type { SessionEvent } from '@deepseek-ai/dsh-session';
|
|
29
|
+
|
|
30
|
+
import type { AgentHandle, CreateAgentOptions } from '../microflow/subagents';
|
|
31
|
+
import { SandoreError } from './errors.ts';
|
|
32
|
+
import type { RetryRecord, UserMessageRecord, WorkflowTaskState } from './schema.ts';
|
|
33
|
+
|
|
34
|
+
/** 重试三模式。 */
|
|
35
|
+
export type RetryMode = 'continue' | 'revert' | 'fork';
|
|
36
|
+
|
|
37
|
+
/** 已执行 Step 的运行时形状(与状态快照 `executedSteps` 元素严格一致)。 */
|
|
38
|
+
type ExecutedStep = WorkflowTaskState['executedSteps'][number];
|
|
39
|
+
|
|
40
|
+
/** fork 源会话最小结构(仅需 `events`,用于安全边界计算与 seed 提取)。 */
|
|
41
|
+
export interface ForkSourceSession {
|
|
42
|
+
readonly events: readonly SessionEvent[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** revert 回退点候选:一个闭合 turn 的末事件 seq + turn 编号 + assistant 文本预览。 */
|
|
46
|
+
export interface RevertBoundaryOption {
|
|
47
|
+
/** 闭合 turn 的 `turn/end` 事件 seq(传给 `retryStep` 的 `revertBoundary`)。 */
|
|
48
|
+
readonly seq: number;
|
|
49
|
+
/** turn 编号(事件 `data.turn`;展示用「Turn N+1」)。 */
|
|
50
|
+
readonly turn: number;
|
|
51
|
+
/** 该 turn 内 assistant 文本块预览(前 80 字符;无 assistant 文本则为空串)。 */
|
|
52
|
+
readonly preview: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 单实例恢复例程的分类结果(供引擎决定是否重跑主循环)。 */
|
|
56
|
+
export interface RecoverResult {
|
|
57
|
+
/** 存在可续跑的子代理 Step(running/interacting 已 followup 冷恢复)。 */
|
|
58
|
+
readonly hasResume: boolean;
|
|
59
|
+
/** 存在合成 session Step(shell,已标 failed,实例不可恢复续跑)。 */
|
|
60
|
+
readonly hasFailure: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 恢复协调器的依赖(全部经回调/选项注入,保持纯协调器可单测)。 */
|
|
64
|
+
export interface RecoveryManagerOptions {
|
|
65
|
+
/** 引擎级取消信号(followup 的 `options.signal`)。 */
|
|
66
|
+
readonly signal: AbortSignal;
|
|
67
|
+
/** 读当前完整状态快照。 */
|
|
68
|
+
readonly readState: () => WorkflowTaskState;
|
|
69
|
+
/** 不可变重建 + 持久化当前状态快照。 */
|
|
70
|
+
readonly commitState: (next: WorkflowTaskState) => void;
|
|
71
|
+
/** 追加一条用户指令消息(写入 `state.userMessages` 并持久化)。 */
|
|
72
|
+
readonly appendUserMessage: (record: UserMessageRecord) => void;
|
|
73
|
+
/** `ctx.subagents.followup` 的注入包装(已绑定宿主主代理作 parent)。 */
|
|
74
|
+
readonly followup: (childId: string, content: ContentBlock[], signal: AbortSignal) => Promise<void> | void;
|
|
75
|
+
/** `ctx.agents.create` 的注入包装(revert/fork 创建带 seed 的新子代理)。 */
|
|
76
|
+
readonly createAgent: (options: CreateAgentOptions) => Promise<AgentHandle>;
|
|
77
|
+
/** `ctx.sessions.get` 的注入包装(安全边界计算读子代理事件流)。 */
|
|
78
|
+
readonly getSession: (id: string) => ForkSourceSession | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* 子代理会话 fork 的注入包装(boundary 省略 = 全量历史)。
|
|
81
|
+
*
|
|
82
|
+
* 2026-08-25 real-e2e:改异步——live 已移除(子代理 settle/dispose)时从持久化
|
|
83
|
+
* 回退读事件(`ctx.sessions.fork` 只认 live,直接抛 SESSION_NOT_FOUND)。
|
|
84
|
+
*/
|
|
85
|
+
readonly forkSession: (source: string, boundary?: number) => ForkSourceSession | Promise<ForkSourceSession>;
|
|
86
|
+
/** 双发 `sandore/step-retried` 事件(含 Step 名称与可选循环迭代路径)。 */
|
|
87
|
+
readonly emitStepRetried: (stepId: string, mode: RetryMode, name: string, loopIteration?: number[]) => void;
|
|
88
|
+
/** 重试/恢复后的续跑执行(whenIdle + finalAssistantOutput + finalize + DAG 推进)。 */
|
|
89
|
+
readonly resumeExecution: () => Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Step 终态集合(completed 实例/Step 的 no-op 判定)。 */
|
|
93
|
+
const TERMINAL_STEP_STATUSES: ReadonlySet<ExecutedStep['status']> = new Set(['completed', 'failed']);
|
|
94
|
+
|
|
95
|
+
/** 当前 ISO 时间戳。 */
|
|
96
|
+
function now(): string {
|
|
97
|
+
return new Date().toISOString();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 文本 ContentBlock 的构造。 */
|
|
101
|
+
function textBlock(text: string): ContentBlock[] {
|
|
102
|
+
return [{ type: 'text', text }];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** 合成新子代理 SessionId(纯 UUID 格式)。 */
|
|
106
|
+
export function synthesizeChildId(_stepId: string): string {
|
|
107
|
+
return globalThis.crypto.randomUUID();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 计算 revert 模式的安全 turn 边界:最后一个闭合 turn 的末事件 seq。
|
|
112
|
+
*
|
|
113
|
+
* 事件流中 `turn/end` 标记一个闭合 turn 的结束;若事件流无任何 `turn/end`(整个
|
|
114
|
+
* 会话处于未闭合 turn),返回 `undefined`(revert 以错误结束)。fork 前据此截取前缀
|
|
115
|
+
* 可避免 DSH `sessions.fork` 的 `OPEN_TURN` 校验失败。
|
|
116
|
+
* @param events - 子代理会话事件流(含 `turn/start`/`turn/end` 边界事件)。
|
|
117
|
+
* @returns 最后一个闭合 turn 的末事件 seq,或无法取到合法闭合 turn 时为 `undefined`。
|
|
118
|
+
*/
|
|
119
|
+
export function computeSafeRevertBoundary(events: readonly SessionEvent[]): number | undefined {
|
|
120
|
+
let lastClosedTurnEndSeq: number | undefined;
|
|
121
|
+
for (const event of events) {
|
|
122
|
+
if (event.type === 'turn/end') {
|
|
123
|
+
lastClosedTurnEndSeq = event.seq;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return lastClosedTurnEndSeq;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 判定会话事件流尾部是否存在未闭合 turn(最后一个 turn 边界为 `turn/start` 而非 `turn/end`)。
|
|
131
|
+
*
|
|
132
|
+
* DSH `sessions.fork` 默认 boundary(=最后事件 seq)在未闭合尾部下确定性抛 `OPEN_TURN`:
|
|
133
|
+
* 只要末事件落在某个 `turn/start` 之后且无匹配 `turn/end`,fork(undefined) 即失败。
|
|
134
|
+
* `retryStep` 的 fork 模式据此自动回退到最后一个闭合 turn/end,避免「子代理 turn 未闭合即失败」
|
|
135
|
+
* 的常见场景下直接抛错。
|
|
136
|
+
* @param events - 子代理会话事件流(含 `turn/start`/`turn/end` 边界事件)。
|
|
137
|
+
* @returns 末事件落在未闭合 turn 内为 true。
|
|
138
|
+
*/
|
|
139
|
+
export function hasUnclosedTurnTail(events: readonly SessionEvent[]): boolean {
|
|
140
|
+
let lastTurnBoundaryType: string | undefined;
|
|
141
|
+
for (const event of events) {
|
|
142
|
+
if (event.type === 'turn/start' || event.type === 'turn/end') {
|
|
143
|
+
lastTurnBoundaryType = event.type;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return lastTurnBoundaryType === 'turn/start';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** 从 assistant/message 事件提取纯文本(仅 text 块;其余块忽略)。 */
|
|
150
|
+
function assistantTextOf(event: SessionEvent): string {
|
|
151
|
+
const message = (event.data as { message?: { content?: ReadonlyArray<{ type: string; text?: string }> } } | undefined)?.message;
|
|
152
|
+
if (message === undefined) return '';
|
|
153
|
+
const content = message.content ?? [];
|
|
154
|
+
return content
|
|
155
|
+
.filter(block => block.type === 'text')
|
|
156
|
+
.map(block => block.text ?? '')
|
|
157
|
+
.join('');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** 截断至 max 字符(超出加省略号)。 */
|
|
161
|
+
function truncate(text: string, max: number): string {
|
|
162
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 列出会话事件流中所有闭合 turn 的回退点候选(UI revert 回退点选择的数据源)。
|
|
167
|
+
*
|
|
168
|
+
* 遍历事件,按 turn/start → … → turn/end 配对;每个闭合 turn 产出一个候选,
|
|
169
|
+
* `seq` = 该 turn/end 的事件 seq,`turn` = 该 turn 编号,`preview` = turn 内
|
|
170
|
+
* assistant 文本块拼接的前 80 字符。未闭合的尾部 turn(有 turn/start 无 turn/end)
|
|
171
|
+
* 不产出候选(与 `computeSafeRevertBoundary` 语义一致)。
|
|
172
|
+
* @param events - 子代理会话事件流。
|
|
173
|
+
* @returns 闭合 turn 回退点候选列表(按事件 seq 升序)。
|
|
174
|
+
*/
|
|
175
|
+
export function listClosedTurnBoundaries(events: readonly SessionEvent[]): RevertBoundaryOption[] {
|
|
176
|
+
const options: RevertBoundaryOption[] = [];
|
|
177
|
+
let currentTurn: number | undefined;
|
|
178
|
+
let currentPreview = '';
|
|
179
|
+
for (const event of events) {
|
|
180
|
+
if (event.type === 'turn/start') {
|
|
181
|
+
currentTurn = (event.data as { turn?: number } | undefined)?.turn ?? 0;
|
|
182
|
+
currentPreview = '';
|
|
183
|
+
} else if (event.type === 'assistant/message') {
|
|
184
|
+
const text = assistantTextOf(event);
|
|
185
|
+
if (text === '') continue;
|
|
186
|
+
currentPreview = currentPreview === '' ? text : `${currentPreview}\n${text}`;
|
|
187
|
+
} else if (event.type === 'turn/end') {
|
|
188
|
+
if (currentTurn !== undefined) {
|
|
189
|
+
options.push({ seq: event.seq, turn: currentTurn, preview: truncate(currentPreview, 80) });
|
|
190
|
+
}
|
|
191
|
+
currentTurn = undefined;
|
|
192
|
+
currentPreview = '';
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return options;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 恢复协调器。
|
|
200
|
+
*
|
|
201
|
+
* 持有注入的依赖,实现 `retryStep`(三模式重试)与 `recover`(Step 分类恢复);
|
|
202
|
+
* 状态更新以「不可变重建 + commitState」落到引擎 `active.state` 与持久化快照。
|
|
203
|
+
*/
|
|
204
|
+
export class RecoveryManager {
|
|
205
|
+
constructor(private readonly deps: RecoveryManagerOptions) {}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 重试单个 failed Step(continue/revert/fork 三模式共用续跑循环)。
|
|
209
|
+
*
|
|
210
|
+
* 前置校验:实例 completed 或 Step completed → 无害 no-op(不抛错、不改状态);
|
|
211
|
+
* Step running/interacting/paused → 抛 `INVALID_OPERATION`;仅 failed 可重试。
|
|
212
|
+
* 随后清残留字段 + 置 running + 追加 `RetryRecord`(result 先 `running`)+ 发射
|
|
213
|
+
* `sandore/step-retried`,按模式准备子代理会话(continue 不变 / revert 安全边界
|
|
214
|
+
* fork + 新 agent / fork 全量 fork + 新 agent),followup 后 `resumeExecution`
|
|
215
|
+
* 续跑,最后据实际结果更新 `retryHistory` 末条 result。
|
|
216
|
+
* @param stepId - 待重试 Step 的定义 id(或唯一执行 id)。
|
|
217
|
+
* @param mode - 重试模式。
|
|
218
|
+
* @param message - 重试附带的提示消息。
|
|
219
|
+
* @param revertBoundary - 可选:revert 模式用户所选闭合 turn/end 的事件 seq;
|
|
220
|
+
* 省略 = 最后一个闭合 turn/end(默认兼容,与改造前行为一致)。
|
|
221
|
+
*/
|
|
222
|
+
async retryStep(stepId: string, mode: RetryMode, message: string, revertBoundary?: number): Promise<void> {
|
|
223
|
+
const state = this.deps.readState();
|
|
224
|
+
if (state.status === 'completed') {
|
|
225
|
+
// completed 实例:结构化 no-op(不抛错、不改变状态)。
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const step = findStep(state.executedSteps, stepId);
|
|
229
|
+
if (step === undefined) {
|
|
230
|
+
throw new SandoreError(`step '${stepId}' not found`, 'INVALID_OPERATION');
|
|
231
|
+
}
|
|
232
|
+
if (step.status === 'completed') {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (step.status !== 'failed') {
|
|
236
|
+
throw new SandoreError(`step '${stepId}' is '${step.status}', only failed steps can be retried`, 'INVALID_OPERATION');
|
|
237
|
+
}
|
|
238
|
+
// shell 合成 session 无真实子代理可续跑:前置拦截(与 recover 分类语义一致),
|
|
239
|
+
// 避免 commitRunningStep/emitStepRetried/retryHistory 产生虚假重试历史与事件。
|
|
240
|
+
if (!isSubagentStep(step)) {
|
|
241
|
+
throw new SandoreError(`step '${stepId}' is not a resumable subagent step (shell cannot be retried)`, 'INVALID_OPERATION');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// 1. 以 failed 段为基底追加新段(保留旧 failed 段定格快照)+ RetryRecord 入新段 retryHistory。
|
|
245
|
+
const retryRecord: RetryRecord = { mode, timestamp: now(), userMessage: message, result: 'running' };
|
|
246
|
+
const cleaned = cleanStepResidue(step);
|
|
247
|
+
const newSegmentId = globalThis.crypto.randomUUID();
|
|
248
|
+
let runningStep: ExecutedStep = {
|
|
249
|
+
...cleaned,
|
|
250
|
+
id: newSegmentId,
|
|
251
|
+
attemptIndex: (cleaned.attemptIndex ?? 0) + 1,
|
|
252
|
+
createdAt: now(),
|
|
253
|
+
startedAt: now(),
|
|
254
|
+
status: 'running',
|
|
255
|
+
retryHistory: [...(cleaned.retryHistory ?? []), retryRecord],
|
|
256
|
+
};
|
|
257
|
+
this.commitRunningStep(state, runningStep);
|
|
258
|
+
this.deps.appendUserMessage({
|
|
259
|
+
id: `usr-${globalThis.crypto.randomUUID()}`,
|
|
260
|
+
kind: 'retry',
|
|
261
|
+
target: stepIdOf(step),
|
|
262
|
+
content: message,
|
|
263
|
+
createdAt: now(),
|
|
264
|
+
});
|
|
265
|
+
this.deps.emitStepRetried(stepIdOf(step), mode, step.name, step.loopIteration);
|
|
266
|
+
|
|
267
|
+
// 2. 按模式准备子代理会话(revert/fork 需 fork + 新 agent + 更新 childId/parentSessionId)。
|
|
268
|
+
let childId = childIdOf(step);
|
|
269
|
+
try {
|
|
270
|
+
if (mode === 'revert' || mode === 'fork') {
|
|
271
|
+
const sourceEvents = this.deps.getSession(childId)?.events ?? [];
|
|
272
|
+
let boundary: number | undefined;
|
|
273
|
+
if (mode === 'revert') {
|
|
274
|
+
// revert 回退点可选:用户传入的 revertBoundary(闭合 turn/end seq)优先;
|
|
275
|
+
// 省略时回退到 computeSafeRevertBoundary(最后一个闭合 turn/end,默认兼容)。
|
|
276
|
+
boundary = revertBoundary ?? computeSafeRevertBoundary(sourceEvents);
|
|
277
|
+
if (boundary === undefined) {
|
|
278
|
+
throw new SandoreError(
|
|
279
|
+
`cannot revert step '${stepId}': no closed turn boundary in child session`,
|
|
280
|
+
'INVALID_OPERATION',
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
// fork 防御(必要,非可选):源会话存在未闭合尾部时 fork(undefined) 确定性
|
|
285
|
+
// 抛 OPEN_TURN(DSH sessions.fork 默认 boundary=末事件 seq,末事件落在未闭合
|
|
286
|
+
// turn 内即拒绝)。检测到未闭合尾部时自动回退到最后一个闭合 turn/end
|
|
287
|
+
// (对齐 computeSafeRevertBoundary 语义);源会话无任何闭合 turn 时仍抛错
|
|
288
|
+
// (无法安全 fork)。源会话尾部已闭合时 boundary 保持 undefined(全量历史)。
|
|
289
|
+
if (hasUnclosedTurnTail(sourceEvents)) {
|
|
290
|
+
boundary = computeSafeRevertBoundary(sourceEvents);
|
|
291
|
+
if (boundary === undefined) {
|
|
292
|
+
throw new SandoreError(
|
|
293
|
+
`cannot fork step '${stepId}': source session has an open turn tail with no closed turn to fall back to`,
|
|
294
|
+
'INVALID_OPERATION',
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
const forked = await this.deps.forkSession(childId, boundary);
|
|
300
|
+
const newChildId = synthesizeChildId(stepIdOf(step));
|
|
301
|
+
await this.deps.createAgent({
|
|
302
|
+
sessionId: newChildId,
|
|
303
|
+
meta: { origin: 'subagent', parentSession: childId, seedLength: forked.events.length },
|
|
304
|
+
seed: forked.events,
|
|
305
|
+
});
|
|
306
|
+
// 更新 sessionMeta.childId 为新 session id,parentSessionId 记旧值。
|
|
307
|
+
// 注意:此处是同 id 段的原地更新(upsert),不能再用 commitRunningStep(append)——
|
|
308
|
+
// 否则 executedSteps 会出现两个同 id 段。
|
|
309
|
+
const sessionMeta = {
|
|
310
|
+
...(runningStep.sessionMeta ?? { command: 'agent' as const, synthetic: false }),
|
|
311
|
+
childId: newChildId,
|
|
312
|
+
synthetic: false,
|
|
313
|
+
};
|
|
314
|
+
runningStep = { ...runningStep, sessionMeta, parentSessionId: childId };
|
|
315
|
+
this.commitStep(this.deps.readState(), runningStep);
|
|
316
|
+
childId = newChildId;
|
|
317
|
+
}
|
|
318
|
+
await this.deps.followup(childId, textBlock(message), this.deps.signal);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
// 会话准备或 followup 失败:回退新段/工作流为 failed 并记录失败结果,保持可重试。
|
|
321
|
+
this.failRetry(newSegmentId);
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 3. 续跑执行(whenIdle + finalAssistantOutput + finalize + DAG 推进)。
|
|
326
|
+
try {
|
|
327
|
+
await this.deps.resumeExecution();
|
|
328
|
+
} catch (error) {
|
|
329
|
+
this.failRetry(newSegmentId);
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// 4. 据实际结果更新新段 retryHistory 末条 result。
|
|
334
|
+
const finalState = this.deps.readState();
|
|
335
|
+
const finalStep = findStep(finalState.executedSteps, newSegmentId);
|
|
336
|
+
const result = finalStep?.status === 'completed' ? 'completed' : 'failed';
|
|
337
|
+
this.setRetryResult(newSegmentId, result);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 单实例恢复例程的 Step 分类处理(崩溃恢复)。
|
|
342
|
+
*
|
|
343
|
+
* 遍历快照 `executedSteps`:`running`/`interacting` 的子代理 Step 经 `followup`
|
|
344
|
+
* 冷恢复(absent child 自动从持久化 Session 恢复);合成 session(shell)
|
|
345
|
+
* 无真实子代理,标记 `failed`(不可恢复);`completed`/`failed`/`paused` 无需处理
|
|
346
|
+
* (paused 登记表由引擎从 `pausedChildIds` 重建,等待手动 resume)。
|
|
347
|
+
* @returns 分类结果(是否有可续跑子代理 / 是否有合成 session 失败)。
|
|
348
|
+
*/
|
|
349
|
+
async recover(): Promise<RecoverResult> {
|
|
350
|
+
const state = this.deps.readState();
|
|
351
|
+
const coldResumePrompt = '请从崩溃前的断点继续完成当前任务。';
|
|
352
|
+
const running = state.executedSteps.filter(step => step.status === 'running' || step.status === 'interacting');
|
|
353
|
+
|
|
354
|
+
// shell 合成 session:子进程已丢失,标 failed(不可恢复)。
|
|
355
|
+
let hasFailure = false;
|
|
356
|
+
for (const step of running) {
|
|
357
|
+
if (isSubagentStep(step)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
this.commitStep(this.deps.readState(), { ...step, status: 'failed', error: 'synthetic session is not recoverable' });
|
|
361
|
+
hasFailure = true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 缺陷 10 多路恢复:遍历全部 running/interacting 子代理 Step 逐个 followup
|
|
365
|
+
// (对齐基准恢复 E 步)。并发落地后多个 Step 同时 running 为常态——全部 followup,
|
|
366
|
+
// 不再只取第一个(原 `.find()` 只取首条属防御性代码,并发后变真实缺陷)。
|
|
367
|
+
// 合成 session 失败时整体失败,不发起 followup。
|
|
368
|
+
let hasResume = false;
|
|
369
|
+
if (!hasFailure) {
|
|
370
|
+
const resumeSteps = running.filter(isSubagentStep);
|
|
371
|
+
for (const resumeStep of resumeSteps) {
|
|
372
|
+
const childId = childIdOf(resumeStep);
|
|
373
|
+
await this.deps.followup(childId, textBlock(coldResumePrompt), this.deps.signal);
|
|
374
|
+
hasResume = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return { hasResume, hasFailure };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** 不可变重建状态:更新单个 Step(按唯一执行 id 定位,不改实例状态)+ updatedAt。 */
|
|
381
|
+
private commitStep(prev: WorkflowTaskState, step: ExecutedStep): void {
|
|
382
|
+
this.deps.commitState({
|
|
383
|
+
...prev,
|
|
384
|
+
executedSteps: prev.executedSteps.map(item => (item.id === step.id ? step : item)),
|
|
385
|
+
updatedAt: now(),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/** 重试发起:追加新执行段(保留旧 failed 段定格快照)并置实例 running + updatedAt。 */
|
|
390
|
+
private commitRunningStep(prev: WorkflowTaskState, step: ExecutedStep): void {
|
|
391
|
+
this.deps.commitState({
|
|
392
|
+
...prev,
|
|
393
|
+
status: 'running',
|
|
394
|
+
executedSteps: [...prev.executedSteps, step],
|
|
395
|
+
updatedAt: now(),
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/** 重试会话准备/续跑失败:Step 回退 failed、实例 failed,记录 retryHistory 末条 result=failed。 */
|
|
400
|
+
private failRetry(stepId: string): void {
|
|
401
|
+
const state = this.deps.readState();
|
|
402
|
+
const step = findStep(state.executedSteps, stepId);
|
|
403
|
+
if (step === undefined) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const retryHistory = markLastRetryResult(step.retryHistory ?? [], 'failed');
|
|
407
|
+
this.deps.commitState({
|
|
408
|
+
...state,
|
|
409
|
+
status: 'failed',
|
|
410
|
+
executedSteps: state.executedSteps.map(item =>
|
|
411
|
+
item.id === stepId ? { ...item, status: 'failed' as const, retryHistory } : item,
|
|
412
|
+
),
|
|
413
|
+
updatedAt: now(),
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** 更新 retryHistory 末条记录的 result。 */
|
|
418
|
+
private setRetryResult(stepId: string, result: RetryRecord['result']): void {
|
|
419
|
+
const state = this.deps.readState();
|
|
420
|
+
const step = findStep(state.executedSteps, stepId);
|
|
421
|
+
if (step === undefined || step.retryHistory === undefined || step.retryHistory.length === 0) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const retryHistory = markLastRetryResult(step.retryHistory, result);
|
|
425
|
+
this.deps.commitState({
|
|
426
|
+
...state,
|
|
427
|
+
executedSteps: state.executedSteps.map(item => (item.id === stepId ? { ...item, retryHistory } : item)),
|
|
428
|
+
updatedAt: now(),
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/** 从 executedSteps 中按定义 stepId 或唯一执行 id 反查 Step。 */
|
|
434
|
+
function findStep(steps: readonly ExecutedStep[], stepId: string): ExecutedStep | undefined {
|
|
435
|
+
return steps.find(step => step.stepId === stepId) ?? steps.find(step => step.id === stepId);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** 反查 Step 的子代理 SessionId(缺省回退唯一执行 id)。 */
|
|
439
|
+
function childIdOf(step: ExecutedStep): string {
|
|
440
|
+
return step.sessionMeta?.childId ?? step.id;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/** Step 对外展示 id(定义 stepId,缺省回退唯一执行 id)。 */
|
|
444
|
+
function stepIdOf(step: ExecutedStep): string {
|
|
445
|
+
return step.stepId ?? step.id;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** 判定 Step 是否为可续接子代理 Step(agent/skill/mcp/prompt,非 shell 合成 session)。 */
|
|
449
|
+
function isSubagentStep(step: ExecutedStep): boolean {
|
|
450
|
+
const command = step.sessionMeta?.command ?? step.command?.type;
|
|
451
|
+
return command === 'agent' || command === 'skill' || command === 'mcp' || command === 'prompt';
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** 清除上一轮重试的残留字段(暂停细节/门禁评估现场;保留 sessionMessages 供消息框展示)。 */
|
|
455
|
+
function cleanStepResidue(step: ExecutedStep): ExecutedStep {
|
|
456
|
+
const {
|
|
457
|
+
pausedAt: _pausedAt,
|
|
458
|
+
pauseEntryId: _pauseEntryId,
|
|
459
|
+
finalizeMeta: _finalizeMeta,
|
|
460
|
+
...rest
|
|
461
|
+
} = step;
|
|
462
|
+
return rest as ExecutedStep;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** 更新 retryHistory 末条记录的 result(返回新数组)。 */
|
|
466
|
+
function markLastRetryResult(history: readonly RetryRecord[], result: RetryRecord['result']): RetryRecord[] {
|
|
467
|
+
if (history.length === 0) {
|
|
468
|
+
return [];
|
|
469
|
+
}
|
|
470
|
+
return history.map((record, index) => (index === history.length - 1 ? { ...record, result } : record));
|
|
471
|
+
}
|