@themoltnet/agent-daemon 0.56.2 → 0.57.0

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 (3) hide show
  1. package/README.md +42 -39
  2. package/dist/cli.js +2106 -1967
  3. package/package.json +8 -7
package/dist/cli.js CHANGED
@@ -12,9 +12,9 @@ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
12
12
  import { parseArgs, promisify } from "node:util";
13
13
  import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
14
14
  import { GuestEnvironmentBoundaryError, assertGuestEnvironmentBoundary, createPiRetryTriage, findMainWorktree, isResolvedPathInsideRoot, normalizeRetryTriageResult, redactRetryTriageSecrets, resolveRuntimeProfileModel } from "@themoltnet/pi-runtime";
15
- import { FILE_SECRET_PROVIDER, FileSecretProvider, connect, createNodeSecretProviderRegistry } from "@themoltnet/sdk/node";
16
- import { constants, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
17
- import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, agentKeyKey, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, deriveMcpUrl, formatSecretReferenceString, getConfigDir, getIdentityDir, identitySeedKey, isCanonicalConfig, parseSecretReferenceString, readConfig, register, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
15
+ import { FILE_SECRET_PROVIDER, FileSecretProvider, RegisterIdentityError, boundedIdentitySignal, connect, createNodeSecretProviderRegistry, register } from "@themoltnet/sdk/node";
16
+ import { constants, copyFileSync, createReadStream, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
17
+ import { AuthenticationError, IDENTITY_ALIAS_PATTERN, MoltNetError, assertIdentityAlias, assertTrustedConfigApiUrl, createExecutorAttestor, formatSecretReferenceString, getConfigDir, getIdentityDir, isCanonicalConfig, parseSecretReferenceString, readConfig, requireSecureCredentialApiUrl, resolveAgentKey, resolveEnvSecretReference, resolveIdentitySeed } from "@themoltnet/sdk";
18
18
  import { execFile, spawn } from "node:child_process";
19
19
  import { X509Certificate, createHash, createPrivateKey, createPublicKey, randomBytes, randomUUID, timingSafeEqual, webcrypto } from "node:crypto";
20
20
  import { once } from "node:events";
@@ -26,6 +26,8 @@ import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk
26
26
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
27
27
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
28
28
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
29
+ import { homedir, platform, tmpdir } from "node:os";
30
+ import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
29
31
  import { AsyncLocalStorage } from "node:async_hooks";
30
32
  import { mkdir, open, readFile, realpath, stat, writeFile } from "node:fs/promises";
31
33
  import { pipeline } from "node:stream/promises";
@@ -37,13 +39,11 @@ import { isIP } from "node:net";
37
39
  import cors from "@fastify/cors";
38
40
  import helmet from "@fastify/helmet";
39
41
  import { Transform, Writable } from "node:stream";
40
- import { writePiConfig } from "@themoltnet/pi-runtime/pi-config";
41
42
  import { pathToFileURL } from "node:url";
42
43
  import { StringDecoder } from "node:string_decoder";
43
44
  import rateLimit from "@fastify/rate-limit";
44
45
  import Fastify from "fastify";
45
46
  import "reflect-metadata";
46
- import { homedir, platform } from "node:os";
47
47
  import { BasicConstraintsExtension, ExtendedKeyUsage, ExtendedKeyUsageExtension, IP, KeyUsageFlags, KeyUsagesExtension, SubjectAlternativeNameExtension, X509CertificateGenerator } from "@peculiar/x509";
48
48
  import { createGzip } from "node:zlib";
49
49
  //#region ../../libs/tasks/src/rubric.ts
@@ -1612,6 +1612,55 @@ Type.Object({
1612
1612
  edges: Type.Array(ProvenanceGraphEdgeSchema)
1613
1613
  }, { $id: "ProvenanceGraph" });
1614
1614
  //#endregion
1615
+ //#region ../../libs/models/src/render-method.ts
1616
+ /**
1617
+ * The `renderMethod` convention for rendered packs (#1857).
1618
+ *
1619
+ * `renderedPacks.renderMethod` is a free-text `varchar(100)`. The server
1620
+ * bifurcates on exactly one thing — whether the label starts with `server:`
1621
+ * — and everything else is a caller-authored render whose markdown the
1622
+ * caller must supply. This module is the single owner of that convention:
1623
+ * the service, the API schemas, the runtime default and the console's
1624
+ * trust-tier derivation all read from here.
1625
+ *
1626
+ * The Go CLI (`apps/moltnet-cli/cobra_pack.go`) cannot import this module;
1627
+ * it carries a pointer comment and its default must be kept in sync with
1628
+ * `DEFAULT_SERVER_RENDER_METHOD` by hand.
1629
+ *
1630
+ * Values observed in production data and accepted unchanged:
1631
+ * `server:pack-to-docs-v1`, `agent:pack-to-docs-v1`, `agent-refined`.
1632
+ * `pi:pack-to-docs-v1` is the live pi-runtime default.
1633
+ */
1634
+ /** Labels carrying this prefix are rendered deterministically by the server. */
1635
+ var SERVER_RENDER_PREFIX = "server:";
1636
+ /**
1637
+ * Prefixes that identify caller-authored markdown.
1638
+ *
1639
+ * `agent:` is the canonical documented label, `pi:` is what the pi-runtime
1640
+ * emits by default, and `agent-` covers the `agent-refined` family that is
1641
+ * live in production data.
1642
+ */
1643
+ var CALLER_AUTHORED_PREFIXES = [
1644
+ "agent:",
1645
+ "pi:",
1646
+ "agent-"
1647
+ ];
1648
+ var DEFAULT_SERVER_RENDER_METHOD = "server:pack-to-docs-v1";
1649
+ var DEFAULT_AGENT_RENDER_METHOD = "agent:pack-to-docs-v1";
1650
+ /**
1651
+ * Write-side validation pattern: a known prefix followed by at least one
1652
+ * non-whitespace character. Stored rows are never re-validated against
1653
+ * this; it applies to new writes at the API boundary only.
1654
+ */
1655
+ var RENDER_METHOD_PATTERN = `^(${[SERVER_RENDER_PREFIX, ...CALLER_AUTHORED_PREFIXES].join("|")})\\S+$`;
1656
+ var RenderMethodSchema = Type.String({
1657
+ minLength: 1,
1658
+ maxLength: 100,
1659
+ pattern: RENDER_METHOD_PATTERN,
1660
+ description: "Render method label. Server render methods start with \"server:\" and must omit renderedMarkdown; caller-authored methods start with \"agent:\", \"pi:\" or \"agent-\" and require it.",
1661
+ examples: [DEFAULT_SERVER_RENDER_METHOD, DEFAULT_AGENT_RENDER_METHOD]
1662
+ });
1663
+ //#endregion
1615
1664
  //#region ../../libs/models/src/signer-constraint.ts
1616
1665
  var SIGNER_CONSTRAINT_TYPE = {
1617
1666
  Human: "human",
@@ -2719,7 +2768,7 @@ var RenderPackInput = Type.Object({
2719
2768
  var RenderPackOutput = Type.Object({
2720
2769
  renderedPackId: Type.Union([Type.String({ format: "uuid" }), Type.Null()]),
2721
2770
  renderedCid: Type.String({ minLength: 1 }),
2722
- renderMethod: Type.String({ minLength: 1 }),
2771
+ renderMethod: RenderMethodSchema,
2723
2772
  byteSize: Type.Number({ minimum: 0 }),
2724
2773
  entriesRendered: Type.Number({ minimum: 0 }),
2725
2774
  summary: Type.String({ minLength: 1 }),
@@ -3384,8 +3433,7 @@ Type.Object({
3384
3433
  outputCid: Type.Union([Cid, Type.Null()]),
3385
3434
  usage: TaskUsage,
3386
3435
  durationMs: Type.Number({ minimum: 0 }),
3387
- error: Type.Optional(TaskError),
3388
- contentSignature: Type.Optional(Type.String())
3436
+ error: Type.Optional(TaskError)
3389
3437
  }, {
3390
3438
  $id: "TaskOutput",
3391
3439
  additionalProperties: false
@@ -3893,6 +3941,7 @@ function loadConfig() {
3893
3941
  profilePrerequisiteEnv: process.env,
3894
3942
  profilePrerequisitePath: process.env.PATH ?? "",
3895
3943
  piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
3944
+ agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3896
3945
  credentialSource: detectCredentialSource(process.env),
3897
3946
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3898
3947
  signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
@@ -3936,8 +3985,9 @@ function readBoolean(name, value) {
3936
3985
  if (value === "false") return false;
3937
3986
  throw new Error(`${name} must be either true or false`);
3938
3987
  }
3939
- function activatePiCodingAgentDir(path) {
3988
+ function activatePiCodingAgentDir(path, env = {}) {
3940
3989
  process.env["PI_CODING_AGENT_DIR"] = path;
3990
+ Object.assign(process.env, env);
3941
3991
  }
3942
3992
  function loadAgentServerEnvConfig() {
3943
3993
  return {
@@ -4803,7 +4853,6 @@ async function finalizeTask(agent, output, ctx = {}) {
4803
4853
  output: output.output,
4804
4854
  outputCid: output.outputCid,
4805
4855
  usage: output.usage,
4806
- ...output.contentSignature ? { contentSignature: output.contentSignature } : {},
4807
4856
  ...daemonState ? { daemonState } : {},
4808
4857
  ...attestation
4809
4858
  });
@@ -5381,1345 +5430,1173 @@ function initWorkerOtel(options) {
5381
5430
  })]);
5382
5431
  });
5383
5432
  }
5384
- //#endregion
5385
- //#region src/lib/pi-agent-dir.ts
5386
- function ensurePiAgentDir(repoRoot, explicitPath) {
5387
- if (explicitPath) {
5388
- mkdirSync(explicitPath, { recursive: true });
5389
- return {
5390
- path: explicitPath,
5391
- source: "env"
5392
- };
5433
+ var NAME_RE = IDENTITY_ALIAS_PATTERN;
5434
+ var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
5435
+ function assertStoreName(kind, value) {
5436
+ try {
5437
+ return assertIdentityAlias(value);
5438
+ } catch {
5439
+ throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
5393
5440
  }
5394
- const path = join(repoRoot, ".pi");
5395
- mkdirSync(path, { recursive: true });
5396
- return {
5397
- path,
5398
- source: "repo"
5399
- };
5400
- }
5401
- //#endregion
5402
- //#region src/lib/runtime-context.ts
5403
- var storage = new AsyncLocalStorage();
5404
- function runWithDaemonRuntimeContext(context, callback) {
5405
- return storage.run(context, callback);
5406
5441
  }
5407
- //#endregion
5408
- //#region src/lib/runtime-profile-retry-triage.ts
5409
- function createRuntimeProfileRetryTriage(options) {
5410
- return async (input) => {
5411
- const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
5412
- return createPiRetryTriage({
5413
- model: modelHandle,
5414
- modelRuntime,
5415
- thinkingLevel: options.runtimeProfile.thinkingLevel,
5416
- piAgentDir: options.piAgentDir,
5417
- timeoutMs: options.timeoutMs,
5418
- cwd: options.cwd
5419
- })(input);
5420
- };
5442
+ function assertProviderId(value) {
5443
+ if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
5444
+ return value;
5421
5445
  }
5422
- //#endregion
5423
- //#region src/lib/runtime-resource-reaper.ts
5424
- var RUNTIME_SLOT_PAGE_LIMIT = 200;
5425
- var ATTEMPT_LOOKUP_CONCURRENCY = 8;
5426
- var TERMINAL_ATTEMPT_STATUSES = new Set([
5427
- "completed",
5428
- "failed",
5429
- "cancelled",
5430
- "aborted",
5431
- "timed_out"
5432
- ]);
5446
+ var AgentServerStoreError = class extends Error {
5447
+ name = "AgentServerStoreError";
5448
+ constructor(code, message, options) {
5449
+ super(message, options);
5450
+ this.code = code;
5451
+ }
5452
+ };
5433
5453
  /**
5434
- * Reap daemon-owned local resources whose runtime slot is no longer usable.
5435
- * The destructive path is deliberately conservative: uncertainty about slot
5436
- * ownership, attempt state, listing completeness, or real filesystem
5437
- * containment retains resources rather than risking another daemon's data.
5454
+ * `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
5455
+ *
5456
+ * Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
5457
+ * and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
5458
+ * so honouring XDG here gave one application two config roots: on a machine
5459
+ * with the variable set, the daemon wrote identities the CLI and SDK could not
5460
+ * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
5461
+ * genuinely custom location.
5438
5462
  */
5439
- async function reapRuntimeSlotResources(deps, input) {
5440
- const now = input.now ?? Date.now();
5441
- const [activePage, idlePage] = await Promise.all([listOwnedSlots(deps.runtimeSlotStore, input, "active"), listOwnedSlots(deps.runtimeSlotStore, input, "idle")]);
5442
- const active = activePage.items;
5443
- const idle = idlePage.items;
5444
- const slots = [...active, ...idle];
5445
- const result = {
5446
- failed: 0,
5447
- failures: [],
5448
- removedSessions: 0,
5449
- removedWorkspaces: 0,
5450
- scanned: slots.length,
5451
- truncated: activePage.truncated || idlePage.truncated,
5452
- unsafePathDetails: [],
5453
- unsafePaths: 0
5454
- };
5455
- const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
5456
- for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
5457
- const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
5458
- const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
5459
- const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
5460
- kind: "ready",
5461
- paths: /* @__PURE__ */ new Set()
5462
- };
5463
- for (const item of slots) {
5464
- if (!reaped.has(item.slot.id)) continue;
5465
- if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
5466
- const sessionDir = item.session?.sessionDir;
5467
- if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
5468
- else try {
5469
- rmSync(sessionDir, {
5470
- force: true,
5471
- recursive: true
5472
- });
5473
- result.removedSessions++;
5474
- } catch (err) {
5475
- recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
5476
- }
5477
- const workspacePath = item.workspace?.worktreePath;
5478
- if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
5479
- const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
5480
- if (!workspaceRoot) {
5481
- recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
5482
- continue;
5483
- }
5484
- await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
5485
- }
5486
- if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
5487
- return result;
5463
+ function resolveAgentServerRoot(input) {
5464
+ const override = input.root?.trim();
5465
+ if (override) return override;
5466
+ return getConfigDir();
5488
5467
  }
5489
- async function listOwnedSlots(store, input, state) {
5490
- const listed = await store.listSlots({
5491
- agentName: input.agentName,
5492
- limit: RUNTIME_SLOT_PAGE_LIMIT,
5493
- runtimeProfileId: input.runtimeProfileId,
5494
- state,
5495
- teamId: input.teamId
5496
- });
5497
- return {
5498
- items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
5499
- truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
5500
- };
5468
+ function providerEnvName(providerId) {
5469
+ return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
5501
5470
  }
5502
- async function findTerminalActiveSlotIds(deps, active, result) {
5503
- const byTask = /* @__PURE__ */ new Map();
5504
- for (const item of active) {
5505
- const taskSlots = byTask.get(item.slot.lastTaskId) ?? [];
5506
- taskSlots.push(item);
5507
- byTask.set(item.slot.lastTaskId, taskSlots);
5508
- }
5509
- const terminal = /* @__PURE__ */ new Set();
5510
- await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
5511
- let attempts;
5512
- try {
5513
- attempts = await deps.taskReader.listAttempts(taskId);
5514
- } catch (err) {
5515
- recordFailure(deps, result, null, `task:${taskId}`, null, err);
5516
- return;
5517
- }
5518
- for (const item of taskSlots) {
5519
- const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
5520
- if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
5521
- }
5522
- });
5523
- return terminal;
5471
+ function assertProviderEnvName(providerId, value) {
5472
+ const expected = providerEnvName(providerId);
5473
+ if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
5474
+ return value;
5524
5475
  }
5525
- async function slotStillQualifiesForReaping(deps, input, original, now, result) {
5526
- let page;
5476
+ function readJson(path) {
5477
+ let raw;
5527
5478
  try {
5528
- page = await listOwnedSlots(deps.runtimeSlotStore, input, original.slot.state);
5529
- } catch (err) {
5530
- recordFailure(deps, result, original.slot.id, `slot:${original.slot.id}`, null, err);
5531
- return false;
5479
+ raw = readFileSync(path, "utf8");
5480
+ } catch (cause) {
5481
+ if (cause.code === "ENOENT") return null;
5482
+ throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
5532
5483
  }
5533
- const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
5534
- if (!current) return false;
5535
- if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
5536
- return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
5537
- }
5538
- async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
5539
- if (!input.mainWorktree) return;
5540
- const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
5541
- if (!existsSync(workspaceRoot)) return;
5542
- let entries;
5543
5484
  try {
5544
- entries = readdirSync(workspaceRoot, { withFileTypes: true });
5545
- } catch (err) {
5546
- recordFailure(deps, result, null, workspaceRoot, workspaceRoot, err);
5547
- return;
5548
- }
5549
- for (const entry of entries) {
5550
- if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
5551
- const workspacePath = resolve(workspaceRoot, entry.name);
5552
- if (retainedWorkspacePaths.has(workspacePath)) continue;
5553
- const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
5554
- if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
5555
- await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
5485
+ return JSON.parse(raw);
5486
+ } catch (cause) {
5487
+ throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
5556
5488
  }
5557
5489
  }
5558
- async function removeWorkspace(deps, result, worktrees, mainWorktree, slotId, workspacePath, workspaceRoot, scratch) {
5559
- if (!existsSync(workspacePath)) return;
5560
- if (!isRealPathInsideRoot(workspacePath, workspaceRoot)) {
5561
- recordUnsafePath(deps, result, slotId, workspacePath, workspaceRoot);
5562
- return;
5563
- }
5564
- if (scratch || !mainWorktree) {
5490
+ function writeJsonAtomic(path, value) {
5491
+ const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
5492
+ try {
5493
+ writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
5494
+ renameSync(temp, path);
5495
+ } catch (cause) {
5565
5496
  try {
5566
- rmSync(workspacePath, {
5567
- force: true,
5568
- recursive: true
5569
- });
5570
- result.removedWorkspaces++;
5571
- } catch (err) {
5572
- recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
5573
- }
5574
- return;
5497
+ rmSync(temp, { force: true });
5498
+ } catch {}
5499
+ throw cause;
5575
5500
  }
5576
- if (worktrees.kind === "unavailable") {
5577
- recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
5578
- return;
5501
+ }
5502
+ var AgentServerStore = class {
5503
+ root;
5504
+ identitiesDir;
5505
+ runsDir;
5506
+ secretsDir;
5507
+ /** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
5508
+ piDir;
5509
+ constructor(root) {
5510
+ this.root = root;
5511
+ this.identitiesDir = join(root, "identities");
5512
+ this.runsDir = join(root, "runs");
5513
+ this.secretsDir = join(root, "secrets");
5514
+ this.piDir = join(root, "pi");
5579
5515
  }
5580
- try {
5581
- const canonical = realpathSync(workspacePath);
5582
- if (worktrees.paths.has(canonical)) {
5583
- await execFileText("git", [
5584
- "-C",
5585
- mainWorktree,
5586
- "worktree",
5587
- "remove",
5588
- "--force",
5589
- workspacePath
5590
- ]);
5591
- worktrees.paths.delete(canonical);
5592
- } else rmSync(workspacePath, {
5593
- force: true,
5594
- recursive: true
5516
+ get piAuthJsonPath() {
5517
+ return join(this.piDir, "auth.json");
5518
+ }
5519
+ /** Create the directory layout (0700) if missing. Idempotent. */
5520
+ ensure() {
5521
+ for (const dir of [
5522
+ this.root,
5523
+ this.identitiesDir,
5524
+ this.runsDir,
5525
+ this.secretsDir
5526
+ ]) mkdirSync(dir, {
5527
+ recursive: true,
5528
+ mode: 448
5595
5529
  });
5596
- result.removedWorkspaces++;
5597
- } catch (err) {
5598
- recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
5530
+ return this;
5599
5531
  }
5600
- }
5601
- async function listRegisteredWorktrees(mainWorktree) {
5602
- try {
5603
- const list = await execFileText("git", [
5604
- "-C",
5605
- mainWorktree,
5606
- "worktree",
5607
- "list",
5608
- "--porcelain"
5609
- ]);
5610
- return {
5611
- kind: "ready",
5612
- paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
5532
+ get statePath() {
5533
+ return join(this.root, "agent-server.json");
5534
+ }
5535
+ readAgentServerState() {
5536
+ const state = readJson(this.statePath);
5537
+ if (!state) return {
5538
+ version: 2,
5539
+ pendingRegistrations: {},
5540
+ activations: {}
5613
5541
  };
5614
- } catch (err) {
5542
+ if (!isRecord$1(state) || state.version !== 2) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported; move agent-server.json aside, run \`moltnet config migrate\`, then add or attach the agents again`);
5543
+ if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; move agent-server.json aside and configure the agent server again");
5544
+ if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 2 activation map; move agent-server.json aside, run `moltnet config migrate`, then add or attach the agents again");
5545
+ for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
5546
+ for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
5547
+ assertStoreName("agent name", alias);
5548
+ if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new AgentServerStoreError("invalid_state", `pending registration "${alias}" is not valid`);
5549
+ }
5615
5550
  return {
5616
- kind: "unavailable",
5617
- reason: errorMessage(err)
5551
+ version: 2,
5552
+ pendingRegistrations: state.pendingRegistrations,
5553
+ activations: state.activations
5618
5554
  };
5619
5555
  }
5620
- }
5621
- function isRealPathInsideRoot(path, root) {
5622
- try {
5623
- return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
5624
- } catch {
5625
- return false;
5556
+ writeAgentServerState(state) {
5557
+ writeJsonAtomic(this.statePath, state);
5626
5558
  }
5627
- }
5628
- function canonicalPath(path) {
5629
- try {
5630
- return realpathSync(path);
5631
- } catch {
5632
- return resolve(path);
5559
+ agentPath(name) {
5560
+ return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
5633
5561
  }
5634
- }
5635
- function recordUnsafePath(deps, result, slotId, path, root) {
5636
- const issue = {
5637
- kind: "unsafe_path",
5638
- path,
5639
- reason: root === null ? "No daemon-owned root was available for this workspace." : "Candidate or root could not be resolved inside the daemon-owned root.",
5640
- root,
5641
- slotId
5642
- };
5643
- result.unsafePaths++;
5644
- result.unsafePathDetails.push(issue);
5645
- emitIssue(deps, issue);
5646
- }
5647
- function recordFailure(deps, result, slotId, path, root, err) {
5648
- const issue = {
5649
- kind: "failure",
5650
- path,
5651
- reason: errorMessage(err),
5652
- root,
5653
- slotId
5654
- };
5655
- result.failed++;
5656
- result.failures.push(issue);
5657
- emitIssue(deps, issue);
5658
- }
5659
- function emitIssue(deps, issue) {
5660
- try {
5661
- deps.onIssue?.(issue);
5662
- } catch {}
5663
- }
5664
- function errorMessage(err) {
5665
- return err instanceof Error ? err.message : String(err);
5666
- }
5667
- async function execFileText(file, args) {
5668
- return new Promise((resolvePromise, reject) => {
5669
- execFile(file, args, {
5670
- encoding: "utf8",
5671
- maxBuffer: 10 * 1024 * 1024
5672
- }, (err, stdout) => {
5673
- if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
5674
- else resolvePromise(stdout);
5562
+ /** Central identity directory, shared with the Go CLI layout. */
5563
+ identityDir(name) {
5564
+ return storeChildPath(this.identitiesDir, "identity alias", name);
5565
+ }
5566
+ get identitySelectorPath() {
5567
+ return join(this.root, "identity-selector.json");
5568
+ }
5569
+ readIdentitySelector() {
5570
+ const selector = readJson(this.identitySelectorPath);
5571
+ if (!selector) return null;
5572
+ if (!isRecord$1(selector) || selector.version !== 1 || selector.default_identity !== void 0 && typeof selector.default_identity !== "string") throw new AgentServerStoreError("invalid_state", "identity-selector.json is not a supported selector document");
5573
+ if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
5574
+ return selector;
5575
+ }
5576
+ writeIdentitySelector(alias) {
5577
+ writeJsonAtomic(this.identitySelectorPath, {
5578
+ version: 1,
5579
+ default_identity: assertStoreName("identity alias", alias)
5675
5580
  });
5676
- });
5677
- }
5678
- async function mapWithConcurrency(items, concurrency, worker) {
5679
- for (let index = 0; index < items.length; index += concurrency) await Promise.all(items.slice(index, index + concurrency).map(worker));
5680
- }
5681
- //#endregion
5682
- //#region src/lib/runtime-sessions.ts
5683
- function resolveRuntimeSessionKind(claimedTask) {
5684
- const continueFrom = resolveContinueFrom(claimedTask);
5685
- if (!continueFrom) return "root";
5686
- return continueFrom.mode === "fork" ? "fork" : "extend";
5687
- }
5688
- async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
5689
- const continueFrom = resolveContinueFrom(claimedTask);
5690
- if (!continueFrom) return null;
5691
- return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
5692
- }
5693
- function applyRuntimeSessionUploadFailure(output, err) {
5694
- if (output.status !== "completed") return output;
5695
- return {
5696
- ...output,
5697
- contentSignature: void 0,
5698
- error: {
5699
- code: "runtime_session_upload_failed",
5700
- message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
5701
- retryable: isTransientUploadError(err)
5702
- },
5703
- output: null,
5704
- outputCid: null,
5705
- status: "failed"
5706
- };
5707
- }
5708
- /**
5709
- * Transient faults worth retrying in-attempt: network-level errors
5710
- * (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
5711
- * validation, not-found) will not heal on retry.
5712
- */
5713
- function isTransientUploadError(error) {
5714
- if (!(error instanceof Error)) return false;
5715
- const statusCode = error.statusCode;
5716
- if (typeof statusCode !== "number") return true;
5717
- return statusCode >= 500 || statusCode === 429;
5718
- }
5719
- var defaultSleep = (ms) => new Promise((resolve) => {
5720
- setTimeout(resolve, ms);
5721
- });
5722
- function createApiRuntimeSessionStore(args) {
5723
- const { agent, logger, uploadRetry } = args;
5724
- return {
5725
- async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
5726
- return agent.runtimeSessions.getForAttempt({
5727
- attemptN,
5728
- taskId
5729
- }, { teamId });
5730
- },
5731
- async hydrateSession(input) {
5732
- const downloaded = await agent.runtimeSessions.download({
5733
- attemptN: input.attemptN,
5734
- taskId: input.taskId
5735
- }, { teamId: input.teamId });
5736
- await mkdir(input.destinationDir, { recursive: true });
5737
- const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
5738
- await pipeline(downloaded, createWriteStream(sessionPath));
5739
- return sessionPath;
5740
- },
5741
- async uploadAttemptFinal(input) {
5742
- const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
5743
- if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
5744
- const maxTries = uploadRetry?.maxTries ?? 3;
5745
- const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
5746
- const sleep = uploadRetry?.sleep ?? defaultSleep;
5747
- for (let tryN = 1;; tryN += 1) try {
5748
- await agent.runtimeSessions.upload({
5749
- attemptN: input.attemptN,
5750
- taskId: input.taskId
5751
- }, createReadStream(sessionPath), {
5752
- parentSessionId: input.parentSessionId ?? void 0,
5753
- sessionKind: input.sessionKind,
5754
- sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
5755
- sourceSlotId: input.sourceSlotId ?? void 0
5756
- }, { teamId: input.teamId });
5757
- return;
5758
- } catch (error) {
5759
- if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
5760
- const delayMs = baseDelayMs * tryN;
5761
- logger?.warn({
5762
- event: "agent-daemon.runtime_session_upload_retry",
5763
- attemptN: input.attemptN,
5764
- delayMs,
5765
- statusCode: uploadStatusCode(error),
5766
- taskId: input.taskId,
5767
- tryN
5768
- }, "Retrying durable runtime session upload");
5769
- await sleep(delayMs);
5770
- }
5771
- }
5772
- };
5773
- }
5774
- function uploadStatusCode(error) {
5775
- const statusCode = error?.statusCode;
5776
- return typeof statusCode === "number" ? statusCode : void 0;
5777
- }
5778
- function resolveContinueFrom(claimedTask) {
5779
- return claimedTask.task.input.continueFrom;
5780
- }
5781
- //#endregion
5782
- //#region src/lib/runtime-slots.ts
5783
- function createApiRuntimeSlotStore(args) {
5784
- const { agent } = args;
5785
- return {
5786
- async beginSlot(input) {
5787
- await agent.runtimeSlots.begin({
5788
- agentName: input.agentName,
5789
- runtimeProfileId: input.runtimeProfileId,
5790
- lastAttemptN: input.lastAttemptN,
5791
- lastTaskId: input.lastTaskId,
5792
- model: input.model,
5793
- provider: input.provider,
5794
- sessionDir: input.sessionDir ?? void 0,
5795
- sessionPath: input.sessionPath ?? void 0,
5796
- slotKey: input.slotKey,
5797
- taskType: input.taskType,
5798
- workspaceId: input.workspaceId ?? void 0,
5799
- workspaceKind: input.workspaceKind,
5800
- worktreeBranch: input.worktreeBranch ?? void 0,
5801
- worktreePath: input.worktreePath ?? void 0
5802
- }, { teamId: input.teamId });
5803
- },
5804
- async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
5805
- await agent.runtimeSlots.finish({
5806
- agentName: identity.agentName,
5807
- attemptN,
5808
- runtimeProfileId: identity.runtimeProfileId,
5809
- model,
5810
- provider,
5811
- sessionPath: sessionPath ?? void 0,
5812
- slotKey,
5813
- taskId
5814
- }, { teamId });
5815
- },
5816
- async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
5817
- const resolved = await agent.runtimeSlots.findLatestForAttempt({
5818
- attemptN,
5819
- taskId
5820
- }, { teamId });
5821
- if (!resolved) return null;
5822
- return {
5823
- slot: {
5824
- expiresAtMs: resolved.slot.expiresAtMs,
5825
- id: resolved.slot.id,
5826
- runtimeProfileId: resolved.slot.runtimeProfileId
5827
- },
5828
- session: resolved.slot.sessionDir ? {
5829
- sessionDir: resolved.slot.sessionDir,
5830
- sessionPath: resolved.slot.sessionPath
5831
- } : null,
5832
- workspace: resolved.workspace ? {
5833
- kind: resolved.workspace.kind,
5834
- workspaceId: resolved.workspace.workspaceId,
5835
- worktreeBranch: resolved.workspace.worktreeBranch,
5836
- worktreePath: resolved.workspace.worktreePath
5837
- } : null
5838
- };
5839
- },
5840
- async listSlots(input) {
5841
- const query = {
5842
- agentName: input.agentName,
5843
- ...input.limit === void 0 ? {} : { limit: input.limit },
5844
- ...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
5845
- ...input.state === void 0 ? {} : { state: input.state }
5846
- };
5847
- return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
5848
- slot: {
5849
- expiresAtMs: resolved.slot.expiresAtMs,
5850
- id: resolved.slot.id,
5851
- lastAttemptN: resolved.slot.lastAttemptN,
5852
- lastTaskId: resolved.slot.lastTaskId,
5853
- runtimeProfileId: resolved.slot.runtimeProfileId,
5854
- slotKey: resolved.slot.slotKey,
5855
- state: resolved.slot.state,
5856
- taskType: resolved.slot.taskType
5857
- },
5858
- session: resolved.slot.sessionDir ? {
5859
- sessionDir: resolved.slot.sessionDir,
5860
- sessionPath: resolved.slot.sessionPath
5861
- } : null,
5862
- workspace: resolved.workspace ? {
5863
- kind: resolved.workspace.kind,
5864
- workspaceId: resolved.workspace.workspaceId,
5865
- worktreeBranch: resolved.workspace.worktreeBranch,
5866
- worktreePath: resolved.workspace.worktreePath
5867
- } : null
5868
- }));
5869
- },
5870
- async close() {}
5871
- };
5872
- }
5873
- //#endregion
5874
- //#region src/lib/secret-redaction.ts
5875
- var REDACTED = "[REDACTED]";
5876
- var MIN_SECRET_LENGTH = 8;
5877
- /**
5878
- * Redact required runtime-profile environment values before task output leaves
5879
- * the daemon. This is defense in depth for shell-capable profiles: a model may
5880
- * read a forwarded provider key, but it must not be able to publish that exact
5881
- * value through a task result.
5882
- */
5883
- function redactRequiredEnvValues(value, requiredEnv, env) {
5884
- const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
5885
- if (secrets.length === 0) return value;
5886
- const redact = (candidate) => {
5887
- if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
5888
- if (Array.isArray(candidate)) return candidate.map(redact);
5889
- if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
5890
- return candidate;
5891
- };
5892
- return redact(value);
5893
- }
5894
- //#endregion
5895
- //#region src/lib/shutdown-signal.ts
5896
- function installShutdownSignalHandlers(opts) {
5897
- const proc = opts.proc ?? process;
5898
- let drainingSignal = null;
5899
- const onSignal = (signal) => {
5900
- if (drainingSignal) {
5901
- proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
5902
- proc.exitCode = signalExitCode(signal);
5581
+ }
5582
+ /** Identity aliases available in the shared user-level store. */
5583
+ listIdentityAliases() {
5584
+ return readdirSync(this.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && IDENTITY_ALIAS_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
5585
+ }
5586
+ resolveIdentityAlias(explicit, active) {
5587
+ const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
5588
+ if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
5589
+ return assertStoreName("identity alias", alias);
5590
+ }
5591
+ readAgentConfig(alias) {
5592
+ return readJson(this.agentPath(alias));
5593
+ }
5594
+ writeAgentConfig(alias, config) {
5595
+ mkdirSync(this.identityDir(alias), {
5596
+ recursive: true,
5597
+ mode: 448
5598
+ });
5599
+ writeJsonAtomic(this.agentPath(alias), config);
5600
+ if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
5601
+ }
5602
+ removeAgentConfig(alias) {
5603
+ rmSync(this.agentPath(alias), { force: true });
5604
+ this.clearIdentitySelectorIfDefault(alias);
5605
+ }
5606
+ /** Clears the persisted default when it names `alias`. */
5607
+ clearIdentitySelectorIfDefault(alias) {
5608
+ let selector = null;
5609
+ try {
5610
+ selector = this.readIdentitySelector();
5611
+ } catch {
5903
5612
  return;
5904
5613
  }
5905
- drainingSignal = signal;
5906
- proc.exitCode = signalExitCode(signal);
5614
+ if (!selector || selector.default_identity !== alias) return;
5615
+ writeJsonAtomic(this.identitySelectorPath, { version: 1 });
5616
+ }
5617
+ readActivation(alias) {
5618
+ return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
5619
+ }
5620
+ hasPendingRegistration(alias) {
5621
+ return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
5622
+ }
5623
+ reserveRegistration(alias, apiUrl) {
5624
+ const name = assertStoreName("agent name", alias);
5625
+ const state = this.readAgentServerState();
5626
+ if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
5627
+ state.pendingRegistrations[name] = {
5628
+ apiUrl,
5629
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5630
+ };
5631
+ this.writeAgentServerState(state);
5632
+ }
5633
+ clearPendingRegistration(alias) {
5634
+ const name = assertStoreName("agent name", alias);
5635
+ const state = this.readAgentServerState();
5636
+ delete state.pendingRegistrations[name];
5637
+ this.writeAgentServerState(state);
5638
+ }
5639
+ writeActivation(activation) {
5640
+ const alias = assertStoreName("agent name", activation.alias);
5641
+ validateActivation(alias, activation);
5642
+ const state = this.readAgentServerState();
5643
+ state.activations[alias] = activation;
5644
+ if (activation.source === "managed") delete state.pendingRegistrations[alias];
5645
+ this.writeAgentServerState(state);
5646
+ }
5647
+ listActivations() {
5648
+ return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
5649
+ }
5650
+ get providersPath() {
5651
+ return join(this.root, "providers.json");
5652
+ }
5653
+ readProviders() {
5654
+ const state = readJson(this.providersPath) ?? {};
5655
+ this.validateProviders(state);
5656
+ return state;
5657
+ }
5658
+ writeProviders(state) {
5659
+ this.validateProviders(state);
5660
+ writeJsonAtomic(this.providersPath, state);
5661
+ }
5662
+ validateProviders(state) {
5663
+ for (const [id, provider] of Object.entries(state)) {
5664
+ assertProviderId(id);
5665
+ assertProviderEnvName(id, provider.envName);
5666
+ }
5667
+ }
5668
+ runDir(id) {
5669
+ return storeChildPath(this.runsDir, "run id", id);
5670
+ }
5671
+ resolveRunLogPath(id) {
5672
+ let root;
5673
+ let runDir;
5907
5674
  try {
5908
- opts.logDrain(signal);
5909
- } catch (err) {
5910
- proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
5675
+ root = realpathSync(this.runsDir);
5676
+ runDir = realpathSync(this.runDir(id));
5677
+ } catch (cause) {
5678
+ throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
5911
5679
  }
5912
- opts.drain(signal);
5913
- };
5914
- const handleSigint = () => onSignal("SIGINT");
5915
- const handleSigterm = () => onSignal("SIGTERM");
5916
- proc.on("SIGINT", handleSigint);
5917
- proc.on("SIGTERM", handleSigterm);
5918
- return { dispose: () => {
5919
- proc.off("SIGINT", handleSigint);
5920
- proc.off("SIGTERM", handleSigterm);
5921
- } };
5922
- }
5923
- function signalExitCode(signal) {
5924
- return signal === "SIGINT" ? 130 : 143;
5925
- }
5926
- //#endregion
5927
- //#region src/lib/source-attempts.ts
5928
- function createApiSourceAttemptResolver(args) {
5929
- const { agent } = args;
5930
- return {
5931
- async findOutputBranch(input) {
5932
- const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
5933
- if (!attempt || attempt.status !== "completed") return null;
5934
- return resolveOutputBranch(attempt.output);
5935
- },
5936
- async findInputRevision(input) {
5937
- const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
5938
- if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
5939
- return resolveTaskWorkspaceRevision(task.input);
5680
+ if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
5681
+ const logPath = join(runDir, "daemon.log");
5682
+ try {
5683
+ if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
5684
+ const resolvedLog = realpathSync(logPath);
5685
+ if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
5686
+ } catch (cause) {
5687
+ if (cause instanceof AgentServerStoreError) throw cause;
5688
+ if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
5940
5689
  }
5941
- };
5942
- }
5943
- function resolveOutputBranch(output) {
5944
- if (!output || typeof output !== "object") return null;
5945
- const branch = output.branch;
5946
- return typeof branch === "string" && branch.length > 0 ? branch : null;
5690
+ return logPath;
5691
+ }
5692
+ createRunDir(id) {
5693
+ const dir = this.runDir(id);
5694
+ const piDir = join(dir, "pi");
5695
+ mkdirSync(piDir, {
5696
+ recursive: true,
5697
+ mode: 448
5698
+ });
5699
+ return {
5700
+ dir,
5701
+ piDir,
5702
+ logPath: join(dir, "daemon.log")
5703
+ };
5704
+ }
5705
+ readRun(id) {
5706
+ return readJson(join(this.runDir(id), "run.json"));
5707
+ }
5708
+ writeRun(record) {
5709
+ writeJsonAtomic(join(this.runDir(record.id), "run.json"), record);
5710
+ }
5711
+ listRuns(limit = Number.POSITIVE_INFINITY) {
5712
+ let ids;
5713
+ try {
5714
+ ids = readdirSync(this.runsDir);
5715
+ } catch {
5716
+ return [];
5717
+ }
5718
+ const sortedIds = ids.filter((id) => NAME_RE.test(id)).sort().reverse();
5719
+ const selectedIds = Number.isFinite(limit) ? sortedIds.slice(0, Math.max(0, limit)) : sortedIds;
5720
+ const records = [];
5721
+ for (const id of selectedIds) {
5722
+ const record = this.readRun(id);
5723
+ if (record) records.push(record);
5724
+ }
5725
+ return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
5726
+ }
5727
+ /** Remove only completed run directories outside the configured budget. */
5728
+ pruneCompletedRuns(options) {
5729
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
5730
+ let retainedBytes = 0;
5731
+ let retainedCount = 0;
5732
+ const removed = [];
5733
+ for (const record of this.listRuns()) {
5734
+ if (record.status === "running") continue;
5735
+ const dir = this.runDir(record.id);
5736
+ const bytes = directoryBytes(dir);
5737
+ const endedAt = Date.parse(record.endedAt ?? record.startedAt);
5738
+ const expired = !Number.isFinite(endedAt) || now - endedAt > options.maxAgeMs;
5739
+ const overCount = retainedCount >= options.maxCount;
5740
+ const overBytes = retainedBytes + bytes > options.maxBytes;
5741
+ if (expired || overCount || overBytes) {
5742
+ rmSync(dir, {
5743
+ recursive: true,
5744
+ force: true
5745
+ });
5746
+ removed.push(record.id);
5747
+ continue;
5748
+ }
5749
+ retainedCount += 1;
5750
+ retainedBytes += bytes;
5751
+ }
5752
+ return removed;
5753
+ }
5754
+ };
5755
+ function directoryBytes(path) {
5756
+ let info;
5757
+ try {
5758
+ info = lstatSync(path);
5759
+ } catch {
5760
+ return 0;
5761
+ }
5762
+ if (info.isSymbolicLink()) return 0;
5763
+ if (!info.isDirectory()) return info.size;
5764
+ let total = 0;
5765
+ for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
5766
+ return total;
5947
5767
  }
5948
- //#endregion
5949
- //#region src/lib/state-dir.ts
5950
- function ensureDaemonStateDirs(mountPath) {
5951
- const rootDir = join(mountPath, ".moltnet", "d");
5952
- const piSessionsDir = join(rootDir, "pi-sessions");
5953
- mkdirSync(piSessionsDir, { recursive: true });
5954
- return {
5955
- rootDir,
5956
- piSessionsDir
5768
+ function isRecord$1(value) {
5769
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5770
+ }
5771
+ function storeChildPath(root, kind, value, suffix = "") {
5772
+ const name = assertStoreName(kind, value);
5773
+ const normalizedRoot = resolve(root);
5774
+ const candidate = resolve(normalizedRoot, `${name}${suffix}`);
5775
+ if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
5776
+ return candidate;
5777
+ }
5778
+ function isStrictDescendant(root, candidate) {
5779
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
5780
+ return candidate !== root && candidate.startsWith(rootPrefix);
5781
+ }
5782
+ function validateActivation(alias, value) {
5783
+ const invalid = () => {
5784
+ throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
5957
5785
  };
5786
+ if (!isRecord$1(value)) invalid();
5787
+ const activation = value;
5788
+ if (activation.alias !== alias) invalid();
5789
+ if (![
5790
+ "subjectId",
5791
+ "publicKey",
5792
+ "fingerprint",
5793
+ "createdAt"
5794
+ ].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
5795
+ if (activation.source === "managed") {
5796
+ if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
5797
+ return;
5798
+ }
5799
+ if (activation.source !== "external" || typeof activation.configPath !== "string" || activation.configPath.length === 0 || typeof activation.configApiUrl !== "string" || activation.configApiUrl.length === 0 || activation.apiUrl !== void 0 && typeof activation.apiUrl !== "string") invalid();
5958
5800
  }
5959
5801
  //#endregion
5960
- //#region src/lib/turn-event-logger.ts
5961
- function makeTurnEventHandler(base, context = {}) {
5962
- const log = base.child({
5963
- name: "agent-daemon.turn",
5964
- ...context
5802
+ //#region src/lib/agent-server/pi-store-config.ts
5803
+ /**
5804
+ * Materialise the Agent Server provider store as a Pi agent dir. Shared by
5805
+ * supervised runs (`runs.ts`) and direct `once`/`poll`/`drain` runs
5806
+ * (`pi-agent-dir.ts`) so both generate the same `models.json`.
5807
+ */
5808
+ /**
5809
+ * Write `models.json` + `settings.json` for the stored providers. API keys
5810
+ * appear only as `$MOLTNET_PROVIDER_<ID>_API_KEY` placeholders.
5811
+ */
5812
+ function writeStorePiConfig(piDir, providers) {
5813
+ writePiConfig({
5814
+ piDir,
5815
+ providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
5816
+ api: provider.api,
5817
+ ...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
5818
+ baseUrl: provider.baseUrl,
5819
+ models: provider.models
5820
+ }]))
5965
5821
  });
5966
- return (event, summary) => {
5967
- if (event === "text_delta") return;
5968
- log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
5969
- event,
5970
- ...summary
5971
- }, `turn.${event}`);
5972
- };
5973
5822
  }
5974
- function makeTurnEventHandlerFactory(base) {
5975
- return (claimedTask) => makeTurnEventHandler(base, {
5976
- taskId: claimedTask.task.id,
5977
- attemptN: claimedTask.attemptN
5978
- });
5823
+ /**
5824
+ * Link a shared Pi `auth.json` into `piDir`. Pi lockfiles the target and
5825
+ * rotates tokens in place, so a link (never a copy) keeps one credential
5826
+ * document. A dangling link is fine — Pi treats a missing auth.json as "no
5827
+ * subscription auth". Failing to create the link is not: the run would start
5828
+ * with a connected subscription silently unavailable.
5829
+ */
5830
+ function linkPiAuth(authPath, piDir, symlinkImpl = symlinkSync) {
5831
+ try {
5832
+ symlinkImpl(authPath, join(piDir, "auth.json"));
5833
+ } catch (cause) {
5834
+ throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
5835
+ }
5979
5836
  }
5980
5837
  //#endregion
5981
- //#region src/cli/poll-shared.ts
5982
- async function runPolling(opts) {
5983
- if (isHelpFlag(opts.argv)) {
5984
- console.log(opts.helpText);
5985
- return 0;
5986
- }
5987
- const { values } = parseArgs({
5988
- args: opts.argv,
5989
- options: {
5990
- ...commonOptionDefs(),
5991
- team: { type: "string" },
5992
- "task-types": { type: "string" },
5993
- "correlation-id": { type: "string" },
5994
- "diary-ids": { type: "string" },
5995
- "wait-for-first-task-sec": { type: "string" },
5996
- "wait-after-task-sec": { type: "string" },
5997
- "poll-interval-ms": { type: "string" },
5998
- "max-poll-interval-ms": { type: "string" },
5999
- "list-limit": { type: "string" },
6000
- sandbox: { type: "string" },
6001
- profile: {
6002
- type: "string",
6003
- multiple: true
6004
- }
6005
- }
6006
- });
6007
- if (!values.team) {
6008
- console.error("Missing required flag: --team\n");
6009
- console.error(opts.helpText);
6010
- return 1;
6011
- }
6012
- const teamId = values.team;
6013
- const profileValues = parseProfileValues(values.profile);
6014
- if (profileValues.length === 0) {
6015
- console.error("Missing required flag: --profile\n");
6016
- console.error(opts.helpText);
6017
- return 1;
5838
+ //#region src/lib/pi-agent-dir.ts
5839
+ /**
5840
+ * Pick the Pi agent dir for direct `once`/`poll`/`drain` runs:
5841
+ *
5842
+ * 1. `PI_CODING_AGENT_DIR`, unchanged (`env`).
5843
+ * 2. When the Agent Server store has providers or a subscription login, a
5844
+ * private dir built like an Agent Server run, with `<agentRoot>/.pi`
5845
+ * config the store does not define layered in (`store`).
5846
+ * 3. `<agentRoot>/.pi` (`repo`).
5847
+ */
5848
+ async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
5849
+ const noop = () => void 0;
5850
+ if (cfg.piCodingAgentDir) {
5851
+ mkdirSync(cfg.piCodingAgentDir, { recursive: true });
5852
+ return {
5853
+ path: cfg.piCodingAgentDir,
5854
+ source: "env",
5855
+ env: {},
5856
+ cleanup: noop
5857
+ };
6018
5858
  }
6019
- let taskTypes;
6020
- try {
6021
- taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
6022
- } catch (err) {
6023
- const message = err instanceof Error ? err.message : String(err);
6024
- console.error(message);
6025
- return 1;
5859
+ const repoPiDir = join(agentRoot, ".pi");
5860
+ const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
5861
+ const providers = store.readProviders();
5862
+ const storeHasAuth = existsSync(store.piAuthJsonPath);
5863
+ if (Object.keys(providers).length === 0 && !storeHasAuth) {
5864
+ mkdirSync(repoPiDir, { recursive: true });
5865
+ return {
5866
+ path: repoPiDir,
5867
+ source: "repo",
5868
+ env: {},
5869
+ cleanup: noop
5870
+ };
6026
5871
  }
6027
- const diaryIds = parseCsv(values["diary-ids"]);
6028
- let baseCommon;
5872
+ const path = mkdtempSync(join(options.tempRoot ?? tmpdir(), "moltnet-pi-"));
5873
+ const cleanup = () => rmSync(path, {
5874
+ recursive: true,
5875
+ force: true
5876
+ });
6029
5877
  try {
6030
- baseCommon = parseCommonOptions(values);
6031
- } catch (err) {
6032
- if (err instanceof MissingRequiredOptionError) {
6033
- console.error(`${err.message}\n`);
6034
- console.error(opts.helpText);
6035
- return 1;
5878
+ writeStorePiConfig(path, providers);
5879
+ const repoModelsPath = join(repoPiDir, "models.json");
5880
+ if (existsSync(repoModelsPath)) {
5881
+ const modelsPath = join(path, "models.json");
5882
+ const merged = mergePiModels(readPiModels(modelsPath), readPiModels(repoModelsPath));
5883
+ writeFileSync(modelsPath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
5884
+ }
5885
+ const repoSettingsPath = join(repoPiDir, "settings.json");
5886
+ if (existsSync(repoSettingsPath)) copyFileSync(repoSettingsPath, join(path, "settings.json"));
5887
+ const repoAuthPath = join(repoPiDir, "auth.json");
5888
+ linkPiAuth(storeHasAuth || !existsSync(repoAuthPath) ? store.piAuthJsonPath : repoAuthPath, path);
5889
+ const secretProviders = options.secretProviders ?? createNodeSecretProviderRegistry().register(new FileSecretProvider({ root: store.secretsDir }));
5890
+ const env = {};
5891
+ for (const providerId of new Set(profiles.map((p) => p.provider))) {
5892
+ const provider = providers[providerId];
5893
+ if (!provider?.apiKeyRef) continue;
5894
+ if (cfg.profilePrerequisiteEnv[provider.envName]) continue;
5895
+ try {
5896
+ env[provider.envName] = await secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
5897
+ } catch {
5898
+ throw new Error(`provider "${providerId}" API key could not be resolved from the Agent Server store`);
5899
+ }
6036
5900
  }
6037
- throw err;
6038
- }
6039
- const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
6040
- const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
6041
- const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
6042
- const waitForFirstTaskSec = optionalNonNegativeInt(values["wait-for-first-task-sec"], "wait-for-first-task-sec", 0);
6043
- const waitAfterTaskSec = optionalNonNegativeInt(values["wait-after-task-sec"], "wait-after-task-sec", 0);
6044
- if (!opts.stopWhenEmpty && (waitForFirstTaskSec > 0 || waitAfterTaskSec > 0)) {
6045
- console.error(`[${opts.modeLabel}] --wait-for-first-task-sec and --wait-after-task-sec are only valid with drain.`);
6046
- return 1;
6047
- }
6048
- if (values.sandbox) {
6049
- console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
6050
- return 1;
5901
+ return {
5902
+ path,
5903
+ source: "store",
5904
+ env,
5905
+ cleanup
5906
+ };
5907
+ } catch (error) {
5908
+ cleanup();
5909
+ throw error;
6051
5910
  }
6052
- if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
6053
- const cfg = loadConfig();
6054
- const credentialSources = {
6055
- profileRequirements: cfg.profileCredentialRequirements,
6056
- bindings: cfg.credentialBindings
5911
+ }
5912
+ function readPiModels(path) {
5913
+ try {
5914
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
5915
+ if (typeof parsed?.providers === "object" && parsed.providers !== null) return parsed;
5916
+ } catch {}
5917
+ throw new Error(`${path} is not a valid Pi models document`);
5918
+ }
5919
+ /**
5920
+ * Store entries win per provider id. Repo models the store lacks are appended
5921
+ * with their metadata; repo-only providers are added unchanged.
5922
+ */
5923
+ function mergePiModels(store, repo) {
5924
+ const providers = {
5925
+ ...repo.providers,
5926
+ ...store.providers
6057
5927
  };
6058
- const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
6059
- const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
6060
- const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
6061
- let gate = "resolve_agent_context";
6062
- try {
6063
- const resolvedContext = await resolveAgentContext(baseCommon.agent, {
6064
- agentRootDir: explicitAgentRootDir,
6065
- credentialSource: cfg.credentialSource,
6066
- envApiUrl: cfg.apiUrl
6067
- });
6068
- gate = "authenticate_and_bind";
6069
- const whoami = await validateStartupBinding({
6070
- agent: resolvedContext.agent,
6071
- teamId,
6072
- expectedAgent: cfg.expectedAgent
6073
- });
6074
- gate = "resolve_signing_material";
6075
- const privateKey = await resolveExecutorSigningPrivateKey({
6076
- credentialSource: cfg.credentialSource,
6077
- agentDir: resolvedContext.agentDir,
6078
- configuredPrivateKey: cfg.signingPrivateKey,
6079
- configuredPrivateKeyRef: cfg.signingPrivateKeyRef
6080
- });
6081
- gate = "validate_scopes";
6082
- validateDaemonScopes(whoami);
6083
- gate = "validate_signing_identity";
6084
- await validateExecutorSigningIdentity({
6085
- whoami,
6086
- signingPrivateKey: privateKey
6087
- });
6088
- gate = "resolve_agent_identity";
6089
- const agentIdentity = await resolveDaemonAgentIdentity({
6090
- agentName: baseCommon.agent,
6091
- whoami,
6092
- credentialSource: cfg.credentialSource,
6093
- agentDir: resolvedContext.agentDir,
6094
- gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
6095
- });
6096
- return {
6097
- ctx: resolvedContext,
6098
- signingPrivateKey: privateKey,
6099
- startupWhoami: whoami,
6100
- agentIdentity,
6101
- hostCapabilitySigner: createLocalSeedSigner({
6102
- privateKeySeed: privateKey,
6103
- agent: resolvedContext.agent,
6104
- identity: agentIdentity
6105
- })
6106
- };
6107
- } catch (error) {
6108
- await logDaemonStartupFailure({
6109
- serviceName: `agent-daemon.${opts.modeLabel}`,
6110
- level: cfg.logLevel || (baseCommon.debug ? "debug" : "info"),
6111
- gate,
6112
- agent: baseCommon.agent,
6113
- credentialSource: cfg.credentialSource,
6114
- error
5928
+ for (const [id, storeProvider] of Object.entries(store.providers)) {
5929
+ const repoModels = repo.providers[id]?.models ?? [];
5930
+ const known = new Set((storeProvider.models ?? []).map((m) => m.id));
5931
+ providers[id] = {
5932
+ ...storeProvider,
5933
+ models: [...storeProvider.models ?? [], ...repoModels.filter((m) => !known.has(m.id))]
5934
+ };
5935
+ }
5936
+ return { providers };
5937
+ }
5938
+ //#endregion
5939
+ //#region src/lib/runtime-context.ts
5940
+ var storage = new AsyncLocalStorage();
5941
+ function runWithDaemonRuntimeContext(context, callback) {
5942
+ return storage.run(context, callback);
5943
+ }
5944
+ //#endregion
5945
+ //#region src/lib/runtime-profile-retry-triage.ts
5946
+ function createRuntimeProfileRetryTriage(options) {
5947
+ return async (input) => {
5948
+ const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
5949
+ return createPiRetryTriage({
5950
+ model: modelHandle,
5951
+ modelRuntime,
5952
+ thinkingLevel: options.runtimeProfile.thinkingLevel,
5953
+ piAgentDir: options.piAgentDir,
5954
+ timeoutMs: options.timeoutMs,
5955
+ cwd: options.cwd
5956
+ })(input);
5957
+ };
5958
+ }
5959
+ //#endregion
5960
+ //#region src/lib/runtime-resource-reaper.ts
5961
+ var RUNTIME_SLOT_PAGE_LIMIT = 200;
5962
+ var ATTEMPT_LOOKUP_CONCURRENCY = 8;
5963
+ var TERMINAL_ATTEMPT_STATUSES = new Set([
5964
+ "completed",
5965
+ "failed",
5966
+ "cancelled",
5967
+ "aborted",
5968
+ "timed_out"
5969
+ ]);
5970
+ /**
5971
+ * Reap daemon-owned local resources whose runtime slot is no longer usable.
5972
+ * The destructive path is deliberately conservative: uncertainty about slot
5973
+ * ownership, attempt state, listing completeness, or real filesystem
5974
+ * containment retains resources rather than risking another daemon's data.
5975
+ */
5976
+ async function reapRuntimeSlotResources(deps, input) {
5977
+ const now = input.now ?? Date.now();
5978
+ const [activePage, idlePage] = await Promise.all([listOwnedSlots(deps.runtimeSlotStore, input, "active"), listOwnedSlots(deps.runtimeSlotStore, input, "idle")]);
5979
+ const active = activePage.items;
5980
+ const idle = idlePage.items;
5981
+ const slots = [...active, ...idle];
5982
+ const result = {
5983
+ failed: 0,
5984
+ failures: [],
5985
+ removedSessions: 0,
5986
+ removedWorkspaces: 0,
5987
+ scanned: slots.length,
5988
+ truncated: activePage.truncated || idlePage.truncated,
5989
+ unsafePathDetails: [],
5990
+ unsafePaths: 0
5991
+ };
5992
+ const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
5993
+ for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
5994
+ const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
5995
+ const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
5996
+ const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
5997
+ kind: "ready",
5998
+ paths: /* @__PURE__ */ new Set()
5999
+ };
6000
+ for (const item of slots) {
6001
+ if (!reaped.has(item.slot.id)) continue;
6002
+ if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
6003
+ const sessionDir = item.session?.sessionDir;
6004
+ if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
6005
+ else try {
6006
+ rmSync(sessionDir, {
6007
+ force: true,
6008
+ recursive: true
6115
6009
  });
6116
- throw error;
6010
+ result.removedSessions++;
6011
+ } catch (err) {
6012
+ recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
6117
6013
  }
6118
- })();
6119
- const daemonRootDir = explicitAgentRootDir ?? process.cwd();
6120
- const resolvedProfiles = await resolveRuntimeProfiles({
6121
- agent: ctx.agent,
6122
- profiles: profileValues,
6123
- teamId,
6124
- cwd: daemonRootDir
6125
- });
6126
- const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
6127
- const preparedRuntimes = /* @__PURE__ */ new Map();
6128
- const profiles = [];
6129
- const skippedProfileBoundaries = [];
6130
- for (const profile of resolvedProfiles) try {
6131
- assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
6132
- const prepared = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
6133
- validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
6134
- tools: prepared.tools,
6135
- executables: prepared.executables
6136
- });
6137
- assertGuestEnvironmentBoundary({
6138
- forwardEnv: profile.requiredEnv,
6139
- sandboxEnv: profile.sandboxConfig.env
6140
- });
6141
- await ctx.agent.tasks.registerExecutorManifest(await prepared.attestor.registration());
6142
- preparedRuntimes.set(profile.id, prepared);
6143
- profiles.push(profile);
6144
- } catch (error) {
6145
- if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
6146
- skippedProfileBoundaries.push({
6147
- profile,
6148
- error
6149
- });
6150
- }
6151
- if (profiles.length === 0) {
6152
- const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
6153
- throw new Error(`No safe runtime profiles remain. ${details}`);
6014
+ const workspacePath = item.workspace?.worktreePath;
6015
+ if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
6016
+ const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
6017
+ if (!workspaceRoot) {
6018
+ recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
6019
+ continue;
6020
+ }
6021
+ await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
6154
6022
  }
6155
- const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
6156
- const runtimeSessionStore = createApiRuntimeSessionStore({
6157
- agent: ctx.agent,
6158
- logger: { warn: (context, message) => rootLogger.warn(context, message) }
6023
+ if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
6024
+ return result;
6025
+ }
6026
+ async function listOwnedSlots(store, input, state) {
6027
+ const listed = await store.listSlots({
6028
+ agentName: input.agentName,
6029
+ limit: RUNTIME_SLOT_PAGE_LIMIT,
6030
+ runtimeProfileId: input.runtimeProfileId,
6031
+ state,
6032
+ teamId: input.teamId
6159
6033
  });
6160
- const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
6161
- const runtimeInstanceId = createRuntimeInstanceId();
6162
- const runtimes = /* @__PURE__ */ new Map();
6163
- for (const profile of profiles) {
6164
- const common = parseCommonOptions(values, { runtimeDefaults: {
6165
- leaseTtlSec: profile.leaseTtlSec,
6166
- heartbeatIntervalMs: profile.heartbeatIntervalMs,
6167
- maxBatchSize: profile.maxBatchSize,
6168
- maxTurns: profile.maxTurns,
6169
- maxBashTimeouts: profile.maxBashTimeouts,
6170
- warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
6171
- } });
6172
- const sandbox = {
6173
- config: profile.sandboxConfig,
6174
- rootDir: profile.mountPath,
6175
- path: profile.source
6176
- };
6177
- const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
6178
- const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
6179
- const slotIdentity = {
6180
- agentName: common.agent,
6181
- runtimeProfileId: profile.id,
6182
- runtimeInstanceId
6183
- };
6184
- const executionPlans = createExecutionPlanCache({
6185
- stateDirs,
6186
- slotIdentity,
6187
- warmSessionTtlSec: common.warmSessionTtlSec,
6188
- workspacePolicy: {
6189
- defaultWorkspaceMode: profile.defaultWorkspaceMode,
6190
- allowedWorkspaceModes: profile.allowedWorkspaceModes
6191
- },
6192
- slotRegistry,
6193
- runtimeSessionStore,
6194
- sourceAttemptResolver
6195
- });
6196
- runtimes.set(profile.id, {
6197
- common,
6198
- profile,
6199
- sandbox,
6200
- stateDirs,
6201
- piAgentDir,
6202
- slotIdentity,
6203
- executionPlans,
6204
- preparedRuntime: preparedRuntimes.get(profile.id)
6205
- });
6034
+ return {
6035
+ items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
6036
+ truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
6037
+ };
6038
+ }
6039
+ async function findTerminalActiveSlotIds(deps, active, result) {
6040
+ const byTask = /* @__PURE__ */ new Map();
6041
+ for (const item of active) {
6042
+ const taskSlots = byTask.get(item.slot.lastTaskId) ?? [];
6043
+ taskSlots.push(item);
6044
+ byTask.set(item.slot.lastTaskId, taskSlots);
6206
6045
  }
6207
- const firstRuntime = runtimes.get(profiles[0].id);
6208
- if (!firstRuntime) throw new Error("No runtime profiles resolved");
6209
- const piAgentDir = firstRuntime.piAgentDir;
6210
- activatePiCodingAgentDir(piAgentDir.path);
6211
- const otelShutdown = await initWorkerOtel({
6212
- serviceName: opts.serviceName,
6213
- agent: ctx.agent,
6214
- endpoint: cfg.otelEndpoint,
6215
- resourceAttributes: {
6216
- "moltnet.team.id": teamId,
6217
- "moltnet.agent.name": baseCommon.agent,
6218
- "moltnet.credential.source": ctx.credentialSource,
6219
- "moltnet.runtime_profile.count": String(profiles.length),
6220
- "moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
6046
+ const terminal = /* @__PURE__ */ new Set();
6047
+ await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
6048
+ let attempts;
6049
+ try {
6050
+ attempts = await deps.taskReader.listAttempts(taskId);
6051
+ } catch (err) {
6052
+ recordFailure(deps, result, null, `task:${taskId}`, null, err);
6053
+ return;
6054
+ }
6055
+ for (const item of taskSlots) {
6056
+ const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
6057
+ if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
6221
6058
  }
6222
6059
  });
6223
- const { logger, shutdown: shutdownLogger } = createRootLogger({
6224
- name: `agent-daemon.${opts.modeLabel}`,
6225
- level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
6226
- });
6227
- const rootLogger = logger.child({
6228
- mode: opts.modeLabel,
6229
- agent: baseCommon.agent,
6230
- teamId,
6231
- runtimeProfileIds: profiles.map((p) => p.id),
6232
- runtimeProfileNames: profiles.map((p) => p.name)
6233
- });
6234
- for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
6235
- runtimeProfileId: profile.id,
6236
- runtimeProfileName: profile.name,
6237
- refusedEnvironmentNames: error.refusedNames
6238
- }, "agent-daemon.runtime_profile_skipped_credential_boundary");
6239
- const abort = new AbortController();
6240
- let runtime = null;
6241
- let active = null;
6242
- const signalHandlers = installShutdownSignalHandlers({
6243
- logDrain: (signal) => {
6244
- rootLogger.warn({ signal }, "agent-daemon.draining");
6245
- },
6246
- drain: (signal) => {
6247
- abort.abort();
6248
- runtime?.stop(`agent-daemon received ${signal}`);
6249
- abortActiveAttemptOnSignal({
6250
- active,
6251
- signal,
6252
- abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
6253
- logFailure: (err, current) => {
6254
- try {
6255
- rootLogger.warn({
6256
- err: err instanceof Error ? err.message : String(err),
6257
- taskId: current.taskId,
6258
- attemptN: current.attemptN
6259
- }, "agent-daemon.abort_on_signal_failed");
6260
- } catch {}
6261
- }
6262
- });
6263
- }
6264
- });
6265
- rootLogger.info({
6266
- credentialSource: cfg.credentialSource,
6267
- subjectType: startupWhoami.subjectType,
6268
- bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
6269
- credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
6270
- boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
6271
- taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
6272
- correlationId: values["correlation-id"] ?? null,
6273
- diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
6274
- pollIntervalMs,
6275
- maxPollIntervalMs,
6276
- profiles: profiles.map((profile) => {
6277
- const runtime = requireRuntime(runtimes, profile.id);
6278
- return {
6279
- id: profile.id,
6280
- name: profile.name,
6281
- provider: profile.provider,
6282
- model: profile.model,
6283
- thinkingLevel: profile.thinkingLevel,
6284
- temperature: profile.temperature,
6285
- topP: profile.topP,
6286
- topK: profile.topK,
6287
- maxOutputTokens: profile.maxOutputTokens,
6288
- sandbox: runtime.sandbox.path,
6289
- leaseTtlSec: runtime.common.leaseTtlSec,
6290
- heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
6291
- maxTurns: runtime.common.maxTurns,
6292
- maxBashTimeouts: runtime.common.maxBashTimeouts,
6293
- warmSessionTtlSec: runtime.common.warmSessionTtlSec,
6294
- profileSessionTtlSec: profile.sessionTtlSec,
6295
- profileWorkspaceTtlSec: profile.workspaceTtlSec,
6296
- defaultWorkspaceMode: profile.defaultWorkspaceMode,
6297
- allowedWorkspaceModes: profile.allowedWorkspaceModes
6298
- };
6299
- }),
6300
- piAgentDir: piAgentDir.path,
6301
- piAgentDirSource: piAgentDir.source
6302
- }, "agent-daemon.starting");
6303
- let reaperRunning = false;
6304
- const reapRuntimeResources = async () => {
6305
- if (reaperRunning) return;
6306
- reaperRunning = true;
6060
+ return terminal;
6061
+ }
6062
+ async function slotStillQualifiesForReaping(deps, input, original, now, result) {
6063
+ let page;
6064
+ try {
6065
+ page = await listOwnedSlots(deps.runtimeSlotStore, input, original.slot.state);
6066
+ } catch (err) {
6067
+ recordFailure(deps, result, original.slot.id, `slot:${original.slot.id}`, null, err);
6068
+ return false;
6069
+ }
6070
+ const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
6071
+ if (!current) return false;
6072
+ if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
6073
+ return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
6074
+ }
6075
+ async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
6076
+ if (!input.mainWorktree) return;
6077
+ const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
6078
+ if (!existsSync(workspaceRoot)) return;
6079
+ let entries;
6080
+ try {
6081
+ entries = readdirSync(workspaceRoot, { withFileTypes: true });
6082
+ } catch (err) {
6083
+ recordFailure(deps, result, null, workspaceRoot, workspaceRoot, err);
6084
+ return;
6085
+ }
6086
+ for (const entry of entries) {
6087
+ if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
6088
+ const workspacePath = resolve(workspaceRoot, entry.name);
6089
+ if (retainedWorkspacePaths.has(workspacePath)) continue;
6090
+ const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
6091
+ if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
6092
+ await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
6093
+ }
6094
+ }
6095
+ async function removeWorkspace(deps, result, worktrees, mainWorktree, slotId, workspacePath, workspaceRoot, scratch) {
6096
+ if (!existsSync(workspacePath)) return;
6097
+ if (!isRealPathInsideRoot(workspacePath, workspaceRoot)) {
6098
+ recordUnsafePath(deps, result, slotId, workspacePath, workspaceRoot);
6099
+ return;
6100
+ }
6101
+ if (scratch || !mainWorktree) {
6307
6102
  try {
6308
- for (const profile of profiles) {
6309
- const selected = requireRuntime(runtimes, profile.id);
6310
- const result = await reapRuntimeSlotResources({
6311
- onIssue: (issue) => {
6312
- rootLogger.warn({
6313
- ...issue,
6314
- runtimeProfileId: profile.id
6315
- }, "agent-daemon.runtime_resource_reap_issue");
6316
- },
6317
- runtimeSlotStore: slotRegistry,
6318
- taskReader: ctx.agent.tasks
6319
- }, {
6320
- agentName: selected.common.agent,
6321
- mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
6322
- runtimeInstanceId,
6323
- runtimeProfileId: profile.id,
6324
- sessionRootDir: selected.stateDirs.piSessionsDir,
6325
- scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
6326
- teamId
6327
- });
6328
- if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
6329
- ...result,
6330
- runtimeProfileId: profile.id
6331
- }, "agent-daemon.runtime_resources_reaped");
6332
- }
6103
+ rmSync(workspacePath, {
6104
+ force: true,
6105
+ recursive: true
6106
+ });
6107
+ result.removedWorkspaces++;
6333
6108
  } catch (err) {
6334
- rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
6335
- } finally {
6336
- reaperRunning = false;
6109
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
6337
6110
  }
6111
+ return;
6112
+ }
6113
+ if (worktrees.kind === "unavailable") {
6114
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
6115
+ return;
6116
+ }
6117
+ try {
6118
+ const canonical = realpathSync(workspacePath);
6119
+ if (worktrees.paths.has(canonical)) {
6120
+ await execFileText("git", [
6121
+ "-C",
6122
+ mainWorktree,
6123
+ "worktree",
6124
+ "remove",
6125
+ "--force",
6126
+ workspacePath
6127
+ ]);
6128
+ worktrees.paths.delete(canonical);
6129
+ } else rmSync(workspacePath, {
6130
+ force: true,
6131
+ recursive: true
6132
+ });
6133
+ result.removedWorkspaces++;
6134
+ } catch (err) {
6135
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
6136
+ }
6137
+ }
6138
+ async function listRegisteredWorktrees(mainWorktree) {
6139
+ try {
6140
+ const list = await execFileText("git", [
6141
+ "-C",
6142
+ mainWorktree,
6143
+ "worktree",
6144
+ "list",
6145
+ "--porcelain"
6146
+ ]);
6147
+ return {
6148
+ kind: "ready",
6149
+ paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
6150
+ };
6151
+ } catch (err) {
6152
+ return {
6153
+ kind: "unavailable",
6154
+ reason: errorMessage(err)
6155
+ };
6156
+ }
6157
+ }
6158
+ function isRealPathInsideRoot(path, root) {
6159
+ try {
6160
+ return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
6161
+ } catch {
6162
+ return false;
6163
+ }
6164
+ }
6165
+ function canonicalPath(path) {
6166
+ try {
6167
+ return realpathSync(path);
6168
+ } catch {
6169
+ return resolve(path);
6170
+ }
6171
+ }
6172
+ function recordUnsafePath(deps, result, slotId, path, root) {
6173
+ const issue = {
6174
+ kind: "unsafe_path",
6175
+ path,
6176
+ reason: root === null ? "No daemon-owned root was available for this workspace." : "Candidate or root could not be resolved inside the daemon-owned root.",
6177
+ root,
6178
+ slotId
6338
6179
  };
6339
- await reapRuntimeResources();
6340
- const reaperTimer = setInterval(() => {
6341
- reapRuntimeResources();
6342
- }, 6e4);
6343
- reaperTimer.unref();
6344
- const outputs = [];
6180
+ result.unsafePaths++;
6181
+ result.unsafePathDetails.push(issue);
6182
+ emitIssue(deps, issue);
6183
+ }
6184
+ function recordFailure(deps, result, slotId, path, root, err) {
6185
+ const issue = {
6186
+ kind: "failure",
6187
+ path,
6188
+ reason: errorMessage(err),
6189
+ root,
6190
+ slotId
6191
+ };
6192
+ result.failed++;
6193
+ result.failures.push(issue);
6194
+ emitIssue(deps, issue);
6195
+ }
6196
+ function emitIssue(deps, issue) {
6345
6197
  try {
6346
- runtime = new AgentRuntime({
6347
- logger: rootLogger,
6348
- source: new PollingApiTaskSource({
6349
- agent: ctx.agent,
6350
- teamId,
6351
- taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
6352
- correlationId: values["correlation-id"],
6353
- profiles: profiles.map((profile) => ({
6354
- profileId: profile.id,
6355
- leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
6356
- })),
6357
- diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
6358
- leaseTtlSec: firstRuntime.common.leaseTtlSec,
6359
- listLimit,
6360
- pollIntervalMs,
6361
- maxPollIntervalMs,
6362
- signal: abort.signal,
6363
- stopWhenEmpty: opts.stopWhenEmpty,
6364
- waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
6365
- waitAfterTaskMs: waitAfterTaskSec * 1e3,
6366
- debug: baseCommon.debug,
6367
- traceIdlePolling: cfg.traceIdlePolling,
6368
- logger: rootLogger,
6369
- executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
6370
- slotRegistry,
6371
- sessionRegistry: runtimeSessionStore,
6372
- sourceAttemptResolver
6373
- }),
6374
- makeReporter: (claimedTask) => {
6375
- const selected = runtimeForClaimedTask(runtimes, claimedTask);
6376
- return new ApiTaskReporter({
6377
- tasks: ctx.agent.tasks,
6378
- teamId: claimedTask.task.teamId,
6379
- leaseTtlSec: selected.common.leaseTtlSec,
6380
- heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
6381
- maxBatchSize: selected.common.maxBatchSize,
6382
- flushIntervalMs: selected.common.flushIntervalMs
6383
- });
6384
- },
6385
- onTaskFinished: async (output, claimedTask) => {
6386
- const selected = runtimeForClaimedTask(runtimes, claimedTask);
6387
- const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
6388
- let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
6389
- if (resolved?.session?.sessionDir) try {
6390
- const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
6391
- await runtimeSessionStore.uploadAttemptFinal({
6392
- attemptN: claimedTask.attemptN,
6393
- parentSessionId: parentSession?.id ?? null,
6394
- sessionDir: resolved.session.sessionDir,
6395
- sessionKind: resolveRuntimeSessionKind(claimedTask),
6396
- sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
6397
- sourceSlotId: resolved.slot.id,
6398
- taskId: claimedTask.task.id,
6399
- teamId: claimedTask.task.teamId
6400
- });
6401
- } catch (err) {
6402
- rootLogger.error({
6403
- err,
6404
- taskId: claimedTask.task.id,
6405
- attemptN: claimedTask.attemptN
6406
- }, "agent-daemon.runtime_session_upload_failed");
6407
- terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
6408
- }
6409
- return finalizeTask(ctx.agent, terminalOutput, {
6410
- task: claimedTask.task,
6411
- slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
6412
- retryTriage: createRuntimeProfileRetryTriage({
6413
- runtimeProfile: selected.profile,
6414
- piAgentDir: selected.piAgentDir.path,
6415
- cwd: ctx.agentRootDir
6416
- }),
6417
- executorAttestor: selected.preparedRuntime.attestor,
6418
- writeCorrelationAnchors: makePrBodyAnchorWriter({
6419
- gh: createGhCliClient(),
6420
- logger: rootLogger.child({
6421
- runtimeProfileId: selected.profile.id,
6422
- runtimeProfileName: selected.profile.name
6423
- })
6424
- }),
6425
- log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
6426
- });
6427
- },
6428
- executeTask: async (claimedTask, reporter) => {
6429
- const selected = runtimeForClaimedTask(runtimes, claimedTask);
6430
- const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
6431
- if (runtimeCredentialConfig) await observeGovernancePlanSafely({
6432
- config: runtimeCredentialConfig,
6433
- profile,
6434
- offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
6435
- registry: createNodeSecretProviderRegistry(),
6436
- executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
6437
- claimAuthority: claimedTask.claimAuthority ?? {},
6438
- taskId: claimedTask.task.id,
6439
- attemptN: claimedTask.attemptN,
6440
- logger: rootLogger
6441
- });
6442
- const taskLogger = rootLogger.child({
6443
- runtimeProfileId: profile.id,
6444
- runtimeProfileName: profile.name,
6445
- provider: profile.provider,
6446
- model: profile.model,
6447
- thinkingLevel: profile.thinkingLevel,
6448
- temperature: profile.temperature,
6449
- topP: profile.topP,
6450
- topK: profile.topK,
6451
- maxOutputTokens: profile.maxOutputTokens
6452
- });
6453
- let executionPlan;
6454
- try {
6455
- executionPlan = await executionPlans.getOrCreate(claimedTask);
6456
- } catch (err) {
6457
- const message = err instanceof Error ? err.message : String(err);
6458
- taskLogger.warn({
6459
- taskId: claimedTask.task.id,
6460
- attemptN: claimedTask.attemptN,
6461
- err: message
6462
- }, "agent-daemon.execution_plan_failed");
6463
- return {
6464
- taskId: claimedTask.task.id,
6465
- attemptN: claimedTask.attemptN,
6466
- status: "failed",
6467
- output: null,
6468
- outputCid: null,
6469
- usage: {
6470
- inputTokens: 0,
6471
- outputTokens: 0
6472
- },
6473
- durationMs: 0,
6474
- error: {
6475
- code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
6476
- message,
6477
- retryable: false
6478
- }
6479
- };
6480
- }
6481
- const sessionDescriptor = executionPlan.descriptor;
6482
- taskLogger.debug({
6483
- taskId: claimedTask.task.id,
6484
- taskType: claimedTask.task.taskType,
6485
- resumable: sessionDescriptor.policy.resumable,
6486
- workspaceMode: executionPlan.workspaceMode,
6487
- workspaceScope: sessionDescriptor.policy.workspaceScope,
6488
- sessionScope: sessionDescriptor.policy.sessionScope,
6489
- slotKey: executionPlan.slotKey,
6490
- slotId: executionPlan.slotId,
6491
- sessionKey: executionPlan.sessionKey,
6492
- piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
6493
- workspaceId: executionPlan.workspaceId
6494
- }, "agent-daemon.task_execution_policy");
6495
- if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
6496
- taskId: claimedTask.task.id,
6497
- attemptN: claimedTask.attemptN,
6498
- status: "failed",
6499
- output: null,
6500
- outputCid: null,
6501
- usage: {
6502
- inputTokens: 0,
6503
- outputTokens: 0
6504
- },
6505
- durationMs: 0,
6506
- error: {
6507
- code: "unsupported_task_type",
6508
- message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
6509
- retryable: true
6510
- }
6511
- };
6512
- if (reporter.cancelSignal.aborted) return {
6513
- taskId: claimedTask.task.id,
6514
- attemptN: claimedTask.attemptN,
6515
- status: "cancelled",
6516
- output: null,
6517
- outputCid: null,
6518
- usage: {
6519
- inputTokens: 0,
6520
- outputTokens: 0
6521
- },
6522
- durationMs: 0,
6523
- error: {
6524
- code: "task_cancelled",
6525
- message: reporter.cancelReason ?? "Task cancelled before executor started.",
6526
- retryable: false
6527
- }
6528
- };
6529
- if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
6530
- ...slotIdentity,
6531
- runtimeProfileId: profile.id,
6532
- provider: profile.provider,
6533
- model: profile.model,
6534
- teamId: claimedTask.task.teamId,
6535
- slotKey: executionPlan.slotKey,
6536
- taskType: claimedTask.task.taskType,
6537
- sessionDir: executionPlan.sessionPersistence.sessionDir,
6538
- sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
6539
- workspaceId: executionPlan.workspaceId,
6540
- worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
6541
- worktreeBranch: executionPlan.worktreeBranch,
6542
- workspaceKind: executionPlan.workspaceKind,
6543
- lastTaskId: claimedTask.task.id,
6544
- lastAttemptN: claimedTask.attemptN
6545
- });
6546
- const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
6547
- agentName: common.agent,
6548
- moltnetAgent: ctx.agent,
6549
- agentIdentity,
6550
- hostCapabilitySigner,
6551
- hostCapabilityLogger: taskLogger,
6552
- agentRootDir: ctx.agentRootDir,
6553
- mountPath: sandbox.rootDir,
6554
- provider: profile.provider,
6555
- model: profile.model,
6556
- thinkingLevel: profile.thinkingLevel,
6557
- temperature: profile.temperature,
6558
- topP: profile.topP,
6559
- topK: profile.topK,
6560
- maxOutputTokens: profile.maxOutputTokens,
6561
- sandboxConfig: sandbox.config,
6562
- forwardEnv: profile.requiredEnv,
6563
- onVmDiagnostic: (diagnostic) => {
6564
- const fields = {
6565
- event: diagnostic.event,
6566
- ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
6567
- };
6568
- if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
6569
- else taskLogger.info(fields, diagnostic.message);
6570
- },
6571
- runtimeProfileContext: profile.context,
6572
- runtimeProfileId: profile.id,
6573
- toolEnforcement: profile.toolEnforcement,
6574
- makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
6575
- makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
6576
- toolPolicyLogger: taskLogger,
6577
- maxTurns: common.maxTurns,
6578
- maxBashTimeouts: common.maxBashTimeouts
6579
- });
6580
- try {
6581
- active = {
6582
- taskId: claimedTask.task.id,
6583
- attemptN: claimedTask.attemptN
6584
- };
6585
- return await runWithDaemonRuntimeContext({
6586
- profileId: profile.id,
6587
- profileName: profile.name,
6588
- provider: profile.provider,
6589
- model: profile.model,
6590
- thinkingLevel: profile.thinkingLevel,
6591
- temperature: profile.temperature,
6592
- topP: profile.topP,
6593
- topK: profile.topK,
6594
- maxOutputTokens: profile.maxOutputTokens
6595
- }, () => rawExecuteTask(claimedTask, reporter));
6596
- } finally {
6597
- active = null;
6598
- executionPlans.delete(claimedTask);
6599
- if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
6600
- }
6601
- }
6198
+ deps.onIssue?.(issue);
6199
+ } catch {}
6200
+ }
6201
+ function errorMessage(err) {
6202
+ return err instanceof Error ? err.message : String(err);
6203
+ }
6204
+ async function execFileText(file, args) {
6205
+ return new Promise((resolvePromise, reject) => {
6206
+ execFile(file, args, {
6207
+ encoding: "utf8",
6208
+ maxBuffer: 10 * 1024 * 1024
6209
+ }, (err, stdout) => {
6210
+ if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
6211
+ else resolvePromise(stdout);
6602
6212
  });
6603
- const drained = await runtime.start();
6604
- outputs.push(...drained);
6605
- rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
6606
- return drained.some((o) => o.status !== "completed") ? 1 : 0;
6607
- } finally {
6608
- clearInterval(reaperTimer);
6609
- signalHandlers.dispose();
6610
- await slotRegistry.close();
6611
- await otelShutdown();
6612
- await shutdownLogger();
6613
- }
6213
+ });
6614
6214
  }
6615
- function resolveRecordedWorkspacePath$1(stateRootDir, mountPath, executionPlan) {
6616
- if (!executionPlan.workspaceId) return null;
6617
- return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
6215
+ async function mapWithConcurrency(items, concurrency, worker) {
6216
+ for (let index = 0; index < items.length; index += concurrency) await Promise.all(items.slice(index, index + concurrency).map(worker));
6618
6217
  }
6619
- function resolveMainWorktree$1(mountPath) {
6620
- try {
6621
- return findMainWorktree(mountPath);
6622
- } catch {
6623
- return null;
6624
- }
6218
+ //#endregion
6219
+ //#region src/lib/runtime-sessions.ts
6220
+ function resolveRuntimeSessionKind(claimedTask) {
6221
+ const continueFrom = resolveContinueFrom(claimedTask);
6222
+ if (!continueFrom) return "root";
6223
+ return continueFrom.mode === "fork" ? "fork" : "extend";
6224
+ }
6225
+ async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
6226
+ const continueFrom = resolveContinueFrom(claimedTask);
6227
+ if (!continueFrom) return null;
6228
+ return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
6229
+ }
6230
+ function applyRuntimeSessionUploadFailure(output, err) {
6231
+ if (output.status !== "completed") return output;
6232
+ return {
6233
+ ...output,
6234
+ error: {
6235
+ code: "runtime_session_upload_failed",
6236
+ message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
6237
+ retryable: isTransientUploadError(err)
6238
+ },
6239
+ output: null,
6240
+ outputCid: null,
6241
+ status: "failed"
6242
+ };
6243
+ }
6244
+ /**
6245
+ * Transient faults worth retrying in-attempt: network-level errors
6246
+ * (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
6247
+ * validation, not-found) will not heal on retry.
6248
+ */
6249
+ function isTransientUploadError(error) {
6250
+ if (!(error instanceof Error)) return false;
6251
+ const statusCode = error.statusCode;
6252
+ if (typeof statusCode !== "number") return true;
6253
+ return statusCode >= 500 || statusCode === 429;
6254
+ }
6255
+ var defaultSleep = (ms) => new Promise((resolve) => {
6256
+ setTimeout(resolve, ms);
6257
+ });
6258
+ function createApiRuntimeSessionStore(args) {
6259
+ const { agent, logger, uploadRetry } = args;
6260
+ return {
6261
+ async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
6262
+ return agent.runtimeSessions.getForAttempt({
6263
+ attemptN,
6264
+ taskId
6265
+ }, { teamId });
6266
+ },
6267
+ async hydrateSession(input) {
6268
+ const downloaded = await agent.runtimeSessions.download({
6269
+ attemptN: input.attemptN,
6270
+ taskId: input.taskId
6271
+ }, { teamId: input.teamId });
6272
+ await mkdir(input.destinationDir, { recursive: true });
6273
+ const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
6274
+ await pipeline(downloaded, createWriteStream(sessionPath));
6275
+ return sessionPath;
6276
+ },
6277
+ async uploadAttemptFinal(input) {
6278
+ const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
6279
+ if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
6280
+ const maxTries = uploadRetry?.maxTries ?? 3;
6281
+ const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
6282
+ const sleep = uploadRetry?.sleep ?? defaultSleep;
6283
+ for (let tryN = 1;; tryN += 1) try {
6284
+ await agent.runtimeSessions.upload({
6285
+ attemptN: input.attemptN,
6286
+ taskId: input.taskId
6287
+ }, createReadStream(sessionPath), {
6288
+ parentSessionId: input.parentSessionId ?? void 0,
6289
+ sessionKind: input.sessionKind,
6290
+ sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
6291
+ sourceSlotId: input.sourceSlotId ?? void 0
6292
+ }, { teamId: input.teamId });
6293
+ return;
6294
+ } catch (error) {
6295
+ if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
6296
+ const delayMs = baseDelayMs * tryN;
6297
+ logger?.warn({
6298
+ event: "agent-daemon.runtime_session_upload_retry",
6299
+ attemptN: input.attemptN,
6300
+ delayMs,
6301
+ statusCode: uploadStatusCode(error),
6302
+ taskId: input.taskId,
6303
+ tryN
6304
+ }, "Retrying durable runtime session upload");
6305
+ await sleep(delayMs);
6306
+ }
6307
+ }
6308
+ };
6309
+ }
6310
+ function uploadStatusCode(error) {
6311
+ const statusCode = error?.statusCode;
6312
+ return typeof statusCode === "number" ? statusCode : void 0;
6313
+ }
6314
+ function resolveContinueFrom(claimedTask) {
6315
+ return claimedTask.task.input.continueFrom;
6316
+ }
6317
+ //#endregion
6318
+ //#region src/lib/runtime-slots.ts
6319
+ function createApiRuntimeSlotStore(args) {
6320
+ const { agent } = args;
6321
+ return {
6322
+ async beginSlot(input) {
6323
+ await agent.runtimeSlots.begin({
6324
+ agentName: input.agentName,
6325
+ runtimeProfileId: input.runtimeProfileId,
6326
+ lastAttemptN: input.lastAttemptN,
6327
+ lastTaskId: input.lastTaskId,
6328
+ model: input.model,
6329
+ provider: input.provider,
6330
+ sessionDir: input.sessionDir ?? void 0,
6331
+ sessionPath: input.sessionPath ?? void 0,
6332
+ slotKey: input.slotKey,
6333
+ taskType: input.taskType,
6334
+ workspaceId: input.workspaceId ?? void 0,
6335
+ workspaceKind: input.workspaceKind,
6336
+ worktreeBranch: input.worktreeBranch ?? void 0,
6337
+ worktreePath: input.worktreePath ?? void 0
6338
+ }, { teamId: input.teamId });
6339
+ },
6340
+ async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath) {
6341
+ await agent.runtimeSlots.finish({
6342
+ agentName: identity.agentName,
6343
+ attemptN,
6344
+ runtimeProfileId: identity.runtimeProfileId,
6345
+ model,
6346
+ provider,
6347
+ sessionPath: sessionPath ?? void 0,
6348
+ slotKey,
6349
+ taskId
6350
+ }, { teamId });
6351
+ },
6352
+ async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
6353
+ const resolved = await agent.runtimeSlots.findLatestForAttempt({
6354
+ attemptN,
6355
+ taskId
6356
+ }, { teamId });
6357
+ if (!resolved) return null;
6358
+ return {
6359
+ slot: {
6360
+ expiresAtMs: resolved.slot.expiresAtMs,
6361
+ id: resolved.slot.id,
6362
+ runtimeProfileId: resolved.slot.runtimeProfileId
6363
+ },
6364
+ session: resolved.slot.sessionDir ? {
6365
+ sessionDir: resolved.slot.sessionDir,
6366
+ sessionPath: resolved.slot.sessionPath
6367
+ } : null,
6368
+ workspace: resolved.workspace ? {
6369
+ kind: resolved.workspace.kind,
6370
+ workspaceId: resolved.workspace.workspaceId,
6371
+ worktreeBranch: resolved.workspace.worktreeBranch,
6372
+ worktreePath: resolved.workspace.worktreePath
6373
+ } : null
6374
+ };
6375
+ },
6376
+ async listSlots(input) {
6377
+ const query = {
6378
+ agentName: input.agentName,
6379
+ ...input.limit === void 0 ? {} : { limit: input.limit },
6380
+ ...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
6381
+ ...input.state === void 0 ? {} : { state: input.state }
6382
+ };
6383
+ return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
6384
+ slot: {
6385
+ expiresAtMs: resolved.slot.expiresAtMs,
6386
+ id: resolved.slot.id,
6387
+ lastAttemptN: resolved.slot.lastAttemptN,
6388
+ lastTaskId: resolved.slot.lastTaskId,
6389
+ runtimeProfileId: resolved.slot.runtimeProfileId,
6390
+ slotKey: resolved.slot.slotKey,
6391
+ state: resolved.slot.state,
6392
+ taskType: resolved.slot.taskType
6393
+ },
6394
+ session: resolved.slot.sessionDir ? {
6395
+ sessionDir: resolved.slot.sessionDir,
6396
+ sessionPath: resolved.slot.sessionPath
6397
+ } : null,
6398
+ workspace: resolved.workspace ? {
6399
+ kind: resolved.workspace.kind,
6400
+ workspaceId: resolved.workspace.workspaceId,
6401
+ worktreeBranch: resolved.workspace.worktreeBranch,
6402
+ worktreePath: resolved.workspace.worktreePath
6403
+ } : null
6404
+ }));
6405
+ },
6406
+ async close() {}
6407
+ };
6625
6408
  }
6626
- function runtimeForClaimedTask(runtimes, claimedTask) {
6627
- if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
6628
- return requireRuntime(runtimes, claimedTask.profileId);
6409
+ //#endregion
6410
+ //#region src/lib/secret-redaction.ts
6411
+ var REDACTED = "[REDACTED]";
6412
+ var MIN_SECRET_LENGTH = 8;
6413
+ /**
6414
+ * Redact required runtime-profile environment values before task output leaves
6415
+ * the daemon. This is defense in depth for shell-capable profiles: a model may
6416
+ * read a forwarded provider key, but it must not be able to publish that exact
6417
+ * value through a task result.
6418
+ */
6419
+ function redactRequiredEnvValues(value, requiredEnv, env) {
6420
+ const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
6421
+ if (secrets.length === 0) return value;
6422
+ const redact = (candidate) => {
6423
+ if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
6424
+ if (Array.isArray(candidate)) return candidate.map(redact);
6425
+ if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
6426
+ return candidate;
6427
+ };
6428
+ return redact(value);
6629
6429
  }
6630
- function requireRuntime(runtimes, profileId) {
6631
- const runtime = runtimes.get(profileId);
6632
- if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
6633
- return runtime;
6430
+ //#endregion
6431
+ //#region src/lib/shutdown-signal.ts
6432
+ function installShutdownSignalHandlers(opts) {
6433
+ const proc = opts.proc ?? process;
6434
+ let drainingSignal = null;
6435
+ const onSignal = (signal) => {
6436
+ if (drainingSignal) {
6437
+ proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
6438
+ proc.exitCode = signalExitCode(signal);
6439
+ return;
6440
+ }
6441
+ drainingSignal = signal;
6442
+ proc.exitCode = signalExitCode(signal);
6443
+ try {
6444
+ opts.logDrain(signal);
6445
+ } catch (err) {
6446
+ proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
6447
+ }
6448
+ opts.drain(signal);
6449
+ };
6450
+ const handleSigint = () => onSignal("SIGINT");
6451
+ const handleSigterm = () => onSignal("SIGTERM");
6452
+ proc.on("SIGINT", handleSigint);
6453
+ proc.on("SIGTERM", handleSigterm);
6454
+ return { dispose: () => {
6455
+ proc.off("SIGINT", handleSigint);
6456
+ proc.off("SIGTERM", handleSigterm);
6457
+ } };
6634
6458
  }
6635
- function parseProfileValues(raw) {
6636
- return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
6459
+ function signalExitCode(signal) {
6460
+ return signal === "SIGINT" ? 130 : 143;
6637
6461
  }
6638
- function parseCsv(raw) {
6639
- return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6462
+ //#endregion
6463
+ //#region src/lib/source-attempts.ts
6464
+ function createApiSourceAttemptResolver(args) {
6465
+ const { agent } = args;
6466
+ return {
6467
+ async findOutputBranch(input) {
6468
+ const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
6469
+ if (!attempt || attempt.status !== "completed") return null;
6470
+ return resolveOutputBranch(attempt.output);
6471
+ },
6472
+ async findInputRevision(input) {
6473
+ const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
6474
+ if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
6475
+ return resolveTaskWorkspaceRevision(task.input);
6476
+ }
6477
+ };
6640
6478
  }
6641
- function optionalPositiveInt(raw, name, defaultValue) {
6642
- if (raw === void 0) return defaultValue;
6643
- const v = Number(raw);
6644
- if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
6645
- return v;
6479
+ function resolveOutputBranch(output) {
6480
+ if (!output || typeof output !== "object") return null;
6481
+ const branch = output.branch;
6482
+ return typeof branch === "string" && branch.length > 0 ? branch : null;
6646
6483
  }
6647
- function optionalNonNegativeInt(raw, name, defaultValue) {
6648
- if (raw === void 0) return defaultValue;
6649
- const v = Number(raw);
6650
- if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
6651
- return v;
6484
+ //#endregion
6485
+ //#region src/lib/state-dir.ts
6486
+ function ensureDaemonStateDirs(mountPath) {
6487
+ const rootDir = join(mountPath, ".moltnet", "d");
6488
+ const piSessionsDir = join(rootDir, "pi-sessions");
6489
+ mkdirSync(piSessionsDir, { recursive: true });
6490
+ return {
6491
+ rootDir,
6492
+ piSessionsDir
6493
+ };
6652
6494
  }
6653
6495
  //#endregion
6654
- //#region src/cli/drain.ts
6655
- function runDrain(argv, runtimeAdapter) {
6656
- return runPolling({
6657
- argv,
6658
- serviceName: "moltnet.agent-daemon.drain",
6659
- stopWhenEmpty: true,
6660
- modeLabel: "drain",
6661
- helpText: DRAIN_HELP,
6662
- runtimeAdapter
6496
+ //#region src/lib/turn-event-logger.ts
6497
+ function makeTurnEventHandler(base, context = {}) {
6498
+ const log = base.child({
6499
+ name: "agent-daemon.turn",
6500
+ ...context
6501
+ });
6502
+ return (event, summary) => {
6503
+ if (event === "text_delta") return;
6504
+ log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
6505
+ event,
6506
+ ...summary
6507
+ }, `turn.${event}`);
6508
+ };
6509
+ }
6510
+ function makeTurnEventHandlerFactory(base) {
6511
+ return (claimedTask) => makeTurnEventHandler(base, {
6512
+ taskId: claimedTask.task.id,
6513
+ attemptN: claimedTask.attemptN
6663
6514
  });
6664
6515
  }
6665
6516
  //#endregion
6666
- //#region src/cli/once.ts
6667
- async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6668
- if (isHelpFlag(argv)) {
6669
- console.log(ONCE_HELP);
6517
+ //#region src/cli/poll-shared.ts
6518
+ async function runPolling(opts) {
6519
+ if (isHelpFlag(opts.argv)) {
6520
+ console.log(opts.helpText);
6670
6521
  return 0;
6671
6522
  }
6672
6523
  const { values } = parseArgs({
6673
- args: argv,
6524
+ args: opts.argv,
6674
6525
  options: {
6675
6526
  ...commonOptionDefs(),
6676
- "task-id": {
6677
- type: "string",
6678
- short: "t"
6679
- },
6680
6527
  team: { type: "string" },
6528
+ "task-types": { type: "string" },
6529
+ "correlation-id": { type: "string" },
6530
+ "diary-ids": { type: "string" },
6531
+ "wait-for-first-task-sec": { type: "string" },
6532
+ "wait-after-task-sec": { type: "string" },
6533
+ "poll-interval-ms": { type: "string" },
6534
+ "max-poll-interval-ms": { type: "string" },
6535
+ "list-limit": { type: "string" },
6681
6536
  sandbox: { type: "string" },
6682
- profile: { type: "string" }
6537
+ profile: {
6538
+ type: "string",
6539
+ multiple: true
6540
+ }
6683
6541
  }
6684
6542
  });
6685
- if (!values["task-id"]) {
6686
- console.error("Missing required flag: --task-id\n");
6687
- console.error(ONCE_HELP);
6543
+ if (!values.team) {
6544
+ console.error("Missing required flag: --team\n");
6545
+ console.error(opts.helpText);
6688
6546
  return 1;
6689
6547
  }
6690
- const taskId = values["task-id"];
6691
- if (!values.profile) {
6548
+ const teamId = values.team;
6549
+ const profileValues = parseProfileValues(values.profile);
6550
+ if (profileValues.length === 0) {
6692
6551
  console.error("Missing required flag: --profile\n");
6693
- console.error(ONCE_HELP);
6552
+ console.error(opts.helpText);
6694
6553
  return 1;
6695
6554
  }
6696
- let opts;
6555
+ let taskTypes;
6697
6556
  try {
6698
- opts = parseCommonOptions(values);
6557
+ taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
6558
+ } catch (err) {
6559
+ const message = err instanceof Error ? err.message : String(err);
6560
+ console.error(message);
6561
+ return 1;
6562
+ }
6563
+ const diaryIds = parseCsv(values["diary-ids"]);
6564
+ let baseCommon;
6565
+ try {
6566
+ baseCommon = parseCommonOptions(values);
6699
6567
  } catch (err) {
6700
6568
  if (err instanceof MissingRequiredOptionError) {
6701
6569
  console.error(`${err.message}\n`);
6702
- console.error(ONCE_HELP);
6570
+ console.error(opts.helpText);
6703
6571
  return 1;
6704
6572
  }
6705
6573
  throw err;
6706
6574
  }
6575
+ const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
6576
+ const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
6577
+ const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
6578
+ const waitForFirstTaskSec = optionalNonNegativeInt(values["wait-for-first-task-sec"], "wait-for-first-task-sec", 0);
6579
+ const waitAfterTaskSec = optionalNonNegativeInt(values["wait-after-task-sec"], "wait-after-task-sec", 0);
6580
+ if (!opts.stopWhenEmpty && (waitForFirstTaskSec > 0 || waitAfterTaskSec > 0)) {
6581
+ console.error(`[${opts.modeLabel}] --wait-for-first-task-sec and --wait-after-task-sec are only valid with drain.`);
6582
+ return 1;
6583
+ }
6707
6584
  if (values.sandbox) {
6708
- console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
6585
+ console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
6709
6586
  return 1;
6710
6587
  }
6588
+ if (taskTypes.length === 0) console.error(`[${opts.modeLabel}] --task-types is empty — daemon will accept any registered type. Pass an explicit list to limit scope (e.g. --task-types fulfill_brief).`);
6711
6589
  const cfg = loadConfig();
6712
6590
  const credentialSources = {
6713
6591
  profileRequirements: cfg.profileCredentialRequirements,
6714
6592
  bindings: cfg.credentialBindings
6715
6593
  };
6716
6594
  const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
6717
- const initialOpts = opts;
6718
6595
  const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
6719
- const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
6596
+ const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
6720
6597
  let gate = "resolve_agent_context";
6721
6598
  try {
6722
- const resolvedContext = await resolveAgentContext(initialOpts.agent, {
6599
+ const resolvedContext = await resolveAgentContext(baseCommon.agent, {
6723
6600
  agentRootDir: explicitAgentRootDir,
6724
6601
  credentialSource: cfg.credentialSource,
6725
6602
  envApiUrl: cfg.apiUrl
@@ -6727,7 +6604,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6727
6604
  gate = "authenticate_and_bind";
6728
6605
  const whoami = await validateStartupBinding({
6729
6606
  agent: resolvedContext.agent,
6730
- teamId: values.team,
6607
+ teamId,
6731
6608
  expectedAgent: cfg.expectedAgent
6732
6609
  });
6733
6610
  gate = "resolve_signing_material";
@@ -6746,7 +6623,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6746
6623
  });
6747
6624
  gate = "resolve_agent_identity";
6748
6625
  const agentIdentity = await resolveDaemonAgentIdentity({
6749
- agentName: initialOpts.agent,
6626
+ agentName: baseCommon.agent,
6750
6627
  whoami,
6751
6628
  credentialSource: cfg.credentialSource,
6752
6629
  agentDir: resolvedContext.agentDir,
@@ -6755,6 +6632,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6755
6632
  return {
6756
6633
  ctx: resolvedContext,
6757
6634
  signingPrivateKey: privateKey,
6635
+ startupWhoami: whoami,
6758
6636
  agentIdentity,
6759
6637
  hostCapabilitySigner: createLocalSeedSigner({
6760
6638
  privateKeySeed: privateKey,
@@ -6764,10 +6642,10 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6764
6642
  };
6765
6643
  } catch (error) {
6766
6644
  await logDaemonStartupFailure({
6767
- serviceName: "agent-daemon.once",
6768
- level: cfg.logLevel || (initialOpts.debug ? "debug" : "info"),
6645
+ serviceName: `agent-daemon.${opts.modeLabel}`,
6646
+ level: cfg.logLevel || (baseCommon.debug ? "debug" : "info"),
6769
6647
  gate,
6770
- agent: initialOpts.agent,
6648
+ agent: baseCommon.agent,
6771
6649
  credentialSource: cfg.credentialSource,
6772
6650
  error
6773
6651
  });
@@ -6775,39 +6653,41 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6775
6653
  }
6776
6654
  })();
6777
6655
  const daemonRootDir = explicitAgentRootDir ?? process.cwd();
6778
- const profile = await resolveRuntimeProfile({
6656
+ const resolvedProfiles = await resolveRuntimeProfiles({
6779
6657
  agent: ctx.agent,
6780
- profile: values.profile,
6781
- teamId: values.team,
6658
+ profiles: profileValues,
6659
+ teamId,
6782
6660
  cwd: daemonRootDir
6783
6661
  });
6784
- assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
6785
- const preparedRuntime = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
6786
- validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
6787
- tools: preparedRuntime.tools,
6788
- executables: preparedRuntime.executables
6789
- });
6790
- assertGuestEnvironmentBoundary({
6791
- forwardEnv: profile.requiredEnv,
6792
- sandboxEnv: profile.sandboxConfig.env
6793
- });
6794
- await ctx.agent.tasks.registerExecutorManifest(await preparedRuntime.attestor.registration());
6795
- opts = parseCommonOptions(values, { runtimeDefaults: {
6796
- leaseTtlSec: profile.leaseTtlSec,
6797
- heartbeatIntervalMs: profile.heartbeatIntervalMs,
6798
- maxBatchSize: profile.maxBatchSize,
6799
- maxTurns: profile.maxTurns,
6800
- maxBashTimeouts: profile.maxBashTimeouts,
6801
- warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
6802
- } });
6803
- const sandbox = {
6804
- config: profile.sandboxConfig,
6805
- rootDir: profile.mountPath,
6806
- path: profile.source
6807
- };
6808
- const piAgentDir = ensurePiAgentDir(sandbox.rootDir, cfg.piCodingAgentDir);
6809
- activatePiCodingAgentDir(piAgentDir.path);
6810
- const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
6662
+ const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
6663
+ const preparedRuntimes = /* @__PURE__ */ new Map();
6664
+ const profiles = [];
6665
+ const skippedProfileBoundaries = [];
6666
+ for (const profile of resolvedProfiles) try {
6667
+ assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
6668
+ const prepared = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
6669
+ validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
6670
+ tools: prepared.tools,
6671
+ executables: prepared.executables
6672
+ });
6673
+ assertGuestEnvironmentBoundary({
6674
+ forwardEnv: profile.requiredEnv,
6675
+ sandboxEnv: profile.sandboxConfig.env
6676
+ });
6677
+ await ctx.agent.tasks.registerExecutorManifest(await prepared.attestor.registration());
6678
+ preparedRuntimes.set(profile.id, prepared);
6679
+ profiles.push(profile);
6680
+ } catch (error) {
6681
+ if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
6682
+ skippedProfileBoundaries.push({
6683
+ profile,
6684
+ error
6685
+ });
6686
+ }
6687
+ if (profiles.length === 0) {
6688
+ const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
6689
+ throw new Error(`No safe runtime profiles remain. ${details}`);
6690
+ }
6811
6691
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
6812
6692
  const runtimeSessionStore = createApiRuntimeSessionStore({
6813
6693
  agent: ctx.agent,
@@ -6815,260 +6695,232 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6815
6695
  });
6816
6696
  const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
6817
6697
  const runtimeInstanceId = createRuntimeInstanceId();
6818
- const slotIdentity = {
6819
- agentName: opts.agent,
6820
- runtimeProfileId: profile.id,
6821
- runtimeInstanceId
6822
- };
6823
- const executionPlans = createExecutionPlanCache({
6824
- stateDirs,
6825
- slotIdentity,
6826
- warmSessionTtlSec: opts.warmSessionTtlSec,
6827
- workspacePolicy: {
6828
- defaultWorkspaceMode: profile.defaultWorkspaceMode,
6829
- allowedWorkspaceModes: profile.allowedWorkspaceModes
6830
- },
6831
- slotRegistry,
6832
- runtimeSessionStore,
6833
- sourceAttemptResolver
6834
- });
6698
+ const piAgentDir = await resolvePiAgentDir(cfg, profiles[0].mountPath, profiles);
6699
+ process.once("exit", piAgentDir.cleanup);
6700
+ activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
6701
+ const runtimes = /* @__PURE__ */ new Map();
6702
+ for (const profile of profiles) {
6703
+ const common = parseCommonOptions(values, { runtimeDefaults: {
6704
+ leaseTtlSec: profile.leaseTtlSec,
6705
+ heartbeatIntervalMs: profile.heartbeatIntervalMs,
6706
+ maxBatchSize: profile.maxBatchSize,
6707
+ maxTurns: profile.maxTurns,
6708
+ maxBashTimeouts: profile.maxBashTimeouts,
6709
+ warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
6710
+ } });
6711
+ const sandbox = {
6712
+ config: profile.sandboxConfig,
6713
+ rootDir: profile.mountPath,
6714
+ path: profile.source
6715
+ };
6716
+ const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
6717
+ const slotIdentity = {
6718
+ agentName: common.agent,
6719
+ runtimeProfileId: profile.id,
6720
+ runtimeInstanceId
6721
+ };
6722
+ const executionPlans = createExecutionPlanCache({
6723
+ stateDirs,
6724
+ slotIdentity,
6725
+ warmSessionTtlSec: common.warmSessionTtlSec,
6726
+ workspacePolicy: {
6727
+ defaultWorkspaceMode: profile.defaultWorkspaceMode,
6728
+ allowedWorkspaceModes: profile.allowedWorkspaceModes
6729
+ },
6730
+ slotRegistry,
6731
+ runtimeSessionStore,
6732
+ sourceAttemptResolver
6733
+ });
6734
+ runtimes.set(profile.id, {
6735
+ common,
6736
+ profile,
6737
+ sandbox,
6738
+ stateDirs,
6739
+ slotIdentity,
6740
+ executionPlans,
6741
+ preparedRuntime: preparedRuntimes.get(profile.id)
6742
+ });
6743
+ }
6744
+ const firstRuntime = runtimes.get(profiles[0].id);
6745
+ if (!firstRuntime) throw new Error("No runtime profiles resolved");
6835
6746
  const otelShutdown = await initWorkerOtel({
6836
- serviceName: "moltnet.agent-daemon.once",
6747
+ serviceName: opts.serviceName,
6837
6748
  agent: ctx.agent,
6838
6749
  endpoint: cfg.otelEndpoint,
6839
6750
  resourceAttributes: {
6840
- "moltnet.task.id": taskId,
6841
- "moltnet.agent.name": opts.agent,
6751
+ "moltnet.team.id": teamId,
6752
+ "moltnet.agent.name": baseCommon.agent,
6842
6753
  "moltnet.credential.source": ctx.credentialSource,
6843
- "moltnet.llm.provider": profile.provider,
6844
- "moltnet.llm.model": profile.model,
6845
- ...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
6846
- ...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
6847
- ...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
6848
- ...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
6849
- ...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
6850
- "moltnet.runtime_profile.id": profile.id
6754
+ "moltnet.runtime_profile.count": String(profiles.length),
6755
+ "moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
6851
6756
  }
6852
6757
  });
6853
6758
  const { logger, shutdown: shutdownLogger } = createRootLogger({
6854
- name: "agent-daemon.once",
6855
- level: cfg.logLevel || (opts.debug ? "debug" : "info")
6759
+ name: `agent-daemon.${opts.modeLabel}`,
6760
+ level: cfg.logLevel || (baseCommon.debug ? "debug" : "info")
6856
6761
  });
6857
6762
  const rootLogger = logger.child({
6858
- mode: "once",
6859
- agent: opts.agent,
6860
- provider: profile.provider,
6861
- model: profile.model,
6862
- thinkingLevel: profile.thinkingLevel,
6863
- temperature: profile.temperature,
6864
- topP: profile.topP,
6865
- topK: profile.topK,
6866
- maxOutputTokens: profile.maxOutputTokens,
6867
- runtimeProfileId: profile.id,
6868
- runtimeProfileName: profile.name
6763
+ mode: opts.modeLabel,
6764
+ agent: baseCommon.agent,
6765
+ teamId,
6766
+ runtimeProfileIds: profiles.map((p) => p.id),
6767
+ runtimeProfileNames: profiles.map((p) => p.name)
6869
6768
  });
6870
- rootLogger.info({
6871
- sandbox: sandbox.path,
6872
- taskId,
6873
- leaseTtlSec: opts.leaseTtlSec,
6874
- heartbeatIntervalMs: opts.heartbeatIntervalMs,
6875
- maxTurns: opts.maxTurns,
6876
- maxBashTimeouts: opts.maxBashTimeouts,
6877
- warmSessionTtlSec: opts.warmSessionTtlSec,
6878
- profileId: profile.id,
6879
- profileSessionTtlSec: profile.sessionTtlSec,
6880
- profileWorkspaceTtlSec: profile.workspaceTtlSec,
6881
- piAgentDir: piAgentDir.path,
6882
- piAgentDirSource: piAgentDir.source
6883
- }, "agent-daemon.starting");
6884
- try {
6885
- const reaped = await reapRuntimeSlotResources({
6886
- onIssue: (issue) => {
6887
- rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
6888
- },
6889
- runtimeSlotStore: slotRegistry,
6890
- taskReader: ctx.agent.tasks
6891
- }, {
6892
- agentName: opts.agent,
6893
- mainWorktree: resolveMainWorktree(sandbox.rootDir),
6894
- runtimeInstanceId,
6895
- runtimeProfileId: profile.id,
6896
- sessionRootDir: stateDirs.piSessionsDir,
6897
- scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
6898
- teamId: profile.teamId
6899
- });
6900
- if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
6901
- } catch (err) {
6902
- rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
6903
- }
6769
+ for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
6770
+ runtimeProfileId: profile.id,
6771
+ runtimeProfileName: profile.name,
6772
+ refusedEnvironmentNames: error.refusedNames
6773
+ }, "agent-daemon.runtime_profile_skipped_credential_boundary");
6774
+ const abort = new AbortController();
6904
6775
  let runtime = null;
6905
- let activeAttemptN = null;
6776
+ let active = null;
6906
6777
  const signalHandlers = installShutdownSignalHandlers({
6907
6778
  logDrain: (signal) => {
6908
- rootLogger.warn({
6909
- signal,
6910
- taskId
6911
- }, "agent-daemon.draining");
6779
+ rootLogger.warn({ signal }, "agent-daemon.draining");
6912
6780
  },
6913
6781
  drain: (signal) => {
6782
+ abort.abort();
6914
6783
  runtime?.stop(`agent-daemon received ${signal}`);
6915
6784
  abortActiveAttemptOnSignal({
6916
- active: activeAttemptN === null ? null : {
6917
- taskId,
6918
- attemptN: activeAttemptN
6919
- },
6785
+ active,
6920
6786
  signal,
6921
- abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
6787
+ abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
6922
6788
  logFailure: (err, current) => {
6923
6789
  try {
6924
6790
  rootLogger.warn({
6925
6791
  err: err instanceof Error ? err.message : String(err),
6926
- taskId: current.taskId,
6927
- attemptN: current.attemptN
6928
- }, "agent-daemon.abort_on_signal_failed");
6929
- } catch (logErr) {
6930
- process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
6931
- }
6932
- }
6933
- });
6934
- }
6935
- });
6936
- try {
6937
- const rawExecuteTask = preparedRuntime.createTaskExecutor({
6938
- agentName: opts.agent,
6939
- moltnetAgent: ctx.agent,
6940
- agentIdentity,
6941
- hostCapabilitySigner,
6942
- hostCapabilityLogger: rootLogger,
6943
- agentRootDir: ctx.agentRootDir,
6944
- mountPath: sandbox.rootDir,
6945
- provider: profile.provider,
6946
- model: profile.model,
6947
- thinkingLevel: profile.thinkingLevel,
6948
- temperature: profile.temperature,
6949
- topP: profile.topP,
6950
- topK: profile.topK,
6951
- maxOutputTokens: profile.maxOutputTokens,
6952
- sandboxConfig: sandbox.config,
6953
- forwardEnv: profile.requiredEnv,
6954
- onVmDiagnostic: (diagnostic) => {
6955
- const fields = {
6956
- event: diagnostic.event,
6957
- ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
6958
- };
6959
- if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
6960
- else rootLogger.info(fields, diagnostic.message);
6961
- },
6962
- runtimeProfileContext: profile.context,
6963
- runtimeProfileId: profile.id,
6964
- toolEnforcement: profile.toolEnforcement,
6965
- makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
6966
- onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
6967
- toolPolicyLogger: rootLogger,
6968
- maxTurns: opts.maxTurns,
6969
- maxBashTimeouts: opts.maxBashTimeouts
6970
- });
6971
- const executeTask = async (claimedTask, reporter) => {
6972
- if (runtimeCredentialConfig) await observeGovernancePlanSafely({
6973
- config: runtimeCredentialConfig,
6974
- profile,
6975
- offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
6976
- registry: createNodeSecretProviderRegistry(),
6977
- executorFingerprint: preparedRuntime.attestor.fingerprint,
6978
- claimAuthority: claimedTask.claimAuthority ?? {},
6979
- taskId: claimedTask.task.id,
6980
- attemptN: claimedTask.attemptN,
6981
- logger: rootLogger
6982
- });
6983
- let executionPlan;
6984
- try {
6985
- executionPlan = await executionPlans.getOrCreate(claimedTask);
6986
- } catch (err) {
6987
- const message = err instanceof Error ? err.message : String(err);
6988
- rootLogger.warn({
6989
- taskId: claimedTask.task.id,
6990
- attemptN: claimedTask.attemptN,
6991
- err: message
6992
- }, "agent-daemon.execution_plan_failed");
6993
- return {
6994
- taskId: claimedTask.task.id,
6995
- attemptN: claimedTask.attemptN,
6996
- status: "failed",
6997
- output: null,
6998
- outputCid: null,
6999
- usage: {
7000
- inputTokens: 0,
7001
- outputTokens: 0
7002
- },
7003
- durationMs: 0,
7004
- error: {
7005
- code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
7006
- message,
7007
- retryable: false
7008
- }
7009
- };
7010
- }
7011
- if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7012
- ...slotIdentity,
7013
- runtimeProfileId: profile.id,
6792
+ taskId: current.taskId,
6793
+ attemptN: current.attemptN
6794
+ }, "agent-daemon.abort_on_signal_failed");
6795
+ } catch {}
6796
+ }
6797
+ });
6798
+ }
6799
+ });
6800
+ rootLogger.info({
6801
+ credentialSource: cfg.credentialSource,
6802
+ subjectType: startupWhoami.subjectType,
6803
+ bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
6804
+ credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
6805
+ boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
6806
+ taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
6807
+ correlationId: values["correlation-id"] ?? null,
6808
+ diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
6809
+ pollIntervalMs,
6810
+ maxPollIntervalMs,
6811
+ profiles: profiles.map((profile) => {
6812
+ const runtime = requireRuntime(runtimes, profile.id);
6813
+ return {
6814
+ id: profile.id,
6815
+ name: profile.name,
7014
6816
  provider: profile.provider,
7015
6817
  model: profile.model,
7016
- teamId: claimedTask.task.teamId,
7017
- slotKey: executionPlan.slotKey,
7018
- taskType: claimedTask.task.taskType,
7019
- sessionDir: executionPlan.sessionPersistence.sessionDir,
7020
- sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
7021
- workspaceId: executionPlan.workspaceId,
7022
- worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, sandbox.rootDir, executionPlan),
7023
- worktreeBranch: executionPlan.worktreeBranch,
7024
- workspaceKind: executionPlan.workspaceKind,
7025
- lastTaskId: claimedTask.task.id,
7026
- lastAttemptN: claimedTask.attemptN
7027
- });
7028
- activeAttemptN = claimedTask.attemptN;
7029
- try {
7030
- return await runWithDaemonRuntimeContext({
7031
- profileId: profile.id,
7032
- profileName: profile.name,
7033
- provider: profile.provider,
7034
- model: profile.model,
7035
- thinkingLevel: profile.thinkingLevel,
7036
- temperature: profile.temperature,
7037
- topP: profile.topP,
7038
- topK: profile.topK,
7039
- maxOutputTokens: profile.maxOutputTokens
7040
- }, () => rawExecuteTask(claimedTask, reporter));
7041
- } finally {
7042
- activeAttemptN = null;
7043
- executionPlans.delete(claimedTask);
7044
- if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
6818
+ thinkingLevel: profile.thinkingLevel,
6819
+ temperature: profile.temperature,
6820
+ topP: profile.topP,
6821
+ topK: profile.topK,
6822
+ maxOutputTokens: profile.maxOutputTokens,
6823
+ sandbox: runtime.sandbox.path,
6824
+ leaseTtlSec: runtime.common.leaseTtlSec,
6825
+ heartbeatIntervalMs: runtime.common.heartbeatIntervalMs,
6826
+ maxTurns: runtime.common.maxTurns,
6827
+ maxBashTimeouts: runtime.common.maxBashTimeouts,
6828
+ warmSessionTtlSec: runtime.common.warmSessionTtlSec,
6829
+ profileSessionTtlSec: profile.sessionTtlSec,
6830
+ profileWorkspaceTtlSec: profile.workspaceTtlSec,
6831
+ defaultWorkspaceMode: profile.defaultWorkspaceMode,
6832
+ allowedWorkspaceModes: profile.allowedWorkspaceModes
6833
+ };
6834
+ }),
6835
+ piAgentDir: piAgentDir.path,
6836
+ piAgentDirSource: piAgentDir.source
6837
+ }, "agent-daemon.starting");
6838
+ let reaperRunning = false;
6839
+ const reapRuntimeResources = async () => {
6840
+ if (reaperRunning) return;
6841
+ reaperRunning = true;
6842
+ try {
6843
+ for (const profile of profiles) {
6844
+ const selected = requireRuntime(runtimes, profile.id);
6845
+ const result = await reapRuntimeSlotResources({
6846
+ onIssue: (issue) => {
6847
+ rootLogger.warn({
6848
+ ...issue,
6849
+ runtimeProfileId: profile.id
6850
+ }, "agent-daemon.runtime_resource_reap_issue");
6851
+ },
6852
+ runtimeSlotStore: slotRegistry,
6853
+ taskReader: ctx.agent.tasks
6854
+ }, {
6855
+ agentName: selected.common.agent,
6856
+ mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
6857
+ runtimeInstanceId,
6858
+ runtimeProfileId: profile.id,
6859
+ sessionRootDir: selected.stateDirs.piSessionsDir,
6860
+ scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
6861
+ teamId
6862
+ });
6863
+ if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
6864
+ ...result,
6865
+ runtimeProfileId: profile.id
6866
+ }, "agent-daemon.runtime_resources_reaped");
7045
6867
  }
7046
- };
7047
- const writeCorrelationAnchors = makePrBodyAnchorWriter({
7048
- gh: createGhCliClient(),
7049
- logger: rootLogger
7050
- });
6868
+ } catch (err) {
6869
+ rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
6870
+ } finally {
6871
+ reaperRunning = false;
6872
+ }
6873
+ };
6874
+ await reapRuntimeResources();
6875
+ const reaperTimer = setInterval(() => {
6876
+ reapRuntimeResources();
6877
+ }, 6e4);
6878
+ reaperTimer.unref();
6879
+ const outputs = [];
6880
+ try {
7051
6881
  runtime = new AgentRuntime({
7052
6882
  logger: rootLogger,
7053
- source: new ApiTaskSource({
6883
+ source: new PollingApiTaskSource({
7054
6884
  agent: ctx.agent,
7055
- taskId,
7056
- teamId: profile.teamId,
7057
- leaseTtlSec: opts.leaseTtlSec,
7058
- profileId: profile.id,
7059
- executorFingerprint: preparedRuntime.attestor.fingerprint
7060
- }),
7061
- makeReporter: () => new ApiTaskReporter({
7062
- tasks: ctx.agent.tasks,
7063
- teamId: profile.teamId,
7064
- leaseTtlSec: opts.leaseTtlSec,
7065
- heartbeatIntervalMs: opts.heartbeatIntervalMs,
7066
- maxBatchSize: opts.maxBatchSize,
7067
- flushIntervalMs: opts.flushIntervalMs
6885
+ teamId,
6886
+ taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
6887
+ correlationId: values["correlation-id"],
6888
+ profiles: profiles.map((profile) => ({
6889
+ profileId: profile.id,
6890
+ leaseTtlSec: requireRuntime(runtimes, profile.id).common.leaseTtlSec
6891
+ })),
6892
+ diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
6893
+ leaseTtlSec: firstRuntime.common.leaseTtlSec,
6894
+ listLimit,
6895
+ pollIntervalMs,
6896
+ maxPollIntervalMs,
6897
+ signal: abort.signal,
6898
+ stopWhenEmpty: opts.stopWhenEmpty,
6899
+ waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
6900
+ waitAfterTaskMs: waitAfterTaskSec * 1e3,
6901
+ debug: baseCommon.debug,
6902
+ traceIdlePolling: cfg.traceIdlePolling,
6903
+ logger: rootLogger,
6904
+ executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
6905
+ slotRegistry,
6906
+ sessionRegistry: runtimeSessionStore,
6907
+ sourceAttemptResolver
7068
6908
  }),
6909
+ makeReporter: (claimedTask) => {
6910
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
6911
+ return new ApiTaskReporter({
6912
+ tasks: ctx.agent.tasks,
6913
+ teamId: claimedTask.task.teamId,
6914
+ leaseTtlSec: selected.common.leaseTtlSec,
6915
+ heartbeatIntervalMs: selected.common.heartbeatIntervalMs,
6916
+ maxBatchSize: selected.common.maxBatchSize,
6917
+ flushIntervalMs: selected.common.flushIntervalMs
6918
+ });
6919
+ },
7069
6920
  onTaskFinished: async (output, claimedTask) => {
6921
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
7070
6922
  const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
7071
- let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
6923
+ let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
7072
6924
  if (resolved?.session?.sessionDir) try {
7073
6925
  const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
7074
6926
  await runtimeSessionStore.uploadAttemptFinal({
@@ -7093,422 +6945,738 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
7093
6945
  task: claimedTask.task,
7094
6946
  slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
7095
6947
  retryTriage: createRuntimeProfileRetryTriage({
7096
- runtimeProfile: profile,
6948
+ runtimeProfile: selected.profile,
7097
6949
  piAgentDir: piAgentDir.path,
7098
6950
  cwd: ctx.agentRootDir
7099
6951
  }),
7100
- executorAttestor: preparedRuntime.attestor,
7101
- writeCorrelationAnchors,
6952
+ executorAttestor: selected.preparedRuntime.attestor,
6953
+ writeCorrelationAnchors: makePrBodyAnchorWriter({
6954
+ gh: createGhCliClient(),
6955
+ logger: rootLogger.child({
6956
+ runtimeProfileId: selected.profile.id,
6957
+ runtimeProfileName: selected.profile.name
6958
+ })
6959
+ }),
7102
6960
  log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
7103
6961
  });
7104
- },
7105
- executeTask
7106
- });
7107
- const [output] = await runtime.start();
7108
- if (!output) {
7109
- rootLogger.error({}, "agent-daemon.no_output");
7110
- return 1;
7111
- }
7112
- console.log("\n[done] TaskOutput:");
7113
- console.log(JSON.stringify(output, null, 2));
7114
- return output.status === "completed" ? 0 : 1;
7115
- } finally {
7116
- signalHandlers.dispose();
7117
- await slotRegistry.close();
7118
- await otelShutdown();
7119
- await shutdownLogger();
7120
- }
7121
- }
7122
- function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
7123
- if (!executionPlan.workspaceId) return null;
7124
- return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
7125
- }
7126
- function resolveMainWorktree(mountPath) {
7127
- try {
7128
- return findMainWorktree(mountPath);
7129
- } catch {
7130
- return null;
7131
- }
7132
- }
7133
- //#endregion
7134
- //#region src/cli/poll.ts
7135
- function runPoll(argv, runtimeAdapter) {
7136
- return runPolling({
7137
- argv,
7138
- serviceName: "moltnet.agent-daemon.poll",
7139
- stopWhenEmpty: false,
7140
- modeLabel: "poll",
7141
- helpText: POLL_HELP,
7142
- runtimeAdapter
7143
- });
7144
- }
7145
- var NAME_RE = IDENTITY_ALIAS_PATTERN;
7146
- var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
7147
- function assertStoreName(kind, value) {
7148
- try {
7149
- return assertIdentityAlias(value);
7150
- } catch {
7151
- throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
7152
- }
7153
- }
7154
- function assertProviderId(value) {
7155
- if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
7156
- return value;
7157
- }
7158
- var AgentServerStoreError = class extends Error {
7159
- name = "AgentServerStoreError";
7160
- constructor(code, message, options) {
7161
- super(message, options);
7162
- this.code = code;
7163
- }
7164
- };
7165
- /**
7166
- * `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
7167
- *
7168
- * Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
7169
- * and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
7170
- * so honouring XDG here gave one application two config roots: on a machine
7171
- * with the variable set, the daemon wrote identities the CLI and SDK could not
7172
- * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
7173
- * genuinely custom location.
7174
- */
7175
- function resolveAgentServerRoot(input) {
7176
- const override = input.root?.trim();
7177
- if (override) return override;
7178
- return getConfigDir();
7179
- }
7180
- function providerEnvName(providerId) {
7181
- return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
7182
- }
7183
- function assertProviderEnvName(providerId, value) {
7184
- const expected = providerEnvName(providerId);
7185
- if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
7186
- return value;
7187
- }
7188
- function readJson(path) {
7189
- let raw;
7190
- try {
7191
- raw = readFileSync(path, "utf8");
7192
- } catch (cause) {
7193
- if (cause.code === "ENOENT") return null;
7194
- throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
7195
- }
7196
- try {
7197
- return JSON.parse(raw);
7198
- } catch (cause) {
7199
- throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
7200
- }
7201
- }
7202
- function writeJsonAtomic(path, value) {
7203
- const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
7204
- try {
7205
- writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
7206
- renameSync(temp, path);
7207
- } catch (cause) {
7208
- try {
7209
- rmSync(temp, { force: true });
7210
- } catch {}
7211
- throw cause;
7212
- }
7213
- }
7214
- var AgentServerStore = class {
7215
- root;
7216
- identitiesDir;
7217
- runsDir;
7218
- secretsDir;
7219
- /** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
7220
- piDir;
7221
- constructor(root) {
7222
- this.root = root;
7223
- this.identitiesDir = join(root, "identities");
7224
- this.runsDir = join(root, "runs");
7225
- this.secretsDir = join(root, "secrets");
7226
- this.piDir = join(root, "pi");
7227
- }
7228
- get piAuthJsonPath() {
7229
- return join(this.piDir, "auth.json");
7230
- }
7231
- /** Create the directory layout (0700) if missing. Idempotent. */
7232
- ensure() {
7233
- for (const dir of [
7234
- this.root,
7235
- this.identitiesDir,
7236
- this.runsDir,
7237
- this.secretsDir
7238
- ]) mkdirSync(dir, {
7239
- recursive: true,
7240
- mode: 448
7241
- });
7242
- return this;
7243
- }
7244
- get statePath() {
7245
- return join(this.root, "agent-server.json");
7246
- }
7247
- readAgentServerState() {
7248
- const state = readJson(this.statePath);
7249
- if (!state) return {
7250
- version: 2,
7251
- pendingRegistrations: {},
7252
- activations: {}
7253
- };
7254
- if (!isRecord$1(state) || state.version !== 2) throw new AgentServerStoreError("invalid_state", `agent-server.json version ${String(isRecord$1(state) ? state.version : void 0)} is not supported; move agent-server.json aside, run \`moltnet config migrate\`, then add or attach the agents again`);
7255
- if ("pairedOrigins" in state) throw new AgentServerStoreError("invalid_state", "agent-server.json uses the obsolete pairing format; move agent-server.json aside and configure the agent server again");
7256
- if (!isRecord$1(state.pendingRegistrations) || !isRecord$1(state.activations)) throw new AgentServerStoreError("invalid_state", "agent-server.json is missing the version 2 activation map; move agent-server.json aside, run `moltnet config migrate`, then add or attach the agents again");
7257
- for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
7258
- for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
7259
- assertStoreName("agent name", alias);
7260
- if (!isRecord$1(registration) || typeof registration.apiUrl !== "string" || registration.apiUrl.length === 0 || typeof registration.createdAt !== "string" || registration.createdAt.length === 0) throw new AgentServerStoreError("invalid_state", `pending registration "${alias}" is not valid`);
7261
- }
7262
- return {
7263
- version: 2,
7264
- pendingRegistrations: state.pendingRegistrations,
7265
- activations: state.activations
7266
- };
7267
- }
7268
- writeAgentServerState(state) {
7269
- writeJsonAtomic(this.statePath, state);
7270
- }
7271
- agentPath(name) {
7272
- return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
7273
- }
7274
- /** Central identity directory, shared with the Go CLI layout. */
7275
- identityDir(name) {
7276
- return storeChildPath(this.identitiesDir, "identity alias", name);
7277
- }
7278
- get identitySelectorPath() {
7279
- return join(this.root, "identity-selector.json");
7280
- }
7281
- readIdentitySelector() {
7282
- const selector = readJson(this.identitySelectorPath);
7283
- if (!selector) return null;
7284
- if (!isRecord$1(selector) || selector.version !== 1 || selector.default_identity !== void 0 && typeof selector.default_identity !== "string") throw new AgentServerStoreError("invalid_state", "identity-selector.json is not a supported selector document");
7285
- if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
7286
- return selector;
7287
- }
7288
- writeIdentitySelector(alias) {
7289
- writeJsonAtomic(this.identitySelectorPath, {
7290
- version: 1,
7291
- default_identity: assertStoreName("identity alias", alias)
6962
+ },
6963
+ executeTask: async (claimedTask, reporter) => {
6964
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
6965
+ const { common, executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
6966
+ if (runtimeCredentialConfig) await observeGovernancePlanSafely({
6967
+ config: runtimeCredentialConfig,
6968
+ profile,
6969
+ offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
6970
+ registry: createNodeSecretProviderRegistry(),
6971
+ executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
6972
+ claimAuthority: claimedTask.claimAuthority ?? {},
6973
+ taskId: claimedTask.task.id,
6974
+ attemptN: claimedTask.attemptN,
6975
+ logger: rootLogger
6976
+ });
6977
+ const taskLogger = rootLogger.child({
6978
+ runtimeProfileId: profile.id,
6979
+ runtimeProfileName: profile.name,
6980
+ provider: profile.provider,
6981
+ model: profile.model,
6982
+ thinkingLevel: profile.thinkingLevel,
6983
+ temperature: profile.temperature,
6984
+ topP: profile.topP,
6985
+ topK: profile.topK,
6986
+ maxOutputTokens: profile.maxOutputTokens
6987
+ });
6988
+ let executionPlan;
6989
+ try {
6990
+ executionPlan = await executionPlans.getOrCreate(claimedTask);
6991
+ } catch (err) {
6992
+ const message = err instanceof Error ? err.message : String(err);
6993
+ taskLogger.warn({
6994
+ taskId: claimedTask.task.id,
6995
+ attemptN: claimedTask.attemptN,
6996
+ err: message
6997
+ }, "agent-daemon.execution_plan_failed");
6998
+ return {
6999
+ taskId: claimedTask.task.id,
7000
+ attemptN: claimedTask.attemptN,
7001
+ status: "failed",
7002
+ output: null,
7003
+ outputCid: null,
7004
+ usage: {
7005
+ inputTokens: 0,
7006
+ outputTokens: 0
7007
+ },
7008
+ durationMs: 0,
7009
+ error: {
7010
+ code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
7011
+ message,
7012
+ retryable: false
7013
+ }
7014
+ };
7015
+ }
7016
+ const sessionDescriptor = executionPlan.descriptor;
7017
+ taskLogger.debug({
7018
+ taskId: claimedTask.task.id,
7019
+ taskType: claimedTask.task.taskType,
7020
+ resumable: sessionDescriptor.policy.resumable,
7021
+ workspaceMode: executionPlan.workspaceMode,
7022
+ workspaceScope: sessionDescriptor.policy.workspaceScope,
7023
+ sessionScope: sessionDescriptor.policy.sessionScope,
7024
+ slotKey: executionPlan.slotKey,
7025
+ slotId: executionPlan.slotId,
7026
+ sessionKey: executionPlan.sessionKey,
7027
+ piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
7028
+ workspaceId: executionPlan.workspaceId
7029
+ }, "agent-daemon.task_execution_policy");
7030
+ if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
7031
+ taskId: claimedTask.task.id,
7032
+ attemptN: claimedTask.attemptN,
7033
+ status: "failed",
7034
+ output: null,
7035
+ outputCid: null,
7036
+ usage: {
7037
+ inputTokens: 0,
7038
+ outputTokens: 0
7039
+ },
7040
+ durationMs: 0,
7041
+ error: {
7042
+ code: "unsupported_task_type",
7043
+ message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
7044
+ retryable: true
7045
+ }
7046
+ };
7047
+ if (reporter.cancelSignal.aborted) return {
7048
+ taskId: claimedTask.task.id,
7049
+ attemptN: claimedTask.attemptN,
7050
+ status: "cancelled",
7051
+ output: null,
7052
+ outputCid: null,
7053
+ usage: {
7054
+ inputTokens: 0,
7055
+ outputTokens: 0
7056
+ },
7057
+ durationMs: 0,
7058
+ error: {
7059
+ code: "task_cancelled",
7060
+ message: reporter.cancelReason ?? "Task cancelled before executor started.",
7061
+ retryable: false
7062
+ }
7063
+ };
7064
+ if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7065
+ ...slotIdentity,
7066
+ runtimeProfileId: profile.id,
7067
+ provider: profile.provider,
7068
+ model: profile.model,
7069
+ teamId: claimedTask.task.teamId,
7070
+ slotKey: executionPlan.slotKey,
7071
+ taskType: claimedTask.task.taskType,
7072
+ sessionDir: executionPlan.sessionPersistence.sessionDir,
7073
+ sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
7074
+ workspaceId: executionPlan.workspaceId,
7075
+ worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
7076
+ worktreeBranch: executionPlan.worktreeBranch,
7077
+ workspaceKind: executionPlan.workspaceKind,
7078
+ lastTaskId: claimedTask.task.id,
7079
+ lastAttemptN: claimedTask.attemptN
7080
+ });
7081
+ const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
7082
+ agentName: common.agent,
7083
+ moltnetAgent: ctx.agent,
7084
+ agentIdentity,
7085
+ hostCapabilitySigner,
7086
+ hostCapabilityLogger: taskLogger,
7087
+ agentRootDir: ctx.agentRootDir,
7088
+ mountPath: sandbox.rootDir,
7089
+ provider: profile.provider,
7090
+ model: profile.model,
7091
+ thinkingLevel: profile.thinkingLevel,
7092
+ temperature: profile.temperature,
7093
+ topP: profile.topP,
7094
+ topK: profile.topK,
7095
+ maxOutputTokens: profile.maxOutputTokens,
7096
+ sandboxConfig: sandbox.config,
7097
+ forwardEnv: profile.requiredEnv,
7098
+ onVmDiagnostic: (diagnostic) => {
7099
+ const fields = {
7100
+ event: diagnostic.event,
7101
+ ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
7102
+ };
7103
+ if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
7104
+ else taskLogger.info(fields, diagnostic.message);
7105
+ },
7106
+ runtimeProfileContext: profile.context,
7107
+ runtimeProfileId: profile.id,
7108
+ toolEnforcement: profile.toolEnforcement,
7109
+ makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
7110
+ makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
7111
+ toolPolicyLogger: taskLogger,
7112
+ maxTurns: common.maxTurns,
7113
+ maxBashTimeouts: common.maxBashTimeouts
7114
+ });
7115
+ try {
7116
+ active = {
7117
+ taskId: claimedTask.task.id,
7118
+ attemptN: claimedTask.attemptN
7119
+ };
7120
+ return await runWithDaemonRuntimeContext({
7121
+ profileId: profile.id,
7122
+ profileName: profile.name,
7123
+ provider: profile.provider,
7124
+ model: profile.model,
7125
+ thinkingLevel: profile.thinkingLevel,
7126
+ temperature: profile.temperature,
7127
+ topP: profile.topP,
7128
+ topK: profile.topK,
7129
+ maxOutputTokens: profile.maxOutputTokens
7130
+ }, () => rawExecuteTask(claimedTask, reporter));
7131
+ } finally {
7132
+ active = null;
7133
+ executionPlans.delete(claimedTask);
7134
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
7135
+ }
7136
+ }
7292
7137
  });
7138
+ const drained = await runtime.start();
7139
+ outputs.push(...drained);
7140
+ rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
7141
+ return drained.some((o) => o.status !== "completed") ? 1 : 0;
7142
+ } finally {
7143
+ clearInterval(reaperTimer);
7144
+ signalHandlers.dispose();
7145
+ await slotRegistry.close();
7146
+ await otelShutdown();
7147
+ await shutdownLogger();
7293
7148
  }
7294
- /** Identity aliases available in the shared user-level store. */
7295
- listIdentityAliases() {
7296
- return readdirSync(this.identitiesDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && IDENTITY_ALIAS_PATTERN.test(entry.name)).map((entry) => entry.name).sort((left, right) => left.localeCompare(right));
7297
- }
7298
- resolveIdentityAlias(explicit, active) {
7299
- const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
7300
- if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
7301
- return assertStoreName("identity alias", alias);
7302
- }
7303
- readAgentConfig(alias) {
7304
- return readJson(this.agentPath(alias));
7305
- }
7306
- writeAgentConfig(alias, config) {
7307
- mkdirSync(this.identityDir(alias), {
7308
- recursive: true,
7309
- mode: 448
7310
- });
7311
- writeJsonAtomic(this.agentPath(alias), config);
7312
- if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
7149
+ }
7150
+ function resolveRecordedWorkspacePath$1(stateRootDir, mountPath, executionPlan) {
7151
+ if (!executionPlan.workspaceId) return null;
7152
+ return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
7153
+ }
7154
+ function resolveMainWorktree$1(mountPath) {
7155
+ try {
7156
+ return findMainWorktree(mountPath);
7157
+ } catch {
7158
+ return null;
7313
7159
  }
7314
- removeAgentConfig(alias) {
7315
- rmSync(this.agentPath(alias), { force: true });
7316
- this.clearIdentitySelectorIfDefault(alias);
7160
+ }
7161
+ function runtimeForClaimedTask(runtimes, claimedTask) {
7162
+ if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
7163
+ return requireRuntime(runtimes, claimedTask.profileId);
7164
+ }
7165
+ function requireRuntime(runtimes, profileId) {
7166
+ const runtime = runtimes.get(profileId);
7167
+ if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
7168
+ return runtime;
7169
+ }
7170
+ function parseProfileValues(raw) {
7171
+ return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
7172
+ }
7173
+ function parseCsv(raw) {
7174
+ return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7175
+ }
7176
+ function optionalPositiveInt(raw, name, defaultValue) {
7177
+ if (raw === void 0) return defaultValue;
7178
+ const v = Number(raw);
7179
+ if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
7180
+ return v;
7181
+ }
7182
+ function optionalNonNegativeInt(raw, name, defaultValue) {
7183
+ if (raw === void 0) return defaultValue;
7184
+ const v = Number(raw);
7185
+ if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
7186
+ return v;
7187
+ }
7188
+ //#endregion
7189
+ //#region src/cli/drain.ts
7190
+ function runDrain(argv, runtimeAdapter) {
7191
+ return runPolling({
7192
+ argv,
7193
+ serviceName: "moltnet.agent-daemon.drain",
7194
+ stopWhenEmpty: true,
7195
+ modeLabel: "drain",
7196
+ helpText: DRAIN_HELP,
7197
+ runtimeAdapter
7198
+ });
7199
+ }
7200
+ //#endregion
7201
+ //#region src/cli/once.ts
7202
+ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
7203
+ if (isHelpFlag(argv)) {
7204
+ console.log(ONCE_HELP);
7205
+ return 0;
7317
7206
  }
7318
- /** Clears the persisted default when it names `alias`. */
7319
- clearIdentitySelectorIfDefault(alias) {
7320
- let selector = null;
7321
- try {
7322
- selector = this.readIdentitySelector();
7323
- } catch {
7324
- return;
7207
+ const { values } = parseArgs({
7208
+ args: argv,
7209
+ options: {
7210
+ ...commonOptionDefs(),
7211
+ "task-id": {
7212
+ type: "string",
7213
+ short: "t"
7214
+ },
7215
+ team: { type: "string" },
7216
+ sandbox: { type: "string" },
7217
+ profile: { type: "string" }
7325
7218
  }
7326
- if (!selector || selector.default_identity !== alias) return;
7327
- writeJsonAtomic(this.identitySelectorPath, { version: 1 });
7328
- }
7329
- readActivation(alias) {
7330
- return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
7331
- }
7332
- hasPendingRegistration(alias) {
7333
- return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
7334
- }
7335
- reserveRegistration(alias, apiUrl) {
7336
- const name = assertStoreName("agent name", alias);
7337
- const state = this.readAgentServerState();
7338
- if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
7339
- state.pendingRegistrations[name] = {
7340
- apiUrl,
7341
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
7342
- };
7343
- this.writeAgentServerState(state);
7344
- }
7345
- clearPendingRegistration(alias) {
7346
- const name = assertStoreName("agent name", alias);
7347
- const state = this.readAgentServerState();
7348
- delete state.pendingRegistrations[name];
7349
- this.writeAgentServerState(state);
7350
- }
7351
- writeActivation(activation) {
7352
- const alias = assertStoreName("agent name", activation.alias);
7353
- validateActivation(alias, activation);
7354
- const state = this.readAgentServerState();
7355
- state.activations[alias] = activation;
7356
- if (activation.source === "managed") delete state.pendingRegistrations[alias];
7357
- this.writeAgentServerState(state);
7358
- }
7359
- listActivations() {
7360
- return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
7361
- }
7362
- get providersPath() {
7363
- return join(this.root, "providers.json");
7364
- }
7365
- readProviders() {
7366
- const state = readJson(this.providersPath) ?? {};
7367
- this.validateProviders(state);
7368
- return state;
7219
+ });
7220
+ if (!values["task-id"]) {
7221
+ console.error("Missing required flag: --task-id\n");
7222
+ console.error(ONCE_HELP);
7223
+ return 1;
7369
7224
  }
7370
- writeProviders(state) {
7371
- this.validateProviders(state);
7372
- writeJsonAtomic(this.providersPath, state);
7225
+ const taskId = values["task-id"];
7226
+ if (!values.profile) {
7227
+ console.error("Missing required flag: --profile\n");
7228
+ console.error(ONCE_HELP);
7229
+ return 1;
7373
7230
  }
7374
- validateProviders(state) {
7375
- for (const [id, provider] of Object.entries(state)) {
7376
- assertProviderId(id);
7377
- assertProviderEnvName(id, provider.envName);
7231
+ let opts;
7232
+ try {
7233
+ opts = parseCommonOptions(values);
7234
+ } catch (err) {
7235
+ if (err instanceof MissingRequiredOptionError) {
7236
+ console.error(`${err.message}\n`);
7237
+ console.error(ONCE_HELP);
7238
+ return 1;
7378
7239
  }
7240
+ throw err;
7379
7241
  }
7380
- runDir(id) {
7381
- return storeChildPath(this.runsDir, "run id", id);
7242
+ if (values.sandbox) {
7243
+ console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
7244
+ return 1;
7382
7245
  }
7383
- resolveRunLogPath(id) {
7384
- let root;
7385
- let runDir;
7246
+ const cfg = loadConfig();
7247
+ const credentialSources = {
7248
+ profileRequirements: cfg.profileCredentialRequirements,
7249
+ bindings: cfg.credentialBindings
7250
+ };
7251
+ const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
7252
+ const initialOpts = opts;
7253
+ const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
7254
+ const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
7255
+ let gate = "resolve_agent_context";
7386
7256
  try {
7387
- root = realpathSync(this.runsDir);
7388
- runDir = realpathSync(this.runDir(id));
7389
- } catch (cause) {
7390
- throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
7257
+ const resolvedContext = await resolveAgentContext(initialOpts.agent, {
7258
+ agentRootDir: explicitAgentRootDir,
7259
+ credentialSource: cfg.credentialSource,
7260
+ envApiUrl: cfg.apiUrl
7261
+ });
7262
+ gate = "authenticate_and_bind";
7263
+ const whoami = await validateStartupBinding({
7264
+ agent: resolvedContext.agent,
7265
+ teamId: values.team,
7266
+ expectedAgent: cfg.expectedAgent
7267
+ });
7268
+ gate = "resolve_signing_material";
7269
+ const privateKey = await resolveExecutorSigningPrivateKey({
7270
+ credentialSource: cfg.credentialSource,
7271
+ agentDir: resolvedContext.agentDir,
7272
+ configuredPrivateKey: cfg.signingPrivateKey,
7273
+ configuredPrivateKeyRef: cfg.signingPrivateKeyRef
7274
+ });
7275
+ gate = "validate_scopes";
7276
+ validateDaemonScopes(whoami);
7277
+ gate = "validate_signing_identity";
7278
+ await validateExecutorSigningIdentity({
7279
+ whoami,
7280
+ signingPrivateKey: privateKey
7281
+ });
7282
+ gate = "resolve_agent_identity";
7283
+ const agentIdentity = await resolveDaemonAgentIdentity({
7284
+ agentName: initialOpts.agent,
7285
+ whoami,
7286
+ credentialSource: cfg.credentialSource,
7287
+ agentDir: resolvedContext.agentDir,
7288
+ gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
7289
+ });
7290
+ return {
7291
+ ctx: resolvedContext,
7292
+ signingPrivateKey: privateKey,
7293
+ agentIdentity,
7294
+ hostCapabilitySigner: createLocalSeedSigner({
7295
+ privateKeySeed: privateKey,
7296
+ agent: resolvedContext.agent,
7297
+ identity: agentIdentity
7298
+ })
7299
+ };
7300
+ } catch (error) {
7301
+ await logDaemonStartupFailure({
7302
+ serviceName: "agent-daemon.once",
7303
+ level: cfg.logLevel || (initialOpts.debug ? "debug" : "info"),
7304
+ gate,
7305
+ agent: initialOpts.agent,
7306
+ credentialSource: cfg.credentialSource,
7307
+ error
7308
+ });
7309
+ throw error;
7391
7310
  }
7392
- if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
7393
- const logPath = join(runDir, "daemon.log");
7394
- try {
7395
- if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
7396
- const resolvedLog = realpathSync(logPath);
7397
- if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
7398
- } catch (cause) {
7399
- if (cause instanceof AgentServerStoreError) throw cause;
7400
- if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
7311
+ })();
7312
+ const daemonRootDir = explicitAgentRootDir ?? process.cwd();
7313
+ const profile = await resolveRuntimeProfile({
7314
+ agent: ctx.agent,
7315
+ profile: values.profile,
7316
+ teamId: values.team,
7317
+ cwd: daemonRootDir
7318
+ });
7319
+ assertRuntimeAdapterSupportsProfile(runtimeAdapter, profile);
7320
+ const preparedRuntime = attestPreparedRuntime(await runtimeAdapter.prepare({ profile }), signingPrivateKey);
7321
+ validateRuntimeProfilePrerequisites(profile, cfg.profilePrerequisiteEnv, {
7322
+ tools: preparedRuntime.tools,
7323
+ executables: preparedRuntime.executables
7324
+ });
7325
+ assertGuestEnvironmentBoundary({
7326
+ forwardEnv: profile.requiredEnv,
7327
+ sandboxEnv: profile.sandboxConfig.env
7328
+ });
7329
+ await ctx.agent.tasks.registerExecutorManifest(await preparedRuntime.attestor.registration());
7330
+ opts = parseCommonOptions(values, { runtimeDefaults: {
7331
+ leaseTtlSec: profile.leaseTtlSec,
7332
+ heartbeatIntervalMs: profile.heartbeatIntervalMs,
7333
+ maxBatchSize: profile.maxBatchSize,
7334
+ maxTurns: profile.maxTurns,
7335
+ maxBashTimeouts: profile.maxBashTimeouts,
7336
+ warmSessionTtlSec: resolveProfileWarmSessionTtlSec(profile)
7337
+ } });
7338
+ const sandbox = {
7339
+ config: profile.sandboxConfig,
7340
+ rootDir: profile.mountPath,
7341
+ path: profile.source
7342
+ };
7343
+ const piAgentDir = await resolvePiAgentDir(cfg, sandbox.rootDir, [profile]);
7344
+ process.once("exit", piAgentDir.cleanup);
7345
+ activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
7346
+ const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
7347
+ const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
7348
+ const runtimeSessionStore = createApiRuntimeSessionStore({
7349
+ agent: ctx.agent,
7350
+ logger: { warn: (context, message) => rootLogger.warn(context, message) }
7351
+ });
7352
+ const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
7353
+ const runtimeInstanceId = createRuntimeInstanceId();
7354
+ const slotIdentity = {
7355
+ agentName: opts.agent,
7356
+ runtimeProfileId: profile.id,
7357
+ runtimeInstanceId
7358
+ };
7359
+ const executionPlans = createExecutionPlanCache({
7360
+ stateDirs,
7361
+ slotIdentity,
7362
+ warmSessionTtlSec: opts.warmSessionTtlSec,
7363
+ workspacePolicy: {
7364
+ defaultWorkspaceMode: profile.defaultWorkspaceMode,
7365
+ allowedWorkspaceModes: profile.allowedWorkspaceModes
7366
+ },
7367
+ slotRegistry,
7368
+ runtimeSessionStore,
7369
+ sourceAttemptResolver
7370
+ });
7371
+ const otelShutdown = await initWorkerOtel({
7372
+ serviceName: "moltnet.agent-daemon.once",
7373
+ agent: ctx.agent,
7374
+ endpoint: cfg.otelEndpoint,
7375
+ resourceAttributes: {
7376
+ "moltnet.task.id": taskId,
7377
+ "moltnet.agent.name": opts.agent,
7378
+ "moltnet.credential.source": ctx.credentialSource,
7379
+ "moltnet.llm.provider": profile.provider,
7380
+ "moltnet.llm.model": profile.model,
7381
+ ...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
7382
+ ...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
7383
+ ...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
7384
+ ...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
7385
+ ...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
7386
+ "moltnet.runtime_profile.id": profile.id
7401
7387
  }
7402
- return logPath;
7388
+ });
7389
+ const { logger, shutdown: shutdownLogger } = createRootLogger({
7390
+ name: "agent-daemon.once",
7391
+ level: cfg.logLevel || (opts.debug ? "debug" : "info")
7392
+ });
7393
+ const rootLogger = logger.child({
7394
+ mode: "once",
7395
+ agent: opts.agent,
7396
+ provider: profile.provider,
7397
+ model: profile.model,
7398
+ thinkingLevel: profile.thinkingLevel,
7399
+ temperature: profile.temperature,
7400
+ topP: profile.topP,
7401
+ topK: profile.topK,
7402
+ maxOutputTokens: profile.maxOutputTokens,
7403
+ runtimeProfileId: profile.id,
7404
+ runtimeProfileName: profile.name
7405
+ });
7406
+ rootLogger.info({
7407
+ sandbox: sandbox.path,
7408
+ taskId,
7409
+ leaseTtlSec: opts.leaseTtlSec,
7410
+ heartbeatIntervalMs: opts.heartbeatIntervalMs,
7411
+ maxTurns: opts.maxTurns,
7412
+ maxBashTimeouts: opts.maxBashTimeouts,
7413
+ warmSessionTtlSec: opts.warmSessionTtlSec,
7414
+ profileId: profile.id,
7415
+ profileSessionTtlSec: profile.sessionTtlSec,
7416
+ profileWorkspaceTtlSec: profile.workspaceTtlSec,
7417
+ piAgentDir: piAgentDir.path,
7418
+ piAgentDirSource: piAgentDir.source
7419
+ }, "agent-daemon.starting");
7420
+ try {
7421
+ const reaped = await reapRuntimeSlotResources({
7422
+ onIssue: (issue) => {
7423
+ rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
7424
+ },
7425
+ runtimeSlotStore: slotRegistry,
7426
+ taskReader: ctx.agent.tasks
7427
+ }, {
7428
+ agentName: opts.agent,
7429
+ mainWorktree: resolveMainWorktree(sandbox.rootDir),
7430
+ runtimeInstanceId,
7431
+ runtimeProfileId: profile.id,
7432
+ sessionRootDir: stateDirs.piSessionsDir,
7433
+ scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
7434
+ teamId: profile.teamId
7435
+ });
7436
+ if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
7437
+ } catch (err) {
7438
+ rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
7403
7439
  }
7404
- createRunDir(id) {
7405
- const dir = this.runDir(id);
7406
- const piDir = join(dir, "pi");
7407
- mkdirSync(piDir, {
7408
- recursive: true,
7409
- mode: 448
7440
+ let runtime = null;
7441
+ let activeAttemptN = null;
7442
+ const signalHandlers = installShutdownSignalHandlers({
7443
+ logDrain: (signal) => {
7444
+ rootLogger.warn({
7445
+ signal,
7446
+ taskId
7447
+ }, "agent-daemon.draining");
7448
+ },
7449
+ drain: (signal) => {
7450
+ runtime?.stop(`agent-daemon received ${signal}`);
7451
+ abortActiveAttemptOnSignal({
7452
+ active: activeAttemptN === null ? null : {
7453
+ taskId,
7454
+ attemptN: activeAttemptN
7455
+ },
7456
+ signal,
7457
+ abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
7458
+ logFailure: (err, current) => {
7459
+ try {
7460
+ rootLogger.warn({
7461
+ err: err instanceof Error ? err.message : String(err),
7462
+ taskId: current.taskId,
7463
+ attemptN: current.attemptN
7464
+ }, "agent-daemon.abort_on_signal_failed");
7465
+ } catch (logErr) {
7466
+ process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
7467
+ }
7468
+ }
7469
+ });
7470
+ }
7471
+ });
7472
+ try {
7473
+ const rawExecuteTask = preparedRuntime.createTaskExecutor({
7474
+ agentName: opts.agent,
7475
+ moltnetAgent: ctx.agent,
7476
+ agentIdentity,
7477
+ hostCapabilitySigner,
7478
+ hostCapabilityLogger: rootLogger,
7479
+ agentRootDir: ctx.agentRootDir,
7480
+ mountPath: sandbox.rootDir,
7481
+ provider: profile.provider,
7482
+ model: profile.model,
7483
+ thinkingLevel: profile.thinkingLevel,
7484
+ temperature: profile.temperature,
7485
+ topP: profile.topP,
7486
+ topK: profile.topK,
7487
+ maxOutputTokens: profile.maxOutputTokens,
7488
+ sandboxConfig: sandbox.config,
7489
+ forwardEnv: profile.requiredEnv,
7490
+ onVmDiagnostic: (diagnostic) => {
7491
+ const fields = {
7492
+ event: diagnostic.event,
7493
+ ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
7494
+ };
7495
+ if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
7496
+ else rootLogger.info(fields, diagnostic.message);
7497
+ },
7498
+ runtimeProfileContext: profile.context,
7499
+ runtimeProfileId: profile.id,
7500
+ toolEnforcement: profile.toolEnforcement,
7501
+ makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
7502
+ onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
7503
+ toolPolicyLogger: rootLogger,
7504
+ maxTurns: opts.maxTurns,
7505
+ maxBashTimeouts: opts.maxBashTimeouts
7410
7506
  });
7411
- return {
7412
- dir,
7413
- piDir,
7414
- logPath: join(dir, "daemon.log")
7507
+ const executeTask = async (claimedTask, reporter) => {
7508
+ if (runtimeCredentialConfig) await observeGovernancePlanSafely({
7509
+ config: runtimeCredentialConfig,
7510
+ profile,
7511
+ offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
7512
+ registry: createNodeSecretProviderRegistry(),
7513
+ executorFingerprint: preparedRuntime.attestor.fingerprint,
7514
+ claimAuthority: claimedTask.claimAuthority ?? {},
7515
+ taskId: claimedTask.task.id,
7516
+ attemptN: claimedTask.attemptN,
7517
+ logger: rootLogger
7518
+ });
7519
+ let executionPlan;
7520
+ try {
7521
+ executionPlan = await executionPlans.getOrCreate(claimedTask);
7522
+ } catch (err) {
7523
+ const message = err instanceof Error ? err.message : String(err);
7524
+ rootLogger.warn({
7525
+ taskId: claimedTask.task.id,
7526
+ attemptN: claimedTask.attemptN,
7527
+ err: message
7528
+ }, "agent-daemon.execution_plan_failed");
7529
+ return {
7530
+ taskId: claimedTask.task.id,
7531
+ attemptN: claimedTask.attemptN,
7532
+ status: "failed",
7533
+ output: null,
7534
+ outputCid: null,
7535
+ usage: {
7536
+ inputTokens: 0,
7537
+ outputTokens: 0
7538
+ },
7539
+ durationMs: 0,
7540
+ error: {
7541
+ code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
7542
+ message,
7543
+ retryable: false
7544
+ }
7545
+ };
7546
+ }
7547
+ if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7548
+ ...slotIdentity,
7549
+ runtimeProfileId: profile.id,
7550
+ provider: profile.provider,
7551
+ model: profile.model,
7552
+ teamId: claimedTask.task.teamId,
7553
+ slotKey: executionPlan.slotKey,
7554
+ taskType: claimedTask.task.taskType,
7555
+ sessionDir: executionPlan.sessionPersistence.sessionDir,
7556
+ sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
7557
+ workspaceId: executionPlan.workspaceId,
7558
+ worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, sandbox.rootDir, executionPlan),
7559
+ worktreeBranch: executionPlan.worktreeBranch,
7560
+ workspaceKind: executionPlan.workspaceKind,
7561
+ lastTaskId: claimedTask.task.id,
7562
+ lastAttemptN: claimedTask.attemptN
7563
+ });
7564
+ activeAttemptN = claimedTask.attemptN;
7565
+ try {
7566
+ return await runWithDaemonRuntimeContext({
7567
+ profileId: profile.id,
7568
+ profileName: profile.name,
7569
+ provider: profile.provider,
7570
+ model: profile.model,
7571
+ thinkingLevel: profile.thinkingLevel,
7572
+ temperature: profile.temperature,
7573
+ topP: profile.topP,
7574
+ topK: profile.topK,
7575
+ maxOutputTokens: profile.maxOutputTokens
7576
+ }, () => rawExecuteTask(claimedTask, reporter));
7577
+ } finally {
7578
+ activeAttemptN = null;
7579
+ executionPlans.delete(claimedTask);
7580
+ if (executionPlan.slotKey) await slotRegistry.finishSlot(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN, slotIdentity, executionPlan.slotKey, profile.provider, profile.model, executionPlan.sessionPersistence ? resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir) : null);
7581
+ }
7415
7582
  };
7416
- }
7417
- readRun(id) {
7418
- return readJson(join(this.runDir(id), "run.json"));
7419
- }
7420
- writeRun(record) {
7421
- writeJsonAtomic(join(this.runDir(record.id), "run.json"), record);
7422
- }
7423
- listRuns(limit = Number.POSITIVE_INFINITY) {
7424
- let ids;
7425
- try {
7426
- ids = readdirSync(this.runsDir);
7427
- } catch {
7428
- return [];
7429
- }
7430
- const sortedIds = ids.filter((id) => NAME_RE.test(id)).sort().reverse();
7431
- const selectedIds = Number.isFinite(limit) ? sortedIds.slice(0, Math.max(0, limit)) : sortedIds;
7432
- const records = [];
7433
- for (const id of selectedIds) {
7434
- const record = this.readRun(id);
7435
- if (record) records.push(record);
7436
- }
7437
- return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
7438
- }
7439
- /** Remove only completed run directories outside the configured budget. */
7440
- pruneCompletedRuns(options) {
7441
- const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
7442
- let retainedBytes = 0;
7443
- let retainedCount = 0;
7444
- const removed = [];
7445
- for (const record of this.listRuns()) {
7446
- if (record.status === "running") continue;
7447
- const dir = this.runDir(record.id);
7448
- const bytes = directoryBytes(dir);
7449
- const endedAt = Date.parse(record.endedAt ?? record.startedAt);
7450
- const expired = !Number.isFinite(endedAt) || now - endedAt > options.maxAgeMs;
7451
- const overCount = retainedCount >= options.maxCount;
7452
- const overBytes = retainedBytes + bytes > options.maxBytes;
7453
- if (expired || overCount || overBytes) {
7454
- rmSync(dir, {
7455
- recursive: true,
7456
- force: true
7583
+ const writeCorrelationAnchors = makePrBodyAnchorWriter({
7584
+ gh: createGhCliClient(),
7585
+ logger: rootLogger
7586
+ });
7587
+ runtime = new AgentRuntime({
7588
+ logger: rootLogger,
7589
+ source: new ApiTaskSource({
7590
+ agent: ctx.agent,
7591
+ taskId,
7592
+ teamId: profile.teamId,
7593
+ leaseTtlSec: opts.leaseTtlSec,
7594
+ profileId: profile.id,
7595
+ executorFingerprint: preparedRuntime.attestor.fingerprint
7596
+ }),
7597
+ makeReporter: () => new ApiTaskReporter({
7598
+ tasks: ctx.agent.tasks,
7599
+ teamId: profile.teamId,
7600
+ leaseTtlSec: opts.leaseTtlSec,
7601
+ heartbeatIntervalMs: opts.heartbeatIntervalMs,
7602
+ maxBatchSize: opts.maxBatchSize,
7603
+ flushIntervalMs: opts.flushIntervalMs
7604
+ }),
7605
+ onTaskFinished: async (output, claimedTask) => {
7606
+ const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
7607
+ let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
7608
+ if (resolved?.session?.sessionDir) try {
7609
+ const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
7610
+ await runtimeSessionStore.uploadAttemptFinal({
7611
+ attemptN: claimedTask.attemptN,
7612
+ parentSessionId: parentSession?.id ?? null,
7613
+ sessionDir: resolved.session.sessionDir,
7614
+ sessionKind: resolveRuntimeSessionKind(claimedTask),
7615
+ sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
7616
+ sourceSlotId: resolved.slot.id,
7617
+ taskId: claimedTask.task.id,
7618
+ teamId: claimedTask.task.teamId
7619
+ });
7620
+ } catch (err) {
7621
+ rootLogger.error({
7622
+ err,
7623
+ taskId: claimedTask.task.id,
7624
+ attemptN: claimedTask.attemptN
7625
+ }, "agent-daemon.runtime_session_upload_failed");
7626
+ terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
7627
+ }
7628
+ return finalizeTask(ctx.agent, terminalOutput, {
7629
+ task: claimedTask.task,
7630
+ slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
7631
+ retryTriage: createRuntimeProfileRetryTriage({
7632
+ runtimeProfile: profile,
7633
+ piAgentDir: piAgentDir.path,
7634
+ cwd: ctx.agentRootDir
7635
+ }),
7636
+ executorAttestor: preparedRuntime.attestor,
7637
+ writeCorrelationAnchors,
7638
+ log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
7457
7639
  });
7458
- removed.push(record.id);
7459
- continue;
7460
- }
7461
- retainedCount += 1;
7462
- retainedBytes += bytes;
7640
+ },
7641
+ executeTask
7642
+ });
7643
+ const [output] = await runtime.start();
7644
+ if (!output) {
7645
+ rootLogger.error({}, "agent-daemon.no_output");
7646
+ return 1;
7463
7647
  }
7464
- return removed;
7648
+ console.log("\n[done] TaskOutput:");
7649
+ console.log(JSON.stringify(output, null, 2));
7650
+ return output.status === "completed" ? 0 : 1;
7651
+ } finally {
7652
+ signalHandlers.dispose();
7653
+ await slotRegistry.close();
7654
+ await otelShutdown();
7655
+ await shutdownLogger();
7465
7656
  }
7466
- };
7467
- function directoryBytes(path) {
7468
- let info;
7657
+ }
7658
+ function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
7659
+ if (!executionPlan.workspaceId) return null;
7660
+ return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
7661
+ }
7662
+ function resolveMainWorktree(mountPath) {
7469
7663
  try {
7470
- info = lstatSync(path);
7664
+ return findMainWorktree(mountPath);
7471
7665
  } catch {
7472
- return 0;
7666
+ return null;
7473
7667
  }
7474
- if (info.isSymbolicLink()) return 0;
7475
- if (!info.isDirectory()) return info.size;
7476
- let total = 0;
7477
- for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
7478
- return total;
7479
- }
7480
- function isRecord$1(value) {
7481
- return typeof value === "object" && value !== null && !Array.isArray(value);
7482
- }
7483
- function storeChildPath(root, kind, value, suffix = "") {
7484
- const name = assertStoreName(kind, value);
7485
- const normalizedRoot = resolve(root);
7486
- const candidate = resolve(normalizedRoot, `${name}${suffix}`);
7487
- if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
7488
- return candidate;
7489
- }
7490
- function isStrictDescendant(root, candidate) {
7491
- const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
7492
- return candidate !== root && candidate.startsWith(rootPrefix);
7493
7668
  }
7494
- function validateActivation(alias, value) {
7495
- const invalid = () => {
7496
- throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
7497
- };
7498
- if (!isRecord$1(value)) invalid();
7499
- const activation = value;
7500
- if (activation.alias !== alias) invalid();
7501
- if (![
7502
- "subjectId",
7503
- "publicKey",
7504
- "fingerprint",
7505
- "createdAt"
7506
- ].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
7507
- if (activation.source === "managed") {
7508
- if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
7509
- return;
7510
- }
7511
- if (activation.source !== "external" || typeof activation.configPath !== "string" || activation.configPath.length === 0 || typeof activation.configApiUrl !== "string" || activation.configApiUrl.length === 0 || activation.apiUrl !== void 0 && typeof activation.apiUrl !== "string") invalid();
7669
+ //#endregion
7670
+ //#region src/cli/poll.ts
7671
+ function runPoll(argv, runtimeAdapter) {
7672
+ return runPolling({
7673
+ argv,
7674
+ serviceName: "moltnet.agent-daemon.poll",
7675
+ stopWhenEmpty: false,
7676
+ modeLabel: "poll",
7677
+ helpText: POLL_HELP,
7678
+ runtimeAdapter
7679
+ });
7512
7680
  }
7513
7681
  //#endregion
7514
7682
  //#region src/lib/provider-lock.ts
@@ -9199,7 +9367,6 @@ function snapshot(login) {
9199
9367
  * the current config and verifies it with authenticated `whoami` before use.
9200
9368
  */
9201
9369
  var MAX_CONFIG_BYTES = 64 * 1024;
9202
- var IDENTITY_OPERATION_TIMEOUT_MS = 15e3;
9203
9370
  var pendingAliases = /* @__PURE__ */ new WeakMap();
9204
9371
  var AgentServerIdentityError = class extends Error {
9205
9372
  name = "AgentServerIdentityError";
@@ -9222,7 +9389,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9222
9389
  const alias = assertStoreName("agent name", input.name);
9223
9390
  if (!input.enrollmentToken.trim()) throw new AgentServerIdentityError("enrollment_required", "an enrollment token from the target team is required — a self-registered agent would be stranded in its own personal team");
9224
9391
  const releaseAlias = reserveAlias(store, alias);
9225
- let registered = false;
9392
+ let registered;
9226
9393
  try {
9227
9394
  let apiUrl;
9228
9395
  try {
@@ -9231,61 +9398,40 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9231
9398
  throw new AgentServerIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
9232
9399
  }
9233
9400
  store.reserveRegistration(alias, apiUrl);
9234
- const result = await register({
9235
- credentialType: "agent_key",
9236
- apiUrl,
9237
- enrollmentToken: input.enrollmentToken,
9238
- signal: boundedIdentitySignal(input.signal)
9239
- });
9240
- if (result.credentials.type !== "agent_key") throw new AgentServerIdentityError("unsupported_credential", `registration returned credential type "${result.credentials.type}"; agent server manages agent-key credentials only`);
9241
- const now = (/* @__PURE__ */ new Date()).toISOString();
9242
- const { subjectId, fingerprint, publicKey, privateKey } = result.identity;
9243
- registered = true;
9244
- const agentKeyReference = {
9245
- provider: FILE_SECRET_PROVIDER,
9246
- key: agentKeyKey(subjectId)
9247
- };
9248
- const seedReference = {
9249
- provider: FILE_SECRET_PROVIDER,
9250
- key: identitySeedKey(fingerprint)
9251
- };
9252
- const config = {
9253
- subject_id: subjectId,
9254
- subject_type: "agent",
9255
- registered_at: now,
9256
- agent_key_ref: agentKeyReference,
9257
- keys: {
9258
- public_key: publicKey,
9259
- fingerprint,
9260
- private_key_ref: seedReference
9261
- },
9262
- endpoints: {
9263
- api: result.apiUrl,
9264
- mcp: deriveMcpUrl(result.apiUrl)
9401
+ try {
9402
+ registered = await register({
9403
+ name: alias,
9404
+ apiUrl,
9405
+ credentialType: "agent_key",
9406
+ enrollmentToken: input.enrollmentToken,
9407
+ secretProvider: secrets,
9408
+ configDir: store.identityDir(alias),
9409
+ publishAlias: false,
9410
+ signal: input.signal,
9411
+ connectAgent
9412
+ });
9413
+ } catch (cause) {
9414
+ if (cause instanceof RegisterIdentityError) {
9415
+ if (cause.nothingRegistered) {
9416
+ store.clearPendingRegistration(alias);
9417
+ throw new AgentServerIdentityError(cause.code === "alias_exists" ? "agent_exists" : "registration_failed", cause.message, { cause });
9418
+ }
9419
+ if (cause.code === "unsupported_credential") throw new AgentServerIdentityError("unsupported_credential", `${cause.message}; agent server manages agent-key credentials only`, { cause });
9420
+ if (cause.code === "identity_mismatch") throw new AgentServerIdentityError("verification_failed", cause.message, { cause });
9265
9421
  }
9266
- };
9267
- store.writeAgentConfig(alias, config);
9268
- await secrets.write(agentKeyReference.key, result.credentials.secret);
9269
- await secrets.write(seedReference.key, privateKey);
9270
- const whoami = await callWhoami(connectAgent, {
9271
- agentKey: result.credentials.secret,
9272
- apiUrl: result.apiUrl
9273
- }, store.agentPath(alias), input.signal);
9274
- assertIdentityMatches(whoami, {
9275
- publicKey,
9276
- fingerprint
9277
- }, "authenticated whoami", `new managed agent "${alias}"`);
9278
- assertSubjectMatches(whoami, config, "authenticated whoami", `managed config ${store.agentPath(alias)}`);
9422
+ throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, cause instanceof RegisterIdentityError ? cause : {}), { cause });
9423
+ }
9424
+ const { config, whoami, identity } = registered;
9279
9425
  const boundTeamId = boundTeamIdFromWhoami(whoami);
9280
9426
  const activation = {
9281
9427
  alias,
9282
9428
  source: "managed",
9283
9429
  subjectId: whoami.subjectId,
9284
- publicKey,
9285
- fingerprint,
9430
+ publicKey: identity.publicKey,
9431
+ fingerprint: identity.fingerprint,
9286
9432
  ...boundTeamId ? { boundTeamId } : {},
9287
- createdAt: now,
9288
- apiUrl: result.apiUrl
9433
+ createdAt: config.registered_at,
9434
+ apiUrl: config.endpoints.api
9289
9435
  };
9290
9436
  store.writeActivation(activation);
9291
9437
  return {
@@ -9294,18 +9440,27 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9294
9440
  ...boundTeamId ? { boundTeamId } : {}
9295
9441
  };
9296
9442
  } catch (cause) {
9297
- if (!registered && cause instanceof MoltNetError && cause.statusCode !== void 0 && cause.statusCode >= 400 && cause.statusCode < 500) {
9298
- store.clearPendingRegistration(alias);
9299
- throw new AgentServerIdentityError("registration_failed", registrationRejectionMessage(alias, cause), { cause });
9300
- }
9301
- if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", registered ? `the remote agent was registered but local activation is incomplete; reconcile or clear its pending Agent Server record before retrying` : `registration for "${alias}" may be incomplete; inspect the remote API before changing its pending Agent Server record`, { cause });
9443
+ if (cause instanceof AgentServerIdentityError) throw cause;
9444
+ if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, registered ? {
9445
+ ...registered.identity,
9446
+ configPath: registered.configPath
9447
+ } : {}), { cause });
9302
9448
  throw cause;
9303
9449
  } finally {
9304
9450
  releaseAlias();
9305
9451
  }
9306
9452
  }
9307
- function registrationRejectionMessage(alias, cause) {
9308
- return `registration for "${alias}" was rejected${cause.statusCode === void 0 ? "" : ` (${cause.statusCode})`}: ${cause.detail?.trim() || cause.message}`;
9453
+ /**
9454
+ * Guidance for a registration that may have committed. The HTTP response
9455
+ * carries only this message, so it names the reconcile call and, when the
9456
+ * server outcome is unknown, the fingerprint to look up first. Resume needs
9457
+ * the written config, so without one only abandon is offered.
9458
+ */
9459
+ function incompleteRegistrationMessage(alias, known) {
9460
+ const endpoint = `POST /v1/agents/${alias}/reconcile`;
9461
+ const reconcile = known.configPath ? `Finish it with ${endpoint} and {"action":"resume"}, or discard the local record with {"action":"abandon"}.` : `No local config was written, so it cannot be resumed; discard the local record with ${endpoint} and {"action":"abandon"}.`;
9462
+ if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}`;
9463
+ return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}`;
9309
9464
  }
9310
9465
  /** Resume a fully persisted registration or explicitly abandon local recovery. */
9311
9466
  async function reconcileManagedRegistration(store, secrets, aliasInput, action, connectAgent = connect, signal) {
@@ -9528,10 +9683,6 @@ async function callWhoami(connectAgent, options, source, signal) {
9528
9683
  throw verificationError(`could not authenticate ${source} against the API`, cause);
9529
9684
  }
9530
9685
  }
9531
- function boundedIdentitySignal(signal) {
9532
- const timeout = AbortSignal.timeout(IDENTITY_OPERATION_TIMEOUT_MS);
9533
- return signal ? AbortSignal.any([signal, timeout]) : timeout;
9534
- }
9535
9686
  function identityFromConfig(config) {
9536
9687
  const publicKey = config?.keys?.public_key?.trim();
9537
9688
  const fingerprint = config?.keys?.fingerprint?.trim();
@@ -9776,20 +9927,8 @@ var RunManager = class {
9776
9927
  recursive: true,
9777
9928
  mode: 448
9778
9929
  });
9779
- writePiConfig({
9780
- piDir,
9781
- providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
9782
- api: provider.api,
9783
- ...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
9784
- baseUrl: provider.baseUrl,
9785
- models: provider.models
9786
- }]))
9787
- });
9788
- try {
9789
- (this.options.symlinkImpl ?? symlinkSync)(this.store.piAuthJsonPath, join(piDir, "auth.json"));
9790
- } catch (cause) {
9791
- throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
9792
- }
9930
+ writeStorePiConfig(piDir, providers);
9931
+ linkPiAuth(this.store.piAuthJsonPath, piDir, this.options.symlinkImpl ?? symlinkSync);
9793
9932
  const entry = this.entrypoint();
9794
9933
  logStream = createWriteStream(logPath, {
9795
9934
  fd: openSync(logPath, "a", 384),
@@ -11163,7 +11302,7 @@ function normalizeAgentServerError(error) {
11163
11302
  message: error.message
11164
11303
  };
11165
11304
  if (error instanceof AgentServerIdentityError) return {
11166
- statusCode: error.code === "agent_exists" ? 409 : 400,
11305
+ statusCode: error.code === "agent_exists" || error.code === "registration_incomplete" ? 409 : 400,
11167
11306
  code: error.code,
11168
11307
  message: error.message
11169
11308
  };
@@ -11864,7 +12003,7 @@ async function writeCache(cache) {
11864
12003
  }
11865
12004
  //#endregion
11866
12005
  //#region src/version.ts
11867
- var DAEMON_VERSION = "0.56.2";
12006
+ var DAEMON_VERSION = "0.57.0";
11868
12007
  //#endregion
11869
12008
  //#region src/cli.ts
11870
12009
  async function runAgentDaemonCli(options) {