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

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 (312) hide show
  1. package/dist/admin/admin.controller.d.ts +9 -4
  2. package/dist/admin/admin.controller.js +13 -4
  3. package/dist/admin/extensions.controller.d.ts +8 -1
  4. package/dist/admin/extensions.controller.js +22 -2
  5. package/dist/agents/agent-lookup.d.ts +3 -0
  6. package/dist/agents/agent-lookup.js +20 -2
  7. package/dist/agents/agent-profile.service.d.ts +29 -0
  8. package/dist/agents/agent-profile.service.js +117 -0
  9. package/dist/agents/agents.controller.d.ts +93 -3
  10. package/dist/agents/agents.controller.js +114 -4
  11. package/dist/agents/agents.module.js +6 -2
  12. package/dist/agents/agents.service.d.ts +13 -5
  13. package/dist/agents/agents.service.js +49 -13
  14. package/dist/agents/definition.d.ts +35 -8
  15. package/dist/agents/definition.js +203 -17
  16. package/dist/agents/instruction-files.d.ts +60 -0
  17. package/dist/agents/instruction-files.js +111 -0
  18. package/dist/agents/models.controller.d.ts +9 -3
  19. package/dist/agents/models.controller.js +19 -4
  20. package/dist/agents/profile.d.ts +96 -0
  21. package/dist/agents/profile.js +51 -0
  22. package/dist/agents/readiness.controller.d.ts +26 -0
  23. package/dist/agents/readiness.controller.js +56 -0
  24. package/dist/agents/templates.js +20 -5
  25. package/dist/background/background.controller.d.ts +38 -0
  26. package/dist/background/background.controller.js +87 -0
  27. package/dist/background/background.module.d.ts +10 -0
  28. package/dist/background/background.module.js +32 -0
  29. package/dist/background/finish.d.ts +43 -0
  30. package/dist/background/finish.js +94 -0
  31. package/dist/background/launch-lock.d.ts +14 -0
  32. package/dist/background/launch-lock.js +39 -0
  33. package/dist/background/launch.d.ts +36 -0
  34. package/dist/background/launch.js +70 -0
  35. package/dist/background/poll.job.d.ts +53 -0
  36. package/dist/background/poll.job.js +182 -0
  37. package/dist/background/render.d.ts +23 -0
  38. package/dist/background/render.js +48 -0
  39. package/dist/background/session.d.ts +37 -0
  40. package/dist/background/session.js +66 -0
  41. package/dist/background/status.d.ts +35 -0
  42. package/dist/background/status.js +57 -0
  43. package/dist/background/stop.d.ts +62 -0
  44. package/dist/background/stop.js +225 -0
  45. package/dist/background/store.d.ts +150 -0
  46. package/dist/background/store.js +351 -0
  47. package/dist/background/store.module.d.ts +6 -0
  48. package/dist/background/store.module.js +22 -0
  49. package/dist/background/tasks.service.d.ts +67 -0
  50. package/dist/background/tasks.service.js +186 -0
  51. package/dist/background/wake-rules.d.ts +57 -0
  52. package/dist/background/wake-rules.js +83 -0
  53. package/dist/background/wake.d.ts +28 -0
  54. package/dist/background/wake.js +57 -0
  55. package/dist/catalog/catalog-content.js +4 -1
  56. package/dist/catalog/claude/agents.js +15 -10
  57. package/dist/catalog/marketplaces.service.d.ts +1 -1
  58. package/dist/catalog/plugin-import.js +1 -1
  59. package/dist/catalog/plugin-upgrade.js +1 -1
  60. package/dist/channels/cards.d.ts +8 -2
  61. package/dist/channels/cards.js +17 -9
  62. package/dist/channels/connections.service.d.ts +16 -1
  63. package/dist/channels/connections.service.js +38 -4
  64. package/dist/channels/threads.d.ts +5 -0
  65. package/dist/channels/threads.js +8 -0
  66. package/dist/config/schema.d.ts +18 -0
  67. package/dist/config/schema.js +104 -2
  68. package/dist/conversations/access.js +2 -0
  69. package/dist/conversations/artifacts.d.ts +1 -1
  70. package/dist/conversations/artifacts.js +1 -1
  71. package/dist/conversations/attachments.service.d.ts +30 -0
  72. package/dist/conversations/attachments.service.js +130 -5
  73. package/dist/conversations/conversations.controller.d.ts +36 -5
  74. package/dist/conversations/conversations.controller.js +62 -12
  75. package/dist/conversations/conversations.module.js +3 -0
  76. package/dist/conversations/conversations.service.d.ts +44 -2
  77. package/dist/conversations/conversations.service.js +140 -12
  78. package/dist/conversations/event-log.d.ts +12 -1
  79. package/dist/conversations/event-log.js +29 -1
  80. package/dist/conversations/events.d.ts +81 -3
  81. package/dist/conversations/events.js +83 -16
  82. package/dist/conversations/queue.d.ts +3 -1
  83. package/dist/conversations/queue.js +13 -2
  84. package/dist/conversations/queue.service.js +3 -1
  85. package/dist/conversations/titles.d.ts +7 -0
  86. package/dist/conversations/titles.js +15 -0
  87. package/dist/db/kernel-schema.d.ts +60 -4
  88. package/dist/db/pool.d.ts +5 -0
  89. package/dist/db/pool.js +3 -0
  90. package/dist/errors.d.ts +8 -0
  91. package/dist/errors.js +8 -0
  92. package/dist/evals/recorded-tools.d.ts +27 -1
  93. package/dist/evals/recorded-tools.js +85 -10
  94. package/dist/evals/run-replay.d.ts +10 -2
  95. package/dist/evals/run-replay.js +49 -12
  96. package/dist/evals/step-replay.js +12 -6
  97. package/dist/evals/trajectory.d.ts +2 -2
  98. package/dist/evals/trajectory.js +1 -1
  99. package/dist/extensions/admin-ops.js +2 -0
  100. package/dist/extensions/admin-view.d.ts +3 -1
  101. package/dist/extensions/admin-view.js +10 -2
  102. package/dist/extensions/blobs.js +1 -0
  103. package/dist/extensions/config-issues.d.ts +13 -1
  104. package/dist/extensions/config-issues.js +22 -5
  105. package/dist/extensions/config-json-schema.d.ts +43 -7
  106. package/dist/extensions/config-json-schema.js +124 -58
  107. package/dist/extensions/deployment-config-store.d.ts +6 -0
  108. package/dist/extensions/deployment-config.d.ts +50 -3
  109. package/dist/extensions/deployment-config.js +125 -28
  110. package/dist/extensions/display-name.d.ts +22 -0
  111. package/dist/extensions/display-name.js +77 -0
  112. package/dist/extensions/extensions.module.js +6 -0
  113. package/dist/extensions/host.d.ts +26 -1
  114. package/dist/extensions/host.js +82 -9
  115. package/dist/extensions/org-config.controller.d.ts +11 -3
  116. package/dist/extensions/org-config.controller.js +14 -6
  117. package/dist/extensions/org-config.d.ts +6 -0
  118. package/dist/extensions/org-config.js +12 -13
  119. package/dist/extensions/package.d.ts +14 -1
  120. package/dist/extensions/package.js +51 -2
  121. package/dist/extensions/permission-view.d.ts +3 -0
  122. package/dist/extensions/point-defaults.controller.d.ts +34 -0
  123. package/dist/extensions/point-defaults.controller.js +71 -0
  124. package/dist/extensions/point-defaults.d.ts +64 -0
  125. package/dist/extensions/point-defaults.js +206 -0
  126. package/dist/extensions/registry.d.ts +1 -1
  127. package/dist/extensions/registry.js +2 -0
  128. package/dist/http/api.module.js +8 -0
  129. package/dist/http/nest-logger.d.ts +6 -5
  130. package/dist/http/nest-logger.js +24 -10
  131. package/dist/identity/auth.controller.js +9 -5
  132. package/dist/identity/cookies.d.ts +20 -5
  133. package/dist/identity/cookies.js +44 -6
  134. package/dist/identity/me.controller.d.ts +5 -1
  135. package/dist/identity/me.controller.js +12 -3
  136. package/dist/identity/sessions.d.ts +12 -0
  137. package/dist/identity/sessions.js +45 -0
  138. package/dist/identity/settings.js +1 -1
  139. package/dist/index.d.ts +1 -1
  140. package/dist/infra/config/kernel-config.service.js +6 -0
  141. package/dist/infra/database/database.module.js +1 -0
  142. package/dist/infra/events/kernel-events.d.ts +8 -0
  143. package/dist/infra/lifecycle/process-guards.js +4 -0
  144. package/dist/infra/logging/logging.module.d.ts +4 -2
  145. package/dist/infra/logging/logging.module.js +24 -6
  146. package/dist/infra/queue/redis-connection.d.ts +6 -0
  147. package/dist/infra/queue/redis-connection.js +7 -0
  148. package/dist/infra/redis/redis.module.js +6 -2
  149. package/dist/infra/tokens.d.ts +6 -1
  150. package/dist/infra/tokens.js +6 -1
  151. package/dist/lifecycle/kernel-lifecycle.js +5 -0
  152. package/dist/log.d.ts +13 -1
  153. package/dist/log.js +93 -17
  154. package/dist/models/purposes.d.ts +23 -0
  155. package/dist/models/purposes.js +31 -10
  156. package/dist/orgs/offboarding.d.ts +3 -1
  157. package/dist/orgs/offboarding.js +26 -16
  158. package/dist/orgs/settings.service.d.ts +7 -2
  159. package/dist/orgs/settings.service.js +15 -4
  160. package/dist/roles/worker.module.js +6 -0
  161. package/dist/runtime/context/build.d.ts +15 -13
  162. package/dist/runtime/context/build.js +45 -27
  163. package/dist/runtime/context/claude-tools.d.ts +3 -1
  164. package/dist/runtime/context/claude-tools.js +12 -12
  165. package/dist/runtime/context/compaction.js +4 -0
  166. package/dist/runtime/context/legacy-text.d.ts +22 -0
  167. package/dist/runtime/context/legacy-text.js +37 -0
  168. package/dist/runtime/context/skills.d.ts +29 -11
  169. package/dist/runtime/context/skills.js +37 -30
  170. package/dist/runtime/langgraph-loop.js +3 -2
  171. package/dist/runtime/loop.d.ts +5 -4
  172. package/dist/runtime/maintenance.d.ts +4 -2
  173. package/dist/runtime/maintenance.js +17 -5
  174. package/dist/runtime/model-call.d.ts +17 -1
  175. package/dist/runtime/model-call.js +4 -3
  176. package/dist/runtime/providers.d.ts +6 -0
  177. package/dist/runtime/providers.js +8 -0
  178. package/dist/runtime/run-finish.d.ts +1 -1
  179. package/dist/runtime/run-queue.d.ts +28 -5
  180. package/dist/runtime/run-queue.js +131 -35
  181. package/dist/runtime/run-worker.d.ts +2 -2
  182. package/dist/runtime/run-worker.js +20 -7
  183. package/dist/runtime/runtime.module.js +8 -3
  184. package/dist/runtime/step-log.d.ts +6 -0
  185. package/dist/runtime/step-log.js +16 -2
  186. package/dist/runtime/subagents/definition.d.ts +1 -6
  187. package/dist/runtime/subagents/definition.js +14 -13
  188. package/dist/runtime/subagents/dispatch.d.ts +2 -2
  189. package/dist/runtime/subagents/dispatch.js +2 -2
  190. package/dist/runtime/subagents/org-agents.js +2 -1
  191. package/dist/runtime/subagents/roster.d.ts +1 -1
  192. package/dist/runtime/subagents/roster.js +1 -1
  193. package/dist/runtime/subagents/skill-fork.d.ts +2 -2
  194. package/dist/runtime/subagents/skill-fork.js +2 -2
  195. package/dist/runtime/subagents/subagents.service.d.ts +3 -3
  196. package/dist/runtime/subagents/subagents.service.js +3 -3
  197. package/dist/runtime/usage-report.d.ts +2 -1
  198. package/dist/runtime/usage-report.js +2 -1
  199. package/dist/runtime/waits.d.ts +13 -7
  200. package/dist/runtime/waits.js +42 -16
  201. package/dist/sandbox/sandbox.module.js +6 -1
  202. package/dist/sandbox/sandbox.service.d.ts +61 -3
  203. package/dist/sandbox/sandbox.service.js +141 -7
  204. package/dist/skills/catalog.d.ts +4 -4
  205. package/dist/skills/catalog.js +4 -4
  206. package/dist/skills/changes.service.d.ts +2 -2
  207. package/dist/skills/changes.service.js +5 -4
  208. package/dist/skills/invocation-keys.d.ts +1 -1
  209. package/dist/skills/invocation-keys.js +1 -1
  210. package/dist/skills/invoke.d.ts +1 -1
  211. package/dist/skills/invoke.js +5 -4
  212. package/dist/tools/approvals.service.d.ts +6 -0
  213. package/dist/tools/approvals.service.js +21 -1
  214. package/dist/tools/builtin-names.d.ts +91 -0
  215. package/dist/tools/builtin-names.js +143 -0
  216. package/dist/tools/builtins/agent.d.ts +7 -7
  217. package/dist/tools/builtins/agent.js +7 -11
  218. package/dist/tools/builtins/ask-user.d.ts +1 -1
  219. package/dist/tools/builtins/ask-user.js +2 -2
  220. package/dist/tools/builtins/builtin-tools.d.ts +17 -3
  221. package/dist/tools/builtins/builtin-tools.js +36 -4
  222. package/dist/tools/builtins/read-tool-output.d.ts +1 -1
  223. package/dist/tools/builtins/read-tool-output.js +2 -2
  224. package/dist/tools/builtins/sandbox/bash.d.ts +4 -4
  225. package/dist/tools/builtins/sandbox/bash.js +259 -29
  226. package/dist/tools/builtins/sandbox/common.d.ts +30 -7
  227. package/dist/tools/builtins/sandbox/common.js +9 -5
  228. package/dist/tools/builtins/sandbox/deliver.d.ts +1 -1
  229. package/dist/tools/builtins/sandbox/deliver.js +12 -12
  230. package/dist/tools/builtins/sandbox/edit.d.ts +1 -1
  231. package/dist/tools/builtins/sandbox/edit.js +41 -30
  232. package/dist/tools/builtins/sandbox/file-state.d.ts +34 -0
  233. package/dist/tools/builtins/sandbox/file-state.js +34 -0
  234. package/dist/tools/builtins/sandbox/glob-match.d.ts +1 -1
  235. package/dist/tools/builtins/sandbox/glob-match.js +1 -1
  236. package/dist/tools/builtins/sandbox/glob.d.ts +1 -1
  237. package/dist/tools/builtins/sandbox/glob.js +4 -4
  238. package/dist/tools/builtins/sandbox/grep.d.ts +7 -4
  239. package/dist/tools/builtins/sandbox/grep.js +147 -30
  240. package/dist/tools/builtins/sandbox/index.d.ts +3 -1
  241. package/dist/tools/builtins/sandbox/index.js +7 -1
  242. package/dist/tools/builtins/sandbox/read.d.ts +2 -2
  243. package/dist/tools/builtins/sandbox/read.js +21 -14
  244. package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
  245. package/dist/tools/builtins/sandbox/task-output.js +304 -0
  246. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  247. package/dist/tools/builtins/sandbox/task-stop.js +112 -0
  248. package/dist/tools/builtins/sandbox/write.d.ts +1 -1
  249. package/dist/tools/builtins/sandbox/write.js +37 -33
  250. package/dist/tools/builtins/skill-load.d.ts +3 -3
  251. package/dist/tools/builtins/skill-load.js +32 -16
  252. package/dist/tools/builtins/skill-write.d.ts +4 -4
  253. package/dist/tools/builtins/skill-write.js +10 -10
  254. package/dist/tools/builtins/tool-search.d.ts +13 -9
  255. package/dist/tools/builtins/tool-search.js +25 -14
  256. package/dist/tools/builtins/web/errors.d.ts +8 -0
  257. package/dist/tools/builtins/web/errors.js +53 -0
  258. package/dist/tools/builtins/web/extract.d.ts +62 -0
  259. package/dist/tools/builtins/web/extract.js +267 -0
  260. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  261. package/dist/tools/builtins/web/fetch.js +174 -0
  262. package/dist/tools/builtins/web/index.d.ts +13 -0
  263. package/dist/tools/builtins/web/index.js +12 -0
  264. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  265. package/dist/tools/builtins/web/page-cache.js +77 -0
  266. package/dist/tools/builtins/web/search.d.ts +25 -0
  267. package/dist/tools/builtins/web/search.js +209 -0
  268. package/dist/tools/builtins/web/url.d.ts +17 -0
  269. package/dist/tools/builtins/web/url.js +43 -0
  270. package/dist/tools/connectors.service.js +4 -4
  271. package/dist/tools/display-names.d.ts +9 -0
  272. package/dist/tools/display-names.js +24 -0
  273. package/dist/tools/drift.js +5 -5
  274. package/dist/tools/elicitation.js +1 -1
  275. package/dist/tools/info.d.ts +20 -4
  276. package/dist/tools/info.js +55 -32
  277. package/dist/tools/legacy-data.d.ts +34 -0
  278. package/dist/tools/legacy-data.js +76 -0
  279. package/dist/tools/legacy-keys.d.ts +51 -0
  280. package/dist/tools/legacy-keys.js +115 -0
  281. package/dist/tools/naming.d.ts +1 -5
  282. package/dist/tools/naming.js +1 -7
  283. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  284. package/dist/tools/oauth/flow-paths.js +1 -1
  285. package/dist/tools/outputs.js +1 -1
  286. package/dist/tools/pipeline.d.ts +13 -7
  287. package/dist/tools/pipeline.js +112 -39
  288. package/dist/tools/policy.d.ts +1 -14
  289. package/dist/tools/policy.js +13 -19
  290. package/dist/tools/questions.controller.d.ts +1 -1
  291. package/dist/tools/questions.controller.js +1 -1
  292. package/dist/tools/questions.service.d.ts +1 -1
  293. package/dist/tools/registry.d.ts +38 -24
  294. package/dist/tools/registry.js +54 -47
  295. package/dist/tools/resources.d.ts +1 -1
  296. package/dist/tools/resources.js +1 -1
  297. package/dist/tools/tools.module.js +13 -1
  298. package/dist/tools/validate.js +1 -1
  299. package/package.json +3 -3
  300. package/src/migrations/0063_tool_keys_claude_code.sql +220 -0
  301. package/src/migrations/0064_account_last_org.sql +7 -0
  302. package/src/migrations/0065_conversation_archive.sql +8 -0
  303. package/src/migrations/0066_extension_display_name.sql +4 -0
  304. package/src/migrations/0067_agent_profiles.sql +20 -0
  305. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  306. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  307. package/src/migrations/0070_background_tasks.sql +37 -0
  308. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  309. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  310. package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
  311. package/dist/tools/builtins/sandbox/names.d.ts +0 -7
  312. package/dist/tools/builtins/sandbox/names.js +0 -20
@@ -1,5 +1,7 @@
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";
4
+ import { InstructionFilesSchema } from "./instruction-files.js";
3
5
  /** 模型引用:供应商插件名 + 模型 id。 */
4
6
  export const ModelRef = z.object({ provider: z.string(), model: z.string() });
5
7
  /**
@@ -37,14 +39,7 @@ export const AgentToolRef = z.preprocess(normalizeToolRef, z.discriminatedUnion(
37
39
  })
38
40
  .strict(),
39
41
  ]));
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
- ];
42
+ export { DEFAULT_AGENT_TOOLS } from "../tools/builtin-names.js";
48
43
  /** Roster names of inline templates and allowlisted org agents (the template name rule). */
49
44
  export const SUBAGENT_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
50
45
  /**
@@ -68,7 +63,7 @@ export const InlineAgentTemplate = z
68
63
  })
69
64
  .strict();
70
65
  /**
71
- * Sub-agents this agent may dispatch with the builtin `agent` tool (phase C, spec §5.2). No
66
+ * Sub-agents this agent may dispatch with the builtin `Agent` tool (phase C, spec §5.2). No
72
67
  * default: definitions without it are unchanged and gain no version.
73
68
  */
74
69
  export const SubagentsSchema = z
@@ -100,7 +95,16 @@ export const SubagentsSchema = z
100
95
  export const AgentDefinitionSchema = z.object({
101
96
  name: z.string().min(1).max(80),
102
97
  avatarUrl: z.url().nullable().default(null),
98
+ /**
99
+ * The model-visible system prompt. Derived on save from `instructionFiles` when they are given
100
+ * (`deriveInstructions`); every reader (build.ts, the sub-agent copy, evals, replay) reads this.
101
+ */
103
102
  instructions: z.string().max(100_000).default(""),
103
+ /**
104
+ * The prompt files, the authoring form of `instructions` (spec 2026-11-04 §3.3). No default:
105
+ * definitions without it (every older version) read as one `AGENTS.md` and gain no version.
106
+ */
107
+ instructionFiles: InstructionFilesSchema.optional(),
104
108
  model: ModelRef,
105
109
  fallback: ModelRef.nullable().default(null),
106
110
  /** 主模型的参数(按主模型的声明校验)。 */
@@ -118,7 +122,7 @@ export const AgentDefinitionSchema = z.object({
118
122
  * 技能(M5 T13):`orgSkills` 是引用的组织技能 id(保存时校验属于本组织且未删除,不可重复)。
119
123
  * agent 自己的技能(`scope=agent`)自动对它生效,不进定义。可省略(较早保存的定义也没有),按空处理;
120
124
  * 不给默认值:没用技能的定义保存前后不变,不会因此多出一个版本。
121
- * `writePolicy`(M6 T27,D10):私有 agent 的 `skill_write` 是否固定确认,缺省按 `allow`(不写进定义);
125
+ * `writePolicy`(M6 T27,D10):私有 agent 的 `SkillWrite` 是否固定确认,缺省按 `allow`(不写进定义);
122
126
  * 共享 agent 不论取值都走 R3 + R4。
123
127
  */
124
128
  skills: z
@@ -136,20 +140,202 @@ export const AgentDefinitionSchema = z.object({
136
140
  })
137
141
  .strict()
138
142
  .optional(),
139
- /** Sub-agents (phase C); offered only when the builtin `agent` tool is in `tools`. */
143
+ /** Sub-agents (phase C); offered only when the builtin `Agent` tool is in `tools`. */
140
144
  subagents: SubagentsSchema.optional(),
141
145
  });
146
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
147
+ /**
148
+ * The connector slugs and extension names `definition`'s `tools` name: connector refs (through
149
+ * `scope.connectorSlugById`), plugin refs, and current-format keys of a template's `tools`;
150
+ * `inherited` (the parent agent's, for an inline template) is added.
151
+ */
152
+ function ownRefs(definition, scope, inherited) {
153
+ const connectors = new Set(inherited?.connectors);
154
+ const extensions = new Set(inherited?.extensions);
155
+ const tools = Array.isArray(definition.tools) ? definition.tools : [];
156
+ for (const r of tools) {
157
+ if (typeof r === "string") {
158
+ const m = /^(mcp|plugin)__([^_].*?)__./.exec(r);
159
+ if (m)
160
+ (m[1] === "mcp" ? connectors : extensions).add(m[2]);
161
+ }
162
+ else if (isRecord(r) && r.kind === "connector" && typeof r.connectorId === "string") {
163
+ const slug = scope.connectorSlugById?.get(r.connectorId);
164
+ if (slug !== undefined)
165
+ connectors.add(slug);
166
+ }
167
+ else if (isRecord(r) && r.kind === "plugin" && typeof r.plugin === "string") {
168
+ extensions.add(r.plugin);
169
+ }
170
+ }
171
+ return { connectors, extensions };
172
+ }
173
+ /**
174
+ * The current key(s) of a stored key. A builtin and an unambiguous `<x>__<tool>` map to one key
175
+ * ({@link legacyToolKey}). When `x` is both a connector slug and an extension name, the
176
+ * definition's own refs decide (only connector `x` -> `mcp__…`, only extension `x` ->
177
+ * `plugin__…`); when they name both or neither, both candidates are returned and the caller
178
+ * decides (a policy keeps a deny / ask on both: it must never be lost). Anything else: as is.
179
+ */
180
+ function currentKeys(key, scope, own) {
181
+ const one = legacyToolKey(key, scope);
182
+ if (one !== null)
183
+ return [one];
184
+ if (scope === undefined || !isLegacyPrefixedKey(key))
185
+ return [key];
186
+ const head = key.slice(0, key.indexOf("__"));
187
+ if (!scope.connectorSlugs.has(head) || !scope.extensionNames.has(head))
188
+ return [key];
189
+ const c = own?.connectors.has(head) === true;
190
+ const e = own?.extensions.has(head) === true;
191
+ if (c && !e)
192
+ return [`mcp__${key}`];
193
+ if (e && !c)
194
+ return [`plugin__${key}`];
195
+ return [`mcp__${key}`, `plugin__${key}`];
196
+ }
197
+ /**
198
+ * A list of tool keys (a template's `tools` / `disallowedTools`) in current keys, deduplicated.
199
+ * A still-ambiguous key becomes both candidates in a deny list (`widen`), and stays as is (inert,
200
+ * so narrower) in an allowlist.
201
+ */
202
+ function keyList(v, scope, own, widen) {
203
+ if (!Array.isArray(v))
204
+ return v;
205
+ let changed = false;
206
+ const out = [];
207
+ for (const x of v) {
208
+ let next = [x];
209
+ if (typeof x === "string") {
210
+ const keys = currentKeys(x, scope, own);
211
+ next = keys.length > 1 && !widen ? [x] : keys;
212
+ }
213
+ if (next.length !== 1 || next[0] !== x)
214
+ changed = true;
215
+ for (const n of next) {
216
+ if (typeof n === "string" && out.includes(n)) {
217
+ changed = true;
218
+ continue;
219
+ }
220
+ out.push(n);
221
+ }
222
+ }
223
+ return changed ? out : v;
224
+ }
225
+ /**
226
+ * `toolPolicies` in current keys; keys that become one keep the stricter policy, and a
227
+ * still-ambiguous key keeps its `ask` / `deny` on both candidates (anything else is dropped).
228
+ */
229
+ function policyKeys(v, scope, own) {
230
+ if (!isRecord(v))
231
+ return v;
232
+ const mapped = Object.entries(v).map(([k, p]) => [k, p, currentKeys(k, scope, own)]);
233
+ if (mapped.every(([k, , keys]) => keys.length === 1 && keys[0] === k))
234
+ return v;
235
+ const rank = (p) => (p === "deny" ? 2 : p === "ask" ? 1 : 0);
236
+ const out = {};
237
+ for (const [, p, keys] of mapped) {
238
+ if (keys.length > 1 && rank(p) === 0)
239
+ continue;
240
+ for (const next of keys) {
241
+ if (!Object.hasOwn(out, next) || rank(p) > rank(out[next]))
242
+ out[next] = p;
243
+ }
244
+ }
245
+ return out;
246
+ }
247
+ /** An agent definition's `tools`: builtin refs by their current names (connector and plugin refs name tools, not keys). */
248
+ function toolRefs(v, scope, own) {
249
+ if (!Array.isArray(v))
250
+ return v;
251
+ // template-shaped (`tools: string[]`): an allowlist
252
+ if (v.some((x) => typeof x === "string"))
253
+ return keyList(v, scope, own, false);
254
+ if (!v.some((r) => isRecord(r) &&
255
+ r.kind === "builtin" &&
256
+ typeof r.name === "string" &&
257
+ isLegacyBuiltinKey(r.name))) {
258
+ return v;
259
+ }
260
+ return v.map((r) => isRecord(r) && r.kind === "builtin" && typeof r.name === "string"
261
+ ? { ...r, name: legacyToolKey(r.name) ?? r.name }
262
+ : r);
263
+ }
264
+ /**
265
+ * Release-candidate tool keys in a stored definition, read as current keys (tool naming spec
266
+ * §8.3), the way {@link normalizeToolRef} reads the app -> connector rename: builtin refs in
267
+ * `tools`, the keys of `toolPolicies`, and the `tools` / `disallowedTools` / `toolPolicies` of a
268
+ * template, the agent's inline sub-agent templates included. Works on an agent definition and on
269
+ * an agent template alike. Old builtin names map without `scope`; an rc `<x>__<tool>` key maps
270
+ * only with one (the org's connector slugs and their ids, the deployment's extension names). When
271
+ * `x` is both a slug and an extension name, the definition's own tool refs decide; when they
272
+ * cannot, a policy or a deny list keeps the restriction on both candidate keys (a deny is never
273
+ * lost) and an allowlist keeps the old key (inert). An unknown key stays as it is. Keys in the
274
+ * current format are never touched. Returns the same object when nothing changes. Saving never
275
+ * goes through this: a new definition with an old key is refused (`AgentsService`).
276
+ */
277
+ export function normalizeToolKeys(definition, scope, inherited) {
278
+ if (!isRecord(definition))
279
+ return definition;
280
+ const own = scope === undefined ? undefined : ownRefs(definition, scope, inherited);
281
+ const next = { ...definition };
282
+ let changed = false;
283
+ const put = (field, value) => {
284
+ if (value === definition[field])
285
+ return;
286
+ next[field] = value;
287
+ changed = true;
288
+ };
289
+ if ("tools" in definition)
290
+ put("tools", toolRefs(definition.tools, scope, own));
291
+ if ("disallowedTools" in definition) {
292
+ put("disallowedTools", keyList(definition.disallowedTools, scope, own, true));
293
+ }
294
+ if ("toolPolicies" in definition) {
295
+ put("toolPolicies", policyKeys(definition.toolPolicies, scope, own));
296
+ }
297
+ const sub = definition.subagents;
298
+ if (isRecord(sub) && Array.isArray(sub.inline)) {
299
+ const inline = sub.inline.map((t) => normalizeToolKeys(t, scope, own));
300
+ if (inline.some((t, i) => t !== sub.inline[i])) {
301
+ put("subagents", { ...sub, inline });
302
+ }
303
+ }
304
+ return changed ? next : definition;
305
+ }
306
+ /**
307
+ * Does a stored definition or template hold an rc `<x>__<tool>` key, which only a
308
+ * {@link LegacyScope} can map? (Callers load the scope only then.)
309
+ */
310
+ export function needsLegacyScope(definition) {
311
+ if (!isRecord(definition))
312
+ return false;
313
+ const lists = [definition.tools, definition.disallowedTools];
314
+ for (const l of lists) {
315
+ if (Array.isArray(l) && l.some((x) => typeof x === "string" && isLegacyPrefixedKey(x))) {
316
+ return true;
317
+ }
318
+ }
319
+ if (isRecord(definition.toolPolicies)) {
320
+ if (Object.keys(definition.toolPolicies).some(isLegacyPrefixedKey))
321
+ return true;
322
+ }
323
+ const sub = definition.subagents;
324
+ return isRecord(sub) && Array.isArray(sub.inline) && sub.inline.some(needsLegacyScope);
325
+ }
142
326
  /**
143
327
  * 库里读出的定义(`agent_versions.definition`,不经 zod 解析):把改名前的 `{ kind: "app" }` 工具引用规范化为
144
- * connector 形(C27)。其余字段原样保留;没有旧形引用时返回原对象。
328
+ * connector 形(C27),and rc tool keys to current keys ({@link normalizeToolKeys}: builtins
329
+ * always, `<x>__<tool>` keys with `scope`). Other fields are kept; without old forms the same
330
+ * object is returned.
145
331
  */
146
- export function normalizeStoredDefinition(def) {
147
- const d = def;
332
+ export function normalizeStoredDefinition(def, scope) {
333
+ let d = def;
148
334
  const tools = d.tools;
149
- if (!Array.isArray(tools) || !tools.some((t) => t?.kind === "app")) {
150
- return d;
335
+ if (Array.isArray(tools) && tools.some((t) => t?.kind === "app")) {
336
+ d = { ...d, tools: tools.map(normalizeToolRef) };
151
337
  }
152
- return { ...d, tools: tools.map(normalizeToolRef) };
338
+ return normalizeToolKeys(d, scope);
153
339
  }
154
340
  /** 按模型声明严格校验一组参数,返回 `{ path: "<field>.<key>", message }[]`(评测候选也用它)。 */
155
341
  export function paramIssues(field, decl, params) {
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import type { AgentDefinition } from "./definition.js";
3
+ /**
4
+ * Prompt files (spec 2026-11-04 §3.3): the authoring form of an agent's system prompt. The kernel
5
+ * joins them into `instructions` on save, and `instructions` stays the one model-visible field
6
+ * (build.ts, the sub-agent copy, eval candidates and replay keep reading it), so no log event
7
+ * changes shape and replay needs no new `DriftReason`.
8
+ */
9
+ export type InstructionFile = {
10
+ name: string;
11
+ content: string;
12
+ };
13
+ /** The default and first file. */
14
+ export declare const AGENTS_MD = "AGENTS.md";
15
+ export declare const INSTRUCTION_FILE_LIMITS: {
16
+ /** At most this many files, at least one. */
17
+ readonly count: 20;
18
+ /** The joined text, the same bound as `instructions`. */
19
+ readonly joinedChars: 100000;
20
+ };
21
+ /** A file name: 1–64 characters without `/` or `\`, then `.md`. */
22
+ export declare const INSTRUCTION_FILE_NAME_RE: RegExp;
23
+ /**
24
+ * The starter prompt of a new agent created without one (model-visible once saved; English like
25
+ * every model-facing text).
26
+ */
27
+ export declare const AGENTS_MD_STARTER: string;
28
+ /**
29
+ * The model-visible join: one file gives exactly its content (so an old agent edited without
30
+ * adding files sends the same text as before); several files give `# <name>\n\n<content>` each,
31
+ * in order, joined by `\n\n`.
32
+ */
33
+ export declare function joinInstructionFiles(files: readonly InstructionFile[]): string;
34
+ /** The files of a definition; a definition without them (every old version) is one `AGENTS.md`. */
35
+ export declare function readInstructionFiles(def: Pick<AgentDefinition, "instructions" | "instructionFiles">): InstructionFile[];
36
+ export declare const InstructionFileSchema: z.ZodObject<{
37
+ name: z.ZodString;
38
+ content: z.ZodString;
39
+ }, z.core.$strict>;
40
+ /**
41
+ * 1–20 files, names unique ignoring case (`AGENTS.md` and `agents.md` would collide on a
42
+ * case-insensitive file system), a joined total of at most 100 000 characters.
43
+ */
44
+ export declare const InstructionFilesSchema: z.ZodArray<z.ZodObject<{
45
+ name: z.ZodString;
46
+ content: z.ZodString;
47
+ }, z.core.$strict>>;
48
+ /** A definition as a save request gives it: `instructions` may be left out. */
49
+ export type AgentDefinitionInput = Omit<AgentDefinition, "instructions"> & {
50
+ instructions?: string | undefined;
51
+ };
52
+ /**
53
+ * Derives the stored definition of a save. With `instructionFiles`, `instructions` is their join
54
+ * (a differing `instructions` given with them → 400 `agent.instructions_mismatch`). With only
55
+ * `instructions` (old clients) the current files are kept while it equals their join, else the
56
+ * definition is stored as given, without files. Given neither,
57
+ * an update keeps the `current` version's prompt, and a new agent (`current` null) gets the
58
+ * `AGENTS.md` starter.
59
+ */
60
+ export declare function deriveInstructions(def: AgentDefinitionInput, current: Pick<AgentDefinition, "instructions" | "instructionFiles"> | null): AgentDefinition;
@@ -0,0 +1,111 @@
1
+ import { z } from "zod";
2
+ import { ApiError } from "../errors.js";
3
+ import { CONTROL_OR_HIDDEN_RE } from "../text.js";
4
+ /** The default and first file. */
5
+ export const AGENTS_MD = "AGENTS.md";
6
+ export const INSTRUCTION_FILE_LIMITS = {
7
+ /** At most this many files, at least one. */
8
+ count: 20,
9
+ /** The joined text, the same bound as `instructions`. */
10
+ joinedChars: 100_000,
11
+ };
12
+ /** A file name: 1–64 characters without `/` or `\`, then `.md`. */
13
+ export const INSTRUCTION_FILE_NAME_RE = /^[^/\\]{1,64}\.md$/;
14
+ /**
15
+ * The starter prompt of a new agent created without one (model-visible once saved; English like
16
+ * every model-facing text).
17
+ */
18
+ export const AGENTS_MD_STARTER = [
19
+ "You are an assistant for this team. Help people get their work done accurately and efficiently.",
20
+ "",
21
+ "- Tone: clear, friendly and concise. Use plain language, keep answers short, and add detail when asked.",
22
+ "- When a request is unclear or missing key details, ask one short clarifying question before acting.",
23
+ " If you go ahead anyway, say what you assumed.",
24
+ "- If you don't know something or can't do it, say so instead of guessing.",
25
+ ].join("\n");
26
+ /**
27
+ * The model-visible join: one file gives exactly its content (so an old agent edited without
28
+ * adding files sends the same text as before); several files give `# <name>\n\n<content>` each,
29
+ * in order, joined by `\n\n`.
30
+ */
31
+ export function joinInstructionFiles(files) {
32
+ if (files.length === 1)
33
+ return files[0]?.content ?? "";
34
+ return files.map((f) => `# ${f.name}\n\n${f.content}`).join("\n\n");
35
+ }
36
+ /** The files of a definition; a definition without them (every old version) is one `AGENTS.md`. */
37
+ export function readInstructionFiles(def) {
38
+ return def.instructionFiles ?? [{ name: AGENTS_MD, content: def.instructions }];
39
+ }
40
+ export const InstructionFileSchema = z
41
+ .object({
42
+ name: z
43
+ .string()
44
+ .regex(INSTRUCTION_FILE_NAME_RE, "a file name is 1–64 characters without / or \\, then .md")
45
+ .refine((n) => !CONTROL_OR_HIDDEN_RE.test(n), "a file name must not contain control or format characters"),
46
+ content: z.string().max(INSTRUCTION_FILE_LIMITS.joinedChars),
47
+ })
48
+ .strict();
49
+ /**
50
+ * 1–20 files, names unique ignoring case (`AGENTS.md` and `agents.md` would collide on a
51
+ * case-insensitive file system), a joined total of at most 100 000 characters.
52
+ */
53
+ export const InstructionFilesSchema = z
54
+ .array(InstructionFileSchema)
55
+ .min(1)
56
+ .max(INSTRUCTION_FILE_LIMITS.count)
57
+ .superRefine((files, ctx) => {
58
+ const seen = new Set();
59
+ files.forEach((f, i) => {
60
+ const key = f.name.toLowerCase();
61
+ if (seen.has(key)) {
62
+ ctx.addIssue({ code: "custom", path: [i, "name"], message: `duplicate file ${f.name}` });
63
+ }
64
+ seen.add(key);
65
+ });
66
+ const joined = joinInstructionFiles(files).length;
67
+ if (joined > INSTRUCTION_FILE_LIMITS.joinedChars) {
68
+ ctx.addIssue({
69
+ code: "custom",
70
+ message: `the joined files are ${joined} characters, more than ${INSTRUCTION_FILE_LIMITS.joinedChars}`,
71
+ });
72
+ }
73
+ });
74
+ /**
75
+ * Derives the stored definition of a save. With `instructionFiles`, `instructions` is their join
76
+ * (a differing `instructions` given with them → 400 `agent.instructions_mismatch`). With only
77
+ * `instructions` (old clients) the current files are kept while it equals their join, else the
78
+ * definition is stored as given, without files. Given neither,
79
+ * an update keeps the `current` version's prompt, and a new agent (`current` null) gets the
80
+ * `AGENTS.md` starter.
81
+ */
82
+ export function deriveInstructions(def, current) {
83
+ const { instructions, instructionFiles, ...rest } = def;
84
+ if (instructionFiles !== undefined) {
85
+ const joined = joinInstructionFiles(instructionFiles);
86
+ if (instructions !== undefined && instructions !== joined) {
87
+ throw new ApiError("agent.instructions_mismatch", "instructions differs from the join of instructionFiles; send only instructionFiles");
88
+ }
89
+ return { ...rest, instructions: joined, instructionFiles };
90
+ }
91
+ if (instructions !== undefined) {
92
+ // an old client re-sending the same joined text (an unrelated edit) keeps the files
93
+ const files = current?.instructionFiles;
94
+ if (files !== undefined && instructions === joinInstructionFiles(files)) {
95
+ return { ...rest, instructions, instructionFiles: files };
96
+ }
97
+ return { ...rest, instructions };
98
+ }
99
+ if (current !== null) {
100
+ return {
101
+ ...rest,
102
+ instructions: current.instructions,
103
+ ...(current.instructionFiles !== undefined && { instructionFiles: current.instructionFiles }),
104
+ };
105
+ }
106
+ return {
107
+ ...rest,
108
+ instructions: AGENTS_MD_STARTER,
109
+ instructionFiles: [{ name: AGENTS_MD, content: AGENTS_MD_STARTER }],
110
+ };
111
+ }
@@ -1,12 +1,18 @@
1
- import type { ModelInfo } from "@capybara-hq/plugin-sdk";
1
+ import type { LocalizedName, ModelInfo } from "@capybara-hq/plugin-sdk";
2
+ import { ExtensionDisplayNames } from "../extensions/display-name.js";
2
3
  import { ProviderRegistry } from "../runtime/providers.js";
3
- /** 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。 */
4
+ /**
5
+ * 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。Each provider
6
+ * carries its extension's display name (null when it has none).
7
+ */
4
8
  export declare class ModelsController {
5
9
  private readonly registry;
6
- constructor(registry: ProviderRegistry);
10
+ private readonly displayNames;
11
+ constructor(registry: ProviderRegistry, displayNames: ExtensionDisplayNames);
7
12
  list(): Promise<{
8
13
  providers: {
9
14
  provider: string;
15
+ displayName: LocalizedName | null;
10
16
  models: ModelInfo[];
11
17
  }[];
12
18
  }>;
@@ -8,16 +8,30 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { Controller, Get, Inject } from "@nestjs/common";
11
+ import { ExtensionDisplayNames } from "../extensions/display-name.js";
11
12
  import { OrgAction } from "../orgs/org.guard.js";
12
13
  import { ProviderRegistry } from "../runtime/providers.js";
13
- /** 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。 */
14
+ /**
15
+ * 可选模型(`/api/v1/orgs/:orgId/models`):已安装的模型供应商及其模型,列表缓存在 Redis。Each provider
16
+ * carries its extension's display name (null when it has none).
17
+ */
14
18
  let ModelsController = class ModelsController {
15
19
  registry;
16
- constructor(registry) {
20
+ displayNames;
21
+ constructor(registry, displayNames) {
17
22
  this.registry = registry;
23
+ this.displayNames = displayNames;
18
24
  }
19
25
  async list() {
20
- return { providers: await this.registry.list() };
26
+ const providers = await this.registry.list();
27
+ const names = await this.displayNames.many(providers.map((p) => p.provider));
28
+ return {
29
+ providers: providers.map((p) => ({
30
+ provider: p.provider,
31
+ displayName: names.get(p.provider) ?? null,
32
+ models: p.models,
33
+ })),
34
+ };
21
35
  }
22
36
  };
23
37
  __decorate([
@@ -26,6 +40,7 @@ __decorate([
26
40
  ], ModelsController.prototype, "list", null);
27
41
  ModelsController = __decorate([
28
42
  Controller("api/v1/orgs/:orgId/models"),
29
- __param(0, Inject(ProviderRegistry))
43
+ __param(0, Inject(ProviderRegistry)),
44
+ __param(1, Inject(ExtensionDisplayNames))
30
45
  ], ModelsController);
31
46
  export { ModelsController };
@@ -0,0 +1,96 @@
1
+ import { z } from "zod";
2
+ /** The ten built-in pixel-animal avatars (drawn by `@capybara-hq/design`). */
3
+ export declare const PRESET_IDS: readonly ["capybara", "black-bear", "panda", "elephant", "fox", "owl", "penguin", "rabbit", "turtle", "whale"];
4
+ export type PresetId = (typeof PRESET_IDS)[number];
5
+ export declare const PROFILE_LIMITS: {
6
+ readonly description: 500;
7
+ readonly quickActions: 9;
8
+ readonly title: 40;
9
+ readonly body: 20000;
10
+ };
11
+ /** A random preset id: the avatar of an agent created without one. */
12
+ export declare function randomPreset(): PresetId;
13
+ export declare const AgentAvatarSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
14
+ kind: z.ZodLiteral<"preset">;
15
+ id: z.ZodEnum<{
16
+ "black-bear": "black-bear";
17
+ capybara: "capybara";
18
+ elephant: "elephant";
19
+ fox: "fox";
20
+ owl: "owl";
21
+ panda: "panda";
22
+ penguin: "penguin";
23
+ rabbit: "rabbit";
24
+ turtle: "turtle";
25
+ whale: "whale";
26
+ }>;
27
+ }, z.core.$strict>, z.ZodObject<{
28
+ kind: z.ZodLiteral<"upload">;
29
+ uploadId: z.ZodString;
30
+ }, z.core.$strict>, z.ZodObject<{
31
+ kind: z.ZodLiteral<"external">;
32
+ url: z.ZodURL;
33
+ }, z.core.$strict>], "kind">;
34
+ /** `null` only for agents created before profiles existed. */
35
+ export type AgentAvatar = z.infer<typeof AgentAvatarSchema> | null;
36
+ export declare const QuickActionSchema: z.ZodObject<{
37
+ title: z.ZodString;
38
+ body: z.ZodString;
39
+ }, z.core.$strict>;
40
+ export type QuickAction = z.infer<typeof QuickActionSchema>;
41
+ export declare const DescriptionSchema: z.ZodString;
42
+ export declare const QuickActionsSchema: z.ZodArray<z.ZodObject<{
43
+ title: z.ZodString;
44
+ body: z.ZodString;
45
+ }, z.core.$strict>>;
46
+ /** What a client may write (`external` is migrated data only). */
47
+ export declare const AvatarInputSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
48
+ kind: z.ZodLiteral<"preset">;
49
+ id: z.ZodEnum<{
50
+ "black-bear": "black-bear";
51
+ capybara: "capybara";
52
+ elephant: "elephant";
53
+ fox: "fox";
54
+ owl: "owl";
55
+ panda: "panda";
56
+ penguin: "penguin";
57
+ rabbit: "rabbit";
58
+ turtle: "turtle";
59
+ whale: "whale";
60
+ }>;
61
+ }, z.core.$strict>, z.ZodObject<{
62
+ kind: z.ZodLiteral<"upload">;
63
+ uploadId: z.ZodString;
64
+ }, z.core.$strict>], "kind">;
65
+ export declare const ProfileInputSchema: z.ZodObject<{
66
+ avatar: z.ZodOptional<z.ZodNullable<z.ZodDiscriminatedUnion<[z.ZodObject<{
67
+ kind: z.ZodLiteral<"preset">;
68
+ id: z.ZodEnum<{
69
+ "black-bear": "black-bear";
70
+ capybara: "capybara";
71
+ elephant: "elephant";
72
+ fox: "fox";
73
+ owl: "owl";
74
+ panda: "panda";
75
+ penguin: "penguin";
76
+ rabbit: "rabbit";
77
+ turtle: "turtle";
78
+ whale: "whale";
79
+ }>;
80
+ }, z.core.$strict>, z.ZodObject<{
81
+ kind: z.ZodLiteral<"upload">;
82
+ uploadId: z.ZodString;
83
+ }, z.core.$strict>], "kind">>>;
84
+ description: z.ZodOptional<z.ZodString>;
85
+ quickActions: z.ZodOptional<z.ZodArray<z.ZodObject<{
86
+ title: z.ZodString;
87
+ body: z.ZodString;
88
+ }, z.core.$strict>>>;
89
+ }, z.core.$strict>;
90
+ export type ProfileInput = z.infer<typeof ProfileInputSchema>;
91
+ export interface AgentProfile {
92
+ avatar: AgentAvatar;
93
+ description: string;
94
+ quickActions: QuickAction[];
95
+ updatedAt: string;
96
+ }
@@ -0,0 +1,51 @@
1
+ import { randomInt } from "node:crypto";
2
+ import { z } from "zod";
3
+ /** The ten built-in pixel-animal avatars (drawn by `@capybara-hq/design`). */
4
+ export const PRESET_IDS = [
5
+ "capybara",
6
+ "black-bear",
7
+ "panda",
8
+ "elephant",
9
+ "fox",
10
+ "owl",
11
+ "penguin",
12
+ "rabbit",
13
+ "turtle",
14
+ "whale",
15
+ ];
16
+ export const PROFILE_LIMITS = {
17
+ description: 500,
18
+ quickActions: 9,
19
+ title: 40,
20
+ body: 20_000,
21
+ };
22
+ /** A random preset id: the avatar of an agent created without one. */
23
+ export function randomPreset() {
24
+ return PRESET_IDS[randomInt(PRESET_IDS.length)];
25
+ }
26
+ export const AgentAvatarSchema = z.discriminatedUnion("kind", [
27
+ z.object({ kind: z.literal("preset"), id: z.enum(PRESET_IDS) }).strict(),
28
+ z.object({ kind: z.literal("upload"), uploadId: z.string().min(1).max(200) }).strict(),
29
+ // only produced by the migration of `definition.avatarUrl`; never accepted from a client
30
+ z.object({ kind: z.literal("external"), url: z.url() }).strict(),
31
+ ]);
32
+ export const QuickActionSchema = z
33
+ .object({
34
+ title: z.string().min(1).max(PROFILE_LIMITS.title),
35
+ body: z.string().min(1).max(PROFILE_LIMITS.body),
36
+ })
37
+ .strict();
38
+ export const DescriptionSchema = z.string().max(PROFILE_LIMITS.description);
39
+ export const QuickActionsSchema = z.array(QuickActionSchema).max(PROFILE_LIMITS.quickActions);
40
+ /** What a client may write (`external` is migrated data only). */
41
+ export const AvatarInputSchema = z.discriminatedUnion("kind", [
42
+ z.object({ kind: z.literal("preset"), id: z.enum(PRESET_IDS) }).strict(),
43
+ z.object({ kind: z.literal("upload"), uploadId: z.string().min(1).max(200) }).strict(),
44
+ ]);
45
+ export const ProfileInputSchema = z
46
+ .object({
47
+ avatar: AvatarInputSchema.nullable().optional(),
48
+ description: DescriptionSchema.optional(),
49
+ quickActions: QuickActionsSchema.optional(),
50
+ })
51
+ .strict();