@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
@@ -1,4 +1,4 @@
1
- import { DOCUMENT_MEDIA_TYPES, hasControlChars, IMAGE_MEDIA_TYPES, isAttachmentId, isSkillName, MEDIA_NAME_MAX_CHARS, } from "@capybara-hq/plugin-sdk";
1
+ import { CONTEXT_LIMITS, DOCUMENT_MEDIA_TYPES, hasControlChars, IMAGE_MEDIA_TYPES, isAttachmentId, isSkillName, MEDIA_NAME_MAX_CHARS, } from "@capybara-hq/plugin-sdk";
2
2
  import { z } from "zod";
3
3
  import { AgentDefinitionSchema, DisabledSkillRef, DisabledToolRef, ModelRef, } from "../agents/definition.js";
4
4
  import { ORG_ROLES } from "../orgs/roles.js";
@@ -228,6 +228,22 @@ export const RunContextSchema = z.object({
228
228
  .optional(),
229
229
  });
230
230
  const ev = (type, payload) => z.object({ type: z.literal(type), payload });
231
+ /**
232
+ * The most UTF-8 bytes a `plugin_context` event's text may hold (memory spec §6.1): the providers'
233
+ * limit, so the log takes every text a provider may return.
234
+ */
235
+ export const PLUGIN_CONTEXT_MAX_BYTES = CONTEXT_LIMITS.maxBytes;
236
+ /**
237
+ * `plugin_context` (memory spec §6.1): the text a `context@1` provider added at a run start. Empty
238
+ * text means the provider has nothing (any more); `hash` is the sha256 (hex) of `text`.
239
+ */
240
+ export const PluginContextSchema = z.object({
241
+ plugin: z.string().min(1).max(64),
242
+ text: z.string().refine((t) => Buffer.byteLength(t, "utf8") <= PLUGIN_CONTEXT_MAX_BYTES, {
243
+ message: `at most ${PLUGIN_CONTEXT_MAX_BYTES} UTF-8 bytes`,
244
+ }),
245
+ hash: z.string().regex(/^[0-9a-f]{64}$/),
246
+ });
231
247
  /**
232
248
  * An agent definition frozen into a log (`subagent_started`): the current schema, with the
233
249
  * selected and pinned skills of older logs still accepted (skills default-on spec §4: history
@@ -485,6 +501,13 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
485
501
  * system message (`runContextText`); a summary covering it restores the current values.
486
502
  */
487
503
  ev("run_context", RunContextSchema),
504
+ /**
505
+ * Text a `context@1` provider added at a run start (memory spec §6.1), written only when its
506
+ * hash differs from the plugin's previous one in the conversation. **Model-visible**: rendered
507
+ * at its position as a system message (`pluginContextText`, nothing for empty text); a summary
508
+ * covering it is followed by the latest non-empty one per plugin again.
509
+ */
510
+ ev("plugin_context", PluginContextSchema),
488
511
  /**
489
512
  * The step's resolved tool set differs from the one the conversation's previous model request
490
513
  * offered (`toolSetChange`): tool names as the model sees them, sorted. Written before that
@@ -599,6 +622,12 @@ export const ConversationEventSchema = z.discriminatedUnion("type", [
599
622
  */
600
623
  reason: z.string().max(APPROVAL_DENY_REASON_MAX_CHARS).optional(),
601
624
  })),
625
+ /**
626
+ * A still-pending approval changed without a decision: a network ask's 「这台设备一直允许」 whose
627
+ * device change was not applied or not confirmed (`detail.attempt`, transactional outbox spec
628
+ * §7). Display only, never model-visible: the page reads the approval again.
629
+ */
630
+ ev("approval_updated", z.object({ approvalId: z.string(), callId: z.string() })),
602
631
  /**
603
632
  * 工具调用需要用户输入(`AskUserQuestion`、插件工具的 `input_required`、MCP elicitation):输入请求已建,
604
633
  * run 暂停等待回答。界面据此显示表单卡片(不进模型请求)。
@@ -0,0 +1,10 @@
1
+ import type { ConversationSource } from "@capybara-hq/plugin-sdk";
2
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
+ /**
4
+ * Where a conversation comes from (memory plugin spec §6.2), stored on its row at creation
5
+ * (`conversations.source`, migration 0124) and never changed. A missing conversation throws
6
+ * rather than guessing a source (a guess could offer a group chat what only a DM may see).
7
+ */
8
+ export declare function conversationSourceOf(db: Pick<KernelSchemaDb, "selectFrom">, conversationId: string): Promise<ConversationSource>;
9
+ /** The source of a conversation created for a channel chat of `chatType`. */
10
+ export declare const channelSource: (chatType: "dm" | "group") => ConversationSource;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Where a conversation comes from (memory plugin spec §6.2), stored on its row at creation
3
+ * (`conversations.source`, migration 0124) and never changed. A missing conversation throws
4
+ * rather than guessing a source (a guess could offer a group chat what only a DM may see).
5
+ */
6
+ export async function conversationSourceOf(db, conversationId) {
7
+ const row = await db
8
+ .selectFrom("conversations")
9
+ .select("source")
10
+ .where("id", "=", conversationId)
11
+ .executeTakeFirst();
12
+ if (row === undefined)
13
+ throw new Error(`conversation ${conversationId} not found`);
14
+ return row.source;
15
+ }
16
+ /** The source of a conversation created for a channel chat of `chatType`. */
17
+ export const channelSource = (chatType) => chatType === "dm" ? "channel_dm" : "channel_group";
@@ -15,3 +15,13 @@ export declare function conversationTree(db: KernelSchemaDb, conversationId: str
15
15
  * `conversations.parent_conversation_id`. Empty for a root or an unknown id.
16
16
  */
17
17
  export declare function conversationAncestors(db: KernelSchemaDb, conversationId: string): Promise<string[]>;
18
+ /**
19
+ * Locks the conversations `ids` and every ancestor of theirs (`FOR NO KEY UPDATE`, in the caller's
20
+ * transaction) tree by tree, root first: the trees in root id order, within a tree by level below
21
+ * the root, then id. A reply locks its root conversation and only then the sub-agents' below it
22
+ * (`ConversationsService.skipWaitsIn`), and a dispatch locks a parent before its child; a locker
23
+ * of conversations in plain id order could take a child before its root and wait for the root
24
+ * while such a path holds the root and waits for the child (40P01). Returns the ids locked (the
25
+ * requested ones and their ancestors); an unknown id is skipped.
26
+ */
27
+ export declare function lockConversationTrees(db: KernelSchemaDb, ids: readonly string[]): Promise<string[]>;
@@ -40,3 +40,38 @@ export async function conversationAncestors(db, conversationId) {
40
40
  SELECT id FROM up WHERE level > 0 ORDER BY level`.execute(db);
41
41
  return r.rows.map((row) => row.id);
42
42
  }
43
+ /**
44
+ * Locks the conversations `ids` and every ancestor of theirs (`FOR NO KEY UPDATE`, in the caller's
45
+ * transaction) tree by tree, root first: the trees in root id order, within a tree by level below
46
+ * the root, then id. A reply locks its root conversation and only then the sub-agents' below it
47
+ * (`ConversationsService.skipWaitsIn`), and a dispatch locks a parent before its child; a locker
48
+ * of conversations in plain id order could take a child before its root and wait for the root
49
+ * while such a path holds the root and waits for the child (40P01). Returns the ids locked (the
50
+ * requested ones and their ancestors); an unknown id is skipped.
51
+ */
52
+ export async function lockConversationTrees(db, ids) {
53
+ const wanted = [...new Set(ids)];
54
+ if (wanted.length === 0)
55
+ return [];
56
+ const r = await sql `
57
+ WITH RECURSIVE up(start, id, parent, level) AS (
58
+ SELECT c.id, c.id, c.parent_conversation_id, 0 FROM capybara.conversations c
59
+ WHERE c.id = ANY(${wanted}::text[])
60
+ UNION ALL
61
+ SELECT up.start, c.id, c.parent_conversation_id, up.level + 1
62
+ FROM capybara.conversations c JOIN up ON c.id = up.parent
63
+ WHERE up.level < ${TREE_MAX_LEVELS}
64
+ ),
65
+ tops AS (
66
+ SELECT DISTINCT ON (start) start, id AS root, level AS height
67
+ FROM up ORDER BY start, level DESC
68
+ ),
69
+ nodes AS (
70
+ SELECT DISTINCT up.id, tops.root, tops.height - up.level AS depth
71
+ FROM up JOIN tops ON tops.start = up.start
72
+ )
73
+ SELECT c.id FROM capybara.conversations c JOIN nodes n ON n.id = c.id
74
+ ORDER BY n.root, n.depth, n.id
75
+ FOR NO KEY UPDATE OF c`.execute(db);
76
+ return r.rows.map((row) => row.id);
77
+ }
@@ -1,4 +1,4 @@
1
- import type { FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
1
+ import type { ConversationSource, FileSystemHandle, ParsedTheme, SandboxTier, SkillInvocation } from "@capybara-hq/plugin-sdk";
2
2
  import type { ColumnType, Generated, Kysely } from "kysely";
3
3
  import type { ModelRef } from "../agents/definition.js";
4
4
  import type { ScheduleChat, ScheduleMarker, ScheduleMode, SchedulePauseReason, ScheduleStatus, ScheduleTrigger, TaskRunSkipReason, TaskRunStatus } from "../schedules/types.js";
@@ -263,6 +263,16 @@ export interface ConversationsTable {
263
263
  device_id: ColumnType<string | null, string | null | undefined, string | null>;
264
264
  /** 0113: the scheduled task that created it (`new` mode); null otherwise. */
265
265
  scheduled_task_id: ColumnType<string | null, string | null | undefined, string | null>;
266
+ /**
267
+ * 0124: where the conversation comes from, set at creation and never changed. Required on insert
268
+ * (the column's `web` default is not relied on: a forgotten source must not read as web).
269
+ */
270
+ source: ColumnType<ConversationSource, ConversationSource, never>;
271
+ /**
272
+ * 0125: when a person stopped this root conversation and its sub-agent tree; cleared when the
273
+ * root next starts a run. While set the tree's wakes are held (agent map spec §6).
274
+ */
275
+ tree_stopped_at: ColumnType<Date | null, Date | string | null | undefined, Date | string | null>;
266
276
  }
267
277
  /** 0060: an org's order of file-preview@1 previewers (UI refresh B Task 11). */
268
278
  export interface FilePreviewersTable {
@@ -340,7 +350,11 @@ export interface RunsTable {
340
350
  waiting_since: TsNullable;
341
351
  /** 0012:累计等待时长(毫秒),不计入运行时长。 */
342
352
  waited_ms: Int8;
343
- /** 0012:续跑任务的 jobId;为空表示按触发消息生成的 jobId。 */
353
+ /**
354
+ * 0012: the run's job id when it is not the trigger's `runJobId`: a resume's
355
+ * (`resumeJobId`), or `<runJobId>-<n>` for a later run of the same trigger (R34); null: the
356
+ * trigger's first run, under `runJobId`.
357
+ */
344
358
  job_id: string | null;
345
359
  /** 0012:最近一次(续跑)回到 queued 的时刻。 */
346
360
  queued_at: TsNullable;
@@ -1224,8 +1238,6 @@ export interface ScheduledTaskRunsTable {
1224
1238
  skip_reason: ColumnType<TaskRunSkipReason | null, TaskRunSkipReason | null | undefined, TaskRunSkipReason | null>;
1225
1239
  error: NullableText;
1226
1240
  missing_grants: ColumnType<string[], string | undefined, string>;
1227
- /** When the scanner's repair last found this started run still in flight. */
1228
- rechecked_at: NullableTs;
1229
1241
  }
1230
1242
  /** 0113: a scheduled task's remembered grants (same shape as `conversation_tool_grants`). */
1231
1243
  export interface ScheduledTaskGrantsTable {
@@ -1333,7 +1345,6 @@ export interface KernelDatabase {
1333
1345
  plugin_install_components: PluginInstallComponentsTable;
1334
1346
  extension_catalog_sources: ExtensionCatalogSourcesTable;
1335
1347
  bootstrap_runs: BootstrapRunsTable;
1336
- platform_migration_runs: PlatformMigrationRunsTable;
1337
1348
  org_notices: OrgNoticesTable;
1338
1349
  platform_notices: PlatformNoticesTable;
1339
1350
  plugin_scope_sync_failures: PluginScopeSyncFailuresTable;
@@ -1397,21 +1408,14 @@ export interface ExtensionClientReportsTable {
1397
1408
  last_at: Ts;
1398
1409
  last_message: string;
1399
1410
  }
1400
- /** 0082: one run of the platform components migration job (`catalog/platform-migration.ts`). */
1401
- export interface PlatformMigrationRunsTable {
1402
- id: string;
1403
- started_at: Ts;
1404
- finished_at: TsNullable;
1405
- stats: Json<Record<string, unknown>>;
1406
- }
1407
- /** 0082: the kinds of {@link OrgNoticesTable}. */
1408
- export type OrgNoticeKind = "skill_renamed" | "item_shared" | "shared_item_updated" | "upload_confirmed" | "upload_rejected";
1411
+ /** 0084 (0121 dropped `skill_renamed`): the kinds of {@link OrgNoticesTable}. */
1412
+ export type OrgNoticeKind = "item_shared" | "shared_item_updated" | "upload_confirmed" | "upload_rejected";
1409
1413
  /** 0082: a notice for an org's admins (an inbox item), until one of them dismisses it. */
1410
1414
  export interface OrgNoticesTable {
1411
1415
  id: string;
1412
1416
  org_id: string;
1413
1417
  kind: OrgNoticeKind;
1414
- /** `skill_renamed`: `{ skillId, from, to, platformSkillId, plugin }`. */
1418
+ /** The kind's shape (`OrgNoticeDetails` in `inbox/org-notices.ts`). */
1415
1419
  detail: Json<Record<string, unknown>>;
1416
1420
  created_at: Ts;
1417
1421
  dismissed_at: TsNullable;
@@ -4,9 +4,9 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
4
4
  import { KernelDb } from "../infra/database/kernel-db.js";
5
5
  import { TransactionHost } from "../infra/database/transaction-host.js";
6
6
  import { LockService } from "../infra/lock/lock.service.js";
7
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
7
8
  import { DevicePolicyService } from "../local-devices/device-policy.service.js";
8
9
  import { MachinesService } from "../local-devices/machines.js";
9
- import type { KernelLogger } from "../log.js";
10
10
  import type { DeviceSandboxLevel } from "../runtime/context/device-paths.js";
11
11
  import { SandboxJobs } from "../sandbox/jobs.js";
12
12
  import { SandboxProviders } from "../sandbox/providers.js";
@@ -112,7 +112,8 @@ export declare const RECENT_POLICY_CHANGES = 10;
112
112
  /**
113
113
  * One sandbox change of a device, as its owner sees it on the device page (§10.4):
114
114
  * - `policy` (`device.policy_changed`): a local policy change sent to the machine (`web`, `im`),
115
- * or made on the machine by its owner (`device`, `capybara-device policy ...`);
115
+ * or made on the machine by its owner (`device`, `capybara-device policy ...`); one sent whose
116
+ * answer never came (`device.policy_unknown`) or that failed (`device.policy_failed`) too;
116
117
  * - `tier` (`device.tier_changed`): a cloud device's tier set by its owner;
117
118
  * - `isolation` (`device.isolation_reported`): the machine reported running without a sandbox,
118
119
  * or with one again.
@@ -141,8 +142,14 @@ export interface DeviceSandboxChangeView {
141
142
  by: string | null;
142
143
  /** `device`: made or reported on the machine itself (a `policy` change, an `isolation`). */
143
144
  via: "web" | "im" | "device";
144
- /** The machine answered with the policy as written (false: the answer never came). */
145
- confirmed: boolean;
145
+ /**
146
+ * What became of a `policy` change sent to the machine: `applied` (also every change made or
147
+ * reported on the machine, and every tier / isolation row); `unknown`, its answer never came
148
+ * (`device.policy_unknown`: an `applied` row follows when the machine reports it); `failed`,
149
+ * the machine refused it, never received it or stopped while applying it
150
+ * (`device.policy_failed`).
151
+ */
152
+ outcome: "applied" | "unknown" | "failed";
146
153
  }
147
154
  /** A change of a local device's policy by its owner (`POST /devices/:id/policy`). */
148
155
  export interface DevicePolicyChange {
@@ -181,8 +188,8 @@ export interface DeviceList {
181
188
  * Personal devices (spec 2026-11-07-environments-design §4.2 items 7, 12, 13; §6; §9):
182
189
  * cloud ones on the deployment's sandbox provider and `device` ones on a connected machine; a
183
190
  * person's devices in an org, visible and usable only by their owner (anyone else gets
184
- * `device.not_found`). Deleting one soft-deletes it and destroys its instance and files in a
185
- * `sandbox-destroy-for` job after the commit.
191
+ * `device.not_found`). Deleting one soft-deletes it and owes the destruction of its instance and
192
+ * files (a `sandbox-destroy-for` job) in the same transaction.
186
193
  */
187
194
  export declare class DevicesService {
188
195
  private readonly db;
@@ -191,14 +198,17 @@ export declare class DevicesService {
191
198
  private readonly providers;
192
199
  private readonly sandbox;
193
200
  private readonly sandboxJobs;
201
+ /** Owes a deleted device's destruction in the delete's transaction. */
202
+ private readonly outbox;
194
203
  private readonly access;
195
204
  private readonly displayNames;
196
- private readonly log;
197
205
  /** Reads the device relay's lease (`online`). */
198
206
  private readonly locks;
199
207
  private readonly machines;
200
208
  private readonly policies;
201
- constructor(db: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, providers: Pick<SandboxProviders, "cloud" | "forName">, sandbox: Pick<SandboxService, "deviceLimits" | "limitsState">, sandboxJobs: Pick<SandboxJobs, "destroy" | "resetFiles">, access: Pick<DeviceAccess, "usable">, displayNames: Pick<ExtensionDisplayNames, "many">, log: Pick<KernelLogger, "warn">,
209
+ constructor(db: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, providers: Pick<SandboxProviders, "cloud" | "forName">, sandbox: Pick<SandboxService, "deviceLimits" | "limitsState">, sandboxJobs: Pick<SandboxJobs, "resetFiles">,
210
+ /** Owes a deleted device's destruction in the delete's transaction. */
211
+ outbox: Pick<OutboxService, "add">, access: Pick<DeviceAccess, "usable">, displayNames: Pick<ExtensionDisplayNames, "many">,
202
212
  /** Reads the device relay's lease (`online`). */
203
213
  locks: Pick<LockService, "holderOf">, machines: Pick<MachinesService, "revoke" | "revokeActive">, policies: Pick<DevicePolicyService, "apply" | "summary">);
204
214
  /**
@@ -284,9 +294,9 @@ export declare class DevicesService {
284
294
  /** Renames the device locked by {@link lockOwn} (`before`); see {@link rename}. */
285
295
  private renameLocked;
286
296
  /**
287
- * Deletes one of the person's live devices: soft delete, its IM default row cleared, and
288
- * after the commit a `sandbox-destroy-for` job (the worker waits for running commands, then
289
- * destroys the instance and the files). Conversations bound to it keep pointing at it; their
297
+ * Deletes one of the person's live devices: soft delete, its IM default row cleared, and a
298
+ * `sandbox-destroy-for` job owed in the same transaction (`device-destroy-<deviceId>`; the
299
+ * worker waits for running commands, then destroys the instance and the files). Conversations bound to it keep pointing at it; their
290
300
  * sandbox tools then fail with `device.not_found`. A local device's machine is
291
301
  * revoked in the same transaction; after the commit it is told `stop { reason: "deleted" }`
292
302
  * (while connected) and its connection is closed.
@@ -306,7 +316,7 @@ export declare class DevicesService {
306
316
  * refused up front with `device.policy_refused` when the machine last reported
307
317
  * `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
308
318
  * `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
309
- * program without the `policyOp` feature → `device.policy_unsupported`; the machine's refusal →
319
+ * program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
310
320
  * `device.policy_refused` with its message. Callers are human sessions only (no personal access
311
321
  * token, no service token, no Platform op).
312
322
  */
@@ -391,8 +401,9 @@ export declare class DevicesService {
391
401
  private views;
392
402
  /**
393
403
  * The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
394
- * `device.policy_changed`, `device.tier_changed`, `device.isolation_reported`), newest first,
395
- * with the deciding account's display name.
404
+ * `device.policy_changed`, `device.policy_unknown`, `device.policy_failed`,
405
+ * `device.tier_changed`, `device.isolation_reported`), newest first, with the deciding account's
406
+ * display name.
396
407
  */
397
408
  private recentSandboxChanges;
398
409
  /**
@@ -15,18 +15,17 @@ import { ApiError } from "../errors.js";
15
15
  import { ExtensionDisplayNames } from "../extensions/display-name.js";
16
16
  import { audit } from "../identity/audit.js";
17
17
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
18
- import { afterCommit } from "../infra/database/after-commit.js";
19
18
  import { KernelDb } from "../infra/database/kernel-db.js";
20
19
  import { TransactionHost } from "../infra/database/transaction-host.js";
21
20
  import { Transactional } from "../infra/database/transactional.decorator.js";
22
21
  import { LockService } from "../infra/lock/lock.service.js";
23
- import { KERNEL_LOGGER } from "../infra/tokens.js";
22
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
24
23
  import { DevicePolicyService } from "../local-devices/device-policy.service.js";
25
24
  import { deviceLeaseKey } from "../local-devices/keys.js";
26
25
  import { MachinesService } from "../local-devices/machines.js";
27
26
  import { deviceFeaturesOf, localPolicyOf } from "../local-devices/policy-summary.js";
28
27
  import { readOrgSettings } from "../orgs/settings.service.js";
29
- import { SandboxJobs } from "../sandbox/jobs.js";
28
+ import { oweDeviceDestroy, SandboxJobs } from "../sandbox/jobs.js";
30
29
  import { isInternalProvider, LOCAL_PROVIDER, SandboxProviders } from "../sandbox/providers.js";
31
30
  import { deviceNotFound, SandboxService } from "../sandbox/sandbox.service.js";
32
31
  import { effectiveSandboxPolicy, readDeploymentSandboxPolicy, } from "../sandbox/tiers.js";
@@ -43,8 +42,8 @@ const nameTaken = () => new ApiError("device.name_taken", "You already have a de
43
42
  * Personal devices (spec 2026-11-07-environments-design §4.2 items 7, 12, 13; §6; §9):
44
43
  * cloud ones on the deployment's sandbox provider and `device` ones on a connected machine; a
45
44
  * person's devices in an org, visible and usable only by their owner (anyone else gets
46
- * `device.not_found`). Deleting one soft-deletes it and destroys its instance and files in a
47
- * `sandbox-destroy-for` job after the commit.
45
+ * `device.not_found`). Deleting one soft-deletes it and owes the destruction of its instance and
46
+ * files (a `sandbox-destroy-for` job) in the same transaction.
48
47
  */
49
48
  let DevicesService = class DevicesService {
50
49
  db;
@@ -53,13 +52,15 @@ let DevicesService = class DevicesService {
53
52
  providers;
54
53
  sandbox;
55
54
  sandboxJobs;
55
+ outbox;
56
56
  access;
57
57
  displayNames;
58
- log;
59
58
  locks;
60
59
  machines;
61
60
  policies;
62
- constructor(db, txHost, config, providers, sandbox, sandboxJobs, access, displayNames, log,
61
+ constructor(db, txHost, config, providers, sandbox, sandboxJobs,
62
+ /** Owes a deleted device's destruction in the delete's transaction. */
63
+ outbox, access, displayNames,
63
64
  /** Reads the device relay's lease (`online`). */
64
65
  locks, machines, policies) {
65
66
  this.db = db;
@@ -68,9 +69,9 @@ let DevicesService = class DevicesService {
68
69
  this.providers = providers;
69
70
  this.sandbox = sandbox;
70
71
  this.sandboxJobs = sandboxJobs;
72
+ this.outbox = outbox;
71
73
  this.access = access;
72
74
  this.displayNames = displayNames;
73
- this.log = log;
74
75
  this.locks = locks;
75
76
  this.machines = machines;
76
77
  this.policies = policies;
@@ -388,9 +389,9 @@ let DevicesService = class DevicesService {
388
389
  });
389
390
  }
390
391
  /**
391
- * Deletes one of the person's live devices: soft delete, its IM default row cleared, and
392
- * after the commit a `sandbox-destroy-for` job (the worker waits for running commands, then
393
- * destroys the instance and the files). Conversations bound to it keep pointing at it; their
392
+ * Deletes one of the person's live devices: soft delete, its IM default row cleared, and a
393
+ * `sandbox-destroy-for` job owed in the same transaction (`device-destroy-<deviceId>`; the
394
+ * worker waits for running commands, then destroys the instance and the files). Conversations bound to it keep pointing at it; their
394
395
  * sandbox tools then fail with `device.not_found`. A local device's machine is
395
396
  * revoked in the same transaction; after the commit it is told `stop { reason: "deleted" }`
396
397
  * (while connected) and its connection is closed.
@@ -417,15 +418,7 @@ let DevicesService = class DevicesService {
417
418
  target: id,
418
419
  detail: { name: before.name, isDefault: before.is_default, ...auditDetail },
419
420
  });
420
- afterCommit(async () => {
421
- try {
422
- await this.sandboxJobs.destroy([id]);
423
- }
424
- catch (err) {
425
- // the hourly destruction sweep destroys deleted devices too
426
- this.log.warn("cannot schedule device destruction", { deviceId: id, err });
427
- }
428
- });
421
+ await oweDeviceDestroy(this.outbox, id, orgId);
429
422
  }
430
423
  /**
431
424
  * Disconnects the machine of one of the person's own local devices (it must sign in
@@ -443,7 +436,7 @@ let DevicesService = class DevicesService {
443
436
  * refused up front with `device.policy_refused` when the machine last reported
444
437
  * `remoteGrants: "off"` (otherwise the machine decides); narrowing is always sent, and
445
438
  * `isolation` is only ever `sandbox`. The machine offline → `device.offline`; a cloud device or a
446
- * program without the `policyOp` feature → `device.policy_unsupported`; the machine's refusal →
439
+ * program without the `policyOpId` feature → `device.policy_unsupported`; the machine's refusal →
447
440
  * `device.policy_refused` with its message. Callers are human sessions only (no personal access
448
441
  * token, no service token, no Platform op).
449
442
  */
@@ -459,7 +452,7 @@ let DevicesService = class DevicesService {
459
452
  const widens = (change.add?.length ?? 0) > 0 || (known !== null && tierWidens(known.tier, change.tier));
460
453
  // only a known "off" is refused here; an unknown summary, a machine that cannot take the op
461
454
  // or none at all are left to apply (the machine decides, or unsupported / offline)
462
- const canApply = summary?.features.includes("policyOp") === true;
455
+ const canApply = summary?.features.includes("policyOpId") === true;
463
456
  if (widens && canApply && known?.remoteGrants === "off") {
464
457
  throw new ApiError("device.policy_refused", remoteGrantsOffMessage(change));
465
458
  }
@@ -900,8 +893,9 @@ let DevicesService = class DevicesService {
900
893
  }
901
894
  /**
902
895
  * The last {@link RECENT_POLICY_CHANGES} sandbox changes of each device in the org (audit rows
903
- * `device.policy_changed`, `device.tier_changed`, `device.isolation_reported`), newest first,
904
- * with the deciding account's display name.
896
+ * `device.policy_changed`, `device.policy_unknown`, `device.policy_failed`,
897
+ * `device.tier_changed`, `device.isolation_reported`), newest first, with the deciding account's
898
+ * display name.
905
899
  */
906
900
  async recentSandboxChanges(orgId, deviceIds) {
907
901
  const out = new Map();
@@ -1021,9 +1015,9 @@ DevicesService = __decorate([
1021
1015
  __param(3, Inject(SandboxProviders)),
1022
1016
  __param(4, Inject(SandboxService)),
1023
1017
  __param(5, Inject(SandboxJobs)),
1024
- __param(6, Inject(DeviceAccess)),
1025
- __param(7, Inject(ExtensionDisplayNames)),
1026
- __param(8, Inject(KERNEL_LOGGER)),
1018
+ __param(6, Inject(OutboxService)),
1019
+ __param(7, Inject(DeviceAccess)),
1020
+ __param(8, Inject(ExtensionDisplayNames)),
1027
1021
  __param(9, Inject(LockService)),
1028
1022
  __param(10, Inject(MachinesService)),
1029
1023
  __param(11, Inject(DevicePolicyService))
@@ -1047,6 +1041,8 @@ const limitsView = (l) => ({
1047
1041
  /** The audit actions a device's recent sandbox changes list. */
1048
1042
  const SANDBOX_CHANGE_ACTIONS = [
1049
1043
  "device.policy_changed",
1044
+ "device.policy_unknown",
1045
+ "device.policy_failed",
1050
1046
  "device.tier_changed",
1051
1047
  "device.isolation_reported",
1052
1048
  ];
@@ -1066,10 +1062,16 @@ function sandboxChangeOf(action, at, d, by) {
1066
1062
  by,
1067
1063
  };
1068
1064
  if (action === "device.tier_changed") {
1069
- return { ...base, kind: "tier", tier: { to: tierOrNull(d.to) }, via: "web", confirmed: true };
1065
+ return {
1066
+ ...base,
1067
+ kind: "tier",
1068
+ tier: { to: tierOrNull(d.to) },
1069
+ via: "web",
1070
+ outcome: "applied",
1071
+ };
1070
1072
  }
1071
1073
  if (action === "device.isolation_reported") {
1072
- return { ...base, kind: "isolation", isolation, via: "device", confirmed: true };
1074
+ return { ...base, kind: "isolation", isolation, via: "device", outcome: "applied" };
1073
1075
  }
1074
1076
  return {
1075
1077
  ...base,
@@ -1086,7 +1088,11 @@ function sandboxChangeOf(action, at, d, by) {
1086
1088
  : null,
1087
1089
  remoteGrants: d.remoteGrants === "owner" || d.remoteGrants === "off" ? d.remoteGrants : null,
1088
1090
  via: d.via === "im" ? "im" : d.via === "device" ? "device" : "web",
1089
- confirmed: d.confirmed === true,
1091
+ outcome: action === "device.policy_unknown"
1092
+ ? "unknown"
1093
+ : action === "device.policy_failed"
1094
+ ? "failed"
1095
+ : "applied",
1090
1096
  };
1091
1097
  }
1092
1098
  /**
@@ -21,7 +21,7 @@ import { OrgLockModule } from "../orgs/org-lock.js";
21
21
  import { ProvidersModule } from "../runtime/providers.js";
22
22
  import { RuntimeModule } from "../runtime/runtime.module.js";
23
23
  import { EvalBudgetModule } from "./budget.js";
24
- import { EVAL_QUEUE_NAME, EvalJobs } from "./jobs.js";
24
+ import { EVAL_CASE_JOB, EVAL_QUEUE_NAME, EvalJobs } from "./jobs.js";
25
25
  import { EvalReplaySeeder } from "./run-replay.js";
26
26
  import { EvalRunsService } from "./runs.service.js";
27
27
  import { EvalSetsService } from "./sets.service.js";
@@ -66,6 +66,10 @@ let EvalsProcessor = class EvalsProcessor extends WorkerHost {
66
66
  this.evals = evals;
67
67
  }
68
68
  process(job, token) {
69
+ // the queue's one job name, owed by an eval run's start
70
+ if (job.name !== EVAL_CASE_JOB) {
71
+ return Promise.reject(new Error(`unknown eval job "${job.name}"`));
72
+ }
69
73
  return runJob(EVAL_QUEUE_NAME, job, () => this.evals.process(job, token));
70
74
  }
71
75
  failed(job, err) {
@@ -1,6 +1,8 @@
1
- import type { Queue } from "bullmq";
1
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
2
2
  /** 评测队列的 BullMQ 队列名(不含 `:`,C7)。 */
3
3
  export declare const EVAL_QUEUE_NAME = "cap-kernel-evals";
4
+ /** The one job name on {@link EVAL_QUEUE_NAME}: one case of an eval run. */
5
+ export declare const EVAL_CASE_JOB = "eval-case";
4
6
  /** 每个用例一个任务(C7;不含 `:`)。 */
5
7
  export declare const evalJobId: (runId: string, caseId: string) => string;
6
8
  /** 评测任务的数据。 */
@@ -10,11 +12,17 @@ export interface EvalJobData {
10
12
  orgId: string;
11
13
  }
12
14
  /**
13
- * The producer of `cap-kernel-evals` (one job per case, {@link evalJobId}); the eval worker
14
- * consumes it on the worker role. A leaf module: the runs service and the worker import it.
15
+ * The producer of `cap-kernel-evals`: owes one {@link EVAL_CASE_JOB} per case (under
16
+ * {@link evalJobId}) in the transaction that writes the run and its pending results (transactional
17
+ * outbox spec §3), so a run never waits for jobs that were never added. The eval worker consumes
18
+ * them on the worker role. A leaf module: the runs service and the worker import it.
15
19
  */
16
20
  export declare class EvalJobs {
17
- private readonly queue;
18
- constructor(queue: Pick<Queue, "addBulk">);
19
- enqueue(jobs: EvalJobData[]): Promise<void>;
21
+ private readonly outbox;
22
+ constructor(outbox: Pick<OutboxService, "addMany">);
23
+ /**
24
+ * Owes the case jobs in the caller's transaction (throws outside one), in one insert: a set has
25
+ * up to `evals.maxCasesPerSet` cases, written while the start holds the org lock.
26
+ */
27
+ owe(jobs: readonly EvalJobData[]): Promise<void>;
20
28
  }
@@ -7,39 +7,41 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __param = (this && this.__param) || function (paramIndex, decorator) {
8
8
  return function (target, key) { decorator(target, key, paramIndex); }
9
9
  };
10
- import { getQueueToken } from "@nestjs/bullmq";
11
10
  import { Inject, Injectable } from "@nestjs/common";
11
+ import { OutboxService } from "../infra/outbox/outbox.service.js";
12
12
  /** 评测队列的 BullMQ 队列名(不含 `:`,C7)。 */
13
13
  export const EVAL_QUEUE_NAME = "cap-kernel-evals";
14
+ /** The one job name on {@link EVAL_QUEUE_NAME}: one case of an eval run. */
15
+ export const EVAL_CASE_JOB = "eval-case";
14
16
  /** 每个用例一个任务(C7;不含 `:`)。 */
15
17
  export const evalJobId = (runId, caseId) => `eval-${runId}-${caseId}`;
16
18
  /**
17
- * The producer of `cap-kernel-evals` (one job per case, {@link evalJobId}); the eval worker
18
- * consumes it on the worker role. A leaf module: the runs service and the worker import it.
19
+ * The producer of `cap-kernel-evals`: owes one {@link EVAL_CASE_JOB} per case (under
20
+ * {@link evalJobId}) in the transaction that writes the run and its pending results (transactional
21
+ * outbox spec §3), so a run never waits for jobs that were never added. The eval worker consumes
22
+ * them on the worker role. A leaf module: the runs service and the worker import it.
19
23
  */
20
24
  let EvalJobs = class EvalJobs {
21
- queue;
22
- constructor(queue) {
23
- this.queue = queue;
25
+ outbox;
26
+ constructor(outbox) {
27
+ this.outbox = outbox;
24
28
  }
25
- async enqueue(jobs) {
26
- await this.queue.addBulk(jobs.map((data) => ({
27
- name: "case",
29
+ /**
30
+ * Owes the case jobs in the caller's transaction (throws outside one), in one insert: a set has
31
+ * up to `evals.maxCasesPerSet` cases, written while the start holds the org lock.
32
+ */
33
+ async owe(jobs) {
34
+ await this.outbox.addMany(jobs.map((data) => ({
35
+ queue: EVAL_QUEUE_NAME,
36
+ name: EVAL_CASE_JOB,
37
+ jobId: evalJobId(data.runId, data.caseId),
28
38
  data,
29
- opts: {
30
- jobId: evalJobId(data.runId, data.caseId),
31
- // 处理函数外的暂时故障(PG / Redis):指数退避重试,不让整个用例一次失败。
32
- // 结果按认领号写入,重做不会重复写结果
33
- attempts: 3,
34
- backoff: { type: "exponential", delay: 1000 },
35
- removeOnComplete: true,
36
- removeOnFail: 1000,
37
- },
39
+ orgId: data.orgId,
38
40
  })));
39
41
  }
40
42
  };
41
43
  EvalJobs = __decorate([
42
44
  Injectable(),
43
- __param(0, Inject(getQueueToken(EVAL_QUEUE_NAME)))
45
+ __param(0, Inject(OutboxService))
44
46
  ], EvalJobs);
45
47
  export { EvalJobs };
@@ -1,4 +1,4 @@
1
- import { type ContentBlock, type Logger, type ModelUsage, type StopReason } from "@capybara-hq/plugin-sdk";
1
+ import { type ContentBlock, type ConversationSource, type Logger, type ModelUsage, type StopReason } from "@capybara-hq/plugin-sdk";
2
2
  import { type ModelRef } from "../agents/definition.js";
3
3
  import { EventLog } from "../conversations/event-log.js";
4
4
  import type { KernelSchemaDb } from "../db/kernel-schema.js";
@@ -98,6 +98,8 @@ export interface ReplaySeed {
98
98
  evalRunId: string;
99
99
  orgId: string;
100
100
  agentId: string;
101
+ /** The original conversation's source: the replay offers the tools of this source. */
102
+ source: ConversationSource;
101
103
  accountId: string;
102
104
  conversationId: string;
103
105
  runId: string;
@@ -121,11 +123,20 @@ export interface ReplaySeed {
121
123
  runContext: {
122
124
  payload: unknown;
123
125
  } | undefined;
126
+ /**
127
+ * The original run's `plugin_context` events (memory spec §6.1), in log order, copied as is:
128
+ * replays never call context providers.
129
+ */
130
+ pluginContexts: {
131
+ payload: {
132
+ plugin: string;
133
+ };
134
+ }[];
124
135
  }
125
136
  /**
126
137
  * The seed of a full-run replay (C6): the `kind = 'eval'` conversation with the events before the
127
138
  * original run at their original seqs, the run row and its `run_started` (and the original
128
- * `skills_catalog`), in one transaction of its own.
139
+ * `skills_catalog`, `run_context` and `plugin_context` events), in one transaction of its own.
129
140
  */
130
141
  export declare class EvalReplaySeeder {
131
142
  private readonly db;