@capybara-hq/kernel 0.1.0-rc.3 → 0.1.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/admin/admin.controller.d.ts +9 -4
  2. package/dist/admin/admin.controller.js +13 -4
  3. package/dist/admin/extensions.controller.d.ts +8 -1
  4. package/dist/admin/extensions.controller.js +22 -2
  5. package/dist/agents/agent-lookup.d.ts +3 -0
  6. package/dist/agents/agent-lookup.js +10 -0
  7. package/dist/agents/agent-profile.service.d.ts +29 -0
  8. package/dist/agents/agent-profile.service.js +117 -0
  9. package/dist/agents/agents.controller.d.ts +93 -3
  10. package/dist/agents/agents.controller.js +114 -4
  11. package/dist/agents/agents.module.js +6 -2
  12. package/dist/agents/agents.service.d.ts +13 -5
  13. package/dist/agents/agents.service.js +30 -7
  14. package/dist/agents/definition.d.ts +4 -0
  15. package/dist/agents/definition.js +10 -0
  16. package/dist/agents/instruction-files.d.ts +60 -0
  17. package/dist/agents/instruction-files.js +111 -0
  18. package/dist/agents/models.controller.d.ts +9 -3
  19. package/dist/agents/models.controller.js +19 -4
  20. package/dist/agents/profile.d.ts +96 -0
  21. package/dist/agents/profile.js +51 -0
  22. package/dist/agents/readiness.controller.d.ts +26 -0
  23. package/dist/agents/readiness.controller.js +56 -0
  24. package/dist/agents/templates.js +1 -1
  25. package/dist/background/background.controller.d.ts +38 -0
  26. package/dist/background/background.controller.js +87 -0
  27. package/dist/background/background.module.d.ts +10 -0
  28. package/dist/background/background.module.js +32 -0
  29. package/dist/background/finish.d.ts +43 -0
  30. package/dist/background/finish.js +94 -0
  31. package/dist/background/launch-lock.d.ts +14 -0
  32. package/dist/background/launch-lock.js +39 -0
  33. package/dist/background/launch.d.ts +36 -0
  34. package/dist/background/launch.js +70 -0
  35. package/dist/background/poll.job.d.ts +53 -0
  36. package/dist/background/poll.job.js +182 -0
  37. package/dist/background/render.d.ts +23 -0
  38. package/dist/background/render.js +48 -0
  39. package/dist/background/session.d.ts +37 -0
  40. package/dist/background/session.js +66 -0
  41. package/dist/background/status.d.ts +35 -0
  42. package/dist/background/status.js +57 -0
  43. package/dist/background/stop.d.ts +62 -0
  44. package/dist/background/stop.js +225 -0
  45. package/dist/background/store.d.ts +150 -0
  46. package/dist/background/store.js +351 -0
  47. package/dist/background/store.module.d.ts +6 -0
  48. package/dist/background/store.module.js +22 -0
  49. package/dist/background/tasks.service.d.ts +67 -0
  50. package/dist/background/tasks.service.js +186 -0
  51. package/dist/background/wake-rules.d.ts +57 -0
  52. package/dist/background/wake-rules.js +83 -0
  53. package/dist/background/wake.d.ts +28 -0
  54. package/dist/background/wake.js +57 -0
  55. package/dist/catalog/claude/agents.js +2 -0
  56. package/dist/catalog/marketplaces.service.d.ts +1 -1
  57. package/dist/catalog/plugin-import.js +1 -1
  58. package/dist/catalog/plugin-upgrade.js +1 -1
  59. package/dist/channels/cards.js +1 -1
  60. package/dist/channels/connections.service.d.ts +16 -1
  61. package/dist/channels/connections.service.js +38 -4
  62. package/dist/channels/threads.d.ts +5 -0
  63. package/dist/channels/threads.js +8 -0
  64. package/dist/config/schema.d.ts +18 -0
  65. package/dist/config/schema.js +102 -0
  66. package/dist/conversations/access.js +2 -0
  67. package/dist/conversations/attachments.service.d.ts +30 -0
  68. package/dist/conversations/attachments.service.js +130 -5
  69. package/dist/conversations/conversations.controller.d.ts +36 -5
  70. package/dist/conversations/conversations.controller.js +62 -12
  71. package/dist/conversations/conversations.module.js +3 -0
  72. package/dist/conversations/conversations.service.d.ts +44 -2
  73. package/dist/conversations/conversations.service.js +140 -12
  74. package/dist/conversations/event-log.d.ts +1 -1
  75. package/dist/conversations/event-log.js +10 -1
  76. package/dist/conversations/events.d.ts +58 -0
  77. package/dist/conversations/events.js +57 -2
  78. package/dist/conversations/queue.d.ts +3 -1
  79. package/dist/conversations/queue.js +13 -2
  80. package/dist/conversations/queue.service.js +3 -1
  81. package/dist/conversations/titles.d.ts +7 -0
  82. package/dist/conversations/titles.js +15 -0
  83. package/dist/db/kernel-schema.d.ts +58 -2
  84. package/dist/db/pool.d.ts +5 -0
  85. package/dist/db/pool.js +3 -0
  86. package/dist/errors.d.ts +8 -0
  87. package/dist/errors.js +8 -0
  88. package/dist/evals/run-replay.d.ts +5 -2
  89. package/dist/evals/run-replay.js +30 -7
  90. package/dist/evals/step-replay.js +12 -6
  91. package/dist/evals/trajectory.js +1 -1
  92. package/dist/extensions/admin-ops.js +2 -0
  93. package/dist/extensions/admin-view.d.ts +3 -1
  94. package/dist/extensions/admin-view.js +10 -2
  95. package/dist/extensions/blobs.js +1 -0
  96. package/dist/extensions/config-issues.d.ts +13 -1
  97. package/dist/extensions/config-issues.js +22 -5
  98. package/dist/extensions/config-json-schema.d.ts +43 -7
  99. package/dist/extensions/config-json-schema.js +124 -58
  100. package/dist/extensions/deployment-config-store.d.ts +6 -0
  101. package/dist/extensions/deployment-config.d.ts +50 -3
  102. package/dist/extensions/deployment-config.js +125 -28
  103. package/dist/extensions/display-name.d.ts +22 -0
  104. package/dist/extensions/display-name.js +77 -0
  105. package/dist/extensions/extensions.module.js +6 -0
  106. package/dist/extensions/host.d.ts +26 -1
  107. package/dist/extensions/host.js +82 -9
  108. package/dist/extensions/org-config.controller.d.ts +11 -3
  109. package/dist/extensions/org-config.controller.js +14 -6
  110. package/dist/extensions/org-config.d.ts +6 -0
  111. package/dist/extensions/org-config.js +12 -13
  112. package/dist/extensions/package.d.ts +14 -1
  113. package/dist/extensions/package.js +51 -2
  114. package/dist/extensions/permission-view.d.ts +3 -0
  115. package/dist/extensions/point-defaults.controller.d.ts +34 -0
  116. package/dist/extensions/point-defaults.controller.js +71 -0
  117. package/dist/extensions/point-defaults.d.ts +64 -0
  118. package/dist/extensions/point-defaults.js +206 -0
  119. package/dist/extensions/registry.d.ts +1 -1
  120. package/dist/extensions/registry.js +2 -0
  121. package/dist/http/api.module.js +8 -0
  122. package/dist/http/nest-logger.d.ts +6 -5
  123. package/dist/http/nest-logger.js +24 -10
  124. package/dist/identity/auth.controller.js +9 -5
  125. package/dist/identity/cookies.d.ts +20 -5
  126. package/dist/identity/cookies.js +44 -6
  127. package/dist/identity/me.controller.d.ts +5 -1
  128. package/dist/identity/me.controller.js +12 -3
  129. package/dist/identity/sessions.d.ts +12 -0
  130. package/dist/identity/sessions.js +45 -0
  131. package/dist/index.d.ts +1 -1
  132. package/dist/infra/config/kernel-config.service.js +6 -0
  133. package/dist/infra/database/database.module.js +1 -0
  134. package/dist/infra/events/kernel-events.d.ts +8 -0
  135. package/dist/infra/lifecycle/process-guards.js +4 -0
  136. package/dist/infra/logging/logging.module.d.ts +4 -2
  137. package/dist/infra/logging/logging.module.js +24 -6
  138. package/dist/infra/queue/redis-connection.d.ts +6 -0
  139. package/dist/infra/queue/redis-connection.js +7 -0
  140. package/dist/infra/redis/redis.module.js +6 -2
  141. package/dist/infra/tokens.d.ts +6 -1
  142. package/dist/infra/tokens.js +6 -1
  143. package/dist/log.d.ts +13 -1
  144. package/dist/log.js +93 -17
  145. package/dist/models/purposes.d.ts +23 -0
  146. package/dist/models/purposes.js +31 -10
  147. package/dist/orgs/offboarding.d.ts +3 -1
  148. package/dist/orgs/offboarding.js +26 -16
  149. package/dist/orgs/settings.service.d.ts +5 -0
  150. package/dist/orgs/settings.service.js +13 -2
  151. package/dist/roles/worker.module.js +6 -0
  152. package/dist/runtime/context/build.d.ts +9 -7
  153. package/dist/runtime/context/build.js +27 -19
  154. package/dist/runtime/context/claude-tools.d.ts +2 -1
  155. package/dist/runtime/context/claude-tools.js +5 -4
  156. package/dist/runtime/context/compaction.js +4 -0
  157. package/dist/runtime/langgraph-loop.js +3 -2
  158. package/dist/runtime/loop.d.ts +4 -3
  159. package/dist/runtime/maintenance.d.ts +4 -2
  160. package/dist/runtime/maintenance.js +17 -5
  161. package/dist/runtime/model-call.d.ts +17 -1
  162. package/dist/runtime/model-call.js +3 -2
  163. package/dist/runtime/providers.d.ts +6 -0
  164. package/dist/runtime/providers.js +8 -0
  165. package/dist/runtime/run-queue.d.ts +25 -2
  166. package/dist/runtime/run-queue.js +129 -33
  167. package/dist/runtime/run-worker.js +16 -3
  168. package/dist/runtime/runtime.module.js +8 -3
  169. package/dist/runtime/step-log.d.ts +6 -0
  170. package/dist/runtime/step-log.js +16 -2
  171. package/dist/runtime/subagents/definition.js +3 -1
  172. package/dist/runtime/usage-report.d.ts +2 -1
  173. package/dist/runtime/usage-report.js +2 -1
  174. package/dist/runtime/waits.d.ts +8 -2
  175. package/dist/runtime/waits.js +37 -11
  176. package/dist/sandbox/sandbox.module.js +6 -1
  177. package/dist/sandbox/sandbox.service.d.ts +61 -3
  178. package/dist/sandbox/sandbox.service.js +141 -7
  179. package/dist/tools/approvals.service.js +2 -0
  180. package/dist/tools/builtin-names.d.ts +20 -2
  181. package/dist/tools/builtin-names.js +29 -2
  182. package/dist/tools/builtins/builtin-tools.d.ts +15 -1
  183. package/dist/tools/builtins/builtin-tools.js +34 -2
  184. package/dist/tools/builtins/sandbox/bash.d.ts +1 -1
  185. package/dist/tools/builtins/sandbox/bash.js +230 -3
  186. package/dist/tools/builtins/sandbox/common.d.ts +27 -4
  187. package/dist/tools/builtins/sandbox/common.js +7 -3
  188. package/dist/tools/builtins/sandbox/deliver.js +1 -1
  189. package/dist/tools/builtins/sandbox/index.d.ts +2 -0
  190. package/dist/tools/builtins/sandbox/index.js +6 -0
  191. package/dist/tools/builtins/sandbox/task-output.d.ts +63 -0
  192. package/dist/tools/builtins/sandbox/task-output.js +304 -0
  193. package/dist/tools/builtins/sandbox/task-stop.d.ts +10 -0
  194. package/dist/tools/builtins/sandbox/task-stop.js +112 -0
  195. package/dist/tools/builtins/web/errors.d.ts +8 -0
  196. package/dist/tools/builtins/web/errors.js +53 -0
  197. package/dist/tools/builtins/web/extract.d.ts +62 -0
  198. package/dist/tools/builtins/web/extract.js +267 -0
  199. package/dist/tools/builtins/web/fetch.d.ts +55 -0
  200. package/dist/tools/builtins/web/fetch.js +174 -0
  201. package/dist/tools/builtins/web/index.d.ts +13 -0
  202. package/dist/tools/builtins/web/index.js +12 -0
  203. package/dist/tools/builtins/web/page-cache.d.ts +25 -0
  204. package/dist/tools/builtins/web/page-cache.js +77 -0
  205. package/dist/tools/builtins/web/search.d.ts +25 -0
  206. package/dist/tools/builtins/web/search.js +209 -0
  207. package/dist/tools/builtins/web/url.d.ts +17 -0
  208. package/dist/tools/builtins/web/url.js +43 -0
  209. package/dist/tools/oauth/flow-paths.d.ts +1 -1
  210. package/dist/tools/oauth/flow-paths.js +1 -1
  211. package/dist/tools/pipeline.js +15 -0
  212. package/dist/tools/registry.d.ts +22 -3
  213. package/dist/tools/registry.js +26 -11
  214. package/dist/tools/tools.module.js +12 -0
  215. package/dist/tools/validate.js +1 -1
  216. package/package.json +3 -3
  217. package/src/migrations/0064_account_last_org.sql +7 -0
  218. package/src/migrations/0065_conversation_archive.sql +8 -0
  219. package/src/migrations/0066_extension_display_name.sql +4 -0
  220. package/src/migrations/0067_agent_profiles.sql +20 -0
  221. package/src/migrations/0068_agent_avatar_uploads.sql +7 -0
  222. package/src/migrations/0069_attachments_agent_idx.sql +3 -0
  223. package/src/migrations/0070_background_tasks.sql +37 -0
  224. package/src/migrations/0071_background_tasks_reserve.sql +6 -0
  225. package/src/migrations/0072_background_tasks_stopping.sql +5 -0
  226. package/src/migrations/0073_org_extension_point_defaults.sql +11 -0
@@ -0,0 +1,57 @@
1
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
2
+ /**
3
+ * The wake rules of a finished background task (spec 2026-11-05-background-bash-design §3), read
4
+ * by `RunQueue.ensureRun` in its transaction: every path that may start a run (a finish, the end of
5
+ * a run, the delayed wake job, a page opening) applies the same rules. `BackgroundWake` is what
6
+ * calls `ensureRun` when a task finishes.
7
+ */
8
+ /** No two wakes of one conversation start within this window. */
9
+ export declare const WAKE_WINDOW_MS = 60000;
10
+ /** The delayed job that wakes a conversation at the end of its window (`cap-kernel-maintenance`). */
11
+ export declare const BG_WAKE_JOB = "bg-wake";
12
+ /**
13
+ * Its job id: one job per conversation and window end (no `:`). `retry` > 0 is a job that fired
14
+ * before the window ended on the database's clock and scheduled itself again: a distinct id, since
15
+ * BullMQ ignores an add under the id of the job still running.
16
+ */
17
+ export declare const bgWakeJobId: (conversationId: string, windowEnd: number, retry?: number) => string;
18
+ export interface BgWakeJobData {
19
+ conversationId: string;
20
+ /** How many times this window's wake fired early (absent: none). */
21
+ retry?: number;
22
+ }
23
+ /**
24
+ * What a conversation's unconsumed finishes decide:
25
+ * - `wake`: start a run whose boundary is the finish at `seq`, as `triggerAccountId`;
26
+ * - `later`: a wake started less than {@link WAKE_WINDOW_MS} ago; the window ends at `windowEnd`
27
+ * (epoch ms, the database's clock), `delayMs` from now;
28
+ * - null: nothing wakes the conversation.
29
+ */
30
+ export type WakeDecision = {
31
+ kind: "wake";
32
+ seq: number;
33
+ triggerAccountId: string;
34
+ } | {
35
+ kind: "later";
36
+ windowEnd: number;
37
+ delayMs: number;
38
+ } | null;
39
+ /**
40
+ * The wake decision for a chat conversation that is idle, whose queue is not paused and has no
41
+ * unconsumed message (the caller checked these under the conversation's row lock). A finish
42
+ * wakes it when:
43
+ * - it is after `consumed`, not `reported` (a tool call already told the model) and has no
44
+ * `stoppedBy` (`"person"`: that person is looking at the conversation; `"deletion"`: the
45
+ * conversation is being deleted);
46
+ * - it belongs to a task of this conversation (the join on `conversation_id` reads the task's
47
+ * trigger account). A sub-agent's task is excluded by the conversation's kind, not here: its
48
+ * finish is in the child's log, and `ensureRun` never wakes a non-chat conversation;
49
+ * - the wake setting is on (the org's override, else `deploymentOn`);
50
+ * - the task's trigger account can still send to the conversation (`canSend`);
51
+ * - no other wake started in the last {@link WAKE_WINDOW_MS} (else `later`).
52
+ * The boundary is the newest finish whose trigger account can send.
53
+ */
54
+ export declare function wakeDecisionIn(t: KernelSchemaDb, conv: {
55
+ id: string;
56
+ orgId: string;
57
+ }, consumed: number, deploymentOn: boolean, canSend: (accountId: string) => Promise<boolean>): Promise<WakeDecision>;
@@ -0,0 +1,83 @@
1
+ import { sql } from "kysely";
2
+ import { effectiveWakeOnBackgroundTask, readOrgSettings } from "../orgs/settings.service.js";
3
+ /**
4
+ * The wake rules of a finished background task (spec 2026-11-05-background-bash-design §3), read
5
+ * by `RunQueue.ensureRun` in its transaction: every path that may start a run (a finish, the end of
6
+ * a run, the delayed wake job, a page opening) applies the same rules. `BackgroundWake` is what
7
+ * calls `ensureRun` when a task finishes.
8
+ */
9
+ /** No two wakes of one conversation start within this window. */
10
+ export const WAKE_WINDOW_MS = 60_000;
11
+ /** The delayed job that wakes a conversation at the end of its window (`cap-kernel-maintenance`). */
12
+ export const BG_WAKE_JOB = "bg-wake";
13
+ /**
14
+ * Its job id: one job per conversation and window end (no `:`). `retry` > 0 is a job that fired
15
+ * before the window ended on the database's clock and scheduled itself again: a distinct id, since
16
+ * BullMQ ignores an add under the id of the job still running.
17
+ */
18
+ export const bgWakeJobId = (conversationId, windowEnd, retry = 0) => `bg-wake-${conversationId}-${windowEnd}${retry > 0 ? `-r${retry}` : ""}`;
19
+ /** How many unconsumed finishes are tried for a trigger account that can still send. */
20
+ const CANDIDATES = 20;
21
+ /**
22
+ * The wake decision for a chat conversation that is idle, whose queue is not paused and has no
23
+ * unconsumed message (the caller checked these under the conversation's row lock). A finish
24
+ * wakes it when:
25
+ * - it is after `consumed`, not `reported` (a tool call already told the model) and has no
26
+ * `stoppedBy` (`"person"`: that person is looking at the conversation; `"deletion"`: the
27
+ * conversation is being deleted);
28
+ * - it belongs to a task of this conversation (the join on `conversation_id` reads the task's
29
+ * trigger account). A sub-agent's task is excluded by the conversation's kind, not here: its
30
+ * finish is in the child's log, and `ensureRun` never wakes a non-chat conversation;
31
+ * - the wake setting is on (the org's override, else `deploymentOn`);
32
+ * - the task's trigger account can still send to the conversation (`canSend`);
33
+ * - no other wake started in the last {@link WAKE_WINDOW_MS} (else `later`).
34
+ * The boundary is the newest finish whose trigger account can send.
35
+ */
36
+ export async function wakeDecisionIn(t, conv, consumed, deploymentOn, canSend) {
37
+ const candidates = await t
38
+ .selectFrom("conversation_events as e")
39
+ .innerJoin("background_tasks as b", (j) => j
40
+ .on(sql `b.id = e.payload ->> 'taskId'`)
41
+ .onRef("b.conversation_id", "=", "e.conversation_id"))
42
+ .select(["e.seq", "b.trigger_account_id"])
43
+ .where("e.conversation_id", "=", conv.id)
44
+ .where("e.type", "=", "background_task_finished")
45
+ .where("e.seq", ">", String(consumed))
46
+ .where(sql `(e.payload ->> 'reported')::boolean = false`)
47
+ .where(sql `e.payload ->> 'stoppedBy' IS NULL`)
48
+ .orderBy("e.seq", "desc")
49
+ .limit(CANDIDATES)
50
+ .execute();
51
+ if (candidates.length === 0)
52
+ return null;
53
+ const org = (await readOrgSettings(t, conv.orgId)).wakeOnBackgroundTask;
54
+ if (!effectiveWakeOnBackgroundTask(deploymentOn, org))
55
+ return null;
56
+ let boundary;
57
+ for (const c of candidates) {
58
+ if (await canSend(c.trigger_account_id)) {
59
+ boundary = { seq: Number(c.seq), triggerAccountId: c.trigger_account_id };
60
+ break;
61
+ }
62
+ }
63
+ if (boundary === undefined)
64
+ return null;
65
+ // the window starts when the last wake run was created (PG, so every replica reads the same)
66
+ const last = await t
67
+ .selectFrom("runs as r")
68
+ .innerJoin("conversation_events as w", (j) => j.onRef("w.conversation_id", "=", "r.conversation_id").onRef("w.seq", "=", "r.trigger_seq"))
69
+ .select([
70
+ sql `floor(extract(epoch from max(r.created_at)) * 1000)::bigint`.as("started"),
71
+ sql `floor(extract(epoch from now()) * 1000)::bigint`.as("now"),
72
+ ])
73
+ .where("r.conversation_id", "=", conv.id)
74
+ .where("w.type", "=", "background_task_finished")
75
+ .executeTakeFirst();
76
+ if (last?.started != null) {
77
+ const windowEnd = Number(last.started) + WAKE_WINDOW_MS;
78
+ const now = Number(last.now);
79
+ if (windowEnd > now)
80
+ return { kind: "later", windowEnd, delayMs: windowEnd - now };
81
+ }
82
+ return { kind: "wake", ...boundary };
83
+ }
@@ -0,0 +1,28 @@
1
+ import type { Logger } from "@capybara-hq/plugin-sdk";
2
+ import { type OnApplicationShutdown } from "@nestjs/common";
3
+ import { KernelEvents } from "../infra/events/kernel-events.js";
4
+ import { RunQueue } from "../runtime/run-queue.js";
5
+ /**
6
+ * Wakes an idle conversation when one of its background tasks finished (spec
7
+ * 2026-11-05-background-bash-design §3). {@link BackgroundTaskFinisher} announces the committed
8
+ * finish (`background_task.finished`, an in-process domain event: the finisher's module sits
9
+ * below the runtime); {@link onFinished} asks the run queue for a run. The wake rules themselves
10
+ * (own task, not reported, the wake setting, the queue not paused, the trigger account can still
11
+ * send, the 60 s window and its delayed `bg-wake` job) are applied by `RunQueue.ensureRun` in its
12
+ * transaction (`wakeDecisionIn`, ./wake-rules.ts), so a run's end, the delayed job and a page
13
+ * opening apply the same rules. Provided by `RuntimeModule`.
14
+ */
15
+ export declare class BackgroundWake implements OnApplicationShutdown {
16
+ private readonly runs;
17
+ private readonly log;
18
+ /** Drops the subscription (the events may outlive this instance). */
19
+ private readonly unsubscribe;
20
+ constructor(runs: Pick<RunQueue, "ensureRun">, events: KernelEvents, log: Pick<Logger, "warn">);
21
+ /**
22
+ * A task of `conversationId` finished (after the commit): starts a wake run when the rules
23
+ * allow it, or schedules one at the end of the wake window. A run in progress absorbs the finish
24
+ * at its next step instead, or its end starts the wake. Never throws.
25
+ */
26
+ onFinished(conversationId: string): Promise<void>;
27
+ onApplicationShutdown(): void;
28
+ }
@@ -0,0 +1,57 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
8
+ return function (target, key) { decorator(target, key, paramIndex); }
9
+ };
10
+ import { Inject, Injectable } from "@nestjs/common";
11
+ import { KernelEvents } from "../infra/events/kernel-events.js";
12
+ import { KERNEL_LOGGER } from "../infra/tokens.js";
13
+ import { RunQueue } from "../runtime/run-queue.js";
14
+ /**
15
+ * Wakes an idle conversation when one of its background tasks finished (spec
16
+ * 2026-11-05-background-bash-design §3). {@link BackgroundTaskFinisher} announces the committed
17
+ * finish (`background_task.finished`, an in-process domain event: the finisher's module sits
18
+ * below the runtime); {@link onFinished} asks the run queue for a run. The wake rules themselves
19
+ * (own task, not reported, the wake setting, the queue not paused, the trigger account can still
20
+ * send, the 60 s window and its delayed `bg-wake` job) are applied by `RunQueue.ensureRun` in its
21
+ * transaction (`wakeDecisionIn`, ./wake-rules.ts), so a run's end, the delayed job and a page
22
+ * opening apply the same rules. Provided by `RuntimeModule`.
23
+ */
24
+ let BackgroundWake = class BackgroundWake {
25
+ runs;
26
+ log;
27
+ /** Drops the subscription (the events may outlive this instance). */
28
+ unsubscribe;
29
+ constructor(runs, events, log) {
30
+ this.runs = runs;
31
+ this.log = log;
32
+ this.unsubscribe = events.on("background_task.finished", ({ conversationId }) => this.onFinished(conversationId));
33
+ }
34
+ /**
35
+ * A task of `conversationId` finished (after the commit): starts a wake run when the rules
36
+ * allow it, or schedules one at the end of the wake window. A run in progress absorbs the finish
37
+ * at its next step instead, or its end starts the wake. Never throws.
38
+ */
39
+ async onFinished(conversationId) {
40
+ try {
41
+ await this.runs.ensureRun(conversationId);
42
+ }
43
+ catch (err) {
44
+ this.log.warn("background task wake failed", { conversationId, err });
45
+ }
46
+ }
47
+ onApplicationShutdown() {
48
+ this.unsubscribe();
49
+ }
50
+ };
51
+ BackgroundWake = __decorate([
52
+ Injectable(),
53
+ __param(0, Inject(RunQueue)),
54
+ __param(1, Inject(KernelEvents)),
55
+ __param(2, Inject(KERNEL_LOGGER))
56
+ ], BackgroundWake);
57
+ export { BackgroundWake };
@@ -26,6 +26,8 @@ const TOOL_MAP = {
26
26
  Agent: "Agent",
27
27
  Task: "Agent",
28
28
  TodoWrite: "plugin__todo__write",
29
+ WebFetch: "WebFetch",
30
+ WebSearch: "WebSearch",
29
31
  };
30
32
  /** Claude ignores these for plugin agents: `not_applicable`. */
31
33
  const NOT_APPLICABLE = new Set(["permissionMode", "hooks", "mcpServers"]);
@@ -164,7 +164,7 @@ export declare function headsToResolve(entries: readonly MarketplaceEntry[], imp
164
164
  /** The host versions (an entry's `minKernel` is judged against them). */
165
165
  export declare const HOST_VERSIONS: {
166
166
  readonly kernel: string;
167
- readonly sdk: "0.1.0-rc.1";
167
+ readonly sdk: "0.1.0-rc.2";
168
168
  };
169
169
  /** Whether this deployment satisfies the entry's `minKernel`. */
170
170
  export declare const entryCompatible: (e: Pick<MarketplaceEntry, "minKernel">) => boolean;
@@ -33,8 +33,8 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
33
33
  import { packPackageTgz } from "../packages/tgz.js";
34
34
  import { checkSkillFiles, SkillArchiveError } from "../skills/package.js";
35
35
  import { StorageService } from "../storage/storage.service.js";
36
+ import { canonicalJson } from "../tools/canonical-json.js";
36
37
  import { endpointCredentialIssues } from "../tools/endpoint.js";
37
- import { canonicalJson } from "../tools/registry.js";
38
38
  import { findConnectorIn } from "./catalog-content.js";
39
39
  import { assertPluginNameOwner, catalogObjectPrefix, itemView, permissionsMismatch, precheckCatalogImport, putCatalogFiles, templateProblems, writeCatalogVersion, } from "./catalog-items.js";
40
40
  import { CLAUDE_PLUGIN_FILE, claudeContentDigest, } from "./claude/manifest.js";
@@ -24,8 +24,8 @@ import { OrgLock } from "../orgs/org-lock.js";
24
24
  import { packagePath } from "../packages/tgz.js";
25
25
  import { SkillsService, skillConflictError } from "../skills/skills.service.js";
26
26
  import { StorageService } from "../storage/storage.service.js";
27
+ import { canonicalJson } from "../tools/canonical-json.js";
27
28
  import { ConnectorsService } from "../tools/connectors.service.js";
28
- import { canonicalJson } from "../tools/registry.js";
29
29
  import { getPath, lockOrgForActions, orgTemplateView, readCatalogAgentTemplates, readCatalogFiles, readCatalogTemplates, requiredActions, setPath, skillOf, templatePluginProblem, } from "./catalog-content.js";
30
30
  import { catalogSkillNames, InstallFailure, journalEntries, missingTemplateInput, PluginInstaller, planActions, planSkillConflicts, resolveDependencies, under, } from "./plugin-install.js";
31
31
  import { assertItemInstallable, CatalogRequestsService } from "./requests.service.js";
@@ -83,7 +83,7 @@ export function webBase(publicUrl) {
83
83
  /** 会话的网页链接(与审批详情的 `conversationPath` 一致:`/chat/<id>`,不含语言前缀)。 */
84
84
  export const conversationUrl = (base, conversationId) => `${base}/chat/${conversationId}`;
85
85
  /** 个人连接页里某个连接器的位置(与网页 ConnectCard 的链接一致)。 */
86
- export const connectUrl = (base, connectorId) => `${base}/settings/connections#connector-${encodeURIComponent(connectorId)}`;
86
+ export const connectUrl = (base, connectorId) => `${base}/account/connections#connector-${encodeURIComponent(connectorId)}`;
87
87
  /**
88
88
  * The tool as the card names it (tool naming spec §7): a builtin by its name; a connector or
89
89
  * extension tool as `<display name> [<tool>]` (the connector's name, the plugin's display name in
@@ -106,6 +106,8 @@ export declare class ChannelConnections implements OnApplicationShutdown {
106
106
  private readonly log;
107
107
  readonly txHost: TransactionHost;
108
108
  private readonly unsubscribe;
109
+ /** `<plugin>@<version>` whose invalid connection hints were warned about (a log dedupe only). */
110
+ private readonly hintsWarned;
109
111
  constructor(db: KernelDb,
110
112
  /** The org row lock (orgs/org-lock.ts): joins the transaction of the write. */
111
113
  orgLock: Pick<OrgLock, "lockAndAuthorize">, keyring: Keyring, registry: Pick<ExtensionRegistry, "list">, bus: ExtensionContext["bus"], redis: Pick<Redis, "del">,
@@ -122,8 +124,21 @@ export declare class ChannelConnections implements OnApplicationShutdown {
122
124
  private channelEntry;
123
125
  /** The extension is not enabled in this organization -> `extension.not_enabled`. */
124
126
  private assertEnabled;
125
- /** `connectionSchema` 的 JSON Schema(同组织插件配置的转换:密钥字段带 `x-secret`、不输出取值)。 */
127
+ /**
128
+ * The connection schema's form JSON Schema (secret fields carry `x-secret`, no values), a copy.
129
+ * Never throws: when even the plain conversion fails, an empty object schema (a save is still
130
+ * validated here).
131
+ */
126
132
  jsonSchema(plugin: string): JsonSchema;
133
+ /**
134
+ * The connection schema's form JSON Schema; invalid hints are stripped and warned about once per
135
+ * `<plugin>@<version>` on this replica (spec §4.1: the connection level is checked at its first
136
+ * conversion, not at load). Never throws: a hint conversion that throws serves the plain schema,
137
+ * else null, with one warning per schema.
138
+ */
139
+ private formSchema;
140
+ /** The loaded version of a channel extension, for warnings ("unknown" when not provided). */
141
+ private versionOf;
127
142
  private parse;
128
143
  private verify;
129
144
  private reportSecrets;
@@ -16,7 +16,7 @@ import { queryEnabledPlugins } from "../ctx/create-ctx.js";
16
16
  import { createSecrets } from "../ctx/secrets.js";
17
17
  import { ApiError } from "../errors.js";
18
18
  import { toIssues } from "../extensions/config-issues.js";
19
- import { configToJsonSchema } from "../extensions/config-json-schema.js";
19
+ import { configMessages, describeConfigUiProblems, safeConfigFormSchema, } from "../extensions/config-json-schema.js";
20
20
  import { enabledExtensionsByOrg } from "../extensions/enabled.js";
21
21
  import { ExtensionRegistry } from "../extensions/registry.js";
22
22
  import { audit } from "../identity/audit.js";
@@ -121,6 +121,8 @@ let ChannelConnections = class ChannelConnections {
121
121
  log;
122
122
  txHost;
123
123
  unsubscribe;
124
+ /** `<plugin>@<version>` whose invalid connection hints were warned about (a log dedupe only). */
125
+ hintsWarned = new Set();
124
126
  constructor(db,
125
127
  /** The org row lock (orgs/org-lock.ts): joins the transaction of the write. */
126
128
  orgLock, keyring, registry, bus, redis,
@@ -171,14 +173,46 @@ let ChannelConnections = class ChannelConnections {
171
173
  throw new ApiError("extension.not_enabled", `Extension ${plugin} is not enabled for this organization`);
172
174
  }
173
175
  }
174
- /** `connectionSchema` 的 JSON Schema(同组织插件配置的转换:密钥字段带 `x-secret`、不输出取值)。 */
176
+ /**
177
+ * The connection schema's form JSON Schema (secret fields carry `x-secret`, no values), a copy.
178
+ * Never throws: when even the plain conversion fails, an empty object schema (a save is still
179
+ * validated here).
180
+ */
175
181
  jsonSchema(plugin) {
176
- return configToJsonSchema(this.channel(plugin).connectionSchema);
182
+ const entry = this.channelEntry(plugin);
183
+ const r = this.formSchema(plugin, entry.version, entry.impl.connectionSchema);
184
+ return r ? structuredClone(r.schema) : { type: "object" };
185
+ }
186
+ /**
187
+ * The connection schema's form JSON Schema; invalid hints are stripped and warned about once per
188
+ * `<plugin>@<version>` on this replica (spec §4.1: the connection level is checked at its first
189
+ * conversion, not at load). Never throws: a hint conversion that throws serves the plain schema,
190
+ * else null, with one warning per schema.
191
+ */
192
+ formSchema(plugin, version, schema) {
193
+ const r = safeConfigFormSchema(schema, "connection", (err, fallback) => this.log.warn("the channel connection form schema could not be built with its UI hints; hints dropped", { plugin, version, schema: fallback, err }));
194
+ const key = `${plugin}@${version}`;
195
+ if (r && r.problems.length > 0 && !this.hintsWarned.has(key)) {
196
+ this.hintsWarned.add(key);
197
+ this.log.warn("extension config UI hints are invalid and ignored", {
198
+ plugin,
199
+ version,
200
+ detail: describeConfigUiProblems("connection", r.problems),
201
+ });
202
+ }
203
+ return r;
204
+ }
205
+ /** The loaded version of a channel extension, for warnings ("unknown" when not provided). */
206
+ versionOf(plugin) {
207
+ return (this.registry
208
+ .list("channel")
209
+ .find((x) => x.plugin === plugin && x.capability === CHANNEL_EXTENSION_POINT)?.version ??
210
+ "unknown");
177
211
  }
178
212
  parse(plugin, schema, config) {
179
213
  const r = schema.safeParse(config);
180
214
  if (!r.success) {
181
- throw new ApiError("extension.config_invalid", `Invalid channel connection config for extension ${plugin}`, toIssues(r.error));
215
+ throw new ApiError("extension.config_invalid", `Invalid channel connection config for extension ${plugin}`, toIssues(r.error, configMessages(this.formSchema(plugin, this.versionOf(plugin), schema)?.schema)));
182
216
  }
183
217
  return r.data;
184
218
  }
@@ -48,3 +48,8 @@ export declare function findThread(db: KernelSchemaDb, k: ThreadKey): Promise<st
48
48
  export declare function insertThread(db: KernelSchemaDb, k: ThreadKey, conversationId: string): Promise<string>;
49
49
  /** 私聊 `/new`:归档当前话题行(下一条消息新建对话)。返回是否归档了一行。 */
50
50
  export declare function archiveThread(db: KernelSchemaDb, k: ThreadKey): Promise<boolean>;
51
+ /**
52
+ * Drops a conversation's channel bindings (its thread rows and its delivery target), so a deleted
53
+ * conversation neither receives inbound messages nor posts to the chat any more.
54
+ */
55
+ export declare function unbindConversation(db: KernelSchemaDb, conversationId: string): Promise<void>;
@@ -160,3 +160,11 @@ export async function archiveThread(db, k) {
160
160
  .executeTakeFirst();
161
161
  return r.numUpdatedRows > 0n;
162
162
  }
163
+ /**
164
+ * Drops a conversation's channel bindings (its thread rows and its delivery target), so a deleted
165
+ * conversation neither receives inbound messages nor posts to the chat any more.
166
+ */
167
+ export async function unbindConversation(db, conversationId) {
168
+ await db.deleteFrom("channel_threads").where("conversation_id", "=", conversationId).execute();
169
+ await db.deleteFrom("channel_deliveries").where("conversation_id", "=", conversationId).execute();
170
+ }
@@ -51,6 +51,7 @@ export declare const KernelConfigSchema: z.ZodObject<{
51
51
  url: z.ZodString;
52
52
  poolMax: z.ZodDefault<z.ZodNumber>;
53
53
  connectTimeoutMs: z.ZodDefault<z.ZodNumber>;
54
+ idleTimeoutMs: z.ZodDefault<z.ZodNumber>;
54
55
  }, z.core.$strip>>;
55
56
  redis: z.ZodPrefault<z.ZodObject<{
56
57
  url: z.ZodString;
@@ -119,6 +120,7 @@ export declare const KernelConfigSchema: z.ZodObject<{
119
120
  }, z.core.$strip>>;
120
121
  runtime: z.ZodDefault<z.ZodObject<{
121
122
  maxSteps: z.ZodDefault<z.ZodNumber>;
123
+ wakeOnBackgroundTask: z.ZodDefault<z.ZodBoolean>;
122
124
  maxRunSeconds: z.ZodDefault<z.ZodNumber>;
123
125
  streamIdleMs: z.ZodDefault<z.ZodNumber>;
124
126
  maxOutputTokens: z.ZodDefault<z.ZodNumber>;
@@ -179,11 +181,27 @@ export declare const KernelConfigSchema: z.ZodObject<{
179
181
  maxDispatchesPerRun: z.ZodDefault<z.ZodNumber>;
180
182
  maxTokensPerRun: z.ZodDefault<z.ZodNumber>;
181
183
  }, z.core.$strip>>;
184
+ web: z.ZodPrefault<z.ZodObject<{
185
+ search: z.ZodPrefault<z.ZodObject<{
186
+ maxResults: z.ZodDefault<z.ZodNumber>;
187
+ }, z.core.$strip>>;
188
+ fetch: z.ZodPrefault<z.ZodObject<{
189
+ maxPageChars: z.ZodDefault<z.ZodNumber>;
190
+ maxAnswerTokens: z.ZodDefault<z.ZodNumber>;
191
+ cacheSeconds: z.ZodDefault<z.ZodNumber>;
192
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
193
+ }, z.core.$strip>>;
194
+ }, z.core.$strip>>;
182
195
  sandbox: z.ZodPrefault<z.ZodObject<{
183
196
  image: z.ZodDefault<z.ZodString>;
184
197
  lockWaitSeconds: z.ZodDefault<z.ZodNumber>;
185
198
  maxConcurrentCommands: z.ZodDefault<z.ZodNumber>;
186
199
  deliverMaxBytes: z.ZodDefault<z.ZodNumber>;
200
+ background: z.ZodPrefault<z.ZodObject<{
201
+ maxTasks: z.ZodDefault<z.ZodNumber>;
202
+ maxSeconds: z.ZodDefault<z.ZodNumber>;
203
+ pollSeconds: z.ZodDefault<z.ZodNumber>;
204
+ }, z.core.$strip>>;
187
205
  limits: z.ZodPrefault<z.ZodObject<{
188
206
  cpu: z.ZodDefault<z.ZodNumber>;
189
207
  memoryMb: z.ZodDefault<z.ZodNumber>;
@@ -179,6 +179,14 @@ export const KernelConfigSchema = z.object({
179
179
  .positive()
180
180
  .default(5000)
181
181
  .meta(meta({ description: "PostgreSQL connect timeout in milliseconds" })),
182
+ idleTimeoutMs: z
183
+ .number()
184
+ .int()
185
+ .positive()
186
+ .default(60_000)
187
+ .meta(meta({
188
+ description: "How long an idle PostgreSQL connection stays in the pool, in milliseconds",
189
+ })),
182
190
  })
183
191
  .prefault({}),
184
192
  redis: z
@@ -568,6 +576,12 @@ export const KernelConfigSchema = z.object({
568
576
  .positive()
569
577
  .default(50)
570
578
  .meta(meta({ description: "Maximum model steps per agent run before the final wrap-up" })),
579
+ wakeOnBackgroundTask: z
580
+ .boolean()
581
+ .default(true)
582
+ .meta(meta({
583
+ description: "Whether the finish of a background Bash task starts a run on an idle conversation (the person who started the task's run is the trigger); an organization can override it in its settings. When false the event is only logged and shown, and the next message picks it up",
584
+ })),
571
585
  maxRunSeconds: z
572
586
  .number()
573
587
  .int()
@@ -656,6 +670,7 @@ export const KernelConfigSchema = z.object({
656
670
  })
657
671
  .default({
658
672
  maxSteps: 50,
673
+ wakeOnBackgroundTask: true,
659
674
  maxRunSeconds: DEFAULT_MAX_RUN_SECONDS,
660
675
  streamIdleMs: 60000,
661
676
  maxOutputTokens: 16000,
@@ -946,6 +961,64 @@ export const KernelConfigSchema = z.object({
946
961
  })),
947
962
  })
948
963
  .prefault({}),
964
+ web: z
965
+ .object({
966
+ search: z
967
+ .object({
968
+ maxResults: z
969
+ .number()
970
+ .int()
971
+ .min(1)
972
+ .max(20)
973
+ .default(10)
974
+ .meta(meta({
975
+ hot: true,
976
+ description: "Maximum number of results one WebSearch call returns to the model, however many the search provider returns",
977
+ })),
978
+ })
979
+ .prefault({}),
980
+ fetch: z
981
+ .object({
982
+ maxPageChars: z
983
+ .number()
984
+ .int()
985
+ .positive()
986
+ .default(400_000)
987
+ .meta(meta({
988
+ hot: true,
989
+ description: "Maximum characters of a fetched page kept for answering a WebFetch prompt; longer pages are truncated",
990
+ })),
991
+ maxAnswerTokens: z
992
+ .number()
993
+ .int()
994
+ .positive()
995
+ .default(4000)
996
+ .meta(meta({
997
+ hot: true,
998
+ description: "Maximum output tokens of the model call that answers a WebFetch prompt from the page",
999
+ })),
1000
+ cacheSeconds: z
1001
+ .number()
1002
+ .int()
1003
+ .min(0)
1004
+ .default(900)
1005
+ .meta(meta({
1006
+ hot: true,
1007
+ description: "Seconds a fetched page is cached per organization and URL; 0 turns the cache off",
1008
+ })),
1009
+ timeoutMs: z
1010
+ .number()
1011
+ .int()
1012
+ .positive()
1013
+ .default(180_000)
1014
+ .meta(meta({
1015
+ hot: true,
1016
+ description: "Time limit of one WebFetch call in milliseconds, the provider's fetch and the answering model call together; replaces tools.timeoutMs for WebFetch",
1017
+ })),
1018
+ })
1019
+ .prefault({}),
1020
+ })
1021
+ .prefault({}),
949
1022
  sandbox: z
950
1023
  .object({
951
1024
  image: z
@@ -980,6 +1053,35 @@ export const KernelConfigSchema = z.object({
980
1053
  .meta(meta({
981
1054
  description: "Largest file in bytes an agent may deliver from its sandbox to the user as an artifact (Deliver)",
982
1055
  })),
1056
+ background: z
1057
+ .object({
1058
+ maxTasks: z
1059
+ .number()
1060
+ .int()
1061
+ .min(1)
1062
+ .max(100)
1063
+ .default(4)
1064
+ .meta(meta({
1065
+ description: "Most background Bash tasks (run_in_background) that may run at once in one conversation's sandbox, sub-agents included; a launch beyond it is refused",
1066
+ })),
1067
+ maxSeconds: z
1068
+ .number()
1069
+ .int()
1070
+ .positive()
1071
+ .default(7200)
1072
+ .meta(meta({
1073
+ description: "Longest a background Bash task may run, in seconds; the task is killed at its deadline and ends as timed_out. Clamped to sandbox.limits.maxCommandSeconds; not limited by runtime.maxRunSeconds",
1074
+ })),
1075
+ pollSeconds: z
1076
+ .number()
1077
+ .int()
1078
+ .min(1)
1079
+ .default(10)
1080
+ .meta(meta({
1081
+ description: "How often the worker checks the running background Bash tasks of every sandbox for a finish, in seconds; read when the worker starts",
1082
+ })),
1083
+ })
1084
+ .prefault({}),
983
1085
  limits: z
984
1086
  .object({
985
1087
  cpu: z
@@ -27,6 +27,7 @@ export const parentReachable = (viewerAccountId) => (eb) => eb.or([
27
27
  .whereRef("p.org_id", "=", "conversations.org_id")
28
28
  .where("p.account_id", "=", viewerAccountId)
29
29
  .where("p.kind", "=", "chat")
30
+ .where("p.deleted_at", "is", null)
30
31
  .where("agents.deleted_at", "is", null)
31
32
  .where(visibleTo(viewerAccountId))),
32
33
  ]);
@@ -53,6 +54,7 @@ let ConversationAccess = class ConversationAccess {
53
54
  .where("conversations.id", "=", conversationId)
54
55
  .where("conversations.org_id", "=", orgId)
55
56
  .where("conversations.account_id", "=", viewerAccountId)
57
+ .where("conversations.deleted_at", "is", null)
56
58
  .where("conversations.kind", "in", ["chat", "subagent"])
57
59
  .where(parentReachable(viewerAccountId))
58
60
  .where((eb) => eb.and([
@@ -28,6 +28,16 @@ export declare const MAX_PENDING_SKILL_PACKAGES = 5;
28
28
  * `POST /skills` 开始处理时再顺延这么久,处理期间不会被清理。
29
29
  */
30
30
  export declare const SKILL_PACKAGE_TTL_HOURS = 1;
31
+ /** The image types an agent avatar upload may have (cropped square in the browser). */
32
+ export declare const AGENT_AVATAR_CONTENT_TYPES: readonly ["image/png", "image/jpeg", "image/webp"];
33
+ /** The size limit of an agent avatar upload (bytes). */
34
+ export declare const AGENT_AVATAR_MAX_BYTES: number;
35
+ /** Unfinished avatar uploads one account may have in an org (counted apart from other uploads). */
36
+ export declare const MAX_PENDING_AGENT_AVATARS = 10;
37
+ /** An avatar upload not saved into a profile expires (and is cleaned up) after this many hours. */
38
+ export declare const AGENT_AVATAR_TTL_HOURS = 24;
39
+ /** Download redirect lifetime of an avatar (seconds); also its `Cache-Control` max-age. */
40
+ export declare const AGENT_AVATAR_DOWNLOAD_TTL_SECONDS = 300;
31
41
  /**
32
42
  * 技能包解出的文件在 `kernel/<orgId>/` 下的前缀:按上传 id,同一上传重试写到同一批键(幂等),
33
43
  * 并发处理同一上传也不会互相覆盖出不同内容。版本行记下这个前缀(`skill_versions.object_prefix`)。
@@ -169,6 +179,26 @@ export declare class AttachmentsService {
169
179
  * 不是该账号的 ready 技能包(如已被并发的请求用掉)→ `attachment.not_found`。
170
180
  */
171
181
  consumeSkillPackage(orgId: string, accountId: string, id: string): Promise<void>;
182
+ /**
183
+ * Makes an agent avatar upload ready to be saved into a profile (outside any transaction: it may
184
+ * read the object store): only the uploader, only the `agent-avatar` purpose, an image type, not
185
+ * expired and not yet used by another agent, else `attachment.not_found`. An upload still
186
+ * `pending` is finalized first (size, type and the image signature are checked). The expiry is
187
+ * extended so the cleanup job cannot delete it before {@link consumeAgentAvatar} runs.
188
+ */
189
+ prepareAgentAvatar(orgId: string, accountId: string, id: string, agentId: string): Promise<void>;
190
+ /**
191
+ * In the caller's transaction, binds a ready avatar upload to `agentId` (row lock; the expiry is
192
+ * cleared, so the cleanup job keeps it while the profile uses it). An upload another agent
193
+ * already uses, or one that is not the account's ready avatar → `attachment.not_found`; saving
194
+ * the same upload to the same agent again is fine.
195
+ */
196
+ consumeAgentAvatar(orgId: string, accountId: string, id: string, agentId: string): Promise<void>;
197
+ /**
198
+ * A short-lived presigned GET (inline, with the checked image type) of the avatar upload bound
199
+ * to `agentId`; `null` when there is none or its object is gone or no longer the finalized one.
200
+ */
201
+ agentAvatarUrl(orgId: string, agentId: string, id: string): Promise<string | null>;
172
202
  /**
173
203
  * 删除过期(`expires_at < now()`)且从未绑定到会话的附件:先把行认领为 `deleting`(`FOR UPDATE SKIP LOCKED`,
174
204
  * 多副本并发时互不重复),再删对象、删行。删对象失败的行保持 `deleting`,下次运行重试。