@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,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 产物收集与输入解析工具集(纯函数 + 文件系统接缝)。
|
|
3
|
+
*
|
|
4
|
+
* 提供四组能力:
|
|
5
|
+
* - 透传变量替换:`replacePassThroughVars`(含 `in-loop-turn`/`all-loop-turn`);
|
|
6
|
+
* - glob→regex 转换与自动区分:`globToRegex`/`escapeRegexChar`/`isGlobPattern`;
|
|
7
|
+
* - 递归文件搜索:`walkDir`/`findFilesByGlob`/`findFilesByRegex`;
|
|
8
|
+
* - 产物文件读写:`writeOutputJson`/`readOutputJson`(`{uuid}_output.json`);
|
|
9
|
+
* - InputFrom 就近匹配:`resolvePathSegments`/`resolveFromInputFrom`/`idMatches`。
|
|
10
|
+
*
|
|
11
|
+
* 文件系统经 `FileSystem` 结构类型接缝(target 导向两段式协议:先 `resolve` 得
|
|
12
|
+
* `FsTarget`,再以 target 调 `listDir`/`readText`/`writeText`)注入,函数接受
|
|
13
|
+
* `fs` 结构类型作为入参,不硬绑 `ctx`。`@deepseek-ai/dsh-fs` 未作为 sandore 运行
|
|
14
|
+
* 时依赖安装,本文件按 `shell.ts`/`subagents.ts` 范式复刻最小结构类型并声明
|
|
15
|
+
* `ctx.fs` 上下文键,宿主运行时注入的真实服务结构兼容即可。
|
|
16
|
+
* @module dsh-sandore/microflow/src/file-utils
|
|
17
|
+
*/
|
|
18
|
+
import type { ExecutedStepDefault, InputFromItem } from '../workflow/types';
|
|
19
|
+
import { textSummary } from './internal.ts';
|
|
20
|
+
|
|
21
|
+
// ── 文件系统接缝(target 导向两段式协议) ───────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** 不透明文件系统目标(`resolve` 返回;`targetKey` 仅供后端识别)。 */
|
|
24
|
+
export interface FsTarget {
|
|
25
|
+
readonly targetKey: string;
|
|
26
|
+
readonly displayPath: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** `listDir` 返回的单个子项(`type` 判定目录性,`target` 供后续递归/读写)。 */
|
|
30
|
+
export interface FsDirEntry {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly type: 'file' | 'directory' | 'other';
|
|
33
|
+
readonly target: FsTarget;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `writeText` 的写入结果。 */
|
|
37
|
+
export interface FsWriteOutcome {
|
|
38
|
+
readonly operation: 'create' | 'update';
|
|
39
|
+
readonly version: string;
|
|
40
|
+
readonly before: string | null;
|
|
41
|
+
readonly after: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 文件系统最小结构接缝(target 导向)。 */
|
|
45
|
+
export interface FileSystem {
|
|
46
|
+
resolve(path: string, opts?: { cwd?: string; signal?: AbortSignal }): Promise<FsTarget>;
|
|
47
|
+
processPath(target: FsTarget): string;
|
|
48
|
+
listDir(target: FsTarget, signal?: AbortSignal): Promise<FsDirEntry[]>;
|
|
49
|
+
readText(target: FsTarget, signal?: AbortSignal): Promise<string>;
|
|
50
|
+
writeText(target: FsTarget, content: string): Promise<FsWriteOutcome>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare module '@deepseek-ai/cordis' {
|
|
54
|
+
interface Context {
|
|
55
|
+
fs: FileSystem;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── 透传变量替换 ────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 替换 `${xxx}` 透传变量:已定义**且值非空**替换为值,未定义或值为空保留原文。
|
|
63
|
+
*
|
|
64
|
+
* 2026-08-25(分支条件变量替换 + 空值守卫):值为空时保留 `${xxx}` 占位符——避免
|
|
65
|
+
* 替换成空导致路径粘连(如 `artifacts/${change-name}/proposal.md` + 空值 →
|
|
66
|
+
* `artifacts//proposal.md` 误匹配);占位符保留后由 `hasUnresolvedVars` 标 deferred,
|
|
67
|
+
* 模型/下游可见「变量未解析」而非静默空值。
|
|
68
|
+
*/
|
|
69
|
+
export function replacePassThroughVars(str: string, vars: Readonly<Record<string, string>>): string {
|
|
70
|
+
return str.replace(/\$\{([^}]+)\}/g, (whole, name: string) => {
|
|
71
|
+
const value = vars[name];
|
|
72
|
+
return value === undefined || value === '' ? whole : value;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 检测字符串是否含未解析的 `${xxx}` 占位符(对齐基准 `hasUnresolvedVars`)。
|
|
78
|
+
*
|
|
79
|
+
* 缺陷 33/34 共享工具:finalize-manager 原内联正则(`hasUnresolvedPassThroughVars`/
|
|
80
|
+
* `fillUnresolvedSingleVar`)与 outputTo 路径延迟解析共用同一变量语法语义。
|
|
81
|
+
*/
|
|
82
|
+
export function hasUnresolvedVars(str: string): boolean {
|
|
83
|
+
return /\$\{[^}]+\}/.test(str);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── outputTo 变量解析(缺陷 34) ──────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
/** outputTo 项解析结果:path/regex 替换后值 + 延迟标记。 */
|
|
89
|
+
export interface ResolvedOutputTo {
|
|
90
|
+
readonly path?: string;
|
|
91
|
+
readonly regex?: string;
|
|
92
|
+
/** 替换后仍含未解析 `${xxx}` → true(需延迟到 finalize 后用更新后的 vars 再解析)。 */
|
|
93
|
+
readonly deferred: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 解析单个 outputTo 项的 `path`/`regex` 中的 `${xxx}` 透传变量占位符
|
|
98
|
+
* (对齐基准 `resolveOutputToVars` L95-114)。
|
|
99
|
+
*
|
|
100
|
+
* 用 {@link replacePassThroughVars} 替换已定义变量;替换后仍含 `${}` 的项(变量未解析、
|
|
101
|
+
* 需延迟到 finalize 再解析)标 `deferred: true` 供调用方延迟处理。**纯函数、不改输入**
|
|
102
|
+
* ——返回新对象,原 outputTo 项保持只读不变,避免 finalize 评估与门禁校验之间的变量
|
|
103
|
+
* 竞态(调用方各自用传入的 vars 副本解析,不依赖提前持久化)。
|
|
104
|
+
*
|
|
105
|
+
* @param item - outputTo 项(只取 `path`/`regex`,`id` 由调用方保留)。
|
|
106
|
+
* @param vars - 透传变量字符串映射(已通过 `toVarMap` 转换)。
|
|
107
|
+
*/
|
|
108
|
+
export function resolveOutputToVars(
|
|
109
|
+
item: { readonly path?: string | undefined; readonly regex?: string | undefined },
|
|
110
|
+
vars: Readonly<Record<string, string>>,
|
|
111
|
+
): ResolvedOutputTo {
|
|
112
|
+
const path = item.path === undefined ? undefined : replacePassThroughVars(item.path, vars);
|
|
113
|
+
const regex = item.regex === undefined ? undefined : replacePassThroughVars(item.regex, vars);
|
|
114
|
+
const deferred =
|
|
115
|
+
(path !== undefined && hasUnresolvedVars(path)) ||
|
|
116
|
+
(regex !== undefined && hasUnresolvedVars(regex));
|
|
117
|
+
return {
|
|
118
|
+
...(path === undefined ? {} : { path }),
|
|
119
|
+
...(regex === undefined ? {} : { regex }),
|
|
120
|
+
deferred,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── glob / regex 转换与自动区分 ─────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/** 转义单个正则元字符(非元字符原样返回)。 */
|
|
127
|
+
export function escapeRegexChar(char: string): string {
|
|
128
|
+
return /[.*+?^${}()|[\]\\]/.test(char) ? `\\${char}` : char;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 将 glob 模式转换为锚定正则:`*`→`[^/]*`、`**`→`.*`(跳过后续 `/`)、`?`→`[^/]`。 */
|
|
132
|
+
export function globToRegex(glob: string): RegExp {
|
|
133
|
+
let pattern = '^';
|
|
134
|
+
for (let i = 0; i < glob.length; i++) {
|
|
135
|
+
const char = glob[i]!;
|
|
136
|
+
if (char === '*') {
|
|
137
|
+
if (glob[i + 1] === '*') {
|
|
138
|
+
pattern += '.*';
|
|
139
|
+
i++;
|
|
140
|
+
if (glob[i + 1] === '/') i++;
|
|
141
|
+
} else {
|
|
142
|
+
pattern += '[^/]*';
|
|
143
|
+
}
|
|
144
|
+
} else if (char === '?') {
|
|
145
|
+
pattern += '[^/]';
|
|
146
|
+
} else {
|
|
147
|
+
pattern += escapeRegexChar(char);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
pattern += '$';
|
|
151
|
+
return new RegExp(pattern);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 判定字符串是否为 glob 模式(对齐基准 `looksLikeGlob` 全特征检测)。
|
|
156
|
+
*
|
|
157
|
+
* 先排除含 regex 特征的模式——转义字符类 `\d`/`\D`/`\w`/`\W`/`\s`/`\S`/`\b`、
|
|
158
|
+
* `(?`(前瞻/后瞻/非捕获)、`.*`、`.+`、`[^`(否定字符类)、`{<数字>`(量词)、
|
|
159
|
+
* `^…$`(锚定)——再要求**未转义**的 `*` 或 `?` 才判为 glob。
|
|
160
|
+
*
|
|
161
|
+
* 修复缺陷 19:原实现只检 `\\.`(反斜杠转义)与 `(?` 两类,导致 `.*`/`[^a]*`/`foo.*`
|
|
162
|
+
* 等「含 * /? 且不含 \ 与 (?」的 regex 写法被误判为 glob → 经 {@link globToRegex}
|
|
163
|
+
* 静默错误匹配(`.*`→`^\.[^/]*$` 只匹配点文件、`[^a]*`→字面 `[^a]` 前缀几乎永不匹配
|
|
164
|
+
* 恒报 missing)。增强后这些模式正确判为非 glob → 走 {@link fileExists} 精确存在性
|
|
165
|
+
* 检查(缺陷 6 已修复:非 glob 不再走 {@link findFilesByRegex},避免 `.*`→
|
|
166
|
+
* `new RegExp('.*')` 匹配一切→门禁恒放行的联动风险)。
|
|
167
|
+
*
|
|
168
|
+
* 合法 glob(`out/*.log`、`out/file?.txt`、递归 `**` 通配)不含上述 regex 特征,
|
|
169
|
+
* 判定不变。
|
|
170
|
+
*/
|
|
171
|
+
export function isGlobPattern(pattern: string): boolean {
|
|
172
|
+
if (/\\[dDwWsSb]|\(\?|\.\*|\.\+|\[\^|\{\d|\^.*\$/.test(pattern)) return false;
|
|
173
|
+
return /(?<!\\)[*?]/.test(pattern);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── 递归文件搜索 ────────────────────────────────────────────────────────────
|
|
177
|
+
|
|
178
|
+
/** 一次递归遍历命中的文件条目(目标 + 相对根目录的路径)。 */
|
|
179
|
+
interface WalkedFile {
|
|
180
|
+
readonly target: FsTarget;
|
|
181
|
+
readonly relativePath: string;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 递归收集目录下全部文件。
|
|
186
|
+
*
|
|
187
|
+
* 跳过点**目录**(`.git`/`.vscode`/`.venv`/`.idea` 等,不递归其子树)与
|
|
188
|
+
* `node_modules`,保留点**文件**(`.env`/`.gitignore` 仍可作产物收集)——折中取舍:
|
|
189
|
+
* 完全对齐基准(`startsWith('.')` 跳过一切点条目)会回归「步骤生成 `.env` 后门禁
|
|
190
|
+
* 校验」场景。同时消除原死判断 `entry.name === '.'`(`listDir` 只返回子项名,永不
|
|
191
|
+
* 出现 `.` 子项,原条件恒假,实际只跳 node_modules;缺陷 18)。
|
|
192
|
+
*
|
|
193
|
+
* 权限容错:`listDir` 抛错(权限拒绝等)时跳过该子树而非中断整次遍历,对齐基准
|
|
194
|
+
* `walkDir` 的 try/catch(缺陷 18 顺带补齐)。
|
|
195
|
+
*/
|
|
196
|
+
async function collectFiles(fs: FileSystem, dir: FsTarget, prefix: string, out: WalkedFile[]): Promise<void> {
|
|
197
|
+
try {
|
|
198
|
+
const entries = await fs.listDir(dir);
|
|
199
|
+
for (const entry of entries) {
|
|
200
|
+
if ((entry.name.startsWith('.') && entry.type === 'directory') || entry.name === 'node_modules') continue;
|
|
201
|
+
const relativePath = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
|
|
202
|
+
if (entry.type === 'directory') {
|
|
203
|
+
await collectFiles(fs, entry.target, relativePath, out);
|
|
204
|
+
} else if (entry.type === 'file') {
|
|
205
|
+
out.push({ target: entry.target, relativePath });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
// 权限拒绝等读取错误跳过该子树(对齐基准 walkDir 的 try/catch 容错)。
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** 递归遍历目录,返回全部文件的进程绝对路径数组。 */
|
|
214
|
+
export async function walkDir(fs: FileSystem, dir: FsTarget): Promise<string[]> {
|
|
215
|
+
const out: WalkedFile[] = [];
|
|
216
|
+
await collectFiles(fs, dir, '', out);
|
|
217
|
+
return out.map(file => fs.processPath(file.target));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** 按 glob 模式搜索 `rootDir` 下匹配的文件,返回进程绝对路径数组。 */
|
|
221
|
+
export async function findFilesByGlob(fs: FileSystem, rootDir: string, pattern: string): Promise<string[]> {
|
|
222
|
+
const regex = globToRegex(pattern);
|
|
223
|
+
const root = await fs.resolve(rootDir);
|
|
224
|
+
const out: WalkedFile[] = [];
|
|
225
|
+
await collectFiles(fs, root, '', out);
|
|
226
|
+
return out.filter(file => regex.test(file.relativePath)).map(file => fs.processPath(file.target));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 按正则搜索 `rootDir` 下相对路径匹配的文件,返回进程绝对路径数组。 */
|
|
230
|
+
export async function findFilesByRegex(fs: FileSystem, rootDir: string, regex: string): Promise<string[]> {
|
|
231
|
+
let re: RegExp;
|
|
232
|
+
try {
|
|
233
|
+
re = new RegExp(regex);
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw new Error(`invalid regex '${regex}': ${error instanceof Error ? error.message : String(error)}`);
|
|
236
|
+
}
|
|
237
|
+
const root = await fs.resolve(rootDir);
|
|
238
|
+
const out: WalkedFile[] = [];
|
|
239
|
+
await collectFiles(fs, root, '', out);
|
|
240
|
+
return out.filter(file => re.test(file.relativePath)).map(file => fs.processPath(file.target));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── 精确存在性检查(非 glob 字面路径) ────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 规范化拼接 `workdir` 与相对路径 `relPath`(剥除 `.`/冗余 `/` 段,处理 `..`)。
|
|
247
|
+
*
|
|
248
|
+
* `relPath` 以 `/` 开头视为绝对路径(忽略 `workdir`)。对齐 `node:path.resolve`
|
|
249
|
+
* 在「workdir + 相对字面产物路径」场景下的可见行为,但不引入 node 依赖
|
|
250
|
+
*(file-utils 保持纯函数 + fs 接缝)。内部辅助,不导出。
|
|
251
|
+
*/
|
|
252
|
+
function joinWorkdirPath(workdir: string, relPath: string): string {
|
|
253
|
+
const raw = relPath.startsWith('/') ? relPath : `${workdir}/${relPath}`;
|
|
254
|
+
return normalizePathSegments(raw);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function normalizePathSegments(path: string): string {
|
|
258
|
+
const isAbsolute = path.startsWith('/');
|
|
259
|
+
const out: string[] = [];
|
|
260
|
+
for (const segment of path.split('/')) {
|
|
261
|
+
if (segment === '' || segment === '.') continue;
|
|
262
|
+
if (segment === '..') {
|
|
263
|
+
out.pop();
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
out.push(segment);
|
|
267
|
+
}
|
|
268
|
+
const joined = out.join('/');
|
|
269
|
+
if (isAbsolute) return joined === '' ? '/' : `/${joined}`;
|
|
270
|
+
return joined;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 精确判定 `workdir` 下相对路径 `relPath` 指向的文件是否存在(非 glob 字面路径)。
|
|
275
|
+
*
|
|
276
|
+
* 与 {@link findFilesByRegex} 的非锚定全树正则匹配不同:本函数对 `relPath` 做字面
|
|
277
|
+
* 存在性检查(`resolve` + `readText` 不抛错即存在),避免路径中的 `.` 被当作正则
|
|
278
|
+
* 元字符导致漏报(树内相似文件误命中→该报缺失却放行)或误报(`./` 前缀使非锚定
|
|
279
|
+
* regex 误判→门禁恒失败)。对齐基准 `fs.existsSync(path.resolve(cwd, relPath))` 语义。
|
|
280
|
+
*
|
|
281
|
+
* 实现载体说明:sandore 仓库内无真实 fs 适配器(`ctx.fs` 由 DSH 宿主注入,
|
|
282
|
+
* {@link FileSystem} 结构类型仅暴露 resolve/listDir/readText/writeText,无 exists
|
|
283
|
+
* 原语),故用 resolve + readText 组合判定存在性——`resolve` 成功且 `readText`
|
|
284
|
+
* 不抛错即存在;任一抛错(含 FS_NOT_FOUND)视为不存在。供 verifyArtifacts 非 glob
|
|
285
|
+
* 分支与缺陷 #16 collectStepArtifacts 复用。
|
|
286
|
+
*/
|
|
287
|
+
export async function fileExists(fs: FileSystem, workdir: string, relPath: string): Promise<boolean> {
|
|
288
|
+
const fullPath = joinWorkdirPath(workdir, relPath);
|
|
289
|
+
try {
|
|
290
|
+
const target = await fs.resolve(fullPath);
|
|
291
|
+
await fs.readText(target);
|
|
292
|
+
return true;
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── 产物收集(outputTo → 实际文件列表) ───────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 收集 Step 的产物文件列表(对齐基准 defact `collectStepArtifacts` L66-93)。
|
|
302
|
+
*
|
|
303
|
+
* 对 `step.outputTo` 每项解析实际生成的产物文件:
|
|
304
|
+
* - `path` 字段:glob 模式 → {@link findFilesByGlob};非 glob 字面路径 →
|
|
305
|
+
* {@link fileExists} 精确存在性检查(**复用缺陷 6 已落地的 fileExists**),
|
|
306
|
+
* 存在则取规范化进程绝对路径;
|
|
307
|
+
* - `regex` 字段:{@link findFilesByRegex}(与 verifyArtifacts 一致,不做 glob 检测)。
|
|
308
|
+
*
|
|
309
|
+
* **缺陷 34 联动**:每项 path/regex 先经 {@link resolveOutputToVars} 用 `vars` 替换
|
|
310
|
+
* `${xxx}` 透传变量占位符,保证收集、门禁校验({@link verifyArtifacts})、
|
|
311
|
+
* `<artifacts>` 提示词块三方使用同一份「已解析 outputTo」语义。vars 缺省为 `{}`——
|
|
312
|
+
* 含 `${xxx}` 的项标 `deferred`,字面占位路径自然不命中文件 → 空值(延迟语义)。
|
|
313
|
+
*
|
|
314
|
+
* 同一 outputTo 项的 `path` 与 `regex` 结果合并到同一 `value`(对齐基准)。
|
|
315
|
+
* 返回扁平的 {@link OutputArtifact} 列表(`id` 为 outputTo 项 id,`value` 为命中
|
|
316
|
+
* 的进程绝对路径数组)。无 outputTo 时返回空数组。坏 glob/regex 容错跳过(对齐
|
|
317
|
+
* 基准 try/catch 静默忽略)。
|
|
318
|
+
*
|
|
319
|
+
* **与 {@link verifyArtifacts} 的关系**:verifyArtifacts 只判定缺失(门禁用,返回
|
|
320
|
+
* 缺失 pattern 列表),collectStepArtifacts 收集实际文件列表(收尾 AI 可见 +
|
|
321
|
+
* Stage 契约 `artifacts[].files` + 前置产物块 `files`);二者共享「解析 outputTo
|
|
322
|
+
* → 匹配文件」核心逻辑(缺陷 6/16/34 联动:非 glob path 复用 fileExists 精确存在性
|
|
323
|
+
* 检查,避免路径中的 `.` 被当作正则元字符导致漏报/误报;变量替换前置统一)。
|
|
324
|
+
*
|
|
325
|
+
* @param fs - 文件系统接缝(与 fileExists/findFilesByGlob/findFilesByRegex 同源)。
|
|
326
|
+
* @param step - 含 `outputTo` 的 Step 视图(兼容 `WorkflowStep` 与 `ExecutedStepDefault`)。
|
|
327
|
+
* @param workdir - 产物根目录(与 verifyArtifacts 的 workdir 一致)。
|
|
328
|
+
* @param vars - 透传变量字符串映射(缺省 `{}`;含未解析占位符的项标 deferred)。
|
|
329
|
+
*/
|
|
330
|
+
export async function collectStepArtifacts(
|
|
331
|
+
fs: FileSystem,
|
|
332
|
+
step: { outputTo?: readonly { readonly id: string; readonly path?: string | undefined; readonly regex?: string | undefined }[] | undefined },
|
|
333
|
+
workdir: string,
|
|
334
|
+
vars: Readonly<Record<string, string>> = {},
|
|
335
|
+
): Promise<OutputArtifact[]> {
|
|
336
|
+
const outputTo = step.outputTo;
|
|
337
|
+
if (outputTo === undefined || outputTo.length === 0) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
const artifacts: OutputArtifact[] = [];
|
|
341
|
+
for (const item of outputTo) {
|
|
342
|
+
const resolved = resolveOutputToVars(item, vars);
|
|
343
|
+
const matched: string[] = [];
|
|
344
|
+
if (resolved.path !== undefined) {
|
|
345
|
+
if (isGlobPattern(resolved.path)) {
|
|
346
|
+
try {
|
|
347
|
+
matched.push(...await findFilesByGlob(fs, workdir, resolved.path));
|
|
348
|
+
} catch {
|
|
349
|
+
// 坏 glob 容错跳过(对齐基准 try/catch 静默忽略)。
|
|
350
|
+
}
|
|
351
|
+
} else if (await fileExists(fs, workdir, resolved.path)) {
|
|
352
|
+
// 非 glob 字面路径:复用缺陷 6 的 fileExists 判定存在性(resolve + readText)。
|
|
353
|
+
// fileExists 已成功意味着 resolve + readText 均通过,此处再 resolve 取
|
|
354
|
+
// processPath 得到与 glob/regex 分支一致的进程绝对路径。
|
|
355
|
+
const target = await fs.resolve(joinWorkdirPath(workdir, resolved.path));
|
|
356
|
+
matched.push(fs.processPath(target));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (resolved.regex !== undefined) {
|
|
360
|
+
try {
|
|
361
|
+
matched.push(...await findFilesByRegex(fs, workdir, resolved.regex));
|
|
362
|
+
} catch {
|
|
363
|
+
// 坏 regex 容错跳过(对齐基准 try/catch 静默忽略)。
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
artifacts.push({ id: item.id, value: matched });
|
|
367
|
+
}
|
|
368
|
+
return artifacts;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// ── {uuid}_output.json 读写 ─────────────────────────────────────────────────
|
|
372
|
+
|
|
373
|
+
/** 单个 outputTo 的产物列表(写入 `{uuid}_output.json`)。 */
|
|
374
|
+
export interface OutputArtifact {
|
|
375
|
+
readonly id: string;
|
|
376
|
+
readonly value: string[];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** `{uuid}_output.json` 的完整结构。 */
|
|
380
|
+
export interface StepOutputFile {
|
|
381
|
+
readonly sessionId: string;
|
|
382
|
+
readonly output: {
|
|
383
|
+
readonly returnPrompt: string;
|
|
384
|
+
readonly artifacts: OutputArtifact[];
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** 由 stepMessageId 推导产物文件名。 */
|
|
389
|
+
export function outputJsonFileName(stepMessageId: string): string {
|
|
390
|
+
return `${stepMessageId}_output.json`;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** 写入 `{stepMessageId}_output.json`,返回文件进程绝对路径。 */
|
|
394
|
+
export async function writeOutputJson(
|
|
395
|
+
fs: FileSystem,
|
|
396
|
+
dir: string,
|
|
397
|
+
stepMessageId: string,
|
|
398
|
+
data: StepOutputFile,
|
|
399
|
+
): Promise<string> {
|
|
400
|
+
const target = await fs.resolve(`${dir}/${outputJsonFileName(stepMessageId)}`);
|
|
401
|
+
await fs.writeText(target, JSON.stringify(data, null, 2));
|
|
402
|
+
return fs.processPath(target);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** 校验并解析产物文件 JSON(`JSON.parse` 返回 unknown,逐字段校验形状)。 */
|
|
406
|
+
function parseOutputJson(text: string): StepOutputFile {
|
|
407
|
+
let raw: unknown;
|
|
408
|
+
try {
|
|
409
|
+
raw = JSON.parse(text);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
throw new Error(`malformed output json: ${error instanceof Error ? error.message : String(error)}`);
|
|
412
|
+
}
|
|
413
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
414
|
+
throw new Error('malformed output json: root is not an object');
|
|
415
|
+
}
|
|
416
|
+
const sessionId = (raw as { sessionId?: unknown }).sessionId;
|
|
417
|
+
const output = (raw as { output?: unknown }).output;
|
|
418
|
+
if (typeof sessionId !== 'string') {
|
|
419
|
+
throw new Error('malformed output json: sessionId is not a string');
|
|
420
|
+
}
|
|
421
|
+
if (typeof output !== 'object' || output === null) {
|
|
422
|
+
throw new Error('malformed output json: output is not an object');
|
|
423
|
+
}
|
|
424
|
+
const returnPrompt = (output as { returnPrompt?: unknown }).returnPrompt;
|
|
425
|
+
const artifacts = (output as { artifacts?: unknown }).artifacts;
|
|
426
|
+
if (typeof returnPrompt !== 'string') {
|
|
427
|
+
throw new Error('malformed output json: output.returnPrompt is not a string');
|
|
428
|
+
}
|
|
429
|
+
if (!Array.isArray(artifacts)) {
|
|
430
|
+
throw new Error('malformed output json: output.artifacts is not an array');
|
|
431
|
+
}
|
|
432
|
+
const parsedArtifacts: OutputArtifact[] = artifacts.map((item, index) => {
|
|
433
|
+
if (typeof item !== 'object' || item === null) {
|
|
434
|
+
throw new Error(`malformed output json: artifacts[${index}] is not an object`);
|
|
435
|
+
}
|
|
436
|
+
const id = (item as { id?: unknown }).id;
|
|
437
|
+
const value = (item as { value?: unknown }).value;
|
|
438
|
+
if (typeof id !== 'string') {
|
|
439
|
+
throw new Error(`malformed output json: artifacts[${index}].id is not a string`);
|
|
440
|
+
}
|
|
441
|
+
if (!Array.isArray(value) || value.some(v => typeof v !== 'string')) {
|
|
442
|
+
throw new Error(`malformed output json: artifacts[${index}].value is not a string array`);
|
|
443
|
+
}
|
|
444
|
+
return { id, value };
|
|
445
|
+
});
|
|
446
|
+
return { sessionId, output: { returnPrompt, artifacts: parsedArtifacts } };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** 读回并校验 `{stepMessageId}_output.json`。 */
|
|
450
|
+
export async function readOutputJson(fs: FileSystem, dir: string, stepMessageId: string): Promise<StepOutputFile> {
|
|
451
|
+
const target = await fs.resolve(`${dir}/${outputJsonFileName(stepMessageId)}`);
|
|
452
|
+
const text = await fs.readText(target);
|
|
453
|
+
return parseOutputJson(text);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ── InputFrom 就近匹配 ──────────────────────────────────────────────────────
|
|
457
|
+
|
|
458
|
+
/** 后缀匹配:`actualId === targetId` 或 `actualId` 以 `targetId-` 开头(循环带后缀 id)。 */
|
|
459
|
+
export function idMatches(actualId: string, targetId: string): boolean {
|
|
460
|
+
return actualId === targetId || actualId.startsWith(`${targetId}-`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** 判断某条已执行 Step 是否匹配 stepId(对 `stepId ?? id` 做后缀匹配)。 */
|
|
464
|
+
function matchesStepId(step: ExecutedStepDefault, stepId: string): boolean {
|
|
465
|
+
return idMatches(step.stepId ?? step.id, stepId);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* 按 `path` 就近匹配已执行 Step。
|
|
470
|
+
*
|
|
471
|
+
* 简化格式:`path` 直接为 Step id(单段),自执行列表末尾往前最近匹配同名 Step
|
|
472
|
+
* (`idMatches` 支持循环后缀 id);不再支持 `stageId.stepId` / `stageId.stepId.opt-xxx`。
|
|
473
|
+
* @param executedSteps - 扁平已执行 Step(含循环迭代条目)。
|
|
474
|
+
* @param _currentIndex - 保留参数(兼容调用方)。
|
|
475
|
+
* @param path - 引用的 Step id。
|
|
476
|
+
*/
|
|
477
|
+
export function resolvePathSegments(
|
|
478
|
+
executedSteps: readonly ExecutedStepDefault[],
|
|
479
|
+
_currentIndex: number,
|
|
480
|
+
path: string,
|
|
481
|
+
): ExecutedStepDefault[] {
|
|
482
|
+
for (let i = executedSteps.length - 1; i >= 0; i--) {
|
|
483
|
+
const step = executedSteps[i]!;
|
|
484
|
+
if (matchesStepId(step, path)) return [step];
|
|
485
|
+
}
|
|
486
|
+
return [];
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 解析 `inputFromItems` 为就近命中的已执行 Step。
|
|
491
|
+
*
|
|
492
|
+
* 每项 `path` 走 {@link resolvePathSegments},`regex` 遍历全部 Step 输出文本做正则
|
|
493
|
+
* 匹配(倒序最近优先);`inputFromItems` 为空时回退前一条执行条目
|
|
494
|
+
* (`currentIndex - 1`)。
|
|
495
|
+
*/
|
|
496
|
+
export function resolveFromInputFrom(
|
|
497
|
+
executedSteps: readonly ExecutedStepDefault[],
|
|
498
|
+
currentIndex: number,
|
|
499
|
+
inputFromItems: readonly InputFromItem[],
|
|
500
|
+
): ExecutedStepDefault[] {
|
|
501
|
+
if (inputFromItems.length === 0) {
|
|
502
|
+
const previous = executedSteps[currentIndex - 1];
|
|
503
|
+
return previous === undefined ? [] : [previous];
|
|
504
|
+
}
|
|
505
|
+
const result: ExecutedStepDefault[] = [];
|
|
506
|
+
for (const item of inputFromItems) {
|
|
507
|
+
if (item.path !== undefined) {
|
|
508
|
+
result.push(...resolvePathSegments(executedSteps, currentIndex, item.path));
|
|
509
|
+
} else if (item.regex !== undefined) {
|
|
510
|
+
let regex: RegExp;
|
|
511
|
+
try {
|
|
512
|
+
regex = new RegExp(item.regex);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw new Error(`invalid inputFrom regex '${item.regex}': ${error instanceof Error ? error.message : String(error)}`);
|
|
515
|
+
}
|
|
516
|
+
const matched = executedSteps.filter(step => regex.test(textSummary(step.output)));
|
|
517
|
+
result.push(...matched.reverse());
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return result;
|
|
521
|
+
}
|