@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/admin-agents.d.ts +16 -3
- package/dist/agents/admin-agents.js +65 -7
- package/dist/agents/agents.schemas.d.ts +24 -6
- package/dist/agents/agents.service.d.ts +4 -3
- package/dist/agents/agents.service.js +39 -26
- package/dist/agents/definition.d.ts +44 -6
- package/dist/agents/definition.js +86 -18
- package/dist/agents/from-template.d.ts +6 -3
- package/dist/agents/from-template.js +57 -44
- package/dist/agents/org-templates.js +97 -41
- package/dist/agents/templates.d.ts +6 -0
- package/dist/agents/templates.js +15 -1
- package/dist/catalog/admin-catalog.controller.d.ts +2 -4
- package/dist/catalog/admin-catalog.controller.js +2 -6
- package/dist/catalog/catalog.module.js +3 -0
- package/dist/catalog/claude/report.d.ts +9 -2
- package/dist/catalog/claude/report.js +1 -0
- package/dist/catalog/claude/translate.js +1 -1
- package/dist/catalog/component-promotion.js +1 -2
- package/dist/catalog/component-state.d.ts +13 -1
- package/dist/catalog/component-state.js +14 -5
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/marketplaces.service.js +2 -1
- package/dist/catalog/org-catalog.controller.d.ts +13 -3
- package/dist/catalog/org-catalog.controller.js +26 -4
- package/dist/catalog/org-catalog.schemas.d.ts +13 -0
- package/dist/catalog/org-catalog.schemas.js +34 -0
- package/dist/catalog/org-plugin-uploads.controller.js +0 -1
- package/dist/catalog/org-plugin-uploads.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.js +0 -3
- package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
- package/dist/catalog/platform-components.d.ts +0 -1
- package/dist/catalog/platform-components.js +3 -8
- package/dist/catalog/plugin-import.d.ts +0 -8
- package/dist/catalog/plugin-import.js +4 -35
- package/dist/catalog/plugin-install.d.ts +2 -2
- package/dist/catalog/plugin-install.js +2 -2
- package/dist/catalog/plugin-package.d.ts +7 -0
- package/dist/catalog/plugin-package.js +27 -1
- package/dist/catalog/plugin-upgrade.d.ts +5 -6
- package/dist/catalog/plugin-upgrade.js +5 -23
- package/dist/catalog/requests.service.d.ts +66 -10
- package/dist/catalog/requests.service.js +196 -15
- package/dist/channels/actions.d.ts +2 -0
- package/dist/channels/actions.js +19 -4
- package/dist/channels/cards.d.ts +16 -2
- package/dist/channels/cards.js +40 -7
- package/dist/channels/channels.module.js +3 -0
- package/dist/channels/outbound.d.ts +22 -1
- package/dist/channels/outbound.js +116 -7
- package/dist/channels/phrases.d.ts +25 -0
- package/dist/channels/phrases.js +19 -0
- package/dist/channels/threads.d.ts +12 -0
- package/dist/channels/threads.js +34 -0
- package/dist/config/schema.d.ts +23 -1
- package/dist/config/schema.js +105 -5
- package/dist/conversations/conversations.service.d.ts +42 -9
- package/dist/conversations/conversations.service.js +57 -25
- package/dist/conversations/events.d.ts +106 -11
- package/dist/conversations/events.js +121 -18
- package/dist/conversations/message-context.d.ts +6 -3
- package/dist/conversations/message-context.js +14 -10
- package/dist/conversations/queue.d.ts +15 -2
- package/dist/conversations/queue.js +22 -3
- package/dist/conversations/queue.service.d.ts +12 -6
- package/dist/conversations/queue.service.js +36 -13
- package/dist/db/kernel-schema.d.ts +105 -3
- package/dist/devices/conversation-devices.d.ts +8 -1
- package/dist/devices/conversation-devices.js +8 -0
- package/dist/devices/devices.controller.d.ts +21 -4
- package/dist/devices/devices.controller.js +46 -8
- package/dist/devices/devices.schemas.d.ts +9 -1
- package/dist/devices/devices.schemas.js +11 -3
- package/dist/devices/devices.service.d.ts +176 -4
- package/dist/devices/devices.service.js +341 -6
- package/dist/devices/dto.d.ts +56 -3
- package/dist/devices/dto.js +51 -1
- package/dist/devices/public-suffix.d.ts +6 -0
- package/dist/devices/public-suffix.js +79 -0
- package/dist/errors.d.ts +46 -5
- package/dist/errors.js +46 -5
- package/dist/evals/reconstruct.d.ts +18 -13
- package/dist/evals/reconstruct.js +50 -36
- package/dist/evals/recorded-tools.d.ts +7 -7
- package/dist/evals/recorded-tools.js +4 -3
- package/dist/evals/trajectory.d.ts +6 -1
- package/dist/evals/trajectory.js +7 -0
- package/dist/extensions/package.js +2 -2
- package/dist/http/api.module.js +5 -0
- package/dist/inbox/inbox.module.js +2 -1
- package/dist/inbox/inbox.service.d.ts +19 -4
- package/dist/inbox/inbox.service.js +14 -3
- package/dist/infra/config/kernel-config.service.js +11 -0
- package/dist/infra/events/kernel-events.d.ts +2 -1
- package/dist/local-devices/device-policy.service.d.ts +65 -0
- package/dist/local-devices/device-policy.service.js +217 -0
- package/dist/local-devices/local-device-gateway.js +4 -1
- package/dist/local-devices/local-device-provider.d.ts +40 -3
- package/dist/local-devices/local-device-provider.js +109 -7
- package/dist/local-devices/local-devices.module.d.ts +2 -1
- package/dist/local-devices/local-devices.module.js +19 -3
- package/dist/local-devices/machines.d.ts +14 -3
- package/dist/local-devices/machines.js +41 -3
- package/dist/local-devices/network-grants.d.ts +104 -0
- package/dist/local-devices/network-grants.js +288 -0
- package/dist/local-devices/policy-summary.d.ts +20 -0
- package/dist/local-devices/policy-summary.js +63 -0
- package/dist/orgs/orgs.module.js +4 -10
- package/dist/orgs/roles.d.ts +1 -1
- package/dist/orgs/roles.js +4 -0
- package/dist/orgs/settings.controller.js +2 -1
- package/dist/orgs/settings.service.d.ts +164 -12
- package/dist/orgs/settings.service.js +212 -24
- package/dist/platform/exclusions.js +11 -1
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/admin-items.ops.js +1 -1
- package/dist/platform/ops/agent-skills.ops.js +2 -2
- package/dist/platform/ops/agent-templates.ops.js +2 -2
- package/dist/platform/ops/agents.ops.js +11 -15
- package/dist/platform/ops/devices.ops.js +3 -1
- package/dist/platform/ops/inbox.ops.js +19 -0
- package/dist/platform/ops/org-settings.ops.js +14 -5
- package/dist/platform/ops/platform.ops.d.ts +9 -1
- package/dist/platform/ops/platform.ops.js +34 -12
- package/dist/platform/ops/requests.ops.js +3 -2
- package/dist/platform/ops/schedules.ops.d.ts +21 -0
- package/dist/platform/ops/schedules.ops.js +470 -0
- package/dist/platform/ops/skills.ops.js +3 -3
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +3 -0
- package/dist/runtime/context/build.js +13 -4
- package/dist/runtime/context/compaction.js +6 -3
- package/dist/runtime/context/device-paths.d.ts +43 -21
- package/dist/runtime/context/device-paths.js +104 -21
- package/dist/runtime/context/run-context.d.ts +9 -0
- package/dist/runtime/context/run-context.js +8 -0
- package/dist/runtime/context/skills.d.ts +14 -1
- package/dist/runtime/context/skills.js +36 -5
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +16 -5
- package/dist/runtime/run-finish.d.ts +6 -1
- package/dist/runtime/run-finish.js +16 -2
- package/dist/runtime/run-queue.js +21 -2
- package/dist/runtime/run-worker.js +14 -33
- package/dist/runtime/runtime.module.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +32 -14
- package/dist/runtime/subagents/definition.js +106 -109
- package/dist/runtime/subagents/dispatch.js +4 -3
- package/dist/runtime/subagents/preamble.d.ts +2 -1
- package/dist/runtime/subagents/preamble.js +2 -1
- package/dist/runtime/waits.js +14 -4
- package/dist/sandbox/sandbox.service.d.ts +120 -10
- package/dist/sandbox/sandbox.service.js +335 -28
- package/dist/sandbox/tiers.d.ts +86 -0
- package/dist/sandbox/tiers.js +96 -0
- package/dist/schedules/effective-settings.d.ts +16 -0
- package/dist/schedules/effective-settings.js +17 -0
- package/dist/schedules/errors.d.ts +8 -0
- package/dist/schedules/errors.js +8 -0
- package/dist/schedules/fire.d.ts +118 -0
- package/dist/schedules/fire.js +625 -0
- package/dist/schedules/grant-rules.d.ts +94 -0
- package/dist/schedules/grant-rules.js +126 -0
- package/dist/schedules/job-ids.d.ts +32 -0
- package/dist/schedules/job-ids.js +27 -0
- package/dist/schedules/pause.d.ts +9 -0
- package/dist/schedules/pause.js +33 -0
- package/dist/schedules/run-tracker.d.ts +81 -0
- package/dist/schedules/run-tracker.js +333 -0
- package/dist/schedules/scan.job.d.ts +64 -0
- package/dist/schedules/scan.job.js +303 -0
- package/dist/schedules/schedule-math.d.ts +31 -0
- package/dist/schedules/schedule-math.js +223 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
- package/dist/schedules/scheduled-tasks.service.js +1193 -0
- package/dist/schedules/schedules.controller.d.ts +68 -0
- package/dist/schedules/schedules.controller.js +242 -0
- package/dist/schedules/schedules.module.d.ts +16 -0
- package/dist/schedules/schedules.module.js +65 -0
- package/dist/schedules/schedules.schemas.d.ts +316 -0
- package/dist/schedules/schedules.schemas.js +186 -0
- package/dist/schedules/store.d.ts +81 -0
- package/dist/schedules/store.js +390 -0
- package/dist/schedules/store.module.d.ts +6 -0
- package/dist/schedules/store.module.js +21 -0
- package/dist/schedules/types.d.ts +108 -0
- package/dist/schedules/types.js +1 -0
- package/dist/schedules/unattended.d.ts +117 -0
- package/dist/schedules/unattended.js +228 -0
- package/dist/setup/oauth-token-probe.js +8 -4
- package/dist/skills/agent-skills.service.d.ts +4 -8
- package/dist/skills/agent-skills.service.js +18 -53
- package/dist/skills/catalog.d.ts +65 -62
- package/dist/skills/catalog.js +109 -206
- package/dist/skills/conversation-catalog.d.ts +3 -3
- package/dist/skills/conversation-catalog.js +5 -12
- package/dist/skills/invoke.d.ts +2 -2
- package/dist/skills/skills.controller.d.ts +3 -1
- package/dist/skills/skills.service.d.ts +38 -37
- package/dist/skills/skills.service.js +168 -107
- package/dist/skills/usable.d.ts +57 -8
- package/dist/skills/usable.js +172 -38
- package/dist/tools/approvals.controller.d.ts +4 -2
- package/dist/tools/approvals.controller.js +17 -2
- package/dist/tools/approvals.schemas.d.ts +1 -0
- package/dist/tools/approvals.schemas.js +3 -1
- package/dist/tools/approvals.service.d.ts +182 -7
- package/dist/tools/approvals.service.js +597 -13
- package/dist/tools/builtin-names.d.ts +25 -4
- package/dist/tools/builtin-names.js +45 -4
- package/dist/tools/builtins/builtin-tools.d.ts +8 -0
- package/dist/tools/builtins/builtin-tools.js +23 -2
- package/dist/tools/builtins/sandbox/bash.js +16 -7
- package/dist/tools/builtins/sandbox/common.d.ts +1 -1
- package/dist/tools/builtins/sandbox/common.js +33 -10
- package/dist/tools/builtins/sandbox/edit.js +7 -3
- package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
- package/dist/tools/builtins/sandbox/file-state.js +33 -3
- package/dist/tools/builtins/sandbox/write.js +8 -4
- package/dist/tools/builtins/schedules.d.ts +107 -0
- package/dist/tools/builtins/schedules.js +789 -0
- package/dist/tools/elicitation.d.ts +2 -0
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/fixed-ask.d.ts +7 -0
- package/dist/tools/fixed-ask.js +9 -0
- package/dist/tools/network-asks.d.ts +109 -0
- package/dist/tools/network-asks.js +221 -0
- package/dist/tools/pipeline.d.ts +53 -6
- package/dist/tools/pipeline.js +202 -13
- package/dist/tools/policy.d.ts +21 -8
- package/dist/tools/policy.js +63 -9
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/tools.module.js +12 -0
- package/package.json +2 -2
- package/src/migrations/0113_scheduled_tasks.sql +108 -0
- package/src/migrations/0114_network_asks.sql +23 -0
- package/src/migrations/0115_network_requests.sql +28 -0
- package/src/migrations/0116_skills_default_on.sql +37 -0
- package/src/migrations/0117_sandbox_tiers.sql +11 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { validateAllowHost, } from "@capybara-hq/plugin-sdk";
|
|
11
|
+
import { Inject, Injectable } from "@nestjs/common";
|
|
12
|
+
import { sql } from "kysely";
|
|
13
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
14
|
+
import { budgetRunOf } from "../runtime/subagents/limits.js";
|
|
15
|
+
import { appendMissingGrant, taskGrantsRunOf, unattendedTierOf } from "../schedules/unattended.js";
|
|
16
|
+
import { deviceFeaturesOf, isolationOf } from "./policy-summary.js";
|
|
17
|
+
/** The most hosts one exec's grants carry (the device protocol's limit). */
|
|
18
|
+
export const MAX_GRANT_HOSTS = 100;
|
|
19
|
+
/** The key prefix of a scheduled task's network grant (`schedules/grant-rules.ts`). */
|
|
20
|
+
const NETWORK_KEY_PREFIX = "network:";
|
|
21
|
+
/** The statuses of a run that has not ended: its run grants still apply. */
|
|
22
|
+
const LIVE_RUN = ["queued", "running", "waiting"];
|
|
23
|
+
/** The statuses of a network ask that refuse its hosts for the rest of the run tree (§6.8). */
|
|
24
|
+
const REFUSING = new Set(["denied", "expired", "skipped", "cancelled"]);
|
|
25
|
+
/** An exact host a grant may carry: no wildcard, valid. */
|
|
26
|
+
const exactHost = (h) => typeof h === "string" && !h.startsWith("*.") && validateAllowHost(h).length === 0;
|
|
27
|
+
/** `detail.deviceId` of a network ask: the device it asked about. */
|
|
28
|
+
function deviceOf(detail) {
|
|
29
|
+
const d = detail?.deviceId;
|
|
30
|
+
return typeof d === "string" ? d : undefined;
|
|
31
|
+
}
|
|
32
|
+
/** `detail.grantedHosts` of a decided network ask: what the decision allowed (absent: none). */
|
|
33
|
+
function grantedOf(detail) {
|
|
34
|
+
const g = detail?.grantedHosts;
|
|
35
|
+
return Array.isArray(g) ? g.filter(exactHost) : [];
|
|
36
|
+
}
|
|
37
|
+
/** `detail.hosts[].host` of a network ask: what it asked about. */
|
|
38
|
+
function askedOf(detail) {
|
|
39
|
+
const hs = detail?.hosts;
|
|
40
|
+
if (!Array.isArray(hs))
|
|
41
|
+
return [];
|
|
42
|
+
return hs.flatMap((h) => {
|
|
43
|
+
const host = h?.host;
|
|
44
|
+
return typeof host === "string" ? [host] : [];
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Network hosts the kernel allows a run beyond a device's local policy (network ask-to-allow spec
|
|
49
|
+
* §6.3): the hosts of the run tree's approved `network` asks with scope `once` (「这次允许」), read
|
|
50
|
+
* from PG for every exec, so a grant decided on one replica reaches a command on any other. A run
|
|
51
|
+
* tree is the runs of one budget window (`runs.budget_run_id` lineage): a sub-agent's run sees the
|
|
52
|
+
* root's grants, another run tree never does. The hosts a decision allowed are its
|
|
53
|
+
* `detail.grantedHosts`; a decision without them grants nothing. In an unattended run of a
|
|
54
|
+
* scheduled task, the task's `network:<host>` grants (spec §8.1) join them.
|
|
55
|
+
*/
|
|
56
|
+
let NetworkGrants = class NetworkGrants {
|
|
57
|
+
db;
|
|
58
|
+
constructor(db) {
|
|
59
|
+
this.db = db;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Hosts allowed for this exec on `deviceId`: the run tree's once-approved network asks in
|
|
63
|
+
* `orgId` about that device, while the tree's root run has not ended (a grant ends with its run;
|
|
64
|
+
* a later run of the same budget window does not inherit it), with the root run as their source
|
|
65
|
+
* and the latest decider as its account; and in an unattended run of a scheduled task, the
|
|
66
|
+
* task's network grants (spec §8.1) when the device is its creator's, with the task as their
|
|
67
|
+
* source and the creator as its account. Both together are one grant: the hosts of both, the
|
|
68
|
+
* task as the source. Undefined when none. The device applies them only when its owner allows
|
|
69
|
+
* remote grants and only within the org's limits.
|
|
70
|
+
*/
|
|
71
|
+
async forExec(input) {
|
|
72
|
+
const run = await this.runGrants(input);
|
|
73
|
+
const task = await this.taskGrants(input);
|
|
74
|
+
if (task === undefined)
|
|
75
|
+
return run;
|
|
76
|
+
if (run === undefined)
|
|
77
|
+
return task;
|
|
78
|
+
// one exec frame carries one source: the task's, the standing approval behind an unattended
|
|
79
|
+
// run (the run grants of an unattended run come from asks only in `wait` mode); the device
|
|
80
|
+
// logs it with the command, and the kernel's approvals keep the per-host record
|
|
81
|
+
const hosts = [...new Set([...task.hosts, ...run.hosts])].slice(0, MAX_GRANT_HOSTS);
|
|
82
|
+
return { hosts, source: task.source };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The run tree's network asks (spec §6.8): what was granted and refused, the run's asks, a
|
|
86
|
+
* pending ask. With `deviceId`, granted and refused count only the asks about that device (a
|
|
87
|
+
* host allowed on one device is not allowed on another); the cap and the pending ask count all.
|
|
88
|
+
*/
|
|
89
|
+
async runState(runId, deviceId) {
|
|
90
|
+
const task = deviceId === undefined ? undefined : await this.taskGrants({ runId, deviceId });
|
|
91
|
+
const root = await budgetRunOf(this.db.db, runId);
|
|
92
|
+
const rows = await this.db
|
|
93
|
+
.selectFrom("approvals")
|
|
94
|
+
.select(["run_id", "status", "scope", "detail"])
|
|
95
|
+
.where("kind", "=", "network")
|
|
96
|
+
.where("run_id", "in", this.treeOf(root))
|
|
97
|
+
.execute();
|
|
98
|
+
const state = {
|
|
99
|
+
granted: new Set(),
|
|
100
|
+
refused: new Set(),
|
|
101
|
+
asks: 0,
|
|
102
|
+
pending: false,
|
|
103
|
+
};
|
|
104
|
+
for (const r of rows) {
|
|
105
|
+
if (r.run_id === runId)
|
|
106
|
+
state.asks++;
|
|
107
|
+
if (r.status === "pending") {
|
|
108
|
+
state.pending = true;
|
|
109
|
+
}
|
|
110
|
+
else if (deviceId !== undefined && deviceOf(r.detail) !== deviceId) {
|
|
111
|
+
// another device's decision says nothing about this one
|
|
112
|
+
}
|
|
113
|
+
else if (r.status === "approved") {
|
|
114
|
+
const granted = grantedOf(r.detail);
|
|
115
|
+
if (r.scope === "once")
|
|
116
|
+
for (const h of granted)
|
|
117
|
+
state.granted.add(h);
|
|
118
|
+
// the hosts left unchecked in a partial decision count as denied (P-D5)
|
|
119
|
+
for (const h of askedOf(r.detail))
|
|
120
|
+
if (!granted.includes(h))
|
|
121
|
+
state.refused.add(h);
|
|
122
|
+
}
|
|
123
|
+
else if (REFUSING.has(r.status)) {
|
|
124
|
+
for (const h of askedOf(r.detail))
|
|
125
|
+
state.refused.add(h);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (task !== undefined)
|
|
129
|
+
state.taskGranted = new Set(task.hosts);
|
|
130
|
+
return state;
|
|
131
|
+
}
|
|
132
|
+
/** The run tree's once-approved hosts as exec grants (source: the root run). */
|
|
133
|
+
async runGrants(input) {
|
|
134
|
+
const root = await budgetRunOf(this.db.db, input.runId);
|
|
135
|
+
const live = await this.db
|
|
136
|
+
.selectFrom("runs")
|
|
137
|
+
.select("id")
|
|
138
|
+
.where("id", "=", root)
|
|
139
|
+
.where("status", "in", LIVE_RUN)
|
|
140
|
+
.executeTakeFirst();
|
|
141
|
+
if (live === undefined)
|
|
142
|
+
return undefined;
|
|
143
|
+
const rows = await this.db
|
|
144
|
+
.selectFrom("approvals")
|
|
145
|
+
.select(["decided_by", "detail"])
|
|
146
|
+
.where("kind", "=", "network")
|
|
147
|
+
.where("org_id", "=", input.orgId)
|
|
148
|
+
.where("status", "=", "approved")
|
|
149
|
+
.where("scope", "=", "once")
|
|
150
|
+
.where("decided_by", "is not", null)
|
|
151
|
+
.where(sql `detail->>'deviceId'`, "=", input.deviceId)
|
|
152
|
+
.where("run_id", "in", this.treeOf(root))
|
|
153
|
+
.orderBy("decided_at", "desc")
|
|
154
|
+
.orderBy("id", "desc")
|
|
155
|
+
.execute();
|
|
156
|
+
const hosts = [...new Set(rows.flatMap((r) => grantedOf(r.detail)))].slice(0, MAX_GRANT_HOSTS);
|
|
157
|
+
const latest = rows.find((r) => grantedOf(r.detail).length > 0);
|
|
158
|
+
if (hosts.length === 0 || latest?.decided_by == null)
|
|
159
|
+
return undefined;
|
|
160
|
+
return { hosts, source: { kind: "run", id: root, accountId: latest.decided_by } };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* A scheduled task's network grants for an unattended run (spec §8.1): the task's
|
|
164
|
+
* `network:<host>` grants when the run serves a task run that is no trial run and no person
|
|
165
|
+
* joined (`taskGrantsRunOf`, inherited by its sub-agents), the task is of `orgId` (when given)
|
|
166
|
+
* and `deviceId` is a live device of the task's org owned by the task's creator (a creator
|
|
167
|
+
* approves domains for their own devices only). Exact valid hosts only.
|
|
168
|
+
*/
|
|
169
|
+
async taskGrants(input) {
|
|
170
|
+
const task = await taskGrantsRunOf(this.db.db, input.runId);
|
|
171
|
+
if (task === null || (input.orgId !== undefined && task.orgId !== input.orgId)) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const device = await this.db
|
|
175
|
+
.selectFrom("devices")
|
|
176
|
+
.select("id")
|
|
177
|
+
.where("id", "=", input.deviceId)
|
|
178
|
+
.where("org_id", "=", task.orgId)
|
|
179
|
+
.where("owner_account_id", "=", task.accountId)
|
|
180
|
+
.where("deleted_at", "is", null)
|
|
181
|
+
.executeTakeFirst();
|
|
182
|
+
if (device === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
const rows = await this.db
|
|
185
|
+
.selectFrom("scheduled_task_grants")
|
|
186
|
+
.select("tool_key")
|
|
187
|
+
.where("task_id", "=", task.taskId)
|
|
188
|
+
.where("tool_key", "like", `${NETWORK_KEY_PREFIX}%`)
|
|
189
|
+
.orderBy("tool_key")
|
|
190
|
+
.execute();
|
|
191
|
+
const hosts = rows
|
|
192
|
+
.map((r) => r.tool_key.slice(NETWORK_KEY_PREFIX.length))
|
|
193
|
+
.filter(exactHost)
|
|
194
|
+
.slice(0, MAX_GRANT_HOSTS);
|
|
195
|
+
if (hosts.length === 0)
|
|
196
|
+
return undefined;
|
|
197
|
+
return { hosts, source: { kind: "task", id: task.taskId, accountId: task.accountId } };
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The tier rule of an unattended run on `deviceId` (sandbox dev tiers spec §11.4), read fresh:
|
|
201
|
+
* null for an attended run; otherwise the task run and whether the task grants `tier:open` on
|
|
202
|
+
* this device (`unattendedTierOf`).
|
|
203
|
+
*/
|
|
204
|
+
async unattendedTier(input) {
|
|
205
|
+
return await unattendedTierOf(this.db.db, input.runId, input.deviceId);
|
|
206
|
+
}
|
|
207
|
+
/** Records `key` in the task run's `missing_grants` (once). */
|
|
208
|
+
async recordMissing(taskRunId, key) {
|
|
209
|
+
await appendMissingGrant(this.db.db, taskRunId, key);
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Whether the device's active machine runs commands without a sandbox
|
|
213
|
+
* (`device_machines.local_policy.isolation`, read fresh at each exec, spec §4.3), read from the
|
|
214
|
+
* stored JSON's isolation field alone ({@link isolationOf}). Fails closed for a program that
|
|
215
|
+
* applies tiers (feature `tiers`, which always sends a summary): no summary or any isolation but
|
|
216
|
+
* `sandbox` counts as unsandboxed. False without a machine, or for an older program without
|
|
217
|
+
* tiers (it has no unsandboxed mode).
|
|
218
|
+
*/
|
|
219
|
+
async unsandboxed(deviceId) {
|
|
220
|
+
const row = await this.db
|
|
221
|
+
.selectFrom("device_machines")
|
|
222
|
+
.select(["local_policy", "features"])
|
|
223
|
+
.where("device_id", "=", deviceId)
|
|
224
|
+
.where("revoked_at", "is", null)
|
|
225
|
+
.executeTakeFirst();
|
|
226
|
+
if (row === undefined)
|
|
227
|
+
return false;
|
|
228
|
+
const isolation = isolationOf(row.local_policy);
|
|
229
|
+
if (isolation !== null)
|
|
230
|
+
return isolation === "none";
|
|
231
|
+
return deviceFeaturesOf(row.features).includes("tiers");
|
|
232
|
+
}
|
|
233
|
+
/** The runs of the tree whose budget run is `root`. */
|
|
234
|
+
treeOf(root) {
|
|
235
|
+
return this.db
|
|
236
|
+
.selectFrom("runs")
|
|
237
|
+
.select("id")
|
|
238
|
+
.where((eb) => eb.or([eb("id", "=", root), eb("budget_run_id", "=", root)]));
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
NetworkGrants = __decorate([
|
|
242
|
+
Injectable(),
|
|
243
|
+
__param(0, Inject(KernelDb))
|
|
244
|
+
], NetworkGrants);
|
|
245
|
+
export { NetworkGrants };
|
|
246
|
+
const normalize = (host) => {
|
|
247
|
+
const h = host.toLowerCase();
|
|
248
|
+
return h.endsWith(".") ? h.slice(0, -1) : h;
|
|
249
|
+
};
|
|
250
|
+
/** Whether the allowlist entry `pattern` admits `host` (`*.x.com`: subdomains, never the apex). */
|
|
251
|
+
const covers = (pattern, host) => pattern === host || (pattern.startsWith("*.") && host.endsWith(pattern.slice(1)));
|
|
252
|
+
/** Whether the org's effective egress lets a command reach `host`. */
|
|
253
|
+
export function orgAllows(host, limits) {
|
|
254
|
+
if (limits.egress === "none")
|
|
255
|
+
return false;
|
|
256
|
+
if (limits.egress === "open")
|
|
257
|
+
return true;
|
|
258
|
+
const h = normalize(host);
|
|
259
|
+
return limits.allowHosts.some((p) => covers(p.toLowerCase(), h));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* The kernel's own layer for a device's report (spec §4.1 item 3): it trusts the device's word that
|
|
263
|
+
* a host was refused, never its word that the org allows it. A host the device calls `device` that
|
|
264
|
+
* the org's effective egress (`limits`, read for this exec) does not allow becomes `org`; one that
|
|
265
|
+
* is no valid host name becomes `address`. Other layers are kept.
|
|
266
|
+
*/
|
|
267
|
+
export function relayer(blocked, limits) {
|
|
268
|
+
return blocked.map((b) => {
|
|
269
|
+
if (b.layer !== "device")
|
|
270
|
+
return b;
|
|
271
|
+
const h = normalize(b.host);
|
|
272
|
+
if (validateAllowHost(h).length > 0 || h.startsWith("*.")) {
|
|
273
|
+
return { ...b, layer: "address" };
|
|
274
|
+
}
|
|
275
|
+
return orgAllows(b.host, limits) ? b : { ...b, layer: "org" };
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* `grants` cut to the hosts the org's effective egress (`limits`, read for this exec) admits, or
|
|
280
|
+
* undefined when none is left. The device intersects them with the limits too; the kernel does
|
|
281
|
+
* it first so a grant never reaches a device beyond what the org allows.
|
|
282
|
+
*/
|
|
283
|
+
export function grantsWithin(grants, limits) {
|
|
284
|
+
if (grants === undefined)
|
|
285
|
+
return undefined;
|
|
286
|
+
const hosts = grants.hosts.filter((h) => orgAllows(h, limits));
|
|
287
|
+
return hosts.length === 0 ? undefined : { ...grants, hosts };
|
|
288
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type DeviceFeature, type DeviceLocalPolicySummary } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
/**
|
|
3
|
+
* The known features in `v` (a hello's `features` or the stored column), in a fixed order and
|
|
4
|
+
* deduped; anything else (unknown names, a non-array) is dropped.
|
|
5
|
+
*/
|
|
6
|
+
export declare function deviceFeaturesOf(v: unknown): DeviceFeature[];
|
|
7
|
+
/**
|
|
8
|
+
* `v` as a local policy summary (network ask-to-allow spec §5.4), or null when it is absent or not
|
|
9
|
+
* a valid one. The device is the authority on its policy: the kernel only keeps what it last said,
|
|
10
|
+
* for the device page and to pick a card's options, never to decide what a command may reach.
|
|
11
|
+
*/
|
|
12
|
+
export declare function localPolicyOf(v: unknown): DeviceLocalPolicySummary | null;
|
|
13
|
+
/**
|
|
14
|
+
* The isolation a policy summary states, read from its `isolation` field alone (a hello's raw
|
|
15
|
+
* `policy` or the stored column), so a summary the kernel cannot otherwise read (a newer program's
|
|
16
|
+
* new value elsewhere, a broken one) still tells whether commands run without a sandbox: `sandbox`
|
|
17
|
+
* only when it says exactly that, `none` for any other present summary (fail closed), null when
|
|
18
|
+
* there is none.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isolationOf(v: unknown): "sandbox" | "none" | null;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { isSandboxTier, } from "@capybara-hq/plugin-sdk";
|
|
2
|
+
const KNOWN_FEATURES = ["networkReport", "policyOp", "tiers"];
|
|
3
|
+
/**
|
|
4
|
+
* The known features in `v` (a hello's `features` or the stored column), in a fixed order and
|
|
5
|
+
* deduped; anything else (unknown names, a non-array) is dropped.
|
|
6
|
+
*/
|
|
7
|
+
export function deviceFeaturesOf(v) {
|
|
8
|
+
if (!Array.isArray(v))
|
|
9
|
+
return [];
|
|
10
|
+
return KNOWN_FEATURES.filter((f) => v.includes(f));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `v` as a local policy summary (network ask-to-allow spec §5.4), or null when it is absent or not
|
|
14
|
+
* a valid one. The device is the authority on its policy: the kernel only keeps what it last said,
|
|
15
|
+
* for the device page and to pick a card's options, never to decide what a command may reach.
|
|
16
|
+
*/
|
|
17
|
+
export function localPolicyOf(v) {
|
|
18
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
19
|
+
return null;
|
|
20
|
+
const o = v;
|
|
21
|
+
if (o.tier !== undefined && !isSandboxTier(o.tier))
|
|
22
|
+
return null;
|
|
23
|
+
if (!isSandboxTier(o.effectiveTier))
|
|
24
|
+
return null;
|
|
25
|
+
if (o.isolation !== "sandbox" && o.isolation !== "none")
|
|
26
|
+
return null;
|
|
27
|
+
if (o.localhost !== "on" && o.localhost !== "off-proxy-detected" && o.localhost !== "off") {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (o.unixSockets !== "roots" && o.unixSockets !== "all-masked" && o.unixSockets !== "off") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
if (o.remoteGrants !== "owner" && o.remoteGrants !== "off")
|
|
34
|
+
return null;
|
|
35
|
+
if (!Array.isArray(o.allowedDomains))
|
|
36
|
+
return null;
|
|
37
|
+
if (!o.allowedDomains.every((d) => typeof d === "string"))
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
...(o.tier !== undefined && { tier: o.tier }),
|
|
41
|
+
effectiveTier: o.effectiveTier,
|
|
42
|
+
isolation: o.isolation,
|
|
43
|
+
localhost: o.localhost,
|
|
44
|
+
unixSockets: o.unixSockets,
|
|
45
|
+
allowedDomains: [...o.allowedDomains],
|
|
46
|
+
remoteGrants: o.remoteGrants,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The isolation a policy summary states, read from its `isolation` field alone (a hello's raw
|
|
51
|
+
* `policy` or the stored column), so a summary the kernel cannot otherwise read (a newer program's
|
|
52
|
+
* new value elsewhere, a broken one) still tells whether commands run without a sandbox: `sandbox`
|
|
53
|
+
* only when it says exactly that, `none` for any other present summary (fail closed), null when
|
|
54
|
+
* there is none.
|
|
55
|
+
*/
|
|
56
|
+
export function isolationOf(v) {
|
|
57
|
+
if (v === undefined || v === null)
|
|
58
|
+
return null;
|
|
59
|
+
if (typeof v === "object" && !Array.isArray(v)) {
|
|
60
|
+
return v.isolation === "sandbox" ? "sandbox" : "none";
|
|
61
|
+
}
|
|
62
|
+
return "none";
|
|
63
|
+
}
|
package/dist/orgs/orgs.module.js
CHANGED
|
@@ -21,7 +21,7 @@ import { MembersService } from "./members.service.js";
|
|
|
21
21
|
import { Offboarding } from "./offboarding.js";
|
|
22
22
|
import { OrgLockModule } from "./org-lock.js";
|
|
23
23
|
import { OrgsService } from "./orgs.service.js";
|
|
24
|
-
import {
|
|
24
|
+
import { OrgSettingsModule } from "./settings.service.js";
|
|
25
25
|
/**
|
|
26
26
|
* Organizations: orgs, org settings, members, invitations, offboarding (with its sweep cron) and
|
|
27
27
|
* directory sync (the hourly scan cron, one-off syncs on the maintenance queue).
|
|
@@ -35,6 +35,7 @@ OrgsModule = __decorate([
|
|
|
35
35
|
// offboarding publishes the events it cancelled
|
|
36
36
|
EventLogModule,
|
|
37
37
|
OrgLockModule,
|
|
38
|
+
OrgSettingsModule,
|
|
38
39
|
ExtensionsModule,
|
|
39
40
|
// offboarding clears credentials and cancels approvals and questions
|
|
40
41
|
ToolsModule,
|
|
@@ -49,18 +50,11 @@ OrgsModule = __decorate([
|
|
|
49
50
|
// the producer side of directory-sync jobs (the maintenance processor consumes them)
|
|
50
51
|
QueueModule.registerQueue(MAINTENANCE_QUEUE_NAME),
|
|
51
52
|
],
|
|
52
|
-
providers: [
|
|
53
|
-
OrgsService,
|
|
54
|
-
OrgSettingsService,
|
|
55
|
-
MembersService,
|
|
56
|
-
InvitationsService,
|
|
57
|
-
Offboarding,
|
|
58
|
-
DirectorySync,
|
|
59
|
-
],
|
|
53
|
+
providers: [OrgsService, MembersService, InvitationsService, Offboarding, DirectorySync],
|
|
60
54
|
exports: [
|
|
61
55
|
OrgLockModule,
|
|
56
|
+
OrgSettingsModule,
|
|
62
57
|
OrgsService,
|
|
63
|
-
OrgSettingsService,
|
|
64
58
|
MembersService,
|
|
65
59
|
InvitationsService,
|
|
66
60
|
Offboarding,
|
package/dist/orgs/roles.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export type OrgRole = (typeof ORG_ROLES)[number];
|
|
|
4
4
|
/** `role` 是否不低于 `min`。 */
|
|
5
5
|
export declare function roleAtLeast(role: OrgRole, min: OrgRole): boolean;
|
|
6
6
|
/** 组织级动作;路由用 `@OrgAction(action)` 声明所需动作。 */
|
|
7
|
-
export type OrgAction = "org.read" | "org.update" | "org.delete" | "org.transfer" | "org.members.read" | "org.members.manage" | "org.invitations.manage" | "org.tokens.manage" | "org.extensions.read" | "org.extensions.manage" | "agents.create" | "agents.use" | "agents.manage" | "connectors.manage" | "connectors.use" | "approvals.decide" | "channels.manage" | "skills.read" | "skills.manage" | "ui.use" | "trajectories.read" | "evals.manage" | "org.models.manage" | "catalog.read" | "catalog.request" | "catalog.decide" | "catalog.upload";
|
|
7
|
+
export type OrgAction = "org.read" | "org.update" | "org.delete" | "org.transfer" | "org.members.read" | "org.members.manage" | "org.invitations.manage" | "org.tokens.manage" | "org.extensions.read" | "org.extensions.manage" | "agents.create" | "agents.use" | "agents.manage" | "connectors.manage" | "connectors.use" | "approvals.decide" | "channels.manage" | "skills.read" | "skills.manage" | "ui.use" | "trajectories.read" | "evals.manage" | "org.models.manage" | "catalog.read" | "catalog.request" | "catalog.decide" | "catalog.upload" | "schedules.use" | "schedules.manage";
|
|
8
8
|
/** 每个动作所需的最低角色。 */
|
|
9
9
|
export declare const ACTION_MIN_ROLE: Record<OrgAction, OrgRole>;
|
|
10
10
|
/** 只允许 user principal 执行的动作:服务令牌即使角色足够也拒绝(除 SERVICE_ALLOWED 外全部)。 */
|
package/dist/orgs/roles.js
CHANGED
|
@@ -47,6 +47,10 @@ export const ACTION_MIN_ROLE = {
|
|
|
47
47
|
"catalog.decide": "admin",
|
|
48
48
|
// An org admin uploads a plugin package the org owns (spec §14.1 item 1, ruling P3-7)
|
|
49
49
|
"catalog.upload": "admin",
|
|
50
|
+
// Scheduled tasks (spec 2026-11-11 §10): create, edit, pause, resume, run and delete one's own
|
|
51
|
+
"schedules.use": "member",
|
|
52
|
+
// list every task of the org, pause, resume and delete any (never edit another person's)
|
|
53
|
+
"schedules.manage": "admin",
|
|
50
54
|
};
|
|
51
55
|
/** 服务令牌也可执行的动作:只读的 org.read、org.members.read,以及使用 agent 的 agents.use。 */
|
|
52
56
|
const SERVICE_ALLOWED = new Set([
|
|
@@ -30,7 +30,8 @@ let OrgSettingsController = class OrgSettingsController {
|
|
|
30
30
|
async update(principal, org, body) {
|
|
31
31
|
const p = requireUser(principal);
|
|
32
32
|
return {
|
|
33
|
-
|
|
33
|
+
// an API token may narrow the sandbox, never widen it (sandbox dev tiers spec §2)
|
|
34
|
+
settings: await this.settings.update(org.orgId, body, p.account.id, patTokenId(p), undefined, { onlyNarrows: patTokenId(p) !== undefined }),
|
|
34
35
|
deployment: this.settings.deployment(),
|
|
35
36
|
sandboxLimits: await this.settings.sandboxLimits(org.orgId),
|
|
36
37
|
};
|