@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
@@ -40,6 +40,16 @@ export const MAX_PENDING_SKILL_PACKAGES = 5;
40
40
  * `POST /skills` 开始处理时再顺延这么久,处理期间不会被清理。
41
41
  */
42
42
  export const SKILL_PACKAGE_TTL_HOURS = 1;
43
+ /** The image types an agent avatar upload may have (cropped square in the browser). */
44
+ export const AGENT_AVATAR_CONTENT_TYPES = ["image/png", "image/jpeg", "image/webp"];
45
+ /** The size limit of an agent avatar upload (bytes). */
46
+ export const AGENT_AVATAR_MAX_BYTES = 2 * 1024 * 1024;
47
+ /** Unfinished avatar uploads one account may have in an org (counted apart from other uploads). */
48
+ export const MAX_PENDING_AGENT_AVATARS = 10;
49
+ /** An avatar upload not saved into a profile expires (and is cleaned up) after this many hours. */
50
+ export const AGENT_AVATAR_TTL_HOURS = 24;
51
+ /** Download redirect lifetime of an avatar (seconds); also its `Cache-Control` max-age. */
52
+ export const AGENT_AVATAR_DOWNLOAD_TTL_SECONDS = 300;
43
53
  /**
44
54
  * 技能包解出的文件在 `kernel/<orgId>/` 下的前缀:按上传 id,同一上传重试写到同一批键(幂等),
45
55
  * 并发处理同一上传也不会互相覆盖出不同内容。版本行记下这个前缀(`skill_versions.object_prefix`)。
@@ -56,7 +66,11 @@ const notFound = () => new ApiError("attachment.not_found", "Attachment not foun
56
66
  export const normalizeContentType = (ct) => (ct.split(";")[0] ?? "").trim().toLowerCase();
57
67
  const isImage = (ct) => IMAGE_MEDIA_TYPES.includes(ct);
58
68
  /** 对象键:只用附件 id,用户的文件名不进键。技能包另放 `skill-packages/<id>`。 */
59
- const objectKey = (id, purpose = "chat") => purpose === "chat" ? `attachments/${id}` : `skill-packages/${id}`;
69
+ const objectKey = (id, purpose = "chat") => purpose === "chat"
70
+ ? `attachments/${id}`
71
+ : purpose === "agent-avatar"
72
+ ? `agent-avatars/${id}`
73
+ : `skill-packages/${id}`;
60
74
  const startsWith = (b, sig, at = 0) => sig.every((x, i) => b[at + i] === x);
61
75
  const ascii = (s) => [...s].map((c) => c.charCodeAt(0));
62
76
  /** 文件开头是否与声明的类型相符(图片与 PDF 看签名,文本不能含 NUL)。 */
@@ -139,12 +153,19 @@ let AttachmentsService = class AttachmentsService {
139
153
  const cfg = this.config.get().attachments;
140
154
  const contentType = normalizeContentType(input.contentType);
141
155
  const skill = purpose === "skill-package";
156
+ const avatar = purpose === "agent-avatar";
142
157
  if (skill
143
158
  ? contentType !== SKILL_PACKAGE_CONTENT_TYPE
144
- : !cfg.allowedTypes.includes(contentType)) {
159
+ : avatar
160
+ ? !AGENT_AVATAR_CONTENT_TYPES.includes(contentType)
161
+ : !cfg.allowedTypes.includes(contentType)) {
145
162
  throw new ApiError("attachment.type_not_allowed", `Attachment type ${JSON.stringify(contentType)} is not allowed`);
146
163
  }
147
- const limit = skill ? SKILL_PACKAGE_UPLOAD_MAX_BYTES : this.limitFor(contentType);
164
+ const limit = skill
165
+ ? SKILL_PACKAGE_UPLOAD_MAX_BYTES
166
+ : avatar
167
+ ? AGENT_AVATAR_MAX_BYTES
168
+ : this.limitFor(contentType);
148
169
  if (input.size > limit) {
149
170
  throw new ApiError("attachment.too_large", `Attachment is ${input.size} bytes, over the limit of ${limit} bytes`);
150
171
  }
@@ -156,13 +177,21 @@ let AttachmentsService = class AttachmentsService {
156
177
  .where("status", "=", "pending")
157
178
  .where("purpose", "=", purpose)
158
179
  .executeTakeFirst();
159
- const maxPending = skill ? MAX_PENDING_SKILL_PACKAGES : MAX_PENDING_ATTACHMENTS;
180
+ const maxPending = skill
181
+ ? MAX_PENDING_SKILL_PACKAGES
182
+ : avatar
183
+ ? MAX_PENDING_AGENT_AVATARS
184
+ : MAX_PENDING_ATTACHMENTS;
160
185
  if (Number(pending?.n ?? 0) >= maxPending) {
161
186
  throw new ApiError("attachment.too_many_pending", `Too many unfinished uploads (at most ${maxPending}); finish or wait for them first`);
162
187
  }
163
188
  const id = randomUUID();
164
189
  const key = objectKey(id, purpose);
165
- const ttlHours = skill ? SKILL_PACKAGE_TTL_HOURS : cfg.unboundTtlHours;
190
+ const ttlHours = skill
191
+ ? SKILL_PACKAGE_TTL_HOURS
192
+ : avatar
193
+ ? AGENT_AVATAR_TTL_HOURS
194
+ : cfg.unboundTtlHours;
166
195
  const upload = await this.storage.kernel(orgId).presignPut(key, {
167
196
  contentType,
168
197
  maxSize: input.size,
@@ -541,6 +570,99 @@ let AttachmentsService = class AttachmentsService {
541
570
  .where("id", "=", id)
542
571
  .execute();
543
572
  }
573
+ /**
574
+ * Makes an agent avatar upload ready to be saved into a profile (outside any transaction: it may
575
+ * read the object store): only the uploader, only the `agent-avatar` purpose, an image type, not
576
+ * expired and not yet used by another agent, else `attachment.not_found`. An upload still
577
+ * `pending` is finalized first (size, type and the image signature are checked). The expiry is
578
+ * extended so the cleanup job cannot delete it before {@link consumeAgentAvatar} runs.
579
+ */
580
+ async prepareAgentAvatar(orgId, accountId, id, agentId) {
581
+ this.storage.assertAvailable();
582
+ const row = await this.db
583
+ .selectFrom("attachments")
584
+ .select([
585
+ "status",
586
+ "purpose",
587
+ "content_type",
588
+ "agent_id",
589
+ sql `expires_at is not null and expires_at <= now()`.as("expired"),
590
+ ])
591
+ .where("id", "=", id)
592
+ .where("org_id", "=", orgId)
593
+ .where("account_id", "=", accountId)
594
+ .executeTakeFirst();
595
+ if (row?.purpose !== "agent-avatar" ||
596
+ row.status === "deleting" ||
597
+ row.expired ||
598
+ (row.agent_id !== null && row.agent_id !== agentId) ||
599
+ !AGENT_AVATAR_CONTENT_TYPES.includes(row.content_type)) {
600
+ throw notFound();
601
+ }
602
+ if (row.status === "pending")
603
+ await this.finalize(orgId, accountId, id);
604
+ await this.db
605
+ .updateTable("attachments")
606
+ .set({ expires_at: sql `now() + make_interval(hours => ${AGENT_AVATAR_TTL_HOURS})` })
607
+ .where("id", "=", id)
608
+ .where("status", "=", "ready")
609
+ .where("agent_id", "is", null)
610
+ .where("expires_at", "is not", null)
611
+ .execute();
612
+ }
613
+ /**
614
+ * In the caller's transaction, binds a ready avatar upload to `agentId` (row lock; the expiry is
615
+ * cleared, so the cleanup job keeps it while the profile uses it). An upload another agent
616
+ * already uses, or one that is not the account's ready avatar → `attachment.not_found`; saving
617
+ * the same upload to the same agent again is fine.
618
+ */
619
+ async consumeAgentAvatar(orgId, accountId, id, agentId) {
620
+ const trx = this.db.db;
621
+ const row = await trx
622
+ .selectFrom("attachments")
623
+ .select(["status", "agent_id"])
624
+ .where("id", "=", id)
625
+ .where("org_id", "=", orgId)
626
+ .where("account_id", "=", accountId)
627
+ .where("purpose", "=", "agent-avatar")
628
+ .forUpdate()
629
+ .executeTakeFirst();
630
+ if (row?.status !== "ready" || (row.agent_id !== null && row.agent_id !== agentId)) {
631
+ throw notFound();
632
+ }
633
+ await trx
634
+ .updateTable("attachments")
635
+ .set({ agent_id: agentId, expires_at: null })
636
+ .where("id", "=", id)
637
+ .execute();
638
+ }
639
+ /**
640
+ * A short-lived presigned GET (inline, with the checked image type) of the avatar upload bound
641
+ * to `agentId`; `null` when there is none or its object is gone or no longer the finalized one.
642
+ */
643
+ async agentAvatarUrl(orgId, agentId, id) {
644
+ this.storage.assertAvailable();
645
+ const row = await this.db
646
+ .selectFrom("attachments")
647
+ .select(["content_type", "object_key", "etag"])
648
+ .where("id", "=", id)
649
+ .where("org_id", "=", orgId)
650
+ .where("agent_id", "=", agentId)
651
+ .where("purpose", "=", "agent-avatar")
652
+ .where("status", "=", "ready")
653
+ .executeTakeFirst();
654
+ if (!row)
655
+ return null;
656
+ const store = this.storage.kernel(orgId);
657
+ const head = await store.head(row.object_key);
658
+ if (!head || !this.sameContent(row.etag, head.etag, { orgId, attachmentId: id }))
659
+ return null;
660
+ return store.presignGet(row.object_key, {
661
+ ttlSeconds: AGENT_AVATAR_DOWNLOAD_TTL_SECONDS,
662
+ disposition: "inline",
663
+ responseContentType: row.content_type,
664
+ });
665
+ }
544
666
  /**
545
667
  * 删除过期(`expires_at < now()`)且从未绑定到会话的附件:先把行认领为 `deleting`(`FOR UPDATE SKIP LOCKED`,
546
668
  * 多副本并发时互不重复),再删对象、删行。删对象失败的行保持 `deleting`,下次运行重试。
@@ -613,6 +735,9 @@ __decorate([
613
735
  __decorate([
614
736
  Transactional({ propagation: "mandatory" })
615
737
  ], AttachmentsService.prototype, "consumeSkillPackage", null);
738
+ __decorate([
739
+ Transactional({ propagation: "mandatory" })
740
+ ], AttachmentsService.prototype, "consumeAgentAvatar", null);
616
741
  AttachmentsService = __decorate([
617
742
  Injectable(),
618
743
  __param(0, Inject(KernelDb)),
@@ -12,11 +12,27 @@ import type { QueueItem, QueuePauseReason } from "./queue.js";
12
12
  import { ConversationQueue } from "./queue.service.js";
13
13
  import { ConversationSandboxStatus, type SandboxStatusView } from "./sandbox-status.js";
14
14
  declare const ListQuery: z.ZodObject<{
15
+ q: z.ZodOptional<z.ZodString>;
16
+ state: z.ZodOptional<z.ZodEnum<{
17
+ running: "running";
18
+ waiting: "waiting";
19
+ }>>;
20
+ archived: z.ZodOptional<z.ZodEnum<{
21
+ 1: "1";
22
+ }>>;
15
23
  after: z.ZodOptional<z.ZodString>;
16
24
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
17
25
  agentId: z.ZodOptional<z.ZodString>;
18
26
  }, z.core.$strip>;
19
27
  declare const GroupsQuery: z.ZodObject<{
28
+ q: z.ZodOptional<z.ZodString>;
29
+ state: z.ZodOptional<z.ZodEnum<{
30
+ running: "running";
31
+ waiting: "waiting";
32
+ }>>;
33
+ archived: z.ZodOptional<z.ZodEnum<{
34
+ 1: "1";
35
+ }>>;
20
36
  perGroup: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
21
37
  limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
22
38
  after: z.ZodOptional<z.ZodString>;
@@ -142,9 +158,13 @@ declare const QueueEditBody: z.ZodObject<{
142
158
  * A rename: 1–120 characters counted in code points after trimming, with no control or format
143
159
  * (bidi, zero-width) characters, the same rule generated and tool titles follow.
144
160
  */
145
- declare const RenameBody: z.ZodObject<{
146
- title: z.ZodString;
161
+ declare const PatchBody: z.ZodObject<{
162
+ title: z.ZodOptional<z.ZodString>;
163
+ archived: z.ZodOptional<z.ZodBoolean>;
147
164
  }, z.core.$strict>;
165
+ declare const IdParams: z.ZodObject<{
166
+ id: z.ZodString;
167
+ }, z.core.$strip>;
148
168
  /**
149
169
  * 会话(`/api/v1/orgs/:orgId/conversations`):`agents.use`(访客以上),只接受人类用户;
150
170
  * 服务令牌 → 403 `forbidden`。只有会话所有者可见,其他人一律 `conversation.not_found`。
@@ -158,15 +178,25 @@ export declare class ConversationsController {
158
178
  private readonly queue;
159
179
  private readonly files;
160
180
  constructor(conversations: ConversationsService, outputs: ConversationOutputs, artifacts: ConversationArtifacts, sandboxStatus: ConversationSandboxStatus, toolInfo: ConversationToolInfo, queue: ConversationQueue, files: ConversationFiles);
161
- /** 201 when created; 200 when `message.clientMessageId` repeats an earlier first send. */
181
+ /**
182
+ * 201 when created; 200 when `message.clientMessageId` repeats an earlier first send. With a
183
+ * message the answer has `event` (the `user_message`) and `events`: every event the first send
184
+ * wrote, in seq order from seq 1 (a `model_switched` first when it switched).
185
+ */
162
186
  create(principal: Principal, org: OrgContext, body: z.infer<typeof CreateBody>, reply: FastifyReply): Promise<{
163
187
  conversation: ConversationView;
164
188
  event?: ConversationEvent;
189
+ events?: ConversationEvent[];
165
190
  }>;
166
- /** Rename (owner only): `titleSource` becomes `manual` and the title is never regenerated. */
167
- rename(principal: Principal, org: OrgContext, id: string, body: z.infer<typeof RenameBody>): Promise<{
191
+ /**
192
+ * Rename and / or archive (owner only). A rename makes `titleSource` `manual`, so the title is
193
+ * never regenerated; `archived` archives or unarchives. Returns the final view.
194
+ */
195
+ patch(principal: Principal, org: OrgContext, id: string, body: z.infer<typeof PatchBody>): Promise<{
168
196
  conversation: ConversationView;
169
197
  }>;
198
+ /** Soft delete (owner only): stops an active run and hides the conversation everywhere. */
199
+ remove(principal: Principal, org: OrgContext, { id }: z.infer<typeof IdParams>): Promise<void>;
170
200
  /** 我的会话,最近更新的在前:`next` 为下一页游标(作为 `after` 传回),没有更多时为 null。 */
171
201
  list(principal: Principal, org: OrgContext, q: z.infer<typeof ListQuery>): Promise<{
172
202
  conversations: ConversationView[];
@@ -180,6 +210,7 @@ export declare class ConversationsController {
180
210
  groups(principal: Principal, org: OrgContext, q: z.infer<typeof GroupsQuery>): Promise<{
181
211
  groups: ConversationGroup[];
182
212
  next: string | null;
213
+ archivedCount: number;
183
214
  }>;
184
215
  get(principal: Principal, org: OrgContext, id: string): Promise<{
185
216
  conversation: ConversationView;
@@ -12,7 +12,7 @@ import { z } from "zod";
12
12
  import { ModelRef } from "../agents/definition.js";
13
13
  import { CurrentPrincipal, requireUser } from "../identity/principal.js";
14
14
  import { createZodDto } from "../infra/validation/create-zod-dto.js";
15
- import { ZBody, ZQuery } from "../infra/validation/zod-params.js";
15
+ import { ZBody, ZParam, ZQuery } from "../infra/validation/zod-params.js";
16
16
  import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
17
17
  import { CONTROL_OR_HIDDEN_RE } from "../text.js";
18
18
  import { MAX_ATTACHMENTS } from "../tools/connector-content.js";
@@ -23,17 +23,25 @@ import { ConversationFiles, FILE_CONTENT_MAX_BYTES, FILES_PAGE_MAX, } from "./fi
23
23
  import { ConversationOutputs, OUTPUT_PAGE_MAX_CHARS } from "./outputs-read.js";
24
24
  import { ConversationQueue } from "./queue.service.js";
25
25
  import { ConversationSandboxStatus } from "./sandbox-status.js";
26
+ /** Shared by the list and groups queries: title search, run activity and the archived list. */
27
+ const searchFields = {
28
+ q: z.string().min(1).max(100).optional(),
29
+ state: z.enum(["running", "waiting"]).optional(),
30
+ archived: z.enum(["1"]).optional(),
31
+ };
26
32
  const ListQuery = z.object({
27
33
  after: z.string().min(1).max(100).optional(),
28
34
  limit: z.coerce.number().int().min(1).max(200).default(50),
29
35
  /** Only this agent's conversations (the sidebar's per-group "More"). */
30
36
  agentId: z.string().min(1).max(100).optional(),
37
+ ...searchFields,
31
38
  });
32
39
  const GroupsQuery = z.object({
33
40
  perGroup: z.coerce.number().int().min(1).max(20).default(5),
34
41
  limit: z.coerce.number().int().min(1).max(50).default(20),
35
42
  /** The last group's agent id from the previous page (`next`). */
36
43
  after: z.string().min(1).max(100).optional(),
44
+ ...searchFields,
37
45
  });
38
46
  const SubagentsQuery = z.object({
39
47
  /** The previous page's last sub-agent id (`next`). */
@@ -143,7 +151,7 @@ const QueueEditBody = z.object({ text: z.string().max(100_000) }).strict();
143
151
  * A rename: 1–120 characters counted in code points after trimming, with no control or format
144
152
  * (bidi, zero-width) characters, the same rule generated and tool titles follow.
145
153
  */
146
- const RenameBody = z
154
+ const PatchBody = z
147
155
  .object({
148
156
  title: z
149
157
  .string()
@@ -151,12 +159,19 @@ const RenameBody = z
151
159
  .trim()
152
160
  .refine((t) => t.length > 0, "title must not be empty")
153
161
  .refine((t) => Array.from(t).length <= CONVERSATION_TITLE_MAX_CHARS, `title must be at most ${CONVERSATION_TITLE_MAX_CHARS} characters`)
154
- .refine((t) => !CONTROL_OR_HIDDEN_RE.test(t), "title must not contain control or format characters"),
162
+ .refine((t) => !CONTROL_OR_HIDDEN_RE.test(t), "title must not contain control or format characters")
163
+ .optional(),
164
+ /** true archives (hidden from the default lists), false unarchives. */
165
+ archived: z.boolean().optional(),
155
166
  })
156
- .strict();
167
+ .strict()
168
+ .refine((b) => b.title !== undefined || b.archived !== undefined, "title or archived is required");
169
+ const IdParams = z.object({ id: z.string().min(1).max(100) });
157
170
  class ConversationCreateBodyDto extends createZodDto(CreateBody, "ConversationCreateBody") {
158
171
  }
159
- class ConversationRenameBodyDto extends createZodDto(RenameBody, "ConversationRenameBody") {
172
+ class ConversationPatchBodyDto extends createZodDto(PatchBody, "ConversationPatchBody") {
173
+ }
174
+ class ConversationIdParamsDto extends createZodDto(IdParams, "ConversationIdParams") {
160
175
  }
161
176
  class ConversationListQueryDto extends createZodDto(ListQuery, "ConversationListQuery") {
162
177
  }
@@ -203,7 +218,11 @@ let ConversationsController = class ConversationsController {
203
218
  this.queue = queue;
204
219
  this.files = files;
205
220
  }
206
- /** 201 when created; 200 when `message.clientMessageId` repeats an earlier first send. */
221
+ /**
222
+ * 201 when created; 200 when `message.clientMessageId` repeats an earlier first send. With a
223
+ * message the answer has `event` (the `user_message`) and `events`: every event the first send
224
+ * wrote, in seq order from seq 1 (a `model_switched` first when it switched).
225
+ */
207
226
  async create(principal, org, body, reply) {
208
227
  const p = requireUser(principal);
209
228
  if (body.message === undefined) {
@@ -222,15 +241,32 @@ let ConversationsController = class ConversationsController {
222
241
  ...(m.skill !== undefined && { skill: m.skill }),
223
242
  });
224
243
  reply.status(out.created ? 201 : 200);
225
- return { conversation: out.conversation, event: out.event };
244
+ return { conversation: out.conversation, event: out.event, events: out.events };
226
245
  }
227
- /** Rename (owner only): `titleSource` becomes `manual` and the title is never regenerated. */
228
- async rename(principal, org, id, body) {
246
+ /**
247
+ * Rename and / or archive (owner only). A rename makes `titleSource` `manual`, so the title is
248
+ * never regenerated; `archived` archives or unarchives. Returns the final view.
249
+ */
250
+ async patch(principal, org, id, body) {
229
251
  const p = requireUser(principal);
252
+ const renamed = body.title === undefined
253
+ ? undefined
254
+ : await this.conversations.rename(org.orgId, id, p.account.id, body.title);
255
+ if (body.archived === undefined) {
256
+ // The refine guarantees a title when there is no `archived`
257
+ return {
258
+ conversation: renamed ?? (await this.conversations.get(org.orgId, id, p.account.id)),
259
+ };
260
+ }
230
261
  return {
231
- conversation: await this.conversations.rename(org.orgId, id, p.account.id, body.title),
262
+ conversation: await this.conversations.setArchived(org.orgId, id, p.account.id, body.archived),
232
263
  };
233
264
  }
265
+ /** Soft delete (owner only): stops an active run and hides the conversation everywhere. */
266
+ async remove(principal, org, { id }) {
267
+ const p = requireUser(principal);
268
+ await this.conversations.remove(org.orgId, id, p.account.id);
269
+ }
234
270
  /** 我的会话,最近更新的在前:`next` 为下一页游标(作为 `after` 传回),没有更多时为 null。 */
235
271
  async list(principal, org, q) {
236
272
  const p = requireUser(principal);
@@ -238,6 +274,9 @@ let ConversationsController = class ConversationsController {
238
274
  limit: q.limit + 1,
239
275
  ...(q.after !== undefined && { after: q.after }),
240
276
  ...(q.agentId !== undefined && { agentId: q.agentId }),
277
+ ...(q.q !== undefined && { q: q.q }),
278
+ ...(q.state !== undefined && { state: q.state }),
279
+ ...(q.archived !== undefined && { archived: true }),
241
280
  });
242
281
  const conversations = rows.slice(0, q.limit);
243
282
  const next = rows.length > q.limit ? (conversations.at(-1)?.id ?? null) : null;
@@ -254,6 +293,9 @@ let ConversationsController = class ConversationsController {
254
293
  perGroup: q.perGroup,
255
294
  limit: q.limit,
256
295
  ...(q.after !== undefined && { after: q.after }),
296
+ ...(q.q !== undefined && { q: q.q }),
297
+ ...(q.state !== undefined && { state: q.state }),
298
+ ...(q.archived !== undefined && { archived: true }),
257
299
  });
258
300
  }
259
301
  async get(principal, org, id) {
@@ -443,8 +485,16 @@ __decorate([
443
485
  __param(0, CurrentPrincipal()),
444
486
  __param(1, CurrentOrg()),
445
487
  __param(2, Param("id")),
446
- __param(3, ZBody(ConversationRenameBodyDto))
447
- ], ConversationsController.prototype, "rename", null);
488
+ __param(3, ZBody(ConversationPatchBodyDto))
489
+ ], ConversationsController.prototype, "patch", null);
490
+ __decorate([
491
+ Delete(":id"),
492
+ HttpCode(204),
493
+ OrgAction("agents.use"),
494
+ __param(0, CurrentPrincipal()),
495
+ __param(1, CurrentOrg()),
496
+ __param(2, ZParam(ConversationIdParamsDto))
497
+ ], ConversationsController.prototype, "remove", null);
448
498
  __decorate([
449
499
  Get(),
450
500
  OrgAction("agents.use"),
@@ -6,6 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { Module } from "@nestjs/common";
8
8
  import { AgentLookupModule } from "../agents/agent-lookup.js";
9
+ import { BackgroundModule } from "../background/background.module.js";
9
10
  import { ProvidersModule } from "../runtime/providers.js";
10
11
  import { RuntimeModule } from "../runtime/runtime.module.js";
11
12
  import { SandboxModule } from "../sandbox/sandbox.module.js";
@@ -44,6 +45,8 @@ ConversationsModule = __decorate([
44
45
  ProvidersModule,
45
46
  // the sandbox status
46
47
  SandboxModule,
48
+ // the stop of a deleted conversation's background tasks
49
+ BackgroundModule,
47
50
  ],
48
51
  providers: [
49
52
  ConversationsService,
@@ -1,5 +1,6 @@
1
1
  import { AgentLookup } from "../agents/agent-lookup.js";
2
2
  import type { ModelRef } from "../agents/definition.js";
3
+ import { BackgroundTaskStopper } from "../background/stop.js";
3
4
  import type { TitleSource } from "../db/kernel-schema.js";
4
5
  import { KernelDb } from "../infra/database/kernel-db.js";
5
6
  import { TransactionHost } from "../infra/database/transaction-host.js";
@@ -31,7 +32,16 @@ export interface ConversationView {
31
32
  parentConversationId: string | null;
32
33
  /** A sub-agent's type as resolved at its first dispatch; null for any other conversation. */
33
34
  subagentType: string | null;
35
+ /** When the owner archived it; null = not archived (hidden from the default lists). */
36
+ archivedAt: Date | null;
37
+ /**
38
+ * What the latest run is doing: `running` (queued or running), `waiting` (an approval or a
39
+ * question), else null. Only the list queries compute it; other responses carry null.
40
+ */
41
+ activity: "running" | "waiting" | null;
34
42
  }
43
+ /** Escapes `\`, `%` and `_` so user text is matched literally by `LIKE … ESCAPE '\'`. */
44
+ export declare function escapeLike(s: string): string;
35
45
  /** One agent's group in the sidebar list: the caller's newest conversations with that agent. */
36
46
  export interface ConversationGroup {
37
47
  agent: {
@@ -150,6 +160,8 @@ export declare class ConversationsService {
150
160
  private readonly attachments;
151
161
  /** Renders a person's skill invocation (phase D). */
152
162
  private readonly invoker;
163
+ /** Stops a deleted conversation's background tasks. */
164
+ private readonly background;
153
165
  readonly txHost: TransactionHost;
154
166
  constructor(db: KernelDb, eventLog: EventLog, agents: Pick<AgentLookup, "get">, access: Pick<ConversationAccess, "isAccessible">,
155
167
  /** Starts and stops the conversation's runs. */
@@ -161,7 +173,9 @@ export declare class ConversationsService {
161
173
  /** Binds uploaded files to the conversation in the message's transaction. */
162
174
  attachments: Pick<AttachmentsService, "bindForMessage">,
163
175
  /** Renders a person's skill invocation (phase D). */
164
- invoker: Pick<SkillInvoker, "prepare">, txHost: TransactionHost);
176
+ invoker: Pick<SkillInvoker, "prepare">,
177
+ /** Stops a deleted conversation's background tasks. */
178
+ background: Pick<BackgroundTaskStopper, "stopForDeletedConversation">, txHost: TransactionHost);
165
179
  /**
166
180
  * 不阻塞地调用一次 `ensureRun`(打开 SSE、读事件时):它幂等,并顺带清理孤儿运行,
167
181
  * 会话不会因为某次运行结束后的投递失败而永远停在「运行中」。失败只记日志。
@@ -175,12 +189,15 @@ export declare class ConversationsService {
175
189
  * message's `clientMessageId` (`create_request_id`), so a retried send returns the same
176
190
  * conversation and event with `created: false`. An agent the caller cannot use →
177
191
  * `agent.not_found` and nothing is written. Queues a run afterwards, like `postUserMessage`.
192
+ * `events` are every event the create wrote, in seq order from seq 1 (a `model_switched` before
193
+ * the message when the send switched), so a client can seed the new conversation's log whole.
178
194
  */
179
195
  createWithMessage(orgId: string, accountId: string, agentId: string, input: UserMessageInput & {
180
196
  clientMessageId: string;
181
197
  }): Promise<{
182
198
  conversation: ConversationView;
183
199
  event: ConversationEvent;
200
+ events: ConversationEvent[];
184
201
  created: boolean;
185
202
  }>;
186
203
  /** The conversation row and its first message (undefined: a concurrent retry committed first). */
@@ -197,6 +214,23 @@ export declare class ConversationsService {
197
214
  * job never overwrites it. Publishes `changed: title`. `title` is already trimmed and checked.
198
215
  */
199
216
  rename(orgId: string, conversationId: string, accountId: string, title: string): Promise<ConversationView>;
217
+ /**
218
+ * Archives or unarchives a conversation (owner only, as {@link rename}). An archived
219
+ * conversation leaves the default lists but stays readable. Publishes `changed: archived`.
220
+ */
221
+ setArchived(orgId: string, conversationId: string, accountId: string, archived: boolean): Promise<ConversationView>;
222
+ /**
223
+ * Soft-deletes a conversation (owner only; a sub-agent's conversation is `conversation.read_only`):
224
+ * stops an active run, hides the conversation everywhere for its owner and drops its channel
225
+ * bindings, then stops every running background task in its sandbox (its sub-agents' too;
226
+ * outside the delete's transaction, a task whose sandbox cannot be reached ends `lost` with
227
+ * `kill_pending`). The tasks are stopped after the conversation is marked deleted, so no finish
228
+ * (the stops' own, or one the poller writes meanwhile) can wake a run on it: `ensureRun` refuses a
229
+ * deleted conversation (the stops' finishes carry `stoppedBy: "deletion"` as well). The event
230
+ * log, runs and trajectory stay (enterprise admins still see them).
231
+ */
232
+ remove(orgId: string, conversationId: string, accountId: string): Promise<void>;
233
+ private markDeleted;
200
234
  /** 读会话:非所有者、或 agent 已对所有者不可用 → `conversation.not_found`。 */
201
235
  get(orgId: string, conversationId: string, viewerAccountId: string): Promise<ConversationView>;
202
236
  /**
@@ -216,6 +250,9 @@ export declare class ConversationsService {
216
250
  after?: string;
217
251
  limit: number;
218
252
  agentId?: string;
253
+ archived?: boolean;
254
+ q?: string;
255
+ state?: "running" | "waiting";
219
256
  }): Promise<ConversationView[]>;
220
257
  /**
221
258
  * My chat conversations grouped by agent (the sidebar, UI refresh spec §5.9), in one statement.
@@ -228,9 +265,13 @@ export declare class ConversationsService {
228
265
  perGroup: number;
229
266
  limit: number;
230
267
  after?: string;
268
+ archived?: boolean;
269
+ q?: string;
270
+ state?: "running" | "waiting";
231
271
  }): Promise<{
232
272
  groups: ConversationGroup[];
233
273
  next: string | null;
274
+ archivedCount: number;
234
275
  }>;
235
276
  /**
236
277
  * The sub-agents of a conversation, newest first (sub-agents spec §14.3); owner only, as
@@ -292,7 +333,8 @@ export declare class ConversationsService {
292
333
  checkPick(orgId: string, conv: ConversationView, accountId: string, pick: Pick<UserMessageInput, "model" | "effort">): Promise<void>;
293
334
  /**
294
335
  * Writes the message (preceded by its `model_switched` when it switches) inside `t` and updates
295
- * `conversations.selection`; returns the `user_message` event. The caller notifies after commit.
336
+ * `conversations.selection`; returns the events written, in seq order (the `user_message`
337
+ * last). The caller notifies after commit.
296
338
  */
297
339
  private writeMessage;
298
340
  /**