@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
@@ -1,7 +1,7 @@
1
1
  import { renderBackgroundFinished } from "../../background/render.js";
2
2
  import { renderSubagentDelivery } from "../subagents/render.js";
3
3
  import { latestSummary } from "./build.js";
4
- import { senderTranscriptLabel } from "./run-context.js";
4
+ import { scheduleHeader, senderTranscriptLabel } from "./run-context.js";
5
5
  import { toolsChangedText } from "./tool-changes.js";
6
6
  /** 压缩请求的标记:放在 system 中,便于识别(model-fake 据此返回摘要)。 */
7
7
  export const COMPACTION_MARKER = "CAPYBARA_COMPACTION";
@@ -106,9 +106,12 @@ function keepToolPairs(candidate, userIdx, boundary) {
106
106
  function transcriptLine(e) {
107
107
  switch (e.type) {
108
108
  case "user_message": {
109
- const text = renderBlocks(e.payload.content);
110
- if (!text)
109
+ const body = renderBlocks(e.payload.content);
110
+ if (!body)
111
111
  return null;
112
+ // scheduled tasks spec §12: the header the request shows, so the summary keeps the origin
113
+ const schedule = e.payload.schedule;
114
+ const text = schedule === undefined ? body : `${scheduleHeader(schedule)} ${body}`;
112
115
  // run context spec §5: the sender as logged (older logs have none: unchanged)
113
116
  const sender = e.payload.sender;
114
117
  return sender !== undefined
@@ -1,38 +1,60 @@
1
- import type { SandboxRoots } from "@capybara-hq/plugin-sdk";
1
+ import type { SandboxRoots, SandboxTier } from "@capybara-hq/plugin-sdk";
2
2
  import type { ConversationEvent } from "../../conversations/events.js";
3
3
  /**
4
- * The `device_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
5
- * of every conversation records the instance's roots (the defaults included), and a later call
6
- * records them again whenever they differ from the conversation's latest recorded ones (a
7
- * connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
8
- * its position as a system message; in the log, so it needs no `DriftReason`.
4
+ * The `device_paths` event (environments spec §13.1, ruling R22; sandbox dev tiers spec §11.5):
5
+ * the first sandbox tool call of every conversation records the instance's roots (the defaults
6
+ * included) and the device's sandbox level, and a later call records them again whenever they
7
+ * differ from the conversation's latest recorded ones (a connected machine's real paths, a mode
8
+ * switch back to the defaults, a tier or isolation change). Model-visible: rendered at its
9
+ * position as a system message; in the log, so it needs no `DriftReason`. Events recorded before
10
+ * tiers have no `tier` / `isolation` and render as they always did.
9
11
  */
10
- /** The system line of an `device_paths` event (model-visible, English). */
11
- export declare function devicePathsText(r: SandboxRoots): string;
12
+ /** The sandbox level a device's commands run at (spec §11.5), as the model is told it. */
13
+ export interface DeviceSandboxLevel {
14
+ tier: SandboxTier;
15
+ isolation: "sandbox" | "none";
16
+ /** `none`: the org's or the platform's sandbox network is off (every tier). */
17
+ network?: "none" | undefined;
18
+ /** A local device's localhost and unix sockets, as its machine reported them (absent: cloud). */
19
+ localhost?: "on" | "off-proxy-detected" | "off" | undefined;
20
+ unixSockets?: "roots" | "all-masked" | "off" | undefined;
21
+ /** The call is in a scheduled run without a person: nobody can approve a domain. */
22
+ unattended?: true | undefined;
23
+ }
24
+ /** The payload of a `device_paths` event; the level fields are absent on events before tiers. */
25
+ export interface DevicePathsPayload extends Omit<DeviceSandboxLevel, "tier" | "isolation"> {
26
+ roots: SandboxRoots;
27
+ tier?: SandboxTier | undefined;
28
+ isolation?: "sandbox" | "none" | undefined;
29
+ }
30
+ /** The sandbox level line of a `device_paths` event (model-visible, English; spec §11.5). */
31
+ export declare function sandboxLevelText(level: DeviceSandboxLevel): string;
32
+ /** The level a payload records; undefined for an event recorded before tiers. */
33
+ export declare function devicePathsLevel(p: DevicePathsPayload): DeviceSandboxLevel | undefined;
34
+ /** The system message of a `device_paths` event (model-visible, English). */
35
+ export declare function devicePathsText(p: DevicePathsPayload): string;
12
36
  /** Whether two sets of roots are the same paths. */
13
37
  export declare const sameRoots: (a: SandboxRoots, b: SandboxRoots) => boolean;
14
38
  /**
15
- * The idempotency key of an `device_paths` event for `r` that follows the conversation's
16
- * event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
17
- * calls that saw the same latest event write it once.
39
+ * The idempotency key of a `device_paths` event for `p` that follows the conversation's event at
40
+ * `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel calls that
41
+ * saw the same latest event write it once.
18
42
  */
19
- export declare const devicePathsIdemKey: (r: SandboxRoots, prevSeq: number) => string;
43
+ export declare const devicePathsIdemKey: (p: DevicePathsPayload, prevSeq: number) => string;
20
44
  /**
21
- * The `device_paths` event to append when a sandbox call holds an instance with `roots` and
22
- * the conversation's latest such event is `latest`; null when nothing changed.
45
+ * The `device_paths` event to append when a sandbox call holds an instance with `roots` in a
46
+ * device at `level`, and the conversation's latest such event is `latest`; null when nothing
47
+ * changed. Without a `level` (not known: test stubs, a replay of a call recorded before tiers)
48
+ * only the roots are compared and recorded.
23
49
  */
24
50
  export declare function devicePathsChange(latest: {
25
51
  seq: number;
26
- payload: {
27
- roots: SandboxRoots;
28
- };
29
- } | undefined, roots: SandboxRoots, runId: string | undefined): {
52
+ payload: DevicePathsPayload;
53
+ } | undefined, roots: SandboxRoots, runId: string | undefined, level?: DeviceSandboxLevel): {
30
54
  type: "device_paths";
31
55
  runId?: string;
32
56
  idemKey: string;
33
- payload: {
34
- roots: SandboxRoots;
35
- };
57
+ payload: DevicePathsPayload;
36
58
  } | null;
37
59
  /** The latest `device_paths` event at or before `seq`; undefined without one. */
38
60
  export declare function devicePathsAt(events: readonly ConversationEvent[], seq: number): Extract<ConversationEvent, {
@@ -1,38 +1,121 @@
1
1
  import { createHash } from "node:crypto";
2
- /**
3
- * The `device_paths` event (environments spec §13.1, ruling R22): the first sandbox tool call
4
- * of every conversation records the instance's roots (the defaults included), and a later call
5
- * records them again whenever they differ from the conversation's latest recorded ones (a
6
- * connected machine's real paths, a mode switch back to the defaults). Model-visible: rendered at
7
- * its position as a system message; in the log, so it needs no `DriftReason`.
8
- */
9
- /** The system line of an `device_paths` event (model-visible, English). */
10
- export function devicePathsText(r) {
11
- return `This device's directories: workspace ${r.workspace}, skills ${r.skills} (read only), temporary files ${r.tmp}. Use these paths in commands and tool calls.`;
2
+ /** The roots line of a `device_paths` event (model-visible, English). */
3
+ const rootsText = (r) => `This device's directories: workspace ${r.workspace}, skills ${r.skills} (read only), temporary files ${r.tmp}. Use these paths in commands and tool calls.`;
4
+ const TIER_NAMES = {
5
+ strict: "strict",
6
+ dev: "development",
7
+ open: "open",
8
+ };
9
+ /** What a domain outside the reachable ones needs: the user's approval, unless nobody is there. */
10
+ const otherDomains = (unattended) => unattended
11
+ ? "approvals are not available in a scheduled run, so other domains stay blocked."
12
+ : "other domains need the user's approval.";
13
+ /** Local servers and unix sockets in `dev` / `open` (a cloud instance has both). */
14
+ function localText(level) {
15
+ const { localhost, unixSockets } = level;
16
+ if (localhost === undefined || unixSockets === undefined) {
17
+ return "Local servers and unix sockets work.";
18
+ }
19
+ const servers = localhost === "on"
20
+ ? "Local servers work"
21
+ : localhost === "off-proxy-detected"
22
+ ? "Local servers are blocked on this machine because a local proxy was detected (the user can allow them on the device)"
23
+ : "Local servers are blocked";
24
+ const sockets = unixSockets === "roots"
25
+ ? "unix sockets work under the device's directories only"
26
+ : unixSockets === "all-masked"
27
+ ? "unix sockets work inside the device's container only"
28
+ : "unix sockets are blocked";
29
+ return `${servers}; ${sockets}.`;
30
+ }
31
+ /** The sandbox level line of a `device_paths` event (model-visible, English; spec §11.5). */
32
+ export function sandboxLevelText(level) {
33
+ if (level.isolation === "none") {
34
+ return "Sandbox level: open, without a sandbox. Commands run directly on the user's computer as the user, with their files, credentials and network; be careful with anything that deletes files or sends data out.";
35
+ }
36
+ const head = `Sandbox level: ${TIER_NAMES[level.tier]}.`;
37
+ if (level.network === "none") {
38
+ return `${head} The network is switched off for this organization: no domain is reachable and approval requests cannot change that.`;
39
+ }
40
+ const unattended = level.unattended === true;
41
+ switch (level.tier) {
42
+ case "strict":
43
+ return `${head} Only the organization's allowed domains are reachable; ${otherDomains(unattended)}`;
44
+ case "dev":
45
+ return `${head} Package registries and common developer hosts are reachable; ${otherDomains(unattended)} ${localText(level)}`;
46
+ case "open":
47
+ return `${head} Any public address is reachable. ${localText(level)}`;
48
+ }
12
49
  }
50
+ /** The level a payload records; undefined for an event recorded before tiers. */
51
+ export function devicePathsLevel(p) {
52
+ if (p.tier === undefined)
53
+ return undefined;
54
+ return {
55
+ tier: p.tier,
56
+ isolation: p.isolation ?? "sandbox",
57
+ ...(p.network !== undefined && { network: p.network }),
58
+ ...(p.localhost !== undefined && { localhost: p.localhost }),
59
+ ...(p.unixSockets !== undefined && { unixSockets: p.unixSockets }),
60
+ ...(p.unattended === true && { unattended: true }),
61
+ };
62
+ }
63
+ /** The system message of a `device_paths` event (model-visible, English). */
64
+ export function devicePathsText(p) {
65
+ const roots = rootsText(p.roots);
66
+ const level = devicePathsLevel(p);
67
+ return level === undefined ? roots : `${roots}\n${sandboxLevelText(level)}`;
68
+ }
69
+ /** The fields of a level, in a fixed order (absent ones left out). */
70
+ const levelKey = (l) => l === undefined
71
+ ? []
72
+ : [
73
+ l.tier,
74
+ l.isolation,
75
+ ...(l.network !== undefined ? [`network:${l.network}`] : []),
76
+ ...(l.localhost !== undefined ? [`localhost:${l.localhost}`] : []),
77
+ ...(l.unixSockets !== undefined ? [`unixSockets:${l.unixSockets}`] : []),
78
+ ...(l.unattended === true ? ["unattended"] : []),
79
+ ];
13
80
  /** Whether two sets of roots are the same paths. */
14
81
  export const sameRoots = (a, b) => a.workspace === b.workspace && a.skills === b.skills && a.tmp === b.tmp;
15
82
  /**
16
- * The idempotency key of an `device_paths` event for `r` that follows the conversation's
17
- * event at `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel
18
- * calls that saw the same latest event write it once.
83
+ * The idempotency key of a `device_paths` event for `p` that follows the conversation's event at
84
+ * `prevSeq` (0: none). Unique per change (A → B → A gets three events), while parallel calls that
85
+ * saw the same latest event write it once.
19
86
  */
20
- export const devicePathsIdemKey = (r, prevSeq) => `device_paths:${createHash("sha256")
21
- .update(JSON.stringify([r.workspace, r.skills, r.tmp]))
87
+ export const devicePathsIdemKey = (p, prevSeq) => `device_paths:${createHash("sha256")
88
+ .update(JSON.stringify([
89
+ p.roots.workspace,
90
+ p.roots.skills,
91
+ p.roots.tmp,
92
+ ...levelKey(devicePathsLevel(p)),
93
+ ]))
22
94
  .digest("hex")
23
95
  .slice(0, 16)}:${prevSeq}`;
24
96
  /**
25
- * The `device_paths` event to append when a sandbox call holds an instance with `roots` and
26
- * the conversation's latest such event is `latest`; null when nothing changed.
97
+ * The `device_paths` event to append when a sandbox call holds an instance with `roots` in a
98
+ * device at `level`, and the conversation's latest such event is `latest`; null when nothing
99
+ * changed. Without a `level` (not known: test stubs, a replay of a call recorded before tiers)
100
+ * only the roots are compared and recorded.
27
101
  */
28
- export function devicePathsChange(latest, roots, runId) {
29
- if (latest && sameRoots(latest.payload.roots, roots))
102
+ export function devicePathsChange(latest, roots, runId, level) {
103
+ if (latest &&
104
+ sameRoots(latest.payload.roots, roots) &&
105
+ (level === undefined ||
106
+ JSON.stringify(levelKey(devicePathsLevel(latest.payload))) ===
107
+ JSON.stringify(levelKey(level)))) {
30
108
  return null;
109
+ }
110
+ const payload = {
111
+ roots: { workspace: roots.workspace, skills: roots.skills, tmp: roots.tmp },
112
+ ...(level !== undefined && devicePathsLevel({ roots, ...level })),
113
+ };
31
114
  return {
32
115
  type: "device_paths",
33
116
  ...(runId !== undefined && { runId }),
34
- idemKey: devicePathsIdemKey(roots, latest?.seq ?? 0),
35
- payload: { roots: { workspace: roots.workspace, skills: roots.skills, tmp: roots.tmp } },
117
+ idemKey: devicePathsIdemKey(payload, latest?.seq ?? 0),
118
+ payload,
36
119
  };
37
120
  }
38
121
  /** The latest `device_paths` event at or before `seq`; undefined without one. */
@@ -16,6 +16,15 @@ export declare const RUN_CONTEXT_DESCRIPTION_MAX = 200;
16
16
  export declare const SENDER_NAME_MAX = 80;
17
17
  /** A sender's name as logged: one line, at most {@link SENDER_NAME_MAX} code points. */
18
18
  export declare const sanitizeSenderName: (name: string) => string;
19
+ /**
20
+ * The English header of a scheduled task's message (scheduled tasks spec §12):
21
+ * `[Scheduled task "<name>", run for <local time>]`, from the marker fields fixed in the log, so a
22
+ * later rename of the task never changes a rebuilt request. The name is one line, JSON-quoted.
23
+ */
24
+ export declare const scheduleHeader: (m: {
25
+ taskName: string;
26
+ localTime: string;
27
+ }) => string;
19
28
  /** An agent's description as a snapshot carries it; empty → undefined. */
20
29
  export declare function snapshotDescription(description: string | undefined): string | undefined;
21
30
  /** Today in `timeZone` as `YYYY-MM-DD` (the run start's clock read; never during request building). */
@@ -16,6 +16,14 @@ function oneLine(text, max, ellipsis) {
16
16
  }
17
17
  /** A sender's name as logged: one line, at most {@link SENDER_NAME_MAX} code points. */
18
18
  export const sanitizeSenderName = (name) => oneLine(name, SENDER_NAME_MAX, false);
19
+ /** The longest task name a schedule header shows, in code points. */
20
+ const SCHEDULE_NAME_MAX = 200;
21
+ /**
22
+ * The English header of a scheduled task's message (scheduled tasks spec §12):
23
+ * `[Scheduled task "<name>", run for <local time>]`, from the marker fields fixed in the log, so a
24
+ * later rename of the task never changes a rebuilt request. The name is one line, JSON-quoted.
25
+ */
26
+ export const scheduleHeader = (m) => `[Scheduled task ${JSON.stringify(oneLine(m.taskName, SCHEDULE_NAME_MAX, true))}, run for ${oneLine(m.localTime, 64, false)}]`;
19
27
  /** An agent's description as a snapshot carries it; empty → undefined. */
20
28
  export function snapshotDescription(description) {
21
29
  if (description === undefined)
@@ -37,9 +37,22 @@ export declare const SKILL_TEXTS: SkillTexts;
37
37
  export declare const SKILLS_CATALOG_HEADER: string;
38
38
  /** 目录里每个技能描述最多的码点数(超出确定地截断;`Skill` 仍返回完整正文)。 */
39
39
  export declare const CATALOG_DESCRIPTION_MAX_CHARS = 300;
40
+ /** 一行:`- <name>: <description>`(描述折叠成一行并截断)。 */
41
+ export declare const catalogLine: (s: Pick<CatalogSkill, "name" | "description">) => string;
42
+ /**
43
+ * The heading of the name-only entries of a listing (skills default-on spec §7): the entries a
44
+ * snapshot recorded past the catalog budget (`nameOnly`). Every listing (the system catalog, a
45
+ * change notice's new or updated skills, the current catalog after a compaction) puts them after
46
+ * its described lines under this heading, names in code point order joined by ", ". Introduced
47
+ * with phase 2: no request before it has a name-only entry, so the legacy texts need no copy.
48
+ */
49
+ export declare const MORE_SKILLS_HEADER: string;
40
50
  /** 第一份目录(进 system 缓存段),按名字排序。 */
41
51
  export declare function skillsCatalogText(skills: readonly CatalogSkill[], texts?: SkillTexts): string;
42
- /** 两份快照是否相同(按 id、版本、名字、描述与来源)。顺序无关。 */
52
+ /**
53
+ * 两份快照是否相同(按 id、版本、名字、描述与来源)。顺序无关。 Also by `nameOnly` (absent = false):
54
+ * an entry crossing the catalog budget writes a new snapshot and reads as updated.
55
+ */
43
56
  export declare function sameSkills(a: readonly CatalogSkill[], b: readonly CatalogSkill[]): boolean;
44
57
  /**
45
58
  * Two pinned lists are the same: id, version and source in order (absent = []). Bodies follow the
@@ -28,13 +28,40 @@ function shortDescription(d) {
28
28
  : `${cps.slice(0, CATALOG_DESCRIPTION_MAX_CHARS - 1).join("")}…`;
29
29
  }
30
30
  /** 一行:`- <name>: <description>`(描述折叠成一行并截断)。 */
31
- const line = (s) => `- ${s.name}: ${shortDescription(s.description)}`;
32
- const lines = (skills) => [...skills].sort(byName).map(line).join("\n");
31
+ export const catalogLine = (s) => `- ${s.name}: ${shortDescription(s.description)}`;
32
+ /**
33
+ * The heading of the name-only entries of a listing (skills default-on spec §7): the entries a
34
+ * snapshot recorded past the catalog budget (`nameOnly`). Every listing (the system catalog, a
35
+ * change notice's new or updated skills, the current catalog after a compaction) puts them after
36
+ * its described lines under this heading, names in code point order joined by ", ". Introduced
37
+ * with phase 2: no request before it has a name-only entry, so the legacy texts need no copy.
38
+ */
39
+ export const MORE_SKILLS_HEADER = `More skills (names only; call ${SKILL_LOAD} with the name to read one):`;
40
+ /**
41
+ * The listing of `skills`: the described ones as `- <name>: <description>` lines sorted by name,
42
+ * then the name-only ones under {@link MORE_SKILLS_HEADER}. Without name-only entries (every
43
+ * snapshot before phase 2) it is exactly the described lines, as before.
44
+ */
45
+ function lines(skills) {
46
+ const described = skills.filter((s) => s.nameOnly !== true);
47
+ const nameOnly = skills.filter((s) => s.nameOnly === true);
48
+ const head = [...described].sort(byName).map(catalogLine).join("\n");
49
+ if (nameOnly.length === 0)
50
+ return head;
51
+ const more = `${MORE_SKILLS_HEADER}\n${[...nameOnly]
52
+ .sort(byName)
53
+ .map((s) => s.name)
54
+ .join(", ")}`;
55
+ return head === "" ? more : `${head}\n${more}`;
56
+ }
33
57
  /** 第一份目录(进 system 缓存段),按名字排序。 */
34
58
  export function skillsCatalogText(skills, texts = SKILL_TEXTS) {
35
59
  return `${texts.skillsCatalogHeader}\n${lines(skills)}`;
36
60
  }
37
- /** 两份快照是否相同(按 id、版本、名字、描述与来源)。顺序无关。 */
61
+ /**
62
+ * 两份快照是否相同(按 id、版本、名字、描述与来源)。顺序无关。 Also by `nameOnly` (absent = false):
63
+ * an entry crossing the catalog budget writes a new snapshot and reads as updated.
64
+ */
38
65
  export function sameSkills(a, b) {
39
66
  if (a.length !== b.length)
40
67
  return false;
@@ -47,7 +74,8 @@ export function sameSkills(a, b) {
47
74
  p.version === s.version &&
48
75
  p.name === s.name &&
49
76
  p.description === s.description &&
50
- p.source === s.source);
77
+ p.source === s.source &&
78
+ (p.nameOnly ?? false) === (s.nameOnly ?? false));
51
79
  });
52
80
  }
53
81
  /**
@@ -94,7 +122,10 @@ function catalogChanges(prev, next, pinnedIds) {
94
122
  return (p !== undefined &&
95
123
  p.name === s.name &&
96
124
  !pinnedIds?.has(s.id) &&
97
- (p.version !== s.version || p.description !== s.description || p.source !== s.source));
125
+ (p.version !== s.version ||
126
+ p.description !== s.description ||
127
+ p.source !== s.source ||
128
+ (p.nameOnly ?? false) !== (s.nameOnly ?? false)));
98
129
  });
99
130
  return { added, updated, removed };
100
131
  }
@@ -5,6 +5,7 @@ import { ScheduledRuns } from "../infra/schedule/scheduled-runs.js";
5
5
  import type { KernelLogger } from "../log.js";
6
6
  import { DirectorySync } from "../orgs/directory-sync.js";
7
7
  import { SandboxService } from "../sandbox/sandbox.service.js";
8
+ import { ScheduledTaskFirer } from "../schedules/fire.js";
8
9
  import { ToolDrift } from "../tools/drift.js";
9
10
  import { RunQueue } from "./run-queue.js";
10
11
  import { Waits } from "./waits.js";
@@ -16,7 +17,7 @@ export declare const MAINTENANCE_CONCURRENCY = 5;
16
17
  * each one-off job by name to its domain (`wait-timeout` → {@link Waits}, `sandbox-destroy-for` and
17
18
  * `sandbox-reset-files` → {@link SandboxService}, `drift-connector` → {@link ToolDrift}, `directory-sync` →
18
19
  * {@link DirectorySync}, `bg-wake` → {@link RunQueue.ensureRun}, `owned-plugin-cleanup` →
19
- * {@link OwnedPluginCleanup}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
20
+ * {@link OwnedPluginCleanup}, `sched-fire` → {@link ScheduledTaskFirer}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
20
21
  * such as `sandbox-suspend-idle`) runs that job's body once ({@link ScheduledRuns.runNow}), as the
21
22
  * maintenance handlers did when those jobs were schedulers on this queue: an operator's or a
22
23
  * test's one-off run, not a second schedule (decision R23 of the Nest kernel spec). Any other
@@ -29,10 +30,11 @@ export declare class MaintenanceProcessor extends WorkerHost<Worker> {
29
30
  private readonly directory;
30
31
  private readonly runs;
31
32
  private readonly ownedCleanup;
33
+ private readonly scheduledTasks;
32
34
  private readonly scheduled;
33
35
  private readonly log;
34
36
  private readonly queue;
35
- constructor(waits: Pick<Waits, "processTimeout">, sandbox: Pick<SandboxService, "destroyDevice" | "resetFileSystem">, drift: Pick<ToolDrift, "check">, directory: Pick<DirectorySync, "runSyncJob">, runs: Pick<RunQueue, "ensureRun">, ownedCleanup: Pick<OwnedPluginCleanup, "run">, scheduled: Pick<ScheduledRuns, "runNow">, log: Pick<KernelLogger, "error" | "warn">, queue: Pick<Queue, "removeJobScheduler">);
37
+ constructor(waits: Pick<Waits, "processTimeout">, sandbox: Pick<SandboxService, "destroyDevice" | "resetFileSystem">, drift: Pick<ToolDrift, "check">, directory: Pick<DirectorySync, "runSyncJob">, runs: Pick<RunQueue, "ensureRun">, ownedCleanup: Pick<OwnedPluginCleanup, "run">, scheduledTasks: Pick<ScheduledTaskFirer, "fire">, scheduled: Pick<ScheduledRuns, "runNow">, log: Pick<KernelLogger, "error" | "warn">, queue: Pick<Queue, "removeJobScheduler">);
36
38
  /**
37
39
  * Removes the retired job schedulers (`RETIRED_SCHEDULER_IDS`, runtime/retired-schedulers.ts): every fixed-period
38
40
  * maintenance job is a `@LockedCron` / `@LockedInterval` now. Idempotent; called once when a
@@ -24,6 +24,8 @@ import { OrgsModule } from "../orgs/orgs.module.js";
24
24
  import { runSandboxDestroyFor, runSandboxResetFiles, SANDBOX_DESTROY_FOR_JOB, SANDBOX_RESET_FILES_JOB, } from "../sandbox/jobs.js";
25
25
  import { SandboxModule } from "../sandbox/sandbox.module.js";
26
26
  import { SandboxService } from "../sandbox/sandbox.service.js";
27
+ import { ScheduledTaskFireModule, ScheduledTaskFirer } from "../schedules/fire.js";
28
+ import { SCHED_FIRE_JOB } from "../schedules/job-ids.js";
27
29
  import { ToolDrift } from "../tools/drift.js";
28
30
  import { DRIFT_CONNECTOR_JOB } from "../tools/job-ids.js";
29
31
  import { ToolsModule } from "../tools/tools.module.js";
@@ -40,7 +42,7 @@ export const MAINTENANCE_CONCURRENCY = 5;
40
42
  * each one-off job by name to its domain (`wait-timeout` → {@link Waits}, `sandbox-destroy-for` and
41
43
  * `sandbox-reset-files` → {@link SandboxService}, `drift-connector` → {@link ToolDrift}, `directory-sync` →
42
44
  * {@link DirectorySync}, `bg-wake` → {@link RunQueue.ensureRun}, `owned-plugin-cleanup` →
43
- * {@link OwnedPluginCleanup}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
45
+ * {@link OwnedPluginCleanup}, `sched-fire` → {@link ScheduledTaskFirer}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
44
46
  * such as `sandbox-suspend-idle`) runs that job's body once ({@link ScheduledRuns.runNow}), as the
45
47
  * maintenance handlers did when those jobs were schedulers on this queue: an operator's or a
46
48
  * test's one-off run, not a second schedule (decision R23 of the Nest kernel spec). Any other
@@ -53,10 +55,11 @@ let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
53
55
  directory;
54
56
  runs;
55
57
  ownedCleanup;
58
+ scheduledTasks;
56
59
  scheduled;
57
60
  log;
58
61
  queue;
59
- constructor(waits, sandbox, drift, directory, runs, ownedCleanup, scheduled, log, queue) {
62
+ constructor(waits, sandbox, drift, directory, runs, ownedCleanup, scheduledTasks, scheduled, log, queue) {
60
63
  super();
61
64
  this.waits = waits;
62
65
  this.sandbox = sandbox;
@@ -64,6 +67,7 @@ let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
64
67
  this.directory = directory;
65
68
  this.runs = runs;
66
69
  this.ownedCleanup = ownedCleanup;
70
+ this.scheduledTasks = scheduledTasks;
67
71
  this.scheduled = scheduled;
68
72
  this.log = log;
69
73
  this.queue = queue;
@@ -113,6 +117,10 @@ let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
113
117
  case OWNED_PLUGIN_CLEANUP_JOB:
114
118
  await this.ownedCleanup.run(job.data.itemId);
115
119
  return;
120
+ // one task run of a scheduled task (scheduled tasks spec §6)
121
+ case SCHED_FIRE_JOB:
122
+ await this.scheduledTasks.fire(job.data.taskRunId);
123
+ return;
116
124
  default:
117
125
  if (await this.scheduled.runNow(job.name))
118
126
  return;
@@ -140,9 +148,10 @@ MaintenanceProcessor = __decorate([
140
148
  __param(3, Inject(DirectorySync)),
141
149
  __param(4, Inject(RunQueue)),
142
150
  __param(5, Inject(OwnedPluginCleanup)),
143
- __param(6, Inject(ScheduledRuns)),
144
- __param(7, Inject(KERNEL_LOGGER)),
145
- __param(8, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME)))
151
+ __param(6, Inject(ScheduledTaskFirer)),
152
+ __param(7, Inject(ScheduledRuns)),
153
+ __param(8, Inject(KERNEL_LOGGER)),
154
+ __param(9, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME)))
146
155
  ], MaintenanceProcessor);
147
156
  export { MaintenanceProcessor };
148
157
  /**
@@ -164,6 +173,8 @@ MaintenanceModule = __decorate([
164
173
  OrgsModule,
165
174
  // the removed org plugins' cleanup and its daily sweep (worker-only through this module)
166
175
  OwnedPluginCleanupModule,
176
+ // the scheduled task fire job and the scanner (worker-only through this module)
177
+ ScheduledTaskFireModule,
167
178
  QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
168
179
  ],
169
180
  providers: [
@@ -1,6 +1,7 @@
1
1
  import { type AppendEvent, EventLog } from "../conversations/event-log.js";
2
2
  import { KernelDb } from "../infra/database/kernel-db.js";
3
3
  import { TransactionHost } from "../infra/database/transaction-host.js";
4
+ import { ScheduledTaskRunTracker } from "../schedules/run-tracker.js";
4
5
  /** 运行的终态。 */
5
6
  export type TerminalStatus = "completed" | "failed" | "aborted";
6
7
  export declare const TERMINAL_STATUSES: ReadonlySet<string>;
@@ -31,7 +32,11 @@ export declare class RunFinish {
31
32
  private readonly kdb;
32
33
  private readonly eventLog;
33
34
  readonly txHost: TransactionHost;
34
- constructor(kdb: KernelDb, eventLog: EventLog, txHost: TransactionHost);
35
+ /** A scheduled task's run follows its kernel run's end in the same transaction (spec §6). */
36
+ private readonly taskRuns;
37
+ constructor(kdb: KernelDb, eventLog: EventLog, txHost: TransactionHost,
38
+ /** A scheduled task's run follows its kernel run's end in the same transaction (spec §6). */
39
+ taskRuns: Pick<ScheduledTaskRunTracker, "onRunFinished">);
35
40
  /**
36
41
  * 在调用方的事务内写运行的终态:会话行锁 → 运行仍未结束才写终态事件、推进 `consumed_seq`、更新 `runs.status`。
37
42
  * 返回终态事件的 seq;运行已是终态(例如已被其他副本标记失败)→ 返回 null,什么都不写。
@@ -14,6 +14,7 @@ import { pauseQueueIn } from "../conversations/queue.js";
14
14
  import { KernelDb } from "../infra/database/kernel-db.js";
15
15
  import { TransactionHost } from "../infra/database/transaction-host.js";
16
16
  import { Transactional } from "../infra/database/transactional.decorator.js";
17
+ import { ScheduledTaskRunTracker } from "../schedules/run-tracker.js";
17
18
  export const TERMINAL_STATUSES = new Set([
18
19
  "completed",
19
20
  "failed",
@@ -27,10 +28,14 @@ let RunFinish = class RunFinish {
27
28
  kdb;
28
29
  eventLog;
29
30
  txHost;
30
- constructor(kdb, eventLog, txHost) {
31
+ taskRuns;
32
+ constructor(kdb, eventLog, txHost,
33
+ /** A scheduled task's run follows its kernel run's end in the same transaction (spec §6). */
34
+ taskRuns) {
31
35
  this.kdb = kdb;
32
36
  this.eventLog = eventLog;
33
37
  this.txHost = txHost;
38
+ this.taskRuns = taskRuns;
34
39
  }
35
40
  /**
36
41
  * 在调用方的事务内写运行的终态:会话行锁 → 运行仍未结束才写终态事件、推进 `consumed_seq`、更新 `runs.status`。
@@ -76,6 +81,7 @@ let RunFinish = class RunFinish {
76
81
  .set({ status: a.status, finished_at: sql `now()` })
77
82
  .where("id", "=", a.runId)
78
83
  .execute();
84
+ await this.taskRuns.onRunFinished(a.runId, a.status, failureMessage(a.event));
79
85
  // a failed or stopped run pauses the queued messages until the person resumes (spec §5.7)
80
86
  if (a.status !== "completed")
81
87
  await pauseQueueIn(t, a.conversationId);
@@ -102,9 +108,17 @@ RunFinish = __decorate([
102
108
  Injectable(),
103
109
  __param(0, Inject(KernelDb)),
104
110
  __param(1, Inject(EventLog)),
105
- __param(2, Inject(TransactionHost))
111
+ __param(2, Inject(TransactionHost)),
112
+ __param(3, Inject(ScheduledTaskRunTracker))
106
113
  ], RunFinish);
107
114
  export { RunFinish };
115
+ /** The message of a `run_failed` event's error, for the task run (undefined otherwise). */
116
+ function failureMessage(event) {
117
+ if (event.type !== "run_failed")
118
+ return undefined;
119
+ const m = event.payload?.error?.message;
120
+ return typeof m === "string" ? m : undefined;
121
+ }
108
122
  /**
109
123
  * After a terminal event committed: notify the log, and for a failed or stopped run also send
110
124
  * `changed: queue` (the queue may have paused).
@@ -22,6 +22,8 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
22
22
  import { Transactional } from "../infra/database/transactional.decorator.js";
23
23
  import { KernelEvents } from "../infra/events/kernel-events.js";
24
24
  import { KERNEL_LOGGER } from "../infra/tokens.js";
25
+ import { linkStartedRunIn, unlinkRunIn } from "../schedules/run-tracker.js";
26
+ import { scheduledTaskRunAt } from "../schedules/unattended.js";
25
27
  import { ToolPipeline } from "../tools/pipeline.js";
26
28
  import { KyselyCheckpointSaver } from "./checkpointer.js";
27
29
  import { ConversationLock } from "./conversation-lock.js";
@@ -364,6 +366,9 @@ let RunQueue = class RunQueue {
364
366
  if (agent.deleted_at !== null)
365
367
  return null;
366
368
  const runId = randomUUID();
369
+ // scheduled tasks spec §4: a task's message makes the run unattended; a wake inherits it
370
+ // from the run that produced its event; a person's message never does
371
+ const taskRunId = await scheduledTaskRunAt(t, conversationId, trigger.seq);
367
372
  await t
368
373
  .insertInto("runs")
369
374
  .values({
@@ -376,8 +381,12 @@ let RunQueue = class RunQueue {
376
381
  // a chat conversation always has its person-triggered run before any wake (a wake event
377
382
  // comes from a run of it or of its tree); null would only make the wake run its own window
378
383
  budget_run_id: isWake ? await budgetWindowOf(t, conversationId) : null,
384
+ scheduled_task_run_id: taskRunId,
379
385
  })
380
386
  .execute();
387
+ // the task run its message started follows this run from now on (spec §6)
388
+ if (taskRunId !== null)
389
+ await linkStartedRunIn(t, taskRunId, runId, conversationId);
381
390
  return { runId, jobId, kind: "chat" };
382
391
  }
383
392
  /**
@@ -460,6 +469,8 @@ let RunQueue = class RunQueue {
460
469
  trigger_account_id: wake.triggerAccountId,
461
470
  trigger_seq: wake.seq,
462
471
  budget_run_id: await budgetWindowOf(t, rootId),
472
+ // a parent's message or a wake: unattended when the run behind it was (scheduled tasks §4)
473
+ scheduled_task_run_id: await scheduledTaskRunAt(t, conversationId, wake.seq),
463
474
  })
464
475
  .execute();
465
476
  return { runId, jobId, kind: "subagent" };
@@ -706,11 +717,16 @@ let RunQueue = class RunQueue {
706
717
  .catch((err) => this.log.warn("checkpoint delete failed", { runId, err }));
707
718
  }
708
719
  async deleteQueued(runId) {
709
- await this.db
720
+ const gone = await this.db
710
721
  .deleteFrom("runs")
711
722
  .where("id", "=", runId)
712
723
  .where("status", "=", "queued")
713
- .execute();
724
+ .returning("scheduled_task_run_id")
725
+ .executeTakeFirst();
726
+ // its scheduled task run waits for the message's next run again
727
+ if (gone?.scheduled_task_run_id != null) {
728
+ await unlinkRunIn(this.db, gone.scheduled_task_run_id, runId);
729
+ }
714
730
  }
715
731
  };
716
732
  __decorate([
@@ -722,6 +738,9 @@ __decorate([
722
738
  __decorate([
723
739
  Transactional({ propagation: "requires_new" })
724
740
  ], RunQueue.prototype, "repairOrphanIn", null);
741
+ __decorate([
742
+ Transactional()
743
+ ], RunQueue.prototype, "deleteQueued", null);
725
744
  RunQueue = __decorate([
726
745
  Injectable(),
727
746
  __param(0, Inject(KernelDb)),