@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
@@ -0,0 +1,86 @@
1
+ import { type SandboxTier } from "@capybara-hq/plugin-sdk";
2
+ import { z } from "zod";
3
+ import type { KernelConfig } from "../config/schema.js";
4
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
5
+ import type { KernelLogger } from "../log.js";
6
+ /**
7
+ * The platform's sandbox tiers (sandbox dev tiers spec §4.1): `max`, `default` and
8
+ * `allowUnsandboxed`. Each field is read env > config file (or Nacos) > the super admin's choice in
9
+ * the admin console (deployment setting {@link PLATFORM_TIERS_SETTING}) > {@link PLATFORM_TIER_DEFAULTS}.
10
+ * A field set in the startup config is read-only in the admin console. Read live from PG at every
11
+ * use, so a change applies on every replica at once.
12
+ */
13
+ /** The `deployment_settings` key of the super admin's choice. */
14
+ export declare const PLATFORM_TIERS_SETTING = "sandboxTiers";
15
+ /** The fields, in display order. */
16
+ export declare const PLATFORM_TIER_FIELDS: readonly ["max", "default", "allowUnsandboxed"];
17
+ export type PlatformTierField = (typeof PLATFORM_TIER_FIELDS)[number];
18
+ /** The stored choice: each field absent follows the default. */
19
+ export declare const PlatformTiersStoredSchema: z.ZodObject<{
20
+ max: z.ZodOptional<z.ZodEnum<{
21
+ dev: "dev";
22
+ open: "open";
23
+ strict: "strict";
24
+ }>>;
25
+ default: z.ZodOptional<z.ZodEnum<{
26
+ dev: "dev";
27
+ open: "open";
28
+ strict: "strict";
29
+ }>>;
30
+ allowUnsandboxed: z.ZodOptional<z.ZodBoolean>;
31
+ }, z.core.$strict>;
32
+ export type PlatformTiersStored = z.infer<typeof PlatformTiersStoredSchema>;
33
+ /**
34
+ * A change of the stored choice (`PUT /api/v1/admin/settings` `sandboxTiers`): each given field
35
+ * replaces the stored one, null clears it (back to the default); at least one field.
36
+ */
37
+ export declare const PLATFORM_TIERS_PATCH: z.ZodObject<{
38
+ max: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
39
+ dev: "dev";
40
+ open: "open";
41
+ strict: "strict";
42
+ }>>>;
43
+ default: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
44
+ dev: "dev";
45
+ open: "open";
46
+ strict: "strict";
47
+ }>>>;
48
+ allowUnsandboxed: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
49
+ }, z.core.$strict>;
50
+ export type PlatformTiersPatch = z.infer<typeof PLATFORM_TIERS_PATCH>;
51
+ /** The platform's effective tiers. */
52
+ export interface PlatformTiers {
53
+ max: SandboxTier;
54
+ /** At most {@link max}. */
55
+ default: SandboxTier;
56
+ allowUnsandboxed: boolean;
57
+ }
58
+ /** What a field falls back to when neither the startup config nor the admin console sets it. */
59
+ export declare const PLATFORM_TIER_DEFAULTS: Readonly<PlatformTiers>;
60
+ /** Where a field's effective value comes from. */
61
+ export type PlatformTierSource = "env" | "file" | "nacos" | "db" | "default";
62
+ /** The admin console's view: the effective values and where each comes from. */
63
+ export interface PlatformTiersView extends PlatformTiers {
64
+ sources: Record<PlatformTierField, PlatformTierSource>;
65
+ }
66
+ /** The startup config's `sandbox.tiers` (each field undefined when not set there). */
67
+ export type ConfigTiers = KernelConfig["sandbox"]["tiers"];
68
+ /**
69
+ * Parses the stored choice: missing -> `{}`; invalid -> `{ max: "strict" }`, narrower, never
70
+ * wider (the same rule an org's invalid `sandboxTier` follows), with a warning on `log`.
71
+ */
72
+ export declare function parseStoredPlatformTiers(value: unknown, log?: Pick<KernelLogger, "warn">): PlatformTiersStored;
73
+ /** The default the layers choose before it is cut to the max. */
74
+ export declare const chosenPlatformDefault: (config: ConfigTiers, stored: PlatformTiersStored) => SandboxTier;
75
+ /** The effective tiers of the startup config over the stored choice; the default is cut to the max. */
76
+ export declare function resolvePlatformTiers(config: ConfigTiers, stored: PlatformTiersStored): PlatformTiers;
77
+ /** Whether `next` reaches further than `prev` anywhere: a higher max or default, or unsandboxed allowed. */
78
+ export declare function platformTiersWiden(prev: PlatformTiers, next: PlatformTiers): boolean;
79
+ /** The stored choice with `patch` applied (null clears a field). */
80
+ export declare function applyPlatformTiersPatch(stored: PlatformTiersStored, patch: PlatformTiersPatch): PlatformTiersStored;
81
+ /** The stored choice, read live (in the caller's transaction when there is one). */
82
+ export declare function readStoredPlatformTiers(db: KernelSchemaDb, log?: Pick<KernelLogger, "warn">): Promise<PlatformTiersStored>;
83
+ /** The platform's effective tiers, read live: see {@link resolvePlatformTiers}. */
84
+ export declare function readPlatformTiers(db: KernelSchemaDb, config: {
85
+ get(): Pick<KernelConfig, "sandbox">;
86
+ }, log?: Pick<KernelLogger, "warn">): Promise<PlatformTiers>;
@@ -0,0 +1,97 @@
1
+ import { minTier, tierAtMost } from "@capybara-hq/plugin-sdk";
2
+ import { z } from "zod";
3
+ /**
4
+ * The platform's sandbox tiers (sandbox dev tiers spec §4.1): `max`, `default` and
5
+ * `allowUnsandboxed`. Each field is read env > config file (or Nacos) > the super admin's choice in
6
+ * the admin console (deployment setting {@link PLATFORM_TIERS_SETTING}) > {@link PLATFORM_TIER_DEFAULTS}.
7
+ * A field set in the startup config is read-only in the admin console. Read live from PG at every
8
+ * use, so a change applies on every replica at once.
9
+ */
10
+ /** The `deployment_settings` key of the super admin's choice. */
11
+ export const PLATFORM_TIERS_SETTING = "sandboxTiers";
12
+ /** The fields, in display order. */
13
+ export const PLATFORM_TIER_FIELDS = ["max", "default", "allowUnsandboxed"];
14
+ const Tier = z.enum(["strict", "dev", "open"]);
15
+ /** The stored choice: each field absent follows the default. */
16
+ export const PlatformTiersStoredSchema = z
17
+ .object({
18
+ max: Tier.optional(),
19
+ default: Tier.optional(),
20
+ allowUnsandboxed: z.boolean().optional(),
21
+ })
22
+ .strict();
23
+ /**
24
+ * A change of the stored choice (`PUT /api/v1/admin/settings` `sandboxTiers`): each given field
25
+ * replaces the stored one, null clears it (back to the default); at least one field.
26
+ */
27
+ export const PLATFORM_TIERS_PATCH = z
28
+ .object({
29
+ max: Tier.nullable().optional(),
30
+ default: Tier.nullable().optional(),
31
+ allowUnsandboxed: z.boolean().nullable().optional(),
32
+ })
33
+ .strict()
34
+ .refine((o) => Object.values(o).some((v) => v !== undefined), {
35
+ message: "give max, default, allowUnsandboxed or several",
36
+ });
37
+ /** What a field falls back to when neither the startup config nor the admin console sets it. */
38
+ export const PLATFORM_TIER_DEFAULTS = Object.freeze({
39
+ max: "dev",
40
+ default: "dev",
41
+ allowUnsandboxed: true,
42
+ });
43
+ /**
44
+ * Parses the stored choice: missing -> `{}`; invalid -> `{ max: "strict" }`, narrower, never
45
+ * wider (the same rule an org's invalid `sandboxTier` follows), with a warning on `log`.
46
+ */
47
+ export function parseStoredPlatformTiers(value, log) {
48
+ if (value === undefined)
49
+ return {};
50
+ const parsed = PlatformTiersStoredSchema.safeParse(value);
51
+ if (parsed.success)
52
+ return parsed.data;
53
+ log?.warn("invalid deployment setting; it reads as max strict", { key: PLATFORM_TIERS_SETTING });
54
+ return { max: "strict" };
55
+ }
56
+ /** The default the layers choose before it is cut to the max. */
57
+ export const chosenPlatformDefault = (config, stored) => config.default ?? stored.default ?? PLATFORM_TIER_DEFAULTS.default;
58
+ /** The effective tiers of the startup config over the stored choice; the default is cut to the max. */
59
+ export function resolvePlatformTiers(config, stored) {
60
+ const max = config.max ?? stored.max ?? PLATFORM_TIER_DEFAULTS.max;
61
+ return {
62
+ max,
63
+ default: minTier(chosenPlatformDefault(config, stored), max),
64
+ allowUnsandboxed: config.allowUnsandboxed ?? stored.allowUnsandboxed ?? PLATFORM_TIER_DEFAULTS.allowUnsandboxed,
65
+ };
66
+ }
67
+ /** Whether `next` reaches further than `prev` anywhere: a higher max or default, or unsandboxed allowed. */
68
+ export function platformTiersWiden(prev, next) {
69
+ return (!tierAtMost(next.max, prev.max) ||
70
+ !tierAtMost(next.default, prev.default) ||
71
+ (next.allowUnsandboxed && !prev.allowUnsandboxed));
72
+ }
73
+ /** The stored choice with `patch` applied (null clears a field). */
74
+ export function applyPlatformTiersPatch(stored, patch) {
75
+ const out = { ...stored };
76
+ for (const f of PLATFORM_TIER_FIELDS) {
77
+ const v = patch[f];
78
+ if (v === null)
79
+ delete out[f];
80
+ else if (v !== undefined)
81
+ out[f] = v;
82
+ }
83
+ return out;
84
+ }
85
+ /** The stored choice, read live (in the caller's transaction when there is one). */
86
+ export async function readStoredPlatformTiers(db, log) {
87
+ const row = await db
88
+ .selectFrom("deployment_settings")
89
+ .select("value")
90
+ .where("key", "=", PLATFORM_TIERS_SETTING)
91
+ .executeTakeFirst();
92
+ return parseStoredPlatformTiers(row?.value, log);
93
+ }
94
+ /** The platform's effective tiers, read live: see {@link resolvePlatformTiers}. */
95
+ export async function readPlatformTiers(db, config, log) {
96
+ return resolvePlatformTiers(config.get().sandbox.tiers, await readStoredPlatformTiers(db, log));
97
+ }
@@ -2,7 +2,7 @@ import { type ExecResult, type SandboxActor, type SandboxHandle, type SandboxLim
2
2
  import type { Redis } from "ioredis";
3
3
  import { type Selectable } from "kysely";
4
4
  import { BackgroundTaskStore } from "../background/store.js";
5
- import { type KernelConfig } from "../config/schema.js";
5
+ import type { KernelConfig } from "../config/schema.js";
6
6
  import type { DeviceInstancesTable } from "../db/kernel-schema.js";
7
7
  import { ApiError } from "../errors.js";
8
8
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
@@ -106,6 +106,19 @@ export declare const deviceNotFound: () => ApiError;
106
106
  * owner's: `capybara-device logout`, then `login` into the device, recreates it (ruling R24).
107
107
  */
108
108
  export declare const deviceFilesLost: (local?: boolean) => ApiError;
109
+ /**
110
+ * A cloud sandbox provider refused to make (or move) an instance with open egress, the open tier
111
+ * (409; sandbox dev tiers spec §5.3): above all a provider set to refuse it (sandbox-docker /
112
+ * sandbox-e2b `openEgress: "off"`). Details `[{ tier }]`. The provider's own error goes only to
113
+ * the log: it may name the provider's settings.
114
+ */
115
+ export declare const sandboxTierRefused: (tier: SandboxTier) => ApiError;
116
+ /**
117
+ * A cloud sandbox provider failed to make a device's file system or instance for another reason
118
+ * (502): often its deployment config (image, runtime, network), sometimes a passing fault. The
119
+ * provider's own error goes only to the log.
120
+ */
121
+ export declare const sandboxCreateFailed: () => ApiError;
109
122
  /**
110
123
  * The row still records the instance `gone` (same handle and same `created_at`). Handles compare
111
124
  * canonically: a jsonb round trip reorders a multi-key handle's keys (final review m9).
@@ -117,11 +130,11 @@ export declare const isInstance: (row: Pick<Row, "handle" | "created_at">, gone:
117
130
  * run time limit (Ruling M20);
118
131
  * - `sandbox.tiers.max` is open but the current provider plugin sets `openEgress: "off"`: every
119
132
  * creation of an open-tier instance is refused.
120
- * With both time limits at their defaults (900 < 7200, a known trade-off) it is only info; warn
121
- * when an operator changed one and they still disagree.
133
+ * No run duration limit (`runtime.maxRunSeconds` 0, the default) cuts no command, so there is
134
+ * nothing to tell.
122
135
  */
123
136
  export declare function sandboxConfigWarnings(cfg: Pick<KernelConfig, "runtime" | "sandbox" | "extensionConfig">, providerPlugin?: string): {
124
- level: "info" | "warn";
137
+ level: "warn";
125
138
  message: string;
126
139
  }[];
127
140
  /** No sandbox provider (503). */
@@ -465,7 +478,7 @@ export declare class SandboxService {
465
478
  /**
466
479
  * Under the exclusive lease, right after a resume or a (re)build: kills the sessions of the
467
480
  * background tasks marked lost while their process may live on (`kill_pending`), in one exec of
468
- * `pkill -KILL -s <sid>`, then clears their flags. Tasks of another instance have no process
481
+ * `pkill -KILL -s <sid>` (`-g` on macOS), then clears their flags. Tasks of another instance have no process
469
482
  * here and are only cleared. A gone instance and an abort are thrown; another failure is warned
470
483
  * and leaves the flags of this instance's tasks for the next resume.
471
484
  */
@@ -11,8 +11,8 @@ import { randomUUID } from "node:crypto";
11
11
  import { compareTiers, isFileSystemGoneError, isSandboxGoneError, rootsOf, } from "@capybara-hq/plugin-sdk";
12
12
  import { Inject, Injectable } from "@nestjs/common";
13
13
  import { sql, } from "kysely";
14
+ import { SESSION_MODE_SCRIPT } from "../background/launch.js";
14
15
  import { BackgroundTaskStore } from "../background/store.js";
15
- import { DEFAULT_MAX_COMMAND_SECONDS, DEFAULT_MAX_RUN_SECONDS, } from "../config/schema.js";
16
16
  import { ApiError } from "../errors.js";
17
17
  import { readSandboxLimitOverride } from "../identity/settings.js";
18
18
  import { KernelConfigService } from "../infra/config/kernel-config.service.js";
@@ -22,8 +22,9 @@ import { effectiveSandboxLimits, readOrgSettings } from "../orgs/settings.servic
22
22
  import { appendMissingGrant, TIER_OPEN_GRANT_KEY, UNATTENDED_OPEN_TIER_ERROR, unattendedTierOf, } from "../schedules/unattended.js";
23
23
  import { canonicalJson } from "../tools/canonical-json.js";
24
24
  import { SandboxLeases, SandboxLockLostError, sandboxBusy, } from "./leases.js";
25
- import { LOCAL_PROVIDER, providerForDevice, SandboxProviders, } from "./providers.js";
26
- import { appliedOf, effectiveSandboxPolicy, instanceLimitsJson, limitsForTier, readInstanceLimits, sameApplied, } from "./tiers.js";
25
+ import { isInternalProvider, LOCAL_PROVIDER, providerForDevice, SandboxProviders, } from "./providers.js";
26
+ import { appliedOf, effectiveSandboxPolicy, instanceLimitsJson, limitsForTier, readDeploymentSandboxPolicy, readInstanceLimits, sameApplied, } from "./tiers.js";
27
+ import { SandboxProviderUnreachableError } from "./unreachable.js";
27
28
  // Moved to leases.ts (phase C); re-exported for existing importers
28
29
  export { SANDBOX_LOCK_PREFIX, SANDBOX_LOCK_TTL_MS, SandboxLockLostError, sandboxBusy, } from "./leases.js";
29
30
  /** Margin (ms) of a holder record's TTL beyond the longest command: by then the in-sandbox limit has ended its commands. */
@@ -57,6 +58,46 @@ export const deviceNotFound = () => new ApiError("device.not_found", "This devic
57
58
  export const deviceFilesLost = (local = false) => local
58
59
  ? new ApiError("device.files_lost", "The local device's directory is gone from its machine; run capybara-device logout, then capybara-device login into this device, on that machine to recreate it.", [{ source: "local" }])
59
60
  : new ApiError("device.files_lost", "The device's files are gone; reset its files from the devices page to start with an empty workspace.");
61
+ /**
62
+ * A cloud sandbox provider refused to make (or move) an instance with open egress, the open tier
63
+ * (409; sandbox dev tiers spec §5.3): above all a provider set to refuse it (sandbox-docker /
64
+ * sandbox-e2b `openEgress: "off"`). Details `[{ tier }]`. The provider's own error goes only to
65
+ * the log: it may name the provider's settings.
66
+ */
67
+ export const sandboxTierRefused = (tier) => new ApiError("sandbox.tier_refused", `The cloud sandbox can't run this device at the ${tier} tier: the sandbox provider refused to create it there. An org admin can lower the tier on the device page or in the org's sandbox settings, or a super admin can allow it in the sandbox extension's settings.`, [{ tier }]);
68
+ /**
69
+ * A cloud sandbox provider failed to make a device's file system or instance for another reason
70
+ * (502): often its deployment config (image, runtime, network), sometimes a passing fault. The
71
+ * provider's own error goes only to the log.
72
+ */
73
+ export const sandboxCreateFailed = () => new ApiError("sandbox.create_failed", "The cloud sandbox provider could not create this device's sandbox. Retry; if it keeps failing, a super admin should check the sandbox extension's settings.");
74
+ /**
75
+ * What a failed `createFileSystem` / `create` / `update` of a cloud provider becomes: an abort,
76
+ * a gone error, an unreachable provider and a kernel error stay as they are (their callers and the
77
+ * tools handle them); any other error is logged (warn, redacted) and replaced by
78
+ * {@link sandboxTierRefused} when `limits` ask for open egress (the only egress a provider's
79
+ * config refuses), else (a create) by {@link sandboxCreateFailed}; a failed `update` below open
80
+ * stays as it is. A kernel-internal provider (`@local`) keeps its own errors.
81
+ */
82
+ function providerRefusal(log, err, facts, limits, signal) {
83
+ if (signal.aborted ||
84
+ isInternalProvider(facts.provider) ||
85
+ isSandboxGoneError(err) ||
86
+ isFileSystemGoneError(err) ||
87
+ err instanceof SandboxProviderUnreachableError ||
88
+ err instanceof ApiError) {
89
+ return err;
90
+ }
91
+ if (limits.egress === "open") {
92
+ // spec §5.3: a provider set to refuse open egress (`openEgress: "off"`) fails here
93
+ log.warn("the sandbox provider refused an instance in the open tier; check its openEgress setting or lower sandbox.tiers.max", { ...facts, tier: limits.tier, err });
94
+ return sandboxTierRefused(limits.tier);
95
+ }
96
+ if (facts.op === "update")
97
+ return err;
98
+ log.warn("the sandbox provider failed to create a device's sandbox", { ...facts, err });
99
+ return sandboxCreateFailed();
100
+ }
60
101
  /**
61
102
  * The row still records the instance `gone` (same handle and same `created_at`). Handles compare
62
103
  * canonically: a jsonb round trip reorders a multi-key handle's keys (final review m9).
@@ -86,8 +127,8 @@ function tagged(impl, tag, actor, before) {
86
127
  * run time limit (Ruling M20);
87
128
  * - `sandbox.tiers.max` is open but the current provider plugin sets `openEgress: "off"`: every
88
129
  * creation of an open-tier instance is refused.
89
- * With both time limits at their defaults (900 < 7200, a known trade-off) it is only info; warn
90
- * when an operator changed one and they still disagree.
130
+ * No run duration limit (`runtime.maxRunSeconds` 0, the default) cuts no command, so there is
131
+ * nothing to tell.
91
132
  */
92
133
  export function sandboxConfigWarnings(cfg, providerPlugin) {
93
134
  const out = [];
@@ -103,10 +144,9 @@ export function sandboxConfigWarnings(cfg, providerPlugin) {
103
144
  }
104
145
  const run = cfg.runtime.maxRunSeconds;
105
146
  const cmd = cfg.sandbox.limits.maxCommandSeconds;
106
- if (run < cmd) {
107
- const defaults = run === DEFAULT_MAX_RUN_SECONDS && cmd === DEFAULT_MAX_COMMAND_SECONDS;
147
+ if (run > 0 && run < cmd) {
108
148
  out.push({
109
- level: defaults ? "info" : "warn",
149
+ level: "warn",
110
150
  message: `runtime.maxRunSeconds (${run}) is less than sandbox.limits.maxCommandSeconds (${cmd}): sandbox commands are limited to the time left in the run, so commands longer than about ${run} seconds cannot finish; raise runtime.maxRunSeconds to at least ${cmd} if agents need long commands`,
111
151
  });
112
152
  }
@@ -278,12 +318,12 @@ let SandboxService = class SandboxService {
278
318
  * default.
279
319
  */
280
320
  async devicePolicy(device) {
281
- const cfg = this.config.get().sandbox;
282
- const [settings, tier] = await Promise.all([
321
+ const [deployment, settings, tier] = await Promise.all([
322
+ readDeploymentSandboxPolicy(this.db.db, this.config),
283
323
  readOrgSettings(this.db.db, device.orgId),
284
324
  device.deviceId === undefined ? null : this.deviceTier(device.orgId, device.deviceId),
285
325
  ]);
286
- return effectiveSandboxPolicy(cfg, settings, tier);
326
+ return effectiveSandboxPolicy(deployment, settings, tier);
287
327
  }
288
328
  /** The device's own tier (`devices.tier`); null when it follows the org or is not there. */
289
329
  async deviceTier(orgId, deviceId) {
@@ -701,6 +741,17 @@ let SandboxService = class SandboxService {
701
741
  if (intent)
702
742
  await this.clearIntent(env, token);
703
743
  intent = false;
744
+ // after a wait, the limits again under the lease: a tier narrowed just before the
745
+ // holder we waited for let go is applied now (asShared checks `ready` with them)
746
+ if (waited) {
747
+ try {
748
+ want = await wantNow();
749
+ }
750
+ catch (err) {
751
+ await lease.release();
752
+ throw err;
753
+ }
754
+ }
704
755
  const e = await this.asShared(ctx, p, lease, ready, signal);
705
756
  if (e)
706
757
  return e;
@@ -1088,7 +1139,7 @@ let SandboxService = class SandboxService {
1088
1139
  /**
1089
1140
  * Under the exclusive lease, right after a resume or a (re)build: kills the sessions of the
1090
1141
  * background tasks marked lost while their process may live on (`kill_pending`), in one exec of
1091
- * `pkill -KILL -s <sid>`, then clears their flags. Tasks of another instance have no process
1142
+ * `pkill -KILL -s <sid>` (`-g` on macOS), then clears their flags. Tasks of another instance have no process
1092
1143
  * here and are only cleared. A gone instance and an abort are thrown; another failure is warned
1093
1144
  * and leaves the flags of this instance's tasks for the next resume.
1094
1145
  */
@@ -1100,10 +1151,12 @@ let SandboxService = class SandboxService {
1100
1151
  let cleared = rows;
1101
1152
  if (mine.length > 0) {
1102
1153
  // pkill exits 1 when the session has no process left: only a higher status is a failure
1103
- const command = `rc=0; for sid in ${mine.map((r) => r.sid).join(" ")}; do pkill -KILL -s "$sid"; c=$?; [ "$c" -le 1 ] || rc=$c; done; exit $rc`;
1154
+ const command = `${SESSION_MODE_SCRIPT}\nrc=0; for sid in ${mine.map((r) => r.sid).join(" ")}; do tk_kill KILL "$sid"; c=$?; [ "$c" -le 1 ] || rc=$c; done; exit $rc`;
1104
1155
  try {
1105
1156
  const r = await p.impl.exec(handle, {
1106
1157
  command,
1158
+ // tk_kill's control files are relative to the workspace root (macOS devices)
1159
+ cwd: rootsOf(handle).workspace,
1107
1160
  timeoutMs: PENDING_KILL_TIMEOUT_MS,
1108
1161
  maxOutputBytes: 4096,
1109
1162
  signal,
@@ -1366,7 +1419,12 @@ let SandboxService = class SandboxService {
1366
1419
  ? row.fs_handle
1367
1420
  : null;
1368
1421
  if (fs === null) {
1369
- fs = await p.impl.createFileSystem({ name, limits, signal });
1422
+ try {
1423
+ fs = await p.impl.createFileSystem({ name, limits, signal });
1424
+ }
1425
+ catch (err) {
1426
+ throw providerRefusal(this.log, err, { deviceId: id, provider: p.plugin, op: "createFileSystem" }, limits, signal);
1427
+ }
1370
1428
  // A lost lease fails this write and leaves the file system alone: createFileSystem returns
1371
1429
  // the existing one for a name, so the new holder may already use it, files and all.
1372
1430
  await this.fenced(held, id, fence, {
@@ -1389,11 +1447,7 @@ let SandboxService = class SandboxService {
1389
1447
  }
1390
1448
  catch (err) {
1391
1449
  if (!isFileSystemGoneError(err)) {
1392
- if (limits.egress === "open" && !signal.aborted) {
1393
- // spec §5.3: a provider set to refuse open egress (`openEgress: "off"`) fails here
1394
- this.log.warn("the sandbox provider did not create an instance in the open tier; check its openEgress setting or lower sandbox.tiers.max", { deviceId: id, provider: p.plugin, err });
1395
- }
1396
- throw err;
1450
+ throw providerRefusal(this.log, err, { deviceId: id, provider: p.plugin, op: "create" }, limits, signal);
1397
1451
  }
1398
1452
  this.log.warn("device file system is gone at the provider; its files are lost", {
1399
1453
  deviceId: id,
@@ -1465,7 +1519,8 @@ let SandboxService = class SandboxService {
1465
1519
  // the instance may run with half-applied limits and cannot be recreated here: stop it,
1466
1520
  // so the next use resumes it and applies them again
1467
1521
  await this.quarantine(held, id, handle, p, fence);
1468
- throw err;
1522
+ // a provider bound to its file system that refuses the new tier (E2B, `openEgress: "off"`)
1523
+ throw providerRefusal(this.log, err, { deviceId: id, provider: p.plugin, op: "update" }, want, signal);
1469
1524
  }
1470
1525
  else {
1471
1526
  // a new instance gets the limits at create: recreate rather than trust a failed update
@@ -1,16 +1,28 @@
1
1
  import { type SandboxTier } from "@capybara-hq/plugin-sdk";
2
2
  import type { KernelConfig } from "../config/schema.js";
3
+ import type { KernelSchemaDb } from "../db/kernel-schema.js";
3
4
  import type { OrgSettings } from "../orgs/settings.service.js";
5
+ import { type PlatformTiers } from "./platform-tiers.js";
4
6
  /**
5
7
  * Sandbox tiers (sandbox dev tiers spec §4): who may reach which tier, and the network each tier
6
8
  * gets. One function, {@link effectiveSandboxPolicy}, combines the platform's ceiling and default
7
9
  * (`sandbox.tiers`), the org's (`sandboxTier`) and the device's own choice.
8
10
  */
9
- /** The deployment config the policy reads (`sandbox.limits` network fields and `sandbox.tiers`). */
11
+ /**
12
+ * What the policy reads of the platform: the `sandbox.limits` network fields and the effective
13
+ * platform tiers (`readPlatformTiers`: the startup config over the admin console's choice).
14
+ */
10
15
  export interface DeploymentSandboxPolicy {
11
16
  limits: Pick<KernelConfig["sandbox"]["limits"], "egress" | "allowHosts" | "devHosts">;
12
- tiers: KernelConfig["sandbox"]["tiers"];
17
+ tiers: PlatformTiers;
13
18
  }
19
+ /**
20
+ * The platform side of the policy, read live: the `sandbox.limits` network fields of the config
21
+ * and the effective platform tiers (in the caller's transaction when there is one).
22
+ */
23
+ export declare function readDeploymentSandboxPolicy(db: KernelSchemaDb, config: {
24
+ get(): Pick<KernelConfig, "sandbox">;
25
+ }): Promise<DeploymentSandboxPolicy>;
14
26
  /** The org settings the policy reads. */
15
27
  export type OrgSandboxSettings = Pick<OrgSettings, "sandboxEgress" | "sandboxTier">;
16
28
  /**
@@ -1,4 +1,12 @@
1
1
  import { isSandboxTier, minTier, tierAtMost } from "@capybara-hq/plugin-sdk";
2
+ import { readPlatformTiers } from "./platform-tiers.js";
3
+ /**
4
+ * The platform side of the policy, read live: the `sandbox.limits` network fields of the config
5
+ * and the effective platform tiers (in the caller's transaction when there is one).
6
+ */
7
+ export async function readDeploymentSandboxPolicy(db, config) {
8
+ return { limits: config.get().sandbox.limits, tiers: await readPlatformTiers(db, config) };
9
+ }
2
10
  /** The org's base list `L`: see {@link BaseSandboxEgress}. */
3
11
  export function baseSandboxEgress(deployment, org) {
4
12
  if (deployment.egress === "none" || org?.mode === "none")
@@ -0,0 +1,134 @@
1
+ import type { ScheduledTask, ScheduleMode, SchedulePauseReason, ScheduleStatus, ScheduleTrigger, TaskRun, TaskRunSkipReason, TaskRunStatus } from "./types.js";
2
+ /** Planned occurrences reach this far ahead of now (spec §5.2, "about six weeks"). */
3
+ export declare const CALENDAR_HORIZON_DAYS = 42;
4
+ /** The longest range one request may ask for: a 6-week month grid plus a DST hour (§5.1). */
5
+ export declare const CALENDAR_RANGE_DAYS = 43;
6
+ /** The longest range with `taskId` (the panel's one-day list, §5.4). */
7
+ export declare const CALENDAR_TASK_RANGE_DAYS = 2;
8
+ /** A task-day with more items than this is collapsed into one summary (§5.4). */
9
+ export declare const CALENDAR_COLLAPSE_OVER = 6;
10
+ /**
11
+ * Ticks taken per request after the first local day, across all tasks (§5.5). One tick costs
12
+ * about 150-340 µs (cron's `getNextDateFrom`, measured 2026-11-12 with a ticker built once per
13
+ * task), so 3,000 keeps a worst-case request near one second.
14
+ */
15
+ export declare const CALENDAR_TICK_BUDGET = 3000;
16
+ /** Entries (`items` + `collapsed`) per request after the first local day (§5.5). */
17
+ export declare const CALENDAR_ITEM_CAP = 3000;
18
+ /** Run rows read per request after the first local day (§5.5). */
19
+ export declare const CALENDAR_RUN_ROW_CAP = 20000;
20
+ /**
21
+ * The first requested local day is always returned whole, outside the limits above; only these
22
+ * safety caps stop it (then nothing is returned, `truncated`, `horizon` = `from`).
23
+ */
24
+ export declare const CALENDAR_FIRST_DAY_TICK_CAP = 10000;
25
+ export declare const CALENDAR_FIRST_DAY_RUN_CAP = 20000;
26
+ /** The limits of one assembly; tests lower them. */
27
+ export interface CalendarLimits {
28
+ horizonDays: number;
29
+ collapseOver: number;
30
+ /**
31
+ * Ticks taken (planned ticks in the range, the due tick included), after the first day. Not
32
+ * counted: each stream's one lookahead evaluation past its `until` and the doubling search of
33
+ * `scanStep` for a task already due, at most about 2 × tasks evaluations more.
34
+ */
35
+ tickBudget: number;
36
+ itemCap: number;
37
+ runRowCap: number;
38
+ firstDayTickCap: number;
39
+ firstDayRunCap: number;
40
+ }
41
+ export declare const CALENDAR_LIMITS: CalendarLimits;
42
+ /** How many run rows the service reads: enough to tell where either run cap cuts. */
43
+ export declare const calendarRunReadLimit: (limits: CalendarLimits) => number;
44
+ /** A task an entry of the answer names. */
45
+ export interface CalendarTask {
46
+ id: string;
47
+ name: string;
48
+ agentId: string;
49
+ accountId: string;
50
+ status: ScheduleStatus;
51
+ pauseReason: SchedulePauseReason | null;
52
+ trigger: ScheduleTrigger;
53
+ mode: ScheduleMode;
54
+ jitterSeconds: number;
55
+ nextRunAt: string | null;
56
+ }
57
+ export type CalendarItem = {
58
+ kind: "run";
59
+ taskId: string;
60
+ /** The task run's id. */
61
+ id: string;
62
+ scheduledFor: string;
63
+ firedAt: string | null;
64
+ status: TaskRunStatus;
65
+ skipReason: TaskRunSkipReason | null;
66
+ trial: boolean;
67
+ manual: boolean;
68
+ /** Null for another member's run (admin `all`, spec §15 Q3), as `error` and `missingGrants`. */
69
+ conversationId: string | null;
70
+ error: string | null;
71
+ missingGrants: number | null;
72
+ } | {
73
+ kind: "occurrence";
74
+ taskId: string;
75
+ /** The tick, before jitter. */
76
+ scheduledFor: string;
77
+ /** When it fires: the tick plus the task's jitter. */
78
+ at: string;
79
+ };
80
+ /** A task's items of one local day, summarized (§5.4). */
81
+ export interface CalendarCollapsed {
82
+ taskId: string;
83
+ /** YYYY-MM-DD in the request's `timeZone`. */
84
+ day: string;
85
+ count: number;
86
+ upcoming: number;
87
+ running: number;
88
+ completed: number;
89
+ failed: number;
90
+ skipped: number;
91
+ /** The earliest and latest `scheduledFor` of the day's items. */
92
+ first: string;
93
+ last: string;
94
+ }
95
+ export interface CalendarAnswer {
96
+ tasks: CalendarTask[];
97
+ items: CalendarItem[];
98
+ collapsed: CalendarCollapsed[];
99
+ /** Nothing is returned after it: 42 days ahead, or the cut. */
100
+ horizon: string;
101
+ /** A limit cut the range at a whole local day. */
102
+ truncated: boolean;
103
+ }
104
+ export interface CalendarInput {
105
+ /** The live tasks the viewer may see in this request. */
106
+ tasks: readonly ScheduledTask[];
107
+ /**
108
+ * Their runs with `scheduledFor` in `[from, to)`, trial included, ordered by `scheduledFor`, at
109
+ * most {@link calendarRunReadLimit} (rows past it are not read; the range is cut before them).
110
+ */
111
+ runs: readonly TaskRun[];
112
+ /** The viewer: another creator's runs lose `conversationId`, `error` and `missingGrants`. */
113
+ viewerAccountId: string;
114
+ from: Date;
115
+ to: Date;
116
+ /** The viewer's zone: local days for the collapse and the cut. */
117
+ timeZone: string;
118
+ now: Date;
119
+ /** False when the org turned scheduled tasks off: nothing would fire, so nothing is planned. */
120
+ plan: boolean;
121
+ /** The org's catch-up window, as the scanner reads it. */
122
+ catchUpWindowMinutes: number;
123
+ /** False for a `taskId` read: every item, never collapsed. */
124
+ collapse: boolean;
125
+ }
126
+ /** The calendar answer from the rows (spec §5.2–§5.5). */
127
+ export declare function assembleCalendar(input: CalendarInput, limits?: CalendarLimits): CalendarAnswer;
128
+ /** YYYY-MM-DD of the instant (epoch ms) in `timeZone`. */
129
+ export declare function dayKey(ms: number, timeZone: string): string;
130
+ /**
131
+ * The first instant (epoch ms) of the local day of `ms` in `timeZone`: midnight, or the end of a
132
+ * DST gap that swallows it. A day is at most 25 hours long, so 27 hours back is an earlier day.
133
+ */
134
+ export declare function startOfLocalDay(ms: number, timeZone: string): number;