@quantiya/codevibe-claude-plugin 2.0.21 → 2.0.23

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 (20) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/node_modules/@quantiya/codevibe-core/dist/diagnostics/shape-only.d.ts +11 -0
  3. package/node_modules/@quantiya/codevibe-core/dist/index.js +321 -282
  4. package/node_modules/@quantiya/codevibe-core/dist/local-executor/process-tree.d.ts +2 -0
  5. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/__tests__/o5-diagnostics.test.d.ts +1 -0
  6. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/cli.js +517 -115
  7. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/quorum-loop.d.ts +8 -1
  8. package/node_modules/@quantiya/codevibe-core/dist/orchestration-shell/reducer.d.ts +4 -5
  9. package/node_modules/@quantiya/codevibe-core/dist/reviewer/provider.d.ts +7 -7
  10. package/node_modules/@quantiya/codevibe-core/dist/substrate/command-runner.d.ts +1 -0
  11. package/node_modules/@quantiya/codevibe-core/dist/substrate/sandbox-exec.d.ts +3 -2
  12. package/node_modules/@quantiya/codevibe-core/dist/substrate/types.d.ts +20 -0
  13. package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/apikey-bootstrap.d.ts +32 -1
  14. package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/engage-substrate.d.ts +2 -0
  15. package/node_modules/@quantiya/codevibe-core/dist/substrate-launch/sanitized-env.d.ts +6 -0
  16. package/node_modules/@quantiya/codevibe-core/package.json +1 -1
  17. package/node_modules/fs-ext/build/Makefile +1 -1
  18. package/node_modules/fs-ext/build/Release/fs_ext.node +0 -0
  19. package/node_modules/fs-ext/build/config.gypi +0 -2
  20. package/package.json +2 -2
@@ -8410,9 +8410,10 @@ function reduceSessionTaskTerminal(state, event) {
8410
8410
  if (terminal === null) return state;
8411
8411
  let taskId = "taskId" in event ? event.taskId : void 0;
8412
8412
  if (!taskId) return state;
8413
- let existing = state.sessionTasks.get(taskId), sessionTasks = new Map(state.sessionTasks);
8413
+ let runningTasks = new Map(state.runningTasks), evictedRunningTask = runningTasks.delete(taskId), existing = state.sessionTasks.get(taskId), sessionTasks = new Map(state.sessionTasks);
8414
8414
  if (existing) {
8415
- if (existing.origin !== "single" || existing.status !== "running") return state;
8415
+ if (existing.origin !== "single" || existing.status !== "running")
8416
+ return evictedRunningTask ? { ...state, runningTasks } : state;
8416
8417
  sessionTasks.set(taskId, { ...existing, status: terminal });
8417
8418
  } else
8418
8419
  sessionTasks.set(taskId, {
@@ -8421,7 +8422,7 @@ function reduceSessionTaskTerminal(state, event) {
8421
8422
  status: terminal,
8422
8423
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
8423
8424
  });
8424
- return { ...state, sessionTasks };
8425
+ return { ...state, runningTasks, sessionTasks };
8425
8426
  }
8426
8427
  var REVIEWER_STATUS_PHASES = /* @__PURE__ */ new Set([
8427
8428
  "reviewers_dispatched",
@@ -8724,9 +8725,9 @@ function reduceClarificationAnswered(state, answer) {
8724
8725
  };
8725
8726
  }
8726
8727
  function reduceTaskLifecycle(state, task) {
8727
- let next = new Map(state.runningTasks);
8728
- task.status === "completed" || task.status === "cancelled" || task.status === "failed" ? next.delete(task.taskId) : next.set(task.taskId, task);
8729
- let history = new Map(state.sessionTasks), existing = state.sessionTasks.get(task.taskId), existingIsTerminal = existing?.origin === "single" && (existing.status === "completed" || existing.status === "cancelled" || existing.status === "failed"), incomingIsTerminal = task.status === "completed" || task.status === "cancelled" || task.status === "failed";
8728
+ let existing = state.sessionTasks.get(task.taskId), existingIsTerminal = existing?.origin === "single" && (existing.status === "completed" || existing.status === "cancelled" || existing.status === "failed"), incomingIsTerminal = task.status === "completed" || task.status === "cancelled" || task.status === "failed", next = new Map(state.runningTasks);
8729
+ incomingIsTerminal || existingIsTerminal && !incomingIsTerminal ? next.delete(task.taskId) : next.set(task.taskId, task);
8730
+ let history = new Map(state.sessionTasks);
8730
8731
  return history.set(task.taskId, {
8731
8732
  taskId: task.taskId,
8732
8733
  agentKind: task.agentKind,
@@ -20276,6 +20277,37 @@ async function bindWorkingDirectory(payload){
20276
20277
  throw new Error('target cwd incarnation changed before launch');
20277
20278
  }
20278
20279
  }
20280
+ function holdLaunchPathAuthorities(payload){
20281
+ const authorities=payload.launchPathAuthorities;
20282
+ if(authorities===undefined)return [];
20283
+ if(!Array.isArray(authorities)||authorities.length>16)throw new Error('invalid launch path authorities');
20284
+ const held=[];
20285
+ try{
20286
+ for(const authority of authorities){
20287
+ if(!authority||authority.version!==1||
20288
+ typeof authority.canonicalPath!=='string'||!require('node:path').isAbsolute(authority.canonicalPath)||authority.canonicalPath.includes('\0')||
20289
+ typeof authority.dev!=='string'||typeof authority.ino!=='string'||
20290
+ typeof authority.birthtimeNs!=='string')throw new Error('invalid launch path authority');
20291
+ const flags=fs.constants.O_RDONLY|(fs.constants.O_DIRECTORY||0)|(fs.constants.O_NOFOLLOW||0);
20292
+ const fd=fs.openSync(authority.canonicalPath,flags);
20293
+ held.push(fd);
20294
+ const opened=fs.fstatSync(fd,{bigint:true});
20295
+ const live=fs.lstatSync(authority.canonicalPath,{bigint:true});
20296
+ const canonical=fs.realpathSync(authority.canonicalPath);
20297
+ if(!opened.isDirectory()||!live.isDirectory()||live.isSymbolicLink()||
20298
+ opened.dev.toString()!==authority.dev||opened.ino.toString()!==authority.ino||
20299
+ opened.birthtimeNs.toString()!==authority.birthtimeNs||
20300
+ live.dev.toString()!==authority.dev||live.ino.toString()!==authority.ino||
20301
+ live.birthtimeNs.toString()!==authority.birthtimeNs||canonical!==authority.canonicalPath){
20302
+ throw new Error('launch path authority changed before target spawn');
20303
+ }
20304
+ }
20305
+ return held;
20306
+ }catch(err){
20307
+ for(const fd of held){try{fs.closeSync(fd);}catch{}}
20308
+ throw err;
20309
+ }
20310
+ }
20279
20311
  const hostPipe=fs.createReadStream(null,{fd:5,autoClose:false});
20280
20312
  for(const event of ['end','close','error'])hostPipe.once(event,killOwnGroup);
20281
20313
  for(const [signal] of [['SIGTERM'],['SIGINT'],['SIGHUP']])process.on(signal,killOwnGroup);
@@ -20284,9 +20316,17 @@ for(const [signal] of [['SIGTERM'],['SIGINT'],['SIGHUP']])process.on(signal,kill
20284
20316
  const start=await readLine(4);
20285
20317
  if(start!=='START')throw new Error('target start not authorized');
20286
20318
  await bindWorkingDirectory(payload);
20319
+ // This is deliberately synchronous and immediately adjacent to cp.spawn:
20320
+ // keep exact directory descriptors open across native process creation so a
20321
+ // pathname replacement during durable-owner publication cannot be adopted.
20322
+ const launchPathFds=holdLaunchPathAuthorities(payload);
20287
20323
  // No pathname cwd is passed here: the child inherits the wrapper's already-
20288
20324
  // authenticated kernel cwd reference, so a later rename cannot redirect it.
20289
- target=cp.spawn(payload.command,payload.args,{env:payload.env,stdio:['inherit','inherit','inherit'],detached:false,windowsHide:true});
20325
+ try{
20326
+ target=cp.spawn(payload.command,payload.args,{env:payload.env,stdio:['inherit','inherit','inherit'],detached:false,windowsHide:true});
20327
+ }finally{
20328
+ for(const fd of launchPathFds){try{fs.closeSync(fd);}catch{}}
20329
+ }
20290
20330
  target.once('error',err=>{send({type:'target-exit',code:127,error:String(err&&err.message||err)});});
20291
20331
  target.once('exit',(code,signal)=>{send({type:'target-exit',code:Number.isInteger(code)?code:(signal?128:1)});});
20292
20332
  // Remain the authenticated group/session leader until the outside host has
@@ -20386,6 +20426,7 @@ function spawnPosixOwnedProcess(command, args, options, seams = {}) {
20386
20426
  args,
20387
20427
  cwd: targetCwd,
20388
20428
  cwdAuthority: seams.workingDirAuthority,
20429
+ launchPathAuthorities: seams.launchPathAuthorities,
20389
20430
  env: options?.env ?? process.env,
20390
20431
  ...seams.cleanupTimeoutMs === void 0 ? {} : { cleanupTimeoutMs: seams.cleanupTimeoutMs }
20391
20432
  }), "utf8");
@@ -26408,6 +26449,38 @@ var LocalGemmaPlannerAdapter = class {
26408
26449
  // src/orchestration-shell/quorum-loop.ts
26409
26450
  var import_node_fs24 = require("node:fs");
26410
26451
 
26452
+ // src/diagnostics/shape-only.ts
26453
+ function outputShapeOnly(raw) {
26454
+ if (typeof raw != "string" || raw.length === 0)
26455
+ return {
26456
+ bytes: 0,
26457
+ lines: 0,
26458
+ leadClass: "empty",
26459
+ fenceCount: 0,
26460
+ bulletLines: 0,
26461
+ blankLines: 0
26462
+ };
26463
+ let lines = raw.split(`
26464
+ `), trimmed = raw.trim(), leadClass;
26465
+ return trimmed.length === 0 ? leadClass = "empty" : trimmed.startsWith("{") || trimmed.startsWith("[") ? leadClass = "json-open" : trimmed.startsWith("```") ? leadClass = "fence" : /^#{1,6}\s/.test(trimmed) ? leadClass = "heading" : /^[-*+]\s/.test(trimmed) ? leadClass = "bullet" : /^\d+[.)]\s/.test(trimmed) ? leadClass = "ordinal" : /^[A-Za-z]/.test(trimmed) ? leadClass = "prose" : leadClass = "other", {
26466
+ bytes: Buffer.byteLength(raw, "utf8"),
26467
+ lines: lines.length,
26468
+ leadClass,
26469
+ fenceCount: (raw.match(/```/g) ?? []).length,
26470
+ bulletLines: lines.filter((line) => /^\s*[-*+]\s/.test(line)).length,
26471
+ blankLines: lines.filter((line) => line.trim().length === 0).length
26472
+ };
26473
+ }
26474
+ function errorShapeOnly(error) {
26475
+ let value = error, message = typeof value?.message == "string" ? value.message : "", causeCount = Array.isArray(value?.causes) ? value.causes.length : value?.cause !== void 0 ? 1 : 0;
26476
+ return {
26477
+ errorClass: error instanceof Error ? "error" : "non-error",
26478
+ messageBytes: Buffer.byteLength(message, "utf8"),
26479
+ hasCause: value?.cause !== void 0,
26480
+ causeCount
26481
+ };
26482
+ }
26483
+
26411
26484
  // src/reduced-trust-notice.ts
26412
26485
  var surfacedRationales = /* @__PURE__ */ new Set();
26413
26486
  function noticeKey(sessionId, tier, reason) {
@@ -28103,7 +28176,8 @@ var ClassBConsumer = class {
28103
28176
  };
28104
28177
 
28105
28178
  // src/substrate-launch/apikey-bootstrap.ts
28106
- var import_node_fs14 = require("node:fs"), os19 = __toESM(require("node:os")), path32 = __toESM(require("node:path")), HELPER_SCRIPT_NAME = "broker-apikey-helper.sh", TOKEN_FILE_NAME = "broker-token", CLAUDE_SETTINGS_NAME = "settings.json", CODEX_CONFIG_NAME = "config.toml", SANDBOX_BOOTSTRAP_DIR = "/codevibe/agent", SANDBOX_AGENT_CONFIG_DIR = "/codevibe/agent-config", CODEX_PROVIDER_ID = "codevibe_broker";
28179
+ var import_node_fs14 = require("node:fs"), os19 = __toESM(require("node:os")), path32 = __toESM(require("node:path"));
28180
+ var HELPER_SCRIPT_NAME = "broker-apikey-helper.sh", TOKEN_FILE_NAME = "broker-token", CLAUDE_SETTINGS_NAME = "settings.json", CODEX_CONFIG_NAME = "config.toml", SANDBOX_BOOTSTRAP_DIR = "/codevibe/agent", SANDBOX_AGENT_CONFIG_DIR = "/codevibe/agent-config", CODEX_PROVIDER_ID = "codevibe_broker";
28107
28181
  function helperScriptSource(tokenFileAbsPathInSandbox) {
28108
28182
  return `#!/bin/sh
28109
28183
  # CP-7 apiKeyHelper \u2014 emits the current broker token (NEVER the vendor key).
@@ -28144,12 +28218,16 @@ function isInsideOrEqual(child, parent) {
28144
28218
  return rel.length > 0 && !rel.startsWith("..") && !path32.isAbsolute(rel);
28145
28219
  }
28146
28220
  var ApiKeyBootstrap = class _ApiKeyBootstrap {
28147
- constructor(hostDir, sandboxDir, configHostDir, configSandboxDir, tokenFileHostPath) {
28221
+ constructor(hostDir, sandboxDir, configHostDir, configSandboxDir, tokenFileHostPath, claudeScratchHostDir, claudeScratchSandboxDir, claudeScratchIdentity, beforeClaudeScratchCleanupCommit) {
28148
28222
  this.hostDir = hostDir;
28149
28223
  this.sandboxDir = sandboxDir;
28150
28224
  this.configHostDir = configHostDir;
28151
28225
  this.configSandboxDir = configSandboxDir;
28152
28226
  this.tokenFileHostPath = tokenFileHostPath;
28227
+ this.claudeScratchHostDir = claudeScratchHostDir;
28228
+ this.claudeScratchSandboxDir = claudeScratchSandboxDir;
28229
+ this.claudeScratchIdentity = claudeScratchIdentity;
28230
+ this.beforeClaudeScratchCleanupCommit = beforeClaudeScratchCleanupCommit;
28153
28231
  }
28154
28232
  /**
28155
28233
  * Materialize the bootstrap: create the host dir (0700), write the token file
@@ -28165,9 +28243,8 @@ var ApiKeyBootstrap = class _ApiKeyBootstrap {
28165
28243
  );
28166
28244
  await import_node_fs14.promises.chmod(rawConfigHostDir, 448).catch(() => {
28167
28245
  });
28168
- let hostDir = await import_node_fs14.promises.realpath(rawHostDir), configHostDir = await import_node_fs14.promises.realpath(rawConfigHostDir);
28246
+ let hostDir = await import_node_fs14.promises.realpath(rawHostDir), configHostDir = await import_node_fs14.promises.realpath(rawConfigHostDir), realWorkdir;
28169
28247
  if (input.workdir !== void 0) {
28170
- let realWorkdir;
28171
28248
  try {
28172
28249
  realWorkdir = await import_node_fs14.promises.realpath(input.workdir);
28173
28250
  } catch {
@@ -28204,7 +28281,7 @@ var ApiKeyBootstrap = class _ApiKeyBootstrap {
28204
28281
  "ApiKeyBootstrap.create: provide `sandboxConfigDir` or `configDirEqualsHostDir`"
28205
28282
  );
28206
28283
  let tokenInSandbox = path32.posix.join(sandboxDir, TOKEN_FILE_NAME), helperInSandbox = path32.posix.join(sandboxDir, HELPER_SCRIPT_NAME), tokenFileHostPath = path32.join(hostDir, TOKEN_FILE_NAME);
28207
- return await import_node_fs14.promises.writeFile(tokenFileHostPath, input.initialToken, {
28284
+ await import_node_fs14.promises.writeFile(tokenFileHostPath, input.initialToken, {
28208
28285
  encoding: "utf8",
28209
28286
  mode: 384
28210
28287
  }), await import_node_fs14.promises.writeFile(
@@ -28219,12 +28296,107 @@ var ApiKeyBootstrap = class _ApiKeyBootstrap {
28219
28296
  path32.join(configHostDir, CODEX_CONFIG_NAME),
28220
28297
  codexConfig(input.sandboxBrokerUrl, helperInSandbox),
28221
28298
  { encoding: "utf8", mode: 384 }
28222
- ), new _ApiKeyBootstrap(
28299
+ );
28300
+ let claudeScratchHostDir = null, claudeScratchSandboxDir = null, claudeScratchIdentity = null;
28301
+ if (input.claudeScratchHostRoot !== void 0) {
28302
+ if (input.provider !== "anthropic" || !input.sandboxDirEqualsHostDir) {
28303
+ let primary = new Error(
28304
+ "ApiKeyBootstrap.create: a short Claude scratch root is valid only for the A5 anthropic path"
28305
+ ), cleanupErrors = [];
28306
+ for (let directory of [hostDir, configHostDir])
28307
+ try {
28308
+ await import_node_fs14.promises.rm(directory, { recursive: !0, force: !0 });
28309
+ } catch (cleanupError) {
28310
+ cleanupErrors.push(cleanupError);
28311
+ }
28312
+ throw cleanupErrors.length > 0 ? Object.assign(
28313
+ new Error(
28314
+ "ApiKeyBootstrap.create: invalid scratch configuration and cleanup did not complete"
28315
+ ),
28316
+ { causes: [primary, ...cleanupErrors] }
28317
+ ) : primary;
28318
+ }
28319
+ let rawScratchDir = null;
28320
+ try {
28321
+ let canonicalScratchRoot = await import_node_fs14.promises.realpath(
28322
+ input.claudeScratchHostRoot
28323
+ );
28324
+ rawScratchDir = await import_node_fs14.promises.mkdtemp(path32.join(canonicalScratchRoot, "cv"));
28325
+ let initial = await import_node_fs14.promises.lstat(rawScratchDir, { bigint: !0 });
28326
+ if (!initial.isDirectory() || initial.isSymbolicLink())
28327
+ throw new Error(
28328
+ "ApiKeyBootstrap.create: generated Claude scratch root is not a real directory"
28329
+ );
28330
+ await input.beforeClaudeScratchOpen?.(rawScratchDir);
28331
+ let scratchHandle = await import_node_fs14.promises.open(
28332
+ rawScratchDir,
28333
+ import_node_fs14.constants.O_RDONLY | import_node_fs14.constants.O_NOFOLLOW | (typeof import_node_fs14.constants.O_DIRECTORY == "number" ? import_node_fs14.constants.O_DIRECTORY : 0)
28334
+ );
28335
+ try {
28336
+ let opened = await scratchHandle.stat({ bigint: !0 });
28337
+ if (!opened.isDirectory() || opened.dev !== initial.dev || opened.ino !== initial.ino || opened.birthtimeNs !== initial.birthtimeNs)
28338
+ throw new Error(
28339
+ "ApiKeyBootstrap.create: Claude scratch root changed while binding its directory handle"
28340
+ );
28341
+ await scratchHandle.chmod(448);
28342
+ let sealed = await scratchHandle.stat({ bigint: !0 }), live = await import_node_fs14.promises.lstat(rawScratchDir, { bigint: !0 }), canonical = rawScratchDir, currentUid = process.getuid?.(), mode = sealed.mode & 0o777n;
28343
+ if (!sealed.isDirectory() || !live.isDirectory() || live.isSymbolicLink() || live.dev !== sealed.dev || live.ino !== sealed.ino || live.birthtimeNs !== sealed.birthtimeNs || currentUid === void 0 || sealed.uid !== BigInt(currentUid) || mode !== 0o700n)
28344
+ throw new Error(
28345
+ "ApiKeyBootstrap.create: generated Claude scratch root failed its descriptor-bound private-directory identity check"
28346
+ );
28347
+ claudeScratchHostDir = canonical, claudeScratchSandboxDir = rawScratchDir, claudeScratchIdentity = {
28348
+ version: 1,
28349
+ canonicalPath: canonical,
28350
+ dev: sealed.dev.toString(),
28351
+ ino: sealed.ino.toString(),
28352
+ birthtimeNs: sealed.birthtimeNs.toString()
28353
+ };
28354
+ } finally {
28355
+ await scratchHandle.close();
28356
+ }
28357
+ if (Buffer.byteLength(rawScratchDir, "utf8") > 30)
28358
+ throw new Error(
28359
+ "ApiKeyBootstrap.create: generated Claude scratch path exceeds the 30-byte A5 safety bound"
28360
+ );
28361
+ if (realWorkdir !== void 0 && isInsideOrEqual(claudeScratchHostDir, realWorkdir))
28362
+ throw new Error(
28363
+ "ApiKeyBootstrap.create: Claude scratch root is inside the agent workdir (fail-closed)"
28364
+ );
28365
+ } catch (error) {
28366
+ let cleanupErrors = [];
28367
+ if (claudeScratchIdentity !== null)
28368
+ try {
28369
+ await anchoredRemoveTree(
28370
+ claudeScratchIdentity.canonicalPath,
28371
+ claudeScratchIdentity
28372
+ );
28373
+ } catch (cleanupError) {
28374
+ cleanupErrors.push(cleanupError);
28375
+ }
28376
+ for (let directory of [hostDir, configHostDir])
28377
+ try {
28378
+ await import_node_fs14.promises.rm(directory, { recursive: !0, force: !0 });
28379
+ } catch (cleanupError) {
28380
+ cleanupErrors.push(cleanupError);
28381
+ }
28382
+ throw cleanupErrors.length > 0 ? Object.assign(
28383
+ new Error(
28384
+ "ApiKeyBootstrap.create: initialization failed and cleanup did not complete"
28385
+ ),
28386
+ { causes: [error, ...cleanupErrors] }
28387
+ ) : error;
28388
+ }
28389
+ }
28390
+ return new _ApiKeyBootstrap(
28223
28391
  hostDir,
28224
28392
  sandboxDir,
28225
28393
  configHostDir,
28226
28394
  sandboxConfigDir,
28227
- tokenFileHostPath
28395
+ tokenFileHostPath,
28396
+ claudeScratchHostDir,
28397
+ claudeScratchSandboxDir,
28398
+ claudeScratchIdentity,
28399
+ input.beforeClaudeScratchCleanupCommit
28228
28400
  );
28229
28401
  }
28230
28402
  /** The `SubstrateSpec.agentBootstrap` value for this bootstrap. */
@@ -28235,6 +28407,14 @@ var ApiKeyBootstrap = class _ApiKeyBootstrap {
28235
28407
  get configSpec() {
28236
28408
  return { hostDir: this.configHostDir, sandboxDir: this.configSandboxDir };
28237
28409
  }
28410
+ /** The unique writable Claude scratch grant, absent on Docker/Codex paths. */
28411
+ get claudeScratchSpec() {
28412
+ return this.claudeScratchHostDir === null || this.claudeScratchSandboxDir === null || this.claudeScratchIdentity === null ? null : {
28413
+ hostDir: this.claudeScratchHostDir,
28414
+ sandboxDir: this.claudeScratchSandboxDir,
28415
+ authority: this.claudeScratchIdentity
28416
+ };
28417
+ }
28238
28418
  /**
28239
28419
  * REFRESH the broker token (rotation / on a broker 401). Atomically rewrites
28240
28420
  * the token file (write a temp + rename) so the agent never reads a partial
@@ -28245,15 +28425,41 @@ var ApiKeyBootstrap = class _ApiKeyBootstrap {
28245
28425
  let tmp = `${this.tokenFileHostPath}.tmp-${process.pid}-${Date.now()}`;
28246
28426
  await import_node_fs14.promises.writeFile(tmp, token, { encoding: "utf8", mode: 384 }), await import_node_fs14.promises.rename(tmp, this.tokenFileHostPath);
28247
28427
  }
28248
- /** Remove the bootstrap dir (session teardown). Idempotent. */
28428
+ /**
28429
+ * Remove the bootstrap, config, and unique Claude scratch dirs. Scratch
28430
+ * cleanup is identity-bound: a replaced pathname is retained and reported,
28431
+ * never recursively removed as though it still belonged to this seat.
28432
+ */
28249
28433
  async destroy() {
28250
- await import_node_fs14.promises.rm(this.hostDir, { recursive: !0, force: !0 }).catch(
28251
- () => {
28434
+ let cleanupErrors = [];
28435
+ if (this.claudeScratchHostDir !== null && this.claudeScratchIdentity !== null)
28436
+ try {
28437
+ await anchoredRemoveTree(
28438
+ this.claudeScratchIdentity.canonicalPath,
28439
+ this.claudeScratchIdentity,
28440
+ {
28441
+ ...this.beforeClaudeScratchCleanupCommit ? {
28442
+ beforeCommit: () => this.beforeClaudeScratchCleanupCommit(
28443
+ this.claudeScratchHostDir
28444
+ )
28445
+ } : {}
28446
+ }
28447
+ );
28448
+ } catch (error) {
28449
+ cleanupErrors.push(error);
28252
28450
  }
28253
- ), await import_node_fs14.promises.rm(this.configHostDir, { recursive: !0, force: !0 }).catch(
28254
- () => {
28451
+ for (let directory of [this.hostDir, this.configHostDir])
28452
+ try {
28453
+ await import_node_fs14.promises.rm(directory, { recursive: !0, force: !0 });
28454
+ } catch (error) {
28455
+ cleanupErrors.push(error);
28255
28456
  }
28256
- );
28457
+ if (cleanupErrors.length === 1) throw cleanupErrors[0];
28458
+ if (cleanupErrors.length > 1)
28459
+ throw Object.assign(
28460
+ new Error("ApiKeyBootstrap.destroy: cleanup did not complete"),
28461
+ { causes: cleanupErrors }
28462
+ );
28257
28463
  }
28258
28464
  };
28259
28465
 
@@ -42775,10 +42981,7 @@ var LocalExecutorImpl = class {
42775
42981
  }).catch(() => {
42776
42982
  }), {
42777
42983
  args: { ...addClaudeSubstrateAuthArgs(args, result.agentConfigDir), substrate: result.substrateHandle },
42778
- onExit: async () => {
42779
- await result.teardown().catch(() => {
42780
- });
42781
- },
42984
+ onExit: () => result.teardown(),
42782
42985
  // Stage-2 r3 Codex HIGH — the ONLY return that proves a real confining
42783
42986
  // boundary (`result.mode === 'substrate'`, not reduced_trust / no-engager /
42784
42987
  // pre-TaskAuthorized). The danger-sandbox authority keys on THIS boolean,
@@ -42870,7 +43073,11 @@ var LocalExecutorImpl = class {
42870
43073
  }
42871
43074
  }
42872
43075
  async spawnWithLifecycle(args, spawnFn, onSubstrateExit) {
42873
- let role = args.role, lifecycleAudit = args.lifecycleAudit ?? "active_task", lifecycleTaskId = args.taskId ?? this.taskId, lifecycleCtx = lifecycleTaskId ? { taskId: lifecycleTaskId, ...this.baseCtx } : null, full = {
43076
+ let role = args.role, lifecycleAudit = args.lifecycleAudit ?? "active_task", lifecycleTaskId = args.taskId ?? this.taskId, lifecycleCtx = lifecycleTaskId ? { taskId: lifecycleTaskId, ...this.baseCtx } : null, substrateCleanupError, substrateCleanupPromise, cleanupSubstrate = async () => {
43077
+ onSubstrateExit && (substrateCleanupPromise || (substrateCleanupPromise = onSubstrateExit().catch((error) => {
43078
+ throw substrateCleanupError = error, error;
43079
+ })), await substrateCleanupPromise);
43080
+ }, full = {
42874
43081
  ...args,
42875
43082
  agentKind: args.agentKind ?? this.adapter,
42876
43083
  onProcessSpawned: async (info) => {
@@ -42884,35 +43091,51 @@ var LocalExecutorImpl = class {
42884
43091
  });
42885
43092
  },
42886
43093
  onProcessExited: async (info) => {
42887
- if (await args.onProcessExited?.(info), lifecycleAudit !== "none") {
42888
- let ctx = lifecycleCtx;
42889
- ctx !== null && await this.emitter.emitProcessExited(ctx, {
42890
- pid: info.pid,
42891
- exitCode: info.exitCode,
42892
- failureClass: info.failureClass,
42893
- occurredAt: info.exitedAt
42894
- });
43094
+ try {
43095
+ if (await args.onProcessExited?.(info), lifecycleAudit !== "none") {
43096
+ let ctx = lifecycleCtx;
43097
+ ctx !== null && await this.emitter.emitProcessExited(ctx, {
43098
+ pid: info.pid,
43099
+ exitCode: info.exitCode,
43100
+ failureClass: info.failureClass,
43101
+ occurredAt: info.exitedAt
43102
+ });
43103
+ }
43104
+ } catch {
42895
43105
  }
42896
- onSubstrateExit && await onSubstrateExit().catch(() => {
43106
+ await cleanupSubstrate().catch(() => {
42897
43107
  });
42898
43108
  }
42899
43109
  };
42900
43110
  try {
42901
- return await spawnFn(full);
43111
+ let handle = await spawnFn(full), done = handle.done.then((info) => {
43112
+ if (substrateCleanupError !== void 0)
43113
+ throw Object.assign(
43114
+ new Error("implementor substrate cleanup failed after process exit"),
43115
+ { cause: substrateCleanupError }
43116
+ );
43117
+ return info;
43118
+ });
43119
+ return { ...handle, done };
42902
43120
  } catch (err) {
42903
- if (onSubstrateExit && await onSubstrateExit().catch(() => {
42904
- }), err instanceof AuthorityError) {
43121
+ await cleanupSubstrate().catch(() => {
43122
+ });
43123
+ let primary = err;
43124
+ if (err instanceof AuthorityError) {
42905
43125
  let safeArgv = redactAgyPrintArgv(args.argv), safeDetail = redactAgyPrintValuesFromText(err.refusal.detail, args.argv), safeError = new AuthorityError({
42906
43126
  ...err.refusal,
42907
43127
  detail: safeDetail
42908
43128
  });
42909
- throw await this.bridge.bridgeAuthorityRefusal(safeError, {
43129
+ await this.bridge.bridgeAuthorityRefusal(safeError, {
42910
43130
  refusedMessageId: "spawn:" + role + ":" + Date.now(),
42911
43131
  shellContent: `Refused spawn (${role}): ${safeDetail}`,
42912
43132
  shellMetadata: { role, argv: safeArgv }
42913
- }), safeError;
43133
+ }), primary = safeError;
42914
43134
  }
42915
- throw err;
43135
+ throw substrateCleanupError !== void 0 ? Object.assign(
43136
+ new Error("implementor spawn failed and substrate cleanup also failed"),
43137
+ { causes: [primary, substrateCleanupError] }
43138
+ ) : primary;
42916
43139
  }
42917
43140
  }
42918
43141
  // --- Hook bridge surface ---------------------------------------------------
@@ -49034,10 +49257,11 @@ var ChildProcessCommandRunner = class {
49034
49257
  signal: opts.signal,
49035
49258
  ...opts.env !== void 0 ? { env: opts.env } : {},
49036
49259
  ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {}
49037
- }, proc = opts.cwdAuthority ? spawnPosixOwnedProcess(argv[0], argv.slice(1), spawnOptions, {
49038
- workingDirAuthority: opts.cwdAuthority
49260
+ }, needsOwnedProcessHost = opts.cwdAuthority !== void 0 || (opts.launchPathAuthorities?.length ?? 0) > 0, proc = needsOwnedProcessHost ? spawnPosixOwnedProcess(argv[0], argv.slice(1), spawnOptions, {
49261
+ workingDirAuthority: opts.cwdAuthority,
49262
+ launchPathAuthorities: opts.launchPathAuthorities
49039
49263
  }) : (0, import_node_child_process7.spawn)(argv[0], argv.slice(1), spawnOptions);
49040
- return opts.cwdAuthority && Object.defineProperty(proc, "hostProcessTreeOwnership", {
49264
+ return needsOwnedProcessHost && Object.defineProperty(proc, "hostProcessTreeOwnership", {
49041
49265
  value: "posix-host",
49042
49266
  configurable: !1,
49043
49267
  enumerable: !1,
@@ -49617,13 +49841,14 @@ async function resolveOnParentPath(cmd) {
49617
49841
  return null;
49618
49842
  }
49619
49843
  var SandboxExecHandle = class {
49620
- constructor(id, runner, profilePath, sanitizedEnv, workdir, workdirAuthority, auditDir = null) {
49844
+ constructor(id, runner, profilePath, sanitizedEnv, workdir, workdirAuthority, scratchAuthority, auditDir = null) {
49621
49845
  this.id = id;
49622
49846
  this.runner = runner;
49623
49847
  this.profilePath = profilePath;
49624
49848
  this.sanitizedEnv = sanitizedEnv;
49625
49849
  this.workdir = workdir;
49626
49850
  this.workdirAuthority = workdirAuthority;
49851
+ this.scratchAuthority = scratchAuthority;
49627
49852
  this.auditDir = auditDir;
49628
49853
  this.egressFidelity = "coarse";
49629
49854
  this.tornDown = !1;
@@ -49659,13 +49884,18 @@ var SandboxExecHandle = class {
49659
49884
  ; exec-time agent-binary read allowance (argv0 resolution, 2026-07-03)
49660
49885
  ${allowLines}
49661
49886
  `
49887
+ ), this.scratchAuthority && await assertWorkspaceRootAuthority(
49888
+ this.scratchAuthority.canonicalPath,
49889
+ this.scratchAuthority,
49890
+ "sandbox-exec Claude scratch handoff"
49662
49891
  );
49663
49892
  let sbArgv = ["sandbox-exec", "-f", this.profilePath, resolvedArgv0, ...argv.slice(1)], proc = this.runner.spawnLong(sbArgv, {
49664
49893
  stdinTty: opts.stdinTty,
49665
49894
  signal: opts.signal,
49666
49895
  env: this.sanitizedEnv,
49667
49896
  cwd: this.workdir,
49668
- ...this.workdirAuthority ? { cwdAuthority: this.workdirAuthority } : {}
49897
+ ...this.workdirAuthority ? { cwdAuthority: this.workdirAuthority } : {},
49898
+ ...this.scratchAuthority ? { launchPathAuthorities: [this.scratchAuthority] } : {}
49669
49899
  });
49670
49900
  return this.proc = proc, proc;
49671
49901
  }
@@ -49723,6 +49953,22 @@ ${allowLines}
49723
49953
  }
49724
49954
  extraWriteDirs.push(configDir);
49725
49955
  }
49956
+ if (spec.agentScratch) {
49957
+ try {
49958
+ await assertWorkspaceRootAuthority(
49959
+ spec.agentScratch.hostDir,
49960
+ spec.agentScratch.authority,
49961
+ "sandbox-exec Claude scratch launch"
49962
+ );
49963
+ } catch (error) {
49964
+ throw new SubstrateLaunchError(
49965
+ "sandbox_exec",
49966
+ "Claude scratch grant target identity could not be verified (fail-closed)",
49967
+ error
49968
+ );
49969
+ }
49970
+ extraWriteDirs.push(spec.agentScratch.authority.canonicalPath);
49971
+ }
49726
49972
  let resolvedAuditDir = null;
49727
49973
  if (spec.auditDir) {
49728
49974
  resolvedAuditDir = spec.auditDir;
@@ -49763,6 +50009,17 @@ ${allowLines}
49763
50009
  throw await import_node_fs23.promises.rm(profilePath, { force: !0 }).catch(() => {
49764
50010
  }), err;
49765
50011
  }
50012
+ if (spec.agentScratch)
50013
+ try {
50014
+ await assertWorkspaceRootAuthority(
50015
+ spec.agentScratch.authority.canonicalPath,
50016
+ spec.agentScratch.authority,
50017
+ "sandbox-exec Claude scratch profile handoff"
50018
+ );
50019
+ } catch (err) {
50020
+ throw await import_node_fs23.promises.rm(profilePath, { force: !0 }).catch(() => {
50021
+ }), err;
50022
+ }
49766
50023
  return new SandboxExecHandle(
49767
50024
  id,
49768
50025
  this.runner,
@@ -49770,6 +50027,7 @@ ${allowLines}
49770
50027
  finalEnv,
49771
50028
  canonicalWorkdir,
49772
50029
  spec.workdirAuthority,
50030
+ spec.agentScratch?.authority,
49773
50031
  resolvedAuditDir
49774
50032
  );
49775
50033
  }
@@ -50396,13 +50654,13 @@ function formatReviewerError(detail) {
50396
50654
  case "timeout":
50397
50655
  return `${detail.agent} reviewer timed out after ${detail.elapsed_ms}ms`;
50398
50656
  case "spawn_failed":
50399
- return `${detail.agent} reviewer spawn failed: ${detail.reason}`;
50657
+ return `${detail.agent} reviewer spawn failed`;
50400
50658
  case "parse_failure":
50401
50659
  return `${detail.agent} reviewer output was unparseable`;
50402
50660
  case "cancelled":
50403
50661
  return "reviewer cancelled before completion";
50404
50662
  case "internal_join_failure":
50405
- return `reviewer task internal join failure: ${detail.reason}`;
50663
+ return "reviewer task internal join failure";
50406
50664
  }
50407
50665
  }
50408
50666
 
@@ -50944,22 +51202,25 @@ var CONTINUATION_EXPIRES_TTL_MS = 1440 * 60 * 1e3, ACTIVE_AGENT_KINDS = ["CLAUDE
50944
51202
  function isActiveAgentKind2(agent) {
50945
51203
  return agent === "CLAUDE" || agent === "CODEX" || agent === "ANTIGRAVITY";
50946
51204
  }
50947
- var IMPLEMENTOR_TICK_MS = 1e4, TICK_SCAN_MAX_TRACKED = 5e3, TICK_SCAN_TIMEOUT_MS = 1e3, BACKGROUND_RECOVERY_ABORTED = /* @__PURE__ */ Symbol("background-recovery-aborted"), REVIEWER_ERROR_LOG_RAW_OUTPUT_CAP = 4e3;
51205
+ var IMPLEMENTOR_TICK_MS = 1e4, TICK_SCAN_MAX_TRACKED = 5e3, TICK_SCAN_TIMEOUT_MS = 1e3, BACKGROUND_RECOVERY_ABORTED = /* @__PURE__ */ Symbol("background-recovery-aborted");
50948
51206
  function describeReviewerErrorDetail(detail) {
50949
51207
  switch (detail.kind) {
50950
51208
  case "timeout":
50951
51209
  return { agent: detail.agent, elapsedMs: detail.elapsed_ms };
50952
51210
  case "spawn_failed":
50953
- return { agent: detail.agent, reason: detail.reason };
51211
+ return {
51212
+ agent: detail.agent,
51213
+ reasonBytes: Buffer.byteLength(detail.reason ?? "", "utf8")
51214
+ };
50954
51215
  case "parse_failure":
50955
51216
  return {
50956
51217
  agent: detail.agent,
50957
- rawOutput: detail.raw_output.slice(0, REVIEWER_ERROR_LOG_RAW_OUTPUT_CAP)
51218
+ rawOutputShape: outputShapeOnly(detail.raw_output)
50958
51219
  };
50959
51220
  case "cancelled":
50960
51221
  return {};
50961
51222
  case "internal_join_failure":
50962
- return { reason: detail.reason };
51223
+ return { reasonBytes: Buffer.byteLength(detail.reason ?? "", "utf8") };
50963
51224
  }
50964
51225
  }
50965
51226
  var REVIEWER_AGENT_DISPLAY_NAME = {
@@ -53273,8 +53534,8 @@ var QuorumLoop = class _QuorumLoop {
53273
53534
  taskId: args.taskId,
53274
53535
  trackIndex: args.validateDiffScope.trackIndex,
53275
53536
  exitCode: exit.exitCode,
53276
- stderrExcerpt: handle.stderr().slice(0, 800),
53277
- stdoutTail: handle.stdout().slice(-3e3)
53537
+ stderrShape: outputShapeOnly(handle.stderr()),
53538
+ stdoutShape: outputShapeOnly(handle.stdout())
53278
53539
  }
53279
53540
  ), await this.reportTeamTrackFailure(
53280
53541
  args.taskId,
@@ -53296,8 +53557,8 @@ var QuorumLoop = class _QuorumLoop {
53296
53557
  // exits 0 is still attributable from the log.
53297
53558
  exitCode: exit.exitCode,
53298
53559
  runtimeMs: exit.runtimeMs,
53299
- stdoutTail: handle.stdout().slice(-300),
53300
- stderrTail: handle.stderr().slice(-300)
53560
+ stdoutShape: outputShapeOnly(handle.stdout()),
53561
+ stderrShape: outputShapeOnly(handle.stderr())
53301
53562
  });
53302
53563
  try {
53303
53564
  let bundle = createSnapshotTrackBundle({
@@ -53374,8 +53635,8 @@ var QuorumLoop = class _QuorumLoop {
53374
53635
  exitCode: exit.exitCode,
53375
53636
  runtimeMs: exit.runtimeMs,
53376
53637
  ...files.length === 0 ? {
53377
- stdoutTail: handle.stdout().slice(-300),
53378
- stderrTail: handle.stderr().slice(-300)
53638
+ stdoutShape: outputShapeOnly(handle.stdout()),
53639
+ stderrShape: outputShapeOnly(handle.stderr())
53379
53640
  } : {}
53380
53641
  }), this.surfaceHalt(reason), await this.discardShadow(args.taskId);
53381
53642
  }
@@ -53390,15 +53651,20 @@ var QuorumLoop = class _QuorumLoop {
53390
53651
  }
53391
53652
  logger.warn("[QuorumLoop] implementor round failed", {
53392
53653
  gateId,
53393
- err: err.message
53654
+ ...errorShapeOnly(err)
53394
53655
  });
53395
53656
  let reason = classifyImplementorRoundFailure(err);
53396
- this.surfaceHalt(`The task could not proceed \u2014 ${reason}.`), this.emitProgress(
53397
- { phase: "round_failed", round: args.roundNumber, reason, taskId: args.taskId },
53398
- originEpoch
53399
- );
53657
+ this.surfaceHalt(`The task could not proceed \u2014 ${reason}.`);
53400
53658
  let reviseFeedbackId = args.reviseFeedbackId, preserveResetForRetry = submissionStarted && args.reviewScopeReset === !0 && reviseFeedbackId !== void 0 && this.reviewScopeResetTasks.has(args.taskId);
53401
- preserveResetForRetry && this.seenReviseFeedbackIds.delete(reviseFeedbackId), await this.discardShadow(args.taskId, void 0, {
53659
+ this.emitProgress(
53660
+ {
53661
+ phase: "round_failed",
53662
+ round: args.roundNumber,
53663
+ reason,
53664
+ ...preserveResetForRetry ? {} : { taskId: args.taskId }
53665
+ },
53666
+ originEpoch
53667
+ ), preserveResetForRetry && this.seenReviseFeedbackIds.delete(reviseFeedbackId), await this.discardShadow(args.taskId, void 0, {
53402
53668
  preserveReviseContext: preserveResetForRetry
53403
53669
  });
53404
53670
  } finally {
@@ -54464,7 +54730,7 @@ var QuorumLoop = class _QuorumLoop {
54464
54730
  if (promptText === null) {
54465
54731
  logger.warn("[QuorumLoop] getReviewerPrompt exhausted \u2014 ESCALATE", {
54466
54732
  key,
54467
- err: lastErr?.message,
54733
+ ...errorShapeOnly(lastErr),
54468
54734
  audit: "synthesized ESCALATE (#C1F-8): prompt_fetch_failed"
54469
54735
  }), await this.submitVerdict(args, this.synthesizeEscalate(args, DESKTOP_DISPATCH_ESCALATE_REASONING), sessionKey);
54470
54736
  return;
@@ -54499,13 +54765,13 @@ var QuorumLoop = class _QuorumLoop {
54499
54765
  failureReason,
54500
54766
  audit: "synthesized ESCALATE (#C1F-8): reviewer_substrate_engage_failed",
54501
54767
  ...describeReviewerErrorDetail(engageErr.detail),
54502
- message: engageErr.message
54768
+ ...errorShapeOnly(engageErr)
54503
54769
  }), await this.submitVerdict(args, this.synthesizeEscalate(args, cleanReasoning), sessionKey);
54504
54770
  return;
54505
54771
  }
54506
54772
  throw engageErr;
54507
54773
  }
54508
- let verdict;
54774
+ let verdict, evaluationError;
54509
54775
  try {
54510
54776
  verdict = await this.evaluateSeatWithTimeout(spec, args.gateId, seatSubstrate.handle);
54511
54777
  } catch (err) {
@@ -54533,7 +54799,7 @@ var QuorumLoop = class _QuorumLoop {
54533
54799
  // Audit prefix kept in LOGS only (#C1F-8) — never on the wire.
54534
54800
  audit: `synthesized ESCALATE (#C1F-8): reviewer_error:${err.detail.kind}`,
54535
54801
  ...describeReviewerErrorDetail(err.detail),
54536
- message: err.message,
54802
+ ...errorShapeOnly(err),
54537
54803
  // 2026-08-22 (dogfood) — TIMEOUT DIAGNOSTICS, local log only.
54538
54804
  //
54539
54805
  // A live 5-minute reviewer timeout on a ONE-FILE deletion diff was
@@ -54550,16 +54816,51 @@ var QuorumLoop = class _QuorumLoop {
54550
54816
  ...this.describeReviewShapeForDiagnostics(args.gateId)
54551
54817
  }), verdict = this.synthesizeEscalate(args, cleanReasoning);
54552
54818
  } else
54553
- throw err;
54554
- } finally {
54555
- await seatSubstrate.teardown().catch(() => {
54819
+ evaluationError = err;
54820
+ }
54821
+ let cleanupError;
54822
+ try {
54823
+ await seatSubstrate.teardown();
54824
+ } catch (error) {
54825
+ cleanupError = error, logger.warn("[QuorumLoop] reviewer substrate cleanup failed \u2014 forcing ESCALATE", {
54826
+ key,
54827
+ seatId: args.seatId,
54828
+ role: args.role,
54829
+ agentKind: args.agentKind,
54830
+ ...errorShapeOnly(error),
54831
+ audit: "synthesized ESCALATE: reviewer_substrate_cleanup_failed"
54556
54832
  });
54557
54833
  }
54834
+ if (evaluationError !== void 0) {
54835
+ let terminalError = cleanupError !== void 0 ? Object.assign(
54836
+ new Error("reviewer evaluation failed and substrate cleanup also failed"),
54837
+ { causes: [evaluationError, cleanupError] }
54838
+ ) : Object.assign(
54839
+ new Error("reviewer evaluation failed unexpectedly"),
54840
+ { cause: evaluationError }
54841
+ );
54842
+ logger.warn("[QuorumLoop] unexpected reviewer evaluation failure \u2014 forcing ESCALATE", {
54843
+ key,
54844
+ seatId: args.seatId,
54845
+ role: args.role,
54846
+ agentKind: args.agentKind,
54847
+ ...errorShapeOnly(terminalError),
54848
+ audit: "synthesized ESCALATE: reviewer_evaluation_failed"
54849
+ }), verdict = this.synthesizeEscalate(
54850
+ args,
54851
+ DESKTOP_DISPATCH_ESCALATE_REASONING
54852
+ );
54853
+ }
54854
+ if (cleanupError !== void 0 && (verdict = this.synthesizeEscalate(
54855
+ args,
54856
+ DESKTOP_DISPATCH_ESCALATE_REASONING
54857
+ )), verdict === void 0)
54858
+ throw new Error("reviewer completed without a verdict");
54558
54859
  reviewScope !== null && (verdict = enforceReviewRoundScope(verdict, reviewScope)), await this.submitVerdict(args, verdict, sessionKey);
54559
54860
  } catch (err) {
54560
54861
  logger.warn("[QuorumLoop] spawnOneSeat failed \u2014 dropped", {
54561
54862
  key,
54562
- err: err.message
54863
+ ...errorShapeOnly(err)
54563
54864
  });
54564
54865
  } finally {
54565
54866
  this.runningSeats.delete(key);
@@ -54652,19 +54953,18 @@ var QuorumLoop = class _QuorumLoop {
54652
54953
  if (e.name === REVIEWER_CREDENTIAL_MISSING)
54653
54954
  try {
54654
54955
  this.surfaceHalt(
54655
- `Reviewer (${seatLabel(args.agentKind, args.seatId)}) cannot start sandboxed: ${e.message}`
54956
+ `Reviewer (${seatLabel(args.agentKind, args.seatId)}) cannot start sandboxed: the required provider credential is unavailable. Run \`codevibe vendor-key setup\`` + (args.agentKind === "codex" ? " (or `codevibe vendor-key import-codex` for a ChatGPT subscription)" : "") + ", or set CODEVIBE_SANDBOX_REVIEWERS=0 to opt out of reviewer sandboxing."
54656
54957
  );
54657
54958
  } catch {
54658
54959
  }
54659
54960
  throw new ReviewerErrorClass({
54660
54961
  kind: "spawn_failed",
54661
54962
  agent: args.agentKind,
54662
- reason: `CP-7: reviewer substrate engage failed for a Trusted agent \u2014 refusing the unsandboxed (ambient-cred) fallback (fail-closed): ${e.message}`,
54963
+ reason: "CP-7: reviewer substrate engage failed for a Trusted agent \u2014 refusing the unsandboxed (ambient-cred) fallback (fail-closed)",
54663
54964
  failureReason: "spawn_failed"
54664
54965
  });
54665
54966
  }
54666
- return result.mode === "reduced_trust" ? (this.surfaceReviewerReducedTrust(args, result.reducedTrustReason), await result.teardown().catch(() => {
54667
- }), NO_TEARDOWN) : (result.reducedTrust && this.surfaceReviewerReducedTrust(
54967
+ return result.mode === "reduced_trust" ? (this.surfaceReviewerReducedTrust(args, result.reducedTrustReason), await result.teardown(), NO_TEARDOWN) : (result.reducedTrust && this.surfaceReviewerReducedTrust(
54668
54968
  args,
54669
54969
  result.reducedTrustReason ?? "coarse egress (A5 sandbox-exec) \u2014 reviewer is sandboxed but loopback-only."
54670
54970
  ), {
@@ -54706,7 +55006,7 @@ var QuorumLoop = class _QuorumLoop {
54706
55006
  reason
54707
55007
  ), logger.warn("[QuorumLoop] reduced-trust badge surfaceHalt threw (badge is best-effort) \u2014 continuing", {
54708
55008
  seatId: args.seatId,
54709
- err: e.message
55009
+ ...errorShapeOnly(e)
54710
55010
  });
54711
55011
  }
54712
55012
  }
@@ -54761,13 +55061,20 @@ var QuorumLoop = class _QuorumLoop {
54761
55061
  });
54762
55062
  if (result.mode === "substrate")
54763
55063
  return { mode: "substrate", handle: result.substrateHandle, teardown: () => result.teardown() };
54764
- await result.teardown().catch(() => {
54765
- });
55064
+ try {
55065
+ await result.teardown();
55066
+ } catch (cleanupError) {
55067
+ return logger.warn("[QuorumLoop] Wave B reduced-trust cleanup failed \u2014 refusing fallback", {
55068
+ taskId,
55069
+ agent,
55070
+ ...errorShapeOnly(cleanupError)
55071
+ }), { mode: "none" };
55072
+ }
54766
55073
  } catch (e) {
54767
55074
  logger.warn("[QuorumLoop] Wave B substrate engage failed \u2014 falling through to trusted-container/none", {
54768
55075
  taskId,
54769
55076
  agent,
54770
- err: e.message
55077
+ ...errorShapeOnly(e)
54771
55078
  });
54772
55079
  }
54773
55080
  return isTrustedContainerBoundary() ? { mode: "trusted_container", teardown: async () => {
@@ -54798,9 +55105,11 @@ var QuorumLoop = class _QuorumLoop {
54798
55105
  ...substrate !== void 0 ? { substrate } : {}
54799
55106
  });
54800
55107
  if (!outcome.exit_success)
54801
- throw new Error(
54802
- `class-2 resolver model call exited non-zero: ${outcome.stderr.trim().slice(0, 200)}`
54803
- );
55108
+ throw logger.warn("[QuorumLoop] class-2 resolver model call exited non-zero", {
55109
+ ownerTaskId: args.ownerTaskId,
55110
+ stdoutShape: outputShapeOnly(outcome.stdout),
55111
+ stderrShape: outputShapeOnly(outcome.stderr)
55112
+ }), new Error("class-2 resolver model call exited non-zero");
54804
55113
  return outcome.stdout;
54805
55114
  };
54806
55115
  return {
@@ -54844,7 +55153,7 @@ var QuorumLoop = class _QuorumLoop {
54844
55153
  return { establishable: !0, via: "ladder_tier" };
54845
55154
  } catch (err) {
54846
55155
  logger.warn("[QuorumLoop] A1d resolver probe: ladder select failed \u2014 treating as no tier", {
54847
- err: err.message
55156
+ ...errorShapeOnly(err)
54848
55157
  });
54849
55158
  }
54850
55159
  return (this.deps.resolverProbeDeps?.trustedContainer ?? isTrustedContainerBoundary)() ? { establishable: !0, via: "trusted_container" } : { establishable: !1 };
@@ -54975,8 +55284,8 @@ var QuorumLoop = class _QuorumLoop {
54975
55284
  taskId: tid,
54976
55285
  exitCode: exit.exitCode,
54977
55286
  runtimeMs: exit.runtimeMs,
54978
- stdoutTail: handle.stdout().slice(-2e3),
54979
- stderrExcerpt: handle.stderr().slice(0, 600)
55287
+ stdoutShape: outputShapeOnly(handle.stdout()),
55288
+ stderrShape: outputShapeOnly(handle.stderr())
54980
55289
  }), exit.failureClass !== null)
54981
55290
  throw new Error(`agentic resolver implementor failed: ${exit.failureClass}`);
54982
55291
  }
@@ -55039,6 +55348,7 @@ var QuorumLoop = class _QuorumLoop {
55039
55348
  });
55040
55349
  continue;
55041
55350
  }
55351
+ let candidateError;
55042
55352
  try {
55043
55353
  let substrate = confinement.mode === "substrate" ? confinement.handle : void 0, spec = {
55044
55354
  seat_id: 0,
@@ -55061,14 +55371,27 @@ var QuorumLoop = class _QuorumLoop {
55061
55371
  ...pass ? {} : { reason: `final-tip Tier-2 verdict: ${verdict.verdict}` }
55062
55372
  };
55063
55373
  } catch (e) {
55064
- lastFailure = `final-tip Tier-2 reviewer error (${agent}): ${e.message}`, logger.warn("[QuorumLoop] final-tip Tier-2 candidate failed \u2014 trying next", {
55374
+ candidateError = e, lastFailure = `final-tip Tier-2 reviewer ${agent} failed to produce a verdict`, logger.warn("[QuorumLoop] final-tip Tier-2 candidate failed \u2014 trying next", {
55065
55375
  ownerTaskId: args.ownerTaskId,
55066
55376
  agent,
55067
- err: e.message.slice(0, 300)
55377
+ ...errorShapeOnly(e)
55068
55378
  });
55069
55379
  } finally {
55070
- await confinement.teardown().catch(() => {
55071
- });
55380
+ try {
55381
+ await confinement.teardown();
55382
+ } catch (cleanupError) {
55383
+ throw logger.warn("[QuorumLoop] final-tip Tier-2 substrate cleanup failed \u2014 failing closed", {
55384
+ ownerTaskId: args.ownerTaskId,
55385
+ agent,
55386
+ ...errorShapeOnly(cleanupError)
55387
+ }), candidateError !== void 0 ? Object.assign(
55388
+ new Error("final-tip Tier-2 evaluation failed and substrate cleanup also failed"),
55389
+ { causes: [candidateError, cleanupError] }
55390
+ ) : Object.assign(
55391
+ new Error("final-tip Tier-2 substrate cleanup failed"),
55392
+ { cause: cleanupError }
55393
+ );
55394
+ }
55072
55395
  }
55073
55396
  }
55074
55397
  return {
@@ -65187,7 +65510,7 @@ function buildSanitizedBaseEnv(input) {
65187
65510
  let v = safeSource[key];
65188
65511
  typeof v == "string" && v.length > 0 && (env[key] = v);
65189
65512
  }
65190
- return input.provider === "anthropic" ? env.ANTHROPIC_BASE_URL = input.sandboxBrokerUrl : env.OPENAI_BASE_URL = `${input.sandboxBrokerUrl.replace(/\/+$/, "")}/v1`, input.provider === "anthropic" ? (env.CLAUDE_CONFIG_DIR = input.sandboxBootstrapDir, env.CLAUDE_CODE_TMPDIR = input.sandboxBootstrapDir) : env.CODEX_HOME = input.sandboxBootstrapDir, env;
65513
+ return input.provider === "anthropic" ? env.ANTHROPIC_BASE_URL = input.sandboxBrokerUrl : env.OPENAI_BASE_URL = `${input.sandboxBrokerUrl.replace(/\/+$/, "")}/v1`, input.provider === "anthropic" ? (env.CLAUDE_CONFIG_DIR = input.sandboxBootstrapDir, env.CLAUDE_CODE_TMPDIR = input.claudeScratchDir ?? input.sandboxBootstrapDir) : env.CODEX_HOME = input.sandboxBootstrapDir, env;
65191
65514
  }
65192
65515
  var FORBIDDEN_KEY_PATTERNS = [
65193
65516
  /^ANTHROPIC_API_KEY$/i,
@@ -65313,16 +65636,36 @@ async function engageSubstrate(input) {
65313
65636
  try {
65314
65637
  ({ hostBrokerAddr } = await broker.start());
65315
65638
  } catch (e) {
65316
- throw await broker.stop().catch(() => {
65317
- }), new Error(
65318
- `CP-7: broker failed to start \u2014 refusing to launch the implementor (fail-closed): ${e.message}`
65639
+ let primary = Object.assign(
65640
+ new Error("CP-7: broker failed to start \u2014 refusing to launch the implementor (fail-closed)"),
65641
+ { cause: e }
65319
65642
  );
65643
+ try {
65644
+ await broker.stop();
65645
+ } catch (cleanupError) {
65646
+ throw Object.assign(
65647
+ new Error("CP-7: broker start failed and cleanup also failed"),
65648
+ { causes: [primary, cleanupError] }
65649
+ );
65650
+ }
65651
+ throw primary;
65320
65652
  }
65321
65653
  let initialTokenObj = broker.currentBrokerToken();
65322
- if (!initialTokenObj)
65323
- throw await broker.stop().catch(() => {
65324
- }), new Error("CP-7: broker minted no token \u2014 refusing to launch (fail-closed)");
65325
- let initialToken = initialTokenObj.value, sandboxBrokerUrl = selection.tier === "docker" ? `http://127.0.0.1:${RELAY_PORT}` : `http://${hostBrokerAddr}`, sandboxHome = selection.tier === "docker" ? CONTAINER_WORKDIR : input.workdir, sandboxBootstrapMount = selection.tier === "docker" ? SANDBOX_BOOTSTRAP_DIR : null, sandboxConfigMount = selection.tier === "docker" ? SANDBOX_AGENT_CONFIG_DIR : null, bootstrap, handle;
65654
+ if (!initialTokenObj) {
65655
+ let primary = new Error(
65656
+ "CP-7: broker minted no token \u2014 refusing to launch (fail-closed)"
65657
+ );
65658
+ try {
65659
+ await broker.stop();
65660
+ } catch (cleanupError) {
65661
+ throw Object.assign(
65662
+ new Error("CP-7: broker minted no token and cleanup also failed"),
65663
+ { causes: [primary, cleanupError] }
65664
+ );
65665
+ }
65666
+ throw primary;
65667
+ }
65668
+ let initialToken = initialTokenObj.value, sandboxBrokerUrl = selection.tier === "docker" ? `http://127.0.0.1:${RELAY_PORT}` : `http://${hostBrokerAddr}`, sandboxHome = selection.tier === "docker" ? CONTAINER_WORKDIR : input.workdir, sandboxBootstrapMount = selection.tier === "docker" ? SANDBOX_BOOTSTRAP_DIR : null, sandboxConfigMount = selection.tier === "docker" ? SANDBOX_AGENT_CONFIG_DIR : null, bootstrap, handle, activeTeardown;
65326
65669
  try {
65327
65670
  bootstrap = await ApiKeyBootstrap.create({
65328
65671
  provider,
@@ -65333,6 +65676,7 @@ async function engageSubstrate(input) {
65333
65676
  sandboxConfigDir: sandboxConfigMount ?? SANDBOX_AGENT_CONFIG_DIR
65334
65677
  } : { sandboxDirEqualsHostDir: !0, configDirEqualsHostDir: !0 },
65335
65678
  hostRoot: input.bootstrapHostRoot,
65679
+ ...selection.tier === "sandbox_exec" && provider === "anthropic" ? { claudeScratchHostRoot: input.claudeScratchHostRoot ?? "/tmp" } : {},
65336
65680
  // M-2 — fail closed if the bootstrap dir lands inside the agent's rw
65337
65681
  // workdir mount (the token would leak through it).
65338
65682
  workdir: input.workdir,
@@ -65344,11 +65688,12 @@ async function engageSubstrate(input) {
65344
65688
  ), agentConfigSpec = sandboxConfigMount !== null ? { hostDir: bootstrap.configHostDir, sandboxDir: sandboxConfigMount } : (
65345
65689
  // A5: sandbox fs == host fs → config path equals the host dir.
65346
65690
  { hostDir: bootstrap.configHostDir, sandboxDir: bootstrap.configHostDir }
65347
- ), sanitizedBase = buildSanitizedBaseEnv({
65691
+ ), agentScratchSpec = bootstrap.claudeScratchSpec, sanitizedBase = buildSanitizedBaseEnv({
65348
65692
  provider,
65349
65693
  sandboxBrokerUrl,
65350
65694
  sandboxHome,
65351
65695
  sandboxBootstrapDir: agentConfigSpec.sandboxDir,
65696
+ ...agentScratchSpec !== null ? { claudeScratchDir: agentScratchSpec.sandboxDir } : {},
65352
65697
  safeSource: input.localeSource
65353
65698
  });
65354
65699
  assertNoAmbientCreds(sanitizedBase);
@@ -65362,6 +65707,7 @@ async function engageSubstrate(input) {
65362
65707
  sanitizedEnv: finalEnv,
65363
65708
  agentBootstrap: agentBootstrapSpec,
65364
65709
  agentConfig: agentConfigSpec,
65710
+ ...agentScratchSpec !== null ? { agentScratch: agentScratchSpec } : {},
65365
65711
  // CP-7 W3 — Stage-2 r1 HIGH. The resolved audit dir → A5 trailing deny.
65366
65712
  // Undefined when a test injects its own in-memory sink (no real tree).
65367
65713
  ...resolvedAuditDir !== null ? { auditDir: resolvedAuditDir } : {}
@@ -65370,12 +65716,30 @@ async function engageSubstrate(input) {
65370
65716
  `[CP-7] Substrate engaged (tier=${selection.tier}, egress=${handle.egressFidelity}) \u2014 agent is creditless, broker holds the key`,
65371
65717
  { taskId: input.taskId, agent: input.agentKind }
65372
65718
  );
65373
- let liveBootstrap = bootstrap, liveHandle = handle, liveBroker = broker, refreshTimer = null, teardown = async () => {
65374
- refreshTimer && (clearInterval(refreshTimer), refreshTimer = null), await liveHandle.teardown().catch(() => {
65375
- }), await liveBroker.stop().catch(() => {
65376
- }), await liveBootstrap.destroy().catch(() => {
65377
- });
65378
- }, launchId = `launch-${input.taskId}-${Date.now()}`, denialReason = liveHandle.egressFidelity === "strict" ? "structural_deny_network_none" : "coarse_loopback_only", postureRes;
65719
+ let liveBootstrap = bootstrap, liveHandle = handle, liveBroker = broker, refreshTimer = null, teardownPromise, teardown = () => teardownPromise || (teardownPromise = (async () => {
65720
+ refreshTimer && (clearInterval(refreshTimer), refreshTimer = null);
65721
+ let failures = [];
65722
+ for (let cleanup of [
65723
+ () => liveHandle.teardown(),
65724
+ () => liveBroker.stop(),
65725
+ () => liveBootstrap.destroy()
65726
+ ])
65727
+ try {
65728
+ await cleanup();
65729
+ } catch (error) {
65730
+ failures.push(error);
65731
+ }
65732
+ if (failures.length > 0)
65733
+ throw logger.warn("[CP-7] substrate cleanup failed \u2014 task cannot pass", {
65734
+ taskId: input.taskId,
65735
+ failureCount: failures.length,
65736
+ ...errorShapeOnly(failures[0])
65737
+ }), failures.length === 1 ? failures[0] : Object.assign(new Error("CP-7 substrate cleanup failed"), {
65738
+ causes: failures
65739
+ });
65740
+ })(), teardownPromise);
65741
+ activeTeardown = teardown;
65742
+ let launchId = `launch-${input.taskId}-${Date.now()}`, denialReason = liveHandle.egressFidelity === "strict" ? "structural_deny_network_none" : "coarse_loopback_only", postureRes;
65379
65743
  try {
65380
65744
  postureRes = await auditSink.emit("egress_denied", {
65381
65745
  destination: "*",
@@ -65384,13 +65748,16 @@ async function engageSubstrate(input) {
65384
65748
  caller_event_id: launchId
65385
65749
  });
65386
65750
  } catch (e) {
65387
- throw await teardown(), new Error(
65388
- `CP-7 W3: egress-posture audit emit THREW \u2014 refusing to launch the creditless agent (audit-before-effect fail-closed): ${e.message}`
65751
+ throw Object.assign(
65752
+ new Error(
65753
+ "CP-7 W3: egress-posture audit emit failed \u2014 refusing to launch the creditless agent (audit-before-effect fail-closed)"
65754
+ ),
65755
+ { cause: e }
65389
65756
  );
65390
65757
  }
65391
65758
  if (!("ack" in postureRes))
65392
- throw await teardown(), new Error(
65393
- `CP-7 W3: egress-posture audit nack'd ("${postureRes.nack}") \u2014 refusing to launch the creditless agent (no durable egress posture \u2192 no agent).`
65759
+ throw new Error(
65760
+ "CP-7 W3: egress-posture audit was rejected \u2014 refusing to launch the creditless agent (no durable egress posture \u2192 no agent)"
65394
65761
  );
65395
65762
  let refreshIntervalMs = input.tokenRefreshIntervalMs ?? 600 * 1e3, inFlightRefresh = null, doRefreshOnce = async () => {
65396
65763
  if (!(typeof liveBroker.rotateBrokerToken == "function" && typeof liveBroker.commitBrokerTokenRotation == "function" && typeof liveBroker.rollbackBrokerTokenRotation == "function")) {
@@ -65403,10 +65770,23 @@ async function engageSubstrate(input) {
65403
65770
  try {
65404
65771
  await liveBootstrap.refresh(rotated.token.value), liveBroker.commitBrokerTokenRotation(rotated);
65405
65772
  } catch (e) {
65406
- throw liveBroker.rollbackBrokerTokenRotation(rotated), logger.warn(
65773
+ liveBroker.rollbackBrokerTokenRotation(rotated), logger.warn(
65407
65774
  "[CP-7] broker-token refresh write FAILED \u2014 rolled back to the prior token and tearing down (fail-closed)",
65408
- { taskId: input.taskId, err: e.message }
65409
- ), await teardown(), e instanceof Error ? e : new Error(String(e));
65775
+ { taskId: input.taskId, ...errorShapeOnly(e) }
65776
+ );
65777
+ let refreshFailure = Object.assign(
65778
+ new Error("CP-7 broker-token refresh failed"),
65779
+ { cause: e }
65780
+ );
65781
+ try {
65782
+ await teardown();
65783
+ } catch (cleanupError) {
65784
+ throw Object.assign(
65785
+ new Error("CP-7 broker-token refresh failed and cleanup also failed"),
65786
+ { causes: [refreshFailure, cleanupError] }
65787
+ );
65788
+ }
65789
+ throw refreshFailure;
65410
65790
  }
65411
65791
  }, doRefresh = async () => {
65412
65792
  let next = (inFlightRefresh ?? Promise.resolve()).catch(() => {
@@ -65433,10 +65813,32 @@ async function engageSubstrate(input) {
65433
65813
  teardown
65434
65814
  };
65435
65815
  } catch (e) {
65436
- throw await handle?.teardown().catch(() => {
65437
- }), await bootstrap?.destroy().catch(() => {
65438
- }), await broker.stop().catch(() => {
65439
- }), e instanceof Error ? e : new Error(String(e));
65816
+ let primary = e instanceof Error ? e : new Error("CP-7 substrate launch failed"), cleanupFailures = [];
65817
+ if (activeTeardown)
65818
+ try {
65819
+ await activeTeardown();
65820
+ } catch (cleanupError) {
65821
+ cleanupFailures.push(cleanupError);
65822
+ }
65823
+ else
65824
+ for (let cleanup of [
65825
+ ...handle ? [() => handle.teardown()] : [],
65826
+ ...bootstrap ? [() => bootstrap.destroy()] : [],
65827
+ () => broker.stop()
65828
+ ])
65829
+ try {
65830
+ await cleanup();
65831
+ } catch (cleanupError) {
65832
+ cleanupFailures.push(cleanupError);
65833
+ }
65834
+ throw cleanupFailures.length > 0 ? (logger.warn("[CP-7] failed launch also failed cleanup", {
65835
+ taskId: input.taskId,
65836
+ failureCount: cleanupFailures.length,
65837
+ ...errorShapeOnly(cleanupFailures[0])
65838
+ }), Object.assign(
65839
+ new Error("CP-7 substrate launch failed and cleanup also failed"),
65840
+ { causes: [primary, ...cleanupFailures] }
65841
+ )) : primary;
65440
65842
  }
65441
65843
  }
65442
65844
  var RESOLVER_AGENT_STATE_DIR_SEGMENTS = {