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.
Files changed (87) hide show
  1. package/dist/audit/readAudit.d.ts +3 -3
  2. package/dist/auth/profileStore.d.ts +3 -1
  3. package/dist/auth/profileStore.js +31 -30
  4. package/dist/auth/types.d.ts +16 -0
  5. package/dist/cli/authCommands.d.ts +1 -1
  6. package/dist/cli/authCommands.js +21 -1
  7. package/dist/cli/runCli.d.ts +2 -0
  8. package/dist/cli/runCli.js +2 -1
  9. package/dist/cli/trainingBooted.d.ts +2 -0
  10. package/dist/cli/trainingBooted.js +5 -1
  11. package/dist/compiler/containerDaimonBrokerRender.js +12 -2
  12. package/dist/compiler/containerDaimonGatewayRender.js +4 -0
  13. package/dist/compiler/containerDaimonUidEntrypointRender.js +3 -1
  14. package/dist/compiler/runProject.d.ts +2 -0
  15. package/dist/compiler/runProject.js +6 -5
  16. package/dist/compiler/runProjectAuth.js +4 -1
  17. package/dist/compiler/training/rewind/contract.d.ts +6 -6
  18. package/dist/compiler/training/trial/boot.d.ts +20 -1
  19. package/dist/compiler/training/trial/boot.js +102 -24
  20. package/dist/compiler/training/trial/docker.d.ts +2 -0
  21. package/dist/compiler/training/trial/docker.js +1 -1
  22. package/dist/compiler/training/trial/gateway.d.ts +5 -0
  23. package/dist/compiler/training/trial/gateway.js +18 -3
  24. package/dist/compiler/training/trial/homeLogin.d.ts +31 -0
  25. package/dist/compiler/training/trial/homeLogin.js +69 -0
  26. package/dist/compiler/training/trial/index.d.ts +5 -1
  27. package/dist/compiler/training/trial/index.js +5 -1
  28. package/dist/compiler/training/trial/judge.d.ts +15 -0
  29. package/dist/compiler/training/trial/judge.js +42 -0
  30. package/dist/compiler/training/trial/launcher.d.ts +28 -13
  31. package/dist/compiler/training/trial/launcher.js +168 -24
  32. package/dist/compiler/training/trial/login.d.ts +35 -0
  33. package/dist/compiler/training/trial/login.js +85 -0
  34. package/dist/compiler/training/trial/realmTurns.d.ts +20 -0
  35. package/dist/compiler/training/trial/realmTurns.js +69 -0
  36. package/dist/manifest/renderSpawnfile.js +2 -1
  37. package/dist/runtime/daimon/config.js +3 -3
  38. package/dist/runtime/daimon/engineDescriptors.generated.d.ts +1 -1
  39. package/dist/runtime/daimon/engineDescriptors.generated.js +1 -1
  40. package/dist/runtime/daimon/index.d.ts +1 -1
  41. package/dist/runtime/daimon/index.js +1 -1
  42. package/dist/runtime/daimon/runAuth.js +8 -0
  43. package/dist/runtime/daimon/subscriptionRealms.d.ts +18 -0
  44. package/dist/runtime/daimon/subscriptionRealms.js +14 -1
  45. package/dist/runtime/daimon/training/booted/adapter.d.ts +5 -1
  46. package/dist/runtime/daimon/training/booted/adapter.js +3 -2
  47. package/dist/runtime/daimon/training/booted/adapter.js.map +1 -1
  48. package/dist/runtime/daimon/training/booted/index.d.ts +3 -1
  49. package/dist/runtime/daimon/training/booted/index.js +2 -1
  50. package/dist/runtime/daimon/training/booted/index.js.map +1 -1
  51. package/dist/runtime/daimon/training/booted/judge.d.ts +39 -0
  52. package/dist/runtime/daimon/training/booted/judge.js +122 -0
  53. package/dist/runtime/daimon/training/booted/judge.js.map +1 -0
  54. package/dist/runtime/trainingGraderProviders.js +10 -4
  55. package/dist/runtime/trainingProviders.d.ts +4 -1
  56. package/dist/runtime/trainingProviders.js +13 -6
  57. package/dist/runtime/usageRequestLedger.d.ts +2 -0
  58. package/dist/runtime/usageRequestLedger.js +3 -1
  59. package/dist/shared/trainingLegacy/trainingLegacyHashDomains.d.ts +1 -0
  60. package/dist/shared/trainingLegacy/trainingLegacyHashDomains.js +1 -0
  61. package/dist/shared/trainingLegacy/trainingLegacyHashDomains.js.map +1 -1
  62. package/package.json +1 -1
  63. package/runtimes.yaml +5 -5
  64. package/dist/runtime/daimon/training/providers/codex-subscription/index.d.ts +0 -1
  65. package/dist/runtime/daimon/training/providers/codex-subscription/index.js +0 -2
  66. package/dist/runtime/daimon/training/providers/codex-subscription/index.js.map +0 -1
  67. package/dist/runtime/daimon/training/providers/codex-subscription/protocol.d.ts +0 -17
  68. package/dist/runtime/daimon/training/providers/codex-subscription/protocol.js +0 -80
  69. package/dist/runtime/daimon/training/providers/codex-subscription/protocol.js.map +0 -1
  70. package/dist/runtime/daimon/training/providers/codex-subscription/provider.d.ts +0 -12
  71. package/dist/runtime/daimon/training/providers/codex-subscription/provider.js +0 -125
  72. package/dist/runtime/daimon/training/providers/codex-subscription/provider.js.map +0 -1
  73. package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.d.ts +0 -6
  74. package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.js +0 -219
  75. package/dist/runtime/daimon/training/providers/codex-subscription/strict-schema.js.map +0 -1
  76. package/dist/runtime/daimon/training/providers/grok-subscription/cli.d.ts +0 -19
  77. package/dist/runtime/daimon/training/providers/grok-subscription/cli.js +0 -81
  78. package/dist/runtime/daimon/training/providers/grok-subscription/cli.js.map +0 -1
  79. package/dist/runtime/daimon/training/providers/grok-subscription/index.d.ts +0 -3
  80. package/dist/runtime/daimon/training/providers/grok-subscription/index.js +0 -4
  81. package/dist/runtime/daimon/training/providers/grok-subscription/index.js.map +0 -1
  82. package/dist/runtime/daimon/training/providers/grok-subscription/protocol.d.ts +0 -14
  83. package/dist/runtime/daimon/training/providers/grok-subscription/protocol.js +0 -89
  84. package/dist/runtime/daimon/training/providers/grok-subscription/protocol.js.map +0 -1
  85. package/dist/runtime/daimon/training/providers/grok-subscription/provider.d.ts +0 -9
  86. package/dist/runtime/daimon/training/providers/grok-subscription/provider.js +0 -37
  87. package/dist/runtime/daimon/training/providers/grok-subscription/provider.js.map +0 -1
@@ -3,12 +3,12 @@ import type { AuditReport } from "./types.js";
3
3
  export declare const auditReportSchema: z.ZodObject<{
4
4
  checks: z.ZodArray<z.ZodObject<{
5
5
  category: z.ZodEnum<{
6
- provenance: "provenance";
7
- causal: "causal";
8
6
  container: "container";
9
- control: "control";
10
7
  memory: "memory";
11
8
  network: "network";
9
+ provenance: "provenance";
10
+ causal: "causal";
11
+ control: "control";
12
12
  }>;
13
13
  evidence: z.ZodRecord<z.ZodString, z.ZodUnknown>;
14
14
  id: z.ZodString;
@@ -1,9 +1,11 @@
1
- import type { AuthProfile, ImportedAuthKind, ResolvedAuthProfile } from "./types.js";
1
+ import type { AuthProfile, ImportedAuthKind, ResolvedAuthProfile, SubscriptionRealmBinding } from "./types.js";
2
2
  export declare const createResolvedAuthProfile: (profileName: string, profile: AuthProfile) => ResolvedAuthProfile;
3
3
  export declare const loadAuthProfile: (profileName: string) => Promise<ResolvedAuthProfile | null>;
4
4
  export declare const requireAuthProfile: (profileName: string) => Promise<ResolvedAuthProfile>;
5
5
  export declare const ensureAuthProfile: (profileName: string) => Promise<ResolvedAuthProfile>;
6
6
  export declare const setAuthProfileEnv: (profileName: string, env: Record<string, string>) => Promise<ResolvedAuthProfile>;
7
+ /** Records an engine's volume-held login on the profile (names only, never login bytes). */
8
+ export declare const registerSubscriptionRealm: (profileName: string, engine: string, binding: SubscriptionRealmBinding) => Promise<ResolvedAuthProfile>;
7
9
  export declare const registerImportedAuth: (profileName: string, kind: ImportedAuthKind) => Promise<{
8
10
  directory: string;
9
11
  profile: ResolvedAuthProfile;
@@ -4,7 +4,7 @@ import { ensurePrivateDirectory, fileExists, readUtf8File, removeDirectory, writ
4
4
  import { SpawnfileError } from "../shared/index.js";
5
5
  import { resolveAuthHome, resolveImportedAuthDirectory, resolveProfileDirectory, resolveProfilePath, resolveProfilesRoot } from "./paths.js";
6
6
  const importedAuthEntrySchema = z.object({
7
- kind: z.enum(["claude-code", "codex"]),
7
+ kind: z.enum(["claude-code", "codex", "grok"]),
8
8
  relativePath: z.string().min(1)
9
9
  });
10
10
  const authProfileSchema = z.object({
@@ -12,9 +12,15 @@ const authProfileSchema = z.object({
12
12
  imports: z
13
13
  .object({
14
14
  "claude-code": importedAuthEntrySchema.optional(),
15
- codex: importedAuthEntrySchema.optional()
15
+ codex: importedAuthEntrySchema.optional(),
16
+ grok: importedAuthEntrySchema.optional()
16
17
  })
17
18
  .default({}),
19
+ realms: z.record(z.string().regex(/^[a-z][a-z0-9-]*$/u), z.object({
20
+ realmVolume: z.string().min(1),
21
+ unlockVolume: z.string().min(1),
22
+ homeVolume: z.string().min(1).optional()
23
+ }).strict()).optional(),
18
24
  version: z.literal(1)
19
25
  });
20
26
  const createEmptyProfile = () => ({
@@ -35,6 +41,17 @@ export const createResolvedAuthProfile = (profileName, profile) => ({
35
41
  name: profileName,
36
42
  profileDirectory: resolveProfileDirectory(profileName),
37
43
  profilePath: resolveProfilePath(profileName),
44
+ ...(profile.realms && Object.keys(profile.realms).length > 0 ? { realms: structuredClone(profile.realms) } : {}),
45
+ version: 1
46
+ });
47
+ /** The stored form of a resolved profile: import paths relative to its directory again. */
48
+ const storedProfile = (current) => ({
49
+ env: { ...current.env },
50
+ imports: Object.fromEntries(Object.entries(current.imports).map(([kind, entry]) => [
51
+ kind,
52
+ { kind: entry.kind, relativePath: path.relative(current.profileDirectory, entry.path) }
53
+ ])),
54
+ ...(current.realms ? { realms: structuredClone(current.realms) } : {}),
38
55
  version: 1
39
56
  });
40
57
  export const loadAuthProfile = async (profileName) => {
@@ -80,20 +97,13 @@ const writeProfile = async (profileName, profile) => {
80
97
  };
81
98
  export const setAuthProfileEnv = async (profileName, env) => {
82
99
  const current = (await loadAuthProfile(profileName)) ?? (await ensureAuthProfile(profileName));
83
- return writeProfile(profileName, {
84
- env: {
85
- ...current.env,
86
- ...env
87
- },
88
- imports: Object.fromEntries(Object.entries(current.imports).map(([kind, entry]) => [
89
- kind,
90
- {
91
- kind: entry.kind,
92
- relativePath: path.relative(current.profileDirectory, entry.path)
93
- }
94
- ])),
95
- version: 1
96
- });
100
+ const stored = storedProfile(current);
101
+ return writeProfile(profileName, { ...stored, env: { ...stored.env, ...env } });
102
+ };
103
+ /** Records an engine's volume-held login on the profile (names only, never login bytes). */
104
+ export const registerSubscriptionRealm = async (profileName, engine, binding) => {
105
+ const stored = storedProfile((await loadAuthProfile(profileName)) ?? (await ensureAuthProfile(profileName)));
106
+ return writeProfile(profileName, { ...stored, realms: { ...stored.realms, [engine]: { ...binding } } });
97
107
  };
98
108
  export const registerImportedAuth = async (profileName, kind) => {
99
109
  const current = (await loadAuthProfile(profileName)) ?? (await ensureAuthProfile(profileName));
@@ -104,22 +114,13 @@ export const registerImportedAuth = async (profileName, kind) => {
104
114
  if (kind !== "grok")
105
115
  await removeDirectory(importDirectory);
106
116
  await ensurePrivateDirectory(importDirectory);
117
+ const stored = storedProfile(current);
107
118
  const nextProfile = {
108
- env: { ...current.env },
119
+ ...stored,
109
120
  imports: {
110
- ...Object.fromEntries(Object.entries(current.imports).map(([entryKind, entry]) => [
111
- entryKind,
112
- {
113
- kind: entry.kind,
114
- relativePath: path.relative(current.profileDirectory, entry.path)
115
- }
116
- ])),
117
- [kind]: {
118
- kind,
119
- relativePath: path.relative(current.profileDirectory, importDirectory)
120
- }
121
- },
122
- version: 1
121
+ ...stored.imports,
122
+ [kind]: { kind, relativePath: path.relative(current.profileDirectory, importDirectory) }
123
+ }
123
124
  };
124
125
  return {
125
126
  directory: importDirectory,
@@ -3,9 +3,23 @@ export interface ImportedAuthEntry {
3
3
  kind: ImportedAuthKind;
4
4
  relativePath: string;
5
5
  }
6
+ /**
7
+ * A login that lives in Docker volumes rather than an importable file: the
8
+ * engine's durable realm volume, the volume holding its unlock secret, and the
9
+ * volume holding the engine's login files from its runtime home (seeded into
10
+ * each fresh home at boot, written back after the container stops).
11
+ * Created once by `spawnfile auth login <engine>`; the profile records names only.
12
+ */
13
+ export interface SubscriptionRealmBinding {
14
+ realmVolume: string;
15
+ unlockVolume: string;
16
+ /** Absent on a login recorded before home login files were kept: log in again. */
17
+ homeVolume?: string;
18
+ }
6
19
  export interface AuthProfile {
7
20
  env: Record<string, string>;
8
21
  imports: Partial<Record<ImportedAuthKind, ImportedAuthEntry>>;
22
+ realms?: Record<string, SubscriptionRealmBinding>;
9
23
  version: 1;
10
24
  }
11
25
  export interface ResolvedAuthProfile {
@@ -18,5 +32,7 @@ export interface ResolvedAuthProfile {
18
32
  name: string;
19
33
  profileDirectory: string;
20
34
  profilePath: string;
35
+ /** Volume-held logins by engine id; absent means none. */
36
+ realms?: Record<string, SubscriptionRealmBinding>;
21
37
  version: 1;
22
38
  }
@@ -1,5 +1,5 @@
1
1
  import type { Command } from "commander";
2
2
  import type { CliHandlers, CliStreams } from "./runCli.js";
3
- type AuthCommandHandlers = Pick<CliHandlers, "importClaudeCodeAuth" | "importCodexAuth" | "importGrokAuth" | "importEnvFile" | "initializeTargetSecretSourceLifecycle" | "provisionCredentials" | "requireAuthProfile" | "syncProjectAuth">;
3
+ type AuthCommandHandlers = Pick<CliHandlers, "importClaudeCodeAuth" | "importCodexAuth" | "importGrokAuth" | "importEnvFile" | "initializeTargetSecretSourceLifecycle" | "loginSubscriptionRealm" | "provisionCredentials" | "requireAuthProfile" | "syncProjectAuth">;
4
4
  export declare const registerAuthCommands: (program: Command, handlers: AuthCommandHandlers, streams: CliStreams, stdin: AsyncIterable<unknown>) => void;
5
5
  export {};
@@ -9,10 +9,12 @@ import { envFileAliasOption, resolveEnvFileOption, runtimeEnvFileOption } from "
9
9
  const formatAuthProfileSummary = (profile) => {
10
10
  const envKeys = Object.keys(profile.env).sort();
11
11
  const importedKinds = Object.keys(profile.imports).sort();
12
+ const realms = Object.keys(profile.realms ?? {}).sort();
12
13
  return [
13
14
  `profile: ${profile.name}`,
14
15
  `env: ${envKeys.length > 0 ? envKeys.join(", ") : "none"}`,
15
- `imports: ${importedKinds.length > 0 ? importedKinds.join(", ") : "none"}`
16
+ `imports: ${importedKinds.length > 0 ? importedKinds.join(", ") : "none"}`,
17
+ ...(realms.length > 0 ? [`logins: ${realms.join(", ")}`] : [])
16
18
  ];
17
19
  };
18
20
  const emitLines = (streams, lines) => lines.forEach((line) => streams.stdout(line));
@@ -108,6 +110,24 @@ export const registerAuthCommands = (program, handlers, streams, stdin) => {
108
110
  const profile = await handlers.importCodexAuth(options.profile, options.from);
109
111
  emitLines(streams, formatAuthProfileSummary(profile));
110
112
  });
113
+ authCommand
114
+ .command("login")
115
+ .description("Sign an auth profile into its own volume-held login (AGY) from this terminal")
116
+ .argument("<engine>", "Daimon engine whose login lives in a keyring realm, e.g. agy")
117
+ .option("-p, --profile <name>", "Auth profile name", "default")
118
+ .option("--dry-run", "Prepare the login volumes and print the login command without running it")
119
+ .action(async (engine, options) => {
120
+ if (!options.dryRun && process.stdin.isTTY !== true) {
121
+ throw new Error("spawnfile auth login is interactive; run it from a terminal");
122
+ }
123
+ const result = await handlers.loginSubscriptionRealm({ engine, profileName: options.profile, dryRun: options.dryRun === true });
124
+ emitLines(streams, [
125
+ ...formatAuthProfileSummary(result.profile),
126
+ `realm volume: ${result.binding.realmVolume}`,
127
+ `unlock volume: ${result.binding.unlockVolume}`,
128
+ ...(options.dryRun ? [`login command: ${result.command.join(" ")}`] : [])
129
+ ]);
130
+ });
111
131
  authCommand
112
132
  .command("sync")
113
133
  .description("Provision auth material a project requires into a profile")
@@ -1,3 +1,4 @@
1
+ import { loginSubscriptionRealm } from "../compiler/training/trial/index.js";
1
2
  import { importClaudeCodeAuth, importCodexAuth, importGrokAuth, importEnvFile, initializeTargetSecretSourceLifecycle, provisionCredentials, requireAuthProfile } from "../auth/index.js";
2
3
  import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, buildUpReceipt, clearProjectModelFallbacks, compileProject, createTrainingContext, createTrainingPlan, loadTrainingProject, resolveTrainingPlan, materializeTrainingMoment, lowerTrainingExecution, allocateTrainingOutputParent, initProject, listInitTemplates, publishProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
3
4
  import { devStopProject, devUpProject } from "../dev/index.js";
@@ -37,6 +38,7 @@ export interface CliHandlers {
37
38
  importClaudeCodeAuth: typeof importClaudeCodeAuth;
38
39
  importCodexAuth: typeof importCodexAuth;
39
40
  importGrokAuth: typeof importGrokAuth;
41
+ loginSubscriptionRealm: typeof loginSubscriptionRealm;
40
42
  importEnvFile: typeof importEnvFile;
41
43
  initProject: typeof initProject;
42
44
  listInitTemplates: typeof listInitTemplates;
@@ -1,4 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { loginSubscriptionRealm } from "../compiler/training/trial/index.js";
2
3
  import { Command } from "commander";
3
4
  import { importClaudeCodeAuth, importCodexAuth, importGrokAuth, importEnvFile, initializeTargetSecretSourceLifecycle, provisionCredentials, requireAuthProfile } from "../auth/index.js";
4
5
  import { addAgentProject, addProjectSurface, addProjectModelFallback, addSubagentProject, addTeamProject, buildOrganizationView, buildCompilePlan, buildProject, buildUpReceipt, clearProjectModelFallbacks, compileProject, createTrainingContext, createTrainingPlan, loadTrainingProject, resolveTrainingPlan, materializeTrainingMoment, lowerTrainingExecution, allocateTrainingOutputParent, initProject, listInitTemplates, preflightCompilePlan, publishProject, upProject, removeProjectSurface, runProject, setProjectPrimaryModel, setProjectRuntime, setProjectSurfaceAccess, showProjectSurfaces, syncProjectAuth } from "../compiler/index.js";
@@ -44,7 +45,7 @@ const createDefaultHandlers = () => ({
44
45
  addAgentProject, addProjectModelFallback, addProjectSurface,
45
46
  addSubagentProject, addTeamProject, clearProjectModelFallbacks,
46
47
  importClaudeCodeAuth, importCodexAuth, importGrokAuth, importEnvFile, initializeTargetSecretSourceLifecycle,
47
- provisionCredentials,
48
+ loginSubscriptionRealm, provisionCredentials,
48
49
  createDockerProbeGateway, exportRunArtifacts, downDeployment, inspectDockerDeployment,
49
50
  listDeploymentRecords, listHomeDeploymentRecords,
50
51
  initProject, listInitTemplates, listRuntimeAdapters, removeProjectSurface, requireAuthProfile,
@@ -3,5 +3,7 @@ import type { RunTrainingOptions } from "./trainingExecution.js";
3
3
  * Production-booted training for a Daimon subject: the training core and the
4
4
  * grader run here, and every trial is the subject's own compiled organization
5
5
  * in its own container, sharing the run's one auth gateway (and so one login).
6
+ * Every subscription judgment of the run wakes one of the run's warm, tool-free
7
+ * judge containers on that same gateway; none reads a personal login.
6
8
  */
7
9
  export declare const runBootedTraining: (options: RunTrainingOptions) => Promise<number>;
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { requireAuthProfile } from "../auth/index.js";
3
3
  import { createBootedTrialRun } from "../compiler/training/trial/index.js";
4
- import { createDaimonBootedAdapter } from "../runtime/daimon/training/booted/index.js";
4
+ import { bindBootedJudges, createDaimonBootedAdapter } from "../runtime/daimon/training/booted/index.js";
5
5
  import { SpawnfileError } from "../shared/index.js";
6
6
  const failure = (message) => new SpawnfileError("runtime_error", message);
7
7
  const flag = (args, name) => {
@@ -12,6 +12,8 @@ const flag = (args, name) => {
12
12
  * Production-booted training for a Daimon subject: the training core and the
13
13
  * grader run here, and every trial is the subject's own compiled organization
14
14
  * in its own container, sharing the run's one auth gateway (and so one login).
15
+ * Every subscription judgment of the run wakes one of the run's warm, tool-free
16
+ * judge containers on that same gateway; none reads a personal login.
15
17
  */
16
18
  export const runBootedTraining = async (options) => {
17
19
  if (!options.authProfile)
@@ -29,6 +31,7 @@ export const runBootedTraining = async (options) => {
29
31
  authProfile: await requireAuthProfile(options.authProfile),
30
32
  scratchRoot: path.join(path.resolve(out), "booted-trials")
31
33
  });
34
+ bindBootedJudges(run);
32
35
  try {
33
36
  const { main } = await import("../runtime/trainingCli.js");
34
37
  return await main(["train", ...options.args, ...(flag(options.args, "runtime") ? [] : ["--runtime", "daimon"]), ...instructions], {
@@ -36,6 +39,7 @@ export const runBootedTraining = async (options) => {
36
39
  }, { adapter: (_runtime, adapter) => createDaimonBootedAdapter({ launcher: run, timeoutMs: adapter.timeoutMs }) });
37
40
  }
38
41
  finally {
42
+ bindBootedJudges(undefined);
39
43
  await run.close();
40
44
  }
41
45
  };
@@ -51,9 +51,19 @@ export const renderDaimonWorkspaceResourceSecurity = (resources, owners = { link
51
51
  * `EPERM` the moment root no longer owns the path.
52
52
  */
53
53
  export const renderDaimonUsageLedgerProvisioning = () => {
54
- const { directoryPath } = DAIMON_GROK_TURN_USAGE_LEDGER;
54
+ const { directoryPath, filePath } = DAIMON_GROK_TURN_USAGE_LEDGER;
55
+ // The ledger file itself is shared too: the broker uid appends Grok rows and
56
+ // the organization uid appends Codex/AGY rows, and Daimon's wake fuse creates
57
+ // it empty as the organization uid at 0640 when absent, which leaves the
58
+ // broker unable to append and every Grok usage row silently lost. Root owns
59
+ // it first (it holds no CAP_DAC_OVERRIDE or CAP_FOWNER), then hands it to
60
+ // broker:organization at 0660, on every start so a reattached volume heals.
61
+ const owner = `${DAIMON_BROKER_UID}:${DAIMON_ORGANIZATION_UID}`;
55
62
  return [
56
- `chown 0:0 ${directoryPath} && chmod 0770 ${directoryPath} && chown ${DAIMON_BROKER_UID}:${DAIMON_ORGANIZATION_UID} ${directoryPath}`,
63
+ `chown 0:0 ${directoryPath} && chmod 0770 ${directoryPath}`,
64
+ `if [ -L ${filePath} ] || { [ -e ${filePath} ] && [ ! -f ${filePath} ]; }; then echo "usage ledger ${filePath} is not a regular file" >&2; exit 1; fi`,
65
+ `if [ -e ${filePath} ]; then chown 0:0 ${filePath}; else (umask 077 && : > ${filePath}); fi && chmod 0660 ${filePath} && chown ${owner} ${filePath}`,
66
+ `chown ${owner} ${directoryPath}`,
57
67
  `setpriv --clear-groups --reuid ${DAIMON_ORGANIZATION_UID} --regid ${DAIMON_ORGANIZATION_UID} --inh-caps=-all --ambient-caps=-all --bounding-set=-all -- bash -ceu 'probe=${directoryPath}/.daimon-usage-probe; umask 007; : > "$probe"; rm "$probe"'`
58
68
  ];
59
69
  };
@@ -88,6 +88,10 @@ export const renderDaimonGatewayProvisioning = (plans) => {
88
88
  ` ${KEY_VARIABLE}=\${${DAIMON_GATEWAY_KEY_ENV}:?${DAIMON_GATEWAY_KEY_ENV} must accompany ${DAIMON_GATEWAY_URL_ENV}}`,
89
89
  // Only the runtime and broker launches below get them, as with the in-container gateway.
90
90
  ` export -n ${DAIMON_GATEWAY_URL_ENV} ${DAIMON_GATEWAY_KEY_ENV}`,
91
+ // Empty root-only placeholders: a brokered worker's sandbox masks these paths and refuses one that is missing.
92
+ ...(descriptors.some((descriptor) => descriptor.broker !== null)
93
+ ? [DAIMON_GATEWAY_ETC, DAIMON_GATEWAY_RUN, DAIMON_GATEWAY_BOOTSTRAP_ROOT, ...descriptors.map(daimonGatewayRealmPath)] : [])
94
+ .map((target) => ` if [ ! -e ${target} ]; then install -d -o root -g root -m 0700 ${target}; fi`),
91
95
  "else",
92
96
  ` ${EXTERNAL_VARIABLE}=0 ${URL_VARIABLE}=${DAIMON_GATEWAY_URL}`,
93
97
  // Rotated every start: 32 random bytes as lowercase hex, held only in this root shell.
@@ -253,7 +253,9 @@ export const renderDaimonUidEntrypoint = (runtimePlans, persistentMountPaths = [
253
253
  " if [ \"$EUID\" -eq 0 ]; then exit 1; fi",
254
254
  " test \"$(sed -n \"s/^CapEff:[[:space:]]*//p\" /proc/self/status)\" = 0000000000000000",
255
255
  " exec \"$@\"",
256
- `' bash "\${runtime_command[@]}" &`,
256
+ // Explicit stdin: a non-interactive shell gives `&` jobs /dev/null, which would
257
+ // starve the interactive `auth <engine> login` of the operator's terminal.
258
+ `' bash "\${runtime_command[@]}" <&0 &`,
257
259
  "runtime_pid=$!",
258
260
  "trap - EXIT TERM INT HUP",
259
261
  "stopping=0",
@@ -29,6 +29,7 @@ export declare const createDockerRunInvocation: (compileResult: CompileProjectRe
29
29
  name: string;
30
30
  value: string;
31
31
  };
32
+ command?: readonly string[];
32
33
  /** See `RuntimeAuthPreparationInput.containerCredentialUid`. */
33
34
  containerCredentialUid?: number;
34
35
  containerName?: string;
@@ -38,6 +39,7 @@ export declare const createDockerRunInvocation: (compileResult: CompileProjectRe
38
39
  dockerContext?: string;
39
40
  dockerHost?: string;
40
41
  envFilePath?: string;
42
+ interactive?: boolean;
41
43
  network?: string;
42
44
  }) => Promise<DockerRunInvocation>;
43
45
  interface ResolvedRunDeploymentOptions {
@@ -6,7 +6,7 @@ import { ensureDirectory, fileExists, removeDirectory, writeUtf8File } from "../
6
6
  import { appendDockerLabelArgs, createDockerDeploymentLabels, createDockerDeploymentUnitId, createDockerProjectLabel, dockerContextNameForTarget, dockerHostValueForTarget, normalizeDeploymentName, readDeploymentRecordFromOutput, resolveDeploymentRecordPath, verifyDockerDeploymentTarget, writeDockerDeploymentRecordForRun } from "../deployment/index.js";
7
7
  import { DAIMON_DOCKER_RUNTIME_SECURITY_ARGS, DEFAULT_OUTPUT_DIRECTORY, SpawnfileError } from "../shared/index.js";
8
8
  import { ensureNoopolisRunId, resolveNoopolisRunId } from "../runtime/index.js";
9
- import { DAIMON_GATEWAY_URL_ENV, isDaimonGatewayRealmMountId } from "../runtime/daimon/index.js";
9
+ import { DAIMON_GATEWAY_URL_ENV, daimonProfileRealmVolume, isDaimonGatewayRealmMountId } from "../runtime/daimon/index.js";
10
10
  import { compileProject } from "./compileProject.js";
11
11
  import { createDefaultImageTag, resolveDockerBuildArchitecture } from "./buildProject.js";
12
12
  import { slugify } from "./helpers.js";
@@ -60,7 +60,7 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
60
60
  args.push("-d", "--restart", "unless-stopped");
61
61
  }
62
62
  else {
63
- args.push("--rm");
63
+ args.push("--rm", ...(options.interactive ? ["-it"] : []));
64
64
  }
65
65
  args.push("--name", containerName);
66
66
  if (options.network)
@@ -83,9 +83,10 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
83
83
  // reattached volume that already holds state is untouched by dropping it.
84
84
  // (Target/secrets volumes under src/target/* keep `volume-nocopy`: no
85
85
  // image content backs those paths and they must stay opaque.)
86
- // An external auth gateway in the run env holds every gatewayed realm; the org mounts none.
86
+ // An external auth gateway holds every gatewayed realm (the org mounts none); a profile-held login reattaches its volume.
87
87
  const persistentMounts = (containerReport.persistent_mounts ?? [])
88
- .filter((mount) => !env[DAIMON_GATEWAY_URL_ENV] || !isDaimonGatewayRealmMountId(mount.id));
88
+ .filter((mount) => !env[DAIMON_GATEWAY_URL_ENV] || !isDaimonGatewayRealmMountId(mount.id))
89
+ .map((mount) => ({ ...mount, volume_name: daimonProfileRealmVolume(mount.id, options.authProfile) ?? mount.volume_name }));
89
90
  for (const mount of persistentMounts) {
90
91
  args.push("--mount", `type=volume,source=${mount.volume_name},target=${mount.mount_path}`);
91
92
  }
@@ -103,7 +104,7 @@ export const createDockerRunInvocation = async (compileResult, imageTag, options
103
104
  args.push("--env-file", envFilePath);
104
105
  args.push(...(await resolveAuthMountArgs(containerReport, options.authProfile ?? null)));
105
106
  args.push(...preparedRuntimeAuth.mountArgs);
106
- args.push(imageTag);
107
+ args.push(imageTag, ...(options.command ?? []));
107
108
  return {
108
109
  args,
109
110
  command: options.dockerCommand ?? "docker",
@@ -107,6 +107,7 @@ export const assertDeclaredModelAuthSatisfied = (containerReport, authProfile, c
107
107
  throw new SpawnfileError("validation_error", `Auth profile ${authProfile.name} is missing required auth imports: ${missingImportKinds.join(", ")}`);
108
108
  }
109
109
  };
110
+ const RUNTIME_HOME_IMPORT_KINDS = ["claude-code", "codex"];
110
111
  const getImportMountTargetName = (kind) => kind === "claude-code" ? ".claude" : ".codex";
111
112
  const createGeneratedRuntimeSecret = (secretName, generatedMoltnetSecrets) => {
112
113
  if (secretName === "OPENCLAW_GATEWAY_TOKEN" || secretName === "OPENCLAW_HOOKS_TOKEN") {
@@ -221,7 +222,9 @@ export const resolveAuthMountArgs = async (containerReport, authProfile) => {
221
222
  return [];
222
223
  }
223
224
  const args = [];
224
- for (const [kind, entry] of Object.entries(authProfile.imports)) {
225
+ // Only runtime-home logins are bind-mounted; a Grok import belongs to the auth gateway alone.
226
+ for (const kind of RUNTIME_HOME_IMPORT_KINDS) {
227
+ const entry = authProfile.imports[kind];
225
228
  if (!entry) {
226
229
  continue;
227
230
  }
@@ -17,9 +17,9 @@ export declare const materializedInputSchema: z.ZodObject<{
17
17
  id: z.ZodString;
18
18
  declaredBy: z.ZodString;
19
19
  kind: z.ZodEnum<{
20
- moltnet: "moltnet";
21
- memory: "memory";
22
20
  volume: "volume";
21
+ memory: "memory";
22
+ moltnet: "moltnet";
23
23
  git: "git";
24
24
  feed: "feed";
25
25
  bundle: "bundle";
@@ -81,9 +81,9 @@ export declare const materializedMomentSchema: z.ZodObject<{
81
81
  id: z.ZodString;
82
82
  declaredBy: z.ZodString;
83
83
  kind: z.ZodEnum<{
84
- moltnet: "moltnet";
85
- memory: "memory";
86
84
  volume: "volume";
85
+ memory: "memory";
86
+ moltnet: "moltnet";
87
87
  git: "git";
88
88
  feed: "feed";
89
89
  bundle: "bundle";
@@ -228,9 +228,9 @@ export declare const materializedMomentJsonSchema: z.core.ZodStandardJSONSchemaP
228
228
  id: z.ZodString;
229
229
  declaredBy: z.ZodString;
230
230
  kind: z.ZodEnum<{
231
- moltnet: "moltnet";
232
- memory: "memory";
233
231
  volume: "volume";
232
+ memory: "memory";
233
+ moltnet: "moltnet";
234
234
  git: "git";
235
235
  feed: "feed";
236
236
  bundle: "bundle";
@@ -1,3 +1,4 @@
1
+ import type { ResolvedAuthProfile } from "../../../auth/index.js";
1
2
  import { type DaimonControlCall } from "../../../runtime/daimon/control.js";
2
3
  import { type UsageRecord } from "../../../runtime/usageLedger.js";
3
4
  import { type TrialDocker } from "./docker.js";
@@ -27,6 +28,8 @@ export interface TrialWake {
27
28
  usage: UsageRecord[];
28
29
  /** The engine's own newest session record, bounded; null when none exists. */
29
30
  transcript: string | null;
31
+ /** Per model request of this wake, the tool-call names its response carried; null where the runtime could not say. */
32
+ requestToolCalls: (readonly string[] | null)[];
30
33
  }
31
34
  export interface BootedTrial {
32
35
  container: string;
@@ -44,10 +47,26 @@ export interface PreparedTrial {
44
47
  runtimeRoot: string;
45
48
  engines: string[];
46
49
  agentId: string;
47
- boot(gateway: TrainingGatewayAccess, options?: {
50
+ /**
51
+ * Boots the organization. `gateway` is null for an organization on no
52
+ * gatewayed engine; `realms` is a profile view holding only the
53
+ * volume-held logins (`spawnfile auth login`) this organization mounts.
54
+ */
55
+ boot(gateway: TrainingGatewayAccess | null, options?: {
48
56
  readyTimeoutMs?: number;
57
+ realms?: ResolvedAuthProfile;
49
58
  }): Promise<BootedTrial>;
59
+ /** Runs the organization's own `auth <engine> login` attached to the operator's terminal, or returns its argv on `dryRun`. */
60
+ login(realms: ResolvedAuthProfile, options?: {
61
+ dryRun?: boolean;
62
+ }): Promise<readonly string[]>;
50
63
  dispose(): Promise<void>;
51
64
  }
65
+ /**
66
+ * Readable and collision-free: trial ids may share long prefixes. The cut can
67
+ * land just after a hyphen, so it is trimmed: the slug names a kebab-case
68
+ * deployment, which refuses a doubled hyphen.
69
+ */
70
+ export declare const trialSlug: (trialId: string) => string;
52
71
  /** Compiles and builds the trial's one-agent organization under its own lineage, as production does. */
53
72
  export declare const prepareTrial: (options: TrialOptions) => Promise<PreparedTrial>;