paseo-plugin-zcode-provider 0.3.0 → 0.4.1

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 (78) hide show
  1. package/NOTICE.md +2 -2
  2. package/README.md +9 -9
  3. package/package.json +4 -4
  4. package/server/account.ts +10 -0
  5. package/server/build-info.ts +1 -1
  6. package/server/discovery/manifest.ts +2 -2
  7. package/server/mapping.ts +31 -0
  8. package/server/provider.ts +5 -18
  9. package/server/runtime/pins.ts +3 -3
  10. package/server/session.ts +7 -10
  11. package/server/unsupported-capabilities.ts +4 -1
  12. package/server/vendor/zcode/NOTICE.md +4 -1
  13. package/server/vendor/zcode/packages/rpc/src/channelClient.ts +1 -0
  14. package/server/vendor/zcode/packages/rpc/src/channelServer.ts +2 -0
  15. package/server/vendor/zcode/packages/rpc/src/channels.shared.ts +1 -0
  16. package/server/vendor/zcode/packages/rpc/src/protocol.ts +3 -2
  17. package/server/vendor/zcode/packages/shared/src/app-runtime-preferences.ts +18 -0
  18. package/server/vendor/zcode/packages/shared/src/attachment-source.ts +5 -0
  19. package/server/vendor/zcode/packages/shared/src/bots.ts +385 -2
  20. package/server/vendor/zcode/packages/shared/src/browser-use/commands.ts +3 -1
  21. package/server/vendor/zcode/packages/shared/src/channel-mention.ts +131 -0
  22. package/server/vendor/zcode/packages/shared/src/conversationSelection.ts +37 -0
  23. package/server/vendor/zcode/packages/shared/src/dynamic-workflow-feature.ts +137 -0
  24. package/server/vendor/zcode/packages/shared/src/env.ts +15 -11
  25. package/server/vendor/zcode/packages/shared/src/highspeed.ts +232 -0
  26. package/server/vendor/zcode/packages/shared/src/mcp-apps/appTools.ts +148 -0
  27. package/server/vendor/zcode/packages/shared/src/mcp-apps/contract.ts +262 -0
  28. package/server/vendor/zcode/packages/shared/src/mcp-apps/instance.ts +12 -0
  29. package/server/vendor/zcode/packages/shared/src/mcp-apps/resourceMime.ts +73 -0
  30. package/server/vendor/zcode/packages/shared/src/mcp-apps/sampling.ts +117 -0
  31. package/server/vendor/zcode/packages/shared/src/mcp-apps/schemas.ts +90 -0
  32. package/server/vendor/zcode/packages/shared/src/model-execution.ts +58 -1
  33. package/server/vendor/zcode/packages/shared/src/oauth.ts +3 -0
  34. package/server/vendor/zcode/packages/shared/src/off-peak-execution.ts +73 -0
  35. package/server/vendor/zcode/packages/shared/src/protocol.ts +78 -4
  36. package/server/vendor/zcode/packages/shared/src/remoteTarget.ts +65 -0
  37. package/server/vendor/zcode/packages/shared/src/request-security-edition/verification-policy.ts +46 -0
  38. package/server/vendor/zcode/packages/shared/src/server-remote.ts +93 -0
  39. package/server/vendor/zcode/packages/shared/src/source-server/compatibility.ts +115 -0
  40. package/server/vendor/zcode/packages/shared/src/source-server/contracts.ts +426 -0
  41. package/server/vendor/zcode/packages/shared/src/source-server/errorCodes.ts +74 -0
  42. package/server/vendor/zcode/packages/shared/src/sqlite-migrations/contract.ts +83 -0
  43. package/server/vendor/zcode/packages/shared/src/subagent-profile.ts +62 -0
  44. package/server/vendor/zcode/packages/shared/src/subagent-runtime-config.ts +51 -0
  45. package/server/vendor/zcode/packages/shared/src/subagents-types.ts +164 -0
  46. package/server/vendor/zcode/packages/shared/src/usage-stats.ts +14 -0
  47. package/server/vendor/zcode/packages/shared/src/validationAppSettings.ts +136 -3
  48. package/server/vendor/zcode/packages/shared/src/version.ts +13 -0
  49. package/server/vendor/zcode/packages/shared/src/zcode-agent-policy.ts +24 -1
  50. package/server/vendor/zcode/packages/shared/src/zcode-protocol/index.ts +697 -36
  51. package/server/vendor/zcode/packages/shared/src/zcode-protocol/trace.ts +11 -0
  52. package/server/vendor/zcode/packages/shared/src/zcode-protocol-legacy-types.ts +14 -2
  53. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/apply.ts +28 -9
  54. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/attachment-ref.ts +3 -0
  55. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/command.ts +73 -53
  56. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-session-config.ts +15 -0
  57. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/create-workflow-display.ts +47 -1
  58. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/delta.ts +58 -2
  59. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/input-intent.ts +31 -0
  60. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/rows.ts +28 -13
  61. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index-workflow-activity.ts +37 -3
  62. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/sessions-index.ts +36 -0
  63. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/snapshot.ts +21 -0
  64. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/submission.ts +1 -1
  65. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/toolDisplay.ts +32 -4
  66. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/transport.ts +31 -6
  67. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-assembler.ts +4 -1
  68. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/wire-fault.ts +4 -1
  69. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-artifacts.ts +62 -4
  70. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-command-rejections.ts +30 -0
  71. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-row-meta.ts +40 -3
  72. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-run-settings-command.ts +2 -2
  73. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs-delta.ts +6 -5
  74. package/server/vendor/zcode/packages/shared/src/zcode-protocol-v4/workflow-runs.ts +103 -26
  75. package/server/vendor/zcode/packages/shared/src/zcode-task-mode-schema.ts +13 -1
  76. package/server/vendor/zcode/packages/shared/src/zcode-task-types-core.ts +31 -2
  77. package/server/vendor/zcode/packages/shared/src/zcodeEndpoint.ts +188 -73
  78. package/server/vendor/zcode/provenance.json +189 -80
@@ -1,10 +1,18 @@
1
+ import { conversationQuotesSchema } from "../conversationSelection.js";
2
+ import { botGroupInputSourceSchema } from "../bots.js";
1
3
  // ConversationRow:行类型自包含。
2
4
  // 三条结构性规则:row 自包含(渲染任一行不需看别的行);
3
5
  // 结构变化换整行(row.upserted),文本增长用 append(row.delta);turn 是 row 上的标签不是容器。
4
6
  import { z } from "zod";
7
+ import { highspeedMessageMetaSchema } from "../highspeed.js";
5
8
  import { executionOutputPreviewSchema } from "../execution-output-preview.js";
6
9
  import { timestampSchema } from "./core.js";
7
- import { backgroundResultOriginMetaSchema, workflowLaunchMetaSchema } from "./workflow-row-meta.js";
10
+ import { conversationInputSourceSchema } from "./input-intent.js";
11
+ import {
12
+ backgroundResultOriginMetaSchema,
13
+ workflowLaunchMetaSchema,
14
+ } from "./workflow-row-meta.js";
15
+ import { attachmentRefSchema } from "./attachment-ref.js";
8
16
 
9
17
  // RowBase。rowId:session 内单调、永不复用、事件日志的确定性纯函数。
10
18
  const rowBaseFields = {
@@ -35,10 +43,12 @@ export type RowActions = NonNullable<z.infer<typeof rowActionsSchema>>;
35
43
  // workflow 轮的行级元数据拆到了 workflow-row-meta.ts(max-lines);名字在此原样再导出。
36
44
  export {
37
45
  backgroundResultOriginMetaSchema,
46
+ workflowBlockedMetaSchema,
38
47
  workflowLaunchMetaSchema,
39
48
  workflowNotificationMetaSchema,
40
49
  workflowSettingsAmendMetaSchema,
41
50
  type BackgroundResultOriginMeta,
51
+ type WorkflowBlockedMeta,
42
52
  type WorkflowLaunchMeta,
43
53
  type WorkflowNotificationMeta,
44
54
  type WorkflowSettingsAmendMeta,
@@ -79,6 +89,12 @@ export const turnHeaderRowSchema = z.object({
79
89
  endedAt: timestampSchema.optional(),
80
90
  // 权威工时:排除权限等待/用户输入等待/verifier 等待。
81
91
  activeMs: z.number().optional(),
92
+ // 当前 product turn 内主链路 ModelComplete 的 output usage 累计;optional 兼容旧快照。
93
+ outputTokens: z.number().int().nonnegative().optional(),
94
+ // CLI 从运行时事件聚合的真实耗时;optional 兼容旧快照及普通 turn。
95
+ modelDurationMs: z.number().nonnegative().optional(),
96
+ toolDurationMs: z.number().nonnegative().optional(),
97
+ otherDurationMs: z.number().nonnegative().optional(),
82
98
  // guide 不切 product turn,但每条 accepted guide 都开启独立视觉工作段。
83
99
  // 普通 turn 缺省以保持旧 snapshot 兼容;一旦出现 guide,CLI 负责完整投影首段与后续段。
84
100
  workSegments: z.array(turnWorkSegmentSchema).optional(),
@@ -100,6 +116,9 @@ export type TurnHeaderRow = z.infer<typeof turnHeaderRowSchema>;
100
116
  export const userInputRowSchema = z.object({
101
117
  ...rowBaseFields,
102
118
  kind: z.literal("userInput"),
119
+ inputOrigin: z.enum(["desktop", "mobile"]).optional(),
120
+ botGroupSource: botGroupInputSourceSchema.optional(),
121
+ conversationQuotes: conversationQuotesSchema.optional(),
103
122
  text: z.string(),
104
123
  // text 从此下标起是引擎附加文本(dwf ask 尾注 /
105
124
  // nudge),GUI 把它折进默认收起的披露;0 = 整条都是;缺席 = 无(老转录、非工作流会话)。
@@ -135,17 +154,12 @@ export const userInputRowSchema = z.object({
135
154
  rootSourceCommandId: z.string().optional(),
136
155
  // 提交端身份由 CLI admission 写入;旧 transcript 可缺省。
137
156
  clientId: z.string().optional(),
138
- attachments: z
139
- .array(
140
- z.object({
141
- ref: z.string(),
142
- fileName: z.string(),
143
- mime: z.string(),
144
- bytes: z.number(),
145
- previewRef: z.string().optional(),
146
- }),
147
- )
148
- .optional(),
157
+ // accelerated 历史展示的持久事实;缺省兼容普通消息和旧 snapshot。
158
+ highspeed: highspeedMessageMetaSchema.optional(),
159
+ // 重复定义曾在出队后的 row 校验中剥掉来源,导致历史附件丢失预览入口;与入站引用共用 schema。
160
+ attachments: z.array(attachmentRefSchema).optional(),
161
+ // 插件 UI 代发的消息带来源;卡片显示"来自插件 X"。旧 transcript 可缺省。
162
+ source: conversationInputSourceSchema.optional(),
149
163
  });
150
164
  export type UserInputRow = z.infer<typeof userInputRowSchema>;
151
165
 
@@ -203,9 +217,10 @@ export const toolCallRowSchema = z.object({
203
217
  // display 解析失败只丢这张卡的载荷,不拒整条 row(理由见 toolDisplay.ts 的 toolOutputSchema
204
218
  // 注释:装饰载荷不得决定 row/帧/订阅的生死)。
205
219
  display: toolCallDisplaySchema.optional().catch(undefined),
220
+ // (2026-09-12):原 widgetState / widgetStateVisibility 已删除,插件 UI 界面状态不再进 row。
206
221
  // status=error 时必带。
207
222
  error: z.object({ code: z.string(), message: z.string() }).optional(),
208
- // 仅 replayable 档运行中出现,终态清除。
223
+ // 运行中出现(ToolCallProgress 事件),终态清除。
209
224
  progress: toolProgressSchema.optional(),
210
225
  // continuous/replayable 共用的有界 Bash 内容,终态或后台移交时清除。
211
226
  outputPreview: executionOutputPreviewSchema.optional(),
@@ -41,6 +41,12 @@ export const sessionWorkflowPhaseSummarySchema = z.object({
41
41
  * 退化路(已进入的 phase)是按进入序拼出来的,没有并行可言,所以那时整个键缺席。
42
42
  */
43
43
  alongside: z.array(z.number().int().nonnegative()).max(WORKFLOW_RUNS_LIMITS.maxPhases).optional(),
44
+ /**
45
+ * 这一站是一处**还开着的留白**(docs/dynamic-workflow/presentation.md「Holes on the timeline」):
46
+ * `open` = 还没走到,`waiting` = run 停在这里等主代理补全。侧栏据此把灯画成虚线(等待时警示色)。
47
+ * 补全之后它就是一个普通的站,键缺席。只有声明表那条路有这个事实(`run.phaseHoles`)。
48
+ */
49
+ hole: z.enum(["open", "waiting"]).optional(),
44
50
  });
45
51
  export type SessionWorkflowPhaseSummary = z.infer<typeof sessionWorkflowPhaseSummarySchema>;
46
52
 
@@ -62,6 +68,12 @@ export const sessionWorkflowRunSummarySchema = z.object({
62
68
  currentPhase: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
63
69
  /** status === "running" 的子代理数(tooltip 的「{n} agents working」)。 */
64
70
  agentsWorking: z.number().int().nonnegative(),
71
+ /**
72
+ * 正在等主代理补全的留白名(`run.holes` 里第一条 waiting 的 `name`):侧栏运行行写
73
+ * 「{name} · 等待补全」(docs/dynamic-workflow/presentation.md「Holes on the timeline」的「The sidebar」)。
74
+ * 没有停驻的留白时缺席。
75
+ */
76
+ waitingHole: z.string().min(1).max(WORKFLOW_RUNS_LIMITS.maxPhaseNameLength).optional(),
65
77
  });
66
78
  export type SessionWorkflowRunSummary = z.infer<typeof sessionWorkflowRunSummarySchema>;
67
79
 
@@ -98,16 +110,26 @@ function phaseNameMatches(stationName: string, stamp: string): boolean {
98
110
  );
99
111
  }
100
112
 
101
- function derivePhases(run: WorkflowRunState): SessionWorkflowPhaseSummary[] {
113
+ /**
114
+ * 站点表的唯一推导。侧栏运行行(经 `summarizeRun`)与任务岛的 run 行都读它,所以两处的灯
115
+ * 永远对同一件事说同一句话;任务岛不另写第二套。
116
+ */
117
+ export function deriveSessionWorkflowPhases(run: WorkflowRunState): SessionWorkflowPhaseSummary[] {
102
118
  const entered = new Set((run.phases ?? []).map((phase) => phase.name));
103
119
  const current = run.currentPhase;
104
120
  let names: string[];
105
121
  // 「同时在跑」的下标说的是**声明表**里的位置,所以只有走声明表这条路时它才有意义;退化路
106
122
  // (已进入的 phase + 当前 phase)是另一个下标空间,那时整张表不带。
107
123
  let alongside: readonly (readonly number[])[] | undefined;
124
+ // 还开着的留白(声明表的下标);等待中的按名字从 `run.holes` 认——留白名按规则 9012 唯一。
125
+ let holeIndexes: ReadonlySet<number> = new Set();
126
+ const waitingNames = new Set(
127
+ (run.holes ?? []).filter((hole) => hole.state === "waiting").map((hole) => hole.name),
128
+ );
108
129
  if (run.phaseNames !== undefined && run.phaseNames.length > 0) {
109
130
  names = run.phaseNames;
110
131
  alongside = run.phaseAlongside;
132
+ holeIndexes = new Set(run.phaseHoles ?? []);
111
133
  } else {
112
134
  names = (run.phases ?? []).map((phase) => phase.name);
113
135
  if (current !== undefined && !entered.has(current)) names = [...names, current];
@@ -139,7 +161,14 @@ function derivePhases(run: WorkflowRunState): SessionWorkflowPhaseSummary[] {
139
161
  const beside = (alongside?.[index] ?? []).filter(
140
162
  (other) => Number.isInteger(other) && other >= 0 && other < emitted && other !== index,
141
163
  );
142
- return { name, status, ...(beside.length === 0 ? {} : { alongside: beside }) };
164
+ // 留白站:等待中的按 `run.holes` 认(名字唯一),其余下标表点名的是还没走到的留白。
165
+ const hole = waitingNames.has(name) ? "waiting" : holeIndexes.has(index) ? "open" : undefined;
166
+ return {
167
+ name,
168
+ status,
169
+ ...(beside.length === 0 ? {} : { alongside: beside }),
170
+ ...(hole === undefined ? {} : { hole }),
171
+ };
143
172
  });
144
173
  }
145
174
 
@@ -148,6 +177,10 @@ function summarizeRun(
148
177
  work: BackgroundWorkSummary | undefined,
149
178
  ): SessionWorkflowRunSummary {
150
179
  const name = work?.title.trim();
180
+ // 在跑的 run 才有「等补全」可言:终态 run 的 waiting 记录已被 reducer 剥掉,这里再守一道。
181
+ const waitingHole = isSessionWorkflowRunLive(run.status)
182
+ ? (run.holes ?? []).find((hole) => hole.state === "waiting")?.name
183
+ : undefined;
151
184
  return {
152
185
  runId: run.runId,
153
186
  ...(run.toolCallId === undefined ? {} : { toolCallId: run.toolCallId }),
@@ -155,9 +188,10 @@ function summarizeRun(
155
188
  status: run.status,
156
189
  ...(run.stopReason === undefined ? {} : { stopReason: run.stopReason }),
157
190
  ...(work === undefined ? {} : { startedAt: work.startedAt }),
158
- phases: derivePhases(run),
191
+ phases: deriveSessionWorkflowPhases(run),
159
192
  ...(run.currentPhase === undefined ? {} : { currentPhase: run.currentPhase }),
160
193
  agentsWorking: run.actors.filter((actor) => actor.status === "running").length,
194
+ ...(waitingHole === undefined ? {} : { waitingHole }),
161
195
  };
162
196
  }
163
197
 
@@ -35,6 +35,7 @@ export const sessionSummarySchema = z.object({
35
35
  // custom = 用户显式重命名;default/generated 都不是产品语义上的手动标题。
36
36
  // optional 是为了兼容旧 sessions-index frame / 旧持久化摘要。
37
37
  titleSource: sessionMetaStateSchema.shape.titleSource.optional(),
38
+ lastOutcome: z.enum(["completed", "error", "interrupted"]).optional(),
38
39
  phase: sessionPhaseSchema,
39
40
  sessionEnded: z.boolean(),
40
41
  // 列表小圆点用(此处保留布尔,避免为侧栏订阅整个 backgroundWorks)。
@@ -72,6 +73,41 @@ export const sessionsIndexDeltaSchema = z.discriminatedUnion("op", [
72
73
  ]);
73
74
  export type SessionsIndexDelta = z.infer<typeof sessionsIndexDeltaSchema>;
74
75
 
76
+ /** snapshot 缺席不是删除;按明确 ID 查询持久事实,不会创建或恢复运行实例。 */
77
+ export const sessionsIndexReconcileParamsSchema = z
78
+ .object({
79
+ workspaceId: z.string().min(1),
80
+ sessionIds: z.array(z.string().min(1)).max(256),
81
+ })
82
+ .strict();
83
+ export type SessionsIndexReconcileParams = z.infer<typeof sessionsIndexReconcileParamsSchema>;
84
+ export const sessionsIndexReconcileResultSchema = z
85
+ .object({
86
+ sessions: z
87
+ .array(
88
+ z.discriminatedUnion("exists", [
89
+ z
90
+ .object({ sessionId: z.string(), exists: z.literal(false), deleted: z.literal(false) })
91
+ .strict(),
92
+ z
93
+ .object({
94
+ sessionId: z.string(),
95
+ exists: z.literal(true),
96
+ deleted: z.boolean(),
97
+ title: z.string(),
98
+ titleSource: sessionMetaStateSchema.shape.titleSource,
99
+ createdAt: timestampSchema,
100
+ updatedAt: timestampSchema,
101
+ lastOutcome: z.enum(["completed", "error", "interrupted"]).optional(),
102
+ })
103
+ .strict(),
104
+ ]),
105
+ )
106
+ .max(256),
107
+ })
108
+ .strict();
109
+ export type SessionsIndexReconcileResult = z.infer<typeof sessionsIndexReconcileResultSchema>;
110
+
75
111
  /** sessions-index topic key 构造(与 parseSessionsIndexTopic 对偶)。 */
76
112
  export function sessionsIndexTopic(workspaceId: string): string {
77
113
  return `sessions-index/${workspaceId}`;
@@ -60,6 +60,7 @@ export const activeWorkSummarySchema = z.object({
60
60
  "turnSteer",
61
61
  ]),
62
62
  foregroundExecutionId: z.string().min(1).optional(),
63
+ sourceCommandId: z.string().min(1).optional(),
63
64
  startedAt: timestampSchema,
64
65
  });
65
66
  export type ActiveWorkSummary = z.infer<typeof activeWorkSummarySchema>;
@@ -99,6 +100,10 @@ export const errorAttributionSchema = z
99
100
  statusCode: z.number().int().min(100).max(599).optional(),
100
101
  providerErrorCode: z.string().min(1).max(160).optional(),
101
102
  retryable: z.boolean().optional(),
103
+ /** string=使用该 Highspeed 卡,null=明确未使用,undefined=旧 Agent 未发布。 */
104
+ highspeedCardId: z.string().nullable().optional(),
105
+ /** true=本轮 Highspeed 失败已命中退回声明但无可用退回目标;Renderer 据此作废匹配卡。 */
106
+ highspeedFallbackUnavailable: z.boolean().optional(),
102
107
  })
103
108
  .strict();
104
109
  export type ErrorAttribution = z.infer<typeof errorAttributionSchema>;
@@ -467,7 +472,21 @@ export const workspaceHookAdmissionStateSchema = z.object({
467
472
  });
468
473
  export type WorkspaceHookAdmissionSnapshotState = z.infer<typeof workspaceHookAdmissionStateSchema>;
469
474
 
475
+ // 冷历史可读不代表 runtime 已可执行;缺席兼容旧发送端。
476
+ export const runtimeInitializationSchema = z.discriminatedUnion("status", [
477
+ z.object({ status: z.literal("initializing"), attemptId: z.string().min(1) }),
478
+ z.object({ status: z.literal("ready"), attemptId: z.string().min(1) }),
479
+ z.object({
480
+ status: z.literal("failed"),
481
+ attemptId: z.string().min(1),
482
+ reasonCode: z.string(),
483
+ message: z.string(),
484
+ }),
485
+ ]);
486
+ export type RuntimeInitialization = z.infer<typeof runtimeInitializationSchema>;
487
+
470
488
  export const conversationSnapshotSchema = z.object({
489
+ runtimeInitialization: runtimeInitializationSchema.optional(),
471
490
  protocolVersion: z.literal(1),
472
491
  sessionId: z.string(),
473
492
  logEpoch: z.string(),
@@ -503,6 +522,8 @@ export const conversationSnapshotSchema = z.object({
503
522
  // 旧快照/旧发送端不携带此字段 → 解析得 null,不破坏兼容性(遵守冻结规则)。
504
523
  // pendingCount === 0 时投影层置 null(提示条消失)。
505
524
  workspaceHookAdmission: workspaceHookAdmissionStateSchema.nullable().default(null),
525
+ // (2026-09-12):原 additive 字段 pluginUiSurfaces 已删除,插件面板 widgetState 只留在
526
+ // 宿主 renderer 内存。
506
527
  // B 区
507
528
  rows: rowsWindowSchema,
508
529
  });
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  /** Composer 可以显式提交的 Agent mode;auto 是 Runtime 内部状态,不进入用户 Submission。 */
4
- export const submissionModeSchema = z.enum(["build", "edit", "plan", "yolo"]);
4
+ export const submissionModeSchema = z.enum(["build", "edit", "plan", "yolo", "guarded"]);
5
5
  export type SubmissionMode = z.infer<typeof submissionModeSchema>;
@@ -6,6 +6,7 @@ import { bashOutputDisplaySchema } from "../bash-output-display.js";
6
6
  import { timestampSchema } from "./core.js";
7
7
  import { OFFICIAL_MCP_TOOL_ERROR_CODES } from "../official-mcp-tool-error.js";
8
8
  import { cuaRequestAccessStatusSchema } from "./cuaPermission.js";
9
+ import { mcpToolDisplayUiSchema } from "../mcp-apps/schemas.js";
9
10
  import { toolCallCreateWorkflowDisplaySchema } from "./create-workflow-display.js";
10
11
  import {
11
12
  toolCallEvalWorkflowSnippetDisplaySchema,
@@ -20,7 +21,7 @@ import {
20
21
  // errorCode/suggestedAction/media(screenshot) 等结构化内容带到 renderer)。consume-main 之前
21
22
  // 缺这个 union + toolOutputSchema.display 字段——协议层 zod 校验会把 agent 下发的 display 整个
22
23
  // strip 掉,导致 UI 永远拿不到 display?.kind==="cua",CUA 工具调用退化成 fallback 渲染。
23
- const toolResultDisplaySchema = z.discriminatedUnion("kind", [
24
+ export const toolResultDisplaySchema = z.discriminatedUnion("kind", [
24
25
  bashOutputDisplaySchema,
25
26
  z.object({
26
27
  kind: z.literal("file_diff"),
@@ -133,6 +134,8 @@ const toolResultDisplaySchema = z.discriminatedUnion("kind", [
133
134
  .object({ code: z.enum(OFFICIAL_MCP_TOOL_ERROR_CODES) })
134
135
  .strict()
135
136
  .optional(),
137
+ // 插件 UI 元数据;与 CLI contracts 的 mcpToolResultDisplayPayloadSchema 同源复用,见 mcp-apps/schemas.ts。
138
+ ui: mcpToolDisplayUiSchema.optional(),
136
139
  }),
137
140
  // buildToolOutput 把 CLI 侧 ToolResultDisplayPayload 原样塞进 toolOutput.display,
138
141
  // 而这条 union 是 strict 的——create_workflow 不在成员里,CreateWorkflow 的 display 会被整段
@@ -150,8 +153,27 @@ const toolResultDisplaySchema = z.discriminatedUnion("kind", [
150
153
  ]);
151
154
  export type ToolResultDisplay = z.infer<typeof toolResultDisplaySchema>;
152
155
 
153
- // toolCall。终态 output 全档统一 head+tail 截断,超出走 truncated.ref 按需拉。
154
- // display 是展示载荷;版本不兼容时降级为无卡片,避免同一内容导致整条订阅反复恢复失败。
156
+ // §4.4.5 toolCall。终态 output 全档统一 head+tail 截断(R-10),超出走 truncated.ref 按需拉。
157
+ //
158
+ // ⚠ display 在信封层**不设门**(`.optional().catch(undefined)`):解析不过就退化成「这张卡没有
159
+ // 载荷」,而不是让整条 row、整帧、整条订阅失败。四个嵌入点共用这条规则——本文件的
160
+ // toolOutputSchema、rows.ts 的 toolCallRow、snapshot.ts 的确认预览、workflow-row-meta.ts 的
161
+ // 启动行图;spec 见 docs/v4-refactor/10-protocol-spec.md §4.4.5 与
162
+ // docs/v4-refactor/04-sync-and-recovery.md 封闭规则 11。
163
+ //
164
+ // 根因(2026-09-21,sess_4142de31):display 是装饰载荷,却长在 liveness 关键的信封里。CLI 给
165
+ // `list_workflow_runs` 的 run 行加了两个 lineage 键,本侧镜像没跟上 → 整帧被
166
+ // `proto.frameAssemblyInvalidPayload` 拒 → 恢复阶梯在同一份内容上重试 → 会话停在
167
+ // `fault.subscription.recoveryFailed`;而每次快照都重放同一份存量载荷,会话永不自愈。同款事故
168
+ // 2026-09-17 已经发生过一次(`get_workflow_run` 的 `providerStop`),当时只修了那一个字段。
169
+ //
170
+ // 严格性没有丢,只是回到该在的那一层:渲染侧 readToolResultDisplay
171
+ // (packages/ui/src/ToolCallBlocks/toolResultDisplay.ts)本来就按 kind 逐个 strict 解析,失败即
172
+ // 回 undefined、卡片退化成纯文本——这正是各 spec 一直承诺的行为。信封层那道门是重复的第二道
173
+ // 门,唯一的独有效果是杀死会话。
174
+ //
175
+ // 成员 schema 仍然 `.strict()`:它们定义「本端认得的形状」,catch 只把「不认得」从致命降级为
176
+ // 无卡。代价是 skew 变静默,补偿手段是构造侧与镜像侧的 parity 测试,而不是让线上订阅去发现。
155
177
  export const toolOutputSchema = z.object({
156
178
  text: z.string(),
157
179
  display: toolResultDisplaySchema.optional().catch(undefined),
@@ -164,9 +186,14 @@ export const toolOutputSchema = z.object({
164
186
  });
165
187
  export type ToolOutput = z.infer<typeof toolOutputSchema>;
166
188
 
189
+ // 工具运行中的进度():MCP `notifications/progress` 经 ToolCallProgress 事件投影到行上。
190
+ // fraction 由 payload progress/total 推得(total 缺失时不填);bytes / previewLine 保留给 shell 输出型进度。
167
191
  export const toolProgressSchema = z.object({
168
- bytes: z.number(),
192
+ bytes: z.number().optional(),
169
193
  previewLine: z.string().optional(),
194
+ fraction: z.number().min(0).max(1).optional(),
195
+ total: z.number().optional(),
196
+ message: z.string().max(200).optional(),
170
197
  updatedAt: timestampSchema,
171
198
  });
172
199
  export type ToolProgress = z.infer<typeof toolProgressSchema>;
@@ -245,6 +272,7 @@ const toolCallMcpDisplaySchema = z
245
272
  .object({ code: z.enum(OFFICIAL_MCP_TOOL_ERROR_CODES) })
246
273
  .strict()
247
274
  .optional(),
275
+ ui: mcpToolDisplayUiSchema.optional(),
248
276
  })
249
277
  .strict();
250
278
 
@@ -35,6 +35,7 @@ export const hostCapabilitiesSchema = z.object({
35
35
  * `state.updated`,并且先经 `clampWorkflowRunsForLegacy` 裁到旧界。
36
36
  */
37
37
  workflowRunDeltas: z.boolean().optional(),
38
+ historyFirst: z.boolean().optional(),
38
39
  });
39
40
  export type HostCapabilities = z.infer<typeof hostCapabilitiesSchema>;
40
41
 
@@ -82,6 +83,7 @@ export const clientHelloSchema = z
82
83
  * 是 `.strict()` 的,老 Host 见到不认识的键会整条 clientHello 解析失败、连接握不上手。
83
84
  */
84
85
  workflowRunDeltas: z.boolean().optional(),
86
+ historyFirst: z.boolean().optional(),
85
87
  })
86
88
  .strict()
87
89
  .optional(),
@@ -112,6 +114,8 @@ export const subscribeParamsSchema = z
112
114
  topic: z.string(),
113
115
  // 水位不变量:仅当客户端真持有该时刻一致状态才允许带。
114
116
  base: z.object({ logEpoch: z.string(), seq: z.number() }).optional(),
117
+ // 手动重试仅消费当前 failed attempt;普通重连不重跑 hook。
118
+ retryInitialization: z.string().min(1).optional(),
115
119
  // QoS hint,只影响调度,不影响语义。
116
120
  visibility: z.enum(["foreground", "background"]).optional(),
117
121
  })
@@ -142,6 +146,7 @@ export const conversationOpenTimingSchema = z
142
146
  cliRequestMs: openTimingMsSchema,
143
147
  cliBootstrapMs: openTimingMsSchema,
144
148
  cliSessionRestoreMs: openTimingMsSchema,
149
+ historyReadyMs: openTimingMsSchema,
145
150
  initialFrameEncodeMs: openTimingMsSchema,
146
151
  cliProcessState: z.enum(["spawned", "reused"]).optional(),
147
152
  sessionRuntimeState: z.enum(["cold", "warm"]).optional(),
@@ -335,6 +340,7 @@ export const V4_METHODS = {
335
340
  controllerResync: "v4/controller/resync",
336
341
  controllerUnsubscribe: "v4/controller/unsubscribe",
337
342
  conversationSubscribe: "v4/conversation/subscribe",
343
+ sessionsIndexReconcile: "v4/sessionsIndex/reconcile",
338
344
  conversationResync: "v4/conversation/resync",
339
345
  conversationUnsubscribe: "v4/conversation/unsubscribe",
340
346
  // 行分页 query(rows/range):只读、无状态、超时重发安全。
@@ -343,6 +349,7 @@ export const V4_METHODS = {
343
349
  conversationPlans: "v4/conversation/plans",
344
350
  conversationFileChanges: "v4/conversation/fileChanges",
345
351
  backgroundBashOutput: "v4/conversation/backgroundBashOutput",
352
+ offPeakControl: "v4/conversation/offPeakControl",
346
353
  conversationFileRewindPreview: "v4/conversation/fileRewindPreview",
347
354
  // workflow run 的事件日志分页(详情页审计面):只读、无状态、超时重发安全。
348
355
  // 新方法天然偏斜安全——旧桌面根本不会调用它。
@@ -405,6 +412,7 @@ export type V4ConnectionFlowParams = z.infer<typeof v4ConnectionFlowParamsSchema
405
412
  export const v4ConnectionFlowResultSchema = z.object({}).strict();
406
413
 
407
414
  export const V4_NOTIFICATIONS = {
415
+ offPeakLifecycle: "v4/offPeak/lifecycle",
408
416
  // 下行帧(snapshot / deltas),params = ConversationTopicFrame。
409
417
  conversationFrame: "v4/conversation/frame",
410
418
  // 仅 live ingest 的无正文事实;不进入 topic snapshot/recovery。
@@ -412,6 +420,7 @@ export const V4_NOTIFICATIONS = {
412
420
  localTtftFacts: "v4/telemetry/local-ttft",
413
421
  // 仅当前进程 live ToolCallResult 产生;历史与 replayable 链路不得补造。
414
422
  cuaPermissionObservation: "v4/cua/permission-observation",
423
+ pluginUiToolResult: "v4/plugin-ui/tool-result",
415
424
  } as const;
416
425
 
417
426
  /** 3.3.6 SSH 历史任务归属证明与 sessions-index 冷种子共享同一有界窗口。 */
@@ -420,6 +429,7 @@ export const MAX_LEGACY_TASK_IDS_PER_SUBSCRIBE = 200;
420
429
  // subscribe 请求 = 通用 SubscribeParams + connectionId(重订阅替换按
421
430
  // (connectionId, topic) 判定;stdio 单管道场景由 host 为每个下游客户端分配)。
422
431
  export const v4ConversationSubscribeParamsSchema = subscribeParamsSchema.extend({
432
+ additionalDirectories: z.array(z.string().trim().min(1)).max(256).optional(),
423
433
  connectionId: z.string(),
424
434
  clientMode: z.enum(["desktop-continuous", "web-remote-replayable"]),
425
435
  // 当前可信 attachment 的 workspace;cold resume 优先使用它恢复身份,live 不消费。
@@ -434,6 +444,7 @@ export const v4ConversationSubscribeParamsSchema = subscribeParamsSchema.extend(
434
444
  * 不是某一次订阅可以自选的口味。缺席 = 按旧消费者处理(整键 patch + 旧界裁剪)。
435
445
  */
436
446
  workflowRunDeltas: z.boolean().optional(),
447
+ historyFirst: z.boolean().optional(),
437
448
  });
438
449
  export type V4ConversationSubscribeParams = z.infer<typeof v4ConversationSubscribeParamsSchema>;
439
450
 
@@ -596,13 +607,24 @@ export type V4ConversationFileChangesResult = z.infer<typeof v4ConversationFileC
596
607
  // (`appendEvent` 单调分配),在 workflowRuns[].lastEventSequence 抬升时重取。
597
608
  // 刻意**不是** v4 command:command 的 ACK 结果是 commandResultSchema 那个封闭的「变更结果」
598
609
  // 判别联合,把一页只读事件塞进去等于把读放进写的词汇表,还要白背 baseRevision/幂等那套机制。
610
+
611
+ /**
612
+ * 事件日志一页的两道界,网关按它们取数(apps/zcode-cli/packages/dynamic-workflow/docs/execution-engine.md
613
+ * 「Reading the journal」)。字节按存储里的序列化字节计、取协议单帧上限的四分之一:条数单独
614
+ * 管不住字节,500 条带着大 report item 的事件足以顶到单帧上限。
615
+ */
616
+ export const WORKFLOW_RUN_EVENTS_PAGE_LIMITS = {
617
+ maxEvents: 500,
618
+ maxBytes: PROTOCOL_V4_LIMITS.logicalFrameAssemblyMaxBytes / 4,
619
+ } as const;
620
+
599
621
  export const v4ConversationWorkflowRunEventsParamsSchema = z
600
622
  .object({
601
623
  sessionId: z.string().min(1),
602
624
  runId: z.string().min(1),
603
625
  /** 只取 sequence 严格大于该值的事件;缺省从头取。 */
604
626
  afterSequence: z.number().int().nonnegative().optional(),
605
- limit: z.number().int().positive().max(500).optional(),
627
+ limit: z.number().int().positive().max(WORKFLOW_RUN_EVENTS_PAGE_LIMITS.maxEvents).optional(),
606
628
  })
607
629
  .strict();
608
630
  export type V4ConversationWorkflowRunEventsParams = z.infer<
@@ -623,7 +645,7 @@ export const v4ConversationWorkflowRunEventsResultSchema = z
623
645
  })
624
646
  .strict(),
625
647
  ),
626
- /** 本页取满 limit 且后面仍有事件。 */
648
+ /** 本页之后仍有事件(页可能因条数或字节上界提前收尾)。 */
627
649
  hasMore: z.boolean(),
628
650
  })
629
651
  .strict();
@@ -1016,8 +1038,9 @@ export const v4AttachmentReadResultSchema = z
1016
1038
  (value) =>
1017
1039
  value.startsWith("image/") ||
1018
1040
  value.startsWith("video/") ||
1019
- value.split(";", 1)[0]?.trim().toLowerCase() === "application/pdf",
1020
- "attachment preview only supports image/video/pdf media types",
1041
+ value.split(";", 1)[0]?.trim().toLowerCase() === "application/pdf" ||
1042
+ value.split(";", 1)[0]?.trim().toLowerCase() === "text/plain",
1043
+ "attachment preview only supports image/video/pdf/plain text types",
1021
1044
  ),
1022
1045
  totalBytes: z.number().int().nonnegative().max(PROTOCOL_V4_LIMITS.attachmentPreviewMaxBytes),
1023
1046
  nextOffset: z.number().int().positive().nullable(),
@@ -1025,7 +1048,9 @@ export const v4AttachmentReadResultSchema = z
1025
1048
  .strict()
1026
1049
  .superRefine((value, context) => {
1027
1050
  if (
1028
- value.mediaType.startsWith("image/") &&
1051
+ // 根因:运行时已支持材料文本,响应 schema 仍只接受媒体,导致跨进程预览失败。
1052
+ (value.mediaType.startsWith("image/") ||
1053
+ value.mediaType.split(";", 1)[0]?.trim().toLowerCase() === "text/plain") &&
1029
1054
  value.totalBytes > PROTOCOL_V4_LIMITS.attachmentMaxBytes
1030
1055
  ) {
1031
1056
  context.addIssue({
@@ -1033,7 +1058,7 @@ export const v4AttachmentReadResultSchema = z
1033
1058
  maximum: PROTOCOL_V4_LIMITS.attachmentMaxBytes,
1034
1059
  origin: "number",
1035
1060
  inclusive: true,
1036
- message: "image preview exceeds total byte limit",
1061
+ message: "image/text preview exceeds total byte limit",
1037
1062
  path: ["totalBytes"],
1038
1063
  });
1039
1064
  }
@@ -192,7 +192,10 @@ export class TopicWireFrameAssembler<F> {
192
192
  accept(wire: TopicWireFrameCandidate, now = Date.now()): TopicWireAssemblyEvent<F>[] {
193
193
  const events = this.expire(now);
194
194
  const key = routeKey(wire.topic, wire.subscriptionId);
195
- if (!Number.isSafeInteger(wire.logicalFrameOrdinal) || wire.logicalFrameOrdinal < 1) {
195
+ if (
196
+ !Number.isSafeInteger(wire.logicalFrameOrdinal) ||
197
+ wire.logicalFrameOrdinal < 1
198
+ ) {
196
199
  events.push(this.fault(wire, "proto.frameAssemblyMetadataMismatch"));
197
200
  return events;
198
201
  }
@@ -1,11 +1,14 @@
1
1
  // V4 physical assembly fault 的分类:**内容确定性失败** vs 瞬态失败。
2
+ // spec:docs/v4-refactor/04-sync-and-recovery.md 封闭规则 11、
3
+ // docs/v4-refactor/05-transport-and-backpressure.md「客户端规则」。
2
4
  //
3
5
  // 恢复阶梯默认把 fault 当瞬态处理:先 same-sub resync(服务端可能回 resume),再断档则升级
4
6
  // forceSnapshot,仍不成则 fail closed。这套升级对丢片、超时、校验和不符是对的——重投一次很可能
5
7
  // 就好了。对 **schema 拒收** 却是错的:字节已经过了 length/checksum/UTF-8/JSON 四道关,被拒说明
6
8
  // 本端读不懂对端发来的**内容**,而 resume 档只会把同一批 delta 再投一遍,必然同样被拒。
7
9
  //
8
- // 工具卡载荷出现不兼容字段时,重投相同内容无法恢复;应尝试快照后明确报告内容不兼容。
10
+ // 2026-09-21(sess_4142de31)的事故就是这条:一个工具卡载荷多带两个键,恢复阶梯在同一份内容上
11
+ // 烧掉三帧后 fail closed,用户手动「重新连接」两次、每次再烧三帧,会话再也打不开。
9
12
  //
10
13
  // 本模块只回答「这个 reason code 是不是确定性内容失败」,不决定怎么处置——处置在两个 store 里
11
14
  // (conversationProjectionStore / sessionsIndexStore)。
@@ -49,8 +49,49 @@ export const WORKFLOW_ARTIFACT_LIMITS = {
49
49
  maxItemsPerPage: 500,
50
50
  /** 一页的缺省条数(调用方不传 limit 时网关用它)。 */
51
51
  defaultItemsPerPage: 200,
52
+ /**
53
+ * `workflowRunArtifactData` 一页的字节上界(按存储里的序列化字节计),由网关传给存储层。
54
+ * 取协议单帧上限(16 MiB)的四分之一:条数上界单独管不住字节——500 条 × 32 KiB 就顶到了
55
+ * 单帧上限,于是一页可能根本发不出去。一页至少带一条,所以单条的上限(`REPORT_CAPS`)
56
+ * 必须远小于单帧上限。
57
+ */
58
+ maxPageBytes: PROTOCOL_V4_LIMITS.logicalFrameAssemblyMaxBytes / 4,
59
+ /** `workflowRunArtifactData` 一次最多点名几个字段路径(一张看板的 spec 用不到这么多)。 */
60
+ maxFieldsPerRequest: 32,
61
+ /** 一个字段路径的最大长度。 */
62
+ maxFieldPathLength: 256,
63
+ /**
64
+ * 一个字段值的序列化字节上界。超过的值回成一段截短的字符串(前 1,000 个字符加「…」):
65
+ * 图表只用数值,表格与看板的格子本来就只显示短文本。
66
+ */
67
+ maxFieldValueBytes: 4096,
52
68
  } as const;
53
69
 
70
+ /**
71
+ * 按点路径从一条 report item 里取值:"timing.after"、"rounds.0.ms"(docs/execution-engine.md
72
+ * 「Reading the journal」)。
73
+ *
74
+ * 这是**协议上的契约**,不只是渲染器的工具函数:看板取数时 CLI 在 SQLite 里按同一套规则把
75
+ * 字段取出来(adapters 的 dwf-journal-fields.ts),渲染器拿到的值必须与它自己从整条 item 里
76
+ * 取出来的逐字相同。规则:按 `.` 切段;当前值是数组时,段按 `Number(段)` 当下标(非负整数且
77
+ * 在界内才算);是对象时按键取;走不通(null、标量、越界、缺键)得 `undefined`。
78
+ */
79
+ export function readWorkflowArtifactField(item: unknown, path: string): unknown {
80
+ let cursor: unknown = item;
81
+ for (const segment of path.split(".")) {
82
+ if (cursor === null || cursor === undefined) return undefined;
83
+ if (Array.isArray(cursor)) {
84
+ const index = Number(segment);
85
+ if (!Number.isInteger(index) || index < 0 || index >= cursor.length) return undefined;
86
+ cursor = cursor[index];
87
+ continue;
88
+ }
89
+ if (typeof cursor !== "object") return undefined;
90
+ cursor = (cursor as Record<string, unknown>)[segment];
91
+ }
92
+ return cursor;
93
+ }
94
+
54
95
  /**
55
96
  * 一个产物版本的**元数据**(journal `dwf_node.result_json` 上 `ArtifactVersionRecord` 的
56
97
  * zod 镜像)。字节不在这里——`uri` 指向 tool-artifact store,取字节走
@@ -120,7 +161,7 @@ export type WorkflowRunArtifact = z.infer<typeof workflowRunArtifactSchema>;
120
161
  * `workflowRunArtifactRead` 取字节)按需拉——权威始终在 journal。
121
162
  *
122
163
  * `itemCount` 在这里的职责是**刷新信号**:看板 hook 见它变化就带 `afterSequence` 增量取数。
123
- * 把标签 report 的原值放进快照会让 256 条 × 32KB 的最坏情形把状态帧撑到 2MB。
164
+ * 把标签 report 的原值放进这个高频状态键,一个 run 的条目有多少字节,每一帧就要搬多少字节。
124
165
  */
125
166
  export const workflowRunArtifactSummarySchema = z
126
167
  .object({
@@ -174,6 +215,18 @@ export const v4ConversationWorkflowRunArtifactDataParamsSchema = z
174
215
  afterSequence: z.number().int().nonnegative().optional(),
175
216
  /** 缺省 200、钳 [1, 500]——两者都在网关侧执行(存储层不得自造页大小,也不得再钳)。 */
176
217
  limit: z.number().int().positive().max(WORKFLOW_ARTIFACT_LIMITS.maxItemsPerPage).optional(),
218
+ /**
219
+ * 只取这些字段路径({@link readWorkflowArtifactField} 的规则),而不是整条 item:看板只读
220
+ * spec 里点名的字段,一条 1 MiB 的 item 不该为了一个数被整条搬进渲染器。给了它,结果每条
221
+ * 带 `fields` 而不带 `item`。缺省 = 整条 item(老渲染器的形状)。
222
+ *
223
+ * 偏斜:老 CLI 的 strict schema 不认这个键、整条请求被拒;渲染器据此退回不带它重试。
224
+ */
225
+ fields: z
226
+ .array(z.string().min(1).max(WORKFLOW_ARTIFACT_LIMITS.maxFieldPathLength))
227
+ .min(1)
228
+ .max(WORKFLOW_ARTIFACT_LIMITS.maxFieldsPerRequest)
229
+ .optional(),
177
230
  })
178
231
  .strict();
179
232
  export type V4ConversationWorkflowRunArtifactDataParams = z.infer<
@@ -193,14 +246,19 @@ export const v4ConversationWorkflowRunArtifactDataResultSchema = z
193
246
  /**
194
247
  * 条目原值,**不做预览序列化**:看板的纯函数要按字段路径取数
195
248
  * (`ChartSpec.x.field` 形如 "timing.after"),拿到一段 pretty JSON 文本就没法取了。
196
- * 单条在线上已由 `REPORT_CAPS.maxItemSerializedBytes`(32KB)与事件载荷有界化
197
- * 双重保证,这里不再叠一层界。
249
+ * 单条由写入侧的 `REPORT_CAPS.maxItemSerializedBytes` 有界,一页由
250
+ * `WORKFLOW_ARTIFACT_LIMITS.maxPageBytes` 有界。请求带 `fields` 时缺席。
198
251
  */
199
252
  item: z.unknown(),
253
+ /**
254
+ * 请求带 `fields` 时:路径 → 值。走不通的路径不在表里(= `undefined`);超过
255
+ * `maxFieldValueBytes` 的值是一段截短的字符串。
256
+ */
257
+ fields: z.record(z.string(), z.unknown()).optional(),
200
258
  })
201
259
  .strict(),
202
260
  ),
203
- /** 本页取满 limit 且后面仍有条目(网关多取一条判定)。 */
261
+ /** 本页之后仍有条目(页可能因条数或字节上界提前收尾;由存储层判定)。 */
204
262
  hasMore: z.boolean(),
205
263
  })
206
264
  .strict();