@capybara-hq/kernel 0.1.0-rc.2 → 0.1.0-rc.3

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 (133) hide show
  1. package/dist/agents/agent-lookup.js +10 -2
  2. package/dist/agents/agents.service.js +19 -6
  3. package/dist/agents/definition.d.ts +31 -8
  4. package/dist/agents/definition.js +193 -17
  5. package/dist/agents/templates.js +19 -4
  6. package/dist/catalog/catalog-content.js +4 -1
  7. package/dist/catalog/claude/agents.js +13 -10
  8. package/dist/catalog/marketplaces.service.d.ts +1 -1
  9. package/dist/channels/cards.d.ts +8 -2
  10. package/dist/channels/cards.js +16 -8
  11. package/dist/config/schema.js +2 -2
  12. package/dist/conversations/artifacts.d.ts +1 -1
  13. package/dist/conversations/artifacts.js +1 -1
  14. package/dist/conversations/event-log.d.ts +11 -0
  15. package/dist/conversations/event-log.js +19 -0
  16. package/dist/conversations/events.d.ts +23 -3
  17. package/dist/conversations/events.js +26 -14
  18. package/dist/db/kernel-schema.d.ts +2 -2
  19. package/dist/evals/recorded-tools.d.ts +27 -1
  20. package/dist/evals/recorded-tools.js +85 -10
  21. package/dist/evals/run-replay.d.ts +5 -0
  22. package/dist/evals/run-replay.js +19 -5
  23. package/dist/evals/trajectory.d.ts +2 -2
  24. package/dist/identity/settings.js +1 -1
  25. package/dist/lifecycle/kernel-lifecycle.js +5 -0
  26. package/dist/orgs/settings.service.d.ts +2 -2
  27. package/dist/orgs/settings.service.js +2 -2
  28. package/dist/runtime/context/build.d.ts +6 -6
  29. package/dist/runtime/context/build.js +18 -8
  30. package/dist/runtime/context/claude-tools.d.ts +2 -1
  31. package/dist/runtime/context/claude-tools.js +9 -10
  32. package/dist/runtime/context/legacy-text.d.ts +22 -0
  33. package/dist/runtime/context/legacy-text.js +37 -0
  34. package/dist/runtime/context/skills.d.ts +29 -11
  35. package/dist/runtime/context/skills.js +37 -30
  36. package/dist/runtime/loop.d.ts +1 -1
  37. package/dist/runtime/model-call.js +1 -1
  38. package/dist/runtime/run-finish.d.ts +1 -1
  39. package/dist/runtime/run-queue.d.ts +3 -3
  40. package/dist/runtime/run-queue.js +2 -2
  41. package/dist/runtime/run-worker.d.ts +2 -2
  42. package/dist/runtime/run-worker.js +4 -4
  43. package/dist/runtime/subagents/definition.d.ts +1 -6
  44. package/dist/runtime/subagents/definition.js +11 -12
  45. package/dist/runtime/subagents/dispatch.d.ts +2 -2
  46. package/dist/runtime/subagents/dispatch.js +2 -2
  47. package/dist/runtime/subagents/org-agents.js +2 -1
  48. package/dist/runtime/subagents/roster.d.ts +1 -1
  49. package/dist/runtime/subagents/roster.js +1 -1
  50. package/dist/runtime/subagents/skill-fork.d.ts +2 -2
  51. package/dist/runtime/subagents/skill-fork.js +2 -2
  52. package/dist/runtime/subagents/subagents.service.d.ts +3 -3
  53. package/dist/runtime/subagents/subagents.service.js +3 -3
  54. package/dist/runtime/waits.d.ts +5 -5
  55. package/dist/runtime/waits.js +5 -5
  56. package/dist/skills/catalog.d.ts +4 -4
  57. package/dist/skills/catalog.js +4 -4
  58. package/dist/skills/changes.service.d.ts +2 -2
  59. package/dist/skills/changes.service.js +5 -4
  60. package/dist/skills/invocation-keys.d.ts +1 -1
  61. package/dist/skills/invocation-keys.js +1 -1
  62. package/dist/skills/invoke.d.ts +1 -1
  63. package/dist/skills/invoke.js +5 -4
  64. package/dist/tools/approvals.service.d.ts +6 -0
  65. package/dist/tools/approvals.service.js +19 -1
  66. package/dist/tools/builtin-names.d.ts +73 -0
  67. package/dist/tools/builtin-names.js +116 -0
  68. package/dist/tools/builtins/agent.d.ts +7 -7
  69. package/dist/tools/builtins/agent.js +7 -11
  70. package/dist/tools/builtins/ask-user.d.ts +1 -1
  71. package/dist/tools/builtins/ask-user.js +2 -2
  72. package/dist/tools/builtins/builtin-tools.d.ts +2 -2
  73. package/dist/tools/builtins/builtin-tools.js +2 -2
  74. package/dist/tools/builtins/read-tool-output.d.ts +1 -1
  75. package/dist/tools/builtins/read-tool-output.js +2 -2
  76. package/dist/tools/builtins/sandbox/bash.d.ts +5 -5
  77. package/dist/tools/builtins/sandbox/bash.js +31 -28
  78. package/dist/tools/builtins/sandbox/common.d.ts +3 -3
  79. package/dist/tools/builtins/sandbox/common.js +2 -2
  80. package/dist/tools/builtins/sandbox/deliver.d.ts +1 -1
  81. package/dist/tools/builtins/sandbox/deliver.js +11 -11
  82. package/dist/tools/builtins/sandbox/edit.d.ts +1 -1
  83. package/dist/tools/builtins/sandbox/edit.js +41 -30
  84. package/dist/tools/builtins/sandbox/file-state.d.ts +34 -0
  85. package/dist/tools/builtins/sandbox/file-state.js +34 -0
  86. package/dist/tools/builtins/sandbox/glob-match.d.ts +1 -1
  87. package/dist/tools/builtins/sandbox/glob-match.js +1 -1
  88. package/dist/tools/builtins/sandbox/glob.d.ts +1 -1
  89. package/dist/tools/builtins/sandbox/glob.js +4 -4
  90. package/dist/tools/builtins/sandbox/grep.d.ts +7 -4
  91. package/dist/tools/builtins/sandbox/grep.js +147 -30
  92. package/dist/tools/builtins/sandbox/index.d.ts +1 -1
  93. package/dist/tools/builtins/sandbox/index.js +1 -1
  94. package/dist/tools/builtins/sandbox/read.d.ts +2 -2
  95. package/dist/tools/builtins/sandbox/read.js +21 -14
  96. package/dist/tools/builtins/sandbox/write.d.ts +1 -1
  97. package/dist/tools/builtins/sandbox/write.js +37 -33
  98. package/dist/tools/builtins/skill-load.d.ts +3 -3
  99. package/dist/tools/builtins/skill-load.js +32 -16
  100. package/dist/tools/builtins/skill-write.d.ts +4 -4
  101. package/dist/tools/builtins/skill-write.js +10 -10
  102. package/dist/tools/builtins/tool-search.d.ts +13 -9
  103. package/dist/tools/builtins/tool-search.js +25 -14
  104. package/dist/tools/connectors.service.js +4 -4
  105. package/dist/tools/display-names.d.ts +9 -0
  106. package/dist/tools/display-names.js +24 -0
  107. package/dist/tools/drift.js +5 -5
  108. package/dist/tools/elicitation.js +1 -1
  109. package/dist/tools/info.d.ts +20 -4
  110. package/dist/tools/info.js +55 -32
  111. package/dist/tools/legacy-data.d.ts +34 -0
  112. package/dist/tools/legacy-data.js +76 -0
  113. package/dist/tools/legacy-keys.d.ts +51 -0
  114. package/dist/tools/legacy-keys.js +115 -0
  115. package/dist/tools/naming.d.ts +1 -5
  116. package/dist/tools/naming.js +1 -7
  117. package/dist/tools/outputs.js +1 -1
  118. package/dist/tools/pipeline.d.ts +13 -7
  119. package/dist/tools/pipeline.js +97 -39
  120. package/dist/tools/policy.d.ts +1 -14
  121. package/dist/tools/policy.js +13 -19
  122. package/dist/tools/questions.controller.d.ts +1 -1
  123. package/dist/tools/questions.controller.js +1 -1
  124. package/dist/tools/questions.service.d.ts +1 -1
  125. package/dist/tools/registry.d.ts +16 -21
  126. package/dist/tools/registry.js +29 -37
  127. package/dist/tools/resources.d.ts +1 -1
  128. package/dist/tools/resources.js +1 -1
  129. package/dist/tools/tools.module.js +1 -1
  130. package/package.json +2 -2
  131. package/src/migrations/0063_tool_keys_claude_code.sql +220 -0
  132. package/dist/tools/builtins/sandbox/names.d.ts +0 -7
  133. package/dist/tools/builtins/sandbox/names.js +0 -20
@@ -191,6 +191,25 @@ let EventLog = class EventLog {
191
191
  .executeTakeFirst();
192
192
  return r && toEvent(r);
193
193
  }
194
+ /**
195
+ * The file state (`tool_result.view.file`) the latest `Read` / `Write` / `Edit` result of this
196
+ * conversation recorded for `path` (a normalized sandbox path); null when none did. One index
197
+ * probe per call: the partial index `conversation_events_file_state_idx` of migration 0063
198
+ * (conversation_id, view.file.path, seq DESC; tool_result rows only) answers it, also for a path
199
+ * never read (the query's predicate must keep matching that index's expression).
200
+ */
201
+ async latestFileState(conversationId, path) {
202
+ const r = await this.db
203
+ .selectFrom("conversation_events")
204
+ .select(sql `payload->'view'->'file'`.as("file"))
205
+ .where("conversation_id", "=", conversationId)
206
+ .where("type", "=", "tool_result")
207
+ .where(sql `payload->'view'->'file'->>'path' = ${path}`)
208
+ .orderBy("seq", "desc")
209
+ .limit(1)
210
+ .executeTakeFirst();
211
+ return r ? { size: r.file.size, sha256: r.file.sha256 } : null;
212
+ }
194
213
  validate(e) {
195
214
  const r = ConversationEventSchema.safeParse({ type: e.type, payload: e.payload });
196
215
  if (!r.success) {
@@ -86,10 +86,10 @@ export type LogContentBlock = Exclude<ContentBlock, MediaBlock> | (MediaBlock ex
86
86
  } : never : never);
87
87
  /**
88
88
  * Structured view of a builtin tool's result (UI refresh B, spec §5.5 / §5.9): display only, like
89
- * `display`. `command`: `sandbox_bash` (the model's `description`, the exit code, null when the
89
+ * `display`. `command`: `Bash` (the model's `description`, the exit code, null when the
90
90
  * command was stopped, and whether it timed out; the web shows "Failed" for a non-zero exit even
91
- * though `isError` stays false); `edit`: `sandbox_edit` / `sandbox_write` (a capped line diff);
92
- * `read`: `sandbox_read` (the line range shown).
91
+ * though `isError` stays false); `edit`: `Edit` / `Write` (a capped line diff);
92
+ * `read`: `Read` (the line range shown).
93
93
  */
94
94
  export declare const ToolViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
95
95
  kind: z.ZodLiteral<"command">;
@@ -103,6 +103,11 @@ export declare const ToolViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
103
103
  added: z.ZodNumber;
104
104
  removed: z.ZodNumber;
105
105
  truncated: z.ZodBoolean;
106
+ file: z.ZodOptional<z.ZodObject<{
107
+ path: z.ZodString;
108
+ size: z.ZodNumber;
109
+ sha256: z.ZodString;
110
+ }, z.core.$strip>>;
106
111
  hunks: z.ZodArray<z.ZodObject<{
107
112
  oldStart: z.ZodNumber;
108
113
  newStart: z.ZodNumber;
@@ -120,6 +125,11 @@ export declare const ToolViewSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
120
125
  path: z.ZodString;
121
126
  fromLine: z.ZodOptional<z.ZodNumber>;
122
127
  toLine: z.ZodOptional<z.ZodNumber>;
128
+ file: z.ZodOptional<z.ZodObject<{
129
+ path: z.ZodString;
130
+ size: z.ZodNumber;
131
+ sha256: z.ZodString;
132
+ }, z.core.$strip>>;
123
133
  }, z.core.$strip>], "kind">;
124
134
  export type ToolView = z.infer<typeof ToolViewSchema>;
125
135
  /**
@@ -441,6 +451,11 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
441
451
  added: z.ZodNumber;
442
452
  removed: z.ZodNumber;
443
453
  truncated: z.ZodBoolean;
454
+ file: z.ZodOptional<z.ZodObject<{
455
+ path: z.ZodString;
456
+ size: z.ZodNumber;
457
+ sha256: z.ZodString;
458
+ }, z.core.$strip>>;
444
459
  hunks: z.ZodArray<z.ZodObject<{
445
460
  oldStart: z.ZodNumber;
446
461
  newStart: z.ZodNumber;
@@ -458,6 +473,11 @@ export declare const ConversationEventSchema: z.ZodDiscriminatedUnion<[z.ZodObje
458
473
  path: z.ZodString;
459
474
  fromLine: z.ZodOptional<z.ZodNumber>;
460
475
  toLine: z.ZodOptional<z.ZodNumber>;
476
+ file: z.ZodOptional<z.ZodObject<{
477
+ path: z.ZodString;
478
+ size: z.ZodNumber;
479
+ sha256: z.ZodString;
480
+ }, z.core.$strip>>;
461
481
  }, z.core.$strip>], "kind">>;
462
482
  card: z.ZodOptional<z.ZodObject<{
463
483
  plugin: z.ZodString;
@@ -75,12 +75,22 @@ const STOP_REASONS = ["end_turn", "tool_use", "max_tokens", "refusal", "other"];
75
75
  const EFFORT_ENUM = ["low", "medium", "high", "xhigh", "max"];
76
76
  const seqNo = z.number().int().nonnegative();
77
77
  const count = z.number().int().nonnegative();
78
+ /**
79
+ * The state of a file a `Read` / `Write` / `Edit` saw or left (display only, but the kernel reads it
80
+ * back from the log to decide read-before-write): the full normalized path, the file's full size and
81
+ * the sha256 of the bytes handled (the first bytes only for a file over `Read`'s byte limit).
82
+ */
83
+ const FileStateViewSchema = z.object({
84
+ path: z.string().max(4096),
85
+ size: count,
86
+ sha256: z.string().regex(/^[0-9a-f]{64}$/),
87
+ });
78
88
  /**
79
89
  * Structured view of a builtin tool's result (UI refresh B, spec §5.5 / §5.9): display only, like
80
- * `display`. `command`: `sandbox_bash` (the model's `description`, the exit code, null when the
90
+ * `display`. `command`: `Bash` (the model's `description`, the exit code, null when the
81
91
  * command was stopped, and whether it timed out; the web shows "Failed" for a non-zero exit even
82
- * though `isError` stays false); `edit`: `sandbox_edit` / `sandbox_write` (a capped line diff);
83
- * `read`: `sandbox_read` (the line range shown).
92
+ * though `isError` stays false); `edit`: `Edit` / `Write` (a capped line diff);
93
+ * `read`: `Read` (the line range shown).
84
94
  */
85
95
  export const ToolViewSchema = z.discriminatedUnion("kind", [
86
96
  z.object({
@@ -96,6 +106,7 @@ export const ToolViewSchema = z.discriminatedUnion("kind", [
96
106
  added: count,
97
107
  removed: count,
98
108
  truncated: z.boolean(),
109
+ file: FileStateViewSchema.optional(),
99
110
  hunks: z
100
111
  .array(z.object({
101
112
  oldStart: count,
@@ -114,6 +125,7 @@ export const ToolViewSchema = z.discriminatedUnion("kind", [
114
125
  path: z.string().max(TOOL_VIEW_PATH_MAX_CHARS),
115
126
  fromLine: count.optional(),
116
127
  toLine: count.optional(),
128
+ file: FileStateViewSchema.optional(),
117
129
  }),
118
130
  ]);
119
131
  const ModelUsageShape = {
@@ -260,13 +272,13 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
260
272
  */
261
273
  absorbedUpToSeq: seqNo.optional(),
262
274
  /**
263
- * 本次请求带给模型的工具(按发送顺序:未启用 tool_search 时按 key 排序;启用时为内置工具按 key 排序,
275
+ * 本次请求带给模型的工具(按发送顺序:未启用 ToolSearch 时按 key 排序;启用时为内置工具按 key 排序,
264
276
  * 其后是已加载工具按首次加载顺序)及其定义指纹(`ResolvedTool.specHash`)。
265
277
  * 模型不支持工具时为空数组。较早的事件可能没有。
266
278
  */
267
279
  tools: z.array(z.object({ key: z.string(), specHash: z.string() })).optional(),
268
280
  /**
269
- * 启用 tool_search 时 system 第二块目录里的工具(按 key 排序)及定义指纹;定义同样存在
281
+ * 启用 ToolSearch 时 system 第二块目录里的工具(按 key 排序)及定义指纹;定义同样存在
270
282
  * `capybara.tool_specs`,目录文本可由它们还原。未启用时不写。
271
283
  */
272
284
  catalog: z.array(z.object({ key: z.string(), specHash: z.string() })).optional(),
@@ -339,7 +351,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
339
351
  outputId: z.string().optional(),
340
352
  contentTypes: z.array(z.string()).optional(),
341
353
  /**
342
- * `tool_search` 本次加载的工具 key(没有匹配时不写)。已加载集合 = 会话中全部 tool_result 的
354
+ * `ToolSearch` 本次加载的工具 key(没有匹配时不写)。已加载集合 = 会话中全部 tool_result 的
343
355
  * `loadedTools` 并集(含已被摘要覆盖的部分),构建请求时由日志还原,不放进程内存。
344
356
  */
345
357
  loadedTools: z.array(z.string()).optional(),
@@ -395,7 +407,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
395
407
  reason: z.string().max(APPROVAL_DENY_REASON_MAX_CHARS).optional(),
396
408
  })),
397
409
  /**
398
- * 工具调用需要用户输入(`ask_user`、插件工具的 `input_required`、MCP elicitation):输入请求已建,
410
+ * 工具调用需要用户输入(`AskUserQuestion`、插件工具的 `input_required`、MCP elicitation):输入请求已建,
399
411
  * run 暂停等待回答。界面据此显示表单卡片(不进模型请求)。
400
412
  */
401
413
  ev("question_asked", z.object({
@@ -423,7 +435,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
423
435
  reason: z.enum(["provider_rejected"]),
424
436
  })),
425
437
  /**
426
- * agent 经 `sandbox_deliver` 把沙箱里的文件交付给用户(M5 T8,C3):字节在对象存储
438
+ * agent 经 `Deliver` 把沙箱里的文件交付给用户(M5 T8,C3):字节在对象存储
427
439
  * `kernel/<orgId>/<objectKey>`(`objectKey` = `artifacts/<conversationId>/<artifactId>`,相对内核前缀),
428
440
  * 这里只有元数据。网页从日志重建产物列表;不进模型请求(模型看到的是同一调用的 tool_result 确认)。
429
441
  */
@@ -439,7 +451,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
439
451
  /**
440
452
  * 本对话此刻生效的技能(M5 T14,Ruling M17):运行开始时内核解析生效的技能集合(agent 版本选中的组织技能 +
441
453
  * agent 自己的技能,未删除,取 current_version),与日志里上一份快照不同才写。模型看到的技能目录只来自这里:
442
- * 第一份(在任何 `model_call` 之前)进 system 缓存段,之后的在所在位置渲染为英文变更提醒;`skill_load` 按最新一份
454
+ * 第一份(在任何 `model_call` 之前)进 system 缓存段,之后的在所在位置渲染为英文变更提醒;`Skill` 按最新一份
443
455
  * 把名字解析为 id 与版本。按 name 排序。
444
456
  */
445
457
  ev("skills_catalog", z.object({
@@ -451,12 +463,12 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
451
463
  version: z.number().int().positive(),
452
464
  /**
453
465
  * 内容来源(对象前缀,T15):已生成的版本为 `skill_versions.object_prefix`,试用中的草稿为申请的
454
- * `object_prefix`。同号的草稿与正式版本靠它区分;`skill_load` 只按它解析。
466
+ * `object_prefix`。同号的草稿与正式版本靠它区分;`Skill` 只按它解析。
455
467
  */
456
468
  source: z.string().min(1).max(512),
457
469
  /**
458
470
  * The catalog item (plugin) name the skill came from (phase B); absent for the org's
459
- * own and the agent's skills, and in snapshots written before phase B. `skill_load`
471
+ * own and the agent's skills, and in snapshots written before phase B. `Skill`
460
472
  * resolves `<plugin>:<name>` with it; it is not model-visible otherwise.
461
473
  */
462
474
  plugin: z.string().min(1).max(64).optional(),
@@ -465,7 +477,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
465
477
  /**
466
478
  * Pinned skills (phase B, spec §9.1) in the agent's order: each a skill of `skills` (same
467
479
  * id, version and source) with its SKILL.md body as read at run start or written by
468
- * skill_write. Absent means none (every snapshot before phase B).
480
+ * SkillWrite. Absent means none (every snapshot before phase B).
469
481
  */
470
482
  pinned: z
471
483
  .array(z.object({
@@ -534,7 +546,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
534
546
  warnings: z.array(z.string()).optional(),
535
547
  })),
536
548
  /**
537
- * Parent log (phase C, spec §14.1): the `agent` call `callId` started (or resumed) the sub-agent
549
+ * Parent log (phase C, spec §14.1): the `Agent` call `callId` started (or resumed) the sub-agent
538
550
  * `subagentId` (its child conversation id) with run `childRunId`. Not model-visible: the model
539
551
  * sees the call's tool_call / tool_result pair.
540
552
  */
@@ -550,7 +562,7 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
550
562
  tierFallback: z.boolean().optional(),
551
563
  /**
552
564
  * Phase D: the seq of the person's fork invocation this dispatch runs (`callId` is
553
- * `skill-<seq>`, step 0, no tool call); absent for an `agent` call.
565
+ * `skill-<seq>`, step 0, no tool call); absent for an `Agent` call.
554
566
  */
555
567
  invokedSeq: seqNo.optional(),
556
568
  })),
@@ -269,7 +269,7 @@ export interface RunsTable {
269
269
  /** 0054: when this run's `subagents` wait expires (set while it waits on sub-agents). */
270
270
  wait_expires_at: TsNullable;
271
271
  }
272
- /** 0053: one row per sub-agent dispatch (an `agent` call of a parent run). */
272
+ /** 0053: one row per sub-agent dispatch (an `Agent` call of a parent run). */
273
273
  export interface SubagentDispatchesTable {
274
274
  parent_run_id: string;
275
275
  parent_call_id: string;
@@ -400,7 +400,7 @@ export interface ApprovalsTable {
400
400
  /** 0056: the reason given with a denial (null without one). */
401
401
  deny_reason: Generated<string | null>;
402
402
  }
403
- /** 0010_tools.sql:ask_user 提问。 */
403
+ /** 0010_tools.sql:AskUserQuestion 提问。 */
404
404
  export interface QuestionsTable {
405
405
  id: string;
406
406
  org_id: string;
@@ -1,4 +1,5 @@
1
1
  import type { EventLog } from "../conversations/event-log.js";
2
+ import { type LegacyScope } from "../tools/legacy-keys.js";
2
3
  import { type ToolPipeline, type ToolStepContext } from "../tools/pipeline.js";
3
4
  import type { CaseSnapshot } from "./snapshot.js";
4
5
  /**
@@ -15,12 +16,34 @@ export interface RecordedCall {
15
16
  inputKey: string;
16
17
  content: string;
17
18
  isError: boolean;
18
- /** 原结果里的 `tool_search` 已加载工具(照抄:之后请求的已加载集合由日志还原)。 */
19
+ /** 原结果里的 `ToolSearch` 已加载工具(照抄:之后请求的已加载集合由日志还原)。 */
19
20
  loadedTools?: string[];
20
21
  contentTypes?: string[];
21
22
  /** 原 `tool_call_started` 的定义指纹(没有开始事件的调用为空串)。 */
22
23
  specHash: string;
24
+ /** The recorded call used release-candidate names; `toolKey` and `inputKey` are already the current ones. */
25
+ legacy?: true;
23
26
  }
27
+ /**
28
+ * The connectors and extensions an rc `<x>__<tool>` key can name, from positive evidence in the
29
+ * snapshot only (a head with no evidence, or evidence of both kinds, stays unmapped, so its
30
+ * recorded calls are unmatched, which is a safe miss):
31
+ * - a connector: the tool definition the key was recorded with (`toolSpecs`, found by the specHash of
32
+ * its `tool_call_started` or its `model_call` tools / catalog entry) has `connectorData`;
33
+ * - an extension: that definition exists without `connectorData` and its name is the key's tool
34
+ * part, or an agent definition names exactly that tool as a `{ kind: "plugin", plugin: x, name }` ref.
35
+ */
36
+ export declare function legacyScopeOf(s: CaseSnapshot): LegacyScope;
37
+ /**
38
+ * A recorded call in the current names: an rc builtin or `<x>__<tool>` key becomes its current
39
+ * key with the input's parameters renamed. Only the recorded side is ever normalized; a call
40
+ * already in current names (or one that cannot be mapped) is returned as is.
41
+ */
42
+ export declare function currentCall(name: string, input: unknown, scope: LegacyScope): {
43
+ name: string;
44
+ input: unknown;
45
+ legacy: boolean;
46
+ };
24
47
  /** 规范化 JSON:对象键递归按码点排序,数组保持顺序;`undefined` 按 JSON 规则省略。 */
25
48
  export declare function canonicalJson(v: unknown): string;
26
49
  /** The matching key of a call: the canonical JSON of its input without the ignored fields. */
@@ -43,7 +66,10 @@ export declare class RecordedToolPipeline implements Pick<ToolPipeline, "runStep
43
66
  private readonly d;
44
67
  private readonly used;
45
68
  private misses;
69
+ private legacyHits;
46
70
  constructor(d: RecordedToolPipelineDeps);
71
+ /** Calls so far matched with a recording made under release-candidate tool names. */
72
+ legacyMatches(): number;
47
73
  /** 至今没有匹配到记录的调用数。 */
48
74
  unmatched(): number;
49
75
  runStep(ctx: ToolStepContext): Promise<{
@@ -1,4 +1,6 @@
1
1
  import { assistantIdemKey } from "../runtime/model-call.js";
2
+ import { MATCH_IGNORED_FIELDS } from "../tools/builtin-names.js";
3
+ import { isLegacyPrefixedKey, legacyToolInput, legacyToolKey, } from "../tools/legacy-keys.js";
2
4
  import { RUN_STOPPED_TEXT } from "../tools/pipeline.js";
3
5
  /**
4
6
  * 整轮回放的工具管线(C6 / Review Focus 3):只回放原运行记录的工具结果,**从不执行任何工具**——
@@ -7,6 +9,77 @@ import { RUN_STOPPED_TEXT } from "../tools/pipeline.js";
7
9
  */
8
10
  /** 没有匹配记录时模型看到的工具结果(模型可见,英文)。 */
9
11
  export const REPLAY_NO_RECORDING = "[replay] No recorded result matches this tool call (same tool and arguments); the tool was not executed during replay.";
12
+ /**
13
+ * The connectors and extensions an rc `<x>__<tool>` key can name, from positive evidence in the
14
+ * snapshot only (a head with no evidence, or evidence of both kinds, stays unmapped, so its
15
+ * recorded calls are unmatched, which is a safe miss):
16
+ * - a connector: the tool definition the key was recorded with (`toolSpecs`, found by the specHash of
17
+ * its `tool_call_started` or its `model_call` tools / catalog entry) has `connectorData`;
18
+ * - an extension: that definition exists without `connectorData` and its name is the key's tool
19
+ * part, or an agent definition names exactly that tool as a `{ kind: "plugin", plugin: x, name }` ref.
20
+ */
21
+ export function legacyScopeOf(s) {
22
+ const hashes = new Map();
23
+ const note = (key, hash) => {
24
+ if (!isLegacyPrefixedKey(key))
25
+ return;
26
+ const set = hashes.get(key) ?? new Set();
27
+ if (hash !== undefined && hash !== "")
28
+ set.add(hash);
29
+ hashes.set(key, set);
30
+ };
31
+ for (const e of s.events) {
32
+ if (e.type === "tool_call_started") {
33
+ const p = e.payload;
34
+ note(p.toolKey, p.specHash);
35
+ }
36
+ else if (e.type === "model_call") {
37
+ const r = e.payload.request;
38
+ for (const t of [...(r?.tools ?? []), ...(r?.catalog ?? [])])
39
+ note(t.key, t.specHash);
40
+ }
41
+ }
42
+ const extensionNames = new Set();
43
+ const connectorSlugs = new Set();
44
+ const planted = new Set();
45
+ for (const def of Object.values(s.definitions)) {
46
+ for (const t of def.tools)
47
+ if (t.kind === "plugin")
48
+ planted.add(`${t.plugin}__${t.name}`);
49
+ }
50
+ for (const [key, set] of hashes) {
51
+ const i = key.indexOf("__");
52
+ const head = key.slice(0, i);
53
+ const tool = key.slice(i + 2);
54
+ for (const h of set) {
55
+ const spec = s.toolSpecs[h];
56
+ if (spec === undefined)
57
+ continue;
58
+ if (spec.connectorData !== undefined) {
59
+ connectorSlugs.add(head);
60
+ }
61
+ else if (spec.name === tool) {
62
+ extensionNames.add(head);
63
+ }
64
+ }
65
+ }
66
+ for (const key of planted) {
67
+ if (hashes.has(key))
68
+ extensionNames.add(key.slice(0, key.indexOf("__")));
69
+ }
70
+ return { connectorSlugs, extensionNames };
71
+ }
72
+ /**
73
+ * A recorded call in the current names: an rc builtin or `<x>__<tool>` key becomes its current
74
+ * key with the input's parameters renamed. Only the recorded side is ever normalized; a call
75
+ * already in current names (or one that cannot be mapped) is returned as is.
76
+ */
77
+ export function currentCall(name, input, scope) {
78
+ const key = legacyToolKey(name, scope);
79
+ if (key === null)
80
+ return { name, input, legacy: false };
81
+ return { name: key, input: legacyToolInput(name, input), legacy: true };
82
+ }
10
83
  /** 规范化 JSON:对象键递归按码点排序,数组保持顺序;`undefined` 按 JSON 规则省略。 */
11
84
  export function canonicalJson(v) {
12
85
  const norm = (x) => {
@@ -23,14 +96,6 @@ export function canonicalJson(v) {
23
96
  };
24
97
  return JSON.stringify(norm(v) ?? null);
25
98
  }
26
- /**
27
- * Input fields left out when matching, per tool: display-only arguments the model writes that do not
28
- * change what the tool does. `sandbox_bash`'s `description` became required in UI refresh B, so a
29
- * recording without it and a replayed call with one (or a different wording) still match.
30
- */
31
- const MATCH_IGNORED_FIELDS = {
32
- sandbox_bash: ["description"],
33
- };
34
99
  /** The matching key of a call: the canonical JSON of its input without the ignored fields. */
35
100
  export function matchKey(toolKey, input) {
36
101
  const ignored = MATCH_IGNORED_FIELDS[toolKey];
@@ -64,6 +129,7 @@ export function recordedCalls(s) {
64
129
  started.set(p.callId, p.specHash);
65
130
  }
66
131
  }
132
+ const scope = legacyScopeOf(s);
67
133
  const out = [];
68
134
  for (const e of own) {
69
135
  if (e.type !== "assistant_message")
@@ -76,9 +142,11 @@ export function recordedCalls(s) {
76
142
  const r = results.get(call.id);
77
143
  if (!r)
78
144
  continue;
145
+ const cur = currentCall(call.name, call.input, scope);
79
146
  out.push({
80
- toolKey: call.name,
81
- inputKey: matchKey(call.name, call.input),
147
+ toolKey: cur.name,
148
+ inputKey: matchKey(cur.name, cur.input),
149
+ ...(cur.legacy && { legacy: true }),
82
150
  content: r.content,
83
151
  isError: r.isError,
84
152
  ...(r.loadedTools !== undefined && { loadedTools: r.loadedTools }),
@@ -98,9 +166,14 @@ export class RecordedToolPipeline {
98
166
  d;
99
167
  used = new Set();
100
168
  misses = 0;
169
+ legacyHits = 0;
101
170
  constructor(d) {
102
171
  this.d = d;
103
172
  }
173
+ /** Calls so far matched with a recording made under release-candidate tool names. */
174
+ legacyMatches() {
175
+ return this.legacyHits;
176
+ }
104
177
  /** 至今没有匹配到记录的调用数。 */
105
178
  unmatched() {
106
179
  return this.misses;
@@ -162,6 +235,8 @@ export class RecordedToolPipeline {
162
235
  if (i < 0)
163
236
  return undefined;
164
237
  this.used.add(i);
238
+ if (this.d.recordings[i]?.legacy === true)
239
+ this.legacyHits++;
165
240
  return this.d.recordings[i];
166
241
  }
167
242
  }
@@ -37,6 +37,11 @@ export interface RunReplayResult {
37
37
  usage: ModelUsage;
38
38
  steps: number;
39
39
  unmatched: number;
40
+ /**
41
+ * Set when a call was matched with a recording made under the release-candidate tool names
42
+ * (`sandbox_edit` for `Edit`, `docs__search` for `mcp__docs__search`, old parameter names).
43
+ */
44
+ legacyKeyMatch?: true;
40
45
  }
41
46
  /** 原运行(基线)的同形摘要。 */
42
47
  export type RunBaseline = Omit<RunReplayResult, "conversationId" | "outcome" | "unmatched">;
@@ -22,7 +22,7 @@ import { RunError } from "../runtime/model-call.js";
22
22
  import { recordedSkillForks } from "../runtime/subagents/skill-fork.js";
23
23
  import { skillsCatalogIdemKey } from "../skills/catalog.js";
24
24
  import { ACTIVE_EVAL_STATUSES, addUsage, consumeTokens, TOO_MANY_ERRORS, usageTokens, } from "./budget.js";
25
- import { RecordedToolPipeline, recordedCalls } from "./recorded-tools.js";
25
+ import { currentCall, legacyScopeOf, RecordedToolPipeline, recordedCalls, } from "./recorded-tools.js";
26
26
  /** 写种子时发现已失去认领或运行已不在执行:本次执行停下,不留评测对话。 */
27
27
  export class ReplayClaimLost extends Error {
28
28
  constructor() {
@@ -177,7 +177,7 @@ const ZERO = {
177
177
  cacheWriteTokens: 0,
178
178
  };
179
179
  /** 一次运行的工具调用序列、最后的回复、停止原因、用量与步数(基线与候选同一算法)。 */
180
- function summarize(events, runId) {
180
+ function summarize(events, runId, scope) {
181
181
  const toolCalls = [];
182
182
  let usage = ZERO;
183
183
  let last = null;
@@ -189,8 +189,14 @@ function summarize(events, runId) {
189
189
  const p = e.payload;
190
190
  steps.add(p.step);
191
191
  for (const b of p.content) {
192
- if (b.type === "tool_call")
192
+ if (b.type !== "tool_call")
193
+ continue;
194
+ if (scope === undefined)
193
195
  toolCalls.push({ name: b.name, input: b.input });
196
+ else {
197
+ const { name, input } = currentCall(b.name, b.input, scope);
198
+ toolCalls.push({ name, input });
199
+ }
194
200
  }
195
201
  last = { content: p.content, stopReason: p.stopReason };
196
202
  }
@@ -214,7 +220,9 @@ function summarize(events, runId) {
214
220
  }
215
221
  /** 原运行(快照)的摘要,作为整轮回放的基线。 */
216
222
  export function runBaseline(s) {
217
- return summarize(s.events, s.runId);
223
+ // an rc recording is read in the current tool names, so the diff against a candidate only
224
+ // shows real differences
225
+ return summarize(s.events, s.runId, legacyScopeOf(s));
218
226
  }
219
227
  /**
220
228
  * 删掉一个评测对话(崩溃重跑前清掉上一次的残留):先删它各次运行的 checkpoint,再删对话(事件、运行随之级联删除)。
@@ -558,5 +566,11 @@ export async function replayRun(d, s, candidate, evalRunId, signal) {
558
566
  });
559
567
  await Promise.allSettled([...charges]);
560
568
  const summary = summarize(await readAll(eventLog, conversationId), runId);
561
- return { conversationId, outcome: outcome.kind, ...summary, unmatched: pipeline.unmatched() };
569
+ return {
570
+ conversationId,
571
+ outcome: outcome.kind,
572
+ ...summary,
573
+ unmatched: pipeline.unmatched(),
574
+ ...(pipeline.legacyMatches() > 0 && { legacyKeyMatch: true }),
575
+ };
562
576
  }
@@ -54,7 +54,7 @@ export interface TrajectoryToolCall {
54
54
  byAccountId: string | null;
55
55
  decidedAt: string | null;
56
56
  } | null;
57
- /** 最近一次输入请求(`ask_user`、插件 `input_required`、MCP elicitation)。 */
57
+ /** 最近一次输入请求(`AskUserQuestion`、插件 `input_required`、MCP elicitation)。 */
58
58
  question: {
59
59
  questionId: string;
60
60
  askedAt: string;
@@ -64,7 +64,7 @@ export interface TrajectoryToolCall {
64
64
  /** A `tool.preExecute` policy hook denied the call (it never started). */
65
65
  hookDenied?: TrajectoryHookDenial;
66
66
  /**
67
- * An `agent` call that dispatched (or resumed) a sub-agent (phase C, spec §15.4): the child
67
+ * An `Agent` call that dispatched (or resumed) a sub-agent (phase C, spec §15.4): the child
68
68
  * conversation (`subagentId`, whose trajectory the viewer links to), the run it started and that
69
69
  * run's end (`running` until its `subagent_finished`).
70
70
  */
@@ -68,7 +68,7 @@ export const DEPLOYMENT_SETTINGS = z.object({
68
68
  /** 组织能否自建连接器(不经企业目录)。 */
69
69
  customConnectors: CustomPolicy.default("allow"),
70
70
  /**
71
- * 组织能否自建技能(不经企业目录):deny 时组织管理员不能上传技能包。agent 自写技能(`skill_write`,私有 agent 的
71
+ * 组织能否自建技能(不经企业目录):deny 时组织管理员不能上传技能包。agent 自写技能(`SkillWrite`,私有 agent 的
72
72
  * agent 级技能与共享 agent 经负责人审批的变更)不受此开关约束——那是 agent 定义的一部分,由 R3 / R4 把关。
73
73
  */
74
74
  customSkills: CustomPolicy.default("allow"),
@@ -36,6 +36,7 @@ import { GatewayRole } from "../roles/gateway-role.js";
36
36
  import { WorkerRole } from "../roles/worker-role.js";
37
37
  import { UsageReporter } from "../runtime/usage-report.js";
38
38
  import { sandboxConfigWarnings } from "../sandbox/sandbox.service.js";
39
+ import { warnLegacyToolKeys } from "../tools/legacy-data.js";
39
40
  import { KERNEL_APP_OPTIONS } from "./tokens.js";
40
41
  /**
41
42
  * The stop phase budgets. Each phase waits `min(its budget, what is left of the total)`, where the
@@ -193,6 +194,10 @@ let KernelLifecycle = class KernelLifecycle {
193
194
  // In flight for the stop: aborted when the shutdown begins, which then waits for its session to end
194
195
  await this.shutdown.trackInFlight(migrate(config.db.url, { schema: KERNEL_SCHEMA, dir: KERNEL_MIGRATIONS_DIR }, { connectTimeoutMs: config.db.connectTimeoutMs, signal: this.shutdown.signal }));
195
196
  this.checkpoint();
197
+ // Rows still holding a release-candidate tool key that migration 0063 could not map: one
198
+ // read-only count, a WARN when there are any (tool naming spec §8.2); never blocks the start
199
+ await warnLegacyToolKeys(this.kdb.db, this.log).catch((e) => this.log.warn("tools.legacy_key_count_failed", { error: errMsg(e) }));
200
+ this.checkpoint();
196
201
  // official 清单来源按配置同步(幂等;每个副本启动时都做)
197
202
  await this.marketplaces.syncOfficialFromConfig();
198
203
  // The bootstrap plugins (spec §7), once per manifest across replicas (the Redis lock
@@ -8,10 +8,10 @@ import { OrgLock } from "./org-lock.js";
8
8
  /**
9
9
  * 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
10
10
  * (更新时与存量合并),新版本加字段不需要迁移。
11
- * - `toolSearch`:工具超过阈值时是否改用目录 + `tool_search`(D11,默认 on)。
11
+ * - `toolSearch`:工具超过阈值时是否改用目录 + `ToolSearch`(D11,默认 on)。
12
12
  * - `locale`:组织默认语言(`zh` / `en`,默认 en),机器人在 IM 里说的固定话术按它选择。
13
13
  * - `sandboxToolPolicy`:沙箱工具的默认策略(M5 T8,默认 allow)。为 `ask` 时除只读的
14
- * `sandbox_read` / `sandbox_glob` / `sandbox_grep` 外都需要审批(执行时实时读取)。
14
+ * `Read` / `Glob` / `Grep` 外都需要审批(执行时实时读取)。
15
15
  * - `sandboxEgress`:组织的沙箱出站(Ruling M19 / D6),缺省跟随部署的 `sandbox.limits.egress` + `allowHosts`。
16
16
  * 见 {@link effectiveSandboxEgress}。存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
17
17
  */
@@ -21,10 +21,10 @@ import { OrgLock } from "./org-lock.js";
21
21
  /**
22
22
  * 组织设置(`capybara.orgs.settings` jsonb)。读取宽松:缺失或非法的字段按默认值,未知键保留不动
23
23
  * (更新时与存量合并),新版本加字段不需要迁移。
24
- * - `toolSearch`:工具超过阈值时是否改用目录 + `tool_search`(D11,默认 on)。
24
+ * - `toolSearch`:工具超过阈值时是否改用目录 + `ToolSearch`(D11,默认 on)。
25
25
  * - `locale`:组织默认语言(`zh` / `en`,默认 en),机器人在 IM 里说的固定话术按它选择。
26
26
  * - `sandboxToolPolicy`:沙箱工具的默认策略(M5 T8,默认 allow)。为 `ask` 时除只读的
27
- * `sandbox_read` / `sandbox_glob` / `sandbox_grep` 外都需要审批(执行时实时读取)。
27
+ * `Read` / `Glob` / `Grep` 外都需要审批(执行时实时读取)。
28
28
  * - `sandboxEgress`:组织的沙箱出站(Ruling M19 / D6),缺省跟随部署的 `sandbox.limits.egress` + `allowHosts`。
29
29
  * 见 {@link effectiveSandboxEgress}。存量值非法时按 `none` 读(宁可断网,不悄悄放宽到部署默认)。
30
30
  */
@@ -25,13 +25,13 @@ export interface ModelCallMeta {
25
25
  params: Record<string, ParamValue>;
26
26
  /**
27
27
  * 请求带的工具(按发送顺序)及定义指纹;定义本身按指纹存在 `capybara.tool_specs`。
28
- * 未启用 tool_search 时按 key 排序;启用时见 {@link BuildModelRequestInput.toolSearch}。
28
+ * 未启用 ToolSearch 时按 key 排序;启用时见 {@link BuildModelRequestInput.toolSearch}。
29
29
  */
30
30
  tools: {
31
31
  key: string;
32
32
  specHash: string;
33
33
  }[];
34
- /** 启用 tool_search 时 system 第二块目录里的工具(按 key 排序)及定义指纹;未启用没有该字段。 */
34
+ /** 启用 ToolSearch 时 system 第二块目录里的工具(按 key 排序)及定义指纹;未启用没有该字段。 */
35
35
  catalog?: {
36
36
  key: string;
37
37
  specHash: string;
@@ -75,8 +75,8 @@ export interface BuildModelRequestInput {
75
75
  */
76
76
  tools?: RequestTool[];
77
77
  /**
78
- * 启用 tool_search(spec 5.5,由 `ToolRegistry.forAgent` 判定)。启用且有工具时:
79
- * - `tools` 只放内置工具(含 `tool_search`,按 key 排序)与**已加载**工具的完整定义;
78
+ * 启用 ToolSearch(spec 5.5,由 `ToolRegistry.forAgent` 判定)。启用且有工具时:
79
+ * - `tools` 只放内置工具(含 `ToolSearch`,按 key 排序)与**已加载**工具的完整定义;
80
80
  * 已加载 = 日志中全部 `tool_result.loadedTools` 的并集(含摘要之前的;不在本步可用工具中的忽略),
81
81
  * 按首次加载顺序追加在内置工具之后——新加载只在末尾追加,已发送的工具顺序不变;
82
82
  * - 其余工具(不论是否已加载)以目录形式放进 system 第二个块(按 key 排序,打缓存断点),
@@ -224,7 +224,7 @@ export declare function summaryText(summary: string): string;
224
224
  * 由会话事件日志 + agent 定义构建模型请求(纯函数,不做 I/O)。
225
225
  * - system:`BEHAVIOR_RULES + "\n\n" + agent.instructions` 一个块,打缓存断点(a sub-agent's log:
226
226
  * `BEHAVIOR_RULES`, its preamble and its instructions, all from its `subagent_started`);日志里第一份 `skills_catalog`
227
- * 快照在任何 `model_call` 之前时,技能目录接在这个块末尾(Ruling M17,见 ./skills.ts);启用 tool_search 时
227
+ * 快照在任何 `model_call` 之前时,技能目录接在这个块末尾(Ruling M17,见 ./skills.ts);启用 ToolSearch 时
228
228
  * 第二个块是工具目录(见 {@link BuildModelRequestInput.toolSearch});
229
229
  * - 之后的 `skills_catalog` 在所在位置渲染为 system 变更提醒;被压缩摘要覆盖的,若摘要时生效的目录与 system 里的
230
230
  * 不同,紧随摘要补一条完整的当前目录;
@@ -245,7 +245,7 @@ export declare function buildModelRequest(input: BuildModelRequestInput): {
245
245
  mediaTokens: number;
246
246
  };
247
247
  /**
248
- * 日志里 `tool_search` 加载过的工具 key(全部 `tool_result.loadedTools` 的并集,按首次出现顺序)。
248
+ * 日志里 `ToolSearch` 加载过的工具 key(全部 `tool_result.loadedTools` 的并集,按首次出现顺序)。
249
249
  * 不看压缩摘要的边界:加载过的工具在摘要之后仍保持加载(模型可能凭摘要继续使用它们,且已发送的工具列表只追加)。
250
250
  */
251
251
  export declare function loadedToolKeys(events: readonly ConversationEvent[]): string[];