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

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 (234) hide show
  1. package/bootstrap.json +25 -25
  2. package/dist/admin/admin.controller.d.ts +3 -3
  3. package/dist/admin/admin.controller.js +6 -2
  4. package/dist/admin/admin.module.d.ts +2 -1
  5. package/dist/admin/admin.module.js +7 -3
  6. package/dist/admin/admin.schemas.d.ts +13 -0
  7. package/dist/admin/outbox-dead.controller.d.ts +23 -0
  8. package/dist/admin/outbox-dead.controller.js +62 -0
  9. package/dist/admin/outbox-dead.schemas.d.ts +8 -0
  10. package/dist/admin/outbox-dead.schemas.js +18 -0
  11. package/dist/admin/outbox-dead.service.d.ts +66 -0
  12. package/dist/admin/outbox-dead.service.js +179 -0
  13. package/dist/admin/overview.service.d.ts +40 -5
  14. package/dist/admin/overview.service.js +41 -7
  15. package/dist/agents/from-template.js +1 -1
  16. package/dist/background/finish.d.ts +5 -2
  17. package/dist/background/finish.js +14 -3
  18. package/dist/background/launch.d.ts +44 -5
  19. package/dist/background/launch.js +92 -9
  20. package/dist/background/session.d.ts +14 -2
  21. package/dist/background/session.js +26 -4
  22. package/dist/background/status.d.ts +1 -1
  23. package/dist/background/status.js +3 -3
  24. package/dist/background/stop.d.ts +0 -2
  25. package/dist/background/stop.js +1 -21
  26. package/dist/background/store.d.ts +8 -0
  27. package/dist/background/store.js +24 -0
  28. package/dist/catalog/admin-catalog.controller.d.ts +21 -2
  29. package/dist/catalog/admin-catalog.controller.js +45 -6
  30. package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
  31. package/dist/catalog/admin-catalog.schemas.js +8 -0
  32. package/dist/catalog/catalog-content.d.ts +5 -0
  33. package/dist/catalog/catalog-content.js +1 -0
  34. package/dist/catalog/catalog.module.d.ts +2 -1
  35. package/dist/catalog/catalog.module.js +5 -1
  36. package/dist/catalog/marketplaces.service.d.ts +1 -1
  37. package/dist/catalog/platform-import-update.d.ts +50 -0
  38. package/dist/catalog/platform-import-update.js +116 -0
  39. package/dist/catalog/plugin-import.d.ts +36 -1
  40. package/dist/catalog/plugin-import.js +23 -1
  41. package/dist/catalog/plugin-upgrade.d.ts +18 -0
  42. package/dist/catalog/plugin-upgrade.js +19 -0
  43. package/dist/catalog/requests.service.js +2 -2
  44. package/dist/channels/channels.module.d.ts +5 -2
  45. package/dist/channels/channels.module.js +9 -2
  46. package/dist/channels/deliver-job.d.ts +25 -0
  47. package/dist/channels/deliver-job.js +25 -0
  48. package/dist/channels/inbound.d.ts +1 -1
  49. package/dist/channels/inbound.js +2 -15
  50. package/dist/channels/jobs.d.ts +28 -18
  51. package/dist/channels/jobs.js +63 -26
  52. package/dist/config/schema.d.ts +3 -6
  53. package/dist/config/schema.js +20 -27
  54. package/dist/conversations/agent-tree.d.ts +16 -0
  55. package/dist/conversations/agent-tree.js +36 -0
  56. package/dist/conversations/conversations.service.d.ts +44 -14
  57. package/dist/conversations/conversations.service.js +52 -38
  58. package/dist/conversations/event-log.d.ts +7 -10
  59. package/dist/conversations/event-log.js +24 -15
  60. package/dist/conversations/events.js +2 -1
  61. package/dist/conversations/queue.service.d.ts +8 -3
  62. package/dist/conversations/queue.service.js +37 -13
  63. package/dist/conversations/titles.d.ts +21 -8
  64. package/dist/conversations/titles.js +42 -15
  65. package/dist/db/kernel-schema.d.ts +26 -8
  66. package/dist/devices/devices.service.d.ts +12 -3
  67. package/dist/devices/devices.service.js +30 -9
  68. package/dist/errors.d.ts +15 -0
  69. package/dist/errors.js +15 -0
  70. package/dist/evals/run-replay.js +14 -8
  71. package/dist/extensions/extensions.module.js +3 -0
  72. package/dist/extensions/lifecycle.d.ts +6 -1
  73. package/dist/extensions/lifecycle.js +9 -2
  74. package/dist/extensions/usage-subscribers.d.ts +43 -0
  75. package/dist/extensions/usage-subscribers.js +108 -0
  76. package/dist/http/api.module.js +2 -0
  77. package/dist/identity/settings.d.ts +53 -3
  78. package/dist/identity/settings.js +92 -9
  79. package/dist/index.d.ts +1 -0
  80. package/dist/index.js +1 -0
  81. package/dist/infra/cli/kernel-cli.module.js +5 -0
  82. package/dist/infra/config/kernel-config.service.d.ts +7 -0
  83. package/dist/infra/config/kernel-config.service.js +20 -0
  84. package/dist/infra/events/kernel-events.d.ts +9 -35
  85. package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
  86. package/dist/infra/outbox/outbox-relay.module.js +18 -0
  87. package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
  88. package/dist/infra/outbox/outbox.dispatcher.js +310 -0
  89. package/dist/infra/outbox/outbox.module.d.ts +24 -0
  90. package/dist/infra/outbox/outbox.module.js +50 -0
  91. package/dist/infra/outbox/outbox.relay.d.ts +31 -0
  92. package/dist/infra/outbox/outbox.relay.js +76 -0
  93. package/dist/infra/outbox/outbox.service.d.ts +51 -0
  94. package/dist/infra/outbox/outbox.service.js +131 -0
  95. package/dist/infra/outbox/registry.d.ts +120 -0
  96. package/dist/infra/outbox/registry.js +108 -0
  97. package/dist/infra/queue/queue-names.d.ts +8 -0
  98. package/dist/infra/queue/queue-names.js +16 -0
  99. package/dist/kernel.module.js +3 -0
  100. package/dist/local-devices/device-policy.service.d.ts +4 -0
  101. package/dist/local-devices/device-policy.service.js +61 -3
  102. package/dist/local-devices/local-device-gateway.d.ts +4 -2
  103. package/dist/local-devices/local-device-gateway.js +39 -14
  104. package/dist/local-devices/machines.d.ts +25 -1
  105. package/dist/local-devices/machines.js +91 -15
  106. package/dist/local-devices/policy-summary.d.ts +44 -1
  107. package/dist/local-devices/policy-summary.js +69 -0
  108. package/dist/models/embeddings.d.ts +9 -9
  109. package/dist/models/embeddings.js +11 -18
  110. package/dist/models/models.module.js +3 -0
  111. package/dist/orgs/orgs.service.js +7 -0
  112. package/dist/orgs/settings.controller.js +2 -2
  113. package/dist/orgs/settings.service.d.ts +11 -5
  114. package/dist/orgs/settings.service.js +28 -14
  115. package/dist/overrides.d.ts +20 -3
  116. package/dist/overrides.js +10 -2
  117. package/dist/platform/exclusions.js +5 -0
  118. package/dist/platform/ops/admin.ops.js +50 -9
  119. package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
  120. package/dist/platform/ops/catalog-admin.ops.js +32 -4
  121. package/dist/platform/ops/org-settings.ops.js +3 -3
  122. package/dist/platform/ops/orgs.ops.js +1 -0
  123. package/dist/platform/ops/outbox.ops.d.ts +21 -0
  124. package/dist/platform/ops/outbox.ops.js +125 -0
  125. package/dist/platform/ops/platform.ops.js +1 -1
  126. package/dist/platform/ops/schedules.ops.js +40 -1
  127. package/dist/platform/platform-ops.module.d.ts +2 -1
  128. package/dist/platform/platform-ops.module.js +3 -0
  129. package/dist/platform/route-schemas.js +3 -0
  130. package/dist/roles/worker.module.js +9 -4
  131. package/dist/runtime/langgraph-loop.js +13 -5
  132. package/dist/runtime/limits.d.ts +15 -0
  133. package/dist/runtime/limits.js +25 -0
  134. package/dist/runtime/loop.d.ts +8 -3
  135. package/dist/runtime/loop.js +2 -1
  136. package/dist/runtime/maintenance.d.ts +4 -2
  137. package/dist/runtime/maintenance.js +17 -2
  138. package/dist/runtime/model-call.d.ts +14 -16
  139. package/dist/runtime/model-call.js +14 -24
  140. package/dist/runtime/queue-lost-jobs.d.ts +24 -0
  141. package/dist/runtime/queue-lost-jobs.js +51 -0
  142. package/dist/runtime/run-control.d.ts +42 -5
  143. package/dist/runtime/run-control.js +66 -12
  144. package/dist/runtime/run-finish.d.ts +39 -12
  145. package/dist/runtime/run-finish.js +97 -14
  146. package/dist/runtime/run-jobs.d.ts +95 -13
  147. package/dist/runtime/run-jobs.js +199 -28
  148. package/dist/runtime/run-orphan-check.job.d.ts +142 -0
  149. package/dist/runtime/run-orphan-check.job.js +339 -0
  150. package/dist/runtime/run-processors.d.ts +16 -9
  151. package/dist/runtime/run-processors.js +1 -1
  152. package/dist/runtime/run-queue.d.ts +64 -55
  153. package/dist/runtime/run-queue.js +147 -284
  154. package/dist/runtime/run-steps.d.ts +1 -2
  155. package/dist/runtime/run-steps.js +8 -11
  156. package/dist/runtime/run-worker.d.ts +6 -17
  157. package/dist/runtime/run-worker.js +32 -34
  158. package/dist/runtime/runtime.module.d.ts +2 -3
  159. package/dist/runtime/runtime.module.js +5 -6
  160. package/dist/runtime/step-log.d.ts +16 -4
  161. package/dist/runtime/step-log.js +26 -6
  162. package/dist/runtime/stream.controller.d.ts +2 -4
  163. package/dist/runtime/stream.controller.js +5 -11
  164. package/dist/runtime/subagents/child.js +1 -1
  165. package/dist/runtime/subagents/definition.d.ts +2 -1
  166. package/dist/runtime/subagents/definition.js +4 -2
  167. package/dist/runtime/subagents/deliveries.d.ts +6 -3
  168. package/dist/runtime/subagents/deliveries.js +16 -7
  169. package/dist/runtime/subagents/dispatch.d.ts +12 -12
  170. package/dist/runtime/subagents/dispatch.js +13 -38
  171. package/dist/runtime/subagents/messages.d.ts +5 -1
  172. package/dist/runtime/subagents/messages.js +11 -3
  173. package/dist/runtime/subagents/skill-fork.d.ts +3 -3
  174. package/dist/runtime/subagents/skill-fork.js +5 -5
  175. package/dist/runtime/subagents/subagents.service.js +1 -1
  176. package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
  177. package/dist/runtime/subagents/tree-jobs.js +86 -0
  178. package/dist/runtime/usage-report-jobs.d.ts +23 -48
  179. package/dist/runtime/usage-report-jobs.js +67 -119
  180. package/dist/runtime/usage-report.d.ts +28 -77
  181. package/dist/runtime/usage-report.js +35 -213
  182. package/dist/runtime/waits.d.ts +48 -198
  183. package/dist/runtime/waits.js +103 -805
  184. package/dist/sandbox/platform-tiers.d.ts +86 -0
  185. package/dist/sandbox/platform-tiers.js +97 -0
  186. package/dist/sandbox/sandbox.service.d.ts +18 -5
  187. package/dist/sandbox/sandbox.service.js +75 -20
  188. package/dist/sandbox/tiers.d.ts +14 -2
  189. package/dist/sandbox/tiers.js +8 -0
  190. package/dist/schedules/calendar.d.ts +134 -0
  191. package/dist/schedules/calendar.js +410 -0
  192. package/dist/schedules/fire.d.ts +1 -3
  193. package/dist/schedules/fire.js +5 -10
  194. package/dist/schedules/run-tracker.d.ts +0 -5
  195. package/dist/schedules/run-tracker.js +0 -13
  196. package/dist/schedules/scan.job.js +15 -20
  197. package/dist/schedules/schedule-math.d.ts +42 -0
  198. package/dist/schedules/schedule-math.js +52 -0
  199. package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
  200. package/dist/schedules/scheduled-tasks.service.js +53 -0
  201. package/dist/schedules/schedules.controller.d.ts +5 -1
  202. package/dist/schedules/schedules.controller.js +15 -1
  203. package/dist/schedules/schedules.schemas.d.ts +35 -0
  204. package/dist/schedules/schedules.schemas.js +27 -0
  205. package/dist/schedules/store.d.ts +5 -0
  206. package/dist/schedules/store.js +20 -0
  207. package/dist/tools/approvals.service.d.ts +26 -7
  208. package/dist/tools/approvals.service.js +88 -10
  209. package/dist/tools/builtin-names.d.ts +5 -3
  210. package/dist/tools/builtin-names.js +12 -4
  211. package/dist/tools/builtins/builtin-tools.d.ts +1 -2
  212. package/dist/tools/builtins/builtin-tools.js +21 -17
  213. package/dist/tools/builtins/sandbox/bash.js +9 -3
  214. package/dist/tools/builtins/sandbox/common.d.ts +18 -1
  215. package/dist/tools/builtins/sandbox/common.js +14 -1
  216. package/dist/tools/builtins/sandbox/index.d.ts +2 -1
  217. package/dist/tools/builtins/sandbox/index.js +4 -1
  218. package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
  219. package/dist/tools/builtins/sandbox/task-list.js +130 -0
  220. package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
  221. package/dist/tools/builtins/sandbox/task-output.js +21 -3
  222. package/dist/tools/builtins/web/extract.d.ts +2 -4
  223. package/dist/tools/builtins/web/extract.js +2 -5
  224. package/dist/tools/pipeline.d.ts +1 -1
  225. package/dist/tools/pipeline.js +1 -1
  226. package/dist/tools/questions.service.d.ts +15 -6
  227. package/dist/tools/questions.service.js +20 -8
  228. package/dist/tools/registry.d.ts +4 -2
  229. package/dist/tools/registry.js +4 -3
  230. package/dist/tools/tools.module.js +4 -3
  231. package/package.json +3 -3
  232. package/src/migrations/0118_device_policy_ops.sql +6 -0
  233. package/src/migrations/0119_outbox.sql +22 -0
  234. package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
@@ -104,6 +104,13 @@ export function validateTrigger(t, now) {
104
104
  export function nextTick(t, after) {
105
105
  return ticker(t)(after);
106
106
  }
107
+ /**
108
+ * The scanner's ticker for one trigger ({@link nextTick} without rebuilding its `CronTime`s per
109
+ * call), for callers that walk the same trigger many times ({@link ticksIn}).
110
+ */
111
+ export function tickerOf(t) {
112
+ return ticker(t);
113
+ }
107
114
  /** How far back a DST transition can move a tick: longer than any gap (spec §5). */
108
115
  const TRANSITION_LOOKBACK_MS = 6 * 3600_000;
109
116
  /**
@@ -138,6 +145,51 @@ function ticker(t) {
138
145
  return null;
139
146
  };
140
147
  }
148
+ /**
149
+ * The ticks in `(after, until]` (before jitter), at most `max`, from the same ticker
150
+ * {@link nextTick} and {@link dueTicks} use, so a list of planned runs equals what the scanner
151
+ * fires (calendar spec §5.2). `exhausted`: `max` ticks were taken before a tick past `until` was
152
+ * seen, so more may follow. A `once` trigger yields its one tick when it lies in the window.
153
+ * Costs one tick evaluation per tick returned, plus one when the walk reaches `until`. A caller
154
+ * that walks one trigger in many windows passes a {@link tickerOf} it built once.
155
+ */
156
+ export function ticksIn(t, after, until, max) {
157
+ const ticks = [];
158
+ if (after.getTime() >= until.getTime())
159
+ return { ticks, exhausted: false };
160
+ const next = typeof t === "function" ? t : ticker(t);
161
+ let cursor = after;
162
+ while (ticks.length < max) {
163
+ const tick = next(cursor);
164
+ if (!tick || tick.getTime() > until.getTime())
165
+ return { ticks, exhausted: false };
166
+ ticks.push(tick);
167
+ cursor = tick;
168
+ }
169
+ return { ticks, exhausted: true };
170
+ }
171
+ /**
172
+ * One scan of a due task (`next_run_at <= now`) as of `now`, the schedule math alone: the ticks
173
+ * are before jitter, so the due tick is `nextRunAt - jitter` and a tick is due once
174
+ * `tick + jitter <= now`. The scanner applies the result; the calendar's parity test drives it.
175
+ * Throws when the trigger cannot be evaluated.
176
+ */
177
+ export function scanStep(task, now, catchUpWindowMinutes) {
178
+ const jitterMs = task.jitterSeconds * 1000;
179
+ const dueTick = new Date(task.nextRunAt.getTime() - jitterMs);
180
+ const until = new Date(now.getTime() - jitterMs);
181
+ const ticks = dueTicks(task.trigger, new Date(dueTick.getTime() - 1), until, catchUpWindowMinutes);
182
+ const next = nextTick(task.trigger, until);
183
+ const expires = task.expiresAt?.getTime() ?? Number.POSITIVE_INFINITY;
184
+ const fire = ticks.fire !== null && ticks.fire.getTime() <= expires ? ticks.fire : null;
185
+ const first = ticks.firstMissed;
186
+ const missed = first !== null && first.getTime() <= expires ? first : null;
187
+ if (next === null)
188
+ return { missed, fire, nextRunAt: null, end: "done" };
189
+ if (next.getTime() > expires)
190
+ return { missed, fire, nextRunAt: null, end: "expired" };
191
+ return { missed, fire, nextRunAt: new Date(next.getTime() + jitterMs), end: null };
192
+ }
141
193
  /** The wall clock of `at` in `timeZone`, read as if it were UTC (epoch ms, seconds dropped). */
142
194
  function localWallClock(at, timeZone) {
143
195
  return Math.floor(wallOf(at.getTime(), timeZone) / 60000) * 60000;
@@ -13,6 +13,7 @@ import { OrgLock } from "../orgs/org-lock.js";
13
13
  import { ProviderRegistry } from "../runtime/providers.js";
14
14
  import { SandboxService } from "../sandbox/sandbox.service.js";
15
15
  import { ToolRegistry } from "../tools/registry.js";
16
+ import { type CalendarAnswer, type CalendarLimits } from "./calendar.js";
16
17
  import { type GrantContext, type GrantRefusalCode, grantRefusal, grantRefusalOf, networkGrantRefusal } from "./grant-rules.js";
17
18
  import { ScheduledTaskStore } from "./store.js";
18
19
  import type { ScheduleChat, ScheduledTask, ScheduledTaskGrant, ScheduleMode, SchedulePauseReason, TaskRun } from "./types.js";
@@ -231,6 +232,21 @@ export declare class ScheduledTasksService {
231
232
  all?: boolean;
232
233
  conversationId?: string;
233
234
  }): Promise<ScheduledTaskListItem[]>;
235
+ /**
236
+ * The calendar between two instants (calendar spec §5): the runs and planned occurrences of the
237
+ * actor's own tasks, or with `all` (org admins only, else `forbidden`) every task of the org; with
238
+ * `taskId` only that task (one the actor may see, else `schedule.not_found`), never collapsed.
239
+ * Another creator's runs come without `conversationId`, `error` and `missingGrants` (§15 Q3).
240
+ * A range longer than 43 days (2 with `taskId`), or not forward, is `validation_failed`. `now`
241
+ * is for tests.
242
+ */
243
+ calendar(orgId: string, actor: ScheduleActor, q: {
244
+ from: Date;
245
+ to: Date;
246
+ timeZone: string;
247
+ all?: boolean;
248
+ taskId?: string;
249
+ }, now?: Date, limits?: CalendarLimits): Promise<CalendarAnswer>;
234
250
  /**
235
251
  * The tasks of `tasks` that post into the conversation: a `conversation` task naming it, or a
236
252
  * `chat` task whose chat the conversation is a thread of (spec §10 side panel).
@@ -32,6 +32,7 @@ import { ProviderRegistry } from "../runtime/providers.js";
32
32
  import { SandboxService } from "../sandbox/sandbox.service.js";
33
33
  import { isTimeZone } from "../time-zone.js";
34
34
  import { ToolRegistry } from "../tools/registry.js";
35
+ import { assembleCalendar, CALENDAR_LIMITS, CALENDAR_RANGE_DAYS, CALENDAR_TASK_RANGE_DAYS, calendarRunReadLimit, } from "./calendar.js";
35
36
  import { readScheduleSettings } from "./effective-settings.js";
36
37
  import { grantNotAllowed, scheduleDisabled, scheduleForbidden, scheduleNotFound, targetInvalid, } from "./errors.js";
37
38
  import { grantRefusal, grantRefusalOf, isNetworkGrantKey, isPolicyGrantKey, isTierGrantKey, NETWORK_GRANT_PREFIX, NETWORK_GRANT_SPEC_HASH, networkGrantHost, networkGrantRefusal, TIER_GRANT_SPEC_HASH, tierGrantRefusal, } from "./grant-rules.js";
@@ -363,6 +364,58 @@ let ScheduledTasksService = class ScheduledTasksService {
363
364
  }
364
365
  return this.withLastRuns(tasks);
365
366
  }
367
+ /**
368
+ * The calendar between two instants (calendar spec §5): the runs and planned occurrences of the
369
+ * actor's own tasks, or with `all` (org admins only, else `forbidden`) every task of the org; with
370
+ * `taskId` only that task (one the actor may see, else `schedule.not_found`), never collapsed.
371
+ * Another creator's runs come without `conversationId`, `error` and `missingGrants` (§15 Q3).
372
+ * A range longer than 43 days (2 with `taskId`), or not forward, is `validation_failed`. `now`
373
+ * is for tests.
374
+ */
375
+ async calendar(orgId, actor, q, now = new Date(), limits = CALENDAR_LIMITS) {
376
+ const role = await this.liveRole(orgId, actor.accountId);
377
+ const manage = can(role, "schedules.manage");
378
+ if (q.all === true && !manage) {
379
+ throw new ApiError("forbidden", "Only org admins can read every scheduled task");
380
+ }
381
+ const span = q.to.getTime() - q.from.getTime();
382
+ const maxDays = q.taskId === undefined ? CALENDAR_RANGE_DAYS : CALENDAR_TASK_RANGE_DAYS;
383
+ if (!(span > 0))
384
+ throw new ApiError("validation_failed", "from must be before to");
385
+ if (span > maxDays * DAY_MS) {
386
+ throw new ApiError("validation_failed", `The range is at most ${maxDays} days${q.taskId === undefined ? "" : " with taskId"}`);
387
+ }
388
+ const t = this.db.db;
389
+ let tasks;
390
+ if (q.taskId !== undefined) {
391
+ const task = await this.store.getTask(t, orgId, q.taskId);
392
+ if (!task || (task.accountId !== actor.accountId && !manage))
393
+ throw scheduleNotFound();
394
+ tasks = [task];
395
+ }
396
+ else {
397
+ tasks =
398
+ q.all === true
399
+ ? await this.store.listForOrg(t, orgId)
400
+ : await this.store.listForAccount(t, orgId, actor.accountId);
401
+ }
402
+ const runs = await this.store.listRunsBetween(t, orgId, tasks.map((x) => x.id), q.from, q.to,
403
+ // one row past the caps tells the assembly where to cut
404
+ calendarRunReadLimit(limits));
405
+ const settings = await readScheduleSettings(t, this.config, orgId);
406
+ return assembleCalendar({
407
+ tasks,
408
+ runs,
409
+ viewerAccountId: actor.accountId,
410
+ from: q.from,
411
+ to: q.to,
412
+ timeZone: q.timeZone,
413
+ now,
414
+ plan: settings.enabled,
415
+ catchUpWindowMinutes: settings.catchUpWindowMinutes,
416
+ collapse: q.taskId === undefined,
417
+ }, limits);
418
+ }
366
419
  /**
367
420
  * The tasks of `tasks` that post into the conversation: a `conversation` task naming it, or a
368
421
  * `chat` task whose chat the conversation is a thread of (spec §10 side panel).
@@ -1,8 +1,9 @@
1
1
  import type { z } from "zod";
2
2
  import { type Principal } from "../identity/principal.js";
3
3
  import { type OrgContext } from "../orgs/org.guard.js";
4
+ import type { CalendarAnswer } from "./calendar.js";
4
5
  import { type ChatTarget, type GrantableTool, type ScheduledTaskGrantView, type ScheduledTaskListItem, ScheduledTasksService } from "./scheduled-tasks.service.js";
5
- import { type ScheduleChatTargetsQuery, type ScheduleCreateBody, type ScheduleGrantableQuery, type ScheduleGrantParams, type ScheduleGrantsBody, type ScheduleListQuery, type ScheduleParams, type ScheduleRunBody, type ScheduleRunsQuery, type ScheduleUpdateBody } from "./schedules.schemas.js";
6
+ import { type ScheduleCalendarQuery, type ScheduleChatTargetsQuery, type ScheduleCreateBody, type ScheduleGrantableQuery, type ScheduleGrantParams, type ScheduleGrantsBody, type ScheduleListQuery, type ScheduleParams, type ScheduleRunBody, type ScheduleRunsQuery, type ScheduleUpdateBody } from "./schedules.schemas.js";
6
7
  import type { ScheduledTask, ScheduledTaskGrant, TaskRun } from "./types.js";
7
8
  /**
8
9
  * Scheduled tasks (`/api/v1/orgs/:orgId/schedules`, spec 2026-11-11-scheduled-tasks §10); human
@@ -10,6 +11,8 @@ import type { ScheduledTask, ScheduledTaskGrant, TaskRun } from "./types.js";
10
11
  * - `GET /` → `{ items }`: the caller's own tasks, each with its `lastRun`; `?all=true` every task
11
12
  * of the org (org admins, `schedules.manage`, enforced by the service: 403 `forbidden`
12
13
  * otherwise); `?conversationId=` only those posting into that conversation;
14
+ * - `GET /calendar?from&to&timeZone[&all][&taskId]` → the calendar (runs, planned occurrences,
15
+ * collapsed task-days; calendar spec §5.1), `org.read` like the list, `all` as the list's;
13
16
  * - `GET /chat-targets` → `{ items }`: the chats the caller can point a `chat` task at;
14
17
  * - `GET /grantable-tools?agentId=` → `{ items }`: the agent's tools, each grantable or why not;
15
18
  * - `POST /` → 201 `{ task }` (`timeZone` is the browser zone, a fallback only);
@@ -30,6 +33,7 @@ export declare class SchedulesController {
30
33
  list(principal: Principal, org: OrgContext, q: z.infer<typeof ScheduleListQuery>): Promise<{
31
34
  items: ScheduledTaskListItem[];
32
35
  }>;
36
+ calendar(principal: Principal, org: OrgContext, q: z.infer<typeof ScheduleCalendarQuery>): Promise<CalendarAnswer>;
33
37
  chatTargets(principal: Principal, org: OrgContext, _q: z.infer<typeof ScheduleChatTargetsQuery>): Promise<{
34
38
  items: ChatTarget[];
35
39
  }>;
@@ -12,13 +12,15 @@ import { CurrentPrincipal, requireUser } from "../identity/principal.js";
12
12
  import { ZBody, ZParam, ZQuery } from "../infra/validation/zod-params.js";
13
13
  import { CurrentOrg, OrgAction } from "../orgs/org.guard.js";
14
14
  import { ScheduledTasksService, } from "./scheduled-tasks.service.js";
15
- import { createInputOf, grantsOf, ScheduleChatTargetsQueryDto, ScheduleCreateBodyDto, ScheduleGrantableQueryDto, ScheduleGrantParamsDto, ScheduleGrantsBodyDto, ScheduleListQueryDto, ScheduleParamsDto, ScheduleRunBodyDto, ScheduleRunsQueryDto, ScheduleUpdateBodyDto, updateInputOf, } from "./schedules.schemas.js";
15
+ import { calendarInputOf, createInputOf, grantsOf, ScheduleCalendarQueryDto, ScheduleChatTargetsQueryDto, ScheduleCreateBodyDto, ScheduleGrantableQueryDto, ScheduleGrantParamsDto, ScheduleGrantsBodyDto, ScheduleListQueryDto, ScheduleParamsDto, ScheduleRunBodyDto, ScheduleRunsQueryDto, ScheduleUpdateBodyDto, updateInputOf, } from "./schedules.schemas.js";
16
16
  /**
17
17
  * Scheduled tasks (`/api/v1/orgs/:orgId/schedules`, spec 2026-11-11-scheduled-tasks §10); human
18
18
  * users only (a service token gets 403).
19
19
  * - `GET /` → `{ items }`: the caller's own tasks, each with its `lastRun`; `?all=true` every task
20
20
  * of the org (org admins, `schedules.manage`, enforced by the service: 403 `forbidden`
21
21
  * otherwise); `?conversationId=` only those posting into that conversation;
22
+ * - `GET /calendar?from&to&timeZone[&all][&taskId]` → the calendar (runs, planned occurrences,
23
+ * collapsed task-days; calendar spec §5.1), `org.read` like the list, `all` as the list's;
22
24
  * - `GET /chat-targets` → `{ items }`: the chats the caller can point a `chat` task at;
23
25
  * - `GET /grantable-tools?agentId=` → `{ items }`: the agent's tools, each grantable or why not;
24
26
  * - `POST /` → 201 `{ task }` (`timeZone` is the browser zone, a fallback only);
@@ -48,6 +50,11 @@ let SchedulesController = class SchedulesController {
48
50
  });
49
51
  return { items };
50
52
  }
53
+ // declared before `:id`; org.read like the list (a creator demoted to guest still reads their own)
54
+ async calendar(principal, org, q) {
55
+ const p = requireUser(principal);
56
+ return this.tasks.calendar(org.orgId, { accountId: p.account.id }, calendarInputOf(q));
57
+ }
51
58
  async chatTargets(principal, org, _q) {
52
59
  const p = requireUser(principal);
53
60
  return { items: await this.tasks.chatTargets(org.orgId, p.account.id) };
@@ -134,6 +141,13 @@ __decorate([
134
141
  __param(1, CurrentOrg()),
135
142
  __param(2, ZQuery(ScheduleListQueryDto))
136
143
  ], SchedulesController.prototype, "list", null);
144
+ __decorate([
145
+ Get("calendar"),
146
+ OrgAction("org.read"),
147
+ __param(0, CurrentPrincipal()),
148
+ __param(1, CurrentOrg()),
149
+ __param(2, ZQuery(ScheduleCalendarQueryDto))
150
+ ], SchedulesController.prototype, "calendar", null);
137
151
  __decorate([
138
152
  Get("chat-targets"),
139
153
  OrgAction("schedules.use"),
@@ -138,6 +138,21 @@ export declare const ScheduleListQuery: z.ZodObject<{
138
138
  }>>;
139
139
  conversationId: z.ZodOptional<z.ZodString>;
140
140
  }, z.core.$strict>;
141
+ /**
142
+ * The calendar's range (`from < to`, at most 43 days, 2 with `taskId`: checked by the service),
143
+ * the viewer's zone (local days for the collapse and the cut), `all` (org admins) and `taskId`
144
+ * (one task, never collapsed).
145
+ */
146
+ export declare const ScheduleCalendarQuery: z.ZodObject<{
147
+ from: z.ZodString;
148
+ to: z.ZodString;
149
+ timeZone: z.ZodString;
150
+ all: z.ZodOptional<z.ZodEnum<{
151
+ false: "false";
152
+ true: "true";
153
+ }>>;
154
+ taskId: z.ZodOptional<z.ZodString>;
155
+ }, z.core.$strict>;
141
156
  export declare const ScheduleChatTargetsQuery: z.ZodObject<{}, z.core.$strict>;
142
157
  export declare const ScheduleGrantableQuery: z.ZodObject<{
143
158
  agentId: z.ZodString;
@@ -258,6 +273,18 @@ declare const ScheduleListQueryDto_base: import("../infra/validation/create-zod-
258
273
  }, z.core.$strict>>;
259
274
  export declare class ScheduleListQueryDto extends ScheduleListQueryDto_base {
260
275
  }
276
+ declare const ScheduleCalendarQueryDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{
277
+ from: z.ZodString;
278
+ to: z.ZodString;
279
+ timeZone: z.ZodString;
280
+ all: z.ZodOptional<z.ZodEnum<{
281
+ false: "false";
282
+ true: "true";
283
+ }>>;
284
+ taskId: z.ZodOptional<z.ZodString>;
285
+ }, z.core.$strict>>;
286
+ export declare class ScheduleCalendarQueryDto extends ScheduleCalendarQueryDto_base {
287
+ }
261
288
  declare const ScheduleChatTargetsQueryDto_base: import("../infra/validation/create-zod-dto.js").ZodDtoClass<z.ZodObject<{}, z.core.$strict>>;
262
289
  export declare class ScheduleChatTargetsQueryDto extends ScheduleChatTargetsQueryDto_base {
263
290
  }
@@ -313,4 +340,12 @@ export declare function createInputOf(body: z.infer<typeof ScheduleCreateBody>):
313
340
  };
314
341
  /** A patch body as the service's input (an explicit `timeZone` changes the task's zone). */
315
342
  export declare function updateInputOf(body: z.infer<typeof ScheduleUpdateBody>): UpdateTaskInput;
343
+ /** A calendar query as the service's input. */
344
+ export declare const calendarInputOf: (q: z.infer<typeof ScheduleCalendarQuery>) => {
345
+ from: Date;
346
+ to: Date;
347
+ timeZone: string;
348
+ all: boolean;
349
+ taskId?: string;
350
+ };
316
351
  export {};
@@ -86,6 +86,22 @@ export const ScheduleListQuery = z
86
86
  conversationId: Id.optional(),
87
87
  })
88
88
  .strict();
89
+ /** An instant with an offset (`2026-11-09T00:00:00+08:00`). */
90
+ const Instant = z.string().datetime({ offset: true });
91
+ /**
92
+ * The calendar's range (`from < to`, at most 43 days, 2 with `taskId`: checked by the service),
93
+ * the viewer's zone (local days for the collapse and the cut), `all` (org admins) and `taskId`
94
+ * (one task, never collapsed).
95
+ */
96
+ export const ScheduleCalendarQuery = z
97
+ .object({
98
+ from: Instant,
99
+ to: Instant,
100
+ timeZone: IanaTimeZone,
101
+ all: z.enum(["true", "false"]).optional(),
102
+ taskId: Id.optional(),
103
+ })
104
+ .strict();
89
105
  export const ScheduleChatTargetsQuery = z.object({}).strict();
90
106
  export const ScheduleGrantableQuery = z.object({ agentId: Id }).strict();
91
107
  export const ScheduleParams = z.object({ id: Id });
@@ -103,6 +119,8 @@ export class ScheduleUpdateBodyDto extends createZodDto(ScheduleUpdateBody, "Sch
103
119
  }
104
120
  export class ScheduleListQueryDto extends createZodDto(ScheduleListQuery, "ScheduleListQuery") {
105
121
  }
122
+ export class ScheduleCalendarQueryDto extends createZodDto(ScheduleCalendarQuery, "ScheduleCalendarQuery") {
123
+ }
106
124
  export class ScheduleChatTargetsQueryDto extends createZodDto(ScheduleChatTargetsQuery, "ScheduleChatTargetsQuery") {
107
125
  }
108
126
  export class ScheduleGrantableQueryDto extends createZodDto(ScheduleGrantableQuery, "ScheduleGrantableQuery") {
@@ -120,6 +138,7 @@ export class ScheduleGrantParamsDto extends createZodDto(ScheduleGrantParams, "S
120
138
  /** The schedule routes the Platform operations mirror (`check:platform-ops` keys). */
121
139
  export const SCHEDULE_ROUTES = [
122
140
  { route: "GET /api/v1/orgs/:orgId/schedules", query: ScheduleListQuery },
141
+ { route: "GET /api/v1/orgs/:orgId/schedules/calendar", query: ScheduleCalendarQuery },
123
142
  { route: "GET /api/v1/orgs/:orgId/schedules/chat-targets", query: ScheduleChatTargetsQuery },
124
143
  { route: "GET /api/v1/orgs/:orgId/schedules/grantable-tools", query: ScheduleGrantableQuery },
125
144
  { route: "POST /api/v1/orgs/:orgId/schedules", body: ScheduleCreateBody },
@@ -184,3 +203,11 @@ export function updateInputOf(body) {
184
203
  ...(expiresAt !== undefined && { expiresAt: expiresAt === null ? null : new Date(expiresAt) }),
185
204
  };
186
205
  }
206
+ /** A calendar query as the service's input. */
207
+ export const calendarInputOf = (q) => ({
208
+ from: new Date(q.from),
209
+ to: new Date(q.to),
210
+ timeZone: q.timeZone,
211
+ all: q.all === "true",
212
+ ...(q.taskId !== undefined && { taskId: q.taskId }),
213
+ });
@@ -68,6 +68,11 @@ export declare class ScheduledTaskStore {
68
68
  countFiredSince(t: KernelSchemaDb, orgId: string, since: Date): Promise<number>;
69
69
  /** A task's runs, latest tick first. */
70
70
  listRuns(t: KernelSchemaDb, taskId: string, limit: number): Promise<TaskRun[]>;
71
+ /**
72
+ * The runs of these tasks of the org with `scheduled_for` in `[from, to)`, trial included, by tick, at most
73
+ * `limit` (the calendar; `scheduled_task_runs_task_idx` per task).
74
+ */
75
+ listRunsBetween(t: KernelSchemaDb, orgId: string, taskIds: readonly string[], from: Date, to: Date, limit: number): Promise<TaskRun[]>;
71
76
  listGrants(t: KernelSchemaDb, taskId: string): Promise<ScheduledTaskGrant[]>;
72
77
  /** Adds a grant, replacing the task's grant of the same tool key. */
73
78
  addGrant(t: KernelSchemaDb, taskId: string, grant: ScheduledTaskGrant): Promise<void>;
@@ -329,6 +329,26 @@ let ScheduledTaskStore = class ScheduledTaskStore {
329
329
  .execute();
330
330
  return rows.map(toRun);
331
331
  }
332
+ /**
333
+ * The runs of these tasks of the org with `scheduled_for` in `[from, to)`, trial included, by tick, at most
334
+ * `limit` (the calendar; `scheduled_task_runs_task_idx` per task).
335
+ */
336
+ async listRunsBetween(t, orgId, taskIds, from, to, limit) {
337
+ if (taskIds.length === 0)
338
+ return [];
339
+ const rows = await t
340
+ .selectFrom("scheduled_task_runs")
341
+ .selectAll()
342
+ .where("org_id", "=", orgId)
343
+ .where("task_id", "in", [...taskIds])
344
+ .where("scheduled_for", ">=", from)
345
+ .where("scheduled_for", "<", to)
346
+ .orderBy("scheduled_for")
347
+ .orderBy("id")
348
+ .limit(limit)
349
+ .execute();
350
+ return rows.map(toRun);
351
+ }
332
352
  async listGrants(t, taskId) {
333
353
  const rows = await t
334
354
  .selectFrom("scheduled_task_grants")
@@ -8,6 +8,7 @@ import { KernelEvents } from "../infra/events/kernel-events.js";
8
8
  import { LockService } from "../infra/lock/lock.service.js";
9
9
  import { DevicePolicyService } from "../local-devices/device-policy.service.js";
10
10
  import { type OrgRole } from "../orgs/roles.js";
11
+ import { RunResume } from "../runtime/run-jobs.js";
11
12
  import { SandboxService } from "../sandbox/sandbox.service.js";
12
13
  import { ScheduledTaskStore } from "../schedules/store.js";
13
14
  import type { ApprovalPolicySource } from "./pipeline.js";
@@ -206,10 +207,12 @@ export interface ToolGrantView {
206
207
  * 工具调用审批:新建(loop 调用)、列出 / 查看 / 处理(HTTP),以及「以后都允许」授权的查看与撤销。
207
208
  * 状态全部在 PG:处理决定在事务里 `SELECT … FOR UPDATE` 审批行,多副本并发处理同一审批时恰好一个生效。
208
209
  *
209
- * A decision is announced after the commit as the domain event `approval.decided` (exactly once:
210
- * only the decision that changed the row from pending), which the run's waits follow to resume it
211
- * (spec §3b; the wait sweep resumes a run whose resume was lost). Events are appended in the
212
- * context's transaction (`EventLog.appendIn`) and published after the commit.
210
+ * A decision resumes its waiting run in the decision's transaction (`RunResume.resumeIn`, after the
211
+ * decided row: lock order approval, then run; transactional outbox spec §6). It is announced after
212
+ * the commit as the domain event `approval.decided` (exactly once: only the decision that changed
213
+ * the row from pending; no kernel subscriber: the IM card update is owed by the `approval_decided`
214
+ * append, transactional outbox spec §8.1). Events are appended in the context's transaction
215
+ * (`EventLog.appendIn`) and published after the commit.
213
216
  */
214
217
  export declare class ApprovalsService {
215
218
  private readonly db;
@@ -226,6 +229,8 @@ export declare class ApprovalsService {
226
229
  readonly lockService: LockService;
227
230
  /** A trial run's 「这次允许」 becomes a task grant only within the org's egress. */
228
231
  private readonly sandbox;
232
+ /** A decision resumes its waiting run in the decision's transaction. */
233
+ private readonly resumes;
229
234
  constructor(db: KernelDb, config: Pick<KernelConfigService, "get">, eventLog: Pick<EventLog, "appendIn" | "notify">, events: Pick<KernelEvents, "emit">, txHost: TransactionHost, schedules: Pick<ScheduledTaskStore, "addGrant">,
230
235
  /** A trial approval is kept only for a tool the task may hold as a grant (its agent's tools). */
231
236
  registry: Pick<ToolRegistry, "forAgent">,
@@ -234,7 +239,9 @@ export declare class ApprovalsService {
234
239
  /** One decision of a network ask at a time, across replicas (the op is sent before the transaction). */
235
240
  lockService: LockService,
236
241
  /** A trial run's 「这次允许」 becomes a task grant only within the org's egress. */
237
- sandbox: Pick<SandboxService, "ceilingLimits">);
242
+ sandbox: Pick<SandboxService, "ceilingLimits">,
243
+ /** A decision resumes its waiting run in the decision's transaction. */
244
+ resumes: Pick<RunResume, "resumeIn">);
238
245
  /**
239
246
  * 新建 pending 审批(`expires_at` = 现在 + `tools.approvalTimeoutHours`)并写 `approval_requested`,同一事务。
240
247
  * 按 (run_id, call_id, attempt) 幂等:已存在则返回已有记录(不覆盖),事件按 `approval_requested:<approvalId>` 幂等。
@@ -346,13 +353,25 @@ export declare class ApprovalsService {
346
353
  * active runs `authorAccountId` started, in the conversation and in its sub-agents'
347
354
  * conversations at any depth (their asks are shown in the root). Each becomes `skipped` with
348
355
  * `approval_decided{skipped}` by the author; access stays blocked. Returns the skipped records and
349
- * the highest seq written per conversation; the caller notifies and emits
350
- * `approval.decided{skipped}` after the commit, so the runs resume.
356
+ * the highest seq written per conversation; the caller resumes their waiting runs in this
357
+ * transaction once every skipped row is locked (`ConversationsService.skipWaitsIn`), and notifies
358
+ * and emits the domain event `approval.decided{skipped}` after the commit (its IM card update is
359
+ * owed by the append, transactional outbox spec §8.1).
351
360
  */
352
361
  skipNetworkIn(conversationId: string, authorAccountId: string): Promise<{
353
362
  records: ApprovalRecord[];
354
363
  seqs: Map<string, number>;
355
364
  }>;
365
+ /**
366
+ * Locks the conversation row of approval `id` (`SELECT … FOR UPDATE`; re-entrant with the lock
367
+ * `EventLog.appendIn` takes later). Every write to an approval row takes its conversation's lock
368
+ * first, then the approval row, then the run row, as the stop of a waiting run
369
+ * (`Waits.abortWaitingIn`) and the questions do, so no two of them can deadlock. False: no such
370
+ * approval (in `orgId`).
371
+ */
372
+ private lockConversationOf;
373
+ /** Locks, in id order, the conversations `ids` selects; returns their ids. */
374
+ private lockConversations;
356
375
  /** `conversationId` and its sub-agents' conversations at any depth (at most 16 levels). */
357
376
  private subtreeOf;
358
377
  /**