@clawscarf/cli 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/THIRD_PARTY_NOTICES.md +102 -0
- package/deploy/execution/browser/LICENSE.playwright +202 -0
- package/deploy/execution/browser/seccomp.json +640 -0
- package/deploy/execution/browser-node/configuration.js +85 -0
- package/deploy/execution/browser-node/operator.js +167 -0
- package/deploy/execution/network/node-ingress.cfg +35 -0
- package/deploy/models/catalog.json +275 -0
- package/deploy/openshell/policy.yaml +24 -0
- package/generated/http/LICENSE.md +21 -0
- package/generated/http/client/client.gen.d.ts +2 -0
- package/generated/http/client/client.gen.js +216 -0
- package/generated/http/client/index.d.ts +10 -0
- package/generated/http/client/index.js +6 -0
- package/generated/http/client/types.gen.d.ts +120 -0
- package/generated/http/client/types.gen.js +2 -0
- package/generated/http/client/utils.gen.d.ts +37 -0
- package/generated/http/client/utils.gen.js +228 -0
- package/generated/http/core/auth.gen.d.ts +25 -0
- package/generated/http/core/auth.gen.js +14 -0
- package/generated/http/core/bodySerializer.gen.d.ts +25 -0
- package/generated/http/core/bodySerializer.gen.js +57 -0
- package/generated/http/core/params.gen.d.ts +43 -0
- package/generated/http/core/params.gen.js +109 -0
- package/generated/http/core/pathSerializer.gen.d.ts +33 -0
- package/generated/http/core/pathSerializer.gen.js +106 -0
- package/generated/http/core/queryKeySerializer.gen.d.ts +18 -0
- package/generated/http/core/queryKeySerializer.gen.js +92 -0
- package/generated/http/core/serverSentEvents.gen.d.ts +71 -0
- package/generated/http/core/serverSentEvents.gen.js +132 -0
- package/generated/http/core/types.gen.d.ts +83 -0
- package/generated/http/core/types.gen.js +2 -0
- package/generated/http/core/utils.gen.d.ts +19 -0
- package/generated/http/core/utils.gen.js +87 -0
- package/package.json +37 -0
- package/packs/README.md +130 -0
- package/packs/research-team/pack.json +20 -0
- package/packs/research-team/researcher/CLAW.md +19 -0
- package/packs/research-team/researcher/package.json +8 -0
- package/packs/research-team/researcher/profiles/openclaw.yml +6 -0
- package/packs/research-team/reviewer/CLAW.md +19 -0
- package/packs/research-team/reviewer/package.json +8 -0
- package/packs/research-team/reviewer/profiles/openclaw.yml +6 -0
- package/pnpm-lock.yaml +7158 -0
- package/recipes/README.md +37 -0
- package/recipes/team-server/recipe.json +28 -0
- package/release/README.md +137 -0
- package/release/components.json +29 -0
- package/release/operator.md +63 -0
- package/runtime/configuration.js +135 -0
- package/runtime/connections-configuration.js +70 -0
- package/runtime/model-contract.js +92 -0
- package/runtime/releases/0.1.0-alpha.1.json +37 -0
- package/scripts/clawscarf.js +92 -0
- package/scripts/cloud/login.js +134 -0
- package/scripts/cloud/registration.js +232 -0
- package/scripts/cloud/url.js +9 -0
- package/scripts/connections.js +193 -0
- package/scripts/controller.js +143 -0
- package/scripts/deployment/browser-node-compose.js +81 -0
- package/scripts/deployment/browser-node-helper.js +9 -0
- package/scripts/deployment/browser-node-pairing.js +165 -0
- package/scripts/deployment/browser-node.js +154 -0
- package/scripts/deployment/browser.js +143 -0
- package/scripts/deployment/certificates.js +76 -0
- package/scripts/deployment/compose.js +191 -0
- package/scripts/deployment/configuration.js +275 -0
- package/scripts/deployment/connection-policy.js +80 -0
- package/scripts/deployment/connection-settings.js +60 -0
- package/scripts/deployment/connections-runtime.js +31 -0
- package/scripts/deployment/connections.js +213 -0
- package/scripts/deployment/controller-compose.js +59 -0
- package/scripts/deployment/database.js +199 -0
- package/scripts/deployment/entrypoint.js +7 -0
- package/scripts/deployment/images.js +17 -0
- package/scripts/deployment/initial-services.js +53 -0
- package/scripts/deployment/launch.js +177 -0
- package/scripts/deployment/logs.js +15 -0
- package/scripts/deployment/model-gateway-compose.js +74 -0
- package/scripts/deployment/model-gateway.js +125 -0
- package/scripts/deployment/models.js +82 -0
- package/scripts/deployment/networks.js +354 -0
- package/scripts/deployment/policy.js +56 -0
- package/scripts/deployment/preflight.js +124 -0
- package/scripts/deployment/prepare.js +205 -0
- package/scripts/deployment/process.js +70 -0
- package/scripts/deployment/relay.js +58 -0
- package/scripts/deployment/runtime-binding.js +100 -0
- package/scripts/deployment/runtime.js +232 -0
- package/scripts/deployment/state.js +154 -0
- package/scripts/deployment/team.js +76 -0
- package/scripts/deployment/upgrade-rpc.py +148 -0
- package/scripts/deployment/upgrade-state.js +47 -0
- package/scripts/deployment/upgrade.js +298 -0
- package/scripts/deployment/volumes.js +24 -0
- package/scripts/errors.js +9 -0
- package/scripts/installation/administrator.js +23 -0
- package/scripts/installation/command.js +176 -0
- package/scripts/installation/configuration.js +104 -0
- package/scripts/installation/configure.js +122 -0
- package/scripts/installation/delete.js +111 -0
- package/scripts/installation/doctor.js +8 -0
- package/scripts/installation/errors.js +8 -0
- package/scripts/installation/files.js +51 -0
- package/scripts/installation/installer/cloud.js +22 -0
- package/scripts/installation/installer/collect.js +245 -0
- package/scripts/installation/installer/inputs.js +59 -0
- package/scripts/installation/installer/menu.js +49 -0
- package/scripts/installation/installer/prompts.js +139 -0
- package/scripts/installation/installer/run.js +251 -0
- package/scripts/installation/installer/secrets.js +16 -0
- package/scripts/installation/installer/sections/access.js +69 -0
- package/scripts/installation/installer/sections/certificates.js +18 -0
- package/scripts/installation/installer/sections/connections.js +4 -0
- package/scripts/installation/installer/sections/external-models.js +35 -0
- package/scripts/installation/installer/sections/models.js +119 -0
- package/scripts/installation/installer/sections/packs.js +82 -0
- package/scripts/installation/installer/sections/resources.js +10 -0
- package/scripts/installation/installer/settings.js +192 -0
- package/scripts/installation/installer/summary.js +44 -0
- package/scripts/installation/lifecycle.js +127 -0
- package/scripts/installation/location.js +3 -0
- package/scripts/installation/models.js +42 -0
- package/scripts/installation/options.js +360 -0
- package/scripts/installation/packs.js +150 -0
- package/scripts/installation/plan.js +121 -0
- package/scripts/installation/prerequisites.js +170 -0
- package/scripts/installation/recipes/catalog.js +55 -0
- package/scripts/installation/recipes/definition.js +32 -0
- package/scripts/installation/reconfigure.js +255 -0
- package/scripts/installation/requirements.js +23 -0
- package/scripts/installation/resolve.js +217 -0
- package/scripts/installation/runtime.js +87 -0
- package/scripts/installation/save.js +95 -0
- package/scripts/installation/setup.js +91 -0
- package/scripts/models/catalog.js +9 -0
- package/scripts/models/configuration.js +184 -0
- package/scripts/models/credentials.js +132 -0
- package/scripts/models/runtime.js +87 -0
- package/scripts/output.js +34 -0
- package/scripts/packs/connections.js +95 -0
- package/scripts/packs/lifecycle.js +125 -0
- package/scripts/packs/model.js +66 -0
- package/scripts/packs/native.js +69 -0
- package/scripts/packs/openshell.js +148 -0
- package/scripts/packs/policy.js +189 -0
- package/scripts/packs/requirements.in +1 -0
- package/scripts/packs/requirements.js +11 -0
- package/scripts/packs/requirements.txt +116 -0
- package/scripts/packs/source.js +90 -0
- package/scripts/packs/transport.py +68 -0
- package/scripts/people.js +52 -0
- package/scripts/release/create.js +62 -0
- package/scripts/release/definition.js +51 -0
- package/scripts/session.js +27 -0
- package/services/access/generated/client.gen.d.ts +12 -0
- package/services/access/generated/client.gen.js +3 -0
- package/services/access/generated/fastify.gen.d.ts +49 -0
- package/services/access/generated/fastify.gen.js +2 -0
- package/services/access/generated/index.d.ts +2 -0
- package/services/access/generated/index.js +2 -0
- package/services/access/generated/sdk.gen.d.ts +28 -0
- package/services/access/generated/sdk.gen.js +107 -0
- package/services/access/generated/types.gen.d.ts +349 -0
- package/services/access/generated/types.gen.js +2 -0
- package/services/access/migrations/001_access.sql +39 -0
- package/services/access/migrations/002_administrator_setup.sql +16 -0
- package/services/access/migrations/003_local_login_completion.sql +5 -0
- package/services/access/migrations/004_invitations.sql +21 -0
- package/services/access/repo/postgres.js +341 -0
- package/services/access/repo/session-logout.js +28 -0
- package/services/access/types/credential.js +2 -0
- package/services/access/types/errors.js +7 -0
- package/services/access/types/ingress.js +1 -0
- package/services/access/types/model.js +1 -0
- package/services/access/types/native-errors.js +7 -0
- package/services/access/types/native.js +1 -0
- package/services/cloud/generated/client.gen.d.ts +12 -0
- package/services/cloud/generated/client.gen.js +3 -0
- package/services/cloud/generated/index.d.ts +2 -0
- package/services/cloud/generated/index.js +2 -0
- package/services/cloud/generated/sdk.gen.d.ts +145 -0
- package/services/cloud/generated/sdk.gen.js +488 -0
- package/services/cloud/generated/types.gen.d.ts +2034 -0
- package/services/cloud/generated/types.gen.js +2 -0
- package/services/connections/cloud/generated/client.gen.d.ts +12 -0
- package/services/connections/cloud/generated/client.gen.js +3 -0
- package/services/connections/cloud/generated/fastify.gen.d.ts +68 -0
- package/services/connections/cloud/generated/fastify.gen.js +2 -0
- package/services/connections/cloud/generated/index.d.ts +2 -0
- package/services/connections/cloud/generated/index.js +2 -0
- package/services/connections/cloud/generated/sdk.gen.d.ts +95 -0
- package/services/connections/cloud/generated/sdk.gen.js +210 -0
- package/services/connections/cloud/generated/types.gen.d.ts +1044 -0
- package/services/connections/cloud/generated/types.gen.js +2 -0
- package/services/connections/migrations/001_connections.sql +220 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { OperatorError } from "../errors.js";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { verifyNetworkPolicy } from "./policy.js";
|
|
10
|
+
import { NativeClaws } from "./native.js";
|
|
11
|
+
const execute = promisify(execFile);
|
|
12
|
+
const snapshotSchema = z.object({
|
|
13
|
+
id: z.uuid(),
|
|
14
|
+
name: z.string(),
|
|
15
|
+
phase: z.literal("Ready"),
|
|
16
|
+
});
|
|
17
|
+
export class OpenShellClaws extends NativeClaws {
|
|
18
|
+
options;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super("/app/clawscarf/bin/openclaw");
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async shell(args) {
|
|
24
|
+
const child = execute(this.options.executable, [...args, "--gateway", this.options.gateway], {
|
|
25
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
26
|
+
timeout: 120000,
|
|
27
|
+
env: this.options.env ?? process.env,
|
|
28
|
+
});
|
|
29
|
+
child.child.stdin?.end();
|
|
30
|
+
const { stdout } = await child;
|
|
31
|
+
return stdout;
|
|
32
|
+
}
|
|
33
|
+
boundTarget;
|
|
34
|
+
async exec(command, stdin) {
|
|
35
|
+
if (!this.boundTarget)
|
|
36
|
+
await this.target();
|
|
37
|
+
if (!this.boundTarget)
|
|
38
|
+
throw new OperatorError("An observed target is required.");
|
|
39
|
+
const child = execute(this.options.python, [fileURLToPath(new URL("./transport.py", import.meta.url))], {
|
|
40
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
41
|
+
timeout: 120000,
|
|
42
|
+
env: this.options.env ?? process.env,
|
|
43
|
+
});
|
|
44
|
+
child.child.stdin?.end(JSON.stringify({
|
|
45
|
+
gateway: this.options.gateway,
|
|
46
|
+
sandboxId: this.boundTarget.sandboxId,
|
|
47
|
+
command,
|
|
48
|
+
stdin: stdin?.toString("base64") ?? "",
|
|
49
|
+
}));
|
|
50
|
+
return (await child).stdout;
|
|
51
|
+
}
|
|
52
|
+
async target() {
|
|
53
|
+
const gateway = snapshotSchema.parse(JSON.parse(await this.shell([
|
|
54
|
+
"sandbox",
|
|
55
|
+
"get",
|
|
56
|
+
this.options.sandbox,
|
|
57
|
+
"--output",
|
|
58
|
+
"json",
|
|
59
|
+
])));
|
|
60
|
+
if (gateway.name !== this.options.sandbox)
|
|
61
|
+
throw new OperatorError("Unexpected sandbox identity.");
|
|
62
|
+
const target = {
|
|
63
|
+
kind: "openshell",
|
|
64
|
+
gateway: this.options.gateway,
|
|
65
|
+
sandbox: gateway.name,
|
|
66
|
+
sandboxId: gateway.id,
|
|
67
|
+
};
|
|
68
|
+
if (this.boundTarget && this.boundTarget.sandboxId !== target.sandboxId)
|
|
69
|
+
throw new OperatorError("Sandbox identity changed; create a new pack preview.");
|
|
70
|
+
this.boundTarget = target;
|
|
71
|
+
return target;
|
|
72
|
+
}
|
|
73
|
+
async version() {
|
|
74
|
+
if ((this.options.env ?? process.env).OPENCLAW_EXPERIMENTAL_CLAWS !== "1")
|
|
75
|
+
throw new OperatorError("Set OPENCLAW_EXPERIMENTAL_CLAWS=1 to acknowledge the experimental native Claws contract.");
|
|
76
|
+
const stdout = await this.exec([
|
|
77
|
+
"/app/clawscarf/bin/openclaw",
|
|
78
|
+
"--version",
|
|
79
|
+
]);
|
|
80
|
+
if (!/\b2026\.9\.4\b/.test(stdout))
|
|
81
|
+
throw new OperatorError("Packs require the pinned OpenClaw 2026.9.4 release.");
|
|
82
|
+
}
|
|
83
|
+
async run(args) {
|
|
84
|
+
return JSON.parse(await this.exec(["/app/clawscarf/bin/openclaw", ...args]));
|
|
85
|
+
}
|
|
86
|
+
async source(root, digest, existing) {
|
|
87
|
+
const targetRoot = existing ??
|
|
88
|
+
(await this.exec([
|
|
89
|
+
"/usr/local/bin/node",
|
|
90
|
+
"-e",
|
|
91
|
+
"process.stdout.write(require('node:fs').mkdtempSync('/home/node/.clawscarf-pack-'))",
|
|
92
|
+
])).trim();
|
|
93
|
+
if (!/^\/home\/node\/\.clawscarf-pack-[A-Za-z0-9]+$/.test(targetRoot))
|
|
94
|
+
throw new OperatorError("Invalid pack staging directory.");
|
|
95
|
+
if (!existing) {
|
|
96
|
+
const directory = await mkdtemp(join(tmpdir(), "clawscarf-pack-upload-"));
|
|
97
|
+
try {
|
|
98
|
+
const archive = join(directory, "source.tar");
|
|
99
|
+
await execute("tar", ["-cf", archive, "-C", root, "."], {
|
|
100
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" },
|
|
101
|
+
});
|
|
102
|
+
await this.exec(["tar", "-xf", "-", "-C", targetRoot], await readFile(archive));
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await rm(directory, { recursive: true, force: true });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const observed = z
|
|
109
|
+
.object({ digest: z.string() })
|
|
110
|
+
.parse(JSON.parse(await this.exec([
|
|
111
|
+
"/app/clawscarf/bin/packs",
|
|
112
|
+
"inspect",
|
|
113
|
+
targetRoot,
|
|
114
|
+
"--json",
|
|
115
|
+
])));
|
|
116
|
+
if (observed.digest !== digest)
|
|
117
|
+
throw new OperatorError("Target pack differs from reviewed source; create a new preview.");
|
|
118
|
+
return targetRoot;
|
|
119
|
+
}
|
|
120
|
+
async network(requirements) {
|
|
121
|
+
if (!requirements.length)
|
|
122
|
+
return null;
|
|
123
|
+
const target = await this.target();
|
|
124
|
+
if (target.kind !== "openshell")
|
|
125
|
+
throw new OperatorError("An OpenShell target is required.");
|
|
126
|
+
for (const requirement of requirements) {
|
|
127
|
+
await this.exec([
|
|
128
|
+
"/usr/local/bin/node",
|
|
129
|
+
"-e",
|
|
130
|
+
"const fs=require('node:fs');fs.accessSync(process.argv[1],fs.constants.X_OK);if(fs.realpathSync(process.argv[1])!==process.argv[1])process.exit(1)",
|
|
131
|
+
requirement.binary,
|
|
132
|
+
]);
|
|
133
|
+
}
|
|
134
|
+
return verifyNetworkPolicy(requirements, {
|
|
135
|
+
sandbox: target.sandbox,
|
|
136
|
+
sandboxId: target.sandboxId,
|
|
137
|
+
run: (args) => this.shell(args),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async binary(name) {
|
|
141
|
+
await this.exec([
|
|
142
|
+
"/usr/local/bin/node",
|
|
143
|
+
"-e",
|
|
144
|
+
"const fs=require('node:fs'),path=require('node:path');if(!process.env.PATH.split(path.delimiter).some(p=>{try{fs.accessSync(path.join(p,process.argv[1]),fs.constants.X_OK);return true}catch{return false}}))process.exit(1)",
|
|
145
|
+
name,
|
|
146
|
+
]);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { isAbsolute, normalize } from "node:path/posix";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const host = z
|
|
4
|
+
.string()
|
|
5
|
+
.max(253)
|
|
6
|
+
.regex(/^(?=.{1,253}$)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/)
|
|
7
|
+
.refine((value) => !/^[0-9.]+$/.test(value) &&
|
|
8
|
+
value.split(".").every((label) => label.length <= 63), "Use an exact DNS hostname, not an IP address.");
|
|
9
|
+
const binary = z
|
|
10
|
+
.string()
|
|
11
|
+
.refine((value) => isAbsolute(value) &&
|
|
12
|
+
normalize(value) === value &&
|
|
13
|
+
!/[\s*?[\]{}]/.test(value) &&
|
|
14
|
+
Array.from(value).every((character) => character.charCodeAt(0) >= 32), "Use an exact canonical executable path inside the runtime.");
|
|
15
|
+
export const networkRequirementSchema = z.strictObject({
|
|
16
|
+
binary,
|
|
17
|
+
host,
|
|
18
|
+
port: z.number().int().min(1).max(65535),
|
|
19
|
+
protocol: z.literal("tcp"),
|
|
20
|
+
});
|
|
21
|
+
const hash = z.string().regex(/^[a-f0-9]{64}$/);
|
|
22
|
+
const revision = z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^\d+$/)
|
|
25
|
+
.refine((value) => BigInt(value) <= 18446744073709551615n);
|
|
26
|
+
export const policyProofSchema = z.strictObject({
|
|
27
|
+
sandboxId: z.string().min(1),
|
|
28
|
+
version: z.number().int().positive(),
|
|
29
|
+
hash,
|
|
30
|
+
configRevision: revision,
|
|
31
|
+
});
|
|
32
|
+
export class PolicyVerificationError extends Error {
|
|
33
|
+
code;
|
|
34
|
+
constructor(code, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.code = code;
|
|
37
|
+
this.name = "PolicyVerificationError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const sandboxSchema = z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
name: z.string(),
|
|
43
|
+
phase: z.literal("Ready"),
|
|
44
|
+
current_policy_version: z.number().int().positive(),
|
|
45
|
+
});
|
|
46
|
+
const effectiveSchema = z.object({
|
|
47
|
+
sandbox: z.string(),
|
|
48
|
+
version: z.number().int().positive(),
|
|
49
|
+
hash,
|
|
50
|
+
config_revision: revision,
|
|
51
|
+
status: z.literal("effective"),
|
|
52
|
+
policy_source: z.string(),
|
|
53
|
+
policy: z.object({
|
|
54
|
+
network_policies: z.record(z.string(), z.unknown()).default({}),
|
|
55
|
+
network_middlewares: z.record(z.string(), z.unknown()).default({}),
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
const loadedSchema = z.object({
|
|
59
|
+
sandbox: z.string(),
|
|
60
|
+
version: z.number().int().positive(),
|
|
61
|
+
active_version: z.number().int().positive(),
|
|
62
|
+
hash,
|
|
63
|
+
status: z.string(),
|
|
64
|
+
loaded_at_ms: z.number().int().positive().optional(),
|
|
65
|
+
load_error: z.string().optional(),
|
|
66
|
+
});
|
|
67
|
+
const entrySchema = z.strictObject({
|
|
68
|
+
name: z.string().optional(),
|
|
69
|
+
endpoints: z.array(z.record(z.string(), z.unknown())),
|
|
70
|
+
binaries: z.array(z.strictObject({ path: z.string() })),
|
|
71
|
+
});
|
|
72
|
+
const endpointSchema = z.strictObject({
|
|
73
|
+
host,
|
|
74
|
+
port: z.number().int().min(1).max(65535),
|
|
75
|
+
protocol: z.literal("tcp"),
|
|
76
|
+
enforcement: z.literal("enforce").optional(),
|
|
77
|
+
});
|
|
78
|
+
/** JSON.parse's source context preserves the CLI's uint64 fingerprint exactly. */
|
|
79
|
+
function parse(raw) {
|
|
80
|
+
return JSON.parse(raw, (key, value, context) => {
|
|
81
|
+
if (key !== "config_revision")
|
|
82
|
+
return value;
|
|
83
|
+
if (typeof value !== "number" ||
|
|
84
|
+
!context?.source ||
|
|
85
|
+
!/^\d+$/.test(context.source))
|
|
86
|
+
throw new PolicyVerificationError("policy_unverified", "OpenShell returned an invalid config revision.");
|
|
87
|
+
return context.source;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function verifyCoverage(policy, requirements) {
|
|
91
|
+
if (Object.keys(policy.network_middlewares).length)
|
|
92
|
+
throw new PolicyVerificationError("policy_unsupported", "This pack verifier cannot qualify middleware-dependent network policy. Review the native policy explicitly.");
|
|
93
|
+
const entries = Object.values(policy.network_policies).map((value) => {
|
|
94
|
+
const result = entrySchema.safeParse(value);
|
|
95
|
+
if (!result.success)
|
|
96
|
+
throw new PolicyVerificationError("policy_unsupported", "Unsupported network rule shape; this verifier supports exact executable and unconditional TCP grants only.");
|
|
97
|
+
return result.data;
|
|
98
|
+
});
|
|
99
|
+
for (const requirement of requirements) {
|
|
100
|
+
let permitted = false;
|
|
101
|
+
for (const entry of entries) {
|
|
102
|
+
for (const endpoint of entry.endpoints) {
|
|
103
|
+
if (typeof endpoint.host !== "string" ||
|
|
104
|
+
/[*?[\]{}]/.test(endpoint.host))
|
|
105
|
+
throw new PolicyVerificationError("policy_unsupported", "Hostless and wildcard endpoint rules require native policy review; this verifier does not infer their coverage.");
|
|
106
|
+
if (endpoint.host !== requirement.host)
|
|
107
|
+
continue;
|
|
108
|
+
if (entry.binaries.some((value) => /[*?[\]{}]/.test(value.path)))
|
|
109
|
+
throw new PolicyVerificationError("policy_unsupported", "Wildcard executable rules require native policy review. Use a canonical executable requirement.");
|
|
110
|
+
if (!entry.binaries.some((value) => value.path === requirement.binary))
|
|
111
|
+
continue;
|
|
112
|
+
if (endpoint.protocol === undefined)
|
|
113
|
+
throw new PolicyVerificationError("policy_unsupported", `Policy for ${requirement.host} permits explicit-proxy passthrough, not native TCP. This TCP requirement needs an explicit protocol: tcp grant; review the required transport without changing policy automatically.`);
|
|
114
|
+
const result = endpointSchema.safeParse(endpoint);
|
|
115
|
+
if (!result.success)
|
|
116
|
+
throw new PolicyVerificationError("policy_unsupported", `Cannot qualify conditional, audited or non-TCP policy for ${requirement.host}; this verifier does not evaluate L7 rules.`);
|
|
117
|
+
if (result.data.port === requirement.port)
|
|
118
|
+
permitted = true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!permitted)
|
|
122
|
+
throw new PolicyVerificationError("network_not_permitted", `No exact loaded TCP grant for ${requirement.binary} to ${requirement.host}:${String(requirement.port)}.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Observes loaded policy coverage, never changes policy or claims a successful connection. */
|
|
126
|
+
export async function verifyNetworkPolicy(requirements, target) {
|
|
127
|
+
if (!requirements.length)
|
|
128
|
+
return null;
|
|
129
|
+
const parsed = z.array(networkRequirementSchema).parse(requirements);
|
|
130
|
+
const readSandbox = async () => sandboxSchema.parse(parse(await target.run([
|
|
131
|
+
"sandbox",
|
|
132
|
+
"get",
|
|
133
|
+
target.sandbox,
|
|
134
|
+
"--output",
|
|
135
|
+
"json",
|
|
136
|
+
])));
|
|
137
|
+
const readEffective = async () => effectiveSchema.parse(parse(await target.run([
|
|
138
|
+
"policy",
|
|
139
|
+
"get",
|
|
140
|
+
target.sandbox,
|
|
141
|
+
"--full",
|
|
142
|
+
"--output",
|
|
143
|
+
"json",
|
|
144
|
+
])));
|
|
145
|
+
const identity = await readSandbox();
|
|
146
|
+
if (identity.id !== target.sandboxId || identity.name !== target.sandbox)
|
|
147
|
+
throw new PolicyVerificationError("policy_changed", "The target sandbox was replaced; create a new pack preview.");
|
|
148
|
+
const effective = await readEffective();
|
|
149
|
+
if (effective.sandbox !== target.sandbox ||
|
|
150
|
+
effective.policy_source !== "sandbox")
|
|
151
|
+
throw new PolicyVerificationError("policy_unsupported", "Global policy requires a per-sandbox effective-load acknowledgment this verifier cannot obtain. Use explicit native review.");
|
|
152
|
+
const loaded = loadedSchema.parse(parse(await target.run([
|
|
153
|
+
"policy",
|
|
154
|
+
"get",
|
|
155
|
+
target.sandbox,
|
|
156
|
+
"--rev",
|
|
157
|
+
String(effective.version),
|
|
158
|
+
"--output",
|
|
159
|
+
"json",
|
|
160
|
+
])));
|
|
161
|
+
if (loaded.sandbox !== target.sandbox ||
|
|
162
|
+
loaded.status !== "loaded" ||
|
|
163
|
+
!loaded.loaded_at_ms ||
|
|
164
|
+
loaded.load_error ||
|
|
165
|
+
loaded.version !== effective.version ||
|
|
166
|
+
loaded.active_version !== effective.version ||
|
|
167
|
+
identity.current_policy_version !== effective.version)
|
|
168
|
+
throw new PolicyVerificationError("policy_unverified", "The requested policy is not confirmed loaded. Wait for native policy activation, then retry the preview.");
|
|
169
|
+
if (loaded.hash !== effective.hash)
|
|
170
|
+
throw new PolicyVerificationError("policy_unsupported", "Effective policy differs from the acknowledged revision, including possible provider composition. Native review is required.");
|
|
171
|
+
verifyCoverage(effective.policy, parsed);
|
|
172
|
+
const after = await readEffective();
|
|
173
|
+
const finalIdentity = await readSandbox();
|
|
174
|
+
if (finalIdentity.id !== identity.id ||
|
|
175
|
+
finalIdentity.name !== identity.name ||
|
|
176
|
+
finalIdentity.current_policy_version !== loaded.version ||
|
|
177
|
+
after.hash !== effective.hash ||
|
|
178
|
+
after.version !== effective.version ||
|
|
179
|
+
after.config_revision !== effective.config_revision ||
|
|
180
|
+
after.policy_source !== effective.policy_source ||
|
|
181
|
+
after.sandbox !== effective.sandbox)
|
|
182
|
+
throw new PolicyVerificationError("policy_changed", "Runtime policy changed during verification; create a new pack preview.");
|
|
183
|
+
return {
|
|
184
|
+
sandboxId: identity.id,
|
|
185
|
+
version: loaded.version,
|
|
186
|
+
hash: effective.hash,
|
|
187
|
+
configRevision: effective.config_revision,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openshell==0.0.116
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { verifyConnections } from "./connections.js";
|
|
2
|
+
export async function requirements(pack, member, native, bindingsFile, sourceRoot = "") {
|
|
3
|
+
for (const binary of pack.execution.binaries)
|
|
4
|
+
await native.binary(binary);
|
|
5
|
+
const network = await native.network(pack.execution.network);
|
|
6
|
+
const model = member.requirements.model === "configured-default"
|
|
7
|
+
? await native.defaultModel()
|
|
8
|
+
: null;
|
|
9
|
+
const connections = await verifyConnections(member, native, sourceRoot, bindingsFile);
|
|
10
|
+
return { model, connections, network };
|
|
11
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# This file was autogenerated by uv via the following command:
|
|
2
|
+
# uv pip compile scripts/packs/requirements.in --generate-hashes --output-file scripts/packs/requirements.txt --python-version 3.12
|
|
3
|
+
anyio==4.15.1 \
|
|
4
|
+
--hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \
|
|
5
|
+
--hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94
|
|
6
|
+
# via httpx
|
|
7
|
+
certifi==2026.7.22 \
|
|
8
|
+
--hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \
|
|
9
|
+
--hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55
|
|
10
|
+
# via
|
|
11
|
+
# httpcore
|
|
12
|
+
# httpx
|
|
13
|
+
cloudpickle==3.1.2 \
|
|
14
|
+
--hash=sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414 \
|
|
15
|
+
--hash=sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a
|
|
16
|
+
# via openshell
|
|
17
|
+
grpcio==1.84.0 \
|
|
18
|
+
--hash=sha256:026d757df86c5b7a41de8200b9a2cda454aaa5004cb0c7e3374c66eb82f61499 \
|
|
19
|
+
--hash=sha256:06619ba1515e5ee69fb2a514e95dd8be05ce74cb3928d5b34f87f87c86fe3c27 \
|
|
20
|
+
--hash=sha256:08735e3d08d24ab3132cf87e2e5dea8746cabcc7d676c2b0b7362f195feef9d9 \
|
|
21
|
+
--hash=sha256:0d532ade4486dad9b302ffa4d4683d67561051c26d17c4023322845e9fa10140 \
|
|
22
|
+
--hash=sha256:158c1c11cfb61b4849c3caf4d52de6f5ecd376e14446feb4a90dc95a90d616f5 \
|
|
23
|
+
--hash=sha256:15bb76489e337fc492685c9758e2fd4d4ab516b901ad830dc5a91987decf00be \
|
|
24
|
+
--hash=sha256:19aaf172fc2edbefccce3f6e92c5150975dbe56c45744e9e87cf72ebdf85bfbe \
|
|
25
|
+
--hash=sha256:209414080da8c20af94df1395b635da52dd57b5edc9e917e1deca0dc1c4bb55e \
|
|
26
|
+
--hash=sha256:210e4c32f907045eb8158273e60c6ab69a3947697df6245dbda381f26c59485b \
|
|
27
|
+
--hash=sha256:23e6e8e8a75cff88e0a793bfd3becea03a13e2763ae90c1ff573bc19ca5b429a \
|
|
28
|
+
--hash=sha256:27b8b36200a9fbee6e120246f4a8a41657549107ef19fb2c819c4b2fd524f39a \
|
|
29
|
+
--hash=sha256:28d2609691da93051e998495108bbddd2a9f7a561253bae94828d81290f30c15 \
|
|
30
|
+
--hash=sha256:2c024da73b296f040b8360e60bd73a659b230093684a438da0e1260f34cc724e \
|
|
31
|
+
--hash=sha256:393d8a78bff6731ecc5ad2151a821f8fbc1709b137ebb9c25a4ef399fbdcc914 \
|
|
32
|
+
--hash=sha256:3d6a82c4fc6c85f2fb7572c86bdb86f84c97b6580e5f6599f711800bac48a5d8 \
|
|
33
|
+
--hash=sha256:3de427b05f244ba2c2a9bdc67e7a6731c8340811524ecc4435466549f8af1d17 \
|
|
34
|
+
--hash=sha256:406583b4e8fb2282ebd392e12b963e601c1f82e07125a8c2cb5b144e7e024796 \
|
|
35
|
+
--hash=sha256:4119efa6519871719ad81f33bc95ab87857dcb1c5801f30a6e592f2c41164169 \
|
|
36
|
+
--hash=sha256:42959bd50dd660ffc3f2a9bec15a6da4f9aaa0dda555d59ff2d2e80b908456a8 \
|
|
37
|
+
--hash=sha256:455ed6083353b8e938f1d58c765eab2fbb165731e5b507be30fee344915a2a11 \
|
|
38
|
+
--hash=sha256:465eef3d17e59ad22a556fc0138f7c7c799df426734344daec42c797d49fda99 \
|
|
39
|
+
--hash=sha256:47ecf0d9b81d981f07b61bd89eced9d2582f5eaacc3aaa36ad27f81aef70a27f \
|
|
40
|
+
--hash=sha256:49717e857899f4136d7657bf5aded61ac479110a075438290923a4d86af7cd02 \
|
|
41
|
+
--hash=sha256:4aaeceeb7fa7d824c322d1ec3208c8495c88478a927295553235435fc49043ad \
|
|
42
|
+
--hash=sha256:57dc36a5ab0e676f5f6e171de2917fd0aef73f32a9aaf23956bfe19997a30bd1 \
|
|
43
|
+
--hash=sha256:5933a052946873d01a42119a05420d669bdca436aeba2d1851988ccb12b421c0 \
|
|
44
|
+
--hash=sha256:5deda5b4bf62769eb98c119cca43d40e1231e34846b19db5cdea821d446a2253 \
|
|
45
|
+
--hash=sha256:61386101ecaa096b694d0dd278caf99a56aeec78440cc17e918eef0b50f2d567 \
|
|
46
|
+
--hash=sha256:659728f20fc7a0933ed7b1945435e31014b97ab8a5a7edcbaa70da4794aeb191 \
|
|
47
|
+
--hash=sha256:70bb4ce8be0c5606bec259cbd7152374470396413b7863a658a08c849e6b29ff \
|
|
48
|
+
--hash=sha256:71fd60e6e426d293d0a2f685115ad0a0845117602cf13605a4be7524fb5f7bba \
|
|
49
|
+
--hash=sha256:756ea5c2da00fa65c930284892d2a9706828704ca3ba40b4c51c4834eb39fcfd \
|
|
50
|
+
--hash=sha256:800b7e00d92553313c0463c200087930aa78678ec1d528193aeb50906f55989b \
|
|
51
|
+
--hash=sha256:82da34ae4f639c73ac46e521e00c0a49bf86f717b9fb1f405f133e98731e38dc \
|
|
52
|
+
--hash=sha256:8e1a45d174b6b8589f51dce1cea804aa6c1f72c9c80cba91ae2caabeb6d90540 \
|
|
53
|
+
--hash=sha256:8e3f508d0e9e6236ba2f08d56e33355e434e785e813149a1b8477d3edf69779d \
|
|
54
|
+
--hash=sha256:986e9751d416d7a6eaa2fecdac38da63153d63a4b340ba7d624889c490451500 \
|
|
55
|
+
--hash=sha256:9b73836ba0e16fcbb57c31cf6cbc2907c8d8c790b83679df454b74bd15e0be04 \
|
|
56
|
+
--hash=sha256:9bab4cf571653a8afffb83ce21aa27b51dfe629b526b7b6adec35491fe1fc2ea \
|
|
57
|
+
--hash=sha256:a71d24f40b0cc6798feaa978c7411dc1135b7018e9fc0442db611c139bf58344 \
|
|
58
|
+
--hash=sha256:a9383401d9f116f98cacd4eba6c505a6edb80ba65badfc8e8ed8ae64983bcc44 \
|
|
59
|
+
--hash=sha256:b44f0a0fc7bc6677d38cc80bca1a32814ce6c8f200fb8b3c1a61c9d77eaefbf3 \
|
|
60
|
+
--hash=sha256:b5c6f20d657ae09ae4e30d9d3a21edd13f1219d58cc6f999b9d1bb63be9c1baa \
|
|
61
|
+
--hash=sha256:b61692f0069b3eee2fc8a3a1b7f6c044df9e03fede6ce69b3ca832e1c39f26c5 \
|
|
62
|
+
--hash=sha256:b8c62888c3e49debf37ad9773e3c02f77b0c1e811f8fb0962f2b6c3bbab5b97a \
|
|
63
|
+
--hash=sha256:bd8ea8eb3817b226057cc1c0e7ec4b378dcda52043b972b6ff12b1152178967d \
|
|
64
|
+
--hash=sha256:c5559b492007dc09b4de9b95dab05f0b5e53547aad230cf07e46c7dd017a3be5 \
|
|
65
|
+
--hash=sha256:d0fdd25faece8a1f95e8a3a8006e29701b5cf8dadb4a8132e68f3134637004a5 \
|
|
66
|
+
--hash=sha256:e094dd21f077af8194923fc263cad872eaa1802bb0156fd7e5ae18e99cd86715 \
|
|
67
|
+
--hash=sha256:e41c3993eee896c617dbd8a505085d28b6e84a0445ed9a1f40f95808473cf678 \
|
|
68
|
+
--hash=sha256:e88d304f094f4937bc27ec6a435e218a084168f11ec630c8d5d39b431d08d81d \
|
|
69
|
+
--hash=sha256:e90e3bdf7b5eac005fef631adae9cafde16f922def207b80a7c46b253c18ad20 \
|
|
70
|
+
--hash=sha256:ed2c1493c44d0932f1e55fdb5d1ead658c68288ec5d51b8c4928422d98633ef9 \
|
|
71
|
+
--hash=sha256:edb6f87fc60ff438557291501b3e16c7a77c3b01a52d782cf276dccc7c5dd89c \
|
|
72
|
+
--hash=sha256:efb29f8633bf6630dc89de4fe0353ac3d7e4b70ef7b6e29fb40f00e68c127fa5 \
|
|
73
|
+
--hash=sha256:f6c972474ce691aca74e58d17625450cef153dc4760364cadeb167983ea6d589 \
|
|
74
|
+
--hash=sha256:f6d178ba6dc8e82976c184b65fddde172d054c17237993a3e083efe4f134d55b \
|
|
75
|
+
--hash=sha256:f9a456bdbed52a01c9ab8423bdebab04a5363c78676edc55ab9b58bd13bdf9e1 \
|
|
76
|
+
--hash=sha256:fbdbcd06986ede3ce584083b1dc2afe6808e8943e5cf50ad11183c03aceda25a \
|
|
77
|
+
--hash=sha256:fc66cb50c93554b86db0b6625ab5c6e9051dbf8847c08d93c84918e02e413fb7 \
|
|
78
|
+
--hash=sha256:fff5ef3fe1bba7d6147e5f19e01e5e122ac2c076486887ddcb8d42e663400fbe
|
|
79
|
+
# via openshell
|
|
80
|
+
h11==0.16.0 \
|
|
81
|
+
--hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \
|
|
82
|
+
--hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
|
|
83
|
+
# via httpcore
|
|
84
|
+
httpcore==1.0.9 \
|
|
85
|
+
--hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \
|
|
86
|
+
--hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8
|
|
87
|
+
# via httpx
|
|
88
|
+
httpx==0.28.1 \
|
|
89
|
+
--hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \
|
|
90
|
+
--hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad
|
|
91
|
+
# via openshell
|
|
92
|
+
idna==3.19 \
|
|
93
|
+
--hash=sha256:5e0811a4383b21dc5838069f801c4fb62113b7447663d2530d2bd6e77b49bf15 \
|
|
94
|
+
--hash=sha256:815e7be7a7806d54abb586dc943addc79e8b2ee16915059658cbeff4b1b43bf4
|
|
95
|
+
# via
|
|
96
|
+
# anyio
|
|
97
|
+
# httpx
|
|
98
|
+
openshell==0.0.116 \
|
|
99
|
+
--hash=sha256:5a31eb4e38d7b5d746956404145b7335557f9060ac7a987c65ea5af4d708b3fc
|
|
100
|
+
# via -r scripts/packs/requirements.in
|
|
101
|
+
protobuf==7.36.1 \
|
|
102
|
+
--hash=sha256:0b53ce95272aad50ad25d7ff03373743209822e8ba42ea7fad27d2bee1547d00 \
|
|
103
|
+
--hash=sha256:39c518c05586c016d7874ff6079ee115bcec1ea5fbb1d177fbf7867ef4c67e44 \
|
|
104
|
+
--hash=sha256:3cf2ee25d006cee57294a1196ea43b37feb78e0dcd1e8af5c1aeddb777655aca \
|
|
105
|
+
--hash=sha256:43d3d37b1eb24c113b9b7d02008cac44e423f00b611b7781ae998d7623972969 \
|
|
106
|
+
--hash=sha256:51139351435d9b43d88a55eaa49fb6f737fbb478fb0cbf2cf694d1a04a9d3363 \
|
|
107
|
+
--hash=sha256:7d951e46b3f963d6c264c367c437921de9d5aedd9c3f9612b9077736b4e3ad5c \
|
|
108
|
+
--hash=sha256:97198b77e369a0abd8e262b8f6c7266c55ddb796a3a12c76d7b8881188ed83aa \
|
|
109
|
+
--hash=sha256:d0f6470f0ce2b84e3feaea2d4b816378b37ba4d4aa08a274305373de93e2d524
|
|
110
|
+
# via openshell
|
|
111
|
+
typing-extensions==4.16.0 \
|
|
112
|
+
--hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \
|
|
113
|
+
--hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5
|
|
114
|
+
# via
|
|
115
|
+
# anyio
|
|
116
|
+
# grpcio
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { OperatorError } from "../errors.js";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { lstat, readFile, readdir, realpath, cp, mkdtemp, writeFile, rm, } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { packSchema } from "./model.js";
|
|
8
|
+
export async function openPack(directory) {
|
|
9
|
+
const root = await realpath(directory);
|
|
10
|
+
const manifest = packSchema.parse(JSON.parse(await readFile(join(root, "pack.json"), "utf8")));
|
|
11
|
+
if (new Set(manifest.members.map((item) => item.id)).size !==
|
|
12
|
+
manifest.members.length)
|
|
13
|
+
throw new OperatorError("Pack member IDs must be unique.");
|
|
14
|
+
for (const member of manifest.members) {
|
|
15
|
+
if (new Set(member.requirements.connections.map((item) => item.slot)).size !==
|
|
16
|
+
member.requirements.connections.length)
|
|
17
|
+
throw new OperatorError("Connection slots must be unique within a member.");
|
|
18
|
+
const source = await realpath(resolve(root, member.source));
|
|
19
|
+
if (!source.startsWith(root + sep))
|
|
20
|
+
throw new OperatorError("Claw source must remain inside its pack.");
|
|
21
|
+
}
|
|
22
|
+
const digest = createHash("sha256");
|
|
23
|
+
let size = 0;
|
|
24
|
+
async function visit(path) {
|
|
25
|
+
const metadata = await lstat(path);
|
|
26
|
+
if (metadata.isSymbolicLink() ||
|
|
27
|
+
(metadata.nlink > 1 && !metadata.isDirectory()))
|
|
28
|
+
throw new OperatorError("Pack sources cannot use linked files.");
|
|
29
|
+
if (metadata.isDirectory()) {
|
|
30
|
+
for (const item of (await readdir(path)).sort())
|
|
31
|
+
await visit(join(path, item));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!metadata.isFile() || (size += metadata.size) > 8 * 1024 * 1024)
|
|
35
|
+
throw new OperatorError("Pack source exceeds the 8 MiB limit or is not a regular file.");
|
|
36
|
+
digest.update(JSON.stringify({
|
|
37
|
+
path: relative(root, path).split(sep).join("/"),
|
|
38
|
+
executable: metadata.mode & 0o111,
|
|
39
|
+
sha256: createHash("sha256")
|
|
40
|
+
.update(await readFile(path))
|
|
41
|
+
.digest("hex"),
|
|
42
|
+
}) + "\n");
|
|
43
|
+
}
|
|
44
|
+
await visit(root);
|
|
45
|
+
return { root, manifest, digest: "sha256:" + digest.digest("hex") };
|
|
46
|
+
}
|
|
47
|
+
export async function prepareSource(source, member, connections, native, existing) {
|
|
48
|
+
if (!connections.length)
|
|
49
|
+
return native.source(source.root, source.digest, existing);
|
|
50
|
+
if ((await native.target()).kind !== "openshell")
|
|
51
|
+
throw new OperatorError("Connection file installation requires operator-side OpenShell execution.");
|
|
52
|
+
if (!member.connectionFile)
|
|
53
|
+
throw new OperatorError("Connection file is not declared.");
|
|
54
|
+
const directory = await mkdtemp(join(tmpdir(), "clawscarf-pack-bound-"));
|
|
55
|
+
try {
|
|
56
|
+
await cp(source.root, directory, { recursive: true });
|
|
57
|
+
await writeFile(join(directory, member.source, member.connectionFile), JSON.stringify({
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
connections: connections.map(({ slot, connectionId, name, connectorId }) => ({
|
|
60
|
+
slot,
|
|
61
|
+
connectionId,
|
|
62
|
+
name,
|
|
63
|
+
connectorId,
|
|
64
|
+
})),
|
|
65
|
+
}, null, 2) + "\n", { mode: 0o600 });
|
|
66
|
+
const prepared = await openPack(directory);
|
|
67
|
+
const target = await native.source(directory, prepared.digest, existing);
|
|
68
|
+
const inspected = z
|
|
69
|
+
.object({
|
|
70
|
+
valid: z.literal(true),
|
|
71
|
+
manifest: z.object({
|
|
72
|
+
workspace: z.object({
|
|
73
|
+
files: z.array(z.object({ source: z.string(), path: z.string() })),
|
|
74
|
+
}),
|
|
75
|
+
}),
|
|
76
|
+
})
|
|
77
|
+
.parse(await native.run([
|
|
78
|
+
"claws",
|
|
79
|
+
"inspect",
|
|
80
|
+
join(target, member.source),
|
|
81
|
+
"--json",
|
|
82
|
+
]));
|
|
83
|
+
if (!inspected.manifest.workspace.files.some((file) => file.source === member.connectionFile))
|
|
84
|
+
throw new OperatorError("connectionFile must be declared in the native Claw workspace.files; its ownership cannot be inferred.");
|
|
85
|
+
return target;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await rm(directory, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Operator-only bridge to the pinned official OpenShell SDK; no runtime secrets."""
|
|
2
|
+
import base64
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from openshell.sandbox import SandboxClient
|
|
9
|
+
|
|
10
|
+
MAX_INPUT = 16 * 1024 * 1024
|
|
11
|
+
MAX_OUTPUT = 4 * 1024 * 1024
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def parse_request():
|
|
15
|
+
raw = sys.stdin.buffer.read(MAX_INPUT + 1)
|
|
16
|
+
if len(raw) > MAX_INPUT:
|
|
17
|
+
raise ValueError("Operator payload too large")
|
|
18
|
+
value = json.loads(raw)
|
|
19
|
+
if not isinstance(value, dict) or set(value) != {
|
|
20
|
+
"gateway", "sandboxId", "command", "stdin"
|
|
21
|
+
}:
|
|
22
|
+
raise ValueError("Invalid operator request")
|
|
23
|
+
gateway = value["gateway"]
|
|
24
|
+
sandbox_id = value["sandboxId"]
|
|
25
|
+
command = value["command"]
|
|
26
|
+
encoded = value["stdin"]
|
|
27
|
+
if not isinstance(gateway, str) or not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_.-]{0,127}", gateway):
|
|
28
|
+
raise ValueError("Invalid gateway name")
|
|
29
|
+
if not isinstance(sandbox_id, str) or str(UUID(sandbox_id)) != sandbox_id:
|
|
30
|
+
raise ValueError("Invalid sandbox UUID")
|
|
31
|
+
if not isinstance(command, list) or not 1 <= len(command) <= 128:
|
|
32
|
+
raise ValueError("Invalid command")
|
|
33
|
+
if not all(isinstance(arg, str) and len(arg) <= 8192 and "\0" not in arg for arg in command):
|
|
34
|
+
raise ValueError("Invalid command argument")
|
|
35
|
+
if not isinstance(encoded, str):
|
|
36
|
+
raise ValueError("Invalid stdin")
|
|
37
|
+
return gateway, sandbox_id, command, base64.b64decode(encoded, validate=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def main():
|
|
41
|
+
try:
|
|
42
|
+
gateway, sandbox_id, command, stdin = parse_request()
|
|
43
|
+
except (ValueError, TypeError):
|
|
44
|
+
sys.stderr.write("Invalid operator execution request.\n")
|
|
45
|
+
return 2
|
|
46
|
+
try:
|
|
47
|
+
with SandboxClient.from_active_cluster(cluster=gateway, timeout=120) as client:
|
|
48
|
+
result = client.exec(
|
|
49
|
+
sandbox_id, command,
|
|
50
|
+
env={"OPENCLAW_EXPERIMENTAL_CLAWS": "1"},
|
|
51
|
+
stdin=stdin,
|
|
52
|
+
timeout_seconds=110,
|
|
53
|
+
)
|
|
54
|
+
except Exception:
|
|
55
|
+
sys.stderr.write("OpenShell execution has no confirmed completion; inspect native state before retrying.\n")
|
|
56
|
+
return 3
|
|
57
|
+
if result.exit_code != 0:
|
|
58
|
+
sys.stderr.write(f"Native command exited with status {result.exit_code}; inspect native state before retrying.\n")
|
|
59
|
+
return 1
|
|
60
|
+
if len(result.stdout.encode("utf8")) > MAX_OUTPUT:
|
|
61
|
+
sys.stderr.write("Native response exceeded the operator output limit; inspect native state before retrying.\n")
|
|
62
|
+
return 3
|
|
63
|
+
sys.stdout.write(result.stdout)
|
|
64
|
+
return 0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
sys.exit(main())
|