@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
@@ -8,7 +8,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { randomUUID } from "node:crypto";
11
- import { ToolError, } from "@capybara-hq/plugin-sdk";
11
+ import { connectorToolKey, ToolError, } from "@capybara-hq/plugin-sdk";
12
12
  import { Inject, Injectable } from "@nestjs/common";
13
13
  import { sql } from "kysely";
14
14
  import { z } from "zod";
@@ -25,7 +25,7 @@ import { OrgLock } from "../orgs/org-lock.js";
25
25
  import { CredentialMissing, Credentials, isOAuth, } from "./credentials.js";
26
26
  import { snapshot, ToolDrift, toolIssue, } from "./drift.js";
27
27
  import { endpointCredentialIssues, endpointView } from "./endpoint.js";
28
- import { deriveSlug, isValidSlug, SLUG_RULE, toolKey } from "./naming.js";
28
+ import { deriveSlug, isValidSlug, SLUG_RULE } from "./naming.js";
29
29
  import { checkOAuthUrl, discoverMcp, OAuthSetupError, oauthHttpOptions, pickAuthMethod, registerClient, } from "./oauth/discovery.js";
30
30
  import { callbackUrl, publicUrlRequired } from "./oauth/flow-paths.js";
31
31
  import { OAuthGrantError, OAuthUnavailable, tokenRequest, } from "./oauth/tokens.js";
@@ -619,7 +619,7 @@ let ConnectorsService = class ConnectorsService {
619
619
  issues = snap.issues;
620
620
  tools = snap.rows.map((r) => ({
621
621
  name: r.name,
622
- key: toolKey(slug, r.name),
622
+ key: connectorToolKey(slug, r.name),
623
623
  description: r.spec.description,
624
624
  inputSchema: r.spec.inputSchema,
625
625
  annotations: r.spec.annotations ?? null,
@@ -720,7 +720,7 @@ let ConnectorsService = class ConnectorsService {
720
720
  const next = t.pending_spec;
721
721
  return {
722
722
  name: t.name,
723
- key: toolKey(r.slug, t.name),
723
+ key: connectorToolKey(r.slug, t.name),
724
724
  description: spec.description,
725
725
  inputSchema: spec.inputSchema,
726
726
  annotations: spec.annotations ?? null,
@@ -0,0 +1,9 @@
1
+ import type { LocalizedName } from "@capybara-hq/plugin-sdk";
2
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
+ /**
4
+ * The display names of plugins, for naming their extensions' tools (`<display name> [<tool>]`,
5
+ * tool naming spec §7): the live catalog item's `displayName`, else the plugin name in both
6
+ * languages (a bootstrap or uploaded plugin without a catalog item, or one without a display name).
7
+ * Display only.
8
+ */
9
+ export declare function pluginDisplayNames(db: KernelSchemaDb, names: readonly string[]): Promise<Map<string, LocalizedName>>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The display names of plugins, for naming their extensions' tools (`<display name> [<tool>]`,
3
+ * tool naming spec §7): the live catalog item's `displayName`, else the plugin name in both
4
+ * languages (a bootstrap or uploaded plugin without a catalog item, or one without a display name).
5
+ * Display only.
6
+ */
7
+ export async function pluginDisplayNames(db, names) {
8
+ const unique = [...new Set(names)];
9
+ const out = new Map(unique.map((n) => [n, { zh: n, en: n }]));
10
+ if (unique.length === 0)
11
+ return out;
12
+ const rows = await db
13
+ .selectFrom("catalog_items")
14
+ .select(["name", "display_name"])
15
+ .where("name", "in", unique)
16
+ .where("deleted_at", "is", null)
17
+ .execute();
18
+ for (const r of rows) {
19
+ const d = r.display_name;
20
+ if (d && typeof d.zh === "string" && typeof d.en === "string" && d.zh !== "" && d.en !== "")
21
+ out.set(r.name, { zh: d.zh, en: d.en });
22
+ }
23
+ return out;
24
+ }
@@ -7,7 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { TOOL_TITLE_MAX, ToolError, validateToolPresentation, } from "@capybara-hq/plugin-sdk";
10
+ import { connectorToolKey, TOOL_TITLE_MAX, ToolError, validateToolPresentation, } from "@capybara-hq/plugin-sdk";
11
11
  import { getQueueToken } from "@nestjs/bullmq";
12
12
  import { Inject, Injectable } from "@nestjs/common";
13
13
  import { sql } from "kysely";
@@ -21,7 +21,7 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
21
21
  import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
22
22
  import { HIDDEN_FORMAT_RE } from "../text.js";
23
23
  import { CredentialMissing, Credentials, isOAuth } from "./credentials.js";
24
- import { toolKey, toolKeyIssue } from "./naming.js";
24
+ import { toolKeyIssue } from "./naming.js";
25
25
  import { redactDeep, redactorFor } from "./redact.js";
26
26
  import { canonicalJson, policyFromAnnotations, specHash, ToolRegistry } from "./registry.js";
27
27
  import { resourceToolSpecs } from "./resources.js";
@@ -105,7 +105,7 @@ export function snapshot(slug, specs, resources) {
105
105
  ...(isObj(s.connectorData) && { connectorData: s.connectorData }),
106
106
  ...displayFields(s),
107
107
  };
108
- let issue = toolKeyIssue(toolKey(slug, name));
108
+ let issue = toolKeyIssue(connectorToolKey(slug, name));
109
109
  if (!issue && !isObj(s.inputSchema))
110
110
  issue = "Tool inputSchema must be an object";
111
111
  if (issue)
@@ -122,7 +122,7 @@ export function snapshot(slug, specs, resources) {
122
122
  });
123
123
  continue;
124
124
  }
125
- const issue = toolKeyIssue(toolKey(slug, spec.name));
125
+ const issue = toolKeyIssue(connectorToolKey(slug, spec.name));
126
126
  if (issue)
127
127
  issues.push({ name: spec.name, reason: issue });
128
128
  rows.push({ name: spec.name, spec, specHash: specHash(spec), issue, source: "resources" });
@@ -172,7 +172,7 @@ const displayOf = (spec) => {
172
172
  };
173
173
  /** 工具当前不能启用的原因(视图、启用与复核校验共用)。 */
174
174
  export function toolIssue(slug, spec) {
175
- const keyIssue = toolKeyIssue(toolKey(slug, spec.name));
175
+ const keyIssue = toolKeyIssue(connectorToolKey(slug, spec.name));
176
176
  if (keyIssue)
177
177
  return keyIssue;
178
178
  if (!isObj(spec.inputSchema))
@@ -33,7 +33,7 @@ export class ElicitationSuspended extends Error {
33
33
  }
34
34
  /** 不能展示的 elicitation 被拒绝时附在工具结果后的说明(模型可见,英文)。 */
35
35
  export const UNSUPPORTED_ELICITATION_NOTE = "The app asked the user for input that cannot be shown in this client, so the request was declined";
36
- const unsupportedNote = (why) => `[${UNSUPPORTED_ELICITATION_NOTE} (${why}). If the call needs that input, ask the user for it with ask_user and pass it as tool arguments, or try another approach.]`;
36
+ const unsupportedNote = (why) => `[${UNSUPPORTED_ELICITATION_NOTE} (${why}). If the call needs that input, ask the user for it with AskUserQuestion and pass it as tool arguments, or try another approach.]`;
37
37
  /** URL 模式表单里「已完成」开关的字段名。 */
38
38
  export const URL_COMPLETED_FIELD = "completed";
39
39
  /** 表单标题(远端给的 message)最多字符数。 */
@@ -1,3 +1,4 @@
1
+ import { type LocalizedName } from "@capybara-hq/plugin-sdk";
1
2
  import { ConversationAccess } from "../conversations/access.js";
2
3
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
4
  import { KernelDb } from "../infra/database/kernel-db.js";
@@ -11,16 +12,32 @@ export type ToolKind = "command" | "read" | "edit" | "search" | "connector" | "m
11
12
  export interface ToolInfo {
12
13
  /** The model-visible tool key, as asked for. */
13
14
  key: string;
15
+ /**
16
+ * Set only for a release-candidate builtin key (old history, tool naming spec §9): the current
17
+ * tool it stands for. Everything else describes that tool, with `keyArgs` in the old parameter
18
+ * names the logged input carries. Pages map a call's name through it (the `Agent` row, labels).
19
+ */
20
+ aliasOf?: string;
14
21
  /** The declared display title (`ToolSpec.title`), or null. */
15
22
  title: string | null;
16
23
  kind: ToolKind;
24
+ /** True exactly for the builtin tools that run in the conversation's sandbox. */
25
+ sandbox: boolean;
17
26
  /** The declared key arguments (`ToolSpec.keyArgs`), or []. */
18
27
  keyArgs: string[];
28
+ /**
29
+ * The local tool name parsed from a connector or extension key (`mcp__crm__delete` -> `delete`),
30
+ * shown in brackets after the source's display name; null for builtins and unparsable keys.
31
+ */
32
+ tool: string | null;
19
33
  source: {
20
34
  kind: "builtin";
21
- } | {
35
+ }
36
+ /** `displayName`: the plugin's catalog display name, else its name. */
37
+ | {
22
38
  kind: "extension";
23
39
  name: string;
40
+ displayName: LocalizedName;
24
41
  } | {
25
42
  kind: "connector";
26
43
  id: string;
@@ -31,8 +48,7 @@ export interface ToolInfo {
31
48
  kind: "unknown";
32
49
  };
33
50
  }
34
- /** Kinds of the kernel builtin tools; a builtin missing here is a plain `tool`. */
35
- export declare const BUILTIN_TOOL_KINDS: Readonly<Record<string, ToolKind>>;
51
+ export { BUILTIN_TOOL_KINDS } from "./builtin-names.js";
36
52
  /** At most this many keys per request, each at most {@link TOOL_INFO_KEY_MAX} characters. */
37
53
  export declare const TOOL_INFO_MAX_KEYS = 100;
38
54
  export declare const TOOL_INFO_KEY_MAX = 200;
@@ -42,7 +58,7 @@ export interface ToolInfoDeps {
42
58
  }
43
59
  /**
44
60
  * Presentation metadata for `keys` in this org, in request order with repeats dropped. Builtins
45
- * by name; `<prefix>__<tool>` keys first as a loaded extension's registration, then as a live
61
+ * by name (a release-candidate builtin key as its current tool, with `aliasOf`); `plugin__<plugin>__<tool>` keys as a loaded extension's registration, and `mcp__<slug>__<tool>` keys as a live
46
62
  * connector's current stored tool (its reviewed definition, never a pending one). Anything else
47
63
  * comes back as an unknown plain tool.
48
64
  *
@@ -7,28 +7,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
+ import { connectorToolKey, extensionToolKey, parseToolKey, } from "@capybara-hq/plugin-sdk";
10
11
  import { Inject, Injectable } from "@nestjs/common";
11
12
  import { ConversationAccess } from "../conversations/access.js";
12
13
  import { ApiError } from "../errors.js";
13
14
  import { KernelDb } from "../infra/database/kernel-db.js";
14
15
  import { UUID_RE } from "../ui/registry.js";
15
- import { KEY_SEPARATOR, toolKey } from "./naming.js";
16
+ import { BUILTIN_TOOL_KINDS, SANDBOX_TOOL_NAMES } from "./builtin-names.js";
17
+ import { pluginDisplayNames } from "./display-names.js";
18
+ import { isLegacyBuiltinKey, LEGACY_BUILTINS } from "./legacy-keys.js";
16
19
  import { ToolRegistry } from "./registry.js";
17
- /** Kinds of the kernel builtin tools; a builtin missing here is a plain `tool`. */
18
- export const BUILTIN_TOOL_KINDS = {
19
- sandbox_bash: "command",
20
- sandbox_read: "read",
21
- sandbox_write: "edit",
22
- sandbox_edit: "edit",
23
- sandbox_glob: "search",
24
- sandbox_grep: "search",
25
- sandbox_deliver: "tool",
26
- tool_search: "meta",
27
- skill_load: "meta",
28
- skill_write: "meta",
29
- read_tool_output: "meta",
30
- ask_user: "meta",
31
- };
20
+ export { BUILTIN_TOOL_KINDS } from "./builtin-names.js";
32
21
  /** At most this many keys per request, each at most {@link TOOL_INFO_KEY_MAX} characters. */
33
22
  export const TOOL_INFO_MAX_KEYS = 100;
34
23
  export const TOOL_INFO_KEY_MAX = 200;
@@ -38,7 +27,7 @@ const display = (spec) => ({
38
27
  });
39
28
  /**
40
29
  * Presentation metadata for `keys` in this org, in request order with repeats dropped. Builtins
41
- * by name; `<prefix>__<tool>` keys first as a loaded extension's registration, then as a live
30
+ * by name (a release-candidate builtin key as its current tool, with `aliasOf`); `plugin__<plugin>__<tool>` keys as a loaded extension's registration, and `mcp__<slug>__<tool>` keys as a live
42
31
  * connector's current stored tool (its reviewed definition, never a pending one). Anything else
43
32
  * comes back as an unknown plain tool.
44
33
  *
@@ -55,17 +44,13 @@ const display = (spec) => ({
55
44
  export async function toolInfo(deps, orgId, keys) {
56
45
  const unique = [...new Set(keys)];
57
46
  const builtins = new Map(deps.registry.builtinSpecs().map((s) => [s.name, s]));
58
- const extensions = new Map(deps.registry.extensionToolSpecs().map((t) => [toolKey(t.plugin, t.spec.name), t]));
59
- const split = (key) => {
60
- const i = key.indexOf(KEY_SEPARATOR);
61
- return i > 0 ? [key.slice(0, i), key.slice(i + KEY_SEPARATOR.length)] : null;
62
- };
47
+ const extensions = new Map(deps.registry.extensionToolSpecs().map((t) => [extensionToolKey(t.plugin, t.spec.name), t]));
63
48
  const slugs = [
64
49
  ...new Set(unique.flatMap((k) => {
65
50
  if (builtins.has(k) || extensions.has(k))
66
51
  return [];
67
- const p = split(k);
68
- return p ? [p[0]] : [];
52
+ const p = parseToolKey(k);
53
+ return p?.kind === "connector" ? [p.slug] : [];
69
54
  })),
70
55
  ];
71
56
  const connectorRows = slugs.length === 0
@@ -78,15 +63,37 @@ export async function toolInfo(deps, orgId, keys) {
78
63
  .where("c.deleted_at", "is", null)
79
64
  .where("c.slug", "in", slugs)
80
65
  .execute();
81
- const connectorTools = new Map(connectorRows.map((r) => [toolKey(r.slug, r.tool), r]));
66
+ const connectorTools = new Map(connectorRows.map((r) => [connectorToolKey(r.slug, r.tool), r]));
67
+ const displayNames = await pluginDisplayNames(deps.db, unique.flatMap((k) => {
68
+ const e = extensions.get(k);
69
+ return e ? [e.plugin] : [];
70
+ }));
71
+ const localTool = (key) => {
72
+ const p = parseToolKey(key);
73
+ return p && p.kind !== "builtin" ? p.tool : null;
74
+ };
75
+ const builtinInfo = (key, name, spec) => ({
76
+ key,
77
+ ...display(spec),
78
+ kind: BUILTIN_TOOL_KINDS[name] ?? "tool",
79
+ sandbox: SANDBOX_TOOL_NAMES.includes(name),
80
+ tool: null,
81
+ source: { kind: "builtin" },
82
+ });
82
83
  return unique.map((key) => {
83
84
  const b = builtins.get(key);
84
- if (b) {
85
+ if (b)
86
+ return builtinInfo(key, key, b);
87
+ const legacy = isLegacyBuiltinKey(key) ? LEGACY_BUILTINS[key] : undefined;
88
+ const current = legacy && builtins.get(legacy.name);
89
+ if (legacy && current) {
90
+ // the logged input of an rc call names the old parameters: key arguments in those names
91
+ const oldName = new Map(Object.entries(legacy.params).map(([o, n]) => [n, o]));
92
+ const info = builtinInfo(key, legacy.name, current);
85
93
  return {
86
- key,
87
- ...display(b),
88
- kind: BUILTIN_TOOL_KINDS[key] ?? "tool",
89
- source: { kind: "builtin" },
94
+ ...info,
95
+ aliasOf: legacy.name,
96
+ keyArgs: info.keyArgs.map((a) => oldName.get(a) ?? a),
90
97
  };
91
98
  }
92
99
  const e = extensions.get(key);
@@ -95,7 +102,13 @@ export async function toolInfo(deps, orgId, keys) {
95
102
  key,
96
103
  ...display(e.spec),
97
104
  kind: "tool",
98
- source: { kind: "extension", name: e.plugin },
105
+ sandbox: false,
106
+ tool: e.spec.name,
107
+ source: {
108
+ kind: "extension",
109
+ name: e.plugin,
110
+ displayName: displayNames.get(e.plugin) ?? { zh: e.plugin, en: e.plugin },
111
+ },
99
112
  };
100
113
  const c = connectorTools.get(key);
101
114
  if (c) {
@@ -104,10 +117,20 @@ export async function toolInfo(deps, orgId, keys) {
104
117
  key,
105
118
  ...display(c.spec),
106
119
  kind: "connector",
120
+ sandbox: false,
121
+ tool: c.tool,
107
122
  source: { kind: "connector", id: c.id, name: c.name },
108
123
  };
109
124
  }
110
- return { key, title: null, kind: "tool", keyArgs: [], source: { kind: "unknown" } };
125
+ return {
126
+ key,
127
+ title: null,
128
+ kind: "tool",
129
+ sandbox: false,
130
+ keyArgs: [],
131
+ tool: localTool(key),
132
+ source: { kind: "unknown" },
133
+ };
111
134
  });
112
135
  }
113
136
  /** `GET /conversations/:id/tools`: {@link toolInfo} for a conversation the viewer can read. */
@@ -0,0 +1,34 @@
1
+ import { type AgentDefinition } from "../agents/definition.js";
2
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
+ import type { KernelLogger } from "../log.js";
4
+ import { type LegacyScope } from "./legacy-keys.js";
5
+ /**
6
+ * Stored release-candidate tool keys (tool naming spec §8): the scope that reads an rc
7
+ * `<x>__<tool>` key in an org, the stored-definition readers that use it, and the startup notice
8
+ * for the rows migration 0063 could not map. No in-process state: each call reads PostgreSQL.
9
+ */
10
+ /**
11
+ * What an rc `<x>__<tool>` key can name in `orgId`: the org's live connector slugs and the
12
+ * deployment's extension names (installed ones and config-declared ones, which claim their name
13
+ * in `extension_name_owners`). Migration 0063 reads the same two sources.
14
+ */
15
+ export declare function loadLegacyScope(db: KernelSchemaDb, orgId: string): Promise<LegacyScope>;
16
+ /**
17
+ * A stored agent definition of `orgId` with every rc key it can map read as the current key
18
+ * ({@link normalizeStoredDefinition}); the scope is read only when the definition holds an rc
19
+ * `<x>__<tool>` key.
20
+ */
21
+ export declare function normalizeStoredDefinitionIn(db: KernelSchemaDb, orgId: string, def: unknown): Promise<AgentDefinition>;
22
+ /** A stored agent template (or any template-shaped value) of `orgId` in current keys. */
23
+ export declare function normalizeTemplateIn<T>(db: KernelSchemaDb, orgId: string, t: T): Promise<T>;
24
+ /**
25
+ * Rows still holding an rc tool key after migration 0063: grants and pending approvals and
26
+ * questions it could not map (an ambiguous or unknown `<x>__<tool>`). Read-only.
27
+ */
28
+ export declare function countLegacyToolKeys(db: KernelSchemaDb): Promise<number>;
29
+ /**
30
+ * The startup notice (spec §8.2): one WARN `tools.legacy_key_unmapped` with the count of rows
31
+ * still holding an rc key, only when there are any. A grant that no longer matches stops applying
32
+ * (the person is asked again), so this is a notice, not a failure.
33
+ */
34
+ export declare function warnLegacyToolKeys(db: KernelSchemaDb, log: Pick<KernelLogger, "warn">): Promise<void>;
@@ -0,0 +1,76 @@
1
+ import { sql } from "kysely";
2
+ import { needsLegacyScope, normalizeStoredDefinition, normalizeToolKeys, } from "../agents/definition.js";
3
+ import { LEGACY_BUILTINS } from "./legacy-keys.js";
4
+ /**
5
+ * Stored release-candidate tool keys (tool naming spec §8): the scope that reads an rc
6
+ * `<x>__<tool>` key in an org, the stored-definition readers that use it, and the startup notice
7
+ * for the rows migration 0063 could not map. No in-process state: each call reads PostgreSQL.
8
+ */
9
+ /**
10
+ * What an rc `<x>__<tool>` key can name in `orgId`: the org's live connector slugs and the
11
+ * deployment's extension names (installed ones and config-declared ones, which claim their name
12
+ * in `extension_name_owners`). Migration 0063 reads the same two sources.
13
+ */
14
+ export async function loadLegacyScope(db, orgId) {
15
+ const [connectors, installs, owners] = await Promise.all([
16
+ db
17
+ .selectFrom("connectors")
18
+ .select(["id", "slug"])
19
+ .where("org_id", "=", orgId)
20
+ .where("deleted_at", "is", null)
21
+ .execute(),
22
+ db.selectFrom("extension_installs").select("name").execute(),
23
+ db.selectFrom("extension_name_owners").select("name").execute(),
24
+ ]);
25
+ return {
26
+ connectorSlugs: new Set(connectors.map((r) => r.slug)),
27
+ connectorSlugById: new Map(connectors.map((r) => [r.id, r.slug])),
28
+ extensionNames: new Set([...installs, ...owners].map((r) => r.name)),
29
+ };
30
+ }
31
+ /**
32
+ * A stored agent definition of `orgId` with every rc key it can map read as the current key
33
+ * ({@link normalizeStoredDefinition}); the scope is read only when the definition holds an rc
34
+ * `<x>__<tool>` key.
35
+ */
36
+ export async function normalizeStoredDefinitionIn(db, orgId, def) {
37
+ return normalizeStoredDefinition(def, needsLegacyScope(def) ? await loadLegacyScope(db, orgId) : undefined);
38
+ }
39
+ /** A stored agent template (or any template-shaped value) of `orgId` in current keys. */
40
+ export async function normalizeTemplateIn(db, orgId, t) {
41
+ return normalizeToolKeys(t, needsLegacyScope(t) ? await loadLegacyScope(db, orgId) : undefined);
42
+ }
43
+ /**
44
+ * The SQL test for an rc key, the predicate of migration 0063's partial indexes (so the count
45
+ * below reads only those, nearly empty, indexes): an old builtin name, or `<x>__<tool>` not in the
46
+ * current `mcp__` / `plugin__` format.
47
+ */
48
+ const legacyKeySql = (col) => sql `(${col} = ANY (ARRAY[${sql.join(Object.keys(LEGACY_BUILTINS).map((k) => sql.lit(k)))}]::text[]) OR (${col} ~ '^[a-z0-9][a-z0-9_-]*__.' AND ${col} !~ '^(mcp|plugin)__'))`;
49
+ /**
50
+ * Rows still holding an rc tool key after migration 0063: grants and pending approvals and
51
+ * questions it could not map (an ambiguous or unknown `<x>__<tool>`). Read-only.
52
+ */
53
+ export async function countLegacyToolKeys(db) {
54
+ const r = await sql `
55
+ SELECT
56
+ (SELECT count(*) FROM capybara.tool_grants WHERE ${legacyKeySql(sql.ref("tool_key"))})
57
+ + (SELECT count(*) FROM capybara.conversation_tool_grants
58
+ WHERE ${legacyKeySql(sql.ref("tool_key"))})
59
+ + (SELECT count(*) FROM capybara.approvals
60
+ WHERE status = 'pending' AND ${legacyKeySql(sql.ref("tool_key"))})
61
+ + (SELECT count(*) FROM capybara.questions
62
+ WHERE status = 'pending' AND ${legacyKeySql(sql.raw("(spec->>'toolKey')"))})
63
+ AS n
64
+ `.execute(db);
65
+ return Number(r.rows[0]?.n ?? 0);
66
+ }
67
+ /**
68
+ * The startup notice (spec §8.2): one WARN `tools.legacy_key_unmapped` with the count of rows
69
+ * still holding an rc key, only when there are any. A grant that no longer matches stops applying
70
+ * (the person is asked again), so this is a notice, not a failure.
71
+ */
72
+ export async function warnLegacyToolKeys(db, log) {
73
+ const count = await countLegacyToolKeys(db);
74
+ if (count > 0)
75
+ log.warn("tools.legacy_key_unmapped", { count });
76
+ }
@@ -0,0 +1,51 @@
1
+ import { type BuiltinName } from "./builtin-names.js";
2
+ /**
3
+ * The release-candidate builtin tool names (tool naming spec §8.1). FROZEN: it describes the names
4
+ * of the 0.1.0 release candidates and never grows. Old keys stay in logs, recordings and stored
5
+ * configuration; this map is how the kernel reads them. `check:terms` exempts this file.
6
+ */
7
+ export interface LegacyBuiltin {
8
+ /** The new name. */
9
+ name: BuiltinName;
10
+ /** Old parameter name -> new parameter name (only the renamed ones). */
11
+ params: Readonly<Record<string, string>>;
12
+ /** New parameters whose value is in milliseconds where the old one was in seconds (× 1000). */
13
+ msParams?: readonly string[];
14
+ }
15
+ export declare const LEGACY_BUILTINS: Readonly<Record<string, LegacyBuiltin>>;
16
+ /** Is `key` a release-candidate builtin name? */
17
+ export declare const isLegacyBuiltinKey: (key: string) => boolean;
18
+ /** What a connector slug / extension name can be, to read an old `<x>__<tool>` key. */
19
+ export interface LegacyScope {
20
+ connectorSlugs: ReadonlySet<string>;
21
+ extensionNames: ReadonlySet<string>;
22
+ /**
23
+ * Connector id -> slug, for reading a stored definition's connector refs when a key is
24
+ * ambiguous (a slug that is also an extension name); optional.
25
+ */
26
+ connectorSlugById?: ReadonlyMap<string, string>;
27
+ }
28
+ /**
29
+ * Is `key` an rc `<x>__<tool>` key, which only a {@link LegacyScope} can map? Keys in the current
30
+ * format (`mcp__…`, `plugin__…`) never are.
31
+ */
32
+ export declare const isLegacyPrefixedKey: (key: string) => boolean;
33
+ /**
34
+ * The current key of a release-candidate key: a builtin by the frozen map; `<x>__<tool>` becomes
35
+ * `mcp__x__tool` when `x` is only a connector slug in `scope`, `plugin__x__tool` when only an
36
+ * extension name, else null (ambiguous, unknown, or not an old key; a key in the current format
37
+ * is never mapped again).
38
+ */
39
+ export declare function legacyToolKey(key: string, scope?: LegacyScope): string | null;
40
+ /** The input of an old builtin call in the current parameter names (seconds -> milliseconds). */
41
+ export declare function legacyToolInput(oldKey: string, input: unknown): unknown;
42
+ /** The model-visible hint for a call to an old builtin key; null for any other key. */
43
+ export declare function legacyHint(key: string): string | null;
44
+ /**
45
+ * The current key(s) an rc key stands for, to name in a refusal when a definition is saved with
46
+ * it: a builtin's new name; for `<x>__<tool>`, the candidates among `known` (the agent's tool
47
+ * keys), else both `mcp__<x>__<tool>` and `plugin__<x>__<tool>`. Empty when `key` is no rc key.
48
+ */
49
+ export declare function legacyKeyCandidates(key: string, known?: ReadonlySet<string>): string[];
50
+ /** The refusal text for saving rc key `key` (null when it is none): names the new key(s). */
51
+ export declare function legacyKeySaveText(key: string, known?: ReadonlySet<string>): string | null;
@@ -0,0 +1,115 @@
1
+ import { BUILTIN } from "./builtin-names.js";
2
+ export const LEGACY_BUILTINS = Object.freeze({
3
+ sandbox_bash: {
4
+ name: BUILTIN.Bash,
5
+ params: { timeoutSeconds: "timeout" },
6
+ msParams: ["timeout"],
7
+ },
8
+ sandbox_read: { name: BUILTIN.Read, params: { path: "file_path" } },
9
+ sandbox_write: { name: BUILTIN.Write, params: { path: "file_path" } },
10
+ sandbox_edit: {
11
+ name: BUILTIN.Edit,
12
+ params: {
13
+ path: "file_path",
14
+ oldText: "old_string",
15
+ newText: "new_string",
16
+ replaceAll: "replace_all",
17
+ },
18
+ },
19
+ sandbox_glob: { name: BUILTIN.Glob, params: {} },
20
+ sandbox_grep: { name: BUILTIN.Grep, params: { ignoreCase: "-i" } },
21
+ sandbox_deliver: { name: BUILTIN.Deliver, params: { path: "file_path" } },
22
+ skill_load: { name: BUILTIN.Skill, params: { name: "skill" } },
23
+ skill_write: { name: BUILTIN.SkillWrite, params: {} },
24
+ ask_user: { name: BUILTIN.AskUserQuestion, params: {} },
25
+ read_tool_output: { name: BUILTIN.ReadToolOutput, params: {} },
26
+ tool_search: { name: BUILTIN.ToolSearch, params: { limit: "max_results" } },
27
+ agent: { name: BUILTIN.Agent, params: {} },
28
+ });
29
+ /** Is `key` a release-candidate builtin name? */
30
+ export const isLegacyBuiltinKey = (key) => Object.hasOwn(LEGACY_BUILTINS, key);
31
+ /** A key in the current format for connector and extension tools (`mcp__…`, `plugin__…`). */
32
+ const CURRENT_PREFIXED_RE = /^(mcp|plugin)__/;
33
+ /** An rc connector or extension key `<x>__<tool>` (the SQL of migration 0063 uses the same test). */
34
+ const LEGACY_PREFIXED_RE = /^[a-z0-9][a-z0-9_-]*__./;
35
+ /**
36
+ * Is `key` an rc `<x>__<tool>` key, which only a {@link LegacyScope} can map? Keys in the current
37
+ * format (`mcp__…`, `plugin__…`) never are.
38
+ */
39
+ export const isLegacyPrefixedKey = (key) => LEGACY_PREFIXED_RE.test(key) && !CURRENT_PREFIXED_RE.test(key);
40
+ /**
41
+ * The current key of a release-candidate key: a builtin by the frozen map; `<x>__<tool>` becomes
42
+ * `mcp__x__tool` when `x` is only a connector slug in `scope`, `plugin__x__tool` when only an
43
+ * extension name, else null (ambiguous, unknown, or not an old key; a key in the current format
44
+ * is never mapped again).
45
+ */
46
+ export function legacyToolKey(key, scope) {
47
+ if (isLegacyBuiltinKey(key))
48
+ return LEGACY_BUILTINS[key].name;
49
+ if (scope === undefined || !isLegacyPrefixedKey(key))
50
+ return null;
51
+ const i = key.indexOf("__");
52
+ if (i <= 0 || i + 2 >= key.length)
53
+ return null;
54
+ const head = key.slice(0, i);
55
+ const tool = key.slice(i + 2);
56
+ const connector = scope.connectorSlugs.has(head);
57
+ const extension = scope.extensionNames.has(head);
58
+ if (connector === extension)
59
+ return null;
60
+ return connector ? `mcp__${head}__${tool}` : `plugin__${head}__${tool}`;
61
+ }
62
+ /** The input of an old builtin call in the current parameter names (seconds -> milliseconds). */
63
+ export function legacyToolInput(oldKey, input) {
64
+ if (!isLegacyBuiltinKey(oldKey))
65
+ return input;
66
+ if (typeof input !== "object" || input === null || Array.isArray(input))
67
+ return input;
68
+ const { params, msParams } = LEGACY_BUILTINS[oldKey];
69
+ const out = {};
70
+ for (const [k, v] of Object.entries(input)) {
71
+ const next = Object.hasOwn(params, k) ? params[k] : k;
72
+ out[next] = msParams?.includes(next) === true && typeof v === "number" ? v * 1000 : v;
73
+ }
74
+ return out;
75
+ }
76
+ /** The new parameters of the builtins whose parameters were renamed (model-visible hint text). */
77
+ const HINT_PARAMS = {
78
+ Bash: "command, description, timeout in milliseconds",
79
+ Read: "file_path, offset, limit",
80
+ Write: "file_path, content",
81
+ Edit: "file_path, old_string, new_string, replace_all",
82
+ Grep: "pattern, path, glob, -i, output_mode, -n, -A, -B, -C, head_limit, offset",
83
+ Deliver: "file_path, name",
84
+ Skill: "skill, args",
85
+ ToolSearch: "query, max_results",
86
+ };
87
+ /** The model-visible hint for a call to an old builtin key; null for any other key. */
88
+ export function legacyHint(key) {
89
+ if (!isLegacyBuiltinKey(key))
90
+ return null;
91
+ const { name } = LEGACY_BUILTINS[key];
92
+ const params = HINT_PARAMS[name];
93
+ return `Tool "${key}" no longer exists; it is now "${name}"${params === undefined ? "" : ` (parameters: ${params})`}.`;
94
+ }
95
+ /**
96
+ * The current key(s) an rc key stands for, to name in a refusal when a definition is saved with
97
+ * it: a builtin's new name; for `<x>__<tool>`, the candidates among `known` (the agent's tool
98
+ * keys), else both `mcp__<x>__<tool>` and `plugin__<x>__<tool>`. Empty when `key` is no rc key.
99
+ */
100
+ export function legacyKeyCandidates(key, known) {
101
+ if (isLegacyBuiltinKey(key))
102
+ return [LEGACY_BUILTINS[key].name];
103
+ if (!isLegacyPrefixedKey(key))
104
+ return [];
105
+ const all = [`mcp__${key}`, `plugin__${key}`];
106
+ const hit = all.filter((k) => known?.has(k) === true);
107
+ return hit.length > 0 ? hit : all;
108
+ }
109
+ /** The refusal text for saving rc key `key` (null when it is none): names the new key(s). */
110
+ export function legacyKeySaveText(key, known) {
111
+ const c = legacyKeyCandidates(key, known);
112
+ if (c.length === 0)
113
+ return null;
114
+ return `"${key}" is a release-candidate tool key; use ${c.map((x) => `"${x}"`).join(" or ")}`;
115
+ }
@@ -1,14 +1,12 @@
1
1
  /**
2
2
  * 工具命名(Global Constraints):模型可见的工具 key 必须匹配 `^[a-zA-Z0-9_-]{1,64}$`;
3
- * 插件工具为 `<plugin>__<tool>`,连接器工具为 `<connectorSlug>__<tool>`,内置工具就是其名字本身。
3
+ * 插件工具为 `plugin__<plugin>__<tool>`,连接器工具为 `mcp__<connectorSlug>__<tool>`(拼与拆见 SDK 的 tool-key.ts),内置工具就是其名字本身。
4
4
  * 超长或含非法字符的 key 一律报出来,不截断、不猜。
5
5
  */
6
6
  export declare const TOOL_KEY_RE: RegExp;
7
7
  export declare const TOOL_KEY_MAX = 64;
8
8
  /** 连接器 slug:小写字母开头,小写字母 / 数字 / 下划线,最长 24。 */
9
9
  export declare const APP_SLUG_RE: RegExp;
10
- /** 前缀与工具名之间的分隔符。 */
11
- export declare const KEY_SEPARATOR = "__";
12
10
  /**
13
11
  * slug 是否合法:除正则外,不得含连续下划线、不得以下划线结尾——否则 `a__b` + `c` 与 `a` + `b__c`
14
12
  * 会拼出同一个 key。
@@ -21,7 +19,5 @@ export declare const SLUG_RULE = "Slug must start with a lowercase letter, conta
21
19
  * 截到 24 位(再去掉尾部下划线)。推导不出合法 slug(如名称全是中文)时返回 null,由调用方要求显式给出。
22
20
  */
23
21
  export declare function deriveSlug(name: string): string | null;
24
- /** 拼 `<prefix>__<name>`。 */
25
- export declare function toolKey(prefix: string, name: string): string;
26
22
  /** key 不合法时返回英文原因,合法时返回 null。 */
27
23
  export declare function toolKeyIssue(key: string): string | null;