spawnfile 0.1.34 → 0.1.35
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/audit/readAudit.d.ts +3 -3
- package/dist/auth/profileStore.d.ts +3 -1
- package/dist/auth/profileStore.js +31 -30
- package/dist/auth/types.d.ts +16 -0
- package/dist/cli/authCommands.d.ts +1 -1
- package/dist/cli/authCommands.js +21 -1
- package/dist/cli/runCli.d.ts +2 -0
- package/dist/cli/runCli.js +2 -1
- package/dist/cli/trainingBooted.d.ts +2 -0
- package/dist/cli/trainingBooted.js +5 -1
- package/dist/compiler/containerDaimonBrokerRender.js +12 -2
- package/dist/compiler/containerDaimonGatewayRender.js +4 -0
- package/dist/compiler/containerDaimonUidEntrypointRender.js +3 -1
- package/dist/compiler/runProject.d.ts +2 -0
- package/dist/compiler/runProject.js +6 -5
- package/dist/compiler/runProjectAuth.js +4 -1
- package/dist/compiler/training/rewind/contract.d.ts +6 -6
- package/dist/compiler/training/trial/boot.d.ts +20 -1
- package/dist/compiler/training/trial/boot.js +102 -24
- package/dist/compiler/training/trial/docker.d.ts +2 -0
- package/dist/compiler/training/trial/docker.js +1 -1
- package/dist/compiler/training/trial/gateway.d.ts +5 -0
- package/dist/compiler/training/trial/gateway.js +18 -3
- package/dist/compiler/training/trial/homeLogin.d.ts +31 -0
- package/dist/compiler/training/trial/homeLogin.js +69 -0
- package/dist/compiler/training/trial/index.d.ts +5 -1
- package/dist/compiler/training/trial/index.js +5 -1
- package/dist/compiler/training/trial/judge.d.ts +15 -0
- package/dist/compiler/training/trial/judge.js +42 -0
- package/dist/compiler/training/trial/launcher.d.ts +28 -13
- package/dist/compiler/training/trial/launcher.js +168 -24
- package/dist/compiler/training/trial/login.d.ts +35 -0
- package/dist/compiler/training/trial/login.js +85 -0
- package/dist/compiler/training/trial/realmTurns.d.ts +20 -0
- package/dist/compiler/training/trial/realmTurns.js +69 -0
- package/dist/manifest/renderSpawnfile.js +2 -1
- package/dist/runtime/daimon/config.js +3 -3
- package/dist/runtime/daimon/engineDescriptors.generated.d.ts +1 -1
- package/dist/runtime/daimon/engineDescriptors.generated.js +1 -1
- package/dist/runtime/daimon/index.d.ts +1 -1
- package/dist/runtime/daimon/index.js +1 -1
- package/dist/runtime/daimon/runAuth.js +8 -0
- package/dist/runtime/daimon/subscriptionRealms.d.ts +18 -0
- package/dist/runtime/daimon/subscriptionRealms.js +14 -1
- package/dist/runtime/daimon/training/booted/adapter.d.ts +5 -1
- package/dist/runtime/daimon/training/booted/adapter.js +3 -2
- package/dist/runtime/daimon/training/booted/adapter.js.map +1 -1
- package/dist/runtime/daimon/training/booted/index.d.ts +3 -1
- package/dist/runtime/daimon/training/booted/index.js +2 -1
- package/dist/runtime/daimon/training/booted/index.js.map +1 -1
- package/dist/runtime/daimon/training/booted/judge.d.ts +39 -0
- package/dist/runtime/daimon/training/booted/judge.js +122 -0
- package/dist/runtime/daimon/training/booted/judge.js.map +1 -0
- package/dist/runtime/trainingGraderProviders.js +10 -4
- package/dist/runtime/trainingProviders.d.ts +4 -1
- package/dist/runtime/trainingProviders.js +13 -6
- package/dist/runtime/usageRequestLedger.d.ts +2 -0
- package/dist/runtime/usageRequestLedger.js +3 -1
- package/dist/shared/trainingLegacy/trainingLegacyHashDomains.d.ts +1 -0
- package/dist/shared/trainingLegacy/trainingLegacyHashDomains.js +1 -0
- package/dist/shared/trainingLegacy/trainingLegacyHashDomains.js.map +1 -1
- package/package.json +1 -1
- package/runtimes.yaml +5 -5
- package/dist/runtime/daimon/training/providers/codex-subscription/index.d.ts +0 -1
- package/dist/runtime/daimon/training/providers/codex-subscription/index.js +0 -2
- package/dist/runtime/daimon/training/providers/codex-subscription/index.js.map +0 -1
- package/dist/runtime/daimon/training/providers/codex-subscription/protocol.d.ts +0 -17
- package/dist/runtime/daimon/training/providers/codex-subscription/protocol.js +0 -80
- package/dist/runtime/daimon/training/providers/codex-subscription/protocol.js.map +0 -1
- package/dist/runtime/daimon/training/providers/codex-subscription/provider.d.ts +0 -12
- package/dist/runtime/daimon/training/providers/codex-subscription/provider.js +0 -125
- package/dist/runtime/daimon/training/providers/codex-subscription/provider.js.map +0 -1
- package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.d.ts +0 -6
- package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.js +0 -219
- package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.js.map +0 -1
- package/dist/runtime/daimon/training/providers/grok-subscription/cli.d.ts +0 -19
- package/dist/runtime/daimon/training/providers/grok-subscription/cli.js +0 -81
- package/dist/runtime/daimon/training/providers/grok-subscription/cli.js.map +0 -1
- package/dist/runtime/daimon/training/providers/grok-subscription/index.d.ts +0 -3
- package/dist/runtime/daimon/training/providers/grok-subscription/index.js +0 -4
- package/dist/runtime/daimon/training/providers/grok-subscription/index.js.map +0 -1
- package/dist/runtime/daimon/training/providers/grok-subscription/protocol.d.ts +0 -14
- package/dist/runtime/daimon/training/providers/grok-subscription/protocol.js +0 -89
- package/dist/runtime/daimon/training/providers/grok-subscription/protocol.js.map +0 -1
- package/dist/runtime/daimon/training/providers/grok-subscription/provider.d.ts +0 -9
- package/dist/runtime/daimon/training/providers/grok-subscription/provider.js +0 -37
- package/dist/runtime/daimon/training/providers/grok-subscription/provider.js.map +0 -1
|
@@ -1,44 +1,188 @@
|
|
|
1
1
|
import { mkdir, mkdtemp } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { daimonGatewayedEngines } from "../../../runtime/daimon/gateway.js";
|
|
4
|
+
import { getDaimonEngineDescriptor } from "../../../runtime/daimon/index.js";
|
|
3
5
|
import { ensureNoopolisRunId } from "../../../runtime/index.js";
|
|
6
|
+
import { SpawnfileError } from "../../../shared/index.js";
|
|
4
7
|
import { prepareTrial } from "./boot.js";
|
|
5
8
|
import { createTrialDocker } from "./docker.js";
|
|
6
|
-
import { startTrainingGateway } from "./gateway.js";
|
|
9
|
+
import { startTrainingGateway, trainingGatewayLogins } from "./gateway.js";
|
|
10
|
+
import { JUDGE_INSTRUCTIONS, writeJudgeAgent } from "./judge.js";
|
|
11
|
+
import { HOME_LOGIN_FILES } from "./homeLogin.js";
|
|
12
|
+
import { createRealmTurns } from "./realmTurns.js";
|
|
7
13
|
/**
|
|
8
|
-
* One training run's
|
|
9
|
-
* one-agent organization,
|
|
10
|
-
*
|
|
14
|
+
* One training run's containers: each trial boot compiles and starts the
|
|
15
|
+
* subject's one-agent organization, each judge is a tool-free one-agent
|
|
16
|
+
* organization booted the same way, and all of them share the run's single
|
|
17
|
+
* auth gateway, started from the first image on first use. The gateway holds
|
|
18
|
+
* every gatewayed login of the run's auth profile, so trials and judges on
|
|
19
|
+
* any engine reach it with one key.
|
|
11
20
|
*/
|
|
12
21
|
export const createBootedTrialRun = (options) => {
|
|
13
22
|
const docker = options.docker ?? createTrialDocker();
|
|
23
|
+
const turns = options.turns ?? createRealmTurns();
|
|
24
|
+
const prepare = options.prepare ?? prepareTrial;
|
|
14
25
|
const runId = ensureNoopolisRunId();
|
|
26
|
+
const logins = trainingGatewayLogins(options.authProfile);
|
|
15
27
|
let gateway;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
const judges = new Map();
|
|
29
|
+
let closed = false;
|
|
30
|
+
/** The gateway this organization needs (null: no gatewayed engine), started on first use. */
|
|
31
|
+
const gatewayFor = async (prepared) => {
|
|
32
|
+
const gatewayed = daimonGatewayedEngines(prepared.engines);
|
|
33
|
+
if (gatewayed.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
const missing = gatewayed.filter((descriptor) => !logins.includes(descriptor.id));
|
|
36
|
+
if (missing.length > 0) {
|
|
37
|
+
throw new SpawnfileError("validation_error", `Auth profile ${options.authProfile.name} has no ${missing.map(({ id }) => id).join(", ")} login; run \`spawnfile auth import <engine> --profile ${options.authProfile.name}\``);
|
|
38
|
+
}
|
|
39
|
+
gateway ??= (async () => {
|
|
40
|
+
const image = `spawnfile-training-gateway:${runId.replace(/[^A-Za-z0-9_.-]/gu, "-").slice(0, 100)}`;
|
|
41
|
+
await docker(["tag", prepared.image, image]);
|
|
42
|
+
return { image, gateway: await startTrainingGateway({ authProfile: options.authProfile, docker, engines: logins,
|
|
43
|
+
image, runId, runtimeRoot: prepared.runtimeRoot }) };
|
|
44
|
+
})();
|
|
45
|
+
return (await gateway).gateway;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The volume-held login (`spawnfile auth login`) this organization mounts,
|
|
49
|
+
* as a profile view that carries nothing else: no env, no imports.
|
|
50
|
+
*/
|
|
51
|
+
const realmFor = (prepared) => {
|
|
52
|
+
const engine = prepared.engines.find((id) => getDaimonEngineDescriptor(id)?.auth.staging === "secret-service");
|
|
53
|
+
if (engine === undefined)
|
|
54
|
+
return undefined;
|
|
55
|
+
const binding = options.authProfile.realms?.[engine];
|
|
56
|
+
// A login recorded before its home login files were kept cannot enroll a fresh home.
|
|
57
|
+
if (!binding || (HOME_LOGIN_FILES[engine] !== undefined && binding.homeVolume === undefined)) {
|
|
58
|
+
throw new SpawnfileError("validation_error", `Auth profile ${options.authProfile.name} has no ${engine} login; run \`spawnfile auth login ${engine} --profile ${options.authProfile.name}\``);
|
|
59
|
+
}
|
|
60
|
+
return { volume: binding.realmVolume, profile: { ...options.authProfile, env: {}, imports: {}, realms: { [engine]: binding } } };
|
|
61
|
+
};
|
|
62
|
+
/** Boots one container, holding its login realm's turn (if any) until the container is gone. */
|
|
63
|
+
const bootContainer = async (prepared, turn = {}) => {
|
|
64
|
+
const access = await gatewayFor(prepared);
|
|
65
|
+
const realm = realmFor(prepared);
|
|
66
|
+
const release = realm ? await turns.take(realm.volume, turn) : undefined;
|
|
67
|
+
try {
|
|
68
|
+
const booted = await prepared.boot(access, realm ? { realms: realm.profile } : {});
|
|
69
|
+
return { ...booted, teardown: async () => { try {
|
|
70
|
+
await booted.teardown();
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
release?.();
|
|
74
|
+
} } };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
release?.();
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const start = async (prepared, signal) => {
|
|
82
|
+
try {
|
|
83
|
+
const booted = await bootContainer(prepared, signal ? { signal } : {});
|
|
84
|
+
return { ...booted, teardown: async () => { try {
|
|
85
|
+
await booted.teardown();
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await prepared.dispose();
|
|
89
|
+
} } };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
await prepared.dispose();
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* A judge on a volume-held login takes turns with the run's trials: it stays
|
|
98
|
+
* warm while nobody else waits for the realm, and hands the turn over (its
|
|
99
|
+
* container removed) after its current judgment once someone does. Its image
|
|
100
|
+
* is built once and kept.
|
|
101
|
+
*/
|
|
102
|
+
const takeTurnsJudge = (prepared) => {
|
|
103
|
+
let booted;
|
|
104
|
+
let container = "";
|
|
105
|
+
let busy = 0;
|
|
106
|
+
let evict = false;
|
|
107
|
+
const down = async () => {
|
|
108
|
+
const current = booted;
|
|
109
|
+
booted = undefined;
|
|
110
|
+
evict = false;
|
|
111
|
+
await (await current?.catch(() => undefined))?.teardown();
|
|
112
|
+
};
|
|
113
|
+
const timeoutMs = options.judgeRealmWaitMs ?? 30 * 60_000;
|
|
114
|
+
return {
|
|
115
|
+
get container() { return container; },
|
|
116
|
+
scanCredentials: async () => (await booted?.catch(() => undefined))?.scanCredentials() ?? [],
|
|
117
|
+
wake: async (text, wakeOptions) => {
|
|
118
|
+
busy++;
|
|
119
|
+
try {
|
|
120
|
+
if (!booted) {
|
|
121
|
+
const pending = bootContainer(prepared, { timeoutMs, ...(wakeOptions.signal ? { signal: wakeOptions.signal } : {}),
|
|
122
|
+
onContended: () => { evict = true; if (busy === 0)
|
|
123
|
+
void down(); } });
|
|
124
|
+
booted = pending;
|
|
125
|
+
pending.then((judge) => { container = judge.container; }, () => { if (booted === pending)
|
|
126
|
+
booted = undefined; });
|
|
31
127
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
128
|
+
return await (await booted).wake(text, wakeOptions);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
busy--;
|
|
132
|
+
if (evict && busy === 0)
|
|
133
|
+
await down();
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
teardown: async () => { try {
|
|
137
|
+
await down();
|
|
35
138
|
}
|
|
36
|
-
|
|
139
|
+
finally {
|
|
37
140
|
await prepared.dispose();
|
|
38
|
-
|
|
141
|
+
} }
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const directory = async (prefix) => {
|
|
145
|
+
await mkdir(options.scratchRoot, { recursive: true });
|
|
146
|
+
return mkdtemp(path.join(options.scratchRoot, prefix));
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
boot: async ({ trialId, instructions, signal }) => start(await prepare({
|
|
150
|
+
agentManifestPath: options.agentManifestPath, docker, instructions, trialId, directory: await directory("trial-")
|
|
151
|
+
}), signal),
|
|
152
|
+
judge: (spec) => {
|
|
153
|
+
if (closed)
|
|
154
|
+
return Promise.reject(new SpawnfileError("runtime_error", "the training run is closed"));
|
|
155
|
+
const key = JSON.stringify([spec.engine, spec.model, spec.effort ?? null]);
|
|
156
|
+
let judge = judges.get(key);
|
|
157
|
+
if (!judge) {
|
|
158
|
+
judge = (async () => {
|
|
159
|
+
const root = await directory("judge-");
|
|
160
|
+
const prepared = await prepare({
|
|
161
|
+
agentManifestPath: await writeJudgeAgent(spec, path.join(root, "source")), docker, instructions: JUDGE_INSTRUCTIONS,
|
|
162
|
+
trialId: `judge-${spec.engine}-${key}-${runId}`, directory: root
|
|
163
|
+
});
|
|
164
|
+
let turnTaking;
|
|
165
|
+
try {
|
|
166
|
+
turnTaking = realmFor(prepared) !== undefined;
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
await prepared.dispose();
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
return turnTaking ? takeTurnsJudge(prepared) : start(prepared);
|
|
173
|
+
})();
|
|
174
|
+
judges.set(key, judge);
|
|
175
|
+
// A failed boot is not cached: the next judgment retries it.
|
|
176
|
+
judge.catch(() => { if (judges.get(key) === judge)
|
|
177
|
+
judges.delete(key); });
|
|
39
178
|
}
|
|
179
|
+
return judge;
|
|
40
180
|
},
|
|
41
181
|
close: async () => {
|
|
182
|
+
closed = true;
|
|
183
|
+
const booted = await Promise.all([...judges.values()].map((judge) => judge.catch(() => undefined)));
|
|
184
|
+
judges.clear();
|
|
185
|
+
await Promise.all(booted.map((judge) => judge?.teardown().catch(() => undefined)));
|
|
42
186
|
const started = await gateway?.catch(() => undefined);
|
|
43
187
|
if (!started)
|
|
44
188
|
return;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ResolvedAuthProfile, type SubscriptionRealmBinding } from "../../../auth/index.js";
|
|
2
|
+
import { prepareTrial } from "./boot.js";
|
|
3
|
+
import { type TrialDocker } from "./docker.js";
|
|
4
|
+
export interface SubscriptionRealmLoginOptions {
|
|
5
|
+
profileName: string;
|
|
6
|
+
engine: string;
|
|
7
|
+
/** Prepare volumes and image and return the login command without running it or recording the login. */
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
docker?: TrialDocker;
|
|
10
|
+
/** Where the throwaway login organization is compiled (default: a fresh temp directory). */
|
|
11
|
+
scratchRoot?: string;
|
|
12
|
+
/** Builds the throwaway one-agent organization the login runs in (tests only). */
|
|
13
|
+
prepare?: typeof prepareTrial;
|
|
14
|
+
}
|
|
15
|
+
export interface SubscriptionRealmLoginResult {
|
|
16
|
+
binding: SubscriptionRealmBinding;
|
|
17
|
+
/** The exact `docker run` argv the login uses. */
|
|
18
|
+
command: readonly string[];
|
|
19
|
+
profile: ResolvedAuthProfile;
|
|
20
|
+
}
|
|
21
|
+
/** The profile's volume names for an engine's volume-held login. */
|
|
22
|
+
export declare const subscriptionRealmBinding: (profileName: string, engine: string) => Required<SubscriptionRealmBinding>;
|
|
23
|
+
export declare const ensureSubscriptionRealmVolumes: (docker: TrialDocker, image: string, engine: string, binding: SubscriptionRealmBinding) => Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* `spawnfile auth login <engine>`: gives an auth profile its own volume-held
|
|
26
|
+
* login for an engine that keeps its login in a Daimon secret-service realm
|
|
27
|
+
* (AGY). Creates the profile's realm, unlock and home login volumes, then runs
|
|
28
|
+
* Daimon's own interactive `auth <engine> login` in a throwaway,
|
|
29
|
+
* production-booted one-agent organization (a tool-free judge) attached to the
|
|
30
|
+
* operator's terminal, keeps the engine's login files from that throwaway home
|
|
31
|
+
* in the home login volume, and records the volume names on the profile once
|
|
32
|
+
* it succeeds.
|
|
33
|
+
* Spawnfile never reads the login or the unlock secret.
|
|
34
|
+
*/
|
|
35
|
+
export declare const loginSubscriptionRealm: (options: SubscriptionRealmLoginOptions) => Promise<SubscriptionRealmLoginResult>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ensureAuthProfile, registerSubscriptionRealm } from "../../../auth/index.js";
|
|
5
|
+
import { getDaimonEngineDescriptor } from "../../../runtime/daimon/index.js";
|
|
6
|
+
import { DAIMON_ORGANIZATION_UID } from "../../../runtime/daimon/runtimeIdentity.js";
|
|
7
|
+
import { DAIMON_PROFILE_UNLOCK_FILE } from "../../../runtime/daimon/subscriptionRealms.js";
|
|
8
|
+
import { SpawnfileError } from "../../../shared/index.js";
|
|
9
|
+
import { slugify } from "../../helpers.js";
|
|
10
|
+
import { prepareTrial } from "./boot.js";
|
|
11
|
+
import { createTrialDocker } from "./docker.js";
|
|
12
|
+
import { trainingGatewayRealmVolume } from "./gateway.js";
|
|
13
|
+
import { JUDGE_INSTRUCTIONS, writeJudgeAgent } from "./judge.js";
|
|
14
|
+
const LABEL = "spawnfile.subscription-realm";
|
|
15
|
+
/** The profile's volume names for an engine's volume-held login. */
|
|
16
|
+
export const subscriptionRealmBinding = (profileName, engine) => {
|
|
17
|
+
const prefix = `spawnfile-training-${slugify(profileName) || "profile"}-${engine}`;
|
|
18
|
+
return { homeVolume: `${prefix}-home`, realmVolume: trainingGatewayRealmVolume(profileName, engine), unlockVolume: `${prefix}-unlock` };
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Creates the unlock secret inside its volume exactly once, as the organization
|
|
22
|
+
* uid Daimon checks it against (one `0600` regular file, no newline). Generated
|
|
23
|
+
* and written inside a network-less container: the bytes never reach the host.
|
|
24
|
+
* An existing secret is kept (replacing it would orphan the encrypted keyring)
|
|
25
|
+
* unless it is unsafe, which is refused.
|
|
26
|
+
*/
|
|
27
|
+
const UNLOCK_PROGRAM = [
|
|
28
|
+
"const fs = require('node:fs'), crypto = require('node:crypto');",
|
|
29
|
+
"const [uid, name] = process.argv.slice(1); const target = `/unlock/${name}`;",
|
|
30
|
+
"try { const entry = fs.lstatSync(target);",
|
|
31
|
+
" if (!entry.isFile() || entry.nlink !== 1 || entry.uid !== Number(uid) || (entry.mode & 0o777) !== 0o600 || entry.size < 1) { console.error('existing unlock secret is unsafe'); process.exit(3); }",
|
|
32
|
+
" process.exit(0);",
|
|
33
|
+
"} catch (error) { if (error.code !== 'ENOENT') throw error; }",
|
|
34
|
+
"const fd = fs.openSync(target, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);",
|
|
35
|
+
"const bytes = Buffer.from(crypto.randomBytes(32).toString('hex'));",
|
|
36
|
+
"fs.writeSync(fd, bytes); bytes.fill(0); fs.fchmodSync(fd, 0o600); fs.fsyncSync(fd); fs.fchownSync(fd, Number(uid), Number(uid)); fs.closeSync(fd);"
|
|
37
|
+
].join("\n");
|
|
38
|
+
export const ensureSubscriptionRealmVolumes = async (docker, image, engine, binding) => {
|
|
39
|
+
for (const volume of [binding.realmVolume, binding.unlockVolume, ...(binding.homeVolume ? [binding.homeVolume] : [])]) {
|
|
40
|
+
await docker(["volume", "create", "--label", `${LABEL}=${engine}`, volume]);
|
|
41
|
+
}
|
|
42
|
+
await docker(["run", "--rm", "--network", "none", "--user", "0:0", "--cap-drop", "ALL", "--cap-add", "CHOWN",
|
|
43
|
+
"--security-opt", "no-new-privileges", "--entrypoint", "node", "--mount", `type=volume,source=${binding.unlockVolume},target=/unlock`,
|
|
44
|
+
image, "-e", UNLOCK_PROGRAM, String(DAIMON_ORGANIZATION_UID), DAIMON_PROFILE_UNLOCK_FILE]);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* `spawnfile auth login <engine>`: gives an auth profile its own volume-held
|
|
48
|
+
* login for an engine that keeps its login in a Daimon secret-service realm
|
|
49
|
+
* (AGY). Creates the profile's realm, unlock and home login volumes, then runs
|
|
50
|
+
* Daimon's own interactive `auth <engine> login` in a throwaway,
|
|
51
|
+
* production-booted one-agent organization (a tool-free judge) attached to the
|
|
52
|
+
* operator's terminal, keeps the engine's login files from that throwaway home
|
|
53
|
+
* in the home login volume, and records the volume names on the profile once
|
|
54
|
+
* it succeeds.
|
|
55
|
+
* Spawnfile never reads the login or the unlock secret.
|
|
56
|
+
*/
|
|
57
|
+
export const loginSubscriptionRealm = async (options) => {
|
|
58
|
+
const descriptor = getDaimonEngineDescriptor(options.engine);
|
|
59
|
+
if (descriptor?.auth.staging !== "secret-service") {
|
|
60
|
+
throw new SpawnfileError("validation_error", `Engine ${options.engine} has no volume-held login; use \`spawnfile auth import ${options.engine}\``);
|
|
61
|
+
}
|
|
62
|
+
const docker = options.docker ?? createTrialDocker();
|
|
63
|
+
const current = await ensureAuthProfile(options.profileName);
|
|
64
|
+
// An earlier login keeps its volumes; one recorded before home login files were kept gains its home volume.
|
|
65
|
+
const binding = { ...subscriptionRealmBinding(current.name, options.engine), ...current.realms?.[options.engine] };
|
|
66
|
+
const realms = { ...current, env: {}, imports: {}, realms: { [options.engine]: binding } };
|
|
67
|
+
const scratch = options.scratchRoot ?? os.tmpdir();
|
|
68
|
+
await mkdir(scratch, { recursive: true });
|
|
69
|
+
const root = await mkdtemp(path.join(scratch, "spawnfile-auth-login-"));
|
|
70
|
+
let prepared;
|
|
71
|
+
try {
|
|
72
|
+
prepared = await (options.prepare ?? prepareTrial)({
|
|
73
|
+
agentManifestPath: await writeJudgeAgent({ engine: options.engine, model: "default" }, path.join(root, "source")),
|
|
74
|
+
directory: root, docker, instructions: JUDGE_INSTRUCTIONS, trialId: `login-${options.engine}-${current.name}`
|
|
75
|
+
});
|
|
76
|
+
await ensureSubscriptionRealmVolumes(docker, prepared.image, options.engine, binding);
|
|
77
|
+
const command = await prepared.login(realms, { dryRun: options.dryRun === true });
|
|
78
|
+
const profile = options.dryRun ? current : await registerSubscriptionRealm(current.name, options.engine, binding);
|
|
79
|
+
return { binding, command, profile };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await prepared?.dispose();
|
|
83
|
+
await rm(root, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Gives the turn back; idempotent. */
|
|
2
|
+
export type ReleaseRealmTurn = () => void;
|
|
3
|
+
export interface RealmTurnOptions {
|
|
4
|
+
/** Called once on the holder when another caller starts waiting: a warm holder should hand the turn over. */
|
|
5
|
+
onContended?: () => void;
|
|
6
|
+
/** Bound on the wait; on expiry the caller is refused instead of waiting forever. */
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* First-come turns on exclusive login realms, one holder per realm at a time.
|
|
12
|
+
* An engine whose login is one realm held under an exclusive lock (Daimon's
|
|
13
|
+
* AGY keyring, `flock` on `.daimon-lease`) admits one container per login:
|
|
14
|
+
* every container of a run that mounts that realm takes a turn here first, so
|
|
15
|
+
* a second container never boots into the lock and dies at readiness.
|
|
16
|
+
*/
|
|
17
|
+
export interface RealmTurns {
|
|
18
|
+
take(realm: string, options?: RealmTurnOptions): Promise<ReleaseRealmTurn>;
|
|
19
|
+
}
|
|
20
|
+
export declare const createRealmTurns: () => RealmTurns;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { SpawnfileError } from "../../../shared/index.js";
|
|
2
|
+
export const createRealmTurns = () => {
|
|
3
|
+
const realms = new Map();
|
|
4
|
+
const release = (key, state) => {
|
|
5
|
+
let released = false;
|
|
6
|
+
return () => {
|
|
7
|
+
if (released)
|
|
8
|
+
return;
|
|
9
|
+
released = true;
|
|
10
|
+
const next = state.queue.shift();
|
|
11
|
+
if (!next) {
|
|
12
|
+
realms.delete(key);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
state.holder = next.options;
|
|
16
|
+
state.contended = false;
|
|
17
|
+
next.grant();
|
|
18
|
+
if (state.queue.length > 0)
|
|
19
|
+
notify(state);
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const notify = (state) => {
|
|
23
|
+
if (state.contended)
|
|
24
|
+
return;
|
|
25
|
+
state.contended = true;
|
|
26
|
+
state.holder?.onContended?.();
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
take: (key, options = {}) => {
|
|
30
|
+
if (options.signal?.aborted)
|
|
31
|
+
return Promise.reject(new SpawnfileError("runtime_error", `cancelled waiting for login realm ${key}`));
|
|
32
|
+
const state = realms.get(key);
|
|
33
|
+
if (!state) {
|
|
34
|
+
const fresh = { holder: options, contended: false, queue: [] };
|
|
35
|
+
realms.set(key, fresh);
|
|
36
|
+
return Promise.resolve(release(key, fresh));
|
|
37
|
+
}
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
let timer;
|
|
40
|
+
const leave = (message) => {
|
|
41
|
+
const index = state.queue.indexOf(waiter);
|
|
42
|
+
if (index < 0)
|
|
43
|
+
return;
|
|
44
|
+
state.queue.splice(index, 1);
|
|
45
|
+
if (timer)
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
48
|
+
reject(new SpawnfileError("runtime_error", message));
|
|
49
|
+
};
|
|
50
|
+
const onAbort = () => leave(`cancelled waiting for login realm ${key}`);
|
|
51
|
+
const waiter = {
|
|
52
|
+
options,
|
|
53
|
+
grant: () => {
|
|
54
|
+
if (timer)
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
57
|
+
resolve(release(key, state));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
state.queue.push(waiter);
|
|
61
|
+
if (options.timeoutMs !== undefined) {
|
|
62
|
+
timer = setTimeout(() => leave(`login realm ${key} stayed held by another container for ${options.timeoutMs}ms; one login admits one container at a time`), options.timeoutMs);
|
|
63
|
+
}
|
|
64
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
65
|
+
notify(state);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
};
|
|
@@ -31,7 +31,8 @@ const orderModelTarget = (target) => {
|
|
|
31
31
|
["provider", target.provider],
|
|
32
32
|
["name", target.name],
|
|
33
33
|
["auth", orderModelEntryAuth(target.auth)],
|
|
34
|
-
["endpoint", target.endpoint]
|
|
34
|
+
["endpoint", target.endpoint],
|
|
35
|
+
["reasoning_effort", target.reasoning_effort]
|
|
35
36
|
]);
|
|
36
37
|
};
|
|
37
38
|
const orderExecution = (execution) => {
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { CLI_ENGINE_SKILL_BASE_DIRECTORIES } from "../common.js";
|
|
3
3
|
import { daimonGrokSkillsError } from "./grokSkillsSupport.js";
|
|
4
4
|
import { daimonAgentSkillsFromResolved } from "./grokWorkerSkills.js";
|
|
5
|
-
import { daimonGatewayedSubscriptionRealms } from "./subscriptionRealms.js";
|
|
5
|
+
import { daimonGatewayedSubscriptionRealms, daimonSubscriptionRealmMountId } from "./subscriptionRealms.js";
|
|
6
6
|
import { parseEveryScheduleMs } from "../scheduleUtils.js";
|
|
7
7
|
import { SpawnfileError } from "../../shared/index.js";
|
|
8
8
|
import { DAIMON_AGY_SUBSCRIPTION_REALM, DAIMON_GROK_TURN_USAGE_LEDGER } from "./contractManifest.js";
|
|
@@ -32,7 +32,7 @@ export const DAIMON_RUNTIME_HOMES_DIRECTORY = "runtime-homes";
|
|
|
32
32
|
const DAIMON_MAX_CONFIG_BYTES = 1_048_576;
|
|
33
33
|
const DAIMON_MAX_INSTRUCTION_BYTES = 16_384;
|
|
34
34
|
const DAIMON_MAX_INSTRUCTION_CODEPOINTS = 16_384;
|
|
35
|
-
import { DAIMON_ENGINES, DAIMON_DEFAULT_ENGINE, getDaimonEngineDescriptor, daimonEngineHasBroker } from "./engines.js";
|
|
35
|
+
import { DAIMON_ENGINES, DAIMON_DEFAULT_ENGINE, DAIMON_SECRET_SERVICE_ENGINE_ID, getDaimonEngineDescriptor, daimonEngineHasBroker } from "./engines.js";
|
|
36
36
|
export { DAIMON_ENGINES } from "./engines.js";
|
|
37
37
|
const normalizeSchedule = (node) => {
|
|
38
38
|
const schedule = node.schedule;
|
|
@@ -315,7 +315,7 @@ export const createDaimonContainerTargets = async (inputs) => {
|
|
|
315
315
|
mountPath: DAIMON_GROK_TURN_USAGE_LEDGER.directoryPath,
|
|
316
316
|
reason: "Daimon per-turn engine usage ledger"
|
|
317
317
|
}, ...(hasAgy ? [{
|
|
318
|
-
id:
|
|
318
|
+
id: daimonSubscriptionRealmMountId(DAIMON_SECRET_SERVICE_ENGINE_ID),
|
|
319
319
|
// The AGY subscription credential is an OS-keyring entry created by an
|
|
320
320
|
// interactive browser OAuth that has no headless equivalent; it lives
|
|
321
321
|
// in this volume. Without this lifecycle the volume name folds in the
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const DAIMON_ENGINE_DESCRIPTOR_SOURCE_COMMIT: "
|
|
1
|
+
export declare const DAIMON_ENGINE_DESCRIPTOR_SOURCE_COMMIT: "c5337565df0b60151ae731b17878f784c13a9337";
|
|
2
2
|
export declare const DAIMON_V5_ENGINE_DESCRIPTORS: {
|
|
3
3
|
readonly agy: {
|
|
4
4
|
readonly auth: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/* v8 ignore file -- generated data module */
|
|
2
2
|
// Generated by scripts/generate-daimon-engine-descriptors.ts from pinned Daimon static descriptors. Do not edit.
|
|
3
|
-
export const DAIMON_ENGINE_DESCRIPTOR_SOURCE_COMMIT = "
|
|
3
|
+
export const DAIMON_ENGINE_DESCRIPTOR_SOURCE_COMMIT = "c5337565df0b60151ae731b17878f784c13a9337";
|
|
4
4
|
export const DAIMON_V5_ENGINE_DESCRIPTORS = { "agy": { "auth": { "busEnvironment": "DBUS_SESSION_BUS_ADDRESS", "format": null, "method": null, "provider": null, "realm": { "directoryMode": 448, "durableMountPath": "/var/lib/spawnfile/daimon/agy-subscription-realm", "fileMode": 384, "maxUnlockBytes": 4096, "unlockMountPath": "/var/lib/spawnfile/daimon/agy-unlock-secret", "unlockSourceSlot": "agy-unlock-secret" }, "staging": "secret-service" }, "authKinds": ["subscription"], "broker": null, "capabilities": { "broker": false, "nativeSandbox": false, "portableCredential": false, "secretService": true }, "credentialMaterial": null, "homeDirectory": "", "homeEnvironment": "ANTIGRAVITY_CLI_HOME", "id": "agy", "modelSelection": { "configurable": false, "declarationRequiresBroker": false, "defaults": { "model": null, "reasoningEffort": null }, "models": null, "reasoningEfforts": [], "requiresPairedDeclaration": false }, "requiredBinaries": ["agy", "dbus-daemon", "gnome-keyring-daemon"], "sandbox": { "configurable": false }, "training": { "inferenceGrants": null, "subject": { "isolation": null, "reason": "no verified per-agent sandbox", "supported": false } } }, "codex": { "auth": { "directoryMode": 448, "fileMode": 384, "fileName": "auth.json", "format": { "accessTokenKeys": ["access_token", "accessToken", "token"], "containerKey": "tokens", "fallbackToRoot": true, "refreshTokenKeys": ["refresh_token", "refreshToken"] }, "maxCredentialBytes": 65536, "method": "codex", "provider": "openai", "realm": { "agentCredentialRelativePath": ".codex/auth.json", "bootstrapMountPath": "/var/lib/spawnfile/daimon/codex-bootstrap-auth", "bootstrapSourceSlot": "codex-auth", "directoryMode": 448, "durableMountPath": "/var/lib/spawnfile/daimon/codex-subscription-realm", "fileMode": 384, "maxCredentialBytes": 65536 }, "staging": "portable-realm" }, "authKinds": ["subscription"], "broker": null, "capabilities": { "broker": false, "nativeSandbox": true, "portableCredential": true, "secretService": false }, "credentialMaterial": { "destinationRelativePath": ".codex/auth.json", "directoryMode": 448, "fileMode": 384, "sourceRelativePath": ".daimon-inbound/codex-auth", "sourceSlot": "codex-auth" }, "homeDirectory": ".codex", "homeEnvironment": "CODEX_HOME", "id": "codex", "modelSelection": { "configurable": true, "declarationRequiresBroker": false, "defaults": { "model": null, "reasoningEffort": null }, "models": { "kind": "non-empty-string" }, "reasoningEfforts": ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra", "persistent"], "requiresPairedDeclaration": false }, "piAuthMethod": "codex", "piProvider": "openai-codex", "piSourceProvider": "openai", "requiredBinaries": ["codex"], "sandbox": { "configurable": true, "policy": { "mode": "workspace-write", "networkAccess": false, "webSearch": "disabled" } }, "training": { "inferenceGrants": null, "subject": { "isolation": "native-sandbox", "supported": true } } }, "grok": { "auth": { "directoryMode": 448, "fileMode": 384, "fileName": "auth.json", "format": { "accessTokenKeys": ["key"], "entryKeyPattern": "^https://auth\\.x\\.ai::", "expiresAtKey": "expires_at", "refreshTokenKeys": ["refresh_token"] }, "maxCredentialBytes": 65536, "method": "grok", "provider": "xai", "realm": { "agentCredentialRelativePath": ".grok/auth.json", "bootstrapMountPath": "/var/lib/spawnfile/daimon/grok-bootstrap-auth", "bootstrapSourceSlot": "grok-auth", "directoryMode": 448, "durableMountPath": "/var/lib/spawnfile/daimon/grok-subscription-realm", "fileMode": 384, "maxCredentialBytes": 65536 }, "staging": "broker-realm" }, "authKinds": ["subscription"], "broker": { "artifacts": { "arm64Sha256": "d0dc1c03197c3e90f09b66ad7c89b4f7b448d06dc84c03b247dce976d6018d6e", "sourceSha256": "15836d7ad69f12ef581a6e5b8635c9db43b2177dac609160c0749d3d1fefbaa8", "x64Sha256": "95aa9c272225e74ff9f45f54dde4bc30aee324e9ba24d8a876db9151c81b454d" }, "backendSocketPath": "/run/daimon-engine-broker/backend.sock", "bounds": { "capabilityBundleBytes": 8196, "capabilityBytes": 4096, "outputBytes": 262144, "promptBytes": 65536 }, "controlProtocolVersion": "noopolis.daimon.engine-broker.v4", "controlSocketPath": "/run/daimon-engine-broker/control.sock", "credentialHomePath": "/var/lib/spawnfile/daimon/grok-subscription-realm", "grokCliArtifacts": { "arm64": { "bytes": 136090504, "sha256": "39ab87666877d64ef3a40aa60fbe0c3b6a6acd7001b78fe60e2c76bb6cfc4a94", "url": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-1.0.34-linux-aarch64" }, "x64": { "bytes": 163035648, "sha256": "be5905e107d2b8b5f3c142d21ecfe4c8fd32a913d2fd551b788707930c4dc80d", "url": "https://storage.googleapis.com/grok-build-public-artifacts/cli/grok-1.0.34-linux-x86_64" } }, "grokCliBuild": "3736acbc8658", "grokCliVersion": "1.0.34", "grokExecutablePath": "/usr/local/bin/grok", "identities": { "brokerUid": 2100, "firstWorkerUid": 2200, "organizationUid": 2000 }, "inferenceGrants": { "bodyMembers": ["messages", "model", "reasoning_effort", "response_format", "stream", "stream_options"], "client": { "configSha256": { "grok-4.5": { "high": "ffbc33728b821e9854fbc7c93601e599225da421ecfd6ebf10d314afcc28d6f2", "low": "a07f7436f1268bb399ec233c65d3b3d8fb99a11a1f175f8da1ca133c9367bc74", "medium": "1f4c0d4dad1f3b09419b5739db6423a09e0049abc091594c64123a75dd53dfb9" }, "grok-4.6": { "high": "5652656effa82f0c4f09cf8226b16e6140332a5a358b571194bb5563312367ac", "low": "79314d039f787e4ebfec7dacf57adc969086b948f564dec008f0ed6367e6062f", "medium": "6f538de0547c0c4e6a3f04ae08595ceadadabb06b75f6b6ee4c428744bb95cd8" }, "grok-build": { "high": "98d16f2b7d12f4eb540d625c853e51d227933e204923e43e8b9b4176f10aca2c", "low": "ca15c6a562a008227d39c51d3a3a83715663089b3784e8b46debb1fb67b3c4a1", "medium": "01783fb6beadcf6f8486fff0836820ad43fab5662b812a907fe9cfdb83e9804d" } }, "envKey": "DAIMON_INFERENCE_GRANT", "modelId": "daimon-inference-grok" }, "failureCodes": ["auth_stale", "grant_limit", "invalid_request", "unavailable"], "ledgerDedupeKey": ["grant", "request"], "ledgerVersion": "noopolis.daimon.inference-usage.v1", "limits": { "maxRequests": 64, "maxTokens": 2000000 }, "maxInFlightRequestsPerGrant": 1, "maxLiveGrants": 8, "messageRoles": ["system", "user", "assistant"], "purposes": ["judge", "optimizer"], "requestKinds": ["request_inference_grant", "release_inference_grant"], "tokenPrefix": "inference_", "ttlMs": 600000 }, "launcherSocketPath": "/run/daimon-engine-broker/launcher.sock", "mcpFacade": { "host": "127.0.0.1", "path": "/mcp", "port": 43124 }, "nativeAbiVersion": 2, "nativeExecutablePath": "/opt/daimon/bin/daimon-engine-broker", "projectionVersion": "noopolis.daimon.grok-broker-projection.v3", "providerProxy": { "host": "127.0.0.1", "port": 43123 }, "registrationPath": "/etc/daimon-engine-broker/registrations.bin", "runRequestBudget": { "countsCredentialRetries": true, "fileName": "run-requests.json", "includesInferenceGrants": true, "maximumField": "maxRunRequests", "storeField": "runRequestStore" }, "serviceConfigPath": "/etc/daimon-engine-broker/service.json", "serviceConfigVersions": ["noopolis.daimon.engine-broker-service.v1", "noopolis.daimon.engine-broker-service.v2", "noopolis.daimon.engine-broker-service.v3"], "slotPreflightVersion": "noopolis.daimon.grok-slot-preflight.v2", "slotRouting": { "controlCaller": "organization", "key": "slot", "repeatedAgentIds": true, "resetRequest": "reset_slot", "resetResponse": "slot_reset", "slotEnvironment": "DAIMON_ENGINE_BROKER_SLOT", "turnStoreDirectory": "slot-{slot}", "workersMayControl": false }, "turnLimits": { "bounds": { "maxRequests": [1, 48], "maxTokens": [1, 10000000], "timeoutMs": [1000, 3600000] }, "keys": ["maxRequests", "maxTokens", "timeoutMs"], "limitReasons": ["tokens", "requests", "timeout", "none"], "maxInFlightRequests": 1, "missingUsageEstimate": { "inputBytesPerToken": 2, "outputTokens": 4096 }, "requestUsageMaxTokens": 500000, "tokenCeilingOvershoot": "at-most-one-request", "v1Defaults": { "maxRequests": 32, "maxTokens": 300000, "timeoutMs": 240000 }, "wakeMayOnlyLower": true }, "turnRecordVersions": ["noopolis.daimon.engine-broker-turn.v1", "noopolis.daimon.engine-broker-turn.v2"], "turnStorePath": "/var/lib/spawnfile/daimon/grok-subscription-realm/turns", "wakeLimitEnvironment": { "maxTokens": "DAIMON_ENGINE_WAKE_TOKEN_CEILING", "timeoutMs": "DAIMON_ENGINE_WAKE_TIMEOUT_MS" }, "worker": { "clockedConfigSha256": { "grok-4.5": { "high": "907d8967960c342b442d6d256b2585f3c85bd846a78578f596f0b605403d6d34", "low": "3c4f3515b21c1bffb3fb770e2ecb4b918b6c43d5e77c1fd47e1e6698d1fedee7", "medium": "9117e12b2f3f6fa045799592969cbaae1c2f3b1ea79330badac8655b9b41dd7a" }, "grok-4.6": { "high": "203744f6f0dfc0bc80cfb896880497edfd9b3de4dcf33060e8ef4d2ef5f220a7", "low": "751f1dcd04983e5e5883df5a8fefa04f201e187e5ef1f344d3bf83f950830432", "medium": "f1406f93a8091eb0c09e07321254a9c783a89b0bbf5548d9613d53a8d087a517" }, "grok-build": { "high": "6efddadc82c38020d3351829e54bb09fa456cd6d76ea1247902794d82a4a3ffa", "low": "a8c63ba910623a0686930c27db0733f4a2588c7fca9dd7da25c3f18027720ab5", "medium": "6afcd2b90c0d19794f8c0d22147a0f011946a92e613f89dae98b0ca729313101" } }, "configSha256": { "grok-4.5": { "high": "c63c3387ce92d94ec3f690abfe98942afcd7c9e17ff84816bbe751f340ab251f", "low": "8247127c3625ff7c5d8d527a53596b89ec6557a821ac46cfd00bd122b90daff6", "medium": "59288cee61297bb8c002097061a48f77b09d310754187a253ee089f7172a9155" }, "grok-4.6": { "high": "65b0212564fb74042b1503d293fb8d3620276033264c0efade2a539ca09218e3", "low": "ab58499ac32678097c146479896f2b8a8e2b0e39aea22dc0a60b6227e370538e", "medium": "df1a5cc84346e7f6bf6090492fbd19faaefb42953e3bb2e8c6cbc0572242403f" }, "grok-build": { "high": "a23724e00d670caee185ba7690d2daa868173e53905cf446f5329666f01ab4e3", "low": "fb343f2809903f26d21681470943235031f946e99085542fd89555eb7782cbb5", "medium": "8a587ef75c90eab70d19b24583e60051d6fba9d90c558839fdbb15588b4cc656" } }, "defaultModel": "grok-4.6", "defaultReasoningEffort": "low", "home": { "directory": { "group": "worker", "mode": 1017, "uid": 0 }, "organizationRuntimeHome": { "group": "worker", "mode": 456, "owner": "organization" }, "privateTmp": { "mode": 448, "owner": "worker", "relativeToWorkerHome": "tmp" }, "readOnlyFiles": { "gid": 0, "mode": 292, "names": ["config.toml", "managed_config.toml", "requirements.toml", "sandbox.toml", "trusted_folders.toml"], "uid": 0 }, "sandboxEvents": { "group": "broker", "mode": 416, "owner": "worker", "relativePath": "sessions/sandbox-events.jsonl" }, "sessionsDirectory": { "group": "worker", "mode": 1017, "relativePath": "sessions", "uid": 0 }, "sharedTmp": { "maxGroupExclusive": 2200, "mode": 1020, "otherMode": 4, "paths": ["/tmp", "/var/tmp"], "uid": 0 }, "spillDirectory": { "fileMode": 416, "group": "worker", "mode": 1512, "owner": "organization", "relativeToRuntimeHome": "tool-output" } }, "maxTurns": 48, "modelId": "daimon-broker-grok", "models": ["grok-4.6", "grok-4.5", "grok-build"], "reasoningEfforts": ["low", "medium", "high"], "systemPromptSha256": "2c31c0085a54a4efbf9c0cf0b8124c56e47f38691b7f0c7fa233a74abaa8ddf8", "taskClock": { "directoryMode": 493, "directoryPath": "/run/daimon-engine-broker/task-clock/slot-0", "fileMode": 420, "ownerUid": 2100, "preloadPath": "/usr/local/lib/daimon/libfaketime.so.1", "rootDirectoryPath": "/run/daimon-engine-broker/task-clock", "slotDirectoryPattern": "/run/daimon-engine-broker/task-clock/slot-{slot}", "slotFilePattern": "/run/daimon-engine-broker/task-clock/slot-{slot}/faketime", "timestampFilePath": "/run/daimon-engine-broker/task-clock/slot-0/faketime", "version": "noopolis.daimon.grok-broker-task-clock.v2", "workerView": "own-directory-visible-sibling-directories-denied" }, "toolIds": ["run_terminal_cmd", "read_file", "grep", "list_dir", "search_tool", "use_tool"], "visibleTools": ["grep", "list_dir", "read_file", "run_terminal_command", "search_tool", "use_tool"] } }, "capabilities": { "broker": true, "nativeSandbox": true, "portableCredential": true, "secretService": false }, "credentialMaterial": { "destinationRelativePath": ".grok/auth.json" }, "homeDirectory": ".grok", "homeEnvironment": "GROK_HOME", "id": "grok", "modelSelection": { "configurable": true, "declarationRequiresBroker": true, "defaults": { "model": "grok-4.6", "reasoningEffort": "low" }, "models": { "kind": "enum", "values": ["grok-4.6", "grok-4.5", "grok-build"] }, "reasoningEfforts": ["low", "medium", "high"], "requiresPairedDeclaration": true }, "requiredBinaries": ["grok", "daimon-engine-broker"], "sandbox": { "baseProfile": "strict", "configurable": false, "denyPaths": { "canonicalAbsolute": true, "forbiddenCharacterPattern": "[\"\\\\\\u0000-\\u001f\\u007f*?[\\]]", "rootAllowed": false, "trailingSlashAllowed": false }, "profile": "daimon-strict", "restrictNetwork": true }, "training": { "inferenceGrants": { "bodyMembers": ["messages", "model", "reasoning_effort", "response_format", "stream", "stream_options"], "client": { "configSha256": { "grok-4.5": { "high": "ffbc33728b821e9854fbc7c93601e599225da421ecfd6ebf10d314afcc28d6f2", "low": "a07f7436f1268bb399ec233c65d3b3d8fb99a11a1f175f8da1ca133c9367bc74", "medium": "1f4c0d4dad1f3b09419b5739db6423a09e0049abc091594c64123a75dd53dfb9" }, "grok-4.6": { "high": "5652656effa82f0c4f09cf8226b16e6140332a5a358b571194bb5563312367ac", "low": "79314d039f787e4ebfec7dacf57adc969086b948f564dec008f0ed6367e6062f", "medium": "6f538de0547c0c4e6a3f04ae08595ceadadabb06b75f6b6ee4c428744bb95cd8" }, "grok-build": { "high": "98d16f2b7d12f4eb540d625c853e51d227933e204923e43e8b9b4176f10aca2c", "low": "ca15c6a562a008227d39c51d3a3a83715663089b3784e8b46debb1fb67b3c4a1", "medium": "01783fb6beadcf6f8486fff0836820ad43fab5662b812a907fe9cfdb83e9804d" } }, "envKey": "DAIMON_INFERENCE_GRANT", "modelId": "daimon-inference-grok" }, "failureCodes": ["auth_stale", "grant_limit", "invalid_request", "unavailable"], "ledgerDedupeKey": ["grant", "request"], "ledgerVersion": "noopolis.daimon.inference-usage.v1", "limits": { "maxRequests": 64, "maxTokens": 2000000 }, "maxInFlightRequestsPerGrant": 1, "maxLiveGrants": 8, "messageRoles": ["system", "user", "assistant"], "purposes": ["judge", "optimizer"], "requestKinds": ["request_inference_grant", "release_inference_grant"], "tokenPrefix": "inference_", "ttlMs": 600000 }, "subject": { "isolation": "broker-worker", "supported": true } } } };
|
|
@@ -3,4 +3,4 @@ export { DAIMON_PACKAGE_NAME } from './packageName.js';
|
|
|
3
3
|
export * from './engines.js';
|
|
4
4
|
export * from './contractManifestSchema.js';
|
|
5
5
|
export { DAIMON_GATEWAY_KEY_ENV, DAIMON_GATEWAY_URL_ENV } from './gateway.js';
|
|
6
|
-
export { isDaimonGatewayRealmMountId } from './subscriptionRealms.js';
|
|
6
|
+
export { daimonProfileRealmVolume, daimonSubscriptionRealmMountId, isDaimonGatewayRealmMountId } from './subscriptionRealms.js';
|
|
@@ -3,4 +3,4 @@ export { DAIMON_PACKAGE_NAME } from './packageName.js';
|
|
|
3
3
|
export * from './engines.js';
|
|
4
4
|
export * from './contractManifestSchema.js';
|
|
5
5
|
export { DAIMON_GATEWAY_KEY_ENV, DAIMON_GATEWAY_URL_ENV } from './gateway.js';
|
|
6
|
-
export { isDaimonGatewayRealmMountId } from './subscriptionRealms.js';
|
|
6
|
+
export { daimonProfileRealmVolume, daimonSubscriptionRealmMountId, isDaimonGatewayRealmMountId } from './subscriptionRealms.js';
|
|
@@ -7,6 +7,7 @@ import { SpawnfileError } from "../../shared/index.js";
|
|
|
7
7
|
import { assertDaimonRuntimeHome, DAIMON_ORGANIZATION_RUNTIME_CONFIG_VERSIONS } from "./contractManifest.js";
|
|
8
8
|
import { DAIMON_ENGINE_DESCRIPTORS, DAIMON_ENGINES, DAIMON_PORTABLE_ENGINE_ID, DAIMON_SECRET_SERVICE_ENGINE_ID, getDaimonEngineDescriptor } from "./engines.js";
|
|
9
9
|
import { DAIMON_ORGANIZATION_UID } from "./runtimeIdentity.js";
|
|
10
|
+
import { daimonProfileUnlockMountArgs } from "./subscriptionRealms.js";
|
|
10
11
|
const MAX_OPAQUE_CREDENTIAL_BYTES = 64 * 1024;
|
|
11
12
|
export const DAIMON_GROK_ACCESS_TOKEN_MIN_BYTES = 32;
|
|
12
13
|
export const DAIMON_GROK_REFRESH_TOKEN_MIN_BYTES = 16;
|
|
@@ -276,6 +277,13 @@ export const prepareDaimonRuntimeAuth = async (input) => {
|
|
|
276
277
|
const descriptor = descriptorForEngine(engine);
|
|
277
278
|
if (descriptor.auth.staging !== staging)
|
|
278
279
|
continue;
|
|
280
|
+
const bound = descriptor.auth.staging === "secret-service" ? input.authProfile?.realms?.[engine] : undefined;
|
|
281
|
+
if (bound && descriptor.auth.staging === "secret-service") {
|
|
282
|
+
// The profile's own login (`spawnfile auth login`): its unlock secret lives in a
|
|
283
|
+
// volume Daimon checks in-container, so no host file and no host owner gate.
|
|
284
|
+
mountArgs.push(...daimonProfileUnlockMountArgs(bound.unlockVolume, descriptor.auth.realm.unlockMountPath));
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
279
287
|
const source = daimonSourcePathForEngine(engine, process.env, input.authProfile);
|
|
280
288
|
if (descriptor.auth.staging === "portable-realm") {
|
|
281
289
|
const ownerUid = await assertSafeDaimonSourceFile(source, engine, descriptor.auth.maxCredentialBytes, engine);
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import type { ResolvedAuthProfile } from "../../auth/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Realm resources for every gatewayed engine: one exclusive-reattach realm
|
|
4
|
+
* volume the auth gateway alone owns, and one opaque bootstrap leaf mounted
|
|
5
|
+
* under the gateway-private root. The volume id is the realm's identity, so a
|
|
6
|
+
* moved mount path still reattaches the same login.
|
|
7
|
+
*/
|
|
8
|
+
export declare const daimonSubscriptionRealmMountId: (engine: string) => string;
|
|
9
|
+
/** The file inside a profile's unlock volume that is mounted as the realm unlock secret. */
|
|
10
|
+
export declare const DAIMON_PROFILE_UNLOCK_FILE = "unlock";
|
|
11
|
+
/**
|
|
12
|
+
* The volume a compiled realm mount reattaches when the selected auth profile
|
|
13
|
+
* holds that engine's login (`spawnfile auth login <engine>`): the profile's
|
|
14
|
+
* realm volume instead of the deployment-lineage one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const daimonProfileRealmVolume: (mountId: string, profile: Pick<ResolvedAuthProfile, "realms"> | null | undefined) => string | undefined;
|
|
17
|
+
/** The read-only mount of a profile's unlock secret at the engine's declared unlock path. */
|
|
18
|
+
export declare const daimonProfileUnlockMountArgs: (unlockVolume: string, unlockMountPath: string) => string[];
|
|
1
19
|
/**
|
|
2
20
|
* Whether a compiled persistent mount is a gatewayed realm. An organization
|
|
3
21
|
* booted against an external gateway (`DAIMON_GATEWAY_URL` at boot) mounts
|
|
@@ -6,7 +6,20 @@ import { DAIMON_ENGINES } from "./engines.js";
|
|
|
6
6
|
* under the gateway-private root. The volume id is the realm's identity, so a
|
|
7
7
|
* moved mount path still reattaches the same login.
|
|
8
8
|
*/
|
|
9
|
-
const
|
|
9
|
+
export const daimonSubscriptionRealmMountId = (engine) => `daimon-${engine}-subscription-realm`;
|
|
10
|
+
const realmMountId = daimonSubscriptionRealmMountId;
|
|
11
|
+
/** The file inside a profile's unlock volume that is mounted as the realm unlock secret. */
|
|
12
|
+
export const DAIMON_PROFILE_UNLOCK_FILE = "unlock";
|
|
13
|
+
/**
|
|
14
|
+
* The volume a compiled realm mount reattaches when the selected auth profile
|
|
15
|
+
* holds that engine's login (`spawnfile auth login <engine>`): the profile's
|
|
16
|
+
* realm volume instead of the deployment-lineage one.
|
|
17
|
+
*/
|
|
18
|
+
export const daimonProfileRealmVolume = (mountId, profile) => Object.entries(profile?.realms ?? {}).find(([engine]) => realmMountId(engine) === mountId)?.[1].realmVolume;
|
|
19
|
+
/** The read-only mount of a profile's unlock secret at the engine's declared unlock path. */
|
|
20
|
+
export const daimonProfileUnlockMountArgs = (unlockVolume, unlockMountPath) => [
|
|
21
|
+
"--mount", `type=volume,source=${unlockVolume},target=${unlockMountPath},volume-subpath=${DAIMON_PROFILE_UNLOCK_FILE},readonly`
|
|
22
|
+
];
|
|
10
23
|
/**
|
|
11
24
|
* Whether a compiled persistent mount is a gatewayed realm. An organization
|
|
12
25
|
* booted against an external gateway (`DAIMON_GATEWAY_URL` at boot) mounts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { SubjectAdapter } from '../../../../training/index.js';
|
|
1
|
+
import type { SubjectAdapter, Usage } from '../../../../training/index.js';
|
|
2
2
|
export declare const BOOTED_ADAPTER_ID = "daimon-booted";
|
|
3
3
|
export declare const BOOTED_EVIDENCE_SCHEMA = "spawnfile.training.daimon-booted.wake.v1";
|
|
4
4
|
/** One ledger row a wake appended (`noopolis.daimon.turn-usage.v1`), as far as Training reads it. */
|
|
@@ -17,6 +17,8 @@ export interface BootedWake {
|
|
|
17
17
|
text: string;
|
|
18
18
|
usage: readonly BootedUsageRow[];
|
|
19
19
|
transcript: string | null;
|
|
20
|
+
/** Per model request, the tool-call names its response carried; null where the runtime could not say. */
|
|
21
|
+
requestToolCalls?: readonly (readonly string[] | null)[];
|
|
20
22
|
}
|
|
21
23
|
export interface BootedTrialHandle {
|
|
22
24
|
wake(text: string, options: {
|
|
@@ -38,6 +40,8 @@ export interface BootedAdapterOptions {
|
|
|
38
40
|
launcher: BootedTrialLauncher;
|
|
39
41
|
timeoutMs: number;
|
|
40
42
|
}
|
|
43
|
+
/** Ledger rows to `Usage`; no rows means unmetered, never zero. */
|
|
44
|
+
export declare const bootedUsage: (rows: readonly BootedUsageRow[]) => Usage | null;
|
|
41
45
|
/**
|
|
42
46
|
* A training trial as a production-booted organization: the container is the
|
|
43
47
|
* isolation, the wake is the turn, and the evidence is what the runtime itself
|