@byok-sdk/client 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +15 -0
  2. package/dist/adapters/codex/codex-adapter.d.ts +2 -0
  3. package/dist/adapters/codex/process-runner.d.ts +16 -17
  4. package/dist/adapters/detect-outcome.d.ts +18 -0
  5. package/dist/adapters/index.d.ts +1 -1
  6. package/dist/adapters/index.js +189 -50
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/bin/byok-agent-memory-mcp.js +29 -0
  9. package/dist/bin/byok-agent-memory-mcp.js.map +1 -1
  10. package/dist/bin/byok-agent-message-mcp.js +29 -0
  11. package/dist/bin/byok-agent-message-mcp.js.map +1 -1
  12. package/dist/bin/byok-agent-team-mcp.js +29 -0
  13. package/dist/bin/byok-agent-team-mcp.js.map +1 -1
  14. package/dist/bin/byok-agent.js +1832 -932
  15. package/dist/bin/byok-agent.js.map +1 -1
  16. package/dist/bin/byok-approval-mcp.js +29 -0
  17. package/dist/bin/byok-approval-mcp.js.map +1 -1
  18. package/dist/bin/byok-mcp-env.d.ts +2 -0
  19. package/dist/bin/byok-mcp-env.js +36 -0
  20. package/dist/bin/byok-mcp-env.js.map +1 -0
  21. package/dist/bin/commands/doctor.d.ts +3 -0
  22. package/dist/bin/mcp-env-launcher.d.ts +5 -0
  23. package/dist/bin/runtime-probe.d.ts +6 -7
  24. package/dist/daemon/admission-wait.d.ts +2 -0
  25. package/dist/daemon/agent-egress-spool.d.ts +1 -0
  26. package/dist/daemon/agent-message-outbox.d.ts +15 -3
  27. package/dist/daemon/artifact-read.d.ts +7 -0
  28. package/dist/daemon/connection-manager.d.ts +18 -77
  29. package/dist/daemon/create-daemon.d.ts +11 -1
  30. package/dist/daemon/runtime-start.d.ts +3 -0
  31. package/dist/daemon/store.d.ts +2 -0
  32. package/dist/daemon/task-runner.d.ts +23 -0
  33. package/dist/daemon/terminal-commit-queue.d.ts +21 -0
  34. package/dist/daemon/terminal-identity.d.ts +6 -0
  35. package/dist/diagnostics/device-doctor.d.ts +46 -0
  36. package/dist/diagnostics/diagnostics.d.ts +3 -80
  37. package/dist/diagnostics/types.d.ts +82 -0
  38. package/dist/index.d.ts +3 -1
  39. package/dist/index.js +1942 -423
  40. package/dist/index.js.map +1 -1
  41. package/dist/runtime-detection.d.ts +3 -0
  42. package/dist/runtime-failure.d.ts +6 -0
  43. package/dist/sdk-reserved-helper-host.d.ts +1 -1
  44. package/dist/types.d.ts +20 -10
  45. package/dist/util/durable-jsonl.d.ts +12 -0
  46. package/package.json +4 -4
@@ -11,7 +11,7 @@ import { execFile, spawn, spawnSync } from 'child_process';
11
11
  import net, { createServer, createConnection } from 'net';
12
12
  import { isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
13
13
  import * as fs5 from 'fs/promises';
14
- import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, isTaskOfferType, partitionAgentEvents, TASK_TRANSITIONS, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, TERMINAL_PROJECTION_SELECTION_CAPABILITY, PROVIDER_PROFILE_BINDING_CAPABILITY, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, isServerToDaemonType, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, RuntimeIdSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, MESSAGE_TYPES, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionMutationSchema, AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES } from '@byok-sdk/protocol';
14
+ import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, AgentHomeProjectionPayloadSchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, isTaskOfferType, partitionAgentEvents, TASK_TRANSITIONS, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, RuntimeIdSchema, HarnessIdSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, TERMINAL_PROJECTION_SELECTION_CAPABILITY, PROVIDER_PROFILE_BINDING_CAPABILITY, STRICT_AGENT_ONLY_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, AgentHomeProjectionCompletionRequestSchema, byokAgentHomeProjectionCompletionPath, AgentHomeProjectionReadbackSchema, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, isServerToDaemonType, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, AGENT_MEMORY_PROJECTION_CAPABILITY, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MESSAGE_TYPES, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionMutationSchema, AGENT_MEMORY_PROJECTION_MAX_REDACTED_BYTES } from '@byok-sdk/protocol';
15
15
  import { promisify } from 'util';
16
16
  import { createRequire } from 'module';
17
17
  import { createInterface as createInterface$1 } from 'readline/promises';
@@ -1418,7 +1418,8 @@ var DIST_SCRIPT_BY_KIND = Object.freeze({
1418
1418
  "agent-message-mcp": "byok-agent-message-mcp.js",
1419
1419
  "agent-memory-mcp": "byok-agent-memory-mcp.js",
1420
1420
  "approval-mcp": "byok-approval-mcp.js",
1421
- "agent-team-mcp": "byok-agent-team-mcp.js"
1421
+ "agent-team-mcp": "byok-agent-team-mcp.js",
1422
+ "mcp-env": "byok-mcp-env.js"
1422
1423
  });
1423
1424
  function assertExecutable(executable) {
1424
1425
  if (!path14__default.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
@@ -1436,8 +1437,11 @@ function resolveSdkReservedHelperBin(kind, host) {
1436
1437
  source: "self-executable"
1437
1438
  });
1438
1439
  }
1440
+ const entryDir = path14__default.dirname(fileURLToPath(import.meta.url));
1441
+ const entryKind = path14__default.basename(entryDir);
1442
+ const distDir = entryKind === "adapters" || entryKind === "bin" ? path14__default.dirname(entryDir) : entryDir;
1439
1443
  const script = path14__default.join(
1440
- path14__default.dirname(fileURLToPath(import.meta.url)),
1444
+ distDir,
1441
1445
  "bin",
1442
1446
  DIST_SCRIPT_BY_KIND[kind]
1443
1447
  );
@@ -1915,6 +1919,20 @@ var DeviceStore = class _DeviceStore {
1915
1919
  await opened.handle.close();
1916
1920
  }
1917
1921
  }
1922
+ /** Rebuild only the non-secret projection, while the caller owns the store lease. */
1923
+ async reconcileMetadata(authority) {
1924
+ const projection = {
1925
+ deviceId: authority.deviceId,
1926
+ tenantId: authority.tenantId,
1927
+ devicePublicKey: authority.devicePublicKey
1928
+ };
1929
+ const current = await this.load();
1930
+ if (current !== void 0 && current.deviceId === projection.deviceId && current.tenantId === projection.tenantId && current.devicePublicKey === projection.devicePublicKey) {
1931
+ return false;
1932
+ }
1933
+ await this.save(projection);
1934
+ return true;
1935
+ }
1918
1936
  async save(record4) {
1919
1937
  assertDeviceMetadata(record4);
1920
1938
  const storeDir = path14__default.dirname(this.filePath);
@@ -2361,7 +2379,7 @@ function resolveLocalAgentReleaseIdentity(input) {
2361
2379
 
2362
2380
  // src/bin/official-release.ts
2363
2381
  var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
2364
- version: "0.14.0"
2382
+ version: "0.15.0"
2365
2383
  });
2366
2384
 
2367
2385
  // src/bin/config.ts
@@ -2762,6 +2780,19 @@ function projectRuntimeBoundaryFailure(value, expectedPhase) {
2762
2780
  contractViolation: true
2763
2781
  };
2764
2782
  }
2783
+ var RuntimeStartupDisposalFailure = class extends Error {
2784
+ retryDisposal;
2785
+ constructor(retryDisposal, options) {
2786
+ super("runtime startup failed and process ownership remains quarantined", options);
2787
+ this.name = "RuntimeStartupDisposalFailure";
2788
+ this.retryDisposal = retryDisposal;
2789
+ Object.defineProperty(this, /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1"), { value: true });
2790
+ Object.freeze(this);
2791
+ }
2792
+ };
2793
+ function isRuntimeStartupDisposalFailure(value) {
2794
+ return typeof value === "object" && value !== null && value[/* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1")] === true && typeof value.retryDisposal === "function";
2795
+ }
2765
2796
 
2766
2797
  // src/adapters/taskkill-pid-set.ts
2767
2798
  var INTEGER_PATTERN = /\d+/g;
@@ -5519,6 +5550,7 @@ async function acquireDaemonOwner(storeDir, role, clock = () => /* @__PURE__ */
5519
5550
  }
5520
5551
 
5521
5552
  // src/types.ts
5553
+ var RUNTIME_DETECTION_FAILURE_KINDS = ["not-found", "not-executable", "timeout", "probe-failed"];
5522
5554
  function frozenStrings(values) {
5523
5555
  return values === void 0 ? void 0 : Object.freeze([...values]);
5524
5556
  }
@@ -5743,6 +5775,132 @@ var GitWorkspaceStore = class {
5743
5775
  await atomicWriteFile(this.filePath, JSON.stringify(ledger, null, 2), { mode: 384 });
5744
5776
  }
5745
5777
  };
5778
+ function terminalIdentity(runtimeId, agentRef) {
5779
+ return {
5780
+ ...runtimeId === void 0 || RuntimeIdSchema.safeParse(runtimeId).success ? {} : { harnessId: HarnessIdSchema.parse(runtimeId) },
5781
+ ...agentRef === void 0 ? {} : { agentRef }
5782
+ };
5783
+ }
5784
+
5785
+ // src/daemon/terminal-commit-queue.ts
5786
+ var TerminalCommitQueue = class {
5787
+ constructor(committed) {
5788
+ this.committed = committed;
5789
+ }
5790
+ committed;
5791
+ pending = /* @__PURE__ */ new Map();
5792
+ tail = Promise.resolve();
5793
+ timer;
5794
+ stopped = false;
5795
+ get pendingCount() {
5796
+ return this.pending.size;
5797
+ }
5798
+ hasPending(taskId) {
5799
+ return this.pending.has(taskId);
5800
+ }
5801
+ enqueue(taskId, commit) {
5802
+ if (!this.pending.has(taskId)) this.pending.set(taskId, { commit, receipt: Promise.resolve(), running: false });
5803
+ void this.attempt(taskId).catch(() => void 0);
5804
+ }
5805
+ receipt(taskId) {
5806
+ return this.pending.get(taskId)?.receipt ?? Promise.resolve();
5807
+ }
5808
+ retry(taskId) {
5809
+ return this.attempt(taskId);
5810
+ }
5811
+ resume() {
5812
+ this.stopped = false;
5813
+ if (this.pending.size > 0) this.armRetry();
5814
+ }
5815
+ async stop() {
5816
+ this.stopped = true;
5817
+ if (this.timer) clearTimeout(this.timer);
5818
+ this.timer = void 0;
5819
+ await Promise.all([...this.pending.keys()].map((taskId) => this.attempt(taskId)));
5820
+ }
5821
+ attempt(taskId) {
5822
+ const entry = this.pending.get(taskId);
5823
+ if (!entry) return Promise.resolve();
5824
+ if (entry.running) return entry.receipt;
5825
+ entry.running = true;
5826
+ const receipt = this.tail.catch(() => void 0).then(entry.commit);
5827
+ entry.receipt = receipt.then(() => {
5828
+ if (this.pending.get(taskId) === entry) this.pending.delete(taskId);
5829
+ this.committed(taskId);
5830
+ }, (error) => {
5831
+ entry.running = false;
5832
+ this.armRetry();
5833
+ throw error;
5834
+ });
5835
+ this.tail = entry.receipt;
5836
+ void entry.receipt.catch(() => void 0);
5837
+ return entry.receipt;
5838
+ }
5839
+ armRetry() {
5840
+ if (this.timer || this.stopped) return;
5841
+ this.timer = setTimeout(() => {
5842
+ this.timer = void 0;
5843
+ for (const taskId of this.pending.keys()) void this.attempt(taskId).catch(() => void 0);
5844
+ }, 1e3);
5845
+ this.timer.unref?.();
5846
+ }
5847
+ };
5848
+
5849
+ // src/runtime-detection.ts
5850
+ function validateRuntimeDetectResult(value) {
5851
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
5852
+ throw new TypeError("invalid runtime detection result");
5853
+ }
5854
+ const result = value;
5855
+ if (!Object.hasOwn(result, "kind")) throw new TypeError("invalid runtime detection result");
5856
+ const { kind, version, authPresent } = result;
5857
+ if (kind === "available") {
5858
+ if (Object.keys(result).some((key) => !["kind", "version", "authPresent"].includes(key)) || version !== void 0 && typeof version !== "string" || authPresent !== void 0 && typeof authPresent !== "boolean") {
5859
+ throw new TypeError("invalid runtime detection result");
5860
+ }
5861
+ return Object.freeze({
5862
+ kind,
5863
+ ...version === void 0 ? {} : { version },
5864
+ ...authPresent === void 0 ? {} : { authPresent }
5865
+ });
5866
+ }
5867
+ if (RUNTIME_DETECTION_FAILURE_KINDS.some((candidate) => candidate === kind) && Object.keys(result).every((key) => key === "kind")) {
5868
+ return Object.freeze({ kind });
5869
+ }
5870
+ throw new TypeError("invalid runtime detection result");
5871
+ }
5872
+ function classifyDetectError(error) {
5873
+ const code = error !== null && typeof error === "object" ? error.code : void 0;
5874
+ if (code === "ENOENT") return { kind: "not-found" };
5875
+ if (code === "EACCES" || code === "EPERM" || code === "ENOEXEC") return { kind: "not-executable" };
5876
+ return { kind: "probe-failed" };
5877
+ }
5878
+ async function probeRuntimeVersion(command, timeoutMs) {
5879
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("invalid runtime probe timeout");
5880
+ return new Promise((resolve) => {
5881
+ let timer;
5882
+ let timedOut = false;
5883
+ try {
5884
+ const child = execFile(command, ["--version"], { encoding: "utf8" }, (error, stdout, stderr) => {
5885
+ if (timer) clearTimeout(timer);
5886
+ if (error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") resolve({ kind: "probe-failed" });
5887
+ else if (timedOut) resolve({ kind: "timeout" });
5888
+ else if (error) resolve(classifyDetectError(error));
5889
+ else resolve({ kind: "available", stdout, stderr });
5890
+ });
5891
+ timer = setTimeout(() => {
5892
+ timedOut = true;
5893
+ child.stdout?.destroy();
5894
+ child.stderr?.destroy();
5895
+ child.kill("SIGKILL");
5896
+ }, timeoutMs);
5897
+ timer.unref?.();
5898
+ } catch (error) {
5899
+ if (timer) clearTimeout(timer);
5900
+ resolve(classifyDetectError(error));
5901
+ }
5902
+ });
5903
+ }
5746
5904
 
5747
5905
  // src/adapters/pi/mcp-config.ts
5748
5906
  var BYOK_PI_MCP_CONFIG_PATH = "BYOK_PI_MCP_CONFIG_PATH";
@@ -6076,12 +6234,13 @@ var PiAdapter = class {
6076
6234
  async detect() {
6077
6235
  try {
6078
6236
  const bin = this.resolveBin();
6079
- const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
6080
- const version = stdout.trim() || stderr.trim();
6237
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS);
6238
+ if (probe.kind !== "available") return probe;
6239
+ const version = probe.stdout.trim() || probe.stderr.trim();
6081
6240
  const authPresent = PROVIDER_CREDENTIAL_ENV_NAMES.some((name) => process.env[name] !== void 0);
6082
- return { present: true, version, authPresent };
6083
- } catch {
6084
- return { present: false };
6241
+ return { kind: "available", version, authPresent };
6242
+ } catch (error) {
6243
+ return classifyDetectError(error);
6085
6244
  }
6086
6245
  }
6087
6246
  async prepare(input) {
@@ -7211,14 +7370,15 @@ var ClaudeAdapter = class {
7211
7370
  environmentRequirements: { credentialNames: [] }
7212
7371
  });
7213
7372
  async detect() {
7214
- const bin = this.resolveBin();
7215
7373
  try {
7216
- const { stdout } = await execFileAsync2(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS2 });
7217
- const version = stdout.trim();
7374
+ const bin = this.resolveBin();
7375
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS2);
7376
+ if (probe.kind !== "available") return probe;
7377
+ const version = probe.stdout.trim();
7218
7378
  const authPresent = await this.probeAuthPresent(bin.command);
7219
- return { present: true, version, authPresent };
7220
- } catch {
7221
- return { present: false };
7379
+ return { kind: "available", version, authPresent };
7380
+ } catch (error) {
7381
+ return classifyDetectError(error);
7222
7382
  }
7223
7383
  }
7224
7384
  async prepare(input) {
@@ -7837,10 +7997,18 @@ function unmappedFrameKey(evt) {
7837
7997
  return evt.type;
7838
7998
  }
7839
7999
  var STDERR_RING_CAPACITY3 = 20;
8000
+ var CODEX_MAX_FRAME_BYTES = 1024 * 1024;
8001
+ var CODEX_MAX_DEFERRED_BYTES = 4 * 1024 * 1024;
8002
+ var CODEX_MAX_STDERR_BYTES = 64 * 1024;
7840
8003
  var CodexProcessRunner = class {
7841
8004
  child;
7842
8005
  onEvent;
7843
- buffer = "";
8006
+ frameChunks = [];
8007
+ frameBytes = 0;
8008
+ deferredBytes = 0;
8009
+ stderrBytes = 0;
8010
+ transportFailure;
8011
+ onFailure;
7844
8012
  stderrRing = [];
7845
8013
  closed = false;
7846
8014
  exitCode = null;
@@ -7863,11 +8031,12 @@ var CodexProcessRunner = class {
7863
8031
  deferredEvents = [];
7864
8032
  constructor(options) {
7865
8033
  this.onEvent = options.onEvent;
8034
+ this.onFailure = options.onFailure;
7866
8035
  const spawnFn = options.spawnFn ?? spawn;
7867
8036
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
7868
8037
  cwd: options.cwd,
7869
8038
  env: options.env,
7870
- stdio: ["ignore", "pipe", "pipe"]
8039
+ stdio: [options.instruction === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
7871
8040
  }));
7872
8041
  this.closedPromise = new Promise((resolve) => {
7873
8042
  this.resolveClosed = resolve;
@@ -7875,7 +8044,6 @@ var CodexProcessRunner = class {
7875
8044
  this.adopted = this.adoptOwnedTree(options);
7876
8045
  this.adopted.catch(() => {
7877
8046
  });
7878
- this.child.stdout.setEncoding("utf8");
7879
8047
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
7880
8048
  this.child.stderr.setEncoding("utf8");
7881
8049
  this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
@@ -7884,6 +8052,12 @@ var CodexProcessRunner = class {
7884
8052
  this.exitSignal = signal;
7885
8053
  this.finishClosing();
7886
8054
  });
8055
+ if (options.instruction !== void 0) {
8056
+ this.child.stdin.on("error", () => {
8057
+ this.failTransport("codex prompt stdin failed before delivery");
8058
+ });
8059
+ this.child.stdin.end(options.instruction, "utf8");
8060
+ }
7887
8061
  this.child.on("error", () => {
7888
8062
  this.finishClosing();
7889
8063
  });
@@ -7969,12 +8143,19 @@ var CodexProcessRunner = class {
7969
8143
  throw failure;
7970
8144
  }
7971
8145
  this.adoption = "adopted";
8146
+ this.deferredBytes = 0;
7972
8147
  for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
7973
8148
  }
7974
8149
  /** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
7975
- deliver(evt) {
8150
+ deliver(evt, bytes) {
8151
+ if (this.transportFailure) return;
7976
8152
  if (this.adoption === "failed") return;
7977
8153
  if (this.adoption === "pending") {
8154
+ if (this.deferredBytes + bytes > CODEX_MAX_DEFERRED_BYTES) {
8155
+ this.failTransport("codex pre-adoption event buffer exceeded its byte budget");
8156
+ return;
8157
+ }
8158
+ this.deferredBytes += bytes;
7978
8159
  this.deferredEvents.push(evt);
7979
8160
  return;
7980
8161
  }
@@ -7991,6 +8172,7 @@ var CodexProcessRunner = class {
7991
8172
  * plus an empty stderr tail would bury the only reason anyone can act on.
7992
8173
  */
7993
8174
  buildExitError(context) {
8175
+ if (this.transportFailure) return this.transportFailure;
7994
8176
  if (this.adoptionFailure !== void 0) return this.adoptionFailure;
7995
8177
  const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
7996
8178
  if (this.stderrRing.length > 0) {
@@ -7998,18 +8180,45 @@ var CodexProcessRunner = class {
7998
8180
  }
7999
8181
  return new Error(parts.join("; "));
8000
8182
  }
8183
+ failTransport(reason) {
8184
+ if (this.transportFailure) return;
8185
+ this.transportFailure = new RuntimeExecutionFailure({
8186
+ phase: "run",
8187
+ category: "infrastructure",
8188
+ retry: "non-retryable",
8189
+ reason
8190
+ });
8191
+ this.frameChunks.length = 0;
8192
+ this.frameBytes = 0;
8193
+ this.deferredEvents.length = 0;
8194
+ this.deferredBytes = 0;
8195
+ this.onFailure?.(this.transportFailure);
8196
+ this.kill();
8197
+ }
8001
8198
  onData(chunk) {
8002
- this.buffer += chunk;
8003
- let newlineIndex = this.buffer.indexOf("\n");
8004
- while (newlineIndex !== -1) {
8005
- let line = this.buffer.slice(0, newlineIndex);
8006
- this.buffer = this.buffer.slice(newlineIndex + 1);
8007
- if (line.endsWith("\r")) line = line.slice(0, -1);
8008
- if (line.length > 0) this.parseLine(line);
8009
- newlineIndex = this.buffer.indexOf("\n");
8199
+ if (this.transportFailure) return;
8200
+ let offset = 0;
8201
+ while (offset < chunk.length) {
8202
+ const newline = chunk.indexOf(10, offset);
8203
+ const end = newline === -1 ? chunk.length : newline;
8204
+ const bytes = end - offset;
8205
+ if (this.frameBytes + bytes > CODEX_MAX_FRAME_BYTES) {
8206
+ this.failTransport("codex raw JSONL frame exceeded its byte budget");
8207
+ return;
8208
+ }
8209
+ if (bytes > 0) this.frameChunks.push(Buffer.from(chunk.subarray(offset, end)));
8210
+ this.frameBytes += bytes;
8211
+ if (newline === -1) return;
8212
+ const frame = Buffer.concat(this.frameChunks, this.frameBytes);
8213
+ this.frameChunks.length = 0;
8214
+ this.frameBytes = 0;
8215
+ const line = frame.toString("utf8").replace(/\r$/, "");
8216
+ if (line.length > 0) this.parseLine(line, frame.length);
8217
+ if (this.transportFailure) return;
8218
+ offset = newline + 1;
8010
8219
  }
8011
8220
  }
8012
- parseLine(line) {
8221
+ parseLine(line, bytes) {
8013
8222
  let parsed;
8014
8223
  try {
8015
8224
  parsed = JSON.parse(line);
@@ -8019,14 +8228,18 @@ var CodexProcessRunner = class {
8019
8228
  if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
8020
8229
  return;
8021
8230
  }
8022
- this.deliver(parsed);
8231
+ this.deliver(parsed, bytes);
8023
8232
  }
8024
8233
  onStderr(chunk) {
8025
- for (const rawLine of chunk.split("\n")) {
8234
+ const bounded2 = Buffer.from(chunk).subarray(-CODEX_MAX_STDERR_BYTES).toString("utf8");
8235
+ for (const rawLine of bounded2.split("\n")) {
8026
8236
  const line = rawLine.trim();
8027
8237
  if (line.length === 0) continue;
8028
8238
  this.stderrRing.push(line);
8029
- if (this.stderrRing.length > STDERR_RING_CAPACITY3) this.stderrRing.shift();
8239
+ this.stderrBytes += Buffer.byteLength(line);
8240
+ while (this.stderrRing.length > STDERR_RING_CAPACITY3 || this.stderrBytes > CODEX_MAX_STDERR_BYTES) {
8241
+ this.stderrBytes -= Buffer.byteLength(this.stderrRing.shift());
8242
+ }
8030
8243
  }
8031
8244
  }
8032
8245
  };
@@ -8059,14 +8272,15 @@ var CodexAdapter = class {
8059
8272
  environmentRequirements: { credentialNames: [] }
8060
8273
  });
8061
8274
  async detect() {
8062
- const bin = this.resolveBin();
8063
8275
  try {
8064
- const versionResult = await execFileAsync3(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS3 });
8065
- const version = versionResult.stdout.trim() || versionResult.stderr.trim();
8276
+ const bin = this.resolveBin();
8277
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS3);
8278
+ if (probe.kind !== "available") return probe;
8279
+ const version = probe.stdout.trim() || probe.stderr.trim();
8066
8280
  const authPresent = await this.probeAuthPresent(bin);
8067
- return { present: true, version, authPresent };
8068
- } catch {
8069
- return { present: false };
8281
+ return { kind: "available", version, authPresent };
8282
+ } catch (error) {
8283
+ return classifyDetectError(error);
8070
8284
  }
8071
8285
  }
8072
8286
  /**
@@ -8209,7 +8423,7 @@ ${withStreams.stderr ?? ""}`);
8209
8423
  reason: "prepared codex operation received a manifest with different runtime selection"
8210
8424
  });
8211
8425
  }
8212
- const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
8426
+ const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, runtimeEnv, this.options.sdkHelperHost, preparedMcpGrants);
8213
8427
  const { sessionRef, runner } = await runCodexTurn({
8214
8428
  command,
8215
8429
  resumeRef: startInput.manifest.sessionRef,
@@ -8225,6 +8439,7 @@ ${withStreams.stderr ?? ""}`);
8225
8439
  expectedSessionRef: startInput.manifest.sessionRef,
8226
8440
  preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
8227
8441
  failurePhase: "start",
8442
+ signal: startInput.signal,
8228
8443
  terminal
8229
8444
  });
8230
8445
  return new CodexSession({
@@ -8232,6 +8447,7 @@ ${withStreams.stderr ?? ""}`);
8232
8447
  command,
8233
8448
  workspaceDir,
8234
8449
  env: startInput.env,
8450
+ mcpEnvironment: Object.fromEntries(Object.entries(runtimeEnv).filter(([key]) => key.startsWith("BYOK_MCP_PAYLOAD_"))),
8235
8451
  spawnFn: this.options.spawnFn,
8236
8452
  queue,
8237
8453
  recordUnmapped,
@@ -8284,16 +8500,18 @@ function codexMcpToolApprovalArgs(name, tools) {
8284
8500
  }
8285
8501
  return args;
8286
8502
  }
8287
- function codexMcpConfigArgs(servers, grants = []) {
8503
+ function codexMcpConfigArgs(servers, env, helperHost, grants = []) {
8288
8504
  if (servers === void 0 || Object.keys(servers).length === 0) return [];
8289
8505
  const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
8290
8506
  const args = ["--ignore-user-config"];
8291
8507
  for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
8292
- args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(server.command)}`);
8293
- if (server.args !== void 0) args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...server.args])}`);
8294
- for (const [key, value] of Object.entries(server.env ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
8295
- args.push("-c", `mcp_servers.${name}.env.${key}=${JSON.stringify(value)}`);
8296
- }
8508
+ const key = `BYOK_MCP_PAYLOAD_${randomBytes(16).toString("hex").toUpperCase()}`;
8509
+ env[key] = JSON.stringify(server);
8510
+ const helper = resolveSdkReservedHelperBin("mcp-env", helperHost);
8511
+ args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(helper.command)}`);
8512
+ args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...helper.args])}`);
8513
+ args.push("-c", `mcp_servers.${name}.env.BYOK_MCP_ENV_KEY=${JSON.stringify(key)}`);
8514
+ args.push("-c", `mcp_servers.${name}.env_vars=${JSON.stringify([key])}`);
8297
8515
  const granted = grantedTools.get(name);
8298
8516
  if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
8299
8517
  }
@@ -8321,7 +8539,7 @@ function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = fa
8321
8539
  ...modelId ? ["--model", modelId] : [],
8322
8540
  ...preparedGit ? [] : ["--skip-git-repo-check"],
8323
8541
  ...policyArgs,
8324
- instruction
8542
+ "-"
8325
8543
  ];
8326
8544
  }
8327
8545
  async function runCodexTurn(params) {
@@ -8345,9 +8563,18 @@ async function runCodexTurn(params) {
8345
8563
  runner = new CodexProcessRunner({
8346
8564
  command: params.command,
8347
8565
  args: argv,
8566
+ instruction: params.instruction,
8348
8567
  cwd: params.cwd,
8349
8568
  env: params.env,
8350
8569
  spawnFn: params.spawnFn,
8570
+ onFailure: (failure) => {
8571
+ if (!firstLineSettled) {
8572
+ firstLineSettled = true;
8573
+ rejectFirstLine(failure);
8574
+ }
8575
+ params.terminal.failure = failure;
8576
+ params.queue.end();
8577
+ },
8351
8578
  onEvent: (evt) => {
8352
8579
  if (!firstLineSettled) {
8353
8580
  firstLineSettled = true;
@@ -8414,6 +8641,16 @@ async function runCodexTurn(params) {
8414
8641
  params.queue.end();
8415
8642
  });
8416
8643
  let sessionRef;
8644
+ const abortStartup = () => rejectFirstLine(new RuntimeExecutionFailure({
8645
+ phase: params.failurePhase,
8646
+ category: "infrastructure",
8647
+ retry: "non-retryable",
8648
+ reason: "codex startup cancelled or exceeded its deadline"
8649
+ }));
8650
+ const startupTimer = setTimeout(abortStartup, 3e4);
8651
+ startupTimer.unref?.();
8652
+ params.signal?.addEventListener("abort", abortStartup, { once: true });
8653
+ if (params.signal?.aborted) abortStartup();
8417
8654
  try {
8418
8655
  sessionRef = await new Promise((resolve, reject) => {
8419
8656
  let settled = false;
@@ -8445,20 +8682,31 @@ async function runCodexTurn(params) {
8445
8682
  });
8446
8683
  });
8447
8684
  } catch (err) {
8448
- runner.kill();
8685
+ await disposeFailedStartup(runner, err);
8449
8686
  throw err;
8687
+ } finally {
8688
+ clearTimeout(startupTimer);
8689
+ params.signal?.removeEventListener("abort", abortStartup);
8450
8690
  }
8451
8691
  if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
8452
- runner.kill();
8453
- throw new RuntimeExecutionFailure({
8692
+ const failure = new RuntimeExecutionFailure({
8454
8693
  phase: params.failurePhase,
8455
8694
  category: "authority",
8456
8695
  retry: "non-retryable",
8457
8696
  reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
8458
8697
  });
8698
+ await disposeFailedStartup(runner, failure);
8699
+ throw failure;
8459
8700
  }
8460
8701
  return { sessionRef, runner };
8461
8702
  }
8703
+ async function disposeFailedStartup(runner, cause) {
8704
+ try {
8705
+ await runner.dispose();
8706
+ } catch (disposalFailure) {
8707
+ throw new RuntimeStartupDisposalFailure(() => runner.dispose(), { cause: new AggregateError([cause, disposalFailure]) });
8708
+ }
8709
+ }
8462
8710
  var CodexSession = class {
8463
8711
  /**
8464
8712
  * NOT `readonly` (cross-model review finding): `followUp()` below
@@ -8494,6 +8742,7 @@ var CodexSession = class {
8494
8742
  * recomputed from anything a later turn supplies, so a follow-up can never
8495
8743
  * widen this session's MCP authority.
8496
8744
  */
8745
+ mcpEnvironment;
8497
8746
  mcpConfigArgs;
8498
8747
  terminal;
8499
8748
  currentRunner;
@@ -8513,6 +8762,7 @@ var CodexSession = class {
8513
8762
  this.modelId = options.modelId;
8514
8763
  this.terminal = options.terminal;
8515
8764
  this.mcpConfigArgs = options.mcpConfigArgs;
8765
+ this.mcpEnvironment = Object.freeze({ ...options.mcpEnvironment });
8516
8766
  this.currentRunner = options.initialRunner;
8517
8767
  this.ownedRunners.add(options.initialRunner);
8518
8768
  void this.forgetRunnerOnceClosed(options.initialRunner);
@@ -8625,7 +8875,7 @@ var CodexSession = class {
8625
8875
  // frozen MCP config replayed verbatim — see `mcpConfigArgs`.
8626
8876
  policyArgs: [...mapping.args, ...this.mcpConfigArgs],
8627
8877
  cwd: this.workspaceDir,
8628
- env: withoutProviderCredentials(this.env),
8878
+ env: { ...withoutProviderCredentials(this.env), ...this.mcpEnvironment },
8629
8879
  spawnFn: this.spawnFn,
8630
8880
  workspaceDir: this.workspaceDir,
8631
8881
  queue: this.queue,
@@ -9141,15 +9391,7 @@ var AuthManager = class {
9141
9391
  if (await this.opts.store.load() === void 0) return void 0;
9142
9392
  throw new DeviceRecordRePairRequiredError();
9143
9393
  }
9144
- const projection = {
9145
- deviceId: authority.deviceId,
9146
- tenantId: authority.tenantId,
9147
- devicePublicKey: authority.devicePublicKey
9148
- };
9149
- const current = await this.opts.store.load();
9150
- if (current === void 0 || current.deviceId !== projection.deviceId || current.tenantId !== projection.tenantId || current.devicePublicKey !== projection.devicePublicKey) {
9151
- await this.opts.store.save(projection);
9152
- }
9394
+ await this.opts.store.reconcileMetadata(authority);
9153
9395
  return Object.freeze({ ...authority });
9154
9396
  }
9155
9397
  };
@@ -9446,9 +9688,9 @@ async function probeDarwin(run) {
9446
9688
  return DARWIN_UUID_RE.exec(result.stdout)?.[1];
9447
9689
  }
9448
9690
  async function probeLinux(readFile3) {
9449
- for (const path46 of LINUX_MACHINE_ID_PATHS) {
9691
+ for (const path47 of LINUX_MACHINE_ID_PATHS) {
9450
9692
  try {
9451
- const contents = (await readFile3(path46)).trim();
9693
+ const contents = (await readFile3(path47)).trim();
9452
9694
  if (contents.length > 0) return contents;
9453
9695
  } catch {
9454
9696
  }
@@ -9480,7 +9722,7 @@ async function withTimeout2(probe, timeoutMs) {
9480
9722
  async function resolveMachineId(options) {
9481
9723
  const platform = options.platform ?? process.platform;
9482
9724
  const run = options.run ?? runDeviceCommand;
9483
- const readFile3 = options.readFile ?? ((path46) => fs5.readFile(path46, "utf8"));
9725
+ const readFile3 = options.readFile ?? ((path47) => fs5.readFile(path47, "utf8"));
9484
9726
  let probe;
9485
9727
  if (platform === "darwin") probe = probeDarwin(run);
9486
9728
  else if (platform === "linux") probe = probeLinux(readFile3);
@@ -10504,13 +10746,35 @@ var LongPollClient = class {
10504
10746
  }
10505
10747
  async loop(signal) {
10506
10748
  let retryAttempt = 0;
10749
+ let readCursor;
10750
+ let readAheadSupported = false;
10751
+ let warnedWindowAt;
10507
10752
  while (this.running && !signal.aborted) {
10508
10753
  try {
10509
10754
  await this.opts.auth.getValidAccessToken();
10510
10755
  const base = toHttpBase(this.opts.serverUrl);
10511
10756
  const url = new URL(BYOK_EVENTS_PATH, base);
10512
10757
  const cursor = this.opts.getCursor();
10758
+ const ack = cursor ?? 0;
10759
+ if (readCursor !== void 0 && readCursor - ack >= MAILBOX_READ_AHEAD_MAX_SEQS) {
10760
+ if (warnedWindowAt !== ack) {
10761
+ warnedWindowAt = ack;
10762
+ console.warn("[byok/client] mailbox read-ahead window full; acknowledgement is stalled");
10763
+ }
10764
+ readCursor = ack;
10765
+ await sleep(this.opts.retryDelayMs ?? 2e3, signal);
10766
+ continue;
10767
+ }
10768
+ const afterSeq = Math.max(ack, readCursor ?? ack);
10513
10769
  if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
10770
+ if (afterSeq > ack) {
10771
+ if (!readAheadSupported) {
10772
+ const error = new Error("server does not advertise mailbox-read-ahead; cannot read past unacknowledged work");
10773
+ this.warnRouteFailure(this.eventsEndpoint, void 0, error);
10774
+ throw error;
10775
+ }
10776
+ url.searchParams.set("afterSeq", String(afterSeq));
10777
+ }
10514
10778
  let res;
10515
10779
  try {
10516
10780
  res = await authedFetch(url, { method: "GET", signal }, this.opts.auth);
@@ -10529,6 +10793,8 @@ var LongPollClient = class {
10529
10793
  return;
10530
10794
  }
10531
10795
  this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
10796
+ readCursor = void 0;
10797
+ readAheadSupported = false;
10532
10798
  this.opts.onServerCapabilitiesInvalidated?.();
10533
10799
  this.opts.onPollFailure?.();
10534
10800
  this.opts.onOperationalOutcome?.("failure");
@@ -10538,12 +10804,16 @@ var LongPollClient = class {
10538
10804
  }
10539
10805
  let parsed;
10540
10806
  try {
10541
- parsed = parseLooseEventsPollResponse(await res.json(), cursor ?? 0);
10542
- validateTrustedEventsPage(parsed.events, cursor ?? 0, parsed.cursor);
10807
+ parsed = parseLooseEventsPollResponse(await res.json(), afterSeq);
10808
+ validateTrustedEventsPage(parsed.events, afterSeq, parsed.cursor);
10809
+ if (afterSeq > ack && !parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY)) throw new Error("mailbox read-ahead capability was withdrawn");
10543
10810
  } catch (err) {
10544
10811
  this.warnRouteFailure(this.eventsEndpoint, res.status, err);
10545
10812
  throw err;
10546
10813
  }
10814
+ readAheadSupported = parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY);
10815
+ const madeReadProgress = parsed.cursor > afterSeq;
10816
+ readCursor = parsed.cursor;
10547
10817
  this.opts.onServerCapabilities?.(parsed.capabilities);
10548
10818
  let hadValidationFailureThisBatch = false;
10549
10819
  let acceptedAnyEntry = false;
@@ -10567,10 +10837,11 @@ var LongPollClient = class {
10567
10837
  if (this.opts.onEnvelope(envelope) !== false) acceptedAnyEntry = true;
10568
10838
  }
10569
10839
  if (parsed.events.length === 0) {
10840
+ readCursor = this.opts.getCursor() ?? 0;
10570
10841
  retryAttempt = 0;
10571
10842
  this.opts.onOperationalOutcome?.("success");
10572
10843
  await sleep(this.opts.idleDelayMs ?? 250, signal);
10573
- } else if (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor) {
10844
+ } else if (!madeReadProgress && (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor)) {
10574
10845
  this.opts.onOperationalOutcome?.("failure");
10575
10846
  const baseMs = this.opts.retryDelayMs ?? 2e3;
10576
10847
  await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs, signal);
@@ -10579,6 +10850,8 @@ var LongPollClient = class {
10579
10850
  this.opts.onOperationalOutcome?.("success");
10580
10851
  }
10581
10852
  } catch (err) {
10853
+ readCursor = void 0;
10854
+ readAheadSupported = false;
10582
10855
  this.opts.onServerCapabilitiesInvalidated?.();
10583
10856
  if (this.noteRevoked(err)) return;
10584
10857
  if (!this.running || signal.aborted) return;
@@ -10631,6 +10904,7 @@ function createConnectionHelloEnvelope(opts, getCursor) {
10631
10904
  productId: opts.productId,
10632
10905
  clientVersion: opts.clientVersion,
10633
10906
  runtimes: opts.runtimes,
10907
+ harnesses: opts.harnesses,
10634
10908
  configuredToolsets: configuredToolsets === void 0 ? void 0 : [...configuredToolsets],
10635
10909
  cursor: getCursor()
10636
10910
  });
@@ -10644,8 +10918,8 @@ var ConnectionManager = class {
10644
10918
  serverUrl: opts.serverUrl,
10645
10919
  auth: opts.auth,
10646
10920
  // The long-poll query cursor is also the kernel's irreversible ack.
10647
- // Only report the successfully processed cursor here. `deliveredSeq`
10648
- // remains a local dedup watermark; using it on the wire would ack an
10921
+ // Only report the successfully persisted cursor; using received work
10922
+ // on the wire would ack an
10649
10923
  // in-flight envelope before its handler settles and make a later
10650
10924
  // failure impossible to redeliver.
10651
10925
  getCursor: () => this.cursor,
@@ -10694,25 +10968,11 @@ var ConnectionManager = class {
10694
10968
  * guarantees in docs/protocol.md §9.
10695
10969
  */
10696
10970
  stalledAtSeq;
10697
- /**
10698
- * Design A (Wave 2, F3-on-long-poll): the second, in-memory watermark
10699
- * alongside the durable `cursor`. `cursor` only ever advances AFTER a
10700
- * `task.*` handler's side effects resolve successfully, and is persisted
10701
- * (see `advanceCursor`) — that semantics is unchanged. `deliveredSeq`
10702
- * advances eagerly, the instant a `task.*` envelope is admitted past
10703
- * dedup (see `deliver`/`noteDelivered`), independent of whether its
10704
- * handler has even started, let alone succeeded. It exists so a repeated
10705
- * read at the durable cursor does not re-dispatch an envelope already in
10706
- * flight — `handleOffer` must not start a second adapter session while a
10707
- * first attempt is still running. On WS this same field is written the
10708
- * same way, but since a live WS connection only ever pushes a given `seq`
10709
- * once, it never has an observable effect there beyond mirroring
10710
- * `cursor` (see `dedupWatermark`'s doc comment for why redelivery
10711
- * correctness doesn't depend on resetting it anywhere).
10712
- */
10713
- deliveredSeq;
10714
- /** Finding F3: serializes `onEnvelope` calls into a per-connection FIFO — one envelope's handler always fully settles before the next one starts. */
10971
+ /** Drain barrier for independently running handlers; not an admission lock. */
10715
10972
  processingChain = Promise.resolve();
10973
+ cursorInitialization;
10974
+ completionTail = Promise.resolve();
10975
+ controlTails = /* @__PURE__ */ new Map();
10716
10976
  /**
10717
10977
  * Design B (finding N4): the ONE outbound queue holds `Envelope` OBJECTS,
10718
10978
  * never re-encoded/rebuilt strings, so a
@@ -10738,38 +10998,13 @@ var ConnectionManager = class {
10738
10998
  terminalError;
10739
10999
  settledWaiters = [];
10740
11000
  pendingCursorSave = Promise.resolve();
10741
- /**
10742
- * Finding P2 (Fix 2b): seqs currently admitted into `processingChain` but
10743
- * not yet settled — added in `deliver()` the moment a `task.*` envelope is
10744
- * accepted past the ordinary watermark check, removed in `process()`'s
10745
- * `finally` once that specific attempt resolves (success OR failure).
10746
- * While stalled, `dedupWatermark()` deliberately stays frozen below
10747
- * already-delivered seqs (see its own doc comment) so the failed seq's own
10748
- * redelivery can get through — but that same frozen watermark also means
10749
- * every OTHER seq above it rides along on every re-poll too. Without this,
10750
- * a seq already mid-flight (e.g. a `task.offer` whose prepared operation start()
10751
- * hasn't resolved yet) would be re-enqueued into `processingChain` on
10752
- * every such re-poll, piling up duplicate copies that — once the first
10753
- * finally resolves and the chain unwinds through them — run its handler
10754
- * again; for `task.offer` specifically, a second adapter session
10755
- * orphaning the first (`TaskRunner`'s own `this.tasks.has` guard, finding
10756
- * P2c, is the second, independent layer against exactly that).
10757
- */
11001
+ /** Admission dedup while an individual handler is running. */
10758
11002
  inFlightSeqs = /* @__PURE__ */ new Set();
10759
- /**
10760
- * Finding P2 (Fix 2b): seqs whose handler has already resolved
10761
- * successfully at least once this session, tracked only while a stall is
10762
- * in effect — cleared the moment `stalledAtSeq` itself clears (see
10763
- * `process()`), since once unstalled the ordinary watermark check via
10764
- * `deliveredSeq` already covers everything delivered so far, making this
10765
- * redundant. Needed because the stall-gap-prevention rule in `process()`
10766
- * deliberately does NOT advance `cursor` past a seq above the
10767
- * still-unresolved `stalledAtSeq`, even once that seq's own handler
10768
- * succeeds — so `dedupWatermark()` alone can't distinguish "already
10769
- * succeeded, don't re-run" from "never yet attempted" for anything in
10770
- * that gap.
10771
- */
11003
+ /** Successful side effects retained until their durable acknowledgement. */
10772
11004
  processedSeqs = /* @__PURE__ */ new Set();
11005
+ failedEnvelopes = /* @__PURE__ */ new Map();
11006
+ /** Received work lacking a successful handler receipt, including malformed frames. */
11007
+ unresolvedSeqs = /* @__PURE__ */ new Set();
10773
11008
  /**
10774
11009
  * Finding P3: the pending `drainOutbox` long-poll retry backoff, if any —
10775
11010
  * cancellable so `enterRevoked()` can unblock it immediately instead of
@@ -11062,8 +11297,8 @@ var ConnectionManager = class {
11062
11297
  * - F3 (at-most-once): the old code persisted the cursor advance BEFORE
11063
11298
  * `onEnvelope` even ran (fire-and-forget) — a handler that then failed
11064
11299
  * left a redelivery-proof envelope permanently marked processed. Inbound
11065
- * envelopes are now serialized through `processingChain` (one handler
11066
- * fully settles before the next starts) and the cursor only advances
11300
+ * handlers may run independently; their receipt commits are serialized.
11301
+ * The cursor only advances
11067
11302
  * AFTER the handler resolves successfully; a rejection leaves the
11068
11303
  * cursor where it was (see `stalledAtSeq`), so a future reconnect's
11069
11304
  * redelivery re-attempts it — safe because every server->daemon type is
@@ -11079,62 +11314,72 @@ var ConnectionManager = class {
11079
11314
  if (tracked && watermark !== void 0 && envelope.seq <= watermark) return false;
11080
11315
  if (tracked) {
11081
11316
  const seq3 = envelope.seq;
11082
- if (this.inFlightSeqs.has(seq3) || this.processedSeqs.has(seq3)) return false;
11317
+ if (this.inFlightSeqs.has(seq3)) return false;
11318
+ if (!this.processedSeqs.has(seq3)) this.unresolvedSeqs.add(seq3);
11083
11319
  this.inFlightSeqs.add(seq3);
11084
- this.noteDelivered(seq3);
11085
11320
  }
11086
- this.processingChain = this.processingChain.then(() => this.process(envelope, tracked));
11321
+ const controlKey = envelope.task_id !== void 0 && !envelope.type.startsWith("task.offer") ? envelope.task_id : void 0;
11322
+ const prior = controlKey === void 0 ? Promise.resolve() : this.controlTails.get(controlKey) ?? Promise.resolve();
11323
+ const operation = prior.then(() => this.process(envelope, tracked));
11324
+ if (controlKey !== void 0) {
11325
+ this.controlTails.set(controlKey, operation);
11326
+ void operation.finally(() => {
11327
+ if (this.controlTails.get(controlKey) === operation) this.controlTails.delete(controlKey);
11328
+ });
11329
+ }
11330
+ this.processingChain = Promise.all([this.processingChain, operation]).then(() => void 0);
11087
11331
  return true;
11088
11332
  }
11089
- /**
11090
- * The local watermark `deliver()` dedupes inbound `task.*` envelopes
11091
- * against. It is deliberately NOT the long-poll query cursor: that query
11092
- * is the kernel acknowledgement and uses only the successfully processed
11093
- * `cursor` (see the constructor). Normally this local watermark is
11094
- * `deliveredSeq` — which is always >= `cursor` (every envelope that
11095
- * reaches `advanceCursor` already passed through `noteDelivered` first,
11096
- * see `deliver`) — so this is the literal `max(cursor, deliveredSeq)` the
11097
- * design calls for, just expressed via that invariant rather than an
11098
- * explicit `Math.max`.
11099
- *
11100
- * While `stalledAtSeq` is set, this collapses to the durable `cursor`
11101
- * alone, deliberately ignoring however far `deliveredSeq` had already run
11102
- * ahead before the failure was known: that's what lets the stalled
11103
- * envelope's own redelivery (and everything after it, right up to a
11104
- * fresh success) get past this same dedup check instead of being
11105
- * self-deduped by the client's own earlier eager tracking of envelopes
11106
- * whose outcome wasn't known yet. No separate "reset deliveredSeq on
11107
- * reconnect" step is needed for this to be correct — collapsing to
11108
- * `cursor` exactly while stalled already produces the right answer on
11109
- * every long-poll retry path. NOT resetting it unconditionally on every
11110
- * retry lets `deliveredSeq` keep doing its job of not re-dispatching
11111
- * something already in flight while a handler is still running.
11112
- */
11113
- dedupWatermark() {
11114
- return this.stalledAtSeq !== void 0 ? this.cursor : this.deliveredSeq ?? this.cursor;
11333
+ /** A committed terminal can settle a failed offer receipt even if cloud
11334
+ * cancellation has since filtered that offer from mailbox replay. */
11335
+ retryTaskReceipts(taskId) {
11336
+ for (const envelope of this.failedEnvelopes.values()) {
11337
+ if (envelope.task_id === taskId) this.deliver(envelope);
11338
+ }
11115
11339
  }
11116
- /** Design A: eagerly advance the in-memory delivery watermark — called for every `task.*` envelope `deliver()` admits past dedup, regardless of transport or of whether its handler has even started yet. */
11117
- noteDelivered(seq3) {
11118
- if (this.deliveredSeq === void 0 || seq3 > this.deliveredSeq) this.deliveredSeq = seq3;
11340
+ /** Only a durable acknowledgement can deduplicate a whole prefix. */
11341
+ dedupWatermark() {
11342
+ return this.cursor;
11119
11343
  }
11120
11344
  async process(envelope, tracked) {
11121
11345
  const seq3 = tracked ? envelope.seq : void 0;
11122
11346
  try {
11347
+ if (tracked && seq3 <= (this.cursor ?? 0)) return;
11123
11348
  if (tracked && this.cursor === void 0) {
11124
- await this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0);
11125
- this.cursor = 0;
11349
+ this.cursorInitialization ??= this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0).then(() => {
11350
+ this.cursor = 0;
11351
+ }).catch((error) => {
11352
+ this.cursorInitialization = void 0;
11353
+ throw error;
11354
+ });
11355
+ await this.cursorInitialization;
11126
11356
  }
11127
- await this.opts.onEnvelope(envelope);
11357
+ if (!tracked || !this.processedSeqs.has(seq3)) await this.opts.onEnvelope(envelope);
11128
11358
  if (!tracked) return;
11129
- if (this.stalledAtSeq !== void 0 && envelope.seq !== this.stalledAtSeq) {
11130
- this.processedSeqs.add(seq3);
11131
- return;
11132
- }
11133
- this.stalledAtSeq = void 0;
11134
- await this.advanceCursor(envelope.seq);
11135
- this.processedSeqs.clear();
11359
+ this.failedEnvelopes.delete(seq3);
11360
+ this.processedSeqs.add(seq3);
11361
+ this.unresolvedSeqs.delete(seq3);
11362
+ const completion = this.completionTail.catch(() => void 0).then(async () => {
11363
+ let firstUnresolved = Infinity;
11364
+ for (const value of this.unresolvedSeqs) firstUnresolved = Math.min(firstUnresolved, value);
11365
+ let prefix = this.cursor ?? 0;
11366
+ for (const value of this.processedSeqs) if (value < firstUnresolved) prefix = Math.max(prefix, value);
11367
+ if (prefix > (this.cursor ?? 0)) await this.advanceCursor(prefix);
11368
+ for (const value of this.processedSeqs) {
11369
+ if (value <= (this.cursor ?? 0)) {
11370
+ this.processedSeqs.delete(value);
11371
+ this.failedEnvelopes.delete(value);
11372
+ }
11373
+ }
11374
+ this.stalledAtSeq = this.unresolvedSeqs.size > 0 ? firstUnresolved : void 0;
11375
+ });
11376
+ this.completionTail = completion;
11377
+ await completion;
11136
11378
  } catch (err) {
11137
- if (tracked && this.stalledAtSeq === void 0) this.stalledAtSeq = envelope.seq;
11379
+ if (tracked) {
11380
+ this.failedEnvelopes.set(seq3, envelope);
11381
+ this.stalledAtSeq = Math.min(envelope.seq, this.stalledAtSeq ?? Infinity);
11382
+ }
11138
11383
  console.error(
11139
11384
  `[byok/client] envelope handler failed for ${envelope.type}${typeof envelope.seq === "number" ? ` (seq=${envelope.seq})` : ""}; cursor left unadvanced so a reconnect redelivers it:`,
11140
11385
  err
@@ -11143,11 +11388,10 @@ var ConnectionManager = class {
11143
11388
  if (tracked) this.inFlightSeqs.delete(seq3);
11144
11389
  }
11145
11390
  }
11146
- /** Serialized with handler completion, so invalid work cannot be acked by later success. */
11391
+ /** Recorded synchronously at receive, before a later successful receipt can commit. */
11147
11392
  noteValidationFailure(seq3) {
11148
- this.processingChain = this.processingChain.then(() => {
11149
- if (this.stalledAtSeq === void 0) this.stalledAtSeq = seq3;
11150
- });
11393
+ this.unresolvedSeqs.add(seq3);
11394
+ this.stalledAtSeq = Math.min(seq3, this.stalledAtSeq ?? seq3);
11151
11395
  }
11152
11396
  async advanceCursor(seq3) {
11153
11397
  if (this.cursor !== void 0 && seq3 <= this.cursor) return;
@@ -11517,7 +11761,7 @@ var CursorStore = class {
11517
11761
  async save(serverUrl, deviceId, cursor) {
11518
11762
  const file = this.fileFor(serverUrl, deviceId);
11519
11763
  await promises.mkdir(path14__default.dirname(file), { recursive: true, mode: 448 });
11520
- await atomicWriteFile(file, JSON.stringify({ cursor }));
11764
+ await atomicWriteFile(file, JSON.stringify({ cursor }), { fsync: true });
11521
11765
  }
11522
11766
  /** Remove any persisted cursor for (serverUrl, deviceId) — a no-op if none exists. Called from `pair()` (finding F5) so a device that's about to be replaced never leaves a cursor a future, unrelated device could somehow inherit. */
11523
11767
  async clear(serverUrl, deviceId) {
@@ -12010,9 +12254,9 @@ function isSqliteAvailable() {
12010
12254
  }
12011
12255
  }
12012
12256
  var SECURE_FILE_MODE = 384;
12013
- function openJournalDatabase(path46, busyTimeoutMs, faults) {
12257
+ function openJournalDatabase(path47, busyTimeoutMs, faults) {
12014
12258
  const { DatabaseSync } = loadSqliteModule();
12015
- const db = new DatabaseSync(path46, { timeout: busyTimeoutMs });
12259
+ const db = new DatabaseSync(path47, { timeout: busyTimeoutMs });
12016
12260
  try {
12017
12261
  faults?.onStep?.("after-open");
12018
12262
  db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
@@ -12156,9 +12400,9 @@ var RECEIVED_STATE = "received";
12156
12400
  function byteLength2(value) {
12157
12401
  return Buffer.byteLength(value, "utf8");
12158
12402
  }
12159
- function fileBytes(path46) {
12403
+ function fileBytes(path47) {
12160
12404
  try {
12161
- return statSync(path46).size;
12405
+ return statSync(path47).size;
12162
12406
  } catch {
12163
12407
  return 0;
12164
12408
  }
@@ -13498,6 +13742,109 @@ async function spillOversizedEvent(event, deps) {
13498
13742
  return bounded2.result;
13499
13743
  }
13500
13744
 
13745
+ // src/daemon/admission-wait.ts
13746
+ async function awaitAdmission(operation, signal) {
13747
+ signal.throwIfAborted();
13748
+ let onAbort;
13749
+ const aborted = new Promise((_, reject) => {
13750
+ onAbort = () => reject(signal.reason);
13751
+ signal.addEventListener("abort", onAbort, { once: true });
13752
+ });
13753
+ try {
13754
+ return await Promise.race([Promise.resolve().then(() => {
13755
+ signal.throwIfAborted();
13756
+ return operation();
13757
+ }), aborted]);
13758
+ } finally {
13759
+ signal.removeEventListener("abort", onAbort);
13760
+ }
13761
+ }
13762
+
13763
+ // src/daemon/runtime-start.ts
13764
+ async function startOwnedRuntime(start, input, signal, timeoutMs) {
13765
+ const controller = new AbortController();
13766
+ let session;
13767
+ let failure;
13768
+ let settled = false;
13769
+ const retryDisposal = async () => {
13770
+ if (session) {
13771
+ let timer2;
13772
+ try {
13773
+ await Promise.race([
13774
+ Promise.resolve().then(() => session.interrupt()).catch(() => void 0),
13775
+ new Promise((resolve) => {
13776
+ timer2 = setTimeout(resolve, 1e3);
13777
+ timer2.unref?.();
13778
+ })
13779
+ ]);
13780
+ } finally {
13781
+ if (timer2) clearTimeout(timer2);
13782
+ }
13783
+ return session.close();
13784
+ }
13785
+ if (isRuntimeStartupDisposalFailure(failure)) return failure.retryDisposal();
13786
+ if (settled && isRuntimeExecutionFailure(failure)) return;
13787
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup has not returned a cleanup receipt" });
13788
+ };
13789
+ let rejectAbort;
13790
+ const aborted = new Promise((_, reject) => {
13791
+ rejectAbort = reject;
13792
+ });
13793
+ const abort = () => {
13794
+ controller.abort();
13795
+ rejectAbort(new RuntimeStartupDisposalFailure(retryDisposal));
13796
+ };
13797
+ signal.addEventListener("abort", abort, { once: true });
13798
+ const timer = setTimeout(abort, timeoutMs);
13799
+ timer.unref?.();
13800
+ try {
13801
+ if (signal.aborted) {
13802
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup was cancelled before invocation" });
13803
+ }
13804
+ const startup = Promise.resolve().then(() => start({ ...input, signal: controller.signal })).then((value) => {
13805
+ session = value;
13806
+ settled = true;
13807
+ return value;
13808
+ }, (error) => {
13809
+ failure = error;
13810
+ settled = true;
13811
+ throw error;
13812
+ });
13813
+ return await Promise.race([startup, aborted]);
13814
+ } finally {
13815
+ clearTimeout(timer);
13816
+ signal.removeEventListener("abort", abort);
13817
+ }
13818
+ }
13819
+
13820
+ // src/daemon/artifact-read.ts
13821
+ var DEFAULT_ARTIFACT_LIMITS = Object.freeze({
13822
+ maxFileBytes: 16 * 1024 * 1024,
13823
+ maxTaskBytes: 64 * 1024 * 1024
13824
+ });
13825
+ async function readArtifactBytes(handle, maxBytes, signal, account) {
13826
+ signal.throwIfAborted();
13827
+ const stat = await handle.stat();
13828
+ if (stat.size > maxBytes) throw new Error("artifact local byte budget exceeded");
13829
+ const chunks = [];
13830
+ let total = 0;
13831
+ const stream = handle.createReadStream({ autoClose: false, start: 0, signal, highWaterMark: 64 * 1024 });
13832
+ try {
13833
+ for await (const data of stream) {
13834
+ signal.throwIfAborted();
13835
+ const chunk = data;
13836
+ account(chunk.length);
13837
+ total += chunk.length;
13838
+ if (total > maxBytes) throw new Error("artifact local byte budget exceeded");
13839
+ chunks.push(chunk);
13840
+ }
13841
+ signal.throwIfAborted();
13842
+ return Buffer.concat(chunks, total);
13843
+ } finally {
13844
+ stream.destroy();
13845
+ }
13846
+ }
13847
+
13501
13848
  // src/daemon/environment.ts
13502
13849
  var BASE_PLATFORM_ALLOWLIST = [
13503
13850
  "PATH",
@@ -13697,6 +14044,78 @@ var ProgressBatcher = class {
13697
14044
  }
13698
14045
  }
13699
14046
  };
14047
+ var DurableJsonlFile = class {
14048
+ constructor(filePath) {
14049
+ this.filePath = filePath;
14050
+ }
14051
+ filePath;
14052
+ failure;
14053
+ assertWritable() {
14054
+ if (this.failure !== void 0) {
14055
+ throw new Error("durable JSONL writer is quarantined after uncertain I/O; reopen and validate the log before retrying", { cause: this.failure });
14056
+ }
14057
+ }
14058
+ /** Replay is not a durability receipt: confirm the recovered bytes before use. */
14059
+ async confirmRecovered() {
14060
+ let handle;
14061
+ try {
14062
+ handle = await promises.open(this.filePath, "r+");
14063
+ } catch (error) {
14064
+ if (error.code === "ENOENT") return;
14065
+ throw error;
14066
+ }
14067
+ try {
14068
+ await handle.sync();
14069
+ } finally {
14070
+ await handle.close();
14071
+ }
14072
+ await this.syncDirectory();
14073
+ }
14074
+ async append(json) {
14075
+ this.assertWritable();
14076
+ try {
14077
+ const handle = await promises.open(this.filePath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
14078
+ try {
14079
+ const bytes = Buffer.from(`${json}
14080
+ `, "utf8");
14081
+ let offset = 0;
14082
+ while (offset < bytes.length) {
14083
+ const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset);
14084
+ if (bytesWritten <= 0) throw new Error("durable JSONL append made no progress");
14085
+ offset += bytesWritten;
14086
+ }
14087
+ await handle.sync();
14088
+ } finally {
14089
+ await handle.close();
14090
+ }
14091
+ await this.syncDirectory();
14092
+ } catch (error) {
14093
+ this.failure = error;
14094
+ throw error;
14095
+ }
14096
+ }
14097
+ async replace(lines) {
14098
+ this.assertWritable();
14099
+ try {
14100
+ await atomicWriteFile(this.filePath, lines.map((line) => `${line}
14101
+ `).join(""), { mode: 384, fsync: true });
14102
+ } catch (error) {
14103
+ this.failure = error;
14104
+ throw error;
14105
+ }
14106
+ }
14107
+ async syncDirectory() {
14108
+ if (process.platform === "win32") return;
14109
+ const directory = await promises.open(path14__default.dirname(this.filePath), "r");
14110
+ try {
14111
+ await directory.sync();
14112
+ } finally {
14113
+ await directory.close();
14114
+ }
14115
+ }
14116
+ };
14117
+
14118
+ // src/daemon/agent-message-outbox.ts
13700
14119
  var AGENT_MESSAGE_DIRECTORY = path14__default.join(".byok", "messages");
13701
14120
  var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
13702
14121
  var AgentMessageOutboxError = class extends Error {
@@ -13723,11 +14142,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13723
14142
  constructor(homeDir, outboxPath) {
13724
14143
  this.homeDir = homeDir;
13725
14144
  this.outboxPath = outboxPath;
14145
+ this.file = new DurableJsonlFile(outboxPath);
13726
14146
  }
13727
14147
  homeDir;
13728
14148
  outboxPath;
13729
14149
  pendingByTask = /* @__PURE__ */ new Map();
14150
+ revokedTasks = /* @__PURE__ */ new Set();
13730
14151
  dispositionByTask = /* @__PURE__ */ new Map();
14152
+ file;
13731
14153
  nextCursor = 1;
13732
14154
  logEntries = 0;
13733
14155
  writeTail = Promise.resolve();
@@ -13736,6 +14158,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13736
14158
  await ensureSecureDir(directory);
13737
14159
  const outbox = new _AgentMessageOutbox(homeDir, path14__default.join(directory, AGENT_MESSAGE_OUTBOX_FILENAME));
13738
14160
  await outbox.load();
14161
+ await outbox.file.confirmRecovered();
13739
14162
  return outbox;
13740
14163
  }
13741
14164
  /** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
@@ -13776,9 +14199,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13776
14199
  records() {
13777
14200
  return Object.freeze([...this.pendingByTask.values()].sort((a, b) => a.cursor - b.cursor));
13778
14201
  }
13779
- /** Activated records with no exact disposition yet; only these may be transport-replayed. */
14202
+ /** Records without disposition or local revoke; transport replay additionally requires session binding. */
13780
14203
  retryableRecords() {
13781
- return Object.freeze(this.records().filter((record4) => !this.dispositionByTask.has(record4.taskId)));
14204
+ return Object.freeze(this.records().filter((record4) => !this.dispositionByTask.has(record4.taskId) && !this.revokedTasks.has(record4.taskId)));
13782
14205
  }
13783
14206
  get(taskId) {
13784
14207
  return this.pendingByTask.get(taskId);
@@ -13790,13 +14213,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13790
14213
  if (byteCount < 1 || byteCount > input.requirement.maxBytes) throw new AgentMessageOutboxError("message body exceeds the offer byte contract");
13791
14214
  const existing = this.pendingByTask.get(input.taskId);
13792
14215
  if (existing !== void 0) {
13793
- if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || !sameAgentRef(existing.agentRef, input.agentRef)) {
14216
+ if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || input.sessionRef !== void 0 && existing.sessionRef !== input.sessionRef || !sameAgentRef(existing.agentRef, input.agentRef)) {
13794
14217
  throw new AgentMessageOutboxError("required-message task already has a different immutable draft");
13795
14218
  }
13796
14219
  return existing;
13797
14220
  }
13798
- if (this.pendingByTask.size >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
13799
- const pendingBytes = this.records().reduce((sum, record5) => sum + record5.byteCount, 0);
14221
+ const sending = this.records().filter((record5) => !this.isTerminalEvidence(record5.taskId));
14222
+ if (sending.length >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
14223
+ const pendingBytes = sending.reduce((sum, record5) => sum + record5.byteCount, 0);
13800
14224
  if (pendingBytes + byteCount > input.maxPendingBytes) throw new AgentMessageOutboxError("message outbox byte quota is exhausted");
13801
14225
  const record4 = Object.freeze({
13802
14226
  schema: 1,
@@ -13818,8 +14242,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13818
14242
  return record4;
13819
14243
  });
13820
14244
  }
13821
- async activate(taskId, sessionRef) {
14245
+ async activate(taskId, sessionRef, isCurrent = () => true) {
13822
14246
  return this.exclusive(async () => {
14247
+ if (!isCurrent() || this.revokedTasks.has(taskId)) return void 0;
13823
14248
  const record4 = this.pendingByTask.get(taskId);
13824
14249
  if (record4 === void 0) return void 0;
13825
14250
  if (record4.sessionRef !== void 0) {
@@ -13832,6 +14257,18 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13832
14257
  return activated;
13833
14258
  });
13834
14259
  }
14260
+ /** Local lifecycle cancellation, never a synthesized consumer disposition. */
14261
+ async revoke(taskId) {
14262
+ return this.exclusive(async () => {
14263
+ const record4 = this.pendingByTask.get(taskId);
14264
+ if (record4 === void 0 || this.revokedTasks.has(taskId)) return;
14265
+ await this.appendEntry({ schema: 1, kind: "revoke", taskId, messageId: record4.messageId });
14266
+ this.revokedTasks.add(taskId);
14267
+ });
14268
+ }
14269
+ isTerminalEvidence(taskId) {
14270
+ return this.revokedTasks.has(taskId) || this.dispositionByTask.get(taskId)?.outcome === "refused";
14271
+ }
13835
14272
  publishPayload(record4) {
13836
14273
  if (record4.sessionRef === void 0) throw new AgentMessageOutboxError("message draft is not bound to a durable session");
13837
14274
  return AgentMessagePublishPayloadSchema.parse({
@@ -13855,6 +14292,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13855
14292
  await this.appendEntry({ schema: 1, kind: "disposition", taskId, disposition });
13856
14293
  if (disposition.outcome === "accepted") {
13857
14294
  this.pendingByTask.delete(taskId);
14295
+ this.revokedTasks.delete(taskId);
13858
14296
  this.dispositionByTask.delete(taskId);
13859
14297
  } else {
13860
14298
  this.dispositionByTask.set(taskId, disposition);
@@ -13873,6 +14311,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13873
14311
  if (error.code === "ENOENT") return;
13874
14312
  throw error;
13875
14313
  }
14314
+ if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentMessageOutboxError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
13876
14315
  for (const line of raw.split("\n")) {
13877
14316
  if (line.length === 0) continue;
13878
14317
  const entry = JSON.parse(line);
@@ -13887,11 +14326,16 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13887
14326
  if (record4 === void 0 || record4.messageId !== entry.messageId) throw new AgentMessageOutboxError("message activation has no exact draft");
13888
14327
  if (record4.sessionRef !== void 0 && record4.sessionRef !== entry.sessionRef) throw new AgentMessageOutboxError("message activation session conflicts");
13889
14328
  this.pendingByTask.set(entry.taskId, Object.freeze({ ...record4, sessionRef: entry.sessionRef }));
14329
+ } else if (entry.kind === "revoke") {
14330
+ const record4 = this.pendingByTask.get(entry.taskId);
14331
+ if (record4 === void 0 || record4.messageId !== entry.messageId) throw new AgentMessageOutboxError("message revocation has no exact draft");
14332
+ this.revokedTasks.add(entry.taskId);
13890
14333
  } else if (entry.kind === "disposition") {
13891
14334
  const record4 = this.pendingByTask.get(entry.taskId);
13892
14335
  if (record4 === void 0 || !exactDisposition(record4, entry.disposition)) throw new AgentMessageOutboxError("message outbox contains a mismatched disposition");
13893
14336
  if (entry.disposition.outcome === "accepted") {
13894
14337
  this.pendingByTask.delete(entry.taskId);
14338
+ this.revokedTasks.delete(entry.taskId);
13895
14339
  this.dispositionByTask.delete(entry.taskId);
13896
14340
  } else {
13897
14341
  this.dispositionByTask.set(entry.taskId, entry.disposition);
@@ -13902,23 +14346,47 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13902
14346
  }
13903
14347
  }
13904
14348
  async appendEntry(entry) {
13905
- const handle = await promises.open(this.outboxPath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
13906
- try {
13907
- await handle.write(`${stableJson(entry)}
13908
- `, void 0, "utf8");
13909
- await handle.sync();
13910
- this.logEntries += 1;
13911
- } finally {
13912
- await handle.close();
13913
- }
14349
+ await this.file.append(stableJson(entry));
14350
+ this.logEntries += 1;
13914
14351
  }
13915
- async compact() {
13916
- const entries = this.records().flatMap((record4) => {
14352
+ /**
14353
+ * Explicit operator maintenance under the Agent-home single-writer lease.
14354
+ * Copy complete terminal evidence durably before removing it from the live log.
14355
+ * Archives are audit artifacts, never another replay input. held remains live.
14356
+ */
14357
+ async archiveTerminalRecords(archiveDirectory) {
14358
+ return this.exclusive(async () => {
14359
+ if (!path14__default.isAbsolute(archiveDirectory)) throw new AgentMessageOutboxError("message archive directory must be absolute");
14360
+ const terminal = this.records().filter((record4) => this.isTerminalEvidence(record4.taskId));
14361
+ if (terminal.length === 0) return void 0;
14362
+ await ensureSecureDir(archiveDirectory);
14363
+ const archivePath = path14__default.join(archiveDirectory, `message-terminal-${randomUUID()}.jsonl`);
14364
+ await atomicWriteFile(archivePath, this.snapshotEntries(terminal).map((entry) => `${stableJson(entry)}
14365
+ `).join(""), { mode: 384, fsync: true });
14366
+ const retained = this.records().filter((record4) => !this.isTerminalEvidence(record4.taskId));
14367
+ const entries = this.snapshotEntries(retained);
14368
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
14369
+ for (const record4 of terminal) {
14370
+ this.pendingByTask.delete(record4.taskId);
14371
+ this.dispositionByTask.delete(record4.taskId);
14372
+ this.revokedTasks.delete(record4.taskId);
14373
+ }
14374
+ this.logEntries = entries.length;
14375
+ return archivePath;
14376
+ });
14377
+ }
14378
+ snapshotEntries(records) {
14379
+ return records.flatMap((record4) => {
13917
14380
  const append = { schema: 1, kind: "append", record: record4 };
13918
14381
  const disposition = this.dispositionByTask.get(record4.taskId);
13919
- return disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record4.taskId, disposition }];
14382
+ const entries = disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record4.taskId, disposition }];
14383
+ if (this.revokedTasks.has(record4.taskId)) entries.push({ schema: 1, kind: "revoke", taskId: record4.taskId, messageId: record4.messageId });
14384
+ return entries;
13920
14385
  });
13921
- await atomicWriteFile(this.outboxPath, entries.map(stableJson).join(entries.length === 0 ? "" : "\n"), { mode: 384 });
14386
+ }
14387
+ async compact() {
14388
+ const entries = this.snapshotEntries(this.records());
14389
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
13922
14390
  this.logEntries = entries.length;
13923
14391
  }
13924
14392
  async exclusive(fn) {
@@ -13929,6 +14397,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13929
14397
  });
13930
14398
  await prior;
13931
14399
  try {
14400
+ this.file.assertWritable();
13932
14401
  return await fn();
13933
14402
  } finally {
13934
14403
  release();
@@ -15109,7 +15578,7 @@ var TaskRunner = class {
15109
15578
  */
15110
15579
  stoppingOffers = false;
15111
15580
  get activeTaskCount() {
15112
- return this.tasks.size;
15581
+ return this.tasks.size + this.startupOwners.size;
15113
15582
  }
15114
15583
  /**
15115
15584
  * Transport-boundary classification for the currently active task. Legacy
@@ -15155,6 +15624,12 @@ var TaskRunner = class {
15155
15624
  const active = this.tasks.get(taskId);
15156
15625
  const context = pending ?? (active?.messageRequirement && active.agentRef && active.messageOutbox ? { agentRef: active.agentRef, requirement: active.messageRequirement, outbox: active.messageOutbox } : void 0);
15157
15626
  if (context === void 0) throw new Error("task has no active required Agent message contract");
15627
+ const isCurrent = () => {
15628
+ if (this.messageContextByToken.get(input.contextToken) !== taskId || this.messageContextByTask.get(taskId) !== input.contextToken || this.pendingCancelled.has(taskId)) return false;
15629
+ const current2 = this.tasks.get(taskId);
15630
+ return current2 === void 0 ? active === void 0 && this.pendingMessageTasks.get(taskId) === pending : (active === void 0 || current2 === active) && current2.messageOutbox === context.outbox && this.canPublishAgentMessage(current2);
15631
+ };
15632
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15158
15633
  const record4 = await context.outbox.appendDraft({
15159
15634
  taskId,
15160
15635
  tenantId: this.deps.tenantId,
@@ -15165,8 +15640,17 @@ var TaskRunner = class {
15165
15640
  maxPendingEvents: 64,
15166
15641
  maxPendingBytes: 4 * 1024 * 1024
15167
15642
  });
15168
- if (active === void 0) return { messageId: record4.messageId, state: "staged" };
15169
- const activated = await context.outbox.activate(taskId, active.session.sessionRef);
15643
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15644
+ const current = this.tasks.get(taskId);
15645
+ if (current === void 0) return { messageId: record4.messageId, state: "staged" };
15646
+ const activated = await this.activateAgentMessage(
15647
+ context.outbox,
15648
+ taskId,
15649
+ current.session.sessionRef,
15650
+ isCurrent,
15651
+ () => current.messageSendAttempted === true
15652
+ );
15653
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15170
15654
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
15171
15655
  this.sendAgentMessageRecord(context.outbox, activated);
15172
15656
  return { messageId: activated.messageId, state: "pending" };
@@ -15202,19 +15686,39 @@ var TaskRunner = class {
15202
15686
  });
15203
15687
  return opened;
15204
15688
  }
15689
+ /** A recovery terminal must not close first-message admission before this durable draft has a disposition. */
15690
+ hasPendingRecoveredAgentMessage(taskId) {
15691
+ return this.recoveredMessageOutboxes.get(taskId)?.retryableRecords().some((record4) => record4.taskId === taskId && record4.sessionRef !== void 0) === true;
15692
+ }
15205
15693
  /** Retry stable recovered records after a transport handshake/re-handshake. */
15206
15694
  retryRecoveredAgentMessages() {
15207
15695
  for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
15208
15696
  const record4 = outbox.get(taskId);
15209
- if (record4?.sessionRef !== void 0) this.sendAgentMessageRecord(outbox, record4);
15697
+ if (record4?.sessionRef !== void 0 && this.hasPendingRecoveredAgentMessage(taskId)) this.sendAgentMessageRecord(outbox, record4);
15698
+ }
15699
+ }
15700
+ canPublishAgentMessage(active) {
15701
+ return this.tasks.get(active.taskId) === active && !active.beingTornDown;
15702
+ }
15703
+ /** All authors and startup use the same queue-time and post-I/O authority check. */
15704
+ async activateAgentMessage(outbox, taskId, sessionRef, isCurrent, wasSendAttempted = () => false) {
15705
+ if (!isCurrent()) return void 0;
15706
+ const record4 = await outbox.activate(taskId, sessionRef, isCurrent);
15707
+ if (!isCurrent()) {
15708
+ if (!wasSendAttempted()) await outbox.revoke(taskId);
15709
+ return void 0;
15210
15710
  }
15711
+ return record4;
15211
15712
  }
15212
15713
  sendAgentMessageRecord(outbox, record4) {
15714
+ const active = this.tasks.get(record4.taskId);
15715
+ const authorized = active === void 0 ? this.recoveredMessageOutboxes.get(record4.taskId) === outbox : active.messageOutbox === outbox && this.canPublishAgentMessage(active);
15716
+ if (!authorized || record4.sessionRef === void 0 || !outbox.retryableRecords().some((pending) => pending.messageId === record4.messageId)) return;
15717
+ if (active !== void 0) active.messageSendAttempted = true;
15213
15718
  this.deps.send(createEnvelope("agent.message.publish", outbox.publishPayload(record4), {
15214
15719
  taskId: record4.taskId,
15215
15720
  sessionRef: record4.sessionRef
15216
15721
  }));
15217
- const active = this.tasks.get(record4.taskId);
15218
15722
  if (active?.messageOutbox === outbox) {
15219
15723
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15220
15724
  active.messageRetryTimer = setTimeout(() => {
@@ -15254,6 +15758,7 @@ var TaskRunner = class {
15254
15758
  if (outcome === "accepted") this.recoveredMessageOutboxes.delete(taskId);
15255
15759
  return;
15256
15760
  }
15761
+ if (this.tasks.get(taskId) !== active) return;
15257
15762
  if (outcome === "held") {
15258
15763
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15259
15764
  active.messageRetryTimer = void 0;
@@ -15263,6 +15768,8 @@ var TaskRunner = class {
15263
15768
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15264
15769
  active.messageRetryTimer = void 0;
15265
15770
  this.revokeAgentMessageContext(taskId);
15771
+ active.pendingMessageCompletion = void 0;
15772
+ await this.fail(taskId, "required Agent message was refused", false);
15266
15773
  return;
15267
15774
  }
15268
15775
  if (outcome !== "accepted") return;
@@ -15281,6 +15788,9 @@ var TaskRunner = class {
15281
15788
  /** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
15282
15789
  stopAcceptingOffers() {
15283
15790
  this.stoppingOffers = true;
15791
+ if (this.startupRetryTimer) clearTimeout(this.startupRetryTimer);
15792
+ this.startupRetryTimer = void 0;
15793
+ for (const abort of this.inFlightBlobAborts.values()) abort.abort();
15284
15794
  }
15285
15795
  /**
15286
15796
  * Shutdown of every currently ACTIVE task for the control socket's
@@ -15319,9 +15829,65 @@ var TaskRunner = class {
15319
15829
  * ahead of this method — see `daemon-control-socket.test.ts`'s dedicated
15320
15830
  * regression test for the exact scenario.
15321
15831
  */
15322
- async shutdownActiveTasks(reason) {
15323
- const active = [...this.tasks.values()];
15832
+ /** Startup resources have an owner even before a Session can become active. */
15833
+ startupRetryTimer;
15834
+ claimedHarnesses = /* @__PURE__ */ new Map();
15835
+ homeReservations = /* @__PURE__ */ new Map();
15836
+ startupOwners = /* @__PURE__ */ new Map();
15837
+ startupDisposals = /* @__PURE__ */ new Map();
15838
+ disposeStartupOwner(taskId) {
15839
+ const existing = this.startupDisposals.get(taskId);
15840
+ if (existing) return existing;
15841
+ const attempt = this.disposeStartupOwnerOnce(taskId).finally(() => this.startupDisposals.delete(taskId));
15842
+ this.startupDisposals.set(taskId, attempt);
15843
+ return attempt;
15844
+ }
15845
+ async disposeStartupOwnerOnce(taskId) {
15846
+ const owner = this.startupOwners.get(taskId);
15847
+ if (!owner) return true;
15848
+ try {
15849
+ await owner.settle?.();
15850
+ await owner.dispose();
15851
+ await this.deps.awaitTerminalCommit?.(taskId);
15852
+ await owner.release();
15853
+ } catch (caught) {
15854
+ const failure = isRuntimeDisposalFailure(caught) ? caught : new RuntimeDisposalFailure({
15855
+ stage: "quiescence",
15856
+ reason: "startup runtime disposal has not been confirmed"
15857
+ }, { cause: caught });
15858
+ this.deps.onRuntimeDisposalFailure?.({
15859
+ taskId,
15860
+ runtimeId: owner.runtimeId,
15861
+ stage: failure.stage,
15862
+ reason: failure.message
15863
+ });
15864
+ console.error(`[byok/client] startup ownership retained for ${taskId}: ${failure.message}`);
15865
+ if (!this.startupRetryTimer && !this.stoppingOffers) {
15866
+ this.startupRetryTimer = setTimeout(() => {
15867
+ this.startupRetryTimer = void 0;
15868
+ for (const id of this.startupOwners.keys()) {
15869
+ if (!this.inFlightOffers.has(id)) void this.disposeStartupOwner(id);
15870
+ }
15871
+ }, 1e3);
15872
+ this.startupRetryTimer.unref?.();
15873
+ }
15874
+ return false;
15875
+ }
15876
+ if (this.startupOwners.get(taskId) === owner) this.startupOwners.delete(taskId);
15877
+ this.pendingMessageTasks.delete(taskId);
15878
+ this.revokeAgentMessageContext(taskId);
15879
+ this.revokeAgentMemoryContext(taskId);
15880
+ return true;
15881
+ }
15882
+ async shutdownActiveTasks(reason) {
15883
+ const active = [...this.tasks.values()];
15324
15884
  await Promise.all(active.map((task) => this.shutdownTask(task, reason)));
15885
+ for (const taskId of this.startupOwners.keys()) {
15886
+ if (!await this.disposeStartupOwner(taskId)) throw new RuntimeDisposalFailure({
15887
+ stage: "quiescence",
15888
+ reason: "startup runtime ownership remains quarantined"
15889
+ });
15890
+ }
15325
15891
  }
15326
15892
  /**
15327
15893
  * M5 batch-3 (workstream 2): the ONE shared per-task teardown sequence —
@@ -15353,14 +15919,19 @@ var TaskRunner = class {
15353
15919
  * a genuine protocol bug, not a benign race — mirrors `pump()`'s own
15354
15920
  * identity-check guard for the same class of race.
15355
15921
  */
15922
+ async interruptBounded(session) {
15923
+ await raceSettleFirst(
15924
+ () => session.interrupt(),
15925
+ this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS
15926
+ );
15927
+ }
15356
15928
  async teardownActiveTask(active, reason, retryable) {
15357
15929
  if (active.finalizationStarted) return this.finish(active.taskId);
15358
15930
  if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
15359
15931
  active.beingTornDown = true;
15360
15932
  active.blobAbort.abort();
15361
15933
  await this.observeGit(active, "salvage");
15362
- const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
15363
- await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
15934
+ await this.interruptBounded(active.session);
15364
15935
  if (this.tasks.get(active.taskId) !== active) return true;
15365
15936
  await this.persistAgentTerminalEvidence(active, "failed", reason);
15366
15937
  this.deps.send(
@@ -15462,7 +16033,7 @@ var TaskRunner = class {
15462
16033
  }
15463
16034
  }
15464
16035
  async handleOffer(taskId, payload, strictAgentOffer) {
15465
- if (this.tasks.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
16036
+ if (this.tasks.has(taskId) || this.startupOwners.has(taskId) || this.inFlightOffers.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
15466
16037
  return;
15467
16038
  }
15468
16039
  let agentRef;
@@ -15505,9 +16076,29 @@ var TaskRunner = class {
15505
16076
  }
15506
16077
  this.inFlightOffers.add(taskId);
15507
16078
  const blobAbort = new AbortController();
16079
+ const startupDeadline = Date.now() + (this.deps.startupTimeoutMs ?? 3e4);
16080
+ const startupTimer = setTimeout(
16081
+ () => blobAbort.abort(new Error("runtime startup deadline exceeded during admission/start")),
16082
+ this.deps.startupTimeoutMs ?? 3e4
16083
+ );
16084
+ startupTimer.unref?.();
16085
+ const admissionWithdrawn = () => {
16086
+ if (!blobAbort.signal.aborted) return false;
16087
+ const cancelled = this.pendingCancelled.has(taskId);
16088
+ decline(cancelled ? "cancelled before claim" : this.stoppingOffers ? "daemon is shutting down" : "runtime startup deadline exceeded", !cancelled);
16089
+ return true;
16090
+ };
15508
16091
  this.inFlightBlobAborts.set(taskId, blobAbort);
15509
16092
  let agentBinding;
15510
16093
  let agentLeaseTransferred = false;
16094
+ let reservedHome;
16095
+ const releaseReservation = () => {
16096
+ if (reservedHome === void 0) return;
16097
+ const remaining = this.homeReservations.get(reservedHome) - 1;
16098
+ if (remaining === 0) this.homeReservations.delete(reservedHome);
16099
+ else this.homeReservations.set(reservedHome, remaining);
16100
+ reservedHome = void 0;
16101
+ };
15511
16102
  try {
15512
16103
  if (this.pendingCancelled.has(taskId)) {
15513
16104
  const reason = this.pendingCancelled.get(taskId);
@@ -15532,11 +16123,14 @@ var TaskRunner = class {
15532
16123
  );
15533
16124
  return;
15534
16125
  }
15535
- const active2 = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome);
15536
- if (active2 >= limit) {
15537
- decline(`agent home busy: ${active2} active attempt(s)`, true);
16126
+ if (admissionWithdrawn()) return;
16127
+ const active = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome) + (this.homeReservations.get(canonicalHome) ?? 0);
16128
+ if (active >= limit) {
16129
+ decline(`agent home busy: ${active} active attempt(s)`, true);
15538
16130
  return;
15539
16131
  }
16132
+ this.homeReservations.set(canonicalHome, (this.homeReservations.get(canonicalHome) ?? 0) + 1);
16133
+ reservedHome = canonicalHome;
15540
16134
  }
15541
16135
  const guarded = this.deps.admissionGuard?.({ taskId, payload });
15542
16136
  if (guarded !== void 0 && !guarded.admit) {
@@ -15568,6 +16162,14 @@ var TaskRunner = class {
15568
16162
  );
15569
16163
  return;
15570
16164
  }
16165
+ if (payload.harnessId !== void 0 && (payload.runtime !== void 0 || payload.dispatchSelection !== void 0)) {
16166
+ decline("harnessId cannot be combined with a built-in runtime selection", false);
16167
+ return;
16168
+ }
16169
+ if (payload.harnessId !== void 0 && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16170
+ decline("server does not support custom-harness identity", false);
16171
+ return;
16172
+ }
15571
16173
  const requiredToolsets = "requiredToolsets" in payload ? payload.requiredToolsets : void 0;
15572
16174
  const resolvedMcp = requiredToolsets ? this.resolveMcpServers(requiredToolsets) : void 0;
15573
16175
  if (resolvedMcp && !resolvedMcp.ok) {
@@ -15580,17 +16182,29 @@ var TaskRunner = class {
15580
16182
  return;
15581
16183
  }
15582
16184
  const offered = withoutRequiredToolsets(payload);
15583
- const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
16185
+ const requestedRuntime = payload.harnessId ?? payload.dispatchSelection?.runtimeId ?? payload.runtime;
15584
16186
  const requiresAgentMemoryMcp = agentRef !== void 0 && this.deps.agentMemoryMcpBin !== void 0 && isAgentMemorySecureFilesystemAvailable(this.deps.agentMemoryFilesystemHelperBin !== void 0);
15585
- const pick = await this.pickAdapter(
15586
- requestedRuntime,
15587
- payload.policy.mode,
15588
- requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp
15589
- );
16187
+ let pick;
16188
+ try {
16189
+ pick = await this.pickAdapter(
16190
+ requestedRuntime,
16191
+ payload.policy.mode,
16192
+ requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp,
16193
+ blobAbort.signal
16194
+ );
16195
+ } catch (error) {
16196
+ if (!admissionWithdrawn()) decline(`runtime detection failed: ${errorMessage5(error)}`, true);
16197
+ return;
16198
+ }
16199
+ if (admissionWithdrawn()) return;
15590
16200
  if (!pick.ok) {
15591
16201
  decline(pick.reason, pick.retryable);
15592
16202
  return;
15593
16203
  }
16204
+ if (!isKnownRuntimeId(pick.descriptor.id) && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16205
+ decline("server does not support the selected custom harness identity", false);
16206
+ return;
16207
+ }
15594
16208
  const env = buildRuntimeEnv({
15595
16209
  ambient: process.env,
15596
16210
  requirements: pick.descriptor.environmentRequirements,
@@ -15664,18 +16278,20 @@ var TaskRunner = class {
15664
16278
  }
15665
16279
  let prepared;
15666
16280
  try {
15667
- prepared = await pick.adapter.prepare({
16281
+ prepared = await awaitAdmission(() => pick.adapter.prepare({
16282
+ signal: blobAbort.signal,
15668
16283
  offer: offered,
15669
16284
  policy: decision.policy,
15670
16285
  descriptor: pick.descriptor,
15671
16286
  requiredToolsetIds: requiredToolsets ?? [],
15672
16287
  ...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers },
15673
16288
  ...mcpToolsetTools === void 0 ? {} : { mcpToolsetTools }
15674
- });
16289
+ }), blobAbort.signal);
15675
16290
  } catch (error) {
15676
- decline(`runtime preparation failed: ${errorMessage5(error)}`, true);
16291
+ if (!admissionWithdrawn()) decline(`runtime preparation failed: ${errorMessage5(error)}`, true);
15677
16292
  return;
15678
16293
  }
16294
+ if (admissionWithdrawn()) return;
15679
16295
  if (prepared.kind === "reject") {
15680
16296
  decline(prepared.reason, prepared.retryable);
15681
16297
  return;
@@ -15683,6 +16299,7 @@ var TaskRunner = class {
15683
16299
  if (agentRef !== void 0) {
15684
16300
  try {
15685
16301
  agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
16302
+ releaseReservation();
15686
16303
  } catch (error) {
15687
16304
  decline(
15688
16305
  `Agent home admission failed: ${errorMessage5(error)}`,
@@ -15807,7 +16424,16 @@ var TaskRunner = class {
15807
16424
  },
15808
16425
  forwardedEnvironmentNames: Object.freeze(Object.keys(env).sort())
15809
16426
  });
16427
+ if (admissionWithdrawn()) {
16428
+ gitLease?.release();
16429
+ return;
16430
+ }
15810
16431
  await this.deps.beforeClaim?.(taskId, manifest.descriptor.id);
16432
+ if (admissionWithdrawn()) {
16433
+ gitLease?.release();
16434
+ return;
16435
+ }
16436
+ if (!isKnownRuntimeId(manifest.descriptor.id)) this.claimedHarnesses.set(taskId, manifest.descriptor.id);
15811
16437
  this.deps.send(
15812
16438
  createEnvelope(
15813
16439
  "task.claim",
@@ -15825,6 +16451,7 @@ var TaskRunner = class {
15825
16451
  // where an auto-selected task left the server never learning
15826
16452
  // which runtime actually ran.
15827
16453
  runtime: isKnownRuntimeId(manifest.descriptor.id) ? manifest.descriptor.id : void 0,
16454
+ ...!isKnownRuntimeId(manifest.descriptor.id) ? { harnessId: manifest.descriptor.id } : {},
15828
16455
  // S0/D-4 (`task.claim.capabilities`, docs/protocol.md §2.4): the
15829
16456
  // selected adapter's own capability self-report, carried on the
15830
16457
  // same message that establishes the task↔runtime binding. The
@@ -15942,8 +16569,57 @@ var TaskRunner = class {
15942
16569
  };
15943
16570
  let session;
15944
16571
  try {
15945
- session = await prepared.operation.start(startInput);
16572
+ session = await startOwnedRuntime(
16573
+ (input) => prepared.operation.start(input),
16574
+ startInput,
16575
+ blobAbort.signal,
16576
+ Math.max(0, startupDeadline - Date.now())
16577
+ );
15946
16578
  } catch (err) {
16579
+ const disposalFailure = isRuntimeStartupDisposalFailure(err);
16580
+ const cancelled = this.pendingCancelled.has(taskId);
16581
+ if (disposalFailure || cancelled) {
16582
+ if (cancelled) this.revokeAgentMessageContext(taskId);
16583
+ const ownedBinding2 = agentBinding;
16584
+ const cancellation = createEnvelope("task.cancelled", {
16585
+ reason: this.pendingCancelled.get(taskId),
16586
+ ...terminalIdentity(this.claimedHarnesses.get(taskId)),
16587
+ ...agentBinding === void 0 ? {} : { agentRef: agentBinding.resolution.agentRef }
16588
+ }, { taskId });
16589
+ let cancellationSettled = false;
16590
+ this.startupOwners.set(taskId, {
16591
+ runtimeId: pick.descriptor.id,
16592
+ dispose: disposalFailure ? err.retryDisposal : async () => {
16593
+ },
16594
+ ...cancelled ? { settle: async () => {
16595
+ if (cancellationSettled) return;
16596
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
16597
+ this.deps.send(cancellation);
16598
+ cancellationSettled = true;
16599
+ this.pendingCancelled.delete(taskId);
16600
+ this.addFinishedTaskId(taskId);
16601
+ } } : {},
16602
+ release: async () => {
16603
+ await ownedBinding2?.lease.release();
16604
+ gitLease?.release();
16605
+ }
16606
+ });
16607
+ agentLeaseTransferred = true;
16608
+ if (disposalFailure) this.deps.onRuntimeDisposalFailure?.({
16609
+ taskId,
16610
+ runtimeId: pick.descriptor.id,
16611
+ stage: "quiescence",
16612
+ reason: err.message
16613
+ });
16614
+ if (!cancelled) {
16615
+ if (agentBinding === void 0) await this.fail(taskId, errorMessage5(err), false);
16616
+ else await this.failClaimedAgent(taskId, errorMessage5(err), false, {
16617
+ binding: agentBinding,
16618
+ runtimeId: pick.descriptor.id
16619
+ });
16620
+ }
16621
+ return;
16622
+ }
15947
16623
  const failure = projectRuntimeBoundaryFailure(err, "start");
15948
16624
  if (failure.contractViolation) {
15949
16625
  console.error("[byok/client] runtime adapter start() returned an untyped failure", err);
@@ -15960,143 +16636,169 @@ var TaskRunner = class {
15960
16636
  }
15961
16637
  return;
15962
16638
  }
15963
- if (agentBinding !== void 0) {
15964
- try {
15965
- await agentBinding.lease.bindSession(session.sessionRef);
15966
- } catch (error) {
15967
- await session.close().catch(() => {
15968
- });
15969
- await this.failClaimedAgent(
15970
- taskId,
15971
- `Agent session execution lease could not bind the runtime session: ${errorMessage5(error)}`,
15972
- false,
15973
- {
15974
- binding: agentBinding,
15975
- runtimeId: pick.descriptor.id,
15976
- sessionRef: session.sessionRef
15977
- }
15978
- );
15979
- return;
16639
+ const ownedBinding = agentBinding;
16640
+ this.startupOwners.set(taskId, {
16641
+ runtimeId: pick.descriptor.id,
16642
+ dispose: () => session.close(),
16643
+ release: async () => {
16644
+ await ownedBinding?.lease.release();
16645
+ gitLease?.release();
15980
16646
  }
15981
- }
15982
- let active;
15983
- active = {
15984
- taskId,
15985
- blobAbort,
15986
- egressEnabled: "egressPolicy" in payload,
15987
- adapter: pick.adapter,
15988
- session,
15989
- workspaceDir,
15990
- ...agentBinding === void 0 ? {} : {
15991
- agentBinding,
15992
- agentRef: agentBinding.resolution.agentRef,
15993
- agentHandoff: {
15994
- sessionRef: session.sessionRef,
15995
- runtimeId: pick.descriptor.id,
15996
- cwd: workspaceDir
15997
- }
15998
- },
15999
- gitWorkspaceId,
16000
- gitLease,
16001
- gitBaseline,
16002
- summaryParts: [],
16003
- finalTextParts: [],
16004
- batcher: new ProgressBatcher(
16005
- (seq3, events) => {
16006
- const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
16007
- agentRef: active.agentRef,
16647
+ });
16648
+ try {
16649
+ if (agentBinding !== void 0) {
16650
+ try {
16651
+ await agentBinding.lease.bindSession(session.sessionRef);
16652
+ } catch (error) {
16653
+ await this.failClaimedAgent(
16008
16654
  taskId,
16009
- events,
16010
- serverCapabilities: this.deps.getServerCapabilities?.() ?? []
16011
- }) ?? [] : events;
16012
- if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq: seq3, events: [...projected] }, { taskId, seq: seq3 }));
16013
- },
16014
- this.deps.batcherOptions
16015
- ),
16016
- approvalQueue: [],
16017
- outputBytesSoFar: 0,
16018
- startedAtMs: Date.now(),
16019
- ...messageRequirement === void 0 ? {} : {
16020
- messageRequirement,
16021
- messageOutbox: this.pendingMessageTasks.get(taskId).outbox
16022
- },
16023
- ...terminalProjection === void 0 ? {} : { terminalProjection }
16024
- };
16025
- if (agentBinding !== void 0) {
16026
- const binding = agentBinding;
16027
- try {
16028
- await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
16029
- agentRef: binding.resolution.agentRef,
16030
- taskId,
16031
- sessionRef: session.sessionRef,
16032
- runtimeId: pick.descriptor.id,
16033
- cwd: workspaceDir,
16034
- leaseId: binding.lease.leaseId
16035
- }));
16036
- } catch (error) {
16037
- await session.close().catch(() => {
16038
- });
16039
- await this.failClaimedAgent(
16040
- taskId,
16041
- `Agent session handoff could not be durably written before start: ${errorMessage5(error)}`,
16042
- false,
16043
- {
16044
- binding: agentBinding,
16655
+ `Agent session execution lease could not bind the runtime session: ${errorMessage5(error)}`,
16656
+ false,
16657
+ {
16658
+ binding: agentBinding,
16659
+ runtimeId: pick.descriptor.id,
16660
+ sessionRef: session.sessionRef
16661
+ }
16662
+ );
16663
+ return;
16664
+ }
16665
+ }
16666
+ let active;
16667
+ active = {
16668
+ taskId,
16669
+ blobAbort,
16670
+ egressEnabled: "egressPolicy" in payload,
16671
+ adapter: pick.adapter,
16672
+ session,
16673
+ workspaceDir,
16674
+ ...agentBinding === void 0 ? {} : {
16675
+ agentBinding,
16676
+ agentRef: agentBinding.resolution.agentRef,
16677
+ agentHandoff: {
16678
+ sessionRef: session.sessionRef,
16045
16679
  runtimeId: pick.descriptor.id,
16046
- sessionRef: session.sessionRef
16680
+ cwd: workspaceDir
16047
16681
  }
16682
+ },
16683
+ gitWorkspaceId,
16684
+ gitLease,
16685
+ gitBaseline,
16686
+ summaryParts: [],
16687
+ finalTextParts: [],
16688
+ batcher: new ProgressBatcher(
16689
+ (seq3, events) => {
16690
+ const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
16691
+ agentRef: active.agentRef,
16692
+ taskId,
16693
+ events,
16694
+ serverCapabilities: this.deps.getServerCapabilities?.() ?? []
16695
+ }) ?? [] : events;
16696
+ if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq: seq3, events: [...projected] }, { taskId, seq: seq3 }));
16697
+ },
16698
+ this.deps.batcherOptions
16699
+ ),
16700
+ approvalQueue: [],
16701
+ outputBytesSoFar: 0,
16702
+ startedAtMs: Date.now(),
16703
+ ...messageRequirement === void 0 ? {} : {
16704
+ messageRequirement,
16705
+ messageOutbox: this.pendingMessageTasks.get(taskId).outbox
16706
+ },
16707
+ ...terminalProjection === void 0 ? {} : { terminalProjection }
16708
+ };
16709
+ if (agentBinding !== void 0) {
16710
+ const binding = agentBinding;
16711
+ try {
16712
+ await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
16713
+ agentRef: binding.resolution.agentRef,
16714
+ taskId,
16715
+ sessionRef: session.sessionRef,
16716
+ runtimeId: pick.descriptor.id,
16717
+ cwd: workspaceDir,
16718
+ leaseId: binding.lease.leaseId
16719
+ }));
16720
+ } catch (error) {
16721
+ await this.failClaimedAgent(
16722
+ taskId,
16723
+ `Agent session handoff could not be durably written before start: ${errorMessage5(error)}`,
16724
+ false,
16725
+ {
16726
+ binding: agentBinding,
16727
+ runtimeId: pick.descriptor.id,
16728
+ sessionRef: session.sessionRef
16729
+ }
16730
+ );
16731
+ return;
16732
+ }
16733
+ }
16734
+ const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await this.activateAgentMessage(
16735
+ active.messageOutbox,
16736
+ taskId,
16737
+ session.sessionRef,
16738
+ () => !this.pendingCancelled.has(taskId) && !blobAbort.signal.aborted
16739
+ );
16740
+ if (this.pendingCancelled.has(taskId)) {
16741
+ const reason = this.pendingCancelled.get(taskId);
16742
+ this.pendingCancelled.delete(taskId);
16743
+ agentLeaseTransferred = agentBinding !== void 0;
16744
+ this.startupOwners.delete(taskId);
16745
+ this.tasks.set(taskId, active);
16746
+ this.reserveSemanticTerminal(active);
16747
+ await this.interruptBounded(session);
16748
+ await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
16749
+ await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16750
+ this.deps.send(
16751
+ createEnvelope(
16752
+ "task.cancelled",
16753
+ { reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16754
+ { taskId }
16755
+ )
16048
16756
  );
16757
+ await this.finish(taskId);
16049
16758
  return;
16050
16759
  }
16051
- }
16052
- const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await active.messageOutbox.activate(taskId, session.sessionRef);
16053
- if (this.pendingCancelled.has(taskId)) {
16054
- const reason = this.pendingCancelled.get(taskId);
16055
- this.pendingCancelled.delete(taskId);
16760
+ this.deps.send(createEnvelope("task.started", {}, { taskId }));
16056
16761
  agentLeaseTransferred = agentBinding !== void 0;
16762
+ this.inFlightBlobAborts.delete(taskId);
16763
+ this.startupOwners.delete(taskId);
16057
16764
  this.tasks.set(taskId, active);
16058
- this.reserveSemanticTerminal(active);
16059
- try {
16060
- await session.interrupt();
16061
- } catch {
16765
+ this.pendingMessageTasks.delete(taskId);
16766
+ if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
16767
+ this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
16062
16768
  }
16063
- await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
16064
- await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16065
- this.deps.send(
16066
- createEnvelope(
16067
- "task.cancelled",
16068
- { reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16069
- { taskId }
16070
- )
16071
- );
16072
- await this.finish(taskId);
16073
- return;
16074
- }
16075
- this.deps.send(createEnvelope("task.started", {}, { taskId }));
16076
- agentLeaseTransferred = agentBinding !== void 0;
16077
- this.inFlightBlobAborts.delete(taskId);
16078
- this.tasks.set(taskId, active);
16079
- this.pendingMessageTasks.delete(taskId);
16080
- if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
16081
- this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
16082
- }
16083
- if (payload.limits?.maxDurationMs !== void 0) {
16084
- this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
16085
- }
16086
- void this.pump(active);
16087
- if (agentBinding === void 0) {
16088
- void this.deps.sessionWorkspaces.record(session.sessionRef, {
16089
- workspaceDir,
16090
- runtimeSessionId: session.sessionRef,
16091
- ...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
16092
- }).catch(() => {
16093
- });
16094
- }
16095
- if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
16096
- void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
16769
+ if (payload.limits?.maxDurationMs !== void 0) {
16770
+ this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
16771
+ }
16772
+ void this.pump(active);
16773
+ if (agentBinding === void 0) {
16774
+ void this.deps.sessionWorkspaces.record(session.sessionRef, {
16775
+ workspaceDir,
16776
+ runtimeSessionId: session.sessionRef,
16777
+ ...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
16778
+ }).catch(() => {
16779
+ });
16780
+ }
16781
+ if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
16782
+ void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
16783
+ });
16784
+ }
16785
+ } catch (error) {
16786
+ if (this.tasks.has(taskId)) throw error;
16787
+ const reason = `runtime activation failed: ${errorMessage5(error)}`;
16788
+ if (agentBinding === void 0) await this.fail(taskId, reason, false);
16789
+ else await this.failClaimedAgent(taskId, reason, false, {
16790
+ binding: agentBinding,
16791
+ runtimeId: pick.descriptor.id,
16792
+ sessionRef: session.sessionRef
16097
16793
  });
16098
16794
  }
16099
16795
  } finally {
16796
+ clearTimeout(startupTimer);
16797
+ releaseReservation();
16798
+ if (this.startupOwners.has(taskId)) {
16799
+ agentLeaseTransferred = true;
16800
+ await this.disposeStartupOwner(taskId);
16801
+ }
16100
16802
  if (!agentLeaseTransferred) {
16101
16803
  this.pendingMessageTasks.delete(taskId);
16102
16804
  this.revokeAgentMessageContext(taskId);
@@ -16108,6 +16810,7 @@ var TaskRunner = class {
16108
16810
  }
16109
16811
  this.inFlightBlobAborts.delete(taskId);
16110
16812
  this.inFlightOffers.delete(taskId);
16813
+ this.claimedHarnesses.delete(taskId);
16111
16814
  }
16112
16815
  }
16113
16816
  withAgentMessageMcp(existing, taskId, requirement) {
@@ -16377,7 +17080,13 @@ var TaskRunner = class {
16377
17080
  maxPendingBytes: 4 * 1024 * 1024
16378
17081
  });
16379
17082
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16380
- const activated = await outbox.activate(active.taskId, active.session.sessionRef);
17083
+ const activated = await this.activateAgentMessage(
17084
+ outbox,
17085
+ active.taskId,
17086
+ active.session.sessionRef,
17087
+ () => this.canPublishAgentMessage(active),
17088
+ () => active.messageSendAttempted === true
17089
+ );
16381
17090
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16382
17091
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
16383
17092
  this.sendAgentMessageRecord(outbox, activated);
@@ -16484,7 +17193,16 @@ var TaskRunner = class {
16484
17193
  }
16485
17194
  let bytes;
16486
17195
  try {
16487
- bytes = await opened.handle.readFile();
17196
+ const limits = this.deps.artifactLimits ?? DEFAULT_ARTIFACT_LIMITS;
17197
+ const remaining = limits.maxTaskBytes - (active.artifactBytesSoFar ?? 0);
17198
+ bytes = await readArtifactBytes(
17199
+ opened.handle,
17200
+ Math.min(limits.maxFileBytes, remaining),
17201
+ active.blobAbort.signal,
17202
+ (count) => {
17203
+ active.artifactBytesSoFar = (active.artifactBytesSoFar ?? 0) + count;
17204
+ }
17205
+ );
16488
17206
  } catch (err) {
16489
17207
  this.reportArtifactError(active, name, `failed to read artifact "${name}": ${errorMessage5(err)}`);
16490
17208
  return;
@@ -16492,6 +17210,7 @@ var TaskRunner = class {
16492
17210
  await opened.handle.close().catch(() => {
16493
17211
  });
16494
17212
  }
17213
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16495
17214
  if (bytes.length <= MAX_INLINE_ARTIFACT_BYTES) {
16496
17215
  const inline = bytes.toString("base64");
16497
17216
  if (new TextEncoder().encode(inline).length <= MAX_INLINE_ARTIFACT_BYTES) {
@@ -16503,6 +17222,7 @@ var TaskRunner = class {
16503
17222
  const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
16504
17223
  signal: active.blobAbort.signal
16505
17224
  });
17225
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16506
17226
  this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
16507
17227
  } catch (err) {
16508
17228
  this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage5(err)}`);
@@ -16510,12 +17230,17 @@ var TaskRunner = class {
16510
17230
  }
16511
17231
  /** Loud, non-silent artifact failure (finding F7): logged, and folded into this task's own progress stream as an `error` AgentEvent rather than swallowed — the task itself can still complete normally, but the omission is now visible. */
16512
17232
  reportArtifactError(active, name, reason) {
17233
+ if (active.blobAbort.signal.aborted || active.beingTornDown) return;
16513
17234
  console.error(`[byok/client] artifact "${name}" for task ${active.taskId} dropped: ${reason}`);
16514
17235
  active.batcher.push({ type: "error", message: reason });
16515
17236
  }
16516
17237
  async handleCancel(taskId, reason) {
16517
17238
  const active = this.tasks.get(taskId);
16518
17239
  if (!active) {
17240
+ if (this.startupOwners.has(taskId) && !this.inFlightOffers.has(taskId)) {
17241
+ await this.disposeStartupOwner(taskId);
17242
+ return;
17243
+ }
16519
17244
  this.setPendingCancelled(taskId, reason);
16520
17245
  this.inFlightBlobAborts.get(taskId)?.abort();
16521
17246
  return;
@@ -16529,10 +17254,7 @@ var TaskRunner = class {
16529
17254
  return;
16530
17255
  }
16531
17256
  active.blobAbort.abort();
16532
- try {
16533
- await active.session.interrupt();
16534
- } catch {
16535
- }
17257
+ await this.interruptBounded(active.session);
16536
17258
  await this.observeGit(active, "salvage");
16537
17259
  await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16538
17260
  this.deps.send(
@@ -16974,10 +17696,7 @@ var TaskRunner = class {
16974
17696
  await active.semanticTerminalSettled;
16975
17697
  return;
16976
17698
  }
16977
- try {
16978
- await active.session.interrupt();
16979
- } catch {
16980
- }
17699
+ await this.interruptBounded(active.session);
16981
17700
  await this.observeGit(active, "salvage");
16982
17701
  await this.persistAgentTerminalEvidence(active, "failed", reason ?? "rejected");
16983
17702
  this.deps.send(
@@ -17012,7 +17731,7 @@ var TaskRunner = class {
17012
17731
  this.deps.send(
17013
17732
  createEnvelope(
17014
17733
  "task.fail",
17015
- active === void 0 ? { reason, retryable } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
17734
+ active === void 0 ? { reason, retryable, ...terminalIdentity(this.claimedHarnesses.get(taskId)) } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
17016
17735
  { taskId }
17017
17736
  )
17018
17737
  );
@@ -17026,6 +17745,7 @@ var TaskRunner = class {
17026
17745
  * the wire and handleOffer's finally block releases the lease.
17027
17746
  */
17028
17747
  async failClaimedAgent(taskId, reason, retryable, context) {
17748
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
17029
17749
  const agentRef = context.binding.resolution.agentRef;
17030
17750
  const result = await this.retryAgentTerminalEvidence(
17031
17751
  () => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
@@ -17051,7 +17771,7 @@ var TaskRunner = class {
17051
17771
  }
17052
17772
  this.deps.send(createEnvelope(
17053
17773
  "task.fail",
17054
- { reason, retryable, agentRef },
17774
+ { reason, retryable, ...terminalIdentity(context.runtimeId, agentRef) },
17055
17775
  { taskId }
17056
17776
  ));
17057
17777
  return result.ok;
@@ -17069,7 +17789,8 @@ var TaskRunner = class {
17069
17789
  terminalInferenceUsagePayload(active) {
17070
17790
  const release = this.deps.localAgentRelease;
17071
17791
  const runtimeId = active.adapter.descriptor.id;
17072
- if (release === void 0 || active.lastUsage === void 0 || !isKnownRuntimeId(runtimeId)) return {};
17792
+ if (!isKnownRuntimeId(runtimeId)) return {};
17793
+ if (release === void 0 || active.lastUsage === void 0) return {};
17073
17794
  const nowMs = Date.now();
17074
17795
  const durationMs = terminalUsageNumber(nowMs - active.startedAtMs, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS);
17075
17796
  const promptTokens = terminalUsageNumber(active.lastUsage?.inputTokens, TERMINAL_INFERENCE_USAGE_MAX_TOKENS);
@@ -17087,7 +17808,7 @@ var TaskRunner = class {
17087
17808
  }
17088
17809
  /** Exact Agent identity projection for claim/terminal wire payloads. */
17089
17810
  agentTerminalPayload(active) {
17090
- return active.agentRef === void 0 ? {} : { agentRef: active.agentRef };
17811
+ return terminalIdentity(active.adapter.descriptor.id, active.agentRef);
17091
17812
  }
17092
17813
  /**
17093
17814
  * additive-minor (`task.complete.document`): the whole daemon-side gate
@@ -17244,6 +17965,14 @@ var TaskRunner = class {
17244
17965
  }
17245
17966
  /** Persist Agent terminal truth before wire when local storage is available. */
17246
17967
  async persistAgentTerminalEvidence(active, cause, reason) {
17968
+ if (cause === "cancelled" || cause === "failed" && !active.messageSendAttempted) {
17969
+ try {
17970
+ await active.messageOutbox?.revoke(active.taskId);
17971
+ } catch (error) {
17972
+ active.resolveSemanticTerminalSettled?.(false);
17973
+ throw error;
17974
+ }
17975
+ }
17247
17976
  active.terminalCause = cause;
17248
17977
  active.terminalReason = reason;
17249
17978
  if (active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0) {
@@ -17306,7 +18035,19 @@ var TaskRunner = class {
17306
18035
  error
17307
18036
  });
17308
18037
  }
17309
- async finish(taskId) {
18038
+ async retryTerminalFinalization(taskId) {
18039
+ const active = this.tasks.get(taskId);
18040
+ if (active?.finalizationStarted) await this.finish(taskId);
18041
+ }
18042
+ finalizationAttempts = /* @__PURE__ */ new Map();
18043
+ finish(taskId) {
18044
+ const existing = this.finalizationAttempts.get(taskId);
18045
+ if (existing) return existing;
18046
+ const attempt = this.finishOnce(taskId).finally(() => this.finalizationAttempts.delete(taskId));
18047
+ this.finalizationAttempts.set(taskId, attempt);
18048
+ return attempt;
18049
+ }
18050
+ async finishOnce(taskId) {
17310
18051
  const active = this.tasks.get(taskId);
17311
18052
  if (!active) return true;
17312
18053
  if (!active.finalizationStarted) {
@@ -17357,6 +18098,13 @@ var TaskRunner = class {
17357
18098
  return false;
17358
18099
  }
17359
18100
  if (this.tasks.get(taskId) !== active) return true;
18101
+ try {
18102
+ await this.deps.awaitTerminalCommit?.(taskId);
18103
+ } catch {
18104
+ active.resolveSemanticTerminalSettled?.(false);
18105
+ return false;
18106
+ }
18107
+ if (this.tasks.get(taskId) !== active) return true;
17360
18108
  if (active.agentBinding !== void 0 && active.agentRef !== void 0 && active.agentHandoff !== void 0) {
17361
18109
  const agentRef = active.agentRef;
17362
18110
  const handoff = active.agentHandoff;
@@ -17468,7 +18216,7 @@ var TaskRunner = class {
17468
18216
  * is device-specific (which runtimes happen to be installed here), so a
17469
18217
  * different device's installed runtime set might satisfy it.
17470
18218
  */
17471
- async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets) {
18219
+ async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets, signal) {
17472
18220
  const allowlist = this.deps.runtimeAllowlist;
17473
18221
  if (requestedRuntime) {
17474
18222
  if (allowlist && !allowlist.includes(requestedRuntime)) {
@@ -17497,8 +18245,8 @@ var TaskRunner = class {
17497
18245
  retryable: false
17498
18246
  };
17499
18247
  }
17500
- const detected = await adapter.detect();
17501
- if (!detected.present) {
18248
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18249
+ if (detected.kind !== "available") {
17502
18250
  return {
17503
18251
  ok: false,
17504
18252
  reason: `runtime "${requestedRuntime}" is not installed/available on this device`,
@@ -17513,8 +18261,8 @@ var TaskRunner = class {
17513
18261
  const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
17514
18262
  if (!adapterSupportsMode(descriptor, policyMode)) continue;
17515
18263
  if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) continue;
17516
- const detected = await adapter.detect();
17517
- if (detected.present) return { ok: true, adapter, descriptor };
18264
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18265
+ if (detected.kind === "available") return { ok: true, adapter, descriptor };
17518
18266
  }
17519
18267
  return {
17520
18268
  ok: false,
@@ -17678,10 +18426,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17678
18426
  constructor(homeDir, spoolPath) {
17679
18427
  this.homeDir = homeDir;
17680
18428
  this.spoolPath = spoolPath;
18429
+ this.file = new DurableJsonlFile(spoolPath);
17681
18430
  }
17682
18431
  homeDir;
17683
18432
  spoolPath;
17684
18433
  pending = /* @__PURE__ */ new Map();
18434
+ file;
17685
18435
  nextCursor = 1;
17686
18436
  logEntries = 0;
17687
18437
  writeTail = Promise.resolve();
@@ -17690,6 +18440,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17690
18440
  await ensureSecureDir(directory);
17691
18441
  const spool = new _AgentReliableSpool(homeDir, path14__default.join(directory, AGENT_RELIABLE_SPOOL_FILENAME));
17692
18442
  await spool.load();
18443
+ await spool.file.confirmRecovered();
17693
18444
  return spool;
17694
18445
  }
17695
18446
  get pendingEvents() {
@@ -17709,6 +18460,25 @@ var AgentReliableSpool = class _AgentReliableSpool {
17709
18460
  const payloadJson = stableRecordJson(input.payload);
17710
18461
  const byteCount = Buffer.byteLength(payloadJson, "utf8");
17711
18462
  if (byteCount <= 0) throw new AgentReliableSpoolError("reliable payload must have positive serialized bytes");
18463
+ const eventId = input.eventId ?? randomUUID();
18464
+ const existing = this.pending.get(eventId);
18465
+ if (existing !== void 0) {
18466
+ if (!sameRecord(existing, {
18467
+ wireType: "agent.egress.reliable",
18468
+ agentRef: input.agentRef,
18469
+ tenantId: input.tenantId,
18470
+ policyRevision: input.policyRevision,
18471
+ eventId,
18472
+ cursor: existing.cursor,
18473
+ payloadHash: contentHash(payloadJson),
18474
+ byteCount,
18475
+ sessionRef: input.sessionRef,
18476
+ taskId: input.taskId
18477
+ }) || stableRecordJson(existing.payload) !== payloadJson || input.createdAt !== void 0 && input.createdAt !== existing.createdAt) {
18478
+ throw new AgentReliableSpoolError(`reliable eventId ${eventId} differs from its existing record`);
18479
+ }
18480
+ return existing;
18481
+ }
17712
18482
  if (this.pending.size >= policy.reliable.maxPendingEventsPerAgent) {
17713
18483
  throw new AgentReliableQuotaError("quota_exceeded", "reliable per-Agent event quota is exhausted");
17714
18484
  }
@@ -17718,8 +18488,6 @@ var AgentReliableSpool = class _AgentReliableSpool {
17718
18488
  if (tenantPendingBytes + byteCount > policy.reliable.maxPendingBytesPerTenant) {
17719
18489
  throw new AgentReliableQuotaError("quota_exceeded", "reliable tenant byte quota is exhausted");
17720
18490
  }
17721
- const eventId = input.eventId ?? randomUUID();
17722
- if (this.pending.has(eventId)) throw new AgentReliableSpoolError(`reliable eventId ${eventId} already exists`);
17723
18491
  const record4 = Object.freeze({
17724
18492
  schema: 1,
17725
18493
  wireType: "agent.egress.reliable",
@@ -17828,6 +18596,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17828
18596
  if (error.code === "ENOENT") return;
17829
18597
  throw error;
17830
18598
  }
18599
+ if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentReliableSpoolError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
17831
18600
  for (const line of raw.split("\n")) {
17832
18601
  if (line.length === 0) continue;
17833
18602
  let entry;
@@ -17850,19 +18619,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17850
18619
  }
17851
18620
  }
17852
18621
  async appendEntry(entry) {
17853
- const handle = await promises.open(this.spoolPath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
17854
- try {
17855
- await handle.write(`${stableRecordJson(entry)}
17856
- `, void 0, "utf8");
17857
- await handle.sync();
17858
- this.logEntries += 1;
17859
- } finally {
17860
- await handle.close();
17861
- }
18622
+ await this.file.append(stableRecordJson(entry));
18623
+ this.logEntries += 1;
17862
18624
  }
17863
18625
  async compact() {
17864
18626
  const entries = this.records().map((record4) => ({ schema: 1, kind: "append", record: record4 }));
17865
- await atomicWriteFile(this.spoolPath, entries.map((entry) => stableRecordJson(entry)).join(entries.length > 0 ? "\n" : ""), { mode: 384 });
18627
+ await this.file.replace(entries.map((entry) => stableRecordJson(entry)));
17866
18628
  this.logEntries = entries.length;
17867
18629
  }
17868
18630
  async exclusive(fn) {
@@ -17873,6 +18635,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17873
18635
  });
17874
18636
  await prior;
17875
18637
  try {
18638
+ this.file.assertWritable();
17876
18639
  return await fn();
17877
18640
  } finally {
17878
18641
  release();
@@ -19247,17 +20010,26 @@ function isRuntimeId(id) {
19247
20010
  return id === "pi" || id === "claude" || id === "codex";
19248
20011
  }
19249
20012
  async function detectRuntimes(adapters) {
19250
- const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: await adapter.detect() })));
20013
+ const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: validateRuntimeDetectResult(await adapter.detect()) })));
19251
20014
  const runtimes = [];
20015
+ const harnesses = [];
19252
20016
  for (const { adapter, detected } of detections) {
19253
- if (!detected.present || !isRuntimeId(adapter.descriptor.id)) continue;
20017
+ if (detected.kind !== "available") continue;
20018
+ if (!isRuntimeId(adapter.descriptor.id)) {
20019
+ harnesses.push({
20020
+ id: HarnessIdSchema.parse(adapter.descriptor.id),
20021
+ ...detected.version === void 0 ? {} : { version: detected.version },
20022
+ capabilities: toRuntimeInfoCapabilities(adapter.descriptor.capabilities)
20023
+ });
20024
+ continue;
20025
+ }
19254
20026
  const info = { id: adapter.descriptor.id };
19255
20027
  if (detected.version !== void 0) info.version = detected.version;
19256
20028
  if (detected.authPresent !== void 0) info.authPresent = detected.authPresent;
19257
20029
  info.capabilities = toRuntimeInfoCapabilities(adapter.descriptor.capabilities);
19258
20030
  runtimes.push(info);
19259
20031
  }
19260
- return runtimes;
20032
+ return { runtimes, harnesses };
19261
20033
  }
19262
20034
  function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentOnly = false, agentHomeProjectionConfigured = false, agentEgressConfigured = false, contentReadPolicies, providerProfileBindingConfigured = false) {
19263
20035
  const flags = [];
@@ -19347,7 +20119,7 @@ function buildAdapter(id, config) {
19347
20119
  resolveApprovalMcpBin: () => resolveApprovalMcpBin(config.sdkHelperHost)
19348
20120
  });
19349
20121
  case "codex":
19350
- return new CodexAdapter();
20122
+ return new CodexAdapter({ sdkHelperHost: config.sdkHelperHost });
19351
20123
  }
19352
20124
  }
19353
20125
  function buildDefaultAdapters(config) {
@@ -19424,6 +20196,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19424
20196
  const localAgentRelease = resolveLocalAgentReleaseIdentity(config.localAgentRelease);
19425
20197
  const toolsetRegistry = new McpToolsetRegistry(config.mcpToolsets);
19426
20198
  validatePiByokLauncherConfig(config.piByokLauncher);
20199
+ if (config.startupTimeoutMs !== void 0 && (!Number.isSafeInteger(config.startupTimeoutMs) || config.startupTimeoutMs <= 0)) throw new Error("startupTimeoutMs must be a positive safe integer");
20200
+ if (config.artifactLimits !== void 0) {
20201
+ for (const value of [config.artifactLimits.maxFileBytes, config.artifactLimits.maxTaskBytes]) {
20202
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error("artifactLimits must contain positive safe integer byte budgets");
20203
+ }
20204
+ }
19427
20205
  if (config.maxTaskOutputBytes !== void 0 && !(config.maxTaskOutputBytes > 0)) {
19428
20206
  throw new Error(
19429
20207
  `DaemonConfig.maxTaskOutputBytes must be a positive number (or omitted to use the ${DEFAULT_MAX_TASK_OUTPUT_BYTES}-byte default) \u2014 got ${config.maxTaskOutputBytes}. Pass Number.POSITIVE_INFINITY to explicitly disable the cap; 0 or a negative number is rejected rather than silently treated as "disabled".`
@@ -19553,7 +20331,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19553
20331
  pressureEngine = ownedPressureEngine;
19554
20332
  }
19555
20333
  }
19556
- let journalTerminalTail = Promise.resolve();
20334
+ const terminalCommits = new TerminalCommitQueue((taskId) => {
20335
+ connection?.retryTaskReceipts(taskId);
20336
+ void runner?.retryTerminalFinalization(taskId).catch((error) => {
20337
+ console.error("[byok/client] terminal finalization still requires recovery", error);
20338
+ });
20339
+ });
19557
20340
  const observer = new DaemonObserver();
19558
20341
  const approvalRegistry = new ApprovalRegistry();
19559
20342
  let connection;
@@ -19688,6 +20471,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19688
20471
  await runLifecycleMutation(startUnderLease);
19689
20472
  }
19690
20473
  async function startUnderLease() {
20474
+ terminalCommits.resume();
19691
20475
  if (!daemonOwnerLease) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
19692
20476
  try {
19693
20477
  let record4;
@@ -19739,7 +20523,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19739
20523
  const terminal = createEnvelope("task.fail", {
19740
20524
  reason: "daemon_interrupted",
19741
20525
  retryable: false,
19742
- ...payload.agentRef === void 0 ? {} : { agentRef: payload.agentRef }
20526
+ ...terminalIdentity(task.claimedRuntime, payload.agentRef),
20527
+ recovery: { kind: "daemon_interrupted", offerId: offer.id }
19743
20528
  }, { taskId: task.taskId });
19744
20529
  const bytes = encodeEnvelope(terminal);
19745
20530
  await activeJournal.recordTerminal({
@@ -19767,7 +20552,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19767
20552
  await gitWorkspaceStore?.reconcile();
19768
20553
  }
19769
20554
  blobLifecycleAbort = new AbortController();
19770
- const [runtimes, blobClient] = await Promise.all([
20555
+ const [{ runtimes, harnesses }, blobClient] = await Promise.all([
19771
20556
  detectRuntimes(adapters),
19772
20557
  Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
19773
20558
  ]);
@@ -19788,6 +20573,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19788
20573
  tenantId: record4.tenantId,
19789
20574
  deviceId: record4.deviceId
19790
20575
  }) : void 0;
20576
+ capabilities.push("custom-harness");
19791
20577
  const sendSanitizedEnvelope = (envelope) => {
19792
20578
  const terminalKind = terminalKindOf(envelope.type);
19793
20579
  if (!activeJournal || !journalIdentity || terminalKind === void 0 || envelope.task_id === void 0) {
@@ -19797,7 +20583,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19797
20583
  }
19798
20584
  const taskId = envelope.task_id;
19799
20585
  const bytes = encodeEnvelope(envelope);
19800
- journalTerminalTail = journalTerminalTail.catch(() => void 0).then(async () => {
20586
+ terminalCommits.enqueue(taskId, async () => {
19801
20587
  try {
19802
20588
  await activeJournal.recordTerminal({
19803
20589
  taskId,
@@ -19825,7 +20611,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19825
20611
  const failure = createEnvelope("task.fail", {
19826
20612
  reason: "terminal_result_too_large",
19827
20613
  retryable: false,
19828
- ...offerPayload.agentRef === void 0 ? {} : { agentRef: offerPayload.agentRef }
20614
+ ...terminalIdentity(task.claimedRuntime, offerPayload.agentRef)
19829
20615
  }, { taskId });
19830
20616
  const failureBytes = encodeEnvelope(failure);
19831
20617
  await activeJournal.recordTerminal({
@@ -19843,8 +20629,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19843
20629
  observer.handleOutboundEnvelope(decoded);
19844
20630
  connection?.send(decoded);
19845
20631
  }
19846
- }).catch((error) => {
19847
- console.error("[byok/client] terminal durability failed; execution remains unsettled", error);
19848
20632
  });
19849
20633
  };
19850
20634
  const sendEnvelope = (candidate) => {
@@ -19926,7 +20710,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19926
20710
  // Finding F5(a): see TaskRunnerDeps.shutdownInterruptTimeoutMs's own doc comment.
19927
20711
  shutdownInterruptTimeoutMs: overrides.shutdown?.taskInterruptTimeoutMs,
19928
20712
  // M5 batch-3 (workstream 2): see DaemonConfig.maxTaskOutputBytes's own doc comment — already validated above.
20713
+ awaitTerminalCommit: (taskId) => terminalCommits.receipt(taskId),
19929
20714
  maxTaskOutputBytes: config.maxTaskOutputBytes,
20715
+ artifactLimits: config.artifactLimits,
20716
+ startupTimeoutMs: config.startupTimeoutMs,
19930
20717
  // See DaemonConfig.maxInlineEventBytes's own doc comment — already validated above.
19931
20718
  maxInlineEventBytes: config.maxInlineEventBytes,
19932
20719
  // additive-minor (`task.complete.document`): passed through verbatim,
@@ -19974,6 +20761,20 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19974
20761
  if (config.agentHome !== void 0 && config.agentEgress !== void 0) {
19975
20762
  await runner.recoverAgentMessageOutboxes(path14__default.join(config.agentHome.hostStorageRoot, "agents"));
19976
20763
  }
20764
+ const replayRecoveryTerminals = async (taskId) => {
20765
+ if (!activeJournal || !journalIdentity) return;
20766
+ for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
20767
+ if (terminal.truthState !== "pending" || taskId !== void 0 && terminal.taskId !== taskId) continue;
20768
+ if (runner?.hasPendingRecoveredAgentMessage(terminal.taskId)) continue;
20769
+ const envelope = decodeEnvelope(terminal.bytes);
20770
+ if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
20771
+ throw new Error("durable terminal execution binding mismatch");
20772
+ }
20773
+ overrides.executionRecoveryFault?.("terminal:before-send");
20774
+ observer.handleOutboundEnvelope(envelope);
20775
+ connection?.send(envelope);
20776
+ }
20777
+ };
19977
20778
  const handleAgentHomeProjectionEnvelope = async (envelope) => {
19978
20779
  if (envelope.type !== "agent.home.projection") return false;
19979
20780
  if (agentHomeManager === void 0 || agentHomeProjectionCompletion === void 0) {
@@ -20108,6 +20909,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20108
20909
  capabilities,
20109
20910
  clientVersion: localAgentRelease.version,
20110
20911
  runtimes,
20912
+ harnesses,
20111
20913
  getConfiguredToolsets: () => toolsetRegistry.snapshot().configuredToolsets,
20112
20914
  auth,
20113
20915
  cursorStore,
@@ -20149,12 +20951,22 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20149
20951
  activePressureEngine?.assertAckCriticalAllowed();
20150
20952
  await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
20151
20953
  if (isTaskOfferType(envelope.type) && envelope.task_id !== void 0) {
20954
+ if (terminalCommits.hasPending(envelope.task_id)) {
20955
+ await terminalCommits.retry(envelope.task_id);
20956
+ return;
20957
+ }
20152
20958
  const task = await activeJournal.readTask(envelope.task_id, journalIdentity);
20153
20959
  if (!task) throw new Error("durable offer has no execution record");
20154
- if (task.localState !== "received") return;
20960
+ if (task.localState !== "received") {
20961
+ await terminalCommits.retry(envelope.task_id);
20962
+ return;
20963
+ }
20155
20964
  }
20156
20965
  await runner?.handleEnvelope(envelope);
20157
- await journalTerminalTail;
20966
+ if (envelope.type === "agent.message.disposition") {
20967
+ await replayRecoveryTerminals(envelope.task_id);
20968
+ }
20969
+ if (envelope.task_id !== void 0) await terminalCommits.receipt(envelope.task_id);
20158
20970
  } : (envelope) => {
20159
20971
  if (tenantRebinding) {
20160
20972
  return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
@@ -20202,18 +21014,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20202
21014
  });
20203
21015
  }
20204
21016
  });
20205
- if (activeJournal && journalIdentity) {
20206
- for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
20207
- if (terminal.truthState !== "pending") continue;
20208
- const envelope = decodeEnvelope(terminal.bytes);
20209
- if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
20210
- throw new Error("durable terminal execution binding mismatch");
20211
- }
20212
- overrides.executionRecoveryFault?.("terminal:before-send");
20213
- observer.handleOutboundEnvelope(envelope);
20214
- connection.send(envelope);
20215
- }
20216
- }
21017
+ await replayRecoveryTerminals();
20217
21018
  await connection.start();
20218
21019
  await connection.waitForConnection();
20219
21020
  runner.retryRecoveredAgentMessages();
@@ -20328,7 +21129,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20328
21129
  pendingLateMutationBarriers.add(observedActiveTeardown);
20329
21130
  void observedActiveTeardown.finally(() => pendingLateMutationBarriers.delete(observedActiveTeardown)).catch(() => void 0);
20330
21131
  }
20331
- const journalTailSettled = journal ? await attempt(() => journalTerminalTail, true) : true;
21132
+ const journalTailSettled = journal ? await attempt(() => terminalCommits.stop(), true) : true;
20332
21133
  const maintenanceSettled = await attempt(() => maintenanceStopped, true);
20333
21134
  const drainTimeoutMs = opts.drainTimeoutMs ?? overrides.shutdown?.outboxDrainTimeoutMs ?? DEFAULT_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT_MS;
20334
21135
  const connectionStopped = await attempt(() => connection?.stop(drainTimeoutMs), true);
@@ -20848,6 +21649,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20848
21649
  revoked: connection?.isRevoked() ?? auth.isRevoked(),
20849
21650
  deviceId: auth.deviceId,
20850
21651
  activeTaskCount: runner?.activeTaskCount ?? 0,
21652
+ pendingTerminalCommits: terminalCommits.pendingCount,
20851
21653
  branding: config.branding,
20852
21654
  operationalHealth: operationalHealth.snapshot(),
20853
21655
  toolsets: toolsetRegistry.status(),
@@ -20980,7 +21782,7 @@ ${args.map((a) => ` ${plistString(a)}`).join("\n")}
20980
21782
  }
20981
21783
  function createLaunchdLifecycle(def, deps = {}) {
20982
21784
  const run = deps.run ?? defaultRunner;
20983
- const fs36 = deps.fs ?? promises;
21785
+ const fs37 = deps.fs ?? promises;
20984
21786
  const homedir = deps.homedir ?? (() => os__default.homedir());
20985
21787
  const getuid = deps.getuid ?? (() => {
20986
21788
  if (typeof process.getuid !== "function") {
@@ -20994,7 +21796,7 @@ function createLaunchdLifecycle(def, deps = {}) {
20994
21796
  const serviceTarget = () => `${domainTarget()}/${label}`;
20995
21797
  async function fileExists(p) {
20996
21798
  try {
20997
- await fs36.stat(p);
21799
+ await fs37.stat(p);
20998
21800
  return true;
20999
21801
  } catch {
21000
21802
  return false;
@@ -21002,9 +21804,9 @@ function createLaunchdLifecycle(def, deps = {}) {
21002
21804
  }
21003
21805
  async function writePlist(program) {
21004
21806
  const xml = generateLaunchdPlist({ label, program, logDir: def.logDir });
21005
- await fs36.mkdir(path14__default.dirname(plistPath()), { recursive: true });
21006
- await fs36.mkdir(def.logDir, { recursive: true });
21007
- await fs36.writeFile(plistPath(), xml, "utf8");
21807
+ await fs37.mkdir(path14__default.dirname(plistPath()), { recursive: true });
21808
+ await fs37.mkdir(def.logDir, { recursive: true });
21809
+ await fs37.writeFile(plistPath(), xml, "utf8");
21008
21810
  }
21009
21811
  async function install(opts = {}) {
21010
21812
  await writePlist(opts.program ?? def.program);
@@ -21015,7 +21817,7 @@ function createLaunchdLifecycle(def, deps = {}) {
21015
21817
  }
21016
21818
  async function uninstall() {
21017
21819
  await runIdempotent(run, "launchctl", ["bootout", serviceTarget()], "launchctl bootout", LAUNCHD_NOT_LOADED);
21018
- await fs36.rm(plistPath(), { force: true });
21820
+ await fs37.rm(plistPath(), { force: true });
21019
21821
  }
21020
21822
  async function start() {
21021
21823
  if (!await fileExists(plistPath())) {
@@ -21099,14 +21901,14 @@ WantedBy=default.target
21099
21901
  }
21100
21902
  function createSystemdLifecycle(def, deps = {}) {
21101
21903
  const run = deps.run ?? defaultRunner;
21102
- const fs36 = deps.fs ?? promises;
21904
+ const fs37 = deps.fs ?? promises;
21103
21905
  const homedir = deps.homedir ?? (() => os__default.homedir());
21104
21906
  const name = sanitizeServiceName(def.name);
21105
21907
  const unitName = `${name}.service`;
21106
21908
  const unitPath = () => path14__default.join(homedir(), ".config", "systemd", "user", unitName);
21107
21909
  async function fileExists(p) {
21108
21910
  try {
21109
- await fs36.stat(p);
21911
+ await fs37.stat(p);
21110
21912
  return true;
21111
21913
  } catch {
21112
21914
  return false;
@@ -21114,9 +21916,9 @@ function createSystemdLifecycle(def, deps = {}) {
21114
21916
  }
21115
21917
  async function writeUnit(program) {
21116
21918
  const unit = generateSystemdUnit({ name, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
21117
- await fs36.mkdir(path14__default.dirname(unitPath()), { recursive: true });
21118
- await fs36.mkdir(def.logDir, { recursive: true });
21119
- await fs36.writeFile(unitPath(), unit, "utf8");
21919
+ await fs37.mkdir(path14__default.dirname(unitPath()), { recursive: true });
21920
+ await fs37.mkdir(def.logDir, { recursive: true });
21921
+ await fs37.writeFile(unitPath(), unit, "utf8");
21120
21922
  }
21121
21923
  async function install(opts = {}) {
21122
21924
  await writeUnit(opts.program ?? def.program);
@@ -21125,7 +21927,7 @@ function createSystemdLifecycle(def, deps = {}) {
21125
21927
  }
21126
21928
  async function uninstall() {
21127
21929
  await runIdempotent(run, "systemctl", ["--user", "disable", "--now", unitName], "systemctl disable --now", SYSTEMD_NOT_LOADED);
21128
- await fs36.rm(unitPath(), { force: true });
21930
+ await fs37.rm(unitPath(), { force: true });
21129
21931
  await run("systemctl", ["--user", "daemon-reload"]);
21130
21932
  }
21131
21933
  async function start() {
@@ -21188,7 +21990,7 @@ ${argXml}${cwdXml}
21188
21990
  }
21189
21991
  function createWinswLifecycle(def, deps = {}) {
21190
21992
  const run = deps.run ?? defaultRunner;
21191
- const fs36 = deps.fs ?? promises;
21993
+ const fs37 = deps.fs ?? promises;
21192
21994
  const windows = def.windows;
21193
21995
  if (!windows) {
21194
21996
  throw new Error("WinSW service lifecycle requires `ServiceDefinition.windows.winswBin` (the product-bundled WinSW executable path)");
@@ -21200,7 +22002,7 @@ function createWinswLifecycle(def, deps = {}) {
21200
22002
  const xmlPath = path14__default.join(installDir, `${id}.xml`);
21201
22003
  async function fileExists(p) {
21202
22004
  try {
21203
- await fs36.stat(p);
22005
+ await fs37.stat(p);
21204
22006
  return true;
21205
22007
  } catch {
21206
22008
  return false;
@@ -21208,10 +22010,10 @@ function createWinswLifecycle(def, deps = {}) {
21208
22010
  }
21209
22011
  async function writeFiles(program) {
21210
22012
  const xml = generateWinswXml({ id, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
21211
- await fs36.mkdir(installDir, { recursive: true });
21212
- await fs36.mkdir(def.logDir, { recursive: true });
21213
- await fs36.copyFile(winswBin, exePath);
21214
- await fs36.writeFile(xmlPath, xml, "utf8");
22013
+ await fs37.mkdir(installDir, { recursive: true });
22014
+ await fs37.mkdir(def.logDir, { recursive: true });
22015
+ await fs37.copyFile(winswBin, exePath);
22016
+ await fs37.writeFile(xmlPath, xml, "utf8");
21215
22017
  }
21216
22018
  async function install(opts = {}) {
21217
22019
  await writeFiles(opts.program ?? def.program);
@@ -21221,8 +22023,8 @@ function createWinswLifecycle(def, deps = {}) {
21221
22023
  async function uninstall() {
21222
22024
  await runIdempotent(run, exePath, ["stop"], "winsw stop", WINSW_NOT_INSTALLED);
21223
22025
  await runIdempotent(run, exePath, ["uninstall"], "winsw uninstall", WINSW_NOT_INSTALLED);
21224
- await fs36.rm(exePath, { force: true });
21225
- await fs36.rm(xmlPath, { force: true });
22026
+ await fs37.rm(exePath, { force: true });
22027
+ await fs37.rm(xmlPath, { force: true });
21226
22028
  }
21227
22029
  async function start() {
21228
22030
  if (!await fileExists(xmlPath)) {
@@ -21265,376 +22067,31 @@ function createServiceLifecycle(def, opts = {}) {
21265
22067
  }
21266
22068
  }
21267
22069
 
21268
- // src/bin/format.ts
21269
- function quote(text) {
21270
- return JSON.stringify(text);
21271
- }
21272
- function redactedByteCountPlaceholder(text) {
21273
- return `[redacted: ${Buffer.byteLength(text, "utf8")} bytes]`;
22070
+ // src/bin/runtime-probe.ts
22071
+ var ALL_RUNTIME_IDS2 = ["pi", "claude", "codex"];
22072
+ var RUNTIME_PROBE_TIMEOUT_MS = 5e3;
22073
+ var MAX_RUNTIME_ID_CHARS = 128;
22074
+ var MAX_RUNTIME_VERSION_CHARS = 256;
22075
+ var MAX_PERMISSION_MODES = 32;
22076
+ var MAX_PERMISSION_MODE_CHARS = 64;
22077
+ function boundedSingleLine(value, maxChars) {
22078
+ return value.replace(/[\r\n\t]/g, " ").slice(0, maxChars);
21274
22079
  }
21275
- var STABLE_GIT_ERROR_CATEGORIES = new Set(GIT_ERROR_CATEGORIES);
21276
- function stableGitErrorCategory(value) {
21277
- return value !== void 0 && STABLE_GIT_ERROR_CATEGORIES.has(value) ? value : void 0;
21278
- }
21279
- function formatAgentEvent(event) {
21280
- switch (event.type) {
21281
- case "progress":
21282
- return `progress: ${quote(event.text)}`;
21283
- case "tool_use":
21284
- return `tool_use: ${event.tool}`;
21285
- case "tool_result":
21286
- return `tool_result: ${event.tool}`;
21287
- case "artifact":
21288
- return `artifact: ${event.name} (${event.contentType})`;
21289
- case "needs_approval":
21290
- return `needs_approval: ${quote(event.summary)}`;
21291
- case "turn_end":
21292
- return "turn_end";
21293
- case "error":
21294
- return `error: ${quote(event.message)}`;
21295
- case "usage": {
21296
- const parts = [];
21297
- if (event.inputTokens !== void 0) parts.push(`in=${event.inputTokens}`);
21298
- if (event.outputTokens !== void 0) parts.push(`out=${event.outputTokens}`);
21299
- if (event.totalTokens !== void 0) parts.push(`total=${event.totalTokens}`);
21300
- return `usage: ${parts.length ? parts.join(" ") : "(no fields reported)"}`;
21301
- }
21302
- }
21303
- }
21304
- function formatDaemonEventLine(event, options = {}) {
21305
- const prefix = `[${event.ts}]`;
21306
- switch (event.kind) {
21307
- case "offered":
21308
- return `${prefix} offered taskId=${event.taskId}${event.runtime ? ` runtime=${event.runtime}` : ""}`;
21309
- case "claimed":
21310
- return `${prefix} claimed taskId=${event.taskId}${event.claimedRuntime !== void 0 ? ` claimedRuntime=${event.claimedRuntime}` : ""}`;
21311
- case "started":
21312
- return `${prefix} started taskId=${event.taskId}`;
21313
- case "progress":
21314
- return `${prefix} progress taskId=${event.taskId} ${formatAgentEvent(event.event)}`;
21315
- case "artifact":
21316
- return `${prefix} artifact taskId=${event.taskId} name=${event.name} contentType=${event.contentType}`;
21317
- case "awaiting-approval": {
21318
- const summary = options.redactApprovalSummary ? redactedByteCountPlaceholder(event.summary) : event.summary;
21319
- return `${prefix} awaiting-approval taskId=${event.taskId}${event.approvalId ? ` approvalId=${event.approvalId}` : ""} summary=${quote(summary)}`;
21320
- }
21321
- case "completed":
21322
- return `${prefix} completed taskId=${event.taskId} sessionRef=${event.sessionRef} summary=${quote(event.summary)}`;
21323
- case "failed":
21324
- return `${prefix} failed taskId=${event.taskId} retryable=${event.retryable}${event.preClaim ? " preClaim=true" : ""} reason=${quote(event.reason)}`;
21325
- case "cancelled":
21326
- return `${prefix} cancelled taskId=${event.taskId}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
21327
- case "connection":
21328
- return `${prefix} connection state=${event.state}`;
21329
- case "paired":
21330
- return `${prefix} paired deviceId=${event.deviceId}`;
21331
- case "unpaired":
21332
- return `${prefix} unpaired`;
21333
- case "runtimes-detected":
21334
- return `${prefix} runtimes-detected ids=${event.runtimes.map((r) => r.id).join(",") || "(none)"}`;
21335
- case "shutdown-requested":
21336
- return `${prefix} shutdown-requested reason=${quote(event.reason)}`;
21337
- case "shutdown-complete":
21338
- return `${prefix} shutdown-complete reason=${quote(event.reason)}${event.undeliveredOutboxCount !== void 0 ? ` undeliveredOutboxCount=${event.undeliveredOutboxCount}` : ""}`;
21339
- case "stale-approval-decision":
21340
- return `${prefix} stale-approval-decision taskId=${event.taskId} decision=${event.decision}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
21341
- case "runtime-disposal-failed":
21342
- return `${prefix} runtime-disposal-failed taskId=${event.taskId} runtime=${event.runtimeId} stage=${event.stage} reason=${quote(event.reason)}`;
21343
- case "git-workspace": {
21344
- const parts = [
21345
- `${prefix} git-workspace taskId=${event.taskId}`,
21346
- `workspaceId=${event.workspaceId}`,
21347
- `phase=${event.phase}`,
21348
- event.headChanged !== void 0 ? `headChanged=${event.headChanged}` : void 0,
21349
- event.commitsSinceBaseline !== void 0 ? `commits=${event.commitsSinceBaseline}` : void 0,
21350
- event.dirty ? `dirty=${event.dirty.staged}/${event.dirty.unstaged}/${event.dirty.untracked}/${event.dirty.conflicted}` : void 0,
21351
- stableGitErrorCategory(event.errorCategory) ? `errorCategory=${stableGitErrorCategory(event.errorCategory)}` : void 0
21352
- ].filter((part) => part !== void 0);
21353
- return parts.join(" ");
21354
- }
21355
- case "device-assertion": {
21356
- const parts = event.result === "issued" ? [
21357
- `${prefix} device-assertion result=issued`,
21358
- `audience=${quote(event.audience)}`,
21359
- `jti=${event.jti}`,
21360
- `expiresAt=${event.expiresAt}`
21361
- ] : [
21362
- `${prefix} device-assertion result=denied`,
21363
- `reason=${event.reason}`,
21364
- event.audienceSize !== void 0 ? `audienceSize=${event.audienceSize}` : void 0
21365
- ].filter((part) => part !== void 0);
21366
- return parts.join(" ");
21367
- }
21368
- }
21369
- }
21370
- function formatTaskLine(task) {
21371
- const git = "git" in task ? task.git : void 0;
21372
- const gitStatus = git ? [
21373
- `git=${git.phase}`,
21374
- git.commitsSinceBaseline !== void 0 ? `commits=${git.commitsSinceBaseline}` : void 0,
21375
- git.dirty ? `dirty=${git.dirty.staged}/${git.dirty.unstaged}/${git.dirty.untracked}/${git.dirty.conflicted}` : void 0
21376
- ].filter((part) => part !== void 0).join(" ") : void 0;
21377
- const parts = [
21378
- task.taskId,
21379
- task.state,
21380
- task.runtime ? `runtime=${task.runtime}` : void 0,
21381
- gitStatus,
21382
- task.claimedRuntime !== void 0 ? `claimedRuntime=${task.claimedRuntime}` : void 0,
21383
- `updatedAt=${task.updatedAt}`,
21384
- task.sessionRef ? `sessionRef=${task.sessionRef}` : void 0,
21385
- task.declined ? "declined=true" : void 0,
21386
- task.summary ? `summary=${quote(task.summary)}` : void 0
21387
- ].filter((part) => Boolean(part));
21388
- return parts.join(" ");
21389
- }
21390
- function formatTaskListLines(tasks) {
21391
- if (tasks.length === 0) return ["(no tasks observed yet)"];
21392
- return tasks.map(formatTaskLine);
21393
- }
21394
- function formatRuntimeLines(runtimes) {
21395
- if (runtimes.length === 0) return ["(no runtimes configured \u2014 check runtimeAllowlist)"];
21396
- return runtimes.map((r) => {
21397
- if (!r.present) return `${r.id}: absent`;
21398
- const caps = [];
21399
- if (r.steer) caps.push("steer");
21400
- if (r.resume) caps.push("resume");
21401
- const parts = [
21402
- "present",
21403
- r.version ? `version=${r.version}` : void 0,
21404
- r.authPresent !== void 0 ? `authPresent=${r.authPresent}` : void 0,
21405
- `capabilities=${caps.length ? caps.join(",") : "(none)"}`,
21406
- `modes=${r.permissionModes.length ? r.permissionModes.join(",") : "(none)"}`
21407
- ].filter((part) => Boolean(part));
21408
- return `${r.id}: ${parts.join(" ")}`;
21409
- });
21410
- }
21411
- function formatStatusLines(view) {
21412
- const lines = [];
21413
- const label = view.branding?.displayName ?? view.productName;
21414
- lines.push(`product: ${label} (${view.productId})`);
21415
- lines.push(
21416
- `local-agent-release: ${view.localAgentRelease.version}${view.localAgentRelease.buildId ? ` buildId=${view.localAgentRelease.buildId}` : ""}`
21417
- );
21418
- if (view.branding?.supportUrl) lines.push(`support: ${view.branding.supportUrl}`);
21419
- lines.push(`paired: ${view.paired ? "yes" : "no"}${view.deviceId ? ` deviceId=${view.deviceId}` : ""}`);
21420
- lines.push(
21421
- view.connection ? `connection: last-known=${view.connection.state} at=${view.connection.ts}` : "connection: unknown (no audit log yet \u2014 run `byok-agent start` at least once to begin observing)"
21422
- );
21423
- const runtimeSummary = view.runtimes.map((r) => `${r.id}=${r.present ? "present" : "absent"}`).join(" ");
21424
- lines.push(`runtimes: ${runtimeSummary || "(none configured)"}`);
21425
- const c = view.taskCounts;
21426
- lines.push(
21427
- `tasks: total=${c.total} offered=${c.Offered} claimed=${c.Claimed} running=${c.Running} awaitApproval=${c.AwaitApproval} complete=${c.Complete} failed=${c.Failed} cancelled=${c.Cancelled}`
21428
- );
21429
- lines.push(`audit-log: ${view.auditLogPath} (${view.auditLogLineCount} event${view.auditLogLineCount === 1 ? "" : "s"})`);
21430
- return lines;
21431
- }
21432
- function formatLiveStatusLines(live) {
21433
- const liveRelease = live.localAgentRelease;
21434
- const lines = [
21435
- `live: pid=${live.pid} uptimeMs=${live.uptimeMs} transport=${live.transport}`,
21436
- liveRelease ? `live-local-agent-release: ${liveRelease.version}${liveRelease.buildId ? ` buildId=${liveRelease.buildId}` : ""}` : "live-local-agent-release: unknown",
21437
- `live-paired: ${live.paired ? "yes" : "no"}${live.deviceId ? ` deviceId=${live.deviceId}` : ""}`,
21438
- `live-runtimes: ${live.runtimeIds.length ? live.runtimeIds.join(",") : "(none)"}`,
21439
- `live-toolsets: revision=${live.toolsets.revision} count=${live.toolsets.toolsets.length}`
21440
- ];
21441
- for (const toolset of live.toolsets.toolsets) lines.push(formatToolsetStatusLine("live-toolset", toolset));
21442
- const health = live.operationalHealth;
21443
- if (health.availability === "unavailable") {
21444
- lines.push(`live-operational-health: unavailable reason=${quote(health.reason)}`);
21445
- } else {
21446
- lines.push(
21447
- `live-operational-health: state=${health.state} failures=${health.failureCount}/${health.failureThreshold} windowMs=${health.windowMs} crashes=${health.crashCount}${health.lastCrashAt ? ` lastCrashAt=${health.lastCrashAt}` : ""}`
21448
- );
21449
- }
21450
- if (live.activeTasks.length === 0) {
21451
- lines.push("live-active-tasks: (none)");
21452
- } else {
21453
- for (const task of live.activeTasks) {
21454
- lines.push(`live-active-task: ${task.taskId} ${task.state}`);
21455
- }
21456
- }
21457
- lines.push(`live-approvals-pending: ${live.approvalsPending}`);
21458
- if (live.approvals.length === 0) {
21459
- lines.push("live-approvals: (none)");
21460
- } else {
21461
- for (const approval of live.approvals) {
21462
- lines.push(`live-approval: ${approval.approvalId} taskId=${approval.taskId} summary=${quote(summaryExcerpt(approval.summary))}`);
21463
- }
21464
- }
21465
- if (live.queueWatermarks.length === 0) {
21466
- lines.push("live-queue-watermarks: (none)");
21467
- } else {
21468
- for (const watermark of live.queueWatermarks) {
21469
- lines.push(
21470
- `live-queue-watermark: ${watermark.taskId} progressBatcherPending=${watermark.progressBatcherPending} pendingApprovals=${watermark.pendingApprovals}`
21471
- );
21472
- }
21473
- }
21474
- const storage = live.storage;
21475
- if (storage !== void 0) {
21476
- lines.push(
21477
- `live-storage: state=${storage.pressureState} used=${storage.usedBytes} budget=${storage.budgetBytes} free=${storage.freeBytes} measuredAt=${storage.measuredAt}`
21478
- );
21479
- for (const category of storage.categories) {
21480
- lines.push(`live-storage-category: ${category.category} bytes=${category.bytes}${category.approximate ? " (approximate)" : ""}`);
21481
- }
21482
- if (storage.lastCompaction) {
21483
- lines.push(
21484
- `live-storage-compaction: checkpointed=${storage.lastCompaction.checkpointed} walFramesRemaining=${storage.lastCompaction.walFramesRemaining} pagesVacuumed=${storage.lastCompaction.pagesVacuumed} durationMs=${storage.lastCompaction.durationMs} at=${storage.lastCompaction.at}`
21485
- );
21486
- }
21487
- }
21488
- return lines;
21489
- }
21490
- function formatToolsetStatusLine(prefix, toolset) {
21491
- const observation = toolset.observation;
21492
- return [
21493
- `${prefix}: ${toolset.id}`,
21494
- `servers=${toolset.serverCount}`,
21495
- `definitionRevision=${toolset.definitionRevision}`,
21496
- `state=${observation?.state ?? "unobserved"}`,
21497
- observation?.version === void 0 ? void 0 : `version=${quote(observation.version)}`,
21498
- observation?.observedAt === void 0 ? void 0 : `observedAt=${observation.observedAt}`,
21499
- observation?.reasonCode === void 0 ? void 0 : `reasonCode=${observation.reasonCode}`
21500
- ].filter((part) => part !== void 0).join(" ");
21501
- }
21502
- function formatToolsetsReloadReceiptLines(receipt) {
21503
- return [
21504
- `toolsets-reload: changed=${receipt.changed ? "yes" : "no"} previousRevision=${receipt.previousRevision} revision=${receipt.revision}`,
21505
- ...receipt.toolsets.map((toolset) => formatToolsetStatusLine("toolset", toolset))
21506
- ];
21507
- }
21508
- var SUMMARY_EXCERPT_MAX_LEN = 60;
21509
- function summaryExcerpt(summary) {
21510
- if (!summary) return "(no summary)";
21511
- return summary.length > SUMMARY_EXCERPT_MAX_LEN ? `${summary.slice(0, SUMMARY_EXCERPT_MAX_LEN)}\u2026` : summary;
21512
- }
21513
- function formatAge(ms) {
21514
- const totalSeconds = Math.floor(Math.max(0, ms) / 1e3);
21515
- if (totalSeconds < 60) return `${totalSeconds}s`;
21516
- const totalMinutes = Math.floor(totalSeconds / 60);
21517
- if (totalMinutes < 60) return `${totalMinutes}m`;
21518
- const totalHours = Math.floor(totalMinutes / 60);
21519
- if (totalHours < 24) return `${totalHours}h`;
21520
- const totalDays = Math.floor(totalHours / 24);
21521
- return `${totalDays}d`;
21522
- }
21523
- function formatApprovalsListLines(approvals, nowMs) {
21524
- if (approvals.length === 0) return ["(no pending approvals)"];
21525
- return approvals.map((approval) => {
21526
- const ageMs = nowMs - Date.parse(approval.createdAt);
21527
- return `${approval.approvalId} taskId=${approval.taskId} age=${formatAge(ageMs)} summary=${quote(summaryExcerpt(approval.summary))}`;
21528
- });
21529
- }
21530
-
21531
- // src/bin/commands/approvals.ts
21532
- async function runApprovalsCommand(storeDir, productId, deps = {}) {
21533
- const log = deps.log ?? ((line) => console.log(line));
21534
- const error = deps.error ?? ((line) => console.error(line));
21535
- const connectControl = deps.connectControl ?? connectControlClient;
21536
- const now = deps.now ?? (() => Date.now());
21537
- const conn = await connectControl({ storeDir, productId });
21538
- if (!conn.ok) {
21539
- const message = `cannot list approvals: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
21540
- error(message);
21541
- throw new Error(message);
21542
- }
21543
- try {
21544
- const result = await conn.client.request("approvals.list");
21545
- for (const line of formatApprovalsListLines(result.approvals, now())) log(line);
21546
- } catch (err) {
21547
- if (err instanceof ControlError) {
21548
- error(`approvals list failed: ${err.message}`);
21549
- } else {
21550
- error(`approvals list failed: ${err instanceof Error ? err.message : String(err)}`);
21551
- }
21552
- throw err;
21553
- } finally {
21554
- conn.client.close();
21555
- }
21556
- }
21557
-
21558
- // src/bin/commands/approve-reject.ts
21559
- async function runApproveCommand(storeDir, productId, approvalId, deps = {}) {
21560
- return resolveApproval(storeDir, productId, approvalId, "approve", void 0, deps);
21561
- }
21562
- async function runRejectCommand(storeDir, productId, approvalId, reason, deps = {}) {
21563
- return resolveApproval(storeDir, productId, approvalId, "reject", reason, deps);
21564
- }
21565
- async function resolveApproval(storeDir, productId, approvalId, decision, reason, deps) {
21566
- const log = deps.log ?? ((line) => console.log(line));
21567
- const error = deps.error ?? ((line) => console.error(line));
21568
- const connectControl = deps.connectControl ?? connectControlClient;
21569
- const verb = decision === "approve" ? "approved" : "rejected";
21570
- const conn = await connectControl({ storeDir, productId });
21571
- if (!conn.ok) {
21572
- const message = `cannot ${decision} approvalId=${approvalId}: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
21573
- error(message);
21574
- throw new Error(message);
21575
- }
21576
- try {
21577
- await conn.client.request("approvals.resolve", { approvalId, decision, reason });
21578
- log(`${verb}: approvalId=${approvalId}${reason ? ` reason=${JSON.stringify(reason)}` : ""}`);
21579
- } catch (err) {
21580
- if (err instanceof ControlError && err.code === "not_found") {
21581
- error(`${err.message} \u2014 run \`byok-agent approvals\` to see pending approvalIds`);
21582
- } else {
21583
- error(`${decision} failed: ${err instanceof Error ? err.message : String(err)}`);
21584
- }
21585
- throw err;
21586
- } finally {
21587
- conn.client.close();
21588
- }
21589
- }
21590
-
21591
- // src/bin/commands/pair.ts
21592
- async function runPairCommand(config, code, deps = {}) {
21593
- const log = deps.log ?? ((line) => console.log(line));
21594
- if (deps.daemon) {
21595
- const result2 = await deps.daemon.pair(code);
21596
- log(`paired: deviceId=${result2.deviceId}`);
21597
- return;
21598
- }
21599
- const connectControl = deps.connectControl ?? connectControlClient;
21600
- const conn = await connectControl({ storeDir: resolveStoreDir(config), productId: config.productId });
21601
- if (conn.ok) {
21602
- try {
21603
- const result2 = await conn.client.request("enrollment.pair", { pairingCode: code });
21604
- log(`paired: deviceId=${result2.deviceId}`);
21605
- return;
21606
- } finally {
21607
- conn.client.close();
21608
- }
21609
- }
21610
- const daemon = createDaemon(config);
21611
- const result = await daemon.pair(code);
21612
- log(`paired: deviceId=${result.deviceId}`);
21613
- }
21614
-
21615
- // src/bin/runtime-probe.ts
21616
- var ALL_RUNTIME_IDS2 = ["pi", "claude", "codex"];
21617
- var RUNTIME_PROBE_TIMEOUT_MS = 5e3;
21618
- var MAX_RUNTIME_ID_CHARS = 128;
21619
- var MAX_RUNTIME_VERSION_CHARS = 256;
21620
- var MAX_PERMISSION_MODES = 32;
21621
- var MAX_PERMISSION_MODE_CHARS = 64;
21622
- function boundedSingleLine(value, maxChars) {
21623
- return value.replace(/[\r\n\t]/g, " ").slice(0, maxChars);
21624
- }
21625
- async function detectWithTimeout(adapter, timeoutMs) {
21626
- let timer;
21627
- try {
21628
- return await Promise.race([
21629
- adapter.detect(),
21630
- new Promise((_resolve, reject) => {
21631
- timer = setTimeout(() => reject(new Error("runtime detection timed out")), timeoutMs);
21632
- timer.unref?.();
21633
- })
21634
- ]);
21635
- } finally {
21636
- if (timer) clearTimeout(timer);
21637
- }
22080
+ var RuntimeProbeTimeout = class extends Error {
22081
+ };
22082
+ async function detectWithTimeout(adapter, timeoutMs) {
22083
+ let timer;
22084
+ try {
22085
+ return await Promise.race([
22086
+ adapter.detect(),
22087
+ new Promise((_resolve, reject) => {
22088
+ timer = setTimeout(() => reject(new RuntimeProbeTimeout()), timeoutMs);
22089
+ timer.unref?.();
22090
+ })
22091
+ ]);
22092
+ } finally {
22093
+ if (timer) clearTimeout(timer);
22094
+ }
21638
22095
  }
21639
22096
  function defaultRuntimeAdapters(runtimeAllowlist) {
21640
22097
  const ids = runtimeAllowlist ? ALL_RUNTIME_IDS2.filter((id) => runtimeAllowlist.includes(id)) : ALL_RUNTIME_IDS2;
@@ -21664,18 +22121,19 @@ async function probeRuntimes(adapters, options = {}) {
21664
22121
  steer = caps.steer === true;
21665
22122
  resume = caps.resume === true;
21666
22123
  permissionModes = caps.permissionModes.slice(0, MAX_PERMISSION_MODES).map((mode) => boundedSingleLine(mode, MAX_PERMISSION_MODE_CHARS));
21667
- const detected = await detectWithTimeout(adapter, timeoutMs);
22124
+ const detected = validateRuntimeDetectResult(await detectWithTimeout(adapter, timeoutMs));
21668
22125
  return {
21669
22126
  id,
21670
- present: detected.present === true,
21671
- ...detected.version === void 0 ? {} : { version: boundedSingleLine(detected.version, MAX_RUNTIME_VERSION_CHARS) },
21672
- ...typeof detected.authPresent === "boolean" ? { authPresent: detected.authPresent } : {},
22127
+ present: detected.kind === "available",
22128
+ outcome: detected.kind,
22129
+ ...detected.kind !== "available" || detected.version === void 0 ? {} : { version: boundedSingleLine(detected.version, MAX_RUNTIME_VERSION_CHARS) },
22130
+ ...detected.kind === "available" && typeof detected.authPresent === "boolean" ? { authPresent: detected.authPresent } : {},
21673
22131
  steer,
21674
22132
  resume,
21675
22133
  permissionModes
21676
22134
  };
21677
- } catch {
21678
- return { id, present: false, steer, resume, permissionModes };
22135
+ } catch (error) {
22136
+ return { id, present: false, outcome: error instanceof RuntimeProbeTimeout ? "timeout" : "probe-failed", steer, resume, permissionModes };
21679
22137
  }
21680
22138
  })
21681
22139
  );
@@ -22083,7 +22541,7 @@ function checksFor(snapshot) {
22083
22541
  {
22084
22542
  id: "runtimes",
22085
22543
  status: snapshot.runtimes.some((runtime) => runtime.present) ? "pass" : "warn",
22086
- summary: `${snapshot.runtimes.filter((runtime) => runtime.present).length}/${snapshot.runtimes.length} present`
22544
+ summary: `${snapshot.runtimes.filter((runtime) => runtime.present).length}/${snapshot.runtimes.length} present; ` + RUNTIME_DETECTION_FAILURE_KINDS.map((kind) => `${kind}=${snapshot.runtimes.filter((runtime) => runtime.outcome === kind).length}`).join(" ")
22087
22545
  },
22088
22546
  {
22089
22547
  id: "control",
@@ -22128,6 +22586,7 @@ async function collectDiagnostics(config, storeDir, options = {}) {
22128
22586
  const runtimes = probedRuntimes.map((runtime) => ({
22129
22587
  idHash: stableIdentifierHash(runtime.id),
22130
22588
  present: runtime.present,
22589
+ outcome: runtime.outcome,
22131
22590
  versionPresent: runtime.version !== void 0,
22132
22591
  ...runtime.authPresent === void 0 ? {} : { authPresent: runtime.authPresent },
22133
22592
  steer: runtime.steer,
@@ -22217,172 +22676,586 @@ function publishQuarantineEvidencePinned(quarantineDir, expectedDirectory, sourc
22217
22676
  if (bytesRead === 0) throw new Error("operational health state ended during bounded copy");
22218
22677
  position += bytesRead;
22219
22678
  }
22220
- const afterRead = fstatSync(sourceFd, { bigint: true });
22221
- if (!sameFileState5(afterRead, sourceStat)) {
22222
- throw new Error("operational health state changed during bounded copy");
22679
+ const afterRead = fstatSync(sourceFd, { bigint: true });
22680
+ if (!sameFileState5(afterRead, sourceStat)) {
22681
+ throw new Error("operational health state changed during bounded copy");
22682
+ }
22683
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
22684
+ evidenceTemp = `.${evidenceName}.${randomUUID()}.tmp`;
22685
+ const evidenceFd = openSync(evidenceTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
22686
+ try {
22687
+ writeFileSync(evidenceFd, bytes);
22688
+ fchmodSync(evidenceFd, 384);
22689
+ fsyncSync(evidenceFd);
22690
+ } finally {
22691
+ closeSync(evidenceFd);
22692
+ }
22693
+ linkSync(evidenceTemp, evidenceName);
22694
+ evidenceCreated = true;
22695
+ const evidence = lstatSync(evidenceName, { bigint: true });
22696
+ const tempEvidence = lstatSync(evidenceTemp, { bigint: true });
22697
+ if (!evidence.isFile() || !sameInode2(evidence, tempEvidence) || evidence.size !== sourceStat.size) {
22698
+ throw new Error("quarantined operational health evidence does not match the bounded copy");
22699
+ }
22700
+ unlinkSync(evidenceTemp);
22701
+ evidenceTemp = void 0;
22702
+ const finalOpenSource = fstatSync(sourceFd, { bigint: true });
22703
+ const finalNamedSource = lstatSync(sourcePath, { bigint: true });
22704
+ if (!sameFileState5(finalOpenSource, sourceStat) || !sameFileState5(finalNamedSource, sourceStat)) {
22705
+ throw new Error("operational health source changed before removal");
22706
+ }
22707
+ const manifestBody = `${JSON.stringify(
22708
+ {
22709
+ version: 1,
22710
+ quarantinedAt,
22711
+ reason,
22712
+ sourcePath,
22713
+ evidenceFile: evidenceName,
22714
+ sha256,
22715
+ sizeBytes
22716
+ },
22717
+ null,
22718
+ 2
22719
+ )}
22720
+ `;
22721
+ manifestTemp = `.${manifestName}.${randomUUID()}.tmp`;
22722
+ const manifestFd = openSync(manifestTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
22723
+ try {
22724
+ writeFileSync(manifestFd, manifestBody, "utf8");
22725
+ fchmodSync(manifestFd, 384);
22726
+ fsyncSync(manifestFd);
22727
+ } finally {
22728
+ closeSync(manifestFd);
22729
+ }
22730
+ linkSync(manifestTemp, manifestName);
22731
+ manifestCreated = true;
22732
+ unlinkSync(manifestTemp);
22733
+ manifestTemp = void 0;
22734
+ if (process.platform !== "win32") {
22735
+ const quarantineDirectoryFd = openSync(".", constants.O_RDONLY);
22736
+ try {
22737
+ fsyncSync(quarantineDirectoryFd);
22738
+ } finally {
22739
+ closeSync(quarantineDirectoryFd);
22740
+ }
22741
+ }
22742
+ const removalOpenSource = fstatSync(sourceFd, { bigint: true });
22743
+ const removalNamedSource = lstatSync(sourcePath, { bigint: true });
22744
+ if (!sameFileState5(removalOpenSource, sourceStat) || !sameFileState5(removalNamedSource, sourceStat)) {
22745
+ throw new Error("operational health source changed before removal");
22746
+ }
22747
+ unlinkSync(sourcePath);
22748
+ sourceRemoved = true;
22749
+ if (process.platform !== "win32") {
22750
+ const directoryFd = openSync(path14__default.dirname(sourcePath), constants.O_RDONLY);
22751
+ try {
22752
+ fsyncSync(directoryFd);
22753
+ } finally {
22754
+ closeSync(directoryFd);
22755
+ }
22756
+ }
22757
+ return { sha256, sizeBytes };
22758
+ } catch (err) {
22759
+ if (evidenceTemp) {
22760
+ try {
22761
+ unlinkSync(evidenceTemp);
22762
+ } catch {
22763
+ }
22764
+ }
22765
+ if (manifestTemp) {
22766
+ try {
22767
+ unlinkSync(manifestTemp);
22768
+ } catch {
22769
+ }
22770
+ }
22771
+ if (manifestCreated && !sourceRemoved) {
22772
+ try {
22773
+ unlinkSync(manifestName);
22774
+ } catch {
22775
+ }
22776
+ }
22777
+ if (evidenceCreated && !sourceRemoved) {
22778
+ try {
22779
+ unlinkSync(evidenceName);
22780
+ } catch {
22781
+ }
22782
+ }
22783
+ throw err;
22784
+ } finally {
22785
+ if (entered) process.chdir(previousCwd);
22786
+ }
22787
+ }
22788
+ async function quarantineCorruptOperationalHealth(storeDir, options = {}) {
22789
+ const resolvedStoreDir = path14__default.resolve(storeDir);
22790
+ const owner = await acquireDaemonOwner(resolvedStoreDir, "doctor", options.clock);
22791
+ try {
22792
+ const sourcePath = path14__default.join(resolvedStoreDir, OPERATIONAL_HEALTH_FILENAME);
22793
+ let opened;
22794
+ try {
22795
+ opened = await openOperationalHealthFile(resolvedStoreDir);
22796
+ } catch (err) {
22797
+ throw new Error("operational health state could not be opened safely; refusing quarantine", { cause: err });
22798
+ }
22799
+ if (!opened) return { status: "not-needed", reason: "missing" };
22800
+ const source = opened.handle;
22801
+ try {
22802
+ const inspection = await inspectOperationalHealthHandle(source, opened.stat);
22803
+ if (inspection.status === "valid") return { status: "not-needed", reason: "valid" };
22804
+ if (inspection.status === "unavailable") {
22805
+ throw new Error(`${inspection.reason}; refusing to quarantine unconfirmed corruption`);
22806
+ }
22807
+ const sourceStat = await source.stat({ bigint: true });
22808
+ if (!sourceStat.isFile()) throw new Error("operational health state is not a regular file; refusing quarantine");
22809
+ const quarantineDir = path14__default.join(resolvedStoreDir, JOURNAL_QUARANTINE_DIRNAME);
22810
+ try {
22811
+ const existing = await promises.lstat(quarantineDir);
22812
+ if (!existing.isDirectory() || existing.isSymbolicLink()) {
22813
+ throw new Error("quarantine path is not a real directory; refusing quarantine");
22814
+ }
22815
+ } catch (err) {
22816
+ if (err.code !== "ENOENT") throw err;
22817
+ await promises.mkdir(quarantineDir, { mode: 448 });
22818
+ }
22819
+ await ensureSecureDir(quarantineDir);
22820
+ const securedQuarantine = await promises.lstat(quarantineDir, { bigint: true });
22821
+ if (!securedQuarantine.isDirectory() || securedQuarantine.isSymbolicLink()) {
22822
+ throw new Error("quarantine path changed during validation; refusing quarantine");
22823
+ }
22824
+ const stamp = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString().replace(/[:.]/g, "-");
22825
+ const evidenceName = `${stamp}-${randomUUID()}-${OPERATIONAL_HEALTH_FILENAME}`;
22826
+ const manifestName = `${evidenceName}.manifest.json`;
22827
+ const hashed = publishQuarantineEvidencePinned(
22828
+ quarantineDir,
22829
+ securedQuarantine,
22830
+ sourcePath,
22831
+ source.fd,
22832
+ sourceStat,
22833
+ evidenceName,
22834
+ manifestName,
22835
+ (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
22836
+ inspection.reason
22837
+ );
22838
+ return { status: "quarantined", evidenceName, manifestName, sha256: hashed.sha256, sizeBytes: hashed.sizeBytes };
22839
+ } finally {
22840
+ await source.close();
22841
+ }
22842
+ } finally {
22843
+ await owner.release();
22844
+ }
22845
+ }
22846
+
22847
+ // src/diagnostics/device-doctor.ts
22848
+ var MESSAGES = {
22849
+ "confirmation-required": "Explicit confirmation is required for enrollment metadata repair.",
22850
+ "invalid-target": "An explicit expected tenant and device are required.",
22851
+ "daemon-running": "Stop the daemon before enrollment metadata repair.",
22852
+ "store-busy": "The store is owned by another operation; enrollment metadata repair refused.",
22853
+ "authority-unavailable": "The OS enrollment authority could not be read.",
22854
+ "authority-missing": "No complete OS enrollment exists; use explicit authenticated pairing.",
22855
+ "target-mismatch": "The OS enrollment does not match the expected tenant and device.",
22856
+ "projection-unavailable": "The enrollment metadata could not be read safely; repair refused.",
22857
+ "repair-failed": "Enrollment metadata repair did not complete; inspect the state before retrying."
22858
+ };
22859
+ var DeviceMetadataRepairError = class extends Error {
22860
+ constructor(code) {
22861
+ super(MESSAGES[code]);
22862
+ this.code = code;
22863
+ this.name = "DeviceMetadataRepairError";
22864
+ }
22865
+ code;
22866
+ };
22867
+ async function repairDeviceEnrollmentMetadata(config, input) {
22868
+ if (input?.confirmed !== true) throw new DeviceMetadataRepairError("confirmation-required");
22869
+ if (typeof input.expectedDeviceId !== "string" || input.expectedDeviceId.trim().length === 0 || !isTenantId(input.expectedTenantId)) {
22870
+ throw new DeviceMetadataRepairError("invalid-target");
22871
+ }
22872
+ const { expectedDeviceId, expectedTenantId } = input;
22873
+ const productId = config.productId;
22874
+ let failureCode = "repair-failed";
22875
+ try {
22876
+ const storeDir = DeviceStore.resolveDir(productId, config.storeDir);
22877
+ const control = await connectControlClient({ storeDir, productId });
22878
+ if (control.ok) {
22879
+ control.client.close();
22880
+ throw new DeviceMetadataRepairError("daemon-running");
22223
22881
  }
22224
- const sha256 = createHash("sha256").update(bytes).digest("hex");
22225
- evidenceTemp = `.${evidenceName}.${randomUUID()}.tmp`;
22226
- const evidenceFd = openSync(evidenceTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
22882
+ const owner = await acquireDaemonOwner(storeDir, "doctor");
22227
22883
  try {
22228
- writeFileSync(evidenceFd, bytes);
22229
- fchmodSync(evidenceFd, 384);
22230
- fsyncSync(evidenceFd);
22884
+ const store = new DeviceStore(storeDir, void 0, productId);
22885
+ failureCode = "projection-unavailable";
22886
+ await store.load();
22887
+ failureCode = "authority-unavailable";
22888
+ const authority = await store.credentials.read();
22889
+ if (authority === void 0) throw new DeviceMetadataRepairError("authority-missing");
22890
+ if (authority.deviceId !== expectedDeviceId || authority.tenantId !== expectedTenantId) {
22891
+ throw new DeviceMetadataRepairError("target-mismatch");
22892
+ }
22893
+ failureCode = "repair-failed";
22894
+ const changed = await store.reconcileMetadata(authority);
22895
+ const readback = await store.load();
22896
+ if (readback === void 0 || readback.deviceId !== authority.deviceId || readback.tenantId !== authority.tenantId || readback.devicePublicKey !== authority.devicePublicKey) {
22897
+ throw new DeviceMetadataRepairError("repair-failed");
22898
+ }
22899
+ return { action: "restore-enrollment-metadata", scope: "device", status: changed ? "repaired" : "not-needed" };
22900
+ } catch (error) {
22901
+ if (error instanceof DeviceMetadataRepairError) throw error;
22902
+ throw new DeviceMetadataRepairError(failureCode);
22231
22903
  } finally {
22232
- closeSync(evidenceFd);
22904
+ failureCode = "repair-failed";
22905
+ await owner.release();
22233
22906
  }
22234
- linkSync(evidenceTemp, evidenceName);
22235
- evidenceCreated = true;
22236
- const evidence = lstatSync(evidenceName, { bigint: true });
22237
- const tempEvidence = lstatSync(evidenceTemp, { bigint: true });
22238
- if (!evidence.isFile() || !sameInode2(evidence, tempEvidence) || evidence.size !== sourceStat.size) {
22239
- throw new Error("quarantined operational health evidence does not match the bounded copy");
22907
+ } catch (error) {
22908
+ if (error instanceof DeviceMetadataRepairError) throw error;
22909
+ if (error instanceof DaemonOwnerActiveError) throw new DeviceMetadataRepairError("store-busy");
22910
+ throw new DeviceMetadataRepairError(failureCode);
22911
+ }
22912
+ }
22913
+
22914
+ // src/bin/format.ts
22915
+ function quote(text) {
22916
+ return JSON.stringify(text);
22917
+ }
22918
+ function redactedByteCountPlaceholder(text) {
22919
+ return `[redacted: ${Buffer.byteLength(text, "utf8")} bytes]`;
22920
+ }
22921
+ var STABLE_GIT_ERROR_CATEGORIES = new Set(GIT_ERROR_CATEGORIES);
22922
+ function stableGitErrorCategory(value) {
22923
+ return value !== void 0 && STABLE_GIT_ERROR_CATEGORIES.has(value) ? value : void 0;
22924
+ }
22925
+ function formatAgentEvent(event) {
22926
+ switch (event.type) {
22927
+ case "progress":
22928
+ return `progress: ${quote(event.text)}`;
22929
+ case "tool_use":
22930
+ return `tool_use: ${event.tool}`;
22931
+ case "tool_result":
22932
+ return `tool_result: ${event.tool}`;
22933
+ case "artifact":
22934
+ return `artifact: ${event.name} (${event.contentType})`;
22935
+ case "needs_approval":
22936
+ return `needs_approval: ${quote(event.summary)}`;
22937
+ case "turn_end":
22938
+ return "turn_end";
22939
+ case "error":
22940
+ return `error: ${quote(event.message)}`;
22941
+ case "usage": {
22942
+ const parts = [];
22943
+ if (event.inputTokens !== void 0) parts.push(`in=${event.inputTokens}`);
22944
+ if (event.outputTokens !== void 0) parts.push(`out=${event.outputTokens}`);
22945
+ if (event.totalTokens !== void 0) parts.push(`total=${event.totalTokens}`);
22946
+ return `usage: ${parts.length ? parts.join(" ") : "(no fields reported)"}`;
22240
22947
  }
22241
- unlinkSync(evidenceTemp);
22242
- evidenceTemp = void 0;
22243
- const finalOpenSource = fstatSync(sourceFd, { bigint: true });
22244
- const finalNamedSource = lstatSync(sourcePath, { bigint: true });
22245
- if (!sameFileState5(finalOpenSource, sourceStat) || !sameFileState5(finalNamedSource, sourceStat)) {
22246
- throw new Error("operational health source changed before removal");
22948
+ }
22949
+ }
22950
+ function formatDaemonEventLine(event, options = {}) {
22951
+ const prefix = `[${event.ts}]`;
22952
+ switch (event.kind) {
22953
+ case "offered":
22954
+ return `${prefix} offered taskId=${event.taskId}${event.runtime ? ` runtime=${event.runtime}` : ""}`;
22955
+ case "claimed":
22956
+ return `${prefix} claimed taskId=${event.taskId}${event.claimedRuntime !== void 0 ? ` claimedRuntime=${event.claimedRuntime}` : ""}`;
22957
+ case "started":
22958
+ return `${prefix} started taskId=${event.taskId}`;
22959
+ case "progress":
22960
+ return `${prefix} progress taskId=${event.taskId} ${formatAgentEvent(event.event)}`;
22961
+ case "artifact":
22962
+ return `${prefix} artifact taskId=${event.taskId} name=${event.name} contentType=${event.contentType}`;
22963
+ case "awaiting-approval": {
22964
+ const summary = options.redactApprovalSummary ? redactedByteCountPlaceholder(event.summary) : event.summary;
22965
+ return `${prefix} awaiting-approval taskId=${event.taskId}${event.approvalId ? ` approvalId=${event.approvalId}` : ""} summary=${quote(summary)}`;
22247
22966
  }
22248
- const manifestBody = `${JSON.stringify(
22249
- {
22250
- version: 1,
22251
- quarantinedAt,
22252
- reason,
22253
- sourcePath,
22254
- evidenceFile: evidenceName,
22255
- sha256,
22256
- sizeBytes
22257
- },
22258
- null,
22259
- 2
22260
- )}
22261
- `;
22262
- manifestTemp = `.${manifestName}.${randomUUID()}.tmp`;
22263
- const manifestFd = openSync(manifestTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
22264
- try {
22265
- writeFileSync(manifestFd, manifestBody, "utf8");
22266
- fchmodSync(manifestFd, 384);
22267
- fsyncSync(manifestFd);
22268
- } finally {
22269
- closeSync(manifestFd);
22967
+ case "completed":
22968
+ return `${prefix} completed taskId=${event.taskId} sessionRef=${event.sessionRef} summary=${quote(event.summary)}`;
22969
+ case "failed":
22970
+ return `${prefix} failed taskId=${event.taskId} retryable=${event.retryable}${event.preClaim ? " preClaim=true" : ""} reason=${quote(event.reason)}`;
22971
+ case "cancelled":
22972
+ return `${prefix} cancelled taskId=${event.taskId}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
22973
+ case "connection":
22974
+ return `${prefix} connection state=${event.state}`;
22975
+ case "paired":
22976
+ return `${prefix} paired deviceId=${event.deviceId}`;
22977
+ case "unpaired":
22978
+ return `${prefix} unpaired`;
22979
+ case "runtimes-detected":
22980
+ return `${prefix} runtimes-detected ids=${event.runtimes.map((r) => r.id).join(",") || "(none)"}`;
22981
+ case "shutdown-requested":
22982
+ return `${prefix} shutdown-requested reason=${quote(event.reason)}`;
22983
+ case "shutdown-complete":
22984
+ return `${prefix} shutdown-complete reason=${quote(event.reason)}${event.undeliveredOutboxCount !== void 0 ? ` undeliveredOutboxCount=${event.undeliveredOutboxCount}` : ""}`;
22985
+ case "stale-approval-decision":
22986
+ return `${prefix} stale-approval-decision taskId=${event.taskId} decision=${event.decision}${event.reason ? ` reason=${quote(event.reason)}` : ""}`;
22987
+ case "runtime-disposal-failed":
22988
+ return `${prefix} runtime-disposal-failed taskId=${event.taskId} runtime=${event.runtimeId} stage=${event.stage} reason=${quote(event.reason)}`;
22989
+ case "git-workspace": {
22990
+ const parts = [
22991
+ `${prefix} git-workspace taskId=${event.taskId}`,
22992
+ `workspaceId=${event.workspaceId}`,
22993
+ `phase=${event.phase}`,
22994
+ event.headChanged !== void 0 ? `headChanged=${event.headChanged}` : void 0,
22995
+ event.commitsSinceBaseline !== void 0 ? `commits=${event.commitsSinceBaseline}` : void 0,
22996
+ event.dirty ? `dirty=${event.dirty.staged}/${event.dirty.unstaged}/${event.dirty.untracked}/${event.dirty.conflicted}` : void 0,
22997
+ stableGitErrorCategory(event.errorCategory) ? `errorCategory=${stableGitErrorCategory(event.errorCategory)}` : void 0
22998
+ ].filter((part) => part !== void 0);
22999
+ return parts.join(" ");
22270
23000
  }
22271
- linkSync(manifestTemp, manifestName);
22272
- manifestCreated = true;
22273
- unlinkSync(manifestTemp);
22274
- manifestTemp = void 0;
22275
- if (process.platform !== "win32") {
22276
- const quarantineDirectoryFd = openSync(".", constants.O_RDONLY);
22277
- try {
22278
- fsyncSync(quarantineDirectoryFd);
22279
- } finally {
22280
- closeSync(quarantineDirectoryFd);
22281
- }
23001
+ case "device-assertion": {
23002
+ const parts = event.result === "issued" ? [
23003
+ `${prefix} device-assertion result=issued`,
23004
+ `audience=${quote(event.audience)}`,
23005
+ `jti=${event.jti}`,
23006
+ `expiresAt=${event.expiresAt}`
23007
+ ] : [
23008
+ `${prefix} device-assertion result=denied`,
23009
+ `reason=${event.reason}`,
23010
+ event.audienceSize !== void 0 ? `audienceSize=${event.audienceSize}` : void 0
23011
+ ].filter((part) => part !== void 0);
23012
+ return parts.join(" ");
22282
23013
  }
22283
- const removalOpenSource = fstatSync(sourceFd, { bigint: true });
22284
- const removalNamedSource = lstatSync(sourcePath, { bigint: true });
22285
- if (!sameFileState5(removalOpenSource, sourceStat) || !sameFileState5(removalNamedSource, sourceStat)) {
22286
- throw new Error("operational health source changed before removal");
23014
+ }
23015
+ }
23016
+ function formatTaskLine(task) {
23017
+ const git = "git" in task ? task.git : void 0;
23018
+ const gitStatus = git ? [
23019
+ `git=${git.phase}`,
23020
+ git.commitsSinceBaseline !== void 0 ? `commits=${git.commitsSinceBaseline}` : void 0,
23021
+ git.dirty ? `dirty=${git.dirty.staged}/${git.dirty.unstaged}/${git.dirty.untracked}/${git.dirty.conflicted}` : void 0
23022
+ ].filter((part) => part !== void 0).join(" ") : void 0;
23023
+ const parts = [
23024
+ task.taskId,
23025
+ task.state,
23026
+ task.runtime ? `runtime=${task.runtime}` : void 0,
23027
+ gitStatus,
23028
+ task.claimedRuntime !== void 0 ? `claimedRuntime=${task.claimedRuntime}` : void 0,
23029
+ `updatedAt=${task.updatedAt}`,
23030
+ task.sessionRef ? `sessionRef=${task.sessionRef}` : void 0,
23031
+ task.declined ? "declined=true" : void 0,
23032
+ task.summary ? `summary=${quote(task.summary)}` : void 0
23033
+ ].filter((part) => Boolean(part));
23034
+ return parts.join(" ");
23035
+ }
23036
+ function formatTaskListLines(tasks) {
23037
+ if (tasks.length === 0) return ["(no tasks observed yet)"];
23038
+ return tasks.map(formatTaskLine);
23039
+ }
23040
+ function formatRuntimeLines(runtimes) {
23041
+ if (runtimes.length === 0) return ["(no runtimes configured \u2014 check runtimeAllowlist)"];
23042
+ return runtimes.map((r) => {
23043
+ if (!r.present) return `${r.id}: ${r.outcome}`;
23044
+ const caps = [];
23045
+ if (r.steer) caps.push("steer");
23046
+ if (r.resume) caps.push("resume");
23047
+ const parts = [
23048
+ "present",
23049
+ r.version ? `version=${r.version}` : void 0,
23050
+ r.authPresent !== void 0 ? `authPresent=${r.authPresent}` : void 0,
23051
+ `capabilities=${caps.length ? caps.join(",") : "(none)"}`,
23052
+ `modes=${r.permissionModes.length ? r.permissionModes.join(",") : "(none)"}`
23053
+ ].filter((part) => Boolean(part));
23054
+ return `${r.id}: ${parts.join(" ")}`;
23055
+ });
23056
+ }
23057
+ function formatStatusLines(view) {
23058
+ const lines = [];
23059
+ const label = view.branding?.displayName ?? view.productName;
23060
+ lines.push(`product: ${label} (${view.productId})`);
23061
+ lines.push(
23062
+ `local-agent-release: ${view.localAgentRelease.version}${view.localAgentRelease.buildId ? ` buildId=${view.localAgentRelease.buildId}` : ""}`
23063
+ );
23064
+ if (view.branding?.supportUrl) lines.push(`support: ${view.branding.supportUrl}`);
23065
+ lines.push(`paired: ${view.paired ? "yes" : "no"}${view.deviceId ? ` deviceId=${view.deviceId}` : ""}`);
23066
+ lines.push(
23067
+ view.connection ? `connection: last-known=${view.connection.state} at=${view.connection.ts}` : "connection: unknown (no audit log yet \u2014 run `byok-agent start` at least once to begin observing)"
23068
+ );
23069
+ const runtimeSummary = view.runtimes.map((r) => `${r.id}=${r.present ? "present" : r.outcome}`).join(" ");
23070
+ lines.push(`runtimes: ${runtimeSummary || "(none configured)"}`);
23071
+ const c = view.taskCounts;
23072
+ lines.push(
23073
+ `tasks: total=${c.total} offered=${c.Offered} claimed=${c.Claimed} running=${c.Running} awaitApproval=${c.AwaitApproval} complete=${c.Complete} failed=${c.Failed} cancelled=${c.Cancelled}`
23074
+ );
23075
+ lines.push(`audit-log: ${view.auditLogPath} (${view.auditLogLineCount} event${view.auditLogLineCount === 1 ? "" : "s"})`);
23076
+ return lines;
23077
+ }
23078
+ function formatLiveStatusLines(live) {
23079
+ const liveRelease = live.localAgentRelease;
23080
+ const lines = [
23081
+ `live: pid=${live.pid} uptimeMs=${live.uptimeMs} transport=${live.transport}`,
23082
+ liveRelease ? `live-local-agent-release: ${liveRelease.version}${liveRelease.buildId ? ` buildId=${liveRelease.buildId}` : ""}` : "live-local-agent-release: unknown",
23083
+ `live-paired: ${live.paired ? "yes" : "no"}${live.deviceId ? ` deviceId=${live.deviceId}` : ""}`,
23084
+ `live-runtimes: ${live.runtimeIds.length ? live.runtimeIds.join(",") : "(none)"}`,
23085
+ `live-toolsets: revision=${live.toolsets.revision} count=${live.toolsets.toolsets.length}`
23086
+ ];
23087
+ for (const toolset of live.toolsets.toolsets) lines.push(formatToolsetStatusLine("live-toolset", toolset));
23088
+ const health = live.operationalHealth;
23089
+ if (health.availability === "unavailable") {
23090
+ lines.push(`live-operational-health: unavailable reason=${quote(health.reason)}`);
23091
+ } else {
23092
+ lines.push(
23093
+ `live-operational-health: state=${health.state} failures=${health.failureCount}/${health.failureThreshold} windowMs=${health.windowMs} crashes=${health.crashCount}${health.lastCrashAt ? ` lastCrashAt=${health.lastCrashAt}` : ""}`
23094
+ );
23095
+ }
23096
+ if (live.activeTasks.length === 0) {
23097
+ lines.push("live-active-tasks: (none)");
23098
+ } else {
23099
+ for (const task of live.activeTasks) {
23100
+ lines.push(`live-active-task: ${task.taskId} ${task.state}`);
22287
23101
  }
22288
- unlinkSync(sourcePath);
22289
- sourceRemoved = true;
22290
- if (process.platform !== "win32") {
22291
- const directoryFd = openSync(path14__default.dirname(sourcePath), constants.O_RDONLY);
22292
- try {
22293
- fsyncSync(directoryFd);
22294
- } finally {
22295
- closeSync(directoryFd);
22296
- }
23102
+ }
23103
+ lines.push(`live-approvals-pending: ${live.approvalsPending}`);
23104
+ if (live.approvals.length === 0) {
23105
+ lines.push("live-approvals: (none)");
23106
+ } else {
23107
+ for (const approval of live.approvals) {
23108
+ lines.push(`live-approval: ${approval.approvalId} taskId=${approval.taskId} summary=${quote(summaryExcerpt(approval.summary))}`);
22297
23109
  }
22298
- return { sha256, sizeBytes };
22299
- } catch (err) {
22300
- if (evidenceTemp) {
22301
- try {
22302
- unlinkSync(evidenceTemp);
22303
- } catch {
22304
- }
23110
+ }
23111
+ if (live.queueWatermarks.length === 0) {
23112
+ lines.push("live-queue-watermarks: (none)");
23113
+ } else {
23114
+ for (const watermark of live.queueWatermarks) {
23115
+ lines.push(
23116
+ `live-queue-watermark: ${watermark.taskId} progressBatcherPending=${watermark.progressBatcherPending} pendingApprovals=${watermark.pendingApprovals}`
23117
+ );
22305
23118
  }
22306
- if (manifestTemp) {
22307
- try {
22308
- unlinkSync(manifestTemp);
22309
- } catch {
22310
- }
23119
+ }
23120
+ const storage = live.storage;
23121
+ if (storage !== void 0) {
23122
+ lines.push(
23123
+ `live-storage: state=${storage.pressureState} used=${storage.usedBytes} budget=${storage.budgetBytes} free=${storage.freeBytes} measuredAt=${storage.measuredAt}`
23124
+ );
23125
+ for (const category of storage.categories) {
23126
+ lines.push(`live-storage-category: ${category.category} bytes=${category.bytes}${category.approximate ? " (approximate)" : ""}`);
22311
23127
  }
22312
- if (manifestCreated && !sourceRemoved) {
22313
- try {
22314
- unlinkSync(manifestName);
22315
- } catch {
22316
- }
23128
+ if (storage.lastCompaction) {
23129
+ lines.push(
23130
+ `live-storage-compaction: checkpointed=${storage.lastCompaction.checkpointed} walFramesRemaining=${storage.lastCompaction.walFramesRemaining} pagesVacuumed=${storage.lastCompaction.pagesVacuumed} durationMs=${storage.lastCompaction.durationMs} at=${storage.lastCompaction.at}`
23131
+ );
22317
23132
  }
22318
- if (evidenceCreated && !sourceRemoved) {
22319
- try {
22320
- unlinkSync(evidenceName);
22321
- } catch {
22322
- }
23133
+ }
23134
+ return lines;
23135
+ }
23136
+ function formatToolsetStatusLine(prefix, toolset) {
23137
+ const observation = toolset.observation;
23138
+ return [
23139
+ `${prefix}: ${toolset.id}`,
23140
+ `servers=${toolset.serverCount}`,
23141
+ `definitionRevision=${toolset.definitionRevision}`,
23142
+ `state=${observation?.state ?? "unobserved"}`,
23143
+ observation?.version === void 0 ? void 0 : `version=${quote(observation.version)}`,
23144
+ observation?.observedAt === void 0 ? void 0 : `observedAt=${observation.observedAt}`,
23145
+ observation?.reasonCode === void 0 ? void 0 : `reasonCode=${observation.reasonCode}`
23146
+ ].filter((part) => part !== void 0).join(" ");
23147
+ }
23148
+ function formatToolsetsReloadReceiptLines(receipt) {
23149
+ return [
23150
+ `toolsets-reload: changed=${receipt.changed ? "yes" : "no"} previousRevision=${receipt.previousRevision} revision=${receipt.revision}`,
23151
+ ...receipt.toolsets.map((toolset) => formatToolsetStatusLine("toolset", toolset))
23152
+ ];
23153
+ }
23154
+ var SUMMARY_EXCERPT_MAX_LEN = 60;
23155
+ function summaryExcerpt(summary) {
23156
+ if (!summary) return "(no summary)";
23157
+ return summary.length > SUMMARY_EXCERPT_MAX_LEN ? `${summary.slice(0, SUMMARY_EXCERPT_MAX_LEN)}\u2026` : summary;
23158
+ }
23159
+ function formatAge(ms) {
23160
+ const totalSeconds = Math.floor(Math.max(0, ms) / 1e3);
23161
+ if (totalSeconds < 60) return `${totalSeconds}s`;
23162
+ const totalMinutes = Math.floor(totalSeconds / 60);
23163
+ if (totalMinutes < 60) return `${totalMinutes}m`;
23164
+ const totalHours = Math.floor(totalMinutes / 60);
23165
+ if (totalHours < 24) return `${totalHours}h`;
23166
+ const totalDays = Math.floor(totalHours / 24);
23167
+ return `${totalDays}d`;
23168
+ }
23169
+ function formatApprovalsListLines(approvals, nowMs) {
23170
+ if (approvals.length === 0) return ["(no pending approvals)"];
23171
+ return approvals.map((approval) => {
23172
+ const ageMs = nowMs - Date.parse(approval.createdAt);
23173
+ return `${approval.approvalId} taskId=${approval.taskId} age=${formatAge(ageMs)} summary=${quote(summaryExcerpt(approval.summary))}`;
23174
+ });
23175
+ }
23176
+
23177
+ // src/bin/commands/approvals.ts
23178
+ async function runApprovalsCommand(storeDir, productId, deps = {}) {
23179
+ const log = deps.log ?? ((line) => console.log(line));
23180
+ const error = deps.error ?? ((line) => console.error(line));
23181
+ const connectControl = deps.connectControl ?? connectControlClient;
23182
+ const now = deps.now ?? (() => Date.now());
23183
+ const conn = await connectControl({ storeDir, productId });
23184
+ if (!conn.ok) {
23185
+ const message = `cannot list approvals: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23186
+ error(message);
23187
+ throw new Error(message);
23188
+ }
23189
+ try {
23190
+ const result = await conn.client.request("approvals.list");
23191
+ for (const line of formatApprovalsListLines(result.approvals, now())) log(line);
23192
+ } catch (err) {
23193
+ if (err instanceof ControlError) {
23194
+ error(`approvals list failed: ${err.message}`);
23195
+ } else {
23196
+ error(`approvals list failed: ${err instanceof Error ? err.message : String(err)}`);
22323
23197
  }
22324
23198
  throw err;
22325
23199
  } finally {
22326
- if (entered) process.chdir(previousCwd);
23200
+ conn.client.close();
22327
23201
  }
22328
23202
  }
22329
- async function quarantineCorruptOperationalHealth(storeDir, options = {}) {
22330
- const resolvedStoreDir = path14__default.resolve(storeDir);
22331
- const owner = await acquireDaemonOwner(resolvedStoreDir, "doctor", options.clock);
23203
+
23204
+ // src/bin/commands/approve-reject.ts
23205
+ async function runApproveCommand(storeDir, productId, approvalId, deps = {}) {
23206
+ return resolveApproval(storeDir, productId, approvalId, "approve", void 0, deps);
23207
+ }
23208
+ async function runRejectCommand(storeDir, productId, approvalId, reason, deps = {}) {
23209
+ return resolveApproval(storeDir, productId, approvalId, "reject", reason, deps);
23210
+ }
23211
+ async function resolveApproval(storeDir, productId, approvalId, decision, reason, deps) {
23212
+ const log = deps.log ?? ((line) => console.log(line));
23213
+ const error = deps.error ?? ((line) => console.error(line));
23214
+ const connectControl = deps.connectControl ?? connectControlClient;
23215
+ const verb = decision === "approve" ? "approved" : "rejected";
23216
+ const conn = await connectControl({ storeDir, productId });
23217
+ if (!conn.ok) {
23218
+ const message = `cannot ${decision} approvalId=${approvalId}: daemon not reachable (${conn.reason}) \u2014 is \`byok-agent start\` (or the installed service) running?`;
23219
+ error(message);
23220
+ throw new Error(message);
23221
+ }
22332
23222
  try {
22333
- const sourcePath = path14__default.join(resolvedStoreDir, OPERATIONAL_HEALTH_FILENAME);
22334
- let opened;
22335
- try {
22336
- opened = await openOperationalHealthFile(resolvedStoreDir);
22337
- } catch (err) {
22338
- throw new Error("operational health state could not be opened safely; refusing quarantine", { cause: err });
23223
+ await conn.client.request("approvals.resolve", { approvalId, decision, reason });
23224
+ log(`${verb}: approvalId=${approvalId}${reason ? ` reason=${JSON.stringify(reason)}` : ""}`);
23225
+ } catch (err) {
23226
+ if (err instanceof ControlError && err.code === "not_found") {
23227
+ error(`${err.message} \u2014 run \`byok-agent approvals\` to see pending approvalIds`);
23228
+ } else {
23229
+ error(`${decision} failed: ${err instanceof Error ? err.message : String(err)}`);
22339
23230
  }
22340
- if (!opened) return { status: "not-needed", reason: "missing" };
22341
- const source = opened.handle;
23231
+ throw err;
23232
+ } finally {
23233
+ conn.client.close();
23234
+ }
23235
+ }
23236
+
23237
+ // src/bin/commands/pair.ts
23238
+ async function runPairCommand(config, code, deps = {}) {
23239
+ const log = deps.log ?? ((line) => console.log(line));
23240
+ if (deps.daemon) {
23241
+ const result2 = await deps.daemon.pair(code);
23242
+ log(`paired: deviceId=${result2.deviceId}`);
23243
+ return;
23244
+ }
23245
+ const connectControl = deps.connectControl ?? connectControlClient;
23246
+ const conn = await connectControl({ storeDir: resolveStoreDir(config), productId: config.productId });
23247
+ if (conn.ok) {
22342
23248
  try {
22343
- const inspection = await inspectOperationalHealthHandle(source, opened.stat);
22344
- if (inspection.status === "valid") return { status: "not-needed", reason: "valid" };
22345
- if (inspection.status === "unavailable") {
22346
- throw new Error(`${inspection.reason}; refusing to quarantine unconfirmed corruption`);
22347
- }
22348
- const sourceStat = await source.stat({ bigint: true });
22349
- if (!sourceStat.isFile()) throw new Error("operational health state is not a regular file; refusing quarantine");
22350
- const quarantineDir = path14__default.join(resolvedStoreDir, JOURNAL_QUARANTINE_DIRNAME);
22351
- try {
22352
- const existing = await promises.lstat(quarantineDir);
22353
- if (!existing.isDirectory() || existing.isSymbolicLink()) {
22354
- throw new Error("quarantine path is not a real directory; refusing quarantine");
22355
- }
22356
- } catch (err) {
22357
- if (err.code !== "ENOENT") throw err;
22358
- await promises.mkdir(quarantineDir, { mode: 448 });
22359
- }
22360
- await ensureSecureDir(quarantineDir);
22361
- const securedQuarantine = await promises.lstat(quarantineDir, { bigint: true });
22362
- if (!securedQuarantine.isDirectory() || securedQuarantine.isSymbolicLink()) {
22363
- throw new Error("quarantine path changed during validation; refusing quarantine");
22364
- }
22365
- const stamp = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString().replace(/[:.]/g, "-");
22366
- const evidenceName = `${stamp}-${randomUUID()}-${OPERATIONAL_HEALTH_FILENAME}`;
22367
- const manifestName = `${evidenceName}.manifest.json`;
22368
- const hashed = publishQuarantineEvidencePinned(
22369
- quarantineDir,
22370
- securedQuarantine,
22371
- sourcePath,
22372
- source.fd,
22373
- sourceStat,
22374
- evidenceName,
22375
- manifestName,
22376
- (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
22377
- inspection.reason
22378
- );
22379
- return { status: "quarantined", evidenceName, manifestName, sha256: hashed.sha256, sizeBytes: hashed.sizeBytes };
23249
+ const result2 = await conn.client.request("enrollment.pair", { pairingCode: code });
23250
+ log(`paired: deviceId=${result2.deviceId}`);
23251
+ return;
22380
23252
  } finally {
22381
- await source.close();
23253
+ conn.client.close();
22382
23254
  }
22383
- } finally {
22384
- await owner.release();
22385
23255
  }
23256
+ const daemon = createDaemon(config);
23257
+ const result = await daemon.pair(code);
23258
+ log(`paired: deviceId=${result.deviceId}`);
22386
23259
  }
22387
23260
 
22388
23261
  // src/bin/commands/doctor.ts
@@ -22420,6 +23293,29 @@ function formatDoctorLines(snapshot, fixResult) {
22420
23293
  return lines;
22421
23294
  }
22422
23295
  async function runDoctorCommand(config, options = {}) {
23296
+ if (options.repair !== void 0) {
23297
+ if (options.fix || options.repair !== "restore-enrollment-metadata") {
23298
+ throw new Error("doctor requires one supported repair action; --fix cannot be combined with --repair");
23299
+ }
23300
+ const log2 = options.log ?? ((line) => console.log(line));
23301
+ try {
23302
+ const repair = await repairDeviceEnrollmentMetadata(config, {
23303
+ confirmed: options.confirmed,
23304
+ expectedDeviceId: options.expectedDeviceId,
23305
+ expectedTenantId: options.expectedTenantId
23306
+ });
23307
+ log2(options.json ? JSON.stringify({ repair }, null, 2) : `repair ${repair.action}: ${repair.status} (${repair.scope})`);
23308
+ return;
23309
+ } catch (error) {
23310
+ if (options.json && error instanceof DeviceMetadataRepairError) {
23311
+ log2(JSON.stringify({ repair: { action: "restore-enrollment-metadata", scope: "device", status: "failed", code: error.code } }, null, 2));
23312
+ }
23313
+ throw error;
23314
+ }
23315
+ }
23316
+ if (options.expectedDeviceId !== void 0 || options.expectedTenantId !== void 0) {
23317
+ throw new Error("expected identity flags require --repair restore-enrollment-metadata");
23318
+ }
22423
23319
  if (options.fix && !options.confirmed) throw new DoctorConfirmationRequiredError();
22424
23320
  const storeDir = resolveStoreDir(config);
22425
23321
  let snapshot = await collectDiagnostics(config, storeDir, options);
@@ -23412,8 +24308,8 @@ async function runTasksFollowCommand(config, deps) {
23412
24308
  });
23413
24309
  return;
23414
24310
  }
23415
- const path46 = auditLogPath(storeDir);
23416
- await followAuditLog(path46, (event) => log(formatDaemonEventLine(event)), {
24311
+ const path47 = auditLogPath(storeDir);
24312
+ await followAuditLog(path47, (event) => log(formatDaemonEventLine(event)), {
23417
24313
  signal: deps.signal,
23418
24314
  pollIntervalMs: deps.pollIntervalMs,
23419
24315
  fromEnd: true
@@ -23696,6 +24592,7 @@ function usage() {
23696
24592
  " byok-agent start [--config <path>] (or BYOK_CONFIG env var)",
23697
24593
  " byok-agent status [--config <path>]",
23698
24594
  " byok-agent doctor [--json] [--fix --yes] [--config <path>]",
24595
+ " byok-agent doctor --repair restore-enrollment-metadata --expected-device-id <id> --expected-tenant-id <id> --yes [--json] [--config <path>]",
23699
24596
  " byok-agent support-bundle --output <path> [--config <path>]",
23700
24597
  " byok-agent runtimes [--config <path>]",
23701
24598
  " byok-agent tasks [--follow] [--config <path>]",
@@ -23765,6 +24662,9 @@ async function main() {
23765
24662
  return runDoctorCommand(config, {
23766
24663
  json: hasFlag(rest, "--json"),
23767
24664
  fix: hasFlag(rest, "--fix"),
24665
+ repair: hasFlag(rest, "--repair") ? argValue(rest, "--repair") ?? "" : void 0,
24666
+ expectedDeviceId: hasFlag(rest, "--expected-device-id") ? argValue(rest, "--expected-device-id") ?? "" : void 0,
24667
+ expectedTenantId: hasFlag(rest, "--expected-tenant-id") ? argValue(rest, "--expected-tenant-id") ?? "" : void 0,
23768
24668
  confirmed: hasFlag(rest, "--yes")
23769
24669
  });
23770
24670
  }