@capybara-hq/kernel 0.1.0-rc.24 → 0.1.0-rc.26

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 (198) hide show
  1. package/dist/admin/admin.module.js +2 -0
  2. package/dist/admin/health.service.d.ts +2 -2
  3. package/dist/admin/health.service.js +6 -2
  4. package/dist/admin/outbox-dead.service.d.ts +7 -3
  5. package/dist/admin/outbox-dead.service.js +17 -6
  6. package/dist/admin/overview.service.d.ts +6 -6
  7. package/dist/admin/overview.service.js +7 -7
  8. package/dist/agents/agents.module.js +3 -0
  9. package/dist/agents/agents.service.d.ts +6 -1
  10. package/dist/agents/agents.service.js +11 -2
  11. package/dist/background/wake-rules.d.ts +5 -0
  12. package/dist/background/wake-rules.js +16 -5
  13. package/dist/catalog/catalog-import.module.js +0 -4
  14. package/dist/catalog/catalog-scope.service.d.ts +63 -26
  15. package/dist/catalog/catalog-scope.service.js +146 -71
  16. package/dist/catalog/catalog.module.js +4 -3
  17. package/dist/catalog/catalog.service.d.ts +11 -4
  18. package/dist/catalog/catalog.service.js +21 -5
  19. package/dist/catalog/cli.d.ts +4 -3
  20. package/dist/catalog/cli.js +8 -13
  21. package/dist/catalog/job-ids.d.ts +8 -20
  22. package/dist/catalog/job-ids.js +11 -16
  23. package/dist/catalog/marketplaces.service.d.ts +1 -1
  24. package/dist/catalog/org-plugin-uploads.d.ts +11 -4
  25. package/dist/catalog/org-plugin-uploads.js +21 -6
  26. package/dist/catalog/owned-items.d.ts +6 -13
  27. package/dist/catalog/owned-items.js +11 -29
  28. package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
  29. package/dist/catalog/owned-plugin-cleanup.js +12 -66
  30. package/dist/catalog/platform-components.d.ts +56 -34
  31. package/dist/catalog/platform-components.js +68 -78
  32. package/dist/catalog/plugin-import.d.ts +28 -25
  33. package/dist/catalog/plugin-import.js +90 -76
  34. package/dist/catalog/plugin-upgrade.d.ts +4 -4
  35. package/dist/catalog/plugin-upgrade.js +7 -19
  36. package/dist/catalog/scope-jobs.d.ts +59 -0
  37. package/dist/catalog/scope-jobs.js +65 -0
  38. package/dist/catalog/scope-sync-workers.d.ts +22 -0
  39. package/dist/catalog/scope-sync-workers.js +58 -2
  40. package/dist/catalog/scope-sync.d.ts +40 -65
  41. package/dist/catalog/scope-sync.js +114 -122
  42. package/dist/channels/channels.module.d.ts +9 -8
  43. package/dist/channels/channels.module.js +11 -10
  44. package/dist/channels/connections.service.d.ts +3 -3
  45. package/dist/channels/connections.service.js +6 -6
  46. package/dist/channels/deliver-job.d.ts +10 -3
  47. package/dist/channels/deliver-job.js +7 -3
  48. package/dist/channels/inbound.js +2 -2
  49. package/dist/channels/jobs.d.ts +11 -4
  50. package/dist/channels/jobs.js +18 -3
  51. package/dist/channels/outbound.d.ts +5 -2
  52. package/dist/channels/outbound.js +9 -2
  53. package/dist/channels/threads.d.ts +4 -3
  54. package/dist/channels/threads.js +4 -3
  55. package/dist/conversations/conversations.controller.d.ts +4 -2
  56. package/dist/conversations/conversations.controller.js +8 -4
  57. package/dist/conversations/conversations.service.d.ts +54 -9
  58. package/dist/conversations/conversations.service.js +94 -25
  59. package/dist/conversations/events.d.ts +27 -0
  60. package/dist/conversations/events.js +30 -1
  61. package/dist/conversations/source.d.ts +10 -0
  62. package/dist/conversations/source.js +17 -0
  63. package/dist/conversations/tree.d.ts +10 -0
  64. package/dist/conversations/tree.js +35 -0
  65. package/dist/db/kernel-schema.d.ts +19 -15
  66. package/dist/devices/devices.service.d.ts +25 -14
  67. package/dist/devices/devices.service.js +36 -30
  68. package/dist/evals/evals.module.js +5 -1
  69. package/dist/evals/jobs.d.ts +14 -6
  70. package/dist/evals/jobs.js +21 -19
  71. package/dist/evals/run-replay.d.ts +13 -2
  72. package/dist/evals/run-replay.js +28 -1
  73. package/dist/evals/runs.service.d.ts +5 -6
  74. package/dist/evals/runs.service.js +11 -33
  75. package/dist/evals/snapshot.d.ts +7 -1
  76. package/dist/evals/snapshot.js +2 -1
  77. package/dist/extensions/hooks.d.ts +22 -3
  78. package/dist/extensions/hooks.js +15 -1
  79. package/dist/extensions/lifecycle-hooks.d.ts +99 -0
  80. package/dist/extensions/lifecycle-hooks.js +273 -0
  81. package/dist/extensions/org-config.d.ts +13 -1
  82. package/dist/extensions/org-config.js +19 -2
  83. package/dist/extensions/permission-view.d.ts +9 -5
  84. package/dist/extensions/permission-view.js +9 -4
  85. package/dist/extensions/point-defaults.d.ts +28 -1
  86. package/dist/extensions/point-defaults.js +44 -13
  87. package/dist/inbox/inbox.service.d.ts +6 -20
  88. package/dist/inbox/inbox.service.js +0 -3
  89. package/dist/inbox/org-notices.d.ts +1 -1
  90. package/dist/inbox/platform-inbox.service.d.ts +3 -32
  91. package/dist/inbox/platform-inbox.service.js +5 -54
  92. package/dist/infra/outbox/outbox.service.d.ts +6 -0
  93. package/dist/infra/outbox/outbox.service.js +51 -17
  94. package/dist/infra/outbox/registry.d.ts +176 -0
  95. package/dist/infra/outbox/registry.js +183 -0
  96. package/dist/infra/queue/queue-names.d.ts +1 -1
  97. package/dist/infra/queue/queue-names.js +1 -0
  98. package/dist/local-devices/device-policy.service.d.ts +131 -26
  99. package/dist/local-devices/device-policy.service.js +341 -145
  100. package/dist/local-devices/machines.d.ts +17 -2
  101. package/dist/local-devices/machines.js +66 -5
  102. package/dist/local-devices/policy-op-jobs.d.ts +15 -0
  103. package/dist/local-devices/policy-op-jobs.js +12 -0
  104. package/dist/local-devices/policy-summary.d.ts +36 -3
  105. package/dist/local-devices/policy-summary.js +47 -1
  106. package/dist/orgs/directory-sync.js +1 -1
  107. package/dist/orgs/members.service.js +1 -1
  108. package/dist/orgs/offboarding.d.ts +50 -57
  109. package/dist/orgs/offboarding.js +117 -162
  110. package/dist/orgs/orgs.module.js +3 -0
  111. package/dist/orgs/orgs.service.d.ts +11 -5
  112. package/dist/orgs/orgs.service.js +19 -7
  113. package/dist/overrides.d.ts +7 -0
  114. package/dist/overrides.js +4 -0
  115. package/dist/platform/ops/conversations.ops.js +1 -1
  116. package/dist/platform/ops/inbox.ops.js +4 -20
  117. package/dist/platform/ops/platform.ops.d.ts +1 -0
  118. package/dist/platform/ops/platform.ops.js +6 -3
  119. package/dist/roles/worker-role.d.ts +10 -5
  120. package/dist/roles/worker-role.js +19 -9
  121. package/dist/roles/worker.module.js +2 -4
  122. package/dist/runtime/context/build.d.ts +3 -0
  123. package/dist/runtime/context/build.js +15 -0
  124. package/dist/runtime/context/plugin-context.d.ts +20 -0
  125. package/dist/runtime/context/plugin-context.js +30 -0
  126. package/dist/runtime/langgraph-loop.js +4 -0
  127. package/dist/runtime/loop.d.ts +8 -0
  128. package/dist/runtime/maintenance.d.ts +9 -4
  129. package/dist/runtime/maintenance.js +52 -13
  130. package/dist/runtime/model-call.d.ts +6 -1
  131. package/dist/runtime/model-call.js +1 -0
  132. package/dist/runtime/plugin-context.service.d.ts +75 -0
  133. package/dist/runtime/plugin-context.service.js +220 -0
  134. package/dist/runtime/run-context.service.d.ts +7 -3
  135. package/dist/runtime/run-context.service.js +12 -2
  136. package/dist/runtime/run-control.d.ts +6 -1
  137. package/dist/runtime/run-control.js +13 -0
  138. package/dist/runtime/run-finish.d.ts +2 -2
  139. package/dist/runtime/run-finish.js +11 -6
  140. package/dist/runtime/run-jobs.d.ts +21 -4
  141. package/dist/runtime/run-jobs.js +29 -6
  142. package/dist/runtime/run-orphan-check.job.d.ts +14 -11
  143. package/dist/runtime/run-orphan-check.job.js +16 -11
  144. package/dist/runtime/run-queue.d.ts +29 -24
  145. package/dist/runtime/run-queue.js +118 -57
  146. package/dist/runtime/run-status.d.ts +12 -0
  147. package/dist/runtime/run-status.js +21 -0
  148. package/dist/runtime/run-worker.d.ts +32 -3
  149. package/dist/runtime/run-worker.js +143 -21
  150. package/dist/runtime/runtime.module.js +2 -0
  151. package/dist/runtime/step-log.d.ts +6 -5
  152. package/dist/runtime/step-log.js +6 -5
  153. package/dist/runtime/subagents/dispatch.js +1 -0
  154. package/dist/runtime/subagents/skill-fork.js +3 -0
  155. package/dist/runtime/usage-report.d.ts +3 -1
  156. package/dist/runtime/usage-report.js +4 -2
  157. package/dist/runtime/waits.d.ts +16 -6
  158. package/dist/runtime/waits.js +37 -15
  159. package/dist/sandbox/jobs.d.ts +20 -10
  160. package/dist/sandbox/jobs.js +23 -17
  161. package/dist/schedules/fire.d.ts +25 -4
  162. package/dist/schedules/fire.js +87 -13
  163. package/dist/schedules/job-ids.d.ts +66 -17
  164. package/dist/schedules/job-ids.js +81 -13
  165. package/dist/schedules/run-tracker.js +2 -1
  166. package/dist/schedules/scan.job.d.ts +16 -35
  167. package/dist/schedules/scan.job.js +38 -147
  168. package/dist/schedules/scheduled-tasks.service.d.ts +6 -6
  169. package/dist/schedules/scheduled-tasks.service.js +17 -14
  170. package/dist/schedules/schedules.module.js +0 -4
  171. package/dist/skills/catalog.d.ts +7 -3
  172. package/dist/skills/catalog.js +12 -2
  173. package/dist/skills/changes.controller.d.ts +1 -1
  174. package/dist/skills/changes.controller.js +1 -1
  175. package/dist/tools/approvals.service.d.ts +111 -23
  176. package/dist/tools/approvals.service.js +326 -77
  177. package/dist/tools/builtins/sandbox/task-output.js +21 -6
  178. package/dist/tools/device-op-resolver.d.ts +40 -0
  179. package/dist/tools/device-op-resolver.js +121 -0
  180. package/dist/tools/network-asks.d.ts +1 -1
  181. package/dist/tools/network-asks.js +4 -3
  182. package/dist/tools/pipeline.d.ts +11 -2
  183. package/dist/tools/pipeline.js +5 -0
  184. package/dist/tools/questions.service.d.ts +15 -4
  185. package/dist/tools/questions.service.js +21 -19
  186. package/dist/tools/registry.d.ts +8 -1
  187. package/dist/tools/registry.js +9 -0
  188. package/dist/tools/tools.module.js +4 -0
  189. package/package.json +3 -3
  190. package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
  191. package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
  192. package/src/migrations/0123_pgvector.sql +27 -0
  193. package/src/migrations/0124_conversation_source.sql +36 -0
  194. package/src/migrations/0125_conversation_tree_stopped_at.sql +5 -0
  195. package/dist/catalog/platform-migration.d.ts +0 -115
  196. package/dist/catalog/platform-migration.js +0 -583
  197. package/dist/infra/queue/add-job-once.d.ts +0 -14
  198. package/dist/infra/queue/add-job-once.js +0 -16
@@ -34,12 +34,10 @@ const ADMIN_CONSOLE_PENDING = "Review it in the admin console (plugins, pending
34
34
  const requestName = (r) => r.item?.name ?? r.entry?.name ?? r.agent?.name ?? r.kind;
35
35
  const requester = (r) => r.requestedByName ?? "a deleted account";
36
36
  /** The name an org notice is about. */
37
- const orgNoticeName = (n) => n.kind === "skill_renamed" ? n.skillRenamed.to : n.detail.name;
37
+ const orgNoticeName = (n) => n.detail.name;
38
38
  /** An org notice in one English sentence, as the inbox tells it. */
39
39
  function orgNoticeSummary(n) {
40
40
  switch (n.kind) {
41
- case "skill_renamed":
42
- return `Skill "${n.skillRenamed.from}" was renamed to "${n.skillRenamed.to}": a platform skill on in this org has its name; agents keep it.`;
43
41
  case "item_shared": {
44
42
  const d = n.detail;
45
43
  return `${d.ref.kind} ${d.name} was shared with ${d.org === "all_orgs" ? "every org" : d.org.name}.`;
@@ -182,18 +180,6 @@ function platformRow(i) {
182
180
  next: "Open the plugin in the admin console to see the error and retry.",
183
181
  };
184
182
  }
185
- case "platform_migration": {
186
- const r = i.run;
187
- const failed = r.itemsFailed.slice(0, 5).join(", ");
188
- return {
189
- ...base,
190
- name: "platform components migration",
191
- state: r.stuck ? "stuck" : "failed",
192
- summary: r.stuck
193
- ? "The platform components migration run has not finished for over an hour; its worker stopped. The next run starts at the next worker start or day."
194
- : `The platform components migration could not write ${r.itemsFailed.length} catalog item(s): ${failed}${r.itemsFailed.length > 5 ? ", …" : ""}. The next run tries them again.`,
195
- };
196
- }
197
183
  case "ownership_transferred":
198
184
  return {
199
185
  ...base,
@@ -243,9 +229,7 @@ let InboxOperations = class InboxOperations {
243
229
  if (row === undefined)
244
230
  return null;
245
231
  const base = { id: row.id, createdAt: row.created_at.toISOString() };
246
- return (row.kind === "skill_renamed"
247
- ? { ...base, kind: row.kind, skillRenamed: row.detail }
248
- : { ...base, kind: row.kind, detail: row.detail });
232
+ return { ...base, kind: row.kind, detail: row.detail };
249
233
  }
250
234
  /** A platform notice by id (dismissed or not), or null. */
251
235
  async platformNotice(id) {
@@ -292,7 +276,7 @@ let InboxOperations = class InboxOperations {
292
276
  }),
293
277
  defineOperation({
294
278
  id: "inbox.dismiss-notice",
295
- description: "Dismiss an org notice from the inbox for every org admin (org admins): the id of a notice row from inbox.list (types skill_renamed, item_shared, shared_item_updated, upload_confirmed, upload_rejected). Approvals and requests are not dismissed here: they are decided on the web.",
279
+ description: "Dismiss an org notice from the inbox for every org admin (org admins): the id of a notice row from inbox.list (types item_shared, shared_item_updated, upload_confirmed, upload_rejected). Approvals and requests are not dismissed here: they are decided on the web.",
296
280
  input: NoticeInput,
297
281
  example: NOTICE_EXAMPLE,
298
282
  authz: { orgAction: "catalog.decide" },
@@ -323,7 +307,7 @@ let InboxOperations = class InboxOperations {
323
307
  }),
324
308
  defineOperation({
325
309
  id: "inbox.platform-list",
326
- description: "List the platform inbox (super admin), newest first, one compact row each (type, what, state, summary, next step): orgs' requests to the platform, plugins waiting for a permission confirmation, this org's uploads waiting for you, failed scope syncs, a failed or stuck components migration and ownership notices. Only the newest 100 items are read (truncated: more wait); listed is how many of them are shown, otherOrgItems how many are items of plugins other orgs own (only counted). total counts everything waiting on the platform, other orgs' items included, as the admin console does; byType splits total by type (everything waiting, not the listed page). Nothing is decided here: requests and confirmations are done in the admin console. Pages by 50: pass the returned next as params.cursor.",
310
+ description: "List the platform inbox (super admin), newest first, one compact row each (type, what, state, summary, next step): orgs' requests to the platform, plugins waiting for a permission confirmation, this org's uploads waiting for you, failed scope syncs and ownership notices. Only the newest 100 items are read (truncated: more wait); listed is how many of them are shown, otherOrgItems how many are items of plugins other orgs own (only counted). total counts everything waiting on the platform, other orgs' items included, as the admin console does; byType splits total by type (everything waiting, not the listed page). Nothing is decided here: requests and confirmations are done in the admin console. Pages by 50: pass the returned next as params.cursor.",
327
311
  input: z.object({ cursor: Cursor.optional() }).strict(),
328
312
  example: {},
329
313
  authz: { superAdmin: true },
@@ -32,6 +32,7 @@ export declare class PlatformOperationsProvider implements OnModuleInit {
32
32
  * default-on spec §8): every tool the org offers that the definition does not turn off, builtins
33
33
  * by name, extension tools as `plugin__<plugin>__<tool>`, connector tools as
34
34
  * `mcp__<slug>__<tool>`; the ones that cannot load now are listed with the registry's reason.
35
+ * Listed for the source of the conversation the operation runs in (memory plugin spec §6.2).
35
36
  */
36
37
  private toolKeys;
37
38
  onModuleInit(): void;
@@ -10,6 +10,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
10
10
  import { Inject, Injectable } from "@nestjs/common";
11
11
  import { z } from "zod";
12
12
  import { AgentsService } from "../../agents/agents.service.js";
13
+ import { conversationSourceOf } from "../../conversations/source.js";
13
14
  import { AccountsService } from "../../identity/accounts.js";
14
15
  import { PreferencesSchema } from "../../identity/me.schemas.js";
15
16
  import { KernelDb } from "../../infra/database/kernel-db.js";
@@ -81,12 +82,14 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
81
82
  * default-on spec §8): every tool the org offers that the definition does not turn off, builtins
82
83
  * by name, extension tools as `plugin__<plugin>__<tool>`, connector tools as
83
84
  * `mcp__<slug>__<tool>`; the ones that cannot load now are listed with the registry's reason.
85
+ * Listed for the source of the conversation the operation runs in (memory plugin spec §6.2).
84
86
  */
85
- async toolKeys(orgId, d) {
86
- const resolved = await this.tools.forAgent(orgId, {
87
+ async toolKeys(ctx, d) {
88
+ const resolved = await this.tools.forAgent(ctx.orgId, {
87
89
  disabledTools: d.disabledTools ?? [],
88
90
  subagentTools: d.subagentTools,
89
91
  subagents: d.subagents,
92
+ source: await conversationSourceOf(this.db.db, ctx.conversationId),
90
93
  });
91
94
  return {
92
95
  keys: [...new Set(resolved.tools.map((t) => t.key))],
@@ -142,7 +145,7 @@ let PlatformOperationsProvider = class PlatformOperationsProvider {
142
145
  const agent = await this.agents.get(ctx.orgId, ctx.agentId, ctx.actorAccountId);
143
146
  const d = agent.definition;
144
147
  const [{ keys: toolKeys, unavailable }, skills] = await Promise.all([
145
- this.toolKeys(ctx.orgId, d),
148
+ this.toolKeys(ctx, d),
146
149
  this.skillNames(ctx, d),
147
150
  ]);
148
151
  const sandbox = toolKeys.some((k) => SANDBOX_TOOL_NAMES.includes(k));
@@ -1,9 +1,10 @@
1
- import { PluginScopeSyncProcessor } from "../catalog/scope-sync.js";
1
+ import { PluginScopeSyncProcessor } from "../catalog/scope-sync-workers.js";
2
2
  import { ChannelsProcessor } from "../channels/channels.module.js";
3
3
  import { ChannelOutbound } from "../channels/outbound.js";
4
4
  import { TitleQueue } from "../conversations/titles.js";
5
5
  import { EvalsProcessor } from "../evals/evals.module.js";
6
6
  import { EvalWorker } from "../evals/worker.js";
7
+ import { LifecycleHooksProcessor } from "../extensions/lifecycle-hooks.js";
7
8
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
8
9
  import { LockedIntervalScheduler } from "../infra/schedule/locked-interval.js";
9
10
  import { ScheduledRuns } from "../infra/schedule/scheduled-runs.js";
@@ -15,8 +16,8 @@ import { UsageReporter } from "../runtime/usage-report.js";
15
16
  /**
16
17
  * The worker role's parts, driven by `KernelLifecycle` at fixed points of its start order and stop
17
18
  * phases (so they stay ordered after the extension load and before the extension host closes): the
18
- * queue consumers (runs, sub-agent runs, evals, maintenance, the plugin scope sync, channel
19
- * deliveries, `usage.report`, titles) and the schedulers of the `@LockedCron` / `@LockedInterval` jobs. Provided only by
19
+ * queue consumers (runs, sub-agent runs, evals, maintenance, the plugin scope sync, lifecycle
20
+ * hook deliveries, channel deliveries, `usage.report`, titles) and the schedulers of the `@LockedCron` / `@LockedInterval` jobs. Provided only by
20
21
  * `WorkerModule`, so a process without the worker role has none of them.
21
22
  */
22
23
  export declare class WorkerRole {
@@ -36,13 +37,14 @@ export declare class WorkerRole {
36
37
  private readonly evalsProcessor;
37
38
  private readonly maintenanceProcessor;
38
39
  private readonly scopeProcessor;
40
+ private readonly lifecycleProcessor;
39
41
  /** The startup config (the roles). */
40
42
  private readonly config;
41
43
  /** The run worker was started: the runs phase drains it. */
42
44
  private runsStarted;
43
45
  /** The eval worker was started: the runs phase drains it. */
44
46
  private evalsStarted;
45
- constructor(config: KernelConfigService, log: KernelLogger, intervals: LockedIntervalScheduler, scheduledRuns: ScheduledRuns, runWorker: RunWorker, evalWorker: EvalWorker, usageReport: UsageReporter, titles: TitleQueue, channelOutbound: ChannelOutbound, runsProcessor: RunsProcessor, subagentRunsProcessor: SubagentRunsProcessor, usageProcessor: UsageReportProcessor, titleProcessor: TitleProcessor, channelsProcessor: ChannelsProcessor, evalsProcessor: EvalsProcessor, maintenanceProcessor: MaintenanceProcessor, scopeProcessor: PluginScopeSyncProcessor);
47
+ constructor(config: KernelConfigService, log: KernelLogger, intervals: LockedIntervalScheduler, scheduledRuns: ScheduledRuns, runWorker: RunWorker, evalWorker: EvalWorker, usageReport: UsageReporter, titles: TitleQueue, channelOutbound: ChannelOutbound, runsProcessor: RunsProcessor, subagentRunsProcessor: SubagentRunsProcessor, usageProcessor: UsageReportProcessor, titleProcessor: TitleProcessor, channelsProcessor: ChannelsProcessor, evalsProcessor: EvalsProcessor, maintenanceProcessor: MaintenanceProcessor, scopeProcessor: PluginScopeSyncProcessor, lifecycleProcessor: LifecycleHooksProcessor);
46
48
  /**
47
49
  * Starts the queue consumers (after the extension load), then opens the schedules' start gate:
48
50
  * the cron and interval timers were armed at application bootstrap, but every tick before this
@@ -60,6 +62,9 @@ export declare class WorkerRole {
60
62
  drainRuns(budgetMs: number): Promise<void> | undefined;
61
63
  /** The eval cases in progress stop (undefined: never started). */
62
64
  drainEvals(budgetMs: number): Promise<void> | undefined;
63
- /** Closes the maintenance queue's worker and the plugin scope sync's (it stops between pairs). */
65
+ /**
66
+ * Closes the maintenance queue's worker, the plugin scope sync's (it stops between pairs) and
67
+ * the lifecycle hook deliveries' (calls in flight are aborted and retried).
68
+ */
64
69
  closeMaintenance(): Promise<void>;
65
70
  }
@@ -8,12 +8,13 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
10
  import { Inject, Injectable } from "@nestjs/common";
11
- import { PluginScopeSyncProcessor } from "../catalog/scope-sync.js";
11
+ import { PluginScopeSyncProcessor } from "../catalog/scope-sync-workers.js";
12
12
  import { ChannelsProcessor } from "../channels/channels.module.js";
13
13
  import { ChannelOutbound } from "../channels/outbound.js";
14
14
  import { TitleQueue } from "../conversations/titles.js";
15
15
  import { EvalsProcessor } from "../evals/evals.module.js";
16
16
  import { EvalWorker } from "../evals/worker.js";
17
+ import { LifecycleHooksProcessor } from "../extensions/lifecycle-hooks.js";
17
18
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
18
19
  import { LockedIntervalScheduler } from "../infra/schedule/locked-interval.js";
19
20
  import { ScheduledRuns } from "../infra/schedule/scheduled-runs.js";
@@ -25,8 +26,8 @@ import { UsageReporter } from "../runtime/usage-report.js";
25
26
  /**
26
27
  * The worker role's parts, driven by `KernelLifecycle` at fixed points of its start order and stop
27
28
  * phases (so they stay ordered after the extension load and before the extension host closes): the
28
- * queue consumers (runs, sub-agent runs, evals, maintenance, the plugin scope sync, channel
29
- * deliveries, `usage.report`, titles) and the schedulers of the `@LockedCron` / `@LockedInterval` jobs. Provided only by
29
+ * queue consumers (runs, sub-agent runs, evals, maintenance, the plugin scope sync, lifecycle
30
+ * hook deliveries, channel deliveries, `usage.report`, titles) and the schedulers of the `@LockedCron` / `@LockedInterval` jobs. Provided only by
30
31
  * `WorkerModule`, so a process without the worker role has none of them.
31
32
  */
32
33
  let WorkerRole = class WorkerRole {
@@ -46,13 +47,14 @@ let WorkerRole = class WorkerRole {
46
47
  evalsProcessor;
47
48
  maintenanceProcessor;
48
49
  scopeProcessor;
50
+ lifecycleProcessor;
49
51
  /** The startup config (the roles). */
50
52
  config;
51
53
  /** The run worker was started: the runs phase drains it. */
52
54
  runsStarted = false;
53
55
  /** The eval worker was started: the runs phase drains it. */
54
56
  evalsStarted = false;
55
- constructor(config, log, intervals, scheduledRuns, runWorker, evalWorker, usageReport, titles, channelOutbound, runsProcessor, subagentRunsProcessor, usageProcessor, titleProcessor, channelsProcessor, evalsProcessor, maintenanceProcessor, scopeProcessor) {
57
+ constructor(config, log, intervals, scheduledRuns, runWorker, evalWorker, usageReport, titles, channelOutbound, runsProcessor, subagentRunsProcessor, usageProcessor, titleProcessor, channelsProcessor, evalsProcessor, maintenanceProcessor, scopeProcessor, lifecycleProcessor) {
56
58
  this.log = log;
57
59
  this.intervals = intervals;
58
60
  this.scheduledRuns = scheduledRuns;
@@ -69,6 +71,7 @@ let WorkerRole = class WorkerRole {
69
71
  this.evalsProcessor = evalsProcessor;
70
72
  this.maintenanceProcessor = maintenanceProcessor;
71
73
  this.scopeProcessor = scopeProcessor;
74
+ this.lifecycleProcessor = lifecycleProcessor;
72
75
  this.config = config.get();
73
76
  }
74
77
  /**
@@ -96,6 +99,7 @@ let WorkerRole = class WorkerRole {
96
99
  checkpoint();
97
100
  this.maintenanceProcessor.start();
98
101
  this.scopeProcessor.start();
102
+ this.lifecycleProcessor.start();
99
103
  this.channelOutbound.startWorker(this.channelsProcessor.worker);
100
104
  this.usageReport.startWorker(this.usageProcessor.worker);
101
105
  this.titles.startWorker(this.titleProcessor.worker);
@@ -105,8 +109,6 @@ let WorkerRole = class WorkerRole {
105
109
  // The gate opens: from here on ticks run; the runOnStart jobs run once now, in the background
106
110
  this.scheduledRuns.open();
107
111
  this.intervals.runStartJobs();
108
- // Every plugin's scope converges once per worker start (a failed or lost sync is retried)
109
- this.scopeProcessor.resync();
110
112
  }
111
113
  /**
112
114
  * Closes the start gate and stops the schedulers (no new tick; the runs in progress get the stop
@@ -123,9 +125,16 @@ let WorkerRole = class WorkerRole {
123
125
  drainEvals(budgetMs) {
124
126
  return this.evalsStarted ? this.evalWorker.close(budgetMs) : undefined;
125
127
  }
126
- /** Closes the maintenance queue's worker and the plugin scope sync's (it stops between pairs). */
128
+ /**
129
+ * Closes the maintenance queue's worker, the plugin scope sync's (it stops between pairs) and
130
+ * the lifecycle hook deliveries' (calls in flight are aborted and retried).
131
+ */
127
132
  async closeMaintenance() {
128
- await Promise.all([this.maintenanceProcessor.worker.close(), this.scopeProcessor.close()]);
133
+ await Promise.all([
134
+ this.maintenanceProcessor.worker.close(),
135
+ this.scopeProcessor.close(),
136
+ this.lifecycleProcessor.close(),
137
+ ]);
129
138
  }
130
139
  };
131
140
  WorkerRole = __decorate([
@@ -146,6 +155,7 @@ WorkerRole = __decorate([
146
155
  __param(13, Inject(ChannelsProcessor)),
147
156
  __param(14, Inject(EvalsProcessor)),
148
157
  __param(15, Inject(MaintenanceProcessor)),
149
- __param(16, Inject(PluginScopeSyncProcessor))
158
+ __param(16, Inject(PluginScopeSyncProcessor)),
159
+ __param(17, Inject(LifecycleHooksProcessor))
150
160
  ], WorkerRole);
151
161
  export { WorkerRole };
@@ -10,7 +10,6 @@ import { BackgroundModule } from "../background/background.module.js";
10
10
  import { BackgroundTaskPollJob } from "../background/poll.job.js";
11
11
  import { CatalogModule } from "../catalog/catalog.module.js";
12
12
  import { CatalogJobs } from "../catalog/jobs.js";
13
- import { PlatformMigration } from "../catalog/platform-migration.js";
14
13
  import { PluginScopeWorkersModule } from "../catalog/scope-sync-workers.js";
15
14
  import { ChannelsModule, ChannelWorkersModule } from "../channels/channels.module.js";
16
15
  import { GatewayCheckJob } from "../channels/supervisor.js";
@@ -24,11 +23,11 @@ import { LegacySandboxCleanupJob } from "../devices/legacy-cleanup.job.js";
24
23
  import { EvalWorkersModule } from "../evals/evals.module.js";
25
24
  import { ExtensionsModule } from "../extensions/extensions.module.js";
26
25
  import { PluginsReconcileJob } from "../extensions/lifecycle.js";
26
+ import { LifecycleHookWorkersModule } from "../extensions/lifecycle-hooks.js";
27
27
  import { InitialPasswordCleanupJob } from "../identity/initial-passwords.job.js";
28
28
  import { OutboxRelayModule } from "../infra/outbox/outbox-relay.module.js";
29
29
  import { KernelScheduleModule } from "../infra/schedule/schedule.module.js";
30
30
  import { DirectoryScanJob } from "../orgs/directory-sync.js";
31
- import { OffboardSweepJob } from "../orgs/offboarding.js";
32
31
  import { OrgLockModule } from "../orgs/org-lock.js";
33
32
  import { OrgsModule } from "../orgs/orgs.module.js";
34
33
  import { MaintenanceModule } from "../runtime/maintenance.js";
@@ -69,6 +68,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
69
68
  ChannelWorkersModule.forRoot(config),
70
69
  MaintenanceModule,
71
70
  PluginScopeWorkersModule,
71
+ LifecycleHookWorkersModule,
72
72
  // the domains the fixed-period jobs call
73
73
  RuntimeModule,
74
74
  RunJobsModule,
@@ -100,9 +100,7 @@ let WorkerModule = WorkerModule_1 = class WorkerModule {
100
100
  InitialPasswordCleanupJob,
101
101
  ClientReportsCron,
102
102
  CatalogJobs,
103
- PlatformMigration,
104
103
  DirectoryScanJob,
105
- OffboardSweepJob,
106
104
  PluginsReconcileJob,
107
105
  GatewayCheckJob,
108
106
  WorkerRole,
@@ -237,6 +237,9 @@ export declare function summaryText(summary: string): string;
237
237
  * summary: the latest one follows the summary) /
238
238
  * `run_context` (`role: "system"` at its position, `runContextText`; covered by the summary: the
239
239
  * current context follows the summary whole; a user message's sender line by `senderLines`) /
240
+ * `plugin_context` (`role: "system"` at its position, `pluginContextText`, nothing for empty
241
+ * text; covered by the summary: the latest non-empty one per plugin, in plugin-name order,
242
+ * follows the summary after `device_paths`) /
240
243
  * `background_task_finished`(`role: "system"`, the text of
241
244
  * {@link renderBackgroundFinished}, placed like a user_message)/ `subagent_delivery` (likewise,
242
245
  * the text of {@link renderSubagentDelivery});`tool_result` 事件还原为紧随其 assistant 消息之后的一条 user 消息
@@ -6,6 +6,7 @@ import { catalogText, isAlwaysLoaded } from "../../tools/builtins/tool-search.js
6
6
  import { renderSubagentDelivery } from "../subagents/render.js";
7
7
  import { devicePathsAt, devicePathsText } from "./device-paths.js";
8
8
  import { LEGACY_CATALOG_HEADER, LEGACY_SKILL_TEXTS, usesLegacyToolNames } from "./legacy-text.js";
9
+ import { pluginContextsAfterSummary, pluginContextText } from "./plugin-context.js";
9
10
  import { currentRunContext, runContextEvents, runContextFullText, runContextText, scheduleHeader, senderLines, } from "./run-context.js";
10
11
  import { invokedSkillNames, invokedSkillsText, SKILL_TEXTS, skillsCatalogEvents, snapshotChangeText, snapshotCurrentText, snapshotSystemText, systemSkillsCatalog, } from "./skills.js";
11
12
  import { toolsChangedText } from "./tool-changes.js";
@@ -341,6 +342,9 @@ export function summaryText(summary) {
341
342
  * summary: the latest one follows the summary) /
342
343
  * `run_context` (`role: "system"` at its position, `runContextText`; covered by the summary: the
343
344
  * current context follows the summary whole; a user message's sender line by `senderLines`) /
345
+ * `plugin_context` (`role: "system"` at its position, `pluginContextText`, nothing for empty
346
+ * text; covered by the summary: the latest non-empty one per plugin, in plugin-name order,
347
+ * follows the summary after `device_paths`) /
344
348
  * `background_task_finished`(`role: "system"`, the text of
345
349
  * {@link renderBackgroundFinished}, placed like a user_message)/ `subagent_delivery` (likewise,
346
350
  * the text of {@link renderSubagentDelivery});`tool_result` 事件还原为紧随其 assistant 消息之后的一条 user 消息
@@ -561,6 +565,10 @@ export function buildModelRequest(input) {
561
565
  if (paths) {
562
566
  messages.push({ role: "system", content: devicePathsText(paths.payload) });
563
567
  }
568
+ // memory spec §6.1: each plugin's context in effect at the boundary follows the summary again
569
+ for (const text of pluginContextsAfterSummary(events, after)) {
570
+ messages.push({ role: "system", content: text });
571
+ }
564
572
  // 被摘要覆盖的变更提醒不再发送:摘要时生效的目录与 system 里的不同 → 紧随摘要补上完整的当前目录
565
573
  const covered = skillSnaps.filter((e) => e.seq <= after).at(-1);
566
574
  // (phase B: plus the current pinned skills and tool-name map when they differ from system's)
@@ -639,6 +647,13 @@ export function buildModelRequest(input) {
639
647
  case "device_paths":
640
648
  messages.push({ role: "system", content: devicePathsText(e.payload) });
641
649
  break;
650
+ // a context@1 provider's text (memory spec §6.1); empty text (nothing any more) renders nothing
651
+ case "plugin_context": {
652
+ const text = pluginContextText(e.payload);
653
+ if (text !== "")
654
+ messages.push({ role: "system", content: text });
655
+ break;
656
+ }
642
657
  // hook_denied is not model-visible: the model sees only the denied tool call's tool_result
643
658
  // (and nothing for a denied model request), so request building skips it
644
659
  case "hook_denied":
@@ -0,0 +1,20 @@
1
+ import type { ConversationEvent, EventPayload } from "../../conversations/events.js";
2
+ /**
3
+ * The `plugin_context` event (memory spec §6.1): the text a `context@1` provider added to the
4
+ * conversation at a run start, written only when it changed. Model-visible: rendered at its
5
+ * position as a system message; after a summary that covers it, the latest non-empty one per
6
+ * plugin follows the summary again. Its text is in the log, so it needs no `DriftReason`.
7
+ */
8
+ /** The payload of a `plugin_context` event. */
9
+ export type PluginContextPayload = EventPayload<"plugin_context">;
10
+ /** The idempotency key of a run's `plugin_context` event for `plugin`. */
11
+ export declare const pluginContextIdemKey: (runId: string, plugin: string) => string;
12
+ /** The system message of a `plugin_context` event (empty text renders nothing: ""). */
13
+ export declare function pluginContextText(p: PluginContextPayload): string;
14
+ /** The latest `plugin_context` payload of each plugin at or before `uptoSeq`. */
15
+ export declare function latestPluginContexts(events: readonly ConversationEvent[], uptoSeq: number): Map<string, PluginContextPayload>;
16
+ /**
17
+ * The system messages that follow a summary covering up to `after`: the latest non-empty
18
+ * `plugin_context` of each plugin, in plugin-name order.
19
+ */
20
+ export declare function pluginContextsAfterSummary(events: readonly ConversationEvent[], after: number): string[];
@@ -0,0 +1,30 @@
1
+ /** The idempotency key of a run's `plugin_context` event for `plugin`. */
2
+ export const pluginContextIdemKey = (runId, plugin) => `plugin_context:${runId}:${plugin}`;
3
+ /** The system message of a `plugin_context` event (empty text renders nothing: ""). */
4
+ export function pluginContextText(p) {
5
+ if (p.text === "")
6
+ return "";
7
+ return `<plugin-context plugin="${p.plugin}">\n${p.text}\n</plugin-context>`;
8
+ }
9
+ /** The latest `plugin_context` payload of each plugin at or before `uptoSeq`. */
10
+ export function latestPluginContexts(events, uptoSeq) {
11
+ const latest = new Map();
12
+ for (const e of events) {
13
+ if (e.type !== "plugin_context" || e.seq > uptoSeq)
14
+ continue;
15
+ const prev = latest.get(e.payload.plugin);
16
+ if (!prev || e.seq > prev.seq)
17
+ latest.set(e.payload.plugin, { seq: e.seq, payload: e.payload });
18
+ }
19
+ return new Map([...latest].map(([plugin, v]) => [plugin, v.payload]));
20
+ }
21
+ /**
22
+ * The system messages that follow a summary covering up to `after`: the latest non-empty
23
+ * `plugin_context` of each plugin, in plugin-name order.
24
+ */
25
+ export function pluginContextsAfterSummary(events, after) {
26
+ return [...latestPluginContexts(events, after)]
27
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
28
+ .map(([, p]) => pluginContextText(p))
29
+ .filter((text) => text !== "");
30
+ }
@@ -148,6 +148,7 @@ export class LangGraphLoop {
148
148
  mediaCache,
149
149
  agentId: ctx.agentId,
150
150
  accountId: ctx.triggerAccountId,
151
+ source: ctx.source,
151
152
  ...(ctx.conversationKind !== undefined && { conversationKind: ctx.conversationKind }),
152
153
  ...(ctx.parent !== undefined && { parentRunId: ctx.parent.runId, parent: ctx.parent }),
153
154
  ...(ctx.subagent === true && { subagent: true }),
@@ -221,6 +222,9 @@ export class LangGraphLoop {
221
222
  agentVersion: ctx.agentVersion,
222
223
  agent: ctx.agent,
223
224
  accountId: ctx.triggerAccountId,
225
+ source: ctx.source,
226
+ ownerAccountId: ctx.ownerAccountId,
227
+ role: ctx.role,
224
228
  signal: ctx.signal,
225
229
  ...(ctx.limits.deadline !== undefined && { deadline: ctx.limits.deadline }),
226
230
  ...(ctx.conversationKind !== undefined && { conversationKind: ctx.conversationKind }),
@@ -1,6 +1,8 @@
1
+ import type { ConversationSource } from "@capybara-hq/plugin-sdk";
1
2
  import type { AgentDefinition } from "../agents/definition.js";
2
3
  import type { KernelConfig } from "../config/schema.js";
3
4
  import type { AbsorbableEvent } from "../conversations/events.js";
5
+ import type { OrgRole } from "../orgs/roles.js";
4
6
  import type { StreamSink } from "./model-call.js";
5
7
  /** 一次运行交给 loop 的上下文(spec 4.4)。 */
6
8
  export interface LoopContext {
@@ -12,6 +14,12 @@ export interface LoopContext {
12
14
  agentId: string;
13
15
  /** 发起本次运行的人(对话发起人):工具调用用其个人凭据,审计记为 actor。 */
14
16
  triggerAccountId: string;
17
+ /** The conversation's source, read once per run (memory plugin spec §6.2). */
18
+ source: ConversationSource;
19
+ /** The conversation's owner (`conversations.account_id`); plugin tool handlers receive it. */
20
+ ownerAccountId: string;
21
+ /** The trigger account's org role when the run starts; plugin tool handlers receive it. */
22
+ role: OrgRole;
15
23
  agent: AgentDefinition;
16
24
  agentVersion: number;
17
25
  /** 用户中止或到达 deadline 时触发:进行中的模型调用立即停下。 */
@@ -4,8 +4,10 @@ import { OwnedPluginCleanup } from "../catalog/owned-plugin-cleanup.js";
4
4
  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
+ import { Offboarding } from "../orgs/offboarding.js";
7
8
  import { SandboxService } from "../sandbox/sandbox.service.js";
8
9
  import { ScheduledTaskFirer } from "../schedules/fire.js";
10
+ import { DeviceOpResolver } from "../tools/device-op-resolver.js";
9
11
  import { ToolDrift } from "../tools/drift.js";
10
12
  import { RunQueue } from "./run-queue.js";
11
13
  import { Waits } from "./waits.js";
@@ -16,27 +18,30 @@ export declare const MAINTENANCE_CONCURRENCY = 5;
16
18
  * The one consumer of `cap-kernel-maintenance` (decision R11 of the Nest kernel spec): dispatches
17
19
  * each one-off job by name to its domain (`wait-timeout` → {@link Waits}, `sandbox-destroy-for` and
18
20
  * `sandbox-reset-files` → {@link SandboxService}, `drift-connector` → {@link ToolDrift}, `directory-sync` →
19
- * {@link DirectorySync}, `bg-wake` and `ensure-run` → {@link RunQueue.ensureRun}, `stop-tree` →
21
+ * {@link DirectorySync}, `run-stop` → {@link Offboarding.stopRun}, `bg-wake` and `ensure-run` → {@link RunQueue.ensureRun}, `stop-tree` →
20
22
  * {@link processStopTree} and `deliver-to-parent` → {@link processDeliverToParent} (both in
21
23
  * runtime/subagents/tree-jobs.ts, over {@link Waits}), `owned-plugin-cleanup` →
22
- * {@link OwnedPluginCleanup}, `sched-fire` → {@link ScheduledTaskFirer}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
24
+ * {@link OwnedPluginCleanup}, `sched-fire` and `sched-settle` → {@link ScheduledTaskFirer}, `device-op-resolve` →
25
+ * {@link DeviceOpResolver}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
23
26
  * such as `sandbox-suspend-idle`) runs that job's body once ({@link ScheduledRuns.runNow}), as the
24
27
  * maintenance handlers did when those jobs were schedulers on this queue: an operator's or a
25
28
  * test's one-off run, not a second schedule (decision R23 of the Nest kernel spec). Any other
26
- * name is warned and completes. The producers are the domains' own (same names, ids and options).
29
+ * name fails the job (BullMQ retries it; in a rolling deploy a newer replica knows it). The producers are the domains' own (same names, ids and options).
27
30
  */
28
31
  export declare class MaintenanceProcessor extends WorkerHost<Worker> {
29
32
  private readonly waits;
30
33
  private readonly sandbox;
31
34
  private readonly drift;
32
35
  private readonly directory;
36
+ private readonly offboarding;
33
37
  private readonly runs;
34
38
  private readonly ownedCleanup;
35
39
  private readonly scheduledTasks;
40
+ private readonly deviceOps;
36
41
  private readonly scheduled;
37
42
  private readonly log;
38
43
  private readonly queue;
39
- constructor(waits: Pick<Waits, "processTimeout" | "childFinished" | "stopChildren" | "stopDescendantsOfStopped" | "stopTree">, 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">);
44
+ constructor(waits: Pick<Waits, "processTimeout" | "childFinished" | "stopChildren" | "stopDescendantsOfStopped" | "stopTree">, sandbox: Pick<SandboxService, "destroyDevice" | "resetFileSystem">, drift: Pick<ToolDrift, "check">, directory: Pick<DirectorySync, "runSyncJob">, offboarding: Pick<Offboarding, "stopRun">, runs: Pick<RunQueue, "ensureRun">, ownedCleanup: Pick<OwnedPluginCleanup, "run">, scheduledTasks: Pick<ScheduledTaskFirer, "fire" | "settle">, deviceOps: Pick<DeviceOpResolver, "resolve">, scheduled: Pick<ScheduledRuns, "runNow">, log: Pick<KernelLogger, "error" | "warn">, queue: Pick<Queue, "removeJobScheduler">);
40
45
  /**
41
46
  * Removes the retired job schedulers (`RETIRED_SCHEDULER_IDS`, runtime/retired-schedulers.ts): every fixed-period
42
47
  * maintenance job is a `@LockedCron` / `@LockedInterval` now. Idempotent; called once when a
@@ -19,13 +19,16 @@ import { runJob } from "../infra/queue/run-job.js";
19
19
  import { KernelScheduleModule } from "../infra/schedule/schedule.module.js";
20
20
  import { ScheduledRuns } from "../infra/schedule/scheduled-runs.js";
21
21
  import { KERNEL_LOGGER } from "../infra/tokens.js";
22
+ import { DEVICE_OP_RESOLVE_JOB, } from "../local-devices/policy-op-jobs.js";
22
23
  import { DIRECTORY_SYNC_JOB, DirectorySync } from "../orgs/directory-sync.js";
24
+ import { Offboarding, RUN_STOP_JOB } from "../orgs/offboarding.js";
23
25
  import { OrgsModule } from "../orgs/orgs.module.js";
24
26
  import { runSandboxDestroyFor, runSandboxResetFiles, SANDBOX_DESTROY_FOR_JOB, SANDBOX_RESET_FILES_JOB, } from "../sandbox/jobs.js";
25
27
  import { SandboxModule } from "../sandbox/sandbox.module.js";
26
28
  import { SandboxService } from "../sandbox/sandbox.service.js";
27
29
  import { ScheduledTaskFireModule, ScheduledTaskFirer } from "../schedules/fire.js";
28
- import { SCHED_FIRE_JOB } from "../schedules/job-ids.js";
30
+ import { SCHED_FIRE_JOB, SCHED_SETTLE_JOB, } from "../schedules/job-ids.js";
31
+ import { DeviceOpResolver } from "../tools/device-op-resolver.js";
29
32
  import { ToolDrift } from "../tools/drift.js";
30
33
  import { DRIFT_CONNECTOR_JOB } from "../tools/job-ids.js";
31
34
  import { ToolsModule } from "../tools/tools.module.js";
@@ -42,35 +45,40 @@ export const MAINTENANCE_CONCURRENCY = 5;
42
45
  * The one consumer of `cap-kernel-maintenance` (decision R11 of the Nest kernel spec): dispatches
43
46
  * each one-off job by name to its domain (`wait-timeout` → {@link Waits}, `sandbox-destroy-for` and
44
47
  * `sandbox-reset-files` → {@link SandboxService}, `drift-connector` → {@link ToolDrift}, `directory-sync` →
45
- * {@link DirectorySync}, `bg-wake` and `ensure-run` → {@link RunQueue.ensureRun}, `stop-tree` →
48
+ * {@link DirectorySync}, `run-stop` → {@link Offboarding.stopRun}, `bg-wake` and `ensure-run` → {@link RunQueue.ensureRun}, `stop-tree` →
46
49
  * {@link processStopTree} and `deliver-to-parent` → {@link processDeliverToParent} (both in
47
50
  * runtime/subagents/tree-jobs.ts, over {@link Waits}), `owned-plugin-cleanup` →
48
- * {@link OwnedPluginCleanup}, `sched-fire` → {@link ScheduledTaskFirer}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
51
+ * {@link OwnedPluginCleanup}, `sched-fire` and `sched-settle` → {@link ScheduledTaskFirer}, `device-op-resolve` →
52
+ * {@link DeviceOpResolver}). The name of a periodic job (a `@LockedCron` / `@LockedInterval` job,
49
53
  * such as `sandbox-suspend-idle`) runs that job's body once ({@link ScheduledRuns.runNow}), as the
50
54
  * maintenance handlers did when those jobs were schedulers on this queue: an operator's or a
51
55
  * test's one-off run, not a second schedule (decision R23 of the Nest kernel spec). Any other
52
- * name is warned and completes. The producers are the domains' own (same names, ids and options).
56
+ * name fails the job (BullMQ retries it; in a rolling deploy a newer replica knows it). The producers are the domains' own (same names, ids and options).
53
57
  */
54
58
  let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
55
59
  waits;
56
60
  sandbox;
57
61
  drift;
58
62
  directory;
63
+ offboarding;
59
64
  runs;
60
65
  ownedCleanup;
61
66
  scheduledTasks;
67
+ deviceOps;
62
68
  scheduled;
63
69
  log;
64
70
  queue;
65
- constructor(waits, sandbox, drift, directory, runs, ownedCleanup, scheduledTasks, scheduled, log, queue) {
71
+ constructor(waits, sandbox, drift, directory, offboarding, runs, ownedCleanup, scheduledTasks, deviceOps, scheduled, log, queue) {
66
72
  super();
67
73
  this.waits = waits;
68
74
  this.sandbox = sandbox;
69
75
  this.drift = drift;
70
76
  this.directory = directory;
77
+ this.offboarding = offboarding;
71
78
  this.runs = runs;
72
79
  this.ownedCleanup = ownedCleanup;
73
80
  this.scheduledTasks = scheduledTasks;
81
+ this.deviceOps = deviceOps;
74
82
  this.scheduled = scheduled;
75
83
  this.log = log;
76
84
  this.queue = queue;
@@ -109,6 +117,23 @@ let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
109
117
  case DIRECTORY_SYNC_JOB:
110
118
  await this.directory.runSyncJob(job.data);
111
119
  return;
120
+ // a departed member's run stop, owed by the offboarding (transactional outbox spec §8.1)
121
+ case RUN_STOP_JOB:
122
+ try {
123
+ await this.offboarding.stopRun(job.data);
124
+ }
125
+ catch (err) {
126
+ // nothing stops the run after its last attempt: say so (the failed job is kept)
127
+ if (job.attemptsMade + 1 >= (job.opts.attempts ?? 1)) {
128
+ this.log.error("a departed member's run could not be stopped", {
129
+ jobId: job.id,
130
+ runId: job.data.runId,
131
+ err,
132
+ });
133
+ }
134
+ throw err;
135
+ }
136
+ return;
112
137
  // the end of a conversation's wake window (background Bash spec §3)
113
138
  case BG_WAKE_JOB: {
114
139
  // fired before the window ended (clock skew): it reschedules under its next retry id
@@ -136,10 +161,22 @@ let MaintenanceProcessor = class MaintenanceProcessor extends WorkerHost {
136
161
  case SCHED_FIRE_JOB:
137
162
  await this.scheduledTasks.fire(job.data.taskRunId);
138
163
  return;
164
+ // a task run's settle check, claimed or unfired (ruling R26)
165
+ case SCHED_SETTLE_JOB:
166
+ await this.scheduledTasks.settle(job.data);
167
+ return;
168
+ // a device policy op whose outcome was not recorded (transactional outbox spec §7)
169
+ case DEVICE_OP_RESOLVE_JOB:
170
+ await this.deviceOps.resolve(job.data);
171
+ return;
139
172
  default:
140
173
  if (await this.scheduled.runNow(job.name))
141
174
  return;
142
- this.log.warn("unknown maintenance job", { name: job.name, jobId: job.id });
175
+ // never completed unrun: in a rolling deploy an old worker meets a job a newer replica
176
+ // owed (its outbox row is gone once added); failing it leaves it to BullMQ's retries, so a
177
+ // newer replica can take it within the job's attempts (old workers still go first, see
178
+ // docs/deploy/upgrade.md)
179
+ throw new Error(`unknown maintenance job "${job.name}"`);
143
180
  }
144
181
  }
145
182
  failed(job, err) {
@@ -161,12 +198,14 @@ MaintenanceProcessor = __decorate([
161
198
  __param(1, Inject(SandboxService)),
162
199
  __param(2, Inject(ToolDrift)),
163
200
  __param(3, Inject(DirectorySync)),
164
- __param(4, Inject(RunQueue)),
165
- __param(5, Inject(OwnedPluginCleanup)),
166
- __param(6, Inject(ScheduledTaskFirer)),
167
- __param(7, Inject(ScheduledRuns)),
168
- __param(8, Inject(KERNEL_LOGGER)),
169
- __param(9, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME)))
201
+ __param(4, Inject(Offboarding)),
202
+ __param(5, Inject(RunQueue)),
203
+ __param(6, Inject(OwnedPluginCleanup)),
204
+ __param(7, Inject(ScheduledTaskFirer)),
205
+ __param(8, Inject(DeviceOpResolver)),
206
+ __param(9, Inject(ScheduledRuns)),
207
+ __param(10, Inject(KERNEL_LOGGER)),
208
+ __param(11, Inject(getQueueToken(MAINTENANCE_QUEUE_NAME)))
170
209
  ], MaintenanceProcessor);
171
210
  export { MaintenanceProcessor };
172
211
  /**
@@ -186,7 +225,7 @@ MaintenanceModule = __decorate([
186
225
  SandboxModule,
187
226
  ToolsModule,
188
227
  OrgsModule,
189
- // the removed org plugins' cleanup and its daily sweep (worker-only through this module)
228
+ // the removed org plugins' cleanup job (worker-only through this module)
190
229
  OwnedPluginCleanupModule,
191
230
  // the scheduled task fire job and the scanner (worker-only through this module)
192
231
  ScheduledTaskFireModule,