@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.
- package/bootstrap.json +25 -25
- package/dist/admin/admin.controller.d.ts +3 -3
- package/dist/admin/admin.controller.js +6 -2
- package/dist/admin/admin.module.d.ts +2 -1
- package/dist/admin/admin.module.js +7 -3
- package/dist/admin/admin.schemas.d.ts +13 -0
- package/dist/admin/outbox-dead.controller.d.ts +23 -0
- package/dist/admin/outbox-dead.controller.js +62 -0
- package/dist/admin/outbox-dead.schemas.d.ts +8 -0
- package/dist/admin/outbox-dead.schemas.js +18 -0
- package/dist/admin/outbox-dead.service.d.ts +66 -0
- package/dist/admin/outbox-dead.service.js +179 -0
- package/dist/admin/overview.service.d.ts +40 -5
- package/dist/admin/overview.service.js +41 -7
- package/dist/agents/from-template.js +1 -1
- package/dist/background/finish.d.ts +5 -2
- package/dist/background/finish.js +14 -3
- package/dist/background/launch.d.ts +44 -5
- package/dist/background/launch.js +92 -9
- package/dist/background/session.d.ts +14 -2
- package/dist/background/session.js +26 -4
- package/dist/background/status.d.ts +1 -1
- package/dist/background/status.js +3 -3
- package/dist/background/stop.d.ts +0 -2
- package/dist/background/stop.js +1 -21
- package/dist/background/store.d.ts +8 -0
- package/dist/background/store.js +24 -0
- package/dist/catalog/admin-catalog.controller.d.ts +21 -2
- package/dist/catalog/admin-catalog.controller.js +45 -6
- package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
- package/dist/catalog/admin-catalog.schemas.js +8 -0
- package/dist/catalog/catalog-content.d.ts +5 -0
- package/dist/catalog/catalog-content.js +1 -0
- package/dist/catalog/catalog.module.d.ts +2 -1
- package/dist/catalog/catalog.module.js +5 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/platform-import-update.d.ts +50 -0
- package/dist/catalog/platform-import-update.js +116 -0
- package/dist/catalog/plugin-import.d.ts +36 -1
- package/dist/catalog/plugin-import.js +23 -1
- package/dist/catalog/plugin-upgrade.d.ts +18 -0
- package/dist/catalog/plugin-upgrade.js +19 -0
- package/dist/catalog/requests.service.js +2 -2
- package/dist/channels/channels.module.d.ts +5 -2
- package/dist/channels/channels.module.js +9 -2
- package/dist/channels/deliver-job.d.ts +25 -0
- package/dist/channels/deliver-job.js +25 -0
- package/dist/channels/inbound.d.ts +1 -1
- package/dist/channels/inbound.js +2 -15
- package/dist/channels/jobs.d.ts +28 -18
- package/dist/channels/jobs.js +63 -26
- package/dist/config/schema.d.ts +3 -6
- package/dist/config/schema.js +20 -27
- package/dist/conversations/agent-tree.d.ts +16 -0
- package/dist/conversations/agent-tree.js +36 -0
- package/dist/conversations/conversations.service.d.ts +44 -14
- package/dist/conversations/conversations.service.js +52 -38
- package/dist/conversations/event-log.d.ts +7 -10
- package/dist/conversations/event-log.js +24 -15
- package/dist/conversations/events.js +2 -1
- package/dist/conversations/queue.service.d.ts +8 -3
- package/dist/conversations/queue.service.js +37 -13
- package/dist/conversations/titles.d.ts +21 -8
- package/dist/conversations/titles.js +42 -15
- package/dist/db/kernel-schema.d.ts +26 -8
- package/dist/devices/devices.service.d.ts +12 -3
- package/dist/devices/devices.service.js +30 -9
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +15 -0
- package/dist/evals/run-replay.js +14 -8
- package/dist/extensions/extensions.module.js +3 -0
- package/dist/extensions/lifecycle.d.ts +6 -1
- package/dist/extensions/lifecycle.js +9 -2
- package/dist/extensions/usage-subscribers.d.ts +43 -0
- package/dist/extensions/usage-subscribers.js +108 -0
- package/dist/http/api.module.js +2 -0
- package/dist/identity/settings.d.ts +53 -3
- package/dist/identity/settings.js +92 -9
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/cli/kernel-cli.module.js +5 -0
- package/dist/infra/config/kernel-config.service.d.ts +7 -0
- package/dist/infra/config/kernel-config.service.js +20 -0
- package/dist/infra/events/kernel-events.d.ts +9 -35
- package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
- package/dist/infra/outbox/outbox-relay.module.js +18 -0
- package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
- package/dist/infra/outbox/outbox.dispatcher.js +310 -0
- package/dist/infra/outbox/outbox.module.d.ts +24 -0
- package/dist/infra/outbox/outbox.module.js +50 -0
- package/dist/infra/outbox/outbox.relay.d.ts +31 -0
- package/dist/infra/outbox/outbox.relay.js +76 -0
- package/dist/infra/outbox/outbox.service.d.ts +51 -0
- package/dist/infra/outbox/outbox.service.js +131 -0
- package/dist/infra/outbox/registry.d.ts +120 -0
- package/dist/infra/outbox/registry.js +108 -0
- package/dist/infra/queue/queue-names.d.ts +8 -0
- package/dist/infra/queue/queue-names.js +16 -0
- package/dist/kernel.module.js +3 -0
- package/dist/local-devices/device-policy.service.d.ts +4 -0
- package/dist/local-devices/device-policy.service.js +61 -3
- package/dist/local-devices/local-device-gateway.d.ts +4 -2
- package/dist/local-devices/local-device-gateway.js +39 -14
- package/dist/local-devices/machines.d.ts +25 -1
- package/dist/local-devices/machines.js +91 -15
- package/dist/local-devices/policy-summary.d.ts +44 -1
- package/dist/local-devices/policy-summary.js +69 -0
- package/dist/models/embeddings.d.ts +9 -9
- package/dist/models/embeddings.js +11 -18
- package/dist/models/models.module.js +3 -0
- package/dist/orgs/orgs.service.js +7 -0
- package/dist/orgs/settings.controller.js +2 -2
- package/dist/orgs/settings.service.d.ts +11 -5
- package/dist/orgs/settings.service.js +28 -14
- package/dist/overrides.d.ts +20 -3
- package/dist/overrides.js +10 -2
- package/dist/platform/exclusions.js +5 -0
- package/dist/platform/ops/admin.ops.js +50 -9
- package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
- package/dist/platform/ops/catalog-admin.ops.js +32 -4
- package/dist/platform/ops/org-settings.ops.js +3 -3
- package/dist/platform/ops/orgs.ops.js +1 -0
- package/dist/platform/ops/outbox.ops.d.ts +21 -0
- package/dist/platform/ops/outbox.ops.js +125 -0
- package/dist/platform/ops/platform.ops.js +1 -1
- package/dist/platform/ops/schedules.ops.js +40 -1
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +3 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +9 -4
- package/dist/runtime/langgraph-loop.js +13 -5
- package/dist/runtime/limits.d.ts +15 -0
- package/dist/runtime/limits.js +25 -0
- package/dist/runtime/loop.d.ts +8 -3
- package/dist/runtime/loop.js +2 -1
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -2
- package/dist/runtime/model-call.d.ts +14 -16
- package/dist/runtime/model-call.js +14 -24
- package/dist/runtime/queue-lost-jobs.d.ts +24 -0
- package/dist/runtime/queue-lost-jobs.js +51 -0
- package/dist/runtime/run-control.d.ts +42 -5
- package/dist/runtime/run-control.js +66 -12
- package/dist/runtime/run-finish.d.ts +39 -12
- package/dist/runtime/run-finish.js +97 -14
- package/dist/runtime/run-jobs.d.ts +95 -13
- package/dist/runtime/run-jobs.js +199 -28
- package/dist/runtime/run-orphan-check.job.d.ts +142 -0
- package/dist/runtime/run-orphan-check.job.js +339 -0
- package/dist/runtime/run-processors.d.ts +16 -9
- package/dist/runtime/run-processors.js +1 -1
- package/dist/runtime/run-queue.d.ts +64 -55
- package/dist/runtime/run-queue.js +147 -284
- package/dist/runtime/run-steps.d.ts +1 -2
- package/dist/runtime/run-steps.js +8 -11
- package/dist/runtime/run-worker.d.ts +6 -17
- package/dist/runtime/run-worker.js +32 -34
- package/dist/runtime/runtime.module.d.ts +2 -3
- package/dist/runtime/runtime.module.js +5 -6
- package/dist/runtime/step-log.d.ts +16 -4
- package/dist/runtime/step-log.js +26 -6
- package/dist/runtime/stream.controller.d.ts +2 -4
- package/dist/runtime/stream.controller.js +5 -11
- package/dist/runtime/subagents/child.js +1 -1
- package/dist/runtime/subagents/definition.d.ts +2 -1
- package/dist/runtime/subagents/definition.js +4 -2
- package/dist/runtime/subagents/deliveries.d.ts +6 -3
- package/dist/runtime/subagents/deliveries.js +16 -7
- package/dist/runtime/subagents/dispatch.d.ts +12 -12
- package/dist/runtime/subagents/dispatch.js +13 -38
- package/dist/runtime/subagents/messages.d.ts +5 -1
- package/dist/runtime/subagents/messages.js +11 -3
- package/dist/runtime/subagents/skill-fork.d.ts +3 -3
- package/dist/runtime/subagents/skill-fork.js +5 -5
- package/dist/runtime/subagents/subagents.service.js +1 -1
- package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
- package/dist/runtime/subagents/tree-jobs.js +86 -0
- package/dist/runtime/usage-report-jobs.d.ts +23 -48
- package/dist/runtime/usage-report-jobs.js +67 -119
- package/dist/runtime/usage-report.d.ts +28 -77
- package/dist/runtime/usage-report.js +35 -213
- package/dist/runtime/waits.d.ts +48 -198
- package/dist/runtime/waits.js +103 -805
- package/dist/sandbox/platform-tiers.d.ts +86 -0
- package/dist/sandbox/platform-tiers.js +97 -0
- package/dist/sandbox/sandbox.service.d.ts +18 -5
- package/dist/sandbox/sandbox.service.js +75 -20
- package/dist/sandbox/tiers.d.ts +14 -2
- package/dist/sandbox/tiers.js +8 -0
- package/dist/schedules/calendar.d.ts +134 -0
- package/dist/schedules/calendar.js +410 -0
- package/dist/schedules/fire.d.ts +1 -3
- package/dist/schedules/fire.js +5 -10
- package/dist/schedules/run-tracker.d.ts +0 -5
- package/dist/schedules/run-tracker.js +0 -13
- package/dist/schedules/scan.job.js +15 -20
- package/dist/schedules/schedule-math.d.ts +42 -0
- package/dist/schedules/schedule-math.js +52 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
- package/dist/schedules/scheduled-tasks.service.js +53 -0
- package/dist/schedules/schedules.controller.d.ts +5 -1
- package/dist/schedules/schedules.controller.js +15 -1
- package/dist/schedules/schedules.schemas.d.ts +35 -0
- package/dist/schedules/schedules.schemas.js +27 -0
- package/dist/schedules/store.d.ts +5 -0
- package/dist/schedules/store.js +20 -0
- package/dist/tools/approvals.service.d.ts +26 -7
- package/dist/tools/approvals.service.js +88 -10
- package/dist/tools/builtin-names.d.ts +5 -3
- package/dist/tools/builtin-names.js +12 -4
- package/dist/tools/builtins/builtin-tools.d.ts +1 -2
- package/dist/tools/builtins/builtin-tools.js +21 -17
- package/dist/tools/builtins/sandbox/bash.js +9 -3
- package/dist/tools/builtins/sandbox/common.d.ts +18 -1
- package/dist/tools/builtins/sandbox/common.js +14 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -1
- package/dist/tools/builtins/sandbox/index.js +4 -1
- package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
- package/dist/tools/builtins/sandbox/task-list.js +130 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
- package/dist/tools/builtins/sandbox/task-output.js +21 -3
- package/dist/tools/builtins/web/extract.d.ts +2 -4
- package/dist/tools/builtins/web/extract.js +2 -5
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/pipeline.js +1 -1
- package/dist/tools/questions.service.d.ts +15 -6
- package/dist/tools/questions.service.js +20 -8
- package/dist/tools/registry.d.ts +4 -2
- package/dist/tools/registry.js +4 -3
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0118_device_policy_ops.sql +6 -0
- package/src/migrations/0119_outbox.sql +22 -0
- 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 {
|
|
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
|
-
*
|
|
121
|
-
*
|
|
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: "
|
|
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
|
|
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
|
-
*
|
|
90
|
-
*
|
|
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:
|
|
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
|
|
282
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/sandbox/tiers.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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:
|
|
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
|
/**
|
package/dist/sandbox/tiers.js
CHANGED
|
@@ -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;
|