@byok-sdk/client 0.14.0 → 0.16.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 (47) 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 +3026 -2122
  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 +17 -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 +5 -90
  37. package/dist/diagnostics/operator-actions.d.ts +63 -0
  38. package/dist/diagnostics/types.d.ts +92 -0
  39. package/dist/index.d.ts +5 -1
  40. package/dist/index.js +2575 -470
  41. package/dist/index.js.map +1 -1
  42. package/dist/runtime-detection.d.ts +3 -0
  43. package/dist/runtime-failure.d.ts +6 -0
  44. package/dist/sdk-reserved-helper-host.d.ts +1 -1
  45. package/dist/types.d.ts +20 -10
  46. package/dist/util/durable-jsonl.d.ts +12 -0
  47. 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, readSync, openSync, fchmodSync, fsyncSync, closeSync, opendirSync, realpathSync } 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, AgentMessagePublishPayloadSchema, AgentMessageDispositionPayloadSchema, 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, 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
- import * as os4 from 'os';
8
- import os4__default from 'os';
7
+ import * as os from 'os';
8
+ import os__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';
@@ -246,6 +246,18 @@ var ADMINISTRATORS_SID = "*S-1-5-32-544";
246
246
  function buildIcaclsArgs(dir, username) {
247
247
  return [dir, "/inheritance:r", "/grant:r", `${username}:(OI)(CI)F`, "/grant", `${SYSTEM_SID}:(OI)(CI)F`, "/grant", `${ADMINISTRATORS_SID}:(OI)(CI)F`];
248
248
  }
249
+ function buildIcaclsFileArgs(filePath, username) {
250
+ return [
251
+ filePath,
252
+ "/inheritance:r",
253
+ "/grant:r",
254
+ `${username}:F`,
255
+ "/grant",
256
+ `${SYSTEM_SID}:F`,
257
+ "/grant",
258
+ `${ADMINISTRATORS_SID}:F`
259
+ ];
260
+ }
249
261
  var SecureDirHardeningError = class extends Error {
250
262
  constructor(dir, reason) {
251
263
  super(
@@ -256,6 +268,14 @@ var SecureDirHardeningError = class extends Error {
256
268
  }
257
269
  dir;
258
270
  };
271
+ var SecureFileHardeningError = class extends Error {
272
+ constructor(filePath, reason) {
273
+ super(`failed to apply a restrictive Windows ACL to "${filePath}": ${reason}`);
274
+ this.filePath = filePath;
275
+ this.name = "SecureFileHardeningError";
276
+ }
277
+ filePath;
278
+ };
259
279
  async function ensureSecureDir(dir, opts = {}) {
260
280
  const platform = opts.platform ?? process.platform;
261
281
  const run = opts.run ?? defaultRunner;
@@ -263,7 +283,7 @@ async function ensureSecureDir(dir, opts = {}) {
263
283
  await promises.chmod(dir, 448).catch(() => {
264
284
  });
265
285
  if (platform !== "win32") return;
266
- const { username } = os4__default.userInfo();
286
+ const { username } = os__default.userInfo();
267
287
  let result;
268
288
  try {
269
289
  result = await run("icacls", buildIcaclsArgs(dir, username));
@@ -274,6 +294,22 @@ async function ensureSecureDir(dir, opts = {}) {
274
294
  throw new SecureDirHardeningError(dir, `icacls exited ${result.code}: ${(result.stderr || result.stdout).trim()}`);
275
295
  }
276
296
  }
297
+ async function ensureSecureFile(filePath, opts = {}) {
298
+ const platform = opts.platform ?? process.platform;
299
+ const run = opts.run ?? defaultRunner;
300
+ await promises.chmod(filePath, 384);
301
+ if (platform !== "win32") return;
302
+ const { username } = os__default.userInfo();
303
+ let result;
304
+ try {
305
+ result = await run("icacls", buildIcaclsFileArgs(filePath, username));
306
+ } catch (err) {
307
+ throw new SecureFileHardeningError(filePath, `could not run icacls: ${err instanceof Error ? err.message : String(err)}`);
308
+ }
309
+ if (result.code !== 0) {
310
+ throw new SecureFileHardeningError(filePath, `icacls exited ${result.code}: ${(result.stderr || result.stdout).trim()}`);
311
+ }
312
+ }
277
313
 
278
314
  // src/daemon/path-mutation-gate.ts
279
315
  var PathMutationGateBusyError = class extends Error {
@@ -2211,6 +2247,7 @@ async function acquire(input) {
2211
2247
  var localStateRelocation = Object.freeze({ acquire });
2212
2248
 
2213
2249
  // src/types.ts
2250
+ var RUNTIME_DETECTION_FAILURE_KINDS = ["not-found", "not-executable", "timeout", "probe-failed"];
2214
2251
  function frozenStrings(values) {
2215
2252
  return values === void 0 ? void 0 : Object.freeze([...values]);
2216
2253
  }
@@ -2307,6 +2344,31 @@ function resolveLocalAgentReleaseIdentity(input) {
2307
2344
  ...input.buildId === void 0 ? {} : { buildId: input.buildId }
2308
2345
  });
2309
2346
  }
2347
+ async function runMcpEnvLauncher() {
2348
+ const key = process.env.BYOK_MCP_ENV_KEY;
2349
+ if (!key || !/^BYOK_MCP_PAYLOAD_[A-F0-9]{32}$/.test(key)) throw new Error("invalid MCP environment binding");
2350
+ const encoded = process.env[key];
2351
+ if (!encoded) throw new Error("missing sealed MCP environment");
2352
+ let config;
2353
+ try {
2354
+ config = JSON.parse(encoded);
2355
+ 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();
2356
+ } catch {
2357
+ throw new Error("invalid sealed MCP environment");
2358
+ }
2359
+ const env = { ...process.env };
2360
+ delete env.BYOK_MCP_ENV_KEY;
2361
+ for (const name of Object.keys(env)) if (name.startsWith("BYOK_MCP_PAYLOAD_")) delete env[name];
2362
+ Object.assign(env, config.env);
2363
+ await new Promise((resolve, reject) => {
2364
+ const child = spawn(config.command, config.args ?? [], { env, stdio: "inherit", windowsHide: true });
2365
+ child.once("error", () => reject(new Error("MCP child could not be spawned")));
2366
+ child.once("close", (code) => {
2367
+ if (code === 0) resolve();
2368
+ else reject(new Error("MCP child exited unsuccessfully"));
2369
+ });
2370
+ });
2371
+ }
2310
2372
  var CONTROL_PROTOCOL_VERSION = 1;
2311
2373
  var HANDSHAKE_TIMEOUT_MS = 3e3;
2312
2374
  var UNIX_SOCKET_PATH_SOFT_LIMIT2 = 100;
@@ -3174,6 +3236,9 @@ async function runAgentTeamMcp() {
3174
3236
  }
3175
3237
  async function runSdkReservedHelper(kind) {
3176
3238
  switch (kind) {
3239
+ case "mcp-env":
3240
+ await runMcpEnvLauncher();
3241
+ return;
3177
3242
  case "agent-message-mcp":
3178
3243
  await runAgentMessageMcp();
3179
3244
  return;
@@ -3195,7 +3260,8 @@ var DIST_SCRIPT_BY_KIND = Object.freeze({
3195
3260
  "agent-message-mcp": "byok-agent-message-mcp.js",
3196
3261
  "agent-memory-mcp": "byok-agent-memory-mcp.js",
3197
3262
  "approval-mcp": "byok-approval-mcp.js",
3198
- "agent-team-mcp": "byok-agent-team-mcp.js"
3263
+ "agent-team-mcp": "byok-agent-team-mcp.js",
3264
+ "mcp-env": "byok-mcp-env.js"
3199
3265
  });
3200
3266
  function assertExecutable(executable) {
3201
3267
  if (!path3__default.isAbsolute(executable) || /[\u0000\r\n]/u.test(executable)) {
@@ -3213,8 +3279,11 @@ function resolveSdkReservedHelperBin(kind, host) {
3213
3279
  source: "self-executable"
3214
3280
  });
3215
3281
  }
3282
+ const entryDir = path3__default.dirname(fileURLToPath(import.meta.url));
3283
+ const entryKind = path3__default.basename(entryDir);
3284
+ const distDir = entryKind === "adapters" || entryKind === "bin" ? path3__default.dirname(entryDir) : entryDir;
3216
3285
  const script = path3__default.join(
3217
- path3__default.dirname(fileURLToPath(import.meta.url)),
3286
+ distDir,
3218
3287
  "bin",
3219
3288
  DIST_SCRIPT_BY_KIND[kind]
3220
3289
  );
@@ -3225,7 +3294,7 @@ function resolveSdkReservedHelperBin(kind, host) {
3225
3294
  });
3226
3295
  }
3227
3296
  function isHelperKind(value) {
3228
- return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp";
3297
+ return value === "agent-message-mcp" || value === "agent-memory-mcp" || value === "approval-mcp" || value === "agent-team-mcp" || value === "mcp-env";
3229
3298
  }
3230
3299
  async function runSdkReservedHelperCommand(argv = process.argv.slice(2)) {
3231
3300
  if (argv[0] !== BYOK_SDK_HELPER_SUBCOMMAND) return false;
@@ -3319,6 +3388,32 @@ function projectRuntimeBoundaryFailure(value, expectedPhase) {
3319
3388
  contractViolation: true
3320
3389
  };
3321
3390
  }
3391
+ var RuntimeStartupDisposalFailure = class extends Error {
3392
+ retryDisposal;
3393
+ constructor(retryDisposal, options) {
3394
+ super("runtime startup failed and process ownership remains quarantined", options);
3395
+ this.name = "RuntimeStartupDisposalFailure";
3396
+ this.retryDisposal = retryDisposal;
3397
+ Object.defineProperty(this, /* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1"), { value: true });
3398
+ Object.freeze(this);
3399
+ }
3400
+ };
3401
+ function isRuntimeStartupDisposalFailure(value) {
3402
+ return typeof value === "object" && value !== null && value[/* @__PURE__ */ Symbol.for("@byok-sdk/client/RuntimeStartupDisposalFailure/v1")] === true && typeof value.retryDisposal === "function";
3403
+ }
3404
+ var GIT_ERROR_CATEGORIES = [
3405
+ "git-unavailable",
3406
+ "git-timeout",
3407
+ "git-output-limit",
3408
+ "git-command-failed",
3409
+ "workspace-root-invalid",
3410
+ "workspace-root-conflict",
3411
+ "workspace-not-owned",
3412
+ "repository-root-mismatch",
3413
+ "repository-invalid",
3414
+ "lease-busy",
3415
+ "ledger-invalid"
3416
+ ];
3322
3417
  var GitWorkspaceError = class extends Error {
3323
3418
  constructor(category, message = category) {
3324
3419
  super(message);
@@ -3824,6 +3919,132 @@ var GitWorkspaceStore = class {
3824
3919
  await atomicWriteFile(this.filePath, JSON.stringify(ledger, null, 2), { mode: 384 });
3825
3920
  }
3826
3921
  };
3922
+ function terminalIdentity(runtimeId, agentRef) {
3923
+ return {
3924
+ ...runtimeId === void 0 || RuntimeIdSchema.safeParse(runtimeId).success ? {} : { harnessId: HarnessIdSchema.parse(runtimeId) },
3925
+ ...agentRef === void 0 ? {} : { agentRef }
3926
+ };
3927
+ }
3928
+
3929
+ // src/daemon/terminal-commit-queue.ts
3930
+ var TerminalCommitQueue = class {
3931
+ constructor(committed) {
3932
+ this.committed = committed;
3933
+ }
3934
+ committed;
3935
+ pending = /* @__PURE__ */ new Map();
3936
+ tail = Promise.resolve();
3937
+ timer;
3938
+ stopped = false;
3939
+ get pendingCount() {
3940
+ return this.pending.size;
3941
+ }
3942
+ hasPending(taskId) {
3943
+ return this.pending.has(taskId);
3944
+ }
3945
+ enqueue(taskId, commit) {
3946
+ if (!this.pending.has(taskId)) this.pending.set(taskId, { commit, receipt: Promise.resolve(), running: false });
3947
+ void this.attempt(taskId).catch(() => void 0);
3948
+ }
3949
+ receipt(taskId) {
3950
+ return this.pending.get(taskId)?.receipt ?? Promise.resolve();
3951
+ }
3952
+ retry(taskId) {
3953
+ return this.attempt(taskId);
3954
+ }
3955
+ resume() {
3956
+ this.stopped = false;
3957
+ if (this.pending.size > 0) this.armRetry();
3958
+ }
3959
+ async stop() {
3960
+ this.stopped = true;
3961
+ if (this.timer) clearTimeout(this.timer);
3962
+ this.timer = void 0;
3963
+ await Promise.all([...this.pending.keys()].map((taskId) => this.attempt(taskId)));
3964
+ }
3965
+ attempt(taskId) {
3966
+ const entry = this.pending.get(taskId);
3967
+ if (!entry) return Promise.resolve();
3968
+ if (entry.running) return entry.receipt;
3969
+ entry.running = true;
3970
+ const receipt = this.tail.catch(() => void 0).then(entry.commit);
3971
+ entry.receipt = receipt.then(() => {
3972
+ if (this.pending.get(taskId) === entry) this.pending.delete(taskId);
3973
+ this.committed(taskId);
3974
+ }, (error) => {
3975
+ entry.running = false;
3976
+ this.armRetry();
3977
+ throw error;
3978
+ });
3979
+ this.tail = entry.receipt;
3980
+ void entry.receipt.catch(() => void 0);
3981
+ return entry.receipt;
3982
+ }
3983
+ armRetry() {
3984
+ if (this.timer || this.stopped) return;
3985
+ this.timer = setTimeout(() => {
3986
+ this.timer = void 0;
3987
+ for (const taskId of this.pending.keys()) void this.attempt(taskId).catch(() => void 0);
3988
+ }, 1e3);
3989
+ this.timer.unref?.();
3990
+ }
3991
+ };
3992
+
3993
+ // src/runtime-detection.ts
3994
+ function validateRuntimeDetectResult(value) {
3995
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
3996
+ throw new TypeError("invalid runtime detection result");
3997
+ }
3998
+ const result = value;
3999
+ if (!Object.hasOwn(result, "kind")) throw new TypeError("invalid runtime detection result");
4000
+ const { kind, version, authPresent } = result;
4001
+ if (kind === "available") {
4002
+ if (Object.keys(result).some((key) => !["kind", "version", "authPresent"].includes(key)) || version !== void 0 && typeof version !== "string" || authPresent !== void 0 && typeof authPresent !== "boolean") {
4003
+ throw new TypeError("invalid runtime detection result");
4004
+ }
4005
+ return Object.freeze({
4006
+ kind,
4007
+ ...version === void 0 ? {} : { version },
4008
+ ...authPresent === void 0 ? {} : { authPresent }
4009
+ });
4010
+ }
4011
+ if (RUNTIME_DETECTION_FAILURE_KINDS.some((candidate) => candidate === kind) && Object.keys(result).every((key) => key === "kind")) {
4012
+ return Object.freeze({ kind });
4013
+ }
4014
+ throw new TypeError("invalid runtime detection result");
4015
+ }
4016
+ function classifyDetectError(error) {
4017
+ const code = error !== null && typeof error === "object" ? error.code : void 0;
4018
+ if (code === "ENOENT") return { kind: "not-found" };
4019
+ if (code === "EACCES" || code === "EPERM" || code === "ENOEXEC") return { kind: "not-executable" };
4020
+ return { kind: "probe-failed" };
4021
+ }
4022
+ async function probeRuntimeVersion(command, timeoutMs) {
4023
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new TypeError("invalid runtime probe timeout");
4024
+ return new Promise((resolve) => {
4025
+ let timer;
4026
+ let timedOut = false;
4027
+ try {
4028
+ const child = execFile(command, ["--version"], { encoding: "utf8" }, (error, stdout, stderr) => {
4029
+ if (timer) clearTimeout(timer);
4030
+ if (error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") resolve({ kind: "probe-failed" });
4031
+ else if (timedOut) resolve({ kind: "timeout" });
4032
+ else if (error) resolve(classifyDetectError(error));
4033
+ else resolve({ kind: "available", stdout, stderr });
4034
+ });
4035
+ timer = setTimeout(() => {
4036
+ timedOut = true;
4037
+ child.stdout?.destroy();
4038
+ child.stderr?.destroy();
4039
+ child.kill("SIGKILL");
4040
+ }, timeoutMs);
4041
+ timer.unref?.();
4042
+ } catch (error) {
4043
+ if (timer) clearTimeout(timer);
4044
+ resolve(classifyDetectError(error));
4045
+ }
4046
+ });
4047
+ }
3827
4048
 
3828
4049
  // src/adapters/pi/mcp-config.ts
3829
4050
  var BYOK_PI_MCP_CONFIG_PATH = "BYOK_PI_MCP_CONFIG_PATH";
@@ -4834,12 +5055,13 @@ var PiAdapter = class {
4834
5055
  async detect() {
4835
5056
  try {
4836
5057
  const bin = this.resolveBin();
4837
- const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
4838
- const version = stdout.trim() || stderr.trim();
5058
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS);
5059
+ if (probe.kind !== "available") return probe;
5060
+ const version = probe.stdout.trim() || probe.stderr.trim();
4839
5061
  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 };
5062
+ return { kind: "available", version, authPresent };
5063
+ } catch (error) {
5064
+ return classifyDetectError(error);
4843
5065
  }
4844
5066
  }
4845
5067
  async prepare(input) {
@@ -4952,7 +5174,7 @@ var PiAdapter = class {
4952
5174
  const taskMcpServers = startInput.mcpServers ?? {};
4953
5175
  let mcpConfigPath;
4954
5176
  try {
4955
- mcpConfigDir = await promises.mkdtemp(path3__default.join(os4__default.tmpdir(), "byok-pi-mcp-"));
5177
+ mcpConfigDir = await promises.mkdtemp(path3__default.join(os__default.tmpdir(), "byok-pi-mcp-"));
4956
5178
  await promises.chmod(mcpConfigDir, 448).catch(() => {
4957
5179
  });
4958
5180
  mcpConfigPath = path3__default.join(mcpConfigDir, "mcp-config.json");
@@ -5969,14 +6191,15 @@ var ClaudeAdapter = class {
5969
6191
  environmentRequirements: { credentialNames: [] }
5970
6192
  });
5971
6193
  async detect() {
5972
- const bin = this.resolveBin();
5973
6194
  try {
5974
- const { stdout } = await execFileAsync2(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS2 });
5975
- const version = stdout.trim();
6195
+ const bin = this.resolveBin();
6196
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS2);
6197
+ if (probe.kind !== "available") return probe;
6198
+ const version = probe.stdout.trim();
5976
6199
  const authPresent = await this.probeAuthPresent(bin.command);
5977
- return { present: true, version, authPresent };
5978
- } catch {
5979
- return { present: false };
6200
+ return { kind: "available", version, authPresent };
6201
+ } catch (error) {
6202
+ return classifyDetectError(error);
5980
6203
  }
5981
6204
  }
5982
6205
  async prepare(input) {
@@ -6063,7 +6286,7 @@ var ClaudeAdapter = class {
6063
6286
  });
6064
6287
  }
6065
6288
  if (needsMcpConfig) {
6066
- mcpConfigDir = await promises.mkdtemp(path3__default.join(os4__default.tmpdir(), "byok-mcp-"));
6289
+ mcpConfigDir = await promises.mkdtemp(path3__default.join(os__default.tmpdir(), "byok-mcp-"));
6067
6290
  await promises.chmod(mcpConfigDir, 448).catch(() => {
6068
6291
  });
6069
6292
  const mcpConfigPath = path3__default.join(mcpConfigDir, "mcp-config.json");
@@ -6595,10 +6818,18 @@ function unmappedFrameKey(evt) {
6595
6818
  return evt.type;
6596
6819
  }
6597
6820
  var STDERR_RING_CAPACITY3 = 20;
6821
+ var CODEX_MAX_FRAME_BYTES = 1024 * 1024;
6822
+ var CODEX_MAX_DEFERRED_BYTES = 4 * 1024 * 1024;
6823
+ var CODEX_MAX_STDERR_BYTES = 64 * 1024;
6598
6824
  var CodexProcessRunner = class {
6599
6825
  child;
6600
6826
  onEvent;
6601
- buffer = "";
6827
+ frameChunks = [];
6828
+ frameBytes = 0;
6829
+ deferredBytes = 0;
6830
+ stderrBytes = 0;
6831
+ transportFailure;
6832
+ onFailure;
6602
6833
  stderrRing = [];
6603
6834
  closed = false;
6604
6835
  exitCode = null;
@@ -6621,11 +6852,12 @@ var CodexProcessRunner = class {
6621
6852
  deferredEvents = [];
6622
6853
  constructor(options) {
6623
6854
  this.onEvent = options.onEvent;
6855
+ this.onFailure = options.onFailure;
6624
6856
  const spawnFn = options.spawnFn ?? spawn;
6625
6857
  this.child = spawnFn(options.command, options.args, withOwnedProcessTree({
6626
6858
  cwd: options.cwd,
6627
6859
  env: options.env,
6628
- stdio: ["ignore", "pipe", "pipe"]
6860
+ stdio: [options.instruction === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
6629
6861
  }));
6630
6862
  this.closedPromise = new Promise((resolve) => {
6631
6863
  this.resolveClosed = resolve;
@@ -6633,7 +6865,6 @@ var CodexProcessRunner = class {
6633
6865
  this.adopted = this.adoptOwnedTree(options);
6634
6866
  this.adopted.catch(() => {
6635
6867
  });
6636
- this.child.stdout.setEncoding("utf8");
6637
6868
  this.child.stdout.on("data", (chunk) => this.onData(chunk));
6638
6869
  this.child.stderr.setEncoding("utf8");
6639
6870
  this.child.stderr.on("data", (chunk) => this.onStderr(chunk));
@@ -6642,6 +6873,12 @@ var CodexProcessRunner = class {
6642
6873
  this.exitSignal = signal;
6643
6874
  this.finishClosing();
6644
6875
  });
6876
+ if (options.instruction !== void 0) {
6877
+ this.child.stdin.on("error", () => {
6878
+ this.failTransport("codex prompt stdin failed before delivery");
6879
+ });
6880
+ this.child.stdin.end(options.instruction, "utf8");
6881
+ }
6645
6882
  this.child.on("error", () => {
6646
6883
  this.finishClosing();
6647
6884
  });
@@ -6727,12 +6964,19 @@ var CodexProcessRunner = class {
6727
6964
  throw failure;
6728
6965
  }
6729
6966
  this.adoption = "adopted";
6967
+ this.deferredBytes = 0;
6730
6968
  for (const evt of this.deferredEvents.splice(0)) this.onEvent(evt);
6731
6969
  }
6732
6970
  /** Arrival-order delivery, held back until the tree is backstopped (see `deferredEvents`). */
6733
- deliver(evt) {
6971
+ deliver(evt, bytes) {
6972
+ if (this.transportFailure) return;
6734
6973
  if (this.adoption === "failed") return;
6735
6974
  if (this.adoption === "pending") {
6975
+ if (this.deferredBytes + bytes > CODEX_MAX_DEFERRED_BYTES) {
6976
+ this.failTransport("codex pre-adoption event buffer exceeded its byte budget");
6977
+ return;
6978
+ }
6979
+ this.deferredBytes += bytes;
6736
6980
  this.deferredEvents.push(evt);
6737
6981
  return;
6738
6982
  }
@@ -6749,6 +6993,7 @@ var CodexProcessRunner = class {
6749
6993
  * plus an empty stderr tail would bury the only reason anyone can act on.
6750
6994
  */
6751
6995
  buildExitError(context) {
6996
+ if (this.transportFailure) return this.transportFailure;
6752
6997
  if (this.adoptionFailure !== void 0) return this.adoptionFailure;
6753
6998
  const parts = [`${context} (exit code=${this.exitCode}, signal=${this.exitSignal})`];
6754
6999
  if (this.stderrRing.length > 0) {
@@ -6756,18 +7001,45 @@ var CodexProcessRunner = class {
6756
7001
  }
6757
7002
  return new Error(parts.join("; "));
6758
7003
  }
7004
+ failTransport(reason) {
7005
+ if (this.transportFailure) return;
7006
+ this.transportFailure = new RuntimeExecutionFailure({
7007
+ phase: "run",
7008
+ category: "infrastructure",
7009
+ retry: "non-retryable",
7010
+ reason
7011
+ });
7012
+ this.frameChunks.length = 0;
7013
+ this.frameBytes = 0;
7014
+ this.deferredEvents.length = 0;
7015
+ this.deferredBytes = 0;
7016
+ this.onFailure?.(this.transportFailure);
7017
+ this.kill();
7018
+ }
6759
7019
  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");
7020
+ if (this.transportFailure) return;
7021
+ let offset = 0;
7022
+ while (offset < chunk.length) {
7023
+ const newline = chunk.indexOf(10, offset);
7024
+ const end = newline === -1 ? chunk.length : newline;
7025
+ const bytes = end - offset;
7026
+ if (this.frameBytes + bytes > CODEX_MAX_FRAME_BYTES) {
7027
+ this.failTransport("codex raw JSONL frame exceeded its byte budget");
7028
+ return;
7029
+ }
7030
+ if (bytes > 0) this.frameChunks.push(Buffer.from(chunk.subarray(offset, end)));
7031
+ this.frameBytes += bytes;
7032
+ if (newline === -1) return;
7033
+ const frame = Buffer.concat(this.frameChunks, this.frameBytes);
7034
+ this.frameChunks.length = 0;
7035
+ this.frameBytes = 0;
7036
+ const line = frame.toString("utf8").replace(/\r$/, "");
7037
+ if (line.length > 0) this.parseLine(line, frame.length);
7038
+ if (this.transportFailure) return;
7039
+ offset = newline + 1;
6768
7040
  }
6769
7041
  }
6770
- parseLine(line) {
7042
+ parseLine(line, bytes) {
6771
7043
  let parsed;
6772
7044
  try {
6773
7045
  parsed = JSON.parse(line);
@@ -6777,14 +7049,18 @@ var CodexProcessRunner = class {
6777
7049
  if (!parsed || typeof parsed !== "object" || typeof parsed.type !== "string") {
6778
7050
  return;
6779
7051
  }
6780
- this.deliver(parsed);
7052
+ this.deliver(parsed, bytes);
6781
7053
  }
6782
7054
  onStderr(chunk) {
6783
- for (const rawLine of chunk.split("\n")) {
7055
+ const bounded2 = Buffer.from(chunk).subarray(-CODEX_MAX_STDERR_BYTES).toString("utf8");
7056
+ for (const rawLine of bounded2.split("\n")) {
6784
7057
  const line = rawLine.trim();
6785
7058
  if (line.length === 0) continue;
6786
7059
  this.stderrRing.push(line);
6787
- if (this.stderrRing.length > STDERR_RING_CAPACITY3) this.stderrRing.shift();
7060
+ this.stderrBytes += Buffer.byteLength(line);
7061
+ while (this.stderrRing.length > STDERR_RING_CAPACITY3 || this.stderrBytes > CODEX_MAX_STDERR_BYTES) {
7062
+ this.stderrBytes -= Buffer.byteLength(this.stderrRing.shift());
7063
+ }
6788
7064
  }
6789
7065
  }
6790
7066
  };
@@ -6817,14 +7093,15 @@ var CodexAdapter = class {
6817
7093
  environmentRequirements: { credentialNames: [] }
6818
7094
  });
6819
7095
  async detect() {
6820
- const bin = this.resolveBin();
6821
7096
  try {
6822
- const versionResult = await execFileAsync3(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS3 });
6823
- const version = versionResult.stdout.trim() || versionResult.stderr.trim();
7097
+ const bin = this.resolveBin();
7098
+ const probe = await probeRuntimeVersion(bin.command, DETECT_TIMEOUT_MS3);
7099
+ if (probe.kind !== "available") return probe;
7100
+ const version = probe.stdout.trim() || probe.stderr.trim();
6824
7101
  const authPresent = await this.probeAuthPresent(bin);
6825
- return { present: true, version, authPresent };
6826
- } catch {
6827
- return { present: false };
7102
+ return { kind: "available", version, authPresent };
7103
+ } catch (error) {
7104
+ return classifyDetectError(error);
6828
7105
  }
6829
7106
  }
6830
7107
  /**
@@ -6967,7 +7244,7 @@ ${withStreams.stderr ?? ""}`);
6967
7244
  reason: "prepared codex operation received a manifest with different runtime selection"
6968
7245
  });
6969
7246
  }
6970
- const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, preparedMcpGrants);
7247
+ const mcpConfigArgs = codexMcpConfigArgs(startInput.mcpServers, runtimeEnv, this.options.sdkHelperHost, preparedMcpGrants);
6971
7248
  const { sessionRef, runner } = await runCodexTurn({
6972
7249
  command,
6973
7250
  resumeRef: startInput.manifest.sessionRef,
@@ -6983,6 +7260,7 @@ ${withStreams.stderr ?? ""}`);
6983
7260
  expectedSessionRef: startInput.manifest.sessionRef,
6984
7261
  preparedGit: startInput.manifest.workspace.workspaceId !== void 0,
6985
7262
  failurePhase: "start",
7263
+ signal: startInput.signal,
6986
7264
  terminal
6987
7265
  });
6988
7266
  return new CodexSession({
@@ -6990,6 +7268,7 @@ ${withStreams.stderr ?? ""}`);
6990
7268
  command,
6991
7269
  workspaceDir,
6992
7270
  env: startInput.env,
7271
+ mcpEnvironment: Object.fromEntries(Object.entries(runtimeEnv).filter(([key]) => key.startsWith("BYOK_MCP_PAYLOAD_"))),
6993
7272
  spawnFn: this.options.spawnFn,
6994
7273
  queue,
6995
7274
  recordUnmapped,
@@ -7042,16 +7321,18 @@ function codexMcpToolApprovalArgs(name, tools) {
7042
7321
  }
7043
7322
  return args;
7044
7323
  }
7045
- function codexMcpConfigArgs(servers, grants = []) {
7324
+ function codexMcpConfigArgs(servers, env, helperHost, grants = []) {
7046
7325
  if (servers === void 0 || Object.keys(servers).length === 0) return [];
7047
7326
  const grantedTools = new Map(grants.map((grant) => [grant.server, grant.tools]));
7048
7327
  const args = ["--ignore-user-config"];
7049
7328
  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
- }
7329
+ const key = `BYOK_MCP_PAYLOAD_${randomBytes(16).toString("hex").toUpperCase()}`;
7330
+ env[key] = JSON.stringify(server);
7331
+ const helper = resolveSdkReservedHelperBin("mcp-env", helperHost);
7332
+ args.push("-c", `mcp_servers.${name}.command=${JSON.stringify(helper.command)}`);
7333
+ args.push("-c", `mcp_servers.${name}.args=${JSON.stringify([...helper.args])}`);
7334
+ args.push("-c", `mcp_servers.${name}.env.BYOK_MCP_ENV_KEY=${JSON.stringify(key)}`);
7335
+ args.push("-c", `mcp_servers.${name}.env_vars=${JSON.stringify([key])}`);
7055
7336
  const granted = grantedTools.get(name);
7056
7337
  if (granted !== void 0) args.push(...codexMcpToolApprovalArgs(name, granted));
7057
7338
  }
@@ -7079,7 +7360,7 @@ function buildArgv(resumeRef, policyArgs, instruction, modelId, preparedGit = fa
7079
7360
  ...modelId ? ["--model", modelId] : [],
7080
7361
  ...preparedGit ? [] : ["--skip-git-repo-check"],
7081
7362
  ...policyArgs,
7082
- instruction
7363
+ "-"
7083
7364
  ];
7084
7365
  }
7085
7366
  async function runCodexTurn(params) {
@@ -7103,9 +7384,18 @@ async function runCodexTurn(params) {
7103
7384
  runner = new CodexProcessRunner({
7104
7385
  command: params.command,
7105
7386
  args: argv,
7387
+ instruction: params.instruction,
7106
7388
  cwd: params.cwd,
7107
7389
  env: params.env,
7108
7390
  spawnFn: params.spawnFn,
7391
+ onFailure: (failure) => {
7392
+ if (!firstLineSettled) {
7393
+ firstLineSettled = true;
7394
+ rejectFirstLine(failure);
7395
+ }
7396
+ params.terminal.failure = failure;
7397
+ params.queue.end();
7398
+ },
7109
7399
  onEvent: (evt) => {
7110
7400
  if (!firstLineSettled) {
7111
7401
  firstLineSettled = true;
@@ -7172,6 +7462,16 @@ async function runCodexTurn(params) {
7172
7462
  params.queue.end();
7173
7463
  });
7174
7464
  let sessionRef;
7465
+ const abortStartup = () => rejectFirstLine(new RuntimeExecutionFailure({
7466
+ phase: params.failurePhase,
7467
+ category: "infrastructure",
7468
+ retry: "non-retryable",
7469
+ reason: "codex startup cancelled or exceeded its deadline"
7470
+ }));
7471
+ const startupTimer = setTimeout(abortStartup, 3e4);
7472
+ startupTimer.unref?.();
7473
+ params.signal?.addEventListener("abort", abortStartup, { once: true });
7474
+ if (params.signal?.aborted) abortStartup();
7175
7475
  try {
7176
7476
  sessionRef = await new Promise((resolve, reject) => {
7177
7477
  let settled = false;
@@ -7203,20 +7503,31 @@ async function runCodexTurn(params) {
7203
7503
  });
7204
7504
  });
7205
7505
  } catch (err) {
7206
- runner.kill();
7506
+ await disposeFailedStartup(runner, err);
7207
7507
  throw err;
7508
+ } finally {
7509
+ clearTimeout(startupTimer);
7510
+ params.signal?.removeEventListener("abort", abortStartup);
7208
7511
  }
7209
7512
  if (params.expectedSessionRef !== void 0 && sessionRef !== params.expectedSessionRef) {
7210
- runner.kill();
7211
- throw new RuntimeExecutionFailure({
7513
+ const failure = new RuntimeExecutionFailure({
7212
7514
  phase: params.failurePhase,
7213
7515
  category: "authority",
7214
7516
  retry: "non-retryable",
7215
7517
  reason: `codex exec resume echoed a different thread id than requested (requested ${params.expectedSessionRef}, got ${sessionRef})`
7216
7518
  });
7519
+ await disposeFailedStartup(runner, failure);
7520
+ throw failure;
7217
7521
  }
7218
7522
  return { sessionRef, runner };
7219
7523
  }
7524
+ async function disposeFailedStartup(runner, cause) {
7525
+ try {
7526
+ await runner.dispose();
7527
+ } catch (disposalFailure) {
7528
+ throw new RuntimeStartupDisposalFailure(() => runner.dispose(), { cause: new AggregateError([cause, disposalFailure]) });
7529
+ }
7530
+ }
7220
7531
  var CodexSession = class {
7221
7532
  /**
7222
7533
  * NOT `readonly` (cross-model review finding): `followUp()` below
@@ -7252,6 +7563,7 @@ var CodexSession = class {
7252
7563
  * recomputed from anything a later turn supplies, so a follow-up can never
7253
7564
  * widen this session's MCP authority.
7254
7565
  */
7566
+ mcpEnvironment;
7255
7567
  mcpConfigArgs;
7256
7568
  terminal;
7257
7569
  currentRunner;
@@ -7271,6 +7583,7 @@ var CodexSession = class {
7271
7583
  this.modelId = options.modelId;
7272
7584
  this.terminal = options.terminal;
7273
7585
  this.mcpConfigArgs = options.mcpConfigArgs;
7586
+ this.mcpEnvironment = Object.freeze({ ...options.mcpEnvironment });
7274
7587
  this.currentRunner = options.initialRunner;
7275
7588
  this.ownedRunners.add(options.initialRunner);
7276
7589
  void this.forgetRunnerOnceClosed(options.initialRunner);
@@ -7383,7 +7696,7 @@ var CodexSession = class {
7383
7696
  // frozen MCP config replayed verbatim — see `mcpConfigArgs`.
7384
7697
  policyArgs: [...mapping.args, ...this.mcpConfigArgs],
7385
7698
  cwd: this.workspaceDir,
7386
- env: withoutProviderCredentials(this.env),
7699
+ env: { ...withoutProviderCredentials(this.env), ...this.mcpEnvironment },
7387
7700
  spawnFn: this.spawnFn,
7388
7701
  workspaceDir: this.workspaceDir,
7389
7702
  queue: this.queue,
@@ -7560,7 +7873,7 @@ function isNodeError(error, code) {
7560
7873
  return error instanceof Error && "code" in error && error.code === code;
7561
7874
  }
7562
7875
  async function scavengeStaleWindowsBridges() {
7563
- const temporaryRoot = os4.tmpdir();
7876
+ const temporaryRoot = os.tmpdir();
7564
7877
  let entries;
7565
7878
  try {
7566
7879
  entries = await fs14.readdir(temporaryRoot, { withFileTypes: true });
@@ -7779,7 +8092,7 @@ var DeviceCredentialStore = class {
7779
8092
  }
7780
8093
  async #windows(operation, encoded) {
7781
8094
  await scavengeStaleWindowsBridges();
7782
- const directory = await fs14.mkdtemp(path3.join(os4.tmpdir(), WINDOWS_BRIDGE_DIRECTORY_PREFIX));
8095
+ const directory = await fs14.mkdtemp(path3.join(os.tmpdir(), WINDOWS_BRIDGE_DIRECTORY_PREFIX));
7783
8096
  const executable = path3.join(directory, "credential-bridge.exe");
7784
8097
  const request = [
7785
8098
  operation,
@@ -7966,7 +8279,7 @@ var DeviceStore = class _DeviceStore {
7966
8279
  /** Internal test seam. Product construction always supplies productId and gets an OS store. */
7967
8280
  credentials;
7968
8281
  static defaultDir(productId) {
7969
- return path3__default.join(os4__default.homedir(), ".byok", productId);
8282
+ return path3__default.join(os__default.homedir(), ".byok", productId);
7970
8283
  }
7971
8284
  /**
7972
8285
  * Resolve the one store pathname every daemon/CLI component must share.
@@ -8015,6 +8328,20 @@ var DeviceStore = class _DeviceStore {
8015
8328
  await opened.handle.close();
8016
8329
  }
8017
8330
  }
8331
+ /** Rebuild only the non-secret projection, while the caller owns the store lease. */
8332
+ async reconcileMetadata(authority) {
8333
+ const projection = {
8334
+ deviceId: authority.deviceId,
8335
+ tenantId: authority.tenantId,
8336
+ devicePublicKey: authority.devicePublicKey
8337
+ };
8338
+ const current = await this.load();
8339
+ if (current !== void 0 && current.deviceId === projection.deviceId && current.tenantId === projection.tenantId && current.devicePublicKey === projection.devicePublicKey) {
8340
+ return false;
8341
+ }
8342
+ await this.save(projection);
8343
+ return true;
8344
+ }
8018
8345
  async save(record3) {
8019
8346
  assertDeviceMetadata(record3);
8020
8347
  const storeDir = path3__default.dirname(this.filePath);
@@ -8224,7 +8551,7 @@ var AuthManager = class {
8224
8551
  if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
8225
8552
  }
8226
8553
  }
8227
- const observedDeviceName = this.opts.deviceName ?? os4__default.hostname();
8554
+ const observedDeviceName = this.opts.deviceName ?? os__default.hostname();
8228
8555
  const observedMachineId = await this.opts.machineId?.();
8229
8556
  let keyPair;
8230
8557
  let pairingDeviceName = observedDeviceName;
@@ -8433,15 +8760,7 @@ var AuthManager = class {
8433
8760
  if (await this.opts.store.load() === void 0) return void 0;
8434
8761
  throw new DeviceRecordRePairRequiredError();
8435
8762
  }
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
- }
8763
+ await this.opts.store.reconcileMetadata(authority);
8445
8764
  return Object.freeze({ ...authority });
8446
8765
  }
8447
8766
  };
@@ -8738,9 +9057,9 @@ async function probeDarwin(run) {
8738
9057
  return DARWIN_UUID_RE.exec(result.stdout)?.[1];
8739
9058
  }
8740
9059
  async function probeLinux(readFile3) {
8741
- for (const path38 of LINUX_MACHINE_ID_PATHS) {
9060
+ for (const path43 of LINUX_MACHINE_ID_PATHS) {
8742
9061
  try {
8743
- const contents = (await readFile3(path38)).trim();
9062
+ const contents = (await readFile3(path43)).trim();
8744
9063
  if (contents.length > 0) return contents;
8745
9064
  } catch {
8746
9065
  }
@@ -8772,7 +9091,7 @@ async function withTimeout2(probe, timeoutMs) {
8772
9091
  async function resolveMachineId(options) {
8773
9092
  const platform = options.platform ?? process.platform;
8774
9093
  const run = options.run ?? runDeviceCommand;
8775
- const readFile3 = options.readFile ?? ((path38) => fs14.readFile(path38, "utf8"));
9094
+ const readFile3 = options.readFile ?? ((path43) => fs14.readFile(path43, "utf8"));
8776
9095
  let probe;
8777
9096
  if (platform === "darwin") probe = probeDarwin(run);
8778
9097
  else if (platform === "linux") probe = probeLinux(readFile3);
@@ -10410,13 +10729,35 @@ var LongPollClient = class {
10410
10729
  }
10411
10730
  async loop(signal) {
10412
10731
  let retryAttempt = 0;
10732
+ let readCursor;
10733
+ let readAheadSupported = false;
10734
+ let warnedWindowAt;
10413
10735
  while (this.running && !signal.aborted) {
10414
10736
  try {
10415
10737
  await this.opts.auth.getValidAccessToken();
10416
10738
  const base = toHttpBase(this.opts.serverUrl);
10417
10739
  const url = new URL(BYOK_EVENTS_PATH, base);
10418
10740
  const cursor = this.opts.getCursor();
10741
+ const ack = cursor ?? 0;
10742
+ if (readCursor !== void 0 && readCursor - ack >= MAILBOX_READ_AHEAD_MAX_SEQS) {
10743
+ if (warnedWindowAt !== ack) {
10744
+ warnedWindowAt = ack;
10745
+ console.warn("[byok/client] mailbox read-ahead window full; acknowledgement is stalled");
10746
+ }
10747
+ readCursor = ack;
10748
+ await sleep(this.opts.retryDelayMs ?? 2e3, signal);
10749
+ continue;
10750
+ }
10751
+ const afterSeq = Math.max(ack, readCursor ?? ack);
10419
10752
  if (cursor !== void 0) url.searchParams.set("cursor", String(cursor));
10753
+ if (afterSeq > ack) {
10754
+ if (!readAheadSupported) {
10755
+ const error = new Error("server does not advertise mailbox-read-ahead; cannot read past unacknowledged work");
10756
+ this.warnRouteFailure(this.eventsEndpoint, void 0, error);
10757
+ throw error;
10758
+ }
10759
+ url.searchParams.set("afterSeq", String(afterSeq));
10760
+ }
10420
10761
  let res;
10421
10762
  try {
10422
10763
  res = await authedFetch(url, { method: "GET", signal }, this.opts.auth);
@@ -10435,6 +10776,8 @@ var LongPollClient = class {
10435
10776
  return;
10436
10777
  }
10437
10778
  this.warnRouteFailure(this.eventsEndpoint, res.status, void 0);
10779
+ readCursor = void 0;
10780
+ readAheadSupported = false;
10438
10781
  this.opts.onServerCapabilitiesInvalidated?.();
10439
10782
  this.opts.onPollFailure?.();
10440
10783
  this.opts.onOperationalOutcome?.("failure");
@@ -10444,12 +10787,16 @@ var LongPollClient = class {
10444
10787
  }
10445
10788
  let parsed;
10446
10789
  try {
10447
- parsed = parseLooseEventsPollResponse(await res.json(), cursor ?? 0);
10448
- validateTrustedEventsPage(parsed.events, cursor ?? 0, parsed.cursor);
10790
+ parsed = parseLooseEventsPollResponse(await res.json(), afterSeq);
10791
+ validateTrustedEventsPage(parsed.events, afterSeq, parsed.cursor);
10792
+ if (afterSeq > ack && !parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY)) throw new Error("mailbox read-ahead capability was withdrawn");
10449
10793
  } catch (err) {
10450
10794
  this.warnRouteFailure(this.eventsEndpoint, res.status, err);
10451
10795
  throw err;
10452
10796
  }
10797
+ readAheadSupported = parsed.capabilities.includes(MAILBOX_READ_AHEAD_CAPABILITY);
10798
+ const madeReadProgress = parsed.cursor > afterSeq;
10799
+ readCursor = parsed.cursor;
10453
10800
  this.opts.onServerCapabilities?.(parsed.capabilities);
10454
10801
  let hadValidationFailureThisBatch = false;
10455
10802
  let acceptedAnyEntry = false;
@@ -10473,10 +10820,11 @@ var LongPollClient = class {
10473
10820
  if (this.opts.onEnvelope(envelope) !== false) acceptedAnyEntry = true;
10474
10821
  }
10475
10822
  if (parsed.events.length === 0) {
10823
+ readCursor = this.opts.getCursor() ?? 0;
10476
10824
  retryAttempt = 0;
10477
10825
  this.opts.onOperationalOutcome?.("success");
10478
10826
  await sleep(this.opts.idleDelayMs ?? 250, signal);
10479
- } else if (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor) {
10827
+ } else if (!madeReadProgress && (this.opts.isStalled?.() || hadValidationFailureThisBatch || !acceptedAnyEntry && this.opts.getCursor() === cursor)) {
10480
10828
  this.opts.onOperationalOutcome?.("failure");
10481
10829
  const baseMs = this.opts.retryDelayMs ?? 2e3;
10482
10830
  await sleep(this.opts.retryDelayForAttempt?.(retryAttempt++, baseMs) ?? baseMs, signal);
@@ -10485,6 +10833,8 @@ var LongPollClient = class {
10485
10833
  this.opts.onOperationalOutcome?.("success");
10486
10834
  }
10487
10835
  } catch (err) {
10836
+ readCursor = void 0;
10837
+ readAheadSupported = false;
10488
10838
  this.opts.onServerCapabilitiesInvalidated?.();
10489
10839
  if (this.noteRevoked(err)) return;
10490
10840
  if (!this.running || signal.aborted) return;
@@ -10537,6 +10887,7 @@ function createConnectionHelloEnvelope(opts, getCursor) {
10537
10887
  productId: opts.productId,
10538
10888
  clientVersion: opts.clientVersion,
10539
10889
  runtimes: opts.runtimes,
10890
+ harnesses: opts.harnesses,
10540
10891
  configuredToolsets: configuredToolsets === void 0 ? void 0 : [...configuredToolsets],
10541
10892
  cursor: getCursor()
10542
10893
  });
@@ -10550,8 +10901,8 @@ var ConnectionManager = class {
10550
10901
  serverUrl: opts.serverUrl,
10551
10902
  auth: opts.auth,
10552
10903
  // 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
10904
+ // Only report the successfully persisted cursor; using received work
10905
+ // on the wire would ack an
10555
10906
  // in-flight envelope before its handler settles and make a later
10556
10907
  // failure impossible to redeliver.
10557
10908
  getCursor: () => this.cursor,
@@ -10600,25 +10951,11 @@ var ConnectionManager = class {
10600
10951
  * guarantees in docs/protocol.md §9.
10601
10952
  */
10602
10953
  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. */
10954
+ /** Drain barrier for independently running handlers; not an admission lock. */
10621
10955
  processingChain = Promise.resolve();
10956
+ cursorInitialization;
10957
+ completionTail = Promise.resolve();
10958
+ controlTails = /* @__PURE__ */ new Map();
10622
10959
  /**
10623
10960
  * Design B (finding N4): the ONE outbound queue holds `Envelope` OBJECTS,
10624
10961
  * never re-encoded/rebuilt strings, so a
@@ -10644,38 +10981,13 @@ var ConnectionManager = class {
10644
10981
  terminalError;
10645
10982
  settledWaiters = [];
10646
10983
  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
- */
10984
+ /** Admission dedup while an individual handler is running. */
10664
10985
  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
- */
10986
+ /** Successful side effects retained until their durable acknowledgement. */
10678
10987
  processedSeqs = /* @__PURE__ */ new Set();
10988
+ failedEnvelopes = /* @__PURE__ */ new Map();
10989
+ /** Received work lacking a successful handler receipt, including malformed frames. */
10990
+ unresolvedSeqs = /* @__PURE__ */ new Set();
10679
10991
  /**
10680
10992
  * Finding P3: the pending `drainOutbox` long-poll retry backoff, if any —
10681
10993
  * cancellable so `enterRevoked()` can unblock it immediately instead of
@@ -10968,8 +11280,8 @@ var ConnectionManager = class {
10968
11280
  * - F3 (at-most-once): the old code persisted the cursor advance BEFORE
10969
11281
  * `onEnvelope` even ran (fire-and-forget) — a handler that then failed
10970
11282
  * 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
11283
+ * handlers may run independently; their receipt commits are serialized.
11284
+ * The cursor only advances
10973
11285
  * AFTER the handler resolves successfully; a rejection leaves the
10974
11286
  * cursor where it was (see `stalledAtSeq`), so a future reconnect's
10975
11287
  * redelivery re-attempts it — safe because every server->daemon type is
@@ -10985,62 +11297,72 @@ var ConnectionManager = class {
10985
11297
  if (tracked && watermark !== void 0 && envelope.seq <= watermark) return false;
10986
11298
  if (tracked) {
10987
11299
  const seq = envelope.seq;
10988
- if (this.inFlightSeqs.has(seq) || this.processedSeqs.has(seq)) return false;
11300
+ if (this.inFlightSeqs.has(seq)) return false;
11301
+ if (!this.processedSeqs.has(seq)) this.unresolvedSeqs.add(seq);
10989
11302
  this.inFlightSeqs.add(seq);
10990
- this.noteDelivered(seq);
10991
11303
  }
10992
- this.processingChain = this.processingChain.then(() => this.process(envelope, tracked));
11304
+ const controlKey = envelope.task_id !== void 0 && !envelope.type.startsWith("task.offer") ? envelope.task_id : void 0;
11305
+ const prior = controlKey === void 0 ? Promise.resolve() : this.controlTails.get(controlKey) ?? Promise.resolve();
11306
+ const operation = prior.then(() => this.process(envelope, tracked));
11307
+ if (controlKey !== void 0) {
11308
+ this.controlTails.set(controlKey, operation);
11309
+ void operation.finally(() => {
11310
+ if (this.controlTails.get(controlKey) === operation) this.controlTails.delete(controlKey);
11311
+ });
11312
+ }
11313
+ this.processingChain = Promise.all([this.processingChain, operation]).then(() => void 0);
10993
11314
  return true;
10994
11315
  }
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;
11316
+ /** A committed terminal can settle a failed offer receipt even if cloud
11317
+ * cancellation has since filtered that offer from mailbox replay. */
11318
+ retryTaskReceipts(taskId) {
11319
+ for (const envelope of this.failedEnvelopes.values()) {
11320
+ if (envelope.task_id === taskId) this.deliver(envelope);
11321
+ }
11021
11322
  }
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;
11323
+ /** Only a durable acknowledgement can deduplicate a whole prefix. */
11324
+ dedupWatermark() {
11325
+ return this.cursor;
11025
11326
  }
11026
11327
  async process(envelope, tracked) {
11027
11328
  const seq = tracked ? envelope.seq : void 0;
11028
11329
  try {
11330
+ if (tracked && seq <= (this.cursor ?? 0)) return;
11029
11331
  if (tracked && this.cursor === void 0) {
11030
- await this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0);
11031
- this.cursor = 0;
11332
+ this.cursorInitialization ??= this.opts.cursorStore.save(this.opts.serverUrl, this.opts.deviceId, 0).then(() => {
11333
+ this.cursor = 0;
11334
+ }).catch((error) => {
11335
+ this.cursorInitialization = void 0;
11336
+ throw error;
11337
+ });
11338
+ await this.cursorInitialization;
11032
11339
  }
11033
- await this.opts.onEnvelope(envelope);
11340
+ if (!tracked || !this.processedSeqs.has(seq)) await this.opts.onEnvelope(envelope);
11034
11341
  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();
11342
+ this.failedEnvelopes.delete(seq);
11343
+ this.processedSeqs.add(seq);
11344
+ this.unresolvedSeqs.delete(seq);
11345
+ const completion = this.completionTail.catch(() => void 0).then(async () => {
11346
+ let firstUnresolved = Infinity;
11347
+ for (const value of this.unresolvedSeqs) firstUnresolved = Math.min(firstUnresolved, value);
11348
+ let prefix = this.cursor ?? 0;
11349
+ for (const value of this.processedSeqs) if (value < firstUnresolved) prefix = Math.max(prefix, value);
11350
+ if (prefix > (this.cursor ?? 0)) await this.advanceCursor(prefix);
11351
+ for (const value of this.processedSeqs) {
11352
+ if (value <= (this.cursor ?? 0)) {
11353
+ this.processedSeqs.delete(value);
11354
+ this.failedEnvelopes.delete(value);
11355
+ }
11356
+ }
11357
+ this.stalledAtSeq = this.unresolvedSeqs.size > 0 ? firstUnresolved : void 0;
11358
+ });
11359
+ this.completionTail = completion;
11360
+ await completion;
11042
11361
  } catch (err) {
11043
- if (tracked && this.stalledAtSeq === void 0) this.stalledAtSeq = envelope.seq;
11362
+ if (tracked) {
11363
+ this.failedEnvelopes.set(seq, envelope);
11364
+ this.stalledAtSeq = Math.min(envelope.seq, this.stalledAtSeq ?? Infinity);
11365
+ }
11044
11366
  console.error(
11045
11367
  `[byok/client] envelope handler failed for ${envelope.type}${typeof envelope.seq === "number" ? ` (seq=${envelope.seq})` : ""}; cursor left unadvanced so a reconnect redelivers it:`,
11046
11368
  err
@@ -11049,11 +11371,10 @@ var ConnectionManager = class {
11049
11371
  if (tracked) this.inFlightSeqs.delete(seq);
11050
11372
  }
11051
11373
  }
11052
- /** Serialized with handler completion, so invalid work cannot be acked by later success. */
11374
+ /** Recorded synchronously at receive, before a later successful receipt can commit. */
11053
11375
  noteValidationFailure(seq) {
11054
- this.processingChain = this.processingChain.then(() => {
11055
- if (this.stalledAtSeq === void 0) this.stalledAtSeq = seq;
11056
- });
11376
+ this.unresolvedSeqs.add(seq);
11377
+ this.stalledAtSeq = Math.min(seq, this.stalledAtSeq ?? seq);
11057
11378
  }
11058
11379
  async advanceCursor(seq) {
11059
11380
  if (this.cursor !== void 0 && seq <= this.cursor) return;
@@ -11288,6 +11609,29 @@ var OperationalHealthTracker = class {
11288
11609
  }
11289
11610
  }
11290
11611
  };
11612
+ async function inspectOperationalHealthHandle(handle, expected) {
11613
+ const read = await readOperationalHealthHandle(handle, expected);
11614
+ if (read.status !== "read") return read;
11615
+ const { raw, sizeBytes } = read;
11616
+ let parsed;
11617
+ try {
11618
+ parsed = JSON.parse(raw);
11619
+ } catch {
11620
+ return { status: "corrupt", sizeBytes, reason: "operational health state is corrupt JSON" };
11621
+ }
11622
+ if (!isHealthFile(parsed)) {
11623
+ return { status: "corrupt", sizeBytes, reason: "operational health state has an invalid shape" };
11624
+ }
11625
+ return {
11626
+ status: "valid",
11627
+ sizeBytes,
11628
+ state: parsed.state,
11629
+ failureCount: parsed.failures.length,
11630
+ crashCount: parsed.crashes.length,
11631
+ ...parsed.currentRun ? { currentRunStartedAt: new Date(Date.parse(parsed.currentRun.startedAt)).toISOString() } : {},
11632
+ ...parsed.crashes.at(-1) ? { lastCrashAt: new Date(Date.parse(parsed.crashes.at(-1).detectedAt)).toISOString() } : {}
11633
+ };
11634
+ }
11291
11635
  async function readOperationalHealthHandle(handle, expected) {
11292
11636
  let before;
11293
11637
  try {
@@ -11321,6 +11665,20 @@ async function readOperationalHealthHandle(handle, expected) {
11321
11665
  return { status: "unavailable", sizeBytes, reason: "operational health state could not be read" };
11322
11666
  }
11323
11667
  }
11668
+ async function inspectOperationalHealthFile(storeDir) {
11669
+ let opened;
11670
+ try {
11671
+ opened = await openOperationalHealthFile(storeDir);
11672
+ } catch {
11673
+ return { status: "unavailable", reason: "operational health state could not be opened safely" };
11674
+ }
11675
+ if (!opened) return { status: "missing" };
11676
+ try {
11677
+ return await inspectOperationalHealthHandle(opened.handle, opened.stat);
11678
+ } finally {
11679
+ await opened.handle.close();
11680
+ }
11681
+ }
11324
11682
  function isHealthFile(value) {
11325
11683
  if (typeof value !== "object" || value === null) return false;
11326
11684
  const candidate = value;
@@ -11386,7 +11744,7 @@ var CursorStore = class {
11386
11744
  async save(serverUrl, deviceId, cursor) {
11387
11745
  const file = this.fileFor(serverUrl, deviceId);
11388
11746
  await promises.mkdir(path3__default.dirname(file), { recursive: true, mode: 448 });
11389
- await atomicWriteFile(file, JSON.stringify({ cursor }));
11747
+ await atomicWriteFile(file, JSON.stringify({ cursor }), { fsync: true });
11390
11748
  }
11391
11749
  /** 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
11750
  async clear(serverUrl, deviceId) {
@@ -11879,9 +12237,9 @@ function isSqliteAvailable() {
11879
12237
  }
11880
12238
  }
11881
12239
  var SECURE_FILE_MODE = 384;
11882
- function openJournalDatabase(path38, busyTimeoutMs, faults) {
12240
+ function openJournalDatabase(path43, busyTimeoutMs, faults) {
11883
12241
  const { DatabaseSync } = loadSqliteModule();
11884
- const db = new DatabaseSync(path38, { timeout: busyTimeoutMs });
12242
+ const db = new DatabaseSync(path43, { timeout: busyTimeoutMs });
11885
12243
  try {
11886
12244
  faults?.onStep?.("after-open");
11887
12245
  db.exec("PRAGMA auto_vacuum = INCREMENTAL;");
@@ -12025,9 +12383,9 @@ var RECEIVED_STATE = "received";
12025
12383
  function byteLength2(value) {
12026
12384
  return Buffer.byteLength(value, "utf8");
12027
12385
  }
12028
- function fileBytes(path38) {
12386
+ function fileBytes(path43) {
12029
12387
  try {
12030
- return statSync(path38).size;
12388
+ return statSync(path43).size;
12031
12389
  } catch {
12032
12390
  return 0;
12033
12391
  }
@@ -13370,6 +13728,109 @@ async function spillOversizedEvent(event, deps) {
13370
13728
  return bounded2.result;
13371
13729
  }
13372
13730
 
13731
+ // src/daemon/admission-wait.ts
13732
+ async function awaitAdmission(operation, signal) {
13733
+ signal.throwIfAborted();
13734
+ let onAbort;
13735
+ const aborted = new Promise((_, reject) => {
13736
+ onAbort = () => reject(signal.reason);
13737
+ signal.addEventListener("abort", onAbort, { once: true });
13738
+ });
13739
+ try {
13740
+ return await Promise.race([Promise.resolve().then(() => {
13741
+ signal.throwIfAborted();
13742
+ return operation();
13743
+ }), aborted]);
13744
+ } finally {
13745
+ signal.removeEventListener("abort", onAbort);
13746
+ }
13747
+ }
13748
+
13749
+ // src/daemon/runtime-start.ts
13750
+ async function startOwnedRuntime(start, input, signal, timeoutMs) {
13751
+ const controller = new AbortController();
13752
+ let session;
13753
+ let failure;
13754
+ let settled = false;
13755
+ const retryDisposal = async () => {
13756
+ if (session) {
13757
+ let timer2;
13758
+ try {
13759
+ await Promise.race([
13760
+ Promise.resolve().then(() => session.interrupt()).catch(() => void 0),
13761
+ new Promise((resolve) => {
13762
+ timer2 = setTimeout(resolve, 1e3);
13763
+ timer2.unref?.();
13764
+ })
13765
+ ]);
13766
+ } finally {
13767
+ if (timer2) clearTimeout(timer2);
13768
+ }
13769
+ return session.close();
13770
+ }
13771
+ if (isRuntimeStartupDisposalFailure(failure)) return failure.retryDisposal();
13772
+ if (settled && isRuntimeExecutionFailure(failure)) return;
13773
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup has not returned a cleanup receipt" });
13774
+ };
13775
+ let rejectAbort;
13776
+ const aborted = new Promise((_, reject) => {
13777
+ rejectAbort = reject;
13778
+ });
13779
+ const abort = () => {
13780
+ controller.abort();
13781
+ rejectAbort(new RuntimeStartupDisposalFailure(retryDisposal));
13782
+ };
13783
+ signal.addEventListener("abort", abort, { once: true });
13784
+ const timer = setTimeout(abort, timeoutMs);
13785
+ timer.unref?.();
13786
+ try {
13787
+ if (signal.aborted) {
13788
+ throw new RuntimeDisposalFailure({ stage: "quiescence", reason: "runtime startup was cancelled before invocation" });
13789
+ }
13790
+ const startup = Promise.resolve().then(() => start({ ...input, signal: controller.signal })).then((value) => {
13791
+ session = value;
13792
+ settled = true;
13793
+ return value;
13794
+ }, (error) => {
13795
+ failure = error;
13796
+ settled = true;
13797
+ throw error;
13798
+ });
13799
+ return await Promise.race([startup, aborted]);
13800
+ } finally {
13801
+ clearTimeout(timer);
13802
+ signal.removeEventListener("abort", abort);
13803
+ }
13804
+ }
13805
+
13806
+ // src/daemon/artifact-read.ts
13807
+ var DEFAULT_ARTIFACT_LIMITS = Object.freeze({
13808
+ maxFileBytes: 16 * 1024 * 1024,
13809
+ maxTaskBytes: 64 * 1024 * 1024
13810
+ });
13811
+ async function readArtifactBytes(handle, maxBytes, signal, account) {
13812
+ signal.throwIfAborted();
13813
+ const stat = await handle.stat();
13814
+ if (stat.size > maxBytes) throw new Error("artifact local byte budget exceeded");
13815
+ const chunks = [];
13816
+ let total = 0;
13817
+ const stream = handle.createReadStream({ autoClose: false, start: 0, signal, highWaterMark: 64 * 1024 });
13818
+ try {
13819
+ for await (const data of stream) {
13820
+ signal.throwIfAborted();
13821
+ const chunk = data;
13822
+ account(chunk.length);
13823
+ total += chunk.length;
13824
+ if (total > maxBytes) throw new Error("artifact local byte budget exceeded");
13825
+ chunks.push(chunk);
13826
+ }
13827
+ signal.throwIfAborted();
13828
+ return Buffer.concat(chunks, total);
13829
+ } finally {
13830
+ stream.destroy();
13831
+ }
13832
+ }
13833
+
13373
13834
  // src/daemon/environment.ts
13374
13835
  var BASE_PLATFORM_ALLOWLIST = [
13375
13836
  "PATH",
@@ -13569,45 +14030,121 @@ var ProgressBatcher = class {
13569
14030
  }
13570
14031
  }
13571
14032
  };
13572
- var AGENT_MESSAGE_DIRECTORY = path3__default.join(".byok", "messages");
13573
- var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
13574
- var AgentMessageOutboxError = class extends Error {
13575
- constructor(message) {
13576
- super(message);
13577
- this.name = "AgentMessageOutboxError";
14033
+ var DurableJsonlFile = class {
14034
+ constructor(filePath) {
14035
+ this.filePath = filePath;
13578
14036
  }
13579
- };
13580
- function stableJson(value) {
13581
- const encoded = JSON.stringify(value);
13582
- if (encoded === void 0) throw new AgentMessageOutboxError("message outbox value is not serializable");
13583
- return encoded;
13584
- }
13585
- function hashBody(body) {
13586
- return `sha256:${createHash("sha256").update(body, "utf8").digest("hex")}`;
13587
- }
13588
- function sameAgentRef(left, right) {
13589
- return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
13590
- }
13591
- function exactDisposition(record3, value) {
13592
- return value.messageId === record3.messageId && value.cursor === record3.cursor && value.contract === record3.contract && value.contentHash === record3.contentHash && value.sessionRef === record3.sessionRef && sameAgentRef(value.agentRef, record3.agentRef);
13593
- }
13594
- var AgentMessageOutbox = class _AgentMessageOutbox {
13595
- constructor(homeDir, outboxPath) {
13596
- this.homeDir = homeDir;
13597
- this.outboxPath = outboxPath;
14037
+ filePath;
14038
+ failure;
14039
+ assertWritable() {
14040
+ if (this.failure !== void 0) {
14041
+ throw new Error("durable JSONL writer is quarantined after uncertain I/O; reopen and validate the log before retrying", { cause: this.failure });
14042
+ }
13598
14043
  }
13599
- homeDir;
13600
- outboxPath;
13601
- pendingByTask = /* @__PURE__ */ new Map();
13602
- dispositionByTask = /* @__PURE__ */ new Map();
13603
- nextCursor = 1;
13604
- logEntries = 0;
13605
- writeTail = Promise.resolve();
13606
- static async open(homeDir) {
14044
+ /** Replay is not a durability receipt: confirm the recovered bytes before use. */
14045
+ async confirmRecovered() {
14046
+ let handle;
14047
+ try {
14048
+ handle = await promises.open(this.filePath, "r+");
14049
+ } catch (error) {
14050
+ if (error.code === "ENOENT") return;
14051
+ throw error;
14052
+ }
14053
+ try {
14054
+ await handle.sync();
14055
+ } finally {
14056
+ await handle.close();
14057
+ }
14058
+ await this.syncDirectory();
14059
+ }
14060
+ async append(json) {
14061
+ this.assertWritable();
14062
+ try {
14063
+ const handle = await promises.open(this.filePath, constants.O_APPEND | constants.O_CREAT | constants.O_WRONLY, 384);
14064
+ try {
14065
+ const bytes = Buffer.from(`${json}
14066
+ `, "utf8");
14067
+ let offset = 0;
14068
+ while (offset < bytes.length) {
14069
+ const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset);
14070
+ if (bytesWritten <= 0) throw new Error("durable JSONL append made no progress");
14071
+ offset += bytesWritten;
14072
+ }
14073
+ await handle.sync();
14074
+ } finally {
14075
+ await handle.close();
14076
+ }
14077
+ await this.syncDirectory();
14078
+ } catch (error) {
14079
+ this.failure = error;
14080
+ throw error;
14081
+ }
14082
+ }
14083
+ async replace(lines) {
14084
+ this.assertWritable();
14085
+ try {
14086
+ await atomicWriteFile(this.filePath, lines.map((line) => `${line}
14087
+ `).join(""), { mode: 384, fsync: true });
14088
+ } catch (error) {
14089
+ this.failure = error;
14090
+ throw error;
14091
+ }
14092
+ }
14093
+ async syncDirectory() {
14094
+ if (process.platform === "win32") return;
14095
+ const directory = await promises.open(path3__default.dirname(this.filePath), "r");
14096
+ try {
14097
+ await directory.sync();
14098
+ } finally {
14099
+ await directory.close();
14100
+ }
14101
+ }
14102
+ };
14103
+
14104
+ // src/daemon/agent-message-outbox.ts
14105
+ var AGENT_MESSAGE_DIRECTORY = path3__default.join(".byok", "messages");
14106
+ var AGENT_MESSAGE_OUTBOX_FILENAME = "outbox-v1.jsonl";
14107
+ var AgentMessageOutboxError = class extends Error {
14108
+ constructor(message) {
14109
+ super(message);
14110
+ this.name = "AgentMessageOutboxError";
14111
+ }
14112
+ };
14113
+ function stableJson(value) {
14114
+ const encoded = JSON.stringify(value);
14115
+ if (encoded === void 0) throw new AgentMessageOutboxError("message outbox value is not serializable");
14116
+ return encoded;
14117
+ }
14118
+ function hashBody(body) {
14119
+ return `sha256:${createHash("sha256").update(body, "utf8").digest("hex")}`;
14120
+ }
14121
+ function sameAgentRef(left, right) {
14122
+ return left.agentId === right.agentId && left.profileRevision === right.profileRevision;
14123
+ }
14124
+ function exactDisposition(record3, value) {
14125
+ return value.messageId === record3.messageId && value.cursor === record3.cursor && value.contract === record3.contract && value.contentHash === record3.contentHash && value.sessionRef === record3.sessionRef && sameAgentRef(value.agentRef, record3.agentRef);
14126
+ }
14127
+ var AgentMessageOutbox = class _AgentMessageOutbox {
14128
+ constructor(homeDir, outboxPath) {
14129
+ this.homeDir = homeDir;
14130
+ this.outboxPath = outboxPath;
14131
+ this.file = new DurableJsonlFile(outboxPath);
14132
+ }
14133
+ homeDir;
14134
+ outboxPath;
14135
+ pendingByTask = /* @__PURE__ */ new Map();
14136
+ revokedTasks = /* @__PURE__ */ new Set();
14137
+ dispositionByTask = /* @__PURE__ */ new Map();
14138
+ file;
14139
+ nextCursor = 1;
14140
+ logEntries = 0;
14141
+ writeTail = Promise.resolve();
14142
+ static async open(homeDir) {
13607
14143
  const directory = path3__default.join(homeDir, AGENT_MESSAGE_DIRECTORY);
13608
14144
  await ensureSecureDir(directory);
13609
14145
  const outbox = new _AgentMessageOutbox(homeDir, path3__default.join(directory, AGENT_MESSAGE_OUTBOX_FILENAME));
13610
14146
  await outbox.load();
14147
+ await outbox.file.confirmRecovered();
13611
14148
  return outbox;
13612
14149
  }
13613
14150
  /** Re-open every existing Agent-local message outbox without following Agent-home symlinks. */
@@ -13648,9 +14185,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13648
14185
  records() {
13649
14186
  return Object.freeze([...this.pendingByTask.values()].sort((a, b) => a.cursor - b.cursor));
13650
14187
  }
13651
- /** Activated records with no exact disposition yet; only these may be transport-replayed. */
14188
+ /** Records without disposition or local revoke; transport replay additionally requires session binding. */
13652
14189
  retryableRecords() {
13653
- return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId)));
14190
+ return Object.freeze(this.records().filter((record3) => !this.dispositionByTask.has(record3.taskId) && !this.revokedTasks.has(record3.taskId)));
13654
14191
  }
13655
14192
  get(taskId) {
13656
14193
  return this.pendingByTask.get(taskId);
@@ -13662,13 +14199,14 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13662
14199
  if (byteCount < 1 || byteCount > input.requirement.maxBytes) throw new AgentMessageOutboxError("message body exceeds the offer byte contract");
13663
14200
  const existing = this.pendingByTask.get(input.taskId);
13664
14201
  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)) {
14202
+ 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
14203
  throw new AgentMessageOutboxError("required-message task already has a different immutable draft");
13667
14204
  }
13668
14205
  return existing;
13669
14206
  }
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);
14207
+ const sending = this.records().filter((record4) => !this.isTerminalEvidence(record4.taskId));
14208
+ if (sending.length >= input.maxPendingEvents) throw new AgentMessageOutboxError("message outbox event quota is exhausted");
14209
+ const pendingBytes = sending.reduce((sum, record4) => sum + record4.byteCount, 0);
13672
14210
  if (pendingBytes + byteCount > input.maxPendingBytes) throw new AgentMessageOutboxError("message outbox byte quota is exhausted");
13673
14211
  const record3 = Object.freeze({
13674
14212
  schema: 1,
@@ -13690,8 +14228,9 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13690
14228
  return record3;
13691
14229
  });
13692
14230
  }
13693
- async activate(taskId, sessionRef) {
14231
+ async activate(taskId, sessionRef, isCurrent = () => true) {
13694
14232
  return this.exclusive(async () => {
14233
+ if (!isCurrent() || this.revokedTasks.has(taskId)) return void 0;
13695
14234
  const record3 = this.pendingByTask.get(taskId);
13696
14235
  if (record3 === void 0) return void 0;
13697
14236
  if (record3.sessionRef !== void 0) {
@@ -13704,6 +14243,22 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13704
14243
  return activated;
13705
14244
  });
13706
14245
  }
14246
+ /** Local lifecycle cancellation, never a synthesized consumer disposition. */
14247
+ async revoke(taskId) {
14248
+ return this.exclusive(async () => {
14249
+ const record3 = this.pendingByTask.get(taskId);
14250
+ if (record3 === void 0 || this.revokedTasks.has(taskId)) return;
14251
+ await this.appendEntry({ schema: 1, kind: "revoke", taskId, messageId: record3.messageId });
14252
+ this.revokedTasks.add(taskId);
14253
+ });
14254
+ }
14255
+ /** The same terminal classification used by operator receipts and archival. */
14256
+ terminalRecords() {
14257
+ return Object.freeze(this.records().filter((record3) => this.isTerminalEvidence(record3.taskId)));
14258
+ }
14259
+ isTerminalEvidence(taskId) {
14260
+ return this.revokedTasks.has(taskId) || this.dispositionByTask.get(taskId)?.outcome === "refused";
14261
+ }
13707
14262
  publishPayload(record3) {
13708
14263
  if (record3.sessionRef === void 0) throw new AgentMessageOutboxError("message draft is not bound to a durable session");
13709
14264
  return AgentMessagePublishPayloadSchema.parse({
@@ -13727,6 +14282,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13727
14282
  await this.appendEntry({ schema: 1, kind: "disposition", taskId, disposition });
13728
14283
  if (disposition.outcome === "accepted") {
13729
14284
  this.pendingByTask.delete(taskId);
14285
+ this.revokedTasks.delete(taskId);
13730
14286
  this.dispositionByTask.delete(taskId);
13731
14287
  } else {
13732
14288
  this.dispositionByTask.set(taskId, disposition);
@@ -13745,6 +14301,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13745
14301
  if (error.code === "ENOENT") return;
13746
14302
  throw error;
13747
14303
  }
14304
+ 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
14305
  for (const line of raw.split("\n")) {
13749
14306
  if (line.length === 0) continue;
13750
14307
  const entry = JSON.parse(line);
@@ -13759,11 +14316,16 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13759
14316
  if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message activation has no exact draft");
13760
14317
  if (record3.sessionRef !== void 0 && record3.sessionRef !== entry.sessionRef) throw new AgentMessageOutboxError("message activation session conflicts");
13761
14318
  this.pendingByTask.set(entry.taskId, Object.freeze({ ...record3, sessionRef: entry.sessionRef }));
14319
+ } else if (entry.kind === "revoke") {
14320
+ const record3 = this.pendingByTask.get(entry.taskId);
14321
+ if (record3 === void 0 || record3.messageId !== entry.messageId) throw new AgentMessageOutboxError("message revocation has no exact draft");
14322
+ this.revokedTasks.add(entry.taskId);
13762
14323
  } else if (entry.kind === "disposition") {
13763
14324
  const record3 = this.pendingByTask.get(entry.taskId);
13764
14325
  if (record3 === void 0 || !exactDisposition(record3, entry.disposition)) throw new AgentMessageOutboxError("message outbox contains a mismatched disposition");
13765
14326
  if (entry.disposition.outcome === "accepted") {
13766
14327
  this.pendingByTask.delete(entry.taskId);
14328
+ this.revokedTasks.delete(entry.taskId);
13767
14329
  this.dispositionByTask.delete(entry.taskId);
13768
14330
  } else {
13769
14331
  this.dispositionByTask.set(entry.taskId, entry.disposition);
@@ -13774,23 +14336,47 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13774
14336
  }
13775
14337
  }
13776
14338
  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
- }
14339
+ await this.file.append(stableJson(entry));
14340
+ this.logEntries += 1;
13786
14341
  }
13787
- async compact() {
13788
- const entries = this.records().flatMap((record3) => {
14342
+ /**
14343
+ * Explicit operator maintenance under the Agent-home single-writer lease.
14344
+ * Copy complete terminal evidence durably before removing it from the live log.
14345
+ * Archives are audit artifacts, never another replay input. held remains live.
14346
+ */
14347
+ async archiveTerminalRecords(archiveDirectory) {
14348
+ return this.exclusive(async () => {
14349
+ if (!path3__default.isAbsolute(archiveDirectory)) throw new AgentMessageOutboxError("message archive directory must be absolute");
14350
+ const terminal = this.terminalRecords();
14351
+ if (terminal.length === 0) return void 0;
14352
+ await ensureSecureDir(archiveDirectory);
14353
+ const archivePath = path3__default.join(archiveDirectory, `message-terminal-${randomUUID()}.jsonl`);
14354
+ await atomicWriteFile(archivePath, this.snapshotEntries(terminal).map((entry) => `${stableJson(entry)}
14355
+ `).join(""), { mode: 384, fsync: true });
14356
+ const retained = this.records().filter((record3) => !this.isTerminalEvidence(record3.taskId));
14357
+ const entries = this.snapshotEntries(retained);
14358
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
14359
+ for (const record3 of terminal) {
14360
+ this.pendingByTask.delete(record3.taskId);
14361
+ this.dispositionByTask.delete(record3.taskId);
14362
+ this.revokedTasks.delete(record3.taskId);
14363
+ }
14364
+ this.logEntries = entries.length;
14365
+ return archivePath;
14366
+ });
14367
+ }
14368
+ snapshotEntries(records) {
14369
+ return records.flatMap((record3) => {
13789
14370
  const append = { schema: 1, kind: "append", record: record3 };
13790
14371
  const disposition = this.dispositionByTask.get(record3.taskId);
13791
- return disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record3.taskId, disposition }];
14372
+ const entries = disposition === void 0 ? [append] : [append, { schema: 1, kind: "disposition", taskId: record3.taskId, disposition }];
14373
+ if (this.revokedTasks.has(record3.taskId)) entries.push({ schema: 1, kind: "revoke", taskId: record3.taskId, messageId: record3.messageId });
14374
+ return entries;
13792
14375
  });
13793
- await atomicWriteFile(this.outboxPath, entries.map(stableJson).join(entries.length === 0 ? "" : "\n"), { mode: 384 });
14376
+ }
14377
+ async compact() {
14378
+ const entries = this.snapshotEntries(this.records());
14379
+ await this.file.replace(entries.map((entry) => stableJson(entry)));
13794
14380
  this.logEntries = entries.length;
13795
14381
  }
13796
14382
  async exclusive(fn) {
@@ -13801,6 +14387,7 @@ var AgentMessageOutbox = class _AgentMessageOutbox {
13801
14387
  });
13802
14388
  await prior;
13803
14389
  try {
14390
+ this.file.assertWritable();
13804
14391
  return await fn();
13805
14392
  } finally {
13806
14393
  release();
@@ -14981,7 +15568,7 @@ var TaskRunner = class {
14981
15568
  */
14982
15569
  stoppingOffers = false;
14983
15570
  get activeTaskCount() {
14984
- return this.tasks.size;
15571
+ return this.tasks.size + this.startupOwners.size;
14985
15572
  }
14986
15573
  /**
14987
15574
  * Transport-boundary classification for the currently active task. Legacy
@@ -15027,6 +15614,12 @@ var TaskRunner = class {
15027
15614
  const active = this.tasks.get(taskId);
15028
15615
  const context = pending ?? (active?.messageRequirement && active.agentRef && active.messageOutbox ? { agentRef: active.agentRef, requirement: active.messageRequirement, outbox: active.messageOutbox } : void 0);
15029
15616
  if (context === void 0) throw new Error("task has no active required Agent message contract");
15617
+ const isCurrent = () => {
15618
+ if (this.messageContextByToken.get(input.contextToken) !== taskId || this.messageContextByTask.get(taskId) !== input.contextToken || this.pendingCancelled.has(taskId)) return false;
15619
+ const current2 = this.tasks.get(taskId);
15620
+ return current2 === void 0 ? active === void 0 && this.pendingMessageTasks.get(taskId) === pending : (active === void 0 || current2 === active) && current2.messageOutbox === context.outbox && this.canPublishAgentMessage(current2);
15621
+ };
15622
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15030
15623
  const record3 = await context.outbox.appendDraft({
15031
15624
  taskId,
15032
15625
  tenantId: this.deps.tenantId,
@@ -15037,8 +15630,17 @@ var TaskRunner = class {
15037
15630
  maxPendingEvents: 64,
15038
15631
  maxPendingBytes: 4 * 1024 * 1024
15039
15632
  });
15040
- if (active === void 0) return { messageId: record3.messageId, state: "staged" };
15041
- const activated = await context.outbox.activate(taskId, active.session.sessionRef);
15633
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15634
+ const current = this.tasks.get(taskId);
15635
+ if (current === void 0) return { messageId: record3.messageId, state: "staged" };
15636
+ const activated = await this.activateAgentMessage(
15637
+ context.outbox,
15638
+ taskId,
15639
+ current.session.sessionRef,
15640
+ isCurrent,
15641
+ () => current.messageSendAttempted === true
15642
+ );
15643
+ if (!isCurrent()) throw new Error("invalid or expired Agent message task context");
15042
15644
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
15043
15645
  this.sendAgentMessageRecord(context.outbox, activated);
15044
15646
  return { messageId: activated.messageId, state: "pending" };
@@ -15074,19 +15676,39 @@ var TaskRunner = class {
15074
15676
  });
15075
15677
  return opened;
15076
15678
  }
15679
+ /** A recovery terminal must not close first-message admission before this durable draft has a disposition. */
15680
+ hasPendingRecoveredAgentMessage(taskId) {
15681
+ return this.recoveredMessageOutboxes.get(taskId)?.retryableRecords().some((record3) => record3.taskId === taskId && record3.sessionRef !== void 0) === true;
15682
+ }
15077
15683
  /** Retry stable recovered records after a transport handshake/re-handshake. */
15078
15684
  retryRecoveredAgentMessages() {
15079
15685
  for (const [taskId, outbox] of this.recoveredMessageOutboxes) {
15080
15686
  const record3 = outbox.get(taskId);
15081
- if (record3?.sessionRef !== void 0) this.sendAgentMessageRecord(outbox, record3);
15687
+ if (record3?.sessionRef !== void 0 && this.hasPendingRecoveredAgentMessage(taskId)) this.sendAgentMessageRecord(outbox, record3);
15082
15688
  }
15083
15689
  }
15690
+ canPublishAgentMessage(active) {
15691
+ return this.tasks.get(active.taskId) === active && !active.beingTornDown;
15692
+ }
15693
+ /** All authors and startup use the same queue-time and post-I/O authority check. */
15694
+ async activateAgentMessage(outbox, taskId, sessionRef, isCurrent, wasSendAttempted = () => false) {
15695
+ if (!isCurrent()) return void 0;
15696
+ const record3 = await outbox.activate(taskId, sessionRef, isCurrent);
15697
+ if (!isCurrent()) {
15698
+ if (!wasSendAttempted()) await outbox.revoke(taskId);
15699
+ return void 0;
15700
+ }
15701
+ return record3;
15702
+ }
15084
15703
  sendAgentMessageRecord(outbox, record3) {
15704
+ const active = this.tasks.get(record3.taskId);
15705
+ const authorized = active === void 0 ? this.recoveredMessageOutboxes.get(record3.taskId) === outbox : active.messageOutbox === outbox && this.canPublishAgentMessage(active);
15706
+ if (!authorized || record3.sessionRef === void 0 || !outbox.retryableRecords().some((pending) => pending.messageId === record3.messageId)) return;
15707
+ if (active !== void 0) active.messageSendAttempted = true;
15085
15708
  this.deps.send(createEnvelope("agent.message.publish", outbox.publishPayload(record3), {
15086
15709
  taskId: record3.taskId,
15087
15710
  sessionRef: record3.sessionRef
15088
15711
  }));
15089
- const active = this.tasks.get(record3.taskId);
15090
15712
  if (active?.messageOutbox === outbox) {
15091
15713
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15092
15714
  active.messageRetryTimer = setTimeout(() => {
@@ -15126,6 +15748,7 @@ var TaskRunner = class {
15126
15748
  if (outcome === "accepted") this.recoveredMessageOutboxes.delete(taskId);
15127
15749
  return;
15128
15750
  }
15751
+ if (this.tasks.get(taskId) !== active) return;
15129
15752
  if (outcome === "held") {
15130
15753
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15131
15754
  active.messageRetryTimer = void 0;
@@ -15135,6 +15758,8 @@ var TaskRunner = class {
15135
15758
  if (active.messageRetryTimer !== void 0) clearTimeout(active.messageRetryTimer);
15136
15759
  active.messageRetryTimer = void 0;
15137
15760
  this.revokeAgentMessageContext(taskId);
15761
+ active.pendingMessageCompletion = void 0;
15762
+ await this.fail(taskId, "required Agent message was refused", false);
15138
15763
  return;
15139
15764
  }
15140
15765
  if (outcome !== "accepted") return;
@@ -15153,6 +15778,9 @@ var TaskRunner = class {
15153
15778
  /** M4 Phase 2: stop claiming any FUTURE `task.offer` — see `stoppingOffers`'s own doc comment. Idempotent. */
15154
15779
  stopAcceptingOffers() {
15155
15780
  this.stoppingOffers = true;
15781
+ if (this.startupRetryTimer) clearTimeout(this.startupRetryTimer);
15782
+ this.startupRetryTimer = void 0;
15783
+ for (const abort of this.inFlightBlobAborts.values()) abort.abort();
15156
15784
  }
15157
15785
  /**
15158
15786
  * Shutdown of every currently ACTIVE task for the control socket's
@@ -15191,9 +15819,65 @@ var TaskRunner = class {
15191
15819
  * ahead of this method — see `daemon-control-socket.test.ts`'s dedicated
15192
15820
  * regression test for the exact scenario.
15193
15821
  */
15822
+ /** Startup resources have an owner even before a Session can become active. */
15823
+ startupRetryTimer;
15824
+ claimedHarnesses = /* @__PURE__ */ new Map();
15825
+ homeReservations = /* @__PURE__ */ new Map();
15826
+ startupOwners = /* @__PURE__ */ new Map();
15827
+ startupDisposals = /* @__PURE__ */ new Map();
15828
+ disposeStartupOwner(taskId) {
15829
+ const existing = this.startupDisposals.get(taskId);
15830
+ if (existing) return existing;
15831
+ const attempt = this.disposeStartupOwnerOnce(taskId).finally(() => this.startupDisposals.delete(taskId));
15832
+ this.startupDisposals.set(taskId, attempt);
15833
+ return attempt;
15834
+ }
15835
+ async disposeStartupOwnerOnce(taskId) {
15836
+ const owner = this.startupOwners.get(taskId);
15837
+ if (!owner) return true;
15838
+ try {
15839
+ await owner.settle?.();
15840
+ await owner.dispose();
15841
+ await this.deps.awaitTerminalCommit?.(taskId);
15842
+ await owner.release();
15843
+ } catch (caught) {
15844
+ const failure = isRuntimeDisposalFailure(caught) ? caught : new RuntimeDisposalFailure({
15845
+ stage: "quiescence",
15846
+ reason: "startup runtime disposal has not been confirmed"
15847
+ }, { cause: caught });
15848
+ this.deps.onRuntimeDisposalFailure?.({
15849
+ taskId,
15850
+ runtimeId: owner.runtimeId,
15851
+ stage: failure.stage,
15852
+ reason: failure.message
15853
+ });
15854
+ console.error(`[byok/client] startup ownership retained for ${taskId}: ${failure.message}`);
15855
+ if (!this.startupRetryTimer && !this.stoppingOffers) {
15856
+ this.startupRetryTimer = setTimeout(() => {
15857
+ this.startupRetryTimer = void 0;
15858
+ for (const id of this.startupOwners.keys()) {
15859
+ if (!this.inFlightOffers.has(id)) void this.disposeStartupOwner(id);
15860
+ }
15861
+ }, 1e3);
15862
+ this.startupRetryTimer.unref?.();
15863
+ }
15864
+ return false;
15865
+ }
15866
+ if (this.startupOwners.get(taskId) === owner) this.startupOwners.delete(taskId);
15867
+ this.pendingMessageTasks.delete(taskId);
15868
+ this.revokeAgentMessageContext(taskId);
15869
+ this.revokeAgentMemoryContext(taskId);
15870
+ return true;
15871
+ }
15194
15872
  async shutdownActiveTasks(reason) {
15195
15873
  const active = [...this.tasks.values()];
15196
15874
  await Promise.all(active.map((task) => this.shutdownTask(task, reason)));
15875
+ for (const taskId of this.startupOwners.keys()) {
15876
+ if (!await this.disposeStartupOwner(taskId)) throw new RuntimeDisposalFailure({
15877
+ stage: "quiescence",
15878
+ reason: "startup runtime ownership remains quarantined"
15879
+ });
15880
+ }
15197
15881
  }
15198
15882
  /**
15199
15883
  * M5 batch-3 (workstream 2): the ONE shared per-task teardown sequence —
@@ -15225,14 +15909,19 @@ var TaskRunner = class {
15225
15909
  * a genuine protocol bug, not a benign race — mirrors `pump()`'s own
15226
15910
  * identity-check guard for the same class of race.
15227
15911
  */
15912
+ async interruptBounded(session) {
15913
+ await raceSettleFirst(
15914
+ () => session.interrupt(),
15915
+ this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS
15916
+ );
15917
+ }
15228
15918
  async teardownActiveTask(active, reason, retryable) {
15229
15919
  if (active.finalizationStarted) return this.finish(active.taskId);
15230
15920
  if (!this.reserveSemanticTerminal(active)) return active.semanticTerminalSettled ?? false;
15231
15921
  active.beingTornDown = true;
15232
15922
  active.blobAbort.abort();
15233
15923
  await this.observeGit(active, "salvage");
15234
- const timeoutMs = this.deps.shutdownInterruptTimeoutMs ?? DEFAULT_SHUTDOWN_INTERRUPT_TIMEOUT_MS;
15235
- await raceSettleFirst(() => active.session.interrupt(), timeoutMs);
15924
+ await this.interruptBounded(active.session);
15236
15925
  if (this.tasks.get(active.taskId) !== active) return true;
15237
15926
  await this.persistAgentTerminalEvidence(active, "failed", reason);
15238
15927
  this.deps.send(
@@ -15334,7 +16023,7 @@ var TaskRunner = class {
15334
16023
  }
15335
16024
  }
15336
16025
  async handleOffer(taskId, payload, strictAgentOffer) {
15337
- if (this.tasks.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
16026
+ if (this.tasks.has(taskId) || this.startupOwners.has(taskId) || this.inFlightOffers.has(taskId) || this.finishedTaskIds.has(taskId) || this.strictDeclinedTaskIds.has(taskId)) {
15338
16027
  return;
15339
16028
  }
15340
16029
  let agentRef;
@@ -15377,9 +16066,29 @@ var TaskRunner = class {
15377
16066
  }
15378
16067
  this.inFlightOffers.add(taskId);
15379
16068
  const blobAbort = new AbortController();
16069
+ const startupDeadline = Date.now() + (this.deps.startupTimeoutMs ?? 3e4);
16070
+ const startupTimer = setTimeout(
16071
+ () => blobAbort.abort(new Error("runtime startup deadline exceeded during admission/start")),
16072
+ this.deps.startupTimeoutMs ?? 3e4
16073
+ );
16074
+ startupTimer.unref?.();
16075
+ const admissionWithdrawn = () => {
16076
+ if (!blobAbort.signal.aborted) return false;
16077
+ const cancelled = this.pendingCancelled.has(taskId);
16078
+ decline(cancelled ? "cancelled before claim" : this.stoppingOffers ? "daemon is shutting down" : "runtime startup deadline exceeded", !cancelled);
16079
+ return true;
16080
+ };
15380
16081
  this.inFlightBlobAborts.set(taskId, blobAbort);
15381
16082
  let agentBinding;
15382
16083
  let agentLeaseTransferred = false;
16084
+ let reservedHome;
16085
+ const releaseReservation = () => {
16086
+ if (reservedHome === void 0) return;
16087
+ const remaining = this.homeReservations.get(reservedHome) - 1;
16088
+ if (remaining === 0) this.homeReservations.delete(reservedHome);
16089
+ else this.homeReservations.set(reservedHome, remaining);
16090
+ reservedHome = void 0;
16091
+ };
15383
16092
  try {
15384
16093
  if (this.pendingCancelled.has(taskId)) {
15385
16094
  const reason = this.pendingCancelled.get(taskId);
@@ -15404,11 +16113,14 @@ var TaskRunner = class {
15404
16113
  );
15405
16114
  return;
15406
16115
  }
15407
- const active2 = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome);
15408
- if (active2 >= limit) {
15409
- decline(`agent home busy: ${active2} active attempt(s)`, true);
16116
+ if (admissionWithdrawn()) return;
16117
+ const active = this.deps.agentHome.executionLeaseManager.activeAttemptCount(canonicalHome) + (this.homeReservations.get(canonicalHome) ?? 0);
16118
+ if (active >= limit) {
16119
+ decline(`agent home busy: ${active} active attempt(s)`, true);
15410
16120
  return;
15411
16121
  }
16122
+ this.homeReservations.set(canonicalHome, (this.homeReservations.get(canonicalHome) ?? 0) + 1);
16123
+ reservedHome = canonicalHome;
15412
16124
  }
15413
16125
  const guarded = this.deps.admissionGuard?.({ taskId, payload });
15414
16126
  if (guarded !== void 0 && !guarded.admit) {
@@ -15440,6 +16152,14 @@ var TaskRunner = class {
15440
16152
  );
15441
16153
  return;
15442
16154
  }
16155
+ if (payload.harnessId !== void 0 && (payload.runtime !== void 0 || payload.dispatchSelection !== void 0)) {
16156
+ decline("harnessId cannot be combined with a built-in runtime selection", false);
16157
+ return;
16158
+ }
16159
+ if (payload.harnessId !== void 0 && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16160
+ decline("server does not support custom-harness identity", false);
16161
+ return;
16162
+ }
15443
16163
  const requiredToolsets = "requiredToolsets" in payload ? payload.requiredToolsets : void 0;
15444
16164
  const resolvedMcp = requiredToolsets ? this.resolveMcpServers(requiredToolsets) : void 0;
15445
16165
  if (resolvedMcp && !resolvedMcp.ok) {
@@ -15452,17 +16172,29 @@ var TaskRunner = class {
15452
16172
  return;
15453
16173
  }
15454
16174
  const offered = withoutRequiredToolsets(payload);
15455
- const requestedRuntime = payload.dispatchSelection?.runtimeId ?? payload.runtime;
16175
+ const requestedRuntime = payload.harnessId ?? payload.dispatchSelection?.runtimeId ?? payload.runtime;
15456
16176
  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
- );
16177
+ let pick;
16178
+ try {
16179
+ pick = await this.pickAdapter(
16180
+ requestedRuntime,
16181
+ payload.policy.mode,
16182
+ requiredToolsets !== void 0 || messageRequirement !== void 0 || requiresAgentMemoryMcp,
16183
+ blobAbort.signal
16184
+ );
16185
+ } catch (error) {
16186
+ if (!admissionWithdrawn()) decline(`runtime detection failed: ${errorMessage6(error)}`, true);
16187
+ return;
16188
+ }
16189
+ if (admissionWithdrawn()) return;
15462
16190
  if (!pick.ok) {
15463
16191
  decline(pick.reason, pick.retryable);
15464
16192
  return;
15465
16193
  }
16194
+ if (!isKnownRuntimeId(pick.descriptor.id) && !(this.deps.getServerCapabilities?.() ?? []).includes("custom-harness")) {
16195
+ decline("server does not support the selected custom harness identity", false);
16196
+ return;
16197
+ }
15466
16198
  const env = buildRuntimeEnv({
15467
16199
  ambient: process.env,
15468
16200
  requirements: pick.descriptor.environmentRequirements,
@@ -15536,18 +16268,20 @@ var TaskRunner = class {
15536
16268
  }
15537
16269
  let prepared;
15538
16270
  try {
15539
- prepared = await pick.adapter.prepare({
16271
+ prepared = await awaitAdmission(() => pick.adapter.prepare({
16272
+ signal: blobAbort.signal,
15540
16273
  offer: offered,
15541
16274
  policy: decision.policy,
15542
16275
  descriptor: pick.descriptor,
15543
16276
  requiredToolsetIds: requiredToolsets ?? [],
15544
16277
  ...taskMcpServers === void 0 ? {} : { mcpServers: taskMcpServers },
15545
16278
  ...mcpToolsetTools === void 0 ? {} : { mcpToolsetTools }
15546
- });
16279
+ }), blobAbort.signal);
15547
16280
  } catch (error) {
15548
- decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
16281
+ if (!admissionWithdrawn()) decline(`runtime preparation failed: ${errorMessage6(error)}`, true);
15549
16282
  return;
15550
16283
  }
16284
+ if (admissionWithdrawn()) return;
15551
16285
  if (prepared.kind === "reject") {
15552
16286
  decline(prepared.reason, prepared.retryable);
15553
16287
  return;
@@ -15555,6 +16289,7 @@ var TaskRunner = class {
15555
16289
  if (agentRef !== void 0) {
15556
16290
  try {
15557
16291
  agentBinding = await this.deps.agentHome.acquireExecution(agentRef, { taskId, sessionRef });
16292
+ releaseReservation();
15558
16293
  } catch (error) {
15559
16294
  decline(
15560
16295
  `Agent home admission failed: ${errorMessage6(error)}`,
@@ -15679,7 +16414,16 @@ var TaskRunner = class {
15679
16414
  },
15680
16415
  forwardedEnvironmentNames: Object.freeze(Object.keys(env).sort())
15681
16416
  });
16417
+ if (admissionWithdrawn()) {
16418
+ gitLease?.release();
16419
+ return;
16420
+ }
15682
16421
  await this.deps.beforeClaim?.(taskId, manifest.descriptor.id);
16422
+ if (admissionWithdrawn()) {
16423
+ gitLease?.release();
16424
+ return;
16425
+ }
16426
+ if (!isKnownRuntimeId(manifest.descriptor.id)) this.claimedHarnesses.set(taskId, manifest.descriptor.id);
15683
16427
  this.deps.send(
15684
16428
  createEnvelope(
15685
16429
  "task.claim",
@@ -15697,6 +16441,7 @@ var TaskRunner = class {
15697
16441
  // where an auto-selected task left the server never learning
15698
16442
  // which runtime actually ran.
15699
16443
  runtime: isKnownRuntimeId(manifest.descriptor.id) ? manifest.descriptor.id : void 0,
16444
+ ...!isKnownRuntimeId(manifest.descriptor.id) ? { harnessId: manifest.descriptor.id } : {},
15700
16445
  // S0/D-4 (`task.claim.capabilities`, docs/protocol.md §2.4): the
15701
16446
  // selected adapter's own capability self-report, carried on the
15702
16447
  // same message that establishes the task↔runtime binding. The
@@ -15814,8 +16559,57 @@ var TaskRunner = class {
15814
16559
  };
15815
16560
  let session;
15816
16561
  try {
15817
- session = await prepared.operation.start(startInput);
16562
+ session = await startOwnedRuntime(
16563
+ (input) => prepared.operation.start(input),
16564
+ startInput,
16565
+ blobAbort.signal,
16566
+ Math.max(0, startupDeadline - Date.now())
16567
+ );
15818
16568
  } catch (err) {
16569
+ const disposalFailure = isRuntimeStartupDisposalFailure(err);
16570
+ const cancelled = this.pendingCancelled.has(taskId);
16571
+ if (disposalFailure || cancelled) {
16572
+ if (cancelled) this.revokeAgentMessageContext(taskId);
16573
+ const ownedBinding2 = agentBinding;
16574
+ const cancellation = createEnvelope("task.cancelled", {
16575
+ reason: this.pendingCancelled.get(taskId),
16576
+ ...terminalIdentity(this.claimedHarnesses.get(taskId)),
16577
+ ...agentBinding === void 0 ? {} : { agentRef: agentBinding.resolution.agentRef }
16578
+ }, { taskId });
16579
+ let cancellationSettled = false;
16580
+ this.startupOwners.set(taskId, {
16581
+ runtimeId: pick.descriptor.id,
16582
+ dispose: disposalFailure ? err.retryDisposal : async () => {
16583
+ },
16584
+ ...cancelled ? { settle: async () => {
16585
+ if (cancellationSettled) return;
16586
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
16587
+ this.deps.send(cancellation);
16588
+ cancellationSettled = true;
16589
+ this.pendingCancelled.delete(taskId);
16590
+ this.addFinishedTaskId(taskId);
16591
+ } } : {},
16592
+ release: async () => {
16593
+ await ownedBinding2?.lease.release();
16594
+ gitLease?.release();
16595
+ }
16596
+ });
16597
+ agentLeaseTransferred = true;
16598
+ if (disposalFailure) this.deps.onRuntimeDisposalFailure?.({
16599
+ taskId,
16600
+ runtimeId: pick.descriptor.id,
16601
+ stage: "quiescence",
16602
+ reason: err.message
16603
+ });
16604
+ if (!cancelled) {
16605
+ if (agentBinding === void 0) await this.fail(taskId, errorMessage6(err), false);
16606
+ else await this.failClaimedAgent(taskId, errorMessage6(err), false, {
16607
+ binding: agentBinding,
16608
+ runtimeId: pick.descriptor.id
16609
+ });
16610
+ }
16611
+ return;
16612
+ }
15819
16613
  const failure = projectRuntimeBoundaryFailure(err, "start");
15820
16614
  if (failure.contractViolation) {
15821
16615
  console.error("[byok/client] runtime adapter start() returned an untyped failure", err);
@@ -15832,143 +16626,169 @@ var TaskRunner = class {
15832
16626
  }
15833
16627
  return;
15834
16628
  }
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;
16629
+ const ownedBinding = agentBinding;
16630
+ this.startupOwners.set(taskId, {
16631
+ runtimeId: pick.descriptor.id,
16632
+ dispose: () => session.close(),
16633
+ release: async () => {
16634
+ await ownedBinding?.lease.release();
16635
+ gitLease?.release();
15852
16636
  }
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,
16637
+ });
16638
+ try {
16639
+ if (agentBinding !== void 0) {
16640
+ try {
16641
+ await agentBinding.lease.bindSession(session.sessionRef);
16642
+ } catch (error) {
16643
+ await this.failClaimedAgent(
15880
16644
  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,
16645
+ `Agent session execution lease could not bind the runtime session: ${errorMessage6(error)}`,
16646
+ false,
16647
+ {
16648
+ binding: agentBinding,
16649
+ runtimeId: pick.descriptor.id,
16650
+ sessionRef: session.sessionRef
16651
+ }
16652
+ );
16653
+ return;
16654
+ }
16655
+ }
16656
+ let active;
16657
+ active = {
16658
+ taskId,
16659
+ blobAbort,
16660
+ egressEnabled: "egressPolicy" in payload,
16661
+ adapter: pick.adapter,
16662
+ session,
16663
+ workspaceDir,
16664
+ ...agentBinding === void 0 ? {} : {
16665
+ agentBinding,
16666
+ agentRef: agentBinding.resolution.agentRef,
16667
+ agentHandoff: {
16668
+ sessionRef: session.sessionRef,
15917
16669
  runtimeId: pick.descriptor.id,
15918
- sessionRef: session.sessionRef
16670
+ cwd: workspaceDir
15919
16671
  }
16672
+ },
16673
+ gitWorkspaceId,
16674
+ gitLease,
16675
+ gitBaseline,
16676
+ summaryParts: [],
16677
+ finalTextParts: [],
16678
+ batcher: new ProgressBatcher(
16679
+ (seq, events) => {
16680
+ const projected = active.egressEnabled ? this.deps.agentEgress?.projectLatestValue({
16681
+ agentRef: active.agentRef,
16682
+ taskId,
16683
+ events,
16684
+ serverCapabilities: this.deps.getServerCapabilities?.() ?? []
16685
+ }) ?? [] : events;
16686
+ if (projected.length > 0) this.deps.send(createEnvelope("task.progress", { seq, events: [...projected] }, { taskId, seq }));
16687
+ },
16688
+ this.deps.batcherOptions
16689
+ ),
16690
+ approvalQueue: [],
16691
+ outputBytesSoFar: 0,
16692
+ startedAtMs: Date.now(),
16693
+ ...messageRequirement === void 0 ? {} : {
16694
+ messageRequirement,
16695
+ messageOutbox: this.pendingMessageTasks.get(taskId).outbox
16696
+ },
16697
+ ...terminalProjection === void 0 ? {} : { terminalProjection }
16698
+ };
16699
+ if (agentBinding !== void 0) {
16700
+ const binding = agentBinding;
16701
+ try {
16702
+ await this.deps.agentHome.mutateExecution(binding, () => this.deps.agentSessionHandoffs.record({
16703
+ agentRef: binding.resolution.agentRef,
16704
+ taskId,
16705
+ sessionRef: session.sessionRef,
16706
+ runtimeId: pick.descriptor.id,
16707
+ cwd: workspaceDir,
16708
+ leaseId: binding.lease.leaseId
16709
+ }));
16710
+ } catch (error) {
16711
+ await this.failClaimedAgent(
16712
+ taskId,
16713
+ `Agent session handoff could not be durably written before start: ${errorMessage6(error)}`,
16714
+ false,
16715
+ {
16716
+ binding: agentBinding,
16717
+ runtimeId: pick.descriptor.id,
16718
+ sessionRef: session.sessionRef
16719
+ }
16720
+ );
16721
+ return;
16722
+ }
16723
+ }
16724
+ const activatedMessageRecord = active.messageOutbox === void 0 ? void 0 : await this.activateAgentMessage(
16725
+ active.messageOutbox,
16726
+ taskId,
16727
+ session.sessionRef,
16728
+ () => !this.pendingCancelled.has(taskId) && !blobAbort.signal.aborted
16729
+ );
16730
+ if (this.pendingCancelled.has(taskId)) {
16731
+ const reason = this.pendingCancelled.get(taskId);
16732
+ this.pendingCancelled.delete(taskId);
16733
+ agentLeaseTransferred = agentBinding !== void 0;
16734
+ this.startupOwners.delete(taskId);
16735
+ this.tasks.set(taskId, active);
16736
+ this.reserveSemanticTerminal(active);
16737
+ await this.interruptBounded(session);
16738
+ await this.updateGitPhaseBestEffort(gitWorkspaceId, "cancelled");
16739
+ await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16740
+ this.deps.send(
16741
+ createEnvelope(
16742
+ "task.cancelled",
16743
+ { reason, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16744
+ { taskId }
16745
+ )
15920
16746
  );
16747
+ await this.finish(taskId);
15921
16748
  return;
15922
16749
  }
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);
16750
+ this.deps.send(createEnvelope("task.started", {}, { taskId }));
15928
16751
  agentLeaseTransferred = agentBinding !== void 0;
16752
+ this.inFlightBlobAborts.delete(taskId);
16753
+ this.startupOwners.delete(taskId);
15929
16754
  this.tasks.set(taskId, active);
15930
- this.reserveSemanticTerminal(active);
15931
- try {
15932
- await session.interrupt();
15933
- } catch {
16755
+ this.pendingMessageTasks.delete(taskId);
16756
+ if (active.messageOutbox !== void 0 && activatedMessageRecord !== void 0) {
16757
+ this.sendAgentMessageRecord(active.messageOutbox, activatedMessageRecord);
15934
16758
  }
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(() => {
16759
+ if (payload.limits?.maxDurationMs !== void 0) {
16760
+ this.armMaxDurationTimer(active, payload.limits.maxDurationMs);
16761
+ }
16762
+ void this.pump(active);
16763
+ if (agentBinding === void 0) {
16764
+ void this.deps.sessionWorkspaces.record(session.sessionRef, {
16765
+ workspaceDir,
16766
+ runtimeSessionId: session.sessionRef,
16767
+ ...gitWorkspaceId ? { workspaceKind: "git", gitWorkspaceId } : {}
16768
+ }).catch(() => {
16769
+ });
16770
+ }
16771
+ if (gitWorkspaceId && this.deps.gitWorkspaceStore) {
16772
+ void this.deps.gitWorkspaceStore.attachSession(gitWorkspaceId, session.sessionRef).catch(() => {
16773
+ });
16774
+ }
16775
+ } catch (error) {
16776
+ if (this.tasks.has(taskId)) throw error;
16777
+ const reason = `runtime activation failed: ${errorMessage6(error)}`;
16778
+ if (agentBinding === void 0) await this.fail(taskId, reason, false);
16779
+ else await this.failClaimedAgent(taskId, reason, false, {
16780
+ binding: agentBinding,
16781
+ runtimeId: pick.descriptor.id,
16782
+ sessionRef: session.sessionRef
15969
16783
  });
15970
16784
  }
15971
16785
  } finally {
16786
+ clearTimeout(startupTimer);
16787
+ releaseReservation();
16788
+ if (this.startupOwners.has(taskId)) {
16789
+ agentLeaseTransferred = true;
16790
+ await this.disposeStartupOwner(taskId);
16791
+ }
15972
16792
  if (!agentLeaseTransferred) {
15973
16793
  this.pendingMessageTasks.delete(taskId);
15974
16794
  this.revokeAgentMessageContext(taskId);
@@ -15980,6 +16800,7 @@ var TaskRunner = class {
15980
16800
  }
15981
16801
  this.inFlightBlobAborts.delete(taskId);
15982
16802
  this.inFlightOffers.delete(taskId);
16803
+ this.claimedHarnesses.delete(taskId);
15983
16804
  }
15984
16805
  }
15985
16806
  withAgentMessageMcp(existing, taskId, requirement) {
@@ -16249,7 +17070,13 @@ var TaskRunner = class {
16249
17070
  maxPendingBytes: 4 * 1024 * 1024
16250
17071
  });
16251
17072
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16252
- const activated = await outbox.activate(active.taskId, active.session.sessionRef);
17073
+ const activated = await this.activateAgentMessage(
17074
+ outbox,
17075
+ active.taskId,
17076
+ active.session.sessionRef,
17077
+ () => this.canPublishAgentMessage(active),
17078
+ () => active.messageSendAttempted === true
17079
+ );
16253
17080
  if (this.tasks.get(active.taskId) !== active || active.beingTornDown) return;
16254
17081
  if (activated === void 0) throw new Error("Agent message draft disappeared before activation");
16255
17082
  this.sendAgentMessageRecord(outbox, activated);
@@ -16356,14 +17183,24 @@ var TaskRunner = class {
16356
17183
  }
16357
17184
  let bytes;
16358
17185
  try {
16359
- bytes = await opened.handle.readFile();
16360
- } catch (err) {
17186
+ const limits = this.deps.artifactLimits ?? DEFAULT_ARTIFACT_LIMITS;
17187
+ const remaining = limits.maxTaskBytes - (active.artifactBytesSoFar ?? 0);
17188
+ bytes = await readArtifactBytes(
17189
+ opened.handle,
17190
+ Math.min(limits.maxFileBytes, remaining),
17191
+ active.blobAbort.signal,
17192
+ (count) => {
17193
+ active.artifactBytesSoFar = (active.artifactBytesSoFar ?? 0) + count;
17194
+ }
17195
+ );
17196
+ } catch (err) {
16361
17197
  this.reportArtifactError(active, name, `failed to read artifact "${name}": ${errorMessage6(err)}`);
16362
17198
  return;
16363
17199
  } finally {
16364
17200
  await opened.handle.close().catch(() => {
16365
17201
  });
16366
17202
  }
17203
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16367
17204
  if (bytes.length <= MAX_INLINE_ARTIFACT_BYTES) {
16368
17205
  const inline = bytes.toString("base64");
16369
17206
  if (new TextEncoder().encode(inline).length <= MAX_INLINE_ARTIFACT_BYTES) {
@@ -16375,6 +17212,7 @@ var TaskRunner = class {
16375
17212
  const blobRef = await this.deps.blobClient.uploadArtifact(bytes, contentType, {
16376
17213
  signal: active.blobAbort.signal
16377
17214
  });
17215
+ if (this.tasks.get(active.taskId) !== active || active.blobAbort.signal.aborted || active.beingTornDown) return;
16378
17216
  this.deps.send(createEnvelope("task.artifact", { name, contentType, blobRef }, { taskId: active.taskId }));
16379
17217
  } catch (err) {
16380
17218
  this.reportArtifactError(active, name, `failed to upload artifact "${name}": ${errorMessage6(err)}`);
@@ -16382,12 +17220,17 @@ var TaskRunner = class {
16382
17220
  }
16383
17221
  /** 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
17222
  reportArtifactError(active, name, reason) {
17223
+ if (active.blobAbort.signal.aborted || active.beingTornDown) return;
16385
17224
  console.error(`[byok/client] artifact "${name}" for task ${active.taskId} dropped: ${reason}`);
16386
17225
  active.batcher.push({ type: "error", message: reason });
16387
17226
  }
16388
17227
  async handleCancel(taskId, reason) {
16389
17228
  const active = this.tasks.get(taskId);
16390
17229
  if (!active) {
17230
+ if (this.startupOwners.has(taskId) && !this.inFlightOffers.has(taskId)) {
17231
+ await this.disposeStartupOwner(taskId);
17232
+ return;
17233
+ }
16391
17234
  this.setPendingCancelled(taskId, reason);
16392
17235
  this.inFlightBlobAborts.get(taskId)?.abort();
16393
17236
  return;
@@ -16401,10 +17244,7 @@ var TaskRunner = class {
16401
17244
  return;
16402
17245
  }
16403
17246
  active.blobAbort.abort();
16404
- try {
16405
- await active.session.interrupt();
16406
- } catch {
16407
- }
17247
+ await this.interruptBounded(active.session);
16408
17248
  await this.observeGit(active, "salvage");
16409
17249
  await this.persistAgentTerminalEvidence(active, "cancelled", reason);
16410
17250
  this.deps.send(
@@ -16846,10 +17686,7 @@ var TaskRunner = class {
16846
17686
  await active.semanticTerminalSettled;
16847
17687
  return;
16848
17688
  }
16849
- try {
16850
- await active.session.interrupt();
16851
- } catch {
16852
- }
17689
+ await this.interruptBounded(active.session);
16853
17690
  await this.observeGit(active, "salvage");
16854
17691
  await this.persistAgentTerminalEvidence(active, "failed", reason ?? "rejected");
16855
17692
  this.deps.send(
@@ -16884,7 +17721,7 @@ var TaskRunner = class {
16884
17721
  this.deps.send(
16885
17722
  createEnvelope(
16886
17723
  "task.fail",
16887
- active === void 0 ? { reason, retryable } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
17724
+ active === void 0 ? { reason, retryable, ...terminalIdentity(this.claimedHarnesses.get(taskId)) } : { reason, retryable, ...this.terminalInferenceUsagePayload(active), ...this.agentTerminalPayload(active) },
16888
17725
  { taskId }
16889
17726
  )
16890
17727
  );
@@ -16898,6 +17735,7 @@ var TaskRunner = class {
16898
17735
  * the wire and handleOffer's finally block releases the lease.
16899
17736
  */
16900
17737
  async failClaimedAgent(taskId, reason, retryable, context) {
17738
+ await this.pendingMessageTasks.get(taskId)?.outbox.revoke(taskId);
16901
17739
  const agentRef = context.binding.resolution.agentRef;
16902
17740
  const result = await this.retryAgentTerminalEvidence(
16903
17741
  () => this.deps.agentHome.mutateExecution(context.binding, () => this.deps.agentSessionHandoffs.recordTaskTerminal({
@@ -16923,7 +17761,7 @@ var TaskRunner = class {
16923
17761
  }
16924
17762
  this.deps.send(createEnvelope(
16925
17763
  "task.fail",
16926
- { reason, retryable, agentRef },
17764
+ { reason, retryable, ...terminalIdentity(context.runtimeId, agentRef) },
16927
17765
  { taskId }
16928
17766
  ));
16929
17767
  return result.ok;
@@ -16941,7 +17779,8 @@ var TaskRunner = class {
16941
17779
  terminalInferenceUsagePayload(active) {
16942
17780
  const release = this.deps.localAgentRelease;
16943
17781
  const runtimeId = active.adapter.descriptor.id;
16944
- if (release === void 0 || active.lastUsage === void 0 || !isKnownRuntimeId(runtimeId)) return {};
17782
+ if (!isKnownRuntimeId(runtimeId)) return {};
17783
+ if (release === void 0 || active.lastUsage === void 0) return {};
16945
17784
  const nowMs = Date.now();
16946
17785
  const durationMs = terminalUsageNumber(nowMs - active.startedAtMs, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS);
16947
17786
  const promptTokens = terminalUsageNumber(active.lastUsage?.inputTokens, TERMINAL_INFERENCE_USAGE_MAX_TOKENS);
@@ -16959,7 +17798,7 @@ var TaskRunner = class {
16959
17798
  }
16960
17799
  /** Exact Agent identity projection for claim/terminal wire payloads. */
16961
17800
  agentTerminalPayload(active) {
16962
- return active.agentRef === void 0 ? {} : { agentRef: active.agentRef };
17801
+ return terminalIdentity(active.adapter.descriptor.id, active.agentRef);
16963
17802
  }
16964
17803
  /**
16965
17804
  * additive-minor (`task.complete.document`): the whole daemon-side gate
@@ -17116,6 +17955,14 @@ var TaskRunner = class {
17116
17955
  }
17117
17956
  /** Persist Agent terminal truth before wire when local storage is available. */
17118
17957
  async persistAgentTerminalEvidence(active, cause, reason) {
17958
+ if (cause === "cancelled" || cause === "failed" && !active.messageSendAttempted) {
17959
+ try {
17960
+ await active.messageOutbox?.revoke(active.taskId);
17961
+ } catch (error) {
17962
+ active.resolveSemanticTerminalSettled?.(false);
17963
+ throw error;
17964
+ }
17965
+ }
17119
17966
  active.terminalCause = cause;
17120
17967
  active.terminalReason = reason;
17121
17968
  if (active.agentBinding === void 0 || active.agentRef === void 0 || active.agentHandoff === void 0) {
@@ -17178,7 +18025,19 @@ var TaskRunner = class {
17178
18025
  error
17179
18026
  });
17180
18027
  }
17181
- async finish(taskId) {
18028
+ async retryTerminalFinalization(taskId) {
18029
+ const active = this.tasks.get(taskId);
18030
+ if (active?.finalizationStarted) await this.finish(taskId);
18031
+ }
18032
+ finalizationAttempts = /* @__PURE__ */ new Map();
18033
+ finish(taskId) {
18034
+ const existing = this.finalizationAttempts.get(taskId);
18035
+ if (existing) return existing;
18036
+ const attempt = this.finishOnce(taskId).finally(() => this.finalizationAttempts.delete(taskId));
18037
+ this.finalizationAttempts.set(taskId, attempt);
18038
+ return attempt;
18039
+ }
18040
+ async finishOnce(taskId) {
17182
18041
  const active = this.tasks.get(taskId);
17183
18042
  if (!active) return true;
17184
18043
  if (!active.finalizationStarted) {
@@ -17229,6 +18088,13 @@ var TaskRunner = class {
17229
18088
  return false;
17230
18089
  }
17231
18090
  if (this.tasks.get(taskId) !== active) return true;
18091
+ try {
18092
+ await this.deps.awaitTerminalCommit?.(taskId);
18093
+ } catch {
18094
+ active.resolveSemanticTerminalSettled?.(false);
18095
+ return false;
18096
+ }
18097
+ if (this.tasks.get(taskId) !== active) return true;
17232
18098
  if (active.agentBinding !== void 0 && active.agentRef !== void 0 && active.agentHandoff !== void 0) {
17233
18099
  const agentRef = active.agentRef;
17234
18100
  const handoff = active.agentHandoff;
@@ -17340,7 +18206,7 @@ var TaskRunner = class {
17340
18206
  * is device-specific (which runtimes happen to be installed here), so a
17341
18207
  * different device's installed runtime set might satisfy it.
17342
18208
  */
17343
- async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets) {
18209
+ async pickAdapter(requestedRuntime, policyMode, requiresMcpToolsets, signal) {
17344
18210
  const allowlist = this.deps.runtimeAllowlist;
17345
18211
  if (requestedRuntime) {
17346
18212
  if (allowlist && !allowlist.includes(requestedRuntime)) {
@@ -17369,8 +18235,8 @@ var TaskRunner = class {
17369
18235
  retryable: false
17370
18236
  };
17371
18237
  }
17372
- const detected = await adapter.detect();
17373
- if (!detected.present) {
18238
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18239
+ if (detected.kind !== "available") {
17374
18240
  return {
17375
18241
  ok: false,
17376
18242
  reason: `runtime "${requestedRuntime}" is not installed/available on this device`,
@@ -17385,8 +18251,8 @@ var TaskRunner = class {
17385
18251
  const descriptor = freezeRuntimeAdapterDescriptor(adapter.descriptor);
17386
18252
  if (!adapterSupportsMode(descriptor, policyMode)) continue;
17387
18253
  if (requiresMcpToolsets && !adapterSupportsMcpToolsets(descriptor)) continue;
17388
- const detected = await adapter.detect();
17389
- if (detected.present) return { ok: true, adapter, descriptor };
18254
+ const detected = validateRuntimeDetectResult(await awaitAdmission(() => adapter.detect(signal), signal));
18255
+ if (detected.kind === "available") return { ok: true, adapter, descriptor };
17390
18256
  }
17391
18257
  return {
17392
18258
  ok: false,
@@ -17550,10 +18416,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17550
18416
  constructor(homeDir, spoolPath) {
17551
18417
  this.homeDir = homeDir;
17552
18418
  this.spoolPath = spoolPath;
18419
+ this.file = new DurableJsonlFile(spoolPath);
17553
18420
  }
17554
18421
  homeDir;
17555
18422
  spoolPath;
17556
18423
  pending = /* @__PURE__ */ new Map();
18424
+ file;
17557
18425
  nextCursor = 1;
17558
18426
  logEntries = 0;
17559
18427
  writeTail = Promise.resolve();
@@ -17562,6 +18430,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17562
18430
  await ensureSecureDir(directory);
17563
18431
  const spool = new _AgentReliableSpool(homeDir, path3__default.join(directory, AGENT_RELIABLE_SPOOL_FILENAME));
17564
18432
  await spool.load();
18433
+ await spool.file.confirmRecovered();
17565
18434
  return spool;
17566
18435
  }
17567
18436
  get pendingEvents() {
@@ -17581,6 +18450,25 @@ var AgentReliableSpool = class _AgentReliableSpool {
17581
18450
  const payloadJson = stableRecordJson(input.payload);
17582
18451
  const byteCount = Buffer.byteLength(payloadJson, "utf8");
17583
18452
  if (byteCount <= 0) throw new AgentReliableSpoolError("reliable payload must have positive serialized bytes");
18453
+ const eventId = input.eventId ?? randomUUID();
18454
+ const existing = this.pending.get(eventId);
18455
+ if (existing !== void 0) {
18456
+ if (!sameRecord(existing, {
18457
+ wireType: "agent.egress.reliable",
18458
+ agentRef: input.agentRef,
18459
+ tenantId: input.tenantId,
18460
+ policyRevision: input.policyRevision,
18461
+ eventId,
18462
+ cursor: existing.cursor,
18463
+ payloadHash: contentHash(payloadJson),
18464
+ byteCount,
18465
+ sessionRef: input.sessionRef,
18466
+ taskId: input.taskId
18467
+ }) || stableRecordJson(existing.payload) !== payloadJson || input.createdAt !== void 0 && input.createdAt !== existing.createdAt) {
18468
+ throw new AgentReliableSpoolError(`reliable eventId ${eventId} differs from its existing record`);
18469
+ }
18470
+ return existing;
18471
+ }
17584
18472
  if (this.pending.size >= policy.reliable.maxPendingEventsPerAgent) {
17585
18473
  throw new AgentReliableQuotaError("quota_exceeded", "reliable per-Agent event quota is exhausted");
17586
18474
  }
@@ -17590,8 +18478,6 @@ var AgentReliableSpool = class _AgentReliableSpool {
17590
18478
  if (tenantPendingBytes + byteCount > policy.reliable.maxPendingBytesPerTenant) {
17591
18479
  throw new AgentReliableQuotaError("quota_exceeded", "reliable tenant byte quota is exhausted");
17592
18480
  }
17593
- const eventId = input.eventId ?? randomUUID();
17594
- if (this.pending.has(eventId)) throw new AgentReliableSpoolError(`reliable eventId ${eventId} already exists`);
17595
18481
  const record3 = Object.freeze({
17596
18482
  schema: 1,
17597
18483
  wireType: "agent.egress.reliable",
@@ -17700,6 +18586,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17700
18586
  if (error.code === "ENOENT") return;
17701
18587
  throw error;
17702
18588
  }
18589
+ 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
18590
  for (const line of raw.split("\n")) {
17704
18591
  if (line.length === 0) continue;
17705
18592
  let entry;
@@ -17722,19 +18609,12 @@ var AgentReliableSpool = class _AgentReliableSpool {
17722
18609
  }
17723
18610
  }
17724
18611
  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
- }
18612
+ await this.file.append(stableRecordJson(entry));
18613
+ this.logEntries += 1;
17734
18614
  }
17735
18615
  async compact() {
17736
18616
  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 });
18617
+ await this.file.replace(entries.map((entry) => stableRecordJson(entry)));
17738
18618
  this.logEntries = entries.length;
17739
18619
  }
17740
18620
  async exclusive(fn) {
@@ -17745,6 +18625,7 @@ var AgentReliableSpool = class _AgentReliableSpool {
17745
18625
  });
17746
18626
  await prior;
17747
18627
  try {
18628
+ this.file.assertWritable();
17748
18629
  return await fn();
17749
18630
  } finally {
17750
18631
  release();
@@ -19119,17 +20000,26 @@ function isRuntimeId(id) {
19119
20000
  return id === "pi" || id === "claude" || id === "codex";
19120
20001
  }
19121
20002
  async function detectRuntimes(adapters) {
19122
- const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: await adapter.detect() })));
20003
+ const detections = await Promise.all(adapters.map(async (adapter) => ({ adapter, detected: validateRuntimeDetectResult(await adapter.detect()) })));
19123
20004
  const runtimes = [];
20005
+ const harnesses = [];
19124
20006
  for (const { adapter, detected } of detections) {
19125
- if (!detected.present || !isRuntimeId(adapter.descriptor.id)) continue;
20007
+ if (detected.kind !== "available") continue;
20008
+ if (!isRuntimeId(adapter.descriptor.id)) {
20009
+ harnesses.push({
20010
+ id: HarnessIdSchema.parse(adapter.descriptor.id),
20011
+ ...detected.version === void 0 ? {} : { version: detected.version },
20012
+ capabilities: toRuntimeInfoCapabilities(adapter.descriptor.capabilities)
20013
+ });
20014
+ continue;
20015
+ }
19126
20016
  const info = { id: adapter.descriptor.id };
19127
20017
  if (detected.version !== void 0) info.version = detected.version;
19128
20018
  if (detected.authPresent !== void 0) info.authPresent = detected.authPresent;
19129
20019
  info.capabilities = toRuntimeInfoCapabilities(adapter.descriptor.capabilities);
19130
20020
  runtimes.push(info);
19131
20021
  }
19132
- return runtimes;
20022
+ return { runtimes, harnesses };
19133
20023
  }
19134
20024
  function computeCapabilities(adapters, agentHomeConfigured = false, strictAgentOnly = false, agentHomeProjectionConfigured = false, agentEgressConfigured = false, contentReadPolicies, providerProfileBindingConfigured = false) {
19135
20025
  const flags = [];
@@ -19219,7 +20109,7 @@ function buildAdapter(id, config) {
19219
20109
  resolveApprovalMcpBin: () => resolveApprovalMcpBin(config.sdkHelperHost)
19220
20110
  });
19221
20111
  case "codex":
19222
- return new CodexAdapter();
20112
+ return new CodexAdapter({ sdkHelperHost: config.sdkHelperHost });
19223
20113
  }
19224
20114
  }
19225
20115
  function buildDefaultAdapters(config) {
@@ -19296,6 +20186,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19296
20186
  const localAgentRelease = resolveLocalAgentReleaseIdentity(config.localAgentRelease);
19297
20187
  const toolsetRegistry = new McpToolsetRegistry(config.mcpToolsets);
19298
20188
  validatePiByokLauncherConfig(config.piByokLauncher);
20189
+ if (config.startupTimeoutMs !== void 0 && (!Number.isSafeInteger(config.startupTimeoutMs) || config.startupTimeoutMs <= 0)) throw new Error("startupTimeoutMs must be a positive safe integer");
20190
+ if (config.artifactLimits !== void 0) {
20191
+ for (const value of [config.artifactLimits.maxFileBytes, config.artifactLimits.maxTaskBytes]) {
20192
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error("artifactLimits must contain positive safe integer byte budgets");
20193
+ }
20194
+ }
19299
20195
  if (config.maxTaskOutputBytes !== void 0 && !(config.maxTaskOutputBytes > 0)) {
19300
20196
  throw new Error(
19301
20197
  `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 +20321,12 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19425
20321
  pressureEngine = ownedPressureEngine;
19426
20322
  }
19427
20323
  }
19428
- let journalTerminalTail = Promise.resolve();
20324
+ const terminalCommits = new TerminalCommitQueue((taskId) => {
20325
+ connection?.retryTaskReceipts(taskId);
20326
+ void runner?.retryTerminalFinalization(taskId).catch((error) => {
20327
+ console.error("[byok/client] terminal finalization still requires recovery", error);
20328
+ });
20329
+ });
19429
20330
  const observer = new DaemonObserver();
19430
20331
  const approvalRegistry = new ApprovalRegistry();
19431
20332
  let connection;
@@ -19560,6 +20461,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19560
20461
  await runLifecycleMutation(startUnderLease);
19561
20462
  }
19562
20463
  async function startUnderLease() {
20464
+ terminalCommits.resume();
19563
20465
  if (!daemonOwnerLease) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
19564
20466
  try {
19565
20467
  let record3;
@@ -19611,7 +20513,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19611
20513
  const terminal = createEnvelope("task.fail", {
19612
20514
  reason: "daemon_interrupted",
19613
20515
  retryable: false,
19614
- ...payload.agentRef === void 0 ? {} : { agentRef: payload.agentRef }
20516
+ ...terminalIdentity(task.claimedRuntime, payload.agentRef),
20517
+ recovery: { kind: "daemon_interrupted", offerId: offer.id }
19615
20518
  }, { taskId: task.taskId });
19616
20519
  const bytes = encodeEnvelope(terminal);
19617
20520
  await activeJournal.recordTerminal({
@@ -19639,7 +20542,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19639
20542
  await gitWorkspaceStore?.reconcile();
19640
20543
  }
19641
20544
  blobLifecycleAbort = new AbortController();
19642
- const [runtimes, blobClient] = await Promise.all([
20545
+ const [{ runtimes, harnesses }, blobClient] = await Promise.all([
19643
20546
  detectRuntimes(adapters),
19644
20547
  Promise.resolve(new BlobClient(config.serverUrl, auth, { signal: blobLifecycleAbort.signal }))
19645
20548
  ]);
@@ -19660,6 +20563,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19660
20563
  tenantId: record3.tenantId,
19661
20564
  deviceId: record3.deviceId
19662
20565
  }) : void 0;
20566
+ capabilities.push("custom-harness");
19663
20567
  const sendSanitizedEnvelope = (envelope) => {
19664
20568
  const terminalKind = terminalKindOf(envelope.type);
19665
20569
  if (!activeJournal || !journalIdentity || terminalKind === void 0 || envelope.task_id === void 0) {
@@ -19669,7 +20573,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19669
20573
  }
19670
20574
  const taskId = envelope.task_id;
19671
20575
  const bytes = encodeEnvelope(envelope);
19672
- journalTerminalTail = journalTerminalTail.catch(() => void 0).then(async () => {
20576
+ terminalCommits.enqueue(taskId, async () => {
19673
20577
  try {
19674
20578
  await activeJournal.recordTerminal({
19675
20579
  taskId,
@@ -19697,7 +20601,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19697
20601
  const failure = createEnvelope("task.fail", {
19698
20602
  reason: "terminal_result_too_large",
19699
20603
  retryable: false,
19700
- ...offerPayload.agentRef === void 0 ? {} : { agentRef: offerPayload.agentRef }
20604
+ ...terminalIdentity(task.claimedRuntime, offerPayload.agentRef)
19701
20605
  }, { taskId });
19702
20606
  const failureBytes = encodeEnvelope(failure);
19703
20607
  await activeJournal.recordTerminal({
@@ -19715,8 +20619,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19715
20619
  observer.handleOutboundEnvelope(decoded);
19716
20620
  connection?.send(decoded);
19717
20621
  }
19718
- }).catch((error) => {
19719
- console.error("[byok/client] terminal durability failed; execution remains unsettled", error);
19720
20622
  });
19721
20623
  };
19722
20624
  const sendEnvelope = (candidate) => {
@@ -19798,7 +20700,10 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19798
20700
  // Finding F5(a): see TaskRunnerDeps.shutdownInterruptTimeoutMs's own doc comment.
19799
20701
  shutdownInterruptTimeoutMs: overrides.shutdown?.taskInterruptTimeoutMs,
19800
20702
  // M5 batch-3 (workstream 2): see DaemonConfig.maxTaskOutputBytes's own doc comment — already validated above.
20703
+ awaitTerminalCommit: (taskId) => terminalCommits.receipt(taskId),
19801
20704
  maxTaskOutputBytes: config.maxTaskOutputBytes,
20705
+ artifactLimits: config.artifactLimits,
20706
+ startupTimeoutMs: config.startupTimeoutMs,
19802
20707
  // See DaemonConfig.maxInlineEventBytes's own doc comment — already validated above.
19803
20708
  maxInlineEventBytes: config.maxInlineEventBytes,
19804
20709
  // additive-minor (`task.complete.document`): passed through verbatim,
@@ -19846,6 +20751,20 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19846
20751
  if (config.agentHome !== void 0 && config.agentEgress !== void 0) {
19847
20752
  await runner.recoverAgentMessageOutboxes(path3__default.join(config.agentHome.hostStorageRoot, "agents"));
19848
20753
  }
20754
+ const replayRecoveryTerminals = async (taskId) => {
20755
+ if (!activeJournal || !journalIdentity) return;
20756
+ for (const terminal of await activeJournal.listPendingTerminals(journalIdentity)) {
20757
+ if (terminal.truthState !== "pending" || taskId !== void 0 && terminal.taskId !== taskId) continue;
20758
+ if (runner?.hasPendingRecoveredAgentMessage(terminal.taskId)) continue;
20759
+ const envelope = decodeEnvelope(terminal.bytes);
20760
+ if (envelope.task_id !== terminal.taskId || terminalKindOf(envelope.type) !== terminal.terminalType) {
20761
+ throw new Error("durable terminal execution binding mismatch");
20762
+ }
20763
+ overrides.executionRecoveryFault?.("terminal:before-send");
20764
+ observer.handleOutboundEnvelope(envelope);
20765
+ connection?.send(envelope);
20766
+ }
20767
+ };
19849
20768
  const handleAgentHomeProjectionEnvelope = async (envelope) => {
19850
20769
  if (envelope.type !== "agent.home.projection") return false;
19851
20770
  if (agentHomeManager === void 0 || agentHomeProjectionCompletion === void 0) {
@@ -19980,6 +20899,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
19980
20899
  capabilities,
19981
20900
  clientVersion: localAgentRelease.version,
19982
20901
  runtimes,
20902
+ harnesses,
19983
20903
  getConfiguredToolsets: () => toolsetRegistry.snapshot().configuredToolsets,
19984
20904
  auth,
19985
20905
  cursorStore,
@@ -20021,12 +20941,22 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20021
20941
  activePressureEngine?.assertAckCriticalAllowed();
20022
20942
  await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
20023
20943
  if (isTaskOfferType(envelope.type) && envelope.task_id !== void 0) {
20944
+ if (terminalCommits.hasPending(envelope.task_id)) {
20945
+ await terminalCommits.retry(envelope.task_id);
20946
+ return;
20947
+ }
20024
20948
  const task = await activeJournal.readTask(envelope.task_id, journalIdentity);
20025
20949
  if (!task) throw new Error("durable offer has no execution record");
20026
- if (task.localState !== "received") return;
20950
+ if (task.localState !== "received") {
20951
+ await terminalCommits.retry(envelope.task_id);
20952
+ return;
20953
+ }
20027
20954
  }
20028
20955
  await runner?.handleEnvelope(envelope);
20029
- await journalTerminalTail;
20956
+ if (envelope.type === "agent.message.disposition") {
20957
+ await replayRecoveryTerminals(envelope.task_id);
20958
+ }
20959
+ if (envelope.task_id !== void 0) await terminalCommits.receipt(envelope.task_id);
20030
20960
  } : (envelope) => {
20031
20961
  if (tenantRebinding) {
20032
20962
  return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
@@ -20074,18 +21004,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20074
21004
  });
20075
21005
  }
20076
21006
  });
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
- }
21007
+ await replayRecoveryTerminals();
20089
21008
  await connection.start();
20090
21009
  await connection.waitForConnection();
20091
21010
  runner.retryRecoveredAgentMessages();
@@ -20200,7 +21119,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20200
21119
  pendingLateMutationBarriers.add(observedActiveTeardown);
20201
21120
  void observedActiveTeardown.finally(() => pendingLateMutationBarriers.delete(observedActiveTeardown)).catch(() => void 0);
20202
21121
  }
20203
- const journalTailSettled = journal ? await attempt(() => journalTerminalTail, true) : true;
21122
+ const journalTailSettled = journal ? await attempt(() => terminalCommits.stop(), true) : true;
20204
21123
  const maintenanceSettled = await attempt(() => maintenanceStopped, true);
20205
21124
  const drainTimeoutMs = opts.drainTimeoutMs ?? overrides.shutdown?.outboxDrainTimeoutMs ?? DEFAULT_SHUTDOWN_OUTBOX_DRAIN_TIMEOUT_MS;
20206
21125
  const connectionStopped = await attempt(() => connection?.stop(drainTimeoutMs), true);
@@ -20720,6 +21639,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
20720
21639
  revoked: connection?.isRevoked() ?? auth.isRevoked(),
20721
21640
  deviceId: auth.deviceId,
20722
21641
  activeTaskCount: runner?.activeTaskCount ?? 0,
21642
+ pendingTerminalCommits: terminalCommits.pendingCount,
20723
21643
  branding: config.branding,
20724
21644
  operationalHealth: operationalHealth.snapshot(),
20725
21645
  toolsets: toolsetRegistry.status(),
@@ -21185,8 +22105,8 @@ var TruthMemoryClient = class {
21185
22105
  #requestId;
21186
22106
  #allowedObjectDownloadOrigins;
21187
22107
  async listManifest(query = {}) {
21188
- const path38 = manifestPath(query);
21189
- const response = await this.#proofFetch(path38, {
22108
+ const path43 = manifestPath(query);
22109
+ const response = await this.#proofFetch(path43, {
21190
22110
  method: "GET",
21191
22111
  operation: "truth.list",
21192
22112
  resource: "records",
@@ -21293,9 +22213,9 @@ var TruthMemoryClient = class {
21293
22213
  }
21294
22214
  async #write(kind, recordKey, requestId, payload, expectedPrimary, expectedSnapshots) {
21295
22215
  assertDistinctExpectedWrites([expectedPrimary, ...expectedSnapshots]);
21296
- const path38 = recordPath(kind, recordKey);
22216
+ const path43 = recordPath(kind, recordKey);
21297
22217
  const body = new TextEncoder().encode(JSON.stringify(payload));
21298
- const response = await this.#proofFetch(path38, {
22218
+ const response = await this.#proofFetch(path43, {
21299
22219
  method: "PUT",
21300
22220
  operation: "truth.write",
21301
22221
  resource: `${kind}/${recordKey}`,
@@ -21321,8 +22241,8 @@ var TruthMemoryClient = class {
21321
22241
  };
21322
22242
  }
21323
22243
  async #readVerified(listed) {
21324
- const path38 = recordPath(listed.kind, listed.recordKey);
21325
- const response = await this.#proofFetch(path38, {
22244
+ const path43 = recordPath(listed.kind, listed.recordKey);
22245
+ const response = await this.#proofFetch(path43, {
21326
22246
  method: "GET",
21327
22247
  operation: "truth.read",
21328
22248
  resource: `${listed.kind}/${listed.recordKey}`,
@@ -21383,16 +22303,16 @@ var TruthMemoryClient = class {
21383
22303
  }
21384
22304
  return { ...listed, bytes };
21385
22305
  }
21386
- async #proofFetch(path38, request) {
22306
+ async #proofFetch(path43, request) {
21387
22307
  const proof = await this.options.signer.sign({
21388
22308
  method: request.method,
21389
- path: path38,
22309
+ path: path43,
21390
22310
  operation: request.operation,
21391
22311
  resource: request.resource,
21392
22312
  requestId: request.requestId,
21393
22313
  body: request.body
21394
22314
  });
21395
- const response = await this.#fetch(new URL(path38, this.#base), {
22315
+ const response = await this.#fetch(new URL(path43, this.#base), {
21396
22316
  method: request.method,
21397
22317
  headers: {
21398
22318
  ...request.headers,
@@ -21403,7 +22323,7 @@ var TruthMemoryClient = class {
21403
22323
  if (!response.ok) {
21404
22324
  throw new TruthMemoryClientError(
21405
22325
  "truth_http_failed",
21406
- `truth request ${request.method} ${path38} failed with HTTP ${response.status}`,
22326
+ `truth request ${request.method} ${path43} failed with HTTP ${response.status}`,
21407
22327
  response.status
21408
22328
  );
21409
22329
  }
@@ -21661,7 +22581,7 @@ function plistString(value) {
21661
22581
  function generateLaunchdPlist(def) {
21662
22582
  const { label, program, logDir } = def;
21663
22583
  const args = [program.command, ...program.args];
21664
- const cwd = program.cwd ?? os4__default.homedir();
22584
+ const cwd = program.cwd ?? os__default.homedir();
21665
22585
  const outLog = path3__default.join(logDir, `${label}.out.log`);
21666
22586
  const errLog = path3__default.join(logDir, `${label}.err.log`);
21667
22587
  return `<?xml version="1.0" encoding="UTF-8"?>
@@ -21695,8 +22615,8 @@ ${args.map((a) => ` ${plistString(a)}`).join("\n")}
21695
22615
  }
21696
22616
  function createLaunchdLifecycle(def, deps = {}) {
21697
22617
  const run = deps.run ?? defaultRunner;
21698
- const fs31 = deps.fs ?? promises;
21699
- const homedir = deps.homedir ?? (() => os4__default.homedir());
22618
+ const fs36 = deps.fs ?? promises;
22619
+ const homedir = deps.homedir ?? (() => os__default.homedir());
21700
22620
  const getuid = deps.getuid ?? (() => {
21701
22621
  if (typeof process.getuid !== "function") {
21702
22622
  throw new Error("launchd lifecycle requires a POSIX uid (process.getuid unavailable) \u2014 this module only runs on macOS");
@@ -21709,7 +22629,7 @@ function createLaunchdLifecycle(def, deps = {}) {
21709
22629
  const serviceTarget = () => `${domainTarget()}/${label}`;
21710
22630
  async function fileExists(p) {
21711
22631
  try {
21712
- await fs31.stat(p);
22632
+ await fs36.stat(p);
21713
22633
  return true;
21714
22634
  } catch {
21715
22635
  return false;
@@ -21717,9 +22637,9 @@ function createLaunchdLifecycle(def, deps = {}) {
21717
22637
  }
21718
22638
  async function writePlist(program) {
21719
22639
  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");
22640
+ await fs36.mkdir(path3__default.dirname(plistPath()), { recursive: true });
22641
+ await fs36.mkdir(def.logDir, { recursive: true });
22642
+ await fs36.writeFile(plistPath(), xml, "utf8");
21723
22643
  }
21724
22644
  async function install(opts = {}) {
21725
22645
  await writePlist(opts.program ?? def.program);
@@ -21730,7 +22650,7 @@ function createLaunchdLifecycle(def, deps = {}) {
21730
22650
  }
21731
22651
  async function uninstall() {
21732
22652
  await runIdempotent(run, "launchctl", ["bootout", serviceTarget()], "launchctl bootout", LAUNCHD_NOT_LOADED);
21733
- await fs31.rm(plistPath(), { force: true });
22653
+ await fs36.rm(plistPath(), { force: true });
21734
22654
  }
21735
22655
  async function start() {
21736
22656
  if (!await fileExists(plistPath())) {
@@ -21789,7 +22709,7 @@ function generateSystemdUnit(def) {
21789
22709
  const { name, displayName, program, logDir } = def;
21790
22710
  assertNoControlChars(name, "name");
21791
22711
  assertNoControlChars(displayName, "displayName");
21792
- const cwd = program.cwd ?? os4__default.homedir();
22712
+ const cwd = program.cwd ?? os__default.homedir();
21793
22713
  assertNoControlChars(cwd, "program.cwd");
21794
22714
  const outLog = path3__default.join(logDir, `${name}.out.log`);
21795
22715
  const errLog = path3__default.join(logDir, `${name}.err.log`);
@@ -21814,14 +22734,14 @@ WantedBy=default.target
21814
22734
  }
21815
22735
  function createSystemdLifecycle(def, deps = {}) {
21816
22736
  const run = deps.run ?? defaultRunner;
21817
- const fs31 = deps.fs ?? promises;
21818
- const homedir = deps.homedir ?? (() => os4__default.homedir());
22737
+ const fs36 = deps.fs ?? promises;
22738
+ const homedir = deps.homedir ?? (() => os__default.homedir());
21819
22739
  const name = sanitizeServiceName(def.name);
21820
22740
  const unitName = `${name}.service`;
21821
22741
  const unitPath = () => path3__default.join(homedir(), ".config", "systemd", "user", unitName);
21822
22742
  async function fileExists(p) {
21823
22743
  try {
21824
- await fs31.stat(p);
22744
+ await fs36.stat(p);
21825
22745
  return true;
21826
22746
  } catch {
21827
22747
  return false;
@@ -21829,9 +22749,9 @@ function createSystemdLifecycle(def, deps = {}) {
21829
22749
  }
21830
22750
  async function writeUnit(program) {
21831
22751
  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");
22752
+ await fs36.mkdir(path3__default.dirname(unitPath()), { recursive: true });
22753
+ await fs36.mkdir(def.logDir, { recursive: true });
22754
+ await fs36.writeFile(unitPath(), unit, "utf8");
21835
22755
  }
21836
22756
  async function install(opts = {}) {
21837
22757
  await writeUnit(opts.program ?? def.program);
@@ -21840,7 +22760,7 @@ function createSystemdLifecycle(def, deps = {}) {
21840
22760
  }
21841
22761
  async function uninstall() {
21842
22762
  await runIdempotent(run, "systemctl", ["--user", "disable", "--now", unitName], "systemctl disable --now", SYSTEMD_NOT_LOADED);
21843
- await fs31.rm(unitPath(), { force: true });
22763
+ await fs36.rm(unitPath(), { force: true });
21844
22764
  await run("systemctl", ["--user", "daemon-reload"]);
21845
22765
  }
21846
22766
  async function start() {
@@ -21903,7 +22823,7 @@ ${argXml}${cwdXml}
21903
22823
  }
21904
22824
  function createWinswLifecycle(def, deps = {}) {
21905
22825
  const run = deps.run ?? defaultRunner;
21906
- const fs31 = deps.fs ?? promises;
22826
+ const fs36 = deps.fs ?? promises;
21907
22827
  const windows = def.windows;
21908
22828
  if (!windows) {
21909
22829
  throw new Error("WinSW service lifecycle requires `ServiceDefinition.windows.winswBin` (the product-bundled WinSW executable path)");
@@ -21915,7 +22835,7 @@ function createWinswLifecycle(def, deps = {}) {
21915
22835
  const xmlPath = path3__default.join(installDir, `${id}.xml`);
21916
22836
  async function fileExists(p) {
21917
22837
  try {
21918
- await fs31.stat(p);
22838
+ await fs36.stat(p);
21919
22839
  return true;
21920
22840
  } catch {
21921
22841
  return false;
@@ -21923,10 +22843,10 @@ function createWinswLifecycle(def, deps = {}) {
21923
22843
  }
21924
22844
  async function writeFiles(program) {
21925
22845
  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");
22846
+ await fs36.mkdir(installDir, { recursive: true });
22847
+ await fs36.mkdir(def.logDir, { recursive: true });
22848
+ await fs36.copyFile(winswBin, exePath);
22849
+ await fs36.writeFile(xmlPath, xml, "utf8");
21930
22850
  }
21931
22851
  async function install(opts = {}) {
21932
22852
  await writeFiles(opts.program ?? def.program);
@@ -21936,8 +22856,8 @@ function createWinswLifecycle(def, deps = {}) {
21936
22856
  async function uninstall() {
21937
22857
  await runIdempotent(run, exePath, ["stop"], "winsw stop", WINSW_NOT_INSTALLED);
21938
22858
  await runIdempotent(run, exePath, ["uninstall"], "winsw uninstall", WINSW_NOT_INSTALLED);
21939
- await fs31.rm(exePath, { force: true });
21940
- await fs31.rm(xmlPath, { force: true });
22859
+ await fs36.rm(exePath, { force: true });
22860
+ await fs36.rm(xmlPath, { force: true });
21941
22861
  }
21942
22862
  async function start() {
21943
22863
  if (!await fileExists(xmlPath)) {
@@ -21980,6 +22900,1191 @@ function createServiceLifecycle(def, opts = {}) {
21980
22900
  }
21981
22901
  }
21982
22902
 
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 };
22903
+ // src/bin/runtime-probe.ts
22904
+ var ALL_RUNTIME_IDS2 = ["pi", "claude", "codex"];
22905
+ var RUNTIME_PROBE_TIMEOUT_MS = 5e3;
22906
+ var MAX_RUNTIME_ID_CHARS = 128;
22907
+ var MAX_RUNTIME_VERSION_CHARS = 256;
22908
+ var MAX_PERMISSION_MODES = 32;
22909
+ var MAX_PERMISSION_MODE_CHARS = 64;
22910
+ function boundedSingleLine(value, maxChars) {
22911
+ return value.replace(/[\r\n\t]/g, " ").slice(0, maxChars);
22912
+ }
22913
+ var RuntimeProbeTimeout = class extends Error {
22914
+ };
22915
+ async function detectWithTimeout(adapter, timeoutMs) {
22916
+ let timer;
22917
+ try {
22918
+ return await Promise.race([
22919
+ adapter.detect(),
22920
+ new Promise((_resolve, reject) => {
22921
+ timer = setTimeout(() => reject(new RuntimeProbeTimeout()), timeoutMs);
22922
+ timer.unref?.();
22923
+ })
22924
+ ]);
22925
+ } finally {
22926
+ if (timer) clearTimeout(timer);
22927
+ }
22928
+ }
22929
+ function defaultRuntimeAdapters(runtimeAllowlist) {
22930
+ const ids = runtimeAllowlist ? ALL_RUNTIME_IDS2.filter((id) => runtimeAllowlist.includes(id)) : ALL_RUNTIME_IDS2;
22931
+ return ids.map((id) => {
22932
+ switch (id) {
22933
+ case "pi":
22934
+ return new PiAdapter();
22935
+ case "claude":
22936
+ return new ClaudeAdapter();
22937
+ case "codex":
22938
+ return new CodexAdapter();
22939
+ }
22940
+ });
22941
+ }
22942
+ async function probeRuntimes(adapters, options = {}) {
22943
+ const timeoutMs = options.timeoutMs ?? RUNTIME_PROBE_TIMEOUT_MS;
22944
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error("runtime probe timeout must be a positive integer");
22945
+ return Promise.all(
22946
+ adapters.map(async (adapter) => {
22947
+ let id = "unavailable";
22948
+ let steer = false;
22949
+ let resume = false;
22950
+ let permissionModes = [];
22951
+ try {
22952
+ id = boundedSingleLine(adapter.descriptor.id, MAX_RUNTIME_ID_CHARS);
22953
+ const caps = adapter.descriptor.capabilities;
22954
+ steer = caps.steer === true;
22955
+ resume = caps.resume === true;
22956
+ permissionModes = caps.permissionModes.slice(0, MAX_PERMISSION_MODES).map((mode) => boundedSingleLine(mode, MAX_PERMISSION_MODE_CHARS));
22957
+ const detected = validateRuntimeDetectResult(await detectWithTimeout(adapter, timeoutMs));
22958
+ return {
22959
+ id,
22960
+ present: detected.kind === "available",
22961
+ outcome: detected.kind,
22962
+ ...detected.kind !== "available" || detected.version === void 0 ? {} : { version: boundedSingleLine(detected.version, MAX_RUNTIME_VERSION_CHARS) },
22963
+ ...detected.kind === "available" && typeof detected.authPresent === "boolean" ? { authPresent: detected.authPresent } : {},
22964
+ steer,
22965
+ resume,
22966
+ permissionModes
22967
+ };
22968
+ } catch (error) {
22969
+ return { id, present: false, outcome: error instanceof RuntimeProbeTimeout ? "timeout" : "probe-failed", steer, resume, permissionModes };
22970
+ }
22971
+ })
22972
+ );
22973
+ }
22974
+
22975
+ // src/diagnostics/diagnostics.ts
22976
+ var MAX_QUARANTINE_ENTRIES = 100;
22977
+ var MAX_QUARANTINE_SCAN_ENTRIES = MAX_QUARANTINE_ENTRIES * 2;
22978
+ var MAX_QUARANTINE_READ_BYTES = 128 * 1024 * 1024;
22979
+ var MAX_DEVICE_RECORD_BYTES2 = 64 * 1024;
22980
+ var MAX_JOURNAL_COPY_BYTES = 512 * 1024 * 1024;
22981
+ var MAX_QUARANTINE_MANIFEST_BYTES = 64 * 1024;
22982
+ function safeProtocol(serverUrl) {
22983
+ try {
22984
+ const protocol = new URL(serverUrl).protocol.replace(/:$/, "");
22985
+ return protocol === "http" || protocol === "https" || protocol === "ws" || protocol === "wss" ? protocol : "unsupported";
22986
+ } catch {
22987
+ return "invalid";
22988
+ }
22989
+ }
22990
+ async function inspectDevice(storeDir) {
22991
+ const filePath = path3__default.join(storeDir, "device.json");
22992
+ let pathStat;
22993
+ try {
22994
+ pathStat = await promises.lstat(filePath);
22995
+ if (!pathStat.isFile()) return { status: "unavailable" };
22996
+ } catch (err) {
22997
+ if (err.code === "ENOENT") return { status: "unpaired" };
22998
+ return { status: "unavailable" };
22999
+ }
23000
+ let handle;
23001
+ try {
23002
+ handle = await promises.open(
23003
+ filePath,
23004
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
23005
+ );
23006
+ } catch (err) {
23007
+ if (err.code === "ENOENT") return { status: "unpaired" };
23008
+ return { status: "unavailable" };
23009
+ }
23010
+ try {
23011
+ const stat = await handle.stat();
23012
+ const namedAfterOpen = await promises.lstat(filePath);
23013
+ 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" };
23014
+ const bytes = Buffer.alloc(stat.size);
23015
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
23016
+ const after = await handle.stat();
23017
+ const namedAfterRead = await promises.lstat(filePath);
23018
+ 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) {
23019
+ return { status: "unavailable" };
23020
+ }
23021
+ const parsed = JSON.parse(bytes.toString("utf8"));
23022
+ return typeof parsed.deviceId === "string" && parsed.deviceId.length > 0 ? { status: "paired", deviceIdHash: stableIdentifierHash(parsed.deviceId) } : { status: "unavailable" };
23023
+ } catch {
23024
+ return { status: "unavailable" };
23025
+ } finally {
23026
+ await handle.close();
23027
+ }
23028
+ }
23029
+ async function regularFileIdentity(filePath) {
23030
+ try {
23031
+ const stat = await promises.lstat(filePath, { bigint: true });
23032
+ if (!stat.isFile()) throw new Error("journal component is not a regular file");
23033
+ return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
23034
+ } catch (err) {
23035
+ if (err.code === "ENOENT") return void 0;
23036
+ throw err;
23037
+ }
23038
+ }
23039
+ function sameIdentity(left, right) {
23040
+ if (!left || !right) return left === right;
23041
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23042
+ }
23043
+ function identityFromBigIntStat(stat) {
23044
+ return { dev: stat.dev, ino: stat.ino, size: stat.size, mtimeNs: stat.mtimeNs, ctimeNs: stat.ctimeNs };
23045
+ }
23046
+ async function copyOpenFileBounded(source, expected, destinationPath) {
23047
+ const destination = await promises.open(destinationPath, "wx", 384);
23048
+ try {
23049
+ const buffer = Buffer.allocUnsafe(64 * 1024);
23050
+ const expectedBytes = Number(expected.size);
23051
+ let position = 0;
23052
+ while (position < expectedBytes) {
23053
+ const length = Math.min(buffer.length, expectedBytes - position);
23054
+ const { bytesRead } = await source.read(buffer, 0, length, position);
23055
+ if (bytesRead === 0) return false;
23056
+ let written = 0;
23057
+ while (written < bytesRead) {
23058
+ const result = await destination.write(buffer, written, bytesRead - written, position + written);
23059
+ if (result.bytesWritten === 0) throw new Error("diagnostics snapshot destination stopped accepting bytes");
23060
+ written += result.bytesWritten;
23061
+ }
23062
+ position += bytesRead;
23063
+ }
23064
+ await destination.sync();
23065
+ return sameIdentity(expected, identityFromBigIntStat(await source.stat({ bigint: true })));
23066
+ } finally {
23067
+ await destination.close();
23068
+ }
23069
+ }
23070
+ async function inspectJournal(storeDir) {
23071
+ const journalPath = path3__default.join(storeDir, JOURNAL_DB_FILENAME);
23072
+ try {
23073
+ const mainIdentity = await regularFileIdentity(journalPath);
23074
+ if (mainIdentity === void 0) return { status: "missing" };
23075
+ const walIdentity = await regularFileIdentity(path3__default.join(storeDir, `${JOURNAL_DB_FILENAME}-wal`));
23076
+ let sizeBytes = Number(mainIdentity.size);
23077
+ let walBytes = walIdentity === void 0 ? void 0 : Number(walIdentity.size);
23078
+ if (!isSqliteAvailable()) {
23079
+ return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "not-checked" };
23080
+ }
23081
+ const componentNames = [JOURNAL_DB_FILENAME, `${JOURNAL_DB_FILENAME}-wal`, `${JOURNAL_DB_FILENAME}-shm`];
23082
+ const initial = /* @__PURE__ */ new Map();
23083
+ for (const name of componentNames) initial.set(name, await regularFileIdentity(path3__default.join(storeDir, name)));
23084
+ const snapshotMain = initial.get(JOURNAL_DB_FILENAME);
23085
+ if (!snapshotMain) return { status: "unavailable", reason: "journal changed during diagnostics snapshot" };
23086
+ sizeBytes = Number(snapshotMain.size);
23087
+ walBytes = initial.get(`${JOURNAL_DB_FILENAME}-wal`) === void 0 ? void 0 : Number(initial.get(`${JOURNAL_DB_FILENAME}-wal`).size);
23088
+ const opened = /* @__PURE__ */ new Map();
23089
+ let totalBytes = 0n;
23090
+ try {
23091
+ for (const name of componentNames) {
23092
+ let handle;
23093
+ try {
23094
+ handle = await promises.open(
23095
+ path3__default.join(storeDir, name),
23096
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
23097
+ );
23098
+ } catch (err) {
23099
+ if (err.code === "ENOENT" && name !== JOURNAL_DB_FILENAME) continue;
23100
+ throw err;
23101
+ }
23102
+ const stat = await handle.stat({ bigint: true });
23103
+ if (!stat.isFile()) {
23104
+ await handle.close();
23105
+ throw new Error("journal component is not a regular file");
23106
+ }
23107
+ const identity = identityFromBigIntStat(stat);
23108
+ if (!sameIdentity(initial.get(name), identity)) {
23109
+ await handle.close();
23110
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23111
+ }
23112
+ totalBytes += identity.size;
23113
+ opened.set(name, { handle, identity });
23114
+ }
23115
+ if (totalBytes > BigInt(MAX_JOURNAL_COPY_BYTES)) {
23116
+ return {
23117
+ status: "present",
23118
+ sizeBytes,
23119
+ ...walBytes === void 0 ? {} : { walBytes },
23120
+ integrity: "not-checked",
23121
+ reason: "journal exceeds the bounded diagnostics copy limit"
23122
+ };
23123
+ }
23124
+ const tempDir = await promises.mkdtemp(path3__default.join(os__default.tmpdir(), "byok-journal-inspect-"));
23125
+ const { DatabaseSync } = loadSqliteModule();
23126
+ let db;
23127
+ try {
23128
+ for (const name of componentNames) {
23129
+ const component = opened.get(name);
23130
+ if (component && !await copyOpenFileBounded(component.handle, component.identity, path3__default.join(tempDir, name))) {
23131
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23132
+ }
23133
+ }
23134
+ for (const [name, component] of opened) {
23135
+ if (!sameIdentity(component.identity, identityFromBigIntStat(await component.handle.stat({ bigint: true }))) || !sameIdentity(component.identity, await regularFileIdentity(path3__default.join(storeDir, name)))) {
23136
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23137
+ }
23138
+ }
23139
+ for (const name of componentNames) {
23140
+ if (!opened.has(name) && await regularFileIdentity(path3__default.join(storeDir, name)) !== void 0) {
23141
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal changed during diagnostics snapshot" };
23142
+ }
23143
+ }
23144
+ const header = Buffer.alloc(16);
23145
+ const copiedHandle = await promises.open(path3__default.join(tempDir, JOURNAL_DB_FILENAME), "r");
23146
+ try {
23147
+ const { bytesRead } = await copiedHandle.read(header, 0, header.length, 0);
23148
+ if (bytesRead !== 16 || header.toString("binary") !== "SQLite format 3\0") {
23149
+ return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal has an invalid SQLite header" };
23150
+ }
23151
+ } finally {
23152
+ await copiedHandle.close();
23153
+ }
23154
+ db = new DatabaseSync(path3__default.join(tempDir, JOURNAL_DB_FILENAME), { readOnly: true });
23155
+ const result = db.prepare("PRAGMA quick_check(1)").get();
23156
+ if (result?.quick_check !== "ok") {
23157
+ return { status: "corrupt", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal quick_check failed" };
23158
+ }
23159
+ return { status: "present", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, integrity: "ok" };
23160
+ } catch {
23161
+ return { status: "unavailable", sizeBytes, ...walBytes === void 0 ? {} : { walBytes }, reason: "journal snapshot could not be checked" };
23162
+ } finally {
23163
+ db?.close();
23164
+ await promises.rm(tempDir, { recursive: true, force: true });
23165
+ }
23166
+ } finally {
23167
+ await Promise.all([...opened.values()].map(({ handle }) => handle.close().catch(() => void 0)));
23168
+ }
23169
+ } catch {
23170
+ return { status: "unavailable", reason: "journal files could not be inspected" };
23171
+ }
23172
+ }
23173
+ async function inspectWorkspace(workspaceRoot) {
23174
+ try {
23175
+ const stat = await promises.stat(workspaceRoot);
23176
+ if (!stat.isDirectory()) return { status: "unavailable", reason: "workspace root is not a directory" };
23177
+ try {
23178
+ await promises.access(workspaceRoot, constants.R_OK | constants.W_OK);
23179
+ return { status: "available", writable: true };
23180
+ } catch {
23181
+ return { status: "available", writable: false };
23182
+ }
23183
+ } catch (err) {
23184
+ if (err.code === "ENOENT") return { status: "missing" };
23185
+ return { status: "unavailable", reason: "workspace root could not be inspected" };
23186
+ }
23187
+ }
23188
+ function readPinnedQuarantineFile(name, maxBytes, includeBytes, budget) {
23189
+ if (path3__default.basename(name) !== name || name === "." || name === "..") {
23190
+ throw new Error("quarantine manifest contains an invalid evidence name");
23191
+ }
23192
+ const namedBefore = lstatSync(name, { bigint: true });
23193
+ if (!namedBefore.isFile() || namedBefore.isSymbolicLink()) {
23194
+ throw new Error("quarantine entry is not a real regular file");
23195
+ }
23196
+ const fd = openSync(name, constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0));
23197
+ try {
23198
+ const opened = fstatSync(fd, { bigint: true });
23199
+ if (!opened.isFile() || !sameInode2(opened, namedBefore) || opened.size < 0 || opened.size > BigInt(maxBytes)) {
23200
+ throw new Error("quarantine entry could not be opened as a bounded regular file");
23201
+ }
23202
+ let bytes;
23203
+ if (includeBytes) {
23204
+ const sizeBytes = Number(opened.size);
23205
+ if (sizeBytes > budget.remainingBytes) {
23206
+ throw new Error("quarantine validation exceeds the 128 MiB cumulative read limit");
23207
+ }
23208
+ budget.remainingBytes -= sizeBytes;
23209
+ bytes = Buffer.alloc(sizeBytes);
23210
+ let position = 0;
23211
+ while (position < bytes.length) {
23212
+ const bytesRead = readSync(fd, bytes, position, bytes.length - position, position);
23213
+ if (bytesRead === 0) throw new Error("quarantine entry ended during bounded read");
23214
+ position += bytesRead;
23215
+ }
23216
+ }
23217
+ const openedAfter = fstatSync(fd, { bigint: true });
23218
+ const namedAfter = lstatSync(name, { bigint: true });
23219
+ if (!sameFileState5(openedAfter, opened) || !sameFileState5(namedAfter, opened)) {
23220
+ throw new Error("quarantine entry changed during inspection");
23221
+ }
23222
+ return {
23223
+ sizeBytes: Number(opened.size),
23224
+ modifiedAt: new Date(Number(opened.mtimeMs)).toISOString(),
23225
+ ...bytes === void 0 ? {} : { bytes }
23226
+ };
23227
+ } finally {
23228
+ closeSync(fd);
23229
+ }
23230
+ }
23231
+ function isHealthQuarantineManifest(value) {
23232
+ if (typeof value !== "object" || value === null) return false;
23233
+ const candidate = value;
23234
+ 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;
23235
+ }
23236
+ function isJournalQuarantineManifest(value) {
23237
+ if (typeof value !== "object" || value === null) return false;
23238
+ const candidate = value;
23239
+ 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");
23240
+ }
23241
+ function inspectQuarantinePinned(dir, expectedDirectory) {
23242
+ const previousCwd = process.cwd();
23243
+ try {
23244
+ process.chdir(dir);
23245
+ const pinned = lstatSync(".", { bigint: true });
23246
+ if (!pinned.isDirectory() || !sameInode2(pinned, expectedDirectory)) {
23247
+ throw new Error("quarantine directory changed during inspection");
23248
+ }
23249
+ const selected = [];
23250
+ let scanTruncated = false;
23251
+ const directory = opendirSync(".");
23252
+ try {
23253
+ for (; ; ) {
23254
+ const entry = directory.readSync();
23255
+ if (!entry) break;
23256
+ if (selected.length === MAX_QUARANTINE_SCAN_ENTRIES) {
23257
+ scanTruncated = true;
23258
+ break;
23259
+ }
23260
+ selected.push(entry);
23261
+ }
23262
+ } finally {
23263
+ directory.closeSync();
23264
+ }
23265
+ const evidence = /* @__PURE__ */ new Map();
23266
+ const processedManifests = /* @__PURE__ */ new Set();
23267
+ const readBudget = { remainingBytes: MAX_QUARANTINE_READ_BYTES };
23268
+ const processManifest = (manifestName) => {
23269
+ if (processedManifests.has(manifestName)) return;
23270
+ processedManifests.add(manifestName);
23271
+ const manifestFile = readPinnedQuarantineFile(manifestName, MAX_QUARANTINE_MANIFEST_BYTES, true, readBudget);
23272
+ let parsed;
23273
+ try {
23274
+ parsed = JSON.parse(manifestFile.bytes?.toString("utf8") ?? "");
23275
+ } catch {
23276
+ throw new Error("quarantine manifest is invalid JSON");
23277
+ }
23278
+ if (isHealthQuarantineManifest(parsed)) {
23279
+ if (`${parsed.evidenceFile}.manifest.json` !== manifestName) {
23280
+ throw new Error("health quarantine manifest is not bound to its evidence name");
23281
+ }
23282
+ const file = readPinnedQuarantineFile(parsed.evidenceFile, MAX_OPERATIONAL_HEALTH_FILE_BYTES, true, readBudget);
23283
+ if (file.sizeBytes !== parsed.sizeBytes || createHash("sha256").update(file.bytes ?? Buffer.alloc(0)).digest("hex") !== parsed.sha256) {
23284
+ throw new Error("health quarantine evidence does not match its manifest");
23285
+ }
23286
+ if (evidence.has(parsed.evidenceFile)) throw new Error("quarantine evidence is referenced more than once");
23287
+ evidence.set(parsed.evidenceFile, file);
23288
+ return;
23289
+ }
23290
+ if (isJournalQuarantineManifest(parsed)) {
23291
+ const manifestBase = manifestName.slice(0, -".manifest.json".length);
23292
+ const boundNames = parsed.files.map((file) => {
23293
+ if (path3__default.dirname(path3__default.resolve(file)) !== path3__default.resolve(".")) {
23294
+ throw new Error("journal quarantine manifest points outside quarantine");
23295
+ }
23296
+ return path3__default.basename(file);
23297
+ });
23298
+ if (!boundNames.includes(manifestBase)) {
23299
+ throw new Error("journal quarantine manifest is not bound to its primary database evidence");
23300
+ }
23301
+ for (const name of boundNames) {
23302
+ if (evidence.has(name)) throw new Error("quarantine evidence is referenced more than once");
23303
+ evidence.set(name, readPinnedQuarantineFile(name, MAX_JOURNAL_COPY_BYTES, false, readBudget));
23304
+ }
23305
+ return;
23306
+ }
23307
+ throw new Error("quarantine manifest shape is invalid");
23308
+ };
23309
+ for (const entry of selected) {
23310
+ if (entry.name.endsWith(".manifest.json")) processManifest(entry.name);
23311
+ }
23312
+ for (const entry of selected) {
23313
+ if (!entry.name.endsWith(".manifest.json") && !evidence.has(entry.name)) {
23314
+ const primaryName = entry.name.endsWith("-wal") || entry.name.endsWith("-shm") ? entry.name.slice(0, -4) : entry.name;
23315
+ try {
23316
+ processManifest(`${primaryName}.manifest.json`);
23317
+ } catch {
23318
+ throw new Error("quarantine evidence has no valid manifest binding");
23319
+ }
23320
+ if (!evidence.has(entry.name)) throw new Error("quarantine evidence has no valid manifest binding");
23321
+ }
23322
+ }
23323
+ 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));
23324
+ return {
23325
+ status: "available",
23326
+ count: evidence.size,
23327
+ scannedCount: selected.length,
23328
+ truncated: scanTruncated || evidence.size > entries.length,
23329
+ entries
23330
+ };
23331
+ } finally {
23332
+ process.chdir(previousCwd);
23333
+ }
23334
+ }
23335
+ async function inspectQuarantine(storeDir) {
23336
+ const dir = path3__default.join(storeDir, JOURNAL_QUARANTINE_DIRNAME);
23337
+ let directory;
23338
+ try {
23339
+ directory = await promises.lstat(dir, { bigint: true });
23340
+ } catch (err) {
23341
+ if (err.code === "ENOENT") {
23342
+ return { status: "missing", count: 0, scannedCount: 0, truncated: false, entries: [] };
23343
+ }
23344
+ return {
23345
+ status: "unavailable",
23346
+ count: 0,
23347
+ scannedCount: 0,
23348
+ truncated: false,
23349
+ entries: [],
23350
+ reason: "quarantine directory could not be inspected"
23351
+ };
23352
+ }
23353
+ if (!directory.isDirectory() || directory.isSymbolicLink()) {
23354
+ return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine path is not a real directory" };
23355
+ }
23356
+ try {
23357
+ return inspectQuarantinePinned(dir, directory);
23358
+ } catch {
23359
+ return { status: "unavailable", count: 0, scannedCount: 0, truncated: false, entries: [], reason: "quarantine manifest or evidence binding is invalid" };
23360
+ }
23361
+ }
23362
+ function checksFor(snapshot) {
23363
+ return [
23364
+ {
23365
+ id: "config",
23366
+ status: snapshot.config.serverProtocol === "invalid" ? "fail" : "pass",
23367
+ summary: `server protocol=${snapshot.config.serverProtocol}; hostedJournal=${snapshot.config.hostedJournal}`
23368
+ },
23369
+ {
23370
+ id: "device",
23371
+ status: snapshot.device.status === "unavailable" ? "fail" : snapshot.device.status === "unpaired" ? "warn" : "pass",
23372
+ summary: snapshot.device.status
23373
+ },
23374
+ {
23375
+ id: "runtimes",
23376
+ status: snapshot.runtimes.some((runtime) => runtime.present) ? "pass" : "warn",
23377
+ 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(" ")
23378
+ },
23379
+ {
23380
+ id: "control",
23381
+ status: snapshot.control.status === "online" ? "pass" : "warn",
23382
+ summary: snapshot.control.status === "online" ? `online transport=${snapshot.control.transport}` : "daemon offline"
23383
+ },
23384
+ {
23385
+ id: "health",
23386
+ status: snapshot.health.status === "corrupt" || snapshot.health.status === "unavailable" ? "fail" : snapshot.health.status === "missing" ? "warn" : "pass",
23387
+ summary: snapshot.health.status
23388
+ },
23389
+ {
23390
+ id: "journal",
23391
+ status: snapshot.journal.status === "unavailable" || snapshot.journal.status === "corrupt" ? "fail" : snapshot.journal.status === "missing" ? "warn" : "pass",
23392
+ summary: snapshot.journal.status
23393
+ },
23394
+ {
23395
+ id: "workspace",
23396
+ status: snapshot.workspace.status === "unavailable" ? "fail" : snapshot.workspace.status === "missing" || snapshot.workspace.writable === false ? "warn" : "pass",
23397
+ summary: `${snapshot.workspace.status}${snapshot.workspace.writable === false ? "; read-only" : ""}`
23398
+ },
23399
+ {
23400
+ id: "quarantine",
23401
+ status: snapshot.quarantine.status === "unavailable" ? "fail" : snapshot.quarantine.count > 0 ? "warn" : "pass",
23402
+ summary: `${snapshot.quarantine.count} evidence file(s)`
23403
+ }
23404
+ ];
23405
+ }
23406
+ async function collectDiagnostics(config, storeDir, options = {}) {
23407
+ const resolvedStoreDir = path3__default.resolve(storeDir);
23408
+ const adapters = options.adapters ?? defaultRuntimeAdapters(config.runtimeAllowlist);
23409
+ const connectControl = options.connectControl ?? connectControlClient;
23410
+ const [device, probedRuntimes, health, journal, workspace, quarantine, controlConnection] = await Promise.all([
23411
+ inspectDevice(resolvedStoreDir),
23412
+ probeRuntimes(adapters, { timeoutMs: options.runtimeProbeTimeoutMs }),
23413
+ inspectOperationalHealthFile(resolvedStoreDir),
23414
+ inspectJournal(resolvedStoreDir),
23415
+ inspectWorkspace(config.workspaceRoot),
23416
+ inspectQuarantine(resolvedStoreDir),
23417
+ connectControl({ storeDir: resolvedStoreDir, productId: config.productId })
23418
+ ]);
23419
+ const runtimes = probedRuntimes.map((runtime) => ({
23420
+ idHash: stableIdentifierHash(runtime.id),
23421
+ present: runtime.present,
23422
+ outcome: runtime.outcome,
23423
+ versionPresent: runtime.version !== void 0,
23424
+ ...runtime.authPresent === void 0 ? {} : { authPresent: runtime.authPresent },
23425
+ steer: runtime.steer,
23426
+ resume: runtime.resume,
23427
+ permissionModeCount: runtime.permissionModes.length
23428
+ }));
23429
+ let control;
23430
+ if (!controlConnection.ok) {
23431
+ control = { status: "offline", reason: "daemon control socket unavailable" };
23432
+ } else {
23433
+ try {
23434
+ const live = await controlConnection.client.request("status");
23435
+ control = {
23436
+ status: "online",
23437
+ pid: live.pid,
23438
+ uptimeMs: live.uptimeMs,
23439
+ transport: live.transport,
23440
+ activeTaskCount: live.activeTasks.length,
23441
+ pendingApprovalCount: live.approvalsPending,
23442
+ operationalHealth: live.operationalHealth,
23443
+ ...live.storage ? { storage: live.storage } : {}
23444
+ };
23445
+ } catch {
23446
+ control = { status: "offline", reason: "daemon status request failed" };
23447
+ } finally {
23448
+ controlConnection.client.close();
23449
+ }
23450
+ }
23451
+ const withoutChecks = {
23452
+ version: 1,
23453
+ generatedAt: (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
23454
+ product: { nameHash: stableIdentifierHash(config.productName), idHash: stableIdentifierHash(config.productId) },
23455
+ system: { node: process.versions.node, platform: process.platform, arch: process.arch, sqliteAvailable: isSqliteAvailable() },
23456
+ config: {
23457
+ serverProtocol: safeProtocol(config.serverUrl),
23458
+ customStoreDir: config.storeDir !== void 0,
23459
+ hostedJournal: config.hostedJournal !== void 0,
23460
+ ...config.runtimeAllowlist ? { runtimeAllowlistCount: config.runtimeAllowlist.length } : {}
23461
+ },
23462
+ device,
23463
+ runtimes,
23464
+ control,
23465
+ health,
23466
+ journal,
23467
+ workspace,
23468
+ quarantine
23469
+ };
23470
+ return { ...withoutChecks, checks: checksFor(withoutChecks) };
23471
+ }
23472
+ function stableIdentifierHash(value) {
23473
+ return createHash("sha256").update(value, "utf8").digest("hex");
23474
+ }
23475
+ function sameInode2(left, right) {
23476
+ return left.dev === right.dev && left.ino === right.ino;
23477
+ }
23478
+ function sameFileState5(left, right) {
23479
+ return sameInode2(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23480
+ }
23481
+ function publishQuarantineEvidencePinned(quarantineDir, expectedDirectory, sourcePath, sourceFd, sourceStat, evidenceName, manifestName, quarantinedAt, reason) {
23482
+ const previousCwd = process.cwd();
23483
+ let entered = false;
23484
+ let evidenceCreated = false;
23485
+ let manifestCreated = false;
23486
+ let sourceRemoved = false;
23487
+ let evidenceTemp;
23488
+ let manifestTemp;
23489
+ try {
23490
+ process.chdir(quarantineDir);
23491
+ entered = true;
23492
+ const pinned = lstatSync(".", { bigint: true });
23493
+ if (!pinned.isDirectory() || !sameInode2(pinned, expectedDirectory)) {
23494
+ throw new Error("quarantine path changed before publication; refusing quarantine");
23495
+ }
23496
+ const openSource = fstatSync(sourceFd, { bigint: true });
23497
+ const namedSource = lstatSync(sourcePath, { bigint: true });
23498
+ if (!sameFileState5(openSource, sourceStat) || !sameFileState5(namedSource, sourceStat)) {
23499
+ throw new Error("operational health source identity changed before quarantine");
23500
+ }
23501
+ const sizeBytes = Number(sourceStat.size);
23502
+ if (sizeBytes > MAX_OPERATIONAL_HEALTH_FILE_BYTES) {
23503
+ throw new Error("operational health state exceeds the bounded quarantine read limit");
23504
+ }
23505
+ const bytes = Buffer.alloc(sizeBytes);
23506
+ let position = 0;
23507
+ while (position < sizeBytes) {
23508
+ const bytesRead = readSync(sourceFd, bytes, position, sizeBytes - position, position);
23509
+ if (bytesRead === 0) throw new Error("operational health state ended during bounded copy");
23510
+ position += bytesRead;
23511
+ }
23512
+ const afterRead = fstatSync(sourceFd, { bigint: true });
23513
+ if (!sameFileState5(afterRead, sourceStat)) {
23514
+ throw new Error("operational health state changed during bounded copy");
23515
+ }
23516
+ const sha2562 = createHash("sha256").update(bytes).digest("hex");
23517
+ evidenceTemp = `.${evidenceName}.${randomUUID()}.tmp`;
23518
+ const evidenceFd = openSync(evidenceTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
23519
+ try {
23520
+ writeFileSync(evidenceFd, bytes);
23521
+ fchmodSync(evidenceFd, 384);
23522
+ fsyncSync(evidenceFd);
23523
+ } finally {
23524
+ closeSync(evidenceFd);
23525
+ }
23526
+ linkSync(evidenceTemp, evidenceName);
23527
+ evidenceCreated = true;
23528
+ const evidence = lstatSync(evidenceName, { bigint: true });
23529
+ const tempEvidence = lstatSync(evidenceTemp, { bigint: true });
23530
+ if (!evidence.isFile() || !sameInode2(evidence, tempEvidence) || evidence.size !== sourceStat.size) {
23531
+ throw new Error("quarantined operational health evidence does not match the bounded copy");
23532
+ }
23533
+ unlinkSync(evidenceTemp);
23534
+ evidenceTemp = void 0;
23535
+ const finalOpenSource = fstatSync(sourceFd, { bigint: true });
23536
+ const finalNamedSource = lstatSync(sourcePath, { bigint: true });
23537
+ if (!sameFileState5(finalOpenSource, sourceStat) || !sameFileState5(finalNamedSource, sourceStat)) {
23538
+ throw new Error("operational health source changed before removal");
23539
+ }
23540
+ const manifestBody = `${JSON.stringify(
23541
+ {
23542
+ version: 1,
23543
+ quarantinedAt,
23544
+ reason,
23545
+ sourcePath,
23546
+ evidenceFile: evidenceName,
23547
+ sha256: sha2562,
23548
+ sizeBytes
23549
+ },
23550
+ null,
23551
+ 2
23552
+ )}
23553
+ `;
23554
+ manifestTemp = `.${manifestName}.${randomUUID()}.tmp`;
23555
+ const manifestFd = openSync(manifestTemp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 384);
23556
+ try {
23557
+ writeFileSync(manifestFd, manifestBody, "utf8");
23558
+ fchmodSync(manifestFd, 384);
23559
+ fsyncSync(manifestFd);
23560
+ } finally {
23561
+ closeSync(manifestFd);
23562
+ }
23563
+ linkSync(manifestTemp, manifestName);
23564
+ manifestCreated = true;
23565
+ unlinkSync(manifestTemp);
23566
+ manifestTemp = void 0;
23567
+ if (process.platform !== "win32") {
23568
+ const quarantineDirectoryFd = openSync(".", constants.O_RDONLY);
23569
+ try {
23570
+ fsyncSync(quarantineDirectoryFd);
23571
+ } finally {
23572
+ closeSync(quarantineDirectoryFd);
23573
+ }
23574
+ }
23575
+ const removalOpenSource = fstatSync(sourceFd, { bigint: true });
23576
+ const removalNamedSource = lstatSync(sourcePath, { bigint: true });
23577
+ if (!sameFileState5(removalOpenSource, sourceStat) || !sameFileState5(removalNamedSource, sourceStat)) {
23578
+ throw new Error("operational health source changed before removal");
23579
+ }
23580
+ unlinkSync(sourcePath);
23581
+ sourceRemoved = true;
23582
+ if (process.platform !== "win32") {
23583
+ const directoryFd = openSync(path3__default.dirname(sourcePath), constants.O_RDONLY);
23584
+ try {
23585
+ fsyncSync(directoryFd);
23586
+ } finally {
23587
+ closeSync(directoryFd);
23588
+ }
23589
+ }
23590
+ return { sha256: sha2562, sizeBytes };
23591
+ } catch (err) {
23592
+ if (evidenceTemp) {
23593
+ try {
23594
+ unlinkSync(evidenceTemp);
23595
+ } catch {
23596
+ }
23597
+ }
23598
+ if (manifestTemp) {
23599
+ try {
23600
+ unlinkSync(manifestTemp);
23601
+ } catch {
23602
+ }
23603
+ }
23604
+ if (manifestCreated && !sourceRemoved) {
23605
+ try {
23606
+ unlinkSync(manifestName);
23607
+ } catch {
23608
+ }
23609
+ }
23610
+ if (evidenceCreated && !sourceRemoved) {
23611
+ try {
23612
+ unlinkSync(evidenceName);
23613
+ } catch {
23614
+ }
23615
+ }
23616
+ throw err;
23617
+ } finally {
23618
+ if (entered) process.chdir(previousCwd);
23619
+ }
23620
+ }
23621
+ async function quarantineCorruptOperationalHealth(storeDir, options = {}) {
23622
+ const resolvedStoreDir = path3__default.resolve(storeDir);
23623
+ const owner = await acquireDaemonOwner(resolvedStoreDir, "doctor", options.clock);
23624
+ try {
23625
+ const sourcePath = path3__default.join(resolvedStoreDir, OPERATIONAL_HEALTH_FILENAME);
23626
+ let opened;
23627
+ try {
23628
+ opened = await openOperationalHealthFile(resolvedStoreDir);
23629
+ } catch (err) {
23630
+ throw new Error("operational health state could not be opened safely; refusing quarantine", { cause: err });
23631
+ }
23632
+ if (!opened) return { status: "not-needed", reason: "missing" };
23633
+ const source = opened.handle;
23634
+ try {
23635
+ const inspection = await inspectOperationalHealthHandle(source, opened.stat);
23636
+ if (inspection.status === "valid") return { status: "not-needed", reason: "valid" };
23637
+ if (inspection.status === "unavailable") {
23638
+ throw new Error(`${inspection.reason}; refusing to quarantine unconfirmed corruption`);
23639
+ }
23640
+ const sourceStat = await source.stat({ bigint: true });
23641
+ if (!sourceStat.isFile()) throw new Error("operational health state is not a regular file; refusing quarantine");
23642
+ const quarantineDir = path3__default.join(resolvedStoreDir, JOURNAL_QUARANTINE_DIRNAME);
23643
+ try {
23644
+ const existing = await promises.lstat(quarantineDir);
23645
+ if (!existing.isDirectory() || existing.isSymbolicLink()) {
23646
+ throw new Error("quarantine path is not a real directory; refusing quarantine");
23647
+ }
23648
+ } catch (err) {
23649
+ if (err.code !== "ENOENT") throw err;
23650
+ await promises.mkdir(quarantineDir, { mode: 448 });
23651
+ }
23652
+ await ensureSecureDir(quarantineDir);
23653
+ const securedQuarantine = await promises.lstat(quarantineDir, { bigint: true });
23654
+ if (!securedQuarantine.isDirectory() || securedQuarantine.isSymbolicLink()) {
23655
+ throw new Error("quarantine path changed during validation; refusing quarantine");
23656
+ }
23657
+ const stamp = (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString().replace(/[:.]/g, "-");
23658
+ const evidenceName = `${stamp}-${randomUUID()}-${OPERATIONAL_HEALTH_FILENAME}`;
23659
+ const manifestName = `${evidenceName}.manifest.json`;
23660
+ const hashed = publishQuarantineEvidencePinned(
23661
+ quarantineDir,
23662
+ securedQuarantine,
23663
+ sourcePath,
23664
+ source.fd,
23665
+ sourceStat,
23666
+ evidenceName,
23667
+ manifestName,
23668
+ (options.clock ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
23669
+ inspection.reason
23670
+ );
23671
+ return { status: "quarantined", evidenceName, manifestName, sha256: hashed.sha256, sizeBytes: hashed.sizeBytes };
23672
+ } finally {
23673
+ await source.close();
23674
+ }
23675
+ } finally {
23676
+ await owner.release();
23677
+ }
23678
+ }
23679
+
23680
+ // src/diagnostics/device-doctor.ts
23681
+ function diagnoseDevice(config, options = {}) {
23682
+ return collectDiagnostics(config, DeviceStore.resolveDir(config.productId, config.storeDir), {
23683
+ adapters: options.adapters,
23684
+ runtimeProbeTimeoutMs: options.runtimeProbeTimeoutMs
23685
+ });
23686
+ }
23687
+ var MESSAGES = {
23688
+ "confirmation-required": "Explicit confirmation is required for enrollment metadata repair.",
23689
+ "invalid-target": "An explicit expected tenant and device are required.",
23690
+ "daemon-running": "Stop the daemon before enrollment metadata repair.",
23691
+ "store-busy": "The store is owned by another operation; enrollment metadata repair refused.",
23692
+ "authority-unavailable": "The OS enrollment authority could not be read.",
23693
+ "authority-missing": "No complete OS enrollment exists; use explicit authenticated pairing.",
23694
+ "target-mismatch": "The OS enrollment does not match the expected tenant and device.",
23695
+ "projection-unavailable": "The enrollment metadata could not be read safely; repair refused.",
23696
+ "repair-failed": "Enrollment metadata repair did not complete; inspect the state before retrying."
23697
+ };
23698
+ var DeviceMetadataRepairError = class extends Error {
23699
+ constructor(code) {
23700
+ super(MESSAGES[code]);
23701
+ this.code = code;
23702
+ this.name = "DeviceMetadataRepairError";
23703
+ }
23704
+ code;
23705
+ };
23706
+ async function repairDeviceEnrollmentMetadata(config, input) {
23707
+ if (input?.confirmed !== true) throw new DeviceMetadataRepairError("confirmation-required");
23708
+ if (typeof input.expectedDeviceId !== "string" || input.expectedDeviceId.trim().length === 0 || !isTenantId(input.expectedTenantId)) {
23709
+ throw new DeviceMetadataRepairError("invalid-target");
23710
+ }
23711
+ const { expectedDeviceId, expectedTenantId } = input;
23712
+ const productId = config.productId;
23713
+ let failureCode = "repair-failed";
23714
+ try {
23715
+ const storeDir = DeviceStore.resolveDir(productId, config.storeDir);
23716
+ const control = await connectControlClient({ storeDir, productId });
23717
+ if (control.ok) {
23718
+ control.client.close();
23719
+ throw new DeviceMetadataRepairError("daemon-running");
23720
+ }
23721
+ const owner = await acquireDaemonOwner(storeDir, "doctor");
23722
+ try {
23723
+ const store = new DeviceStore(storeDir, void 0, productId);
23724
+ failureCode = "projection-unavailable";
23725
+ await store.load();
23726
+ failureCode = "authority-unavailable";
23727
+ const authority = await store.credentials.read();
23728
+ if (authority === void 0) throw new DeviceMetadataRepairError("authority-missing");
23729
+ if (authority.deviceId !== expectedDeviceId || authority.tenantId !== expectedTenantId) {
23730
+ throw new DeviceMetadataRepairError("target-mismatch");
23731
+ }
23732
+ failureCode = "repair-failed";
23733
+ const changed = await store.reconcileMetadata(authority);
23734
+ const readback = await store.load();
23735
+ if (readback === void 0 || readback.deviceId !== authority.deviceId || readback.tenantId !== authority.tenantId || readback.devicePublicKey !== authority.devicePublicKey) {
23736
+ throw new DeviceMetadataRepairError("repair-failed");
23737
+ }
23738
+ return { action: "restore-enrollment-metadata", scope: "device", status: changed ? "repaired" : "not-needed" };
23739
+ } catch (error) {
23740
+ if (error instanceof DeviceMetadataRepairError) throw error;
23741
+ throw new DeviceMetadataRepairError(failureCode);
23742
+ } finally {
23743
+ failureCode = "repair-failed";
23744
+ await owner.release();
23745
+ }
23746
+ } catch (error) {
23747
+ if (error instanceof DeviceMetadataRepairError) throw error;
23748
+ if (error instanceof DaemonOwnerActiveError) throw new DeviceMetadataRepairError("store-busy");
23749
+ throw new DeviceMetadataRepairError(failureCode);
23750
+ }
23751
+ }
23752
+ function auditLogPath(storeDir) {
23753
+ return path3__default.join(storeDir, "audit.jsonl");
23754
+ }
23755
+ new Set(GIT_ERROR_CATEGORIES);
23756
+
23757
+ // src/diagnostics/support-bundle.ts
23758
+ var MAX_SUPPORT_AUDIT_TAIL_BYTES = 256 * 1024;
23759
+ var MAX_SUPPORT_AUDIT_FACTS = 200;
23760
+ var AUDIT_KINDS = /* @__PURE__ */ new Set([
23761
+ "artifact",
23762
+ "awaiting-approval",
23763
+ "cancelled",
23764
+ "claimed",
23765
+ "completed",
23766
+ "connection",
23767
+ "failed",
23768
+ "git-workspace",
23769
+ "offered",
23770
+ "paired",
23771
+ "progress",
23772
+ "runtimes-detected",
23773
+ "shutdown-complete",
23774
+ "shutdown-requested",
23775
+ "stale-approval-decision",
23776
+ "started",
23777
+ "unpaired"
23778
+ ]);
23779
+ function isAuditKind(value) {
23780
+ return typeof value === "string" && AUDIT_KINDS.has(value);
23781
+ }
23782
+ function sameFileState6(left, right) {
23783
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.ctimeNs === right.ctimeNs;
23784
+ }
23785
+ async function recentAuditFacts(storeDir) {
23786
+ const filePath = auditLogPath(storeDir);
23787
+ let pathStat;
23788
+ try {
23789
+ pathStat = await promises.lstat(filePath, { bigint: true });
23790
+ if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
23791
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23792
+ }
23793
+ } catch (err) {
23794
+ if (err.code === "ENOENT") {
23795
+ return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23796
+ }
23797
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23798
+ }
23799
+ let handle;
23800
+ try {
23801
+ handle = await promises.open(
23802
+ filePath,
23803
+ constants.O_RDONLY | constants.O_NONBLOCK | (constants.O_NOFOLLOW ?? 0)
23804
+ );
23805
+ } catch (err) {
23806
+ if (err.code === "ENOENT") {
23807
+ return { status: "missing", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23808
+ }
23809
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23810
+ }
23811
+ try {
23812
+ const stat = await handle.stat({ bigint: true });
23813
+ const namedAfterOpen = await promises.lstat(filePath, { bigint: true });
23814
+ if (!stat.isFile() || !namedAfterOpen.isFile() || namedAfterOpen.isSymbolicLink() || !sameFileState6(pathStat, stat) || !sameFileState6(stat, namedAfterOpen)) {
23815
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23816
+ }
23817
+ const sourceSize = Number(stat.size);
23818
+ const length = Math.min(sourceSize, MAX_SUPPORT_AUDIT_TAIL_BYTES);
23819
+ const start = Math.max(0, sourceSize - length);
23820
+ const buffer = Buffer.alloc(length);
23821
+ const { bytesRead } = await handle.read(buffer, 0, length, start);
23822
+ const afterRead = await handle.stat({ bigint: true });
23823
+ const namedAfterRead = await promises.lstat(filePath, { bigint: true });
23824
+ if (bytesRead !== length || namedAfterRead.isSymbolicLink() || !sameFileState6(stat, afterRead) || !sameFileState6(afterRead, namedAfterRead)) {
23825
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23826
+ }
23827
+ let text = buffer.subarray(0, bytesRead).toString("utf8");
23828
+ if (start > 0) text = text.slice(Math.max(0, text.indexOf("\n") + 1));
23829
+ const lines = text.split("\n").filter(Boolean).slice(-MAX_SUPPORT_AUDIT_FACTS);
23830
+ const facts = [];
23831
+ for (const line of lines) {
23832
+ try {
23833
+ const value = JSON.parse(line);
23834
+ if (isAuditKind(value.kind) && typeof value.ts === "string" && Number.isFinite(Date.parse(value.ts))) {
23835
+ facts.push({ kind: value.kind, ts: new Date(Date.parse(value.ts)).toISOString() });
23836
+ }
23837
+ } catch {
23838
+ }
23839
+ }
23840
+ return { status: "available", included: facts.length, sourceBytesRead: bytesRead, sourceTruncated: sourceSize > bytesRead, facts };
23841
+ } catch {
23842
+ return { status: "unavailable", included: 0, sourceBytesRead: 0, sourceTruncated: false, facts: [] };
23843
+ } finally {
23844
+ await handle.close();
23845
+ }
23846
+ }
23847
+ function projectControl(control) {
23848
+ if (control.status === "offline") return { status: "offline" };
23849
+ const transport = ["connecting", "open", "closed", "revoked"].includes(control.transport) ? control.transport : "unavailable";
23850
+ const operationalHealth = control.operationalHealth.availability === "available" ? {
23851
+ availability: "available",
23852
+ state: control.operationalHealth.state,
23853
+ failureCount: control.operationalHealth.failureCount,
23854
+ windowMs: control.operationalHealth.windowMs,
23855
+ failureThreshold: control.operationalHealth.failureThreshold,
23856
+ crashCount: control.operationalHealth.crashCount
23857
+ } : { availability: "unavailable" };
23858
+ return {
23859
+ status: "online",
23860
+ pid: control.pid,
23861
+ uptimeMs: control.uptimeMs,
23862
+ transport,
23863
+ activeTaskCount: control.activeTaskCount,
23864
+ pendingApprovalCount: control.pendingApprovalCount,
23865
+ operationalHealth,
23866
+ storagePresent: control.storage !== void 0
23867
+ };
23868
+ }
23869
+ async function createSupportBundle(config, storeDir, options = {}) {
23870
+ const [snapshot, recentEvents] = await Promise.all([collectDiagnostics(config, storeDir, options), recentAuditFacts(storeDir)]);
23871
+ return {
23872
+ version: 1,
23873
+ generatedAt: snapshot.generatedAt,
23874
+ product: snapshot.product,
23875
+ system: snapshot.system,
23876
+ config: {
23877
+ serverProtocol: snapshot.config.serverProtocol,
23878
+ customStoreDir: snapshot.config.customStoreDir,
23879
+ hostedJournal: snapshot.config.hostedJournal,
23880
+ ...snapshot.config.runtimeAllowlistCount === void 0 ? {} : { runtimeAllowlistCount: snapshot.config.runtimeAllowlistCount }
23881
+ },
23882
+ device: {
23883
+ status: snapshot.device.status,
23884
+ ...snapshot.device.deviceIdHash ? { deviceIdHash: snapshot.device.deviceIdHash } : {}
23885
+ },
23886
+ runtimes: snapshot.runtimes,
23887
+ control: projectControl(snapshot.control),
23888
+ health: snapshot.health,
23889
+ journal: snapshot.journal,
23890
+ workspace: snapshot.workspace,
23891
+ quarantine: {
23892
+ status: snapshot.quarantine.status,
23893
+ count: snapshot.quarantine.count,
23894
+ scannedCount: snapshot.quarantine.scannedCount,
23895
+ truncated: snapshot.quarantine.truncated,
23896
+ entries: snapshot.quarantine.entries
23897
+ },
23898
+ checks: snapshot.checks.map(({ id, status }) => ({ id, status })),
23899
+ recentEvents,
23900
+ redaction: {
23901
+ policy: "allowlist-v1",
23902
+ omitted: [
23903
+ "server URL host/path/query",
23904
+ "store/workspace/config paths",
23905
+ "control token and provider credentials",
23906
+ "task, prompt, tool input/output and approval text",
23907
+ "raw audit and quarantine contents"
23908
+ ],
23909
+ transformed: ["productName/productId/deviceId/runtimeId/quarantine filenames -> SHA-256", "audit events -> closed kind/timestamp only"]
23910
+ }
23911
+ };
23912
+ }
23913
+ async function writeSupportBundle(outputPath, bundle, secureFileOptions = {}) {
23914
+ const dir = path3__default.dirname(outputPath);
23915
+ const parentStat = await promises.stat(dir);
23916
+ if (!parentStat.isDirectory()) throw new Error("support bundle output parent is not a directory");
23917
+ const privateDir = path3__default.join(dir, `.${path3__default.basename(outputPath)}.${process.pid}.${randomUUID()}.private`);
23918
+ const tempPath = path3__default.join(privateDir, "bundle.tmp");
23919
+ try {
23920
+ await promises.mkdir(privateDir, { mode: 448 });
23921
+ await ensureSecureDir(privateDir, secureFileOptions);
23922
+ await atomicWriteFile(tempPath, `${JSON.stringify(bundle, null, 2)}
23923
+ `, { mode: 384, fsync: true });
23924
+ await ensureSecureFile(tempPath, secureFileOptions);
23925
+ await promises.link(tempPath, outputPath);
23926
+ const published = await promises.open(outputPath, "r+");
23927
+ try {
23928
+ await published.sync();
23929
+ } finally {
23930
+ await published.close();
23931
+ }
23932
+ if (process.platform !== "win32") {
23933
+ const parent = await promises.open(dir, "r");
23934
+ try {
23935
+ await parent.sync();
23936
+ } finally {
23937
+ await parent.close();
23938
+ }
23939
+ }
23940
+ } finally {
23941
+ await promises.rm(privateDir, { recursive: true, force: true }).catch(() => void 0);
23942
+ }
23943
+ }
23944
+
23945
+ // src/diagnostics/operator-actions.ts
23946
+ var MESSAGES2 = {
23947
+ "confirmation-required": "Explicit confirmation is required for this local maintenance action.",
23948
+ "invalid-input": "The maintenance action requires valid explicit inputs.",
23949
+ "daemon-running": "Stop the host daemon before local maintenance.",
23950
+ "store-busy": "Another operation owns the device store.",
23951
+ "agent-busy": "Another operation owns the Agent home.",
23952
+ "target-mismatch": "The stored device or Agent records do not match the authorized target.",
23953
+ "source-unavailable": "The local source cannot be accessed safely; preserve it for inspection.",
23954
+ "output-exists": "The selected output already exists; it will not be overwritten.",
23955
+ "operation-failed": "The maintenance operation did not complete; preserve evidence before retrying."
23956
+ };
23957
+ var DeviceOperatorError = class extends Error {
23958
+ constructor(code) {
23959
+ super(MESSAGES2[code]);
23960
+ this.code = code;
23961
+ this.name = "DeviceOperatorError";
23962
+ }
23963
+ code;
23964
+ };
23965
+ function requireConfirmation(input) {
23966
+ if (input?.confirmed !== true) throw new DeviceOperatorError("confirmation-required");
23967
+ }
23968
+ function closedError(error) {
23969
+ if (error instanceof DeviceOperatorError) return error;
23970
+ if (error instanceof DaemonOwnerActiveError) return new DeviceOperatorError("store-busy");
23971
+ if (error instanceof AgentHomeBusyError) return new DeviceOperatorError("agent-busy");
23972
+ return new DeviceOperatorError("operation-failed");
23973
+ }
23974
+ async function requireOffline(config, storeDir) {
23975
+ const control = await connectControlClient({ productId: config.productId, storeDir });
23976
+ if (control.ok) {
23977
+ control.client.close();
23978
+ throw new DeviceOperatorError("daemon-running");
23979
+ }
23980
+ }
23981
+ async function quarantineDeviceOperationalHealth(config, input) {
23982
+ requireConfirmation(input);
23983
+ try {
23984
+ const storeDir = DeviceStore.resolveDir(config.productId, config.storeDir);
23985
+ await requireOffline(config, storeDir);
23986
+ const result = await quarantineCorruptOperationalHealth(storeDir);
23987
+ return { action: "quarantine-operational-health", scope: "device", ...result };
23988
+ } catch (error) {
23989
+ throw closedError(error);
23990
+ }
23991
+ }
23992
+ async function exportDeviceSupportBundle(config, input) {
23993
+ if (typeof input?.outputPath !== "string" || !path3__default.isAbsolute(input.outputPath)) {
23994
+ throw new DeviceOperatorError("invalid-input");
23995
+ }
23996
+ try {
23997
+ const storeDir = DeviceStore.resolveDir(config.productId, config.storeDir);
23998
+ const bundle = await createSupportBundle(config, storeDir, {
23999
+ adapters: input.adapters,
24000
+ runtimeProbeTimeoutMs: input.runtimeProbeTimeoutMs
24001
+ });
24002
+ await writeSupportBundle(input.outputPath, bundle);
24003
+ return { action: "export-support-bundle", scope: "device", status: "written", bundleVersion: bundle.version };
24004
+ } catch (error) {
24005
+ if (error?.code === "EEXIST") throw new DeviceOperatorError("output-exists");
24006
+ throw closedError(error);
24007
+ }
24008
+ }
24009
+ async function existingOutbox(home) {
24010
+ const components = [
24011
+ home,
24012
+ path3__default.join(home, AGENT_HOME_INTERNAL_DIRECTORY),
24013
+ path3__default.join(home, AGENT_MESSAGE_DIRECTORY),
24014
+ path3__default.join(home, AGENT_MESSAGE_DIRECTORY, AGENT_MESSAGE_OUTBOX_FILENAME)
24015
+ ];
24016
+ for (let index = 0; index < components.length; index += 1) {
24017
+ let stat;
24018
+ try {
24019
+ stat = await promises.lstat(components[index]);
24020
+ } catch (error) {
24021
+ if (error.code === "ENOENT") return false;
24022
+ throw new DeviceOperatorError("source-unavailable");
24023
+ }
24024
+ if (stat.isSymbolicLink() || (index === components.length - 1 ? !stat.isFile() : !stat.isDirectory())) {
24025
+ throw new DeviceOperatorError("source-unavailable");
24026
+ }
24027
+ }
24028
+ return true;
24029
+ }
24030
+ async function archiveAgentTerminalMessages(config, input) {
24031
+ requireConfirmation(input);
24032
+ const parsedRef = AgentRefSchema.safeParse(input.agentRef);
24033
+ if (!parsedRef.success || !isTenantId(input.expectedTenantId) || typeof input.expectedDeviceId !== "string" || !input.expectedDeviceId.trim() || typeof input.archiveDirectory !== "string" || !path3__default.isAbsolute(input.archiveDirectory) || !config.agentHome) {
24034
+ throw new DeviceOperatorError("invalid-input");
24035
+ }
24036
+ const notNeeded = { action: "archive-terminal-messages", scope: "agent", status: "not-needed" };
24037
+ try {
24038
+ const storeDir = DeviceStore.resolveDir(config.productId, config.storeDir);
24039
+ await requireOffline(config, storeDir);
24040
+ const owner = await acquireDaemonOwner(storeDir, "doctor");
24041
+ try {
24042
+ const metadata = await new DeviceStore(storeDir, void 0, config.productId).load();
24043
+ if (!metadata || metadata.deviceId !== input.expectedDeviceId || metadata.tenantId !== input.expectedTenantId) {
24044
+ throw new DeviceOperatorError("target-mismatch");
24045
+ }
24046
+ const manager = new AgentHomeManager({
24047
+ hostStorageRoot: config.agentHome.hostStorageRoot,
24048
+ leaseManager: new AgentHomeLeaseManager({ ownerId: stableAgentHomeOwnerId(storeDir, config.productId) })
24049
+ });
24050
+ const home = await manager.layout.canonicalHomePath(parsedRef.data);
24051
+ if (!await existingOutbox(home)) return notNeeded;
24052
+ const binding = await manager.acquire(parsedRef.data);
24053
+ try {
24054
+ if (!await existingOutbox(binding.lease.cwd)) throw new DeviceOperatorError("source-unavailable");
24055
+ const outbox = await AgentMessageOutbox.open(binding.lease.cwd);
24056
+ const records = outbox.records();
24057
+ if (records.some((record3) => record3.tenantId !== input.expectedTenantId || record3.agentRef.agentId !== parsedRef.data.agentId)) {
24058
+ throw new DeviceOperatorError("target-mismatch");
24059
+ }
24060
+ const archivedRecords = outbox.terminalRecords().length;
24061
+ if (archivedRecords === 0) return notNeeded;
24062
+ try {
24063
+ await promises.mkdir(input.archiveDirectory, { mode: 448 });
24064
+ } catch (error) {
24065
+ if (error.code === "EEXIST") throw new DeviceOperatorError("output-exists");
24066
+ throw error;
24067
+ }
24068
+ const archivePath = await outbox.archiveTerminalRecords(input.archiveDirectory);
24069
+ if (!archivePath) throw new DeviceOperatorError("operation-failed");
24070
+ return {
24071
+ action: "archive-terminal-messages",
24072
+ scope: "agent",
24073
+ status: "archived",
24074
+ archivedRecords,
24075
+ archiveFileName: path3__default.basename(archivePath)
24076
+ };
24077
+ } finally {
24078
+ await binding.lease.release();
24079
+ }
24080
+ } finally {
24081
+ await owner.release();
24082
+ }
24083
+ } catch (error) {
24084
+ throw closedError(error);
24085
+ }
24086
+ }
24087
+
24088
+ 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, DeviceOperatorError, 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, archiveAgentTerminalMessages, buildIcaclsArgs, cleanupEligibleAt, cleanupOrderFor, computePressureState, createAgentHomeProjection, createAgentHomeProjectionConsumer, createDaemon, createDaemonWithAdapters, createFilesystemCleanupExecutor, createServiceLifecycle, createStatfsFreeBytesProvider, decodeTeamMemberContext, diagnoseDevice, encodeTeamMemberContext, ensureSecureDir, exportDeviceSupportBundle, freezeRuntimeAdapterDescriptor, generateLaunchdPlist, generateSystemdUnit, generateWinswXml, installSkillPacks, isAgentMemorySecureFilesystemAvailable, isGitWorkspaceConfig, isRuntimeDisposalFailure, isRuntimeExecutionFailure, isRuntimeStartupDisposalFailure, journalHash, listInstalledSkillPacks, localStateRelocation, nodeAgentProgram, openTeamTmuxView, prependGitWorkspaceGuidance, projectRuntimeBoundaryFailure, projectRuntimeExecutionFailure, projectSkillPack, quarantineDeviceOperationalHealth, readDeviceEnrollmentStatus, repairDeviceEnrollmentMetadata, requestDeviceAssertion, resolveLocalAgentReleaseIdentity, resolveLocalStoragePolicy, resolveSdkReservedHelperBin, runSdkReservedHelperCommand, sanitizeServiceName, sealRuntimeOperationManifest, skillPacksRoot, stableAgentHomeOwnerId, validateAgentRef };
21984
24089
  //# sourceMappingURL=index.js.map
21985
24090
  //# sourceMappingURL=index.js.map