paseo-plugin-zcode-provider 0.0.0-stage → 0.2.0

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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +29 -0
  3. package/README.md +112 -2
  4. package/client/account.tsx +827 -0
  5. package/client/diagnostics.tsx +379 -0
  6. package/client/runtime.tsx +296 -0
  7. package/client/settings.tsx +84 -0
  8. package/client/value.tsx +20 -0
  9. package/icon.svg +1 -0
  10. package/index.client.tsx +22 -0
  11. package/index.server.ts +39 -0
  12. package/package.json +77 -4
  13. package/paseo-plugin.json +6 -0
  14. package/server/account.ts +781 -0
  15. package/server/attachments.ts +150 -0
  16. package/server/build-info.ts +2 -0
  17. package/server/conversation.ts +319 -0
  18. package/server/diagnostics.ts +252 -0
  19. package/server/discovery/discover.ts +225 -0
  20. package/server/discovery/manifest.ts +36 -0
  21. package/server/discovery/types.ts +30 -0
  22. package/server/errors.ts +36 -0
  23. package/server/host/bridge.ts +490 -0
  24. package/server/host/schemas.ts +119 -0
  25. package/server/logger.ts +19 -0
  26. package/server/mapping.ts +270 -0
  27. package/server/models.ts +68 -0
  28. package/server/persistence.ts +146 -0
  29. package/server/presentation.ts +470 -0
  30. package/server/provider-status.ts +81 -0
  31. package/server/provider.ts +838 -0
  32. package/server/runtime/managed.ts +501 -0
  33. package/server/runtime/pins.ts +72 -0
  34. package/server/runtime-setup.ts +77 -0
  35. package/server/session-types.ts +49 -0
  36. package/server/session.ts +866 -0
  37. package/server/status.ts +89 -0
  38. package/server/timeline.ts +18 -0
  39. package/server/usage.ts +362 -0
  40. package/server/vendor/zcode/LICENSE +202 -0
  41. package/server/vendor/zcode/NOTICE.md +70 -0
  42. package/server/vendor/zcode/THIRD-PARTY-NOTICES.md +37 -0
  43. package/server/vendor/zcode/packages/model-option-map/src/compiler.ts +92 -0
  44. package/server/vendor/zcode/packages/model-option-map/src/evaluator.ts +214 -0
  45. package/server/vendor/zcode/packages/model-option-map/src/index.ts +5 -0
  46. package/server/vendor/zcode/packages/model-option-map/src/merge-patch.ts +97 -0
  47. package/server/vendor/zcode/packages/model-option-map/src/option-maps.ts +42 -0
  48. package/server/vendor/zcode/packages/model-option-map/src/parser.ts +240 -0
  49. package/server/vendor/zcode/packages/model-option-map/src/tokenizer.ts +141 -0
  50. package/server/vendor/zcode/packages/model-option-map/src/types.ts +36 -0
  51. package/server/vendor/zcode/packages/rpc/src/buffer.ts +88 -0
  52. package/server/vendor/zcode/packages/rpc/src/channelClient.ts +275 -0
  53. package/server/vendor/zcode/packages/rpc/src/channelServer.ts +283 -0
  54. package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +66 -0
  55. package/server/vendor/zcode/packages/rpc/src/channels.ts +4 -0
  56. package/server/vendor/zcode/packages/rpc/src/delayedChannel.ts +17 -0
  57. package/server/vendor/zcode/packages/rpc/src/foundation.ts +260 -0
  58. package/server/vendor/zcode/packages/rpc/src/protocol.ts +397 -0
  59. package/server/vendor/zcode/packages/rpc/src/proxy-channel.ts +213 -0
  60. package/server/vendor/zcode/packages/rpc/src/serialization.ts +291 -0
  61. package/server/vendor/zcode/packages/shared/src/account-provider-state.ts +13 -0
  62. package/server/vendor/zcode/packages/shared/src/background-bash-output.ts +24 -0
  63. package/server/vendor/zcode/packages/shared/src/background-task-controls.ts +404 -0
  64. package/server/vendor/zcode/packages/shared/src/bash-output-display.ts +11 -0
  65. package/server/vendor/zcode/packages/shared/src/bots.ts +558 -0
  66. package/server/vendor/zcode/packages/shared/src/browser-use/backend.ts +80 -0
  67. package/server/vendor/zcode/packages/shared/src/browser-use/command-metadata.ts +170 -0
  68. package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +588 -0
  69. package/server/vendor/zcode/packages/shared/src/browser-use/result.ts +130 -0
  70. package/server/vendor/zcode/packages/shared/src/browser-use/snapshot.ts +77 -0
  71. package/server/vendor/zcode/packages/shared/src/command-types.ts +96 -0
  72. package/server/vendor/zcode/packages/shared/src/config-schema.ts +8 -0
  73. package/server/vendor/zcode/packages/shared/src/cuaAccessibilitySettings.ts +41 -0
  74. package/server/vendor/zcode/packages/shared/src/database-startup.ts +178 -0
  75. package/server/vendor/zcode/packages/shared/src/env.ts +63 -0
  76. package/server/vendor/zcode/packages/shared/src/execution-output-preview.ts +14 -0
  77. package/server/vendor/zcode/packages/shared/src/localTtft.ts +192 -0
  78. package/server/vendor/zcode/packages/shared/src/model-config.ts +123 -0
  79. package/server/vendor/zcode/packages/shared/src/model-execution.ts +23 -0
  80. package/server/vendor/zcode/packages/shared/src/model-provider-family.ts +171 -0
  81. package/server/vendor/zcode/packages/shared/src/model-provider-types.ts +81 -0
  82. package/server/vendor/zcode/packages/shared/src/model-selection.ts +60 -0
  83. package/server/vendor/zcode/packages/shared/src/oauth.ts +203 -0
  84. package/server/vendor/zcode/packages/shared/src/official-mcp-auth.ts +272 -0
  85. package/server/vendor/zcode/packages/shared/src/official-mcp-tool-error.ts +68 -0
  86. package/server/vendor/zcode/packages/shared/src/process-diagnostic.ts +42 -0
  87. package/server/vendor/zcode/packages/shared/src/protocol.ts +369 -0
  88. package/server/vendor/zcode/packages/shared/src/provider-family-connection-selection.ts +32 -0
  89. package/server/vendor/zcode/packages/shared/src/remoteAssetInstallMode.ts +11 -0
  90. package/server/vendor/zcode/packages/shared/src/remoteResourcePackages.ts +91 -0
  91. package/server/vendor/zcode/packages/shared/src/runtimeEnv.ts +315 -0
  92. package/server/vendor/zcode/packages/shared/src/settings-source.ts +10 -0
  93. package/server/vendor/zcode/packages/shared/src/usage-quota.ts +74 -0
  94. package/server/vendor/zcode/packages/shared/src/usage-stats.ts +462 -0
  95. package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +562 -0
  96. package/server/vendor/zcode/packages/shared/src/workspacePurpose.ts +2 -0
  97. package/server/vendor/zcode/packages/shared/src/wslUserValidation.ts +33 -0
  98. package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +25 -0
  99. package/server/vendor/zcode/packages/shared/src/zcode-media-policy.ts +10 -0
  100. package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +3720 -0
  101. package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +529 -0
  102. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +218 -0
  103. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +14 -0
  104. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +482 -0
  105. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/core.ts +100 -0
  106. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +156 -0
  107. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/cuaPermission.ts +41 -0
  108. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +147 -0
  109. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +69 -0
  110. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +432 -0
  111. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/session-config.ts +43 -0
  112. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +195 -0
  113. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +85 -0
  114. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-import.ts +38 -0
  115. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/shared-context-ref.ts +10 -0
  116. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +509 -0
  117. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +5 -0
  118. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +264 -0
  119. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +1152 -0
  120. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +574 -0
  121. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-binary.ts +65 -0
  122. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-codec.ts +240 -0
  123. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +39 -0
  124. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire.ts +159 -0
  125. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +291 -0
  126. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-observation-display.ts +281 -0
  127. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +163 -0
  128. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +62 -0
  129. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +517 -0
  130. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +512 -0
  131. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-config.ts +81 -0
  132. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workspace-hook-review.ts +210 -0
  133. package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +6 -0
  134. package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +1187 -0
  135. package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +296 -0
  136. package/server/vendor/zcode/provenance.json +405 -0
  137. package/shared/account.ts +201 -0
  138. package/shared/diagnostics.ts +52 -0
  139. package/shared/runtime-setup.ts +73 -0
  140. package/vendor/acp-registry/LICENSE +176 -0
  141. package/vendor/paseo/LICENSE +211 -0
@@ -0,0 +1,512 @@
1
+ // ============================================================
2
+ // workflowRuns:dwf 引擎 run 的实时运行态 schema(snapshot.ts 的 workflowRuns 键)
3
+ // ============================================================
4
+ // 从 snapshot.ts 拆出:这块词汇表自成一体(run / actor / node / usage / limits),
5
+ // 拆出让 snapshot.ts 回到 max-lines 上限之内(与 workspace-hook-review.ts 同一先例)。
6
+
7
+ import { z } from "zod";
8
+
9
+ import { workflowRunArtifactSummarySchema } from "./workflow-artifacts.js";
10
+
11
+ // ── workflowRuns:dwf 引擎 run 的实时运行态──
12
+ // 与 subagents 同一个模式:运行态属于 conversation 权威投影,而不是 renderer 的查询缓存。
13
+ // 一条引擎 RunEvent → 一条会话事件 → 这里的键级整体替换,因此不存在「事件 + 另查 RPC」的双时钟。
14
+ export const WORKFLOW_RUNS_LIMITS = {
15
+ /** 最近若干个 run;超出按最旧淘汰。 */
16
+ maxRuns: 8,
17
+ /**
18
+ * actors 与 nodes 使用相同的容量上限,避免节点可展示而所属子代理提前被截断。
19
+ * 键级增量使每个事件只传输改动部分;容量上限用于限制单条 run 的投影大小,
20
+ * 并限制异常脚本持续创建条目带来的资源消耗。跨 run 的总量由 {@link maxTotalEntries} 控制。
21
+ * 这是展示状态的容量限制,不限制引擎实际运行的子代理数量。
22
+ */
23
+ maxActors: 1_024,
24
+ maxNodes: 1_024,
25
+ /**
26
+ * 整个状态键的条目预算:所有 run 的 `nodes.length + actors.length` 之和。
27
+ *
28
+ * 单条 run 的界乘以 {@link maxRuns} 是 16384 条,按每条约 150 字节算就是 ~2.5 MB ——
29
+ * 离 16 MiB 的快照上限不远,而快照是要整份序列化的。预算把最坏情形压回 ~1 MB,
30
+ * 代价是**最旧的终态 run** 会提前离场(它的完整事实仍在 journal 里,详情页照样查得到)。
31
+ * 归约在超预算时只淘汰终态 run,绝不动在跑的 run,也绝不动事件所属的那条。
32
+ */
33
+ maxTotalEntries: 6_144,
34
+ /**
35
+ * 详情页 Results 区的**展示**预算,刻意远小于引擎的 run 级 report 上限(256 条):
36
+ * 协议线上的界是展示预算,引擎的界才是契约,两者不必相等。超出这个界的条目仍在
37
+ * journal 里(`dwf_node.kind = "report"`),只是不进这条高频状态键。
38
+ */
39
+ maxReports: 64,
40
+ maxReportPreviewLength: 2_048,
41
+ maxResultPreviewLength: 2_048,
42
+ maxErrorLength: 2_048,
43
+ /**
44
+ * 同时停驻的升级问题条数。引擎侧的真实上界是
45
+ * per-ask 3 条 × 在飞 ask 数(maxConcurrency),32 因此在任何现实 caps 下都够用;
46
+ * 它同时是一道防线——一个疯掉的脚本不该能把一个高频状态键撑爆。
47
+ */
48
+ maxPendingQuestions: 32,
49
+ /** 问题与补充说明的展示上界。与事件载荷的字符串界(2048)同值,所以正常路径永不截断。 */
50
+ maxQuestionLength: 2_048,
51
+ /** 并发桶的 provider key(`${providerId}/${modelId}`)上界。 */
52
+ maxConcurrencyKeyLength: 256,
53
+ /**
54
+ * 子代理模型串(`providerId/modelId`,可带 `$reasoningLevel` 后缀)的线上上界。与并发桶的
55
+ * provider key 同值:两者是同一族标识串,只是这一条可能多一个推理档后缀。
56
+ */
57
+ maxSubagentModelLength: 256,
58
+ /**
59
+ * `run.concurrencyCeiling` 的上界。天花板按 `min(16, cores − 2)` 推导,这条界只挡坏载荷
60
+ * (reducer 读到界外的值当作读不出,沿用已知值)。
61
+ */
62
+ maxConcurrencyCeiling: 1_024,
63
+ /**
64
+ * 子代理展示名的线上上界(actor.name 与 pendingQuestion.actorName 同值)。名字是脚本作者
65
+ * 写的任意字符串(`agent("reader-" + paths.join("+"))`),reducer 必须按这条界裁剪后再上线:
66
+ * 曾因一个 131 字的名字让父会话之后的每一帧被渲染端拒收,订阅永久失效。
67
+ */
68
+ maxActorNameLength: 128,
69
+ /**
70
+ * 用户面产物的条数。与引擎侧的
71
+ * `ARTIFACT_CAPS.maxArtifactsPerRun` **同值**,理由与 maxReports 的「展示预算 <
72
+ * 引擎契约」相反:产物的引擎上限本来就是 32,把展示界压得更低只会让一个跑在上限上的
73
+ * 脚本在侧板里静默少掉几张卡,而这些卡正是这个特性存在的全部理由。
74
+ */
75
+ maxArtifacts: 32,
76
+ /**
77
+ * 被进入过的阶段条数。与 display
78
+ * 载荷的 `CREATE_WORKFLOW_GRAPH_MAX_PHASES` 同值:时间线上画不出的阶段,投影里也不必记。
79
+ */
80
+ maxPhases: 32,
81
+ /** 阶段名的线上上界,与 display 的 `CREATE_WORKFLOW_GRAPH_MAX_NAME_CHARS` 同值(UI 按名字关联两边)。 */
82
+ maxPhaseNameLength: 128,
83
+ /**
84
+ * 一次 ask 的**任务摘要**上界(`node-queued` 的 `instructionsHead`)。与引擎侧的
85
+ * `INSTRUCTIONS_HEAD_MAX_CHARS` 同值:那一头已经按这条界切好,这里是线上的第二道闸。
86
+ * 240 是「一眼看出这个子代理被派去干什么」所需的长度——再长就是在协议线上搬运指令全文,
87
+ * 而指令全文有 journal 与子代理转录两处可去。
88
+ */
89
+ maxInstructionsHeadLength: 240,
90
+ /** 最近一次工具调用的工具名上界(与 actor/node 的 siteId 同量级,工具名是标识符不是文本)。 */
91
+ maxLastToolNameLength: 64,
92
+ /**
93
+ * 最近一次工具调用的**目标**上界(文件路径、命令头)。与引擎侧的
94
+ * `LAST_TOOL_TARGET_MAX_CHARS` 同值。这条界同时是一条安全界:它只放得下一个路径或命令头,
95
+ * 放不下参数全文或文件内容——后两者永远不该出现在这条高频状态键上。
96
+ */
97
+ maxLastToolTargetLength: 120,
98
+ } as const;
99
+
100
+ /**
101
+ * **旧消费者**(没有 `workflowRunDeltas` 能力的那一代)编译进去的 actors / nodes 界。
102
+ *
103
+ * ⚠ 这两个数**永远不能改**:它们不是我们的界,是别人二进制里的校验界。超界的载荷不会被
104
+ * 剥掉一个键——它会让整个 `state.updated` patch 解析失败、整帧被丢,那条订阅从此静默。所以给这类订阅者
105
+ * 发帧前必须先过 `clampWorkflowRunsForLegacy`。
106
+ *
107
+ * 「前 256 条」不是随手取的:旧归约触界时是**拒新**,它产出的恰好就是最早的那 256 条。
108
+ */
109
+ export const WORKFLOW_RUNS_LEGACY_LIMITS = {
110
+ maxActors: 256,
111
+ maxNodes: 256,
112
+ } as const;
113
+
114
+ /**
115
+ * 一个被控制流进入过的阶段(`phase("…")` 标记)。`name` 是作者原词(时间线按它关联 display 的 `phases[].name`);`rounds` 是进入
116
+ * 次数——单调(reducer 取 max),所以 resume 重放的前缀不会把它加倍。
117
+ */
118
+ export const workflowRunPhaseSchema = z.object({
119
+ name: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength),
120
+ rounds: z.number().int().positive(),
121
+ });
122
+ export type WorkflowRunPhase = z.infer<typeof workflowRunPhaseSchema>;
123
+
124
+ /**
125
+ * 界在某个**出生阶段**上花掉了多少。
126
+ *
127
+ * 两个计数器({@link workflowRunUsageSchema} 的 `nodesUnlisted` / `nodesUnlistedSettled`)说得出
128
+ * 一条 run 总共少列了多少,说不出少在**哪一站**——而读面是按站画的:一个站点的花名册、计数环和
129
+ * 「N more」都得把自己那格的表外条目加回去,否则宽 fan-out 的站点会显示成一个小数字。
130
+ *
131
+ * `phaseName` 缺席 = 无阶段那一格(出生在任何 `phase()` 标记之前,或旧 CLI 没打戳)。`actors` 是
132
+ * 这个阶段**此刻不在 actor 表上**的子代理数(被拒的、被淘汰的、按孤儿规则摘掉的都算),
133
+ * `actorsSettled` 是其中已知已经结束的、`actorsFailed` 又是其中失败的,`settled` 是记在这一格上的
134
+ * 表外已结算节点数。`actors` 可加可减:一个被淘汰的子代理在下次被派活时会回到表上。
135
+ * 零值的可选子键缺席,四个数全零的格子整个不在(与本文件其余「无则缺席」同规)。
136
+ */
137
+ export const workflowRunUnlistedPhaseSchema = z.object({
138
+ phaseName: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
139
+ actors: z.number().int().nonnegative(),
140
+ actorsSettled: z.number().int().nonnegative().optional(),
141
+ actorsFailed: z.number().int().nonnegative().optional(),
142
+ settled: z.number().int().nonnegative(),
143
+ });
144
+ export type WorkflowRunUnlistedPhase = z.infer<typeof workflowRunUnlistedPhaseSchema>;
145
+
146
+ /**
147
+ * run 级用量:观察面,不是控制面。`spentTokens` 直接取
148
+ * 引擎 `usage-updated` 事件携带的已花总量(与 `dwf_run.spent_tokens` 同一同步步骤写入,
149
+ * 二者永远相等);`nodesUsed` 是本 run 已派发(dispatched)的节点数,由节点事件计数——
150
+ * 没有任何上限可以拿来反算,也不需要。
151
+ */
152
+ export const workflowRunUsageSchema = z.object({
153
+ spentTokens: z.number().int().nonnegative(),
154
+ nodesUsed: z.number().int().nonnegative(),
155
+ /**
156
+ * 撞上 {@link WORKFLOW_RUNS_LIMITS.maxNodes} 被**拒之表外**的实例数,以及其中已结算的条数。`truncated` 只说得出「有东西没进来」,说不出
157
+ * 有多少——于是一个 3000 路 fan-out 的 run 在读面上会显示成「1024 步」,那是一句假话。
158
+ *
159
+ * 两条都是**加出来**的计数(被拒实例根本不在表里,没有可去重的身份),所以归约只在事件
160
+ * **抬过水位**时才计,重传的队尾事件不会把它们越推越高。`run-started` 连同整个 usage 一起
161
+ * 清零:resume 会把脚本前缀重发一遍,不清零等于把两世的步数加在一起。零时整个键缺席。
162
+ */
163
+ nodesUnlisted: z.number().int().nonnegative().optional(),
164
+ nodesUnlistedSettled: z.number().int().nonnegative().optional(),
165
+ });
166
+ export type WorkflowRunUsage = z.infer<typeof workflowRunUsageSchema>;
167
+
168
+ /**
169
+ * 一个 actor 实例。`status` 是**派生**的三态:
170
+ * 引擎的 Boundary C 除了 `actor-created` 之外不发任何 actor 生命周期事件,所以状态从该 actor 的
171
+ * 节点与 run 的终态推出来——
172
+ * - `running`:有节点处于 executing / repairing / nudged(模型请求已发出、正在跑);
173
+ * - `waiting`:有 live 节点(queued / dispatched / waiting:还没派下去、在等槽位或在退避),
174
+ * **或**尚无任何节点而 run 未终态(建了还没被 ask);
175
+ * - `completed`:其余(全部节点已结算,或 run 已终态)。
176
+ * 不存在可观察的 actor 级 failed:某次 ask 失败仍是「它的活干完了」,结果在节点上。
177
+ *
178
+ * `sessionId` 是 actor 会话 id(phase 5 的 transcript 下钻直接读它)。它同样不在 Boundary C 上,
179
+ * 而是 run service 按 `(runId, actorRef)` 确定性铸造的同一个函数算出来的。
180
+ */
181
+ export const workflowRunActorSchema = z.object({
182
+ siteId: z.string().min(1).max(64),
183
+ ordinal: z.number().int().nonnegative(),
184
+ name: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxActorNameLength).optional(),
185
+ sessionId: z.string().min(1).max(256).optional(),
186
+ status: z.enum(["waiting", "running", "completed"]),
187
+ /**
188
+ * 这个实例**出生**在哪个阶段:它的 ordinal 被铸造的那一刻,控制流所在的 `phase("…")` 标记名。UI 按**名字**与 `phases[].name`
189
+ * 关联——名字是引擎与分析器唯一共享的词汇,所以界与 `maxPhaseNameLength` 同值。
190
+ *
191
+ * 缺席有两种读法,消费者都要认:出生在任何标记之前(脚本没写 `phase()`,或写在后面),
192
+ * 或者发事件的是不带这个键的旧 CLI。
193
+ */
194
+ phaseName: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
195
+ });
196
+ export type WorkflowRunActor = z.infer<typeof workflowRunActorSchema>;
197
+
198
+ /**
199
+ * 一次 ask 里**最近一次**工具调用(`node-progress` 携带)。`name` 是工具名;`target` 是一个
200
+ * 短到能当标签用的目标——文件工具的路径、Bash 的命令头,读不出时缺席。
201
+ *
202
+ * **刻意只有这两个键**:参数全文与文件内容不进这条高频状态键(每个已解析轮次发一条),
203
+ * 它们在 journal 与子代理转录里。`target` 的界因此既是展示预算也是那条约束的守卫。
204
+ */
205
+ export const workflowRunNodeLastToolSchema = z.object({
206
+ name: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxLastToolNameLength),
207
+ target: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxLastToolTargetLength).optional(),
208
+ });
209
+ export type WorkflowRunNodeLastTool = z.infer<typeof workflowRunNodeLastToolSchema>;
210
+
211
+ /**
212
+ * 一个节点(ask / world-read)实例。`phase` 就是引擎**实际发出的**节点事件:
213
+ * queued → dispatched → executing ⇄ waiting → (repairing | nudged) → settled。
214
+ * `executing` / `waiting` 来自 driver 的观察:
215
+ * `node-executing` = 该 ask 的模型请求真的发出去了;`node-waiting` = 它在等进程级槽位或在退避。
216
+ * `dispatched` 因此是「会话就绪、首个请求尚未准入」的短暂相位,读面把它与 queued / waiting 同归「等待」。
217
+ *
218
+ * `kind` 可缺省:resume 的完结命中短路直接发 `node-settled`(ask 走 scheduler.ts 的
219
+ * releaseCachedAsk / tryImportedSettle,world-read 走 engine-world.ts 的重放与导入命中),
220
+ * 不经 `node-queued`,而 kind 只在 queued 上携带。
221
+ */
222
+ export const workflowRunNodeSchema = z.object({
223
+ siteId: z.string().min(1).max(64),
224
+ ordinal: z.number().int().nonnegative(),
225
+ kind: z.enum(["ask", "world-read"]).optional(),
226
+ phase: z.enum(["queued", "dispatched", "executing", "waiting", "repairing", "nudged", "settled"]),
227
+ outcome: z.enum(["ok", "failed", "cancelled"]).optional(),
228
+ cached: z.boolean().optional(),
229
+ /** 该节点所属 actor 的站点 id(world-read 无 actor)。 */
230
+ actorSiteId: z.string().min(1).max(64).optional(),
231
+ actorOrdinal: z.number().int().nonnegative().optional(),
232
+ /**
233
+ * 这个实例**出生**在哪个阶段,
234
+ * 语义与 {@link workflowRunActorSchema} 的同名键逐字相同:ordinal 被铸造那一刻的
235
+ * `phase("…")` 标记名,UI 按名字与 `phases[].name` 关联;缺席 = 出生在任何标记之前,或旧 CLI。
236
+ *
237
+ * ⚠ 与上面的 `phase` **不是**一回事:`phase` 是节点的生命周期相位(queued / executing /
238
+ * settled…),`phaseName` 是脚本阶段坐标。字段特意不叫 `phase` 就是为了不把两个概念揉在一起。
239
+ *
240
+ * 引擎只在**出生事件**上打戳(`node-queued`,以及 replay 命中时直接发的
241
+ * `node-settled { cached: true }`);其余 `node-*` 不带,由 reducer 向前携带。
242
+ */
243
+ phaseName: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
244
+ /**
245
+ * 这次 ask 的**任务**:作者写的 `instructions` 的头 240 字,随 `node-queued` 到达。读面据它回答「这个子代理被派去干什么」——
246
+ * 相位只说得出「在跑」,说不出在跑什么。
247
+ *
248
+ * 是**作者原文**的头,不含引擎后来追加的尾注(那些是运行时脚手架,不是任务)。
249
+ * 缺席有两种读法,消费者都要认:world-read 节点(没有指令),或不带这个键的旧 CLI/旧 journal。
250
+ */
251
+ instructionsHead: z
252
+ .string()
253
+ .min(1)
254
+ .max(WORKFLOW_RUNS_LIMITS.maxInstructionsHeadLength)
255
+ .optional(),
256
+ /**
257
+ * 这次 ask 走到第几个已解析轮次(1 起,nudge 轮次计入),以及累计工具调用数与最近一次
258
+ * 工具调用——随 `node-progress` 到达,每个已解析轮次一条。
259
+ *
260
+ * 三者一起回答「它在动吗」:一个卡在 `executing` 十分钟的 ask,只有这几个读数能分出
261
+ * 「在干一件长活」与「已经死了」。**没有 `node-progress` 的旧 journal 上三键全缺席**,
262
+ * 读面必须把缺席显示成「不知道」,而不是显示成 0 —— 0 是「一个工具都没调过」的事实。
263
+ *
264
+ * 归约是**后来者覆盖**而不是取 max(与 `phases[].rounds` 相反):同一实例在 resume 里被
265
+ * 重新 queue 时是一次全新的 ask,轮次从 1 重新数,取 max 会把上一世的读数冻在这里。
266
+ */
267
+ turn: z.number().int().positive().optional(),
268
+ toolCalls: z.number().int().nonnegative().optional(),
269
+ lastTool: workflowRunNodeLastToolSchema.optional(),
270
+ });
271
+ export type WorkflowRunNode = z.infer<typeof workflowRunNodeSchema>;
272
+
273
+ /**
274
+ * 本 run 的并发现状。两条界,
275
+ * 实际并发是**两者取小**:
276
+ *
277
+ * - `cap`:本 run 所在 provider key 的**共享**闸门现状(治理器按 key 分桶、按 run 扇出,
278
+ * 随 `concurrency-changed` 移动);`ceiling`:CPU 推导的天花板。事件本身不带 ceiling,
279
+ * 归约按该 run 见过的最大 `previous` / `next` 推导(桶从天花板起步,所以第一条事件的
280
+ * `previous` 就是它;只降不升的序列里它也恒是最大值)。
281
+ * - `limit`:本 run **自己的**界(`CreateWorkflow` / `AmendWorkflow` 的 `max_concurrency`
282
+ * 落到 `caps.maxConcurrency`),随 `run-started` 到达、整条 run 不动。**只在低于天花板时
283
+ * 在场**:跑在天花板上的 run 与从前一模一样,一个键都不多。
284
+ * - `cooldownMs`:带 Retry-After 的限流冻结新派发的时长,**相对量**(同 `retryInMs` 的理由);
285
+ * `idle_reset` 与 run 终态清掉它。
286
+ *
287
+ * UI 只在 `min(cap, limit) < ceiling` 时显示读数(见 workflowRunConcurrencyView)。
288
+ */
289
+ export const workflowRunConcurrencySchema = z.object({
290
+ key: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxConcurrencyKeyLength).optional(),
291
+ cap: z.number().int().positive(),
292
+ ceiling: z.number().int().positive(),
293
+ limit: z.number().int().positive().optional(),
294
+ cooldownMs: z.number().int().nonnegative().optional(),
295
+ });
296
+ export type WorkflowRunConcurrency = z.infer<typeof workflowRunConcurrencySchema>;
297
+
298
+ /**
299
+ * 一条脚本 `report(item)` 交出的**渐进产物**(详情页 Results 区的一行)。
300
+ *
301
+ * `report` 与 `log` 不同类:它有 site 身份、进 journal、有自己的 `RunEvent`,所以它进
302
+ * 事件日志、进 Results 区、也随完成通知回给模型;但它**不进因果图**(纯进度发射、
303
+ * 无任何顺序意义),也**不进 `nodes[]`**——报得勤的工作流不该显得步数虚高。
304
+ *
305
+ * `siteId × ordinal` 是身份(journal 的键),也是归约的去重键:脚本 replay 时每个
306
+ * `report` 调用都会重跑,同一实例必须落成同一行而不是两行。
307
+ *
308
+ * 存**预览文本**而不是原值:序列化规则(string 原样;其余 pretty JSON)与 run 产物
309
+ * 回投那条路径同源,一次算在 CLI 侧,renderer 因此不需要复制一份序列化契约;顺带
310
+ * 让「协议边界上的所有载荷有界」这条不变量落在一个 string 上界上。
311
+ */
312
+ export const workflowRunReportSchema = z.object({
313
+ siteId: z.string().min(1).max(64),
314
+ ordinal: z.number().int().nonnegative(),
315
+ preview: z.string().max(WORKFLOW_RUNS_LIMITS.maxReportPreviewLength),
316
+ /**
317
+ * `report(item, artifactId)` 的第二实参:这条条目喂给哪个**预置看板**。缺席 = 没打标签,照旧只进 Results 区。
318
+ *
319
+ * 带标签的条目**仍然进 `reports`**:一条通道一套上限,标签只是多一个去处,不是改道。
320
+ * 上界与产物 id 同(64),因为它就是一个产物 id。
321
+ */
322
+ artifactId: z.string().min(1).max(64).optional(),
323
+ });
324
+ export type WorkflowRunReport = z.infer<typeof workflowRunReportSchema>;
325
+
326
+ /**
327
+ * 一个**停驻中**的升级问题:某个 actor 撞上真阻塞
328
+ * (坏门、指令自相矛盾、缺关键事实),把问题升级给主代理,并停在自己那次 ask 里等答案。
329
+ *
330
+ * 身份是 `qid`(全局唯一、跨 run),不是站点实例——升级**没有** site 身份:它不写 dwf_node
331
+ * 行、不占 maxNodes,是一次 ask 轮次**内部**的慢工具调用。所以去重键是 qid,而 actor 只是
332
+ * 一个属性。这与 nodes/actors/reports 那三张按 (siteId, ordinal) 去重的表是不同的族。
333
+ *
334
+ * `actorSiteId` / `actorOrdinal` 因此可缺省(形态照抄 `WorkflowRunNode` 的同名一对):一条
335
+ * 问不出提问者是谁的记录仍然要显示——这个特性存在的理由就是让被卡住的问题**可见**,为了
336
+ * 一个读不动的 actor ref 把整条问题藏起来,恰好毁掉它唯一的兜底价值。`actorName` 是 persona
337
+ * 名,匿名 actor 缺席(事件侧刻意不合成兜底标签,由消费者各自决定怎么渲染「无名」)。
338
+ *
339
+ * **纯内存、随 run 终态清空**:真相是 CLI 进程内的停驻 deferred。进程一死那些 deferred 就没了,
340
+ * 于是「还欠谁一个答案」这件事在终态 run 上恒为假——终态 run 按定义没有在听的人。
341
+ */
342
+ export const workflowRunPendingQuestionSchema = z.object({
343
+ /** 全局唯一的问题 id(形如 `dwfq-<runId 片段>-<seq>`)。主代理按它作答。 */
344
+ qid: z.string().min(1).max(128),
345
+ actorSiteId: z.string().min(1).max(64).optional(),
346
+ actorOrdinal: z.number().int().nonnegative().optional(),
347
+ actorName: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxActorNameLength).optional(),
348
+ question: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxQuestionLength),
349
+ context: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxQuestionLength).optional(),
350
+ /**
351
+ * 提问时刻(epoch 毫秒),由事件携带——本模块是纯归约,没有时钟可用。
352
+ *
353
+ * 事件侧**必填**(driver 是唯一生产者,与停驻记录取同一个 `Date.now()`),这里仍然 optional:
354
+ * 老开发机上的 journal 可能重放出 askedAt 之前的事件。所以渲染侧按「有则显示等待时长」处理,
355
+ * 缺席不是错误。
356
+ */
357
+ askedAt: z.number().int().nonnegative().optional(),
358
+ });
359
+ export type WorkflowRunPendingQuestion = z.infer<typeof workflowRunPendingQuestionSchema>;
360
+
361
+ export const workflowRunSchema = z.object({
362
+ runId: z.string().min(1).max(128),
363
+ /** 发起该 run 的 CreateWorkflow 工具调用(工具卡 → 详情页的关联键)。 */
364
+ toolCallId: z.string().min(1).max(128).optional(),
365
+ status: z.enum(["pending", "running", "completed", "errored", "stopped"]),
366
+ /** `status === "stopped"` 才在场。 */
367
+ stopReason: z.enum(["user", "model", "provider", "interrupted", "superseded"]).optional(),
368
+ /**
369
+ * lineage 的两端:本 run 修订自哪个 run
370
+ * (`run-started` 载荷的 `resumedFrom`),以及本 run 被哪次修订停下并替代(`run-settled` 载荷的
371
+ * `supersededBy`,只随 `stopReason: "superseded"` 出现)。两者都 optional,理由与 `reports` 同:
372
+ * 往已有状态键追加字段,旧 CLI 不发它们时少一个键是退化,不是整帧被丢。
373
+ */
374
+ resumedFrom: z.string().min(1).max(128).optional(),
375
+ supersededBy: z.string().min(1).max(128).optional(),
376
+ usage: workflowRunUsageSchema,
377
+ error: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxErrorLength).optional(),
378
+ /**
379
+ * 可恢复。**为真才在场**。
380
+ *
381
+ * 由 CLI 在 `run-settled` 载荷上按 resume 门的同一个谓词给出(live 由 toProgressPayload 算,
382
+ * 冷回放由补种按 journal 行算),reducer 只搬运——UI 绝不自行按 status + failureCode 推导
383
+ * (两处谓词总有一天不一致:按钮亮着但命令被拒)。optional 的理由与 `reports` 同:往已有
384
+ * 状态键追加字段,旧 CLI 不发它时少一个键是退化,不是整帧被丢。
385
+ */
386
+ resumable: z.literal(true).optional(),
387
+ resultPreview: z.string().max(WORKFLOW_RUNS_LIMITS.maxResultPreviewLength).optional(),
388
+ actors: z.array(workflowRunActorSchema).max(WORKFLOW_RUNS_LIMITS.maxActors),
389
+ nodes: z.array(workflowRunNodeSchema).max(WORKFLOW_RUNS_LIMITS.maxNodes),
390
+ /**
391
+ * `report(item)` 交出的渐进产物,按报告顺序。**零条时整个键缺席**(不是空数组):
392
+ * Results 区据此整区不渲染,而不是给不用 `report` 的工作流留一节空壳。
393
+ *
394
+ * 刻意 optional 而不是必填:这是往一个**已有状态键**上追加字段,而已知键上的解析错误
395
+ * 不会被剥离——它会让整个 `state.updated` patch 失败、整帧被丢。
396
+ * 必填意味着任何一个不发 reports 的旧 CLI 都会触发那一档;optional 让它退化成「少一个键」。
397
+ */
398
+ reports: z.array(workflowRunReportSchema).max(WORKFLOW_RUNS_LIMITS.maxReports).optional(),
399
+ /**
400
+ * 停驻中的升级问题,按提问顺序。**零条时整个键缺席**(不是空数组),与 `reports` 同一条
401
+ * 惯例:侧栏据此整区不渲染,而不是给一个没人提问的 run 留一节空壳。
402
+ *
403
+ * 「零条」是这个键的**常态**,而且它会来回进出:问题一被作答就从表里消失,答完最后一个
404
+ * 又退回缺席。所以消费者不能把「见过一次这个键」当成它会一直在。
405
+ *
406
+ * optional 的第二个理由与 `reports` 相同:这是往一个**已有状态键**上追加字段,而已知键上的
407
+ * 解析错误不会被剥离——必填会让任何一个不发该字段的旧 CLI 整帧被丢。
408
+ */
409
+ pendingQuestions: z
410
+ .array(workflowRunPendingQuestionSchema)
411
+ .max(WORKFLOW_RUNS_LIMITS.maxPendingQuestions)
412
+ .optional(),
413
+ /**
414
+ * 并发现状(见 {@link workflowRunConcurrencySchema})。只在**两条界里有一条低于天花板**时
415
+ * 在场:收到过 `concurrency-changed`(共享桶被限流压低),或 `run-started` 带来一个低于天花板
416
+ * 的 `limit`(用户给这次 run 定了上限)。两者都没有的 run 一直跑在天花板上,没有可说的。
417
+ * optional 的理由与 `reports` / `pendingQuestions` 同。
418
+ */
419
+ concurrency: workflowRunConcurrencySchema.optional(),
420
+ /**
421
+ * 本机的并发天花板(`run-started` 载荷的 `concurrencyCeiling`,CLI 铸载荷时拼进去的进程事实)。
422
+ * 与 `concurrency.ceiling` 不同:那是读数芯片自己的水位,只随芯片在场;这一个**只要读得到就在**,
423
+ * 不论本 run 是否低于它——「配置」弹层的步进器停在这里。optional 的理由与 `concurrency` 同:老 CLI 不发,少一个键是退化。
424
+ */
425
+ concurrencyCeiling: z
426
+ .number()
427
+ .int()
428
+ .positive()
429
+ .max(WORKFLOW_RUNS_LIMITS.maxConcurrencyCeiling)
430
+ .optional(),
431
+ /**
432
+ * 这次 run 的**子代理**跑在哪个模型上(`CreateWorkflow` / `AmendWorkflow` 的 `subagent_model`
433
+ * 落到载荷的 `subagentModel`),规范串 `providerId/modelId`,可带 `$reasoningLevel` 后缀。
434
+ * 随 `run-started` 到达、整条 run 不动——与 `concurrency.limit` 同族:用户给这次 run 定下的
435
+ * 条件,不随运行时涨落。
436
+ *
437
+ * **只在用户给这次 run 指定过模型时在场**:不指定的 run 里子代理跟随会话模型,没有可说的。
438
+ * 主代理无论如何都留在会话模型上,所以这个键说的只是子代理那一侧。
439
+ * optional 的理由与 `reports` / `concurrency` 逐字相同:往已有状态键追加字段,旧 CLI 不发它时
440
+ * 少一个键是退化,不是整帧被丢。
441
+ */
442
+ subagentModel: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxSubagentModelLength).optional(),
443
+ /**
444
+ * 本 run 发布的**用户面产物**,按首次出现顺序,每项只带**最新版**的元数据。**零件时整个键缺席**(不是空数组):
445
+ * 侧板的 Artifacts 区据此整区不渲染——「无则缺席」与 `reports` / `pendingQuestions` 同规。
446
+ *
447
+ * optional 的第二个理由与 `reports` 逐字相同,也是这里真正要紧的那个:这是往一个**已有
448
+ * 状态键**上追加字段,而已知键上的解析错误不会被剥离——必填会让任何一个不发产物的旧 CLI
449
+ * 整帧被丢。少一个键是退化,不是错误。
450
+ *
451
+ * ⚠ 术语:这里的 artifact 是脚本发布给用户看的产出,不是 `resultPreview` 背后那个
452
+ * 「脚本顶层返回值」(引擎内部也叫 artifact)。见 workflow-artifacts.ts 的文件头。
453
+ */
454
+ artifacts: z
455
+ .array(workflowRunArtifactSummarySchema)
456
+ .max(WORKFLOW_RUNS_LIMITS.maxArtifacts)
457
+ .optional(),
458
+ /**
459
+ * 被进入过的阶段,按首次进入顺序。
460
+ * **零条时整个键缺席**;optional 的理由与 `reports` 逐字相同(旧 CLI 不发它,少一个键是退化
461
+ * 不是错误)。时间线据它给零成员的站点灯、给所有站补「第一个 ask 派发之前」那段的 running。
462
+ */
463
+ phases: z.array(workflowRunPhaseSchema).max(WORKFLOW_RUNS_LIMITS.maxPhases).optional(),
464
+ /** 控制流最后进入的阶段名(最后一条 `phase-entered`);从未进入过任何阶段时缺席。 */
465
+ currentPhase: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
466
+ /**
467
+ * 脚本**声明**的阶段表,按声明序(`run-launched.phaseNames`)。与 `phases`(已进入的)互补:侧栏迷你轨道据此画出前方还没到的站点。
468
+ * 零条 / 旧 CLI / 无标记脚本时整个键缺席。
469
+ */
470
+ phaseNames: z
471
+ .array(z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength))
472
+ .max(WORKFLOW_RUNS_LIMITS.maxPhases)
473
+ .optional(),
474
+ /**
475
+ * 与 `phaseNames` **按位置对齐**的「同时在跑」表(`run-launched.phaseAlongside`):
476
+ * `phaseAlongside[i]` 是进入 `phaseNames[i]` 时 strand 仍在跑的其他阶段的**下标**,下标落在
477
+ * `phaseNames` 这张表上。侧栏迷你轨道据此把并行的两站之间画成双线段
478
+ *
479
+ * 依附 `phaseNames`:后者不在场时它一定不在场;没有任何阶段并行时同样缺席——缺席就是
480
+ * 「这条轨道是一条直线」。归约保证每个下标都落在被接受的那张表里(workflow-runs-phases.ts)。
481
+ */
482
+ phaseAlongside: z
483
+ .array(z.array(z.number().int().nonnegative()).max(WORKFLOW_RUNS_LIMITS.maxPhases))
484
+ .max(WORKFLOW_RUNS_LIMITS.maxPhases)
485
+ .optional(),
486
+ /**
487
+ * 界在各个出生阶段上花掉了多少(见 {@link workflowRunUnlistedPhaseSchema})。**一格都没有时
488
+ * 整个键缺席**。表长比 `maxPhases` 多一格:那一格是「无阶段」,它与具名阶段共用同一张表。
489
+ *
490
+ * 表满之后新阶段的归属**丢掉**,run 级两个计数器照旧准——一个站点可以少一个它本来就没有的
491
+ * 数字,run 的总数不可以说假话。
492
+ */
493
+ unlistedByPhase: z
494
+ .array(workflowRunUnlistedPhaseSchema)
495
+ .max(WORKFLOW_RUNS_LIMITS.maxPhases + 1)
496
+ .optional(),
497
+ /**
498
+ * actors / nodes / reports / pendingQuestions / artifacts / phases 触到上限后置位;
499
+ * 原始事实仍在 journal。淘汰(给活的新人腾位)同样置位:这条 run 的条目表已经装不下它
500
+ * 自己的事实了,而 `run-started` 正是按这一位决定新一世要不要从空表重开。
501
+ */
502
+ truncated: z.boolean().optional(),
503
+ /** 最后一条已归约事件的 journal sequence;抬升即事件日志重取的触发条件。 */
504
+ lastEventSequence: z.number().int().nonnegative(),
505
+ });
506
+ export type WorkflowRunState = z.infer<typeof workflowRunSchema>;
507
+
508
+ export const workflowRunsStateSchema = z.object({
509
+ revision: z.number().int().nonnegative(),
510
+ runs: z.array(workflowRunSchema).max(WORKFLOW_RUNS_LIMITS.maxRuns),
511
+ });
512
+ export type WorkflowRunsState = z.infer<typeof workflowRunsStateSchema>;
@@ -0,0 +1,81 @@
1
+ // workspace-config topic(v4 additive 新增):workspace 级配置目录的活性数据源。
2
+ // 背景:旧协议里 provider registry / 模型目录热更新经 per-session `state.updated`(settings patch)
3
+ // 下发,host 侧 zcodeTaskIndexSyncer 转成 workspace_config_options_update 广播给 UI;
4
+ // 旧 session/subscribe + state.updated 词表不承载配置目录,统一改走本 topic。
5
+ // 语义:conflated 最新态(同 sessions-index),载荷是 workspace 级配置目录 + slash 命令目录。
6
+ // 注意这是 workspace 级不是 session 级——session 级 current 选择在 conversation topic 的
7
+ // `config`(sessionConfigStateSchema)里;本 topic 的 currentValue 表示 workspace 缺省。
8
+ // 纪律:additive 演进——新增 topic / 新增可选字段合法,改已有字段形状不合法。
9
+ import { z } from "zod";
10
+
11
+ // 与 host 侧 ZCodeConfigSelectValue(zcode-task-types-core)结构对齐:
12
+ // syncer 转发 workspace_config_options_update 时零映射直通,下游 useZCodeConfig 消费面不改。
13
+ export const workspaceConfigSelectValueSchema = z.object({
14
+ value: z.string(),
15
+ name: z.string(),
16
+ description: z.string().optional(),
17
+ // 值来源:原生模型列表或会话侧注入项(UI 去重与展示控制)。
18
+ origin: z.enum(["native", "injected"]).optional(),
19
+ // 模型选项所属供应商/分组 id(provider → model 分组选择)。
20
+ modelProviderId: z.string().optional(),
21
+ modelProviderName: z.string().optional(),
22
+ // 缺失表示旧 payload/能力未知;空数组表示 catalog 已知没有可选 reasoning 档位。
23
+ modelThoughtLevels: z.array(z.string()).optional(),
24
+ modelDefaultThoughtLevel: z.string().optional(),
25
+ });
26
+ export type WorkspaceConfigSelectValue = z.infer<typeof workspaceConfigSelectValueSchema>;
27
+
28
+ // 与 host 侧 ZCodeConfigOption 结构对齐(id: model / mode / thought_level / 自定义)。
29
+ export const workspaceConfigOptionSchema = z.object({
30
+ id: z.string(),
31
+ name: z.string(),
32
+ description: z.string().optional(),
33
+ category: z.string().optional(),
34
+ type: z.enum(["select", "boolean"]),
35
+ currentValue: z.union([z.string(), z.boolean()]),
36
+ options: z.array(workspaceConfigSelectValueSchema).optional(),
37
+ });
38
+ export type WorkspaceConfigOption = z.infer<typeof workspaceConfigOptionSchema>;
39
+
40
+ // slash 命令目录(workspace 级;与 host 侧 ZCodeSlashCommand 对齐)。
41
+ export const workspaceSlashCommandSchema = z.object({
42
+ name: z.string(),
43
+ description: z.string(),
44
+ inputHint: z.string().optional(),
45
+ source: z.enum(["builtin", "custom"]).optional(),
46
+ });
47
+ export type WorkspaceSlashCommand = z.infer<typeof workspaceSlashCommandSchema>;
48
+
49
+ // topic 载荷本体:整体替换语义(conflated 最新态,绝不深合并——同一纪律)。
50
+ export const workspaceConfigStateSchema = z.object({
51
+ configOptions: z.array(workspaceConfigOptionSchema),
52
+ slashCommands: z.array(workspaceSlashCommandSchema),
53
+ });
54
+ export type WorkspaceConfigState = z.infer<typeof workspaceConfigStateSchema>;
55
+
56
+ export const workspaceConfigSnapshotSchema = z.object({
57
+ protocolVersion: z.literal(1),
58
+ workspaceId: z.string(),
59
+ // host 级配置日志代际(与 sessions-index 的 logEpoch 同构、彼此独立)。
60
+ logEpoch: z.string(),
61
+ config: workspaceConfigStateSchema,
62
+ });
63
+ export type WorkspaceConfigSnapshot = z.infer<typeof workspaceConfigSnapshotSchema>;
64
+
65
+ // delta 集合刻意只有一个 op:配置目录是小体量整体替换态,不做字段级增量。
66
+ export const workspaceConfigDeltaSchema = z.discriminatedUnion("op", [
67
+ z.object({ op: z.literal("config.updated"), config: workspaceConfigStateSchema }),
68
+ ]);
69
+ export type WorkspaceConfigDelta = z.infer<typeof workspaceConfigDeltaSchema>;
70
+
71
+ /** workspace-config topic key 构造(与 parseWorkspaceConfigTopic 对偶)。 */
72
+ export function workspaceConfigTopic(workspaceId: string): string {
73
+ return `workspace-config/${workspaceId}`;
74
+ }
75
+
76
+ /** workspace-config topic key 解析("workspace-config/<workspaceId>")。 */
77
+ export function parseWorkspaceConfigTopic(topic: string): string | null {
78
+ if (!topic.startsWith("workspace-config/")) return null;
79
+ const workspaceId = topic.slice("workspace-config/".length);
80
+ return workspaceId.length > 0 ? workspaceId : null;
81
+ }