@sema-ai/registry-core 0.10.3
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/CHANGELOG.md +131 -0
- package/README.md +84 -0
- package/dist/api/auth-bridge.d.ts +332 -0
- package/dist/api/auth-bridge.d.ts.map +1 -0
- package/dist/api/auth-bridge.js +211 -0
- package/dist/api/auth-bridge.js.map +1 -0
- package/dist/api/auth.d.ts +258 -0
- package/dist/api/auth.d.ts.map +1 -0
- package/dist/api/auth.js +204 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/scopes.d.ts +353 -0
- package/dist/api/scopes.d.ts.map +1 -0
- package/dist/api/scopes.js +229 -0
- package/dist/api/scopes.js.map +1 -0
- package/dist/bundle.d.ts +14 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +68 -0
- package/dist/bundle.js.map +1 -0
- package/dist/config-fns.d.ts +236 -0
- package/dist/config-fns.d.ts.map +1 -0
- package/dist/config-fns.js +272 -0
- package/dist/config-fns.js.map +1 -0
- package/dist/cross-domain.d.ts +35 -0
- package/dist/cross-domain.d.ts.map +1 -0
- package/dist/cross-domain.js +119 -0
- package/dist/cross-domain.js.map +1 -0
- package/dist/file-edit.d.ts +37 -0
- package/dist/file-edit.d.ts.map +1 -0
- package/dist/file-edit.js +126 -0
- package/dist/file-edit.js.map +1 -0
- package/dist/file-store.d.ts +39 -0
- package/dist/file-store.d.ts.map +1 -0
- package/dist/file-store.js +142 -0
- package/dist/file-store.js.map +1 -0
- package/dist/fleet.d.ts +479 -0
- package/dist/fleet.d.ts.map +1 -0
- package/dist/fleet.js +303 -0
- package/dist/fleet.js.map +1 -0
- package/dist/hash.d.ts +33 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +60 -0
- package/dist/hash.js.map +1 -0
- package/dist/hooks.d.ts +5478 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +628 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/local-load.d.ts +33 -0
- package/dist/local-load.d.ts.map +1 -0
- package/dist/local-load.js +164 -0
- package/dist/local-load.js.map +1 -0
- package/dist/migrate.d.ts +245 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +543 -0
- package/dist/migrate.js.map +1 -0
- package/dist/node.d.ts +12 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +12 -0
- package/dist/node.js.map +1 -0
- package/dist/reader.d.ts +19 -0
- package/dist/reader.d.ts.map +1 -0
- package/dist/reader.js +2 -0
- package/dist/reader.js.map +1 -0
- package/dist/remote-exec.d.ts +275 -0
- package/dist/remote-exec.d.ts.map +1 -0
- package/dist/remote-exec.js +183 -0
- package/dist/remote-exec.js.map +1 -0
- package/dist/resolve-roster.d.ts +29 -0
- package/dist/resolve-roster.d.ts.map +1 -0
- package/dist/resolve-roster.js +105 -0
- package/dist/resolve-roster.js.map +1 -0
- package/dist/safety-merge-spec.d.ts +328 -0
- package/dist/safety-merge-spec.d.ts.map +1 -0
- package/dist/safety-merge-spec.js +65 -0
- package/dist/safety-merge-spec.js.map +1 -0
- package/dist/scheduler-store-node.d.ts +15 -0
- package/dist/scheduler-store-node.d.ts.map +1 -0
- package/dist/scheduler-store-node.js +43 -0
- package/dist/scheduler-store-node.js.map +1 -0
- package/dist/scheduler-store.d.ts +65 -0
- package/dist/scheduler-store.d.ts.map +1 -0
- package/dist/scheduler-store.js +59 -0
- package/dist/scheduler-store.js.map +1 -0
- package/dist/secret-refs.d.ts +34 -0
- package/dist/secret-refs.d.ts.map +1 -0
- package/dist/secret-refs.js +49 -0
- package/dist/secret-refs.js.map +1 -0
- package/dist/sha256.d.ts +17 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +115 -0
- package/dist/sha256.js.map +1 -0
- package/dist/skills-manifest.d.ts +13 -0
- package/dist/skills-manifest.d.ts.map +1 -0
- package/dist/skills-manifest.js +55 -0
- package/dist/skills-manifest.js.map +1 -0
- package/dist/types.d.ts +9911 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1727 -0
- package/dist/types.js.map +1 -0
- package/package.json +130 -0
package/dist/reader.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DomainConfig, DomainName, EffectiveConfig } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The MINIMAL read surface a consumer needs to resolve config — a local file source (FileConfigStore), the
|
|
4
|
+
* TOC desktop, or a test fixture implements this WITHOUT stubbing the ~32 publish/RBAC/worker-status/lifecycle
|
|
5
|
+
* methods of sema-registry's full ConfigStore.
|
|
6
|
+
*
|
|
7
|
+
* sema-registry's fat `ConfigStore` should `extends ConfigReader` (every store impl already has all three
|
|
8
|
+
* methods, so the extension is satisfied automatically). The signatures here MUST match store.ts exactly
|
|
9
|
+
* (incl. the `<K extends DomainName>` generic and the `| undefined` on getDomain) or that `extends` fails.
|
|
10
|
+
*/
|
|
11
|
+
export interface ConfigReader {
|
|
12
|
+
/** The stored payload for a domain, validated; `undefined` if never set. */
|
|
13
|
+
getDomain<K extends DomainName>(domain: K): Promise<DomainConfig[K] | undefined>;
|
|
14
|
+
/** All domains merged (defaults for unset) + a monotonic version — what a consumer pulls. */
|
|
15
|
+
getEffective(): Promise<EffectiveConfig>;
|
|
16
|
+
/** The current monotonic version ALONE — a cheap read so a conditional poll can short-circuit. */
|
|
17
|
+
getVersion(): Promise<number>;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,SAAS,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACjF,6FAA6F;IAC7F,YAAY,IAAI,OAAO,CAAC,eAAe,CAAC,CAAC;IACzC,kGAAkG;IAClG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CAC/B"}
|
package/dist/reader.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader.js","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
|
|
3
|
+
*
|
|
4
|
+
* Per Clay's decision the exec layer is user-configurable & pluggable. This SCHEMA is a FORWARD-COMPATIBLE
|
|
5
|
+
* SUPERSET: it carries both the backends sema-server already implements today AND the TOC single-machine
|
|
6
|
+
* backends Clay added (the service implements those in PHASES — host + e2b first, local/remote-docker
|
|
7
|
+
* fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
|
|
8
|
+
* FLEET (implemented today, sema-server/src/config.ts):
|
|
9
|
+
* - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
|
|
10
|
+
* - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
|
|
11
|
+
* - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
|
|
12
|
+
* - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
|
|
13
|
+
* TOC single-machine (Clay; service implements in phases — macOS + Linux first):
|
|
14
|
+
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
15
|
+
* agent). No isolation. macOS/Linux.
|
|
16
|
+
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
17
|
+
* - `remote-docker` a user-specified REMOTE host's docker (offload; isolated).
|
|
18
|
+
*
|
|
19
|
+
* SECRET BOUNDARY (identical to the rest of the contract): every secret VALUE the consumer reads from its own
|
|
20
|
+
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_*, the remote-docker TLS/SSH creds) is carried here as an
|
|
21
|
+
* env-NAME ref (the `ENV_NAME` regex from types.ts) — NEVER a literal value. The consumer resolves NAME → value
|
|
22
|
+
* from its own env (service env / k8s secret / the TOC local `.env`), so this package never carries a
|
|
23
|
+
* credential. This is SCHEMA + secret-as-env-NAME ONLY; the service owns the adapter implementations.
|
|
24
|
+
*/
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodObject<{
|
|
27
|
+
provider: z.ZodLiteral<"host">;
|
|
28
|
+
/** Working ROOT for the agent's tools; absent = the workspace/cwd. (≡ sema-server's resolved
|
|
29
|
+
* `workspaceBase` — the adapter runs each task under `<workdir>/ai-agent-host-<id>`.) */
|
|
30
|
+
workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
31
|
+
/** Optional writable-path allowlist bounding what the host-exec tools may touch (defence for a TOC box). */
|
|
32
|
+
allowedPaths: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
33
|
+
/** Shell to spawn (absent = the platform default). */
|
|
34
|
+
shell: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
35
|
+
/** Per-COMMAND timeout (ms) for the host lane — host is long-lived so this bounds a single exec, not the
|
|
36
|
+
* task (mirrors e2b/k8s `timeoutMs`; = sema-server's `commandTimeoutMs`). Absent = adapter default. */
|
|
37
|
+
commandTimeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
provider: "host";
|
|
40
|
+
workdir?: string | undefined;
|
|
41
|
+
allowedPaths?: string[] | undefined;
|
|
42
|
+
shell?: string | undefined;
|
|
43
|
+
commandTimeoutMs?: number | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
provider: "host";
|
|
46
|
+
workdir?: string | undefined;
|
|
47
|
+
allowedPaths?: string[] | undefined;
|
|
48
|
+
shell?: string | undefined;
|
|
49
|
+
commandTimeoutMs?: number | undefined;
|
|
50
|
+
}>, z.ZodObject<{
|
|
51
|
+
image: z.ZodEffects<z.ZodString, string, string>;
|
|
52
|
+
memory: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
53
|
+
cpus: z.ZodOptional<z.ZodNumber>;
|
|
54
|
+
network: z.ZodOptional<z.ZodEnum<["none", "bridge", "host"]>>;
|
|
55
|
+
workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
56
|
+
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
57
|
+
provider: z.ZodLiteral<"local-docker">;
|
|
58
|
+
}, "strip", z.ZodTypeAny, {
|
|
59
|
+
provider: "local-docker";
|
|
60
|
+
image: string;
|
|
61
|
+
memory?: string | undefined;
|
|
62
|
+
workdir?: string | undefined;
|
|
63
|
+
cpus?: number | undefined;
|
|
64
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
65
|
+
mountPath?: string | undefined;
|
|
66
|
+
}, {
|
|
67
|
+
provider: "local-docker";
|
|
68
|
+
image: string;
|
|
69
|
+
memory?: string | undefined;
|
|
70
|
+
workdir?: string | undefined;
|
|
71
|
+
cpus?: number | undefined;
|
|
72
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
73
|
+
mountPath?: string | undefined;
|
|
74
|
+
}>, z.ZodObject<{
|
|
75
|
+
/** The remote docker endpoint — a DOCKER_HOST url: `ssh://user@host` or `tcp://host:2376`. A `user@` is OK
|
|
76
|
+
* (it's the ssh target, not a secret); an embedded PASSWORD is rejected — credentials go in the env-NAME
|
|
77
|
+
* fields below, NEVER in the URL (the secret-as-env-NAME boundary). */
|
|
78
|
+
dockerHost: z.ZodEffects<z.ZodString, string, string>;
|
|
79
|
+
/** env-NAMEs for a tcp+TLS endpoint (NEVER the cert/key bytes). */
|
|
80
|
+
tlsCertEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
81
|
+
tlsKeyEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
82
|
+
tlsCaEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
83
|
+
/** env-NAME for the SSH private key when dockerHost is `ssh://…` (NEVER the key value). */
|
|
84
|
+
sshKeyEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
85
|
+
image: z.ZodEffects<z.ZodString, string, string>;
|
|
86
|
+
memory: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
87
|
+
cpus: z.ZodOptional<z.ZodNumber>;
|
|
88
|
+
network: z.ZodOptional<z.ZodEnum<["none", "bridge", "host"]>>;
|
|
89
|
+
workdir: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
90
|
+
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
91
|
+
provider: z.ZodLiteral<"remote-docker">;
|
|
92
|
+
}, "strip", z.ZodTypeAny, {
|
|
93
|
+
provider: "remote-docker";
|
|
94
|
+
image: string;
|
|
95
|
+
dockerHost: string;
|
|
96
|
+
memory?: string | undefined;
|
|
97
|
+
workdir?: string | undefined;
|
|
98
|
+
cpus?: number | undefined;
|
|
99
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
100
|
+
mountPath?: string | undefined;
|
|
101
|
+
tlsCertEnv?: string | undefined;
|
|
102
|
+
tlsKeyEnv?: string | undefined;
|
|
103
|
+
tlsCaEnv?: string | undefined;
|
|
104
|
+
sshKeyEnv?: string | undefined;
|
|
105
|
+
}, {
|
|
106
|
+
provider: "remote-docker";
|
|
107
|
+
image: string;
|
|
108
|
+
dockerHost: string;
|
|
109
|
+
memory?: string | undefined;
|
|
110
|
+
workdir?: string | undefined;
|
|
111
|
+
cpus?: number | undefined;
|
|
112
|
+
network?: "host" | "none" | "bridge" | undefined;
|
|
113
|
+
mountPath?: string | undefined;
|
|
114
|
+
tlsCertEnv?: string | undefined;
|
|
115
|
+
tlsKeyEnv?: string | undefined;
|
|
116
|
+
tlsCaEnv?: string | undefined;
|
|
117
|
+
sshKeyEnv?: string | undefined;
|
|
118
|
+
}>, z.ZodObject<{
|
|
119
|
+
provider: z.ZodLiteral<"e2b">;
|
|
120
|
+
/** env-NAME holding the E2B API key (service: E2B_API_KEY). NEVER the key. */
|
|
121
|
+
apiKeyEnv: z.ZodEffects<z.ZodString, string, string>;
|
|
122
|
+
/** E2B template id (service: E2B_TEMPLATE). */
|
|
123
|
+
template: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
124
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
125
|
+
/** Idle/liveness bound for a command (service: E2B_LIVENESS_MS, the [49] hang-fix). */
|
|
126
|
+
livenessMs: z.ZodOptional<z.ZodNumber>;
|
|
127
|
+
allowInternetAccess: z.ZodOptional<z.ZodBoolean>;
|
|
128
|
+
/** Which of THIS process's env-NAMEs to forward into the sandbox (service: E2B_SANDBOX_ENV, a CSV of NAMEs). */
|
|
129
|
+
sandboxEnv: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
|
|
130
|
+
}, "strip", z.ZodTypeAny, {
|
|
131
|
+
provider: "e2b";
|
|
132
|
+
apiKeyEnv: string;
|
|
133
|
+
template?: string | undefined;
|
|
134
|
+
timeoutMs?: number | undefined;
|
|
135
|
+
livenessMs?: number | undefined;
|
|
136
|
+
allowInternetAccess?: boolean | undefined;
|
|
137
|
+
sandboxEnv?: string[] | undefined;
|
|
138
|
+
}, {
|
|
139
|
+
provider: "e2b";
|
|
140
|
+
apiKeyEnv: string;
|
|
141
|
+
template?: string | undefined;
|
|
142
|
+
timeoutMs?: number | undefined;
|
|
143
|
+
livenessMs?: number | undefined;
|
|
144
|
+
allowInternetAccess?: boolean | undefined;
|
|
145
|
+
sandboxEnv?: string[] | undefined;
|
|
146
|
+
}>, z.ZodObject<{
|
|
147
|
+
provider: z.ZodLiteral<"k8s">;
|
|
148
|
+
/** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
|
|
149
|
+
image: z.ZodEffects<z.ZodString, string, string>;
|
|
150
|
+
/** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. */
|
|
151
|
+
apiUrl: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
152
|
+
/** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
|
|
153
|
+
tokenEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
154
|
+
/** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
|
|
155
|
+
caCertEnv: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
156
|
+
insecureTls: z.ZodOptional<z.ZodBoolean>;
|
|
157
|
+
namespace: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
158
|
+
runtimeClass: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
159
|
+
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
160
|
+
timeoutMs: z.ZodOptional<z.ZodNumber>;
|
|
161
|
+
/** Sandbox pod resource limits (service: K8S_MEMORY / K8S_CPU; adapter defaults 2Gi / 1 cpu). */
|
|
162
|
+
memory: z.ZodOptional<z.ZodString>;
|
|
163
|
+
cpu: z.ZodOptional<z.ZodString>;
|
|
164
|
+
/** Workspace-durable suspend/resume via MinIO/S3 (service: MINIO_*). Creds are env-NAME refs; the sandbox
|
|
165
|
+
* only ever sees a presigned URL. accessKeyEnv + secretKeyEnv required to enable (mirrors the service's
|
|
166
|
+
* "all three of endpoint/access/secret required" gate). */
|
|
167
|
+
s3Snapshot: z.ZodOptional<z.ZodObject<{
|
|
168
|
+
endpoint: z.ZodEffects<z.ZodString, string, string>;
|
|
169
|
+
bucket: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
170
|
+
accessKeyEnv: z.ZodEffects<z.ZodString, string, string>;
|
|
171
|
+
secretKeyEnv: z.ZodEffects<z.ZodString, string, string>;
|
|
172
|
+
region: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
173
|
+
keyPrefix: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
174
|
+
presignTtlSec: z.ZodOptional<z.ZodNumber>;
|
|
175
|
+
}, "strip", z.ZodTypeAny, {
|
|
176
|
+
endpoint: string;
|
|
177
|
+
accessKeyEnv: string;
|
|
178
|
+
secretKeyEnv: string;
|
|
179
|
+
bucket?: string | undefined;
|
|
180
|
+
region?: string | undefined;
|
|
181
|
+
keyPrefix?: string | undefined;
|
|
182
|
+
presignTtlSec?: number | undefined;
|
|
183
|
+
}, {
|
|
184
|
+
endpoint: string;
|
|
185
|
+
accessKeyEnv: string;
|
|
186
|
+
secretKeyEnv: string;
|
|
187
|
+
bucket?: string | undefined;
|
|
188
|
+
region?: string | undefined;
|
|
189
|
+
keyPrefix?: string | undefined;
|
|
190
|
+
presignTtlSec?: number | undefined;
|
|
191
|
+
}>>;
|
|
192
|
+
}, "strip", z.ZodTypeAny, {
|
|
193
|
+
provider: "k8s";
|
|
194
|
+
image: string;
|
|
195
|
+
cpu?: string | undefined;
|
|
196
|
+
memory?: string | undefined;
|
|
197
|
+
mountPath?: string | undefined;
|
|
198
|
+
timeoutMs?: number | undefined;
|
|
199
|
+
apiUrl?: string | undefined;
|
|
200
|
+
tokenEnv?: string | undefined;
|
|
201
|
+
caCertEnv?: string | undefined;
|
|
202
|
+
insecureTls?: boolean | undefined;
|
|
203
|
+
namespace?: string | undefined;
|
|
204
|
+
runtimeClass?: string | undefined;
|
|
205
|
+
s3Snapshot?: {
|
|
206
|
+
endpoint: string;
|
|
207
|
+
accessKeyEnv: string;
|
|
208
|
+
secretKeyEnv: string;
|
|
209
|
+
bucket?: string | undefined;
|
|
210
|
+
region?: string | undefined;
|
|
211
|
+
keyPrefix?: string | undefined;
|
|
212
|
+
presignTtlSec?: number | undefined;
|
|
213
|
+
} | undefined;
|
|
214
|
+
}, {
|
|
215
|
+
provider: "k8s";
|
|
216
|
+
image: string;
|
|
217
|
+
cpu?: string | undefined;
|
|
218
|
+
memory?: string | undefined;
|
|
219
|
+
mountPath?: string | undefined;
|
|
220
|
+
timeoutMs?: number | undefined;
|
|
221
|
+
apiUrl?: string | undefined;
|
|
222
|
+
tokenEnv?: string | undefined;
|
|
223
|
+
caCertEnv?: string | undefined;
|
|
224
|
+
insecureTls?: boolean | undefined;
|
|
225
|
+
namespace?: string | undefined;
|
|
226
|
+
runtimeClass?: string | undefined;
|
|
227
|
+
s3Snapshot?: {
|
|
228
|
+
endpoint: string;
|
|
229
|
+
accessKeyEnv: string;
|
|
230
|
+
secretKeyEnv: string;
|
|
231
|
+
bucket?: string | undefined;
|
|
232
|
+
region?: string | undefined;
|
|
233
|
+
keyPrefix?: string | undefined;
|
|
234
|
+
presignTtlSec?: number | undefined;
|
|
235
|
+
} | undefined;
|
|
236
|
+
}>, z.ZodObject<{
|
|
237
|
+
provider: z.ZodLiteral<"ssh">;
|
|
238
|
+
host: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
239
|
+
port: z.ZodOptional<z.ZodNumber>;
|
|
240
|
+
username: z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>;
|
|
241
|
+
/** env-NAME holding the SSH private key (service: SSH_PRIVATE_KEY). NEVER the key value. */
|
|
242
|
+
privateKeyEnv: z.ZodEffects<z.ZodString, string, string>;
|
|
243
|
+
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
244
|
+
}, "strip", z.ZodTypeAny, {
|
|
245
|
+
provider: "ssh";
|
|
246
|
+
host: string;
|
|
247
|
+
username: string;
|
|
248
|
+
privateKeyEnv: string;
|
|
249
|
+
mountPath?: string | undefined;
|
|
250
|
+
port?: number | undefined;
|
|
251
|
+
}, {
|
|
252
|
+
provider: "ssh";
|
|
253
|
+
host: string;
|
|
254
|
+
username: string;
|
|
255
|
+
privateKeyEnv: string;
|
|
256
|
+
mountPath?: string | undefined;
|
|
257
|
+
port?: number | undefined;
|
|
258
|
+
}>, z.ZodObject<{
|
|
259
|
+
provider: z.ZodLiteral<"adb">;
|
|
260
|
+
serial: z.ZodEffects<z.ZodString, string, string>;
|
|
261
|
+
adbPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
262
|
+
mountPath: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
|
|
263
|
+
}, "strip", z.ZodTypeAny, {
|
|
264
|
+
provider: "adb";
|
|
265
|
+
serial: string;
|
|
266
|
+
mountPath?: string | undefined;
|
|
267
|
+
adbPath?: string | undefined;
|
|
268
|
+
}, {
|
|
269
|
+
provider: "adb";
|
|
270
|
+
serial: string;
|
|
271
|
+
mountPath?: string | undefined;
|
|
272
|
+
adbPath?: string | undefined;
|
|
273
|
+
}>]>;
|
|
274
|
+
export type RemoteExecSpec = z.infer<typeof RemoteExecSpec>;
|
|
275
|
+
//# sourceMappingURL=remote-exec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-exec.d.ts","sourceRoot":"","sources":["../src/remote-exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqBxB,eAAO,MAAM,cAAc;;IAIvB;8FAC0F;;IAE1F,4GAA4G;;IAE5G,sDAAsD;;IAEtD;4GACwG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAaxG;;4EAEwE;;IAmCxE,mEAAmE;;;;IAInE,2FAA2F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAO3F,8EAA8E;;IAE9E,+CAA+C;;;IAG/C,uFAAuF;;;IAGvF,gHAAgH;;;;;;;;;;;;;;;;;;;;IAOhH,+FAA+F;;IAE/F,yGAAyG;;IAEzG,oFAAoF;;IAEpF,4FAA4F;;;;;;;IAO5F,iGAAiG;;;IAGjG;;gEAE4D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAsB5D,4FAA4F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAY9F,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `remoteExec` — the execution-substrate contract (NET-NEW; not part of DOMAIN_SCHEMAS today).
|
|
3
|
+
*
|
|
4
|
+
* Per Clay's decision the exec layer is user-configurable & pluggable. This SCHEMA is a FORWARD-COMPATIBLE
|
|
5
|
+
* SUPERSET: it carries both the backends sema-server already implements today AND the TOC single-machine
|
|
6
|
+
* backends Clay added (the service implements those in PHASES — host + e2b first, local/remote-docker
|
|
7
|
+
* fast-follow). The schema LEADS; runtime support is phased. The discriminator is `provider`:
|
|
8
|
+
* FLEET (implemented today, sema-server/src/config.ts):
|
|
9
|
+
* - `e2b` per-task E2B Firecracker VM (isolated, suspendable). The default code-agent worker.
|
|
10
|
+
* - `k8s` per-task Kata pod on our k3s (isolated, NOT suspendable). Self-hosted sandbox lane.
|
|
11
|
+
* - `ssh` a real host over SSH (NOT isolated/suspendable). AI-orchestrated batch deploy.
|
|
12
|
+
* - `adb` a real device over ADB (NOT isolated/suspendable). On-device APP testing.
|
|
13
|
+
* TOC single-machine (Clay; service implements in phases — macOS + Linux first):
|
|
14
|
+
* - `host` run tools DIRECTLY on this machine, no container (the TOC default, like a local coding
|
|
15
|
+
* agent). No isolation. macOS/Linux.
|
|
16
|
+
* - `local-docker` containers on THIS machine's docker (isolation for parallel/untrusted fan-out).
|
|
17
|
+
* - `remote-docker` a user-specified REMOTE host's docker (offload; isolated).
|
|
18
|
+
*
|
|
19
|
+
* SECRET BOUNDARY (identical to the rest of the contract): every secret VALUE the consumer reads from its own
|
|
20
|
+
* env (E2B_API_KEY, K8S_TOKEN, SSH_PRIVATE_KEY, MINIO_*, the remote-docker TLS/SSH creds) is carried here as an
|
|
21
|
+
* env-NAME ref (the `ENV_NAME` regex from types.ts) — NEVER a literal value. The consumer resolves NAME → value
|
|
22
|
+
* from its own env (service env / k8s secret / the TOC local `.env`), so this package never carries a
|
|
23
|
+
* credential. This is SCHEMA + secret-as-env-NAME ONLY; the service owns the adapter implementations.
|
|
24
|
+
*/
|
|
25
|
+
import { z } from "zod";
|
|
26
|
+
import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken, hasNul, urlQueryFragmentHasSecret } from "./types.js";
|
|
27
|
+
/** A non-empty exec-bound string (an image / path / shell / arg the adapter feeds to spawn()/fs). Rejects a NUL
|
|
28
|
+
* byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
|
|
29
|
+
const execStr = z.string().min(1).refine((s) => !hasNul(s), "must not contain a NUL byte");
|
|
30
|
+
/** Container knobs shared by the two docker lanes (local-docker / remote-docker). `network` defaults to
|
|
31
|
+
* `none` at the adapter (the safe posture for untrusted code — see DUAL-MODE-DESIGN.md). */
|
|
32
|
+
const DOCKER_FIELDS = {
|
|
33
|
+
/** Sandbox image (required — a run must be reproducible). */
|
|
34
|
+
image: execStr,
|
|
35
|
+
/** docker `--memory` (e.g. "2g"). */
|
|
36
|
+
memory: execStr.optional(),
|
|
37
|
+
/** docker `--cpus` (e.g. 1.5). */
|
|
38
|
+
cpus: z.number().finite().positive().optional(),
|
|
39
|
+
network: z.enum(["none", "bridge", "host"]).optional(),
|
|
40
|
+
workdir: execStr.optional(),
|
|
41
|
+
mountPath: execStr.optional(),
|
|
42
|
+
};
|
|
43
|
+
export const RemoteExecSpec = z.discriminatedUnion("provider", [
|
|
44
|
+
// ── host: run on THIS machine, no container (TOC default; macOS/Linux). No isolation, no secrets.
|
|
45
|
+
z.object({
|
|
46
|
+
provider: z.literal("host"),
|
|
47
|
+
/** Working ROOT for the agent's tools; absent = the workspace/cwd. (≡ sema-server's resolved
|
|
48
|
+
* `workspaceBase` — the adapter runs each task under `<workdir>/ai-agent-host-<id>`.) */
|
|
49
|
+
workdir: execStr.optional(),
|
|
50
|
+
/** Optional writable-path allowlist bounding what the host-exec tools may touch (defence for a TOC box). */
|
|
51
|
+
allowedPaths: z.array(execStr).optional(),
|
|
52
|
+
/** Shell to spawn (absent = the platform default). */
|
|
53
|
+
shell: execStr.optional(),
|
|
54
|
+
/** Per-COMMAND timeout (ms) for the host lane — host is long-lived so this bounds a single exec, not the
|
|
55
|
+
* task (mirrors e2b/k8s `timeoutMs`; = sema-server's `commandTimeoutMs`). Absent = adapter default. */
|
|
56
|
+
commandTimeoutMs: z.number().int().positive().optional(),
|
|
57
|
+
}),
|
|
58
|
+
// ── local-docker: containers on this machine's docker (isolated; enables parallel fan-out). No secrets
|
|
59
|
+
// (local docker.sock). Service implements this fast-follow after host.
|
|
60
|
+
z.object({ provider: z.literal("local-docker"), ...DOCKER_FIELDS }),
|
|
61
|
+
// ── remote-docker: a user-specified remote host's docker. Creds (TLS for tcp://, key for ssh://) are
|
|
62
|
+
// env-NAME refs, never bytes. Service implements this fast-follow.
|
|
63
|
+
z.object({
|
|
64
|
+
provider: z.literal("remote-docker"),
|
|
65
|
+
...DOCKER_FIELDS,
|
|
66
|
+
/** The remote docker endpoint — a DOCKER_HOST url: `ssh://user@host` or `tcp://host:2376`. A `user@` is OK
|
|
67
|
+
* (it's the ssh target, not a secret); an embedded PASSWORD is rejected — credentials go in the env-NAME
|
|
68
|
+
* fields below, NEVER in the URL (the secret-as-env-NAME boundary). */
|
|
69
|
+
dockerHost: z
|
|
70
|
+
.string()
|
|
71
|
+
.min(1)
|
|
72
|
+
.refine((s) => {
|
|
73
|
+
if (hasNul(s))
|
|
74
|
+
return false; // a NUL in DOCKER_HOST crashes the adapter at spawn — reject (R20)
|
|
75
|
+
try {
|
|
76
|
+
const u = new URL(s);
|
|
77
|
+
if (u.password)
|
|
78
|
+
return false; // a `user@` ssh target is fine; an embedded PASSWORD is not
|
|
79
|
+
// a secret can also ride in the username (`ssh://sk-live-…@host`) or the query/fragment — scan them
|
|
80
|
+
// (Round-4 codex). The userinfo password is already rejected above.
|
|
81
|
+
let user = u.username;
|
|
82
|
+
try {
|
|
83
|
+
user = decodeURIComponent(u.username);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
/* keep raw */
|
|
87
|
+
}
|
|
88
|
+
if (user && containsSecretToken(user))
|
|
89
|
+
return false;
|
|
90
|
+
return !urlQueryFragmentHasSecret(u);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// unparseable (e.g. a bare socket path) → reject a `:pw@` userinfo-password pattern AND any
|
|
94
|
+
// secret-shaped TOKEN anywhere (containsSecretToken tokenizes on URL/query delimiters incl =&, so a
|
|
95
|
+
// mid-string `/var/run/sk-…` or `socket?token=sk-…` is caught despite the ^-anchored regex). R4/5 codex.
|
|
96
|
+
if (/:[^/@]+@/.test(s))
|
|
97
|
+
return false;
|
|
98
|
+
if (containsSecretToken(s))
|
|
99
|
+
return false;
|
|
100
|
+
try {
|
|
101
|
+
return !containsSecretToken(decodeURIComponent(s)); // also catch percent-encoded secrets
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return true; // malformed %-escape → the raw scan above already passed
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, "no credentials in dockerHost (password / secret-shaped user/query/fragment) — put them in the env-NAME fields (sshKeyEnv / tls*Env)"),
|
|
108
|
+
/** env-NAMEs for a tcp+TLS endpoint (NEVER the cert/key bytes). */
|
|
109
|
+
tlsCertEnv: ENV_NAME.optional(),
|
|
110
|
+
tlsKeyEnv: ENV_NAME.optional(),
|
|
111
|
+
tlsCaEnv: ENV_NAME.optional(),
|
|
112
|
+
/** env-NAME for the SSH private key when dockerHost is `ssh://…` (NEVER the key value). */
|
|
113
|
+
sshKeyEnv: ENV_NAME.optional(),
|
|
114
|
+
}),
|
|
115
|
+
// ── e2b: the user's E2B account (service provider "e2b"; REMOTE_EXEC=e2b + E2B_API_KEY).
|
|
116
|
+
z.object({
|
|
117
|
+
provider: z.literal("e2b"),
|
|
118
|
+
/** env-NAME holding the E2B API key (service: E2B_API_KEY). NEVER the key. */
|
|
119
|
+
apiKeyEnv: ENV_NAME,
|
|
120
|
+
/** E2B template id (service: E2B_TEMPLATE). */
|
|
121
|
+
template: execStr.optional(),
|
|
122
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
123
|
+
/** Idle/liveness bound for a command (service: E2B_LIVENESS_MS, the [49] hang-fix). */
|
|
124
|
+
livenessMs: z.number().int().positive().optional(),
|
|
125
|
+
allowInternetAccess: z.boolean().optional(),
|
|
126
|
+
/** Which of THIS process's env-NAMEs to forward into the sandbox (service: E2B_SANDBOX_ENV, a CSV of NAMEs). */
|
|
127
|
+
sandboxEnv: z.array(ENV_NAME).optional(),
|
|
128
|
+
}),
|
|
129
|
+
// ── k8s: per-task Kata pod (service provider "k8s"; REMOTE_EXEC=k8s + K8S_SANDBOX_IMAGE).
|
|
130
|
+
z.object({
|
|
131
|
+
provider: z.literal("k8s"),
|
|
132
|
+
/** Sandbox container image (service: K8S_SANDBOX_IMAGE). Required so a run is reproducible. */
|
|
133
|
+
image: execStr,
|
|
134
|
+
/** Kube API URL (service: K8S_API_URL); absent → in-cluster service account. NO embedded credentials. */
|
|
135
|
+
apiUrl: NON_SECRET_URL.optional(),
|
|
136
|
+
/** env-NAME holding the kube bearer token (service: K8S_TOKEN). NEVER the token. */
|
|
137
|
+
tokenEnv: ENV_NAME.optional(),
|
|
138
|
+
/** env-NAME holding the base64 CA cert (service: K8S_CA_CERT_B64). NEVER the cert bytes. */
|
|
139
|
+
caCertEnv: ENV_NAME.optional(),
|
|
140
|
+
insecureTls: z.boolean().optional(),
|
|
141
|
+
namespace: execStr.optional(),
|
|
142
|
+
runtimeClass: execStr.optional(),
|
|
143
|
+
mountPath: execStr.optional(),
|
|
144
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
145
|
+
/** Sandbox pod resource limits (service: K8S_MEMORY / K8S_CPU; adapter defaults 2Gi / 1 cpu). */
|
|
146
|
+
memory: K8S_QUANTITY.optional(),
|
|
147
|
+
cpu: K8S_QUANTITY.optional(),
|
|
148
|
+
/** Workspace-durable suspend/resume via MinIO/S3 (service: MINIO_*). Creds are env-NAME refs; the sandbox
|
|
149
|
+
* only ever sees a presigned URL. accessKeyEnv + secretKeyEnv required to enable (mirrors the service's
|
|
150
|
+
* "all three of endpoint/access/secret required" gate). */
|
|
151
|
+
s3Snapshot: z
|
|
152
|
+
.object({
|
|
153
|
+
endpoint: NON_SECRET_URL, // S3/MinIO endpoint — creds go in accessKeyEnv/secretKeyEnv, never the URL
|
|
154
|
+
bucket: execStr.optional(),
|
|
155
|
+
accessKeyEnv: ENV_NAME,
|
|
156
|
+
secretKeyEnv: ENV_NAME,
|
|
157
|
+
region: execStr.optional(),
|
|
158
|
+
keyPrefix: execStr.optional(),
|
|
159
|
+
presignTtlSec: z.number().int().positive().optional(),
|
|
160
|
+
})
|
|
161
|
+
.optional(),
|
|
162
|
+
}),
|
|
163
|
+
// ── ssh: a real host over SSH (service provider "ssh"; REMOTE_EXEC=ssh + SSH_HOST/SSH_USER/SSH_PRIVATE_KEY).
|
|
164
|
+
z.object({
|
|
165
|
+
provider: z.literal("ssh"),
|
|
166
|
+
// host + username are persisted to config.d/remote-exec.json — scan for a secret-shaped literal, same as the
|
|
167
|
+
// remote-docker dockerHost scans its embedded ssh user (a secret must go in privateKeyEnv, not the login). R14.
|
|
168
|
+
host: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh host — credentials go in privateKeyEnv"),
|
|
169
|
+
port: TCP_PORT.optional(),
|
|
170
|
+
username: execStr.refine((s) => !containsSecretToken(s), "no secret literal in ssh username — credentials go in privateKeyEnv"),
|
|
171
|
+
/** env-NAME holding the SSH private key (service: SSH_PRIVATE_KEY). NEVER the key value. */
|
|
172
|
+
privateKeyEnv: ENV_NAME,
|
|
173
|
+
mountPath: execStr.optional(),
|
|
174
|
+
}),
|
|
175
|
+
// ── adb: a real device over ADB (service provider "adb"; REMOTE_EXEC=adb + ADB_SERIAL). No secrets.
|
|
176
|
+
z.object({
|
|
177
|
+
provider: z.literal("adb"),
|
|
178
|
+
serial: execStr,
|
|
179
|
+
adbPath: execStr.optional(),
|
|
180
|
+
mountPath: execStr.optional(),
|
|
181
|
+
}),
|
|
182
|
+
]);
|
|
183
|
+
//# sourceMappingURL=remote-exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote-exec.js","sourceRoot":"","sources":["../src/remote-exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-worker roster resolution (docs/MULTI-ROSTER.md). Turns the GLOBAL effective config + a worker name
|
|
3
|
+
* into THAT worker's effective config: its `models` domain is the roster's view (model subset + role-map +
|
|
4
|
+
* @-allowlist override), everything else stays global. The `rosters` domain is control-plane-only and is
|
|
5
|
+
* always stripped from the wire payload (no /effective consumer reads it).
|
|
6
|
+
*
|
|
7
|
+
* Pure (no I/O) → unit-testable. Tolerant of dangling refs (unknown worker / missing roster → global
|
|
8
|
+
* fallback); strict referential integrity is enforced at PUT time (the [domain] route), not here.
|
|
9
|
+
*/
|
|
10
|
+
import type { EffectiveConfig, EffectiveWire, ModelRole, RoleTarget, RosterEntry } from "./types.js";
|
|
11
|
+
/** primaryModel 标记喂的角色族(§3/[546])。 */
|
|
12
|
+
export declare const ROSTER_PRIMARY_ROLES: readonly ["default", "team", "synthesize"];
|
|
13
|
+
/** cheapModel 标记喂的角色族(§3/[546])。 */
|
|
14
|
+
export declare const ROSTER_CHEAP_ROLES: readonly ["subagent", "summarize"];
|
|
15
|
+
/** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
|
|
16
|
+
* models.roles(合并点见 {@link resolveEffectiveForWorker})。wire 形状不变——service 零改仍兼容,
|
|
17
|
+
* 收敛后直接读标记亦可(effective 的 rosters 域不上 wire,标记经此物化进 models.roles)。 */
|
|
18
|
+
export declare function rosterMarkerRoles(r: Pick<RosterEntry, "primaryModel" | "cheapModel">): Partial<Record<ModelRole, RoleTarget>>;
|
|
19
|
+
export interface ResolvedEffective {
|
|
20
|
+
/** The wire payload: `models` resolved for the worker, `rosters` stripped, `skills` shipped as a manifest. */
|
|
21
|
+
config: EffectiveWire;
|
|
22
|
+
/** Diagnostic: the roster name applied, or "global" / "global(fallback)". Surfaced as the X-Roster header. */
|
|
23
|
+
rosterLabel: string;
|
|
24
|
+
/** Quoted ETag. Global path = `"v<version>"` (today's behaviour). Scoped path = content hash (excludes
|
|
25
|
+
* version/updatedAt) so an unrelated roster change at a new version doesn't churn this worker's cache. */
|
|
26
|
+
etag: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function resolveEffectiveForWorker(eff: EffectiveConfig, worker: string | null | undefined): ResolvedEffective;
|
|
29
|
+
//# sourceMappingURL=resolve-roster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-roster.d.ts","sourceRoot":"","sources":["../src/resolve-roster.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,SAAS,EAAgB,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AASnH,sCAAsC;AACtC,eAAO,MAAM,oBAAoB,4CAA4E,CAAC;AAC9G,oCAAoC;AACpC,eAAO,MAAM,kBAAkB,oCAAoE,CAAC;AAEpG;;qEAEqE;AACrE,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,cAAc,GAAG,YAAY,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAK7H;AAED,MAAM,WAAW,iBAAiB;IAChC,8GAA8G;IAC9G,MAAM,EAAE,aAAa,CAAC;IACtB,8GAA8G;IAC9G,WAAW,EAAE,MAAM,CAAC;IACpB;+GAC2G;IAC3G,IAAI,EAAE,MAAM,CAAC;CACd;AA4BD,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,iBAAiB,CAuDpH"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { stableHash } from "./hash.js";
|
|
2
|
+
import { toSkillsManifest } from "./skills-manifest.js";
|
|
3
|
+
// ── 主/cheap 角色标记语义(0.6.3, E3/[546]/[548]②)────────────────────────────────
|
|
4
|
+
// service 现有 ModelRoles 口径:default/team/synthesize→主、subagent/summarize→cheap。`verifier` 刻意
|
|
5
|
+
// 不派生(unset→core 的 default 兜底链,与 service 语义一致)。常量导出=单一语义源,service 收敛小单与
|
|
6
|
+
// center 表单同源引用,别第二份。
|
|
7
|
+
/** primaryModel 标记喂的角色族(§3/[546])。 */
|
|
8
|
+
export const ROSTER_PRIMARY_ROLES = ["default", "team", "synthesize"];
|
|
9
|
+
/** cheapModel 标记喂的角色族(§3/[546])。 */
|
|
10
|
+
export const ROSTER_CHEAP_ROLES = ["subagent", "summarize"];
|
|
11
|
+
/** 把 roster 的主/cheap 角色标记派生成 roles 片段(PURE)。显式 roster.roles 逐角色盖过它;它盖过全局
|
|
12
|
+
* models.roles(合并点见 {@link resolveEffectiveForWorker})。wire 形状不变——service 零改仍兼容,
|
|
13
|
+
* 收敛后直接读标记亦可(effective 的 rosters 域不上 wire,标记经此物化进 models.roles)。 */
|
|
14
|
+
export function rosterMarkerRoles(r) {
|
|
15
|
+
const out = {};
|
|
16
|
+
if (r.primaryModel)
|
|
17
|
+
for (const role of ROSTER_PRIMARY_ROLES)
|
|
18
|
+
out[role] = { model: r.primaryModel };
|
|
19
|
+
if (r.cheapModel)
|
|
20
|
+
for (const role of ROSTER_CHEAP_ROLES)
|
|
21
|
+
out[role] = { model: r.cheapModel };
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
/** Per-roster targeting (v2): keep items whose `rosters` is empty (= all rosters) or includes `rosterName`.
|
|
25
|
+
* `rosterName: null` = the unscoped/global pull → no filtering (ship all). */
|
|
26
|
+
function forRoster(items, rosterName) {
|
|
27
|
+
if (!rosterName)
|
|
28
|
+
return items;
|
|
29
|
+
return items.filter((it) => !it.rosters?.length || it.rosters.includes(rosterName));
|
|
30
|
+
}
|
|
31
|
+
/** Shape the wire payload: swap in the resolved `models`, empty the control-plane-only `rosters` domain
|
|
32
|
+
* (never shipped on the wire), ship `skills` as a manifest (content → contentHash), and filter
|
|
33
|
+
* skills + mcp by the worker's roster (`rosterName: null` on the global path = no filter).
|
|
34
|
+
* (The `access` domain was deleted outright in 0.6.0 — its strip here went with it, EXPERT-REDESIGN §9.
|
|
35
|
+
* `runtime` rides the wire as {@link EffectiveConfig}'s RuntimeWire — limit residue + governance mirror
|
|
36
|
+
* (双写过渡, §5) — built upstream in buildEffective; nothing to reshape here.) */
|
|
37
|
+
function shipped(eff, models, rosterName) {
|
|
38
|
+
return {
|
|
39
|
+
...eff,
|
|
40
|
+
models,
|
|
41
|
+
rosters: { rosters: [] },
|
|
42
|
+
// entitlement is per-PRINCIPAL governance (every subject's tier/budget/caps) — control-plane only. A worker
|
|
43
|
+
// pull must NEVER see it (privacy + size); the per-principal view is served separately via /effective?principal=.
|
|
44
|
+
entitlement: { tiers: [], subjects: [], groupBindings: [], budgetGroups: [] },
|
|
45
|
+
skills: toSkillsManifest({ skills: forRoster(eff.skills.skills, rosterName) }),
|
|
46
|
+
mcp: { servers: forRoster(eff.mcp.servers, rosterName) },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export function resolveEffectiveForWorker(eff, worker) {
|
|
50
|
+
if (!worker) {
|
|
51
|
+
return { config: shipped(eff, eff.models, null), rosterLabel: "global", etag: `"v${eff.version}"` };
|
|
52
|
+
}
|
|
53
|
+
const w = eff.workers.workers.find((x) => x.name === worker);
|
|
54
|
+
const roster = w?.roster ? eff.rosters.rosters.find((r) => r.name === w.roster) : undefined;
|
|
55
|
+
if (!roster) {
|
|
56
|
+
// unknown worker / worker without a roster / dangling roster ref → global fallback (diagnosable).
|
|
57
|
+
return { config: shipped(eff, eff.models, null), rosterLabel: "global(fallback)", etag: `"v${eff.version}"` };
|
|
58
|
+
}
|
|
59
|
+
const allow = roster.models.length ? new Set(roster.models) : null; // empty roster.models = full catalog
|
|
60
|
+
const resolvedModels = allow ? eff.models.models.filter((m) => allow.has(m.name)) : eff.models.models;
|
|
61
|
+
// Defence-in-depth: the shipped @-allowlist must only name models actually in this worker's
|
|
62
|
+
// set — otherwise a roster that restricts `models` but keeps a stale allowlist ships an inconsistent config.
|
|
63
|
+
// Validation also blocks this at PUT, but filter here so resolution is self-consistent regardless. Keep
|
|
64
|
+
// `undefined` (no override + no global) as-is = "all enabled models are @-mentionable".
|
|
65
|
+
const finalNames = new Set(resolvedModels.map((m) => m.name));
|
|
66
|
+
const srcAllowlist = roster.atModelAllowlist ?? eff.models.atModelAllowlist;
|
|
67
|
+
// Same defence for `roles` as for the @-allowlist (the MORE load-bearing one — roles drive real model
|
|
68
|
+
// selection, not just @-mention). A `{model}`-form role pointing at a model NOT in this worker's set (an
|
|
69
|
+
// inherited global default under a `models` subset, or a stale role after a catalog deletion — PUT does no
|
|
70
|
+
// dependent re-validation) would otherwise ship a DANGLING role on the wire → the worker resolves that role
|
|
71
|
+
// to a non-existent model. Drop it → the consumer's core fallback (unset role → default) kicks in.
|
|
72
|
+
// `{select}`-form (capability-based) roles name no model → kept.
|
|
73
|
+
// Per-role merge (roster overrides global PER ROLE) — NOT a whole-map replace. The types promise "unset
|
|
74
|
+
// roster roles fall back to global models.roles per role"; a whole-map `??` silently dropped the global
|
|
75
|
+
// `subagent`/`team`/… when a roster set only `default`. Round-3 fix (codex). Then drop any dangling {model}.
|
|
76
|
+
// 0.6.3: marker-derived roles (primaryModel/cheapModel, [546] semantics) sit BETWEEN global and explicit
|
|
77
|
+
// roster.roles — global < markers < explicit per-role. Markers are subset-validated at PUT; a stale marker
|
|
78
|
+
// after a catalog change still falls through the dangling-{model} drop below (defence-in-depth).
|
|
79
|
+
const srcRoles = { ...eff.models.roles, ...rosterMarkerRoles(roster), ...(roster.roles ?? {}) };
|
|
80
|
+
const roles = Object.fromEntries(Object.entries(srcRoles).filter(([, t]) => !(t && "model" in t) || finalNames.has(t.model)));
|
|
81
|
+
// @-allowlist: drop names not in this worker's model set (consistency) — BUT if a NON-EMPTY allowlist would
|
|
82
|
+
// filter to EMPTY (the worker exposes none of the allowlisted models), DON'T ship `[]`: empty means "ALL
|
|
83
|
+
// @-mentionable" (contract, types.ts), which would INVERT the admin's restriction into permissive. Keep the
|
|
84
|
+
// source instead — the consumer also gates on model membership, so nothing the worker lacks becomes
|
|
85
|
+
// mentionable; the restriction is preserved (a dangling allowlist name simply never matches). Round-5 (codex).
|
|
86
|
+
const filteredAllow = srcAllowlist?.filter((n) => finalNames.has(n));
|
|
87
|
+
// 档位组(0.9.0):直通 + 逐档悬空-drop(照上面 roles 的 dangling-{model} drop 先例)——roster 收窄后
|
|
88
|
+
// 组内绑到本 worker 模型集之外的档剔除,引擎侧「未绑档沿链降档 fail-open」接手([604]),与 roles
|
|
89
|
+
// drop→core fallback 同构。组名/active 选择不动(组是 center 侧抽象,per-worker 只影响绑定可见性)。
|
|
90
|
+
const tierGroups = eff.models.tierGroups.map((g) => ({
|
|
91
|
+
...g,
|
|
92
|
+
tiers: Object.fromEntries(Object.entries(g.tiers).filter(([, m]) => typeof m !== "string" || finalNames.has(m))),
|
|
93
|
+
}));
|
|
94
|
+
const models = {
|
|
95
|
+
models: resolvedModels,
|
|
96
|
+
roles,
|
|
97
|
+
atModelAllowlist: srcAllowlist && srcAllowlist.length > 0 && filteredAllow && filteredAllow.length === 0 ? srcAllowlist : filteredAllow,
|
|
98
|
+
tierGroups,
|
|
99
|
+
activeTierGroup: eff.models.activeTierGroup,
|
|
100
|
+
};
|
|
101
|
+
const config = shipped(eff, models, roster.name);
|
|
102
|
+
const { version: _v, updatedAt: _u, ...content } = config;
|
|
103
|
+
return { config, rosterLabel: roster.name, etag: `"${stableHash(content)}"` };
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=resolve-roster.js.map
|