@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
@@ -11,7 +11,8 @@ import { Inject, Injectable, Module } from "@nestjs/common";
11
11
  import { sql } from "kysely";
12
12
  import { ApiError } from "../errors.js";
13
13
  import { KernelDb } from "../infra/database/kernel-db.js";
14
- import { normalizeStoredDefinition } from "./definition.js";
14
+ import { loadLegacyScope } from "../tools/legacy-data.js";
15
+ import { needsLegacyScope, normalizeStoredDefinition } from "./definition.js";
15
16
  export const AGENT_VISIBILITIES = ["private", "org", "members"];
16
17
  const notFound = () => new ApiError("agent.not_found", "Agent not found");
17
18
  /**
@@ -142,6 +143,13 @@ let AgentLookup = class AgentLookup {
142
143
  .orderBy("account_id")
143
144
  .execute();
144
145
  const byId = new Map(rows.map((r) => [r.id, r]));
146
+ // rc `<x>__<tool>` keys read with their org's scope, loaded once per org that needs it
147
+ const scopes = new Map();
148
+ for (const r of rows) {
149
+ if (!scopes.has(r.org_id) && needsLegacyScope(r.definition)) {
150
+ scopes.set(r.org_id, await loadLegacyScope(this.db.db, r.org_id));
151
+ }
152
+ }
145
153
  return ids.flatMap((id) => {
146
154
  const r = byId.get(id);
147
155
  if (!r)
@@ -153,7 +161,7 @@ let AgentLookup = class AgentLookup {
153
161
  ownerAccountId: r.owner_account_id,
154
162
  visibility: r.visibility,
155
163
  currentVersion: r.current_version,
156
- definition: normalizeStoredDefinition(r.definition),
164
+ definition: normalizeStoredDefinition(r.definition, scopes.get(r.org_id)),
157
165
  memberIds: r.visibility === "members"
158
166
  ? memberRows.filter((m) => m.agent_id === id).map((m) => m.account_id)
159
167
  : null,
@@ -24,6 +24,7 @@ import { ProviderRegistry } from "../runtime/providers.js";
24
24
  import { SandboxJobs } from "../sandbox/jobs.js";
25
25
  import { SkillChangesService } from "../skills/changes.service.js";
26
26
  import { assertOrgSkills, assertPinnedSkills } from "../skills/skills.service.js";
27
+ import { legacyKeySaveText } from "../tools/legacy-keys.js";
27
28
  import { ToolRegistry } from "../tools/registry.js";
28
29
  import { AgentLookup, isAgentVisibleTo, visibleTo, } from "./agent-lookup.js";
29
30
  import { normalizeStoredDefinition, paramIssues, SUBAGENT_NAME_RE, SUBAGENT_ROSTER_MAX, } from "./definition.js";
@@ -67,8 +68,13 @@ export async function assertSubagents(t, a) {
67
68
  if (sub === undefined)
68
69
  return;
69
70
  const details = [];
70
- const add = (reason, path, value) => {
71
- details.push({ reason, path, ...(value !== undefined && { value }) });
71
+ const add = (reason, path, value, message) => {
72
+ details.push({
73
+ reason,
74
+ path,
75
+ ...(value !== undefined && { value }),
76
+ ...(message !== undefined && { message }),
77
+ });
72
78
  };
73
79
  const types = new Set(sub.general === false ? [] : ["general-purpose"]);
74
80
  const claim = (type, path) => {
@@ -123,8 +129,11 @@ export async function assertSubagents(t, a) {
123
129
  const base = `subagents.inline.${i}`;
124
130
  name(x.name, `${base}.name`);
125
131
  const tool = (key, path) => {
126
- if (!a.toolKeys.has(key))
127
- add("tool_not_selected", path, key);
132
+ if (a.toolKeys.has(key))
133
+ return;
134
+ // an rc key names its current key(s) (tool naming spec §8.3)
135
+ const renamed = legacyKeySaveText(key, a.toolKeys);
136
+ add("tool_not_selected", path, key, renamed ?? undefined);
128
137
  };
129
138
  for (const [j, key] of (x.tools ?? []).entries())
130
139
  tool(key, `${base}.tools.${j}`);
@@ -167,7 +176,9 @@ export async function assertSubagents(t, a) {
167
176
  if (count > SUBAGENT_ROSTER_MAX)
168
177
  add("too_many", "subagents");
169
178
  if (details.length > 0) {
170
- throw new ApiError("agent.subagents_invalid", `Invalid sub-agents: ${details.map((d) => `${d.path} (${d.reason})`).join(", ")}`, details);
179
+ throw new ApiError("agent.subagents_invalid", `Invalid sub-agents: ${details
180
+ .map((d) => `${d.path} (${d.reason}${d.message === undefined ? "" : `: ${d.message}`})`)
181
+ .join(", ")}`, details);
171
182
  }
172
183
  }
173
184
  /**
@@ -220,9 +231,11 @@ let AgentsService = class AgentsService {
220
231
  const details = [...issues];
221
232
  for (const key of Object.keys(def.toolPolicies)) {
222
233
  if (!keys.has(key)) {
234
+ // an rc key (tool naming spec §8.3): saving writes current keys only, so name them
235
+ const renamed = legacyKeySaveText(key, keys);
223
236
  details.push({
224
237
  path: `toolPolicies.${key}`,
225
- message: `"${key}" is not a tool of this agent`,
238
+ message: renamed ?? `"${key}" is not a tool of this agent`,
226
239
  });
227
240
  }
228
241
  }
@@ -1,5 +1,6 @@
1
1
  import { type ModelParameter } from "@capybara-hq/plugin-sdk";
2
2
  import { z } from "zod";
3
+ import { type LegacyScope } from "../tools/legacy-keys.js";
3
4
  /** 模型引用:供应商插件名 + 模型 id。 */
4
5
  export declare const ModelRef: z.ZodObject<{
5
6
  provider: z.ZodString;
@@ -34,11 +35,7 @@ export declare const AgentToolRef: z.ZodPreprocess<z.ZodDiscriminatedUnion<[z.Zo
34
35
  tools: z.ZodUnion<readonly [z.ZodLiteral<"all">, z.ZodArray<z.ZodString>]>;
35
36
  }, z.core.$strict>], "kind">, unknown>;
36
37
  export type AgentToolRef = z.output<typeof AgentToolRef>;
37
- /**
38
- * 新建 agent 未给 `tools` 时的默认工具(`tool_search` 由注册表按组织设置自动加入,不进定义)。
39
- * 只用于创建请求;修改与读出旧版本都不注入默认值。
40
- */
41
- export declare const DEFAULT_AGENT_TOOLS: AgentToolRef[];
38
+ export { DEFAULT_AGENT_TOOLS } from "../tools/builtin-names.js";
42
39
  /** Roster names of inline templates and allowlisted org agents (the template name rule). */
43
40
  export declare const SUBAGENT_NAME_RE: RegExp;
44
41
  /**
@@ -75,7 +72,7 @@ export declare const InlineAgentTemplate: z.ZodObject<{
75
72
  }, z.core.$strict>;
76
73
  export type InlineAgentTemplate = z.infer<typeof InlineAgentTemplate>;
77
74
  /**
78
- * Sub-agents this agent may dispatch with the builtin `agent` tool (phase C, spec §5.2). No
75
+ * Sub-agents this agent may dispatch with the builtin `Agent` tool (phase C, spec §5.2). No
79
76
  * default: definitions without it are unchanged and gain no version.
80
77
  */
81
78
  export declare const SubagentsSchema: z.ZodObject<{
@@ -192,11 +189,37 @@ export declare const AgentDefinitionSchema: z.ZodObject<{
192
189
  }, z.core.$strict>>;
193
190
  }, z.core.$strip>;
194
191
  export type AgentDefinition = z.infer<typeof AgentDefinitionSchema>;
192
+ /** The connectors and extensions a definition's own tool refs name (to read an ambiguous rc key). */
193
+ interface OwnRefs {
194
+ connectors: ReadonlySet<string>;
195
+ extensions: ReadonlySet<string>;
196
+ }
197
+ /**
198
+ * Release-candidate tool keys in a stored definition, read as current keys (tool naming spec
199
+ * §8.3), the way {@link normalizeToolRef} reads the app -> connector rename: builtin refs in
200
+ * `tools`, the keys of `toolPolicies`, and the `tools` / `disallowedTools` / `toolPolicies` of a
201
+ * template, the agent's inline sub-agent templates included. Works on an agent definition and on
202
+ * an agent template alike. Old builtin names map without `scope`; an rc `<x>__<tool>` key maps
203
+ * only with one (the org's connector slugs and their ids, the deployment's extension names). When
204
+ * `x` is both a slug and an extension name, the definition's own tool refs decide; when they
205
+ * cannot, a policy or a deny list keeps the restriction on both candidate keys (a deny is never
206
+ * lost) and an allowlist keeps the old key (inert). An unknown key stays as it is. Keys in the
207
+ * current format are never touched. Returns the same object when nothing changes. Saving never
208
+ * goes through this: a new definition with an old key is refused (`AgentsService`).
209
+ */
210
+ export declare function normalizeToolKeys(definition: unknown, scope?: LegacyScope, inherited?: OwnRefs): unknown;
211
+ /**
212
+ * Does a stored definition or template hold an rc `<x>__<tool>` key, which only a
213
+ * {@link LegacyScope} can map? (Callers load the scope only then.)
214
+ */
215
+ export declare function needsLegacyScope(definition: unknown): boolean;
195
216
  /**
196
217
  * 库里读出的定义(`agent_versions.definition`,不经 zod 解析):把改名前的 `{ kind: "app" }` 工具引用规范化为
197
- * connector 形(C27)。其余字段原样保留;没有旧形引用时返回原对象。
218
+ * connector 形(C27),and rc tool keys to current keys ({@link normalizeToolKeys}: builtins
219
+ * always, `<x>__<tool>` keys with `scope`). Other fields are kept; without old forms the same
220
+ * object is returned.
198
221
  */
199
- export declare function normalizeStoredDefinition(def: unknown): AgentDefinition;
222
+ export declare function normalizeStoredDefinition(def: unknown, scope?: LegacyScope): AgentDefinition;
200
223
  /** 按模型声明严格校验一组参数,返回 `{ path: "<field>.<key>", message }[]`(评测候选也用它)。 */
201
224
  export declare function paramIssues(field: string, decl: ModelParameter[], params: Record<string, unknown>): {
202
225
  path: string;
@@ -1,5 +1,6 @@
1
1
  import { validateParams } from "@capybara-hq/plugin-sdk";
2
2
  import { z } from "zod";
3
+ import { isLegacyBuiltinKey, isLegacyPrefixedKey, legacyToolKey, } from "../tools/legacy-keys.js";
3
4
  /** 模型引用:供应商插件名 + 模型 id。 */
4
5
  export const ModelRef = z.object({ provider: z.string(), model: z.string() });
5
6
  /**
@@ -37,14 +38,7 @@ export const AgentToolRef = z.preprocess(normalizeToolRef, z.discriminatedUnion(
37
38
  })
38
39
  .strict(),
39
40
  ]));
40
- /**
41
- * 新建 agent 未给 `tools` 时的默认工具(`tool_search` 由注册表按组织设置自动加入,不进定义)。
42
- * 只用于创建请求;修改与读出旧版本都不注入默认值。
43
- */
44
- export const DEFAULT_AGENT_TOOLS = [
45
- { kind: "builtin", name: "ask_user" },
46
- { kind: "builtin", name: "read_tool_output" },
47
- ];
41
+ export { DEFAULT_AGENT_TOOLS } from "../tools/builtin-names.js";
48
42
  /** Roster names of inline templates and allowlisted org agents (the template name rule). */
49
43
  export const SUBAGENT_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
50
44
  /**
@@ -68,7 +62,7 @@ export const InlineAgentTemplate = z
68
62
  })
69
63
  .strict();
70
64
  /**
71
- * Sub-agents this agent may dispatch with the builtin `agent` tool (phase C, spec §5.2). No
65
+ * Sub-agents this agent may dispatch with the builtin `Agent` tool (phase C, spec §5.2). No
72
66
  * default: definitions without it are unchanged and gain no version.
73
67
  */
74
68
  export const SubagentsSchema = z
@@ -118,7 +112,7 @@ export const AgentDefinitionSchema = z.object({
118
112
  * 技能(M5 T13):`orgSkills` 是引用的组织技能 id(保存时校验属于本组织且未删除,不可重复)。
119
113
  * agent 自己的技能(`scope=agent`)自动对它生效,不进定义。可省略(较早保存的定义也没有),按空处理;
120
114
  * 不给默认值:没用技能的定义保存前后不变,不会因此多出一个版本。
121
- * `writePolicy`(M6 T27,D10):私有 agent 的 `skill_write` 是否固定确认,缺省按 `allow`(不写进定义);
115
+ * `writePolicy`(M6 T27,D10):私有 agent 的 `SkillWrite` 是否固定确认,缺省按 `allow`(不写进定义);
122
116
  * 共享 agent 不论取值都走 R3 + R4。
123
117
  */
124
118
  skills: z
@@ -136,20 +130,202 @@ export const AgentDefinitionSchema = z.object({
136
130
  })
137
131
  .strict()
138
132
  .optional(),
139
- /** Sub-agents (phase C); offered only when the builtin `agent` tool is in `tools`. */
133
+ /** Sub-agents (phase C); offered only when the builtin `Agent` tool is in `tools`. */
140
134
  subagents: SubagentsSchema.optional(),
141
135
  });
136
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
137
+ /**
138
+ * The connector slugs and extension names `definition`'s `tools` name: connector refs (through
139
+ * `scope.connectorSlugById`), plugin refs, and current-format keys of a template's `tools`;
140
+ * `inherited` (the parent agent's, for an inline template) is added.
141
+ */
142
+ function ownRefs(definition, scope, inherited) {
143
+ const connectors = new Set(inherited?.connectors);
144
+ const extensions = new Set(inherited?.extensions);
145
+ const tools = Array.isArray(definition.tools) ? definition.tools : [];
146
+ for (const r of tools) {
147
+ if (typeof r === "string") {
148
+ const m = /^(mcp|plugin)__([^_].*?)__./.exec(r);
149
+ if (m)
150
+ (m[1] === "mcp" ? connectors : extensions).add(m[2]);
151
+ }
152
+ else if (isRecord(r) && r.kind === "connector" && typeof r.connectorId === "string") {
153
+ const slug = scope.connectorSlugById?.get(r.connectorId);
154
+ if (slug !== undefined)
155
+ connectors.add(slug);
156
+ }
157
+ else if (isRecord(r) && r.kind === "plugin" && typeof r.plugin === "string") {
158
+ extensions.add(r.plugin);
159
+ }
160
+ }
161
+ return { connectors, extensions };
162
+ }
163
+ /**
164
+ * The current key(s) of a stored key. A builtin and an unambiguous `<x>__<tool>` map to one key
165
+ * ({@link legacyToolKey}). When `x` is both a connector slug and an extension name, the
166
+ * definition's own refs decide (only connector `x` -> `mcp__…`, only extension `x` ->
167
+ * `plugin__…`); when they name both or neither, both candidates are returned and the caller
168
+ * decides (a policy keeps a deny / ask on both: it must never be lost). Anything else: as is.
169
+ */
170
+ function currentKeys(key, scope, own) {
171
+ const one = legacyToolKey(key, scope);
172
+ if (one !== null)
173
+ return [one];
174
+ if (scope === undefined || !isLegacyPrefixedKey(key))
175
+ return [key];
176
+ const head = key.slice(0, key.indexOf("__"));
177
+ if (!scope.connectorSlugs.has(head) || !scope.extensionNames.has(head))
178
+ return [key];
179
+ const c = own?.connectors.has(head) === true;
180
+ const e = own?.extensions.has(head) === true;
181
+ if (c && !e)
182
+ return [`mcp__${key}`];
183
+ if (e && !c)
184
+ return [`plugin__${key}`];
185
+ return [`mcp__${key}`, `plugin__${key}`];
186
+ }
187
+ /**
188
+ * A list of tool keys (a template's `tools` / `disallowedTools`) in current keys, deduplicated.
189
+ * A still-ambiguous key becomes both candidates in a deny list (`widen`), and stays as is (inert,
190
+ * so narrower) in an allowlist.
191
+ */
192
+ function keyList(v, scope, own, widen) {
193
+ if (!Array.isArray(v))
194
+ return v;
195
+ let changed = false;
196
+ const out = [];
197
+ for (const x of v) {
198
+ let next = [x];
199
+ if (typeof x === "string") {
200
+ const keys = currentKeys(x, scope, own);
201
+ next = keys.length > 1 && !widen ? [x] : keys;
202
+ }
203
+ if (next.length !== 1 || next[0] !== x)
204
+ changed = true;
205
+ for (const n of next) {
206
+ if (typeof n === "string" && out.includes(n)) {
207
+ changed = true;
208
+ continue;
209
+ }
210
+ out.push(n);
211
+ }
212
+ }
213
+ return changed ? out : v;
214
+ }
215
+ /**
216
+ * `toolPolicies` in current keys; keys that become one keep the stricter policy, and a
217
+ * still-ambiguous key keeps its `ask` / `deny` on both candidates (anything else is dropped).
218
+ */
219
+ function policyKeys(v, scope, own) {
220
+ if (!isRecord(v))
221
+ return v;
222
+ const mapped = Object.entries(v).map(([k, p]) => [k, p, currentKeys(k, scope, own)]);
223
+ if (mapped.every(([k, , keys]) => keys.length === 1 && keys[0] === k))
224
+ return v;
225
+ const rank = (p) => (p === "deny" ? 2 : p === "ask" ? 1 : 0);
226
+ const out = {};
227
+ for (const [, p, keys] of mapped) {
228
+ if (keys.length > 1 && rank(p) === 0)
229
+ continue;
230
+ for (const next of keys) {
231
+ if (!Object.hasOwn(out, next) || rank(p) > rank(out[next]))
232
+ out[next] = p;
233
+ }
234
+ }
235
+ return out;
236
+ }
237
+ /** An agent definition's `tools`: builtin refs by their current names (connector and plugin refs name tools, not keys). */
238
+ function toolRefs(v, scope, own) {
239
+ if (!Array.isArray(v))
240
+ return v;
241
+ // template-shaped (`tools: string[]`): an allowlist
242
+ if (v.some((x) => typeof x === "string"))
243
+ return keyList(v, scope, own, false);
244
+ if (!v.some((r) => isRecord(r) &&
245
+ r.kind === "builtin" &&
246
+ typeof r.name === "string" &&
247
+ isLegacyBuiltinKey(r.name))) {
248
+ return v;
249
+ }
250
+ return v.map((r) => isRecord(r) && r.kind === "builtin" && typeof r.name === "string"
251
+ ? { ...r, name: legacyToolKey(r.name) ?? r.name }
252
+ : r);
253
+ }
254
+ /**
255
+ * Release-candidate tool keys in a stored definition, read as current keys (tool naming spec
256
+ * §8.3), the way {@link normalizeToolRef} reads the app -> connector rename: builtin refs in
257
+ * `tools`, the keys of `toolPolicies`, and the `tools` / `disallowedTools` / `toolPolicies` of a
258
+ * template, the agent's inline sub-agent templates included. Works on an agent definition and on
259
+ * an agent template alike. Old builtin names map without `scope`; an rc `<x>__<tool>` key maps
260
+ * only with one (the org's connector slugs and their ids, the deployment's extension names). When
261
+ * `x` is both a slug and an extension name, the definition's own tool refs decide; when they
262
+ * cannot, a policy or a deny list keeps the restriction on both candidate keys (a deny is never
263
+ * lost) and an allowlist keeps the old key (inert). An unknown key stays as it is. Keys in the
264
+ * current format are never touched. Returns the same object when nothing changes. Saving never
265
+ * goes through this: a new definition with an old key is refused (`AgentsService`).
266
+ */
267
+ export function normalizeToolKeys(definition, scope, inherited) {
268
+ if (!isRecord(definition))
269
+ return definition;
270
+ const own = scope === undefined ? undefined : ownRefs(definition, scope, inherited);
271
+ const next = { ...definition };
272
+ let changed = false;
273
+ const put = (field, value) => {
274
+ if (value === definition[field])
275
+ return;
276
+ next[field] = value;
277
+ changed = true;
278
+ };
279
+ if ("tools" in definition)
280
+ put("tools", toolRefs(definition.tools, scope, own));
281
+ if ("disallowedTools" in definition) {
282
+ put("disallowedTools", keyList(definition.disallowedTools, scope, own, true));
283
+ }
284
+ if ("toolPolicies" in definition) {
285
+ put("toolPolicies", policyKeys(definition.toolPolicies, scope, own));
286
+ }
287
+ const sub = definition.subagents;
288
+ if (isRecord(sub) && Array.isArray(sub.inline)) {
289
+ const inline = sub.inline.map((t) => normalizeToolKeys(t, scope, own));
290
+ if (inline.some((t, i) => t !== sub.inline[i])) {
291
+ put("subagents", { ...sub, inline });
292
+ }
293
+ }
294
+ return changed ? next : definition;
295
+ }
296
+ /**
297
+ * Does a stored definition or template hold an rc `<x>__<tool>` key, which only a
298
+ * {@link LegacyScope} can map? (Callers load the scope only then.)
299
+ */
300
+ export function needsLegacyScope(definition) {
301
+ if (!isRecord(definition))
302
+ return false;
303
+ const lists = [definition.tools, definition.disallowedTools];
304
+ for (const l of lists) {
305
+ if (Array.isArray(l) && l.some((x) => typeof x === "string" && isLegacyPrefixedKey(x))) {
306
+ return true;
307
+ }
308
+ }
309
+ if (isRecord(definition.toolPolicies)) {
310
+ if (Object.keys(definition.toolPolicies).some(isLegacyPrefixedKey))
311
+ return true;
312
+ }
313
+ const sub = definition.subagents;
314
+ return isRecord(sub) && Array.isArray(sub.inline) && sub.inline.some(needsLegacyScope);
315
+ }
142
316
  /**
143
317
  * 库里读出的定义(`agent_versions.definition`,不经 zod 解析):把改名前的 `{ kind: "app" }` 工具引用规范化为
144
- * connector 形(C27)。其余字段原样保留;没有旧形引用时返回原对象。
318
+ * connector 形(C27),and rc tool keys to current keys ({@link normalizeToolKeys}: builtins
319
+ * always, `<x>__<tool>` keys with `scope`). Other fields are kept; without old forms the same
320
+ * object is returned.
145
321
  */
146
- export function normalizeStoredDefinition(def) {
147
- const d = def;
322
+ export function normalizeStoredDefinition(def, scope) {
323
+ let d = def;
148
324
  const tools = d.tools;
149
- if (!Array.isArray(tools) || !tools.some((t) => t?.kind === "app")) {
150
- return d;
325
+ if (Array.isArray(tools) && tools.some((t) => t?.kind === "app")) {
326
+ d = { ...d, tools: tools.map(normalizeToolRef) };
151
327
  }
152
- return { ...d, tools: tools.map(normalizeToolRef) };
328
+ return normalizeToolKeys(d, scope);
153
329
  }
154
330
  /** 按模型声明严格校验一组参数,返回 `{ path: "<field>.<key>", message }[]`(评测候选也用它)。 */
155
331
  export function paramIssues(field, decl, params) {
@@ -10,7 +10,9 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { Inject, Injectable } from "@nestjs/common";
12
12
  import { KernelDb } from "../infra/database/kernel-db.js";
13
+ import { loadLegacyScope } from "../tools/legacy-data.js";
13
14
  import { canonicalJson } from "../tools/registry.js";
15
+ import { needsLegacyScope, normalizeToolKeys } from "./definition.js";
14
16
  const baseQuery = (db, orgId) => db
15
17
  .selectFrom("agent_templates as t")
16
18
  .innerJoin("agent_template_versions as v", (j) => j.onRef("v.template_id", "=", "t.id").onRef("v.version", "=", "t.current_version"))
@@ -26,16 +28,27 @@ const baseQuery = (db, orgId) => db
26
28
  ])
27
29
  .where("t.org_id", "=", orgId)
28
30
  .where("t.deleted_at", "is", null);
29
- const rowOf = (r) => ({
31
+ /**
32
+ * A stored template row; its tool keys read as current keys (tool naming spec §8.3): old builtin
33
+ * names always, rc `<x>__<tool>` keys with `scope` (see {@link scopedRows}).
34
+ */
35
+ const rowOf = (r, scope) => ({
30
36
  id: r.id,
31
37
  name: r.name,
32
38
  plugin: r.plugin_name,
33
39
  version: r.current_version,
34
- template: r.definition,
40
+ template: normalizeToolKeys(r.definition, scope),
35
41
  sourceFormat: r.source_format,
36
42
  catalogItemId: r.catalog_item_id,
37
43
  catalogVersion: r.catalog_version,
38
44
  });
45
+ /** The rows of one org as {@link TemplateRow}s, reading the org's legacy scope once when needed. */
46
+ async function scopedRows(db, orgId, rows) {
47
+ const scope = rows.some((r) => needsLegacyScope(r.definition))
48
+ ? await loadLegacyScope(db, orgId)
49
+ : undefined;
50
+ return rows.map((r) => rowOf(r, scope));
51
+ }
39
52
  /** The live templates of `ids` in the org (deleted and foreign ids are left out), by id. */
40
53
  export async function loadTemplates(db, orgId, ids) {
41
54
  if (ids.length === 0)
@@ -43,7 +56,7 @@ export async function loadTemplates(db, orgId, ids) {
43
56
  const rows = await baseQuery(db, orgId)
44
57
  .where("t.id", "in", [...ids])
45
58
  .execute();
46
- return new Map(rows.map((r) => [r.id, rowOf(r)]));
59
+ return new Map((await scopedRows(db, orgId, rows)).map((t) => [t.id, t]));
47
60
  }
48
61
  /** Reads the org's agent templates (consumed by the roster, the agent page and Task 19's API). */
49
62
  let AgentTemplatesService = class AgentTemplatesService {
@@ -84,7 +97,9 @@ let AgentTemplatesService = class AgentTemplatesService {
84
97
  const r = await q.executeTakeFirst();
85
98
  if (!r)
86
99
  return null;
87
- const t = rowOf(r);
100
+ const [t] = await scopedRows(this.db.db, orgId, [r]);
101
+ if (!t)
102
+ return null;
88
103
  return { id: t.id, version: t.version, plugin: t.plugin, template: t.template };
89
104
  }
90
105
  };
@@ -4,6 +4,7 @@
4
4
  // module whose provider injects PluginInstaller (an ESM import cycle would run decorators early).
5
5
  import { createHash } from "node:crypto";
6
6
  import { parseAgentTemplate, parseConnectorTemplateFile, } from "@capybara-hq/plugin-sdk";
7
+ import { normalizeToolKeys } from "../agents/definition.js";
7
8
  import { ApiError } from "../errors.js";
8
9
  import { can } from "../orgs/roles.js";
9
10
  import { packagePath } from "../packages/tgz.js";
@@ -318,6 +319,8 @@ export async function readCatalogAgentTemplates(storage, v) {
318
319
  if (!parsed || "errors" in parsed) {
319
320
  throw new ApiError("internal", `Stored agent template ${path} is missing or invalid`);
320
321
  }
321
- return { path, template: parsed.template };
322
+ // an rc package's template may name old builtins: read as the current names (tool naming
323
+ // spec §8.3); rc `<x>__<tool>` keys are read with the org's scope by `loadTemplates`
324
+ return { path, template: normalizeToolKeys(parsed.template) };
322
325
  });
323
326
  }
@@ -13,16 +13,19 @@ import { item } from "./report.js";
13
13
  export const GENERATED_AGENTS_DIR = ".capybara-generated/agents";
14
14
  /** Claude Code tool names -> Capybara tool keys (spec §16.4; anything else is dropped). */
15
15
  const TOOL_MAP = {
16
- Bash: "sandbox_bash",
17
- Read: "sandbox_read",
18
- Write: "sandbox_write",
19
- Edit: "sandbox_edit",
20
- MultiEdit: "sandbox_edit",
21
- Glob: "sandbox_glob",
22
- Grep: "sandbox_grep",
23
- Skill: "skill_load",
24
- TodoWrite: "todo__write",
25
- ToolSearch: "tool_search",
16
+ Bash: "Bash",
17
+ Read: "Read",
18
+ Write: "Write",
19
+ Edit: "Edit",
20
+ MultiEdit: "Edit",
21
+ Glob: "Glob",
22
+ Grep: "Grep",
23
+ Skill: "Skill",
24
+ AskUserQuestion: "AskUserQuestion",
25
+ ToolSearch: "ToolSearch",
26
+ Agent: "Agent",
27
+ Task: "Agent",
28
+ TodoWrite: "plugin__todo__write",
26
29
  };
27
30
  /** Claude ignores these for plugin agents: `not_applicable`. */
28
31
  const NOT_APPLICABLE = new Set(["permissionMode", "hooks", "mcpServers"]);
@@ -164,7 +164,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
164
164
  /** The host versions (an entry's `minKernel` is judged against them). */
165
165
  export declare const HOST_VERSIONS: {
166
166
  readonly kernel: string;
167
- readonly sdk: "0.1.0-rc.0";
167
+ readonly sdk: "0.1.0-rc.1";
168
168
  };
169
169
  /** Whether this deployment satisfies the entry's `minKernel`. */
170
170
  export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
@@ -1,6 +1,6 @@
1
- import type { ChannelCard, FormAnswer, InputForm, OutboundMessage } from "@capybara-hq/plugin-sdk";
1
+ import { type ChannelCard, type FormAnswer, type InputForm, type OutboundMessage } from "@capybara-hq/plugin-sdk";
2
2
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
- import type { ApprovalsService } from "../tools/approvals.service.js";
3
+ import type { ApprovalsService, ApprovalView } from "../tools/approvals.service.js";
4
4
  import { type ChannelLocale } from "./phrases.js";
5
5
  /** 审批卡片里工具参数预览的最大长度(字符)。 */
6
6
  export declare const INPUT_PREVIEW_MAX = 500;
@@ -23,6 +23,12 @@ export declare function webBase(publicUrl: string | undefined): string | undefin
23
23
  export declare const conversationUrl: (base: string, conversationId: string) => string;
24
24
  /** 个人连接页里某个连接器的位置(与网页 ConnectCard 的链接一致)。 */
25
25
  export declare const connectUrl: (base: string, connectorId: string) => string;
26
+ /**
27
+ * The tool as the card names it (tool naming spec §7): a builtin by its name; a connector or
28
+ * extension tool as `<display name> [<tool>]` (the connector's name, the plugin's display name in
29
+ * the card's language), the way the web labels it.
30
+ */
31
+ export declare function approvalToolName(v: Pick<ApprovalView, "toolKey" | "toolName" | "connector" | "extension">, locale: ChannelLocale): string;
26
32
  export interface CardDeps {
27
33
  db: KernelSchemaDb;
28
34
  approvals: Pick<ApprovalsService, "describe">;
@@ -1,4 +1,4 @@
1
- import { KEY_SEPARATOR } from "../tools/naming.js";
1
+ import { parseToolKey, } from "@capybara-hq/plugin-sdk";
2
2
  import { phrase } from "./phrases.js";
3
3
  /** 审批卡片里工具参数预览的最大长度(字符)。 */
4
4
  export const INPUT_PREVIEW_MAX = 500;
@@ -84,12 +84,20 @@ export function webBase(publicUrl) {
84
84
  export const conversationUrl = (base, conversationId) => `${base}/chat/${conversationId}`;
85
85
  /** 个人连接页里某个连接器的位置(与网页 ConnectCard 的链接一致)。 */
86
86
  export const connectUrl = (base, connectorId) => `${base}/settings/connections#connector-${encodeURIComponent(connectorId)}`;
87
- /** 来源内的工具名:连接器工具去掉 `<slug>__`(审批详情已处理),插件工具去掉 `<插件>__`。 */
88
- function shortToolName(v) {
87
+ /**
88
+ * The tool as the card names it (tool naming spec §7): a builtin by its name; a connector or
89
+ * extension tool as `<display name> [<tool>]` (the connector's name, the plugin's display name in
90
+ * the card's language), the way the web labels it.
91
+ */
92
+ export function approvalToolName(v, locale) {
93
+ // `toolName` is already the connector's own name for its tool
89
94
  if (v.connector)
90
- return v.toolName;
91
- const i = v.toolKey.indexOf(KEY_SEPARATOR);
92
- return i > 0 ? v.toolKey.slice(i + KEY_SEPARATOR.length) : v.toolKey;
95
+ return `${v.connector.name} [${v.toolName}]`;
96
+ const parsed = parseToolKey(v.toolKey);
97
+ const tool = parsed && parsed.kind !== "builtin" ? parsed.tool : v.toolName;
98
+ if (v.extension)
99
+ return `${v.extension.displayName[locale]} [${tool}]`;
100
+ return v.toolKey;
93
101
  }
94
102
  /** 账号显示名;不存在返回 undefined。 */
95
103
  async function displayName(db, accountId) {
@@ -115,8 +123,8 @@ export async function approvalCard(d, approvalId, base, locale, redact) {
115
123
  kind: "approval",
116
124
  id: v.id,
117
125
  title: phrase("approvalTitle", locale),
118
- toolName: shortToolName(v),
119
- ...(v.connector && { appName: v.connector.name }),
126
+ // the label carries the source, so no separate appName (renderers would repeat it)
127
+ toolName: approvalToolName(v, locale),
120
128
  agentName: v.agent.name || "Agent",
121
129
  inputPreview: inputPreview(v.input, redact),
122
130
  scopes: v.requestedFor === null ? ["once"] : ["once", "session", "always"],
@@ -751,7 +751,7 @@ export const KernelConfigSchema = z.object({
751
751
  .int()
752
752
  .positive()
753
753
  .default(20000)
754
- .meta(meta({ description: "Maximum characters returned by one read_tool_output call" })),
754
+ .meta(meta({ description: "Maximum characters returned by one ReadToolOutput call" })),
755
755
  oauthAllowInsecureLoopback: z
756
756
  .boolean()
757
757
  .default(false)
@@ -978,7 +978,7 @@ export const KernelConfigSchema = z.object({
978
978
  .positive()
979
979
  .default(104_857_600)
980
980
  .meta(meta({
981
- description: "Largest file in bytes an agent may deliver from its sandbox to the user as an artifact (sandbox_deliver)",
981
+ description: "Largest file in bytes an agent may deliver from its sandbox to the user as an artifact (Deliver)",
982
982
  })),
983
983
  limits: z
984
984
  .object({
@@ -20,7 +20,7 @@ export interface ArtifactPreview {
20
20
  truncated: boolean;
21
21
  }
22
22
  /**
23
- * 产物(M5 T8 / T11):agent 经 `sandbox_deliver` 交付的文件。先按会话流的规则判定会话可见
23
+ * 产物(M5 T8 / T11):agent 经 `Deliver` 交付的文件。先按会话流的规则判定会话可见
24
24
  * (不可见 → `conversation.not_found`),再只在这个会话的日志里找 `artifact_created`
25
25
  * (没有 → `conversation.artifact_not_found`)。
26
26
  *
@@ -62,7 +62,7 @@ async function readPrefix(body, max) {
62
62
  return out.subarray(0, n);
63
63
  }
64
64
  /**
65
- * 产物(M5 T8 / T11):agent 经 `sandbox_deliver` 交付的文件。先按会话流的规则判定会话可见
65
+ * 产物(M5 T8 / T11):agent 经 `Deliver` 交付的文件。先按会话流的规则判定会话可见
66
66
  * (不可见 → `conversation.not_found`),再只在这个会话的日志里找 `artifact_created`
67
67
  * (没有 → `conversation.artifact_not_found`)。
68
68
  *
@@ -123,6 +123,17 @@ export declare class EventLog {
123
123
  latestOfType<T extends ConversationEventType>(conversationId: string, type: T): Promise<Extract<ConversationEvent, {
124
124
  type: T;
125
125
  }> | undefined>;
126
+ /**
127
+ * The file state (`tool_result.view.file`) the latest `Read` / `Write` / `Edit` result of this
128
+ * conversation recorded for `path` (a normalized sandbox path); null when none did. One index
129
+ * probe per call: the partial index `conversation_events_file_state_idx` of migration 0063
130
+ * (conversation_id, view.file.path, seq DESC; tool_result rows only) answers it, also for a path
131
+ * never read (the query's predicate must keep matching that index's expression).
132
+ */
133
+ latestFileState(conversationId: string, path: string): Promise<{
134
+ size: number;
135
+ sha256: string;
136
+ } | null>;
126
137
  private validate;
127
138
  /** The append itself, in the context's transaction (both callers open or join one). */
128
139
  private write;