@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22

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 (242) hide show
  1. package/dist/agents/admin-agents.d.ts +16 -3
  2. package/dist/agents/admin-agents.js +65 -7
  3. package/dist/agents/agents.schemas.d.ts +24 -6
  4. package/dist/agents/agents.service.d.ts +4 -3
  5. package/dist/agents/agents.service.js +39 -26
  6. package/dist/agents/definition.d.ts +44 -6
  7. package/dist/agents/definition.js +86 -18
  8. package/dist/agents/from-template.d.ts +6 -3
  9. package/dist/agents/from-template.js +57 -44
  10. package/dist/agents/org-templates.js +97 -41
  11. package/dist/agents/templates.d.ts +6 -0
  12. package/dist/agents/templates.js +15 -1
  13. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  14. package/dist/catalog/admin-catalog.controller.js +2 -6
  15. package/dist/catalog/catalog.module.js +3 -0
  16. package/dist/catalog/claude/report.d.ts +9 -2
  17. package/dist/catalog/claude/report.js +1 -0
  18. package/dist/catalog/claude/translate.js +1 -1
  19. package/dist/catalog/component-promotion.js +1 -2
  20. package/dist/catalog/component-state.d.ts +13 -1
  21. package/dist/catalog/component-state.js +14 -5
  22. package/dist/catalog/marketplaces.service.d.ts +1 -1
  23. package/dist/catalog/marketplaces.service.js +2 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  25. package/dist/catalog/org-catalog.controller.js +26 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  27. package/dist/catalog/org-catalog.schemas.js +34 -0
  28. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  29. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  30. package/dist/catalog/org-plugin-uploads.js +0 -3
  31. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  32. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  33. package/dist/catalog/platform-components.d.ts +0 -1
  34. package/dist/catalog/platform-components.js +3 -8
  35. package/dist/catalog/plugin-import.d.ts +0 -8
  36. package/dist/catalog/plugin-import.js +4 -35
  37. package/dist/catalog/plugin-install.d.ts +2 -2
  38. package/dist/catalog/plugin-install.js +2 -2
  39. package/dist/catalog/plugin-package.d.ts +7 -0
  40. package/dist/catalog/plugin-package.js +27 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  42. package/dist/catalog/plugin-upgrade.js +5 -23
  43. package/dist/catalog/requests.service.d.ts +66 -10
  44. package/dist/catalog/requests.service.js +196 -15
  45. package/dist/channels/actions.d.ts +2 -0
  46. package/dist/channels/actions.js +19 -4
  47. package/dist/channels/cards.d.ts +16 -2
  48. package/dist/channels/cards.js +40 -7
  49. package/dist/channels/channels.module.js +3 -0
  50. package/dist/channels/outbound.d.ts +22 -1
  51. package/dist/channels/outbound.js +116 -7
  52. package/dist/channels/phrases.d.ts +25 -0
  53. package/dist/channels/phrases.js +19 -0
  54. package/dist/channels/threads.d.ts +12 -0
  55. package/dist/channels/threads.js +34 -0
  56. package/dist/config/schema.d.ts +23 -1
  57. package/dist/config/schema.js +105 -5
  58. package/dist/conversations/conversations.service.d.ts +42 -9
  59. package/dist/conversations/conversations.service.js +57 -25
  60. package/dist/conversations/events.d.ts +106 -11
  61. package/dist/conversations/events.js +121 -18
  62. package/dist/conversations/message-context.d.ts +6 -3
  63. package/dist/conversations/message-context.js +14 -10
  64. package/dist/conversations/queue.d.ts +15 -2
  65. package/dist/conversations/queue.js +22 -3
  66. package/dist/conversations/queue.service.d.ts +12 -6
  67. package/dist/conversations/queue.service.js +36 -13
  68. package/dist/db/kernel-schema.d.ts +105 -3
  69. package/dist/devices/conversation-devices.d.ts +8 -1
  70. package/dist/devices/conversation-devices.js +8 -0
  71. package/dist/devices/devices.controller.d.ts +21 -4
  72. package/dist/devices/devices.controller.js +46 -8
  73. package/dist/devices/devices.schemas.d.ts +9 -1
  74. package/dist/devices/devices.schemas.js +11 -3
  75. package/dist/devices/devices.service.d.ts +176 -4
  76. package/dist/devices/devices.service.js +341 -6
  77. package/dist/devices/dto.d.ts +56 -3
  78. package/dist/devices/dto.js +51 -1
  79. package/dist/devices/public-suffix.d.ts +6 -0
  80. package/dist/devices/public-suffix.js +79 -0
  81. package/dist/errors.d.ts +46 -5
  82. package/dist/errors.js +46 -5
  83. package/dist/evals/reconstruct.d.ts +18 -13
  84. package/dist/evals/reconstruct.js +50 -36
  85. package/dist/evals/recorded-tools.d.ts +7 -7
  86. package/dist/evals/recorded-tools.js +4 -3
  87. package/dist/evals/trajectory.d.ts +6 -1
  88. package/dist/evals/trajectory.js +7 -0
  89. package/dist/extensions/package.js +2 -2
  90. package/dist/http/api.module.js +5 -0
  91. package/dist/inbox/inbox.module.js +2 -1
  92. package/dist/inbox/inbox.service.d.ts +19 -4
  93. package/dist/inbox/inbox.service.js +14 -3
  94. package/dist/infra/config/kernel-config.service.js +11 -0
  95. package/dist/infra/events/kernel-events.d.ts +2 -1
  96. package/dist/local-devices/device-policy.service.d.ts +65 -0
  97. package/dist/local-devices/device-policy.service.js +217 -0
  98. package/dist/local-devices/local-device-gateway.js +4 -1
  99. package/dist/local-devices/local-device-provider.d.ts +40 -3
  100. package/dist/local-devices/local-device-provider.js +109 -7
  101. package/dist/local-devices/local-devices.module.d.ts +2 -1
  102. package/dist/local-devices/local-devices.module.js +19 -3
  103. package/dist/local-devices/machines.d.ts +14 -3
  104. package/dist/local-devices/machines.js +41 -3
  105. package/dist/local-devices/network-grants.d.ts +104 -0
  106. package/dist/local-devices/network-grants.js +288 -0
  107. package/dist/local-devices/policy-summary.d.ts +20 -0
  108. package/dist/local-devices/policy-summary.js +63 -0
  109. package/dist/orgs/orgs.module.js +4 -10
  110. package/dist/orgs/roles.d.ts +1 -1
  111. package/dist/orgs/roles.js +4 -0
  112. package/dist/orgs/settings.controller.js +2 -1
  113. package/dist/orgs/settings.service.d.ts +164 -12
  114. package/dist/orgs/settings.service.js +212 -24
  115. package/dist/platform/exclusions.js +11 -1
  116. package/dist/platform/operation.js +4 -0
  117. package/dist/platform/ops/admin-items.ops.js +1 -1
  118. package/dist/platform/ops/agent-skills.ops.js +2 -2
  119. package/dist/platform/ops/agent-templates.ops.js +2 -2
  120. package/dist/platform/ops/agents.ops.js +11 -15
  121. package/dist/platform/ops/devices.ops.js +3 -1
  122. package/dist/platform/ops/inbox.ops.js +19 -0
  123. package/dist/platform/ops/org-settings.ops.js +14 -5
  124. package/dist/platform/ops/platform.ops.d.ts +9 -1
  125. package/dist/platform/ops/platform.ops.js +34 -12
  126. package/dist/platform/ops/requests.ops.js +3 -2
  127. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  128. package/dist/platform/ops/schedules.ops.js +470 -0
  129. package/dist/platform/ops/skills.ops.js +3 -3
  130. package/dist/platform/platform-ops.module.d.ts +2 -1
  131. package/dist/platform/platform-ops.module.js +5 -0
  132. package/dist/platform/route-schemas.js +3 -0
  133. package/dist/roles/worker.module.js +3 -0
  134. package/dist/runtime/context/build.js +13 -4
  135. package/dist/runtime/context/compaction.js +6 -3
  136. package/dist/runtime/context/device-paths.d.ts +43 -21
  137. package/dist/runtime/context/device-paths.js +104 -21
  138. package/dist/runtime/context/run-context.d.ts +9 -0
  139. package/dist/runtime/context/run-context.js +8 -0
  140. package/dist/runtime/context/skills.d.ts +14 -1
  141. package/dist/runtime/context/skills.js +36 -5
  142. package/dist/runtime/maintenance.d.ts +4 -2
  143. package/dist/runtime/maintenance.js +16 -5
  144. package/dist/runtime/run-finish.d.ts +6 -1
  145. package/dist/runtime/run-finish.js +16 -2
  146. package/dist/runtime/run-queue.js +21 -2
  147. package/dist/runtime/run-worker.js +14 -33
  148. package/dist/runtime/runtime.module.js +3 -0
  149. package/dist/runtime/subagents/definition.d.ts +32 -14
  150. package/dist/runtime/subagents/definition.js +106 -109
  151. package/dist/runtime/subagents/dispatch.js +4 -3
  152. package/dist/runtime/subagents/preamble.d.ts +2 -1
  153. package/dist/runtime/subagents/preamble.js +2 -1
  154. package/dist/runtime/waits.js +14 -4
  155. package/dist/sandbox/sandbox.service.d.ts +120 -10
  156. package/dist/sandbox/sandbox.service.js +335 -28
  157. package/dist/sandbox/tiers.d.ts +86 -0
  158. package/dist/sandbox/tiers.js +96 -0
  159. package/dist/schedules/effective-settings.d.ts +16 -0
  160. package/dist/schedules/effective-settings.js +17 -0
  161. package/dist/schedules/errors.d.ts +8 -0
  162. package/dist/schedules/errors.js +8 -0
  163. package/dist/schedules/fire.d.ts +118 -0
  164. package/dist/schedules/fire.js +625 -0
  165. package/dist/schedules/grant-rules.d.ts +94 -0
  166. package/dist/schedules/grant-rules.js +126 -0
  167. package/dist/schedules/job-ids.d.ts +32 -0
  168. package/dist/schedules/job-ids.js +27 -0
  169. package/dist/schedules/pause.d.ts +9 -0
  170. package/dist/schedules/pause.js +33 -0
  171. package/dist/schedules/run-tracker.d.ts +81 -0
  172. package/dist/schedules/run-tracker.js +333 -0
  173. package/dist/schedules/scan.job.d.ts +64 -0
  174. package/dist/schedules/scan.job.js +303 -0
  175. package/dist/schedules/schedule-math.d.ts +31 -0
  176. package/dist/schedules/schedule-math.js +223 -0
  177. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  178. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  179. package/dist/schedules/schedules.controller.d.ts +68 -0
  180. package/dist/schedules/schedules.controller.js +242 -0
  181. package/dist/schedules/schedules.module.d.ts +16 -0
  182. package/dist/schedules/schedules.module.js +65 -0
  183. package/dist/schedules/schedules.schemas.d.ts +316 -0
  184. package/dist/schedules/schedules.schemas.js +186 -0
  185. package/dist/schedules/store.d.ts +81 -0
  186. package/dist/schedules/store.js +390 -0
  187. package/dist/schedules/store.module.d.ts +6 -0
  188. package/dist/schedules/store.module.js +21 -0
  189. package/dist/schedules/types.d.ts +108 -0
  190. package/dist/schedules/types.js +1 -0
  191. package/dist/schedules/unattended.d.ts +117 -0
  192. package/dist/schedules/unattended.js +228 -0
  193. package/dist/setup/oauth-token-probe.js +8 -4
  194. package/dist/skills/agent-skills.service.d.ts +4 -8
  195. package/dist/skills/agent-skills.service.js +18 -53
  196. package/dist/skills/catalog.d.ts +65 -62
  197. package/dist/skills/catalog.js +109 -206
  198. package/dist/skills/conversation-catalog.d.ts +3 -3
  199. package/dist/skills/conversation-catalog.js +5 -12
  200. package/dist/skills/invoke.d.ts +2 -2
  201. package/dist/skills/skills.controller.d.ts +3 -1
  202. package/dist/skills/skills.service.d.ts +38 -37
  203. package/dist/skills/skills.service.js +168 -107
  204. package/dist/skills/usable.d.ts +57 -8
  205. package/dist/skills/usable.js +172 -38
  206. package/dist/tools/approvals.controller.d.ts +4 -2
  207. package/dist/tools/approvals.controller.js +17 -2
  208. package/dist/tools/approvals.schemas.d.ts +1 -0
  209. package/dist/tools/approvals.schemas.js +3 -1
  210. package/dist/tools/approvals.service.d.ts +182 -7
  211. package/dist/tools/approvals.service.js +597 -13
  212. package/dist/tools/builtin-names.d.ts +25 -4
  213. package/dist/tools/builtin-names.js +45 -4
  214. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  215. package/dist/tools/builtins/builtin-tools.js +23 -2
  216. package/dist/tools/builtins/sandbox/bash.js +16 -7
  217. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  218. package/dist/tools/builtins/sandbox/common.js +33 -10
  219. package/dist/tools/builtins/sandbox/edit.js +7 -3
  220. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  221. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  222. package/dist/tools/builtins/sandbox/write.js +8 -4
  223. package/dist/tools/builtins/schedules.d.ts +107 -0
  224. package/dist/tools/builtins/schedules.js +789 -0
  225. package/dist/tools/elicitation.d.ts +2 -0
  226. package/dist/tools/elicitation.js +1 -1
  227. package/dist/tools/fixed-ask.d.ts +7 -0
  228. package/dist/tools/fixed-ask.js +9 -0
  229. package/dist/tools/network-asks.d.ts +109 -0
  230. package/dist/tools/network-asks.js +221 -0
  231. package/dist/tools/pipeline.d.ts +53 -6
  232. package/dist/tools/pipeline.js +202 -13
  233. package/dist/tools/policy.d.ts +21 -8
  234. package/dist/tools/policy.js +63 -9
  235. package/dist/tools/registry.d.ts +10 -1
  236. package/dist/tools/tools.module.js +12 -0
  237. package/package.json +2 -2
  238. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  239. package/src/migrations/0114_network_asks.sql +23 -0
  240. package/src/migrations/0115_network_requests.sql +28 -0
  241. package/src/migrations/0116_skills_default_on.sql +37 -0
  242. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -27,6 +27,10 @@ export declare const BUILTIN: {
27
27
  readonly WebFetch: "WebFetch";
28
28
  readonly Platform: "Platform";
29
29
  readonly CurrentTime: "CurrentTime";
30
+ readonly ScheduleCreate: "ScheduleCreate";
31
+ readonly ScheduleUpdate: "ScheduleUpdate";
32
+ readonly ScheduleList: "ScheduleList";
33
+ readonly ScheduleDelete: "ScheduleDelete";
30
34
  };
31
35
  export type BuiltinName = (typeof BUILTIN)[keyof typeof BUILTIN];
32
36
  /**
@@ -47,6 +51,11 @@ export declare const BASH_COMPANION_TOOLS: readonly BuiltinName[];
47
51
  * already went through `Bash`'s policy (background Bash spec §4.5).
48
52
  */
49
53
  export declare const SANDBOX_ASK_TOOL_NAMES: ReadonlySet<string>;
54
+ /**
55
+ * The builtins that manage the person's scheduled tasks (scheduled tasks spec §11): create and
56
+ * update are fixed asks ({@link R3_FIXED_ASK_TOOL_KEYS}), list and delete only read or narrow.
57
+ */
58
+ export declare const SCHEDULE_TOOL_NAMES: readonly BuiltinName[];
50
59
  /**
51
60
  * Every builtin name. Saving an agent checks turned-off builtins against this list, not against
52
61
  * what is registered now, so the order the implementations land in never affects a saved
@@ -54,7 +63,8 @@ export declare const SANDBOX_ASK_TOOL_NAMES: ReadonlySet<string>;
54
63
  * by the registry; `Agent` dispatches sub-agents and its description is built per agent;
55
64
  * `SendMessage` and `SubagentHandback` are added by the registry ({@link SUBAGENT_MESSAGE_TOOLS}).
56
65
  * `Platform` calls the platform's own operations as the run's person (Platform tool spec).
57
- * `CurrentTime` reads the clock (the run context carries only the date).
66
+ * `CurrentTime` reads the clock (the run context carries only the date). The schedule tools
67
+ * manage the person's scheduled tasks ({@link SCHEDULE_TOOL_NAMES}).
58
68
  */
59
69
  export declare const BUILTIN_TOOL_NAMES: readonly BuiltinName[];
60
70
  /**
@@ -80,12 +90,23 @@ export declare const SUBAGENT_HANDBACK: "SubagentHandback";
80
90
  * `SubagentHandback` to a sub-agent. A definition's ref to either is ignored.
81
91
  */
82
92
  export declare const SUBAGENT_MESSAGE_TOOLS: ReadonlySet<string>;
93
+ /**
94
+ /**
95
+ * Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
96
+ * `ask` and remembered grants never let them through. `ScheduleCreate` and `ScheduleUpdate` set
97
+ * what a task runs unattended, so a person approves every task an agent creates or widens
98
+ * (scheduled tasks spec §11), never covered by a task grant and refused in an unattended run in
99
+ * `fail` mode. Connector and extension tools express the same rule with
100
+ * `annotations.requiresUserInteraction`.
101
+ */
102
+ export declare const R3_FIXED_ASK_TOOL_KEYS: ReadonlySet<string>;
83
103
  /**
84
104
  * Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
85
- * not see the child) and `Deliver` (delivering files is the parent's job). `Agent`
86
- * is removed only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
105
+ * not see the child), `Deliver` (delivering files is the parent's job) and the schedule tools (a
106
+ * person manages tasks from the conversation they see, never from a child). `Agent` is removed
107
+ * only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
87
108
  */
88
- export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver"];
109
+ export declare const CHILD_REMOVED_TOOLS: readonly ["AskUserQuestion", "Deliver", "ScheduleCreate", "ScheduleUpdate", "ScheduleList", "ScheduleDelete"];
89
110
  /**
90
111
  * The builtins every agent has unless it turns them off (tools default-on spec §4), in this
91
112
  * order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
@@ -27,6 +27,10 @@ export const BUILTIN = {
27
27
  WebFetch: "WebFetch",
28
28
  Platform: "Platform",
29
29
  CurrentTime: "CurrentTime",
30
+ ScheduleCreate: "ScheduleCreate",
31
+ ScheduleUpdate: "ScheduleUpdate",
32
+ ScheduleList: "ScheduleList",
33
+ ScheduleDelete: "ScheduleDelete",
30
34
  };
31
35
  /**
32
36
  * The builtin tools that run in the conversation's sandbox. `TaskOutput` and `TaskStop` read and
@@ -61,6 +65,16 @@ export const SANDBOX_ASK_TOOL_NAMES = new Set([
61
65
  BUILTIN.Edit,
62
66
  BUILTIN.Deliver,
63
67
  ]);
68
+ /**
69
+ * The builtins that manage the person's scheduled tasks (scheduled tasks spec §11): create and
70
+ * update are fixed asks ({@link R3_FIXED_ASK_TOOL_KEYS}), list and delete only read or narrow.
71
+ */
72
+ export const SCHEDULE_TOOL_NAMES = [
73
+ BUILTIN.ScheduleCreate,
74
+ BUILTIN.ScheduleUpdate,
75
+ BUILTIN.ScheduleList,
76
+ BUILTIN.ScheduleDelete,
77
+ ];
64
78
  /**
65
79
  * Every builtin name. Saving an agent checks turned-off builtins against this list, not against
66
80
  * what is registered now, so the order the implementations land in never affects a saved
@@ -68,7 +82,8 @@ export const SANDBOX_ASK_TOOL_NAMES = new Set([
68
82
  * by the registry; `Agent` dispatches sub-agents and its description is built per agent;
69
83
  * `SendMessage` and `SubagentHandback` are added by the registry ({@link SUBAGENT_MESSAGE_TOOLS}).
70
84
  * `Platform` calls the platform's own operations as the run's person (Platform tool spec).
71
- * `CurrentTime` reads the clock (the run context carries only the date).
85
+ * `CurrentTime` reads the clock (the run context carries only the date). The schedule tools
86
+ * manage the person's scheduled tasks ({@link SCHEDULE_TOOL_NAMES}).
72
87
  */
73
88
  export const BUILTIN_TOOL_NAMES = [
74
89
  BUILTIN.AskUserQuestion,
@@ -82,6 +97,7 @@ export const BUILTIN_TOOL_NAMES = [
82
97
  ...WEB_TOOL_NAMES,
83
98
  BUILTIN.Platform,
84
99
  BUILTIN.CurrentTime,
100
+ ...SCHEDULE_TOOL_NAMES,
85
101
  ];
86
102
  const BUILTIN_NAME_SET = new Set(Object.values(BUILTIN));
87
103
  /**
@@ -112,12 +128,33 @@ export const SUBAGENT_MESSAGE_TOOLS = new Set([
112
128
  SEND_MESSAGE,
113
129
  SUBAGENT_HANDBACK,
114
130
  ]);
131
+ /**
132
+ /**
133
+ * Tool keys on the spec 3.6 R3 fixed list ("allowed is not confirmed"): their policy is at least
134
+ * `ask` and remembered grants never let them through. `ScheduleCreate` and `ScheduleUpdate` set
135
+ * what a task runs unattended, so a person approves every task an agent creates or widens
136
+ * (scheduled tasks spec §11), never covered by a task grant and refused in an unattended run in
137
+ * `fail` mode. Connector and extension tools express the same rule with
138
+ * `annotations.requiresUserInteraction`.
139
+ */
140
+ export const R3_FIXED_ASK_TOOL_KEYS = new Set([
141
+ BUILTIN.ScheduleCreate,
142
+ BUILTIN.ScheduleUpdate,
143
+ ]);
115
144
  /**
116
145
  * Tools a sub-agent never gets (sub-agents spec §5.3, §5.5): `AskUserQuestion` (the person does
117
- * not see the child) and `Deliver` (delivering files is the parent's job). `Agent`
118
- * is removed only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
146
+ * not see the child), `Deliver` (delivering files is the parent's job) and the schedule tools (a
147
+ * person manages tasks from the conversation they see, never from a child). `Agent` is removed
148
+ * only from a child at `subagents.maxDepth` (agent map spec §4.1, `effectiveDefinition`).
119
149
  */
120
- export const CHILD_REMOVED_TOOLS = [BUILTIN.AskUserQuestion, BUILTIN.Deliver];
150
+ export const CHILD_REMOVED_TOOLS = [
151
+ BUILTIN.AskUserQuestion,
152
+ BUILTIN.Deliver,
153
+ BUILTIN.ScheduleCreate,
154
+ BUILTIN.ScheduleUpdate,
155
+ BUILTIN.ScheduleList,
156
+ BUILTIN.ScheduleDelete,
157
+ ];
121
158
  /**
122
159
  * The builtins every agent has unless it turns them off (tools default-on spec §4), in this
123
160
  * order: {@link BUILTIN_TOOL_NAMES} without the ones the registry manages by rule (`ToolSearch`,
@@ -150,6 +187,10 @@ export const BUILTIN_TOOL_KINDS = {
150
187
  [BUILTIN.WebFetch]: "read",
151
188
  [BUILTIN.Platform]: "tool",
152
189
  [BUILTIN.CurrentTime]: "tool",
190
+ [BUILTIN.ScheduleCreate]: "tool",
191
+ [BUILTIN.ScheduleUpdate]: "tool",
192
+ [BUILTIN.ScheduleList]: "tool",
193
+ [BUILTIN.ScheduleDelete]: "tool",
153
194
  };
154
195
  /**
155
196
  * Input fields left out when matching a replayed call with a recorded one (full-run replay), per
@@ -16,9 +16,11 @@ import type { HookRunner } from "../../runtime/hook-calls.js";
16
16
  import { StepLog } from "../../runtime/step-log.js";
17
17
  import { UsageReporter } from "../../runtime/usage-report.js";
18
18
  import { SandboxService } from "../../sandbox/sandbox.service.js";
19
+ import { ScheduledTaskStore } from "../../schedules/store.js";
19
20
  import { StorageService } from "../../storage/storage.service.js";
20
21
  import { ToolOutputs } from "../outputs.js";
21
22
  import type { BuiltinTool } from "../registry.js";
23
+ import { ScheduleToolsPort, ScheduleUpdates } from "./schedules.js";
22
24
  import { WebPages } from "./web/index.js";
23
25
  /**
24
26
  * The kernel's builtin tools, built once from the injected services (their specs: names,
@@ -33,6 +35,12 @@ export declare class BuiltinTools {
33
35
  purposes: ModelPurposesService, host: {
34
36
  readonly hooks?: HookRunner;
35
37
  } | undefined, usageReport: UsageReporter, stepLog: StepLog, log: Logger, background: BackgroundTaskStore, finisher: BackgroundTaskFinisher, stopper: BackgroundTaskStopper, platformOps: PlatformOperations, platformActor: PlatformActor, agentDocs: AgentDocs,
38
+ /** The schedule tools read tasks and grants; their writes go through the task service. */
39
+ scheduleStore: ScheduledTaskStore,
40
+ /** `ScheduleUpdate`'s write: its field change and grant changes in one transaction. */
41
+ scheduleUpdates: ScheduleUpdates,
42
+ /** The task service, bound by `SchedulesModule` at start (a registration port: no cycle). */
43
+ schedulePort: ScheduleToolsPort,
36
44
  /** The clock `CurrentTime` reads (the run context's clock seam; `Date.now` by default). */
37
45
  now?: () => number);
38
46
  /** The builtin tools, in a fixed order. */
@@ -28,6 +28,7 @@ import { conversationTimeZone, RUN_CONTEXT_CLOCK } from "../../runtime/run-conte
28
28
  import { StepLog } from "../../runtime/step-log.js";
29
29
  import { UsageReporter } from "../../runtime/usage-report.js";
30
30
  import { SandboxService } from "../../sandbox/sandbox.service.js";
31
+ import { ScheduledTaskStore } from "../../schedules/store.js";
31
32
  import { StorageService } from "../../storage/storage.service.js";
32
33
  import { ToolOutputs } from "../outputs.js";
33
34
  import { agentTool } from "./agent.js";
@@ -37,6 +38,7 @@ import { handbackTool } from "./handback.js";
37
38
  import { platformTool } from "./platform.js";
38
39
  import { readToolOutputTool } from "./read-tool-output.js";
39
40
  import { sandboxTools } from "./sandbox/index.js";
41
+ import { ScheduleToolsPort, ScheduleUpdates, scheduleTools } from "./schedules.js";
40
42
  import { sendMessageTool } from "./send-message.js";
41
43
  import { skillLoadTool } from "./skill-load.js";
42
44
  import { toolSearchTool } from "./tool-search.js";
@@ -52,6 +54,12 @@ let BuiltinTools = class BuiltinTools {
52
54
  constructor(db, config, outputs, storage, attachments, eventLog, sandbox, devices, points, webPages,
53
55
  /** The WebFetch extraction call: the `web_fetch` purpose, its hooks, usage and denials. */
54
56
  purposes, host, usageReport, stepLog, log, background, finisher, stopper, platformOps, platformActor, agentDocs,
57
+ /** The schedule tools read tasks and grants; their writes go through the task service. */
58
+ scheduleStore,
59
+ /** `ScheduleUpdate`'s write: its field change and grant changes in one transaction. */
60
+ scheduleUpdates,
61
+ /** The task service, bound by `SchedulesModule` at start (a registration port: no cycle). */
62
+ schedulePort,
55
63
  /** The clock `CurrentTime` reads (the run context's clock seam; `Date.now` by default). */
56
64
  now) {
57
65
  const sandboxDeps = {
@@ -110,6 +118,16 @@ let BuiltinTools = class BuiltinTools {
110
118
  now: now ?? Date.now,
111
119
  timeZoneOf: ({ orgId, conversationId }) => conversationTimeZone(db.db, orgId, conversationId),
112
120
  }),
121
+ // the person's scheduled tasks (scheduled tasks spec §11)
122
+ ...scheduleTools({
123
+ db: () => db.db,
124
+ config,
125
+ store: scheduleStore,
126
+ service: () => schedulePort.get(),
127
+ updates: scheduleUpdates,
128
+ timeZoneOf: (orgId, conversationId) => conversationTimeZone(db.db, orgId, conversationId),
129
+ now: now ?? Date.now,
130
+ }),
113
131
  ];
114
132
  }
115
133
  /** The builtin tools, in a fixed order. */
@@ -140,7 +158,10 @@ BuiltinTools = __decorate([
140
158
  __param(18, Inject(PlatformOperations)),
141
159
  __param(19, Inject(PlatformActor)),
142
160
  __param(20, Inject(AgentDocs)),
143
- __param(21, Optional()),
144
- __param(21, Inject(RUN_CONTEXT_CLOCK))
161
+ __param(21, Inject(ScheduledTaskStore)),
162
+ __param(22, Inject(ScheduleUpdates)),
163
+ __param(23, Inject(ScheduleToolsPort)),
164
+ __param(24, Optional()),
165
+ __param(24, Inject(RUN_CONTEXT_CLOCK))
145
166
  ], BuiltinTools);
146
167
  export { BuiltinTools };
@@ -38,7 +38,10 @@ const SPEC = {
38
38
  "deployment's background maximum (7200000 by default). A background task ends when its " +
39
39
  "command exits, and processes it left running are stopped. A background task is lost if the " +
40
40
  "sandbox is suspended (it is suspended after it has been running for a while, " +
41
- "sandbox.limits.maxRunningMinutes), so keep its results in files under the workspace directory.",
41
+ "sandbox.limits.maxRunningMinutes), so keep its results in files under the workspace directory.\n\n" +
42
+ "Network access goes through a proxy set in HTTP_PROXY / HTTPS_PROXY / ALL_PROXY; a tool " +
43
+ "that ignores them fails with 'Operation not permitted'. A blocked domain is reported after " +
44
+ "the output.",
42
45
  inputSchema: {
43
46
  type: "object",
44
47
  properties: {
@@ -349,18 +352,22 @@ export function sandboxBashTool(deps) {
349
352
  // sandbox is obtained, so the time spent waiting for the lock and resuming is deducted
350
353
  let ms = askedMs;
351
354
  let workspace = DEFAULT_SANDBOX_ROOTS.workspace;
352
- const { value: r, notices } = await inSandbox(deps, ctx, ({ h, roots, p, signal }) => {
355
+ // the device the command ran on (a refused network host is asked about there)
356
+ let deviceId = "";
357
+ const { value: r, notices } = await inSandbox(deps, ctx, (s) => {
353
358
  const left = Math.floor(runTimeLeftMs(ctx.runDeadline) / 1000) * 1000;
354
359
  ms = Math.max(1000, Math.min(askedMs, left));
355
- workspace = roots.workspace;
356
- return p.exec(h, {
360
+ workspace = s.roots.workspace;
361
+ deviceId = s.deviceId;
362
+ return s.p.exec(s.h, {
357
363
  command,
358
- cwd: roots.workspace,
364
+ cwd: s.roots.workspace,
359
365
  timeoutMs: ms,
360
366
  maxOutputBytes: MAX_OUTPUT_BYTES,
361
- signal,
367
+ signal: s.signal,
362
368
  });
363
369
  });
370
+ const blocked = r.network?.blocked ?? [];
364
371
  const seconds = ms / 1000;
365
372
  const clamped = ms < askedMs;
366
373
  const shown = clip(command, 120);
@@ -371,7 +378,7 @@ export function sandboxBashTool(deps) {
371
378
  return {
372
379
  content: [{ type: "text", text: withNotices(notices, text) }],
373
380
  isError: r.timedOut || r.exitCode === null,
374
- // display only: a non-zero exit shows as failed on the row, `isError` stays as it was
381
+ // display only: a non-zero exit is information on the row (not a failure), like `isError`
375
382
  view: {
376
383
  kind: "command",
377
384
  ...(description !== undefined && { description }),
@@ -383,6 +390,8 @@ export function sandboxBashTool(deps) {
383
390
  : r.exitCode === null
384
391
  ? display(`命令被中止:${shown}`, `Command stopped: ${shown}`)
385
392
  : display(`运行命令(退出码 ${r.exitCode}):${shown}`, `Ran command (exit ${r.exitCode}): ${shown}`),
393
+ // kernel-internal: the pipeline asks the device owner or writes notes (spec §6.2)
394
+ ...(blocked.length > 0 && deviceId !== "" && { networkReport: { deviceId, blocked } }),
386
395
  };
387
396
  },
388
397
  };
@@ -23,7 +23,7 @@ export interface SandboxToolDeps {
23
23
  * `usableNow` (absent in test stubs: only the cloud provider counts) tells a listing whether a
24
24
  * conversation's device can run without a cloud provider (ruling R8).
25
25
  */
26
- devices: Pick<ConversationDevices, "forTool"> & Partial<Pick<ConversationDevices, "usableNow">>;
26
+ devices: Pick<ConversationDevices, "forTool"> & Partial<Pick<ConversationDevices, "usableNow" | "sandboxLevel">>;
27
27
  /** Background `Bash` tasks; absent in test stubs, where `run_in_background` is refused. */
28
28
  background?: Pick<BackgroundTaskStore, "reserve" | "markStarted" | "failLaunch" | "finishStoppedLaunch" | "releaseStop" | "get" | "advanceOffset">;
29
29
  /**
@@ -1,10 +1,12 @@
1
1
  import { DEFAULT_SANDBOX_ROOTS, DeviceProtocolError, isFileSystemGoneError, isSandboxGoneError, ToolError, validateSandboxPath, validateSandboxPathShape, } from "@capybara-hq/plugin-sdk";
2
2
  import { ANONYMOUS_NO_DEVICE, } from "../../../devices/conversation-devices.js";
3
3
  import { ApiError } from "../../../errors.js";
4
+ import { UNSANDBOXED_NOT_ALLOWED_ERROR } from "../../../local-devices/local-device-provider.js";
4
5
  import { devicePathsChange } from "../../../runtime/context/device-paths.js";
5
6
  import { SandboxPathBusyError } from "../../../sandbox/leases.js";
6
7
  import { SandboxLockLostError, } from "../../../sandbox/sandbox.service.js";
7
8
  import { SandboxProviderUnreachableError } from "../../../sandbox/unreachable.js";
9
+ import { UNATTENDED_OPEN_TIER_ERROR, UNATTENDED_UNSANDBOXED_ERROR, } from "../../../schedules/unattended.js";
8
10
  // ---- 模型可见文案(英文) ----
9
11
  /** This conversation's first use of its device (in the instance's workspace). */
10
12
  export const sandboxStartedNotice = (roots) => `Device started (first use in this conversation); the working directory is ${roots.workspace}, shared with the device's other conversations.`;
@@ -300,6 +302,11 @@ async function copyAttachments(deps, ctx, s, conv, list) {
300
302
  }
301
303
  return { copied, failures };
302
304
  }
305
+ /**
306
+ * A device that runs commands without a sandbox the org does not allow (sandbox dev tiers spec
307
+ * §4.3): the kernel's `device.unsandboxed_not_allowed` text plus what the model can do.
308
+ */
309
+ const UNSANDBOXED_TEXT = `${UNSANDBOXED_NOT_ALLOWED_ERROR} Ask the user to do that, or to start a new conversation on another device.`;
303
310
  /**
304
311
  * Sandbox service errors → actionable text for the model; anything else is returned unchanged
305
312
  * (the pipeline reports a generic tool failure). A provider that cannot be reached becomes a
@@ -343,6 +350,13 @@ export function mapSandboxError(e, log) {
343
350
  return new ToolError(DEVICE_OFFLINE_TEXT);
344
351
  if (e.code === "device.program_outdated")
345
352
  return new ToolError(DEVICE_OUTDATED_TEXT);
353
+ if (e.code === "device.unsandboxed_not_allowed" && detail?.reason === "unattended") {
354
+ return new ToolError(UNATTENDED_UNSANDBOXED_ERROR);
355
+ }
356
+ if (e.code === "device.unsandboxed_not_allowed")
357
+ return new ToolError(UNSANDBOXED_TEXT);
358
+ if (e.code === "device.tier_not_granted")
359
+ return new ToolError(UNATTENDED_OPEN_TIER_ERROR);
346
360
  }
347
361
  // an operation outside withSandbox's own mapping (it records lost files itself)
348
362
  if (isFileSystemGoneError(e))
@@ -373,10 +387,15 @@ export async function inSandbox(deps, ctx, fn) {
373
387
  deviceId,
374
388
  // a sub-agent is told when another holder rebuilt the shared instance (final review m5)
375
389
  holderConversationId: ctx.conversationId,
376
- actor: { accountId: ctx.accountId, conversationId: ctx.conversationId },
390
+ // the run routes its network grants to a connected device (network ask-to-allow P-D3)
391
+ actor: {
392
+ accountId: ctx.accountId,
393
+ conversationId: ctx.conversationId,
394
+ ...(ctx.runId !== undefined && { runId: ctx.runId }),
395
+ },
377
396
  }, async (h, p, info, signal) => {
378
397
  const s = { deviceId, h, roots: info.roots, p, signal };
379
- await recordDevicePaths(deps, ctx, info.roots);
398
+ await recordDevicePaths(deps, ctx, deviceId, info.roots);
380
399
  const notices = [];
381
400
  if (info.filesReset)
382
401
  notices.push(sandboxFilesResetNotice(info.roots));
@@ -397,17 +416,21 @@ export async function inSandbox(deps, ctx, fn) {
397
416
  }
398
417
  }
399
418
  /**
400
- * Environments spec §13.1, ruling R22: the calling conversation's log (the log its model reads; a
401
- * sub-agent's own too) gets an `device_paths` event on its first sandbox call, the default
402
- * roots included, and again whenever the held instance's roots differ from the latest recorded
403
- * ones; always before the call's result. One indexed read per call (migration 0094); parallel
404
- * calls that saw the same latest event write one event (its idempotency key).
419
+ * Environments spec §13.1, ruling R22; sandbox dev tiers spec §11.5: the calling conversation's
420
+ * log (the log its model reads; a sub-agent's own too) gets a `device_paths` event on its first
421
+ * sandbox call, the default roots included, and again whenever the held instance's roots or the
422
+ * device's sandbox level (tier, isolation) differ from the latest recorded ones; always before the
423
+ * call's result. Two indexed reads per call (migration 0094, the device's row); parallel calls
424
+ * that saw the same latest event write one event (its idempotency key).
405
425
  */
406
- async function recordDevicePaths(deps, ctx, roots) {
426
+ async function recordDevicePaths(deps, ctx, deviceId, roots) {
407
427
  if (!deps.eventLog.latestOfType)
408
428
  return;
409
- const latest = await deps.eventLog.latestOfType(ctx.conversationId, "device_paths");
410
- const change = devicePathsChange(latest, roots, ctx.runId);
429
+ const [latest, level] = await Promise.all([
430
+ deps.eventLog.latestOfType(ctx.conversationId, "device_paths"),
431
+ deps.devices.sandboxLevel?.(ctx.orgId, deviceId, ctx.runId) ?? null,
432
+ ]);
433
+ const change = devicePathsChange(latest, roots, ctx.runId, level ?? undefined);
411
434
  if (change)
412
435
  await deps.eventLog.append(ctx.conversationId, [change]);
413
436
  }
@@ -1,7 +1,7 @@
1
1
  import { ToolError } from "@capybara-hq/plugin-sdk";
2
2
  import { lineDiff, viewPath } from "../../view.js";
3
3
  import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
4
- import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
4
+ import { assertReadAndUnchanged, FileGuardError, fileStateOf, guardResult, keepGuardRefusal, normalizeSandboxPath, recordedFile, } from "./file-state.js";
5
5
  import { READ_MAX_BYTES } from "./read.js";
6
6
  /** 可编辑的最大文件(字节):更大的用 Bash 处理。 */
7
7
  const EDIT_MAX_BYTES = 10 * 1024 * 1024;
@@ -57,7 +57,8 @@ export function sandboxEditTool(deps) {
57
57
  throw new ToolError(SAME_TEXT);
58
58
  checkPathShape(input.file_path, "file_path");
59
59
  let path = "";
60
- const { value: edit, notices } = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
60
+ // a read-before-write refusal is the guard working: a result with the guard view
61
+ const ran = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
61
62
  path = checkWritable(input.file_path, roots);
62
63
  // read, replace, write: one holder at a time per file
63
64
  return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
@@ -92,7 +93,10 @@ export function sandboxEditTool(deps) {
92
93
  file: recordedFile(normalizeSandboxPath(path), nextBytes.subarray(0, READ_MAX_BYTES), nextBytes.byteLength),
93
94
  };
94
95
  });
95
- });
96
+ }).catch(keepGuardRefusal);
97
+ if (ran instanceof FileGuardError)
98
+ return guardResult(ran);
99
+ const { value: edit, notices } = ran;
96
100
  const { n } = edit;
97
101
  const text = `Edited ${path}: replaced ${n} occurrence${n === 1 ? "" : "s"}.`;
98
102
  return {
@@ -1,3 +1,4 @@
1
+ import { ToolError } from "@capybara-hq/plugin-sdk";
1
2
  import type { EventLog } from "../../../conversations/event-log.js";
2
3
  /**
3
4
  * Read before `Write` / `Edit` (tool naming spec §4.5). The state of a file as a `Read`, `Write` or
@@ -17,6 +18,35 @@ export interface RecordedFile extends FileState {
17
18
  }
18
19
  export declare const NOT_READ_TEXT = "File has not been read yet. Read it first before writing to it.";
19
20
  export declare const MODIFIED_TEXT = "File has been modified since it was read, either by the user or by another command. Read it again before writing to it.";
21
+ /** Why the read-before-write check refused a `Write` / `Edit` (`tool_result.view.reason`). */
22
+ export type FileGuardReason = "not_read" | "modified";
23
+ /**
24
+ * The read-before-write check refused the call: the guard working as designed, not a failed tool.
25
+ * The model sees the message as an error result (as any `ToolError`); the tools turn it into a
26
+ * result with the display-only `guard` view ({@link guardResult}) so the row reads as a neutral
27
+ * outcome.
28
+ */
29
+ export declare class FileGuardError extends ToolError {
30
+ readonly reason: FileGuardReason;
31
+ constructor(reason: FileGuardReason);
32
+ }
33
+ /**
34
+ * The result of a call the read-before-write check refused: the same text and `isError` the model
35
+ * got before the marker existed, plus the display-only `guard` view (never in a request).
36
+ */
37
+ export declare function guardResult(e: FileGuardError): {
38
+ content: {
39
+ type: "text";
40
+ text: string;
41
+ }[];
42
+ isError: boolean;
43
+ view: {
44
+ kind: "guard";
45
+ reason: FileGuardReason;
46
+ };
47
+ };
48
+ /** A `.catch` that returns a {@link FileGuardError} and rethrows everything else. */
49
+ export declare function keepGuardRefusal(e: unknown): FileGuardError;
20
50
  /** The state of `bytes`, the bytes handled of a file whose full size is `fullSize`. */
21
51
  export declare function fileStateOf(bytes: Uint8Array, fullSize: number): FileState;
22
52
  /** The `view.file` value for a file at `path` (already normalized). */
@@ -26,7 +56,7 @@ export declare function normalizeSandboxPath(path: string): string;
26
56
  /** The latest recorded state of `path` in the conversation's log; null when it was never recorded. */
27
57
  export declare function lastFileState(log: Pick<EventLog, "latestFileState">, conversationId: string, path: string): Promise<FileState | null>;
28
58
  /**
29
- * Throws the model-visible error unless the conversation read (or wrote) the file and it is
59
+ * Throws the model-visible error ({@link FileGuardError}) unless the conversation read (or wrote) the file and it is
30
60
  * unchanged: `current` is the state of the content the caller just read.
31
61
  */
32
62
  export declare function assertReadAndUnchanged(deps: {
@@ -3,6 +3,36 @@ import { posix } from "node:path";
3
3
  import { ToolError } from "@capybara-hq/plugin-sdk";
4
4
  export const NOT_READ_TEXT = "File has not been read yet. Read it first before writing to it.";
5
5
  export const MODIFIED_TEXT = "File has been modified since it was read, either by the user or by another command. Read it again before writing to it.";
6
+ /**
7
+ * The read-before-write check refused the call: the guard working as designed, not a failed tool.
8
+ * The model sees the message as an error result (as any `ToolError`); the tools turn it into a
9
+ * result with the display-only `guard` view ({@link guardResult}) so the row reads as a neutral
10
+ * outcome.
11
+ */
12
+ export class FileGuardError extends ToolError {
13
+ reason;
14
+ constructor(reason) {
15
+ super(reason === "not_read" ? NOT_READ_TEXT : MODIFIED_TEXT);
16
+ this.reason = reason;
17
+ }
18
+ }
19
+ /**
20
+ * The result of a call the read-before-write check refused: the same text and `isError` the model
21
+ * got before the marker existed, plus the display-only `guard` view (never in a request).
22
+ */
23
+ export function guardResult(e) {
24
+ return {
25
+ content: [{ type: "text", text: e.message }],
26
+ isError: true,
27
+ view: { kind: "guard", reason: e.reason },
28
+ };
29
+ }
30
+ /** A `.catch` that returns a {@link FileGuardError} and rethrows everything else. */
31
+ export function keepGuardRefusal(e) {
32
+ if (e instanceof FileGuardError)
33
+ return e;
34
+ throw e;
35
+ }
6
36
  /** The state of `bytes`, the bytes handled of a file whose full size is `fullSize`. */
7
37
  export function fileStateOf(bytes, fullSize) {
8
38
  return { size: fullSize, sha256: createHash("sha256").update(bytes).digest("hex") };
@@ -21,14 +51,14 @@ export async function lastFileState(log, conversationId, path) {
21
51
  return log.latestFileState(conversationId, normalizeSandboxPath(path));
22
52
  }
23
53
  /**
24
- * Throws the model-visible error unless the conversation read (or wrote) the file and it is
54
+ * Throws the model-visible error ({@link FileGuardError}) unless the conversation read (or wrote) the file and it is
25
55
  * unchanged: `current` is the state of the content the caller just read.
26
56
  */
27
57
  export async function assertReadAndUnchanged(deps, conversationId, path, current) {
28
58
  const seen = await lastFileState(deps.eventLog, conversationId, path);
29
59
  if (seen === null)
30
- throw new ToolError(NOT_READ_TEXT);
60
+ throw new FileGuardError("not_read");
31
61
  if (seen.size !== current.size || seen.sha256 !== current.sha256) {
32
- throw new ToolError(MODIFIED_TEXT);
62
+ throw new FileGuardError("modified");
33
63
  }
34
64
  }
@@ -1,7 +1,7 @@
1
1
  import { ToolError } from "@capybara-hq/plugin-sdk";
2
2
  import { lineDiff, TOOL_VIEW_DIFF_MAX_BYTES, viewPath } from "../../view.js";
3
3
  import { checkPathShape, checkWritable, clip, display, inSandbox, looksBinary, sandboxBaseTimeoutMs, unavailableWhenNoSandbox, withNotices, withPathLease, } from "./common.js";
4
- import { assertReadAndUnchanged, fileStateOf, normalizeSandboxPath, recordedFile, } from "./file-state.js";
4
+ import { assertReadAndUnchanged, FileGuardError, fileStateOf, guardResult, keepGuardRefusal, normalizeSandboxPath, recordedFile, } from "./file-state.js";
5
5
  import { READ_MAX_BYTES } from "./read.js";
6
6
  const SPEC = {
7
7
  name: "Write",
@@ -46,7 +46,8 @@ export function sandboxWriteTool(deps) {
46
46
  const data = new TextEncoder().encode(input.content);
47
47
  checkPathShape(input.file_path, "file_path");
48
48
  let path = "";
49
- const { value, notices } = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
49
+ // a read-before-write refusal is the guard working: a result with the guard view
50
+ const ran = await inSandbox(deps, ctx, ({ deviceId, h, roots, p, signal }) => {
50
51
  path = checkWritable(input.file_path, roots);
51
52
  // stat, read the previous content, write: one holder at a time per file
52
53
  return withPathLease(deps, { deviceId }, path, signal, async (signal) => {
@@ -72,8 +73,11 @@ export function sandboxWriteTool(deps) {
72
73
  await p.writeFile(h, path, data, signal);
73
74
  return { existed: st !== null, previous };
74
75
  });
75
- });
76
- const { existed, previous } = value;
76
+ }).catch(keepGuardRefusal);
77
+ if (ran instanceof FileGuardError)
78
+ return guardResult(ran);
79
+ const { existed, previous } = ran.value;
80
+ const { notices } = ran;
77
81
  const text = `${existed ? "Overwrote" : "Created"} ${path} (${data.byteLength} bytes).`;
78
82
  return {
79
83
  content: [{ type: "text", text: withNotices(notices, text) }],