@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,15 +1,17 @@
1
1
  import { ToolError } from "@capybara-hq/plugin-sdk";
2
- import { clip, display, inSandbox, runBudgetNote, runTimeLeftMs, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
3
- /** 不给 `timeoutSeconds` 时的命令超时(秒),不超过 `sandbox.limits.maxCommandSeconds`。 */
2
+ import { backgroundMaxSeconds, LAUNCH_EXEC_TIMEOUT_MS, launchScript, newTaskId, startedText, taskOutputPath, taskSidPath, } from "../../../background/launch.js";
3
+ import { KILL_SESSION_TIMEOUT_MS, killSession, SID_RE } from "../../../background/session.js";
4
+ import { clip, display, inSandbox, noSandboxError, runBudgetNote, runTimeLeftMs, sandboxBaseTimeoutMs, sandboxConversationOf, unavailableWhenNoSandbox, withNotices, } from "./common.js";
5
+ /** The command timeout in seconds when `timeout` is not given, at most `sandbox.limits.maxCommandSeconds`. */
4
6
  export const BASH_DEFAULT_TIMEOUT_SECONDS = 120;
5
7
  /** The longest `description` (the shell row shows it instead of the command). */
6
8
  export const BASH_DESCRIPTION_MAX_CHARS = 120;
7
- /** bash 结果超过这么多字符即转存(走现有输出存档,模型用 read_tool_output 分页读)。 */
9
+ /** bash 结果超过这么多字符即转存(走现有输出存档,模型用 ReadToolOutput 分页读)。 */
8
10
  export const BASH_SPILL_CHARS = 30_000;
9
11
  /** 一次命令最多收集的输出字节(stdout + stderr);超出的丢弃并在结果里说明。 */
10
12
  const MAX_OUTPUT_BYTES = 4 * 1024 * 1024;
11
13
  const SPEC = {
12
- name: "sandbox_bash",
14
+ name: "Bash",
13
15
  // Display only (the tool row; not in the fingerprint, never sent to the model).
14
16
  keyArgs: ["command"],
15
17
  description: "Run a shell command (bash) in this conversation's sandbox, an isolated Linux machine. " +
@@ -20,12 +22,22 @@ const SPEC = {
20
22
  "Files the user attached to the conversation are under /workspace/attachments, " +
21
23
  "and skill files under /skills (read-only). Each call runs in a fresh shell (no state such as " +
22
24
  "cd or environment variables carries over). The result has stdout, stderr and the exit code; " +
23
- "long output is saved and can be paged with read_tool_output. A command that runs longer than " +
24
- "timeoutSeconds is stopped. Prefer sandbox_read, sandbox_write, sandbox_edit, sandbox_glob and " +
25
- "sandbox_grep for reading, writing and searching files. To give a file to the user, use " +
26
- "sandbox_deliver. Other sub-agents of this conversation may run commands in this sandbox at " +
25
+ "long output is saved and can be paged with ReadToolOutput. A command that runs longer than " +
26
+ "timeout milliseconds is stopped. Prefer Read, Write, Edit, Glob and " +
27
+ "Grep for reading, writing and searching files. To give a file to the user, use " +
28
+ "Deliver. Other sub-agents of this conversation may run commands in this sandbox at " +
27
29
  "the same time. Always pass description: a few words saying what the command is for; the " +
28
- "user sees it instead of the command.",
30
+ "user sees it instead of the command.\n\n" +
31
+ "You can use the run_in_background parameter to run the command in the background, which " +
32
+ "allows you to continue working while the command runs, e.g. a dev server, a long build or a " +
33
+ "test suite. The call returns at once with a task id and the command's output goes to a file " +
34
+ "under /workspace/.capybara/tasks. Read the output with TaskOutput (or Read) and stop the " +
35
+ "task with TaskStop; you will be told when it finishes. You do not need to use '&' at the end " +
36
+ "of the command when using this parameter. In the background, timeout defaults to the " +
37
+ "deployment's background maximum (7200000 by default). A background task ends when its " +
38
+ "command exits, and processes it left running are stopped. A background task is lost if the " +
39
+ "sandbox is suspended (it is suspended after it has been running for a while, " +
40
+ "sandbox.limits.maxRunningMinutes), so keep its results in files under /workspace.",
29
41
  inputSchema: {
30
42
  type: "object",
31
43
  properties: {
@@ -40,21 +52,25 @@ const SPEC = {
40
52
  maxLength: BASH_DESCRIPTION_MAX_CHARS,
41
53
  description: 'What this command does, in a few words (e.g. "Generate the final PDF"). Shown to the user.',
42
54
  },
43
- timeoutSeconds: {
55
+ timeout: {
44
56
  type: "integer",
45
57
  minimum: 1,
46
- description: `Seconds before the command is stopped. Default ${BASH_DEFAULT_TIMEOUT_SECONDS}; the deployment sets a maximum (7200 by default).`,
58
+ description: `Milliseconds before the command is stopped. Default ${BASH_DEFAULT_TIMEOUT_SECONDS * 1000} (in the background: the background maximum); the deployment sets a maximum (7200000 by default); a larger value is lowered to it.`,
59
+ },
60
+ run_in_background: {
61
+ type: "boolean",
62
+ description: "Set to true to run this command in the background. Use TaskOutput to read the output later.",
47
63
  },
48
64
  },
49
65
  required: ["command", "description"],
50
66
  additionalProperties: false,
51
67
  },
52
68
  };
53
- /** 本次命令的超时(秒):给定的值,或默认值(不超过上限)。 */
54
- const secondsFor = (input, max) => input.timeoutSeconds ?? Math.min(BASH_DEFAULT_TIMEOUT_SECONDS, max);
69
+ /** This command's timeout in milliseconds: the given value or the default, at most the maximum. */
70
+ const msFor = (input, maxSeconds) => Math.min(input.timeout ?? BASH_DEFAULT_TIMEOUT_SECONDS * 1000, maxSeconds * 1000);
55
71
  /**
56
72
  * 命令结果 → 给模型的文本。`budgetClamped`:运行的时限把超时收紧到了 `seconds`(Ruling M20),超时说明改为
57
- * 指向 `runtime.maxRunSeconds`,而不是让模型加大 timeoutSeconds。
73
+ * 指向 `runtime.maxRunSeconds`,而不是让模型加大 timeout。
58
74
  */
59
75
  export function formatExec(r, seconds, budgetClamped = false) {
60
76
  const parts = [];
@@ -68,7 +84,7 @@ export function formatExec(r, seconds, budgetClamped = false) {
68
84
  parts.push(`[timedOut: the command did not finish within ${seconds}s and was stopped. ${runBudgetNote(seconds)} The sandbox is still usable; split the work into shorter steps that save progress to files under /workspace, or run long jobs in the background and check on them in a later run.]`);
69
85
  }
70
86
  else if (r.timedOut) {
71
- parts.push(`[timedOut: the command did not finish within ${seconds}s and was stopped. The sandbox is still usable; pass a larger timeoutSeconds (up to the deployment maximum), or split the work into shorter steps that save progress to files under /workspace.]`);
87
+ parts.push(`[timedOut: the command did not finish within ${seconds}s and was stopped. The sandbox is still usable; pass a larger timeout in milliseconds (up to the deployment maximum), or split the work into shorter steps that save progress to files under /workspace.]`);
72
88
  }
73
89
  else if (r.exitCode === null) {
74
90
  parts.push("[The command was stopped before it finished; its effects may be partial. Check the sandbox state before retrying.]");
@@ -80,7 +96,210 @@ export function formatExec(r, seconds, budgetClamped = false) {
80
96
  }
81
97
  return parts.join("\n");
82
98
  }
83
- /** 内置工具 `sandbox_bash`:在本对话的沙箱里执行命令。 */
99
+ /** The most output the launch exec keeps (it prints only the session id, or an error). */
100
+ const LAUNCH_MAX_OUTPUT_BYTES = 64 * 1024;
101
+ /** Above about this many bytes of base64, a provider may refuse the launch exec's command. */
102
+ const LARGE_COMMAND_BASE64_BYTES = 96 * 1024;
103
+ /** Why a launch exec did not start the command, for the model and the row's reason. */
104
+ function launchFailure(r) {
105
+ if (r.timedOut)
106
+ return `the launch did not finish within ${LAUNCH_EXEC_TIMEOUT_MS / 1000}s`;
107
+ if (r.exitCode === null)
108
+ return "the launch was stopped before it finished";
109
+ const said = clip(r.stderr || (r.exitCode === 0 ? "" : r.stdout), 500).replace(/\.$/, "");
110
+ if (said !== "")
111
+ return said;
112
+ return r.exitCode === 0
113
+ ? "the launch printed no session id"
114
+ : `the launch exited with code ${r.exitCode}`;
115
+ }
116
+ /**
117
+ * `Bash` with `run_in_background` (spec 2026-11-05-background-bash-design §2.1, §4.2):
118
+ * 1. reserves the task's row (`BackgroundTaskStore.reserve`: `running`, no `sid`), refused once
119
+ * the sandbox runs `sandbox.background.maxTasks` tasks; the count and the insert are one step
120
+ * under the sandbox's launch lock;
121
+ * 2. runs one short launch exec under the call's shared lease, and in the same lease records the
122
+ * session id and the instance (`markStarted`); a command that started but could not be
123
+ * recorded is killed there (`pkill -KILL -s <sid>`), so no process runs untracked;
124
+ * 3. any failure finishes the row as `failed` with the reason (`failLaunch`);
125
+ * 4. a stop that claimed the reservation meanwhile (`markStarted` answers `stopping`) has the
126
+ * command killed at once and the row finished `stopped` (`finishStoppedLaunch`).
127
+ * The command runs in a session of its own under its own `timeout` (default and maximum
128
+ * {@link backgroundMaxSeconds}).
129
+ */
130
+ async function runInBackground(deps, ctx, input) {
131
+ const { background, sandbox } = deps;
132
+ if (!background || !sandbox.currentInstance) {
133
+ throw new ToolError("Background tasks are not available here; call Bash without run_in_background.");
134
+ }
135
+ if (ctx.runId === undefined) {
136
+ throw new ToolError("Background tasks can only be started from an agent run; call Bash without run_in_background.");
137
+ }
138
+ if (!sandbox.available)
139
+ throw noSandboxError();
140
+ const currentInstance = sandbox.currentInstance.bind(sandbox);
141
+ const cfg = deps.config.get();
142
+ const maxMs = backgroundMaxSeconds(cfg) * 1000;
143
+ const requested = input.timeout;
144
+ const seconds = Math.max(1, Math.ceil(Math.min(requested ?? maxMs, maxMs) / 1000));
145
+ const root = sandboxConversationOf(ctx);
146
+ const maxTasks = cfg.sandbox.background.maxTasks;
147
+ const id = newTaskId();
148
+ const description = input.description ?? clip(input.command, 120);
149
+ const startedAt = new Date();
150
+ const reserved = await background.reserve({
151
+ id,
152
+ orgId: ctx.orgId,
153
+ conversationId: ctx.conversationId,
154
+ sandboxConversationId: root,
155
+ runId: ctx.runId,
156
+ callId: ctx.callId,
157
+ // the run's trigger account (the pipeline passes `runs.trigger_account_id` as `accountId`)
158
+ triggerAccountId: ctx.accountId,
159
+ description,
160
+ command: input.command,
161
+ outputPath: taskOutputPath(id),
162
+ startedAt,
163
+ deadlineAt: new Date(startedAt.getTime() + seconds * 1000),
164
+ }, maxTasks);
165
+ if (!reserved) {
166
+ throw new ToolError(`${maxTasks} background tasks are already running; stop one with TaskStop or wait for one to finish, then retry`);
167
+ }
168
+ // the conversation's background panel lists the task live (a sub-agent's task shows in its
169
+ // parent's panel too): announced when it starts and when its launch ends it (failed, stopped),
170
+ // since a client may have listed the reservation as running meanwhile; a failed publish only logs
171
+ const announce = async () => {
172
+ if (!deps.eventLog.publishChanged)
173
+ return;
174
+ for (const c of new Set([ctx.conversationId, root])) {
175
+ await deps.eventLog.publishChanged(c, "background");
176
+ }
177
+ };
178
+ const large = Math.ceil(Buffer.byteLength(input.command, "utf8") / 3) * 4 > LARGE_COMMAND_BASE64_BYTES
179
+ ? " A very large command (over about 96 KB after base64) cannot be started in the background; write it to a script under /workspace and run that instead."
180
+ : "";
181
+ // set once the row has its sid: from then on the task runs and is tracked, whatever follows
182
+ let started = false;
183
+ let launch;
184
+ let notices;
185
+ try {
186
+ ({ value: launch, notices } = await inSandbox(deps, ctx, async ({ h, p, signal }) => {
187
+ // right after the launch a process may fork while it is killed: killSession repeats
188
+ const kill = (sid) => killSession(p, h, sid);
189
+ // the session id the launch wrote before printing it: the launch may have ended (aborted,
190
+ // timed out, thrown) after starting the command but before its output came back
191
+ const sidFile = async () => {
192
+ const f = await p
193
+ .readFile(h, `/workspace/${taskSidPath(id)}`, {
194
+ maxBytes: 64,
195
+ signal: AbortSignal.timeout(KILL_SESSION_TIMEOUT_MS),
196
+ })
197
+ .catch(() => null);
198
+ const v = f ? new TextDecoder().decode(f.data).trim() : "";
199
+ return SID_RE.test(v) ? v : null;
200
+ };
201
+ let r;
202
+ try {
203
+ r = await p.exec(h, {
204
+ command: launchScript(id, input.command, seconds),
205
+ cwd: "/workspace",
206
+ timeoutMs: LAUNCH_EXEC_TIMEOUT_MS,
207
+ maxOutputBytes: LAUNCH_MAX_OUTPUT_BYTES,
208
+ signal,
209
+ });
210
+ }
211
+ catch (e) {
212
+ const sid = await sidFile();
213
+ if (sid !== null)
214
+ await kill(sid);
215
+ throw e;
216
+ }
217
+ const out = r.stdout.trim();
218
+ if (r.exitCode !== 0 || !SID_RE.test(out)) {
219
+ const sid = SID_RE.test(out) ? out : await sidFile();
220
+ if (sid !== null)
221
+ await kill(sid);
222
+ return { kind: "not_started", why: launchFailure(r) };
223
+ }
224
+ const sid = out;
225
+ try {
226
+ // under the shared lease: no rebuild can come between the launch and this read
227
+ const instance = await currentInstance(root);
228
+ if (instance === null)
229
+ throw new Error("the sandbox instance could not be read");
230
+ const marked = await background.markStarted(id, {
231
+ sid,
232
+ sandboxCreatedAt: instance.createdAt,
233
+ });
234
+ if (!marked)
235
+ throw new Error("its task record was finished meanwhile");
236
+ started = true;
237
+ if (marked === "stopping") {
238
+ // a stop claimed the reservation before the command started: kill it at once
239
+ if (await kill(sid)) {
240
+ await background.finishStoppedLaunch(id);
241
+ return { kind: "stopped" };
242
+ }
243
+ // not confirmed: the task runs on and is tracked like any other (the poller checks it)
244
+ await background.releaseStop(id);
245
+ }
246
+ return { kind: "started" };
247
+ }
248
+ catch (e) {
249
+ const stopped = await kill(sid);
250
+ const why = e instanceof Error ? e.message : String(e);
251
+ return { kind: "unrecorded", why, stopped };
252
+ }
253
+ }));
254
+ }
255
+ catch (e) {
256
+ if (started) {
257
+ // the task runs and its row has its sid: an error after that (releasing the lease, say)
258
+ // does not undo the launch
259
+ launch = { kind: "started" };
260
+ notices = [];
261
+ }
262
+ else {
263
+ // no sandbox, a busy or lost lease, an abort, a provider error: nothing started (or what
264
+ // did was killed above, when its session id could be read)
265
+ const why = e instanceof Error ? e.message : String(e);
266
+ await background.failLaunch(id, `launch failed: ${why}`).catch(() => undefined);
267
+ await announce();
268
+ if (large === "" || e instanceof ToolError)
269
+ throw e;
270
+ throw new ToolError(`The background task was not started: ${clip(why, 500).replace(/\.$/, "")}. Check the sandbox, or call Bash without run_in_background.${large}`);
271
+ }
272
+ }
273
+ if (launch.kind === "not_started") {
274
+ await background.failLaunch(id, launch.why);
275
+ await announce();
276
+ throw new ToolError(`The background task was not started: ${launch.why}. Check the sandbox, or call Bash without run_in_background.${large}`);
277
+ }
278
+ if (launch.kind === "stopped") {
279
+ await announce();
280
+ throw new ToolError(`Background task ${id} ("${description}") was stopped while it was starting, so nothing runs. Call Bash again if it is still needed.`);
281
+ }
282
+ if (launch.kind === "unrecorded") {
283
+ await background.failLaunch(id, `started but not recorded: ${launch.why}`);
284
+ await announce();
285
+ if (launch.stopped) {
286
+ throw new ToolError(`The background command started but could not be recorded (${launch.why}), so it was stopped. Retry, or call Bash without run_in_background.`);
287
+ }
288
+ throw new ToolError(`The background command started but could not be recorded (${launch.why}); an attempt to stop it failed, so it ends at its timeout. Check the sandbox before you retry, or call Bash without run_in_background.`);
289
+ }
290
+ await announce();
291
+ const lowered = requested !== undefined && requested > maxMs
292
+ ? `\n[timeout ${requested} ms is over the background task maximum; it was lowered to ${maxMs} ms.]`
293
+ : "";
294
+ const shown = clip(input.command, 120);
295
+ return {
296
+ content: [{ type: "text", text: withNotices(notices, startedText(id, description) + lowered) }],
297
+ isError: false,
298
+ view: { kind: "command", description, exitCode: null, timedOut: false },
299
+ display: display(`后台运行命令:${shown}`, `Started in the background: ${shown}`),
300
+ };
301
+ }
302
+ /** 内置工具 `Bash`:在本对话的沙箱里执行命令。 */
84
303
  export function sandboxBashTool(deps) {
85
304
  return {
86
305
  spec: SPEC,
@@ -90,34 +309,45 @@ export function sandboxBashTool(deps) {
90
309
  // 命令自己的超时先到(提供方停掉命令、返回 timedOut),管线的时限再留出等锁与创建 / 恢复的余量
91
310
  timeoutMs: (input) => {
92
311
  const cfg = deps.config.get();
312
+ // a background call is only the short launch; the task's own timeout runs in the sandbox
313
+ if (input.run_in_background === true) {
314
+ return LAUNCH_EXEC_TIMEOUT_MS + sandboxBaseTimeoutMs(cfg);
315
+ }
93
316
  const max = cfg.sandbox.limits.maxCommandSeconds;
94
- return Math.min(secondsFor(input, max), max) * 1000 + sandboxBaseTimeoutMs(cfg);
317
+ return msFor(input, max) + sandboxBaseTimeoutMs(cfg);
95
318
  },
96
319
  async run(input, ctx) {
320
+ if (input.run_in_background === true) {
321
+ return runInBackground(deps, ctx, input);
322
+ }
97
323
  const { command, description } = input;
98
324
  const max = deps.config.get().sandbox.limits.maxCommandSeconds;
99
- const req = input.timeoutSeconds;
100
- if (req !== undefined && req > max) {
101
- throw new ToolError(`timeoutSeconds ${req} is over the limit of ${max} seconds; pass a value of at most ${max}.`);
102
- }
103
- const asked = secondsFor(input, max);
104
- // 运行截止前命令要自己超时(Ruling M20):在拿到沙箱之后算,等锁与恢复用掉的时间一并扣掉
105
- let seconds = asked;
325
+ const requested = input.timeout;
326
+ const maxMs = max * 1000;
327
+ const askedMs = msFor(input, max);
328
+ // The run's deadline makes the command time out itself first (Ruling M20): counted after the
329
+ // sandbox is obtained, so the time spent waiting for the lock and resuming is deducted
330
+ let ms = askedMs;
106
331
  const { value: r, notices } = await inSandbox(deps, ctx, ({ h, p, signal }) => {
107
- const left = Math.floor(runTimeLeftMs(ctx.runDeadline) / 1000);
108
- seconds = Math.max(1, Math.min(asked, left));
332
+ const left = Math.floor(runTimeLeftMs(ctx.runDeadline) / 1000) * 1000;
333
+ ms = Math.max(1000, Math.min(askedMs, left));
109
334
  return p.exec(h, {
110
335
  command,
111
336
  cwd: "/workspace",
112
- timeoutMs: seconds * 1000,
337
+ timeoutMs: ms,
113
338
  maxOutputBytes: MAX_OUTPUT_BYTES,
114
339
  signal,
115
340
  });
116
341
  });
117
- const clamped = seconds < asked;
342
+ const seconds = ms / 1000;
343
+ const clamped = ms < askedMs;
118
344
  const shown = clip(command, 120);
345
+ const lowered = requested !== undefined && requested > maxMs
346
+ ? `\n[timeout ${requested} ms is over the deployment maximum; it was lowered to ${maxMs} ms.]`
347
+ : "";
348
+ const text = formatExec(r, seconds, clamped) + lowered;
119
349
  return {
120
- content: [{ type: "text", text: withNotices(notices, formatExec(r, seconds, clamped)) }],
350
+ content: [{ type: "text", text: withNotices(notices, text) }],
121
351
  isError: r.timedOut || r.exitCode === null,
122
352
  // display only: a non-zero exit shows as failed on the row, `isError` stays as it was
123
353
  view: {
@@ -1,4 +1,7 @@
1
- import { type SandboxHandle, type SandboxProvider } from "@capybara-hq/plugin-sdk";
1
+ import { type SandboxHandle, type SandboxProvider, ToolError } from "@capybara-hq/plugin-sdk";
2
+ import type { BackgroundTaskFinisher } from "../../../background/finish.js";
3
+ import type { BackgroundTaskStopper } from "../../../background/stop.js";
4
+ import type { BackgroundTaskStore } from "../../../background/store.js";
2
5
  import type { KernelConfig } from "../../../config/schema.js";
3
6
  import type { AttachmentsService } from "../../../conversations/attachments.service.js";
4
7
  import type { EventLog } from "../../../conversations/event-log.js";
@@ -8,12 +11,26 @@ import type { StorageService } from "../../../storage/storage.service.js";
8
11
  import type { BuiltinCallContext } from "../../registry.js";
9
12
  /** 沙箱工具的依赖(都按调用时取,多副本下无进程内状态)。 */
10
13
  export interface SandboxToolDeps {
11
- /** `pathLease` may be absent (test stubs): the kernel's own file writes then take no path lease. */
12
- sandbox: Pick<SandboxService, "available" | "withSandbox"> & Partial<Pick<SandboxService, "pathLease">>;
14
+ /**
15
+ * `pathLease` may be absent (test stubs): the kernel's own file writes then take no path lease.
16
+ * `currentInstance` too: a background `Bash` launch records the instance it started in; and
17
+ * `ifRunning`, which `TaskOutput` reads a task through.
18
+ */
19
+ sandbox: Pick<SandboxService, "available" | "withSandbox"> & Partial<Pick<SandboxService, "pathLease" | "currentInstance" | "ifRunning">>;
20
+ /** Background `Bash` tasks; absent in test stubs, where `run_in_background` is refused. */
21
+ background?: Pick<BackgroundTaskStore, "reserve" | "markStarted" | "failLaunch" | "finishStoppedLaunch" | "releaseStop" | "get" | "advanceOffset">;
22
+ /**
23
+ * What `TaskOutput` and `TaskStop` finish and stop tasks with; absent in test stubs, where both
24
+ * answer that background tasks are not available.
25
+ */
26
+ tasks?: {
27
+ finisher: Pick<BackgroundTaskFinisher, "finish">;
28
+ stopper: Pick<BackgroundTaskStopper, "stop">;
29
+ };
13
30
  attachments: Pick<AttachmentsService, "listForConversation" | "openForConversation">;
14
31
  storage: Pick<StorageService, "kernel" | "available">;
15
- /** `publishChanged`: `sandbox_deliver` announces `changed: files` (UI refresh B). */
16
- eventLog: Pick<EventLog, "append"> & Partial<Pick<EventLog, "publishChanged">>;
32
+ /** `publishChanged`: `Deliver` announces `changed: files` (UI refresh B). */
33
+ eventLog: Pick<EventLog, "append" | "latestFileState"> & Partial<Pick<EventLog, "publishChanged">>;
17
34
  config: Pick<KernelConfigService, "get">;
18
35
  }
19
36
  /** 回调拿到的沙箱:句柄、提供方与「调用 signal + 锁丢失」合并后的 signal(每个提供方调用都传它)。 */
@@ -31,6 +48,8 @@ export declare const SANDBOX_STARTED_NOTICE = "Sandbox started for this conversa
31
48
  export declare const SANDBOX_RECREATED_NOTICE = "Sandbox was recreated; previous files are gone.";
32
49
  /** 没有沙箱提供方时「工具不可用」的原因(进提醒,内核在后面补句号):与调用时的错误同一句话。 */
33
50
  export declare const SANDBOX_UNAVAILABLE_REASON: string;
51
+ /** The error of a sandbox call in a deployment without a sandbox provider. */
52
+ export declare function noSandboxError(): ToolError;
34
53
  /** 沙箱里附件所在目录。 */
35
54
  export declare const ATTACHMENTS_DIR = "/workspace/attachments";
36
55
  /** 已同步附件的标记文件:`{ <附件 id>: <文件名> }`。 */
@@ -80,7 +99,7 @@ export declare function clip(s: string, max: number): string;
80
99
  export declare function withNotices(notices: readonly string[], text: string): string;
81
100
  /** 校验沙箱内路径(`validateSandboxPath`);不合法抛给模型可执行的错误。 */
82
101
  export declare function checkPath(path: string, field?: string): string;
83
- /** 写入类工具的路径:`/skills` 是技能文件的只读副本。 */
102
+ /** A writing tool's `file_path`: `/skills` is the read-only copy of the skill files. */
84
103
  export declare function checkWritable(path: string): string;
85
104
  /** POSIX shell 单引号转义。 */
86
105
  export declare function shellQuote(s: string): string;
@@ -93,6 +112,8 @@ export declare function assignAttachmentNames(items: readonly {
93
112
  id: string;
94
113
  name: string;
95
114
  }[], used: ReadonlySet<string>): Map<string, string>;
115
+ /** 沙箱服务的错误 → 给模型的可执行说明;其余原样抛出(管线按「工具失败」处理)。 */
116
+ export declare function mapSandboxError(e: unknown): unknown;
96
117
  /**
97
118
  * 在本对话的沙箱上执行 `fn`(`SandboxService.withSandbox`:懒创建、恢复、重建):先同步附件,
98
119
  * 再执行;返回结果与要加在结果开头的状态说明(首次使用 / 重建 / 新复制的附件)。
@@ -112,6 +133,8 @@ export declare const sandboxConversationOf: (ctx: BuiltinCallContext) => string;
112
133
  */
113
134
  export declare function withPathLease<T>(deps: Pick<SandboxToolDeps, "sandbox">, ctx: BuiltinCallContext, path: string, signal: AbortSignal, fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
114
135
  /** 本部署是否有沙箱:没有时返回「不可用」原因(注册表据此把沙箱工具报为不可用)。 */
115
- export declare const unavailableWhenNoSandbox: (deps: Pick<SandboxToolDeps, "sandbox">) => () => string | null;
136
+ export declare const unavailableWhenNoSandbox: (deps: Pick<SandboxToolDeps, "sandbox">) => (_ctx?: {
137
+ orgId: string;
138
+ }) => string | null;
116
139
  /** 读出的字节是否像二进制(开头 8 KB 里有 NUL)。 */
117
140
  export declare function looksBinary(data: Uint8Array): boolean;
@@ -12,6 +12,10 @@ const BUSY_TEXT = "The sandbox is busy with another command in this conversation
12
12
  const NO_SANDBOX_TEXT = "No sandbox is configured in this deployment; ask an administrator to install a sandbox plugin, or do the task without the sandbox.";
13
13
  /** 没有沙箱提供方时「工具不可用」的原因(进提醒,内核在后面补句号):与调用时的错误同一句话。 */
14
14
  export const SANDBOX_UNAVAILABLE_REASON = NO_SANDBOX_TEXT.slice(0, -1);
15
+ /** The error of a sandbox call in a deployment without a sandbox provider. */
16
+ export function noSandboxError() {
17
+ return new ToolError(NO_SANDBOX_TEXT);
18
+ }
15
19
  const PATH_BUSY_TEXT = "Another sub-agent is writing this file; wait a moment and retry.";
16
20
  const LOCK_LOST_TEXT = "The sandbox operation was interrupted because the sandbox lock was lost; check the sandbox state and retry.";
17
21
  /** 沙箱里附件所在目录。 */
@@ -78,9 +82,9 @@ export function checkPath(path, field = "path") {
78
82
  }
79
83
  return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
80
84
  }
81
- /** 写入类工具的路径:`/skills` 是技能文件的只读副本。 */
85
+ /** A writing tool's `file_path`: `/skills` is the read-only copy of the skill files. */
82
86
  export function checkWritable(path) {
83
- const p = checkPath(path);
87
+ const p = checkPath(path, "file_path");
84
88
  if (p === "/skills" || p.startsWith("/skills/")) {
85
89
  throw new ToolError(`Path ${clip(p, 200)} is read-only (/skills holds skill files); pass a path under /workspace or /tmp instead.`);
86
90
  }
@@ -235,7 +239,7 @@ async function copyAttachments(deps, ctx, s, reset, conv, list) {
235
239
  return { copied, failures };
236
240
  }
237
241
  /** 沙箱服务的错误 → 给模型的可执行说明;其余原样抛出(管线按「工具失败」处理)。 */
238
- function mapError(e) {
242
+ export function mapSandboxError(e) {
239
243
  if (e instanceof ApiError) {
240
244
  if (e.code === "sandbox.busy")
241
245
  return new ToolError(BUSY_TEXT);
@@ -276,7 +280,7 @@ export async function inSandbox(deps, ctx, fn) {
276
280
  }, ctx.signal);
277
281
  }
278
282
  catch (e) {
279
- throw mapError(e);
283
+ throw mapSandboxError(e);
280
284
  }
281
285
  }
282
286
  /** The conversation whose sandbox a call uses (the parent's for a sub-agent). */
@@ -299,7 +303,7 @@ export async function withPathLease(deps, ctx, path, signal, fn) {
299
303
  }
300
304
  }
301
305
  /** 本部署是否有沙箱:没有时返回「不可用」原因(注册表据此把沙箱工具报为不可用)。 */
302
- export const unavailableWhenNoSandbox = (deps) => () => deps.sandbox.available ? null : SANDBOX_UNAVAILABLE_REASON;
306
+ export const unavailableWhenNoSandbox = (deps) => (_ctx) => deps.sandbox.available ? null : SANDBOX_UNAVAILABLE_REASON;
303
307
  /** 读出的字节是否像二进制(开头 8 KB 里有 NUL)。 */
304
308
  export function looksBinary(data) {
305
309
  return data.subarray(0, 8192).includes(0);
@@ -1,5 +1,5 @@
1
1
  import type { BuiltinTool } from "../../registry.js";
2
2
  import { type SandboxToolDeps } from "./common.js";
3
3
  export declare function contentTypeFor(name: string): string;
4
- /** 内置工具 `sandbox_deliver`:把沙箱里的文件存进对象存储,写 `artifact_created`,用户可下载。 */
4
+ /** 内置工具 `Deliver`:把沙箱里的文件存进对象存储,写 `artifact_created`,用户可下载。 */
5
5
  export declare function sandboxDeliverTool(deps: SandboxToolDeps): BuiltinTool;
@@ -4,18 +4,18 @@ import { isValidArtifactName } from "../../../conversations/events.js";
4
4
  import { ApiError } from "../../../errors.js";
5
5
  import { checkPath, clip, display, inSandbox, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, } from "./common.js";
6
6
  const SPEC = {
7
- name: "sandbox_deliver",
7
+ name: "Deliver",
8
8
  // Display only (the tool row; not in the fingerprint, never sent to the model).
9
- keyArgs: ["path"],
9
+ keyArgs: ["file_path"],
10
10
  description: "Deliver a file from this conversation's sandbox to the user as a downloadable artifact. Use " +
11
11
  "it when the user should receive a file you produced (a report, chart, spreadsheet, archive " +
12
12
  "and so on); files that stay in the sandbox are not visible to the user. Delivers one file " +
13
- "per call; archive a directory first (e.g. zip -r with sandbox_bash). The file size is " +
13
+ "per call; archive a directory first (e.g. tar -czf with Bash). The file size is " +
14
14
  "limited (100 MB by default). Do not use it for intermediate files the user does not need.",
15
15
  inputSchema: {
16
16
  type: "object",
17
17
  properties: {
18
- path: {
18
+ file_path: {
19
19
  type: "string",
20
20
  minLength: 1,
21
21
  description: 'Absolute path of the file in the sandbox, e.g. "/workspace/out/report.pdf".',
@@ -24,10 +24,10 @@ const SPEC = {
24
24
  type: "string",
25
25
  minLength: 1,
26
26
  maxLength: MEDIA_NAME_MAX_CHARS,
27
- description: 'File name shown to the user, e.g. "Q3 report.pdf". Default: the file name in path.',
27
+ description: 'File name shown to the user, e.g. "Q3 report.pdf". Default: the file name in file_path.',
28
28
  },
29
29
  },
30
- required: ["path"],
30
+ required: ["file_path"],
31
31
  additionalProperties: false,
32
32
  },
33
33
  };
@@ -71,9 +71,9 @@ export function contentTypeFor(name) {
71
71
  const ext = dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
72
72
  return CONTENT_TYPES[ext] ?? "application/octet-stream";
73
73
  }
74
- const notFound = (path) => `File not found: ${clip(path, 200)}. Check the path with sandbox_glob.`;
75
- const isDirectory = (path) => `${clip(path, 200)} is a directory; deliver one file, e.g. archive the directory first with sandbox_bash (zip -r) and retry.`;
76
- const tooLarge = (path, size, max) => `File ${clip(path, 200)} is ${size} bytes, over the delivery limit of ${max} bytes; compress or split it with sandbox_bash and retry.`;
74
+ const notFound = (path) => `File not found: ${clip(path, 200)}. Check the path with Glob.`;
75
+ const isDirectory = (path) => `${clip(path, 200)} is a directory; deliver one file, e.g. archive the directory first with Bash (tar -czf) and retry.`;
76
+ const tooLarge = (path, size, max) => `File ${clip(path, 200)} is ${size} bytes, over the delivery limit of ${max} bytes; compress or split it with Bash and retry.`;
77
77
  const BAD_NAME = "name must not contain control characters, / or \\; pass a plain file name such as report.pdf.";
78
78
  /** 默认产物名:路径的文件名部分,过长时保留末尾(扩展名)截到 255 个 UTF-16 单元以内。 */
79
79
  function defaultName(path) {
@@ -90,19 +90,19 @@ function defaultName(path) {
90
90
  const NO_STORAGE = "File storage is not configured in this deployment, so files cannot be delivered; ask an administrator, or provide the result in your reply instead.";
91
91
  /** 「工具不可用」提醒里的原因(内核在后面补句号)。 */
92
92
  const NO_STORAGE_REASON = NO_STORAGE.slice(0, -1);
93
- /** 内置工具 `sandbox_deliver`:把沙箱里的文件存进对象存储,写 `artifact_created`,用户可下载。 */
93
+ /** 内置工具 `Deliver`:把沙箱里的文件存进对象存储,写 `artifact_created`,用户可下载。 */
94
94
  export function sandboxDeliverTool(deps) {
95
95
  return {
96
96
  spec: SPEC,
97
97
  defaultPolicy: "allow",
98
98
  // 没有沙箱或没有对象存储都交付不了(先报沙箱的原因)
99
- unavailable: () => unavailableWhenNoSandbox(deps)() ?? (deps.storage.available ? null : NO_STORAGE_REASON),
99
+ unavailable: (ctx) => unavailableWhenNoSandbox(deps)(ctx) ?? (deps.storage.available ? null : NO_STORAGE_REASON),
100
100
  timeoutMs: () => 2 * sandboxBaseTimeoutMs(deps.config.get()),
101
101
  async run(input, ctx) {
102
102
  // 存储在调用前就核对:不为一个交付不了的文件去 stat / 读沙箱
103
103
  if (!deps.storage.available)
104
104
  throw new ToolError(NO_STORAGE);
105
- const path = checkPath(input.path);
105
+ const path = checkPath(input.file_path, "file_path");
106
106
  const given = input.name?.trim();
107
107
  // 与 artifact_created 的 schema 同一规则,在上传前检查(不合法的名字不会在写事件时才失败)
108
108
  if (given !== undefined && (!isValidArtifactName(given) || /[/\\]/.test(given))) {
@@ -1,4 +1,4 @@
1
1
  import type { BuiltinTool } from "../../registry.js";
2
2
  import { type SandboxToolDeps } from "./common.js";
3
- /** 内置工具 `sandbox_edit`:按精确文本替换。 */
3
+ /** 内置工具 `Edit`:按精确文本替换。 */
4
4
  export declare function sandboxEditTool(deps: SandboxToolDeps): BuiltinTool;