@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. package/dist/agents/admin-agents.d.ts +16 -3
  2. package/dist/agents/admin-agents.js +65 -7
  3. package/dist/agents/agents.schemas.d.ts +24 -6
  4. package/dist/agents/agents.service.d.ts +4 -3
  5. package/dist/agents/agents.service.js +39 -26
  6. package/dist/agents/definition.d.ts +44 -6
  7. package/dist/agents/definition.js +86 -18
  8. package/dist/agents/from-template.d.ts +6 -3
  9. package/dist/agents/from-template.js +57 -44
  10. package/dist/agents/org-templates.js +97 -41
  11. package/dist/agents/templates.d.ts +6 -0
  12. package/dist/agents/templates.js +15 -1
  13. package/dist/catalog/admin-catalog.controller.d.ts +2 -4
  14. package/dist/catalog/admin-catalog.controller.js +2 -6
  15. package/dist/catalog/catalog.module.js +3 -0
  16. package/dist/catalog/claude/report.d.ts +9 -2
  17. package/dist/catalog/claude/report.js +1 -0
  18. package/dist/catalog/claude/translate.js +1 -1
  19. package/dist/catalog/component-promotion.js +1 -2
  20. package/dist/catalog/component-state.d.ts +13 -1
  21. package/dist/catalog/component-state.js +14 -5
  22. package/dist/catalog/marketplaces.service.d.ts +1 -1
  23. package/dist/catalog/marketplaces.service.js +2 -1
  24. package/dist/catalog/org-catalog.controller.d.ts +13 -3
  25. package/dist/catalog/org-catalog.controller.js +26 -4
  26. package/dist/catalog/org-catalog.schemas.d.ts +13 -0
  27. package/dist/catalog/org-catalog.schemas.js +34 -0
  28. package/dist/catalog/org-plugin-uploads.controller.js +0 -1
  29. package/dist/catalog/org-plugin-uploads.d.ts +0 -1
  30. package/dist/catalog/org-plugin-uploads.js +0 -3
  31. package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
  32. package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
  33. package/dist/catalog/platform-components.d.ts +0 -1
  34. package/dist/catalog/platform-components.js +3 -8
  35. package/dist/catalog/plugin-import.d.ts +0 -8
  36. package/dist/catalog/plugin-import.js +4 -35
  37. package/dist/catalog/plugin-install.d.ts +2 -2
  38. package/dist/catalog/plugin-install.js +2 -2
  39. package/dist/catalog/plugin-package.d.ts +7 -0
  40. package/dist/catalog/plugin-package.js +27 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +5 -6
  42. package/dist/catalog/plugin-upgrade.js +5 -23
  43. package/dist/catalog/requests.service.d.ts +66 -10
  44. package/dist/catalog/requests.service.js +196 -15
  45. package/dist/channels/actions.d.ts +2 -0
  46. package/dist/channels/actions.js +19 -4
  47. package/dist/channels/cards.d.ts +16 -2
  48. package/dist/channels/cards.js +40 -7
  49. package/dist/channels/channels.module.js +3 -0
  50. package/dist/channels/outbound.d.ts +22 -1
  51. package/dist/channels/outbound.js +116 -7
  52. package/dist/channels/phrases.d.ts +25 -0
  53. package/dist/channels/phrases.js +19 -0
  54. package/dist/channels/threads.d.ts +12 -0
  55. package/dist/channels/threads.js +34 -0
  56. package/dist/config/schema.d.ts +23 -1
  57. package/dist/config/schema.js +105 -5
  58. package/dist/conversations/conversations.service.d.ts +42 -9
  59. package/dist/conversations/conversations.service.js +57 -25
  60. package/dist/conversations/events.d.ts +106 -11
  61. package/dist/conversations/events.js +121 -18
  62. package/dist/conversations/message-context.d.ts +6 -3
  63. package/dist/conversations/message-context.js +14 -10
  64. package/dist/conversations/queue.d.ts +15 -2
  65. package/dist/conversations/queue.js +22 -3
  66. package/dist/conversations/queue.service.d.ts +12 -6
  67. package/dist/conversations/queue.service.js +36 -13
  68. package/dist/db/kernel-schema.d.ts +105 -3
  69. package/dist/devices/conversation-devices.d.ts +8 -1
  70. package/dist/devices/conversation-devices.js +8 -0
  71. package/dist/devices/devices.controller.d.ts +21 -4
  72. package/dist/devices/devices.controller.js +46 -8
  73. package/dist/devices/devices.schemas.d.ts +9 -1
  74. package/dist/devices/devices.schemas.js +11 -3
  75. package/dist/devices/devices.service.d.ts +176 -4
  76. package/dist/devices/devices.service.js +341 -6
  77. package/dist/devices/dto.d.ts +56 -3
  78. package/dist/devices/dto.js +51 -1
  79. package/dist/devices/public-suffix.d.ts +6 -0
  80. package/dist/devices/public-suffix.js +79 -0
  81. package/dist/errors.d.ts +46 -5
  82. package/dist/errors.js +46 -5
  83. package/dist/evals/reconstruct.d.ts +18 -13
  84. package/dist/evals/reconstruct.js +50 -36
  85. package/dist/evals/recorded-tools.d.ts +7 -7
  86. package/dist/evals/recorded-tools.js +4 -3
  87. package/dist/evals/trajectory.d.ts +6 -1
  88. package/dist/evals/trajectory.js +7 -0
  89. package/dist/extensions/package.js +2 -2
  90. package/dist/http/api.module.js +5 -0
  91. package/dist/inbox/inbox.module.js +2 -1
  92. package/dist/inbox/inbox.service.d.ts +19 -4
  93. package/dist/inbox/inbox.service.js +14 -3
  94. package/dist/infra/config/kernel-config.service.js +11 -0
  95. package/dist/infra/events/kernel-events.d.ts +2 -1
  96. package/dist/local-devices/device-policy.service.d.ts +65 -0
  97. package/dist/local-devices/device-policy.service.js +217 -0
  98. package/dist/local-devices/local-device-gateway.js +4 -1
  99. package/dist/local-devices/local-device-provider.d.ts +40 -3
  100. package/dist/local-devices/local-device-provider.js +109 -7
  101. package/dist/local-devices/local-devices.module.d.ts +2 -1
  102. package/dist/local-devices/local-devices.module.js +19 -3
  103. package/dist/local-devices/machines.d.ts +14 -3
  104. package/dist/local-devices/machines.js +41 -3
  105. package/dist/local-devices/network-grants.d.ts +104 -0
  106. package/dist/local-devices/network-grants.js +288 -0
  107. package/dist/local-devices/policy-summary.d.ts +20 -0
  108. package/dist/local-devices/policy-summary.js +63 -0
  109. package/dist/orgs/orgs.module.js +4 -10
  110. package/dist/orgs/roles.d.ts +1 -1
  111. package/dist/orgs/roles.js +4 -0
  112. package/dist/orgs/settings.controller.js +2 -1
  113. package/dist/orgs/settings.service.d.ts +164 -12
  114. package/dist/orgs/settings.service.js +212 -24
  115. package/dist/platform/exclusions.js +11 -1
  116. package/dist/platform/operation.js +4 -0
  117. package/dist/platform/ops/admin-items.ops.js +1 -1
  118. package/dist/platform/ops/agent-skills.ops.js +2 -2
  119. package/dist/platform/ops/agent-templates.ops.js +2 -2
  120. package/dist/platform/ops/agents.ops.js +11 -15
  121. package/dist/platform/ops/devices.ops.js +3 -1
  122. package/dist/platform/ops/inbox.ops.js +19 -0
  123. package/dist/platform/ops/org-settings.ops.js +14 -5
  124. package/dist/platform/ops/platform.ops.d.ts +9 -1
  125. package/dist/platform/ops/platform.ops.js +34 -12
  126. package/dist/platform/ops/requests.ops.js +3 -2
  127. package/dist/platform/ops/schedules.ops.d.ts +21 -0
  128. package/dist/platform/ops/schedules.ops.js +470 -0
  129. package/dist/platform/ops/skills.ops.js +3 -3
  130. package/dist/platform/platform-ops.module.d.ts +2 -1
  131. package/dist/platform/platform-ops.module.js +5 -0
  132. package/dist/platform/route-schemas.js +3 -0
  133. package/dist/roles/worker.module.js +3 -0
  134. package/dist/runtime/context/build.js +13 -4
  135. package/dist/runtime/context/compaction.js +6 -3
  136. package/dist/runtime/context/device-paths.d.ts +43 -21
  137. package/dist/runtime/context/device-paths.js +104 -21
  138. package/dist/runtime/context/run-context.d.ts +9 -0
  139. package/dist/runtime/context/run-context.js +8 -0
  140. package/dist/runtime/context/skills.d.ts +14 -1
  141. package/dist/runtime/context/skills.js +36 -5
  142. package/dist/runtime/maintenance.d.ts +4 -2
  143. package/dist/runtime/maintenance.js +16 -5
  144. package/dist/runtime/run-finish.d.ts +6 -1
  145. package/dist/runtime/run-finish.js +16 -2
  146. package/dist/runtime/run-queue.js +21 -2
  147. package/dist/runtime/run-worker.js +14 -33
  148. package/dist/runtime/runtime.module.js +3 -0
  149. package/dist/runtime/subagents/definition.d.ts +32 -14
  150. package/dist/runtime/subagents/definition.js +106 -109
  151. package/dist/runtime/subagents/dispatch.js +4 -3
  152. package/dist/runtime/subagents/preamble.d.ts +2 -1
  153. package/dist/runtime/subagents/preamble.js +2 -1
  154. package/dist/runtime/waits.js +14 -4
  155. package/dist/sandbox/sandbox.service.d.ts +120 -10
  156. package/dist/sandbox/sandbox.service.js +335 -28
  157. package/dist/sandbox/tiers.d.ts +86 -0
  158. package/dist/sandbox/tiers.js +96 -0
  159. package/dist/schedules/effective-settings.d.ts +16 -0
  160. package/dist/schedules/effective-settings.js +17 -0
  161. package/dist/schedules/errors.d.ts +8 -0
  162. package/dist/schedules/errors.js +8 -0
  163. package/dist/schedules/fire.d.ts +118 -0
  164. package/dist/schedules/fire.js +625 -0
  165. package/dist/schedules/grant-rules.d.ts +94 -0
  166. package/dist/schedules/grant-rules.js +126 -0
  167. package/dist/schedules/job-ids.d.ts +32 -0
  168. package/dist/schedules/job-ids.js +27 -0
  169. package/dist/schedules/pause.d.ts +9 -0
  170. package/dist/schedules/pause.js +33 -0
  171. package/dist/schedules/run-tracker.d.ts +81 -0
  172. package/dist/schedules/run-tracker.js +333 -0
  173. package/dist/schedules/scan.job.d.ts +64 -0
  174. package/dist/schedules/scan.job.js +303 -0
  175. package/dist/schedules/schedule-math.d.ts +31 -0
  176. package/dist/schedules/schedule-math.js +223 -0
  177. package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
  178. package/dist/schedules/scheduled-tasks.service.js +1193 -0
  179. package/dist/schedules/schedules.controller.d.ts +68 -0
  180. package/dist/schedules/schedules.controller.js +242 -0
  181. package/dist/schedules/schedules.module.d.ts +16 -0
  182. package/dist/schedules/schedules.module.js +65 -0
  183. package/dist/schedules/schedules.schemas.d.ts +316 -0
  184. package/dist/schedules/schedules.schemas.js +186 -0
  185. package/dist/schedules/store.d.ts +81 -0
  186. package/dist/schedules/store.js +390 -0
  187. package/dist/schedules/store.module.d.ts +6 -0
  188. package/dist/schedules/store.module.js +21 -0
  189. package/dist/schedules/types.d.ts +108 -0
  190. package/dist/schedules/types.js +1 -0
  191. package/dist/schedules/unattended.d.ts +117 -0
  192. package/dist/schedules/unattended.js +228 -0
  193. package/dist/setup/oauth-token-probe.js +8 -4
  194. package/dist/skills/agent-skills.service.d.ts +4 -8
  195. package/dist/skills/agent-skills.service.js +18 -53
  196. package/dist/skills/catalog.d.ts +65 -62
  197. package/dist/skills/catalog.js +109 -206
  198. package/dist/skills/conversation-catalog.d.ts +3 -3
  199. package/dist/skills/conversation-catalog.js +5 -12
  200. package/dist/skills/invoke.d.ts +2 -2
  201. package/dist/skills/skills.controller.d.ts +3 -1
  202. package/dist/skills/skills.service.d.ts +38 -37
  203. package/dist/skills/skills.service.js +168 -107
  204. package/dist/skills/usable.d.ts +57 -8
  205. package/dist/skills/usable.js +172 -38
  206. package/dist/tools/approvals.controller.d.ts +4 -2
  207. package/dist/tools/approvals.controller.js +17 -2
  208. package/dist/tools/approvals.schemas.d.ts +1 -0
  209. package/dist/tools/approvals.schemas.js +3 -1
  210. package/dist/tools/approvals.service.d.ts +182 -7
  211. package/dist/tools/approvals.service.js +597 -13
  212. package/dist/tools/builtin-names.d.ts +25 -4
  213. package/dist/tools/builtin-names.js +45 -4
  214. package/dist/tools/builtins/builtin-tools.d.ts +8 -0
  215. package/dist/tools/builtins/builtin-tools.js +23 -2
  216. package/dist/tools/builtins/sandbox/bash.js +16 -7
  217. package/dist/tools/builtins/sandbox/common.d.ts +1 -1
  218. package/dist/tools/builtins/sandbox/common.js +33 -10
  219. package/dist/tools/builtins/sandbox/edit.js +7 -3
  220. package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
  221. package/dist/tools/builtins/sandbox/file-state.js +33 -3
  222. package/dist/tools/builtins/sandbox/write.js +8 -4
  223. package/dist/tools/builtins/schedules.d.ts +107 -0
  224. package/dist/tools/builtins/schedules.js +789 -0
  225. package/dist/tools/elicitation.d.ts +2 -0
  226. package/dist/tools/elicitation.js +1 -1
  227. package/dist/tools/fixed-ask.d.ts +7 -0
  228. package/dist/tools/fixed-ask.js +9 -0
  229. package/dist/tools/network-asks.d.ts +109 -0
  230. package/dist/tools/network-asks.js +221 -0
  231. package/dist/tools/pipeline.d.ts +53 -6
  232. package/dist/tools/pipeline.js +202 -13
  233. package/dist/tools/policy.d.ts +21 -8
  234. package/dist/tools/policy.js +63 -9
  235. package/dist/tools/registry.d.ts +10 -1
  236. package/dist/tools/tools.module.js +12 -0
  237. package/package.json +2 -2
  238. package/src/migrations/0113_scheduled_tasks.sql +108 -0
  239. package/src/migrations/0114_network_asks.sql +23 -0
  240. package/src/migrations/0115_network_requests.sql +28 -0
  241. package/src/migrations/0116_skills_default_on.sql +37 -0
  242. package/src/migrations/0117_sandbox_tiers.sql +11 -0
@@ -169,29 +169,62 @@ async function displayName(db, accountId) {
169
169
  .executeTakeFirst();
170
170
  return r?.display_name || undefined;
171
171
  }
172
+ /**
173
+ * A network ask's card text: the title names the host (or the number of hosts); the preview lists
174
+ * the hosts, then the command clipped to {@link CARD_SUMMARY_MAX} and, for several hosts, that the
175
+ * decision applies to all of them (an IM card has no per-host choice). Model-controlled values
176
+ * are redacted by `redact` (the connection's secret values); the description stays on the web.
177
+ */
178
+ export function networkCardText(detail, locale, redact = (s) => s) {
179
+ const hosts = [...new Set(detail.hosts.map((h) => redact(h.host)))];
180
+ const title = hosts.length === 1
181
+ ? phrase("networkTitleOne", locale).replace("{host}", hosts[0])
182
+ : phrase("networkTitleMany", locale).replace("{n}", String(hosts.length));
183
+ const command = cut(redact(detail.command.trim()), CARD_SUMMARY_MAX);
184
+ return {
185
+ title,
186
+ inputPreview: [
187
+ ...hosts,
188
+ ...(command !== "" ? ["", command] : []),
189
+ ...(hosts.length > 1 ? ["", phrase("networkAllHosts", locale)] : []),
190
+ ].join("\n"),
191
+ scopeLabels: {
192
+ once: phrase("networkAllowOnce", locale),
193
+ always: phrase("networkAllowAlways", locale),
194
+ },
195
+ };
196
+ }
172
197
  /**
173
198
  * The approval card in the approval's current state; undefined when the approval does not exist.
174
- * A role escalation offers only "once". `webUrl` = `http.publicUrl` + the conversation path.
199
+ * A role escalation offers only "once". A network ask names its hosts and command and labels its
200
+ * scopes ({@link networkCardText}). `webUrl` = `http.publicUrl` + the conversation path.
175
201
  */
176
202
  export async function approvalCard(d, approvalId, base, locale, redact) {
177
203
  const v = await d.approvals.describe(approvalId);
178
204
  if (!v)
179
205
  return undefined;
180
206
  const decidedBy = await displayName(d.db, v.decidedBy);
181
- const display = v.summary === null
207
+ const network = v.kind === "network" && v.detail !== null ? networkCardText(v.detail, locale, redact) : null;
208
+ const display = v.summary === null && network === null
182
209
  ? (await toolInfo({ db: d.db, registry: d.registry }, v.orgId, [v.toolKey]))[0]
183
210
  : undefined;
184
211
  return {
185
212
  kind: "approval",
186
213
  id: v.id,
187
- title: phrase("approvalTitle", locale),
188
- // the label carries the source, so no separate appName (renderers would repeat it)
189
- toolName: approvalToolName(v, locale),
214
+ title: network?.title ?? phrase("approvalTitle", locale),
215
+ // the label carries the source, so no separate appName (renderers would repeat it); a
216
+ // network ask's command ran in Bash
217
+ toolName: network !== null ? "Bash" : approvalToolName(v, locale),
190
218
  agentName: v.agent.name || "Agent",
191
- // the field keeps its contract name; it carries the call line, never the raw input
192
- inputPreview: callSummary(v, display, locale, redact),
219
+ // the field keeps its contract name; it carries the call line (a network ask's hosts and
220
+ // command), never the raw input
221
+ inputPreview: network?.inputPreview ?? callSummary(v, display, locale, redact),
193
222
  // the scopes the approval allows (the kernel stores them for per-call policies)
194
223
  scopes: v.scopes,
224
+ // the labels of the scopes offered only
225
+ ...(network !== null && {
226
+ scopeLabels: Object.fromEntries(Object.entries(network.scopeLabels).filter(([k]) => v.scopes.includes(k))),
227
+ }),
195
228
  expiresAt: v.expiresAt,
196
229
  state: v.status,
197
230
  ...(decidedBy !== undefined && { decidedBy }),
@@ -21,6 +21,7 @@ import { runJob } from "../infra/queue/run-job.js";
21
21
  import { OrgLockModule } from "../orgs/org-lock.js";
22
22
  import { OrgsModule } from "../orgs/orgs.module.js";
23
23
  import { RuntimeModule } from "../runtime/runtime.module.js";
24
+ import { SchedulesModule } from "../schedules/schedules.module.js";
24
25
  import { ToolsModule } from "../tools/tools.module.js";
25
26
  import { ChannelConnections } from "./connections.service.js";
26
27
  import { ChannelInbound } from "./inbound.js";
@@ -50,6 +51,8 @@ ChannelsModule = __decorate([
50
51
  ToolsModule,
51
52
  ConversationsModule,
52
53
  RuntimeModule,
54
+ // outbound pauses the scheduled task of a run whose chat is gone (scheduled tasks spec §8)
55
+ SchedulesModule,
53
56
  CHANNEL_QUEUE_MODULE,
54
57
  ],
55
58
  providers: [
@@ -7,6 +7,7 @@ import { KernelDb } from "../infra/database/kernel-db.js";
7
7
  import { TransactionHost } from "../infra/database/transaction-host.js";
8
8
  import { ConversationLock } from "../runtime/conversation-lock.js";
9
9
  import type { StreamBus } from "../runtime/stream.js";
10
+ import { ScheduledTasksService } from "../schedules/scheduled-tasks.service.js";
10
11
  import { ApprovalsService } from "../tools/approvals.service.js";
11
12
  import { QuestionsService } from "../tools/questions.service.js";
12
13
  import { ToolRegistry } from "../tools/registry.js";
@@ -84,6 +85,8 @@ export declare class ChannelOutbound {
84
85
  private readonly config;
85
86
  private readonly log;
86
87
  readonly txHost: TransactionHost;
88
+ /** Pauses the task of a scheduled run whose chat is gone (scheduled tasks spec §8). */
89
+ private readonly schedules;
87
90
  private worker;
88
91
  private closed;
89
92
  /** 停机:进行中的任务在下一个检查点交还队列(由其他副本续投)。 */
@@ -94,7 +97,9 @@ export declare class ChannelOutbound {
94
97
  /** The conversation lock provider; delivery takes it under its own key prefix. */
95
98
  lock: Pick<ConversationLock, "scoped">, connections: Pick<ChannelConnections, "outbound">, approvals: Pick<ApprovalsService, "describe">, questions: Pick<QuestionsService, "forCall">, registry: Pick<ToolRegistry, "builtinSpecs" | "extensionToolSpecs">,
96
99
  /** 入站任务的处理(worker 上执行 `ChannelInbound.handle`);失败抛错由 BullMQ 重试。 */
97
- inbound: Pick<ChannelInbound, "handle">, jobs: Pick<ChannelJobs, "enqueue">, config: Pick<KernelConfigService, "get">, log: Pick<Logger, "error" | "warn" | "info">, txHost: TransactionHost);
100
+ inbound: Pick<ChannelInbound, "handle">, jobs: Pick<ChannelJobs, "enqueue">, config: Pick<KernelConfigService, "get">, log: Pick<Logger, "error" | "warn" | "info">, txHost: TransactionHost,
101
+ /** Pauses the task of a scheduled run whose chat is gone (scheduled tasks spec §8). */
102
+ schedules: Pick<ScheduledTasksService, "pauseFor">);
98
103
  /**
99
104
  * Starts consuming (worker role only): `worker` is the `@Processor` worker of the channel queue
100
105
  * (its job lock and stalled detection follow the run queue's config).
@@ -128,4 +133,20 @@ export declare class ChannelOutbound {
128
133
  * 被别人推进过(投递锁丢失后另一实例接手)返回 false,什么都不写。
129
134
  */
130
135
  advanceIn(a: DeliveryAdvance): Promise<boolean>;
136
+ /**
137
+ * Binds a scheduled group run's thread at its first post (scheduled tasks spec §8): the delivery
138
+ * target gets `threadId` = the posted message's id, and a thread row (key = that id, account =
139
+ * `pending_thread_account_id`) points at the conversation, so replies in that thread continue it,
140
+ * as an inbound top-level message's thread would. Conditional on the binding still pending; when
141
+ * it is not (another attempt bound it), returns the stored target.
142
+ */
143
+ bindThreadIn(conversationId: string, messageId: string): Promise<ChannelTarget>;
144
+ /**
145
+ * A delivery failed for good because the chat is gone or the connection's credentials are
146
+ * (`ChannelSendError` `target_gone` / `auth`): when the run being delivered serves a scheduled
147
+ * task run (`runs.scheduled_task_run_id`), its task pauses with `target_gone` (spec §8), so later
148
+ * ticks do not keep running into the same chat. `runId` is the run of the event being delivered;
149
+ * without one (a streaming draft), the conversation's latest run. A person's run pauses nothing.
150
+ */
151
+ pauseScheduledTaskIn(conversationId: string, runId: string | null): Promise<void>;
131
152
  }
@@ -21,6 +21,7 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
21
21
  import { Transactional } from "../infra/database/transactional.decorator.js";
22
22
  import { KERNEL_BUS, KERNEL_LOGGER } from "../infra/tokens.js";
23
23
  import { ConversationLock, } from "../runtime/conversation-lock.js";
24
+ import { ScheduledTasksService } from "../schedules/scheduled-tasks.service.js";
24
25
  import { ApprovalsService } from "../tools/approvals.service.js";
25
26
  import { QuestionsService } from "../tools/questions.service.js";
26
27
  import { ToolRegistry } from "../tools/registry.js";
@@ -29,6 +30,7 @@ import { ChannelConnections } from "./connections.service.js";
29
30
  import { ChannelInbound } from "./inbound.js";
30
31
  import { ChannelJobs, INBOUND_JOB, idempotencyKey, } from "./jobs.js";
31
32
  import { phrase } from "./phrases.js";
33
+ import { insertThread } from "./threads.js";
32
34
  // The queue, job names, ids and priorities live with the producer (jobs.ts)
33
35
  export { CHANNEL_QUEUE_NAME, DELIVER_ATTEMPTS, DELIVER_JOB, DELIVER_PRIORITY, deliverJobId, followUpJobId, INBOUND_ATTEMPTS, INBOUND_JOB, INBOUND_PRIORITY, idempotencyKey, inboundJobId, } from "./jobs.js";
34
36
  /**
@@ -162,6 +164,7 @@ let ChannelOutbound = class ChannelOutbound {
162
164
  config;
163
165
  log;
164
166
  txHost;
167
+ schedules;
165
168
  worker;
166
169
  closed = false;
167
170
  /** 停机:进行中的任务在下一个检查点交还队列(由其他副本续投)。 */
@@ -172,13 +175,16 @@ let ChannelOutbound = class ChannelOutbound {
172
175
  /** The conversation lock provider; delivery takes it under its own key prefix. */
173
176
  lock, connections, approvals, questions, registry,
174
177
  /** 入站任务的处理(worker 上执行 `ChannelInbound.handle`);失败抛错由 BullMQ 重试。 */
175
- inbound, jobs, config, log, txHost) {
178
+ inbound, jobs, config, log, txHost,
179
+ /** Pauses the task of a scheduled run whose chat is gone (scheduled tasks spec §8). */
180
+ schedules) {
176
181
  this.kdb = kdb;
177
182
  this.inbound = inbound;
178
183
  this.jobs = jobs;
179
184
  this.config = config;
180
185
  this.log = log;
181
186
  this.txHost = txHost;
187
+ this.schedules = schedules;
182
188
  this.lock = lock.scoped(DELIVER_LOCK_PREFIX);
183
189
  this.d = {
184
190
  get db() {
@@ -315,6 +321,9 @@ let ChannelOutbound = class ChannelOutbound {
315
321
  }
316
322
  if (outcome.error)
317
323
  await this.recordError(outcome.error.connectionId, outcome.error.message);
324
+ if (outcome.gone) {
325
+ await this.pauseScheduledTaskIn(conversationId, outcome.gone.runId).catch((err) => this.log.warn("cannot pause the scheduled task of a gone chat", { conversationId, err }));
326
+ }
318
327
  switch (outcome.kind) {
319
328
  case "shutdown":
320
329
  return this.requeue(job, token, 0);
@@ -408,10 +417,77 @@ let ChannelOutbound = class ChannelOutbound {
408
417
  }
409
418
  return true;
410
419
  }
420
+ /**
421
+ * Binds a scheduled group run's thread at its first post (scheduled tasks spec §8): the delivery
422
+ * target gets `threadId` = the posted message's id, and a thread row (key = that id, account =
423
+ * `pending_thread_account_id`) points at the conversation, so replies in that thread continue it,
424
+ * as an inbound top-level message's thread would. Conditional on the binding still pending; when
425
+ * it is not (another attempt bound it), returns the stored target.
426
+ */
427
+ async bindThreadIn(conversationId, messageId) {
428
+ const row = await this.kdb
429
+ .selectFrom("channel_deliveries")
430
+ .select(["connection_id", "target", "pending_thread_account_id"])
431
+ .where("conversation_id", "=", conversationId)
432
+ .forUpdate()
433
+ .executeTakeFirst();
434
+ if (!row)
435
+ throw new DeliveryLostError();
436
+ const stored = row.target;
437
+ if (row.pending_thread_account_id === null)
438
+ return stored;
439
+ const target = { ...stored, threadId: messageId };
440
+ await this.kdb
441
+ .updateTable("channel_deliveries")
442
+ .set({
443
+ target: JSON.stringify(target),
444
+ pending_thread_account_id: null,
445
+ updated_at: new Date(),
446
+ })
447
+ .where("conversation_id", "=", conversationId)
448
+ .execute();
449
+ await insertThread(this.kdb.db, {
450
+ connectionId: row.connection_id,
451
+ chatId: stored.chatId,
452
+ threadKey: messageId,
453
+ accountId: row.pending_thread_account_id,
454
+ }, conversationId);
455
+ return target;
456
+ }
457
+ /**
458
+ * A delivery failed for good because the chat is gone or the connection's credentials are
459
+ * (`ChannelSendError` `target_gone` / `auth`): when the run being delivered serves a scheduled
460
+ * task run (`runs.scheduled_task_run_id`), its task pauses with `target_gone` (spec §8), so later
461
+ * ticks do not keep running into the same chat. `runId` is the run of the event being delivered;
462
+ * without one (a streaming draft), the conversation's latest run. A person's run pauses nothing.
463
+ */
464
+ async pauseScheduledTaskIn(conversationId, runId) {
465
+ let q = this.kdb
466
+ .selectFrom("runs as r")
467
+ .innerJoin("scheduled_task_runs as tr", "tr.id", "r.scheduled_task_run_id")
468
+ .select(["tr.task_id", "r.scheduled_task_run_id"])
469
+ .where("r.conversation_id", "=", conversationId);
470
+ q = runId !== null ? q.where("r.id", "=", runId) : q.orderBy("r.created_at", "desc").limit(1);
471
+ const row = await q.executeTakeFirst();
472
+ if (!row)
473
+ return;
474
+ await this.schedules.pauseFor(this.kdb.db, row.task_id, "target_gone", null);
475
+ this.log.info("scheduled task paused: its chat is gone", {
476
+ conversationId,
477
+ taskId: row.task_id,
478
+ taskRunId: row.scheduled_task_run_id,
479
+ });
480
+ }
411
481
  };
412
482
  __decorate([
413
483
  Transactional()
414
484
  ], ChannelOutbound.prototype, "advanceIn", null);
485
+ __decorate([
486
+ Transactional()
487
+ ], ChannelOutbound.prototype, "bindThreadIn", null);
488
+ __decorate([
489
+ Transactional()
490
+ ], ChannelOutbound.prototype, "pauseScheduledTaskIn", null);
415
491
  ChannelOutbound = __decorate([
416
492
  Injectable(),
417
493
  __param(0, Inject(KernelDb)),
@@ -426,7 +502,8 @@ ChannelOutbound = __decorate([
426
502
  __param(9, Inject(ChannelJobs)),
427
503
  __param(10, Inject(KernelConfigService)),
428
504
  __param(11, Inject(KERNEL_LOGGER)),
429
- __param(12, Inject(TransactionHost))
505
+ __param(12, Inject(TransactionHost)),
506
+ __param(13, Inject(ScheduledTasksService))
430
507
  ], ChannelOutbound);
431
508
  export { ChannelOutbound };
432
509
  /** 一次投递任务(本进程持有):订阅、补读、草稿节流。 */
@@ -446,6 +523,13 @@ class Delivery {
446
523
  persisted = 0;
447
524
  /** 正在投递的事件(不可重试失败按它计数)。 */
448
525
  handling;
526
+ /** The run of the event being delivered (a gone chat pauses its scheduled task). */
527
+ handlingRun = null;
528
+ /**
529
+ * A scheduled group run's thread is still to bind (`pending_thread_account_id` set): the first
530
+ * successful send binds it ({@link ChannelOutbound.bindThreadIn}).
531
+ */
532
+ threadPending = false;
449
533
  /** 当前草稿消息 id(PG 里的 `draft_message_id`)与它所属的步骤(从 PG 读出的草稿不知道步骤)。 */
450
534
  draftId = null;
451
535
  draftKey;
@@ -477,7 +561,13 @@ class Delivery {
477
561
  async run() {
478
562
  const row = await this.d.db
479
563
  .selectFrom("channel_deliveries")
480
- .select(["connection_id", "target", "last_seq", "draft_message_id"])
564
+ .select([
565
+ "connection_id",
566
+ "target",
567
+ "last_seq",
568
+ "draft_message_id",
569
+ "pending_thread_account_id",
570
+ ])
481
571
  .where("conversation_id", "=", this.conversationId)
482
572
  .executeTakeFirst();
483
573
  if (!row)
@@ -514,6 +604,7 @@ class Delivery {
514
604
  return { kind: "done" };
515
605
  this.version = version;
516
606
  this.target = row.target;
607
+ this.threadPending = row.pending_thread_account_id !== null;
517
608
  this.seen = Number(row.last_seq);
518
609
  this.persisted = this.seen;
519
610
  this.draftId = row.draft_message_id;
@@ -527,17 +618,19 @@ class Delivery {
527
618
  if (isChannelSendError(err) && !err.retryable) {
528
619
  const message = this.redact(errMsg(err));
529
620
  // Ruling A22:机器人被移出群 / 会话不存在 → 停止本轮,不计毒事件(不是连接的问题,不记 last_error)
621
+ // either also pauses the scheduled task of the run being delivered (scheduled tasks §8)
622
+ const gone = { runId: this.handlingRun };
530
623
  if (err.reason === "target_gone") {
531
624
  this.d.log.warn("channel delivery target is gone", {
532
625
  conversationId: this.conversationId,
533
626
  error: message,
534
627
  });
535
- return { kind: "stopped" };
628
+ return { kind: "stopped", gone };
536
629
  }
537
630
  const error = { connectionId: row.connection_id, message };
538
631
  // 凭据失效:记 last_error、停止本轮,不计毒事件(换好凭据后之后的事件照常续投)
539
632
  if (err.reason === "auth")
540
- return { kind: "stopped", error };
633
+ return { kind: "stopped", error, gone };
541
634
  if (this.handling !== undefined && (await this.countFailure(this.handling))) {
542
635
  return { kind: "done", seen: this.seen, error };
543
636
  }
@@ -711,8 +804,10 @@ class Delivery {
711
804
  if (this.lease.lost)
712
805
  throw new DeliveryLostError();
713
806
  this.handling = e.seq;
807
+ this.handlingRun = e.runId;
714
808
  await this.handle(e);
715
809
  this.handling = undefined;
810
+ this.handlingRun = null;
716
811
  this.seen = e.seq;
717
812
  }
718
813
  if (evs.length < PAGE)
@@ -772,8 +867,22 @@ class Delivery {
772
867
  .onConflict((oc) => oc.columns(["conversation_id", "connection_id", "card_kind", "card_id"]).doNothing())
773
868
  .execute();
774
869
  }
775
- send(message, idempotencyKey) {
776
- return this.s.send(this.target, message, idempotencyKey, this.locale);
870
+ /**
871
+ * Sends into the delivery target. A scheduled group run's first post is its thread's root: once
872
+ * the platform has it, the thread is bound to its id and later posts go into that thread. The
873
+ * bind comes before the lease check: the root is posted, so it is recorded even when the lease
874
+ * was lost meanwhile (the bind is conditional on still pending, so a holder that bound first
875
+ * keeps its thread), and a retry never posts a second root.
876
+ */
877
+ async send(message, idempotencyKey) {
878
+ const sent = await this.s.send(this.target, message, idempotencyKey, this.locale);
879
+ if (this.threadPending) {
880
+ this.target = await this.d.writes.bindThreadIn(this.conversationId, sent.messageId);
881
+ this.threadPending = false;
882
+ if (this.lease.lost)
883
+ throw new DeliveryLostError();
884
+ }
885
+ return sent;
777
886
  }
778
887
  async handle(e) {
779
888
  switch (e.type) {
@@ -91,6 +91,31 @@ export declare const CHANNEL_PHRASES: {
91
91
  readonly zh: "需要你的批准";
92
92
  readonly en: "Approval required";
93
93
  };
94
+ /** A network ask's card title for one host (`{host}`; network ask-to-allow spec §6.5). */
95
+ readonly networkTitleOne: {
96
+ readonly zh: "智能体想访问 {host},允许吗?";
97
+ readonly en: "The agent wants to reach {host}. Allow?";
98
+ };
99
+ /** A network ask's card title for several hosts (`{n}`). */
100
+ readonly networkTitleMany: {
101
+ readonly zh: "智能体想访问 {n} 个域名,允许吗?";
102
+ readonly en: "The agent wants to reach {n} domains. Allow?";
103
+ };
104
+ /** Under a network ask's host list: an IM card has no per-host choice (spec §6.4). */
105
+ readonly networkAllHosts: {
106
+ readonly zh: "这个决定对以上所有域名生效";
107
+ readonly en: "This decision applies to all the domains above";
108
+ };
109
+ /** A network ask's 「once」 button. */
110
+ readonly networkAllowOnce: {
111
+ readonly zh: "这次允许";
112
+ readonly en: "Allow this time";
113
+ };
114
+ /** A network ask's 「always」 button. */
115
+ readonly networkAllowAlways: {
116
+ readonly zh: "这台设备一直允许";
117
+ readonly en: "Always allow on this device";
118
+ };
94
119
  /** 提问卡片的随附文本。 */
95
120
  readonly questionTitle: {
96
121
  readonly zh: "需要你的回答";
@@ -77,6 +77,25 @@ export const CHANNEL_PHRASES = {
77
77
  },
78
78
  /** 审批卡片的标题与随附文本。 */
79
79
  approvalTitle: { zh: "需要你的批准", en: "Approval required" },
80
+ /** A network ask's card title for one host (`{host}`; network ask-to-allow spec §6.5). */
81
+ networkTitleOne: {
82
+ zh: "智能体想访问 {host},允许吗?",
83
+ en: "The agent wants to reach {host}. Allow?",
84
+ },
85
+ /** A network ask's card title for several hosts (`{n}`). */
86
+ networkTitleMany: {
87
+ zh: "智能体想访问 {n} 个域名,允许吗?",
88
+ en: "The agent wants to reach {n} domains. Allow?",
89
+ },
90
+ /** Under a network ask's host list: an IM card has no per-host choice (spec §6.4). */
91
+ networkAllHosts: {
92
+ zh: "这个决定对以上所有域名生效",
93
+ en: "This decision applies to all the domains above",
94
+ },
95
+ /** A network ask's 「once」 button. */
96
+ networkAllowOnce: { zh: "这次允许", en: "Allow this time" },
97
+ /** A network ask's 「always」 button. */
98
+ networkAllowAlways: { zh: "这台设备一直允许", en: "Always allow on this device" },
80
99
  /** 提问卡片的随附文本。 */
81
100
  questionTitle: { zh: "需要你的回答", en: "Your input is needed" },
82
101
  /** 连接卡片的随附文本。 */
@@ -46,6 +46,18 @@ export declare function findThread(db: KernelSchemaDb, k: ThreadKey): Promise<st
46
46
  * 返回实际生效的会话 id。
47
47
  */
48
48
  export declare function insertThread(db: KernelSchemaDb, k: ThreadKey, conversationId: string): Promise<string>;
49
+ /**
50
+ * The live conversation of a thread, or a new one bound to it, inside the caller's transaction
51
+ * (a scheduled task posting into a chat, scheduled tasks spec §6): the live thread row's
52
+ * conversation when there is one; otherwise `create()`'s conversation, bound with
53
+ * {@link insertThread} and given the chat as its delivery target (`channel_deliveries`), as an
54
+ * inbound message would. When a concurrent inbound bound the thread first, the empty conversation
55
+ * just created is deleted and the winner's returned. `created` is true only for `create()`'s.
56
+ */
57
+ export declare function findOrBindThread(db: KernelSchemaDb, k: ThreadKey, target: ChannelTarget, create: () => Promise<string>): Promise<{
58
+ conversationId: string;
59
+ created: boolean;
60
+ }>;
49
61
  /** 私聊 `/new`:归档当前话题行(下一条消息新建对话)。返回是否归档了一行。 */
50
62
  export declare function archiveThread(db: KernelSchemaDb, k: ThreadKey): Promise<boolean>;
51
63
  /**
@@ -147,6 +147,40 @@ export async function insertThread(db, k, conversationId) {
147
147
  throw new Error("channel thread vanished after a conflicting insert");
148
148
  return winner;
149
149
  }
150
+ /**
151
+ * The live conversation of a thread, or a new one bound to it, inside the caller's transaction
152
+ * (a scheduled task posting into a chat, scheduled tasks spec §6): the live thread row's
153
+ * conversation when there is one; otherwise `create()`'s conversation, bound with
154
+ * {@link insertThread} and given the chat as its delivery target (`channel_deliveries`), as an
155
+ * inbound message would. When a concurrent inbound bound the thread first, the empty conversation
156
+ * just created is deleted and the winner's returned. `created` is true only for `create()`'s.
157
+ */
158
+ export async function findOrBindThread(db, k, target, create) {
159
+ const found = await findThread(db, k);
160
+ if (found !== undefined)
161
+ return { conversationId: found, created: false };
162
+ const id = await create();
163
+ const winner = await insertThread(db, k, id);
164
+ if (winner !== id) {
165
+ await db.deleteFrom("conversations").where("id", "=", id).where("last_seq", "=", "0").execute();
166
+ return { conversationId: winner, created: false };
167
+ }
168
+ await db
169
+ .insertInto("channel_deliveries")
170
+ .values({
171
+ conversation_id: id,
172
+ connection_id: k.connectionId,
173
+ target: JSON.stringify(target),
174
+ last_seq: 0,
175
+ })
176
+ .onConflict((oc) => oc.column("conversation_id").doUpdateSet({
177
+ connection_id: k.connectionId,
178
+ target: JSON.stringify(target),
179
+ updated_at: new Date(),
180
+ }))
181
+ .execute();
182
+ return { conversationId: id, created: true };
183
+ }
150
184
  /** 私聊 `/new`:归档当前话题行(下一条消息新建对话)。返回是否归档了一行。 */
151
185
  export async function archiveThread(db, k) {
152
186
  const r = await db
@@ -182,6 +182,9 @@ export declare const KernelConfigSchema: z.ZodObject<{
182
182
  maxDispatchesPerRun: z.ZodDefault<z.ZodNumber>;
183
183
  maxTokensPerRun: z.ZodDefault<z.ZodNumber>;
184
184
  }, z.core.$strip>>;
185
+ skills: z.ZodPrefault<z.ZodObject<{
186
+ catalogBudgetChars: z.ZodDefault<z.ZodNumber>;
187
+ }, z.core.$strip>>;
185
188
  web: z.ZodPrefault<z.ZodObject<{
186
189
  search: z.ZodPrefault<z.ZodObject<{
187
190
  maxResults: z.ZodDefault<z.ZodNumber>;
@@ -193,6 +196,12 @@ export declare const KernelConfigSchema: z.ZodObject<{
193
196
  timeoutMs: z.ZodDefault<z.ZodNumber>;
194
197
  }, z.core.$strip>>;
195
198
  }, z.core.$strip>>;
199
+ schedules: z.ZodPrefault<z.ZodObject<{
200
+ minIntervalFloorMinutes: z.ZodDefault<z.ZodNumber>;
201
+ maxTasksPerMemberCap: z.ZodDefault<z.ZodNumber>;
202
+ maxTasksPerOrgCap: z.ZodDefault<z.ZodNumber>;
203
+ maxRunsPerHourCap: z.ZodDefault<z.ZodNumber>;
204
+ }, z.core.$strip>>;
196
205
  sandbox: z.ZodPrefault<z.ZodObject<{
197
206
  image: z.ZodOptional<z.ZodString>;
198
207
  lockWaitSeconds: z.ZodDefault<z.ZodNumber>;
@@ -214,9 +223,22 @@ export declare const KernelConfigSchema: z.ZodObject<{
214
223
  egress: z.ZodDefault<z.ZodEnum<{
215
224
  allowlist: "allowlist";
216
225
  none: "none";
217
- open: "open";
218
226
  }>>;
219
227
  allowHosts: z.ZodDefault<z.ZodArray<z.ZodString>>;
228
+ devHosts: z.ZodDefault<z.ZodArray<z.ZodString>>;
229
+ }, z.core.$strip>>;
230
+ tiers: z.ZodPrefault<z.ZodObject<{
231
+ max: z.ZodDefault<z.ZodEnum<{
232
+ dev: "dev";
233
+ open: "open";
234
+ strict: "strict";
235
+ }>>;
236
+ default: z.ZodDefault<z.ZodEnum<{
237
+ dev: "dev";
238
+ open: "open";
239
+ strict: "strict";
240
+ }>>;
241
+ allowUnsandboxed: z.ZodDefault<z.ZodBoolean>;
220
242
  }, z.core.$strip>>;
221
243
  }, z.core.$strip>>;
222
244
  devices: z.ZodPrefault<z.ZodObject<{