@themoltnet/agent-daemon 0.56.2 → 0.57.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -39
  2. package/dist/cli.js +2127 -2049
  3. package/package.json +10 -9
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
3
2
  import { assertRuntimeAdapterSupportsProfile } from "./runtime.js";
3
+ import { a as cryptoService, defaultPiDaemonAdapter, i as compileExecutionPlan, n as createExecutionPlanSnapshot, r as parseCredentialRequirements, t as runtimeExecutionOffer } from "./pi.js";
4
4
  import { createRequire } from "node:module";
5
5
  import { pino, transport } from "pino";
6
6
  import { Type } from "typebox";
@@ -10,11 +10,11 @@ import "multiformats/hashes/sha2";
10
10
  import "typebox/value";
11
11
  import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
12
12
  import { parseArgs, promisify } from "node:util";
13
- import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, resolveProfileWarmSessionTtlSec, resolveRuntimeProfile, resolveRuntimeProfiles, validateRuntimeProfilePrerequisites } from "@themoltnet/agent-runtime";
13
+ import { AgentRuntime, ApiTaskReporter, ApiTaskSource, PollingApiTaskSource, createLocalSeedSigner, resolveAgentIdentity, 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",
@@ -1888,18 +1937,6 @@ var RuntimeProfileRef = Type.Object({ profileId: Type.String({ format: "uuid" })
1888
1937
  $id: "RuntimeProfileRef",
1889
1938
  additionalProperties: false
1890
1939
  });
1891
- var RuntimeProfileLeaseTtlSec = Type.Integer({
1892
- minimum: 1,
1893
- maximum: 86400
1894
- });
1895
- var RuntimeProfileHeartbeatIntervalMs = Type.Integer({
1896
- minimum: 0,
1897
- maximum: 36e5
1898
- });
1899
- var RuntimeProfileMaxBatchSize = Type.Integer({
1900
- minimum: 1,
1901
- maximum: 1e3
1902
- });
1903
1940
  var RuntimeProfileMaxTurns = Type.Integer({
1904
1941
  minimum: 0,
1905
1942
  maximum: 1e4
@@ -1928,21 +1965,8 @@ Type.Object({
1928
1965
  maxOutputTokens: RuntimeProfileNullableMaxOutputTokens,
1929
1966
  runtimeKind: RuntimeProfileRuntimeKind,
1930
1967
  sandbox: RuntimeProfileSandbox,
1931
- sessionStorageMode: Type.Literal("local"),
1932
- workspaceStorageMode: Type.Literal("local"),
1933
1968
  defaultWorkspaceMode: Type.Union([RuntimeProfileWorkspaceMode, Type.Null()]),
1934
1969
  allowedWorkspaceModes: RuntimeProfileAllowedWorkspaceModes,
1935
- sessionTtlSec: Type.Integer({
1936
- minimum: 1,
1937
- maximum: 86400
1938
- }),
1939
- workspaceTtlSec: Type.Integer({
1940
- minimum: 1,
1941
- maximum: 86400
1942
- }),
1943
- leaseTtlSec: RuntimeProfileLeaseTtlSec,
1944
- heartbeatIntervalMs: RuntimeProfileHeartbeatIntervalMs,
1945
- maxBatchSize: RuntimeProfileMaxBatchSize,
1946
1970
  maxTurns: RuntimeProfileMaxTurns,
1947
1971
  maxBashTimeouts: RuntimeProfileMaxBashTimeouts,
1948
1972
  toolEnforcement: RuntimeProfileToolEnforcement,
@@ -2075,6 +2099,7 @@ var ResolvedRuntimeSlot = Type.Object({
2075
2099
  workspace: Type.Union([RuntimeWorkspace, Type.Null()])
2076
2100
  }, { $id: "ResolvedRuntimeSlot" });
2077
2101
  Type.Object({ items: Type.Array(ResolvedRuntimeSlot) }, { $id: "RuntimeSlotListResponse" });
2102
+ var MAX_RUNTIME_WARM_RETENTION_SEC = 86400;
2078
2103
  Type.Object({
2079
2104
  agentName: Type.String({
2080
2105
  minLength: 1,
@@ -2101,7 +2126,11 @@ Type.Object({
2101
2126
  worktreeBranch: Type.Optional(Type.String({ minLength: 1 })),
2102
2127
  workspaceKind: Type.Optional(RuntimeWorkspaceKind),
2103
2128
  lastTaskId: Type.String({ format: "uuid" }),
2104
- lastAttemptN: Type.Integer({ minimum: 1 })
2129
+ lastAttemptN: Type.Integer({ minimum: 1 }),
2130
+ warmRetentionSec: Type.Integer({
2131
+ minimum: 0,
2132
+ maximum: MAX_RUNTIME_WARM_RETENTION_SEC
2133
+ })
2105
2134
  }, {
2106
2135
  $id: "BeginRuntimeSlotBody",
2107
2136
  additionalProperties: false
@@ -2123,7 +2152,11 @@ Type.Object({
2123
2152
  slotKey: Type.String({ minLength: 1 }),
2124
2153
  taskId: Type.String({ format: "uuid" }),
2125
2154
  attemptN: Type.Integer({ minimum: 1 }),
2126
- sessionPath: Type.Optional(Type.String({ minLength: 1 }))
2155
+ sessionPath: Type.Optional(Type.String({ minLength: 1 })),
2156
+ warmRetentionSec: Type.Integer({
2157
+ minimum: 0,
2158
+ maximum: MAX_RUNTIME_WARM_RETENTION_SEC
2159
+ })
2127
2160
  }, {
2128
2161
  $id: "FinishRuntimeSlotBody",
2129
2162
  additionalProperties: false
@@ -2719,7 +2752,7 @@ var RenderPackInput = Type.Object({
2719
2752
  var RenderPackOutput = Type.Object({
2720
2753
  renderedPackId: Type.Union([Type.String({ format: "uuid" }), Type.Null()]),
2721
2754
  renderedCid: Type.String({ minLength: 1 }),
2722
- renderMethod: Type.String({ minLength: 1 }),
2755
+ renderMethod: RenderMethodSchema,
2723
2756
  byteSize: Type.Number({ minimum: 0 }),
2724
2757
  entriesRendered: Type.Number({ minimum: 0 }),
2725
2758
  summary: Type.String({ minLength: 1 }),
@@ -3384,8 +3417,7 @@ Type.Object({
3384
3417
  outputCid: Type.Union([Cid, Type.Null()]),
3385
3418
  usage: TaskUsage,
3386
3419
  durationMs: Type.Number({ minimum: 0 }),
3387
- error: Type.Optional(TaskError),
3388
- contentSignature: Type.Optional(Type.String())
3420
+ error: Type.Optional(TaskError)
3389
3421
  }, {
3390
3422
  $id: "TaskOutput",
3391
3423
  additionalProperties: false
@@ -3413,7 +3445,7 @@ var COMMON_REQUIRED_FLAGS = `\
3413
3445
  --profile <uuid|name> Remote runtime profile. Repeat for poll/drain
3414
3446
  to declare priority order. Provider, model,
3415
3447
  sandbox policy, prerequisites, and runtime
3416
- defaults come from the selected profile.`;
3448
+ execution policy come from the selected profile.`;
3417
3449
  var COMMON_OPTIONAL_FLAGS = `\
3418
3450
  --sandbox <path> Deprecated. Remote runtime profiles define
3419
3451
  sandbox policy.
@@ -3425,24 +3457,10 @@ var COMMON_OPTIONAL_FLAGS = `\
3425
3457
  host git config. Configless agent-key runs must
3426
3458
  provide this flag or MOLTNET_GIT_AUTHOR.
3427
3459
  Env: MOLTNET_GIT_AUTHOR.
3428
- --lease-ttl-sec <n> Sliding liveness window. Silence longer than
3429
- this ends the attempt with lease_expired.
3430
- Default: 300.
3431
3460
  --heartbeat-interval-ms <n> Reporter heartbeat cadence. Default: 60000.
3432
- --max-batch-size <n> Reporter message batch size. Default: 50.
3433
- --flush-interval-ms <n> Reporter flush window. Default: 200.
3434
- --max-turns <n> Cap on tool-use turns per attempt; over this,
3435
- the pi session aborts and the attempt fails
3436
- with code 'max_turns_exceeded'. 0 = disabled.
3437
- Default: 0. Recommended ~30 for fulfill_brief.
3438
- --max-bash-timeouts <n> Cap on bash-tool timeouts per attempt; over
3439
- this, the pi session aborts and the attempt
3440
- fails with code 'max_bash_timeouts_exceeded'.
3441
- 0 = disabled. Default: 3.
3442
- --warm-session-ttl-sec <n> Resumability window for runtime slots
3461
+ --warm-retention-sec <n> Resumability window for runtime slots
3443
3462
  (Pi sessions + reusable worktrees) after use.
3444
- Default: 1800, or min(profile session/workspace
3445
- TTL) when --profile is set.
3463
+ Default: 1800.
3446
3464
  --debug Verbose logging: also log successful list/claim
3447
3465
  outcomes (candidate counts, claim attempts).`;
3448
3466
  var REGISTERED_TASK_TYPES = Object.keys(BUILT_IN_TASK_TYPES).sort();
@@ -3633,6 +3651,8 @@ Options:
3633
3651
  (or MOLTNET_AGENT_SERVER_ROOT).
3634
3652
  --api-url <url> Default MoltNet API for new managed agents.
3635
3653
  Default: https://api.themolt.net.
3654
+ --heartbeat-interval-ms <n> Child reporter heartbeat cadence. Default: 60000.
3655
+ --warm-retention-sec <n> Child session/workspace retention. Default: 1800.
3636
3656
 
3637
3657
  On macOS, the first interactive run asks to trust a per-user local CA in the
3638
3658
  login keychain and serves HTTPS. Run \`agent-daemon server trust --remove\` to
@@ -3893,6 +3913,7 @@ function loadConfig() {
3893
3913
  profilePrerequisiteEnv: process.env,
3894
3914
  profilePrerequisitePath: process.env.PATH ?? "",
3895
3915
  piCodingAgentDir: process.env["PI_CODING_AGENT_DIR"] ?? "",
3916
+ agentServerRoot: process.env["MOLTNET_AGENT_SERVER_ROOT"] ?? "",
3896
3917
  credentialSource: detectCredentialSource(process.env),
3897
3918
  apiUrl: process.env["MOLTNET_API_URL"] ?? "",
3898
3919
  signingPrivateKey: process.env["MOLTNET_PRIVATE_KEY"] ?? "",
@@ -3936,8 +3957,9 @@ function readBoolean(name, value) {
3936
3957
  if (value === "false") return false;
3937
3958
  throw new Error(`${name} must be either true or false`);
3938
3959
  }
3939
- function activatePiCodingAgentDir(path) {
3960
+ function activatePiCodingAgentDir(path, env = {}) {
3940
3961
  process.env["PI_CODING_AGENT_DIR"] = path;
3962
+ Object.assign(process.env, env);
3941
3963
  }
3942
3964
  function loadAgentServerEnvConfig() {
3943
3965
  return {
@@ -4139,10 +4161,10 @@ function slugifySessionComponent(input) {
4139
4161
  }
4140
4162
  //#endregion
4141
4163
  //#region src/lib/task-execution-plan.ts
4142
- function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmSessionTtlSec, runtimeProfileWorkspacePolicy = {}, attemptN) {
4164
+ function buildDaemonTaskExecutionPlan(task, stateDirs, identity, warmRetentionSec, runtimeProfileWorkspacePolicy = {}, attemptN) {
4143
4165
  const descriptor = deriveTaskSessionDescriptor(task);
4144
4166
  const workspaceMode = resolveTaskWorkspaceMode(task, descriptor.policy, runtimeProfileWorkspacePolicy);
4145
- const slotKey = warmSessionTtlSec > 0 && descriptor.sessionKey ? buildRuntimeSlotKey(descriptor.sessionKey, identity.runtimeInstanceId) : null;
4167
+ const slotKey = warmRetentionSec > 0 && descriptor.sessionKey ? buildRuntimeSlotKey(descriptor.sessionKey, identity.runtimeInstanceId) : null;
4146
4168
  const workspaceScope = slotKey !== null ? descriptor.policy.workspaceScope : "attempt";
4147
4169
  const slotId = slotKey ? buildDaemonSlotId(identity, slotKey) : null;
4148
4170
  const sessionDir = slotId ? `${stateDirs.piSessionsDir}/${boundedKeyDirComponent(slotId)}` : null;
@@ -4281,7 +4303,7 @@ function createExecutionPlanCache(args) {
4281
4303
  const key = buildClaimedTaskKey(claimedTask);
4282
4304
  const existing = cache.get(key);
4283
4305
  if (existing) return existing;
4284
- const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmSessionTtlSec, args.workspacePolicy, claimedTask.attemptN), args.stateDirs, args.slotRegistry, runtimeSessionStore, sourceAttemptResolver);
4306
+ const plan = await maybeAttachWarmSlotContext(claimedTask, buildDaemonTaskExecutionPlan(claimedTask.task, args.stateDirs, args.slotIdentity, args.warmRetentionSec, args.workspacePolicy, claimedTask.attemptN), args.stateDirs, args.slotRegistry, runtimeSessionStore, sourceAttemptResolver);
4285
4307
  assertPlanAllowedByWorkspacePolicy(plan, args.workspacePolicy, args.slotIdentity.runtimeProfileId);
4286
4308
  cache.set(key, plan);
4287
4309
  return plan;
@@ -4803,7 +4825,6 @@ async function finalizeTask(agent, output, ctx = {}) {
4803
4825
  output: output.output,
4804
4826
  outputCid: output.outputCid,
4805
4827
  usage: output.usage,
4806
- ...output.contentSignature ? { contentSignature: output.contentSignature } : {},
4807
4828
  ...daemonState ? { daemonState } : {},
4808
4829
  ...attestation
4809
4830
  });
@@ -4973,7 +4994,8 @@ function ownBinding(bindings, name) {
4973
4994
  }
4974
4995
  //#endregion
4975
4996
  //#region ../../libs/execution-integrations/src/runtime-profile.ts
4976
- var CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION = "effective-policy:v1";
4997
+ var CURRENT_EFFECTIVE_POLICY_SNAPSHOT_VERSION = "effective-policy:v2";
4998
+ var DEFAULT_EXECUTION_LEASE_TTL_SEC = 300;
4977
4999
  /**
4978
5000
  * Maps resolved product authority into portable intent. Policy composition is
4979
5001
  * deliberately upstream: this boundary accepts no policy IDs and performs no
@@ -4997,7 +5019,7 @@ function executionIntentFromRuntimeProfile(input) {
4997
5019
  credentialRequirements: structuredClone(input.credentialRequirements),
4998
5020
  requiredCapabilities: [...input.requiredCapabilities ?? []],
4999
5021
  lease: {
5000
- ttlSec: input.profile.leaseTtlSec,
5022
+ ttlSec: DEFAULT_EXECUTION_LEASE_TTL_SEC,
5001
5023
  requiredControls: [...input.requiredLeaseControls ?? []]
5002
5024
  },
5003
5025
  network: {
@@ -5247,14 +5269,9 @@ async function logDaemonStartupFailure(input) {
5247
5269
  }
5248
5270
  //#endregion
5249
5271
  //#region src/lib/options.ts
5250
- var DEFAULTS = {
5251
- leaseTtlSec: 300,
5272
+ var DEFAULT_LOCAL_OPERATIONAL_SETTINGS = {
5252
5273
  heartbeatIntervalMs: 6e4,
5253
- maxBatchSize: 50,
5254
- flushIntervalMs: 200,
5255
- maxTurns: 0,
5256
- maxBashTimeouts: 3,
5257
- warmSessionTtlSec: 1800
5274
+ warmRetentionSec: 1800
5258
5275
  };
5259
5276
  var MissingRequiredOptionError = class extends Error {
5260
5277
  constructor(flag) {
@@ -5263,42 +5280,33 @@ var MissingRequiredOptionError = class extends Error {
5263
5280
  this.name = "MissingRequiredOptionError";
5264
5281
  }
5265
5282
  };
5266
- function parseCommonOptions(args, options = {}) {
5267
- const runtimeDefaults = {
5268
- leaseTtlSec: options.runtimeDefaults?.leaseTtlSec ?? DEFAULTS.leaseTtlSec,
5269
- heartbeatIntervalMs: options.runtimeDefaults?.heartbeatIntervalMs ?? DEFAULTS.heartbeatIntervalMs,
5270
- maxBatchSize: options.runtimeDefaults?.maxBatchSize ?? DEFAULTS.maxBatchSize,
5271
- maxTurns: options.runtimeDefaults?.maxTurns ?? DEFAULTS.maxTurns,
5272
- maxBashTimeouts: options.runtimeDefaults?.maxBashTimeouts ?? DEFAULTS.maxBashTimeouts,
5273
- warmSessionTtlSec: options.runtimeDefaults?.warmSessionTtlSec ?? DEFAULTS.warmSessionTtlSec
5274
- };
5283
+ function parseIdentityProcessOptions(args) {
5275
5284
  if (!args.agent) throw new MissingRequiredOptionError("agent");
5276
5285
  if (!/^[a-zA-Z0-9_-]+$/.test(args.agent)) throw new Error(`Invalid --agent "${args.agent}": must match /^[a-zA-Z0-9_-]+$/`);
5277
5286
  return {
5278
5287
  agent: args.agent,
5279
- leaseTtlSec: parsePositiveInt(args["lease-ttl-sec"], "lease-ttl-sec", runtimeDefaults.leaseTtlSec),
5280
- heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", runtimeDefaults.heartbeatIntervalMs),
5281
- maxBatchSize: parsePositiveInt(args["max-batch-size"], "max-batch-size", runtimeDefaults.maxBatchSize),
5282
- flushIntervalMs: parseNonNegativeInt(args["flush-interval-ms"], "flush-interval-ms", DEFAULTS.flushIntervalMs),
5283
- maxTurns: parseNonNegativeInt(args["max-turns"], "max-turns", runtimeDefaults.maxTurns),
5284
- maxBashTimeouts: parseNonNegativeInt(args["max-bash-timeouts"], "max-bash-timeouts", runtimeDefaults.maxBashTimeouts),
5285
- warmSessionTtlSec: parseNonNegativeInt(args["warm-session-ttl-sec"], "warm-session-ttl-sec", runtimeDefaults.warmSessionTtlSec),
5286
5288
  debug: args.debug === true
5287
5289
  };
5288
5290
  }
5289
- function parsePositiveInt(raw, name, defaultValue) {
5290
- if (raw === void 0) return defaultValue;
5291
- const v = Number(raw);
5292
- if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
5293
- return v;
5291
+ function parseLocalOperationalSettings(args) {
5292
+ return {
5293
+ heartbeatIntervalMs: parseNonNegativeInt(args["heartbeat-interval-ms"], "heartbeat-interval-ms", DEFAULT_LOCAL_OPERATIONAL_SETTINGS.heartbeatIntervalMs),
5294
+ warmRetentionSec: parseNonNegativeInt(args["warm-retention-sec"], "warm-retention-sec", DEFAULT_LOCAL_OPERATIONAL_SETTINGS.warmRetentionSec, MAX_RUNTIME_WARM_RETENTION_SEC)
5295
+ };
5296
+ }
5297
+ function parseRuntimeCommandOptions(args) {
5298
+ return {
5299
+ identity: parseIdentityProcessOptions(args),
5300
+ operations: parseLocalOperationalSettings(args)
5301
+ };
5294
5302
  }
5295
- function parseNonNegativeInt(raw, name, defaultValue) {
5303
+ function parseNonNegativeInt(raw, name, defaultValue, maximum = Number.MAX_SAFE_INTEGER) {
5296
5304
  if (raw === void 0) return defaultValue;
5297
- const v = Number(raw);
5298
- if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
5299
- return v;
5305
+ const value = Number(raw);
5306
+ if (!Number.isInteger(value) || value < 0 || value > maximum) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer no greater than ${maximum}`);
5307
+ return value;
5300
5308
  }
5301
- function commonOptionDefs() {
5309
+ function identityOptionDefs() {
5302
5310
  return {
5303
5311
  agent: {
5304
5312
  type: "string",
@@ -5306,18 +5314,18 @@ function commonOptionDefs() {
5306
5314
  },
5307
5315
  "agent-root": { type: "string" },
5308
5316
  "git-author": { type: "string" },
5309
- "lease-ttl-sec": { type: "string" },
5310
- "heartbeat-interval-ms": { type: "string" },
5311
- "max-batch-size": { type: "string" },
5312
- "flush-interval-ms": { type: "string" },
5313
- "max-turns": { type: "string" },
5314
- "max-bash-timeouts": { type: "string" },
5315
- "warm-session-ttl-sec": { type: "string" },
5316
5317
  debug: { type: "boolean" }
5317
5318
  };
5318
5319
  }
5320
+ function runtimeCommandOptionDefs() {
5321
+ return {
5322
+ ...identityOptionDefs(),
5323
+ "heartbeat-interval-ms": { type: "string" },
5324
+ "warm-retention-sec": { type: "string" }
5325
+ };
5326
+ }
5319
5327
  function validateTaskTypes(types) {
5320
- const unknown = types.filter((t) => !Object.prototype.hasOwnProperty.call(BUILT_IN_TASK_TYPES, t));
5328
+ const unknown = types.filter((taskType) => !Object.prototype.hasOwnProperty.call(BUILT_IN_TASK_TYPES, taskType));
5321
5329
  if (unknown.length > 0) throw new Error(`Unknown task type(s): ${unknown.join(", ")}. Known types: ${knownTaskTypesList()}.`);
5322
5330
  return [...types];
5323
5331
  }
@@ -5381,569 +5389,510 @@ function initWorkerOtel(options) {
5381
5389
  })]);
5382
5390
  });
5383
5391
  }
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
- };
5392
+ var NAME_RE = IDENTITY_ALIAS_PATTERN;
5393
+ var PROVIDER_ID_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
5394
+ function assertStoreName(kind, value) {
5395
+ try {
5396
+ return assertIdentityAlias(value);
5397
+ } catch {
5398
+ throw new AgentServerStoreError("invalid_name", `${kind} must match ${NAME_RE.source}`);
5393
5399
  }
5394
- const path = join(repoRoot, ".pi");
5395
- mkdirSync(path, { recursive: true });
5396
- return {
5397
- path,
5398
- source: "repo"
5399
- };
5400
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
- }
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
- };
5401
+ function assertProviderId(value) {
5402
+ if (!PROVIDER_ID_RE.test(value)) throw new AgentServerStoreError("invalid_name", `provider id must match ${PROVIDER_ID_RE.source}`);
5403
+ return value;
5421
5404
  }
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
- ]);
5405
+ var AgentServerStoreError = class extends Error {
5406
+ name = "AgentServerStoreError";
5407
+ constructor(code, message, options) {
5408
+ super(message, options);
5409
+ this.code = code;
5410
+ }
5411
+ };
5433
5412
  /**
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.
5413
+ * `MOLTNET_AGENT_SERVER_ROOT` override, else `~/.config/moltnet`.
5414
+ *
5415
+ * Deliberately does NOT consult `XDG_CONFIG_HOME`. The Go CLI's GetConfigDir
5416
+ * and @moltnet/agent-config's getConfigDir both resolve `~/.config/moltnet`,
5417
+ * so honouring XDG here gave one application two config roots: on a machine
5418
+ * with the variable set, the daemon wrote identities the CLI and SDK could not
5419
+ * read. `MOLTNET_AGENT_SERVER_ROOT` remains the explicit escape hatch for a
5420
+ * genuinely custom location.
5438
5421
  */
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;
5422
+ function resolveAgentServerRoot(input) {
5423
+ const override = input.root?.trim();
5424
+ if (override) return override;
5425
+ return getConfigDir();
5488
5426
  }
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
- };
5427
+ function providerEnvName(providerId) {
5428
+ return `MOLTNET_PROVIDER_${assertProviderId(providerId).replaceAll("-", "_").toUpperCase()}_API_KEY`;
5501
5429
  }
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;
5430
+ function assertProviderEnvName(providerId, value) {
5431
+ const expected = providerEnvName(providerId);
5432
+ if (value !== expected) throw new AgentServerStoreError("invalid_state", `provider envName must be ${expected}`);
5433
+ return value;
5524
5434
  }
5525
- async function slotStillQualifiesForReaping(deps, input, original, now, result) {
5526
- let page;
5435
+ function readJson(path) {
5436
+ let raw;
5527
5437
  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;
5438
+ raw = readFileSync(path, "utf8");
5439
+ } catch (cause) {
5440
+ if (cause.code === "ENOENT") return null;
5441
+ throw new AgentServerStoreError("io_error", `could not read state at ${path}`, { cause });
5532
5442
  }
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
5443
  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);
5444
+ return JSON.parse(raw);
5445
+ } catch (cause) {
5446
+ throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
5556
5447
  }
5557
5448
  }
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) {
5449
+ function writeJsonAtomic(path, value) {
5450
+ const temp = `${path}.${randomBytes(6).toString("hex")}.tmp`;
5451
+ try {
5452
+ writeFileSync(temp, `${JSON.stringify(value, null, 2)}\n`, { mode: 384 });
5453
+ renameSync(temp, path);
5454
+ } catch (cause) {
5565
5455
  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;
5456
+ rmSync(temp, { force: true });
5457
+ } catch {}
5458
+ throw cause;
5575
5459
  }
5576
- if (worktrees.kind === "unavailable") {
5577
- recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
5578
- return;
5460
+ }
5461
+ var AgentServerStore = class {
5462
+ root;
5463
+ identitiesDir;
5464
+ runsDir;
5465
+ secretsDir;
5466
+ /** Shared Pi credential dir; `auth.json` inside is pi-managed (lockfiled). */
5467
+ piDir;
5468
+ constructor(root) {
5469
+ this.root = root;
5470
+ this.identitiesDir = join(root, "identities");
5471
+ this.runsDir = join(root, "runs");
5472
+ this.secretsDir = join(root, "secrets");
5473
+ this.piDir = join(root, "pi");
5579
5474
  }
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
5475
+ get piAuthJsonPath() {
5476
+ return join(this.piDir, "auth.json");
5477
+ }
5478
+ /** Create the directory layout (0700) if missing. Idempotent. */
5479
+ ensure() {
5480
+ for (const dir of [
5481
+ this.root,
5482
+ this.identitiesDir,
5483
+ this.runsDir,
5484
+ this.secretsDir
5485
+ ]) mkdirSync(dir, {
5486
+ recursive: true,
5487
+ mode: 448
5595
5488
  });
5596
- result.removedWorkspaces++;
5597
- } catch (err) {
5598
- recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
5489
+ return this;
5599
5490
  }
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))))
5491
+ get statePath() {
5492
+ return join(this.root, "agent-server.json");
5493
+ }
5494
+ readAgentServerState() {
5495
+ const state = readJson(this.statePath);
5496
+ if (!state) return {
5497
+ version: 2,
5498
+ pendingRegistrations: {},
5499
+ activations: {}
5613
5500
  };
5614
- } catch (err) {
5501
+ 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`);
5502
+ 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");
5503
+ 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");
5504
+ for (const [alias, activation] of Object.entries(state.activations)) validateActivation(alias, activation);
5505
+ for (const [alias, registration] of Object.entries(state.pendingRegistrations)) {
5506
+ assertStoreName("agent name", alias);
5507
+ 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`);
5508
+ }
5615
5509
  return {
5616
- kind: "unavailable",
5617
- reason: errorMessage(err)
5510
+ version: 2,
5511
+ pendingRegistrations: state.pendingRegistrations,
5512
+ activations: state.activations
5618
5513
  };
5619
5514
  }
5620
- }
5621
- function isRealPathInsideRoot(path, root) {
5622
- try {
5623
- return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
5624
- } catch {
5625
- return false;
5515
+ writeAgentServerState(state) {
5516
+ writeJsonAtomic(this.statePath, state);
5626
5517
  }
5627
- }
5628
- function canonicalPath(path) {
5629
- try {
5630
- return realpathSync(path);
5631
- } catch {
5632
- return resolve(path);
5518
+ agentPath(name) {
5519
+ return join(storeChildPath(this.identitiesDir, "identity alias", name), "moltnet.json");
5633
5520
  }
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);
5521
+ /** Central identity directory, shared with the Go CLI layout. */
5522
+ identityDir(name) {
5523
+ return storeChildPath(this.identitiesDir, "identity alias", name);
5524
+ }
5525
+ get identitySelectorPath() {
5526
+ return join(this.root, "identity-selector.json");
5527
+ }
5528
+ readIdentitySelector() {
5529
+ const selector = readJson(this.identitySelectorPath);
5530
+ if (!selector) return null;
5531
+ 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");
5532
+ if (selector.default_identity) assertStoreName("identity alias", selector.default_identity);
5533
+ return selector;
5534
+ }
5535
+ writeIdentitySelector(alias) {
5536
+ writeJsonAtomic(this.identitySelectorPath, {
5537
+ version: 1,
5538
+ default_identity: assertStoreName("identity alias", alias)
5675
5539
  });
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);
5540
+ }
5541
+ /** Identity aliases available in the shared user-level store. */
5542
+ listIdentityAliases() {
5543
+ 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));
5544
+ }
5545
+ resolveIdentityAlias(explicit, active) {
5546
+ const alias = explicit?.trim() || active?.trim() || this.readIdentitySelector()?.default_identity;
5547
+ if (!alias) throw new AgentServerStoreError("not_found", "no active identity selected");
5548
+ return assertStoreName("identity alias", alias);
5549
+ }
5550
+ readAgentConfig(alias) {
5551
+ return readJson(this.agentPath(alias));
5552
+ }
5553
+ writeAgentConfig(alias, config) {
5554
+ mkdirSync(this.identityDir(alias), {
5555
+ recursive: true,
5556
+ mode: 448
5557
+ });
5558
+ writeJsonAtomic(this.agentPath(alias), config);
5559
+ if (!this.readIdentitySelector()?.default_identity) this.writeIdentitySelector(alias);
5560
+ }
5561
+ removeAgentConfig(alias) {
5562
+ rmSync(this.agentPath(alias), { force: true });
5563
+ this.clearIdentitySelectorIfDefault(alias);
5564
+ }
5565
+ /** Clears the persisted default when it names `alias`. */
5566
+ clearIdentitySelectorIfDefault(alias) {
5567
+ let selector = null;
5568
+ try {
5569
+ selector = this.readIdentitySelector();
5570
+ } catch {
5571
+ return;
5572
+ }
5573
+ if (!selector || selector.default_identity !== alias) return;
5574
+ writeJsonAtomic(this.identitySelectorPath, { version: 1 });
5575
+ }
5576
+ readActivation(alias) {
5577
+ return this.readAgentServerState().activations[assertStoreName("agent name", alias)] ?? null;
5578
+ }
5579
+ hasPendingRegistration(alias) {
5580
+ return Boolean(this.readAgentServerState().pendingRegistrations[assertStoreName("agent name", alias)]);
5581
+ }
5582
+ reserveRegistration(alias, apiUrl) {
5583
+ const name = assertStoreName("agent name", alias);
5584
+ const state = this.readAgentServerState();
5585
+ if (state.activations[name] || state.pendingRegistrations[name]) throw new AgentServerStoreError("already_exists", `agent "${name}" already exists in the agent server store`);
5586
+ state.pendingRegistrations[name] = {
5587
+ apiUrl,
5588
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
5589
+ };
5590
+ this.writeAgentServerState(state);
5591
+ }
5592
+ clearPendingRegistration(alias) {
5593
+ const name = assertStoreName("agent name", alias);
5594
+ const state = this.readAgentServerState();
5595
+ delete state.pendingRegistrations[name];
5596
+ this.writeAgentServerState(state);
5597
+ }
5598
+ writeActivation(activation) {
5599
+ const alias = assertStoreName("agent name", activation.alias);
5600
+ validateActivation(alias, activation);
5601
+ const state = this.readAgentServerState();
5602
+ state.activations[alias] = activation;
5603
+ if (activation.source === "managed") delete state.pendingRegistrations[alias];
5604
+ this.writeAgentServerState(state);
5605
+ }
5606
+ listActivations() {
5607
+ return Object.values(this.readAgentServerState().activations).sort((a, b) => a.alias.localeCompare(b.alias));
5608
+ }
5609
+ get providersPath() {
5610
+ return join(this.root, "providers.json");
5611
+ }
5612
+ readProviders() {
5613
+ const state = readJson(this.providersPath) ?? {};
5614
+ this.validateProviders(state);
5615
+ return state;
5616
+ }
5617
+ writeProviders(state) {
5618
+ this.validateProviders(state);
5619
+ writeJsonAtomic(this.providersPath, state);
5620
+ }
5621
+ validateProviders(state) {
5622
+ for (const [id, provider] of Object.entries(state)) {
5623
+ assertProviderId(id);
5624
+ assertProviderEnvName(id, provider.envName);
5625
+ }
5626
+ }
5627
+ runDir(id) {
5628
+ return storeChildPath(this.runsDir, "run id", id);
5629
+ }
5630
+ resolveRunLogPath(id) {
5631
+ let root;
5632
+ let runDir;
5633
+ try {
5634
+ root = realpathSync(this.runsDir);
5635
+ runDir = realpathSync(this.runDir(id));
5636
+ } catch (cause) {
5637
+ throw new AgentServerStoreError("io_error", "could not resolve run directory", { cause });
5638
+ }
5639
+ if (!isStrictDescendant(root, runDir)) throw new AgentServerStoreError("invalid_state", "run directory escapes its store");
5640
+ const logPath = join(runDir, "daemon.log");
5641
+ try {
5642
+ if (lstatSync(logPath).isSymbolicLink()) throw new AgentServerStoreError("invalid_state", "run log must not be a symbolic link");
5643
+ const resolvedLog = realpathSync(logPath);
5644
+ if (!isStrictDescendant(runDir, resolvedLog)) throw new AgentServerStoreError("invalid_state", "run log escapes its store");
5645
+ } catch (cause) {
5646
+ if (cause instanceof AgentServerStoreError) throw cause;
5647
+ if (cause.code !== "ENOENT") throw new AgentServerStoreError("io_error", "could not resolve run log", { cause });
5648
+ }
5649
+ return logPath;
5650
+ }
5651
+ createRunDir(id) {
5652
+ const dir = this.runDir(id);
5653
+ const piDir = join(dir, "pi");
5654
+ mkdirSync(piDir, {
5655
+ recursive: true,
5656
+ mode: 448
5657
+ });
5658
+ return {
5659
+ dir,
5660
+ piDir,
5661
+ logPath: join(dir, "daemon.log")
5662
+ };
5663
+ }
5664
+ readRun(id) {
5665
+ return readJson(join(this.runDir(id), "run.json"));
5666
+ }
5667
+ writeRun(record) {
5668
+ writeJsonAtomic(join(this.runDir(record.id), "run.json"), record);
5669
+ }
5670
+ listRuns(limit = Number.POSITIVE_INFINITY) {
5671
+ let ids;
5672
+ try {
5673
+ ids = readdirSync(this.runsDir);
5674
+ } catch {
5675
+ return [];
5676
+ }
5677
+ const sortedIds = ids.filter((id) => NAME_RE.test(id)).sort().reverse();
5678
+ const selectedIds = Number.isFinite(limit) ? sortedIds.slice(0, Math.max(0, limit)) : sortedIds;
5679
+ const records = [];
5680
+ for (const id of selectedIds) {
5681
+ const record = this.readRun(id);
5682
+ if (record) records.push(record);
5683
+ }
5684
+ return records.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
5685
+ }
5686
+ /** Remove only completed run directories outside the configured budget. */
5687
+ pruneCompletedRuns(options) {
5688
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
5689
+ let retainedBytes = 0;
5690
+ let retainedCount = 0;
5691
+ const removed = [];
5692
+ for (const record of this.listRuns()) {
5693
+ if (record.status === "running") continue;
5694
+ const dir = this.runDir(record.id);
5695
+ const bytes = directoryBytes(dir);
5696
+ const endedAt = Date.parse(record.endedAt ?? record.startedAt);
5697
+ const expired = !Number.isFinite(endedAt) || now - endedAt > options.maxAgeMs;
5698
+ const overCount = retainedCount >= options.maxCount;
5699
+ const overBytes = retainedBytes + bytes > options.maxBytes;
5700
+ if (expired || overCount || overBytes) {
5701
+ rmSync(dir, {
5702
+ recursive: true,
5703
+ force: true
5704
+ });
5705
+ removed.push(record.id);
5706
+ continue;
5770
5707
  }
5708
+ retainedCount += 1;
5709
+ retainedBytes += bytes;
5771
5710
  }
5711
+ return removed;
5712
+ }
5713
+ };
5714
+ function directoryBytes(path) {
5715
+ let info;
5716
+ try {
5717
+ info = lstatSync(path);
5718
+ } catch {
5719
+ return 0;
5720
+ }
5721
+ if (info.isSymbolicLink()) return 0;
5722
+ if (!info.isDirectory()) return info.size;
5723
+ let total = 0;
5724
+ for (const entry of readdirSync(path, { withFileTypes: true })) total += directoryBytes(join(path, entry.name));
5725
+ return total;
5726
+ }
5727
+ function isRecord$1(value) {
5728
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5729
+ }
5730
+ function storeChildPath(root, kind, value, suffix = "") {
5731
+ const name = assertStoreName(kind, value);
5732
+ const normalizedRoot = resolve(root);
5733
+ const candidate = resolve(normalizedRoot, `${name}${suffix}`);
5734
+ if (!isStrictDescendant(normalizedRoot, candidate)) throw new AgentServerStoreError("invalid_name", `${kind} escapes its store`);
5735
+ return candidate;
5736
+ }
5737
+ function isStrictDescendant(root, candidate) {
5738
+ const rootPrefix = root.endsWith(sep) ? root : `${root}${sep}`;
5739
+ return candidate !== root && candidate.startsWith(rootPrefix);
5740
+ }
5741
+ function validateActivation(alias, value) {
5742
+ const invalid = () => {
5743
+ throw new AgentServerStoreError("invalid_state", `activation "${alias}" is not a valid version 2 activation`);
5772
5744
  };
5745
+ if (!isRecord$1(value)) invalid();
5746
+ const activation = value;
5747
+ if (activation.alias !== alias) invalid();
5748
+ if (![
5749
+ "subjectId",
5750
+ "publicKey",
5751
+ "fingerprint",
5752
+ "createdAt"
5753
+ ].every((field) => typeof activation[field] === "string" && activation[field].length > 0)) invalid();
5754
+ if (activation.source === "managed") {
5755
+ if (typeof activation.apiUrl !== "string" || activation.apiUrl.length === 0 || activation.configPath !== void 0 || activation.configApiUrl !== void 0) invalid();
5756
+ return;
5757
+ }
5758
+ 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();
5773
5759
  }
5774
- function uploadStatusCode(error) {
5775
- const statusCode = error?.statusCode;
5776
- return typeof statusCode === "number" ? statusCode : void 0;
5760
+ //#endregion
5761
+ //#region src/lib/agent-server/pi-store-config.ts
5762
+ /**
5763
+ * Materialise the Agent Server provider store as a Pi agent dir. Shared by
5764
+ * supervised runs (`runs.ts`) and direct `once`/`poll`/`drain` runs
5765
+ * (`pi-agent-dir.ts`) so both generate the same `models.json`.
5766
+ */
5767
+ /**
5768
+ * Write `models.json` + `settings.json` for the stored providers. API keys
5769
+ * appear only as `$MOLTNET_PROVIDER_<ID>_API_KEY` placeholders.
5770
+ */
5771
+ function writeStorePiConfig(piDir, providers) {
5772
+ writePiConfig({
5773
+ piDir,
5774
+ providers: Object.fromEntries(Object.entries(providers).map(([providerId, provider]) => [providerId, {
5775
+ api: provider.api,
5776
+ ...provider.apiKeyRef ? { apiKeyEnvRef: `$${provider.envName}` } : {},
5777
+ baseUrl: provider.baseUrl,
5778
+ models: provider.models
5779
+ }]))
5780
+ });
5777
5781
  }
5778
- function resolveContinueFrom(claimedTask) {
5779
- return claimedTask.task.input.continueFrom;
5782
+ /**
5783
+ * Link a shared Pi `auth.json` into `piDir`. Pi lockfiles the target and
5784
+ * rotates tokens in place, so a link (never a copy) keeps one credential
5785
+ * document. A dangling link is fine — Pi treats a missing auth.json as "no
5786
+ * subscription auth". Failing to create the link is not: the run would start
5787
+ * with a connected subscription silently unavailable.
5788
+ */
5789
+ function linkPiAuth(authPath, piDir, symlinkImpl = symlinkSync) {
5790
+ try {
5791
+ symlinkImpl(authPath, join(piDir, "auth.json"));
5792
+ } catch (cause) {
5793
+ throw new AgentServerStoreError("io_error", "could not link subscription credentials into the run", { cause });
5794
+ }
5780
5795
  }
5781
5796
  //#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;
5797
+ //#region src/lib/pi-agent-dir.ts
5877
5798
  /**
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.
5799
+ * Pick the Pi agent dir for direct `once`/`poll`/`drain` runs:
5800
+ *
5801
+ * 1. `PI_CODING_AGENT_DIR`, unchanged (`env`).
5802
+ * 2. When the Agent Server store has providers or a subscription login, a
5803
+ * private dir built like an Agent Server run, with `<agentRoot>/.pi`
5804
+ * config the store does not define layered in (`store`).
5805
+ * 3. `<agentRoot>/.pi` (`repo`).
5882
5806
  */
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);
5903
- return;
5904
- }
5905
- drainingSignal = signal;
5906
- proc.exitCode = signalExitCode(signal);
5907
- 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");
5807
+ async function resolvePiAgentDir(cfg, agentRoot, profiles, options = {}) {
5808
+ const noop = () => void 0;
5809
+ if (cfg.piCodingAgentDir) {
5810
+ mkdirSync(cfg.piCodingAgentDir, { recursive: true });
5811
+ return {
5812
+ path: cfg.piCodingAgentDir,
5813
+ source: "env",
5814
+ env: {},
5815
+ cleanup: noop
5816
+ };
5817
+ }
5818
+ const repoPiDir = join(agentRoot, ".pi");
5819
+ const store = new AgentServerStore(resolveAgentServerRoot({ root: cfg.agentServerRoot }));
5820
+ const providers = store.readProviders();
5821
+ const storeHasAuth = existsSync(store.piAuthJsonPath);
5822
+ if (Object.keys(providers).length === 0 && !storeHasAuth) {
5823
+ mkdirSync(repoPiDir, { recursive: true });
5824
+ return {
5825
+ path: repoPiDir,
5826
+ source: "repo",
5827
+ env: {},
5828
+ cleanup: noop
5829
+ };
5830
+ }
5831
+ const path = mkdtempSync(join(options.tempRoot ?? tmpdir(), "moltnet-pi-"));
5832
+ const cleanup = () => rmSync(path, {
5833
+ recursive: true,
5834
+ force: true
5835
+ });
5836
+ try {
5837
+ writeStorePiConfig(path, providers);
5838
+ const repoModelsPath = join(repoPiDir, "models.json");
5839
+ if (existsSync(repoModelsPath)) {
5840
+ const modelsPath = join(path, "models.json");
5841
+ const merged = mergePiModels(readPiModels(modelsPath), readPiModels(repoModelsPath));
5842
+ writeFileSync(modelsPath, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
5843
+ }
5844
+ const repoSettingsPath = join(repoPiDir, "settings.json");
5845
+ if (existsSync(repoSettingsPath)) copyFileSync(repoSettingsPath, join(path, "settings.json"));
5846
+ const repoAuthPath = join(repoPiDir, "auth.json");
5847
+ linkPiAuth(storeHasAuth || !existsSync(repoAuthPath) ? store.piAuthJsonPath : repoAuthPath, path);
5848
+ const secretProviders = options.secretProviders ?? createNodeSecretProviderRegistry().register(new FileSecretProvider({ root: store.secretsDir }));
5849
+ const env = {};
5850
+ for (const providerId of new Set(profiles.map((p) => p.provider))) {
5851
+ const provider = providers[providerId];
5852
+ if (!provider?.apiKeyRef) continue;
5853
+ if (cfg.profilePrerequisiteEnv[provider.envName]) continue;
5854
+ try {
5855
+ env[provider.envName] = await secretProviders.resolve(parseSecretReferenceString(provider.apiKeyRef));
5856
+ } catch {
5857
+ throw new Error(`provider "${providerId}" API key could not be resolved from the Agent Server store`);
5858
+ }
5911
5859
  }
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
- } };
5860
+ return {
5861
+ path,
5862
+ source: "store",
5863
+ env,
5864
+ cleanup
5865
+ };
5866
+ } catch (error) {
5867
+ cleanup();
5868
+ throw error;
5869
+ }
5922
5870
  }
5923
- function signalExitCode(signal) {
5924
- return signal === "SIGINT" ? 130 : 143;
5871
+ function readPiModels(path) {
5872
+ try {
5873
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
5874
+ if (typeof parsed?.providers === "object" && parsed.providers !== null) return parsed;
5875
+ } catch {}
5876
+ throw new Error(`${path} is not a valid Pi models document`);
5925
5877
  }
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);
5940
- }
5878
+ /**
5879
+ * Store entries win per provider id. Repo models the store lacks are appended
5880
+ * with their metadata; repo-only providers are added unchanged.
5881
+ */
5882
+ function mergePiModels(store, repo) {
5883
+ const providers = {
5884
+ ...repo.providers,
5885
+ ...store.providers
5941
5886
  };
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;
5887
+ for (const [id, storeProvider] of Object.entries(store.providers)) {
5888
+ const repoModels = repo.providers[id]?.models ?? [];
5889
+ const known = new Set((storeProvider.models ?? []).map((m) => m.id));
5890
+ providers[id] = {
5891
+ ...storeProvider,
5892
+ models: [...storeProvider.models ?? [], ...repoModels.filter((m) => !known.has(m.id))]
5893
+ };
5894
+ }
5895
+ return { providers };
5947
5896
  }
5948
5897
  //#endregion
5949
5898
  //#region src/lib/state-dir.ts
@@ -5957,769 +5906,706 @@ function ensureDaemonStateDirs(mountPath) {
5957
5906
  };
5958
5907
  }
5959
5908
  //#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
5909
+ //#region src/lib/prepare-runtime-profile.ts
5910
+ /** Validate and prepare a profile through the shared daemon execution path. */
5911
+ async function prepareRuntimeProfile(input) {
5912
+ const { profile } = input;
5913
+ assertRuntimeAdapterSupportsProfile(input.runtimeAdapter, profile);
5914
+ const preparedRuntime = attestPreparedRuntime(await input.runtimeAdapter.prepare({ profile }), input.signingPrivateKey);
5915
+ validateRuntimeProfilePrerequisites(profile, input.prerequisiteEnv, {
5916
+ tools: preparedRuntime.tools,
5917
+ executables: preparedRuntime.executables
5965
5918
  });
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}`);
5919
+ assertGuestEnvironmentBoundary({
5920
+ forwardEnv: profile.requiredEnv,
5921
+ sandboxEnv: profile.sandboxConfig.env
5922
+ });
5923
+ await input.agent.tasks.registerExecutorManifest(await preparedRuntime.attestor.registration());
5924
+ const sandbox = {
5925
+ config: profile.sandboxConfig,
5926
+ rootDir: profile.mountPath,
5927
+ path: profile.source
5928
+ };
5929
+ const stateDirs = ensureDaemonStateDirs(sandbox.rootDir);
5930
+ const slotIdentity = {
5931
+ agentName: input.agentName,
5932
+ runtimeProfileId: profile.id,
5933
+ runtimeInstanceId: input.runtimeInstanceId
5934
+ };
5935
+ return {
5936
+ profile,
5937
+ preparedRuntime,
5938
+ sandbox,
5939
+ stateDirs,
5940
+ slotIdentity,
5941
+ executionPlans: createExecutionPlanCache({
5942
+ stateDirs,
5943
+ slotIdentity,
5944
+ warmRetentionSec: input.warmRetentionSec,
5945
+ workspacePolicy: {
5946
+ defaultWorkspaceMode: profile.defaultWorkspaceMode,
5947
+ allowedWorkspaceModes: profile.allowedWorkspaceModes
5948
+ },
5949
+ slotRegistry: input.slotRegistry,
5950
+ runtimeSessionStore: input.runtimeSessionStore,
5951
+ sourceAttemptResolver: input.sourceAttemptResolver
5952
+ })
5972
5953
  };
5973
5954
  }
5974
- function makeTurnEventHandlerFactory(base) {
5975
- return (claimedTask) => makeTurnEventHandler(base, {
5976
- taskId: claimedTask.task.id,
5977
- attemptN: claimedTask.attemptN
5978
- });
5955
+ //#endregion
5956
+ //#region src/lib/runtime-context.ts
5957
+ var storage = new AsyncLocalStorage();
5958
+ function runWithDaemonRuntimeContext(context, callback) {
5959
+ return storage.run(context, callback);
5979
5960
  }
5980
5961
  //#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;
5962
+ //#region src/lib/runtime-profile-retry-triage.ts
5963
+ function createRuntimeProfileRetryTriage(options) {
5964
+ return async (input) => {
5965
+ const { modelHandle, modelRuntime } = await resolveRuntimeProfileModel(options.piAgentDir, options.runtimeProfile.provider, options.runtimeProfile.model);
5966
+ return createPiRetryTriage({
5967
+ model: modelHandle,
5968
+ modelRuntime,
5969
+ thinkingLevel: options.runtimeProfile.thinkingLevel,
5970
+ piAgentDir: options.piAgentDir,
5971
+ timeoutMs: options.timeoutMs,
5972
+ cwd: options.cwd
5973
+ })(input);
5974
+ };
5975
+ }
5976
+ //#endregion
5977
+ //#region src/lib/runtime-resource-reaper.ts
5978
+ var RUNTIME_SLOT_PAGE_LIMIT = 200;
5979
+ var ATTEMPT_LOOKUP_CONCURRENCY = 8;
5980
+ var TERMINAL_ATTEMPT_STATUSES = new Set([
5981
+ "completed",
5982
+ "failed",
5983
+ "cancelled",
5984
+ "aborted",
5985
+ "timed_out"
5986
+ ]);
5987
+ /**
5988
+ * Reap daemon-owned local resources whose runtime slot is no longer usable.
5989
+ * The destructive path is deliberately conservative: uncertainty about slot
5990
+ * ownership, attempt state, listing completeness, or real filesystem
5991
+ * containment retains resources rather than risking another daemon's data.
5992
+ */
5993
+ async function reapRuntimeSlotResources(deps, input) {
5994
+ const now = input.now ?? Date.now();
5995
+ const [activePage, idlePage] = await Promise.all([listOwnedSlots(deps.runtimeSlotStore, input, "active"), listOwnedSlots(deps.runtimeSlotStore, input, "idle")]);
5996
+ const active = activePage.items;
5997
+ const idle = idlePage.items;
5998
+ const slots = [...active, ...idle];
5999
+ const result = {
6000
+ failed: 0,
6001
+ failures: [],
6002
+ removedSessions: 0,
6003
+ removedWorkspaces: 0,
6004
+ scanned: slots.length,
6005
+ truncated: activePage.truncated || idlePage.truncated,
6006
+ unsafePathDetails: [],
6007
+ unsafePaths: 0
6008
+ };
6009
+ const reaped = new Set(idle.filter((item) => item.slot.expiresAtMs <= now).map((item) => item.slot.id));
6010
+ for (const slotId of await findTerminalActiveSlotIds(deps, active, result)) reaped.add(slotId);
6011
+ const retainedSessionDirs = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.session?.sessionDir ? [resolve(item.session.sessionDir)] : []));
6012
+ const retainedWorkspacePaths = new Set(slots.filter((item) => !reaped.has(item.slot.id)).flatMap((item) => item.workspace?.worktreePath ? [resolve(item.workspace.worktreePath)] : []));
6013
+ const worktrees = input.mainWorktree ? await listRegisteredWorktrees(input.mainWorktree) : {
6014
+ kind: "ready",
6015
+ paths: /* @__PURE__ */ new Set()
6016
+ };
6017
+ for (const item of slots) {
6018
+ if (!reaped.has(item.slot.id)) continue;
6019
+ if (!await slotStillQualifiesForReaping(deps, input, item, now, result)) continue;
6020
+ const sessionDir = item.session?.sessionDir;
6021
+ if (sessionDir && !retainedSessionDirs.has(resolve(sessionDir)) && existsSync(sessionDir)) if (!isRealPathInsideRoot(sessionDir, input.sessionRootDir)) recordUnsafePath(deps, result, item.slot.id, sessionDir, input.sessionRootDir);
6022
+ else try {
6023
+ rmSync(sessionDir, {
6024
+ force: true,
6025
+ recursive: true
6026
+ });
6027
+ result.removedSessions++;
6028
+ } catch (err) {
6029
+ recordFailure(deps, result, item.slot.id, sessionDir, input.sessionRootDir, err);
6030
+ }
6031
+ const workspacePath = item.workspace?.worktreePath;
6032
+ if (!workspacePath || retainedWorkspacePaths.has(resolve(workspacePath))) continue;
6033
+ const workspaceRoot = item.workspace?.kind === "scratch" ? input.scratchRootDir : input.mainWorktree ? resolve(input.mainWorktree, ".worktrees") : null;
6034
+ if (!workspaceRoot) {
6035
+ recordUnsafePath(deps, result, item.slot.id, workspacePath, null);
6036
+ continue;
6037
+ }
6038
+ await removeWorkspace(deps, result, worktrees, input.mainWorktree, item.slot.id, workspacePath, workspaceRoot, item.workspace?.kind === "scratch");
6011
6039
  }
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;
6040
+ if (!result.truncated) await reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result);
6041
+ return result;
6042
+ }
6043
+ async function listOwnedSlots(store, input, state) {
6044
+ const listed = await store.listSlots({
6045
+ agentName: input.agentName,
6046
+ limit: RUNTIME_SLOT_PAGE_LIMIT,
6047
+ runtimeProfileId: input.runtimeProfileId,
6048
+ state,
6049
+ teamId: input.teamId
6050
+ });
6051
+ return {
6052
+ items: listed.filter((item) => runtimeSlotKeyBelongsToInstance(item.slot.slotKey, input.runtimeInstanceId)),
6053
+ truncated: listed.length === RUNTIME_SLOT_PAGE_LIMIT
6054
+ };
6055
+ }
6056
+ async function findTerminalActiveSlotIds(deps, active, result) {
6057
+ const byTask = /* @__PURE__ */ new Map();
6058
+ for (const item of active) {
6059
+ const taskSlots = byTask.get(item.slot.lastTaskId) ?? [];
6060
+ taskSlots.push(item);
6061
+ byTask.set(item.slot.lastTaskId, taskSlots);
6018
6062
  }
6019
- let taskTypes;
6063
+ const terminal = /* @__PURE__ */ new Set();
6064
+ await mapWithConcurrency([...byTask.entries()], ATTEMPT_LOOKUP_CONCURRENCY, async ([taskId, taskSlots]) => {
6065
+ let attempts;
6066
+ try {
6067
+ attempts = await deps.taskReader.listAttempts(taskId);
6068
+ } catch (err) {
6069
+ recordFailure(deps, result, null, `task:${taskId}`, null, err);
6070
+ return;
6071
+ }
6072
+ for (const item of taskSlots) {
6073
+ const attempt = attempts.find((candidate) => candidate.attemptN === item.slot.lastAttemptN);
6074
+ if (attempt && TERMINAL_ATTEMPT_STATUSES.has(attempt.status)) terminal.add(item.slot.id);
6075
+ }
6076
+ });
6077
+ return terminal;
6078
+ }
6079
+ async function slotStillQualifiesForReaping(deps, input, original, now, result) {
6080
+ let page;
6020
6081
  try {
6021
- taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
6082
+ page = await listOwnedSlots(deps.runtimeSlotStore, input, original.slot.state);
6022
6083
  } catch (err) {
6023
- const message = err instanceof Error ? err.message : String(err);
6024
- console.error(message);
6025
- return 1;
6084
+ recordFailure(deps, result, original.slot.id, `slot:${original.slot.id}`, null, err);
6085
+ return false;
6026
6086
  }
6027
- const diaryIds = parseCsv(values["diary-ids"]);
6028
- let baseCommon;
6087
+ const current = page.items.find((candidate) => candidate.slot.id === original.slot.id);
6088
+ if (!current) return false;
6089
+ if (current.slot.lastTaskId !== original.slot.lastTaskId || current.slot.lastAttemptN !== original.slot.lastAttemptN) return false;
6090
+ return current.slot.state === "active" || current.slot.state === "idle" && current.slot.expiresAtMs <= now;
6091
+ }
6092
+ async function reapLegacyOrphanWorktrees(deps, input, worktrees, retainedSessionDirs, retainedWorkspacePaths, result) {
6093
+ if (!input.mainWorktree) return;
6094
+ const workspaceRoot = resolve(input.mainWorktree, ".worktrees");
6095
+ if (!existsSync(workspaceRoot)) return;
6096
+ let entries;
6029
6097
  try {
6030
- baseCommon = parseCommonOptions(values);
6098
+ entries = readdirSync(workspaceRoot, { withFileTypes: true });
6031
6099
  } catch (err) {
6032
- if (err instanceof MissingRequiredOptionError) {
6033
- console.error(`${err.message}\n`);
6034
- console.error(opts.helpText);
6035
- return 1;
6036
- }
6037
- throw err;
6100
+ recordFailure(deps, result, null, workspaceRoot, workspaceRoot, err);
6101
+ return;
6038
6102
  }
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;
6103
+ for (const entry of entries) {
6104
+ if (!entry.isDirectory() || !entry.name.startsWith("session-agent%3A")) continue;
6105
+ const workspacePath = resolve(workspaceRoot, entry.name);
6106
+ if (retainedWorkspacePaths.has(workspacePath)) continue;
6107
+ const sessionDir = resolve(input.sessionRootDir, entry.name.slice(8));
6108
+ if (retainedSessionDirs.has(sessionDir) || existsSync(sessionDir)) continue;
6109
+ await removeWorkspace(deps, result, worktrees, input.mainWorktree, null, workspacePath, workspaceRoot, false);
6047
6110
  }
6048
- if (values.sandbox) {
6049
- console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
6050
- return 1;
6111
+ }
6112
+ async function removeWorkspace(deps, result, worktrees, mainWorktree, slotId, workspacePath, workspaceRoot, scratch) {
6113
+ if (!existsSync(workspacePath)) return;
6114
+ if (!isRealPathInsideRoot(workspacePath, workspaceRoot)) {
6115
+ recordUnsafePath(deps, result, slotId, workspacePath, workspaceRoot);
6116
+ return;
6051
6117
  }
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
6057
- };
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";
6118
+ if (scratch || !mainWorktree) {
6062
6119
  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
6120
+ rmSync(workspacePath, {
6121
+ force: true,
6122
+ recursive: true
6115
6123
  });
6116
- throw error;
6124
+ result.removedWorkspaces++;
6125
+ } catch (err) {
6126
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
6117
6127
  }
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
- });
6128
+ return;
6150
6129
  }
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}`);
6130
+ if (worktrees.kind === "unavailable") {
6131
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, new Error(worktrees.reason));
6132
+ return;
6154
6133
  }
6155
- const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
6156
- const runtimeSessionStore = createApiRuntimeSessionStore({
6157
- agent: ctx.agent,
6158
- logger: { warn: (context, message) => rootLogger.warn(context, message) }
6159
- });
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
6134
+ try {
6135
+ const canonical = realpathSync(workspacePath);
6136
+ if (worktrees.paths.has(canonical)) {
6137
+ await execFileText("git", [
6138
+ "-C",
6139
+ mainWorktree,
6140
+ "worktree",
6141
+ "remove",
6142
+ "--force",
6143
+ workspacePath
6144
+ ]);
6145
+ worktrees.paths.delete(canonical);
6146
+ } else rmSync(workspacePath, {
6147
+ force: true,
6148
+ recursive: true
6149
+ });
6150
+ result.removedWorkspaces++;
6151
+ } catch (err) {
6152
+ recordFailure(deps, result, slotId, workspacePath, workspaceRoot, err);
6153
+ }
6154
+ }
6155
+ async function listRegisteredWorktrees(mainWorktree) {
6156
+ try {
6157
+ const list = await execFileText("git", [
6158
+ "-C",
6159
+ mainWorktree,
6160
+ "worktree",
6161
+ "list",
6162
+ "--porcelain"
6163
+ ]);
6164
+ return {
6165
+ kind: "ready",
6166
+ paths: new Set(list.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => canonicalPath(line.slice(9))))
6176
6167
  };
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
6168
+ } catch (err) {
6169
+ return {
6170
+ kind: "unavailable",
6171
+ reason: errorMessage(err)
6183
6172
  };
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
- });
6206
6173
  }
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(",")
6221
- }
6222
- });
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;
6307
- 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
- }
6333
- } catch (err) {
6334
- rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
6335
- } finally {
6336
- reaperRunning = false;
6337
- }
6174
+ }
6175
+ function isRealPathInsideRoot(path, root) {
6176
+ try {
6177
+ return isResolvedPathInsideRoot(realpathSync(path), realpathSync(root));
6178
+ } catch {
6179
+ return false;
6180
+ }
6181
+ }
6182
+ function canonicalPath(path) {
6183
+ try {
6184
+ return realpathSync(path);
6185
+ } catch {
6186
+ return resolve(path);
6187
+ }
6188
+ }
6189
+ function recordUnsafePath(deps, result, slotId, path, root) {
6190
+ const issue = {
6191
+ kind: "unsafe_path",
6192
+ path,
6193
+ reason: root === null ? "No daemon-owned root was available for this workspace." : "Candidate or root could not be resolved inside the daemon-owned root.",
6194
+ root,
6195
+ slotId
6338
6196
  };
6339
- await reapRuntimeResources();
6340
- const reaperTimer = setInterval(() => {
6341
- reapRuntimeResources();
6342
- }, 6e4);
6343
- reaperTimer.unref();
6344
- const outputs = [];
6197
+ result.unsafePaths++;
6198
+ result.unsafePathDetails.push(issue);
6199
+ emitIssue(deps, issue);
6200
+ }
6201
+ function recordFailure(deps, result, slotId, path, root, err) {
6202
+ const issue = {
6203
+ kind: "failure",
6204
+ path,
6205
+ reason: errorMessage(err),
6206
+ root,
6207
+ slotId
6208
+ };
6209
+ result.failed++;
6210
+ result.failures.push(issue);
6211
+ emitIssue(deps, issue);
6212
+ }
6213
+ function emitIssue(deps, issue) {
6345
6214
  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
- }
6215
+ deps.onIssue?.(issue);
6216
+ } catch {}
6217
+ }
6218
+ function errorMessage(err) {
6219
+ return err instanceof Error ? err.message : String(err);
6220
+ }
6221
+ async function execFileText(file, args) {
6222
+ return new Promise((resolvePromise, reject) => {
6223
+ execFile(file, args, {
6224
+ encoding: "utf8",
6225
+ maxBuffer: 10 * 1024 * 1024
6226
+ }, (err, stdout) => {
6227
+ if (err) reject(err instanceof Error ? err : /* @__PURE__ */ new Error("Child process failed without an Error instance."));
6228
+ else resolvePromise(stdout);
6602
6229
  });
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
- }
6230
+ });
6614
6231
  }
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);
6232
+ async function mapWithConcurrency(items, concurrency, worker) {
6233
+ for (let index = 0; index < items.length; index += concurrency) await Promise.all(items.slice(index, index + concurrency).map(worker));
6618
6234
  }
6619
- function resolveMainWorktree$1(mountPath) {
6620
- try {
6621
- return findMainWorktree(mountPath);
6622
- } catch {
6623
- return null;
6624
- }
6235
+ //#endregion
6236
+ //#region src/lib/runtime-sessions.ts
6237
+ function resolveRuntimeSessionKind(claimedTask) {
6238
+ const continueFrom = resolveContinueFrom(claimedTask);
6239
+ if (!continueFrom) return "root";
6240
+ return continueFrom.mode === "fork" ? "fork" : "extend";
6625
6241
  }
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);
6242
+ async function resolveParentRuntimeSession(runtimeSessionStore, claimedTask) {
6243
+ const continueFrom = resolveContinueFrom(claimedTask);
6244
+ if (!continueFrom) return null;
6245
+ return runtimeSessionStore.findRuntimeSessionByTaskAttempt(claimedTask.task.teamId, continueFrom.taskId, continueFrom.attemptN);
6629
6246
  }
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;
6247
+ function applyRuntimeSessionUploadFailure(output, err) {
6248
+ if (output.status !== "completed") return output;
6249
+ return {
6250
+ ...output,
6251
+ error: {
6252
+ code: "runtime_session_upload_failed",
6253
+ message: "Task completed, but durable runtime session checkpoint upload failed: " + (err instanceof Error ? err.message : String(err)),
6254
+ retryable: isTransientUploadError(err)
6255
+ },
6256
+ output: null,
6257
+ outputCid: null,
6258
+ status: "failed"
6259
+ };
6634
6260
  }
6635
- function parseProfileValues(raw) {
6636
- return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
6261
+ /**
6262
+ * Transient faults worth retrying in-attempt: network-level errors
6263
+ * (no HTTP status at all) and 5xx/429 responses. A 4xx (auth,
6264
+ * validation, not-found) will not heal on retry.
6265
+ */
6266
+ function isTransientUploadError(error) {
6267
+ if (!(error instanceof Error)) return false;
6268
+ const statusCode = error.statusCode;
6269
+ if (typeof statusCode !== "number") return true;
6270
+ return statusCode >= 500 || statusCode === 429;
6271
+ }
6272
+ var defaultSleep = (ms) => new Promise((resolve) => {
6273
+ setTimeout(resolve, ms);
6274
+ });
6275
+ function createApiRuntimeSessionStore(args) {
6276
+ const { agent, logger, uploadRetry } = args;
6277
+ return {
6278
+ async findRuntimeSessionByTaskAttempt(teamId, taskId, attemptN) {
6279
+ return agent.runtimeSessions.getForAttempt({
6280
+ attemptN,
6281
+ taskId
6282
+ }, { teamId });
6283
+ },
6284
+ async hydrateSession(input) {
6285
+ const downloaded = await agent.runtimeSessions.download({
6286
+ attemptN: input.attemptN,
6287
+ taskId: input.taskId
6288
+ }, { teamId: input.teamId });
6289
+ await mkdir(input.destinationDir, { recursive: true });
6290
+ const sessionPath = join(input.destinationDir, `remote-${input.taskId}-attempt-${input.attemptN}.jsonl`);
6291
+ await pipeline(downloaded, createWriteStream(sessionPath));
6292
+ return sessionPath;
6293
+ },
6294
+ async uploadAttemptFinal(input) {
6295
+ const sessionPath = resolveLatestPiSessionPath(input.sessionDir);
6296
+ if (!sessionPath) throw new Error(`Cannot upload runtime session for ${input.taskId}/${input.attemptN}: no local session file in ${input.sessionDir}`);
6297
+ const maxTries = uploadRetry?.maxTries ?? 3;
6298
+ const baseDelayMs = uploadRetry?.baseDelayMs ?? 750;
6299
+ const sleep = uploadRetry?.sleep ?? defaultSleep;
6300
+ for (let tryN = 1;; tryN += 1) try {
6301
+ await agent.runtimeSessions.upload({
6302
+ attemptN: input.attemptN,
6303
+ taskId: input.taskId
6304
+ }, createReadStream(sessionPath), {
6305
+ parentSessionId: input.parentSessionId ?? void 0,
6306
+ sessionKind: input.sessionKind,
6307
+ sourceRuntimeProfileId: input.sourceRuntimeProfileId ?? void 0,
6308
+ sourceSlotId: input.sourceSlotId ?? void 0
6309
+ }, { teamId: input.teamId });
6310
+ return;
6311
+ } catch (error) {
6312
+ if (tryN >= maxTries || !isTransientUploadError(error)) throw error;
6313
+ const delayMs = baseDelayMs * tryN;
6314
+ logger?.warn({
6315
+ event: "agent-daemon.runtime_session_upload_retry",
6316
+ attemptN: input.attemptN,
6317
+ delayMs,
6318
+ statusCode: uploadStatusCode(error),
6319
+ taskId: input.taskId,
6320
+ tryN
6321
+ }, "Retrying durable runtime session upload");
6322
+ await sleep(delayMs);
6323
+ }
6324
+ }
6325
+ };
6326
+ }
6327
+ function uploadStatusCode(error) {
6328
+ const statusCode = error?.statusCode;
6329
+ return typeof statusCode === "number" ? statusCode : void 0;
6330
+ }
6331
+ function resolveContinueFrom(claimedTask) {
6332
+ return claimedTask.task.input.continueFrom;
6333
+ }
6334
+ //#endregion
6335
+ //#region src/lib/runtime-slots.ts
6336
+ function createApiRuntimeSlotStore(args) {
6337
+ const { agent } = args;
6338
+ return {
6339
+ async beginSlot(input) {
6340
+ await agent.runtimeSlots.begin({
6341
+ agentName: input.agentName,
6342
+ runtimeProfileId: input.runtimeProfileId,
6343
+ lastAttemptN: input.lastAttemptN,
6344
+ lastTaskId: input.lastTaskId,
6345
+ model: input.model,
6346
+ provider: input.provider,
6347
+ sessionDir: input.sessionDir ?? void 0,
6348
+ sessionPath: input.sessionPath ?? void 0,
6349
+ slotKey: input.slotKey,
6350
+ taskType: input.taskType,
6351
+ warmRetentionSec: input.warmRetentionSec,
6352
+ workspaceId: input.workspaceId ?? void 0,
6353
+ workspaceKind: input.workspaceKind,
6354
+ worktreeBranch: input.worktreeBranch ?? void 0,
6355
+ worktreePath: input.worktreePath ?? void 0
6356
+ }, { teamId: input.teamId });
6357
+ },
6358
+ async finishSlot(teamId, taskId, attemptN, identity, slotKey, provider, model, sessionPath, warmRetentionSec) {
6359
+ await agent.runtimeSlots.finish({
6360
+ agentName: identity.agentName,
6361
+ attemptN,
6362
+ runtimeProfileId: identity.runtimeProfileId,
6363
+ model,
6364
+ provider,
6365
+ sessionPath: sessionPath ?? void 0,
6366
+ slotKey,
6367
+ taskId,
6368
+ warmRetentionSec
6369
+ }, { teamId });
6370
+ },
6371
+ async findLatestSlotByTaskAttempt(teamId, taskId, attemptN) {
6372
+ const resolved = await agent.runtimeSlots.findLatestForAttempt({
6373
+ attemptN,
6374
+ taskId
6375
+ }, { teamId });
6376
+ if (!resolved) return null;
6377
+ return {
6378
+ slot: {
6379
+ expiresAtMs: resolved.slot.expiresAtMs,
6380
+ id: resolved.slot.id,
6381
+ runtimeProfileId: resolved.slot.runtimeProfileId
6382
+ },
6383
+ session: resolved.slot.sessionDir ? {
6384
+ sessionDir: resolved.slot.sessionDir,
6385
+ sessionPath: resolved.slot.sessionPath
6386
+ } : null,
6387
+ workspace: resolved.workspace ? {
6388
+ kind: resolved.workspace.kind,
6389
+ workspaceId: resolved.workspace.workspaceId,
6390
+ worktreeBranch: resolved.workspace.worktreeBranch,
6391
+ worktreePath: resolved.workspace.worktreePath
6392
+ } : null
6393
+ };
6394
+ },
6395
+ async listSlots(input) {
6396
+ const query = {
6397
+ agentName: input.agentName,
6398
+ ...input.limit === void 0 ? {} : { limit: input.limit },
6399
+ ...input.runtimeProfileId === void 0 ? {} : { runtimeProfileId: input.runtimeProfileId },
6400
+ ...input.state === void 0 ? {} : { state: input.state }
6401
+ };
6402
+ return (await agent.runtimeSlots.list(query, { teamId: input.teamId })).map((resolved) => ({
6403
+ slot: {
6404
+ expiresAtMs: resolved.slot.expiresAtMs,
6405
+ id: resolved.slot.id,
6406
+ lastAttemptN: resolved.slot.lastAttemptN,
6407
+ lastTaskId: resolved.slot.lastTaskId,
6408
+ runtimeProfileId: resolved.slot.runtimeProfileId,
6409
+ slotKey: resolved.slot.slotKey,
6410
+ state: resolved.slot.state,
6411
+ taskType: resolved.slot.taskType
6412
+ },
6413
+ session: resolved.slot.sessionDir ? {
6414
+ sessionDir: resolved.slot.sessionDir,
6415
+ sessionPath: resolved.slot.sessionPath
6416
+ } : null,
6417
+ workspace: resolved.workspace ? {
6418
+ kind: resolved.workspace.kind,
6419
+ workspaceId: resolved.workspace.workspaceId,
6420
+ worktreeBranch: resolved.workspace.worktreeBranch,
6421
+ worktreePath: resolved.workspace.worktreePath
6422
+ } : null
6423
+ }));
6424
+ },
6425
+ async close() {}
6426
+ };
6427
+ }
6428
+ //#endregion
6429
+ //#region src/lib/secret-redaction.ts
6430
+ var REDACTED = "[REDACTED]";
6431
+ var MIN_SECRET_LENGTH = 8;
6432
+ /**
6433
+ * Redact required runtime-profile environment values before task output leaves
6434
+ * the daemon. This is defense in depth for shell-capable profiles: a model may
6435
+ * read a forwarded provider key, but it must not be able to publish that exact
6436
+ * value through a task result.
6437
+ */
6438
+ function redactRequiredEnvValues(value, requiredEnv, env) {
6439
+ const secrets = [...new Set(requiredEnv.map((name) => env[name]).filter((secret) => typeof secret === "string" && secret.length >= MIN_SECRET_LENGTH))];
6440
+ if (secrets.length === 0) return value;
6441
+ const redact = (candidate) => {
6442
+ if (typeof candidate === "string") return secrets.reduce((text, secret) => text.replaceAll(secret, REDACTED), candidate);
6443
+ if (Array.isArray(candidate)) return candidate.map(redact);
6444
+ if (candidate && typeof candidate === "object") return Object.fromEntries(Object.entries(candidate).map(([key, nested]) => [key, redact(nested)]));
6445
+ return candidate;
6446
+ };
6447
+ return redact(value);
6448
+ }
6449
+ //#endregion
6450
+ //#region src/lib/shutdown-signal.ts
6451
+ function installShutdownSignalHandlers(opts) {
6452
+ const proc = opts.proc ?? process;
6453
+ let drainingSignal = null;
6454
+ const onSignal = (signal) => {
6455
+ if (drainingSignal) {
6456
+ proc.stderr.write(`[agent-daemon] ${signal} received while already draining from ${drainingSignal}; waiting for cleanup.\n`);
6457
+ proc.exitCode = signalExitCode(signal);
6458
+ return;
6459
+ }
6460
+ drainingSignal = signal;
6461
+ proc.exitCode = signalExitCode(signal);
6462
+ try {
6463
+ opts.logDrain(signal);
6464
+ } catch (err) {
6465
+ proc.stderr.write(`[agent-daemon] failed to log ${signal}: ` + (err instanceof Error ? err.message : String(err)) + "\n");
6466
+ }
6467
+ opts.drain(signal);
6468
+ };
6469
+ const handleSigint = () => onSignal("SIGINT");
6470
+ const handleSigterm = () => onSignal("SIGTERM");
6471
+ proc.on("SIGINT", handleSigint);
6472
+ proc.on("SIGTERM", handleSigterm);
6473
+ return { dispose: () => {
6474
+ proc.off("SIGINT", handleSigint);
6475
+ proc.off("SIGTERM", handleSigterm);
6476
+ } };
6637
6477
  }
6638
- function parseCsv(raw) {
6639
- return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
6478
+ function signalExitCode(signal) {
6479
+ return signal === "SIGINT" ? 130 : 143;
6640
6480
  }
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;
6481
+ //#endregion
6482
+ //#region src/lib/source-attempts.ts
6483
+ function createApiSourceAttemptResolver(args) {
6484
+ const { agent } = args;
6485
+ return {
6486
+ async findOutputBranch(input) {
6487
+ const attempt = (await agent.tasks.listAttempts(input.taskId, { teamId: input.teamId })).find((candidate) => candidate.attemptN === input.attemptN);
6488
+ if (!attempt || attempt.status !== "completed") return null;
6489
+ return resolveOutputBranch(attempt.output);
6490
+ },
6491
+ async findInputRevision(input) {
6492
+ const task = await agent.tasks.get(input.taskId, { teamId: input.teamId });
6493
+ if (task.status !== "completed" || task.acceptedAttemptN !== input.attemptN) return null;
6494
+ return resolveTaskWorkspaceRevision(task.input);
6495
+ }
6496
+ };
6646
6497
  }
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;
6498
+ function resolveOutputBranch(output) {
6499
+ if (!output || typeof output !== "object") return null;
6500
+ const branch = output.branch;
6501
+ return typeof branch === "string" && branch.length > 0 ? branch : null;
6652
6502
  }
6653
6503
  //#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
6504
+ //#region src/lib/turn-event-logger.ts
6505
+ function makeTurnEventHandler(base, context = {}) {
6506
+ const log = base.child({
6507
+ name: "agent-daemon.turn",
6508
+ ...context
6509
+ });
6510
+ return (event, summary) => {
6511
+ if (event === "text_delta") return;
6512
+ log[event === "error" ? "warn" : event === "turn_end" ? "info" : "debug"]({
6513
+ event,
6514
+ ...summary
6515
+ }, `turn.${event}`);
6516
+ };
6517
+ }
6518
+ function makeTurnEventHandlerFactory(base) {
6519
+ return (claimedTask) => makeTurnEventHandler(base, {
6520
+ taskId: claimedTask.task.id,
6521
+ attemptN: claimedTask.attemptN
6663
6522
  });
6664
6523
  }
6665
6524
  //#endregion
6666
- //#region src/cli/once.ts
6667
- async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6668
- if (isHelpFlag(argv)) {
6669
- console.log(ONCE_HELP);
6525
+ //#region src/cli/poll-shared.ts
6526
+ async function runPolling(opts) {
6527
+ if (isHelpFlag(opts.argv)) {
6528
+ console.log(opts.helpText);
6670
6529
  return 0;
6671
6530
  }
6672
6531
  const { values } = parseArgs({
6673
- args: argv,
6532
+ args: opts.argv,
6674
6533
  options: {
6675
- ...commonOptionDefs(),
6676
- "task-id": {
6677
- type: "string",
6678
- short: "t"
6679
- },
6534
+ ...runtimeCommandOptionDefs(),
6680
6535
  team: { type: "string" },
6536
+ "task-types": { type: "string" },
6537
+ "correlation-id": { type: "string" },
6538
+ "diary-ids": { type: "string" },
6539
+ "wait-for-first-task-sec": { type: "string" },
6540
+ "wait-after-task-sec": { type: "string" },
6541
+ "poll-interval-ms": { type: "string" },
6542
+ "max-poll-interval-ms": { type: "string" },
6543
+ "list-limit": { type: "string" },
6681
6544
  sandbox: { type: "string" },
6682
- profile: { type: "string" }
6545
+ profile: {
6546
+ type: "string",
6547
+ multiple: true
6548
+ }
6683
6549
  }
6684
6550
  });
6685
- if (!values["task-id"]) {
6686
- console.error("Missing required flag: --task-id\n");
6687
- console.error(ONCE_HELP);
6551
+ if (!values.team) {
6552
+ console.error("Missing required flag: --team\n");
6553
+ console.error(opts.helpText);
6688
6554
  return 1;
6689
6555
  }
6690
- const taskId = values["task-id"];
6691
- if (!values.profile) {
6556
+ const teamId = values.team;
6557
+ const profileValues = parseProfileValues(values.profile);
6558
+ if (profileValues.length === 0) {
6692
6559
  console.error("Missing required flag: --profile\n");
6693
- console.error(ONCE_HELP);
6560
+ console.error(opts.helpText);
6561
+ return 1;
6562
+ }
6563
+ let taskTypes;
6564
+ try {
6565
+ taskTypes = validateTaskTypes(parseCsv(values["task-types"]));
6566
+ } catch (err) {
6567
+ const message = err instanceof Error ? err.message : String(err);
6568
+ console.error(message);
6694
6569
  return 1;
6695
6570
  }
6696
- let opts;
6571
+ const diaryIds = parseCsv(values["diary-ids"]);
6572
+ let commandOptions;
6697
6573
  try {
6698
- opts = parseCommonOptions(values);
6574
+ commandOptions = parseRuntimeCommandOptions(values);
6699
6575
  } catch (err) {
6700
6576
  if (err instanceof MissingRequiredOptionError) {
6701
6577
  console.error(`${err.message}\n`);
6702
- console.error(ONCE_HELP);
6578
+ console.error(opts.helpText);
6703
6579
  return 1;
6704
6580
  }
6705
6581
  throw err;
6706
6582
  }
6583
+ const { identity, operations } = commandOptions;
6584
+ const pollIntervalMs = optionalPositiveInt(values["poll-interval-ms"], "poll-interval-ms", 2e3);
6585
+ const maxPollIntervalMs = optionalPositiveInt(values["max-poll-interval-ms"], "max-poll-interval-ms", 3e4);
6586
+ const listLimit = optionalPositiveInt(values["list-limit"], "list-limit", 10);
6587
+ const waitForFirstTaskSec = optionalNonNegativeInt(values["wait-for-first-task-sec"], "wait-for-first-task-sec", 0);
6588
+ const waitAfterTaskSec = optionalNonNegativeInt(values["wait-after-task-sec"], "wait-after-task-sec", 0);
6589
+ if (!opts.stopWhenEmpty && (waitForFirstTaskSec > 0 || waitAfterTaskSec > 0)) {
6590
+ console.error(`[${opts.modeLabel}] --wait-for-first-task-sec and --wait-after-task-sec are only valid with drain.`);
6591
+ return 1;
6592
+ }
6707
6593
  if (values.sandbox) {
6708
- console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
6594
+ console.error(`[${opts.modeLabel}] Cannot use --sandbox. Remote runtime profiles define sandbox policy.`);
6709
6595
  return 1;
6710
6596
  }
6597
+ 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
6598
  const cfg = loadConfig();
6712
6599
  const credentialSources = {
6713
6600
  profileRequirements: cfg.profileCredentialRequirements,
6714
6601
  bindings: cfg.credentialBindings
6715
6602
  };
6716
6603
  const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
6717
- const initialOpts = opts;
6718
6604
  const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
6719
- const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
6605
+ const { ctx, signingPrivateKey, startupWhoami, agentIdentity, hostCapabilitySigner } = await (async () => {
6720
6606
  let gate = "resolve_agent_context";
6721
6607
  try {
6722
- const resolvedContext = await resolveAgentContext(initialOpts.agent, {
6608
+ const resolvedContext = await resolveAgentContext(identity.agent, {
6723
6609
  agentRootDir: explicitAgentRootDir,
6724
6610
  credentialSource: cfg.credentialSource,
6725
6611
  envApiUrl: cfg.apiUrl
@@ -6727,7 +6613,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6727
6613
  gate = "authenticate_and_bind";
6728
6614
  const whoami = await validateStartupBinding({
6729
6615
  agent: resolvedContext.agent,
6730
- teamId: values.team,
6616
+ teamId,
6731
6617
  expectedAgent: cfg.expectedAgent
6732
6618
  });
6733
6619
  gate = "resolve_signing_material";
@@ -6746,7 +6632,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6746
6632
  });
6747
6633
  gate = "resolve_agent_identity";
6748
6634
  const agentIdentity = await resolveDaemonAgentIdentity({
6749
- agentName: initialOpts.agent,
6635
+ agentName: identity.agent,
6750
6636
  whoami,
6751
6637
  credentialSource: cfg.credentialSource,
6752
6638
  agentDir: resolvedContext.agentDir,
@@ -6755,6 +6641,7 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6755
6641
  return {
6756
6642
  ctx: resolvedContext,
6757
6643
  signingPrivateKey: privateKey,
6644
+ startupWhoami: whoami,
6758
6645
  agentIdentity,
6759
6646
  hostCapabilitySigner: createLocalSeedSigner({
6760
6647
  privateKeySeed: privateKey,
@@ -6764,10 +6651,10 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6764
6651
  };
6765
6652
  } catch (error) {
6766
6653
  await logDaemonStartupFailure({
6767
- serviceName: "agent-daemon.once",
6768
- level: cfg.logLevel || (initialOpts.debug ? "debug" : "info"),
6654
+ serviceName: `agent-daemon.${opts.modeLabel}`,
6655
+ level: cfg.logLevel || (identity.debug ? "debug" : "info"),
6769
6656
  gate,
6770
- agent: initialOpts.agent,
6657
+ agent: identity.agent,
6771
6658
  credentialSource: cfg.credentialSource,
6772
6659
  error
6773
6660
  });
@@ -6775,39 +6662,27 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6775
6662
  }
6776
6663
  })();
6777
6664
  const daemonRootDir = explicitAgentRootDir ?? process.cwd();
6778
- const profile = await resolveRuntimeProfile({
6665
+ const resolvedProfiles = await resolveRuntimeProfiles({
6779
6666
  agent: ctx.agent,
6780
- profile: values.profile,
6781
- teamId: values.team,
6667
+ profiles: profileValues,
6668
+ teamId,
6782
6669
  cwd: daemonRootDir
6783
6670
  });
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
6671
+ const { logger, shutdown: shutdownLogger } = createRootLogger({
6672
+ name: `agent-daemon.${opts.modeLabel}`,
6673
+ level: cfg.logLevel || (identity.debug ? "debug" : "info")
6789
6674
  });
6790
- assertGuestEnvironmentBoundary({
6791
- forwardEnv: profile.requiredEnv,
6792
- sandboxEnv: profile.sandboxConfig.env
6675
+ const rootLogger = logger.child({
6676
+ mode: opts.modeLabel,
6677
+ agent: identity.agent,
6678
+ teamId,
6679
+ runtimeProfileIds: resolvedProfiles.map((profile) => profile.id),
6680
+ runtimeProfileNames: resolvedProfiles.map((profile) => profile.name)
6793
6681
  });
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);
6682
+ const runtimeAdapter = opts.runtimeAdapter ?? defaultPiDaemonAdapter;
6683
+ const profiles = [];
6684
+ const runtimes = /* @__PURE__ */ new Map();
6685
+ const skippedProfileBoundaries = [];
6811
6686
  const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
6812
6687
  const runtimeSessionStore = createApiRuntimeSessionStore({
6813
6688
  agent: ctx.agent,
@@ -6815,110 +6690,68 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6815
6690
  });
6816
6691
  const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
6817
6692
  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
- });
6693
+ for (const profile of resolvedProfiles) try {
6694
+ const prepared = await prepareRuntimeProfile({
6695
+ agent: ctx.agent,
6696
+ agentName: identity.agent,
6697
+ profile,
6698
+ prerequisiteEnv: cfg.profilePrerequisiteEnv,
6699
+ runtimeAdapter,
6700
+ runtimeInstanceId,
6701
+ signingPrivateKey,
6702
+ slotRegistry,
6703
+ runtimeSessionStore,
6704
+ sourceAttemptResolver,
6705
+ warmRetentionSec: operations.warmRetentionSec
6706
+ });
6707
+ runtimes.set(profile.id, prepared);
6708
+ profiles.push(profile);
6709
+ } catch (error) {
6710
+ if (!(error instanceof GuestEnvironmentBoundaryError)) throw error;
6711
+ skippedProfileBoundaries.push({
6712
+ profile,
6713
+ error
6714
+ });
6715
+ }
6716
+ if (profiles.length === 0) {
6717
+ const details = skippedProfileBoundaries.map(({ profile, error }) => `${profile.name}: ${error.message}`).join("; ");
6718
+ throw new Error(`No safe runtime profiles remain. ${details}`);
6719
+ }
6720
+ const piAgentDir = await resolvePiAgentDir(cfg, profiles[0].mountPath, profiles);
6721
+ process.once("exit", piAgentDir.cleanup);
6722
+ activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
6723
+ if (!runtimes.has(profiles[0].id)) throw new Error("No runtime profiles resolved");
6835
6724
  const otelShutdown = await initWorkerOtel({
6836
- serviceName: "moltnet.agent-daemon.once",
6725
+ serviceName: opts.serviceName,
6837
6726
  agent: ctx.agent,
6838
6727
  endpoint: cfg.otelEndpoint,
6839
6728
  resourceAttributes: {
6840
- "moltnet.task.id": taskId,
6841
- "moltnet.agent.name": opts.agent,
6729
+ "moltnet.team.id": teamId,
6730
+ "moltnet.agent.name": identity.agent,
6842
6731
  "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
6732
+ "moltnet.runtime_profile.count": String(profiles.length),
6733
+ "moltnet.runtime_profile.ids": profiles.map((p) => p.id).join(",")
6851
6734
  }
6852
6735
  });
6853
- const { logger, shutdown: shutdownLogger } = createRootLogger({
6854
- name: "agent-daemon.once",
6855
- level: cfg.logLevel || (opts.debug ? "debug" : "info")
6856
- });
6857
- 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,
6736
+ for (const { profile, error } of skippedProfileBoundaries) rootLogger.warn({
6867
6737
  runtimeProfileId: profile.id,
6868
- runtimeProfileName: profile.name
6869
- });
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
- }
6738
+ runtimeProfileName: profile.name,
6739
+ refusedEnvironmentNames: error.refusedNames
6740
+ }, "agent-daemon.runtime_profile_skipped_credential_boundary");
6741
+ const abort = new AbortController();
6904
6742
  let runtime = null;
6905
- let activeAttemptN = null;
6743
+ let active = null;
6906
6744
  const signalHandlers = installShutdownSignalHandlers({
6907
6745
  logDrain: (signal) => {
6908
- rootLogger.warn({
6909
- signal,
6910
- taskId
6911
- }, "agent-daemon.draining");
6746
+ rootLogger.warn({ signal }, "agent-daemon.draining");
6912
6747
  },
6913
6748
  drain: (signal) => {
6749
+ abort.abort();
6914
6750
  runtime?.stop(`agent-daemon received ${signal}`);
6915
6751
  abortActiveAttemptOnSignal({
6916
- active: activeAttemptN === null ? null : {
6917
- taskId,
6918
- attemptN: activeAttemptN
6919
- },
6752
+ active,
6920
6753
  signal,
6921
- abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
6754
+ abortAttempt: (taskId, attemptN, body) => ctx.agent.tasks.abortAttempt(taskId, attemptN, body),
6922
6755
  logFailure: (err, current) => {
6923
6756
  try {
6924
6757
  rootLogger.warn({
@@ -6926,71 +6759,232 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
6926
6759
  taskId: current.taskId,
6927
6760
  attemptN: current.attemptN
6928
6761
  }, "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
- }
6762
+ } catch {}
6932
6763
  }
6933
6764
  });
6934
6765
  }
6935
6766
  });
6767
+ rootLogger.info({
6768
+ credentialSource: cfg.credentialSource,
6769
+ subjectType: startupWhoami.subjectType,
6770
+ bindingScope: startupWhoami.credentialBinding?.bindingScope ?? null,
6771
+ credentialKeyId: startupWhoami.credentialBinding?.keyId ?? null,
6772
+ boundTeamId: startupWhoami.credentialBinding?.bindingScope === "team" ? startupWhoami.credentialBinding.boundTeamId : null,
6773
+ taskTypes: taskTypes.length > 0 ? taskTypes : ["*"],
6774
+ correlationId: values["correlation-id"] ?? null,
6775
+ diaryIds: diaryIds.length > 0 ? diaryIds : ["*"],
6776
+ pollIntervalMs,
6777
+ maxPollIntervalMs,
6778
+ profiles: profiles.map((profile) => {
6779
+ const runtime = requireRuntime(runtimes, profile.id);
6780
+ return {
6781
+ id: profile.id,
6782
+ name: profile.name,
6783
+ provider: profile.provider,
6784
+ model: profile.model,
6785
+ thinkingLevel: profile.thinkingLevel,
6786
+ temperature: profile.temperature,
6787
+ topP: profile.topP,
6788
+ topK: profile.topK,
6789
+ maxOutputTokens: profile.maxOutputTokens,
6790
+ sandbox: runtime.sandbox.path,
6791
+ heartbeatIntervalMs: operations.heartbeatIntervalMs,
6792
+ maxTurns: profile.maxTurns,
6793
+ maxBashTimeouts: profile.maxBashTimeouts,
6794
+ warmRetentionSec: operations.warmRetentionSec,
6795
+ defaultWorkspaceMode: profile.defaultWorkspaceMode,
6796
+ allowedWorkspaceModes: profile.allowedWorkspaceModes
6797
+ };
6798
+ }),
6799
+ piAgentDir: piAgentDir.path,
6800
+ piAgentDirSource: piAgentDir.source
6801
+ }, "agent-daemon.starting");
6802
+ let reaperRunning = false;
6803
+ const reapRuntimeResources = async () => {
6804
+ if (reaperRunning) return;
6805
+ reaperRunning = true;
6806
+ try {
6807
+ for (const profile of profiles) {
6808
+ const selected = requireRuntime(runtimes, profile.id);
6809
+ const result = await reapRuntimeSlotResources({
6810
+ onIssue: (issue) => {
6811
+ rootLogger.warn({
6812
+ ...issue,
6813
+ runtimeProfileId: profile.id
6814
+ }, "agent-daemon.runtime_resource_reap_issue");
6815
+ },
6816
+ runtimeSlotStore: slotRegistry,
6817
+ taskReader: ctx.agent.tasks
6818
+ }, {
6819
+ agentName: identity.agent,
6820
+ mainWorktree: resolveMainWorktree$1(selected.sandbox.rootDir),
6821
+ runtimeInstanceId,
6822
+ runtimeProfileId: profile.id,
6823
+ sessionRootDir: selected.stateDirs.piSessionsDir,
6824
+ scratchRootDir: join(selected.stateDirs.rootDir, "task-workspaces"),
6825
+ teamId
6826
+ });
6827
+ if (result.removedSessions > 0 || result.removedWorkspaces > 0 || result.failed > 0 || result.unsafePaths > 0 || result.truncated) rootLogger.info({
6828
+ ...result,
6829
+ runtimeProfileId: profile.id
6830
+ }, "agent-daemon.runtime_resources_reaped");
6831
+ }
6832
+ } catch (err) {
6833
+ rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
6834
+ } finally {
6835
+ reaperRunning = false;
6836
+ }
6837
+ };
6838
+ await reapRuntimeResources();
6839
+ const reaperTimer = setInterval(() => {
6840
+ reapRuntimeResources();
6841
+ }, 6e4);
6842
+ reaperTimer.unref();
6843
+ const outputs = [];
6936
6844
  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);
6845
+ runtime = new AgentRuntime({
6846
+ logger: rootLogger,
6847
+ source: new PollingApiTaskSource({
6848
+ agent: ctx.agent,
6849
+ teamId,
6850
+ taskTypes: taskTypes.length > 0 ? taskTypes : void 0,
6851
+ correlationId: values["correlation-id"],
6852
+ profiles: profiles.map((profile) => ({ profileId: profile.id })),
6853
+ diaryIds: diaryIds.length > 0 ? diaryIds : void 0,
6854
+ listLimit,
6855
+ pollIntervalMs,
6856
+ maxPollIntervalMs,
6857
+ signal: abort.signal,
6858
+ stopWhenEmpty: opts.stopWhenEmpty,
6859
+ waitForFirstTaskMs: waitForFirstTaskSec * 1e3,
6860
+ waitAfterTaskMs: waitAfterTaskSec * 1e3,
6861
+ debug: identity.debug,
6862
+ traceIdlePolling: cfg.traceIdlePolling,
6863
+ logger: rootLogger,
6864
+ executorFingerprints: Object.fromEntries(profiles.map((profile) => [profile.id, requireRuntime(runtimes, profile.id).preparedRuntime.attestor.fingerprint])),
6865
+ slotRegistry,
6866
+ sessionRegistry: runtimeSessionStore,
6867
+ sourceAttemptResolver
6868
+ }),
6869
+ makeReporter: (claimedTask) => {
6870
+ return new ApiTaskReporter({
6871
+ tasks: ctx.agent.tasks,
6872
+ teamId: claimedTask.task.teamId,
6873
+ heartbeatIntervalMs: operations.heartbeatIntervalMs,
6874
+ logger: rootLogger
6875
+ });
6961
6876
  },
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({
6877
+ onTaskFinished: async (output, claimedTask) => {
6878
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
6879
+ const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
6880
+ let terminalOutput = redactRequiredEnvValues(output, selected.profile.requiredEnv, cfg.profilePrerequisiteEnv);
6881
+ if (resolved?.session?.sessionDir) try {
6882
+ const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
6883
+ await runtimeSessionStore.uploadAttemptFinal({
6884
+ attemptN: claimedTask.attemptN,
6885
+ parentSessionId: parentSession?.id ?? null,
6886
+ sessionDir: resolved.session.sessionDir,
6887
+ sessionKind: resolveRuntimeSessionKind(claimedTask),
6888
+ sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
6889
+ sourceSlotId: resolved.slot.id,
6890
+ taskId: claimedTask.task.id,
6891
+ teamId: claimedTask.task.teamId
6892
+ });
6893
+ } catch (err) {
6894
+ rootLogger.error({
6895
+ err,
6896
+ taskId: claimedTask.task.id,
6897
+ attemptN: claimedTask.attemptN
6898
+ }, "agent-daemon.runtime_session_upload_failed");
6899
+ terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
6900
+ }
6901
+ return finalizeTask(ctx.agent, terminalOutput, {
6902
+ task: claimedTask.task,
6903
+ slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
6904
+ retryTriage: createRuntimeProfileRetryTriage({
6905
+ runtimeProfile: selected.profile,
6906
+ piAgentDir: piAgentDir.path,
6907
+ cwd: ctx.agentRootDir
6908
+ }),
6909
+ executorAttestor: selected.preparedRuntime.attestor,
6910
+ writeCorrelationAnchors: makePrBodyAnchorWriter({
6911
+ gh: createGhCliClient(),
6912
+ logger: rootLogger.child({
6913
+ runtimeProfileId: selected.profile.id,
6914
+ runtimeProfileName: selected.profile.name
6915
+ })
6916
+ }),
6917
+ log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
6918
+ });
6919
+ },
6920
+ executeTask: async (claimedTask, reporter) => {
6921
+ const selected = runtimeForClaimedTask(runtimes, claimedTask);
6922
+ const { executionPlans, profile, sandbox, slotIdentity, stateDirs } = selected;
6923
+ if (runtimeCredentialConfig) await observeGovernancePlanSafely({
6924
+ config: runtimeCredentialConfig,
6925
+ profile,
6926
+ offer: runtimeExecutionOffer(selected.preparedRuntime, selected.preparedRuntime.attestor.fingerprint),
6927
+ registry: createNodeSecretProviderRegistry(),
6928
+ executorFingerprint: selected.preparedRuntime.attestor.fingerprint,
6929
+ claimAuthority: claimedTask.claimAuthority ?? {},
6930
+ taskId: claimedTask.task.id,
6931
+ attemptN: claimedTask.attemptN,
6932
+ logger: rootLogger
6933
+ });
6934
+ const taskLogger = rootLogger.child({
6935
+ runtimeProfileId: profile.id,
6936
+ runtimeProfileName: profile.name,
6937
+ provider: profile.provider,
6938
+ model: profile.model,
6939
+ thinkingLevel: profile.thinkingLevel,
6940
+ temperature: profile.temperature,
6941
+ topP: profile.topP,
6942
+ topK: profile.topK,
6943
+ maxOutputTokens: profile.maxOutputTokens
6944
+ });
6945
+ let executionPlan;
6946
+ try {
6947
+ executionPlan = await executionPlans.getOrCreate(claimedTask);
6948
+ } catch (err) {
6949
+ const message = err instanceof Error ? err.message : String(err);
6950
+ taskLogger.warn({
6951
+ taskId: claimedTask.task.id,
6952
+ attemptN: claimedTask.attemptN,
6953
+ err: message
6954
+ }, "agent-daemon.execution_plan_failed");
6955
+ return {
6956
+ taskId: claimedTask.task.id,
6957
+ attemptN: claimedTask.attemptN,
6958
+ status: "failed",
6959
+ output: null,
6960
+ outputCid: null,
6961
+ usage: {
6962
+ inputTokens: 0,
6963
+ outputTokens: 0
6964
+ },
6965
+ durationMs: 0,
6966
+ error: {
6967
+ code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
6968
+ message,
6969
+ retryable: false
6970
+ }
6971
+ };
6972
+ }
6973
+ const sessionDescriptor = executionPlan.descriptor;
6974
+ taskLogger.debug({
6989
6975
  taskId: claimedTask.task.id,
6990
- attemptN: claimedTask.attemptN,
6991
- err: message
6992
- }, "agent-daemon.execution_plan_failed");
6993
- return {
6976
+ taskType: claimedTask.task.taskType,
6977
+ resumable: sessionDescriptor.policy.resumable,
6978
+ workspaceMode: executionPlan.workspaceMode,
6979
+ workspaceScope: sessionDescriptor.policy.workspaceScope,
6980
+ sessionScope: sessionDescriptor.policy.sessionScope,
6981
+ slotKey: executionPlan.slotKey,
6982
+ slotId: executionPlan.slotId,
6983
+ sessionKey: executionPlan.sessionKey,
6984
+ piSessionDir: executionPlan.sessionPersistence?.sessionDir ?? null,
6985
+ workspaceId: executionPlan.workspaceId
6986
+ }, "agent-daemon.task_execution_policy");
6987
+ if (taskTypes.length > 0 && !taskTypes.includes(claimedTask.task.taskType)) return {
6994
6988
  taskId: claimedTask.task.id,
6995
6989
  attemptN: claimedTask.attemptN,
6996
6990
  status: "failed",
@@ -7002,513 +6996,611 @@ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
7002
6996
  },
7003
6997
  durationMs: 0,
7004
6998
  error: {
7005
- code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
7006
- message,
6999
+ code: "unsupported_task_type",
7000
+ message: `Daemon does not support task type "${claimedTask.task.taskType}". Configured types: ${taskTypes.join(", ")}.`,
7001
+ retryable: true
7002
+ }
7003
+ };
7004
+ if (reporter.cancelSignal.aborted) return {
7005
+ taskId: claimedTask.task.id,
7006
+ attemptN: claimedTask.attemptN,
7007
+ status: "cancelled",
7008
+ output: null,
7009
+ outputCid: null,
7010
+ usage: {
7011
+ inputTokens: 0,
7012
+ outputTokens: 0
7013
+ },
7014
+ durationMs: 0,
7015
+ error: {
7016
+ code: "task_cancelled",
7017
+ message: reporter.cancelReason ?? "Task cancelled before executor started.",
7007
7018
  retryable: false
7008
7019
  }
7009
7020
  };
7010
- }
7011
- if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7012
- ...slotIdentity,
7013
- runtimeProfileId: profile.id,
7014
- provider: profile.provider,
7015
- 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,
7021
+ if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7022
+ ...slotIdentity,
7023
+ runtimeProfileId: profile.id,
7024
+ provider: profile.provider,
7025
+ model: profile.model,
7026
+ teamId: claimedTask.task.teamId,
7027
+ slotKey: executionPlan.slotKey,
7028
+ taskType: claimedTask.task.taskType,
7029
+ sessionDir: executionPlan.sessionPersistence.sessionDir,
7030
+ sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
7031
+ workspaceId: executionPlan.workspaceId,
7032
+ worktreePath: resolveRecordedWorkspacePath$1(stateDirs.rootDir, sandbox.rootDir, executionPlan),
7033
+ worktreeBranch: executionPlan.worktreeBranch,
7034
+ workspaceKind: executionPlan.workspaceKind,
7035
+ lastTaskId: claimedTask.task.id,
7036
+ lastAttemptN: claimedTask.attemptN,
7037
+ warmRetentionSec: operations.warmRetentionSec
7038
+ });
7039
+ const rawExecuteTask = selected.preparedRuntime.createTaskExecutor({
7040
+ agentName: identity.agent,
7041
+ moltnetAgent: ctx.agent,
7042
+ agentIdentity,
7043
+ hostCapabilitySigner,
7044
+ hostCapabilityLogger: taskLogger,
7045
+ agentRootDir: ctx.agentRootDir,
7046
+ mountPath: sandbox.rootDir,
7033
7047
  provider: profile.provider,
7034
7048
  model: profile.model,
7035
7049
  thinkingLevel: profile.thinkingLevel,
7036
7050
  temperature: profile.temperature,
7037
7051
  topP: profile.topP,
7038
7052
  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);
7045
- }
7046
- };
7047
- const writeCorrelationAnchors = makePrBodyAnchorWriter({
7048
- gh: createGhCliClient(),
7049
- logger: rootLogger
7050
- });
7051
- runtime = new AgentRuntime({
7052
- logger: rootLogger,
7053
- source: new ApiTaskSource({
7054
- 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
7068
- }),
7069
- onTaskFinished: async (output, claimedTask) => {
7070
- const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
7071
- let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
7072
- if (resolved?.session?.sessionDir) try {
7073
- const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
7074
- await runtimeSessionStore.uploadAttemptFinal({
7075
- attemptN: claimedTask.attemptN,
7076
- parentSessionId: parentSession?.id ?? null,
7077
- sessionDir: resolved.session.sessionDir,
7078
- sessionKind: resolveRuntimeSessionKind(claimedTask),
7079
- sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
7080
- sourceSlotId: resolved.slot.id,
7081
- taskId: claimedTask.task.id,
7082
- teamId: claimedTask.task.teamId
7083
- });
7084
- } catch (err) {
7085
- rootLogger.error({
7086
- err,
7053
+ maxOutputTokens: profile.maxOutputTokens,
7054
+ sandboxConfig: sandbox.config,
7055
+ forwardEnv: profile.requiredEnv,
7056
+ onVmDiagnostic: (diagnostic) => {
7057
+ const fields = {
7058
+ event: diagnostic.event,
7059
+ ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
7060
+ };
7061
+ if (diagnostic.level === "warning") taskLogger.warn(fields, diagnostic.message);
7062
+ else taskLogger.info(fields, diagnostic.message);
7063
+ },
7064
+ runtimeProfileContext: profile.context,
7065
+ runtimeProfileId: profile.id,
7066
+ toolEnforcement: profile.toolEnforcement,
7067
+ makeExecutionPlan: (task) => executionPlans.getOrCreate(task),
7068
+ makeOnTurnEvent: makeTurnEventHandlerFactory(taskLogger),
7069
+ toolPolicyLogger: taskLogger,
7070
+ maxTurns: profile.maxTurns,
7071
+ maxBashTimeouts: profile.maxBashTimeouts
7072
+ });
7073
+ try {
7074
+ active = {
7087
7075
  taskId: claimedTask.task.id,
7088
7076
  attemptN: claimedTask.attemptN
7089
- }, "agent-daemon.runtime_session_upload_failed");
7090
- terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
7077
+ };
7078
+ return await runWithDaemonRuntimeContext({
7079
+ profileId: profile.id,
7080
+ profileName: profile.name,
7081
+ provider: profile.provider,
7082
+ model: profile.model,
7083
+ thinkingLevel: profile.thinkingLevel,
7084
+ temperature: profile.temperature,
7085
+ topP: profile.topP,
7086
+ topK: profile.topK,
7087
+ maxOutputTokens: profile.maxOutputTokens
7088
+ }, () => rawExecuteTask(claimedTask, reporter));
7089
+ } finally {
7090
+ active = null;
7091
+ executionPlans.delete(claimedTask);
7092
+ 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, operations.warmRetentionSec);
7091
7093
  }
7092
- return finalizeTask(ctx.agent, terminalOutput, {
7093
- task: claimedTask.task,
7094
- slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
7095
- retryTriage: createRuntimeProfileRetryTriage({
7096
- runtimeProfile: profile,
7097
- piAgentDir: piAgentDir.path,
7098
- cwd: ctx.agentRootDir
7099
- }),
7100
- executorAttestor: preparedRuntime.attestor,
7101
- writeCorrelationAnchors,
7102
- log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
7103
- });
7104
- },
7105
- executeTask
7094
+ }
7106
7095
  });
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;
7096
+ const drained = await runtime.start();
7097
+ outputs.push(...drained);
7098
+ rootLogger.info({ processed: drained.length }, "agent-daemon.drained");
7099
+ return drained.some((o) => o.status !== "completed") ? 1 : 0;
7115
7100
  } 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`;
7101
+ clearInterval(reaperTimer);
7102
+ signalHandlers.dispose();
7103
+ await slotRegistry.close();
7104
+ await otelShutdown();
7105
+ await shutdownLogger();
7106
+ }
7182
7107
  }
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;
7108
+ function resolveRecordedWorkspacePath$1(stateRootDir, mountPath, executionPlan) {
7109
+ if (!executionPlan.workspaceId) return null;
7110
+ return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
7187
7111
  }
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
- }
7112
+ function resolveMainWorktree$1(mountPath) {
7196
7113
  try {
7197
- return JSON.parse(raw);
7198
- } catch (cause) {
7199
- throw new AgentServerStoreError("invalid_state", `corrupt JSON at ${path}`, { cause });
7114
+ return findMainWorktree(mountPath);
7115
+ } catch {
7116
+ return null;
7200
7117
  }
7201
7118
  }
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
- }
7119
+ function runtimeForClaimedTask(runtimes, claimedTask) {
7120
+ if (!claimedTask.profileId) throw new Error(`Claimed task ${claimedTask.task.id} did not include a selected runtime profile`);
7121
+ return requireRuntime(runtimes, claimedTask.profileId);
7213
7122
  }
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)
7292
- });
7293
- }
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);
7313
- }
7314
- removeAgentConfig(alias) {
7315
- rmSync(this.agentPath(alias), { force: true });
7316
- this.clearIdentitySelectorIfDefault(alias);
7123
+ function requireRuntime(runtimes, profileId) {
7124
+ const runtime = runtimes.get(profileId);
7125
+ if (!runtime) throw new Error(`No runtime profile configured for ${profileId}`);
7126
+ return runtime;
7127
+ }
7128
+ function parseProfileValues(raw) {
7129
+ return (raw ?? []).map((s) => s.trim()).filter((s) => s.length > 0);
7130
+ }
7131
+ function parseCsv(raw) {
7132
+ return (raw ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
7133
+ }
7134
+ function optionalPositiveInt(raw, name, defaultValue) {
7135
+ if (raw === void 0) return defaultValue;
7136
+ const v = Number(raw);
7137
+ if (!Number.isInteger(v) || v < 1) throw new Error(`Invalid --${name} "${raw}": must be a positive integer`);
7138
+ return v;
7139
+ }
7140
+ function optionalNonNegativeInt(raw, name, defaultValue) {
7141
+ if (raw === void 0) return defaultValue;
7142
+ const v = Number(raw);
7143
+ if (!Number.isInteger(v) || v < 0) throw new Error(`Invalid --${name} "${raw}": must be a non-negative integer`);
7144
+ return v;
7145
+ }
7146
+ //#endregion
7147
+ //#region src/cli/drain.ts
7148
+ function runDrain(argv, runtimeAdapter) {
7149
+ return runPolling({
7150
+ argv,
7151
+ serviceName: "moltnet.agent-daemon.drain",
7152
+ stopWhenEmpty: true,
7153
+ modeLabel: "drain",
7154
+ helpText: DRAIN_HELP,
7155
+ runtimeAdapter
7156
+ });
7157
+ }
7158
+ //#endregion
7159
+ //#region src/cli/once.ts
7160
+ async function runOnce(argv, runtimeAdapter = defaultPiDaemonAdapter) {
7161
+ if (isHelpFlag(argv)) {
7162
+ console.log(ONCE_HELP);
7163
+ return 0;
7317
7164
  }
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;
7165
+ const { values } = parseArgs({
7166
+ args: argv,
7167
+ options: {
7168
+ ...runtimeCommandOptionDefs(),
7169
+ "task-id": {
7170
+ type: "string",
7171
+ short: "t"
7172
+ },
7173
+ team: { type: "string" },
7174
+ sandbox: { type: "string" },
7175
+ profile: { type: "string" }
7325
7176
  }
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;
7177
+ });
7178
+ if (!values["task-id"]) {
7179
+ console.error("Missing required flag: --task-id\n");
7180
+ console.error(ONCE_HELP);
7181
+ return 1;
7369
7182
  }
7370
- writeProviders(state) {
7371
- this.validateProviders(state);
7372
- writeJsonAtomic(this.providersPath, state);
7183
+ const taskId = values["task-id"];
7184
+ if (!values.profile) {
7185
+ console.error("Missing required flag: --profile\n");
7186
+ console.error(ONCE_HELP);
7187
+ return 1;
7373
7188
  }
7374
- validateProviders(state) {
7375
- for (const [id, provider] of Object.entries(state)) {
7376
- assertProviderId(id);
7377
- assertProviderEnvName(id, provider.envName);
7189
+ let commandOptions;
7190
+ try {
7191
+ commandOptions = parseRuntimeCommandOptions(values);
7192
+ } catch (err) {
7193
+ if (err instanceof MissingRequiredOptionError) {
7194
+ console.error(`${err.message}\n`);
7195
+ console.error(ONCE_HELP);
7196
+ return 1;
7378
7197
  }
7198
+ throw err;
7379
7199
  }
7380
- runDir(id) {
7381
- return storeChildPath(this.runsDir, "run id", id);
7200
+ const { identity, operations } = commandOptions;
7201
+ if (values.sandbox) {
7202
+ console.error("Cannot use --sandbox. Remote runtime profiles define sandbox policy.");
7203
+ return 1;
7382
7204
  }
7383
- resolveRunLogPath(id) {
7384
- let root;
7385
- let runDir;
7205
+ const cfg = loadConfig();
7206
+ const credentialSources = {
7207
+ profileRequirements: cfg.profileCredentialRequirements,
7208
+ bindings: cfg.credentialBindings
7209
+ };
7210
+ const runtimeCredentialConfig = resolveCredentialEnforcement(cfg.credentialEnforcement, credentialSources) === "off" ? null : loadRuntimeCredentialConfig(credentialSources);
7211
+ const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
7212
+ const { ctx, signingPrivateKey, agentIdentity, hostCapabilitySigner } = await (async () => {
7213
+ let gate = "resolve_agent_context";
7386
7214
  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 });
7215
+ const resolvedContext = await resolveAgentContext(identity.agent, {
7216
+ agentRootDir: explicitAgentRootDir,
7217
+ credentialSource: cfg.credentialSource,
7218
+ envApiUrl: cfg.apiUrl
7219
+ });
7220
+ gate = "authenticate_and_bind";
7221
+ const whoami = await validateStartupBinding({
7222
+ agent: resolvedContext.agent,
7223
+ teamId: values.team,
7224
+ expectedAgent: cfg.expectedAgent
7225
+ });
7226
+ gate = "resolve_signing_material";
7227
+ const privateKey = await resolveExecutorSigningPrivateKey({
7228
+ credentialSource: cfg.credentialSource,
7229
+ agentDir: resolvedContext.agentDir,
7230
+ configuredPrivateKey: cfg.signingPrivateKey,
7231
+ configuredPrivateKeyRef: cfg.signingPrivateKeyRef
7232
+ });
7233
+ gate = "validate_scopes";
7234
+ validateDaemonScopes(whoami);
7235
+ gate = "validate_signing_identity";
7236
+ await validateExecutorSigningIdentity({
7237
+ whoami,
7238
+ signingPrivateKey: privateKey
7239
+ });
7240
+ gate = "resolve_agent_identity";
7241
+ const agentIdentity = await resolveDaemonAgentIdentity({
7242
+ agentName: identity.agent,
7243
+ whoami,
7244
+ credentialSource: cfg.credentialSource,
7245
+ agentDir: resolvedContext.agentDir,
7246
+ gitAuthor: values["git-author"] ?? (cfg.gitAuthor || void 0)
7247
+ });
7248
+ return {
7249
+ ctx: resolvedContext,
7250
+ signingPrivateKey: privateKey,
7251
+ agentIdentity,
7252
+ hostCapabilitySigner: createLocalSeedSigner({
7253
+ privateKeySeed: privateKey,
7254
+ agent: resolvedContext.agent,
7255
+ identity: agentIdentity
7256
+ })
7257
+ };
7258
+ } catch (error) {
7259
+ await logDaemonStartupFailure({
7260
+ serviceName: "agent-daemon.once",
7261
+ level: cfg.logLevel || (identity.debug ? "debug" : "info"),
7262
+ gate,
7263
+ agent: identity.agent,
7264
+ credentialSource: cfg.credentialSource,
7265
+ error
7266
+ });
7267
+ throw error;
7391
7268
  }
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 });
7269
+ })();
7270
+ const daemonRootDir = explicitAgentRootDir ?? process.cwd();
7271
+ const profile = await resolveRuntimeProfile({
7272
+ agent: ctx.agent,
7273
+ profile: values.profile,
7274
+ teamId: values.team,
7275
+ cwd: daemonRootDir
7276
+ });
7277
+ const { logger, shutdown: shutdownLogger } = createRootLogger({
7278
+ name: "agent-daemon.once",
7279
+ level: cfg.logLevel || (identity.debug ? "debug" : "info")
7280
+ });
7281
+ const rootLogger = logger.child({
7282
+ mode: "once",
7283
+ agent: identity.agent,
7284
+ provider: profile.provider,
7285
+ model: profile.model,
7286
+ thinkingLevel: profile.thinkingLevel,
7287
+ temperature: profile.temperature,
7288
+ topP: profile.topP,
7289
+ topK: profile.topK,
7290
+ maxOutputTokens: profile.maxOutputTokens,
7291
+ runtimeProfileId: profile.id,
7292
+ runtimeProfileName: profile.name
7293
+ });
7294
+ const slotRegistry = createApiRuntimeSlotStore({ agent: ctx.agent });
7295
+ const runtimeSessionStore = createApiRuntimeSessionStore({
7296
+ agent: ctx.agent,
7297
+ logger: { warn: (context, message) => rootLogger.warn(context, message) }
7298
+ });
7299
+ const sourceAttemptResolver = createApiSourceAttemptResolver({ agent: ctx.agent });
7300
+ const runtimeInstanceId = createRuntimeInstanceId();
7301
+ const { executionPlans, preparedRuntime, sandbox, slotIdentity, stateDirs } = await prepareRuntimeProfile({
7302
+ agent: ctx.agent,
7303
+ agentName: identity.agent,
7304
+ profile,
7305
+ prerequisiteEnv: cfg.profilePrerequisiteEnv,
7306
+ runtimeAdapter,
7307
+ runtimeInstanceId,
7308
+ signingPrivateKey,
7309
+ slotRegistry,
7310
+ runtimeSessionStore,
7311
+ sourceAttemptResolver,
7312
+ warmRetentionSec: operations.warmRetentionSec
7313
+ });
7314
+ const piAgentDir = await resolvePiAgentDir(cfg, sandbox.rootDir, [profile]);
7315
+ process.once("exit", piAgentDir.cleanup);
7316
+ activatePiCodingAgentDir(piAgentDir.path, piAgentDir.env);
7317
+ const otelShutdown = await initWorkerOtel({
7318
+ serviceName: "moltnet.agent-daemon.once",
7319
+ agent: ctx.agent,
7320
+ endpoint: cfg.otelEndpoint,
7321
+ resourceAttributes: {
7322
+ "moltnet.task.id": taskId,
7323
+ "moltnet.agent.name": identity.agent,
7324
+ "moltnet.credential.source": ctx.credentialSource,
7325
+ "moltnet.llm.provider": profile.provider,
7326
+ "moltnet.llm.model": profile.model,
7327
+ ...profile.thinkingLevel ? { "moltnet.llm.thinking_level": profile.thinkingLevel } : {},
7328
+ ...profile.temperature !== null ? { "moltnet.llm.temperature": String(profile.temperature) } : {},
7329
+ ...profile.topP !== null ? { "moltnet.llm.top_p": String(profile.topP) } : {},
7330
+ ...profile.topK !== null ? { "moltnet.llm.top_k": String(profile.topK) } : {},
7331
+ ...profile.maxOutputTokens !== null ? { "moltnet.llm.max_output_tokens": String(profile.maxOutputTokens) } : {},
7332
+ "moltnet.runtime_profile.id": profile.id
7401
7333
  }
7402
- return logPath;
7334
+ });
7335
+ rootLogger.info({
7336
+ sandbox: sandbox.path,
7337
+ taskId,
7338
+ heartbeatIntervalMs: operations.heartbeatIntervalMs,
7339
+ maxTurns: profile.maxTurns,
7340
+ maxBashTimeouts: profile.maxBashTimeouts,
7341
+ warmRetentionSec: operations.warmRetentionSec,
7342
+ profileId: profile.id,
7343
+ piAgentDir: piAgentDir.path,
7344
+ piAgentDirSource: piAgentDir.source
7345
+ }, "agent-daemon.starting");
7346
+ try {
7347
+ const reaped = await reapRuntimeSlotResources({
7348
+ onIssue: (issue) => {
7349
+ rootLogger.warn(issue, "agent-daemon.runtime_resource_reap_issue");
7350
+ },
7351
+ runtimeSlotStore: slotRegistry,
7352
+ taskReader: ctx.agent.tasks
7353
+ }, {
7354
+ agentName: identity.agent,
7355
+ mainWorktree: resolveMainWorktree(sandbox.rootDir),
7356
+ runtimeInstanceId,
7357
+ runtimeProfileId: profile.id,
7358
+ sessionRootDir: stateDirs.piSessionsDir,
7359
+ scratchRootDir: join(stateDirs.rootDir, "task-workspaces"),
7360
+ teamId: profile.teamId
7361
+ });
7362
+ if (reaped.removedSessions > 0 || reaped.removedWorkspaces > 0 || reaped.failed > 0 || reaped.unsafePaths > 0 || reaped.truncated) rootLogger.info(reaped, "agent-daemon.runtime_resources_reaped");
7363
+ } catch (err) {
7364
+ rootLogger.warn({ err }, "agent-daemon.runtime_resource_reap_failed");
7403
7365
  }
7404
- createRunDir(id) {
7405
- const dir = this.runDir(id);
7406
- const piDir = join(dir, "pi");
7407
- mkdirSync(piDir, {
7408
- recursive: true,
7409
- mode: 448
7366
+ let runtime = null;
7367
+ let activeAttemptN = null;
7368
+ const signalHandlers = installShutdownSignalHandlers({
7369
+ logDrain: (signal) => {
7370
+ rootLogger.warn({
7371
+ signal,
7372
+ taskId
7373
+ }, "agent-daemon.draining");
7374
+ },
7375
+ drain: (signal) => {
7376
+ runtime?.stop(`agent-daemon received ${signal}`);
7377
+ abortActiveAttemptOnSignal({
7378
+ active: activeAttemptN === null ? null : {
7379
+ taskId,
7380
+ attemptN: activeAttemptN
7381
+ },
7382
+ signal,
7383
+ abortAttempt: (activeTaskId, attemptN, body) => ctx.agent.tasks.abortAttempt(activeTaskId, attemptN, body),
7384
+ logFailure: (err, current) => {
7385
+ try {
7386
+ rootLogger.warn({
7387
+ err: err instanceof Error ? err.message : String(err),
7388
+ taskId: current.taskId,
7389
+ attemptN: current.attemptN
7390
+ }, "agent-daemon.abort_on_signal_failed");
7391
+ } catch (logErr) {
7392
+ process.stderr.write(`[agent-daemon] failed to log abort error: ` + (logErr instanceof Error ? logErr.message : String(logErr)) + "\n");
7393
+ }
7394
+ }
7395
+ });
7396
+ }
7397
+ });
7398
+ try {
7399
+ const rawExecuteTask = preparedRuntime.createTaskExecutor({
7400
+ agentName: identity.agent,
7401
+ moltnetAgent: ctx.agent,
7402
+ agentIdentity,
7403
+ hostCapabilitySigner,
7404
+ hostCapabilityLogger: rootLogger,
7405
+ agentRootDir: ctx.agentRootDir,
7406
+ mountPath: sandbox.rootDir,
7407
+ provider: profile.provider,
7408
+ model: profile.model,
7409
+ thinkingLevel: profile.thinkingLevel,
7410
+ temperature: profile.temperature,
7411
+ topP: profile.topP,
7412
+ topK: profile.topK,
7413
+ maxOutputTokens: profile.maxOutputTokens,
7414
+ sandboxConfig: sandbox.config,
7415
+ forwardEnv: profile.requiredEnv,
7416
+ onVmDiagnostic: (diagnostic) => {
7417
+ const fields = {
7418
+ event: diagnostic.event,
7419
+ ...diagnostic.brokeredSecretCount !== void 0 && { brokeredSecretCount: diagnostic.brokeredSecretCount }
7420
+ };
7421
+ if (diagnostic.level === "warning") rootLogger.warn(fields, diagnostic.message);
7422
+ else rootLogger.info(fields, diagnostic.message);
7423
+ },
7424
+ runtimeProfileContext: profile.context,
7425
+ runtimeProfileId: profile.id,
7426
+ toolEnforcement: profile.toolEnforcement,
7427
+ makeExecutionPlan: (claimedTask) => executionPlans.getOrCreate(claimedTask),
7428
+ onTurnEvent: makeTurnEventHandler(rootLogger, { taskId }),
7429
+ toolPolicyLogger: rootLogger,
7430
+ maxTurns: profile.maxTurns,
7431
+ maxBashTimeouts: profile.maxBashTimeouts
7410
7432
  });
7411
- return {
7412
- dir,
7413
- piDir,
7414
- logPath: join(dir, "daemon.log")
7433
+ const executeTask = async (claimedTask, reporter) => {
7434
+ if (runtimeCredentialConfig) await observeGovernancePlanSafely({
7435
+ config: runtimeCredentialConfig,
7436
+ profile,
7437
+ offer: runtimeExecutionOffer(preparedRuntime, preparedRuntime.attestor.fingerprint),
7438
+ registry: createNodeSecretProviderRegistry(),
7439
+ executorFingerprint: preparedRuntime.attestor.fingerprint,
7440
+ claimAuthority: claimedTask.claimAuthority ?? {},
7441
+ taskId: claimedTask.task.id,
7442
+ attemptN: claimedTask.attemptN,
7443
+ logger: rootLogger
7444
+ });
7445
+ let executionPlan;
7446
+ try {
7447
+ executionPlan = await executionPlans.getOrCreate(claimedTask);
7448
+ } catch (err) {
7449
+ const message = err instanceof Error ? err.message : String(err);
7450
+ rootLogger.warn({
7451
+ taskId: claimedTask.task.id,
7452
+ attemptN: claimedTask.attemptN,
7453
+ err: message
7454
+ }, "agent-daemon.execution_plan_failed");
7455
+ return {
7456
+ taskId: claimedTask.task.id,
7457
+ attemptN: claimedTask.attemptN,
7458
+ status: "failed",
7459
+ output: null,
7460
+ outputCid: null,
7461
+ usage: {
7462
+ inputTokens: 0,
7463
+ outputTokens: 0
7464
+ },
7465
+ durationMs: 0,
7466
+ error: {
7467
+ code: err instanceof ProducerContextResolutionError ? "producer_context_missing" : "execution_plan_failed",
7468
+ message,
7469
+ retryable: false
7470
+ }
7471
+ };
7472
+ }
7473
+ if (executionPlan.slotKey && executionPlan.sessionPersistence) await slotRegistry.beginSlot({
7474
+ ...slotIdentity,
7475
+ runtimeProfileId: profile.id,
7476
+ provider: profile.provider,
7477
+ model: profile.model,
7478
+ teamId: claimedTask.task.teamId,
7479
+ slotKey: executionPlan.slotKey,
7480
+ taskType: claimedTask.task.taskType,
7481
+ sessionDir: executionPlan.sessionPersistence.sessionDir,
7482
+ sessionPath: resolveLatestPiSessionPath(executionPlan.sessionPersistence.sessionDir),
7483
+ workspaceId: executionPlan.workspaceId,
7484
+ worktreePath: resolveRecordedWorkspacePath(stateDirs.rootDir, sandbox.rootDir, executionPlan),
7485
+ worktreeBranch: executionPlan.worktreeBranch,
7486
+ workspaceKind: executionPlan.workspaceKind,
7487
+ lastTaskId: claimedTask.task.id,
7488
+ lastAttemptN: claimedTask.attemptN,
7489
+ warmRetentionSec: operations.warmRetentionSec
7490
+ });
7491
+ activeAttemptN = claimedTask.attemptN;
7492
+ try {
7493
+ return await runWithDaemonRuntimeContext({
7494
+ profileId: profile.id,
7495
+ profileName: profile.name,
7496
+ provider: profile.provider,
7497
+ model: profile.model,
7498
+ thinkingLevel: profile.thinkingLevel,
7499
+ temperature: profile.temperature,
7500
+ topP: profile.topP,
7501
+ topK: profile.topK,
7502
+ maxOutputTokens: profile.maxOutputTokens
7503
+ }, () => rawExecuteTask(claimedTask, reporter));
7504
+ } finally {
7505
+ activeAttemptN = null;
7506
+ executionPlans.delete(claimedTask);
7507
+ 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, operations.warmRetentionSec);
7508
+ }
7415
7509
  };
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
7510
+ const writeCorrelationAnchors = makePrBodyAnchorWriter({
7511
+ gh: createGhCliClient(),
7512
+ logger: rootLogger
7513
+ });
7514
+ runtime = new AgentRuntime({
7515
+ logger: rootLogger,
7516
+ source: new ApiTaskSource({
7517
+ agent: ctx.agent,
7518
+ taskId,
7519
+ teamId: profile.teamId,
7520
+ profileId: profile.id,
7521
+ executorFingerprint: preparedRuntime.attestor.fingerprint
7522
+ }),
7523
+ makeReporter: () => new ApiTaskReporter({
7524
+ tasks: ctx.agent.tasks,
7525
+ teamId: profile.teamId,
7526
+ heartbeatIntervalMs: operations.heartbeatIntervalMs,
7527
+ logger: rootLogger
7528
+ }),
7529
+ onTaskFinished: async (output, claimedTask) => {
7530
+ const resolved = await slotRegistry.findLatestSlotByTaskAttempt(claimedTask.task.teamId, claimedTask.task.id, claimedTask.attemptN);
7531
+ let terminalOutput = redactRequiredEnvValues(output, profile.requiredEnv, cfg.profilePrerequisiteEnv);
7532
+ if (resolved?.session?.sessionDir) try {
7533
+ const parentSession = await resolveParentRuntimeSession(runtimeSessionStore, claimedTask);
7534
+ await runtimeSessionStore.uploadAttemptFinal({
7535
+ attemptN: claimedTask.attemptN,
7536
+ parentSessionId: parentSession?.id ?? null,
7537
+ sessionDir: resolved.session.sessionDir,
7538
+ sessionKind: resolveRuntimeSessionKind(claimedTask),
7539
+ sourceRuntimeProfileId: resolved.slot.runtimeProfileId,
7540
+ sourceSlotId: resolved.slot.id,
7541
+ taskId: claimedTask.task.id,
7542
+ teamId: claimedTask.task.teamId
7543
+ });
7544
+ } catch (err) {
7545
+ rootLogger.error({
7546
+ err,
7547
+ taskId: claimedTask.task.id,
7548
+ attemptN: claimedTask.attemptN
7549
+ }, "agent-daemon.runtime_session_upload_failed");
7550
+ terminalOutput = applyRuntimeSessionUploadFailure(terminalOutput, err);
7551
+ }
7552
+ return finalizeTask(ctx.agent, terminalOutput, {
7553
+ task: claimedTask.task,
7554
+ slot: resolved ? { expiresAtMs: resolved.slot.expiresAtMs } : null,
7555
+ retryTriage: createRuntimeProfileRetryTriage({
7556
+ runtimeProfile: profile,
7557
+ piAgentDir: piAgentDir.path,
7558
+ cwd: ctx.agentRootDir
7559
+ }),
7560
+ executorAttestor: preparedRuntime.attestor,
7561
+ writeCorrelationAnchors,
7562
+ log: (msg, fields) => rootLogger.warn(fields ?? {}, msg)
7457
7563
  });
7458
- removed.push(record.id);
7459
- continue;
7460
- }
7461
- retainedCount += 1;
7462
- retainedBytes += bytes;
7564
+ },
7565
+ executeTask
7566
+ });
7567
+ const [output] = await runtime.start();
7568
+ if (!output) {
7569
+ rootLogger.error({}, "agent-daemon.no_output");
7570
+ return 1;
7463
7571
  }
7464
- return removed;
7572
+ console.log("\n[done] TaskOutput:");
7573
+ console.log(JSON.stringify(output, null, 2));
7574
+ return output.status === "completed" ? 0 : 1;
7575
+ } finally {
7576
+ signalHandlers.dispose();
7577
+ await slotRegistry.close();
7578
+ await otelShutdown();
7579
+ await shutdownLogger();
7465
7580
  }
7466
- };
7467
- function directoryBytes(path) {
7468
- let info;
7581
+ }
7582
+ function resolveRecordedWorkspacePath(stateRootDir, mountPath, executionPlan) {
7583
+ if (!executionPlan.workspaceId) return null;
7584
+ return executionPlan.workspaceMode === "scratch_mount" ? join(stateRootDir, "task-workspaces", executionPlan.workspaceId) : join(findMainWorktree(mountPath), ".worktrees", executionPlan.workspaceId);
7585
+ }
7586
+ function resolveMainWorktree(mountPath) {
7469
7587
  try {
7470
- info = lstatSync(path);
7588
+ return findMainWorktree(mountPath);
7471
7589
  } catch {
7472
- return 0;
7590
+ return null;
7473
7591
  }
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
7592
  }
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();
7593
+ //#endregion
7594
+ //#region src/cli/poll.ts
7595
+ function runPoll(argv, runtimeAdapter) {
7596
+ return runPolling({
7597
+ argv,
7598
+ serviceName: "moltnet.agent-daemon.poll",
7599
+ stopWhenEmpty: false,
7600
+ modeLabel: "poll",
7601
+ helpText: POLL_HELP,
7602
+ runtimeAdapter
7603
+ });
7512
7604
  }
7513
7605
  //#endregion
7514
7606
  //#region src/lib/provider-lock.ts
@@ -9199,7 +9291,6 @@ function snapshot(login) {
9199
9291
  * the current config and verifies it with authenticated `whoami` before use.
9200
9292
  */
9201
9293
  var MAX_CONFIG_BYTES = 64 * 1024;
9202
- var IDENTITY_OPERATION_TIMEOUT_MS = 15e3;
9203
9294
  var pendingAliases = /* @__PURE__ */ new WeakMap();
9204
9295
  var AgentServerIdentityError = class extends Error {
9205
9296
  name = "AgentServerIdentityError";
@@ -9222,7 +9313,7 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9222
9313
  const alias = assertStoreName("agent name", input.name);
9223
9314
  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
9315
  const releaseAlias = reserveAlias(store, alias);
9225
- let registered = false;
9316
+ let registered;
9226
9317
  try {
9227
9318
  let apiUrl;
9228
9319
  try {
@@ -9231,61 +9322,40 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9231
9322
  throw new AgentServerIdentityError("registration_failed", "registration API URL must use HTTPS or HTTP loopback", { cause });
9232
9323
  }
9233
9324
  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)
9325
+ try {
9326
+ registered = await register({
9327
+ name: alias,
9328
+ apiUrl,
9329
+ credentialType: "agent_key",
9330
+ enrollmentToken: input.enrollmentToken,
9331
+ secretProvider: secrets,
9332
+ configDir: store.identityDir(alias),
9333
+ publishAlias: false,
9334
+ signal: input.signal,
9335
+ connectAgent
9336
+ });
9337
+ } catch (cause) {
9338
+ if (cause instanceof RegisterIdentityError) {
9339
+ if (cause.nothingRegistered) {
9340
+ store.clearPendingRegistration(alias);
9341
+ throw new AgentServerIdentityError(cause.code === "alias_exists" ? "agent_exists" : "registration_failed", cause.message, { cause });
9342
+ }
9343
+ if (cause.code === "unsupported_credential") throw new AgentServerIdentityError("unsupported_credential", `${cause.message}; agent server manages agent-key credentials only`, { cause });
9344
+ if (cause.code === "identity_mismatch") throw new AgentServerIdentityError("verification_failed", cause.message, { cause });
9265
9345
  }
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)}`);
9346
+ throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, cause instanceof RegisterIdentityError ? cause : {}), { cause });
9347
+ }
9348
+ const { config, whoami, identity } = registered;
9279
9349
  const boundTeamId = boundTeamIdFromWhoami(whoami);
9280
9350
  const activation = {
9281
9351
  alias,
9282
9352
  source: "managed",
9283
9353
  subjectId: whoami.subjectId,
9284
- publicKey,
9285
- fingerprint,
9354
+ publicKey: identity.publicKey,
9355
+ fingerprint: identity.fingerprint,
9286
9356
  ...boundTeamId ? { boundTeamId } : {},
9287
- createdAt: now,
9288
- apiUrl: result.apiUrl
9357
+ createdAt: config.registered_at,
9358
+ apiUrl: config.endpoints.api
9289
9359
  };
9290
9360
  store.writeActivation(activation);
9291
9361
  return {
@@ -9294,18 +9364,27 @@ async function createManagedAgent(store, secrets, input, connectAgent = connect)
9294
9364
  ...boundTeamId ? { boundTeamId } : {}
9295
9365
  };
9296
9366
  } 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 });
9367
+ if (cause instanceof AgentServerIdentityError) throw cause;
9368
+ if (store.hasPendingRegistration(alias)) throw new AgentServerIdentityError("registration_incomplete", incompleteRegistrationMessage(alias, registered ? {
9369
+ ...registered.identity,
9370
+ configPath: registered.configPath
9371
+ } : {}), { cause });
9302
9372
  throw cause;
9303
9373
  } finally {
9304
9374
  releaseAlias();
9305
9375
  }
9306
9376
  }
9307
- function registrationRejectionMessage(alias, cause) {
9308
- return `registration for "${alias}" was rejected${cause.statusCode === void 0 ? "" : ` (${cause.statusCode})`}: ${cause.detail?.trim() || cause.message}`;
9377
+ /**
9378
+ * Guidance for a registration that may have committed. The HTTP response
9379
+ * carries only this message, so it names the reconcile call and, when the
9380
+ * server outcome is unknown, the fingerprint to look up first. Resume needs
9381
+ * the written config, so without one only abandon is offered.
9382
+ */
9383
+ function incompleteRegistrationMessage(alias, known) {
9384
+ const endpoint = `POST /v1/agents/${alias}/reconcile`;
9385
+ 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"}.`;
9386
+ if (known.subjectId) return `the remote agent ${known.subjectId} was registered but local activation is incomplete. ${reconcile}`;
9387
+ return `registration for "${alias}" may have completed on the server${known.fingerprint ? ` (fingerprint ${known.fingerprint})` : ""}; look the agent up first. ${reconcile}`;
9309
9388
  }
9310
9389
  /** Resume a fully persisted registration or explicitly abandon local recovery. */
9311
9390
  async function reconcileManagedRegistration(store, secrets, aliasInput, action, connectAgent = connect, signal) {
@@ -9528,10 +9607,6 @@ async function callWhoami(connectAgent, options, source, signal) {
9528
9607
  throw verificationError(`could not authenticate ${source} against the API`, cause);
9529
9608
  }
9530
9609
  }
9531
- function boundedIdentitySignal(signal) {
9532
- const timeout = AbortSignal.timeout(IDENTITY_OPERATION_TIMEOUT_MS);
9533
- return signal ? AbortSignal.any([signal, timeout]) : timeout;
9534
- }
9535
9610
  function identityFromConfig(config) {
9536
9611
  const publicKey = config?.keys?.public_key?.trim();
9537
9612
  const fingerprint = config?.keys?.fingerprint?.trim();
@@ -9691,6 +9766,7 @@ var RunManager = class {
9691
9766
  extraArgs: ["--agent-root", agentRoot]
9692
9767
  };
9693
9768
  })();
9769
+ const runtimeSettings = this.options.runtimeSettings ?? DEFAULT_LOCAL_OPERATIONAL_SETTINGS;
9694
9770
  const args = [
9695
9771
  ...runtimeModule ? ["--runtime", runtimeModule] : [],
9696
9772
  spec.mode,
@@ -9701,6 +9777,10 @@ var RunManager = class {
9701
9777
  ...spec.profiles.flatMap((profile) => ["--profile", profile]),
9702
9778
  "--task-types",
9703
9779
  spec.taskTypes.join(","),
9780
+ "--heartbeat-interval-ms",
9781
+ String(runtimeSettings.heartbeatIntervalMs),
9782
+ "--warm-retention-sec",
9783
+ String(runtimeSettings.warmRetentionSec),
9704
9784
  ...target.extraArgs
9705
9785
  ];
9706
9786
  if (activation.source === "managed") {
@@ -9776,20 +9856,8 @@ var RunManager = class {
9776
9856
  recursive: true,
9777
9857
  mode: 448
9778
9858
  });
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
- }
9859
+ writeStorePiConfig(piDir, providers);
9860
+ linkPiAuth(this.store.piAuthJsonPath, piDir, this.options.symlinkImpl ?? symlinkSync);
9793
9861
  const entry = this.entrypoint();
9794
9862
  logStream = createWriteStream(logPath, {
9795
9863
  fd: openSync(logPath, "a", 384),
@@ -10383,7 +10451,11 @@ var AgentServerStatusSchema = Type.Object({
10383
10451
  identities: Type.Array(schemaRef(AgentServerIdentitySchema)),
10384
10452
  selectedIdentity: Type.Optional(Type.String()),
10385
10453
  providers: Type.Record(Type.String(), schemaRef(AgentServerProviderSchema)),
10386
- runs: Type.Array(schemaRef(AgentServerRunSchema))
10454
+ runs: Type.Array(schemaRef(AgentServerRunSchema)),
10455
+ runtimeSettings: Type.Object({
10456
+ heartbeatIntervalMs: Type.Integer({ minimum: 0 }),
10457
+ warmRetentionSec: Type.Integer({ minimum: 0 })
10458
+ })
10387
10459
  }, { $id: "AgentServerStatus" });
10388
10460
  var PairingStartedSchema = Type.Object({
10389
10461
  pairingId: Type.String(),
@@ -10857,7 +10929,8 @@ function registerStatusRoute(app, options, requirePairedOrigin) {
10857
10929
  identities: identityViews(store),
10858
10930
  ...selected ? { selectedIdentity: selected } : {},
10859
10931
  providers: options.providers.list(),
10860
- runs: runViews(runs)
10932
+ runs: runViews(runs),
10933
+ runtimeSettings: options.runtimeSettings ?? DEFAULT_LOCAL_OPERATIONAL_SETTINGS
10861
10934
  };
10862
10935
  });
10863
10936
  }
@@ -11163,7 +11236,7 @@ function normalizeAgentServerError(error) {
11163
11236
  message: error.message
11164
11237
  };
11165
11238
  if (error instanceof AgentServerIdentityError) return {
11166
- statusCode: error.code === "agent_exists" ? 409 : 400,
11239
+ statusCode: error.code === "agent_exists" || error.code === "registration_incomplete" ? 409 : 400,
11167
11240
  code: error.code,
11168
11241
  message: error.message
11169
11242
  };
@@ -11357,6 +11430,8 @@ async function runAgentServer(argv) {
11357
11430
  "allowed-origins": { type: "string" },
11358
11431
  root: { type: "string" },
11359
11432
  "api-url": { type: "string" },
11433
+ "heartbeat-interval-ms": { type: "string" },
11434
+ "warm-retention-sec": { type: "string" },
11360
11435
  remove: { type: "boolean" }
11361
11436
  }
11362
11437
  });
@@ -11368,6 +11443,7 @@ async function runAgentServer(argv) {
11368
11443
  const allowedOrigins = parseAllowedOrigins(values["allowed-origins"] ?? (envConfig.allowedOrigins || DEFAULT_ALLOWED_ORIGINS));
11369
11444
  const root = values.root ?? resolveAgentServerRoot({ root: envConfig.root });
11370
11445
  const defaultApiUrl = values["api-url"] ?? (envConfig.apiUrl || DEFAULT_API_URL);
11446
+ const runtimeSettings = parseLocalOperationalSettings(values);
11371
11447
  const store = new AgentServerStore(root).ensure();
11372
11448
  if (trustRequested) return runTrustCommand(commandArgs, root);
11373
11449
  const { logger, shutdown: shutdownLogger } = createRootLogger({
@@ -11401,7 +11477,8 @@ async function runAgentServer(argv) {
11401
11477
  externalSecretProviders,
11402
11478
  baseEnv: processEnvSnapshot(),
11403
11479
  logger,
11404
- runtimeRegistry: new RuntimeRegistry(store.root)
11480
+ runtimeRegistry: new RuntimeRegistry(store.root),
11481
+ runtimeSettings
11405
11482
  });
11406
11483
  const tls = isMacos() ? await ensureTrustedLocalTls(root) : void 0;
11407
11484
  const app = buildAgentServer({
@@ -11420,6 +11497,7 @@ async function runAgentServer(argv) {
11420
11497
  cert: tls.cert
11421
11498
  } } : {},
11422
11499
  defaultApiUrl,
11500
+ runtimeSettings,
11423
11501
  ...envConfig.activeIdentity ? { activeIdentity: envConfig.activeIdentity } : {},
11424
11502
  version: "dev",
11425
11503
  logger,
@@ -11643,7 +11721,7 @@ async function runSyncSessions(argv) {
11643
11721
  const { values } = parseArgs({
11644
11722
  args: argv,
11645
11723
  options: {
11646
- ...commonOptionDefs(),
11724
+ ...identityOptionDefs(),
11647
11725
  team: { type: "string" },
11648
11726
  "runtime-profile-id": { type: "string" },
11649
11727
  state: { type: "string" },
@@ -11656,9 +11734,9 @@ async function runSyncSessions(argv) {
11656
11734
  console.error(SYNC_SESSIONS_HELP);
11657
11735
  return 1;
11658
11736
  }
11659
- let opts;
11737
+ let identity;
11660
11738
  try {
11661
- opts = parseCommonOptions(values);
11739
+ identity = parseIdentityProcessOptions(values);
11662
11740
  } catch (err) {
11663
11741
  if (err instanceof MissingRequiredOptionError) {
11664
11742
  console.error(`${err.message}\n`);
@@ -11672,7 +11750,7 @@ async function runSyncSessions(argv) {
11672
11750
  const agentRootDir = resolve(process.cwd(), values["agent-root"] ?? process.cwd());
11673
11751
  const explicitAgentRootDir = values["agent-root"] ? resolve(process.cwd(), values["agent-root"]) : void 0;
11674
11752
  const cfg = loadConfig();
11675
- const ctx = await resolveAgentContext(opts.agent, {
11753
+ const ctx = await resolveAgentContext(identity.agent, {
11676
11754
  credentialSource: cfg.credentialSource,
11677
11755
  envApiUrl: cfg.apiUrl,
11678
11756
  agentRootDir: explicitAgentRootDir
@@ -11687,7 +11765,7 @@ async function runSyncSessions(argv) {
11687
11765
  runtimeSlotStore: createApiRuntimeSlotStore({ agent: ctx.agent }),
11688
11766
  taskReader: ctx.agent.tasks
11689
11767
  }, {
11690
- agentName: opts.agent,
11768
+ agentName: identity.agent,
11691
11769
  dryRun: values["dry-run"] === true,
11692
11770
  limit,
11693
11771
  runtimeProfileId: values["runtime-profile-id"],
@@ -11864,7 +11942,7 @@ async function writeCache(cache) {
11864
11942
  }
11865
11943
  //#endregion
11866
11944
  //#region src/version.ts
11867
- var DAEMON_VERSION = "0.56.2";
11945
+ var DAEMON_VERSION = "0.57.1";
11868
11946
  //#endregion
11869
11947
  //#region src/cli.ts
11870
11948
  async function runAgentDaemonCli(options) {