@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
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import { randomUUID, createHash, randomBytes, createPrivateKey, generateKeyPairSync, sign, timingSafeEqual, createHmac } from 'crypto';
2
- import { promises, mkdirSync, constants, existsSync, linkSync, fstatSync, lstatSync, unlinkSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, readFileSync, realpathSync } from 'fs';
2
+ import { promises, mkdirSync, constants, existsSync, linkSync, fstatSync, lstatSync, unlinkSync, renameSync, writeFileSync, chmodSync, statSync, readdirSync, readFileSync, opendirSync, realpathSync, openSync, readSync, closeSync } from 'fs';
3
3
  import * as path3 from 'path';
4
4
  import path3__default, { join, isAbsolute } from 'path';
5
- import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, AgentHomeProjectionPayloadSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, isTaskOfferType, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, 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';
5
+ import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AgentRefSchema, AgentHomeProjectionPayloadSchema, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, isTaskOfferType, partitionAgentEvents, BYOK_SKILL_PACKS_PATH, byokSkillPackFilePath, BYOK_RECORDS_PATH, byokRecordPath, TASK_TRANSITIONS, AgentEgressPolicySchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentContentReceiptPayloadSchema, decodeEnvelope, createEnvelope, encodeEnvelope, PROTOCOL_VERSION, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, RuntimeIdSchema, HarnessIdSchema, 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';
6
6
  import net2, { createServer, createConnection } from 'net';
7
7
  import * as os4 from 'os';
8
8
  import os4__default from 'os';
9
9
  import { execFile, spawn, spawnSync } from 'child_process';
10
10
  import { fileURLToPath } from 'url';
11
11
  import { createInterface } from 'readline';
12
- import { parseDeviceAssertionEnvelope, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN, isTenantId, nonceSigningBytes, CapabilityDeclarationSchema, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
12
+ import { parseDeviceAssertionEnvelope, SKILL_PACK_MAX_BYTES, hasCapability, parseSkillPackManifest, checkSkillPackManifest, skillPackContentHashInput, checkSkillPackFileContent, SKILL_PACK_ENTRY_PATH, checkSkillPackEntry, isSkillPackPathSafe, DEVICE_PROOF_HEADER, contentHash as contentHash$1, TRUTH_RECORD_KINDS, isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, CONTENT_HASH_PATTERN, nonceSigningBytes, CapabilityDeclarationSchema, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
13
13
  import { promisify } from 'util';
14
14
  import * as fs14 from 'fs/promises';
15
15
  import { createRequire } from 'module';
@@ -2211,6 +2211,7 @@ async function acquire(input) {
2211
2211
  var localStateRelocation = Object.freeze({ acquire });
2212
2212
 
2213
2213
  // src/types.ts
2214
+ var RUNTIME_DETECTION_FAILURE_KINDS = ["not-found", "not-executable", "timeout", "probe-failed"];
2214
2215
  function frozenStrings(values) {
2215
2216
  return values === void 0 ? void 0 : Object.freeze([...values]);
2216
2217
  }
@@ -2307,6 +2308,31 @@ function resolveLocalAgentReleaseIdentity(input) {
2307
2308
  ...input.buildId === void 0 ? {} : { buildId: input.buildId }
2308
2309
  });
2309
2310
  }
2311
+ async function runMcpEnvLauncher() {
2312
+ const key = process.env.BYOK_MCP_ENV_KEY;
2313
+ if (!key || !/^BYOK_MCP_PAYLOAD_[A-F0-9]{32}$/.test(key)) throw new Error("invalid MCP environment binding");
2314
+ const encoded = process.env[key];
2315
+ if (!encoded) throw new Error("missing sealed MCP environment");
2316
+ let config;
2317
+ try {
2318
+ config = JSON.parse(encoded);
2319
+ if (!config || typeof config.command !== "string" || !config.command || config.args !== void 0 && (!Array.isArray(config.args) || config.args.some((value) => typeof value !== "string")) || config.env !== void 0 && (typeof config.env !== "object" || config.env === null || Array.isArray(config.env) || Object.values(config.env).some((value) => typeof value !== "string"))) throw new Error();
2320
+ } catch {
2321
+ throw new Error("invalid sealed MCP environment");
2322
+ }
2323
+ const env = { ...process.env };
2324
+ delete env.BYOK_MCP_ENV_KEY;
2325
+ for (const name of Object.keys(env)) if (name.startsWith("BYOK_MCP_PAYLOAD_")) delete env[name];
2326
+ Object.assign(env, config.env);
2327
+ await new Promise((resolve, reject) => {
2328
+ const child = spawn(config.command, config.args ?? [], { env, stdio: "inherit", windowsHide: true });
2329
+ child.once("error", () => reject(new Error("MCP child could not be spawned")));
2330
+ child.once("close", (code) => {
2331
+ if (code === 0) resolve();
2332
+ else reject(new Error("MCP child exited unsuccessfully"));
2333
+ });
2334
+ });
2335
+ }
2310
2336
  var CONTROL_PROTOCOL_VERSION = 1;
2311
2337
  var HANDSHAKE_TIMEOUT_MS = 3e3;
2312
2338
  var UNIX_SOCKET_PATH_SOFT_LIMIT2 = 100;
@@ -3174,6 +3200,9 @@ async function runAgentTeamMcp() {
3174
3200
  }
3175
3201
  async function runSdkReservedHelper(kind) {
3176
3202
  switch (kind) {
3203
+ case "mcp-env":
3204
+ await runMcpEnvLauncher();
3205
+ return;
3177
3206
  case "agent-message-mcp":
3178
3207
  await runAgentMessageMcp();
3179
3208
  return;
@@ -3195,7 +3224,8 @@ var DIST_SCRIPT_BY_KIND = Object.freeze({
3195
3224
  "agent-message-mcp": "byok-agent-message-mcp.js",
3196
3225
  "agent-memory-mcp": "byok-agent-memory-mcp.js",
3197
3226
  "approval-mcp": "byok-approval-mcp.js",
3198
- "agent-team-mcp": "byok-agent-team-mcp.js"
3227
+ "agent-team-mcp": "byok-agent-team-mcp.js",
3228
+ "mcp-env": "byok-mcp-env.js"
3199
3229
  });
3200
3230
  function assertExecutable(executable) {
3201
3231
  if (!path3__default.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
@@ -3213,8 +3243,11 @@ function resolveSdkReservedHelperBin(kind, host) {
3213
3243
  source: "self-executable"
3214
3244
  });
3215
3245
  }
3246
+ const entryDir = path3__default.dirname(fileURLToPath(import.meta.url));
3247
+ const entryKind = path3__default.basename(entryDir);
3248
+ const distDir = entryKind === "adapters" || entryKind === "bin" ? path3__default.dirname(entryDir) : entryDir;
3216
3249
  const script = path3__default.join(
3217
- path3__default.dirname(fileURLToPath(import.meta.url)),
3250
+ distDir,
3218
3251
  "bin",
3219
3252
  DIST_SCRIPT_BY_KIND[kind]
3220
3253
  );
@@ -3225,7 +3258,7 @@ function resolveSdkReservedHelperBin(kind, host) {
3225
3258
  });
3226
3259
  }
3227
3260
  function isHelperKind(value) {
3228
- return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp";
3261
+ return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp" || value === "mcp-env";
3229
3262
  }
3230
3263
  async function runSdkReservedHelperCommand(argv = process.argv.slice(2)) {
3231
3264
  if (argv[0] !== BYOK_SDK_HELPER_SUBCOMMAND) return false;
@@ -3319,6 +3352,19 @@ function projectRuntimeBoundaryFailure(value, expectedPhase) {
3319
3352
  contractViolation: true
3320
3353
  };
3321
3354
  }
3355
+ var RuntimeStartupDisposalFailure = class extends Error {
3356
+ retryDisposal;
3357
+ constructor(retryDisposal, options) {
3358
+ super("runtime startup failed and process ownership remains quarantined", options);
3359
+ this.name = "RuntimeStartupDisposalFailure";
3360
+ this.retryDisposal = retryDisposal;
3361
+ Object.defineProperty(this, /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1"), { value: true });
3362
+ Object.freeze(this);
3363
+ }
3364
+ };
3365
+ function isRuntimeStartupDisposalFailure(value) {
3366
+ return typeof value === "object" && value !== null && value[/* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1")] === true && typeof value.retryDisposal === "function";
3367
+ }
3322
3368
  var GitWorkspaceError = class extends Error {
3323
3369
  constructor(category, message = category) {
3324
3370
  super(message);
@@ -3824,6 +3870,132 @@ var GitWorkspaceStore = class {
3824
3870
  await atomicWriteFile(this.filePath, JSON.stringify(ledger, null, 2), { mode: 384 });
3825
3871
  }
3826
3872
  };
3873
+ function terminalIdentity(runtimeId, agentRef) {
3874
+ return {
3875
+ ...runtimeId === void 0 || RuntimeIdSchema.safeParse(runtimeId).success ? {} : { harnessId: HarnessIdSchema.parse(runtimeId) },
3876
+ ...agentRef === void 0 ? {} : { agentRef }
3877
+ };
3878
+ }
3879
+
3880
+ // src/daemon/terminal-commit-queue.ts
3881
+ var TerminalCommitQueue = class {
3882
+ constructor(committed) {
3883
+ this.committed = committed;
3884
+ }
3885
+ committed;
3886
+ pending = /* @__PURE__ */ new Map();
3887
+ tail = Promise.resolve();
3888
+ timer;
3889
+ stopped = false;
3890
+ get pendingCount() {
3891
+ return this.pending.size;
3892
+ }
3893
+ hasPending(taskId) {
3894
+ return this.pending.has(taskId);
3895
+ }
3896
+ enqueue(taskId, commit) {
3897
+ if (!this.pending.has(taskId)) this.pending.set(taskId, { commit, receipt: Promise.resolve(), running: false });
3898
+ void this.attempt(taskId).catch(() => void 0);
3899
+ }
3900
+ receipt(taskId) {
3901
+ return this.pending.get(taskId)?.receipt ?? Promise.resolve();
3902
+ }
3903
+ retry(taskId) {
3904
+ return this.attempt(taskId);
3905
+ }
3906
+ resume() {
3907
+ this.stopped = false;
3908
+ if (this.pending.size > 0) this.armRetry();
3909
+ }
3910
+ async stop() {
3911
+ this.stopped = true;
3912
+ if (this.timer) clearTimeout(this.timer);
3913
+ this.timer = void 0;
3914
+ await Promise.all([...this.pending.keys()].map((taskId) => this.attempt(taskId)));
3915
+ }
3916
+ attempt(taskId) {
3917
+ const entry = this.pending.get(taskId);
3918
+ if (!entry) return Promise.resolve();
3919
+ if (entry.running) return entry.receipt;
3920
+ entry.running = true;
3921
+ const receipt = this.tail.catch(() => void 0).then(entry.commit);
3922
+ entry.receipt = receipt.then(() => {
3923
+ if (this.pending.get(taskId) === entry) this.pending.delete(taskId);
3924
+ this.committed(taskId);
3925
+ }, (error) => {
3926
+ entry.running = false;
3927
+ this.armRetry();
3928
+ throw error;
3929
+ });
3930
+ this.tail = entry.receipt;
3931
+ void entry.receipt.catch(() => void 0);
3932
+ return entry.receipt;
3933
+ }
3934
+ armRetry() {
3935
+ if (this.timer || this.stopped) return;
3936
+ this.timer = setTimeout(() => {
3937
+ this.timer = void 0;
3938
+ for (const taskId of this.pending.keys()) void this.attempt(taskId).catch(() => void 0);
3939
+ }, 1e3);
3940
+ this.timer.unref?.();
3941
+ }
3942
+ };
3943
+
3944
+ // src/runtime-detection.ts
3945
+ function validateRuntimeDetectResult(value) {
3946
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3947
+ throw new TypeError("invalid runtime detection result");
3948
+ }
3949
+ const result = value;
3950
+ if (!Object.hasOwn(result, "kind")) throw new TypeError("invalid runtime detection result");
3951
+ const { kind, version, authPresent } = result;
3952
+ if (kind === "available") {
3953
+ if (Object.keys(result).some((key) => !["kind", "version", "authPresent"].includes(key)) || version !== void 0 && typeof version !== "string" || authPresent !== void 0 && typeof authPresent !== "boolean") {
3954
+ throw new TypeError("invalid runtime detection result");
3955
+ }
3956
+ return Object.freeze({
3957
+ kind,
3958
+ ...version === void 0 ? {} : { version },
3959
+ ...authPresent === void 0 ? {} : { authPresent }
3960
+ });
3961
+ }
3962
+ if (RUNTIME_DETECTION_FAILURE_KINDS.some((candidate) => candidate === kind) && Object.keys(result).every((key) => key === "kind")) {
3963
+ return Object.freeze({ kind });
3964
+ }
3965
+ throw new TypeError("invalid runtime detection result");
3966
+ }
3967
+ function classifyDetectError(error) {
3968
+ const code = error !== null && typeof error === "object" ? error.code : void 0;
3969
+ if (code === "ENOENT") return { kind: "not-found" };
3970
+ if (code === "EACCES" || code === "EPERM" || code === "ENOEXEC") return { kind: "not-executable" };
3971
+ return { kind: "probe-failed" };
3972
+ }
3973
+ async function probeRuntimeVersion(command, timeoutMs) {
3974
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("invalid runtime probe timeout");
3975
+ return new Promise((resolve) => {
3976
+ let timer;
3977
+ let timedOut = false;
3978
+ try {
3979
+ const child = execFile(command, ["--version"], { encoding: "utf8" }, (error, stdout, stderr) => {
3980
+ if (timer) clearTimeout(timer);
3981
+ if (error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") resolve({ kind: "probe-failed" });
3982
+ else if (timedOut) resolve({ kind: "timeout" });
3983
+ else if (error) resolve(classifyDetectError(error));
3984
+ else resolve({ kind: "available", stdout, stderr });
3985
+ });
3986
+ timer = setTimeout(() => {
3987
+ timedOut = true;
3988
+ child.stdout?.destroy();
3989
+ child.stderr?.destroy();
3990
+ child.kill("SIGKILL");
3991
+ }, timeoutMs);
3992
+ timer.unref?.();
3993
+ } catch (error) {
3994
+ if (timer) clearTimeout(timer);
3995
+ resolve(classifyDetectError(error));
3996
+ }
3997
+ });
3998
+ }
3827
3999
 
3828
4000
  // src/adapters/pi/mcp-config.ts
3829
4001
  var BYOK_PI_MCP_CONFIG_PATH = "BYOK_PI_MCP_CONFIG_PATH";
@@ -4834,12 +5006,13 @@ var PiAdapter = class {
4834
5006
  async detect() {
4835
5007
  try {
4836
5008
  const bin = this.resolveBin();
4837
- const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
4838
- const version = stdout.trim() || stderr.trim();
5009
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS);
5010
+ if (probe.kind !== "available") return probe;
5011
+ const version = probe.stdout.trim() || probe.stderr.trim();
4839
5012
  const authPresent = PROVIDER_CREDENTIAL_ENV_NAMES.some((name) => process.env[name] !== void 0);
4840
- return { present: true, version, authPresent };
4841
- } catch {
4842
- return { present: false };
5013
+ return { kind: "available", version, authPresent };
5014
+ } catch (error) {
5015
+ return classifyDetectError(error);
4843
5016
  }
4844
5017
  }
4845
5018
  async prepare(input) {
@@ -5969,14 +6142,15 @@ var ClaudeAdapter = class {
5969
6142
  environmentRequirements: { credentialNames: [] }
5970
6143
  });
5971
6144
  async detect() {
5972
- const bin = this.resolveBin();
5973
6145
  try {
5974
- const { stdout } = await execFileAsync2(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS2 });
5975
- const version = stdout.trim();
6146
+ const bin = this.resolveBin();
6147
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS2);
6148
+ if (probe.kind !== "available") return probe;
6149
+ const version = probe.stdout.trim();
5976
6150
  const authPresent = await this.probeAuthPresent(bin.command);
5977
- return { present: true, version, authPresent };
5978
- } catch {
5979
- return { present: false };
6151
+ return { kind: "available", version, authPresent };
6152
+ } catch (error) {
6153
+ return classifyDetectError(error);
5980
6154
  }
5981
6155
  }
5982
6156
  async prepare(input) {
@@ -6595,10 +6769,18 @@ function unmappedFrameKey(evt) {
6595
6769
  return evt.type;
6596
6770
  }
6597
6771
  var STDERR_RING_CAPACITY3 = 20;
6772
+ var CODEX_MAX_FRAME_BYTES = 1024 * 1024;
6773
+ var CODEX_MAX_DEFERRED_BYTES = 4 * 1024 * 1024;
6774
+ var CODEX_MAX_STDERR_BYTES = 64 * 1024;
6598
6775
  var CodexProcessRunner = class {
6599
6776
  child;
6600
6777
  onEvent;
6601
- buffer = "";
6778
+ frameChunks = [];
6779
+ frameBytes = 0;
6780
+ deferredBytes = 0;
6781
+ stderrBytes = 0;
6782
+ transportFailure;
6783
+ onFailure;
6602
6784
  stderrRing = [];
6603
6785
  closed = false;
6604
6786
  exitCode = null;
@@ -6621,11 +6803,12 @@ var CodexProcessRunner = class {
6621
6803
  deferredEvents = [];
6622
6804
  constructor(options) {
6623
6805
  this.onEvent = options.onEvent;
6806
+ this.onFailure = options.onFailure;
6624
6807
  const spawnFn = options.spawnFn ?? spawn;
6625
6808
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
6626
6809
  cwd: options.cwd,
6627
6810
  env: options.env,
6628
- stdio: ["ignore", "pipe", "pipe"]
6811
+ stdio: [options.instruction === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
6629
6812
  }));
6630
6813
  this.closedPromise = new Promise((resolve) => {
6631
6814
  this.resolveClosed = resolve;
@@ -6633,7 +6816,6 @@ var CodexProcessRunner = class {
6633
6816
  this.adopted = this.adoptOwnedTree(options);
6634
6817
  this.adopted.catch(() => {
6635
6818
  });
6636
- this.child.stdout.setEncoding("utf8");
6637
6819
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
6638
6820
  this.child.stderr.setEncoding("utf8");
6639
6821
  this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
@@ -6642,6 +6824,12 @@ var CodexProcessRunner = class {
6642
6824
  this.exitSignal = signal;
6643
6825
  this.finishClosing();
6644
6826
  });
6827
+ if (options.instruction !== void 0) {
6828
+ this.child.stdin.on("error", () => {
6829
+ this.failTransport("codex prompt stdin failed before delivery");
6830
+ });
6831
+ this.child.stdin.end(options.instruction, "utf8");
6832
+ }
6645
6833
  this.child.on("error", () => {
6646
6834
  this.finishClosing();
6647
6835
  });
@@ -6727,12 +6915,19 @@ var CodexProcessRunner = class {
6727
6915
  throw failure;
6728
6916
  }
6729
6917
  this.adoption = "adopted";
6918
+ this.deferredBytes = 0;
6730
6919
  for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
6731
6920
  }
6732
6921
  /** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
6733
- deliver(evt) {
6922
+ deliver(evt, bytes) {
6923
+ if (this.transportFailure) return;
6734
6924
  if (this.adoption === "failed") return;
6735
6925
  if (this.adoption === "pending") {
6926
+ if (this.deferredBytes + bytes > CODEX_MAX_DEFERRED_BYTES) {
6927
+ this.failTransport("codex pre-adoption event buffer exceeded its byte budget");
6928
+ return;
6929
+ }
6930
+ this.deferredBytes += bytes;
6736
6931
  this.deferredEvents.push(evt);
6737
6932
  return;
6738
6933
  }
@@ -6749,6 +6944,7 @@ var CodexProcessRunner = class {
6749
6944
  * plus an empty stderr tail would bury the only reason anyone can act on.
6750
6945
  */
6751
6946
  buildExitError(context) {
6947
+ if (this.transportFailure) return this.transportFailure;
6752
6948
  if (this.adoptionFailure !== void 0) return this.adoptionFailure;
6753
6949
  const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
6754
6950
  if (this.stderrRing.length > 0) {
@@ -6756,18 +6952,45 @@ var CodexProcessRunner = class {
6756
6952
  }
6757
6953
  return new Error(parts.join("; "));
6758
6954
  }
6955
+ failTransport(reason) {
6956
+ if (this.transportFailure) return;
6957
+ this.transportFailure = new RuntimeExecutionFailure({
6958
+ phase: "run",
6959
+ category: "infrastructure",
6960
+ retry: "non-retryable",
6961
+ reason
6962
+ });
6963
+ this.frameChunks.length = 0;
6964
+ this.frameBytes = 0;
6965
+ this.deferredEvents.length = 0;
6966
+ this.deferredBytes = 0;
6967
+ this.onFailure?.(this.transportFailure);
6968
+ this.kill();
6969
+ }
6759
6970
  onData(chunk) {
6760
- this.buffer += chunk;
6761
- let newlineIndex = this.buffer.indexOf("\n");
6762
- while (newlineIndex !== -1) {
6763
- let line = this.buffer.slice(0, newlineIndex);
6764
- this.buffer = this.buffer.slice(newlineIndex + 1);
6765
- if (line.endsWith("\r")) line = line.slice(0, -1);
6766
- if (line.length > 0) this.parseLine(line);
6767
- newlineIndex = this.buffer.indexOf("\n");
6971
+ if (this.transportFailure) return;
6972
+ let offset = 0;
6973
+ while (offset < chunk.length) {
6974
+ const newline = chunk.indexOf(10, offset);
6975
+ const end = newline === -1 ? chunk.length : newline;
6976
+ const bytes = end - offset;
6977
+ if (this.frameBytes + bytes > CODEX_MAX_FRAME_BYTES) {
6978
+ this.failTransport("codex raw JSONL frame exceeded its byte budget");
6979
+ return;
6980
+ }
6981
+ if (bytes > 0) this.frameChunks.push(Buffer.from(chunk.subarray(offset, end)));
6982
+ this.frameBytes += bytes;
6983
+ if (newline === -1) return;
6984
+ const frame = Buffer.concat(this.frameChunks, this.frameBytes);
6985
+ this.frameChunks.length = 0;
6986
+ this.frameBytes = 0;
6987
+ const line = frame.toString("utf8").replace(/\r$/, "");
6988
+ if (line.length > 0) this.parseLine(line, frame.length);
6989
+ if (this.transportFailure) return;
6990
+ offset = newline + 1;
6768
6991
  }
6769
6992
  }
6770
- parseLine(line) {
6993
+ parseLine(line, bytes) {
6771
6994
  let parsed;
6772
6995
  try {
6773
6996
  parsed = JSON.parse(line);
@@ -6777,14 +7000,18 @@ var CodexProcessRunner = class {
6777
7000
  if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
6778
7001
  return;
6779
7002
  }
6780
- this.deliver(parsed);
7003
+ this.deliver(parsed, bytes);
6781
7004
  }
6782
7005
  onStderr(chunk) {
6783
- for (const rawLine of chunk.split("\n")) {
7006
+ const bounded2 = Buffer.from(chunk).subarray(-CODEX_MAX_STDERR_BYTES).toString("utf8");
7007
+ for (const rawLine of bounded2.split("\n")) {
6784
7008
  const line = rawLine.trim();
6785
7009
  if (line.length === 0) continue;
6786
7010
  this.stderrRing.push(line);
6787
- if (this.stderrRing.length > STDERR_RING_CAPACITY3) this.stderrRing.shift();
7011
+ this.stderrBytes += Buffer.byteLength(line);
7012
+ while (this.stderrRing.length > STDERR_RING_CAPACITY3 || this.stderrBytes > CODEX_MAX_STDERR_BYTES) {
7013
+ this.stderrBytes -= Buffer.byteLength(this.stderrRing.shift());
7014
+ }
6788
7015
  }
6789
7016
  }
6790
7017
  };
@@ -6817,14 +7044,15 @@ var CodexAdapter = class {
6817
7044
  environmentRequirements: { credentialNames: [] }
6818
7045
  });
6819
7046
  async detect() {
6820
- const bin = this.resolveBin();
6821
7047
  try {
6822
- const versionResult = await execFileAsync3(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS3 });
6823
- const version = versionResult.stdout.trim() || versionResult.stderr.trim();
7048
+ const bin = this.resolveBin();
7049
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS3);
7050
+ if (probe.kind !== "available") return probe;
7051
+ const version = probe.stdout.trim() || probe.stderr.trim();
6824
7052
  const authPresent = await this.probeAuthPresent(bin);
6825
- return { present: true, version, authPresent };
6826
- } catch {
6827
- return { present: false };
7053
+ return { kind: "available", version, authPresent };
7054
+ } catch (error) {
7055
+ return classifyDetectError(error);
6828
7056
  }
6829
7057
  }
6830
7058
  /**
@@ -6967,7 +7195,7 @@ ${withStreams.stderr ?? ""}`);
6967
7195
  reason: "prepared codex operation received a manifest with different runtime selection"
6968
7196
  });
6969
7197
  }
6970
- const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
7198
+ const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, runtimeEnv, this.options.sdkHelperHost, preparedMcpGrants);
6971
7199
  const { sessionRef, runner } = await runCodexTurn({
6972
7200
  command,
6973
7201
  resumeRef: startInput.manifest.sessionRef,
@@ -6983,6 +7211,7 @@ ${withStreams.stderr ?? ""}`);
6983
7211
  expectedSessionRef: startInput.manifest.sessionRef,
6984
7212
  preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
6985
7213
  failurePhase: "start",
7214
+ signal: startInput.signal,
6986
7215
  terminal
6987
7216
  });
6988
7217
  return new CodexSession({
@@ -6990,6 +7219,7 @@ ${withStreams.stderr ?? ""}`);
6990
7219
  command,
6991
7220
  workspaceDir,
6992
7221
  env: startInput.env,
7222
+ mcpEnvironment: Object.fromEntries(Object.entries(runtimeEnv).filter(([key]) => key.startsWith("BYOK_MCP_PAYLOAD_"))),
6993
7223
  spawnFn: this.options.spawnFn,
6994
7224
  queue,
6995
7225
  recordUnmapped,
@@ -7042,16 +7272,18 @@ function codexMcpToolApprovalArgs(name, tools) {
7042
7272
  }
7043
7273
  return args;
7044
7274
  }
7045
- function codexMcpConfigArgs(servers, grants = []) {
7275
+ function codexMcpConfigArgs(servers, env, helperHost, grants = []) {
7046
7276
  if (servers === void 0 || Object.keys(servers).length === 0) return [];
7047
7277
  const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
7048
7278
  const args = ["--ignore-user-config"];
7049
7279
  for (const [name, server] of Object.entries(servers).sort(([left], [right]) => left.localeCompare(right))) {
7050
- args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(server.command)}`);
7051
- if (server.args !== void 0) args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...server.args])}`);
7052
- for (const [key, value] of Object.entries(server.env ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
7053
- args.push("-c", `mcp_servers.${name}.env.${key}=${JSON.stringify(value)}`);
7054
- }
7280
+ const key = `BYOK_MCP_PAYLOAD_${randomBytes(16).toString("hex").toUpperCase()}`;
7281
+ env[key] = JSON.stringify(server);
7282
+ const helper = resolveSdkReservedHelperBin("mcp-env", helperHost);
7283
+ args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(helper.command)}`);
7284
+ args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...helper.args])}`);
7285
+ args.push("-c", `mcp_servers.${name}.env.BYOK_MCP_ENV_KEY=${JSON.stringify(key)}`);
7286
+ args.push("-c", `mcp_servers.${name}.env_vars=${JSON.stringify([key])}`);
7055
7287
  const granted = grantedTools.get(name);
7056
7288
  if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
7057
7289
  }
@@ -7079,7 +7311,7 @@ function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = fa
7079
7311
  ...modelId ? ["--model", modelId] : [],
7080
7312
  ...preparedGit ? [] : ["--skip-git-repo-check"],
7081
7313
  ...policyArgs,
7082
- instruction
7314
+ "-"
7083
7315
  ];
7084
7316
  }
7085
7317
  async function runCodexTurn(params) {
@@ -7103,9 +7335,18 @@ async function runCodexTurn(params) {
7103
7335
  runner = new CodexProcessRunner({
7104
7336
  command: params.command,
7105
7337
  args: argv,
7338
+ instruction: params.instruction,
7106
7339
  cwd: params.cwd,
7107
7340
  env: params.env,
7108
7341
  spawnFn: params.spawnFn,
7342
+ onFailure: (failure) => {
7343
+ if (!firstLineSettled) {
7344
+ firstLineSettled = true;
7345
+ rejectFirstLine(failure);
7346
+ }
7347
+ params.terminal.failure = failure;
7348
+ params.queue.end();
7349
+ },
7109
7350
  onEvent: (evt) => {
7110
7351
  if (!firstLineSettled) {
7111
7352
  firstLineSettled = true;
@@ -7172,6 +7413,16 @@ async function runCodexTurn(params) {
7172
7413
  params.queue.end();
7173
7414
  });
7174
7415
  let sessionRef;
7416
+ const abortStartup = () => rejectFirstLine(new RuntimeExecutionFailure({
7417
+ phase: params.failurePhase,
7418
+ category: "infrastructure",
7419
+ retry: "non-retryable",
7420
+ reason: "codex startup cancelled or exceeded its deadline"
7421
+ }));
7422
+ const startupTimer = setTimeout(abortStartup, 3e4);
7423
+ startupTimer.unref?.();
7424
+ params.signal?.addEventListener("abort", abortStartup, { once: true });
7425
+ if (params.signal?.aborted) abortStartup();
7175
7426
  try {
7176
7427
  sessionRef = await new Promise((resolve, reject) => {
7177
7428
  let settled = false;
@@ -7203,20 +7454,31 @@ async function runCodexTurn(params) {
7203
7454
  });
7204
7455
  });
7205
7456
  } catch (err) {
7206
- runner.kill();
7457
+ await disposeFailedStartup(runner, err);
7207
7458
  throw err;
7459
+ } finally {
7460
+ clearTimeout(startupTimer);
7461
+ params.signal?.removeEventListener("abort", abortStartup);
7208
7462
  }
7209
7463
  if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
7210
- runner.kill();
7211
- throw new RuntimeExecutionFailure({
7464
+ const failure = new RuntimeExecutionFailure({
7212
7465
  phase: params.failurePhase,
7213
7466
  category: "authority",
7214
7467
  retry: "non-retryable",
7215
7468
  reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
7216
7469
  });
7470
+ await disposeFailedStartup(runner, failure);
7471
+ throw failure;
7217
7472
  }
7218
7473
  return { sessionRef, runner };
7219
7474
  }
7475
+ async function disposeFailedStartup(runner, cause) {
7476
+ try {
7477
+ await runner.dispose();
7478
+ } catch (disposalFailure) {
7479
+ throw new RuntimeStartupDisposalFailure(() => runner.dispose(), { cause: new AggregateError([cause, disposalFailure]) });
7480
+ }
7481
+ }
7220
7482
  var CodexSession = class {
7221
7483
  /**
7222
7484
  * NOT `readonly` (cross-model review finding): `followUp()` below
@@ -7252,6 +7514,7 @@ var CodexSession = class {
7252
7514
  * recomputed from anything a later turn supplies, so a follow-up can never
7253
7515
  * widen this session's MCP authority.
7254
7516
  */
7517
+ mcpEnvironment;
7255
7518
  mcpConfigArgs;
7256
7519
  terminal;
7257
7520
  currentRunner;
@@ -7271,6 +7534,7 @@ var CodexSession = class {
7271
7534
  this.modelId = options.modelId;
7272
7535
  this.terminal = options.terminal;
7273
7536
  this.mcpConfigArgs = options.mcpConfigArgs;
7537
+ this.mcpEnvironment = Object.freeze({ ...options.mcpEnvironment });
7274
7538
  this.currentRunner = options.initialRunner;
7275
7539
  this.ownedRunners.add(options.initialRunner);
7276
7540
  void this.forgetRunnerOnceClosed(options.initialRunner);
@@ -7383,7 +7647,7 @@ var CodexSession = class {
7383
7647
  // frozen MCP config replayed verbatim — see `mcpConfigArgs`.
7384
7648
  policyArgs: [...mapping.args, ...this.mcpConfigArgs],
7385
7649
  cwd: this.workspaceDir,
7386
- env: withoutProviderCredentials(this.env),
7650
+ env: { ...withoutProviderCredentials(this.env), ...this.mcpEnvironment },
7387
7651
  spawnFn: this.spawnFn,
7388
7652
  workspaceDir: this.workspaceDir,
7389
7653
  queue: this.queue,
@@ -8015,6 +8279,20 @@ var DeviceStore = class _DeviceStore {
8015
8279
  await opened.handle.close();
8016
8280
  }
8017
8281
  }
8282
+ /** Rebuild only the non-secret projection, while the caller owns the store lease. */
8283
+ async reconcileMetadata(authority) {
8284
+ const projection = {
8285
+ deviceId: authority.deviceId,
8286
+ tenantId: authority.tenantId,
8287
+ devicePublicKey: authority.devicePublicKey
8288
+ };
8289
+ const current = await this.load();
8290
+ if (current !== void 0 && current.deviceId === projection.deviceId && current.tenantId === projection.tenantId && current.devicePublicKey === projection.devicePublicKey) {
8291
+ return false;
8292
+ }
8293
+ await this.save(projection);
8294
+ return true;
8295
+ }
8018
8296
  async save(record3) {
8019
8297
  assertDeviceMetadata(record3);
8020
8298
  const storeDir = path3__default.dirname(this.filePath);
@@ -8433,15 +8711,7 @@ var AuthManager = class {
8433
8711
  if (await this.opts.store.load() === void 0) return void 0;
8434
8712
  throw new DeviceRecordRePairRequiredError();
8435
8713
  }
8436
- const projection = {
8437
- deviceId: authority.deviceId,
8438
- tenantId: authority.tenantId,
8439
- devicePublicKey: authority.devicePublicKey
8440
- };
8441
- const current = await this.opts.store.load();
8442
- if (current === void 0 || current.deviceId !== projection.deviceId || current.tenantId !== projection.tenantId || current.devicePublicKey !== projection.devicePublicKey) {
8443
- await this.opts.store.save(projection);
8444
- }
8714
+ await this.opts.store.reconcileMetadata(authority);
8445
8715
  return Object.freeze({ ...authority });
8446
8716
  }
8447
8717
  };
@@ -8738,9 +9008,9 @@ async function probeDarwin(run) {
8738
9008
  return DARWIN_UUID_RE.exec(result.stdout)?.[1];
8739
9009
  }
8740
9010
  async function probeLinux(readFile3) {
8741
- for (const path38 of LINUX_MACHINE_ID_PATHS) {
9011
+ for (const path40 of LINUX_MACHINE_ID_PATHS) {
8742
9012
  try {
8743
- const contents = (await readFile3(path38)).trim();
9013
+ const contents = (await readFile3(path40)).trim();
8744
9014
  if (contents.length > 0) return contents;
8745
9015
  } catch {
8746
9016
  }
@@ -8772,7 +9042,7 @@ async function withTimeout2(probe, timeoutMs) {
8772
9042
  async function resolveMachineId(options) {
8773
9043
  const platform = options.platform ?? process.platform;
8774
9044
  const run = options.run ?? runDeviceCommand;
8775
- const readFile3 = options.readFile ?? ((path38) => fs14.readFile(path38, "utf8"));
9045
+ const readFile3 = options.readFile ?? ((path40) => fs14.readFile(path40, "utf8"));
8776
9046
  let probe;
8777
9047
  if (platform === "darwin") probe = probeDarwin(run);
8778
9048
  else if (platform === "linux") probe = probeLinux(readFile3);
@@ -10410,13 +10680,35 @@ var LongPollClient = class {
10410
10680
  }
10411
10681
  async loop(signal) {
10412
10682
  let retryAttempt = 0;
10683
+ let readCursor;
10684
+ let readAheadSupported = false;
10685
+ let warnedWindowAt;
10413
10686
  while (this.running && !signal.aborted) {
10414
10687
  try {
10415
10688
  await this.opts.auth.getValidAccessToken();
10416
10689
  const base = toHttpBase(this.opts.serverUrl);
10417
10690
  const url = new URL(BYOK_EVENTS_PATH, base);
10418
10691
  const cursor = this.opts.getCursor();
10692
+ const ack = cursor ?? 0;
10693
+ if (readCursor !== void 0 && readCursor - ack >= MAILBOX_READ_AHEAD_MAX_SEQS) {
10694
+ if (warnedWindowAt !== ack) {
10695
+ warnedWindowAt = ack;
10696
+ console.warn("[byok/client] mailbox read-ahead window full; acknowledgement is stalled");
10697
+ }
10698
+ readCursor = ack;
10699
+ await sleep(this.opts.retryDelayMs ?? 2e3, signal);
10700
+ continue;
10701
+ }
10702
+ const afterSeq = Math.max(ack, readCursor ?? ack);
10419
10703
  if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
10704
+ if (afterSeq > ack) {
10705
+ if (!readAheadSupported) {
10706
+ const error = new Error("server does not advertise mailbox-read-ahead; cannot read past unacknowledged work");
10707
+ this.warnRouteFailure(this.eventsEndpoint, void 0, error);
10708
+ throw error;
10709
+ }
10710
+ url.searchParams.set("afterSeq", String(afterSeq));
10711
+ }
10420
10712
  let res;
10421
10713
  try {
10422
10714
  res = await authedFetch(url, { method: "GET", signal }, this.opts.auth);
@@ -10435,6 +10727,8 @@ var LongPollClient = class {
10435
10727
  return;
10436
10728
  }
10437
10729
  this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
10730
+ readCursor = void 0;
10731
+ readAheadSupported = false;
10438
10732
  this.opts.onServerCapabilitiesInvalidated?.();
10439
10733
  this.opts.onPollFailure?.();
10440
10734
  this.opts.onOperationalOutcome?.("failure");
@@ -10444,12 +10738,16 @@ var LongPollClient = class {
10444
10738
  }
10445
10739
  let parsed;
10446
10740
  try {
10447
- parsed = parseLooseEventsPollResponse(await res.json(), cursor ?? 0);
10448
- validateTrustedEventsPage(parsed.events, cursor ?? 0, parsed.cursor);
10741
+ parsed = parseLooseEventsPollResponse(await res.json(), afterSeq);
10742
+ validateTrustedEventsPage(parsed.events, afterSeq, parsed.cursor);
10743
+ if (afterSeq > ack && !parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY)) throw new Error("mailbox read-ahead capability was withdrawn");
10449
10744
  } catch (err) {
10450
10745
  this.warnRouteFailure(this.eventsEndpoint, res.status, err);
10451
10746
  throw err;
10452
10747
  }
10748
+ readAheadSupported = parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY);
10749
+ const madeReadProgress = parsed.cursor > afterSeq;
10750
+ readCursor = parsed.cursor;
10453
10751
  this.opts.onServerCapabilities?.(parsed.capabilities);
10454
10752
  let hadValidationFailureThisBatch = false;
10455
10753
  let acceptedAnyEntry = false;
@@ -10473,10 +10771,11 @@ var LongPollClient = class {
10473
10771
  if (this.opts.onEnvelope(envelope) !== false) acceptedAnyEntry = true;
10474
10772
  }
10475
10773
  if (parsed.events.length === 0) {
10774
+ readCursor = this.opts.getCursor() ?? 0;
10476
10775
  retryAttempt = 0;
10477
10776
  this.opts.onOperationalOutcome?.("success");
10478
10777
  await sleep(this.opts.idleDelayMs ?? 250, signal);
10479
- } else if (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor) {
10778
+ } else if (!madeReadProgress && (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor)) {
10480
10779
  this.opts.onOperationalOutcome?.("failure");
10481
10780
  const baseMs = this.opts.retryDelayMs ?? 2e3;
10482
10781
  await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs, signal);
@@ -10485,6 +10784,8 @@ var LongPollClient = class {
10485
10784
  this.opts.onOperationalOutcome?.("success");
10486
10785
  }
10487
10786
  } catch (err) {
10787
+ readCursor = void 0;
10788
+ readAheadSupported = false;
10488
10789
  this.opts.onServerCapabilitiesInvalidated?.();
10489
10790
  if (this.noteRevoked(err)) return;
10490
10791
  if (!this.running || signal.aborted) return;
@@ -10537,6 +10838,7 @@ function createConnectionHelloEnvelope(opts, getCursor) {
10537
10838
  productId: opts.productId,
10538
10839
  clientVersion: opts.clientVersion,
10539
10840
  runtimes: opts.runtimes,
10841
+ harnesses: opts.harnesses,
10540
10842
  configuredToolsets: configuredToolsets === void 0 ? void 0 : [...configuredToolsets],
10541
10843
  cursor: getCursor()
10542
10844
  });
@@ -10550,8 +10852,8 @@ var ConnectionManager = class {
10550
10852
  serverUrl: opts.serverUrl,
10551
10853
  auth: opts.auth,
10552
10854
  // The long-poll query cursor is also the kernel's irreversible ack.
10553
- // Only report the successfully processed cursor here. `deliveredSeq`
10554
- // remains a local dedup watermark; using it on the wire would ack an
10855
+ // Only report the successfully persisted cursor; using received work
10856
+ // on the wire would ack an
10555
10857
  // in-flight envelope before its handler settles and make a later
10556
10858
  // failure impossible to redeliver.
10557
10859
  getCursor: () => this.cursor,
@@ -10600,25 +10902,11 @@ var ConnectionManager = class {
10600
10902
  * guarantees in docs/protocol.md §9.
10601
10903
  */
10602
10904
  stalledAtSeq;
10603
- /**
10604
- * Design A (Wave 2, F3-on-long-poll): the second, in-memory watermark
10605
- * alongside the durable `cursor`. `cursor` only ever advances AFTER a
10606
- * `task.*` handler's side effects resolve successfully, and is persisted
10607
- * (see `advanceCursor`) — that semantics is unchanged. `deliveredSeq`
10608
- * advances eagerly, the instant a `task.*` envelope is admitted past
10609
- * dedup (see `deliver`/`noteDelivered`), independent of whether its
10610
- * handler has even started, let alone succeeded. It exists so a repeated
10611
- * read at the durable cursor does not re-dispatch an envelope already in
10612
- * flight — `handleOffer` must not start a second adapter session while a
10613
- * first attempt is still running. On WS this same field is written the
10614
- * same way, but since a live WS connection only ever pushes a given `seq`
10615
- * once, it never has an observable effect there beyond mirroring
10616
- * `cursor` (see `dedupWatermark`'s doc comment for why redelivery
10617
- * correctness doesn't depend on resetting it anywhere).
10618
- */
10619
- deliveredSeq;
10620
- /** Finding F3: serializes `onEnvelope` calls into a per-connection FIFO — one envelope's handler always fully settles before the next one starts. */
10905
+ /** Drain barrier for independently running handlers; not an admission lock. */
10621
10906
  processingChain = Promise.resolve();
10907
+ cursorInitialization;
10908
+ completionTail = Promise.resolve();
10909
+ controlTails = /* @__PURE__ */ new Map();
10622
10910
  /**
10623
10911
  * Design B (finding N4): the ONE outbound queue holds `Envelope` OBJECTS,
10624
10912
  * never re-encoded/rebuilt strings, so a
@@ -10644,38 +10932,13 @@ var ConnectionManager = class {
10644
10932
  terminalError;
10645
10933
  settledWaiters = [];
10646
10934
  pendingCursorSave = Promise.resolve();
10647
- /**
10648
- * Finding P2 (Fix 2b): seqs currently admitted into `processingChain` but
10649
- * not yet settled — added in `deliver()` the moment a `task.*` envelope is
10650
- * accepted past the ordinary watermark check, removed in `process()`'s
10651
- * `finally` once that specific attempt resolves (success OR failure).
10652
- * While stalled, `dedupWatermark()` deliberately stays frozen below
10653
- * already-delivered seqs (see its own doc comment) so the failed seq's own
10654
- * redelivery can get through — but that same frozen watermark also means
10655
- * every OTHER seq above it rides along on every re-poll too. Without this,
10656
- * a seq already mid-flight (e.g. a `task.offer` whose prepared operation start()
10657
- * hasn't resolved yet) would be re-enqueued into `processingChain` on
10658
- * every such re-poll, piling up duplicate copies that — once the first
10659
- * finally resolves and the chain unwinds through them — run its handler
10660
- * again; for `task.offer` specifically, a second adapter session
10661
- * orphaning the first (`TaskRunner`'s own `this.tasks.has` guard, finding
10662
- * P2c, is the second, independent layer against exactly that).
10663
- */
10935
+ /** Admission dedup while an individual handler is running. */
10664
10936
  inFlightSeqs = /* @__PURE__ */ new Set();
10665
- /**
10666
- * Finding P2 (Fix 2b): seqs whose handler has already resolved
10667
- * successfully at least once this session, tracked only while a stall is
10668
- * in effect — cleared the moment `stalledAtSeq` itself clears (see
10669
- * `process()`), since once unstalled the ordinary watermark check via
10670
- * `deliveredSeq` already covers everything delivered so far, making this
10671
- * redundant. Needed because the stall-gap-prevention rule in `process()`
10672
- * deliberately does NOT advance `cursor` past a seq above the
10673
- * still-unresolved `stalledAtSeq`, even once that seq's own handler
10674
- * succeeds — so `dedupWatermark()` alone can't distinguish "already
10675
- * succeeded, don't re-run" from "never yet attempted" for anything in
10676
- * that gap.
10677
- */
10937
+ /** Successful side effects retained until their durable acknowledgement. */
10678
10938
  processedSeqs = /* @__PURE__ */ new Set();
10939
+ failedEnvelopes = /* @__PURE__ */ new Map();
10940
+ /** Received work lacking a successful handler receipt, including malformed frames. */
10941
+ unresolvedSeqs = /* @__PURE__ */ new Set();
10679
10942
  /**
10680
10943
  * Finding P3: the pending `drainOutbox` long-poll retry backoff, if any —
10681
10944
  * cancellable so `enterRevoked()` can unblock it immediately instead of
@@ -10968,8 +11231,8 @@ var ConnectionManager = class {
10968
11231
  * - F3 (at-most-once): the old code persisted the cursor advance BEFORE
10969
11232
  * `onEnvelope` even ran (fire-and-forget) — a handler that then failed
10970
11233
  * left a redelivery-proof envelope permanently marked processed. Inbound
10971
- * envelopes are now serialized through `processingChain` (one handler
10972
- * fully settles before the next starts) and the cursor only advances
11234
+ * handlers may run independently; their receipt commits are serialized.
11235
+ * The cursor only advances
10973
11236
  * AFTER the handler resolves successfully; a rejection leaves the
10974
11237
  * cursor where it was (see `stalledAtSeq`), so a future reconnect's
10975
11238
  * redelivery re-attempts it — safe because every server->daemon type is
@@ -10985,62 +11248,72 @@ var ConnectionManager = class {
10985
11248
  if (tracked && watermark !== void 0 && envelope.seq <= watermark) return false;
10986
11249
  if (tracked) {
10987
11250
  const seq = envelope.seq;
10988
- if (this.inFlightSeqs.has(seq) || this.processedSeqs.has(seq)) return false;
11251
+ if (this.inFlightSeqs.has(seq)) return false;
11252
+ if (!this.processedSeqs.has(seq)) this.unresolvedSeqs.add(seq);
10989
11253
  this.inFlightSeqs.add(seq);
10990
- this.noteDelivered(seq);
10991
11254
  }
10992
- this.processingChain = this.processingChain.then(() => this.process(envelope, tracked));
11255
+ const controlKey = envelope.task_id !== void 0 && !envelope.type.startsWith("task.offer") ? envelope.task_id : void 0;
11256
+ const prior = controlKey === void 0 ? Promise.resolve() : this.controlTails.get(controlKey) ?? Promise.resolve();
11257
+ const operation = prior.then(() => this.process(envelope, tracked));
11258
+ if (controlKey !== void 0) {
11259
+ this.controlTails.set(controlKey, operation);
11260
+ void operation.finally(() => {
11261
+ if (this.controlTails.get(controlKey) === operation) this.controlTails.delete(controlKey);
11262
+ });
11263
+ }
11264
+ this.processingChain = Promise.all([this.processingChain, operation]).then(() => void 0);
10993
11265
  return true;
10994
11266
  }
10995
- /**
10996
- * The local watermark `deliver()` dedupes inbound `task.*` envelopes
10997
- * against. It is deliberately NOT the long-poll query cursor: that query
10998
- * is the kernel acknowledgement and uses only the successfully processed
10999
- * `cursor` (see the constructor). Normally this local watermark is
11000
- * `deliveredSeq` — which is always >= `cursor` (every envelope that
11001
- * reaches `advanceCursor` already passed through `noteDelivered` first,
11002
- * see `deliver`) — so this is the literal `max(cursor, deliveredSeq)` the
11003
- * design calls for, just expressed via that invariant rather than an
11004
- * explicit `Math.max`.
11005
- *
11006
- * While `stalledAtSeq` is set, this collapses to the durable `cursor`
11007
- * alone, deliberately ignoring however far `deliveredSeq` had already run
11008
- * ahead before the failure was known: that's what lets the stalled
11009
- * envelope's own redelivery (and everything after it, right up to a
11010
- * fresh success) get past this same dedup check instead of being
11011
- * self-deduped by the client's own earlier eager tracking of envelopes
11012
- * whose outcome wasn't known yet. No separate "reset deliveredSeq on
11013
- * reconnect" step is needed for this to be correct — collapsing to
11014
- * `cursor` exactly while stalled already produces the right answer on
11015
- * every long-poll retry path. NOT resetting it unconditionally on every
11016
- * retry lets `deliveredSeq` keep doing its job of not re-dispatching
11017
- * something already in flight while a handler is still running.
11018
- */
11019
- dedupWatermark() {
11020
- return this.stalledAtSeq !== void 0 ? this.cursor : this.deliveredSeq ?? this.cursor;
11267
+ /** A committed terminal can settle a failed offer receipt even if cloud
11268
+ * cancellation has since filtered that offer from mailbox replay. */
11269
+ retryTaskReceipts(taskId) {
11270
+ for (const envelope of this.failedEnvelopes.values()) {
11271
+ if (envelope.task_id === taskId) this.deliver(envelope);
11272
+ }
11021
11273
  }
11022
- /** 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. */
11023
- noteDelivered(seq) {
11024
- if (this.deliveredSeq === void 0 || seq > this.deliveredSeq) this.deliveredSeq = seq;
11274
+ /** Only a durable acknowledgement can deduplicate a whole prefix. */
11275
+ dedupWatermark() {
11276
+ return this.cursor;
11025
11277
  }
11026
11278
  async process(envelope, tracked) {
11027
11279
  const seq = tracked ? envelope.seq : void 0;
11028
11280
  try {
11281
+ if (tracked && seq <= (this.cursor ?? 0)) return;
11029
11282
  if (tracked && this.cursor === void 0) {
11030
- await this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0);
11031
- this.cursor = 0;
11283
+ this.cursorInitialization ??= this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0).then(() => {
11284
+ this.cursor = 0;
11285
+ }).catch((error) => {
11286
+ this.cursorInitialization = void 0;
11287
+ throw error;
11288
+ });
11289
+ await this.cursorInitialization;
11032
11290
  }
11033
- await this.opts.onEnvelope(envelope);
11291
+ if (!tracked || !this.processedSeqs.has(seq)) await this.opts.onEnvelope(envelope);
11034
11292
  if (!tracked) return;
11035
- if (this.stalledAtSeq !== void 0 && envelope.seq !== this.stalledAtSeq) {
11036
- this.processedSeqs.add(seq);
11037
- return;
11038
- }
11039
- this.stalledAtSeq = void 0;
11040
- await this.advanceCursor(envelope.seq);
11041
- this.processedSeqs.clear();
11293
+ this.failedEnvelopes.delete(seq);
11294
+ this.processedSeqs.add(seq);
11295
+ this.unresolvedSeqs.delete(seq);
11296
+ const completion = this.completionTail.catch(() => void 0).then(async () => {
11297
+ let firstUnresolved = Infinity;
11298
+ for (const value of this.unresolvedSeqs) firstUnresolved = Math.min(firstUnresolved, value);
11299
+ let prefix = this.cursor ?? 0;
11300
+ for (const value of this.processedSeqs) if (value < firstUnresolved) prefix = Math.max(prefix, value);
11301
+ if (prefix > (this.cursor ?? 0)) await this.advanceCursor(prefix);
11302
+ for (const value of this.processedSeqs) {
11303
+ if (value <= (this.cursor ?? 0)) {
11304
+ this.processedSeqs.delete(value);
11305
+ this.failedEnvelopes.delete(value);
11306
+ }
11307
+ }
11308
+ this.stalledAtSeq = this.unresolvedSeqs.size > 0 ? firstUnresolved : void 0;
11309
+ });
11310
+ this.completionTail = completion;
11311
+ await completion;
11042
11312
  } catch (err) {
11043
- if (tracked && this.stalledAtSeq === void 0) this.stalledAtSeq = envelope.seq;
11313
+ if (tracked) {
11314
+ this.failedEnvelopes.set(seq, envelope);
11315
+ this.stalledAtSeq = Math.min(envelope.seq, this.stalledAtSeq ?? Infinity);
11316
+ }
11044
11317
  console.error(
11045
11318
  `[byok/client] envelope handler failed for ${envelope.type}${typeof envelope.seq === "number" ? ` (seq=${envelope.seq})` : ""}; cursor left unadvanced so a reconnect redelivers it:`,
11046
11319
  err
@@ -11049,11 +11322,10 @@ var ConnectionManager = class {
11049
11322
  if (tracked) this.inFlightSeqs.delete(seq);
11050
11323
  }
11051
11324
  }
11052
- /** Serialized with handler completion, so invalid work cannot be acked by later success. */
11325
+ /** Recorded synchronously at receive, before a later successful receipt can commit. */
11053
11326
  noteValidationFailure(seq) {
11054
- this.processingChain = this.processingChain.then(() => {
11055
- if (this.stalledAtSeq === void 0) this.stalledAtSeq = seq;
11056
- });
11327
+ this.unresolvedSeqs.add(seq);
11328
+ this.stalledAtSeq = Math.min(seq, this.stalledAtSeq ?? seq);
11057
11329
  }
11058
11330
  async advanceCursor(seq) {
11059
11331
  if (this.cursor !== void 0 && seq <= this.cursor) return;
@@ -11288,6 +11560,29 @@ var OperationalHealthTracker = class {
11288
11560
  }
11289
11561
  }
11290
11562
  };
11563
+ async function inspectOperationalHealthHandle(handle, expected) {
11564
+ const read = await readOperationalHealthHandle(handle, expected);
11565
+ if (read.status !== "read") return read;
11566
+ const { raw, sizeBytes } = read;
11567
+ let parsed;
11568
+ try {
11569
+ parsed = JSON.parse(raw);
11570
+ } catch {
11571
+ return { status: "corrupt", sizeBytes, reason: "operational health state is corrupt JSON" };
11572
+ }
11573
+ if (!isHealthFile(parsed)) {
11574
+ return { status: "corrupt", sizeBytes, reason: "operational health state has an invalid shape" };
11575
+ }
11576
+ return {
11577
+ status: "valid",
11578
+ sizeBytes,
11579
+ state: parsed.state,
11580
+ failureCount: parsed.failures.length,
11581
+ crashCount: parsed.crashes.length,
11582
+ ...parsed.currentRun ? { currentRunStartedAt: new Date(Date.parse(parsed.currentRun.startedAt)).toISOString() } : {},
11583
+ ...parsed.crashes.at(-1) ? { lastCrashAt: new Date(Date.parse(parsed.crashes.at(-1).detectedAt)).toISOString() } : {}
11584
+ };
11585
+ }
11291
11586
  async function readOperationalHealthHandle(handle, expected) {
11292
11587
  let before;
11293
11588
  try {
@@ -11321,6 +11616,20 @@ async function readOperationalHealthHandle(handle, expected) {
11321
11616
  return { status: "unavailable", sizeBytes, reason: "operational health state could not be read" };
11322
11617
  }
11323
11618
  }
11619
+ async function inspectOperationalHealthFile(storeDir) {
11620
+ let opened;
11621
+ try {
11622
+ opened = await openOperationalHealthFile(storeDir);
11623
+ } catch {
11624
+ return { status: "unavailable", reason: "operational health state could not be opened safely" };
11625
+ }
11626
+ if (!opened) return { status: "missing" };
11627
+ try {
11628
+ return await inspectOperationalHealthHandle(opened.handle, opened.stat);
11629
+ } finally {
11630
+ await opened.handle.close();
11631
+ }
11632
+ }
11324
11633
  function isHealthFile(value) {
11325
11634
  if (typeof value !== "object" || value === null) return false;
11326
11635
  const candidate = value;
@@ -11386,7 +11695,7 @@ var CursorStore = class {
11386
11695
  async save(serverUrl, deviceId, cursor) {
11387
11696
  const file = this.fileFor(serverUrl, deviceId);
11388
11697
  await promises.mkdir(path3__default.dirname(file), { recursive: true, mode: 448 });
11389
- await atomicWriteFile(file, JSON.stringify({ cursor }));
11698
+ await atomicWriteFile(file, JSON.stringify({ cursor }), { fsync: true });
11390
11699
  }
11391
11700
  /** 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. */
11392
11701
  async clear(serverUrl, deviceId) {
@@ -11879,9 +12188,9 @@ function isSqliteAvailable() {
11879
12188
  }
11880
12189
  }
11881
12190
  var SECURE_FILE_MODE = 384;
11882
- function openJournalDatabase(path38, busyTimeoutMs, faults) {
12191
+ function openJournalDatabase(path40, busyTimeoutMs, faults) {
11883
12192
  const { DatabaseSync } = loadSqliteModule();
11884
- const db = new DatabaseSync(path38, { timeout: busyTimeoutMs });
12193
+ const db = new DatabaseSync(path40, { timeout: busyTimeoutMs });
11885
12194
  try {
11886
12195
  faults?.onStep?.("after-open");
11887
12196
  db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
@@ -12025,9 +12334,9 @@ var RECEIVED_STATE = "received";
12025
12334
  function byteLength2(value) {
12026
12335
  return Buffer.byteLength(value, "utf8");
12027
12336
  }
12028
- function fileBytes(path38) {
12337
+ function fileBytes(path40) {
12029
12338
  try {
12030
- return statSync(path38).size;
12339
+ return statSync(path40).size;
12031
12340
  } catch {
12032
12341
  return 0;
12033
12342
  }
@@ -13370,6 +13679,109 @@ async function spillOversizedEvent(event, deps) {
13370
13679
  return bounded2.result;
13371
13680
  }
13372
13681
 
13682
+ // src/daemon/admission-wait.ts
13683
+ async function awaitAdmission(operation, signal) {
13684
+ signal.throwIfAborted();
13685
+ let onAbort;
13686
+ const aborted = new Promise((_, reject) => {
13687
+ onAbort = () => reject(signal.reason);
13688
+ signal.addEventListener("abort", onAbort, { once: true });
13689
+ });
13690
+ try {
13691
+ return await Promise.race([Promise.resolve().then(() => {
13692
+ signal.throwIfAborted();
13693
+ return operation();
13694
+ }), aborted]);
13695
+ } finally {
13696
+ signal.removeEventListener("abort", onAbort);
13697
+ }
13698
+ }
13699
+
13700
+ // src/daemon/runtime-start.ts
13701
+ async function startOwnedRuntime(start, input, signal, timeoutMs) {
13702
+ const controller = new AbortController();
13703
+ let session;
13704
+ let failure;
13705
+ let settled = false;
13706
+ const retryDisposal = async () => {
13707
+ if (session) {
13708
+ let timer2;
13709
+ try {
13710
+ await Promise.race([
13711
+ Promise.resolve().then(() => session.interrupt()).catch(() => void 0),
13712
+ new Promise((resolve) => {
13713
+ timer2 = setTimeout(resolve, 1e3);
13714
+ timer2.unref?.();
13715
+ })
13716
+ ]);
13717
+ } finally {
13718
+ if (timer2) clearTimeout(timer2);
13719
+ }
13720
+ return session.close();
13721
+ }
13722
+ if (isRuntimeStartupDisposalFailure(failure)) return failure.retryDisposal();
13723
+ if (settled && isRuntimeExecutionFailure(failure)) return;
13724
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup has not returned a cleanup receipt" });
13725
+ };
13726
+ let rejectAbort;
13727
+ const aborted = new Promise((_, reject) => {
13728
+ rejectAbort = reject;
13729
+ });
13730
+ const abort = () => {
13731
+ controller.abort();
13732
+ rejectAbort(new RuntimeStartupDisposalFailure(retryDisposal));
13733
+ };
13734
+ signal.addEventListener("abort", abort, { once: true });
13735
+ const timer = setTimeout(abort, timeoutMs);
13736
+ timer.unref?.();
13737
+ try {
13738
+ if (signal.aborted) {
13739
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup was cancelled before invocation" });
13740
+ }
13741
+ const startup = Promise.resolve().then(() => start({ ...input, signal: controller.signal })).then((value) => {
13742
+ session = value;
13743
+ settled = true;
13744
+ return value;
13745
+ }, (error) => {
13746
+ failure = error;
13747
+ settled = true;
13748
+ throw error;
13749
+ });
13750
+ return await Promise.race([startup, aborted]);
13751
+ } finally {
13752
+ clearTimeout(timer);
13753
+ signal.removeEventListener("abort", abort);
13754
+ }
13755
+ }
13756
+
13757
+ // src/daemon/artifact-read.ts
13758
+ var DEFAULT_ARTIFACT_LIMITS = Object.freeze({
13759
+ maxFileBytes: 16 * 1024 * 1024,
13760
+ maxTaskBytes: 64 * 1024 * 1024
13761
+ });
13762
+ async function readArtifactBytes(handle, maxBytes, signal, account) {
13763
+ signal.throwIfAborted();
13764
+ const stat = await handle.stat();
13765
+ if (stat.size > maxBytes) throw new Error("artifact local byte budget exceeded");
13766
+ const chunks = [];
13767
+ let total = 0;
13768
+ const stream = handle.createReadStream({ autoClose: false, start: 0, signal, highWaterMark: 64 * 1024 });
13769
+ try {
13770
+ for await (const data of stream) {
13771
+ signal.throwIfAborted();
13772
+ const chunk = data;
13773
+ account(chunk.length);
13774
+ total += chunk.length;
13775
+ if (total > maxBytes) throw new Error("artifact local byte budget exceeded");
13776
+ chunks.push(chunk);
13777
+ }
13778
+ signal.throwIfAborted();
13779
+ return Buffer.concat(chunks, total);
13780
+ } finally {
13781
+ stream.destroy();
13782
+ }
13783
+ }
13784
+
13373
13785
  // src/daemon/environment.ts
13374
13786
  var BASE_PLATFORM_ALLOWLIST = [
13375
13787
  "PATH",
@@ -13569,6 +13981,78 @@ var ProgressBatcher = class {
13569
13981
  }
13570
13982
  }
13571
13983
  };
13984
+ var DurableJsonlFile = class {
13985
+ constructor(filePath) {
13986
+ this.filePath = filePath;
13987
+ }
13988
+ filePath;
13989
+ failure;
13990
+ assertWritable() {
13991
+ if (this.failure !== void 0) {
13992
+ throw new Error("durable JSONL writer is quarantined after uncertain I/O; reopen and validate the log before retrying", { cause: this.failure });
13993
+ }
13994
+ }
13995
+ /** Replay is not a durability receipt: confirm the recovered bytes before use. */
13996
+ async confirmRecovered() {
13997
+ let handle;
13998
+ try {
13999
+ handle = await promises.open(this.filePath, "r+");
14000
+ } catch (error) {
14001
+ if (error.code === "ENOENT") return;
14002
+ throw error;
14003
+ }
14004
+ try {
14005
+ await handle.sync();
14006
+ } finally {
14007
+ await handle.close();
14008
+ }
14009
+ await this.syncDirectory();
14010
+ }
14011
+ async append(json) {
14012
+ this.assertWritable();
14013
+ try {
14014
+ const handle = await promises.open(this.filePath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
14015
+ try {
14016
+ const bytes = Buffer.from(`${json}
14017
+ `, "utf8");
14018
+ let offset = 0;
14019
+ while (offset < bytes.length) {
14020
+ const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset);
14021
+ if (bytesWritten <= 0) throw new Error("durable JSONL append made no progress");
14022
+ offset += bytesWritten;
14023
+ }
14024
+ await handle.sync();
14025
+ } finally {
14026
+ await handle.close();
14027
+ }
14028
+ await this.syncDirectory();
14029
+ } catch (error) {
14030
+ this.failure = error;
14031
+ throw error;
14032
+ }
14033
+ }
14034
+ async replace(lines) {
14035
+ this.assertWritable();
14036
+ try {
14037
+ await atomicWriteFile(this.filePath, lines.map((line) => `${line}
14038
+ `).join(""), { mode: 384, fsync: true });
14039
+ } catch (error) {
14040
+ this.failure = error;
14041
+ throw error;
14042
+ }
14043
+ }
14044
+ async syncDirectory() {
14045
+ if (process.platform === "win32") return;
14046
+ const directory = await promises.open(path3__default.dirname(this.filePath), "r");
14047
+ try {
14048
+ await directory.sync();
14049
+ } finally {
14050
+ await directory.close();
14051
+ }
14052
+ }
14053
+ };
14054
+
14055
+ // src/daemon/agent-message-outbox.ts
13572
14056
  var AGENT_MESSAGE_DIRECTORY = path3__default.join(".byok", "messages");
13573
14057
  var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
13574
14058
  var AgentMessageOutboxError = class extends Error {
@@ -13595,11 +14079,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13595
14079
  constructor(homeDir, outboxPath) {
13596
14080
  this.homeDir = homeDir;
13597
14081
  this.outboxPath = outboxPath;
14082
+ this.file = new DurableJsonlFile(outboxPath);
13598
14083
  }
13599
14084
  homeDir;
13600
14085
  outboxPath;
13601
14086
  pendingByTask = /* @__PURE__ */ new Map();
14087
+ revokedTasks = /* @__PURE__ */ new Set();
13602
14088
  dispositionByTask = /* @__PURE__ */ new Map();
14089
+ file;
13603
14090
  nextCursor = 1;
13604
14091
  logEntries = 0;
13605
14092
  writeTail = Promise.resolve();
@@ -13608,6 +14095,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13608
14095
  await ensureSecureDir(directory);
13609
14096
  const outbox = new _AgentMessageOutbox(homeDir, path3__default.join(directory, AGENT_MESSAGE_OUTBOX_FILENAME));
13610
14097
  await outbox.load();
14098
+ await outbox.file.confirmRecovered();
13611
14099
  return outbox;
13612
14100
  }
13613
14101
  /** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
@@ -13648,9 +14136,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13648
14136
  records() {
13649
14137
  return Object.freeze([...this.pendingByTask.values()].sort((a, b) => a.cursor - b.cursor));
13650
14138
  }
13651
- /** Activated records with no exact disposition yet; only these may be transport-replayed. */
14139
+ /** Records without disposition or local revoke; transport replay additionally requires session binding. */
13652
14140
  retryableRecords() {
13653
- return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId)));
14141
+ return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId) && !this.revokedTasks.has(record3.taskId)));
13654
14142
  }
13655
14143
  get(taskId) {
13656
14144
  return this.pendingByTask.get(taskId);
@@ -13662,13 +14150,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13662
14150
  if (byteCount < 1 || byteCount > input.requirement.maxBytes) throw new AgentMessageOutboxError("message body exceeds the offer byte contract");
13663
14151
  const existing = this.pendingByTask.get(input.taskId);
13664
14152
  if (existing !== void 0) {
13665
- if (existing.tenantId !== input.tenantId || existing.contentType !== input.contentType || existing.body !== input.body || existing.contract !== input.requirement.contract || !sameAgentRef(existing.agentRef, input.agentRef)) {
14153
+ 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)) {
13666
14154
  throw new AgentMessageOutboxError("required-message task already has a different immutable draft");
13667
14155
  }
13668
14156
  return existing;
13669
14157
  }
13670
- if (this.pendingByTask.size >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
13671
- const pendingBytes = this.records().reduce((sum, record4) => sum + record4.byteCount, 0);
14158
+ const sending = this.records().filter((record4) => !this.isTerminalEvidence(record4.taskId));
14159
+ if (sending.length >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
14160
+ const pendingBytes = sending.reduce((sum, record4) => sum + record4.byteCount, 0);
13672
14161
  if (pendingBytes + byteCount > input.maxPendingBytes) throw new AgentMessageOutboxError("message outbox byte quota is exhausted");
13673
14162
  const record3 = Object.freeze({
13674
14163
  schema: 1,
@@ -13690,8 +14179,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13690
14179
  return record3;
13691
14180
  });
13692
14181
  }
13693
- async activate(taskId, sessionRef) {
14182
+ async activate(taskId, sessionRef, isCurrent = () => true) {
13694
14183
  return this.exclusive(async () => {
14184
+ if (!isCurrent() || this.revokedTasks.has(taskId)) return void 0;
13695
14185
  const record3 = this.pendingByTask.get(taskId);
13696
14186
  if (record3 === void 0) return void 0;
13697
14187
  if (record3.sessionRef !== void 0) {
@@ -13704,6 +14194,18 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13704
14194
  return activated;
13705
14195
  });
13706
14196
  }
14197
+ /** Local lifecycle cancellation, never a synthesized consumer disposition. */
14198
+ async revoke(taskId) {
14199
+ return this.exclusive(async () => {
14200
+ const record3 = this.pendingByTask.get(taskId);
14201
+ if (record3 === void 0 || this.revokedTasks.has(taskId)) return;
14202
+ await this.appendEntry({ schema: 1, kind: "revoke", taskId, messageId: record3.messageId });
14203
+ this.revokedTasks.add(taskId);
14204
+ });
14205
+ }
14206
+ isTerminalEvidence(taskId) {
14207
+ return this.revokedTasks.has(taskId) || this.dispositionByTask.get(taskId)?.outcome === "refused";
14208
+ }
13707
14209
  publishPayload(record3) {
13708
14210
  if (record3.sessionRef === void 0) throw new AgentMessageOutboxError("message draft is not bound to a durable session");
13709
14211
  return AgentMessagePublishPayloadSchema.parse({
@@ -13727,6 +14229,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13727
14229
  await this.appendEntry({ schema: 1, kind: "disposition", taskId, disposition });
13728
14230
  if (disposition.outcome === "accepted") {
13729
14231
  this.pendingByTask.delete(taskId);
14232
+ this.revokedTasks.delete(taskId);
13730
14233
  this.dispositionByTask.delete(taskId);
13731
14234
  } else {
13732
14235
  this.dispositionByTask.set(taskId, disposition);
@@ -13745,6 +14248,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13745
14248
  if (error.code === "ENOENT") return;
13746
14249
  throw error;
13747
14250
  }
14251
+ if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentMessageOutboxError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
13748
14252
  for (const line of raw.split("\n")) {
13749
14253
  if (line.length === 0) continue;
13750
14254
  const entry = JSON.parse(line);
@@ -13759,11 +14263,16 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13759
14263
  if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message activation has no exact draft");
13760
14264
  if (record3.sessionRef !== void 0 && record3.sessionRef !== entry.sessionRef) throw new AgentMessageOutboxError("message activation session conflicts");
13761
14265
  this.pendingByTask.set(entry.taskId, Object.freeze({ ...record3, sessionRef: entry.sessionRef }));
14266
+ } else if (entry.kind === "revoke") {
14267
+ const record3 = this.pendingByTask.get(entry.taskId);
14268
+ if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message revocation has no exact draft");
14269
+ this.revokedTasks.add(entry.taskId);
13762
14270
  } else if (entry.kind === "disposition") {
13763
14271
  const record3 = this.pendingByTask.get(entry.taskId);
13764
14272
  if (record3 === void 0 || !exactDisposition(record3, entry.disposition)) throw new AgentMessageOutboxError("message outbox contains a mismatched disposition");
13765
14273
  if (entry.disposition.outcome === "accepted") {
13766
14274
  this.pendingByTask.delete(entry.taskId);
14275
+ this.revokedTasks.delete(entry.taskId);
13767
14276
  this.dispositionByTask.delete(entry.taskId);
13768
14277
  } else {
13769
14278
  this.dispositionByTask.set(entry.taskId, entry.disposition);
@@ -13774,23 +14283,47 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13774
14283
  }
13775
14284
  }
13776
14285
  async appendEntry(entry) {
13777
- const handle = await promises.open(this.outboxPath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
13778
- try {
13779
- await handle.write(`${stableJson(entry)}
13780
- `, void 0, "utf8");
13781
- await handle.sync();
13782
- this.logEntries += 1;
13783
- } finally {
13784
- await handle.close();
13785
- }
14286
+ await this.file.append(stableJson(entry));
14287
+ this.logEntries += 1;
13786
14288
  }
13787
- async compact() {
13788
- const entries = this.records().flatMap((record3) => {
14289
+ /**
14290
+ * Explicit operator maintenance under the Agent-home single-writer lease.
14291
+ * Copy complete terminal evidence durably before removing it from the live log.
14292
+ * Archives are audit artifacts, never another replay input. held remains live.
14293
+ */
14294
+ async archiveTerminalRecords(archiveDirectory) {
14295
+ return this.exclusive(async () => {
14296
+ if (!path3__default.isAbsolute(archiveDirectory)) throw new AgentMessageOutboxError("message archive directory must be absolute");
14297
+ const terminal = this.records().filter((record3) => this.isTerminalEvidence(record3.taskId));
14298
+ if (terminal.length === 0) return void 0;
14299
+ await ensureSecureDir(archiveDirectory);
14300
+ const archivePath = path3__default.join(archiveDirectory, `message-terminal-${randomUUID()}.jsonl`);
14301
+ await atomicWriteFile(archivePath, this.snapshotEntries(terminal).map((entry) => `${stableJson(entry)}
14302
+ `).join(""), { mode: 384, fsync: true });
14303
+ const retained = this.records().filter((record3) => !this.isTerminalEvidence(record3.taskId));
14304
+ const entries = this.snapshotEntries(retained);
14305
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
14306
+ for (const record3 of terminal) {
14307
+ this.pendingByTask.delete(record3.taskId);
14308
+ this.dispositionByTask.delete(record3.taskId);
14309
+ this.revokedTasks.delete(record3.taskId);
14310
+ }
14311
+ this.logEntries = entries.length;
14312
+ return archivePath;
14313
+ });
14314
+ }
14315
+ snapshotEntries(records) {
14316
+ return records.flatMap((record3) => {
13789
14317
  const append = { schema: 1, kind: "append", record: record3 };
13790
14318
  const disposition = this.dispositionByTask.get(record3.taskId);
13791
- return disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record3.taskId, disposition }];
14319
+ const entries = disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record3.taskId, disposition }];
14320
+ if (this.revokedTasks.has(record3.taskId)) entries.push({ schema: 1, kind: "revoke", taskId: record3.taskId, messageId: record3.messageId });
14321
+ return entries;
13792
14322
  });
13793
- await atomicWriteFile(this.outboxPath, entries.map(stableJson).join(entries.length === 0 ? "" : "\n"), { mode: 384 });
14323
+ }
14324
+ async compact() {
14325
+ const entries = this.snapshotEntries(this.records());
14326
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
13794
14327
  this.logEntries = entries.length;
13795
14328
  }
13796
14329
  async exclusive(fn) {
@@ -13801,6 +14334,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13801
14334
  });
13802
14335
  await prior;
13803
14336
  try {
14337
+ this.file.assertWritable();
13804
14338
  return await fn();
13805
14339
  } finally {
13806
14340
  release();
@@ -14981,7 +15515,7 @@ var TaskRunner = class {
14981
15515
  */
14982
15516
  stoppingOffers = false;
14983
15517
  get activeTaskCount() {
14984
- return this.tasks.size;
15518
+ return this.tasks.size + this.startupOwners.size;
14985
15519
  }
14986
15520
  /**
14987
15521
  * Transport-boundary classification for the currently active task. Legacy
@@ -15027,6 +15561,12 @@ var TaskRunner = class {
15027
15561
  const active = this.tasks.get(taskId);
15028
15562
  const context = pending ?? (active?.messageRequirement && active.agentRef && active.messageOutbox ? { agentRef: active.agentRef, requirement: active.messageRequirement, outbox: active.messageOutbox } : void 0);
15029
15563
  if (context === void 0) throw new Error("task has no active required Agent message contract");
15564
+ const isCurrent = () => {
15565
+ if (this.messageContextByToken.get(input.contextToken) !== taskId || this.messageContextByTask.get(taskId) !== input.contextToken || this.pendingCancelled.has(taskId)) return false;
15566
+ const current2 = this.tasks.get(taskId);
15567
+ return current2 === void 0 ? active === void 0 && this.pendingMessageTasks.get(taskId) === pending : (active === void 0 || current2 === active) && current2.messageOutbox === context.outbox && this.canPublishAgentMessage(current2);
15568
+ };
15569
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15030
15570
  const record3 = await context.outbox.appendDraft({
15031
15571
  taskId,
15032
15572
  tenantId: this.deps.tenantId,
@@ -15037,8 +15577,17 @@ var TaskRunner = class {
15037
15577
  maxPendingEvents: 64,
15038
15578
  maxPendingBytes: 4 * 1024 * 1024
15039
15579
  });
15040
- if (active === void 0) return { messageId: record3.messageId, state: "staged" };
15041
- const activated = await context.outbox.activate(taskId, active.session.sessionRef);
15580
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15581
+ const current = this.tasks.get(taskId);
15582
+ if (current === void 0) return { messageId: record3.messageId, state: "staged" };
15583
+ const activated = await this.activateAgentMessage(
15584
+ context.outbox,
15585
+ taskId,
15586
+ current.session.sessionRef,
15587
+ isCurrent,
15588
+ () => current.messageSendAttempted === true
15589
+ );
15590
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15042
15591
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
15043
15592
  this.sendAgentMessageRecord(context.outbox, activated);
15044
15593
  return { messageId: activated.messageId, state: "pending" };
@@ -15074,19 +15623,39 @@ var TaskRunner = class {
15074
15623
  });
15075
15624
  return opened;
15076
15625
  }
15626
+ /** A recovery terminal must not close first-message admission before this durable draft has a disposition. */
15627
+ hasPendingRecoveredAgentMessage(taskId) {
15628
+ return this.recoveredMessageOutboxes.get(taskId)?.retryableRecords().some((record3) => record3.taskId === taskId && record3.sessionRef !== void 0) === true;
15629
+ }
15077
15630
  /** Retry stable recovered records after a transport handshake/re-handshake. */
15078
15631
  retryRecoveredAgentMessages() {
15079
15632
  for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
15080
15633
  const record3 = outbox.get(taskId);
15081
- if (record3?.sessionRef !== void 0) this.sendAgentMessageRecord(outbox, record3);
15634
+ if (record3?.sessionRef !== void 0 && this.hasPendingRecoveredAgentMessage(taskId)) this.sendAgentMessageRecord(outbox, record3);
15082
15635
  }
15083
15636
  }
15637
+ canPublishAgentMessage(active) {
15638
+ return this.tasks.get(active.taskId) === active && !active.beingTornDown;
15639
+ }
15640
+ /** All authors and startup use the same queue-time and post-I/O authority check. */
15641
+ async activateAgentMessage(outbox, taskId, sessionRef, isCurrent, wasSendAttempted = () => false) {
15642
+ if (!isCurrent()) return void 0;
15643
+ const record3 = await outbox.activate(taskId, sessionRef, isCurrent);
15644
+ if (!isCurrent()) {
15645
+ if (!wasSendAttempted()) await outbox.revoke(taskId);
15646
+ return void 0;
15647
+ }
15648
+ return record3;
15649
+ }
15084
15650
  sendAgentMessageRecord(outbox, record3) {
15651
+ const active = this.tasks.get(record3.taskId);
15652
+ const authorized = active === void 0 ? this.recoveredMessageOutboxes.get(record3.taskId) === outbox : active.messageOutbox === outbox && this.canPublishAgentMessage(active);
15653
+ if (!authorized || record3.sessionRef === void 0 || !outbox.retryableRecords().some((pending) => pending.messageId === record3.messageId)) return;
15654
+ if (active !== void 0) active.messageSendAttempted = true;
15085
15655
  this.deps.send(createEnvelope("agent.message.publish", outbox.publishPayload(record3), {
15086
15656
  taskId: record3.taskId,
15087
15657
  sessionRef: record3.sessionRef
15088
15658
  }));
15089
- const active = this.tasks.get(record3.taskId);
15090
15659
  if (active?.messageOutbox === outbox) {
15091
15660
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15092
15661
  active.messageRetryTimer = setTimeout(() => {
@@ -15126,6 +15695,7 @@ var TaskRunner = class {
15126
15695
  if (outcome === "accepted") this.recoveredMessageOutboxes.delete(taskId);
15127
15696
  return;
15128
15697
  }
15698
+ if (this.tasks.get(taskId) !== active) return;
15129
15699
  if (outcome === "held") {
15130
15700
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15131
15701
  active.messageRetryTimer = void 0;
@@ -15135,6 +15705,8 @@ var TaskRunner = class {
15135
15705
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15136
15706
  active.messageRetryTimer = void 0;
15137
15707
  this.revokeAgentMessageContext(taskId);
15708
+ active.pendingMessageCompletion = void 0;
15709
+ await this.fail(taskId, "required Agent message was refused", false);
15138
15710
  return;
15139
15711
  }
15140
15712
  if (outcome !== "accepted") return;
@@ -15153,6 +15725,9 @@ var TaskRunner = class {
15153
15725
  /** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
15154
15726
  stopAcceptingOffers() {
15155
15727
  this.stoppingOffers = true;
15728
+ if (this.startupRetryTimer) clearTimeout(this.startupRetryTimer);
15729
+ this.startupRetryTimer = void 0;
15730
+ for (const abort of this.inFlightBlobAborts.values()) abort.abort();
15156
15731
  }
15157
15732
  /**
15158
15733
  * Shutdown of every currently ACTIVE task for the control socket's
@@ -15191,9 +15766,65 @@ var TaskRunner = class {
15191
15766
  * ahead of this method — see `daemon-control-socket.test.ts`'s dedicated
15192
15767
  * regression test for the exact scenario.
15193
15768
  */
15769
+ /** Startup resources have an owner even before a Session can become active. */
15770
+ startupRetryTimer;
15771
+ claimedHarnesses = /* @__PURE__ */ new Map();
15772
+ homeReservations = /* @__PURE__ */ new Map();
15773
+ startupOwners = /* @__PURE__ */ new Map();
15774
+ startupDisposals = /* @__PURE__ */ new Map();
15775
+ disposeStartupOwner(taskId) {
15776
+ const existing = this.startupDisposals.get(taskId);
15777
+ if (existing) return existing;
15778
+ const attempt = this.disposeStartupOwnerOnce(taskId).finally(() => this.startupDisposals.delete(taskId));
15779
+ this.startupDisposals.set(taskId, attempt);
15780
+ return attempt;
15781
+ }
15782
+ async disposeStartupOwnerOnce(taskId) {
15783
+ const owner = this.startupOwners.get(taskId);
15784
+ if (!owner) return true;
15785
+ try {
15786
+ await owner.settle?.();
15787
+ await owner.dispose();
15788
+ await this.deps.awaitTerminalCommit?.(taskId);
15789
+ await owner.release();
15790
+ } catch (caught) {
15791
+ const failure = isRuntimeDisposalFailure(caught) ? caught : new RuntimeDisposalFailure({
15792
+ stage: "quiescence",
15793
+ reason: "startup runtime disposal has not been confirmed"
15794
+ }, { cause: caught });
15795
+ this.deps.onRuntimeDisposalFailure?.({
15796
+ taskId,
15797
+ runtimeId: owner.runtimeId,
15798
+ stage: failure.stage,
15799
+ reason: failure.message
15800
+ });
15801
+ console.error(`[byok/client] startup ownership retained for ${taskId}: ${failure.message}`);
15802
+ if (!this.startupRetryTimer && !this.stoppingOffers) {
15803
+ this.startupRetryTimer = setTimeout(() => {
15804
+ this.startupRetryTimer = void 0;
15805
+ for (const id of this.startupOwners.keys()) {
15806
+ if (!this.inFlightOffers.has(id)) void this.disposeStartupOwner(id);
15807
+ }
15808
+ }, 1e3);
15809
+ this.startupRetryTimer.unref?.();
15810
+ }
15811
+ return false;
15812
+ }
15813
+ if (this.startupOwners.get(taskId) === owner) this.startupOwners.delete(taskId);
15814
+ this.pendingMessageTasks.delete(taskId);
15815
+ this.revokeAgentMessageContext(taskId);
15816
+ this.revokeAgentMemoryContext(taskId);
15817
+ return true;
15818
+ }
15194
15819
  async shutdownActiveTasks(reason) {
15195
15820
  const active = [...this.tasks.values()];
15196
15821
  await Promise.all(active.map((task) => this.shutdownTask(task, reason)));
15822
+ for (const taskId of this.startupOwners.keys()) {
15823
+ if (!await this.disposeStartupOwner(taskId)) throw new RuntimeDisposalFailure({
15824
+ stage: "quiescence",
15825
+ reason: "startup runtime ownership remains quarantined"
15826
+ });
15827
+ }
15197
15828
  }
15198
15829
  /**
15199
15830
  * M5 batch-3 (workstream 2): the ONE shared per-task teardown sequence —
@@ -15225,14 +15856,19 @@ var TaskRunner = class {
15225
15856
  * a genuine protocol bug, not a benign race — mirrors `pump()`'s own
15226
15857
  * identity-check guard for the same class of race.
15227
15858
  */
15859
+ async interruptBounded(session) {
15860
+ await raceSettleFirst(
15861
+ () => session.interrupt(),
15862
+ this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS
15863
+ );
15864
+ }
15228
15865
  async teardownActiveTask(active, reason, retryable) {
15229
15866
  if (active.finalizationStarted) return this.finish(active.taskId);
15230
15867
  if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
15231
15868
  active.beingTornDown = true;
15232
15869
  active.blobAbort.abort();
15233
15870
  await this.observeGit(active, "salvage");
15234
- const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
15235
- await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
15871
+ await this.interruptBounded(active.session);
15236
15872
  if (this.tasks.get(active.taskId) !== active) return true;
15237
15873
  await this.persistAgentTerminalEvidence(active, "failed", reason);
15238
15874
  this.deps.send(
@@ -15334,7 +15970,7 @@ var TaskRunner = class {
15334
15970
  }
15335
15971
  }
15336
15972
  async handleOffer(taskId, payload, strictAgentOffer) {
15337
- if (this.tasks.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
15973
+ if (this.tasks.has(taskId) || this.startupOwners.has(taskId) || this.inFlightOffers.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
15338
15974
  return;
15339
15975
  }
15340
15976
  let agentRef;
@@ -15377,9 +16013,29 @@ var TaskRunner = class {
15377
16013
  }
15378
16014
  this.inFlightOffers.add(taskId);
15379
16015
  const blobAbort = new AbortController();
16016
+ const startupDeadline = Date.now() + (this.deps.startupTimeoutMs ?? 3e4);
16017
+ const startupTimer = setTimeout(
16018
+ () => blobAbort.abort(new Error("runtime startup deadline exceeded during admission/start")),
16019
+ this.deps.startupTimeoutMs ?? 3e4
16020
+ );
16021
+ startupTimer.unref?.();
16022
+ const admissionWithdrawn = () => {
16023
+ if (!blobAbort.signal.aborted) return false;
16024
+ const cancelled = this.pendingCancelled.has(taskId);
16025
+ decline(cancelled ? "cancelled before claim" : this.stoppingOffers ? "daemon is shutting down" : "runtime startup deadline exceeded", !cancelled);
16026
+ return true;
16027
+ };
15380
16028
  this.inFlightBlobAborts.set(taskId, blobAbort);
15381
16029
  let agentBinding;
15382
16030
  let agentLeaseTransferred = false;
16031
+ let reservedHome;
16032
+ const releaseReservation = () => {
16033
+ if (reservedHome === void 0) return;
16034
+ const remaining = this.homeReservations.get(reservedHome) - 1;
16035
+ if (remaining === 0) this.homeReservations.delete(reservedHome);
16036
+ else this.homeReservations.set(reservedHome, remaining);
16037
+ reservedHome = void 0;
16038
+ };
15383
16039
  try {
15384
16040
  if (this.pendingCancelled.has(taskId)) {
15385
16041
  const reason = this.pendingCancelled.get(taskId);
@@ -15404,11 +16060,14 @@ var TaskRunner = class {
15404
16060
  );
15405
16061
  return;
15406
16062
  }
15407
- const active2 = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome);
15408
- if (active2 >= limit) {
15409
- decline(`agent home busy: ${active2} active attempt(s)`, true);
16063
+ if (admissionWithdrawn()) return;
16064
+ const active = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome) + (this.homeReservations.get(canonicalHome) ?? 0);
16065
+ if (active >= limit) {
16066
+ decline(`agent home busy: ${active} active attempt(s)`, true);
15410
16067
  return;
15411
16068
  }
16069
+ this.homeReservations.set(canonicalHome, (this.homeReservations.get(canonicalHome) ?? 0) + 1);
16070
+ reservedHome = canonicalHome;
15412
16071
  }
15413
16072
  const guarded = this.deps.admissionGuard?.({ taskId, payload });
15414
16073
  if (guarded !== void 0 && !guarded.admit) {
@@ -15440,6 +16099,14 @@ var TaskRunner = class {
15440
16099
  );
15441
16100
  return;
15442
16101
  }
16102
+ if (payload.harnessId !== void 0 && (payload.runtime !== void 0 || payload.dispatchSelection !== void 0)) {
16103
+ decline("harnessId cannot be combined with a built-in runtime selection", false);
16104
+ return;
16105
+ }
16106
+ if (payload.harnessId !== void 0 && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16107
+ decline("server does not support custom-harness identity", false);
16108
+ return;
16109
+ }
15443
16110
  const requiredToolsets = "requiredToolsets" in payload ? payload.requiredToolsets : void 0;
15444
16111
  const resolvedMcp = requiredToolsets ? this.resolveMcpServers(requiredToolsets) : void 0;
15445
16112
  if (resolvedMcp && !resolvedMcp.ok) {
@@ -15452,17 +16119,29 @@ var TaskRunner = class {
15452
16119
  return;
15453
16120
  }
15454
16121
  const offered = withoutRequiredToolsets(payload);
15455
- const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
16122
+ const requestedRuntime = payload.harnessId ?? payload.dispatchSelection?.runtimeId ?? payload.runtime;
15456
16123
  const requiresAgentMemoryMcp = agentRef !== void 0 && this.deps.agentMemoryMcpBin !== void 0 && isAgentMemorySecureFilesystemAvailable(this.deps.agentMemoryFilesystemHelperBin !== void 0);
15457
- const pick = await this.pickAdapter(
15458
- requestedRuntime,
15459
- payload.policy.mode,
15460
- requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp
15461
- );
16124
+ let pick;
16125
+ try {
16126
+ pick = await this.pickAdapter(
16127
+ requestedRuntime,
16128
+ payload.policy.mode,
16129
+ requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp,
16130
+ blobAbort.signal
16131
+ );
16132
+ } catch (error) {
16133
+ if (!admissionWithdrawn()) decline(`runtime detection failed: ${errorMessage6(error)}`, true);
16134
+ return;
16135
+ }
16136
+ if (admissionWithdrawn()) return;
15462
16137
  if (!pick.ok) {
15463
16138
  decline(pick.reason, pick.retryable);
15464
16139
  return;
15465
16140
  }
16141
+ if (!isKnownRuntimeId(pick.descriptor.id) && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16142
+ decline("server does not support the selected custom harness identity", false);
16143
+ return;
16144
+ }
15466
16145
  const env = buildRuntimeEnv({
15467
16146
  ambient: process.env,
15468
16147
  requirements: pick.descriptor.environmentRequirements,
@@ -15536,18 +16215,20 @@ var TaskRunner = class {
15536
16215
  }
15537
16216
  let prepared;
15538
16217
  try {
15539
- prepared = await pick.adapter.prepare({
16218
+ prepared = await awaitAdmission(() => pick.adapter.prepare({
16219
+ signal: blobAbort.signal,
15540
16220
  offer: offered,
15541
16221
  policy: decision.policy,
15542
16222
  descriptor: pick.descriptor,
15543
16223
  requiredToolsetIds: requiredToolsets ?? [],
15544
16224
  ...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers },
15545
16225
  ...mcpToolsetTools === void 0 ? {} : { mcpToolsetTools }
15546
- });
16226
+ }), blobAbort.signal);
15547
16227
  } catch (error) {
15548
- decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
16228
+ if (!admissionWithdrawn()) decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
15549
16229
  return;
15550
16230
  }
16231
+ if (admissionWithdrawn()) return;
15551
16232
  if (prepared.kind === "reject") {
15552
16233
  decline(prepared.reason, prepared.retryable);
15553
16234
  return;
@@ -15555,6 +16236,7 @@ var TaskRunner = class {
15555
16236
  if (agentRef !== void 0) {
15556
16237
  try {
15557
16238
  agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
16239
+ releaseReservation();
15558
16240
  } catch (error) {
15559
16241
  decline(
15560
16242
  `Agent home admission failed: ${errorMessage6(error)}`,
@@ -15679,7 +16361,16 @@ var TaskRunner = class {
15679
16361
  },
15680
16362
  forwardedEnvironmentNames: Object.freeze(Object.keys(env).sort())
15681
16363
  });
16364
+ if (admissionWithdrawn()) {
16365
+ gitLease?.release();
16366
+ return;
16367
+ }
15682
16368
  await this.deps.beforeClaim?.(taskId, manifest.descriptor.id);
16369
+ if (admissionWithdrawn()) {
16370
+ gitLease?.release();
16371
+ return;
16372
+ }
16373
+ if (!isKnownRuntimeId(manifest.descriptor.id)) this.claimedHarnesses.set(taskId, manifest.descriptor.id);
15683
16374
  this.deps.send(
15684
16375
  createEnvelope(
15685
16376
  "task.claim",
@@ -15697,6 +16388,7 @@ var TaskRunner = class {
15697
16388
  // where an auto-selected task left the server never learning
15698
16389
  // which runtime actually ran.
15699
16390
  runtime: isKnownRuntimeId(manifest.descriptor.id) ? manifest.descriptor.id : void 0,
16391
+ ...!isKnownRuntimeId(manifest.descriptor.id) ? { harnessId: manifest.descriptor.id } : {},
15700
16392
  // S0/D-4 (`task.claim.capabilities`, docs/protocol.md §2.4): the
15701
16393
  // selected adapter's own capability self-report, carried on the
15702
16394
  // same message that establishes the task↔runtime binding. The
@@ -15814,8 +16506,57 @@ var TaskRunner = class {
15814
16506
  };
15815
16507
  let session;
15816
16508
  try {
15817
- session = await prepared.operation.start(startInput);
16509
+ session = await startOwnedRuntime(
16510
+ (input) => prepared.operation.start(input),
16511
+ startInput,
16512
+ blobAbort.signal,
16513
+ Math.max(0, startupDeadline - Date.now())
16514
+ );
15818
16515
  } catch (err) {
16516
+ const disposalFailure = isRuntimeStartupDisposalFailure(err);
16517
+ const cancelled = this.pendingCancelled.has(taskId);
16518
+ if (disposalFailure || cancelled) {
16519
+ if (cancelled) this.revokeAgentMessageContext(taskId);
16520
+ const ownedBinding2 = agentBinding;
16521
+ const cancellation = createEnvelope("task.cancelled", {
16522
+ reason: this.pendingCancelled.get(taskId),
16523
+ ...terminalIdentity(this.claimedHarnesses.get(taskId)),
16524
+ ...agentBinding === void 0 ? {} : { agentRef: agentBinding.resolution.agentRef }
16525
+ }, { taskId });
16526
+ let cancellationSettled = false;
16527
+ this.startupOwners.set(taskId, {
16528
+ runtimeId: pick.descriptor.id,
16529
+ dispose: disposalFailure ? err.retryDisposal : async () => {
16530
+ },
16531
+ ...cancelled ? { settle: async () => {
16532
+ if (cancellationSettled) return;
16533
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
16534
+ this.deps.send(cancellation);
16535
+ cancellationSettled = true;
16536
+ this.pendingCancelled.delete(taskId);
16537
+ this.addFinishedTaskId(taskId);
16538
+ } } : {},
16539
+ release: async () => {
16540
+ await ownedBinding2?.lease.release();
16541
+ gitLease?.release();
16542
+ }
16543
+ });
16544
+ agentLeaseTransferred = true;
16545
+ if (disposalFailure) this.deps.onRuntimeDisposalFailure?.({
16546
+ taskId,
16547
+ runtimeId: pick.descriptor.id,
16548
+ stage: "quiescence",
16549
+ reason: err.message
16550
+ });
16551
+ if (!cancelled) {
16552
+ if (agentBinding === void 0) await this.fail(taskId, errorMessage6(err), false);
16553
+ else await this.failClaimedAgent(taskId, errorMessage6(err), false, {
16554
+ binding: agentBinding,
16555
+ runtimeId: pick.descriptor.id
16556
+ });
16557
+ }
16558
+ return;
16559
+ }
15819
16560
  const failure = projectRuntimeBoundaryFailure(err, "start");
15820
16561
  if (failure.contractViolation) {
15821
16562
  console.error("[byok/client] runtime adapter start() returned an untyped failure", err);
@@ -15832,143 +16573,169 @@ var TaskRunner = class {
15832
16573
  }
15833
16574
  return;
15834
16575
  }
15835
- if (agentBinding !== void 0) {
15836
- try {
15837
- await agentBinding.lease.bindSession(session.sessionRef);
15838
- } catch (error) {
15839
- await session.close().catch(() => {
15840
- });
15841
- await this.failClaimedAgent(
15842
- taskId,
15843
- `Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
15844
- false,
15845
- {
15846
- binding: agentBinding,
15847
- runtimeId: pick.descriptor.id,
15848
- sessionRef: session.sessionRef
15849
- }
15850
- );
15851
- return;
16576
+ const ownedBinding = agentBinding;
16577
+ this.startupOwners.set(taskId, {
16578
+ runtimeId: pick.descriptor.id,
16579
+ dispose: () => session.close(),
16580
+ release: async () => {
16581
+ await ownedBinding?.lease.release();
16582
+ gitLease?.release();
15852
16583
  }
15853
- }
15854
- let active;
15855
- active = {
15856
- taskId,
15857
- blobAbort,
15858
- egressEnabled: "egressPolicy" in payload,
15859
- adapter: pick.adapter,
15860
- session,
15861
- workspaceDir,
15862
- ...agentBinding === void 0 ? {} : {
15863
- agentBinding,
15864
- agentRef: agentBinding.resolution.agentRef,
15865
- agentHandoff: {
15866
- sessionRef: session.sessionRef,
15867
- runtimeId: pick.descriptor.id,
15868
- cwd: workspaceDir
15869
- }
15870
- },
15871
- gitWorkspaceId,
15872
- gitLease,
15873
- gitBaseline,
15874
- summaryParts: [],
15875
- finalTextParts: [],
15876
- batcher: new ProgressBatcher(
15877
- (seq, events) => {
15878
- const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
15879
- agentRef: active.agentRef,
16584
+ });
16585
+ try {
16586
+ if (agentBinding !== void 0) {
16587
+ try {
16588
+ await agentBinding.lease.bindSession(session.sessionRef);
16589
+ } catch (error) {
16590
+ await this.failClaimedAgent(
15880
16591
  taskId,
15881
- events,
15882
- serverCapabilities: this.deps.getServerCapabilities?.() ?? []
15883
- }) ?? [] : events;
15884
- if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq, events: [...projected] }, { taskId, seq }));
15885
- },
15886
- this.deps.batcherOptions
15887
- ),
15888
- approvalQueue: [],
15889
- outputBytesSoFar: 0,
15890
- startedAtMs: Date.now(),
15891
- ...messageRequirement === void 0 ? {} : {
15892
- messageRequirement,
15893
- messageOutbox: this.pendingMessageTasks.get(taskId).outbox
15894
- },
15895
- ...terminalProjection === void 0 ? {} : { terminalProjection }
15896
- };
15897
- if (agentBinding !== void 0) {
15898
- const binding = agentBinding;
15899
- try {
15900
- await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
15901
- agentRef: binding.resolution.agentRef,
15902
- taskId,
15903
- sessionRef: session.sessionRef,
15904
- runtimeId: pick.descriptor.id,
15905
- cwd: workspaceDir,
15906
- leaseId: binding.lease.leaseId
15907
- }));
15908
- } catch (error) {
15909
- await session.close().catch(() => {
15910
- });
15911
- await this.failClaimedAgent(
15912
- taskId,
15913
- `Agent session handoff could not be durably written before start: ${errorMessage6(error)}`,
15914
- false,
15915
- {
15916
- binding: agentBinding,
16592
+ `Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
16593
+ false,
16594
+ {
16595
+ binding: agentBinding,
16596
+ runtimeId: pick.descriptor.id,
16597
+ sessionRef: session.sessionRef
16598
+ }
16599
+ );
16600
+ return;
16601
+ }
16602
+ }
16603
+ let active;
16604
+ active = {
16605
+ taskId,
16606
+ blobAbort,
16607
+ egressEnabled: "egressPolicy" in payload,
16608
+ adapter: pick.adapter,
16609
+ session,
16610
+ workspaceDir,
16611
+ ...agentBinding === void 0 ? {} : {
16612
+ agentBinding,
16613
+ agentRef: agentBinding.resolution.agentRef,
16614
+ agentHandoff: {
16615
+ sessionRef: session.sessionRef,
15917
16616
  runtimeId: pick.descriptor.id,
15918
- sessionRef: session.sessionRef
16617
+ cwd: workspaceDir
15919
16618
  }
16619
+ },
16620
+ gitWorkspaceId,
16621
+ gitLease,
16622
+ gitBaseline,
16623
+ summaryParts: [],
16624
+ finalTextParts: [],
16625
+ batcher: new ProgressBatcher(
16626
+ (seq, events) => {
16627
+ const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
16628
+ agentRef: active.agentRef,
16629
+ taskId,
16630
+ events,
16631
+ serverCapabilities: this.deps.getServerCapabilities?.() ?? []
16632
+ }) ?? [] : events;
16633
+ if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq, events: [...projected] }, { taskId, seq }));
16634
+ },
16635
+ this.deps.batcherOptions
16636
+ ),
16637
+ approvalQueue: [],
16638
+ outputBytesSoFar: 0,
16639
+ startedAtMs: Date.now(),
16640
+ ...messageRequirement === void 0 ? {} : {
16641
+ messageRequirement,
16642
+ messageOutbox: this.pendingMessageTasks.get(taskId).outbox
16643
+ },
16644
+ ...terminalProjection === void 0 ? {} : { terminalProjection }
16645
+ };
16646
+ if (agentBinding !== void 0) {
16647
+ const binding = agentBinding;
16648
+ try {
16649
+ await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
16650
+ agentRef: binding.resolution.agentRef,
16651
+ taskId,
16652
+ sessionRef: session.sessionRef,
16653
+ runtimeId: pick.descriptor.id,
16654
+ cwd: workspaceDir,
16655
+ leaseId: binding.lease.leaseId
16656
+ }));
16657
+ } catch (error) {
16658
+ await this.failClaimedAgent(
16659
+ taskId,
16660
+ `Agent session handoff could not be durably written before start: ${errorMessage6(error)}`,
16661
+ false,
16662
+ {
16663
+ binding: agentBinding,
16664
+ runtimeId: pick.descriptor.id,
16665
+ sessionRef: session.sessionRef
16666
+ }
16667
+ );
16668
+ return;
16669
+ }
16670
+ }
16671
+ const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await this.activateAgentMessage(
16672
+ active.messageOutbox,
16673
+ taskId,
16674
+ session.sessionRef,
16675
+ () => !this.pendingCancelled.has(taskId) && !blobAbort.signal.aborted
16676
+ );
16677
+ if (this.pendingCancelled.has(taskId)) {
16678
+ const reason = this.pendingCancelled.get(taskId);
16679
+ this.pendingCancelled.delete(taskId);
16680
+ agentLeaseTransferred = agentBinding !== void 0;
16681
+ this.startupOwners.delete(taskId);
16682
+ this.tasks.set(taskId, active);
16683
+ this.reserveSemanticTerminal(active);
16684
+ await this.interruptBounded(session);
16685
+ await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
16686
+ await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16687
+ this.deps.send(
16688
+ createEnvelope(
16689
+ "task.cancelled",
16690
+ { reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16691
+ { taskId }
16692
+ )
15920
16693
  );
16694
+ await this.finish(taskId);
15921
16695
  return;
15922
16696
  }
15923
- }
15924
- const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await active.messageOutbox.activate(taskId, session.sessionRef);
15925
- if (this.pendingCancelled.has(taskId)) {
15926
- const reason = this.pendingCancelled.get(taskId);
15927
- this.pendingCancelled.delete(taskId);
16697
+ this.deps.send(createEnvelope("task.started", {}, { taskId }));
15928
16698
  agentLeaseTransferred = agentBinding !== void 0;
16699
+ this.inFlightBlobAborts.delete(taskId);
16700
+ this.startupOwners.delete(taskId);
15929
16701
  this.tasks.set(taskId, active);
15930
- this.reserveSemanticTerminal(active);
15931
- try {
15932
- await session.interrupt();
15933
- } catch {
16702
+ this.pendingMessageTasks.delete(taskId);
16703
+ if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
16704
+ this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
15934
16705
  }
15935
- await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
15936
- await this.persistAgentTerminalEvidence(active, "cancelled", reason);
15937
- this.deps.send(
15938
- createEnvelope(
15939
- "task.cancelled",
15940
- { reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
15941
- { taskId }
15942
- )
15943
- );
15944
- await this.finish(taskId);
15945
- return;
15946
- }
15947
- this.deps.send(createEnvelope("task.started", {}, { taskId }));
15948
- agentLeaseTransferred = agentBinding !== void 0;
15949
- this.inFlightBlobAborts.delete(taskId);
15950
- this.tasks.set(taskId, active);
15951
- this.pendingMessageTasks.delete(taskId);
15952
- if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
15953
- this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
15954
- }
15955
- if (payload.limits?.maxDurationMs !== void 0) {
15956
- this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
15957
- }
15958
- void this.pump(active);
15959
- if (agentBinding === void 0) {
15960
- void this.deps.sessionWorkspaces.record(session.sessionRef, {
15961
- workspaceDir,
15962
- runtimeSessionId: session.sessionRef,
15963
- ...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
15964
- }).catch(() => {
15965
- });
15966
- }
15967
- if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
15968
- void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
16706
+ if (payload.limits?.maxDurationMs !== void 0) {
16707
+ this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
16708
+ }
16709
+ void this.pump(active);
16710
+ if (agentBinding === void 0) {
16711
+ void this.deps.sessionWorkspaces.record(session.sessionRef, {
16712
+ workspaceDir,
16713
+ runtimeSessionId: session.sessionRef,
16714
+ ...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
16715
+ }).catch(() => {
16716
+ });
16717
+ }
16718
+ if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
16719
+ void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
16720
+ });
16721
+ }
16722
+ } catch (error) {
16723
+ if (this.tasks.has(taskId)) throw error;
16724
+ const reason = `runtime activation failed: ${errorMessage6(error)}`;
16725
+ if (agentBinding === void 0) await this.fail(taskId, reason, false);
16726
+ else await this.failClaimedAgent(taskId, reason, false, {
16727
+ binding: agentBinding,
16728
+ runtimeId: pick.descriptor.id,
16729
+ sessionRef: session.sessionRef
15969
16730
  });
15970
16731
  }
15971
16732
  } finally {
16733
+ clearTimeout(startupTimer);
16734
+ releaseReservation();
16735
+ if (this.startupOwners.has(taskId)) {
16736
+ agentLeaseTransferred = true;
16737
+ await this.disposeStartupOwner(taskId);
16738
+ }
15972
16739
  if (!agentLeaseTransferred) {
15973
16740
  this.pendingMessageTasks.delete(taskId);
15974
16741
  this.revokeAgentMessageContext(taskId);
@@ -15980,6 +16747,7 @@ var TaskRunner = class {
15980
16747
  }
15981
16748
  this.inFlightBlobAborts.delete(taskId);
15982
16749
  this.inFlightOffers.delete(taskId);
16750
+ this.claimedHarnesses.delete(taskId);
15983
16751
  }
15984
16752
  }
15985
16753
  withAgentMessageMcp(existing, taskId, requirement) {
@@ -16249,7 +17017,13 @@ var TaskRunner = class {
16249
17017
  maxPendingBytes: 4 * 1024 * 1024
16250
17018
  });
16251
17019
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16252
- const activated = await outbox.activate(active.taskId, active.session.sessionRef);
17020
+ const activated = await this.activateAgentMessage(
17021
+ outbox,
17022
+ active.taskId,
17023
+ active.session.sessionRef,
17024
+ () => this.canPublishAgentMessage(active),
17025
+ () => active.messageSendAttempted === true
17026
+ );
16253
17027
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16254
17028
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
16255
17029
  this.sendAgentMessageRecord(outbox, activated);
@@ -16356,7 +17130,16 @@ var TaskRunner = class {
16356
17130
  }
16357
17131
  let bytes;
16358
17132
  try {
16359
- bytes = await opened.handle.readFile();
17133
+ const limits = this.deps.artifactLimits ?? DEFAULT_ARTIFACT_LIMITS;
17134
+ const remaining = limits.maxTaskBytes - (active.artifactBytesSoFar ?? 0);
17135
+ bytes = await readArtifactBytes(
17136
+ opened.handle,
17137
+ Math.min(limits.maxFileBytes, remaining),
17138
+ active.blobAbort.signal,
17139
+ (count) => {
17140
+ active.artifactBytesSoFar = (active.artifactBytesSoFar ?? 0) + count;
17141
+ }
17142
+ );
16360
17143
  } catch (err) {
16361
17144
  this.reportArtifactError(active, name, `failed to read artifact "${name}": ${errorMessage6(err)}`);
16362
17145
  return;
@@ -16364,6 +17147,7 @@ var TaskRunner = class {
16364
17147
  await opened.handle.close().catch(() => {
16365
17148
  });
16366
17149
  }
17150
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16367
17151
  if (bytes.length <= MAX_INLINE_ARTIFACT_BYTES) {
16368
17152
  const inline = bytes.toString("base64");
16369
17153
  if (new TextEncoder().encode(inline).length <= MAX_INLINE_ARTIFACT_BYTES) {
@@ -16375,6 +17159,7 @@ var TaskRunner = class {
16375
17159
  const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
16376
17160
  signal: active.blobAbort.signal
16377
17161
  });
17162
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16378
17163
  this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
16379
17164
  } catch (err) {
16380
17165
  this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage6(err)}`);
@@ -16382,12 +17167,17 @@ var TaskRunner = class {
16382
17167
  }
16383
17168
  /** 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. */
16384
17169
  reportArtifactError(active, name, reason) {
17170
+ if (active.blobAbort.signal.aborted || active.beingTornDown) return;
16385
17171
  console.error(`[byok/client] artifact "${name}" for task ${active.taskId} dropped: ${reason}`);
16386
17172
  active.batcher.push({ type: "error", message: reason });
16387
17173
  }
16388
17174
  async handleCancel(taskId, reason) {
16389
17175
  const active = this.tasks.get(taskId);
16390
17176
  if (!active) {
17177
+ if (this.startupOwners.has(taskId) && !this.inFlightOffers.has(taskId)) {
17178
+ await this.disposeStartupOwner(taskId);
17179
+ return;
17180
+ }
16391
17181
  this.setPendingCancelled(taskId, reason);
16392
17182
  this.inFlightBlobAborts.get(taskId)?.abort();
16393
17183
  return;
@@ -16401,10 +17191,7 @@ var TaskRunner = class {
16401
17191
  return;
16402
17192
  }
16403
17193
  active.blobAbort.abort();
16404
- try {
16405
- await active.session.interrupt();
16406
- } catch {
16407
- }
17194
+ await this.interruptBounded(active.session);
16408
17195
  await this.observeGit(active, "salvage");
16409
17196
  await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16410
17197
  this.deps.send(
@@ -16846,10 +17633,7 @@ var TaskRunner = class {
16846
17633
  await active.semanticTerminalSettled;
16847
17634
  return;
16848
17635
  }
16849
- try {
16850
- await active.session.interrupt();
16851
- } catch {
16852
- }
17636
+ await this.interruptBounded(active.session);
16853
17637
  await this.observeGit(active, "salvage");
16854
17638
  await this.persistAgentTerminalEvidence(active, "failed", reason ?? "rejected");
16855
17639
  this.deps.send(
@@ -16884,7 +17668,7 @@ var TaskRunner = class {
16884
17668
  this.deps.send(
16885
17669
  createEnvelope(
16886
17670
  "task.fail",
16887
- active === void 0 ? { reason, retryable } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
17671
+ active === void 0 ? { reason, retryable, ...terminalIdentity(this.claimedHarnesses.get(taskId)) } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16888
17672
  { taskId }
16889
17673
  )
16890
17674
  );
@@ -16898,6 +17682,7 @@ var TaskRunner = class {
16898
17682
  * the wire and handleOffer's finally block releases the lease.
16899
17683
  */
16900
17684
  async failClaimedAgent(taskId, reason, retryable, context) {
17685
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
16901
17686
  const agentRef = context.binding.resolution.agentRef;
16902
17687
  const result = await this.retryAgentTerminalEvidence(
16903
17688
  () => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
@@ -16923,7 +17708,7 @@ var TaskRunner = class {
16923
17708
  }
16924
17709
  this.deps.send(createEnvelope(
16925
17710
  "task.fail",
16926
- { reason, retryable, agentRef },
17711
+ { reason, retryable, ...terminalIdentity(context.runtimeId, agentRef) },
16927
17712
  { taskId }
16928
17713
  ));
16929
17714
  return result.ok;
@@ -16941,7 +17726,8 @@ var TaskRunner = class {
16941
17726
  terminalInferenceUsagePayload(active) {
16942
17727
  const release = this.deps.localAgentRelease;
16943
17728
  const runtimeId = active.adapter.descriptor.id;
16944
- if (release === void 0 || active.lastUsage === void 0 || !isKnownRuntimeId(runtimeId)) return {};
17729
+ if (!isKnownRuntimeId(runtimeId)) return {};
17730
+ if (release === void 0 || active.lastUsage === void 0) return {};
16945
17731
  const nowMs = Date.now();
16946
17732
  const durationMs = terminalUsageNumber(nowMs - active.startedAtMs, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS);
16947
17733
  const promptTokens = terminalUsageNumber(active.lastUsage?.inputTokens, TERMINAL_INFERENCE_USAGE_MAX_TOKENS);
@@ -16959,7 +17745,7 @@ var TaskRunner = class {
16959
17745
  }
16960
17746
  /** Exact Agent identity projection for claim/terminal wire payloads. */
16961
17747
  agentTerminalPayload(active) {
16962
- return active.agentRef === void 0 ? {} : { agentRef: active.agentRef };
17748
+ return terminalIdentity(active.adapter.descriptor.id, active.agentRef);
16963
17749
  }
16964
17750
  /**
16965
17751
  * additive-minor (`task.complete.document`): the whole daemon-side gate
@@ -17116,6 +17902,14 @@ var TaskRunner = class {
17116
17902
  }
17117
17903
  /** Persist Agent terminal truth before wire when local storage is available. */
17118
17904
  async persistAgentTerminalEvidence(active, cause, reason) {
17905
+ if (cause === "cancelled" || cause === "failed" && !active.messageSendAttempted) {
17906
+ try {
17907
+ await active.messageOutbox?.revoke(active.taskId);
17908
+ } catch (error) {
17909
+ active.resolveSemanticTerminalSettled?.(false);
17910
+ throw error;
17911
+ }
17912
+ }
17119
17913
  active.terminalCause = cause;
17120
17914
  active.terminalReason = reason;
17121
17915
  if (active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0) {
@@ -17178,7 +17972,19 @@ var TaskRunner = class {
17178
17972
  error
17179
17973
  });
17180
17974
  }
17181
- async finish(taskId) {
17975
+ async retryTerminalFinalization(taskId) {
17976
+ const active = this.tasks.get(taskId);
17977
+ if (active?.finalizationStarted) await this.finish(taskId);
17978
+ }
17979
+ finalizationAttempts = /* @__PURE__ */ new Map();
17980
+ finish(taskId) {
17981
+ const existing = this.finalizationAttempts.get(taskId);
17982
+ if (existing) return existing;
17983
+ const attempt = this.finishOnce(taskId).finally(() => this.finalizationAttempts.delete(taskId));
17984
+ this.finalizationAttempts.set(taskId, attempt);
17985
+ return attempt;
17986
+ }
17987
+ async finishOnce(taskId) {
17182
17988
  const active = this.tasks.get(taskId);
17183
17989
  if (!active) return true;
17184
17990
  if (!active.finalizationStarted) {
@@ -17229,6 +18035,13 @@ var TaskRunner = class {
17229
18035
  return false;
17230
18036
  }
17231
18037
  if (this.tasks.get(taskId) !== active) return true;
18038
+ try {
18039
+ await this.deps.awaitTerminalCommit?.(taskId);
18040
+ } catch {
18041
+ active.resolveSemanticTerminalSettled?.(false);
18042
+ return false;
18043
+ }
18044
+ if (this.tasks.get(taskId) !== active) return true;
17232
18045
  if (active.agentBinding !== void 0 && active.agentRef !== void 0 && active.agentHandoff !== void 0) {
17233
18046
  const agentRef = active.agentRef;
17234
18047
  const handoff = active.agentHandoff;
@@ -17340,7 +18153,7 @@ var TaskRunner = class {
17340
18153
  * is device-specific (which runtimes happen to be installed here), so a
17341
18154
  * different device's installed runtime set might satisfy it.
17342
18155
  */
17343
- async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets) {
18156
+ async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets, signal) {
17344
18157
  const allowlist = this.deps.runtimeAllowlist;
17345
18158
  if (requestedRuntime) {
17346
18159
  if (allowlist && !allowlist.includes(requestedRuntime)) {
@@ -17369,8 +18182,8 @@ var TaskRunner = class {
17369
18182
  retryable: false
17370
18183
  };
17371
18184
  }
17372
- const detected = await adapter.detect();
17373
- if (!detected.present) {
18185
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18186
+ if (detected.kind !== "available") {
17374
18187
  return {
17375
18188
  ok: false,
17376
18189
  reason: `runtime "${requestedRuntime}" is not installed/available on this device`,
@@ -17385,8 +18198,8 @@ var TaskRunner = class {
17385
18198
  const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
17386
18199
  if (!adapterSupportsMode(descriptor, policyMode)) continue;
17387
18200
  if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) continue;
17388
- const detected = await adapter.detect();
17389
- if (detected.present) return { ok: true, adapter, descriptor };
18201
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18202
+ if (detected.kind === "available") return { ok: true, adapter, descriptor };
17390
18203
  }
17391
18204
  return {
17392
18205
  ok: false,
@@ -17550,10 +18363,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17550
18363
  constructor(homeDir, spoolPath) {
17551
18364
  this.homeDir = homeDir;
17552
18365
  this.spoolPath = spoolPath;
18366
+ this.file = new DurableJsonlFile(spoolPath);
17553
18367
  }
17554
18368
  homeDir;
17555
18369
  spoolPath;
17556
18370
  pending = /* @__PURE__ */ new Map();
18371
+ file;
17557
18372
  nextCursor = 1;
17558
18373
  logEntries = 0;
17559
18374
  writeTail = Promise.resolve();
@@ -17562,6 +18377,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17562
18377
  await ensureSecureDir(directory);
17563
18378
  const spool = new _AgentReliableSpool(homeDir, path3__default.join(directory, AGENT_RELIABLE_SPOOL_FILENAME));
17564
18379
  await spool.load();
18380
+ await spool.file.confirmRecovered();
17565
18381
  return spool;
17566
18382
  }
17567
18383
  get pendingEvents() {
@@ -17581,6 +18397,25 @@ var AgentReliableSpool = class _AgentReliableSpool {
17581
18397
  const payloadJson = stableRecordJson(input.payload);
17582
18398
  const byteCount = Buffer.byteLength(payloadJson, "utf8");
17583
18399
  if (byteCount <= 0) throw new AgentReliableSpoolError("reliable payload must have positive serialized bytes");
18400
+ const eventId = input.eventId ?? randomUUID();
18401
+ const existing = this.pending.get(eventId);
18402
+ if (existing !== void 0) {
18403
+ if (!sameRecord(existing, {
18404
+ wireType: "agent.egress.reliable",
18405
+ agentRef: input.agentRef,
18406
+ tenantId: input.tenantId,
18407
+ policyRevision: input.policyRevision,
18408
+ eventId,
18409
+ cursor: existing.cursor,
18410
+ payloadHash: contentHash(payloadJson),
18411
+ byteCount,
18412
+ sessionRef: input.sessionRef,
18413
+ taskId: input.taskId
18414
+ }) || stableRecordJson(existing.payload) !== payloadJson || input.createdAt !== void 0 && input.createdAt !== existing.createdAt) {
18415
+ throw new AgentReliableSpoolError(`reliable eventId ${eventId} differs from its existing record`);
18416
+ }
18417
+ return existing;
18418
+ }
17584
18419
  if (this.pending.size >= policy.reliable.maxPendingEventsPerAgent) {
17585
18420
  throw new AgentReliableQuotaError("quota_exceeded", "reliable per-Agent event quota is exhausted");
17586
18421
  }
@@ -17590,8 +18425,6 @@ var AgentReliableSpool = class _AgentReliableSpool {
17590
18425
  if (tenantPendingBytes + byteCount > policy.reliable.maxPendingBytesPerTenant) {
17591
18426
  throw new AgentReliableQuotaError("quota_exceeded", "reliable tenant byte quota is exhausted");
17592
18427
  }
17593
- const eventId = input.eventId ?? randomUUID();
17594
- if (this.pending.has(eventId)) throw new AgentReliableSpoolError(`reliable eventId ${eventId} already exists`);
17595
18428
  const record3 = Object.freeze({
17596
18429
  schema: 1,
17597
18430
  wireType: "agent.egress.reliable",
@@ -17700,6 +18533,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17700
18533
  if (error.code === "ENOENT") return;
17701
18534
  throw error;
17702
18535
  }
18536
+ if (raw.length > 0 && !raw.endsWith("\n")) throw new AgentReliableSpoolError("durable JSONL log has an incomplete trailing frame; preserve the file for explicit repair");
17703
18537
  for (const line of raw.split("\n")) {
17704
18538
  if (line.length === 0) continue;
17705
18539
  let entry;
@@ -17722,19 +18556,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17722
18556
  }
17723
18557
  }
17724
18558
  async appendEntry(entry) {
17725
- const handle = await promises.open(this.spoolPath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
17726
- try {
17727
- await handle.write(`${stableRecordJson(entry)}
17728
- `, void 0, "utf8");
17729
- await handle.sync();
17730
- this.logEntries += 1;
17731
- } finally {
17732
- await handle.close();
17733
- }
18559
+ await this.file.append(stableRecordJson(entry));
18560
+ this.logEntries += 1;
17734
18561
  }
17735
18562
  async compact() {
17736
18563
  const entries = this.records().map((record3) => ({ schema: 1, kind: "append", record: record3 }));
17737
- await atomicWriteFile(this.spoolPath, entries.map((entry) => stableRecordJson(entry)).join(entries.length > 0 ? "\n" : ""), { mode: 384 });
18564
+ await this.file.replace(entries.map((entry) => stableRecordJson(entry)));
17738
18565
  this.logEntries = entries.length;
17739
18566
  }
17740
18567
  async exclusive(fn) {
@@ -17745,6 +18572,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17745
18572
  });
17746
18573
  await prior;
17747
18574
  try {
18575
+ this.file.assertWritable();
17748
18576
  return await fn();
17749
18577
  } finally {
17750
18578
  release();
@@ -19119,17 +19947,26 @@ function isRuntimeId(id) {
19119
19947
  return id === "pi" || id === "claude" || id === "codex";
19120
19948
  }
19121
19949
  async function detectRuntimes(adapters) {
19122
- const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: await adapter.detect() })));
19950
+ const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: validateRuntimeDetectResult(await adapter.detect()) })));
19123
19951
  const runtimes = [];
19952
+ const harnesses = [];
19124
19953
  for (const { adapter, detected } of detections) {
19125
- if (!detected.present || !isRuntimeId(adapter.descriptor.id)) continue;
19954
+ if (detected.kind !== "available") continue;
19955
+ if (!isRuntimeId(adapter.descriptor.id)) {
19956
+ harnesses.push({
19957
+ id: HarnessIdSchema.parse(adapter.descriptor.id),
19958
+ ...detected.version === void 0 ? {} : { version: detected.version },
19959
+ capabilities: toRuntimeInfoCapabilities(adapter.descriptor.capabilities)
19960
+ });
19961
+ continue;
19962
+ }
19126
19963
  const info = { id: adapter.descriptor.id };
19127
19964
  if (detected.version !== void 0) info.version = detected.version;
19128
19965
  if (detected.authPresent !== void 0) info.authPresent = detected.authPresent;
19129
19966
  info.capabilities = toRuntimeInfoCapabilities(adapter.descriptor.capabilities);
19130
19967
  runtimes.push(info);
19131
19968
  }
19132
- return runtimes;
19969
+ return { runtimes, harnesses };
19133
19970
  }
19134
19971
  function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentOnly = false, agentHomeProjectionConfigured = false, agentEgressConfigured = false, contentReadPolicies, providerProfileBindingConfigured = false) {
19135
19972
  const flags = [];
@@ -19219,7 +20056,7 @@ function buildAdapter(id, config) {
19219
20056
  resolveApprovalMcpBin: () => resolveApprovalMcpBin(config.sdkHelperHost)
19220
20057
  });
19221
20058
  case "codex":
19222
- return new CodexAdapter();
20059
+ return new CodexAdapter({ sdkHelperHost: config.sdkHelperHost });
19223
20060
  }
19224
20061
  }
19225
20062
  function buildDefaultAdapters(config) {
@@ -19296,6 +20133,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19296
20133
  const localAgentRelease = resolveLocalAgentReleaseIdentity(config.localAgentRelease);
19297
20134
  const toolsetRegistry = new McpToolsetRegistry(config.mcpToolsets);
19298
20135
  validatePiByokLauncherConfig(config.piByokLauncher);
20136
+ if (config.startupTimeoutMs !== void 0 && (!Number.isSafeInteger(config.startupTimeoutMs) || config.startupTimeoutMs <= 0)) throw new Error("startupTimeoutMs must be a positive safe integer");
20137
+ if (config.artifactLimits !== void 0) {
20138
+ for (const value of [config.artifactLimits.maxFileBytes, config.artifactLimits.maxTaskBytes]) {
20139
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error("artifactLimits must contain positive safe integer byte budgets");
20140
+ }
20141
+ }
19299
20142
  if (config.maxTaskOutputBytes !== void 0 && !(config.maxTaskOutputBytes > 0)) {
19300
20143
  throw new Error(
19301
20144
  `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".`
@@ -19425,7 +20268,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19425
20268
  pressureEngine = ownedPressureEngine;
19426
20269
  }
19427
20270
  }
19428
- let journalTerminalTail = Promise.resolve();
20271
+ const terminalCommits = new TerminalCommitQueue((taskId) => {
20272
+ connection?.retryTaskReceipts(taskId);
20273
+ void runner?.retryTerminalFinalization(taskId).catch((error) => {
20274
+ console.error("[byok/client] terminal finalization still requires recovery", error);
20275
+ });
20276
+ });
19429
20277
  const observer = new DaemonObserver();
19430
20278
  const approvalRegistry = new ApprovalRegistry();
19431
20279
  let connection;
@@ -19560,6 +20408,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19560
20408
  await runLifecycleMutation(startUnderLease);
19561
20409
  }
19562
20410
  async function startUnderLease() {
20411
+ terminalCommits.resume();
19563
20412
  if (!daemonOwnerLease) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
19564
20413
  try {
19565
20414
  let record3;
@@ -19611,7 +20460,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19611
20460
  const terminal = createEnvelope("task.fail", {
19612
20461
  reason: "daemon_interrupted",
19613
20462
  retryable: false,
19614
- ...payload.agentRef === void 0 ? {} : { agentRef: payload.agentRef }
20463
+ ...terminalIdentity(task.claimedRuntime, payload.agentRef),
20464
+ recovery: { kind: "daemon_interrupted", offerId: offer.id }
19615
20465
  }, { taskId: task.taskId });
19616
20466
  const bytes = encodeEnvelope(terminal);
19617
20467
  await activeJournal.recordTerminal({
@@ -19639,7 +20489,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19639
20489
  await gitWorkspaceStore?.reconcile();
19640
20490
  }
19641
20491
  blobLifecycleAbort = new AbortController();
19642
- const [runtimes, blobClient] = await Promise.all([
20492
+ const [{ runtimes, harnesses }, blobClient] = await Promise.all([
19643
20493
  detectRuntimes(adapters),
19644
20494
  Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
19645
20495
  ]);
@@ -19660,6 +20510,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19660
20510
  tenantId: record3.tenantId,
19661
20511
  deviceId: record3.deviceId
19662
20512
  }) : void 0;
20513
+ capabilities.push("custom-harness");
19663
20514
  const sendSanitizedEnvelope = (envelope) => {
19664
20515
  const terminalKind = terminalKindOf(envelope.type);
19665
20516
  if (!activeJournal || !journalIdentity || terminalKind === void 0 || envelope.task_id === void 0) {
@@ -19669,7 +20520,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19669
20520
  }
19670
20521
  const taskId = envelope.task_id;
19671
20522
  const bytes = encodeEnvelope(envelope);
19672
- journalTerminalTail = journalTerminalTail.catch(() => void 0).then(async () => {
20523
+ terminalCommits.enqueue(taskId, async () => {
19673
20524
  try {
19674
20525
  await activeJournal.recordTerminal({
19675
20526
  taskId,
@@ -19697,7 +20548,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19697
20548
  const failure = createEnvelope("task.fail", {
19698
20549
  reason: "terminal_result_too_large",
19699
20550
  retryable: false,
19700
- ...offerPayload.agentRef === void 0 ? {} : { agentRef: offerPayload.agentRef }
20551
+ ...terminalIdentity(task.claimedRuntime, offerPayload.agentRef)
19701
20552
  }, { taskId });
19702
20553
  const failureBytes = encodeEnvelope(failure);
19703
20554
  await activeJournal.recordTerminal({
@@ -19715,8 +20566,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19715
20566
  observer.handleOutboundEnvelope(decoded);
19716
20567
  connection?.send(decoded);
19717
20568
  }
19718
- }).catch((error) => {
19719
- console.error("[byok/client] terminal durability failed; execution remains unsettled", error);
19720
20569
  });
19721
20570
  };
19722
20571
  const sendEnvelope = (candidate) => {
@@ -19798,7 +20647,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19798
20647
  // Finding F5(a): see TaskRunnerDeps.shutdownInterruptTimeoutMs's own doc comment.
19799
20648
  shutdownInterruptTimeoutMs: overrides.shutdown?.taskInterruptTimeoutMs,
19800
20649
  // M5 batch-3 (workstream 2): see DaemonConfig.maxTaskOutputBytes's own doc comment — already validated above.
20650
+ awaitTerminalCommit: (taskId) => terminalCommits.receipt(taskId),
19801
20651
  maxTaskOutputBytes: config.maxTaskOutputBytes,
20652
+ artifactLimits: config.artifactLimits,
20653
+ startupTimeoutMs: config.startupTimeoutMs,
19802
20654
  // See DaemonConfig.maxInlineEventBytes's own doc comment — already validated above.
19803
20655
  maxInlineEventBytes: config.maxInlineEventBytes,
19804
20656
  // additive-minor (`task.complete.document`): passed through verbatim,
@@ -19846,6 +20698,20 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19846
20698
  if (config.agentHome !== void 0 && config.agentEgress !== void 0) {
19847
20699
  await runner.recoverAgentMessageOutboxes(path3__default.join(config.agentHome.hostStorageRoot, "agents"));
19848
20700
  }
20701
+ const replayRecoveryTerminals = async (taskId) => {
20702
+ if (!activeJournal || !journalIdentity) return;
20703
+ for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
20704
+ if (terminal.truthState !== "pending" || taskId !== void 0 && terminal.taskId !== taskId) continue;
20705
+ if (runner?.hasPendingRecoveredAgentMessage(terminal.taskId)) continue;
20706
+ const envelope = decodeEnvelope(terminal.bytes);
20707
+ if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
20708
+ throw new Error("durable terminal execution binding mismatch");
20709
+ }
20710
+ overrides.executionRecoveryFault?.("terminal:before-send");
20711
+ observer.handleOutboundEnvelope(envelope);
20712
+ connection?.send(envelope);
20713
+ }
20714
+ };
19849
20715
  const handleAgentHomeProjectionEnvelope = async (envelope) => {
19850
20716
  if (envelope.type !== "agent.home.projection") return false;
19851
20717
  if (agentHomeManager === void 0 || agentHomeProjectionCompletion === void 0) {
@@ -19980,6 +20846,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19980
20846
  capabilities,
19981
20847
  clientVersion: localAgentRelease.version,
19982
20848
  runtimes,
20849
+ harnesses,
19983
20850
  getConfiguredToolsets: () => toolsetRegistry.snapshot().configuredToolsets,
19984
20851
  auth,
19985
20852
  cursorStore,
@@ -20021,12 +20888,22 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20021
20888
  activePressureEngine?.assertAckCriticalAllowed();
20022
20889
  await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
20023
20890
  if (isTaskOfferType(envelope.type) && envelope.task_id !== void 0) {
20891
+ if (terminalCommits.hasPending(envelope.task_id)) {
20892
+ await terminalCommits.retry(envelope.task_id);
20893
+ return;
20894
+ }
20024
20895
  const task = await activeJournal.readTask(envelope.task_id, journalIdentity);
20025
20896
  if (!task) throw new Error("durable offer has no execution record");
20026
- if (task.localState !== "received") return;
20897
+ if (task.localState !== "received") {
20898
+ await terminalCommits.retry(envelope.task_id);
20899
+ return;
20900
+ }
20027
20901
  }
20028
20902
  await runner?.handleEnvelope(envelope);
20029
- await journalTerminalTail;
20903
+ if (envelope.type === "agent.message.disposition") {
20904
+ await replayRecoveryTerminals(envelope.task_id);
20905
+ }
20906
+ if (envelope.task_id !== void 0) await terminalCommits.receipt(envelope.task_id);
20030
20907
  } : (envelope) => {
20031
20908
  if (tenantRebinding) {
20032
20909
  return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
@@ -20074,18 +20951,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20074
20951
  });
20075
20952
  }
20076
20953
  });
20077
- if (activeJournal && journalIdentity) {
20078
- for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
20079
- if (terminal.truthState !== "pending") continue;
20080
- const envelope = decodeEnvelope(terminal.bytes);
20081
- if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
20082
- throw new Error("durable terminal execution binding mismatch");
20083
- }
20084
- overrides.executionRecoveryFault?.("terminal:before-send");
20085
- observer.handleOutboundEnvelope(envelope);
20086
- connection.send(envelope);
20087
- }
20088
- }
20954
+ await replayRecoveryTerminals();
20089
20955
  await connection.start();
20090
20956
  await connection.waitForConnection();
20091
20957
  runner.retryRecoveredAgentMessages();
@@ -20200,7 +21066,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20200
21066
  pendingLateMutationBarriers.add(observedActiveTeardown);
20201
21067
  void observedActiveTeardown.finally(() => pendingLateMutationBarriers.delete(observedActiveTeardown)).catch(() => void 0);
20202
21068
  }
20203
- const journalTailSettled = journal ? await attempt(() => journalTerminalTail, true) : true;
21069
+ const journalTailSettled = journal ? await attempt(() => terminalCommits.stop(), true) : true;
20204
21070
  const maintenanceSettled = await attempt(() => maintenanceStopped, true);
20205
21071
  const drainTimeoutMs = opts.drainTimeoutMs ?? overrides.shutdown?.outboxDrainTimeoutMs ?? DEFAULT_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT_MS;
20206
21072
  const connectionStopped = await attempt(() => connection?.stop(drainTimeoutMs), true);
@@ -20720,6 +21586,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20720
21586
  revoked: connection?.isRevoked() ?? auth.isRevoked(),
20721
21587
  deviceId: auth.deviceId,
20722
21588
  activeTaskCount: runner?.activeTaskCount ?? 0,
21589
+ pendingTerminalCommits: terminalCommits.pendingCount,
20723
21590
  branding: config.branding,
20724
21591
  operationalHealth: operationalHealth.snapshot(),
20725
21592
  toolsets: toolsetRegistry.status(),
@@ -21185,8 +22052,8 @@ var TruthMemoryClient = class {
21185
22052
  #requestId;
21186
22053
  #allowedObjectDownloadOrigins;
21187
22054
  async listManifest(query = {}) {
21188
- const path38 = manifestPath(query);
21189
- const response = await this.#proofFetch(path38, {
22055
+ const path40 = manifestPath(query);
22056
+ const response = await this.#proofFetch(path40, {
21190
22057
  method: "GET",
21191
22058
  operation: "truth.list",
21192
22059
  resource: "records",
@@ -21293,9 +22160,9 @@ var TruthMemoryClient = class {
21293
22160
  }
21294
22161
  async #write(kind, recordKey, requestId, payload, expectedPrimary, expectedSnapshots) {
21295
22162
  assertDistinctExpectedWrites([expectedPrimary, ...expectedSnapshots]);
21296
- const path38 = recordPath(kind, recordKey);
22163
+ const path40 = recordPath(kind, recordKey);
21297
22164
  const body = new TextEncoder().encode(JSON.stringify(payload));
21298
- const response = await this.#proofFetch(path38, {
22165
+ const response = await this.#proofFetch(path40, {
21299
22166
  method: "PUT",
21300
22167
  operation: "truth.write",
21301
22168
  resource: `${kind}/${recordKey}`,
@@ -21321,8 +22188,8 @@ var TruthMemoryClient = class {
21321
22188
  };
21322
22189
  }
21323
22190
  async #readVerified(listed) {
21324
- const path38 = recordPath(listed.kind, listed.recordKey);
21325
- const response = await this.#proofFetch(path38, {
22191
+ const path40 = recordPath(listed.kind, listed.recordKey);
22192
+ const response = await this.#proofFetch(path40, {
21326
22193
  method: "GET",
21327
22194
  operation: "truth.read",
21328
22195
  resource: `${listed.kind}/${listed.recordKey}`,
@@ -21383,16 +22250,16 @@ var TruthMemoryClient = class {
21383
22250
  }
21384
22251
  return { ...listed, bytes };
21385
22252
  }
21386
- async #proofFetch(path38, request) {
22253
+ async #proofFetch(path40, request) {
21387
22254
  const proof = await this.options.signer.sign({
21388
22255
  method: request.method,
21389
- path: path38,
22256
+ path: path40,
21390
22257
  operation: request.operation,
21391
22258
  resource: request.resource,
21392
22259
  requestId: request.requestId,
21393
22260
  body: request.body
21394
22261
  });
21395
- const response = await this.#fetch(new URL(path38, this.#base), {
22262
+ const response = await this.#fetch(new URL(path40, this.#base), {
21396
22263
  method: request.method,
21397
22264
  headers: {
21398
22265
  ...request.headers,
@@ -21403,7 +22270,7 @@ var TruthMemoryClient = class {
21403
22270
  if (!response.ok) {
21404
22271
  throw new TruthMemoryClientError(
21405
22272
  "truth_http_failed",
21406
- `truth request ${request.method} ${path38} failed with HTTP ${response.status}`,
22273
+ `truth request ${request.method} ${path40} failed with HTTP ${response.status}`,
21407
22274
  response.status
21408
22275
  );
21409
22276
  }
@@ -21695,7 +22562,7 @@ ${args.map((a) => ` ${plistString(a)}`).join("\n")}
21695
22562
  }
21696
22563
  function createLaunchdLifecycle(def, deps = {}) {
21697
22564
  const run = deps.run ?? defaultRunner;
21698
- const fs31 = deps.fs ?? promises;
22565
+ const fs33 = deps.fs ?? promises;
21699
22566
  const homedir = deps.homedir ?? (() => os4__default.homedir());
21700
22567
  const getuid = deps.getuid ?? (() => {
21701
22568
  if (typeof process.getuid !== "function") {
@@ -21709,7 +22576,7 @@ function createLaunchdLifecycle(def, deps = {}) {
21709
22576
  const serviceTarget = () => `${domainTarget()}/${label}`;
21710
22577
  async function fileExists(p) {
21711
22578
  try {
21712
- await fs31.stat(p);
22579
+ await fs33.stat(p);
21713
22580
  return true;
21714
22581
  } catch {
21715
22582
  return false;
@@ -21717,9 +22584,9 @@ function createLaunchdLifecycle(def, deps = {}) {
21717
22584
  }
21718
22585
  async function writePlist(program) {
21719
22586
  const xml = generateLaunchdPlist({ label, program, logDir: def.logDir });
21720
- await fs31.mkdir(path3__default.dirname(plistPath()), { recursive: true });
21721
- await fs31.mkdir(def.logDir, { recursive: true });
21722
- await fs31.writeFile(plistPath(), xml, "utf8");
22587
+ await fs33.mkdir(path3__default.dirname(plistPath()), { recursive: true });
22588
+ await fs33.mkdir(def.logDir, { recursive: true });
22589
+ await fs33.writeFile(plistPath(), xml, "utf8");
21723
22590
  }
21724
22591
  async function install(opts = {}) {
21725
22592
  await writePlist(opts.program ?? def.program);
@@ -21730,7 +22597,7 @@ function createLaunchdLifecycle(def, deps = {}) {
21730
22597
  }
21731
22598
  async function uninstall() {
21732
22599
  await runIdempotent(run, "launchctl", ["bootout", serviceTarget()], "launchctl bootout", LAUNCHD_NOT_LOADED);
21733
- await fs31.rm(plistPath(), { force: true });
22600
+ await fs33.rm(plistPath(), { force: true });
21734
22601
  }
21735
22602
  async function start() {
21736
22603
  if (!await fileExists(plistPath())) {
@@ -21814,14 +22681,14 @@ WantedBy=default.target
21814
22681
  }
21815
22682
  function createSystemdLifecycle(def, deps = {}) {
21816
22683
  const run = deps.run ?? defaultRunner;
21817
- const fs31 = deps.fs ?? promises;
22684
+ const fs33 = deps.fs ?? promises;
21818
22685
  const homedir = deps.homedir ?? (() => os4__default.homedir());
21819
22686
  const name = sanitizeServiceName(def.name);
21820
22687
  const unitName = `${name}.service`;
21821
22688
  const unitPath = () => path3__default.join(homedir(), ".config", "systemd", "user", unitName);
21822
22689
  async function fileExists(p) {
21823
22690
  try {
21824
- await fs31.stat(p);
22691
+ await fs33.stat(p);
21825
22692
  return true;
21826
22693
  } catch {
21827
22694
  return false;
@@ -21829,9 +22696,9 @@ function createSystemdLifecycle(def, deps = {}) {
21829
22696
  }
21830
22697
  async function writeUnit(program) {
21831
22698
  const unit = generateSystemdUnit({ name, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
21832
- await fs31.mkdir(path3__default.dirname(unitPath()), { recursive: true });
21833
- await fs31.mkdir(def.logDir, { recursive: true });
21834
- await fs31.writeFile(unitPath(), unit, "utf8");
22699
+ await fs33.mkdir(path3__default.dirname(unitPath()), { recursive: true });
22700
+ await fs33.mkdir(def.logDir, { recursive: true });
22701
+ await fs33.writeFile(unitPath(), unit, "utf8");
21835
22702
  }
21836
22703
  async function install(opts = {}) {
21837
22704
  await writeUnit(opts.program ?? def.program);
@@ -21840,7 +22707,7 @@ function createSystemdLifecycle(def, deps = {}) {
21840
22707
  }
21841
22708
  async function uninstall() {
21842
22709
  await runIdempotent(run, "systemctl", ["--user", "disable", "--now", unitName], "systemctl disable --now", SYSTEMD_NOT_LOADED);
21843
- await fs31.rm(unitPath(), { force: true });
22710
+ await fs33.rm(unitPath(), { force: true });
21844
22711
  await run("systemctl", ["--user", "daemon-reload"]);
21845
22712
  }
21846
22713
  async function start() {
@@ -21903,7 +22770,7 @@ ${argXml}${cwdXml}
21903
22770
  }
21904
22771
  function createWinswLifecycle(def, deps = {}) {
21905
22772
  const run = deps.run ?? defaultRunner;
21906
- const fs31 = deps.fs ?? promises;
22773
+ const fs33 = deps.fs ?? promises;
21907
22774
  const windows = def.windows;
21908
22775
  if (!windows) {
21909
22776
  throw new Error("WinSW service lifecycle requires `ServiceDefinition.windows.winswBin` (the product-bundled WinSW executable path)");
@@ -21915,7 +22782,7 @@ function createWinswLifecycle(def, deps = {}) {
21915
22782
  const xmlPath = path3__default.join(installDir, `${id}.xml`);
21916
22783
  async function fileExists(p) {
21917
22784
  try {
21918
- await fs31.stat(p);
22785
+ await fs33.stat(p);
21919
22786
  return true;
21920
22787
  } catch {
21921
22788
  return false;
@@ -21923,10 +22790,10 @@ function createWinswLifecycle(def, deps = {}) {
21923
22790
  }
21924
22791
  async function writeFiles(program) {
21925
22792
  const xml = generateWinswXml({ id, displayName: def.displayName ?? def.name, program, logDir: def.logDir });
21926
- await fs31.mkdir(installDir, { recursive: true });
21927
- await fs31.mkdir(def.logDir, { recursive: true });
21928
- await fs31.copyFile(winswBin, exePath);
21929
- await fs31.writeFile(xmlPath, xml, "utf8");
22793
+ await fs33.mkdir(installDir, { recursive: true });
22794
+ await fs33.mkdir(def.logDir, { recursive: true });
22795
+ await fs33.copyFile(winswBin, exePath);
22796
+ await fs33.writeFile(xmlPath, xml, "utf8");
21930
22797
  }
21931
22798
  async function install(opts = {}) {
21932
22799
  await writeFiles(opts.program ?? def.program);
@@ -21936,8 +22803,8 @@ function createWinswLifecycle(def, deps = {}) {
21936
22803
  async function uninstall() {
21937
22804
  await runIdempotent(run, exePath, ["stop"], "winsw stop", WINSW_NOT_INSTALLED);
21938
22805
  await runIdempotent(run, exePath, ["uninstall"], "winsw uninstall", WINSW_NOT_INSTALLED);
21939
- await fs31.rm(exePath, { force: true });
21940
- await fs31.rm(xmlPath, { force: true });
22806
+ await fs33.rm(exePath, { force: true });
22807
+ await fs33.rm(xmlPath, { force: true });
21941
22808
  }
21942
22809
  async function start() {
21943
22810
  if (!await fileExists(xmlPath)) {
@@ -21980,6 +22847,658 @@ function createServiceLifecycle(def, opts = {}) {
21980
22847
  }
21981
22848
  }
21982
22849
 
21983
- export { AGENT_CONTENT_READ_CAPABILITIES, AGENT_CONTENT_READ_CAPABILITY_ARTIFACT, AGENT_CONTENT_READ_CAPABILITY_TRANSCRIPT, AGENT_CONTENT_READ_CAPABILITY_WORKSPACE, AGENT_HOME_PROJECTION_STATE_FILE, AGENT_MEMORY_AUDIT_FILENAME, AGENT_MEMORY_OUTBOX_FILENAME, AgentHomeBusyError, AgentHomeCollisionError, AgentHomeError, AgentHomeLayout, AgentHomeLeaseCorruptError, AgentHomeLeaseManager, AgentHomeManager, AgentHomeResolutionError, AgentMemoryError, AgentMemoryRevisionConflictError, AgentRefValidationError, AgentSessionHandoffCorruptError, AgentSessionHandoffMismatchError, AgentSessionHandoffStore, AgentSessionHandoffStoreError, BYOK_SDK_HELPER_SUBCOMMAND, BlobClient, BlobRequestAbortedError, ClaudeAdapter, CodexAdapter, DEFAULT_ACK_CRITICAL_RESERVE_BYTES, DEFAULT_CLEANUP_BATCH_LIMIT, DEFAULT_HARD_BUDGET_RATIO, DEFAULT_INCREMENTAL_VACUUM_PAGES, DEFAULT_LOG_ROTATION, DEFAULT_NORMAL_COMPACTION_INTERVAL_MS, DEFAULT_PRESSURE_COMPACTION_INTERVAL_MS, DEFAULT_RETENTION_MS, DEFAULT_SOFT_BUDGET_RATIO, DaemonObserver, GitWorkspaceError, GitWorkspaceManager, GitWorkspaceStore, JOURNAL_DB_FILENAME, JOURNAL_QUARANTINE_DIRNAME, JOURNAL_TASK_REF_PREFIX, JournalClosedError, JournalCorruptError, JournalRecordTooLargeError, JournalUnavailableError, JournalUnknownTaskError, LocalStateRelocationBusyError, LocalStateRelocationError, LocalStateRelocationIntegrityError, LocalStorageEmergencyError, LocalStoragePolicyError, LocalStoragePressureEngine, LocalTeamWorkspace, LocalTeamWorkspace as LocalTeamWorkspaceService, McpToolsetDefinitionRevisionConflictError, McpToolsetRevisionConflictError, PI_PACKAGE_NAME, PiAdapter, PolicyUnsupportedError, RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON, ReplayCursorTooOldError, RuntimeDisposalFailure, RuntimeExecutionFailure, SKILL_PACKS_CAPABILITY, SKILL_PACKS_DIRNAME, SKILL_PACK_AUDIT_FILENAME, SKILL_PACK_INSTALL_ERROR_CODES, SKILL_PACK_LOCK_FILENAME, SKILL_PACK_LOCK_SCHEMA, SKILL_PACK_RESPONSE_MAX_BYTES, SecureDirHardeningError, SkillPackInstallError, SqliteLocalTaskJournal, SteerUnsupportedError, TeamTmuxViewError, TeamWorkspaceConflictError, TeamWorkspaceCorruptError, TeamWorkspaceError, TeamWorkspaceLeaseError, TeamWorkspaceNotFoundError, TeamWorkspaceQuotaError, TeamWorkspaceReceiptError, TeamWorkspaceValidationError, TruthMemoryClient, TruthMemoryClientError, UnsupportedServicePlatformError, buildIcaclsArgs, cleanupEligibleAt, cleanupOrderFor, computePressureState, createAgentHomeProjection, createAgentHomeProjectionConsumer, createDaemon, createDaemonWithAdapters, createFilesystemCleanupExecutor, createServiceLifecycle, createStatfsFreeBytesProvider, decodeTeamMemberContext, encodeTeamMemberContext, ensureSecureDir, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, readDeviceEnrollmentStatus, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
22850
+ // src/bin/runtime-probe.ts
22851
+ var ALL_RUNTIME_IDS2 = ["pi", "claude", "codex"];
22852
+ var RUNTIME_PROBE_TIMEOUT_MS = 5e3;
22853
+ var MAX_RUNTIME_ID_CHARS = 128;
22854
+ var MAX_RUNTIME_VERSION_CHARS = 256;
22855
+ var MAX_PERMISSION_MODES = 32;
22856
+ var MAX_PERMISSION_MODE_CHARS = 64;
22857
+ function boundedSingleLine(value, maxChars) {
22858
+ return value.replace(/[\r\n\t]/g, " ").slice(0, maxChars);
22859
+ }
22860
+ var RuntimeProbeTimeout = class extends Error {
22861
+ };
22862
+ async function detectWithTimeout(adapter, timeoutMs) {
22863
+ let timer;
22864
+ try {
22865
+ return await Promise.race([
22866
+ adapter.detect(),
22867
+ new Promise((_resolve, reject) => {
22868
+ timer = setTimeout(() => reject(new RuntimeProbeTimeout()), timeoutMs);
22869
+ timer.unref?.();
22870
+ })
22871
+ ]);
22872
+ } finally {
22873
+ if (timer) clearTimeout(timer);
22874
+ }
22875
+ }
22876
+ function defaultRuntimeAdapters(runtimeAllowlist) {
22877
+ const ids = runtimeAllowlist ? ALL_RUNTIME_IDS2.filter((id) => runtimeAllowlist.includes(id)) : ALL_RUNTIME_IDS2;
22878
+ return ids.map((id) => {
22879
+ switch (id) {
22880
+ case "pi":
22881
+ return new PiAdapter();
22882
+ case "claude":
22883
+ return new ClaudeAdapter();
22884
+ case "codex":
22885
+ return new CodexAdapter();
22886
+ }
22887
+ });
22888
+ }
22889
+ async function probeRuntimes(adapters, options = {}) {
22890
+ const timeoutMs = options.timeoutMs ?? RUNTIME_PROBE_TIMEOUT_MS;
22891
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error("runtime probe timeout must be a positive integer");
22892
+ return Promise.all(
22893
+ adapters.map(async (adapter) => {
22894
+ let id = "unavailable";
22895
+ let steer = false;
22896
+ let resume = false;
22897
+ let permissionModes = [];
22898
+ try {
22899
+ id = boundedSingleLine(adapter.descriptor.id, MAX_RUNTIME_ID_CHARS);
22900
+ const caps = adapter.descriptor.capabilities;
22901
+ steer = caps.steer === true;
22902
+ resume = caps.resume === true;
22903
+ permissionModes = caps.permissionModes.slice(0, MAX_PERMISSION_MODES).map((mode) => boundedSingleLine(mode, MAX_PERMISSION_MODE_CHARS));
22904
+ const detected = validateRuntimeDetectResult(await detectWithTimeout(adapter, timeoutMs));
22905
+ return {
22906
+ id,
22907
+ present: detected.kind === "available",
22908
+ outcome: detected.kind,
22909
+ ...detected.kind !== "available" || detected.version === void 0 ? {} : { version: boundedSingleLine(detected.version, MAX_RUNTIME_VERSION_CHARS) },
22910
+ ...detected.kind === "available" && typeof detected.authPresent === "boolean" ? { authPresent: detected.authPresent } : {},
22911
+ steer,
22912
+ resume,
22913
+ permissionModes
22914
+ };
22915
+ } catch (error) {
22916
+ return { id, present: false, outcome: error instanceof RuntimeProbeTimeout ? "timeout" : "probe-failed", steer, resume, permissionModes };
22917
+ }
22918
+ })
22919
+ );
22920
+ }
22921
+
22922
+ // src/diagnostics/diagnostics.ts
22923
+ var MAX_QUARANTINE_ENTRIES = 100;
22924
+ var MAX_QUARANTINE_SCAN_ENTRIES = MAX_QUARANTINE_ENTRIES * 2;
22925
+ var MAX_QUARANTINE_READ_BYTES = 128 * 1024 * 1024;
22926
+ var MAX_DEVICE_RECORD_BYTES2 = 64 * 1024;
22927
+ var MAX_JOURNAL_COPY_BYTES = 512 * 1024 * 1024;
22928
+ var MAX_QUARANTINE_MANIFEST_BYTES = 64 * 1024;
22929
+ function safeProtocol(serverUrl) {
22930
+ try {
22931
+ const protocol = new URL(serverUrl).protocol.replace(/:$/, "");
22932
+ return protocol === "http" || protocol === "https" || protocol === "ws" || protocol === "wss" ? protocol : "unsupported";
22933
+ } catch {
22934
+ return "invalid";
22935
+ }
22936
+ }
22937
+ async function inspectDevice(storeDir) {
22938
+ const filePath = path3__default.join(storeDir, "device.json");
22939
+ let pathStat;
22940
+ try {
22941
+ pathStat = await promises.lstat(filePath);
22942
+ if (!pathStat.isFile()) return { status: "unavailable" };
22943
+ } catch (err) {
22944
+ if (err.code === "ENOENT") return { status: "unpaired" };
22945
+ return { status: "unavailable" };
22946
+ }
22947
+ let handle;
22948
+ try {
22949
+ handle = await promises.open(
22950
+ filePath,
22951
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
22952
+ );
22953
+ } catch (err) {
22954
+ if (err.code === "ENOENT") return { status: "unpaired" };
22955
+ return { status: "unavailable" };
22956
+ }
22957
+ try {
22958
+ const stat = await handle.stat();
22959
+ const namedAfterOpen = await promises.lstat(filePath);
22960
+ if (!stat.isFile() || !namedAfterOpen.isFile() || namedAfterOpen.isSymbolicLink() || stat.dev !== pathStat.dev || stat.ino !== pathStat.ino || stat.size !== pathStat.size || stat.mtimeMs !== pathStat.mtimeMs || stat.ctimeMs !== pathStat.ctimeMs || stat.dev !== namedAfterOpen.dev || stat.ino !== namedAfterOpen.ino || stat.size !== namedAfterOpen.size || stat.mtimeMs !== namedAfterOpen.mtimeMs || stat.ctimeMs !== namedAfterOpen.ctimeMs || stat.size <= 0 || stat.size > MAX_DEVICE_RECORD_BYTES2) return { status: "unavailable" };
22961
+ const bytes = Buffer.alloc(stat.size);
22962
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
22963
+ const after = await handle.stat();
22964
+ const namedAfterRead = await promises.lstat(filePath);
22965
+ if (bytesRead !== bytes.length || after.dev !== stat.dev || after.ino !== stat.ino || after.size !== stat.size || after.mtimeMs !== stat.mtimeMs || after.ctimeMs !== stat.ctimeMs || namedAfterRead.dev !== stat.dev || namedAfterRead.ino !== stat.ino || namedAfterRead.size !== stat.size || namedAfterRead.mtimeMs !== stat.mtimeMs || namedAfterRead.ctimeMs !== stat.ctimeMs) {
22966
+ return { status: "unavailable" };
22967
+ }
22968
+ const parsed = JSON.parse(bytes.toString("utf8"));
22969
+ return typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? { status: "paired", deviceIdHash: stableIdentifierHash(parsed.deviceId) } : { status: "unavailable" };
22970
+ } catch {
22971
+ return { status: "unavailable" };
22972
+ } finally {
22973
+ await handle.close();
22974
+ }
22975
+ }
22976
+ async function regularFileIdentity(filePath) {
22977
+ try {
22978
+ const stat = await promises.lstat(filePath, { bigint: true });
22979
+ if (!stat.isFile()) throw new Error("journal component is not a regular file");
22980
+ return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
22981
+ } catch (err) {
22982
+ if (err.code === "ENOENT") return void 0;
22983
+ throw err;
22984
+ }
22985
+ }
22986
+ function sameIdentity(left, right) {
22987
+ if (!left || !right) return left === right;
22988
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
22989
+ }
22990
+ function identityFromBigIntStat(stat) {
22991
+ return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
22992
+ }
22993
+ async function copyOpenFileBounded(source, expected, destinationPath) {
22994
+ const destination = await promises.open(destinationPath, "wx", 384);
22995
+ try {
22996
+ const buffer = Buffer.allocUnsafe(64 * 1024);
22997
+ const expectedBytes = Number(expected.size);
22998
+ let position = 0;
22999
+ while (position < expectedBytes) {
23000
+ const length = Math.min(buffer.length, expectedBytes - position);
23001
+ const { bytesRead } = await source.read(buffer, 0, length, position);
23002
+ if (bytesRead === 0) return false;
23003
+ let written = 0;
23004
+ while (written < bytesRead) {
23005
+ const result = await destination.write(buffer, written, bytesRead - written, position + written);
23006
+ if (result.bytesWritten === 0) throw new Error("diagnostics snapshot destination stopped accepting bytes");
23007
+ written += result.bytesWritten;
23008
+ }
23009
+ position += bytesRead;
23010
+ }
23011
+ await destination.sync();
23012
+ return sameIdentity(expected, identityFromBigIntStat(await source.stat({ bigint: true })));
23013
+ } finally {
23014
+ await destination.close();
23015
+ }
23016
+ }
23017
+ async function inspectJournal(storeDir) {
23018
+ const journalPath = path3__default.join(storeDir, JOURNAL_DB_FILENAME);
23019
+ try {
23020
+ const mainIdentity = await regularFileIdentity(journalPath);
23021
+ if (mainIdentity === void 0) return { status: "missing" };
23022
+ const walIdentity = await regularFileIdentity(path3__default.join(storeDir, `${JOURNAL_DB_FILENAME}-wal`));
23023
+ let sizeBytes = Number(mainIdentity.size);
23024
+ let walBytes = walIdentity === void 0 ? void 0 : Number(walIdentity.size);
23025
+ if (!isSqliteAvailable()) {
23026
+ return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "not-checked" };
23027
+ }
23028
+ const componentNames = [JOURNAL_DB_FILENAME, `${JOURNAL_DB_FILENAME}-wal`, `${JOURNAL_DB_FILENAME}-shm`];
23029
+ const initial = /* @__PURE__ */ new Map();
23030
+ for (const name of componentNames) initial.set(name, await regularFileIdentity(path3__default.join(storeDir, name)));
23031
+ const snapshotMain = initial.get(JOURNAL_DB_FILENAME);
23032
+ if (!snapshotMain) return { status: "unavailable", reason: "journal changed during diagnostics snapshot" };
23033
+ sizeBytes = Number(snapshotMain.size);
23034
+ walBytes = initial.get(`${JOURNAL_DB_FILENAME}-wal`) === void 0 ? void 0 : Number(initial.get(`${JOURNAL_DB_FILENAME}-wal`).size);
23035
+ const opened = /* @__PURE__ */ new Map();
23036
+ let totalBytes = 0n;
23037
+ try {
23038
+ for (const name of componentNames) {
23039
+ let handle;
23040
+ try {
23041
+ handle = await promises.open(
23042
+ path3__default.join(storeDir, name),
23043
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
23044
+ );
23045
+ } catch (err) {
23046
+ if (err.code === "ENOENT" && name !== JOURNAL_DB_FILENAME) continue;
23047
+ throw err;
23048
+ }
23049
+ const stat = await handle.stat({ bigint: true });
23050
+ if (!stat.isFile()) {
23051
+ await handle.close();
23052
+ throw new Error("journal component is not a regular file");
23053
+ }
23054
+ const identity = identityFromBigIntStat(stat);
23055
+ if (!sameIdentity(initial.get(name), identity)) {
23056
+ await handle.close();
23057
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23058
+ }
23059
+ totalBytes += identity.size;
23060
+ opened.set(name, { handle, identity });
23061
+ }
23062
+ if (totalBytes > BigInt(MAX_JOURNAL_COPY_BYTES)) {
23063
+ return {
23064
+ status: "present",
23065
+ sizeBytes,
23066
+ ...walBytes === void 0 ? {} : { walBytes },
23067
+ integrity: "not-checked",
23068
+ reason: "journal exceeds the bounded diagnostics copy limit"
23069
+ };
23070
+ }
23071
+ const tempDir = await promises.mkdtemp(path3__default.join(os4__default.tmpdir(), "byok-journal-inspect-"));
23072
+ const { DatabaseSync } = loadSqliteModule();
23073
+ let db;
23074
+ try {
23075
+ for (const name of componentNames) {
23076
+ const component = opened.get(name);
23077
+ if (component && !await copyOpenFileBounded(component.handle, component.identity, path3__default.join(tempDir, name))) {
23078
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23079
+ }
23080
+ }
23081
+ for (const [name, component] of opened) {
23082
+ if (!sameIdentity(component.identity, identityFromBigIntStat(await component.handle.stat({ bigint: true }))) || !sameIdentity(component.identity, await regularFileIdentity(path3__default.join(storeDir, name)))) {
23083
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23084
+ }
23085
+ }
23086
+ for (const name of componentNames) {
23087
+ if (!opened.has(name) && await regularFileIdentity(path3__default.join(storeDir, name)) !== void 0) {
23088
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23089
+ }
23090
+ }
23091
+ const header = Buffer.alloc(16);
23092
+ const copiedHandle = await promises.open(path3__default.join(tempDir, JOURNAL_DB_FILENAME), "r");
23093
+ try {
23094
+ const { bytesRead } = await copiedHandle.read(header, 0, header.length, 0);
23095
+ if (bytesRead !== 16 || header.toString("binary") !== "SQLite format 3\0") {
23096
+ return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal has an invalid SQLite header" };
23097
+ }
23098
+ } finally {
23099
+ await copiedHandle.close();
23100
+ }
23101
+ db = new DatabaseSync(path3__default.join(tempDir, JOURNAL_DB_FILENAME), { readOnly: true });
23102
+ const result = db.prepare("PRAGMA quick_check(1)").get();
23103
+ if (result?.quick_check !== "ok") {
23104
+ return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal quick_check failed" };
23105
+ }
23106
+ return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "ok" };
23107
+ } catch {
23108
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal snapshot could not be checked" };
23109
+ } finally {
23110
+ db?.close();
23111
+ await promises.rm(tempDir, { recursive: true, force: true });
23112
+ }
23113
+ } finally {
23114
+ await Promise.all([...opened.values()].map(({ handle }) => handle.close().catch(() => void 0)));
23115
+ }
23116
+ } catch {
23117
+ return { status: "unavailable", reason: "journal files could not be inspected" };
23118
+ }
23119
+ }
23120
+ async function inspectWorkspace(workspaceRoot) {
23121
+ try {
23122
+ const stat = await promises.stat(workspaceRoot);
23123
+ if (!stat.isDirectory()) return { status: "unavailable", reason: "workspace root is not a directory" };
23124
+ try {
23125
+ await promises.access(workspaceRoot, constants.R_OK | constants.W_OK);
23126
+ return { status: "available", writable: true };
23127
+ } catch {
23128
+ return { status: "available", writable: false };
23129
+ }
23130
+ } catch (err) {
23131
+ if (err.code === "ENOENT") return { status: "missing" };
23132
+ return { status: "unavailable", reason: "workspace root could not be inspected" };
23133
+ }
23134
+ }
23135
+ function readPinnedQuarantineFile(name, maxBytes, includeBytes, budget) {
23136
+ if (path3__default.basename(name) !== name || name === "." || name === "..") {
23137
+ throw new Error("quarantine manifest contains an invalid evidence name");
23138
+ }
23139
+ const namedBefore = lstatSync(name, { bigint: true });
23140
+ if (!namedBefore.isFile() || namedBefore.isSymbolicLink()) {
23141
+ throw new Error("quarantine entry is not a real regular file");
23142
+ }
23143
+ const fd = openSync(name, constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0));
23144
+ try {
23145
+ const opened = fstatSync(fd, { bigint: true });
23146
+ if (!opened.isFile() || !sameInode2(opened, namedBefore) || opened.size < 0 || opened.size > BigInt(maxBytes)) {
23147
+ throw new Error("quarantine entry could not be opened as a bounded regular file");
23148
+ }
23149
+ let bytes;
23150
+ if (includeBytes) {
23151
+ const sizeBytes = Number(opened.size);
23152
+ if (sizeBytes > budget.remainingBytes) {
23153
+ throw new Error("quarantine validation exceeds the 128 MiB cumulative read limit");
23154
+ }
23155
+ budget.remainingBytes -= sizeBytes;
23156
+ bytes = Buffer.alloc(sizeBytes);
23157
+ let position = 0;
23158
+ while (position < bytes.length) {
23159
+ const bytesRead = readSync(fd, bytes, position, bytes.length - position, position);
23160
+ if (bytesRead === 0) throw new Error("quarantine entry ended during bounded read");
23161
+ position += bytesRead;
23162
+ }
23163
+ }
23164
+ const openedAfter = fstatSync(fd, { bigint: true });
23165
+ const namedAfter = lstatSync(name, { bigint: true });
23166
+ if (!sameFileState5(openedAfter, opened) || !sameFileState5(namedAfter, opened)) {
23167
+ throw new Error("quarantine entry changed during inspection");
23168
+ }
23169
+ return {
23170
+ sizeBytes: Number(opened.size),
23171
+ modifiedAt: new Date(Number(opened.mtimeMs)).toISOString(),
23172
+ ...bytes === void 0 ? {} : { bytes }
23173
+ };
23174
+ } finally {
23175
+ closeSync(fd);
23176
+ }
23177
+ }
23178
+ function isHealthQuarantineManifest(value) {
23179
+ if (typeof value !== "object" || value === null) return false;
23180
+ const candidate = value;
23181
+ return candidate.version === 1 && typeof candidate.quarantinedAt === "string" && Number.isFinite(Date.parse(candidate.quarantinedAt)) && typeof candidate.reason === "string" && typeof candidate.sourcePath === "string" && typeof candidate.evidenceFile === "string" && typeof candidate.sha256 === "string" && /^[0-9a-f]{64}$/.test(candidate.sha256) && Number.isSafeInteger(candidate.sizeBytes) && candidate.sizeBytes >= 0 && candidate.sizeBytes <= MAX_OPERATIONAL_HEALTH_FILE_BYTES;
23182
+ }
23183
+ function isJournalQuarantineManifest(value) {
23184
+ if (typeof value !== "object" || value === null) return false;
23185
+ const candidate = value;
23186
+ return typeof candidate.quarantinedAt === "string" && Number.isFinite(Date.parse(candidate.quarantinedAt)) && typeof candidate.reason === "string" && typeof candidate.originalPath === "string" && Array.isArray(candidate.files) && candidate.files.length > 0 && candidate.files.length <= 3 && candidate.files.every((file) => typeof file === "string");
23187
+ }
23188
+ function inspectQuarantinePinned(dir, expectedDirectory) {
23189
+ const previousCwd = process.cwd();
23190
+ try {
23191
+ process.chdir(dir);
23192
+ const pinned = lstatSync(".", { bigint: true });
23193
+ if (!pinned.isDirectory() || !sameInode2(pinned, expectedDirectory)) {
23194
+ throw new Error("quarantine directory changed during inspection");
23195
+ }
23196
+ const selected = [];
23197
+ let scanTruncated = false;
23198
+ const directory = opendirSync(".");
23199
+ try {
23200
+ for (; ; ) {
23201
+ const entry = directory.readSync();
23202
+ if (!entry) break;
23203
+ if (selected.length === MAX_QUARANTINE_SCAN_ENTRIES) {
23204
+ scanTruncated = true;
23205
+ break;
23206
+ }
23207
+ selected.push(entry);
23208
+ }
23209
+ } finally {
23210
+ directory.closeSync();
23211
+ }
23212
+ const evidence = /* @__PURE__ */ new Map();
23213
+ const processedManifests = /* @__PURE__ */ new Set();
23214
+ const readBudget = { remainingBytes: MAX_QUARANTINE_READ_BYTES };
23215
+ const processManifest = (manifestName) => {
23216
+ if (processedManifests.has(manifestName)) return;
23217
+ processedManifests.add(manifestName);
23218
+ const manifestFile = readPinnedQuarantineFile(manifestName, MAX_QUARANTINE_MANIFEST_BYTES, true, readBudget);
23219
+ let parsed;
23220
+ try {
23221
+ parsed = JSON.parse(manifestFile.bytes?.toString("utf8") ?? "");
23222
+ } catch {
23223
+ throw new Error("quarantine manifest is invalid JSON");
23224
+ }
23225
+ if (isHealthQuarantineManifest(parsed)) {
23226
+ if (`${parsed.evidenceFile}.manifest.json` !== manifestName) {
23227
+ throw new Error("health quarantine manifest is not bound to its evidence name");
23228
+ }
23229
+ const file = readPinnedQuarantineFile(parsed.evidenceFile, MAX_OPERATIONAL_HEALTH_FILE_BYTES, true, readBudget);
23230
+ if (file.sizeBytes !== parsed.sizeBytes || createHash("sha256").update(file.bytes ?? Buffer.alloc(0)).digest("hex") !== parsed.sha256) {
23231
+ throw new Error("health quarantine evidence does not match its manifest");
23232
+ }
23233
+ if (evidence.has(parsed.evidenceFile)) throw new Error("quarantine evidence is referenced more than once");
23234
+ evidence.set(parsed.evidenceFile, file);
23235
+ return;
23236
+ }
23237
+ if (isJournalQuarantineManifest(parsed)) {
23238
+ const manifestBase = manifestName.slice(0, -".manifest.json".length);
23239
+ const boundNames = parsed.files.map((file) => {
23240
+ if (path3__default.dirname(path3__default.resolve(file)) !== path3__default.resolve(".")) {
23241
+ throw new Error("journal quarantine manifest points outside quarantine");
23242
+ }
23243
+ return path3__default.basename(file);
23244
+ });
23245
+ if (!boundNames.includes(manifestBase)) {
23246
+ throw new Error("journal quarantine manifest is not bound to its primary database evidence");
23247
+ }
23248
+ for (const name of boundNames) {
23249
+ if (evidence.has(name)) throw new Error("quarantine evidence is referenced more than once");
23250
+ evidence.set(name, readPinnedQuarantineFile(name, MAX_JOURNAL_COPY_BYTES, false, readBudget));
23251
+ }
23252
+ return;
23253
+ }
23254
+ throw new Error("quarantine manifest shape is invalid");
23255
+ };
23256
+ for (const entry of selected) {
23257
+ if (entry.name.endsWith(".manifest.json")) processManifest(entry.name);
23258
+ }
23259
+ for (const entry of selected) {
23260
+ if (!entry.name.endsWith(".manifest.json") && !evidence.has(entry.name)) {
23261
+ const primaryName = entry.name.endsWith("-wal") || entry.name.endsWith("-shm") ? entry.name.slice(0, -4) : entry.name;
23262
+ try {
23263
+ processManifest(`${primaryName}.manifest.json`);
23264
+ } catch {
23265
+ throw new Error("quarantine evidence has no valid manifest binding");
23266
+ }
23267
+ if (!evidence.has(entry.name)) throw new Error("quarantine evidence has no valid manifest binding");
23268
+ }
23269
+ }
23270
+ const entries = [...evidence.entries()].slice(0, MAX_QUARANTINE_ENTRIES).map(([name, file]) => ({ nameHash: stableIdentifierHash(name), sizeBytes: file.sizeBytes, modifiedAt: file.modifiedAt })).sort((a, b) => a.nameHash.localeCompare(b.nameHash));
23271
+ return {
23272
+ status: "available",
23273
+ count: evidence.size,
23274
+ scannedCount: selected.length,
23275
+ truncated: scanTruncated || evidence.size > entries.length,
23276
+ entries
23277
+ };
23278
+ } finally {
23279
+ process.chdir(previousCwd);
23280
+ }
23281
+ }
23282
+ async function inspectQuarantine(storeDir) {
23283
+ const dir = path3__default.join(storeDir, JOURNAL_QUARANTINE_DIRNAME);
23284
+ let directory;
23285
+ try {
23286
+ directory = await promises.lstat(dir, { bigint: true });
23287
+ } catch (err) {
23288
+ if (err.code === "ENOENT") {
23289
+ return { status: "missing", count: 0, scannedCount: 0, truncated: false, entries: [] };
23290
+ }
23291
+ return {
23292
+ status: "unavailable",
23293
+ count: 0,
23294
+ scannedCount: 0,
23295
+ truncated: false,
23296
+ entries: [],
23297
+ reason: "quarantine directory could not be inspected"
23298
+ };
23299
+ }
23300
+ if (!directory.isDirectory() || directory.isSymbolicLink()) {
23301
+ return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine path is not a real directory" };
23302
+ }
23303
+ try {
23304
+ return inspectQuarantinePinned(dir, directory);
23305
+ } catch {
23306
+ return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine manifest or evidence binding is invalid" };
23307
+ }
23308
+ }
23309
+ function checksFor(snapshot) {
23310
+ return [
23311
+ {
23312
+ id: "config",
23313
+ status: snapshot.config.serverProtocol === "invalid" ? "fail" : "pass",
23314
+ summary: `server protocol=${snapshot.config.serverProtocol}; hostedJournal=${snapshot.config.hostedJournal}`
23315
+ },
23316
+ {
23317
+ id: "device",
23318
+ status: snapshot.device.status === "unavailable" ? "fail" : snapshot.device.status === "unpaired" ? "warn" : "pass",
23319
+ summary: snapshot.device.status
23320
+ },
23321
+ {
23322
+ id: "runtimes",
23323
+ status: snapshot.runtimes.some((runtime) => runtime.present) ? "pass" : "warn",
23324
+ 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(" ")
23325
+ },
23326
+ {
23327
+ id: "control",
23328
+ status: snapshot.control.status === "online" ? "pass" : "warn",
23329
+ summary: snapshot.control.status === "online" ? `online transport=${snapshot.control.transport}` : "daemon offline"
23330
+ },
23331
+ {
23332
+ id: "health",
23333
+ status: snapshot.health.status === "corrupt" || snapshot.health.status === "unavailable" ? "fail" : snapshot.health.status === "missing" ? "warn" : "pass",
23334
+ summary: snapshot.health.status
23335
+ },
23336
+ {
23337
+ id: "journal",
23338
+ status: snapshot.journal.status === "unavailable" || snapshot.journal.status === "corrupt" ? "fail" : snapshot.journal.status === "missing" ? "warn" : "pass",
23339
+ summary: snapshot.journal.status
23340
+ },
23341
+ {
23342
+ id: "workspace",
23343
+ status: snapshot.workspace.status === "unavailable" ? "fail" : snapshot.workspace.status === "missing" || snapshot.workspace.writable === false ? "warn" : "pass",
23344
+ summary: `${snapshot.workspace.status}${snapshot.workspace.writable === false ? "; read-only" : ""}`
23345
+ },
23346
+ {
23347
+ id: "quarantine",
23348
+ status: snapshot.quarantine.status === "unavailable" ? "fail" : snapshot.quarantine.count > 0 ? "warn" : "pass",
23349
+ summary: `${snapshot.quarantine.count} evidence file(s)`
23350
+ }
23351
+ ];
23352
+ }
23353
+ async function collectDiagnostics(config, storeDir, options = {}) {
23354
+ const resolvedStoreDir = path3__default.resolve(storeDir);
23355
+ const adapters = options.adapters ?? defaultRuntimeAdapters(config.runtimeAllowlist);
23356
+ const connectControl = options.connectControl ?? connectControlClient;
23357
+ const [device, probedRuntimes, health, journal, workspace, quarantine, controlConnection] = await Promise.all([
23358
+ inspectDevice(resolvedStoreDir),
23359
+ probeRuntimes(adapters, { timeoutMs: options.runtimeProbeTimeoutMs }),
23360
+ inspectOperationalHealthFile(resolvedStoreDir),
23361
+ inspectJournal(resolvedStoreDir),
23362
+ inspectWorkspace(config.workspaceRoot),
23363
+ inspectQuarantine(resolvedStoreDir),
23364
+ connectControl({ storeDir: resolvedStoreDir, productId: config.productId })
23365
+ ]);
23366
+ const runtimes = probedRuntimes.map((runtime) => ({
23367
+ idHash: stableIdentifierHash(runtime.id),
23368
+ present: runtime.present,
23369
+ outcome: runtime.outcome,
23370
+ versionPresent: runtime.version !== void 0,
23371
+ ...runtime.authPresent === void 0 ? {} : { authPresent: runtime.authPresent },
23372
+ steer: runtime.steer,
23373
+ resume: runtime.resume,
23374
+ permissionModeCount: runtime.permissionModes.length
23375
+ }));
23376
+ let control;
23377
+ if (!controlConnection.ok) {
23378
+ control = { status: "offline", reason: "daemon control socket unavailable" };
23379
+ } else {
23380
+ try {
23381
+ const live = await controlConnection.client.request("status");
23382
+ control = {
23383
+ status: "online",
23384
+ pid: live.pid,
23385
+ uptimeMs: live.uptimeMs,
23386
+ transport: live.transport,
23387
+ activeTaskCount: live.activeTasks.length,
23388
+ pendingApprovalCount: live.approvalsPending,
23389
+ operationalHealth: live.operationalHealth,
23390
+ ...live.storage ? { storage: live.storage } : {}
23391
+ };
23392
+ } catch {
23393
+ control = { status: "offline", reason: "daemon status request failed" };
23394
+ } finally {
23395
+ controlConnection.client.close();
23396
+ }
23397
+ }
23398
+ const withoutChecks = {
23399
+ version: 1,
23400
+ generatedAt: (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
23401
+ product: { nameHash: stableIdentifierHash(config.productName), idHash: stableIdentifierHash(config.productId) },
23402
+ system: { node: process.versions.node, platform: process.platform, arch: process.arch, sqliteAvailable: isSqliteAvailable() },
23403
+ config: {
23404
+ serverProtocol: safeProtocol(config.serverUrl),
23405
+ customStoreDir: config.storeDir !== void 0,
23406
+ hostedJournal: config.hostedJournal !== void 0,
23407
+ ...config.runtimeAllowlist ? { runtimeAllowlistCount: config.runtimeAllowlist.length } : {}
23408
+ },
23409
+ device,
23410
+ runtimes,
23411
+ control,
23412
+ health,
23413
+ journal,
23414
+ workspace,
23415
+ quarantine
23416
+ };
23417
+ return { ...withoutChecks, checks: checksFor(withoutChecks) };
23418
+ }
23419
+ function stableIdentifierHash(value) {
23420
+ return createHash("sha256").update(value, "utf8").digest("hex");
23421
+ }
23422
+ function sameInode2(left, right) {
23423
+ return left.dev === right.dev && left.ino === right.ino;
23424
+ }
23425
+ function sameFileState5(left, right) {
23426
+ return sameInode2(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23427
+ }
23428
+
23429
+ // src/diagnostics/device-doctor.ts
23430
+ function diagnoseDevice(config, options = {}) {
23431
+ return collectDiagnostics(config, DeviceStore.resolveDir(config.productId, config.storeDir), {
23432
+ adapters: options.adapters,
23433
+ runtimeProbeTimeoutMs: options.runtimeProbeTimeoutMs
23434
+ });
23435
+ }
23436
+ var MESSAGES = {
23437
+ "confirmation-required": "Explicit confirmation is required for enrollment metadata repair.",
23438
+ "invalid-target": "An explicit expected tenant and device are required.",
23439
+ "daemon-running": "Stop the daemon before enrollment metadata repair.",
23440
+ "store-busy": "The store is owned by another operation; enrollment metadata repair refused.",
23441
+ "authority-unavailable": "The OS enrollment authority could not be read.",
23442
+ "authority-missing": "No complete OS enrollment exists; use explicit authenticated pairing.",
23443
+ "target-mismatch": "The OS enrollment does not match the expected tenant and device.",
23444
+ "projection-unavailable": "The enrollment metadata could not be read safely; repair refused.",
23445
+ "repair-failed": "Enrollment metadata repair did not complete; inspect the state before retrying."
23446
+ };
23447
+ var DeviceMetadataRepairError = class extends Error {
23448
+ constructor(code) {
23449
+ super(MESSAGES[code]);
23450
+ this.code = code;
23451
+ this.name = "DeviceMetadataRepairError";
23452
+ }
23453
+ code;
23454
+ };
23455
+ async function repairDeviceEnrollmentMetadata(config, input) {
23456
+ if (input?.confirmed !== true) throw new DeviceMetadataRepairError("confirmation-required");
23457
+ if (typeof input.expectedDeviceId !== "string" || input.expectedDeviceId.trim().length === 0 || !isTenantId(input.expectedTenantId)) {
23458
+ throw new DeviceMetadataRepairError("invalid-target");
23459
+ }
23460
+ const { expectedDeviceId, expectedTenantId } = input;
23461
+ const productId = config.productId;
23462
+ let failureCode = "repair-failed";
23463
+ try {
23464
+ const storeDir = DeviceStore.resolveDir(productId, config.storeDir);
23465
+ const control = await connectControlClient({ storeDir, productId });
23466
+ if (control.ok) {
23467
+ control.client.close();
23468
+ throw new DeviceMetadataRepairError("daemon-running");
23469
+ }
23470
+ const owner = await acquireDaemonOwner(storeDir, "doctor");
23471
+ try {
23472
+ const store = new DeviceStore(storeDir, void 0, productId);
23473
+ failureCode = "projection-unavailable";
23474
+ await store.load();
23475
+ failureCode = "authority-unavailable";
23476
+ const authority = await store.credentials.read();
23477
+ if (authority === void 0) throw new DeviceMetadataRepairError("authority-missing");
23478
+ if (authority.deviceId !== expectedDeviceId || authority.tenantId !== expectedTenantId) {
23479
+ throw new DeviceMetadataRepairError("target-mismatch");
23480
+ }
23481
+ failureCode = "repair-failed";
23482
+ const changed = await store.reconcileMetadata(authority);
23483
+ const readback = await store.load();
23484
+ if (readback === void 0 || readback.deviceId !== authority.deviceId || readback.tenantId !== authority.tenantId || readback.devicePublicKey !== authority.devicePublicKey) {
23485
+ throw new DeviceMetadataRepairError("repair-failed");
23486
+ }
23487
+ return { action: "restore-enrollment-metadata", scope: "device", status: changed ? "repaired" : "not-needed" };
23488
+ } catch (error) {
23489
+ if (error instanceof DeviceMetadataRepairError) throw error;
23490
+ throw new DeviceMetadataRepairError(failureCode);
23491
+ } finally {
23492
+ failureCode = "repair-failed";
23493
+ await owner.release();
23494
+ }
23495
+ } catch (error) {
23496
+ if (error instanceof DeviceMetadataRepairError) throw error;
23497
+ if (error instanceof DaemonOwnerActiveError) throw new DeviceMetadataRepairError("store-busy");
23498
+ throw new DeviceMetadataRepairError(failureCode);
23499
+ }
23500
+ }
23501
+
23502
+ export { AGENT_CONTENT_READ_CAPABILITIES, AGENT_CONTENT_READ_CAPABILITY_ARTIFACT, AGENT_CONTENT_READ_CAPABILITY_TRANSCRIPT, AGENT_CONTENT_READ_CAPABILITY_WORKSPACE, AGENT_HOME_PROJECTION_STATE_FILE, AGENT_MEMORY_AUDIT_FILENAME, AGENT_MEMORY_OUTBOX_FILENAME, AgentHomeBusyError, AgentHomeCollisionError, AgentHomeError, AgentHomeLayout, AgentHomeLeaseCorruptError, AgentHomeLeaseManager, AgentHomeManager, AgentHomeResolutionError, AgentMemoryError, AgentMemoryRevisionConflictError, AgentRefValidationError, AgentSessionHandoffCorruptError, AgentSessionHandoffMismatchError, AgentSessionHandoffStore, AgentSessionHandoffStoreError, BYOK_SDK_HELPER_SUBCOMMAND, BlobClient, BlobRequestAbortedError, ClaudeAdapter, CodexAdapter, DEFAULT_ACK_CRITICAL_RESERVE_BYTES, DEFAULT_CLEANUP_BATCH_LIMIT, DEFAULT_HARD_BUDGET_RATIO, DEFAULT_INCREMENTAL_VACUUM_PAGES, DEFAULT_LOG_ROTATION, DEFAULT_NORMAL_COMPACTION_INTERVAL_MS, DEFAULT_PRESSURE_COMPACTION_INTERVAL_MS, DEFAULT_RETENTION_MS, DEFAULT_SOFT_BUDGET_RATIO, DaemonObserver, DeviceMetadataRepairError, GitWorkspaceError, GitWorkspaceManager, GitWorkspaceStore, JOURNAL_DB_FILENAME, JOURNAL_QUARANTINE_DIRNAME, JOURNAL_TASK_REF_PREFIX, JournalClosedError, JournalCorruptError, JournalRecordTooLargeError, JournalUnavailableError, JournalUnknownTaskError, LocalStateRelocationBusyError, LocalStateRelocationError, LocalStateRelocationIntegrityError, LocalStorageEmergencyError, LocalStoragePolicyError, LocalStoragePressureEngine, LocalTeamWorkspace, LocalTeamWorkspace as LocalTeamWorkspaceService, McpToolsetDefinitionRevisionConflictError, McpToolsetRevisionConflictError, PI_PACKAGE_NAME, PiAdapter, PolicyUnsupportedError, RUNTIME_ADAPTER_CONTRACT_VIOLATION_REASON, ReplayCursorTooOldError, RuntimeDisposalFailure, RuntimeExecutionFailure, RuntimeStartupDisposalFailure, SKILL_PACKS_CAPABILITY, SKILL_PACKS_DIRNAME, SKILL_PACK_AUDIT_FILENAME, SKILL_PACK_INSTALL_ERROR_CODES, SKILL_PACK_LOCK_FILENAME, SKILL_PACK_LOCK_SCHEMA, SKILL_PACK_RESPONSE_MAX_BYTES, SecureDirHardeningError, SkillPackInstallError, SqliteLocalTaskJournal, SteerUnsupportedError, TeamTmuxViewError, TeamWorkspaceConflictError, TeamWorkspaceCorruptError, TeamWorkspaceError, TeamWorkspaceLeaseError, TeamWorkspaceNotFoundError, TeamWorkspaceQuotaError, TeamWorkspaceReceiptError, TeamWorkspaceValidationError, TruthMemoryClient, TruthMemoryClientError, UnsupportedServicePlatformError, buildIcaclsArgs, cleanupEligibleAt, cleanupOrderFor, computePressureState, createAgentHomeProjection, createAgentHomeProjectionConsumer, createDaemon, createDaemonWithAdapters, createFilesystemCleanupExecutor, createServiceLifecycle, createStatfsFreeBytesProvider, decodeTeamMemberContext, diagnoseDevice, encodeTeamMemberContext, ensureSecureDir, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, isRuntimeStartupDisposalFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, readDeviceEnrollmentStatus, repairDeviceEnrollmentMetadata, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
21984
23503
  //# sourceMappingURL=index.js.map
21985
23504
  //# sourceMappingURL=index.js.map