aitable-workflow-base 0.1.28-beta.1789632898670 → 0.1.28
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/dist/fixtures.d.ts +1 -1
- package/dist/index.d.ts +124 -3
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -1
- package/dist/{stage-state-Dt0VNg82.d.ts → stage-state-BzR0KHBg.d.ts} +28 -2
- package/package.json +1 -1
package/dist/fixtures.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentEvent, K as KnownAgentType, I as InitState, D as DesignState, a as SetupState, R as RunState, b as RunScheduleDTO, c as AnyStageState, d as StageIdDTO, e as StageNextDTO, f as DesignArtifactValidationDTO, P as ProjectDTO, g as ProjectStateDTO, h as StageStateDTO, i as ProjectStateResponseDTO, F as FormQuestion, j as FormAnswers, Q as QuestionForm, k as FormAnswerValue, l as FilledForm } from './stage-state-
|
|
2
|
-
export { m as AgentEventType, n as AgentIdDTO, o as AgentType, p as ApiError, C as CreateProjectRequestDTO, q as CreateWorkflowRequestDTO, r as CreateWorkflowResponseDTO, s as DeleteProjectResponseDTO, t as DesignArtifactManifestDTO, u as DesignConfirmedDTO, v as DesignEventDTO, w as FilesPatchDTO, x as FilesReplaceDTO, y as FilesResponseDTO, z as FormOption, B as InitConfigDTO, E as InitResponseDTO, G as PollReason, H as ProjectExportDTO, J as ProjectFileDTO, L as ProjectWorkflowDTO, M as ProjectsResponseDTO, N as QuestionType, O as RecoveryActionDTO, T as ResetWorkflowResponseDTO, U as RoleConfigDTO, V as
|
|
1
|
+
import { A as AgentEvent, K as KnownAgentType, I as InitState, D as DesignState, a as SetupState, R as RunState, b as RunScheduleDTO, c as AnyStageState, d as StageIdDTO, e as StageNextDTO, f as DesignArtifactValidationDTO, P as ProjectDTO, g as ProjectStateDTO, h as StageStateDTO, i as ProjectStateResponseDTO, F as FormQuestion, j as FormAnswers, Q as QuestionForm, k as FormAnswerValue, l as FilledForm } from './stage-state-BzR0KHBg.js';
|
|
2
|
+
export { m as AgentEventType, n as AgentIdDTO, o as AgentType, p as ApiError, C as CreateProjectRequestDTO, q as CreateWorkflowRequestDTO, r as CreateWorkflowResponseDTO, s as DeleteProjectResponseDTO, t as DesignArtifactManifestDTO, u as DesignConfirmedDTO, v as DesignEventDTO, w as FilesPatchDTO, x as FilesReplaceDTO, y as FilesResponseDTO, z as FormOption, B as InitConfigDTO, E as InitResponseDTO, G as PollReason, H as ProjectExportDTO, J as ProjectFileDTO, L as ProjectWorkflowDTO, M as ProjectsResponseDTO, N as QuestionType, O as RecoveryActionDTO, T as ResetWorkflowResponseDTO, U as RoleConfigDTO, V as RunAgentEventDTO, W as RunControlResultDTO, X as RunEventDTO, Y as RunExecutionEventDTO, Z as RunModeDTO, _ as RunRecordDTO, $ as RunRecordSnapshotDTO, a0 as RunRecordStatusDTO, a1 as RunRecordsPageDTO, a2 as RunSnapshotDTO, a3 as ScaffoldResult, a4 as SetupEventDTO, a5 as SetupSchemaSummaryDTO, a6 as ShowIfCondition, a7 as StageChangedEventDTO, a8 as StageDiagnosticsDTO, a9 as StageErrorDTO, aa as StageStateResponseDTO, S as StageTaskIdentityDTO, ab as WorkflowFilesResponseDTO, ac as WorkflowsResponseDTO } from './stage-state-BzR0KHBg.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* relay — Relay ↔ Gateway 传输协议
|
|
@@ -146,6 +146,17 @@ interface RelayRunDwsMessage {
|
|
|
146
146
|
requestId: string;
|
|
147
147
|
args: string[];
|
|
148
148
|
timeoutMs?: number;
|
|
149
|
+
/**
|
|
150
|
+
* relay 边界内联文件:`服务端绝对路径 → UTF-8 文件内容`。
|
|
151
|
+
*
|
|
152
|
+
* dws 写命令用 `--records-file <path>` 传 records 载荷(规避 Windows cmd.exe 引号破坏),
|
|
153
|
+
* 但该临时文件建在**服务端进程**的 `/tmp`,daemon 在本机 spawn dws 时读不到(执行上下文被
|
|
154
|
+
* 网络边界劈开)。发送侧(GatewayDwsRunner)读出内容随帧下发,接收侧(RelayDaemon)把内容
|
|
155
|
+
* 落到本机临时文件并把 args 中的服务端路径替换为本机路径再执行。
|
|
156
|
+
*
|
|
157
|
+
* 可选:不带时行为与旧帧逐字节一致;旧 daemon 忽略未知字段。
|
|
158
|
+
*/
|
|
159
|
+
inlineFiles?: Record<string, string>;
|
|
149
160
|
}
|
|
150
161
|
/** 服务端下发预签名上传链接(gateway→daemon,daemon 无密钥) */
|
|
151
162
|
interface RelayUploadGrantMessage {
|
|
@@ -204,6 +215,116 @@ declare const RELAY_OVERLOAD_CODE = 1013;
|
|
|
204
215
|
*/
|
|
205
216
|
declare function isRelayMessage(value: unknown): value is RelayMessage;
|
|
206
217
|
|
|
218
|
+
/**
|
|
219
|
+
* agent-trace — Agent 事件到 UI 展示条目的纯映射(Node / 浏览器双端安全)
|
|
220
|
+
*
|
|
221
|
+
* 这段逻辑原先只住在 `packages/web/src/live/agent-trace-buffer.ts` +
|
|
222
|
+
* `tool-summary.ts`。现在本地 relay daemon 也要在 Node 侧把 `AgentEvent` 投影成
|
|
223
|
+
* 可读的执行流(Workbench 页面只渲染投影结果,不再在浏览器里重写一套映射),
|
|
224
|
+
* 于是把「事件 → 展示条目」的**唯一一份**规则下沉到这里:web 浮层与 CLI workbench
|
|
225
|
+
* 共享同一映射,避免两处漂移(见 docs/plans/2026-09-21-local-relay-agent-workbench.md)。
|
|
226
|
+
*
|
|
227
|
+
* 准入本包的硬判据在这里同样成立:不依赖 Node,也不依赖 DOM。凡是引用 core 的
|
|
228
|
+
* `RunEventDTO`/`SetupEventDTO`/`phaseMessage` 的 setup/run 专有映射仍留在 web 侧。
|
|
229
|
+
*/
|
|
230
|
+
|
|
231
|
+
type ToolSummaryInput = {
|
|
232
|
+
toolName?: string;
|
|
233
|
+
toolInput?: string;
|
|
234
|
+
projectId?: string;
|
|
235
|
+
};
|
|
236
|
+
type ToolSummary = {
|
|
237
|
+
label: string;
|
|
238
|
+
detail?: string;
|
|
239
|
+
};
|
|
240
|
+
declare function summarizeToolUse(input: ToolSummaryInput): ToolSummary;
|
|
241
|
+
declare function summarizeToolLabel(toolName?: string, toolInput?: string): string;
|
|
242
|
+
declare function summarizeToolDetail(toolName?: string, toolInput?: string, projectId?: string): string | undefined;
|
|
243
|
+
declare function extractDisplayFile(input: string): {
|
|
244
|
+
name: string;
|
|
245
|
+
purpose: string;
|
|
246
|
+
} | null;
|
|
247
|
+
declare function truncateText(text: string, limit: number): string;
|
|
248
|
+
/** 缓冲中的单条轨迹条目。 */
|
|
249
|
+
interface AgentTraceItem {
|
|
250
|
+
/** 该条目所属的设计轮次(如已知)。 */
|
|
251
|
+
round?: number;
|
|
252
|
+
/** 条目语义:正文 / 思考 / 工具调用 / 工具返回 / 授权请求 / 向人提问 / 状态(结果、错误等)。 */
|
|
253
|
+
kind: "text" | "thinking" | "tool" | "tool_result" | "permission" | "ask" | "status";
|
|
254
|
+
/** 展示文本。 */
|
|
255
|
+
text: string;
|
|
256
|
+
/** 工具名(kind === "tool" 时有意义)。 */
|
|
257
|
+
toolName?: string;
|
|
258
|
+
/**
|
|
259
|
+
* 全文(仅当 `text` 摘要被 `TRACE_TEXT_MAX` 截断时填充,用于浮层点击展开查看完整内容)。
|
|
260
|
+
* 受 `TRACE_FULLTEXT_MAX` 二级上限约束,避免超大工具返回撑爆缓冲。未截断时不带此字段。
|
|
261
|
+
*/
|
|
262
|
+
fullText?: string;
|
|
263
|
+
/** 结构性分组边界标记,无可见内容;置 true 时强制下一条起新组。 */
|
|
264
|
+
boundary?: boolean;
|
|
265
|
+
}
|
|
266
|
+
/** 环形缓冲上限。 */
|
|
267
|
+
declare const TRACE_CAP = 300;
|
|
268
|
+
/** tool_result / permission 摘要文本的最大展示长度(超出截断加省略号)。 */
|
|
269
|
+
declare const TRACE_TEXT_MAX = 200;
|
|
270
|
+
/** fullText 的二级长度上限(超大工具返回安全截断,避免撑爆缓冲)。 */
|
|
271
|
+
declare const TRACE_FULLTEXT_MAX = 4000;
|
|
272
|
+
/** 截断长文本用于浮层摘要展示,超过 max 时保留前 max 个字符并追加省略号。 */
|
|
273
|
+
declare function truncateTraceText(text: string, max?: number): string;
|
|
274
|
+
/** 截断全文用于浮层展开态,超过 TRACE_FULLTEXT_MAX 时安全截断并追加省略号。 */
|
|
275
|
+
declare function truncateFullText(text: string, max?: number): string;
|
|
276
|
+
/**
|
|
277
|
+
* 向缓冲追加若干条目,超出上限时从头部(最旧)丢弃。
|
|
278
|
+
* 返回新数组(不可变更新),空输入原样返回旧引用。
|
|
279
|
+
*/
|
|
280
|
+
declare function appendCapped(prev: AgentTraceItem[], items: AgentTraceItem[], cap?: number): AgentTraceItem[];
|
|
281
|
+
/**
|
|
282
|
+
* 「向人提问」类工具名(ask-user tool)。
|
|
283
|
+
*
|
|
284
|
+
* 这份名单是**唯一事实源**:core 的 ask-human 中止探测器(`coding-agents/ask-human-detector.ts`)
|
|
285
|
+
* 与这里的展示投影都从这里取,避免「哪些工具名算问人」在两处各写一份而漂移。
|
|
286
|
+
* 归一化后比对(小写去非字母数字),兼容 provider 前缀(取点号末段)。
|
|
287
|
+
*/
|
|
288
|
+
declare const ASK_USER_TOOL_NAMES: ReadonlySet<string>;
|
|
289
|
+
/** toolName 是否属于「向人提问」类工具。 */
|
|
290
|
+
declare function isAskUserToolName(toolName?: string): boolean;
|
|
291
|
+
/**
|
|
292
|
+
* 把一个原始 AgentEvent 映射为轨迹条目。
|
|
293
|
+
* - text → kind:"text"
|
|
294
|
+
* - thinking → kind:"thinking"
|
|
295
|
+
* - tool_use → kind:"ask"(命中 ask-user 工具名时)或 kind:"tool"(文本取 summarizeToolUse 的 `label — detail`)
|
|
296
|
+
* - tool_result → kind:"tool_result"(结果摘要,长文截断;带 toolName)
|
|
297
|
+
* - permission_request → kind:"permission"(授权请求摘要,如 toolName + 待授权)
|
|
298
|
+
* - result / error → kind:"status"
|
|
299
|
+
* - 其余无内容事件 → 返回 null(不入库)
|
|
300
|
+
*/
|
|
301
|
+
declare function traceItemFromEvent(event: AgentEvent, round?: number): AgentTraceItem | null;
|
|
302
|
+
/** 分组后单步的展示条目。 */
|
|
303
|
+
interface TraceGroupItem {
|
|
304
|
+
kind: "text" | "thinking" | "tool" | "tool_result" | "permission" | "ask" | "status";
|
|
305
|
+
text: string;
|
|
306
|
+
toolName?: string;
|
|
307
|
+
/** 全文(透传自 AgentTraceItem,tool_result/permission 被截断时供浮层展开)。 */
|
|
308
|
+
fullText?: string;
|
|
309
|
+
}
|
|
310
|
+
/** 分组:一轮内的一段连续执行流。 */
|
|
311
|
+
interface TraceGroup {
|
|
312
|
+
round?: number;
|
|
313
|
+
items: TraceGroupItem[];
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 把扁平缓冲整理为消息条式分组(对标 open-tag groupTraj):
|
|
317
|
+
* - 连续的同类正文(text↔text、thinking↔thinking)折叠成一段,读起来是连续的文字而非滚动日志;
|
|
318
|
+
* - tool / tool_result / permission / status 独立成步;
|
|
319
|
+
* - boundary 标记或 round 变化都会起一个新组。
|
|
320
|
+
*/
|
|
321
|
+
declare function groupTrace(items: AgentTraceItem[]): TraceGroup[];
|
|
322
|
+
/**
|
|
323
|
+
* 把一批 AgentEvent 直接投影为分组后的展示结构(供 Node 侧 workbench API 返回,
|
|
324
|
+
* 浏览器只渲染结果)。过滤掉映射为 null 的空事件。
|
|
325
|
+
*/
|
|
326
|
+
declare function projectAgentEvents(events: readonly AgentEvent[]): TraceGroup[];
|
|
327
|
+
|
|
207
328
|
/**
|
|
208
329
|
* stage-machine/transitions — 「谁能从哪到哪」的唯一定义
|
|
209
330
|
*
|
|
@@ -581,4 +702,4 @@ declare function applyRunExecutionEvent(snapshot: RunSnapshot, event: RunExecuti
|
|
|
581
702
|
*/
|
|
582
703
|
declare function patchRunSnapshotRecord(snapshot: RunSnapshot, itemId: string, patch: Partial<Pick<RunSnapshot["records"][number], "state" | "status" | "currentStepId">>, logText?: string, options?: ApplyRunExecutionEventOptions): RunSnapshot;
|
|
583
704
|
|
|
584
|
-
export { ACTIVE_PHASES, AgentEvent, type AnswerIssueCode, type AnswerValidationIssue, AnyStageState, type ApplyRunExecutionEventOptions, DERIVED_PHASES, DESIGN_GENERATING_PHASES, DesignArtifactValidationDTO, type DesignGeneratingState, DesignState, type DriverId, FilledForm, FormAnswerValue, FormAnswers, FormQuestion, IllegalStageTransitionError, type IllegalTransitionDetail, InitState, KnownAgentType, type NextInput, type NextRuntimeFacts, ProjectDTO, ProjectStateDTO, ProjectStateResponseDTO, QuestionForm, RELAY_OVERLOAD_CODE, RELAY_REJECTED_CODE, type RelayArtifactFile, type RelayArtifactManifest, type RelayErrorMessage, type RelayEventMessage, type RelayInboundMessage, type RelayMessage, type RelayMessageType, type RelayOutboundMessage, type RelayPingMessage, type RelayPongMessage, type RelayReadyAckMessage, type RelayReadyMessage, type RelayResultMessage, type RelayRunAgentMessage, type RelayRunDwsMessage, type RelayRunEventEntry, type RelayRunEventsMessage, type RelayUploadGrantMessage, type RelayUploadRequestMessage, type RelayWorkflowAvailableMessage, type RunExecutionEvent, type RunRecordStatus, RunScheduleDTO, type RunSnapshot, type RunSnapshotLogLevel, type RunSnapshotStepLevelResolver, RunState, SetupState, StageIdDTO, StageNextDTO, StageStateDTO, TRANSITIONS, type TransitionRule, applyRunExecutionEvent, deriveRelayRunState, emptyProjectState, formatAnswerDisplay, isActivePhase, isDesignFailed, isDesignGenerating, isQuestionVisible, isRelayMessage, isTerminalPhase, mergeStageIntoState, nextActiveStage, nextFor, normalizeProjectState, patchRunSnapshotRecord, pruneHiddenAnswers, recoverDesignFromArtifacts, serializeForm, serializeForms, stageStateOf, summarizeForms, transition, validateFormAnswers, visibleQuestions, withStageNext };
|
|
705
|
+
export { ACTIVE_PHASES, ASK_USER_TOOL_NAMES, AgentEvent, type AgentTraceItem, type AnswerIssueCode, type AnswerValidationIssue, AnyStageState, type ApplyRunExecutionEventOptions, DERIVED_PHASES, DESIGN_GENERATING_PHASES, DesignArtifactValidationDTO, type DesignGeneratingState, DesignState, type DriverId, FilledForm, FormAnswerValue, FormAnswers, FormQuestion, IllegalStageTransitionError, type IllegalTransitionDetail, InitState, KnownAgentType, type NextInput, type NextRuntimeFacts, ProjectDTO, ProjectStateDTO, ProjectStateResponseDTO, QuestionForm, RELAY_OVERLOAD_CODE, RELAY_REJECTED_CODE, type RelayArtifactFile, type RelayArtifactManifest, type RelayErrorMessage, type RelayEventMessage, type RelayInboundMessage, type RelayMessage, type RelayMessageType, type RelayOutboundMessage, type RelayPingMessage, type RelayPongMessage, type RelayReadyAckMessage, type RelayReadyMessage, type RelayResultMessage, type RelayRunAgentMessage, type RelayRunDwsMessage, type RelayRunEventEntry, type RelayRunEventsMessage, type RelayUploadGrantMessage, type RelayUploadRequestMessage, type RelayWorkflowAvailableMessage, type RunExecutionEvent, type RunRecordStatus, RunScheduleDTO, type RunSnapshot, type RunSnapshotLogLevel, type RunSnapshotStepLevelResolver, RunState, SetupState, StageIdDTO, StageNextDTO, StageStateDTO, TRACE_CAP, TRACE_FULLTEXT_MAX, TRACE_TEXT_MAX, TRANSITIONS, type ToolSummary, type ToolSummaryInput, type TraceGroup, type TraceGroupItem, type TransitionRule, appendCapped, applyRunExecutionEvent, deriveRelayRunState, emptyProjectState, extractDisplayFile, formatAnswerDisplay, groupTrace, isActivePhase, isAskUserToolName, isDesignFailed, isDesignGenerating, isQuestionVisible, isRelayMessage, isTerminalPhase, mergeStageIntoState, nextActiveStage, nextFor, normalizeProjectState, patchRunSnapshotRecord, projectAgentEvents, pruneHiddenAnswers, recoverDesignFromArtifacts, serializeForm, serializeForms, stageStateOf, summarizeForms, summarizeToolDetail, summarizeToolLabel, summarizeToolUse, traceItemFromEvent, transition, truncateFullText, truncateText, truncateTraceText, validateFormAnswers, visibleQuestions, withStageNext };
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,264 @@ function isRelayMessage(value) {
|
|
|
24
24
|
return typeof type === "string" && RELAY_MESSAGE_TYPES.has(type);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// src/agent-trace.ts
|
|
28
|
+
var FILE_PURPOSES = {
|
|
29
|
+
"workflow.yml": "\u6D41\u7A0B\u7F16\u6392",
|
|
30
|
+
"aitable-workflow.config.yml": "\u8FD0\u884C\u914D\u7F6E",
|
|
31
|
+
"coding-agents.config.json": "\u6267\u884C\u5F15\u64CE",
|
|
32
|
+
".gitkeep": "\u76EE\u5F55\u5360\u4F4D"
|
|
33
|
+
};
|
|
34
|
+
function summarizeToolUse(input) {
|
|
35
|
+
return {
|
|
36
|
+
label: summarizeToolLabel(input.toolName, input.toolInput),
|
|
37
|
+
detail: summarizeToolDetail(input.toolName, input.toolInput, input.projectId)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function summarizeToolLabel(toolName, toolInput) {
|
|
41
|
+
if (!toolName) return "\u5904\u7406\u751F\u6210\u8FDB\u5EA6";
|
|
42
|
+
const normalized = toolName.toLowerCase();
|
|
43
|
+
if (normalized === "bash") return summarizeBashLabel(parseToolInput(toolInput));
|
|
44
|
+
if (normalized.includes("write") || normalized.includes("edit")) {
|
|
45
|
+
const file = extractDisplayFile(parseToolInput(toolInput));
|
|
46
|
+
return file ? file.name : "\u66F4\u65B0\u751F\u6210\u6587\u4EF6";
|
|
47
|
+
}
|
|
48
|
+
if (normalized.includes("read")) return "\u8BFB\u53D6\u4E0A\u4E0B\u6587";
|
|
49
|
+
if (normalized.includes("mkdir")) return "\u521B\u5EFA\u76EE\u5F55";
|
|
50
|
+
if (normalized.includes("grep") || normalized.includes("search")) return "\u68C0\u7D22\u9879\u76EE\u5185\u5BB9";
|
|
51
|
+
return "\u6267\u884C\u751F\u6210\u52A8\u4F5C";
|
|
52
|
+
}
|
|
53
|
+
function summarizeToolDetail(toolName, toolInput, projectId) {
|
|
54
|
+
const input = parseToolInput(toolInput);
|
|
55
|
+
if (toolName?.toLowerCase() === "bash" && input) {
|
|
56
|
+
return summarizeBashDetail(input, projectId);
|
|
57
|
+
}
|
|
58
|
+
const file = input ? extractDisplayFile(input) : null;
|
|
59
|
+
if (file) return file.purpose;
|
|
60
|
+
return toolInput ? truncateText(toolInput.replace(/\s+/g, " "), 64) : void 0;
|
|
61
|
+
}
|
|
62
|
+
function extractDisplayFile(input) {
|
|
63
|
+
const recipesIndex = input.indexOf("recipes/");
|
|
64
|
+
if (recipesIndex >= 0) {
|
|
65
|
+
const path2 = input.slice(recipesIndex).match(/^[^\s'"]+/)?.[0];
|
|
66
|
+
if (path2) {
|
|
67
|
+
const name2 = path2.split("/").pop() ?? path2;
|
|
68
|
+
return { name: name2, purpose: "\u6B65\u9AA4\u811A\u672C" };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const absoluteFile = input.match(/(?:^|[/\\])([^/\\\s'"]+\.(?:yml|yaml|json|ts|tsx|md))$/);
|
|
72
|
+
if (absoluteFile?.[1]) {
|
|
73
|
+
const name2 = absoluteFile[1];
|
|
74
|
+
return { name: name2, purpose: FILE_PURPOSES[name2] ?? "\u9879\u76EE\u6587\u4EF6" };
|
|
75
|
+
}
|
|
76
|
+
const match = input.match(/(?:^|\s)(?:[~./\w-]+\/)?((?:recipes\/)?[^\s'"]+\.(?:yml|yaml|json|ts|tsx|md))/);
|
|
77
|
+
if (!match?.[1]) return null;
|
|
78
|
+
const path = match[1].replace(/^.*?(recipes\/)/u, "$1");
|
|
79
|
+
const name = path.split("/").pop() ?? path;
|
|
80
|
+
const purpose = path.startsWith("recipes/") ? "\u6B65\u9AA4\u811A\u672C" : FILE_PURPOSES[name] ?? "\u9879\u76EE\u6587\u4EF6";
|
|
81
|
+
return { name, purpose };
|
|
82
|
+
}
|
|
83
|
+
function truncateText(text, limit) {
|
|
84
|
+
return text.length > limit ? `${text.slice(0, limit - 1)}\u2026` : text;
|
|
85
|
+
}
|
|
86
|
+
function summarizeBashLabel(command) {
|
|
87
|
+
const normalized = command.trim().toLowerCase();
|
|
88
|
+
if (/^mkdir\b/.test(normalized)) return "\u521B\u5EFA\u76EE\u5F55";
|
|
89
|
+
if (/^(install|touch)\b/.test(normalized)) return "\u51C6\u5907\u751F\u6210\u6587\u4EF6";
|
|
90
|
+
if (/^(ls|find|test|cat|pwd)\b/.test(normalized)) return "\u68C0\u67E5\u751F\u6210\u7ED3\u679C";
|
|
91
|
+
if (/\b(pnpm|npm|tsx|tsc|node|vitest)\b/.test(normalized)) return "\u6821\u9A8C\u751F\u6210\u6587\u4EF6";
|
|
92
|
+
return "\u6267\u884C\u751F\u6210\u547D\u4EE4";
|
|
93
|
+
}
|
|
94
|
+
function summarizeBashDetail(command, projectId) {
|
|
95
|
+
const normalized = command.replace(/\s+/g, " ").trim();
|
|
96
|
+
const projectPath = projectId ? formatProjectRelativePath(normalized, projectId) : null;
|
|
97
|
+
if (projectPath) return projectPath;
|
|
98
|
+
const displayFile = extractDisplayFile(normalized);
|
|
99
|
+
if (displayFile) return `${displayFile.purpose} \xB7 ${displayFile.name}`;
|
|
100
|
+
return `\u547D\u4EE4 \xB7 ${truncateText(stripLocalPrefix(normalized), 72)}`;
|
|
101
|
+
}
|
|
102
|
+
function parseToolInput(toolInput) {
|
|
103
|
+
if (!toolInput) return "";
|
|
104
|
+
const trimmed = toolInput.trim();
|
|
105
|
+
try {
|
|
106
|
+
const parsed = JSON.parse(trimmed);
|
|
107
|
+
for (const key of ["file_path", "path", "command", "cmd"]) {
|
|
108
|
+
const value = parsed[key];
|
|
109
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
}
|
|
113
|
+
return trimmed;
|
|
114
|
+
}
|
|
115
|
+
function formatProjectRelativePath(text, projectId) {
|
|
116
|
+
const escapedProjectId = escapeRegExp(projectId);
|
|
117
|
+
const match = text.match(
|
|
118
|
+
new RegExp(`(?:^|[\\s'"])(?:[^\\s'"]*/)?${escapedProjectId}(?:/([^\\s'"]+))?`)
|
|
119
|
+
);
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
const relative = match[1]?.replace(/^\/+/, "") ?? "";
|
|
122
|
+
return relative ? `${projectId}/${relative}` : projectId;
|
|
123
|
+
}
|
|
124
|
+
function stripLocalPrefix(text) {
|
|
125
|
+
return text.replace(/\/Users\/[^/\s]+\/Documents\/Work\/code\/[^\s]+\/?/g, "").replace(/\/private\/var\/folders\/[^\s]+\/?/g, "").replace(/\/var\/folders\/[^\s]+\/?/g, "");
|
|
126
|
+
}
|
|
127
|
+
function escapeRegExp(value) {
|
|
128
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
129
|
+
}
|
|
130
|
+
var TRACE_CAP = 300;
|
|
131
|
+
var TRACE_TEXT_MAX = 200;
|
|
132
|
+
var TRACE_FULLTEXT_MAX = 4e3;
|
|
133
|
+
function truncateTraceText(text, max = TRACE_TEXT_MAX) {
|
|
134
|
+
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
135
|
+
}
|
|
136
|
+
function truncateFullText(text, max = TRACE_FULLTEXT_MAX) {
|
|
137
|
+
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
138
|
+
}
|
|
139
|
+
function appendCapped(prev, items, cap = TRACE_CAP) {
|
|
140
|
+
if (!items.length) return prev;
|
|
141
|
+
const merged = [...prev, ...items];
|
|
142
|
+
return merged.length > cap ? merged.slice(merged.length - cap) : merged;
|
|
143
|
+
}
|
|
144
|
+
var ASK_USER_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
145
|
+
"askuserquestion",
|
|
146
|
+
"askuser",
|
|
147
|
+
"requestuserinput",
|
|
148
|
+
"requestapproval",
|
|
149
|
+
"humanconfirm",
|
|
150
|
+
"askhuman"
|
|
151
|
+
]);
|
|
152
|
+
function normalizeToolName(name) {
|
|
153
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
154
|
+
}
|
|
155
|
+
function isAskUserToolName(toolName) {
|
|
156
|
+
if (!toolName) return false;
|
|
157
|
+
if (ASK_USER_TOOL_NAMES.has(normalizeToolName(toolName))) return true;
|
|
158
|
+
const lastSegment = toolName.split(".").at(-1);
|
|
159
|
+
if (lastSegment && lastSegment !== toolName) {
|
|
160
|
+
return ASK_USER_TOOL_NAMES.has(normalizeToolName(lastSegment));
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
function tryParseJsonObject(text) {
|
|
165
|
+
if (!text) return void 0;
|
|
166
|
+
try {
|
|
167
|
+
const parsed = JSON.parse(text);
|
|
168
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : void 0;
|
|
169
|
+
} catch {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function summarizeAskUser(event) {
|
|
174
|
+
const raw = event.toolInputRaw ?? tryParseJsonObject(event.toolInput);
|
|
175
|
+
let question;
|
|
176
|
+
let options;
|
|
177
|
+
if (raw) {
|
|
178
|
+
const first = Array.isArray(raw.questions) ? raw.questions[0] : void 0;
|
|
179
|
+
const src = first && typeof first === "object" ? first : raw;
|
|
180
|
+
const q = src.question ?? src.prompt ?? src.text ?? src.header;
|
|
181
|
+
if (typeof q === "string" && q.trim()) question = q.trim();
|
|
182
|
+
const opts = src.options ?? src.choices;
|
|
183
|
+
if (Array.isArray(opts)) {
|
|
184
|
+
options = opts.map((o) => o && typeof o === "object" ? String(o.label ?? "") : String(o)).map((s) => s.trim()).filter(Boolean);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const rawStr = (event.toolInput ?? event.content ?? "").trim();
|
|
188
|
+
const base = question ?? (rawStr ? truncateTraceText(rawStr) : "Agent \u8BF7\u6C42\u4EBA\u5DE5\u8F93\u5165");
|
|
189
|
+
const text = options && options.length ? `${base}\uFF08\u9009\u9879\uFF1A${options.join(" / ")}\uFF09` : base;
|
|
190
|
+
const fullSource = question && rawStr && rawStr !== question ? rawStr : void 0;
|
|
191
|
+
const fullText = fullSource && fullSource.length > TRACE_TEXT_MAX ? truncateFullText(fullSource) : void 0;
|
|
192
|
+
return { text: truncateTraceText(text), fullText };
|
|
193
|
+
}
|
|
194
|
+
function traceItemFromEvent(event, round) {
|
|
195
|
+
switch (event.type) {
|
|
196
|
+
case "text": {
|
|
197
|
+
const text = event.content?.trim();
|
|
198
|
+
if (!text) return null;
|
|
199
|
+
return { kind: "text", text, round };
|
|
200
|
+
}
|
|
201
|
+
case "thinking": {
|
|
202
|
+
const text = event.content?.trim();
|
|
203
|
+
if (!text) return null;
|
|
204
|
+
return { kind: "thinking", text, round };
|
|
205
|
+
}
|
|
206
|
+
case "tool_use": {
|
|
207
|
+
if (isAskUserToolName(event.toolName)) {
|
|
208
|
+
const { text: text2, fullText } = summarizeAskUser(event);
|
|
209
|
+
return fullText ? { kind: "ask", text: text2, toolName: event.toolName, round, fullText } : { kind: "ask", text: text2, toolName: event.toolName, round };
|
|
210
|
+
}
|
|
211
|
+
const summary = summarizeToolUse({ toolName: event.toolName, toolInput: event.toolInput });
|
|
212
|
+
const text = summary.detail ? `${summary.label} \u2014 ${summary.detail}` : summary.label;
|
|
213
|
+
return { kind: "tool", text, toolName: event.toolName, round };
|
|
214
|
+
}
|
|
215
|
+
case "tool_result": {
|
|
216
|
+
const raw = event.content?.trim();
|
|
217
|
+
const toolName = event.toolName?.trim() || void 0;
|
|
218
|
+
const text = raw ? `\u21A9 ${truncateTraceText(raw)}` : toolName ? `\u21A9 ${toolName} \u8FD4\u56DE\u7ED3\u679C` : "\u21A9 \u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C";
|
|
219
|
+
const fullText = raw && raw.length > TRACE_TEXT_MAX ? truncateFullText(raw) : void 0;
|
|
220
|
+
return fullText ? { kind: "tool_result", text, toolName, round, fullText } : { kind: "tool_result", text, toolName, round };
|
|
221
|
+
}
|
|
222
|
+
case "permission_request": {
|
|
223
|
+
const toolName = event.toolName?.trim() || void 0;
|
|
224
|
+
const detail = event.content?.trim();
|
|
225
|
+
const base = toolName ? `${toolName} \u5F85\u6388\u6743` : "\u5F85\u6388\u6743\u8BF7\u6C42";
|
|
226
|
+
const text = detail ? `${base} \u2014 ${truncateTraceText(detail)}` : base;
|
|
227
|
+
const fullText = detail && detail.length > TRACE_TEXT_MAX ? truncateFullText(detail) : void 0;
|
|
228
|
+
return fullText ? { kind: "permission", text, toolName, round, fullText } : { kind: "permission", text, toolName, round };
|
|
229
|
+
}
|
|
230
|
+
case "result": {
|
|
231
|
+
const text = event.content?.trim();
|
|
232
|
+
if (!text) return null;
|
|
233
|
+
return { kind: "status", text, round };
|
|
234
|
+
}
|
|
235
|
+
case "error": {
|
|
236
|
+
const text = event.content?.trim() || "\u6267\u884C\u51FA\u9519";
|
|
237
|
+
return { kind: "status", text, round };
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function groupTrace(items) {
|
|
244
|
+
const groups = [];
|
|
245
|
+
let boundaryPending = false;
|
|
246
|
+
let currentRound;
|
|
247
|
+
let started = false;
|
|
248
|
+
for (const it of items) {
|
|
249
|
+
if (it.boundary) {
|
|
250
|
+
boundaryPending = true;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const roundChanged = it.round !== currentRound;
|
|
254
|
+
if (!started || boundaryPending || roundChanged) {
|
|
255
|
+
groups.push({ round: it.round, items: [] });
|
|
256
|
+
boundaryPending = false;
|
|
257
|
+
currentRound = it.round;
|
|
258
|
+
started = true;
|
|
259
|
+
}
|
|
260
|
+
const gi = groups[groups.length - 1].items;
|
|
261
|
+
if (it.kind === "text" || it.kind === "thinking") {
|
|
262
|
+
const lastItem = gi[gi.length - 1];
|
|
263
|
+
if (lastItem && lastItem.kind === it.kind) {
|
|
264
|
+
lastItem.text += it.text;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
gi.push({ kind: it.kind, text: it.text });
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const step = { kind: it.kind, text: it.text, toolName: it.toolName };
|
|
271
|
+
if (it.fullText !== void 0) step.fullText = it.fullText;
|
|
272
|
+
gi.push(step);
|
|
273
|
+
}
|
|
274
|
+
return groups;
|
|
275
|
+
}
|
|
276
|
+
function projectAgentEvents(events) {
|
|
277
|
+
const items = [];
|
|
278
|
+
for (const event of events) {
|
|
279
|
+
const item = traceItemFromEvent(event);
|
|
280
|
+
if (item) items.push(item);
|
|
281
|
+
}
|
|
282
|
+
return groupTrace(items);
|
|
283
|
+
}
|
|
284
|
+
|
|
27
285
|
// src/stage-machine/transitions.ts
|
|
28
286
|
var INIT = [
|
|
29
287
|
{ from: null, to: "configuring", driver: "init.http" },
|
|
@@ -696,17 +954,25 @@ function createRunLogId() {
|
|
|
696
954
|
}
|
|
697
955
|
export {
|
|
698
956
|
ACTIVE_PHASES,
|
|
957
|
+
ASK_USER_TOOL_NAMES,
|
|
699
958
|
DERIVED_PHASES,
|
|
700
959
|
DESIGN_GENERATING_PHASES,
|
|
701
960
|
IllegalStageTransitionError,
|
|
702
961
|
RELAY_OVERLOAD_CODE,
|
|
703
962
|
RELAY_REJECTED_CODE,
|
|
963
|
+
TRACE_CAP,
|
|
964
|
+
TRACE_FULLTEXT_MAX,
|
|
965
|
+
TRACE_TEXT_MAX,
|
|
704
966
|
TRANSITIONS,
|
|
967
|
+
appendCapped,
|
|
705
968
|
applyRunExecutionEvent,
|
|
706
969
|
deriveRelayRunState,
|
|
707
970
|
emptyProjectState,
|
|
971
|
+
extractDisplayFile,
|
|
708
972
|
formatAnswerDisplay,
|
|
973
|
+
groupTrace,
|
|
709
974
|
isActivePhase,
|
|
975
|
+
isAskUserToolName,
|
|
710
976
|
isDesignFailed,
|
|
711
977
|
isDesignGenerating,
|
|
712
978
|
isQuestionVisible,
|
|
@@ -717,13 +983,21 @@ export {
|
|
|
717
983
|
nextFor,
|
|
718
984
|
normalizeProjectState,
|
|
719
985
|
patchRunSnapshotRecord,
|
|
986
|
+
projectAgentEvents,
|
|
720
987
|
pruneHiddenAnswers,
|
|
721
988
|
recoverDesignFromArtifacts,
|
|
722
989
|
serializeForm,
|
|
723
990
|
serializeForms,
|
|
724
991
|
stageStateOf,
|
|
725
992
|
summarizeForms,
|
|
993
|
+
summarizeToolDetail,
|
|
994
|
+
summarizeToolLabel,
|
|
995
|
+
summarizeToolUse,
|
|
996
|
+
traceItemFromEvent,
|
|
726
997
|
transition,
|
|
998
|
+
truncateFullText,
|
|
999
|
+
truncateText,
|
|
1000
|
+
truncateTraceText,
|
|
727
1001
|
validateFormAnswers,
|
|
728
1002
|
visibleQuestions,
|
|
729
1003
|
withStageNext
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/relay.ts","../src/stage-machine/transitions.ts","../src/stage-machine/relay-run.ts","../src/stage-machine/transition.ts","../src/stage-machine/phase-kind.ts","../src/stage-machine/phase-groups.ts","../src/stage-machine/next.ts","../src/stage-machine/recover.ts","../src/stage-machine/project-state.ts","../src/discovery/visibility.ts","../src/discovery/validate-answers.ts","../src/discovery/serialize.ts","../src/run-state.ts"],"sourcesContent":["/**\n * relay — Relay ↔ Gateway 传输协议\n *\n * 双端共享的稳定线上协议定义(详见 docs/deployment/relay_arch.md §6)。\n * 纯类型 + 常量模块,无运行时副作用,不引入新依赖,不 import server/execution。\n * `event` 帧直接复用现有 `AgentEvent`(./agent-event.ts),不重复定义。\n */\n\nimport type { AgentEvent, KnownAgentType } from \"./agent-event.js\";\n\n// ─── Relay → Gateway ────────────────────────────────────────\n\n/** 首帧:Relay 反连成功后上报本机能力与身份 */\nexport interface RelayReadyMessage {\n type: \"ready\";\n /** 稳定机器身份;首次连接可缺省,由 Gateway 通过 ready:ack 下发 */\n machineId?: string;\n /** 本机可用的 Agent runtime 列表 */\n runtimes: string[];\n /** 是否具备 dws 执行能力 */\n hasDws: boolean;\n hostname: string;\n os: string;\n /**\n * 自注册身份提示(仅 owner-token 首连自注册路径消费):\n * name 为 owner 维度稳定身份键(幂等键 = owner+name);displayName/description 供 UI 展示。\n */\n name?: string;\n displayName?: string;\n description?: string;\n /** 本机并发执行上限(观测用;排队在 daemon 侧,网关不据此路由)。 */\n maxTasks?: number;\n}\n\n/** 心跳应答(对 ping 的响应) */\nexport interface RelayPongMessage {\n type: \"pong\";\n}\n\n/** 上行 Agent 事件帧:透传 AgentEvent 到 Gateway */\nexport interface RelayEventMessage {\n type: \"event\";\n requestId: string;\n event: AgentEvent;\n}\n\n/** 单条引擎运行事件(daemon 赋单调 seq 后上行)。 */\nexport interface RelayRunEventEntry {\n /** 同一 (machineId, projectId) 下单调递增;服务端据此幂等去重与补传定位。 */\n seq: number;\n /** 事件产生时间(ISO)。 */\n ts: string;\n /** 引擎 RunExecutionEvent(结构由 core orchestrator/run-events 定义)。 */\n event: unknown;\n}\n\n/**\n * 上行引擎运行事件批(workflow-pull-claim P1.5):daemon 每 ~500ms 聚合一批发送。\n * 与 `event` 帧的区别:按 projectId 路由(非 requestId),承载 run 阶段的引擎事件,\n * 服务端幂等落 `run_events` 并投影 `work_items`。\n */\nexport interface RelayRunEventsMessage {\n type: \"run:events\";\n projectId: string;\n entries: RelayRunEventEntry[];\n}\n\n/** 任务成功结果 */\nexport interface RelayResultMessage {\n type: \"result\";\n requestId: string;\n ok: true;\n response?: string;\n sessionId?: string;\n /**\n * 产出文件回传清单(对象存储中转)。\n * daemon 已将白名单内产出上传至对象存储(经服务端下发的预签名 URL,\n * daemon 无密钥),服务端据此 GET 并落盘到 project.dir 后再结算本帧。\n * 缺省(旧 daemon / 无产出)→ 跳过落盘,行为回退现状。\n */\n artifacts?: RelayArtifactManifest;\n}\n\n/** 单个产出文件的描述(相对路径 + 对象 key + 校验) */\nexport interface RelayArtifactFile {\n /** 相对 project.dir 的相对路径(落盘目标),白名单内、无 .. */\n path: string;\n /** 对象存储 key(artifacts/<projectId>/<requestId>/<safe-rel>) */\n key: string;\n /** 内容 sha256(hex),落盘后服务端复核 */\n sha256: string;\n /** 字节数 */\n size: number;\n}\n\n/** 产出文件 manifest(result.artifacts / upload:request 共用) */\nexport interface RelayArtifactManifest {\n store: \"oss\" | \"s3\";\n bucket?: string;\n prefix?: string;\n files: RelayArtifactFile[];\n}\n\n/** 任务错误结果 */\nexport interface RelayErrorMessage {\n type: \"error\";\n requestId: string;\n code: string;\n message: string;\n}\n\n/** daemon 上报产出文件清单,请求上传授权(daemon→gateway) */\nexport interface RelayUploadRequestMessage {\n type: \"upload:request\";\n /** 与 run:agent 的 requestId 一致 */\n requestId: string;\n projectId: string;\n files: Array<{\n /** 相对 cwd 的相对路径,白名单内、无 .. */\n path: string;\n size: number;\n sha256: string;\n }>;\n}\n\n// ─── Gateway → Relay ────────────────────────────────────────\n\n/** 确认首帧并下发稳定 machineId */\nexport interface RelayReadyAckMessage {\n type: \"ready:ack\";\n machineId: string;\n}\n\n/** 心跳探测 */\nexport interface RelayPingMessage {\n type: \"ping\";\n}\n\n/** 投递 Agent 运行任务 */\nexport interface RelayRunAgentMessage {\n type: \"run:agent\";\n requestId: string;\n projectId: string;\n agent: KnownAgentType;\n prompt: string;\n workDir?: string;\n sessionId?: string;\n /**\n * 权限模式透传(可选,向后兼容):服务端 Design 经 adapterOptions 注入\n * `bypassPermissions`,daemon 侧据此以带 `--permission-mode bypass_permissions`\n * (qoder)/`--dangerously-skip-permissions`(claudecode)方式启动 agent,\n * 在非交互 relay 环境下放行 Write。缺省时行为不变(旧 daemon 忽略未知字段)。\n */\n permissionMode?: \"bypassPermissions\";\n /** Agent 执行超时;传 null 表示不设置超时并覆盖 daemon 默认值。 */\n timeoutMs?: number | null;\n}\n\n/** 投递 dws 命令任务 */\nexport interface RelayRunDwsMessage {\n type: \"run:dws\";\n requestId: string;\n args: string[];\n timeoutMs?: number;\n}\n\n/** 服务端下发预签名上传链接(gateway→daemon,daemon 无密钥) */\nexport interface RelayUploadGrantMessage {\n type: \"upload:grant\";\n requestId: string;\n grants: Array<{\n /** 对应 upload:request.files[].path */\n path: string;\n method: \"PUT\";\n /** 预签名 PUT URL,daemon 直接 HTTP PUT,不接触 OSS 凭证 */\n url: string;\n headers?: Record<string, string>;\n /** 对象 key(artifacts/<projectId>/<requestId>/<safe-rel>) */\n key: string;\n /** ISO 时间戳,短时效(默认 10min) */\n expiresAt: string;\n }>;\n /**\n * 对未授权(不在白名单 / 越权)的 path,不下发 grant;daemon 应跳过该文件。\n * 未授权原因在此记录,便于 daemon 诊断。\n */\n rejected?: Array<{ path: string; reason: string }>;\n}\n\n/**\n * 唤醒提示(workflow-pull-claim P1):有 project 可认领时广播给候选机器,daemon 收到\n * 立即发起一次 claim。**best-effort**——丢帧只是延迟到下一轮轮询,租约语义不依赖它。\n */\nexport interface RelayWorkflowAvailableMessage {\n type: \"wf:available\";\n projectId: string;\n}\n\n// ─── 联合类型 ────────────────────────────────────────────────\n\n/** Relay → Gateway 方向的帧 */\nexport type RelayInboundMessage =\n | RelayReadyMessage\n | RelayPongMessage\n | RelayEventMessage\n | RelayRunEventsMessage\n | RelayResultMessage\n | RelayErrorMessage\n | RelayUploadRequestMessage;\n\n/** Gateway → Relay 方向的帧 */\nexport type RelayOutboundMessage =\n | RelayReadyAckMessage\n | RelayPingMessage\n | RelayRunAgentMessage\n | RelayRunDwsMessage\n | RelayUploadGrantMessage\n | RelayWorkflowAvailableMessage;\n\n/** Relay ↔ Gateway 全部协议帧 */\nexport type RelayMessage = RelayInboundMessage | RelayOutboundMessage;\n\n/** RelayMessage 的 discriminant 取值 */\nexport type RelayMessageType = RelayMessage[\"type\"];\n\n// ─── 常量 ────────────────────────────────────────────────────\n\n/** machine key 未知/已删/已 rotate 时的永久拒绝 WS close code */\nexport const RELAY_REJECTED_CODE = 4001;\n\n/**\n * 过载/限流时的 WS close code(Try Again Later,RFC6455 §7.4.1 = 1013)。\n *\n * 与 {@link RELAY_REJECTED_CODE}(4001,永久拒绝)区分:1013 表示**临时**过载\n * (连接配额 / per-owner 上限 / upgrade 令牌桶超速),daemon 端不应视为永久拒绝,\n * 应按既有退避策略重连(见 relay/connection.ts:非 4001 的 close 走 scheduleReconnect)。\n */\nexport const RELAY_OVERLOAD_CODE = 1013;\n\n// ─── 类型守卫 ────────────────────────────────────────────────\n\nconst RELAY_MESSAGE_TYPES: ReadonlySet<string> = new Set<RelayMessageType>([\n \"ready\",\n \"pong\",\n \"event\",\n \"run:events\",\n \"result\",\n \"error\",\n \"ready:ack\",\n \"ping\",\n \"run:agent\",\n \"run:dws\",\n \"upload:request\",\n \"upload:grant\",\n \"wf:available\",\n]);\n\n/**\n * 窄化守卫:判定任意值是否为合法的 RelayMessage 判别标签帧。\n * 仅校验 `type` 判别字段,不做深层字段校验(线上帧的字段校验交由消费端)。\n */\nexport function isRelayMessage(value: unknown): value is RelayMessage {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const type = (value as { type?: unknown }).type;\n return typeof type === \"string\" && RELAY_MESSAGE_TYPES.has(type);\n}\n","/**\n * stage-machine/transitions — 「谁能从哪到哪」的唯一定义\n *\n * phase 的切分依据是 driver(推进器):谁负责把这个状态往前推。\n * 本次重构的起因就是 design 的两个推进器共享同一个 running 状态、\n * 前端认错了推进器(详见 spec §1.1 Bug A)。\n *\n * 新增 phase 必须同时在这里登记 driver,并在 phase-kind.ts / next.ts 补齐映射,\n * 否则对应测试会失败。这是防止 phase 枚举无声膨胀的唯一闸口。\n */\nimport type { DesignState, InitState, RunState, SetupState } from \"../stage-state.js\";\n\nexport type DriverId =\n | \"init.http\"\n /** POST /projects/:id/design/discovery */\n | \"design.discovery\"\n /** POST /projects/:id/design (SSE) */\n | \"design.stream\"\n /** POST /projects/:id/setup (SSE) */\n | \"setup.stream\"\n /** 本地引擎进程(relay 模式没有推进器:那两个 phase 是读时派生,见 DERIVED_PHASES) */\n | \"run.local\"\n /** 系统级恢复(产物校验、会话丢失判定) */\n | \"system\";\n\nexport type TransitionRule<S extends { phase: string }> = {\n /** null = 从「该 stage 尚无状态」开始 */\n from: S[\"phase\"] | null;\n to: S[\"phase\"];\n driver: DriverId;\n};\n\nconst INIT: Array<TransitionRule<InitState>> = [\n { from: null, to: \"configuring\", driver: \"init.http\" },\n { from: \"configuring\", to: \"ready\", driver: \"init.http\" },\n { from: \"configuring\", to: \"failed\", driver: \"init.http\" },\n { from: \"failed\", to: \"configuring\", driver: \"init.http\" },\n { from: \"ready\", to: \"configuring\", driver: \"init.http\" },\n // 导入项目 / 发现既有产物时由服务端直接置 ready(替代前端两处伪造,见 spec §4.1)\n { from: null, to: \"ready\", driver: \"system\" },\n];\n\nconst DESIGN: Array<TransitionRule<DesignState>> = [\n { from: null, to: \"clarify_probing\", driver: \"design.discovery\" },\n { from: \"clarify_probing\", to: \"clarify_awaiting\", driver: \"design.discovery\" },\n { from: \"clarify_probing\", to: \"clarify_failed\", driver: \"design.discovery\" },\n // Agent 判断无需澄清,直接确认后进入生成侧\n { from: \"clarify_probing\", to: \"precheck\", driver: \"design.discovery\" },\n // 用户提交答案\n { from: \"clarify_awaiting\", to: \"precheck\", driver: \"design.discovery\" },\n { from: \"clarify_awaiting\", to: \"clarify_failed\", driver: \"design.discovery\" },\n { from: \"clarify_failed\", to: \"clarify_probing\", driver: \"design.discovery\" },\n // 重新设计:丢弃旧 confirmed,回到澄清\n { from: \"ready\", to: \"clarify_probing\", driver: \"design.discovery\" },\n\n { from: \"precheck\", to: \"generating\", driver: \"design.stream\" },\n { from: \"precheck\", to: \"generate_failed\", driver: \"design.stream\" },\n { from: \"generating\", to: \"validating\", driver: \"design.stream\" },\n { from: \"generating\", to: \"generate_failed\", driver: \"design.stream\" },\n // 校验不通过 → 下一轮修正。轮次推进只走这条边,不存在 generating→generating\n { from: \"validating\", to: \"generating\", driver: \"design.stream\" },\n { from: \"validating\", to: \"syncing_schema\", driver: \"design.stream\" },\n { from: \"validating\", to: \"generate_failed\", driver: \"design.stream\" },\n { from: \"syncing_schema\", to: \"ready\", driver: \"design.stream\" },\n { from: \"syncing_schema\", to: \"generate_failed\", driver: \"design.stream\" },\n // 重试:复用已确认的 confirmed\n { from: \"generate_failed\", to: \"precheck\", driver: \"design.stream\" },\n // 产物校验驱动的恢复,仅终态可触发(spec §5,修 Bug C)\n { from: \"ready\", to: \"generate_failed\", driver: \"system\" },\n { from: \"generate_failed\", to: \"ready\", driver: \"system\" },\n];\n\nconst SETUP: Array<TransitionRule<SetupState>> = [\n { from: null, to: \"preflight\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"provisioning_table\", driver: \"setup.stream\" },\n // 表已存在(table_id 已配好,也是重试时的常态)时跳过建表直接同步 schema。\n // 漏了这条边会让「已有表的 setup」永远失败,且真实错误被状态机内部错误顶掉。\n { from: \"preflight\", to: \"syncing_schema\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"failed\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"syncing_schema\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"failed\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"ready\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"failed\", driver: \"setup.stream\" },\n // 恢复一律回 preflight 重跑:建表与字段初始化都是幂等的,\n // 「从中断处继续」既省不了多少时间,又要多存一个说谎风险字段。\n { from: \"auth_awaiting\", to: \"preflight\", driver: \"setup.stream\" },\n { from: \"failed\", to: \"preflight\", driver: \"setup.stream\" },\n { from: \"ready\", to: \"preflight\", driver: \"setup.stream\" },\n];\n\n/**\n * 只有 local 模式的边:引擎跑在本进程,状态是我们自己的,可以落盘。\n *\n * relay 模式(引擎跑在用户机器的 daemon 上)的 `awaiting_machine` / `running_on_machine`\n * **不在这里**——见 DERIVED_PHASES。\n */\nconst RUN: Array<TransitionRule<RunState>> = [\n { from: null, to: \"starting\", driver: \"run.local\" },\n { from: \"starting\", to: \"listening\", driver: \"run.local\" },\n { from: \"starting\", to: \"failed\", driver: \"run.local\" },\n { from: \"listening\", to: \"stopped\", driver: \"run.local\" },\n { from: \"listening\", to: \"failed\", driver: \"run.local\" },\n { from: \"stopped\", to: \"starting\", driver: \"run.local\" },\n { from: \"failed\", to: \"starting\", driver: \"run.local\" },\n // 注意:「进程重启后落盘仍是 listening 但本进程无 session → failed」复用上面\n // listening→failed 这条边,由 system driver 走(system 是恢复通道,见 transition.ts)。\n // 不再单列一条 driver:\"system\" 的同 from→to 规则,否则违反「同一边只能有一个 driver」。\n];\n\nexport const TRANSITIONS = {\n init: INIT,\n design: DESIGN,\n setup: SETUP,\n run: RUN,\n} as const;\n\n/**\n * **读时派生、不落盘、不走 transition()** 的 phase。\n *\n * relay 模式下运行状态的真源是服务端的 run_schedule 行(desired_state /\n * machine_id / 心跳),由 daemon 的心跳驱动。往 project_state 里再存一份就是\n * 第二个副本,两份一旦不一致就回到病根 5(run 三处真源、读时互相强改写)。\n * 所以这两个 phase 每次读取时从 schedule 现算。\n *\n * 代价:它们没有入边。这不是漏配 —— 迁移表测试会显式豁免本清单,新增派生\n * phase 必须在这里登记,否则测试失败。\n */\nexport const DERIVED_PHASES: { [K in keyof typeof TRANSITIONS]?: ReadonlyArray<string> } = {\n run: [\"awaiting_machine\", \"running_on_machine\"],\n};\n","/**\n * stage-machine/relay-run — 由调度快照派生 relay 模式的 run 状态\n *\n * relay 模式下引擎跑在用户机器的 daemon 上,运行状态的真源是服务端的\n * run_schedule 行(desired_state / machine_id / 心跳)。这里把它翻译成 phase,\n * **不落盘、不走 transition()**(见 transitions.ts 的 DERIVED_PHASES)。\n *\n * 原先这件事在前端做(reducers/run.ts:82-101 从 schedule 反推 checkpoint),\n * 服务端另有一份落盘 task,两份不一致时后写的赢 —— spec §1.2 病根 5。\n */\nimport type { RunScheduleDTO } from \"../server.js\";\nimport type { RunState } from \"../stage-state.js\";\n\nexport function deriveRelayRunState(schedule: RunScheduleDTO): RunState {\n if (schedule.desiredState === \"stopped\") return { phase: \"stopped\", reason: \"user\" };\n // 期望运行且有在线机器持租 → 真的在跑。\n if (schedule.machineId && schedule.online) {\n return { phase: \"running_on_machine\", schedule, machineId: schedule.machineId };\n }\n // 期望运行但无人认领(没有在线机器 / 全部满载),或持租机器心跳已过期。\n return { phase: \"awaiting_machine\", schedule, stalledSince: schedule.unassignedSince ?? null };\n}\n","/**\n * stage-machine/transition — 唯一可以写 stage state 的地方\n *\n * 存储层只负责存取,不做业务判断;所有状态变更必须经过这里,\n * 否则「非法组合」会重新出现(spec §1.2 病根 1/2)。\n */\nimport type { AnyStageState } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\nimport { TRANSITIONS, type DriverId } from \"./transitions.js\";\n\nexport type IllegalTransitionDetail = {\n stage: string;\n from: string | null;\n to: string;\n driver: DriverId;\n expectedDriver?: DriverId;\n};\n\nexport class IllegalStageTransitionError extends Error {\n readonly code = \"ILLEGAL_STAGE_TRANSITION\";\n\n constructor(readonly detail: IllegalTransitionDetail) {\n super(\n detail.expectedDriver\n ? `推进器越权:${detail.stage} ${detail.from ?? \"(none)\"}→${detail.to} 只能由 ${detail.expectedDriver} 推进,实际是 ${detail.driver}`\n : `非法状态迁移:${detail.stage} ${detail.from ?? \"(none)\"}→${detail.to}(driver=${detail.driver})`,\n );\n this.name = \"IllegalStageTransitionError\";\n }\n}\n\n/** 浅比较 payload:state 都是纯 JSON,JSON.stringify 足够且可预测。 */\nfunction sameState(a: AnyStageState, b: AnyStageState): boolean {\n return a.phase === b.phase && JSON.stringify(a) === JSON.stringify(b);\n}\n\nexport function transition<S extends AnyStageState>(\n stage: StageIdDTO,\n current: S | null,\n next: S,\n driver: DriverId,\n): S {\n if (current && sameState(current, next)) return current;\n\n const rules = TRANSITIONS[stage] as ReadonlyArray<{ from: string | null; to: string; driver: DriverId }>;\n const from = current?.phase ?? null;\n const rule = rules.find((candidate) => candidate.from === from && candidate.to === next.phase);\n\n if (!rule) throw new IllegalStageTransitionError({ stage, from, to: next.phase, driver });\n // system 是恢复通道(产物校验、会话丢失判定),不受 driver 归属约束。\n if (driver !== \"system\" && rule.driver !== driver) {\n throw new IllegalStageTransitionError({ stage, from, to: next.phase, driver, expectedDriver: rule.driver });\n }\n return next;\n}\n","/**\n * stage-machine/phase-kind — phase 是否「还在推进中」\n *\n * 这里取代了原先的 `statusOf()` / `TaskStatusDTO`。原设计把每个 phase 映射到\n * 六值枚举(idle/running/blocked/failed/succeeded/cancelled)供前端展示,代价是:\n *\n * 1. **一对多的有损压缩**:`stopped`(正常停机)与 `ready` 都压成 `succeeded`,\n * 前端想区分就得回头读 phase,于是同一个判断有两套写法;\n * 2. **两个值无人产出**:`idle` 的真实语义是「stages 里没这个 key」,`cancelled`\n * 从来没有 phase 产出过 —— 枚举比现实大,读代码的人无法判断哪些分支是死的;\n * 3. **诱导误用**:`blocked` 当前只由 `setup.auth_awaiting` 产生,于是\n * 「status === blocked」被当成「在等授权」用,多一个 blocked phase 就静默出错。\n *\n * 所以只留一张表和两个**布尔**谓词。布尔的好处是没有展示语义可借用:想在界面上\n * 区分「成功/失败/等用户」,只能 `switch (state.phase)`。\n *\n * 这两个谓词只服务两条结构规则,不服务展示:\n * - `activeStage` 指针该指向谁(project-state.ts 的 `nextActiveStage`);\n * - 一次写入是否开启新 attempt(server 的 `StageWriter`)。\n */\nimport type { AnyStageState, DesignState, InitState, RunState, SetupState } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\n\n/**\n * 「还在推进中」的 phase:有推进器正在干活,或正等用户输入。\n *\n * 只列 active,终态 = 补集(见 `isTerminalPhase`)—— 两张表就会出现「既不 active\n * 也不 terminal」或「两边都列」的第三种状态,而 phase 只有这两种命运。\n * 新增 phase 时这里漏登记会被 `Record<...>` 的穷尽性检查拦住,编译不过。\n */\nexport const ACTIVE_PHASES: {\n init: Record<InitState[\"phase\"], boolean>;\n design: Record<DesignState[\"phase\"], boolean>;\n setup: Record<SetupState[\"phase\"], boolean>;\n run: Record<RunState[\"phase\"], boolean>;\n} = {\n init: {\n configuring: true,\n ready: false,\n failed: false,\n },\n design: {\n clarify_probing: true,\n // 等用户填澄清卡片也算推进中:activeStage 必须还指向 design,\n // 否则刷新后前端会认为设计阶段已经结束。\n clarify_awaiting: true,\n clarify_failed: false,\n precheck: true,\n generating: true,\n validating: true,\n syncing_schema: true,\n ready: false,\n generate_failed: false,\n },\n setup: {\n preflight: true,\n provisioning_table: true,\n syncing_schema: true,\n // 同上:等用户授权时阶段没结束。\n auth_awaiting: true,\n ready: false,\n failed: false,\n },\n run: {\n starting: true,\n listening: true,\n stopped: false,\n failed: false,\n // relay 模式的两个派生 phase:机器还没认领也算在跑,指针不能摘。\n awaiting_machine: true,\n running_on_machine: true,\n },\n};\n\n/** 该 phase 是否还在推进中(含等待用户输入)。 */\nexport function isActivePhase(stage: StageIdDTO, state: AnyStageState): boolean {\n const table = ACTIVE_PHASES[stage] as Record<string, boolean | undefined>;\n const active = table[state.phase];\n if (active === undefined) throw new Error(`phase 未登记推进中/终态:${stage}.${state.phase}`);\n return active;\n}\n\n/** 该 phase 是否已到终态(成功或失败都算)。 */\nexport function isTerminalPhase(stage: StageIdDTO, state: AnyStageState): boolean {\n return !isActivePhase(stage, state);\n}\n","/**\n * stage-machine/phase-groups — 跨端共享的 phase 分组\n *\n * 与 phase-kind.ts 的区别:那边是**结构规则**(指针 / attempt),只出布尔且只给\n * 状态机自己用;这里是**业务语义**分组,服务端与前端都要问同一个问题。\n *\n * 放在 core 是因为两边写两份就会分叉:`isDesignGenerating` 原先只有前端有\n * (web/state/view-model.ts),服务端的重入守卫改用 `statusOf(...) === \"running\"`\n * 近似表达同一件事,于是 `clarify_awaiting` 该不该拦在两处有两个答案。\n *\n * 分组只按 phase 判断,不引入中间枚举 —— 有了中间枚举就会有人拿它做展示分支。\n */\nimport type { DesignState } from \"../stage-state.js\";\n\n/**\n * design 的「生成流水线正在跑」:确认需求之后、产物落地之前。\n *\n * 不含 `clarify_probing` 与 `clarify_awaiting`:那两个是澄清回合,重新澄清是合法操作。\n */\nexport const DESIGN_GENERATING_PHASES = [\n \"precheck\",\n \"generating\",\n \"validating\",\n \"syncing_schema\",\n] as const satisfies ReadonlyArray<DesignState[\"phase\"]>;\n\n/** 生成流水线内的 design 状态。由上面那份清单推出来,避免清单与类型两处维护。 */\nexport type DesignGeneratingState = Extract<DesignState, { phase: typeof DESIGN_GENERATING_PHASES[number] }>;\n\n// 返回类型是类型守卫而不是 boolean:调用方多数要接着用 state(回 409、回当前状态),\n// 守卫顺手把 null 收掉,省掉一个「理论上不可能为空」的非空断言。\nexport function isDesignGenerating(\n state: DesignState | null | undefined,\n): state is DesignGeneratingState {\n return !!state && (DESIGN_GENERATING_PHASES as ReadonlyArray<string>).includes(state.phase);\n}\n\n/** design 的两个失败终态。合成一个谓词是因为界面与重试入口对两者的处理一致。 */\nexport function isDesignFailed(\n state: DesignState | null | undefined,\n): state is Extract<DesignState, { error: unknown }> {\n return state?.phase === \"clarify_failed\" || state?.phase === \"generate_failed\";\n}\n","/**\n * stage-machine/next — 服务端算「下一步该做什么」并下发\n *\n * 取代前端的 deriveStream(它只看 status==='running' 就去接流,是 Bug A 的引爆点)。\n * 沿用仓库既有哲学:恢复接口的 path 属服务端策略,客户端不自己拼\n * (见 ../server.ts 的 RecoveryActionDTO 注释)。\n */\nimport type { AnyStageState, DesignState, InitState, RunState, SetupState, StageNextDTO } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\n\nconst POLL_MS = 2000;\n\n/** 服务端才知道的运行期事实。 */\nexport type NextRuntimeFacts = {\n /** 该 stage 的活跃 SSE 会话是否在本进程 */\n sessionLocal: boolean;\n /** 会话事件游标;续流时下发,客户端据此重放 */\n cursor?: number;\n};\n\nexport type NextInput = {\n projectId: string;\n stage: StageIdDTO;\n state: AnyStageState;\n facts: NextRuntimeFacts;\n};\n\nexport function nextFor(input: NextInput): StageNextDTO {\n switch (input.stage) {\n case \"init\":\n return initNext(input.projectId, input.state as InitState);\n case \"design\":\n return designNext(input.projectId, input.state as DesignState, input.facts);\n case \"setup\":\n return setupNext(input.projectId, input.state as SetupState, input.facts);\n case \"run\":\n return runNext(input.projectId, input.state as RunState, input.facts);\n }\n}\n\nfunction initNext(projectId: string, state: InitState): StageNextDTO {\n switch (state.phase) {\n case \"configuring\":\n // init 是同步 POST,没有流。刷新页面时轮询到落地为止。\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"no_stream_for_phase\" };\n case \"ready\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新初始化\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/init` },\n },\n };\n }\n}\n\nfunction setupNext(projectId: string, state: SetupState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"preflight\":\n case \"provisioning_table\":\n case \"syncing_schema\":\n return streamOrPoll(`/api/projects/${projectId}/setup`, facts);\n case \"auth_awaiting\":\n return {\n kind: \"await_user\",\n action: {\n type: \"continue\",\n label: \"已完成授权,继续搭建\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/setup/continue` },\n },\n };\n case \"ready\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重试搭建\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/setup/retry` },\n },\n };\n }\n}\n\nfunction runNext(projectId: string, state: RunState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"starting\":\n case \"listening\":\n return streamOrPoll(`/api/projects/${projectId}/run`, facts);\n // relay 模式:执行在用户机器上,本进程永远没有流可接,只能轮询服务端投影。\n case \"awaiting_machine\":\n case \"running_on_machine\":\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"running_elsewhere\" };\n case \"stopped\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重试运行\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/run/retry` },\n },\n };\n }\n}\n\n/** 该 phase 本该有流:会话在本进程就续流,否则只能轮询。 */\nfunction streamOrPoll(endpoint: string, facts: NextRuntimeFacts): StageNextDTO {\n return facts.sessionLocal\n ? { kind: \"attach_stream\", endpoint, ...(facts.cursor === undefined ? {} : { cursor: facts.cursor }) }\n : { kind: \"poll\", intervalMs: POLL_MS, why: \"session_not_local\" };\n}\n\n/**\n * design 的生成流:**永远让客户端开流**,本进程有没有会话只决定要不要带重放游标。\n *\n * 不能像 setup/run 那样用 `streamOrPoll`:`POST /design` 既是「续流」也是「开始生成」\n * (`handleDesign` 自己先查 `designSessions` 决定重放还是新起),而 `precheck` 的语义\n * 恰恰是「澄清已确认、等客户端开流」—— 此刻本进程必然还没有会话。判成 poll 就是死锁:\n * 没有人去开流,phase 永远停在 precheck,客户端每 2 秒拉回同一份状态。\n * 这正是重构后线上的现象:澄清一结束,设计再也不会开始。\n *\n * `generating` / `validating` / `syncing_schema` 没有本地会话时同理。服务端对这种情况\n * 有专门的恢复分支(先落一条 `DESIGN_SESSION_LOST` 再回 `precheck` 重跑,见\n * `packages/server/src/design/design-routes.ts` 的 handleDesign),那段代码只有客户端\n * 重新 POST 才会被执行到 —— 判成 poll 会让它永远不可达,界面同样冻住。\n */\nfunction designStream(endpoint: string, facts: NextRuntimeFacts): StageNextDTO {\n return { kind: \"attach_stream\", endpoint, ...(facts.cursor === undefined ? {} : { cursor: facts.cursor }) };\n}\n\nfunction designNext(projectId: string, state: DesignState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"clarify_probing\":\n // 归 POST /design/discovery 推进,本来就没有流可接。\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"no_stream_for_phase\" };\n case \"clarify_awaiting\":\n return {\n kind: \"await_user\",\n action: {\n type: \"confirm_input\",\n label: \"确认后开始生成\",\n description: \"请先确认需求澄清卡片,再开始生成工作流方案。\",\n api: { method: \"POST\", path: `/api/projects/${projectId}/design/discovery/confirm` },\n },\n };\n case \"precheck\":\n case \"generating\":\n case \"validating\":\n case \"syncing_schema\":\n return designStream(`/api/projects/${projectId}/design`, facts);\n case \"ready\":\n return { kind: \"idle\" };\n case \"clarify_failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新开始需求澄清\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/design/discovery` },\n },\n };\n case \"generate_failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新生成工作流设计\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/design` },\n },\n };\n }\n}\n","/**\n * stage-machine/recover — 产物校验驱动的 design 状态恢复\n *\n * 收掉原先两处各 58 行的重复实现:\n * packages/server/src/repository/workflow-instance-store.ts:204-261\n * apps/gateway/src/infra/pg-workflow-instance-store.ts:227-284\n *\n * 与原实现的关键差异(spec §5,修 Bug C):**只有终态可被恢复覆盖**。\n * 原实现会把 blocked/running 的 design 任务按磁盘 manifest 强改成 succeeded,\n * 导致澄清期刷新被抹成「设计已完成」。\n *\n * 返回 null = 不需要改动,调用方不要落盘。\n */\nimport type { DesignArtifactValidationDTO } from \"../server.js\";\nimport type { DesignState } from \"../stage-state.js\";\nimport { transition } from \"./transition.js\";\n\nexport function recoverDesignFromArtifacts(\n current: DesignState | null,\n validation: DesignArtifactValidationDTO,\n): DesignState | null {\n if (!current) return null;\n // 非终态一律不动:运行中与等待用户的状态不可被产物校验覆盖。\n if (current.phase !== \"ready\" && current.phase !== \"generate_failed\") return null;\n\n if (validation.status === \"ready\") {\n if (current.phase === \"ready\") return null;\n if (!validation.manifest) return null;\n return transition<DesignState>(\n \"design\",\n current,\n // 从磁盘产物恢复时轮次已无从得知:manifest 不记轮数。记 0 表示「未知」,\n // 比编一个数字诚实。\n { phase: \"ready\", confirmed: current.confirmed, manifest: validation.manifest, rounds: 0 },\n \"system\",\n );\n }\n\n if (current.phase === \"generate_failed\") return null;\n return transition<DesignState>(\n \"design\",\n current,\n {\n phase: \"generate_failed\",\n // ready 也带 confirmed,所以恢复到 failed 不丢澄清上下文,\n // generate_failed→precheck 的重试路径仍然可用。\n confirmed: current.confirmed,\n error: {\n code: validation.status === \"artifact_missing\" ? \"ARTIFACT_MISSING\" : \"DESIGN_ARTIFACT_INVALID\",\n message: validation.errors.join(\";\") || \"设计产物不完整,请重新生成工作流设计。\",\n retryable: true,\n details: { missing: validation.missing },\n },\n },\n \"system\",\n );\n}\n","/**\n * stage-machine/project-state — 项目状态的装配与查询(纯函数)\n *\n * 原先住在 `packages/server/src/stage-runtime/task-state.ts`,被 gateway 反向\n * 从 `aitable-workflow-server` 导入(接缝方向反了)。搬到 core 后\n * server 与 gateway 都从 core 取。\n *\n * 这里只做装配与查询,**不做状态判断**——「能不能从 A 到 B」在 transition.ts。\n */\nimport type { ProjectDTO, StageIdDTO } from \"../server.js\";\nimport type {\n AnyStageState,\n ProjectStateDTO,\n ProjectStateResponseDTO,\n StageStateDTO,\n StageNextDTO,\n} from \"../stage-state.js\";\nimport { isActivePhase } from \"./phase-kind.js\";\n\nexport function emptyProjectState(project: ProjectDTO): ProjectStateDTO {\n return {\n projectId: project.id,\n activeStage: null,\n stages: {},\n updatedAt: project.updatedAt,\n };\n}\n\n/**\n * 某阶段变成 `state` 之后,`activeStage` 指针该指向谁。\n *\n * 指针必须跟着状态走:阶段转终态时若不摘掉指针,`state.stages[activeStage]`\n * 就指向一条已结束的阶段,调用方误判成还在跑。\n *\n * 单独导出是因为前端 reducer 收到 `stage.changed` 时要做同一件事\n * (`packages/web/src/state/reducers/task-state.ts`)。这条规则写两份,\n * 两份就会分叉——那正是本次重构要消灭的病根。\n */\nexport function nextActiveStage(\n current: StageIdDTO | null,\n stage: StageIdDTO,\n state: AnyStageState,\n): StageIdDTO | null {\n if (isActivePhase(stage, state)) return stage;\n return current === stage ? null : current;\n}\n\n/** 把一个阶段的新状态并进项目状态,并同步 `activeStage` 指针。 */\nexport function mergeStageIntoState(\n state: ProjectStateDTO,\n stage: StageIdDTO,\n stageState: StageStateDTO,\n): ProjectStateDTO {\n const stages = { ...state.stages, [stage]: stageState } as ProjectStateDTO[\"stages\"];\n return {\n ...state,\n stages,\n activeStage: nextActiveStage(state.activeStage, stage, stageState.state),\n updatedAt: stageState.identity.updatedAt,\n };\n}\n\n/**\n * 落盘数据补形。缺字段按空状态兜底,`activeStage` 与 `stages` 不一致时\n * 以 `stages` 为准重算——指针是派生量,状态才是真源。\n */\nexport function normalizeProjectState(project: ProjectDTO, value: Partial<ProjectStateDTO>): ProjectStateDTO {\n const stages = value.stages ?? {};\n return {\n projectId: value.projectId ?? project.id,\n activeStage: findActiveStage(stages),\n stages,\n runSnapshot: value.runSnapshot,\n updatedAt: value.updatedAt ?? project.updatedAt,\n };\n}\n\n/** 某个阶段的状态;该阶段从未跑过时为 null。 */\nexport function stageStateOf<S extends AnyStageState>(\n state: ProjectStateDTO,\n stage: StageIdDTO,\n): S | null {\n return (state.stages[stage]?.state as S | undefined) ?? null;\n}\n\n/**\n * 读时给每个阶段算 `next`,把落盘形状变成上行形状。\n *\n * `next` 不落盘(见 StageStateDTO 的注释),所以每条上行路径——`GET /state`\n * 与 SSE 的 `stage.changed`——都必须经这里,否则两处会给出不一致的指令。\n */\nexport function withStageNext(\n state: ProjectStateDTO,\n nextOf: (stage: StageIdDTO, stageState: StageStateDTO) => StageNextDTO,\n): ProjectStateResponseDTO {\n const stages: ProjectStateResponseDTO[\"stages\"] = {};\n for (const [stage, stageState] of Object.entries(state.stages) as Array<[StageIdDTO, StageStateDTO | undefined]>) {\n if (!stageState) continue;\n Object.assign(stages, { [stage]: { ...stageState, next: nextOf(stage, stageState) } });\n }\n return { ...state, stages };\n}\n\nfunction findActiveStage(stages: ProjectStateDTO[\"stages\"]): StageIdDTO | null {\n for (const [stage, stageState] of Object.entries(stages) as Array<[StageIdDTO, StageStateDTO | undefined]>) {\n if (stageState && isActivePhase(stage, stageState.state)) return stage;\n }\n return null;\n}\n","/**\n * discovery/visibility — 条件显示求值(单一真源)\n *\n * `FormQuestion.showIf` 的语义必须在四个消费方完全一致:\n * CLI 渲染(cli-form)、答案序列化(serialize/brief)、服务端必填校验\n * (design-routes)、Web 渲染(packages/web 的 QuestionForm)。\n * 任何一方自己实现一遍都会导致「CLI 不问但服务端要求必填」这类错位,\n * 所以统一放这里,并从 core barrel 导出给 web 用。\n *\n * 纯函数,无 IO。\n */\n\nimport type { FormAnswerValue, FormAnswers, FormQuestion, QuestionForm } from \"./types.js\";\n\n/** 答案是否「包含」指定值:数组按成员判断,标量按相等判断(布尔按字符串化) */\nfunction answerContains(value: FormAnswerValue | undefined, expected: string): boolean {\n if (value === undefined) return false;\n if (Array.isArray(value)) return value.includes(expected);\n if (typeof value === \"boolean\") return String(value) === expected;\n return value === expected;\n}\n\n/**\n * 该题当前是否应该提问。\n *\n * 无 `showIf` → 恒可见。依赖题尚未作答(answers 里没有)→ 不可见:\n * 逐题揭示时依赖题一定先于被依赖题作答,所以这个方向是安全的默认。\n */\nexport function isQuestionVisible(question: Pick<FormQuestion, \"showIf\">, answers: FormAnswers): boolean {\n if (!question.showIf) return true;\n return answerContains(answers[question.showIf.questionId], question.showIf.hasValue);\n}\n\n/** 当前答案下应提问的题目列表(保持原顺序) */\nexport function visibleQuestions(form: QuestionForm, answers: FormAnswers): FormQuestion[] {\n return form.questions.filter((question) => isQuestionVisible(question, answers));\n}\n\n/** 剔除不可见题的答案:不可见题不该进入 brief,也不该被服务端校验 */\nexport function pruneHiddenAnswers(form: QuestionForm, answers: FormAnswers): FormAnswers {\n const pruned: FormAnswers = {};\n for (const question of visibleQuestions(form, answers)) {\n const value = answers[question.id];\n if (value !== undefined) pruned[question.id] = value;\n }\n return pruned;\n}\n","/**\n * discovery/validate-answers — 澄清答案校验\n *\n * Agent 现在自己出题(含 required / maxSelections),答案从哪来就都得校一遍:\n * Web 走 HTTP 边界、CLI 走终端,两侧共用这一份实现,避免规则漂移。\n *\n * 只校可见题:showIf 未命中的题从未被问,缺答案是正常的。\n */\n\nimport type { FormAnswerValue, FormAnswers, QuestionForm } from \"./types.js\";\nimport { visibleQuestions } from \"./visibility.js\";\n\n/** 校验规则标识:文案按消费端决定(服务端要带题干,web 字段内联要短),规则只有一份。 */\nexport type AnswerIssueCode = \"required\" | \"max_selections\" | \"not_in_options\";\n\nexport interface AnswerValidationIssue {\n questionId: string;\n code: AnswerIssueCode;\n message: string;\n}\n\n/** 逐题校验答案;返回空数组表示通过 */\nexport function validateFormAnswers(form: QuestionForm, answers: FormAnswers): AnswerValidationIssue[] {\n const issues: AnswerValidationIssue[] = [];\n\n for (const question of visibleQuestions(form, answers)) {\n const value = answers[question.id];\n\n if (question.required && isBlankAnswer(value)) {\n issues.push({ questionId: question.id, code: \"required\", message: `「${question.label}」为必填项` });\n continue;\n }\n\n if (\n question.maxSelections !== undefined &&\n Array.isArray(value) &&\n value.length > question.maxSelections\n ) {\n issues.push({\n questionId: question.id,\n code: \"max_selections\",\n message: `「${question.label}」最多选 ${question.maxSelections} 项,实际 ${value.length} 项`,\n });\n }\n\n // 闭集题(显式 allowCustom: false)才校验取值:默认允许自定义,自由输入照常放行。\n if (question.allowCustom === false && question.options?.length && !isBlankAnswer(value)) {\n const allowed = new Set(question.options.map((option) => option.value));\n const picked = Array.isArray(value) ? value : [String(value)];\n const unknown = picked.filter((item) => !allowed.has(item));\n if (unknown.length > 0) {\n issues.push({\n questionId: question.id,\n code: \"not_in_options\",\n message: `「${question.label}」只能从给定选项中选择,收到:${unknown.join(\"、\")}`,\n });\n }\n }\n }\n\n return issues;\n}\n\nfunction isBlankAnswer(value: FormAnswerValue | undefined): boolean {\n if (value === undefined) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false; // boolean 永远算已回答\n}\n","/**\n * discovery/serialize — 答案序列化\n *\n * 保留原始问卷序列化能力,主要用于 CLI 调试和历史兼容;\n * Web 真实模式注入 Coding Agent 前会优先使用 summarizeForms / LLM brief,\n * 避免把 `[form answers]` 原始格式直接塞进 meta-prompt。\n * 纯函数,便于单测。\n */\n\nimport type {\n FilledForm,\n FormAnswerValue,\n FormAnswers,\n FormQuestion,\n} from \"./types.js\";\nimport { visibleQuestions } from \"./visibility.js\";\n\n/** 序列化单轮表单答案(跳过 showIf 未命中的题) */\nexport function serializeForm(form: FilledForm[\"form\"], answers: FormAnswers): string {\n const lines = [`[form answers — ${form.id}]`];\n for (const q of visibleQuestions(form, answers)) {\n const display = formatAnswerDisplay(q, answers[q.id]);\n lines.push(`- ${q.label}: ${display}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** 序列化多轮表单,段落之间空一行 */\nexport function serializeForms(filled: FilledForm[]): string {\n return filled\n .map(({ form, answers }) => serializeForm(form, answers))\n .join(\"\\n\\n\");\n}\n\n/**\n * 把表单答案汇总成一段简洁的自然语言约束(用于注入 Coding Agent prompt)。\n * 避免输出原始的 `[form answers — id]` 问卷格式,减少 prompt 噪音。\n *\n * 只输出实际提问过的题:showIf 未命中的题从未被问,标成 \"(skipped)\" 会让\n * Agent 误读为「用户明确拒绝回答」。\n */\nexport function summarizeForms(filled: FilledForm[]): string {\n const lines = [\"用户已确认:\"];\n for (const { form, answers } of filled) {\n for (const q of visibleQuestions(form, answers)) {\n lines.push(`- ${q.label}:${formatAnswerDisplay(q, answers[q.id])}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** 格式化单个答案的展示文本 */\nexport function formatAnswerDisplay(q: FormQuestion, value: FormAnswerValue | undefined): string {\n if (value === undefined || isEmpty(value)) return \"(skipped)\";\n\n switch (q.type) {\n case \"switch\":\n return toBool(value) ? \"是\" : \"否\";\n\n case \"radio\": {\n const v = String(value);\n const opt = q.options?.find((o) => o.value === v);\n if (opt && opt.label !== opt.value) return `${opt.label} [value: ${opt.value}]`;\n return v;\n }\n\n case \"checkbox\": {\n const arr = Array.isArray(value) ? value : [String(value)];\n const labels = arr.map((v) => {\n const opt = q.options?.find((o) => o.value === v);\n return opt?.label ?? v;\n });\n return labels.join(\", \");\n }\n\n default:\n return String(value);\n }\n}\n\n// ─── 辅助 ────────────────────────────────────────────────\n\nfunction isEmpty(value: FormAnswerValue): boolean {\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false; // boolean 永远算已回答\n}\n\nfunction toBool(value: FormAnswerValue): boolean {\n if (typeof value === \"boolean\") return value;\n return String(value) === \"true\" || value === \"是\";\n}\n","export type RunRecordStatus = \"todo\" | \"running\" | \"review\" | \"done\" | \"blocked\";\n\nexport type RunSnapshot = {\n records: Array<{\n id: string;\n title: string;\n state: string;\n status: RunRecordStatus;\n currentStepId: string | null;\n doneStepIds: string[];\n createdAt: number;\n updatedAt: number;\n }>;\n logs: Array<{\n id: string;\n ts: number;\n itemId: string;\n stepId: string;\n level: \"info\" | \"ai\" | \"ok\" | \"warn\" | \"error\";\n text: string;\n }>;\n};\n\nexport type RunExecutionEvent =\n | { type: \"run.record.detected\"; record: { id: string; title: string; state: string; currentStep: string | null } }\n | { type: \"run.step.started\"; itemId: string; stepId: string; stepName: string }\n | { type: \"run.step.succeeded\"; itemId: string; stepId: string; stepName: string; logs?: string[] }\n | { type: \"run.step.failed\"; itemId: string; stepId: string; stepName: string; error: string; logs?: string[] }\n | { type: \"run.record.blocked\"; itemId: string; stepId: string; stepName: string; reason: string; logs?: string[] }\n | { type: \"run.record.completed\"; itemId: string };\n\nexport type RunSnapshotLogLevel = RunSnapshot[\"logs\"][number][\"level\"];\nexport type RunSnapshotStepLevelResolver = (\n stepId: string,\n event: Extract<RunExecutionEvent, { type: \"run.step.started\" }>,\n) => RunSnapshotLogLevel;\n\nexport type ApplyRunExecutionEventOptions = {\n stepLevel?: RunSnapshotStepLevelResolver;\n now?: () => number;\n id?: () => string;\n maxLogs?: number;\n};\n\nconst DEFAULT_MAX_LOGS = 300;\n\nexport function applyRunExecutionEvent(\n snapshot: RunSnapshot,\n event: RunExecutionEvent,\n options: ApplyRunExecutionEventOptions = {},\n): RunSnapshot {\n const context = createContext(options);\n\n if (event.type === \"run.record.detected\") {\n return {\n records: upsertRunRecord(snapshot.records, {\n id: event.record.id,\n title: event.record.title || event.record.id,\n state: event.record.state,\n status: event.record.currentStep ? \"running\" : \"todo\",\n currentStepId: event.record.currentStep,\n doneStepIds: [],\n createdAt: context.now(),\n updatedAt: context.now(),\n }),\n logs: appendRunLog(snapshot.logs, event.record.id, event.record.currentStep ?? \"run\", \"info\", `发现待处理记录:${event.record.title || event.record.id}`, context),\n };\n }\n\n if (event.type === \"run.step.started\") {\n const level = options.stepLevel?.(event.stepId, event) ?? \"ai\";\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : \"running\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n updatedAt: context.now(),\n })),\n logs: appendRunLog(snapshot.logs, event.itemId, event.stepId, level, `开始执行步骤:${event.stepName}`, context),\n };\n }\n\n if (event.type === \"run.step.succeeded\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n doneStepIds: addDoneStep(record.doneStepIds, event.stepId),\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"ok\", `步骤完成:${event.stepName}`, context),\n event.itemId,\n event.stepId,\n \"info\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.step.failed\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : \"blocked\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"error\", `步骤失败:${event.stepName} · ${event.error}`, context),\n event.itemId,\n event.stepId,\n \"error\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.record.blocked\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : event.reason.includes(\"人工\") ? \"review\" : \"blocked\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n doneStepIds: addDoneStep(record.doneStepIds, event.stepId),\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"warn\", `${event.stepName}:${event.reason}`, context),\n event.itemId,\n event.stepId,\n \"info\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.record.completed\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: \"done\",\n currentStepId: null,\n updatedAt: context.now(),\n })),\n logs: appendRunLog(snapshot.logs, event.itemId, \"run\", \"ok\", \"流程完成\", context),\n };\n }\n\n return snapshot;\n}\n\n/**\n * 直接改写某条工单(不经事件)。relay 模式的服务端投影用它把 daemon 上报的\n * 状态落进快照。\n *\n * 原先在 `workflow-instance-store.ts:174-202` 与\n * `pg-workflow-instance-store.ts:312-338` 各有一份,连 `slice(-300)` 与\n * 日志 id 生成都逐字重复。\n */\nexport function patchRunSnapshotRecord(\n snapshot: RunSnapshot,\n itemId: string,\n patch: Partial<Pick<RunSnapshot[\"records\"][number], \"state\" | \"status\" | \"currentStepId\">>,\n logText?: string,\n options: ApplyRunExecutionEventOptions = {},\n): RunSnapshot {\n const context = createContext(options);\n return {\n records: updateRunRecord(snapshot.records, itemId, (record) => ({\n ...record,\n ...patch,\n updatedAt: context.now(),\n })),\n logs: logText\n ? appendRunLog(snapshot.logs, itemId, patch.currentStepId ?? \"run\", \"info\", logText, context)\n : snapshot.logs,\n };\n}\n\ntype ApplyContext = {\n now: () => number;\n id: () => string;\n maxLogs: number;\n};\n\nfunction createContext(options: ApplyRunExecutionEventOptions): ApplyContext {\n return {\n now: options.now ?? Date.now,\n id: options.id ?? createRunLogId,\n maxLogs: options.maxLogs ?? DEFAULT_MAX_LOGS,\n };\n}\n\nfunction upsertRunRecord(records: RunSnapshot[\"records\"], next: RunSnapshot[\"records\"][number]): RunSnapshot[\"records\"] {\n const index = records.findIndex((record) => record.id === next.id);\n if (index < 0) return [...records, next];\n return records.map((record) => record.id === next.id ? {\n ...record,\n ...next,\n doneStepIds: record.doneStepIds,\n status: record.status === \"done\" ? record.status : next.status,\n currentStepId: record.status === \"done\" ? record.currentStepId : next.currentStepId,\n createdAt: record.createdAt,\n } : record);\n}\n\nfunction updateRunRecord(\n records: RunSnapshot[\"records\"],\n id: string,\n update: (record: RunSnapshot[\"records\"][number]) => RunSnapshot[\"records\"][number],\n): RunSnapshot[\"records\"] {\n return records.map((record) => record.id === id ? update(record) : record);\n}\n\nfunction addDoneStep(doneStepIds: string[], stepId: string): string[] {\n return doneStepIds.includes(stepId) ? doneStepIds : [...doneStepIds, stepId];\n}\n\nfunction appendRunLog(\n logs: RunSnapshot[\"logs\"],\n itemId: string,\n stepId: string,\n level: RunSnapshotLogLevel,\n text: string,\n context: ApplyContext,\n): RunSnapshot[\"logs\"] {\n const exists = logs.some((log) =>\n log.itemId === itemId &&\n log.stepId === stepId &&\n log.level === level &&\n log.text === text\n );\n if (exists) return logs;\n return [...logs, {\n id: context.id(),\n ts: context.now(),\n itemId,\n stepId,\n level,\n text,\n }].slice(-context.maxLogs);\n}\n\nfunction appendRunLogs(\n logs: RunSnapshot[\"logs\"],\n itemId: string,\n stepId: string,\n level: RunSnapshotLogLevel,\n texts: string[] | undefined,\n context: ApplyContext,\n): RunSnapshot[\"logs\"] {\n if (!texts?.length) return logs;\n return texts.reduce((next, text) => appendRunLog(next, itemId, stepId, level, text, context), logs);\n}\n\nfunction createRunLogId(): string {\n return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/relay.ts","../src/agent-trace.ts","../src/stage-machine/transitions.ts","../src/stage-machine/relay-run.ts","../src/stage-machine/transition.ts","../src/stage-machine/phase-kind.ts","../src/stage-machine/phase-groups.ts","../src/stage-machine/next.ts","../src/stage-machine/recover.ts","../src/stage-machine/project-state.ts","../src/discovery/visibility.ts","../src/discovery/validate-answers.ts","../src/discovery/serialize.ts","../src/run-state.ts"],"sourcesContent":["/**\n * relay — Relay ↔ Gateway 传输协议\n *\n * 双端共享的稳定线上协议定义(详见 docs/deployment/relay_arch.md §6)。\n * 纯类型 + 常量模块,无运行时副作用,不引入新依赖,不 import server/execution。\n * `event` 帧直接复用现有 `AgentEvent`(./agent-event.ts),不重复定义。\n */\n\nimport type { AgentEvent, KnownAgentType } from \"./agent-event.js\";\n\n// ─── Relay → Gateway ────────────────────────────────────────\n\n/** 首帧:Relay 反连成功后上报本机能力与身份 */\nexport interface RelayReadyMessage {\n type: \"ready\";\n /** 稳定机器身份;首次连接可缺省,由 Gateway 通过 ready:ack 下发 */\n machineId?: string;\n /** 本机可用的 Agent runtime 列表 */\n runtimes: string[];\n /** 是否具备 dws 执行能力 */\n hasDws: boolean;\n hostname: string;\n os: string;\n /**\n * 自注册身份提示(仅 owner-token 首连自注册路径消费):\n * name 为 owner 维度稳定身份键(幂等键 = owner+name);displayName/description 供 UI 展示。\n */\n name?: string;\n displayName?: string;\n description?: string;\n /** 本机并发执行上限(观测用;排队在 daemon 侧,网关不据此路由)。 */\n maxTasks?: number;\n}\n\n/** 心跳应答(对 ping 的响应) */\nexport interface RelayPongMessage {\n type: \"pong\";\n}\n\n/** 上行 Agent 事件帧:透传 AgentEvent 到 Gateway */\nexport interface RelayEventMessage {\n type: \"event\";\n requestId: string;\n event: AgentEvent;\n}\n\n/** 单条引擎运行事件(daemon 赋单调 seq 后上行)。 */\nexport interface RelayRunEventEntry {\n /** 同一 (machineId, projectId) 下单调递增;服务端据此幂等去重与补传定位。 */\n seq: number;\n /** 事件产生时间(ISO)。 */\n ts: string;\n /** 引擎 RunExecutionEvent(结构由 core orchestrator/run-events 定义)。 */\n event: unknown;\n}\n\n/**\n * 上行引擎运行事件批(workflow-pull-claim P1.5):daemon 每 ~500ms 聚合一批发送。\n * 与 `event` 帧的区别:按 projectId 路由(非 requestId),承载 run 阶段的引擎事件,\n * 服务端幂等落 `run_events` 并投影 `work_items`。\n */\nexport interface RelayRunEventsMessage {\n type: \"run:events\";\n projectId: string;\n entries: RelayRunEventEntry[];\n}\n\n/** 任务成功结果 */\nexport interface RelayResultMessage {\n type: \"result\";\n requestId: string;\n ok: true;\n response?: string;\n sessionId?: string;\n /**\n * 产出文件回传清单(对象存储中转)。\n * daemon 已将白名单内产出上传至对象存储(经服务端下发的预签名 URL,\n * daemon 无密钥),服务端据此 GET 并落盘到 project.dir 后再结算本帧。\n * 缺省(旧 daemon / 无产出)→ 跳过落盘,行为回退现状。\n */\n artifacts?: RelayArtifactManifest;\n}\n\n/** 单个产出文件的描述(相对路径 + 对象 key + 校验) */\nexport interface RelayArtifactFile {\n /** 相对 project.dir 的相对路径(落盘目标),白名单内、无 .. */\n path: string;\n /** 对象存储 key(artifacts/<projectId>/<requestId>/<safe-rel>) */\n key: string;\n /** 内容 sha256(hex),落盘后服务端复核 */\n sha256: string;\n /** 字节数 */\n size: number;\n}\n\n/** 产出文件 manifest(result.artifacts / upload:request 共用) */\nexport interface RelayArtifactManifest {\n store: \"oss\" | \"s3\";\n bucket?: string;\n prefix?: string;\n files: RelayArtifactFile[];\n}\n\n/** 任务错误结果 */\nexport interface RelayErrorMessage {\n type: \"error\";\n requestId: string;\n code: string;\n message: string;\n}\n\n/** daemon 上报产出文件清单,请求上传授权(daemon→gateway) */\nexport interface RelayUploadRequestMessage {\n type: \"upload:request\";\n /** 与 run:agent 的 requestId 一致 */\n requestId: string;\n projectId: string;\n files: Array<{\n /** 相对 cwd 的相对路径,白名单内、无 .. */\n path: string;\n size: number;\n sha256: string;\n }>;\n}\n\n// ─── Gateway → Relay ────────────────────────────────────────\n\n/** 确认首帧并下发稳定 machineId */\nexport interface RelayReadyAckMessage {\n type: \"ready:ack\";\n machineId: string;\n}\n\n/** 心跳探测 */\nexport interface RelayPingMessage {\n type: \"ping\";\n}\n\n/** 投递 Agent 运行任务 */\nexport interface RelayRunAgentMessage {\n type: \"run:agent\";\n requestId: string;\n projectId: string;\n agent: KnownAgentType;\n prompt: string;\n workDir?: string;\n sessionId?: string;\n /**\n * 权限模式透传(可选,向后兼容):服务端 Design 经 adapterOptions 注入\n * `bypassPermissions`,daemon 侧据此以带 `--permission-mode bypass_permissions`\n * (qoder)/`--dangerously-skip-permissions`(claudecode)方式启动 agent,\n * 在非交互 relay 环境下放行 Write。缺省时行为不变(旧 daemon 忽略未知字段)。\n */\n permissionMode?: \"bypassPermissions\";\n /** Agent 执行超时;传 null 表示不设置超时并覆盖 daemon 默认值。 */\n timeoutMs?: number | null;\n}\n\n/** 投递 dws 命令任务 */\nexport interface RelayRunDwsMessage {\n type: \"run:dws\";\n requestId: string;\n args: string[];\n timeoutMs?: number;\n /**\n * relay 边界内联文件:`服务端绝对路径 → UTF-8 文件内容`。\n *\n * dws 写命令用 `--records-file <path>` 传 records 载荷(规避 Windows cmd.exe 引号破坏),\n * 但该临时文件建在**服务端进程**的 `/tmp`,daemon 在本机 spawn dws 时读不到(执行上下文被\n * 网络边界劈开)。发送侧(GatewayDwsRunner)读出内容随帧下发,接收侧(RelayDaemon)把内容\n * 落到本机临时文件并把 args 中的服务端路径替换为本机路径再执行。\n *\n * 可选:不带时行为与旧帧逐字节一致;旧 daemon 忽略未知字段。\n */\n inlineFiles?: Record<string, string>;\n}\n\n/** 服务端下发预签名上传链接(gateway→daemon,daemon 无密钥) */\nexport interface RelayUploadGrantMessage {\n type: \"upload:grant\";\n requestId: string;\n grants: Array<{\n /** 对应 upload:request.files[].path */\n path: string;\n method: \"PUT\";\n /** 预签名 PUT URL,daemon 直接 HTTP PUT,不接触 OSS 凭证 */\n url: string;\n headers?: Record<string, string>;\n /** 对象 key(artifacts/<projectId>/<requestId>/<safe-rel>) */\n key: string;\n /** ISO 时间戳,短时效(默认 10min) */\n expiresAt: string;\n }>;\n /**\n * 对未授权(不在白名单 / 越权)的 path,不下发 grant;daemon 应跳过该文件。\n * 未授权原因在此记录,便于 daemon 诊断。\n */\n rejected?: Array<{ path: string; reason: string }>;\n}\n\n/**\n * 唤醒提示(workflow-pull-claim P1):有 project 可认领时广播给候选机器,daemon 收到\n * 立即发起一次 claim。**best-effort**——丢帧只是延迟到下一轮轮询,租约语义不依赖它。\n */\nexport interface RelayWorkflowAvailableMessage {\n type: \"wf:available\";\n projectId: string;\n}\n\n// ─── 联合类型 ────────────────────────────────────────────────\n\n/** Relay → Gateway 方向的帧 */\nexport type RelayInboundMessage =\n | RelayReadyMessage\n | RelayPongMessage\n | RelayEventMessage\n | RelayRunEventsMessage\n | RelayResultMessage\n | RelayErrorMessage\n | RelayUploadRequestMessage;\n\n/** Gateway → Relay 方向的帧 */\nexport type RelayOutboundMessage =\n | RelayReadyAckMessage\n | RelayPingMessage\n | RelayRunAgentMessage\n | RelayRunDwsMessage\n | RelayUploadGrantMessage\n | RelayWorkflowAvailableMessage;\n\n/** Relay ↔ Gateway 全部协议帧 */\nexport type RelayMessage = RelayInboundMessage | RelayOutboundMessage;\n\n/** RelayMessage 的 discriminant 取值 */\nexport type RelayMessageType = RelayMessage[\"type\"];\n\n// ─── 常量 ────────────────────────────────────────────────────\n\n/** machine key 未知/已删/已 rotate 时的永久拒绝 WS close code */\nexport const RELAY_REJECTED_CODE = 4001;\n\n/**\n * 过载/限流时的 WS close code(Try Again Later,RFC6455 §7.4.1 = 1013)。\n *\n * 与 {@link RELAY_REJECTED_CODE}(4001,永久拒绝)区分:1013 表示**临时**过载\n * (连接配额 / per-owner 上限 / upgrade 令牌桶超速),daemon 端不应视为永久拒绝,\n * 应按既有退避策略重连(见 relay/connection.ts:非 4001 的 close 走 scheduleReconnect)。\n */\nexport const RELAY_OVERLOAD_CODE = 1013;\n\n// ─── 类型守卫 ────────────────────────────────────────────────\n\nconst RELAY_MESSAGE_TYPES: ReadonlySet<string> = new Set<RelayMessageType>([\n \"ready\",\n \"pong\",\n \"event\",\n \"run:events\",\n \"result\",\n \"error\",\n \"ready:ack\",\n \"ping\",\n \"run:agent\",\n \"run:dws\",\n \"upload:request\",\n \"upload:grant\",\n \"wf:available\",\n]);\n\n/**\n * 窄化守卫:判定任意值是否为合法的 RelayMessage 判别标签帧。\n * 仅校验 `type` 判别字段,不做深层字段校验(线上帧的字段校验交由消费端)。\n */\nexport function isRelayMessage(value: unknown): value is RelayMessage {\n if (typeof value !== \"object\" || value === null) {\n return false;\n }\n const type = (value as { type?: unknown }).type;\n return typeof type === \"string\" && RELAY_MESSAGE_TYPES.has(type);\n}\n","/**\n * agent-trace — Agent 事件到 UI 展示条目的纯映射(Node / 浏览器双端安全)\n *\n * 这段逻辑原先只住在 `packages/web/src/live/agent-trace-buffer.ts` +\n * `tool-summary.ts`。现在本地 relay daemon 也要在 Node 侧把 `AgentEvent` 投影成\n * 可读的执行流(Workbench 页面只渲染投影结果,不再在浏览器里重写一套映射),\n * 于是把「事件 → 展示条目」的**唯一一份**规则下沉到这里:web 浮层与 CLI workbench\n * 共享同一映射,避免两处漂移(见 docs/plans/2026-09-21-local-relay-agent-workbench.md)。\n *\n * 准入本包的硬判据在这里同样成立:不依赖 Node,也不依赖 DOM。凡是引用 core 的\n * `RunEventDTO`/`SetupEventDTO`/`phaseMessage` 的 setup/run 专有映射仍留在 web 侧。\n */\n\nimport type { AgentEvent } from \"./agent-event.js\";\n\n// ─── 工具调用摘要(原 web/live/tool-summary.ts)───────────────\n\nconst FILE_PURPOSES: Record<string, string> = {\n \"workflow.yml\": \"流程编排\",\n \"aitable-workflow.config.yml\": \"运行配置\",\n \"coding-agents.config.json\": \"执行引擎\",\n \".gitkeep\": \"目录占位\",\n};\n\nexport type ToolSummaryInput = {\n toolName?: string;\n toolInput?: string;\n projectId?: string;\n};\n\nexport type ToolSummary = {\n label: string;\n detail?: string;\n};\n\nexport function summarizeToolUse(input: ToolSummaryInput): ToolSummary {\n return {\n label: summarizeToolLabel(input.toolName, input.toolInput),\n detail: summarizeToolDetail(input.toolName, input.toolInput, input.projectId),\n };\n}\n\nexport function summarizeToolLabel(toolName?: string, toolInput?: string): string {\n if (!toolName) return \"处理生成进度\";\n const normalized = toolName.toLowerCase();\n if (normalized === \"bash\") return summarizeBashLabel(parseToolInput(toolInput));\n if (normalized.includes(\"write\") || normalized.includes(\"edit\")) {\n const file = extractDisplayFile(parseToolInput(toolInput));\n return file ? file.name : \"更新生成文件\";\n }\n if (normalized.includes(\"read\")) return \"读取上下文\";\n if (normalized.includes(\"mkdir\")) return \"创建目录\";\n if (normalized.includes(\"grep\") || normalized.includes(\"search\")) return \"检索项目内容\";\n return \"执行生成动作\";\n}\n\nexport function summarizeToolDetail(\n toolName?: string,\n toolInput?: string,\n projectId?: string,\n): string | undefined {\n const input = parseToolInput(toolInput);\n if (toolName?.toLowerCase() === \"bash\" && input) {\n return summarizeBashDetail(input, projectId);\n }\n const file = input ? extractDisplayFile(input) : null;\n if (file) return file.purpose;\n // Don't fall back to raw toolName (e.g. \"Write\") — hide detail if no meaningful content\n return toolInput ? truncateText(toolInput.replace(/\\s+/g, \" \"), 64) : undefined;\n}\n\nexport function extractDisplayFile(input: string): { name: string; purpose: string } | null {\n const recipesIndex = input.indexOf(\"recipes/\");\n if (recipesIndex >= 0) {\n const path = input.slice(recipesIndex).match(/^[^\\s'\"]+/)?.[0];\n if (path) {\n const name = path.split(\"/\").pop() ?? path;\n return { name, purpose: \"步骤脚本\" };\n }\n }\n\n const absoluteFile = input.match(/(?:^|[/\\\\])([^/\\\\\\s'\"]+\\.(?:yml|yaml|json|ts|tsx|md))$/);\n if (absoluteFile?.[1]) {\n const name = absoluteFile[1];\n return { name, purpose: FILE_PURPOSES[name] ?? \"项目文件\" };\n }\n\n const match = input.match(/(?:^|\\s)(?:[~./\\w-]+\\/)?((?:recipes\\/)?[^\\s'\"]+\\.(?:yml|yaml|json|ts|tsx|md))/);\n if (!match?.[1]) return null;\n const path = match[1].replace(/^.*?(recipes\\/)/u, \"$1\");\n const name = path.split(\"/\").pop() ?? path;\n const purpose = path.startsWith(\"recipes/\") ? \"步骤脚本\" : FILE_PURPOSES[name] ?? \"项目文件\";\n return { name, purpose };\n}\n\nexport function truncateText(text: string, limit: number): string {\n return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;\n}\n\nfunction summarizeBashLabel(command: string): string {\n const normalized = command.trim().toLowerCase();\n if (/^mkdir\\b/.test(normalized)) return \"创建目录\";\n if (/^(install|touch)\\b/.test(normalized)) return \"准备生成文件\";\n if (/^(ls|find|test|cat|pwd)\\b/.test(normalized)) return \"检查生成结果\";\n if (/\\b(pnpm|npm|tsx|tsc|node|vitest)\\b/.test(normalized)) return \"校验生成文件\";\n return \"执行生成命令\";\n}\n\nfunction summarizeBashDetail(command: string, projectId?: string): string {\n const normalized = command.replace(/\\s+/g, \" \").trim();\n const projectPath = projectId ? formatProjectRelativePath(normalized, projectId) : null;\n if (projectPath) return projectPath;\n\n const displayFile = extractDisplayFile(normalized);\n if (displayFile) return `${displayFile.purpose} · ${displayFile.name}`;\n\n return `命令 · ${truncateText(stripLocalPrefix(normalized), 72)}`;\n}\n\nfunction parseToolInput(toolInput?: string): string {\n if (!toolInput) return \"\";\n const trimmed = toolInput.trim();\n try {\n const parsed = JSON.parse(trimmed) as Record<string, unknown>;\n for (const key of [\"file_path\", \"path\", \"command\", \"cmd\"]) {\n const value = parsed[key];\n if (typeof value === \"string\" && value.trim()) return value;\n }\n } catch {\n // plain text input\n }\n return trimmed;\n}\n\nfunction formatProjectRelativePath(text: string, projectId: string): string | null {\n const escapedProjectId = escapeRegExp(projectId);\n const match = text.match(\n new RegExp(`(?:^|[\\\\s'\"])(?:[^\\\\s'\"]*/)?${escapedProjectId}(?:/([^\\\\s'\"]+))?`),\n );\n if (!match) return null;\n const relative = match[1]?.replace(/^\\/+/, \"\") ?? \"\";\n return relative ? `${projectId}/${relative}` : projectId;\n}\n\nfunction stripLocalPrefix(text: string): string {\n return text\n .replace(/\\/Users\\/[^/\\s]+\\/Documents\\/Work\\/code\\/[^\\s]+\\/?/g, \"\")\n .replace(/\\/private\\/var\\/folders\\/[^\\s]+\\/?/g, \"\")\n .replace(/\\/var\\/folders\\/[^\\s]+\\/?/g, \"\");\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// ─── 轨迹条目与映射(原 web/live/agent-trace-buffer.ts 的纯函数)──\n\n/** 缓冲中的单条轨迹条目。 */\nexport interface AgentTraceItem {\n /** 该条目所属的设计轮次(如已知)。 */\n round?: number;\n /** 条目语义:正文 / 思考 / 工具调用 / 工具返回 / 授权请求 / 向人提问 / 状态(结果、错误等)。 */\n kind: \"text\" | \"thinking\" | \"tool\" | \"tool_result\" | \"permission\" | \"ask\" | \"status\";\n /** 展示文本。 */\n text: string;\n /** 工具名(kind === \"tool\" 时有意义)。 */\n toolName?: string;\n /**\n * 全文(仅当 `text` 摘要被 `TRACE_TEXT_MAX` 截断时填充,用于浮层点击展开查看完整内容)。\n * 受 `TRACE_FULLTEXT_MAX` 二级上限约束,避免超大工具返回撑爆缓冲。未截断时不带此字段。\n */\n fullText?: string;\n /** 结构性分组边界标记,无可见内容;置 true 时强制下一条起新组。 */\n boundary?: boolean;\n}\n\n/** 环形缓冲上限。 */\nexport const TRACE_CAP = 300;\n\n/** tool_result / permission 摘要文本的最大展示长度(超出截断加省略号)。 */\nexport const TRACE_TEXT_MAX = 200;\n\n/** fullText 的二级长度上限(超大工具返回安全截断,避免撑爆缓冲)。 */\nexport const TRACE_FULLTEXT_MAX = 4000;\n\n/** 截断长文本用于浮层摘要展示,超过 max 时保留前 max 个字符并追加省略号。 */\nexport function truncateTraceText(text: string, max: number = TRACE_TEXT_MAX): string {\n return text.length > max ? `${text.slice(0, max)}…` : text;\n}\n\n/** 截断全文用于浮层展开态,超过 TRACE_FULLTEXT_MAX 时安全截断并追加省略号。 */\nexport function truncateFullText(text: string, max: number = TRACE_FULLTEXT_MAX): string {\n return text.length > max ? `${text.slice(0, max)}…` : text;\n}\n\n/**\n * 向缓冲追加若干条目,超出上限时从头部(最旧)丢弃。\n * 返回新数组(不可变更新),空输入原样返回旧引用。\n */\nexport function appendCapped(\n prev: AgentTraceItem[],\n items: AgentTraceItem[],\n cap: number = TRACE_CAP,\n): AgentTraceItem[] {\n if (!items.length) return prev;\n const merged = [...prev, ...items];\n return merged.length > cap ? merged.slice(merged.length - cap) : merged;\n}\n\n/**\n * 「向人提问」类工具名(ask-user tool)。\n *\n * 这份名单是**唯一事实源**:core 的 ask-human 中止探测器(`coding-agents/ask-human-detector.ts`)\n * 与这里的展示投影都从这里取,避免「哪些工具名算问人」在两处各写一份而漂移。\n * 归一化后比对(小写去非字母数字),兼容 provider 前缀(取点号末段)。\n */\nexport const ASK_USER_TOOL_NAMES: ReadonlySet<string> = new Set([\n \"askuserquestion\",\n \"askuser\",\n \"requestuserinput\",\n \"requestapproval\",\n \"humanconfirm\",\n \"askhuman\",\n]);\n\nfunction normalizeToolName(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n}\n\n/** toolName 是否属于「向人提问」类工具。 */\nexport function isAskUserToolName(toolName?: string): boolean {\n if (!toolName) return false;\n if (ASK_USER_TOOL_NAMES.has(normalizeToolName(toolName))) return true;\n const lastSegment = toolName.split(\".\").at(-1);\n if (lastSegment && lastSegment !== toolName) {\n return ASK_USER_TOOL_NAMES.has(normalizeToolName(lastSegment));\n }\n return false;\n}\n\nfunction tryParseJsonObject(text?: string): Record<string, unknown> | undefined {\n if (!text) return undefined;\n try {\n const parsed: unknown = JSON.parse(text);\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** 从 ask-user tool_use 事件里取一行人类可读的问题(含选项),原文放 fullText 供展开。 */\nfunction summarizeAskUser(event: AgentEvent): { text: string; fullText?: string } {\n const raw = event.toolInputRaw ?? tryParseJsonObject(event.toolInput);\n let question: string | undefined;\n let options: string[] | undefined;\n if (raw) {\n const first = Array.isArray(raw.questions) ? raw.questions[0] : undefined;\n const src = first && typeof first === \"object\" ? (first as Record<string, unknown>) : raw;\n const q = src.question ?? src.prompt ?? src.text ?? src.header;\n if (typeof q === \"string\" && q.trim()) question = q.trim();\n const opts = src.options ?? src.choices;\n if (Array.isArray(opts)) {\n options = opts\n .map((o) => (o && typeof o === \"object\" ? String((o as Record<string, unknown>).label ?? \"\") : String(o)))\n .map((s) => s.trim())\n .filter(Boolean);\n }\n }\n const rawStr = (event.toolInput ?? event.content ?? \"\").trim();\n const base = question ?? (rawStr ? truncateTraceText(rawStr) : \"Agent 请求人工输入\");\n const text = options && options.length ? `${base}(选项:${options.join(\" / \")})` : base;\n // 展开态:问题之外仍有原始载荷时才带 fullText,避免重复。\n const fullSource = question && rawStr && rawStr !== question ? rawStr : undefined;\n const fullText = fullSource && fullSource.length > TRACE_TEXT_MAX ? truncateFullText(fullSource) : undefined;\n return { text: truncateTraceText(text), fullText };\n}\n\n/**\n * 把一个原始 AgentEvent 映射为轨迹条目。\n * - text → kind:\"text\"\n * - thinking → kind:\"thinking\"\n * - tool_use → kind:\"ask\"(命中 ask-user 工具名时)或 kind:\"tool\"(文本取 summarizeToolUse 的 `label — detail`)\n * - tool_result → kind:\"tool_result\"(结果摘要,长文截断;带 toolName)\n * - permission_request → kind:\"permission\"(授权请求摘要,如 toolName + 待授权)\n * - result / error → kind:\"status\"\n * - 其余无内容事件 → 返回 null(不入库)\n */\nexport function traceItemFromEvent(event: AgentEvent, round?: number): AgentTraceItem | null {\n switch (event.type) {\n case \"text\": {\n const text = event.content?.trim();\n if (!text) return null;\n return { kind: \"text\", text, round };\n }\n case \"thinking\": {\n const text = event.content?.trim();\n if (!text) return null;\n return { kind: \"thinking\", text, round };\n }\n case \"tool_use\": {\n if (isAskUserToolName(event.toolName)) {\n const { text, fullText } = summarizeAskUser(event);\n return fullText\n ? { kind: \"ask\", text, toolName: event.toolName, round, fullText }\n : { kind: \"ask\", text, toolName: event.toolName, round };\n }\n const summary = summarizeToolUse({ toolName: event.toolName, toolInput: event.toolInput });\n const text = summary.detail ? `${summary.label} — ${summary.detail}` : summary.label;\n return { kind: \"tool\", text, toolName: event.toolName, round };\n }\n case \"tool_result\": {\n const raw = event.content?.trim();\n const toolName = event.toolName?.trim() || undefined;\n const text = raw\n ? `↩ ${truncateTraceText(raw)}`\n : toolName\n ? `↩ ${toolName} 返回结果`\n : \"↩ 工具返回结果\";\n const fullText = raw && raw.length > TRACE_TEXT_MAX ? truncateFullText(raw) : undefined;\n return fullText\n ? { kind: \"tool_result\", text, toolName, round, fullText }\n : { kind: \"tool_result\", text, toolName, round };\n }\n case \"permission_request\": {\n const toolName = event.toolName?.trim() || undefined;\n const detail = event.content?.trim();\n const base = toolName ? `${toolName} 待授权` : \"待授权请求\";\n const text = detail ? `${base} — ${truncateTraceText(detail)}` : base;\n const fullText = detail && detail.length > TRACE_TEXT_MAX ? truncateFullText(detail) : undefined;\n return fullText\n ? { kind: \"permission\", text, toolName, round, fullText }\n : { kind: \"permission\", text, toolName, round };\n }\n case \"result\": {\n const text = event.content?.trim();\n if (!text) return null;\n return { kind: \"status\", text, round };\n }\n case \"error\": {\n const text = event.content?.trim() || \"执行出错\";\n return { kind: \"status\", text, round };\n }\n default:\n return null;\n }\n}\n\n/** 分组后单步的展示条目。 */\nexport interface TraceGroupItem {\n kind: \"text\" | \"thinking\" | \"tool\" | \"tool_result\" | \"permission\" | \"ask\" | \"status\";\n text: string;\n toolName?: string;\n /** 全文(透传自 AgentTraceItem,tool_result/permission 被截断时供浮层展开)。 */\n fullText?: string;\n}\n\n/** 分组:一轮内的一段连续执行流。 */\nexport interface TraceGroup {\n round?: number;\n items: TraceGroupItem[];\n}\n\n/**\n * 把扁平缓冲整理为消息条式分组(对标 open-tag groupTraj):\n * - 连续的同类正文(text↔text、thinking↔thinking)折叠成一段,读起来是连续的文字而非滚动日志;\n * - tool / tool_result / permission / status 独立成步;\n * - boundary 标记或 round 变化都会起一个新组。\n */\nexport function groupTrace(items: AgentTraceItem[]): TraceGroup[] {\n const groups: TraceGroup[] = [];\n let boundaryPending = false;\n let currentRound: number | undefined;\n let started = false;\n for (const it of items) {\n if (it.boundary) {\n boundaryPending = true;\n continue;\n }\n const roundChanged = it.round !== currentRound;\n if (!started || boundaryPending || roundChanged) {\n groups.push({ round: it.round, items: [] });\n boundaryPending = false;\n currentRound = it.round;\n started = true;\n }\n const gi = groups[groups.length - 1]!.items;\n if (it.kind === \"text\" || it.kind === \"thinking\") {\n const lastItem = gi[gi.length - 1];\n if (lastItem && lastItem.kind === it.kind) {\n lastItem.text += it.text;\n continue;\n }\n gi.push({ kind: it.kind, text: it.text });\n continue;\n }\n // tool / tool_result / permission / ask / status 独立成步\n const step: TraceGroupItem = { kind: it.kind, text: it.text, toolName: it.toolName };\n if (it.fullText !== undefined) step.fullText = it.fullText;\n gi.push(step);\n }\n return groups;\n}\n\n/**\n * 把一批 AgentEvent 直接投影为分组后的展示结构(供 Node 侧 workbench API 返回,\n * 浏览器只渲染结果)。过滤掉映射为 null 的空事件。\n */\nexport function projectAgentEvents(events: readonly AgentEvent[]): TraceGroup[] {\n const items: AgentTraceItem[] = [];\n for (const event of events) {\n const item = traceItemFromEvent(event);\n if (item) items.push(item);\n }\n return groupTrace(items);\n}\n","/**\n * stage-machine/transitions — 「谁能从哪到哪」的唯一定义\n *\n * phase 的切分依据是 driver(推进器):谁负责把这个状态往前推。\n * 本次重构的起因就是 design 的两个推进器共享同一个 running 状态、\n * 前端认错了推进器(详见 spec §1.1 Bug A)。\n *\n * 新增 phase 必须同时在这里登记 driver,并在 phase-kind.ts / next.ts 补齐映射,\n * 否则对应测试会失败。这是防止 phase 枚举无声膨胀的唯一闸口。\n */\nimport type { DesignState, InitState, RunState, SetupState } from \"../stage-state.js\";\n\nexport type DriverId =\n | \"init.http\"\n /** POST /projects/:id/design/discovery */\n | \"design.discovery\"\n /** POST /projects/:id/design (SSE) */\n | \"design.stream\"\n /** POST /projects/:id/setup (SSE) */\n | \"setup.stream\"\n /** 本地引擎进程(relay 模式没有推进器:那两个 phase 是读时派生,见 DERIVED_PHASES) */\n | \"run.local\"\n /** 系统级恢复(产物校验、会话丢失判定) */\n | \"system\";\n\nexport type TransitionRule<S extends { phase: string }> = {\n /** null = 从「该 stage 尚无状态」开始 */\n from: S[\"phase\"] | null;\n to: S[\"phase\"];\n driver: DriverId;\n};\n\nconst INIT: Array<TransitionRule<InitState>> = [\n { from: null, to: \"configuring\", driver: \"init.http\" },\n { from: \"configuring\", to: \"ready\", driver: \"init.http\" },\n { from: \"configuring\", to: \"failed\", driver: \"init.http\" },\n { from: \"failed\", to: \"configuring\", driver: \"init.http\" },\n { from: \"ready\", to: \"configuring\", driver: \"init.http\" },\n // 导入项目 / 发现既有产物时由服务端直接置 ready(替代前端两处伪造,见 spec §4.1)\n { from: null, to: \"ready\", driver: \"system\" },\n];\n\nconst DESIGN: Array<TransitionRule<DesignState>> = [\n { from: null, to: \"clarify_probing\", driver: \"design.discovery\" },\n { from: \"clarify_probing\", to: \"clarify_awaiting\", driver: \"design.discovery\" },\n { from: \"clarify_probing\", to: \"clarify_failed\", driver: \"design.discovery\" },\n // Agent 判断无需澄清,直接确认后进入生成侧\n { from: \"clarify_probing\", to: \"precheck\", driver: \"design.discovery\" },\n // 用户提交答案\n { from: \"clarify_awaiting\", to: \"precheck\", driver: \"design.discovery\" },\n { from: \"clarify_awaiting\", to: \"clarify_failed\", driver: \"design.discovery\" },\n { from: \"clarify_failed\", to: \"clarify_probing\", driver: \"design.discovery\" },\n // 重新设计:丢弃旧 confirmed,回到澄清\n { from: \"ready\", to: \"clarify_probing\", driver: \"design.discovery\" },\n\n { from: \"precheck\", to: \"generating\", driver: \"design.stream\" },\n { from: \"precheck\", to: \"generate_failed\", driver: \"design.stream\" },\n { from: \"generating\", to: \"validating\", driver: \"design.stream\" },\n { from: \"generating\", to: \"generate_failed\", driver: \"design.stream\" },\n // 校验不通过 → 下一轮修正。轮次推进只走这条边,不存在 generating→generating\n { from: \"validating\", to: \"generating\", driver: \"design.stream\" },\n { from: \"validating\", to: \"syncing_schema\", driver: \"design.stream\" },\n { from: \"validating\", to: \"generate_failed\", driver: \"design.stream\" },\n { from: \"syncing_schema\", to: \"ready\", driver: \"design.stream\" },\n { from: \"syncing_schema\", to: \"generate_failed\", driver: \"design.stream\" },\n // 重试:复用已确认的 confirmed\n { from: \"generate_failed\", to: \"precheck\", driver: \"design.stream\" },\n // 产物校验驱动的恢复,仅终态可触发(spec §5,修 Bug C)\n { from: \"ready\", to: \"generate_failed\", driver: \"system\" },\n { from: \"generate_failed\", to: \"ready\", driver: \"system\" },\n];\n\nconst SETUP: Array<TransitionRule<SetupState>> = [\n { from: null, to: \"preflight\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"provisioning_table\", driver: \"setup.stream\" },\n // 表已存在(table_id 已配好,也是重试时的常态)时跳过建表直接同步 schema。\n // 漏了这条边会让「已有表的 setup」永远失败,且真实错误被状态机内部错误顶掉。\n { from: \"preflight\", to: \"syncing_schema\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"preflight\", to: \"failed\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"syncing_schema\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"provisioning_table\", to: \"failed\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"ready\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"auth_awaiting\", driver: \"setup.stream\" },\n { from: \"syncing_schema\", to: \"failed\", driver: \"setup.stream\" },\n // 恢复一律回 preflight 重跑:建表与字段初始化都是幂等的,\n // 「从中断处继续」既省不了多少时间,又要多存一个说谎风险字段。\n { from: \"auth_awaiting\", to: \"preflight\", driver: \"setup.stream\" },\n { from: \"failed\", to: \"preflight\", driver: \"setup.stream\" },\n { from: \"ready\", to: \"preflight\", driver: \"setup.stream\" },\n];\n\n/**\n * 只有 local 模式的边:引擎跑在本进程,状态是我们自己的,可以落盘。\n *\n * relay 模式(引擎跑在用户机器的 daemon 上)的 `awaiting_machine` / `running_on_machine`\n * **不在这里**——见 DERIVED_PHASES。\n */\nconst RUN: Array<TransitionRule<RunState>> = [\n { from: null, to: \"starting\", driver: \"run.local\" },\n { from: \"starting\", to: \"listening\", driver: \"run.local\" },\n { from: \"starting\", to: \"failed\", driver: \"run.local\" },\n { from: \"listening\", to: \"stopped\", driver: \"run.local\" },\n { from: \"listening\", to: \"failed\", driver: \"run.local\" },\n { from: \"stopped\", to: \"starting\", driver: \"run.local\" },\n { from: \"failed\", to: \"starting\", driver: \"run.local\" },\n // 注意:「进程重启后落盘仍是 listening 但本进程无 session → failed」复用上面\n // listening→failed 这条边,由 system driver 走(system 是恢复通道,见 transition.ts)。\n // 不再单列一条 driver:\"system\" 的同 from→to 规则,否则违反「同一边只能有一个 driver」。\n];\n\nexport const TRANSITIONS = {\n init: INIT,\n design: DESIGN,\n setup: SETUP,\n run: RUN,\n} as const;\n\n/**\n * **读时派生、不落盘、不走 transition()** 的 phase。\n *\n * relay 模式下运行状态的真源是服务端的 run_schedule 行(desired_state /\n * machine_id / 心跳),由 daemon 的心跳驱动。往 project_state 里再存一份就是\n * 第二个副本,两份一旦不一致就回到病根 5(run 三处真源、读时互相强改写)。\n * 所以这两个 phase 每次读取时从 schedule 现算。\n *\n * 代价:它们没有入边。这不是漏配 —— 迁移表测试会显式豁免本清单,新增派生\n * phase 必须在这里登记,否则测试失败。\n */\nexport const DERIVED_PHASES: { [K in keyof typeof TRANSITIONS]?: ReadonlyArray<string> } = {\n run: [\"awaiting_machine\", \"running_on_machine\"],\n};\n","/**\n * stage-machine/relay-run — 由调度快照派生 relay 模式的 run 状态\n *\n * relay 模式下引擎跑在用户机器的 daemon 上,运行状态的真源是服务端的\n * run_schedule 行(desired_state / machine_id / 心跳)。这里把它翻译成 phase,\n * **不落盘、不走 transition()**(见 transitions.ts 的 DERIVED_PHASES)。\n *\n * 原先这件事在前端做(reducers/run.ts:82-101 从 schedule 反推 checkpoint),\n * 服务端另有一份落盘 task,两份不一致时后写的赢 —— spec §1.2 病根 5。\n */\nimport type { RunScheduleDTO } from \"../server.js\";\nimport type { RunState } from \"../stage-state.js\";\n\nexport function deriveRelayRunState(schedule: RunScheduleDTO): RunState {\n if (schedule.desiredState === \"stopped\") return { phase: \"stopped\", reason: \"user\" };\n // 期望运行且有在线机器持租 → 真的在跑。\n if (schedule.machineId && schedule.online) {\n return { phase: \"running_on_machine\", schedule, machineId: schedule.machineId };\n }\n // 期望运行但无人认领(没有在线机器 / 全部满载),或持租机器心跳已过期。\n return { phase: \"awaiting_machine\", schedule, stalledSince: schedule.unassignedSince ?? null };\n}\n","/**\n * stage-machine/transition — 唯一可以写 stage state 的地方\n *\n * 存储层只负责存取,不做业务判断;所有状态变更必须经过这里,\n * 否则「非法组合」会重新出现(spec §1.2 病根 1/2)。\n */\nimport type { AnyStageState } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\nimport { TRANSITIONS, type DriverId } from \"./transitions.js\";\n\nexport type IllegalTransitionDetail = {\n stage: string;\n from: string | null;\n to: string;\n driver: DriverId;\n expectedDriver?: DriverId;\n};\n\nexport class IllegalStageTransitionError extends Error {\n readonly code = \"ILLEGAL_STAGE_TRANSITION\";\n\n constructor(readonly detail: IllegalTransitionDetail) {\n super(\n detail.expectedDriver\n ? `推进器越权:${detail.stage} ${detail.from ?? \"(none)\"}→${detail.to} 只能由 ${detail.expectedDriver} 推进,实际是 ${detail.driver}`\n : `非法状态迁移:${detail.stage} ${detail.from ?? \"(none)\"}→${detail.to}(driver=${detail.driver})`,\n );\n this.name = \"IllegalStageTransitionError\";\n }\n}\n\n/** 浅比较 payload:state 都是纯 JSON,JSON.stringify 足够且可预测。 */\nfunction sameState(a: AnyStageState, b: AnyStageState): boolean {\n return a.phase === b.phase && JSON.stringify(a) === JSON.stringify(b);\n}\n\nexport function transition<S extends AnyStageState>(\n stage: StageIdDTO,\n current: S | null,\n next: S,\n driver: DriverId,\n): S {\n if (current && sameState(current, next)) return current;\n\n const rules = TRANSITIONS[stage] as ReadonlyArray<{ from: string | null; to: string; driver: DriverId }>;\n const from = current?.phase ?? null;\n const rule = rules.find((candidate) => candidate.from === from && candidate.to === next.phase);\n\n if (!rule) throw new IllegalStageTransitionError({ stage, from, to: next.phase, driver });\n // system 是恢复通道(产物校验、会话丢失判定),不受 driver 归属约束。\n if (driver !== \"system\" && rule.driver !== driver) {\n throw new IllegalStageTransitionError({ stage, from, to: next.phase, driver, expectedDriver: rule.driver });\n }\n return next;\n}\n","/**\n * stage-machine/phase-kind — phase 是否「还在推进中」\n *\n * 这里取代了原先的 `statusOf()` / `TaskStatusDTO`。原设计把每个 phase 映射到\n * 六值枚举(idle/running/blocked/failed/succeeded/cancelled)供前端展示,代价是:\n *\n * 1. **一对多的有损压缩**:`stopped`(正常停机)与 `ready` 都压成 `succeeded`,\n * 前端想区分就得回头读 phase,于是同一个判断有两套写法;\n * 2. **两个值无人产出**:`idle` 的真实语义是「stages 里没这个 key」,`cancelled`\n * 从来没有 phase 产出过 —— 枚举比现实大,读代码的人无法判断哪些分支是死的;\n * 3. **诱导误用**:`blocked` 当前只由 `setup.auth_awaiting` 产生,于是\n * 「status === blocked」被当成「在等授权」用,多一个 blocked phase 就静默出错。\n *\n * 所以只留一张表和两个**布尔**谓词。布尔的好处是没有展示语义可借用:想在界面上\n * 区分「成功/失败/等用户」,只能 `switch (state.phase)`。\n *\n * 这两个谓词只服务两条结构规则,不服务展示:\n * - `activeStage` 指针该指向谁(project-state.ts 的 `nextActiveStage`);\n * - 一次写入是否开启新 attempt(server 的 `StageWriter`)。\n */\nimport type { AnyStageState, DesignState, InitState, RunState, SetupState } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\n\n/**\n * 「还在推进中」的 phase:有推进器正在干活,或正等用户输入。\n *\n * 只列 active,终态 = 补集(见 `isTerminalPhase`)—— 两张表就会出现「既不 active\n * 也不 terminal」或「两边都列」的第三种状态,而 phase 只有这两种命运。\n * 新增 phase 时这里漏登记会被 `Record<...>` 的穷尽性检查拦住,编译不过。\n */\nexport const ACTIVE_PHASES: {\n init: Record<InitState[\"phase\"], boolean>;\n design: Record<DesignState[\"phase\"], boolean>;\n setup: Record<SetupState[\"phase\"], boolean>;\n run: Record<RunState[\"phase\"], boolean>;\n} = {\n init: {\n configuring: true,\n ready: false,\n failed: false,\n },\n design: {\n clarify_probing: true,\n // 等用户填澄清卡片也算推进中:activeStage 必须还指向 design,\n // 否则刷新后前端会认为设计阶段已经结束。\n clarify_awaiting: true,\n clarify_failed: false,\n precheck: true,\n generating: true,\n validating: true,\n syncing_schema: true,\n ready: false,\n generate_failed: false,\n },\n setup: {\n preflight: true,\n provisioning_table: true,\n syncing_schema: true,\n // 同上:等用户授权时阶段没结束。\n auth_awaiting: true,\n ready: false,\n failed: false,\n },\n run: {\n starting: true,\n listening: true,\n stopped: false,\n failed: false,\n // relay 模式的两个派生 phase:机器还没认领也算在跑,指针不能摘。\n awaiting_machine: true,\n running_on_machine: true,\n },\n};\n\n/** 该 phase 是否还在推进中(含等待用户输入)。 */\nexport function isActivePhase(stage: StageIdDTO, state: AnyStageState): boolean {\n const table = ACTIVE_PHASES[stage] as Record<string, boolean | undefined>;\n const active = table[state.phase];\n if (active === undefined) throw new Error(`phase 未登记推进中/终态:${stage}.${state.phase}`);\n return active;\n}\n\n/** 该 phase 是否已到终态(成功或失败都算)。 */\nexport function isTerminalPhase(stage: StageIdDTO, state: AnyStageState): boolean {\n return !isActivePhase(stage, state);\n}\n","/**\n * stage-machine/phase-groups — 跨端共享的 phase 分组\n *\n * 与 phase-kind.ts 的区别:那边是**结构规则**(指针 / attempt),只出布尔且只给\n * 状态机自己用;这里是**业务语义**分组,服务端与前端都要问同一个问题。\n *\n * 放在 core 是因为两边写两份就会分叉:`isDesignGenerating` 原先只有前端有\n * (web/state/view-model.ts),服务端的重入守卫改用 `statusOf(...) === \"running\"`\n * 近似表达同一件事,于是 `clarify_awaiting` 该不该拦在两处有两个答案。\n *\n * 分组只按 phase 判断,不引入中间枚举 —— 有了中间枚举就会有人拿它做展示分支。\n */\nimport type { DesignState } from \"../stage-state.js\";\n\n/**\n * design 的「生成流水线正在跑」:确认需求之后、产物落地之前。\n *\n * 不含 `clarify_probing` 与 `clarify_awaiting`:那两个是澄清回合,重新澄清是合法操作。\n */\nexport const DESIGN_GENERATING_PHASES = [\n \"precheck\",\n \"generating\",\n \"validating\",\n \"syncing_schema\",\n] as const satisfies ReadonlyArray<DesignState[\"phase\"]>;\n\n/** 生成流水线内的 design 状态。由上面那份清单推出来,避免清单与类型两处维护。 */\nexport type DesignGeneratingState = Extract<DesignState, { phase: typeof DESIGN_GENERATING_PHASES[number] }>;\n\n// 返回类型是类型守卫而不是 boolean:调用方多数要接着用 state(回 409、回当前状态),\n// 守卫顺手把 null 收掉,省掉一个「理论上不可能为空」的非空断言。\nexport function isDesignGenerating(\n state: DesignState | null | undefined,\n): state is DesignGeneratingState {\n return !!state && (DESIGN_GENERATING_PHASES as ReadonlyArray<string>).includes(state.phase);\n}\n\n/** design 的两个失败终态。合成一个谓词是因为界面与重试入口对两者的处理一致。 */\nexport function isDesignFailed(\n state: DesignState | null | undefined,\n): state is Extract<DesignState, { error: unknown }> {\n return state?.phase === \"clarify_failed\" || state?.phase === \"generate_failed\";\n}\n","/**\n * stage-machine/next — 服务端算「下一步该做什么」并下发\n *\n * 取代前端的 deriveStream(它只看 status==='running' 就去接流,是 Bug A 的引爆点)。\n * 沿用仓库既有哲学:恢复接口的 path 属服务端策略,客户端不自己拼\n * (见 ../server.ts 的 RecoveryActionDTO 注释)。\n */\nimport type { AnyStageState, DesignState, InitState, RunState, SetupState, StageNextDTO } from \"../stage-state.js\";\nimport type { StageIdDTO } from \"../server.js\";\n\nconst POLL_MS = 2000;\n\n/** 服务端才知道的运行期事实。 */\nexport type NextRuntimeFacts = {\n /** 该 stage 的活跃 SSE 会话是否在本进程 */\n sessionLocal: boolean;\n /** 会话事件游标;续流时下发,客户端据此重放 */\n cursor?: number;\n};\n\nexport type NextInput = {\n projectId: string;\n stage: StageIdDTO;\n state: AnyStageState;\n facts: NextRuntimeFacts;\n};\n\nexport function nextFor(input: NextInput): StageNextDTO {\n switch (input.stage) {\n case \"init\":\n return initNext(input.projectId, input.state as InitState);\n case \"design\":\n return designNext(input.projectId, input.state as DesignState, input.facts);\n case \"setup\":\n return setupNext(input.projectId, input.state as SetupState, input.facts);\n case \"run\":\n return runNext(input.projectId, input.state as RunState, input.facts);\n }\n}\n\nfunction initNext(projectId: string, state: InitState): StageNextDTO {\n switch (state.phase) {\n case \"configuring\":\n // init 是同步 POST,没有流。刷新页面时轮询到落地为止。\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"no_stream_for_phase\" };\n case \"ready\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新初始化\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/init` },\n },\n };\n }\n}\n\nfunction setupNext(projectId: string, state: SetupState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"preflight\":\n case \"provisioning_table\":\n case \"syncing_schema\":\n return streamOrPoll(`/api/projects/${projectId}/setup`, facts);\n case \"auth_awaiting\":\n return {\n kind: \"await_user\",\n action: {\n type: \"continue\",\n label: \"已完成授权,继续搭建\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/setup/continue` },\n },\n };\n case \"ready\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重试搭建\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/setup/retry` },\n },\n };\n }\n}\n\nfunction runNext(projectId: string, state: RunState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"starting\":\n case \"listening\":\n return streamOrPoll(`/api/projects/${projectId}/run`, facts);\n // relay 模式:执行在用户机器上,本进程永远没有流可接,只能轮询服务端投影。\n case \"awaiting_machine\":\n case \"running_on_machine\":\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"running_elsewhere\" };\n case \"stopped\":\n return { kind: \"idle\" };\n case \"failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重试运行\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/tasks/run/retry` },\n },\n };\n }\n}\n\n/** 该 phase 本该有流:会话在本进程就续流,否则只能轮询。 */\nfunction streamOrPoll(endpoint: string, facts: NextRuntimeFacts): StageNextDTO {\n return facts.sessionLocal\n ? { kind: \"attach_stream\", endpoint, ...(facts.cursor === undefined ? {} : { cursor: facts.cursor }) }\n : { kind: \"poll\", intervalMs: POLL_MS, why: \"session_not_local\" };\n}\n\n/**\n * design 的生成流:**永远让客户端开流**,本进程有没有会话只决定要不要带重放游标。\n *\n * 不能像 setup/run 那样用 `streamOrPoll`:`POST /design` 既是「续流」也是「开始生成」\n * (`handleDesign` 自己先查 `designSessions` 决定重放还是新起),而 `precheck` 的语义\n * 恰恰是「澄清已确认、等客户端开流」—— 此刻本进程必然还没有会话。判成 poll 就是死锁:\n * 没有人去开流,phase 永远停在 precheck,客户端每 2 秒拉回同一份状态。\n * 这正是重构后线上的现象:澄清一结束,设计再也不会开始。\n *\n * `generating` / `validating` / `syncing_schema` 没有本地会话时同理。服务端对这种情况\n * 有专门的恢复分支(先落一条 `DESIGN_SESSION_LOST` 再回 `precheck` 重跑,见\n * `packages/server/src/design/design-routes.ts` 的 handleDesign),那段代码只有客户端\n * 重新 POST 才会被执行到 —— 判成 poll 会让它永远不可达,界面同样冻住。\n */\nfunction designStream(endpoint: string, facts: NextRuntimeFacts): StageNextDTO {\n return { kind: \"attach_stream\", endpoint, ...(facts.cursor === undefined ? {} : { cursor: facts.cursor }) };\n}\n\nfunction designNext(projectId: string, state: DesignState, facts: NextRuntimeFacts): StageNextDTO {\n switch (state.phase) {\n case \"clarify_probing\":\n // 归 POST /design/discovery 推进,本来就没有流可接。\n return { kind: \"poll\", intervalMs: POLL_MS, why: \"no_stream_for_phase\" };\n case \"clarify_awaiting\":\n return {\n kind: \"await_user\",\n action: {\n type: \"confirm_input\",\n label: \"确认后开始生成\",\n description: \"请先确认需求澄清卡片,再开始生成工作流方案。\",\n api: { method: \"POST\", path: `/api/projects/${projectId}/design/discovery/confirm` },\n },\n };\n case \"precheck\":\n case \"generating\":\n case \"validating\":\n case \"syncing_schema\":\n return designStream(`/api/projects/${projectId}/design`, facts);\n case \"ready\":\n return { kind: \"idle\" };\n case \"clarify_failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新开始需求澄清\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/design/discovery` },\n },\n };\n case \"generate_failed\":\n return {\n kind: \"await_user\",\n action: {\n type: \"retry\",\n label: \"重新生成工作流设计\",\n description: state.error.message,\n api: { method: \"POST\", path: `/api/projects/${projectId}/design` },\n },\n };\n }\n}\n","/**\n * stage-machine/recover — 产物校验驱动的 design 状态恢复\n *\n * 收掉原先两处各 58 行的重复实现:\n * packages/server/src/repository/workflow-instance-store.ts:204-261\n * apps/gateway/src/infra/pg-workflow-instance-store.ts:227-284\n *\n * 与原实现的关键差异(spec §5,修 Bug C):**只有终态可被恢复覆盖**。\n * 原实现会把 blocked/running 的 design 任务按磁盘 manifest 强改成 succeeded,\n * 导致澄清期刷新被抹成「设计已完成」。\n *\n * 返回 null = 不需要改动,调用方不要落盘。\n */\nimport type { DesignArtifactValidationDTO } from \"../server.js\";\nimport type { DesignState } from \"../stage-state.js\";\nimport { transition } from \"./transition.js\";\n\nexport function recoverDesignFromArtifacts(\n current: DesignState | null,\n validation: DesignArtifactValidationDTO,\n): DesignState | null {\n if (!current) return null;\n // 非终态一律不动:运行中与等待用户的状态不可被产物校验覆盖。\n if (current.phase !== \"ready\" && current.phase !== \"generate_failed\") return null;\n\n if (validation.status === \"ready\") {\n if (current.phase === \"ready\") return null;\n if (!validation.manifest) return null;\n return transition<DesignState>(\n \"design\",\n current,\n // 从磁盘产物恢复时轮次已无从得知:manifest 不记轮数。记 0 表示「未知」,\n // 比编一个数字诚实。\n { phase: \"ready\", confirmed: current.confirmed, manifest: validation.manifest, rounds: 0 },\n \"system\",\n );\n }\n\n if (current.phase === \"generate_failed\") return null;\n return transition<DesignState>(\n \"design\",\n current,\n {\n phase: \"generate_failed\",\n // ready 也带 confirmed,所以恢复到 failed 不丢澄清上下文,\n // generate_failed→precheck 的重试路径仍然可用。\n confirmed: current.confirmed,\n error: {\n code: validation.status === \"artifact_missing\" ? \"ARTIFACT_MISSING\" : \"DESIGN_ARTIFACT_INVALID\",\n message: validation.errors.join(\";\") || \"设计产物不完整,请重新生成工作流设计。\",\n retryable: true,\n details: { missing: validation.missing },\n },\n },\n \"system\",\n );\n}\n","/**\n * stage-machine/project-state — 项目状态的装配与查询(纯函数)\n *\n * 原先住在 `packages/server/src/stage-runtime/task-state.ts`,被 gateway 反向\n * 从 `aitable-workflow-server` 导入(接缝方向反了)。搬到 core 后\n * server 与 gateway 都从 core 取。\n *\n * 这里只做装配与查询,**不做状态判断**——「能不能从 A 到 B」在 transition.ts。\n */\nimport type { ProjectDTO, StageIdDTO } from \"../server.js\";\nimport type {\n AnyStageState,\n ProjectStateDTO,\n ProjectStateResponseDTO,\n StageStateDTO,\n StageNextDTO,\n} from \"../stage-state.js\";\nimport { isActivePhase } from \"./phase-kind.js\";\n\nexport function emptyProjectState(project: ProjectDTO): ProjectStateDTO {\n return {\n projectId: project.id,\n activeStage: null,\n stages: {},\n updatedAt: project.updatedAt,\n };\n}\n\n/**\n * 某阶段变成 `state` 之后,`activeStage` 指针该指向谁。\n *\n * 指针必须跟着状态走:阶段转终态时若不摘掉指针,`state.stages[activeStage]`\n * 就指向一条已结束的阶段,调用方误判成还在跑。\n *\n * 单独导出是因为前端 reducer 收到 `stage.changed` 时要做同一件事\n * (`packages/web/src/state/reducers/task-state.ts`)。这条规则写两份,\n * 两份就会分叉——那正是本次重构要消灭的病根。\n */\nexport function nextActiveStage(\n current: StageIdDTO | null,\n stage: StageIdDTO,\n state: AnyStageState,\n): StageIdDTO | null {\n if (isActivePhase(stage, state)) return stage;\n return current === stage ? null : current;\n}\n\n/** 把一个阶段的新状态并进项目状态,并同步 `activeStage` 指针。 */\nexport function mergeStageIntoState(\n state: ProjectStateDTO,\n stage: StageIdDTO,\n stageState: StageStateDTO,\n): ProjectStateDTO {\n const stages = { ...state.stages, [stage]: stageState } as ProjectStateDTO[\"stages\"];\n return {\n ...state,\n stages,\n activeStage: nextActiveStage(state.activeStage, stage, stageState.state),\n updatedAt: stageState.identity.updatedAt,\n };\n}\n\n/**\n * 落盘数据补形。缺字段按空状态兜底,`activeStage` 与 `stages` 不一致时\n * 以 `stages` 为准重算——指针是派生量,状态才是真源。\n */\nexport function normalizeProjectState(project: ProjectDTO, value: Partial<ProjectStateDTO>): ProjectStateDTO {\n const stages = value.stages ?? {};\n return {\n projectId: value.projectId ?? project.id,\n activeStage: findActiveStage(stages),\n stages,\n runSnapshot: value.runSnapshot,\n updatedAt: value.updatedAt ?? project.updatedAt,\n };\n}\n\n/** 某个阶段的状态;该阶段从未跑过时为 null。 */\nexport function stageStateOf<S extends AnyStageState>(\n state: ProjectStateDTO,\n stage: StageIdDTO,\n): S | null {\n return (state.stages[stage]?.state as S | undefined) ?? null;\n}\n\n/**\n * 读时给每个阶段算 `next`,把落盘形状变成上行形状。\n *\n * `next` 不落盘(见 StageStateDTO 的注释),所以每条上行路径——`GET /state`\n * 与 SSE 的 `stage.changed`——都必须经这里,否则两处会给出不一致的指令。\n */\nexport function withStageNext(\n state: ProjectStateDTO,\n nextOf: (stage: StageIdDTO, stageState: StageStateDTO) => StageNextDTO,\n): ProjectStateResponseDTO {\n const stages: ProjectStateResponseDTO[\"stages\"] = {};\n for (const [stage, stageState] of Object.entries(state.stages) as Array<[StageIdDTO, StageStateDTO | undefined]>) {\n if (!stageState) continue;\n Object.assign(stages, { [stage]: { ...stageState, next: nextOf(stage, stageState) } });\n }\n return { ...state, stages };\n}\n\nfunction findActiveStage(stages: ProjectStateDTO[\"stages\"]): StageIdDTO | null {\n for (const [stage, stageState] of Object.entries(stages) as Array<[StageIdDTO, StageStateDTO | undefined]>) {\n if (stageState && isActivePhase(stage, stageState.state)) return stage;\n }\n return null;\n}\n","/**\n * discovery/visibility — 条件显示求值(单一真源)\n *\n * `FormQuestion.showIf` 的语义必须在四个消费方完全一致:\n * CLI 渲染(cli-form)、答案序列化(serialize/brief)、服务端必填校验\n * (design-routes)、Web 渲染(packages/web 的 QuestionForm)。\n * 任何一方自己实现一遍都会导致「CLI 不问但服务端要求必填」这类错位,\n * 所以统一放这里,并从 core barrel 导出给 web 用。\n *\n * 纯函数,无 IO。\n */\n\nimport type { FormAnswerValue, FormAnswers, FormQuestion, QuestionForm } from \"./types.js\";\n\n/** 答案是否「包含」指定值:数组按成员判断,标量按相等判断(布尔按字符串化) */\nfunction answerContains(value: FormAnswerValue | undefined, expected: string): boolean {\n if (value === undefined) return false;\n if (Array.isArray(value)) return value.includes(expected);\n if (typeof value === \"boolean\") return String(value) === expected;\n return value === expected;\n}\n\n/**\n * 该题当前是否应该提问。\n *\n * 无 `showIf` → 恒可见。依赖题尚未作答(answers 里没有)→ 不可见:\n * 逐题揭示时依赖题一定先于被依赖题作答,所以这个方向是安全的默认。\n */\nexport function isQuestionVisible(question: Pick<FormQuestion, \"showIf\">, answers: FormAnswers): boolean {\n if (!question.showIf) return true;\n return answerContains(answers[question.showIf.questionId], question.showIf.hasValue);\n}\n\n/** 当前答案下应提问的题目列表(保持原顺序) */\nexport function visibleQuestions(form: QuestionForm, answers: FormAnswers): FormQuestion[] {\n return form.questions.filter((question) => isQuestionVisible(question, answers));\n}\n\n/** 剔除不可见题的答案:不可见题不该进入 brief,也不该被服务端校验 */\nexport function pruneHiddenAnswers(form: QuestionForm, answers: FormAnswers): FormAnswers {\n const pruned: FormAnswers = {};\n for (const question of visibleQuestions(form, answers)) {\n const value = answers[question.id];\n if (value !== undefined) pruned[question.id] = value;\n }\n return pruned;\n}\n","/**\n * discovery/validate-answers — 澄清答案校验\n *\n * Agent 现在自己出题(含 required / maxSelections),答案从哪来就都得校一遍:\n * Web 走 HTTP 边界、CLI 走终端,两侧共用这一份实现,避免规则漂移。\n *\n * 只校可见题:showIf 未命中的题从未被问,缺答案是正常的。\n */\n\nimport type { FormAnswerValue, FormAnswers, QuestionForm } from \"./types.js\";\nimport { visibleQuestions } from \"./visibility.js\";\n\n/** 校验规则标识:文案按消费端决定(服务端要带题干,web 字段内联要短),规则只有一份。 */\nexport type AnswerIssueCode = \"required\" | \"max_selections\" | \"not_in_options\";\n\nexport interface AnswerValidationIssue {\n questionId: string;\n code: AnswerIssueCode;\n message: string;\n}\n\n/** 逐题校验答案;返回空数组表示通过 */\nexport function validateFormAnswers(form: QuestionForm, answers: FormAnswers): AnswerValidationIssue[] {\n const issues: AnswerValidationIssue[] = [];\n\n for (const question of visibleQuestions(form, answers)) {\n const value = answers[question.id];\n\n if (question.required && isBlankAnswer(value)) {\n issues.push({ questionId: question.id, code: \"required\", message: `「${question.label}」为必填项` });\n continue;\n }\n\n if (\n question.maxSelections !== undefined &&\n Array.isArray(value) &&\n value.length > question.maxSelections\n ) {\n issues.push({\n questionId: question.id,\n code: \"max_selections\",\n message: `「${question.label}」最多选 ${question.maxSelections} 项,实际 ${value.length} 项`,\n });\n }\n\n // 闭集题(显式 allowCustom: false)才校验取值:默认允许自定义,自由输入照常放行。\n if (question.allowCustom === false && question.options?.length && !isBlankAnswer(value)) {\n const allowed = new Set(question.options.map((option) => option.value));\n const picked = Array.isArray(value) ? value : [String(value)];\n const unknown = picked.filter((item) => !allowed.has(item));\n if (unknown.length > 0) {\n issues.push({\n questionId: question.id,\n code: \"not_in_options\",\n message: `「${question.label}」只能从给定选项中选择,收到:${unknown.join(\"、\")}`,\n });\n }\n }\n }\n\n return issues;\n}\n\nfunction isBlankAnswer(value: FormAnswerValue | undefined): boolean {\n if (value === undefined) return true;\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false; // boolean 永远算已回答\n}\n","/**\n * discovery/serialize — 答案序列化\n *\n * 保留原始问卷序列化能力,主要用于 CLI 调试和历史兼容;\n * Web 真实模式注入 Coding Agent 前会优先使用 summarizeForms / LLM brief,\n * 避免把 `[form answers]` 原始格式直接塞进 meta-prompt。\n * 纯函数,便于单测。\n */\n\nimport type {\n FilledForm,\n FormAnswerValue,\n FormAnswers,\n FormQuestion,\n} from \"./types.js\";\nimport { visibleQuestions } from \"./visibility.js\";\n\n/** 序列化单轮表单答案(跳过 showIf 未命中的题) */\nexport function serializeForm(form: FilledForm[\"form\"], answers: FormAnswers): string {\n const lines = [`[form answers — ${form.id}]`];\n for (const q of visibleQuestions(form, answers)) {\n const display = formatAnswerDisplay(q, answers[q.id]);\n lines.push(`- ${q.label}: ${display}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** 序列化多轮表单,段落之间空一行 */\nexport function serializeForms(filled: FilledForm[]): string {\n return filled\n .map(({ form, answers }) => serializeForm(form, answers))\n .join(\"\\n\\n\");\n}\n\n/**\n * 把表单答案汇总成一段简洁的自然语言约束(用于注入 Coding Agent prompt)。\n * 避免输出原始的 `[form answers — id]` 问卷格式,减少 prompt 噪音。\n *\n * 只输出实际提问过的题:showIf 未命中的题从未被问,标成 \"(skipped)\" 会让\n * Agent 误读为「用户明确拒绝回答」。\n */\nexport function summarizeForms(filled: FilledForm[]): string {\n const lines = [\"用户已确认:\"];\n for (const { form, answers } of filled) {\n for (const q of visibleQuestions(form, answers)) {\n lines.push(`- ${q.label}:${formatAnswerDisplay(q, answers[q.id])}`);\n }\n }\n return lines.join(\"\\n\");\n}\n\n/** 格式化单个答案的展示文本 */\nexport function formatAnswerDisplay(q: FormQuestion, value: FormAnswerValue | undefined): string {\n if (value === undefined || isEmpty(value)) return \"(skipped)\";\n\n switch (q.type) {\n case \"switch\":\n return toBool(value) ? \"是\" : \"否\";\n\n case \"radio\": {\n const v = String(value);\n const opt = q.options?.find((o) => o.value === v);\n if (opt && opt.label !== opt.value) return `${opt.label} [value: ${opt.value}]`;\n return v;\n }\n\n case \"checkbox\": {\n const arr = Array.isArray(value) ? value : [String(value)];\n const labels = arr.map((v) => {\n const opt = q.options?.find((o) => o.value === v);\n return opt?.label ?? v;\n });\n return labels.join(\", \");\n }\n\n default:\n return String(value);\n }\n}\n\n// ─── 辅助 ────────────────────────────────────────────────\n\nfunction isEmpty(value: FormAnswerValue): boolean {\n if (typeof value === \"string\") return value.trim() === \"\";\n if (Array.isArray(value)) return value.length === 0;\n return false; // boolean 永远算已回答\n}\n\nfunction toBool(value: FormAnswerValue): boolean {\n if (typeof value === \"boolean\") return value;\n return String(value) === \"true\" || value === \"是\";\n}\n","export type RunRecordStatus = \"todo\" | \"running\" | \"review\" | \"done\" | \"blocked\";\n\nexport type RunSnapshot = {\n records: Array<{\n id: string;\n title: string;\n state: string;\n status: RunRecordStatus;\n currentStepId: string | null;\n doneStepIds: string[];\n createdAt: number;\n updatedAt: number;\n }>;\n logs: Array<{\n id: string;\n ts: number;\n itemId: string;\n stepId: string;\n level: \"info\" | \"ai\" | \"ok\" | \"warn\" | \"error\";\n text: string;\n }>;\n};\n\nexport type RunExecutionEvent =\n | { type: \"run.record.detected\"; record: { id: string; title: string; state: string; currentStep: string | null } }\n | { type: \"run.step.started\"; itemId: string; stepId: string; stepName: string }\n | { type: \"run.step.succeeded\"; itemId: string; stepId: string; stepName: string; logs?: string[] }\n | { type: \"run.step.failed\"; itemId: string; stepId: string; stepName: string; error: string; logs?: string[] }\n | { type: \"run.record.blocked\"; itemId: string; stepId: string; stepName: string; reason: string; logs?: string[] }\n | { type: \"run.record.completed\"; itemId: string };\n\nexport type RunSnapshotLogLevel = RunSnapshot[\"logs\"][number][\"level\"];\nexport type RunSnapshotStepLevelResolver = (\n stepId: string,\n event: Extract<RunExecutionEvent, { type: \"run.step.started\" }>,\n) => RunSnapshotLogLevel;\n\nexport type ApplyRunExecutionEventOptions = {\n stepLevel?: RunSnapshotStepLevelResolver;\n now?: () => number;\n id?: () => string;\n maxLogs?: number;\n};\n\nconst DEFAULT_MAX_LOGS = 300;\n\nexport function applyRunExecutionEvent(\n snapshot: RunSnapshot,\n event: RunExecutionEvent,\n options: ApplyRunExecutionEventOptions = {},\n): RunSnapshot {\n const context = createContext(options);\n\n if (event.type === \"run.record.detected\") {\n return {\n records: upsertRunRecord(snapshot.records, {\n id: event.record.id,\n title: event.record.title || event.record.id,\n state: event.record.state,\n status: event.record.currentStep ? \"running\" : \"todo\",\n currentStepId: event.record.currentStep,\n doneStepIds: [],\n createdAt: context.now(),\n updatedAt: context.now(),\n }),\n logs: appendRunLog(snapshot.logs, event.record.id, event.record.currentStep ?? \"run\", \"info\", `发现待处理记录:${event.record.title || event.record.id}`, context),\n };\n }\n\n if (event.type === \"run.step.started\") {\n const level = options.stepLevel?.(event.stepId, event) ?? \"ai\";\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : \"running\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n updatedAt: context.now(),\n })),\n logs: appendRunLog(snapshot.logs, event.itemId, event.stepId, level, `开始执行步骤:${event.stepName}`, context),\n };\n }\n\n if (event.type === \"run.step.succeeded\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n doneStepIds: addDoneStep(record.doneStepIds, event.stepId),\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"ok\", `步骤完成:${event.stepName}`, context),\n event.itemId,\n event.stepId,\n \"info\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.step.failed\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : \"blocked\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"error\", `步骤失败:${event.stepName} · ${event.error}`, context),\n event.itemId,\n event.stepId,\n \"error\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.record.blocked\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: record.status === \"done\" ? record.status : event.reason.includes(\"人工\") ? \"review\" : \"blocked\",\n currentStepId: record.status === \"done\" ? record.currentStepId : event.stepId,\n doneStepIds: addDoneStep(record.doneStepIds, event.stepId),\n updatedAt: context.now(),\n })),\n logs: appendRunLogs(\n appendRunLog(snapshot.logs, event.itemId, event.stepId, \"warn\", `${event.stepName}:${event.reason}`, context),\n event.itemId,\n event.stepId,\n \"info\",\n event.logs,\n context,\n ),\n };\n }\n\n if (event.type === \"run.record.completed\") {\n return {\n records: updateRunRecord(snapshot.records, event.itemId, (record) => ({\n ...record,\n status: \"done\",\n currentStepId: null,\n updatedAt: context.now(),\n })),\n logs: appendRunLog(snapshot.logs, event.itemId, \"run\", \"ok\", \"流程完成\", context),\n };\n }\n\n return snapshot;\n}\n\n/**\n * 直接改写某条工单(不经事件)。relay 模式的服务端投影用它把 daemon 上报的\n * 状态落进快照。\n *\n * 原先在 `workflow-instance-store.ts:174-202` 与\n * `pg-workflow-instance-store.ts:312-338` 各有一份,连 `slice(-300)` 与\n * 日志 id 生成都逐字重复。\n */\nexport function patchRunSnapshotRecord(\n snapshot: RunSnapshot,\n itemId: string,\n patch: Partial<Pick<RunSnapshot[\"records\"][number], \"state\" | \"status\" | \"currentStepId\">>,\n logText?: string,\n options: ApplyRunExecutionEventOptions = {},\n): RunSnapshot {\n const context = createContext(options);\n return {\n records: updateRunRecord(snapshot.records, itemId, (record) => ({\n ...record,\n ...patch,\n updatedAt: context.now(),\n })),\n logs: logText\n ? appendRunLog(snapshot.logs, itemId, patch.currentStepId ?? \"run\", \"info\", logText, context)\n : snapshot.logs,\n };\n}\n\ntype ApplyContext = {\n now: () => number;\n id: () => string;\n maxLogs: number;\n};\n\nfunction createContext(options: ApplyRunExecutionEventOptions): ApplyContext {\n return {\n now: options.now ?? Date.now,\n id: options.id ?? createRunLogId,\n maxLogs: options.maxLogs ?? DEFAULT_MAX_LOGS,\n };\n}\n\nfunction upsertRunRecord(records: RunSnapshot[\"records\"], next: RunSnapshot[\"records\"][number]): RunSnapshot[\"records\"] {\n const index = records.findIndex((record) => record.id === next.id);\n if (index < 0) return [...records, next];\n return records.map((record) => record.id === next.id ? {\n ...record,\n ...next,\n doneStepIds: record.doneStepIds,\n status: record.status === \"done\" ? record.status : next.status,\n currentStepId: record.status === \"done\" ? record.currentStepId : next.currentStepId,\n createdAt: record.createdAt,\n } : record);\n}\n\nfunction updateRunRecord(\n records: RunSnapshot[\"records\"],\n id: string,\n update: (record: RunSnapshot[\"records\"][number]) => RunSnapshot[\"records\"][number],\n): RunSnapshot[\"records\"] {\n return records.map((record) => record.id === id ? update(record) : record);\n}\n\nfunction addDoneStep(doneStepIds: string[], stepId: string): string[] {\n return doneStepIds.includes(stepId) ? doneStepIds : [...doneStepIds, stepId];\n}\n\nfunction appendRunLog(\n logs: RunSnapshot[\"logs\"],\n itemId: string,\n stepId: string,\n level: RunSnapshotLogLevel,\n text: string,\n context: ApplyContext,\n): RunSnapshot[\"logs\"] {\n const exists = logs.some((log) =>\n log.itemId === itemId &&\n log.stepId === stepId &&\n log.level === level &&\n log.text === text\n );\n if (exists) return logs;\n return [...logs, {\n id: context.id(),\n ts: context.now(),\n itemId,\n stepId,\n level,\n text,\n }].slice(-context.maxLogs);\n}\n\nfunction appendRunLogs(\n logs: RunSnapshot[\"logs\"],\n itemId: string,\n stepId: string,\n level: RunSnapshotLogLevel,\n texts: string[] | undefined,\n context: ApplyContext,\n): RunSnapshot[\"logs\"] {\n if (!texts?.length) return logs;\n return texts.reduce((next, text) => appendRunLog(next, itemId, stepId, level, text, context), logs);\n}\n\nfunction createRunLogId(): string {\n return `run_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 7)}`;\n}\n"],"mappings":"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","names":["path","name","text"]}
|
|
@@ -481,9 +481,35 @@ type RunExecutionEventDTO = {
|
|
|
481
481
|
type: "run.record.completed";
|
|
482
482
|
itemId: string;
|
|
483
483
|
};
|
|
484
|
+
/**
|
|
485
|
+
* run 阶段的逐轮 Agent 工具流(对标 `design.agent_event`,事件源换成 orchestrator)。
|
|
486
|
+
*
|
|
487
|
+
* 定位是**高频纯流水**:只供 AgentTracePanel 直播「第 N 轮 thinking → tool_use → tool_result」。
|
|
488
|
+
* 刻意**不进** `RunExecutionEventDTO` —— 后者是 runSnapshot(`state.json`)的增量,落盘的是工单状态
|
|
489
|
+
* 与步骤时间线;工具流正文一旦进快照就会撑爆 `state.json`,且服务重启后也无从回看(不持久化)。
|
|
490
|
+
* 因此 `kind` 只保留展示必需的几种(含正文的 `text`/`thinking` 与收尾的 `result` 摘要),
|
|
491
|
+
* 正文长度在 core 侧已截断。
|
|
492
|
+
* - `text`:agent 的可见回复(流式文本块),本机 8779 据此渲染正文气泡(不落服务端)。
|
|
493
|
+
*/
|
|
494
|
+
type RunAgentEventDTO = {
|
|
495
|
+
type: "run.agent_event";
|
|
496
|
+
itemId: string;
|
|
497
|
+
stepId: string;
|
|
498
|
+
/** 本次 Coding Agent 调用内的工具轮次(首个 tool_use 起算,恒 >= 1)。 */
|
|
499
|
+
round: number;
|
|
500
|
+
kind: "text" | "thinking" | "tool_use" | "tool_result" | "result";
|
|
501
|
+
/** text 正文 / thinking 正文 / tool_result 正文 / result 最终回答摘要(均已截断)。 */
|
|
502
|
+
text?: string;
|
|
503
|
+
/** tool_use 的工具名。 */
|
|
504
|
+
tool?: string;
|
|
505
|
+
/** tool_use 的入参摘要(已截断)。 */
|
|
506
|
+
args?: string;
|
|
507
|
+
};
|
|
484
508
|
type RunEventDTO = StageChangedEventDTO
|
|
485
509
|
/** 工单级流水:不是阶段状态,是 runSnapshot 的增量。 */
|
|
486
|
-
| RunExecutionEventDTO
|
|
510
|
+
| RunExecutionEventDTO
|
|
511
|
+
/** 逐轮工具流:直播专用,不进快照、不参与状态归约。 */
|
|
512
|
+
| RunAgentEventDTO | {
|
|
487
513
|
type: "error";
|
|
488
514
|
error: ApiError["error"];
|
|
489
515
|
};
|
|
@@ -705,4 +731,4 @@ type ProjectStateResponseDTO = Omit<ProjectStateDTO, "stages"> & {
|
|
|
705
731
|
};
|
|
706
732
|
};
|
|
707
733
|
|
|
708
|
-
export type {
|
|
734
|
+
export type { RunRecordSnapshotDTO as $, AgentEvent as A, InitConfigDTO as B, CreateProjectRequestDTO as C, DesignState as D, InitResponseDTO as E, FormQuestion as F, PollReason as G, ProjectExportDTO as H, InitState as I, ProjectFileDTO as J, KnownAgentType as K, ProjectWorkflowDTO as L, ProjectsResponseDTO as M, QuestionType as N, RecoveryActionDTO as O, ProjectDTO as P, QuestionForm as Q, RunState as R, StageTaskIdentityDTO as S, ResetWorkflowResponseDTO as T, RoleConfigDTO as U, RunAgentEventDTO as V, RunControlResultDTO as W, RunEventDTO as X, RunExecutionEventDTO as Y, RunModeDTO as Z, RunRecordDTO as _, SetupState as a, RunRecordStatusDTO as a0, RunRecordsPageDTO as a1, RunSnapshotDTO as a2, ScaffoldResult as a3, SetupEventDTO as a4, SetupSchemaSummaryDTO as a5, ShowIfCondition as a6, StageChangedEventDTO as a7, StageDiagnosticsDTO as a8, StageErrorDTO as a9, StageStateResponseDTO as aa, WorkflowFilesResponseDTO as ab, WorkflowsResponseDTO as ac, RunScheduleDTO as b, AnyStageState as c, StageIdDTO as d, StageNextDTO as e, DesignArtifactValidationDTO as f, ProjectStateDTO as g, StageStateDTO as h, ProjectStateResponseDTO as i, FormAnswers as j, FormAnswerValue as k, FilledForm as l, AgentEventType as m, AgentIdDTO as n, AgentType as o, ApiError as p, CreateWorkflowRequestDTO as q, CreateWorkflowResponseDTO as r, DeleteProjectResponseDTO as s, DesignArtifactManifestDTO as t, DesignConfirmedDTO as u, DesignEventDTO as v, FilesPatchDTO as w, FilesReplaceDTO as x, FilesResponseDTO as y, FormOption as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aitable-workflow-base",
|
|
3
|
-
"version": "0.1.28
|
|
3
|
+
"version": "0.1.28",
|
|
4
4
|
"description": "与运行环境无关的公共代码:前后端共享的 HTTP/SSE 契约、Stage × Phase 状态机、澄清表单语义、运行态 reducer。不依赖 Node 也不依赖 DOM,因此两端都能直接消费。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|