open-walnut 0.6.11 → 0.6.12

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 (35) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli-fast.js +4 -4
  3. package/dist/cli.js +52905 -52312
  4. package/dist/daemon-binaries/acp-daemon-core.cjs +9 -9
  5. package/dist/daemon-binaries/acp-worker.js +7 -7
  6. package/dist/daemon-binaries/acp-worker.js.version +1 -1
  7. package/dist/daemon-binaries/dtach-linux-x64 +0 -0
  8. package/dist/data/skills/walnut-rich-output/SKILL.md +2 -0
  9. package/dist/integrations/mail/index.js +2 -2
  10. package/dist/web/server.js +651 -58
  11. package/dist/web/static/assets/{OfficePreview-DBB8usOU.js → OfficePreview-BRP7aPKQ.js} +2 -2
  12. package/dist/web/static/assets/{docx-preview-DO-M-r7C.js → docx-preview-gD4iKJ2e.js} +1 -1
  13. package/dist/web/static/assets/{index-CDsfHTKk.js → index-B6JtEMq9.js} +1 -1
  14. package/dist/web/static/assets/{index-BH5SrTkh.js → index-B9wMLqkB.js} +1 -1
  15. package/dist/web/static/assets/{index-DrdLtoY7.js → index-BRHuQKnl.js} +1 -1
  16. package/dist/web/static/assets/{index-CJ8rBjS7.js → index-BVcrg2AK.js} +1 -1
  17. package/dist/web/static/assets/{index-Cf1FGt6z.js → index-CYk1zlE1.js} +1 -1
  18. package/dist/web/static/assets/{index-CwQ1J8zo.js → index-Cn8ACC-j.js} +423 -415
  19. package/dist/web/static/assets/{index-BiuGmgtx.js → index-CrRDEOok.js} +1 -1
  20. package/dist/web/static/assets/{index-CDJk2V31.js → index-D3pjiTcv.js} +1 -1
  21. package/dist/web/static/assets/{index-geB69Fz-.css → index-DEgRybbe.css} +1 -1
  22. package/dist/web/static/assets/{index-B0owO7hE.js → index-DZ_5YKaM.js} +1 -1
  23. package/dist/web/static/assets/{index-v_Hru-Ce.js → index-Dd57HhSU.js} +1 -1
  24. package/dist/web/static/assets/{index-ShZZkmTY.js → index-Din30GxM.js} +1 -1
  25. package/dist/web/static/assets/{index-Cr-efJVK.js → index-DxakwTTa.js} +1 -1
  26. package/dist/web/static/assets/{index-CCA160yl.js → index-DyOtvfYi.js} +1 -1
  27. package/dist/web/static/assets/{index-DfAlsi9y.js → index-QQY1_2Fb.js} +1 -1
  28. package/dist/web/static/assets/{index-2Y51Cotd.js → index-lCNb4QzD.js} +1 -1
  29. package/dist/web/static/assets/{index-D22ZkaJd.js → index-rSSibhKo.js} +1 -1
  30. package/dist/web/static/assets/{index-htULZnWT.js → index-tKpaCzaP.js} +1 -1
  31. package/dist/web/static/assets/{index-Cfz0y2sX.js → index-weeQuzTe.js} +1 -1
  32. package/dist/web/static/assets/{jszip.min-Cu9P_1RA.js → jszip.min-CSpmuVKF.js} +1 -1
  33. package/dist/web/static/assets/{pptx-preview.es-O80sMMc7.js → pptx-preview.es-5Knb-k-O.js} +1 -1
  34. package/dist/web/static/index.html +2 -2
  35. package/package.json +1 -1
@@ -6020,6 +6020,7 @@ __export(git_sync_exports, {
6020
6020
  isRepoAsync: () => isRepoAsync,
6021
6021
  isSyncPaused: () => isSyncPaused,
6022
6022
  localContainsRemoteTipForTest: () => localContainsRemoteTipForTest,
6023
+ measureRepoSize: () => measureRepoSize,
6023
6024
  noteNetworkFailure: () => noteNetworkFailure,
6024
6025
  noteNetworkSuccess: () => noteNetworkSuccess,
6025
6026
  parseCloudRemoteUrl: () => parseCloudRemoteUrl,
@@ -6032,6 +6033,7 @@ __export(git_sync_exports, {
6032
6033
  setCompactionInProgress: () => setCompactionInProgress,
6033
6034
  setDiskPullOnly: () => setDiskPullOnly,
6034
6035
  setRemote: () => setRemote,
6036
+ setRewriteAdoptedListener: () => setRewriteAdoptedListener,
6035
6037
  stageAllArgs: () => stageAllArgs,
6036
6038
  stageAllForCommit: () => stageAllForCommit,
6037
6039
  sweepDeadFetchPacks: () => sweepDeadFetchPacks,
@@ -7080,6 +7082,10 @@ async function lwwMerge(branch, remoteBefore = null) {
7080
7082
  reason: noMergeBase ? "no-merge-base" : "upstream-force-moved",
7081
7083
  localOnlyCommitsParked: localOnly
7082
7084
  });
7085
+ try {
7086
+ rewriteAdoptedListener?.();
7087
+ } catch {
7088
+ }
7083
7089
  return { merged: true, conflicts: 0 };
7084
7090
  }
7085
7091
  let mergeError = null;
@@ -7311,6 +7317,9 @@ function checkRepoSize(repoDir = WALNUT_HOME) {
7311
7317
  const now = Date.now();
7312
7318
  if (now - lastRepoSizeCheck < REPO_SIZE_CHECK_INTERVAL_MS) return null;
7313
7319
  lastRepoSizeCheck = now;
7320
+ return measureRepoSize(repoDir, now);
7321
+ }
7322
+ function measureRepoSize(repoDir = WALNUT_HOME, now = Date.now()) {
7314
7323
  const before = measurePackDir(repoDir);
7315
7324
  let sweptBytes = 0;
7316
7325
  let debrisBytes = before.debrisBytes;
@@ -7325,6 +7334,9 @@ function checkRepoSize(repoDir = WALNUT_HOME) {
7325
7334
  const warning2 = `data repo .git has grown to ${gbText(liveBytes)} of live packs (threshold 3GB)${debrisNote}: check for backup-* branches pinning old history, large tracked files, or tmp_pack_* debris; see open-walnut logs -s git`;
7326
7335
  return { liveBytes, debrisBytes, sweptBytes, over, warning: warning2 };
7327
7336
  }
7337
+ function setRewriteAdoptedListener(fn) {
7338
+ rewriteAdoptedListener = fn;
7339
+ }
7328
7340
  function resetRepoSizeCheckForTest() {
7329
7341
  lastRepoSizeCheck = 0;
7330
7342
  }
@@ -7466,7 +7478,7 @@ function getSyncStatus() {
7466
7478
  diskPullOnly
7467
7479
  };
7468
7480
  }
7469
- var LOCAL_TIMEOUT, NETWORK_TIMEOUT, PULL_TIMEOUT, FETCH_TIMEOUT, FETCH_STREAK_FOR_RECOVERY, FETCH_RECOVERY_TIMEOUT, FETCH_DEBRIS_MIN_AGE_MS, KILL_GRACE_MS, compactionInProgress, SYNC_PAUSE_MARKER, lastPauseLogAt, NETWORK_GIT_RE, execAsync, credentialGuardCache, SYNC_PROBE_TTL_MS, gitAvailableProbe, repoProbe, remoteProbe, GITIGNORE_CONTENT, CRITICAL_IGNORE_FILES, CRITICAL_IGNORE_DIRS, CRITICAL_IGNORE_LINES, CRITICAL_IGNORES, EXTRA_IGNORE_PATTERNS, CLOUD_NEVER_STAGE_DIRS, MASS_DIRTY_THRESHOLD, MASS_DIRTY_TRACKED_FRACTION, TRACKED_COUNT_TTL_MS, trackedCountCache, FAILURE_STREAK_FOR_PULL_FIRST, RESURRECTION_TRIP_COUNT, RESURRECTION_LOG_DEPTH, guard, SAFE_MODE_RELOG_MS, lastSafeModeRefusalLog, diskPullOnly, PUSH_FAILURES_FOR_BUNDLE, pushFailureStreak, localContainsRemoteTipForTest, ORPHAN_SURGERY_MIN_AGE_MS, SURGERY_RELOG_MS, lastSurgeryWarnLog, MAX_MARKER_SCAN_FILES, syncInflight, pullInflight, REPO_SIZE_WARN_BYTES, REPO_SIZE_CHECK_INTERVAL_MS, REPO_SIZE_RECOVERY_KEY, lastRepoSizeCheck, gbText, lastSyncCache, LAST_SYNC_CACHE_MS;
7481
+ var LOCAL_TIMEOUT, NETWORK_TIMEOUT, PULL_TIMEOUT, FETCH_TIMEOUT, FETCH_STREAK_FOR_RECOVERY, FETCH_RECOVERY_TIMEOUT, FETCH_DEBRIS_MIN_AGE_MS, KILL_GRACE_MS, compactionInProgress, SYNC_PAUSE_MARKER, lastPauseLogAt, NETWORK_GIT_RE, execAsync, credentialGuardCache, SYNC_PROBE_TTL_MS, gitAvailableProbe, repoProbe, remoteProbe, GITIGNORE_CONTENT, CRITICAL_IGNORE_FILES, CRITICAL_IGNORE_DIRS, CRITICAL_IGNORE_LINES, CRITICAL_IGNORES, EXTRA_IGNORE_PATTERNS, CLOUD_NEVER_STAGE_DIRS, MASS_DIRTY_THRESHOLD, MASS_DIRTY_TRACKED_FRACTION, TRACKED_COUNT_TTL_MS, trackedCountCache, FAILURE_STREAK_FOR_PULL_FIRST, RESURRECTION_TRIP_COUNT, RESURRECTION_LOG_DEPTH, guard, SAFE_MODE_RELOG_MS, lastSafeModeRefusalLog, diskPullOnly, PUSH_FAILURES_FOR_BUNDLE, pushFailureStreak, localContainsRemoteTipForTest, ORPHAN_SURGERY_MIN_AGE_MS, SURGERY_RELOG_MS, lastSurgeryWarnLog, MAX_MARKER_SCAN_FILES, syncInflight, pullInflight, REPO_SIZE_WARN_BYTES, REPO_SIZE_CHECK_INTERVAL_MS, REPO_SIZE_RECOVERY_KEY, lastRepoSizeCheck, gbText, rewriteAdoptedListener, lastSyncCache, LAST_SYNC_CACHE_MS;
7470
7482
  var init_git_sync = __esm({
7471
7483
  "src/integrations/git-sync.ts"() {
7472
7484
  "use strict";
@@ -7757,6 +7769,7 @@ sessions.json.*
7757
7769
  REPO_SIZE_RECOVERY_KEY = "git:repo-size";
7758
7770
  lastRepoSizeCheck = 0;
7759
7771
  gbText = (bytes3) => `${(bytes3 / 1024 / 1024 / 1024).toFixed(1)}GB`;
7772
+ rewriteAdoptedListener = null;
7760
7773
  lastSyncCache = null;
7761
7774
  LAST_SYNC_CACHE_MS = 3e4;
7762
7775
  }
@@ -9204,6 +9217,13 @@ function ensureSilenceSweep() {
9204
9217
  silenceSweepTimer = setInterval(() => {
9205
9218
  const now = Date.now();
9206
9219
  for (const conn of bridges.values()) {
9220
+ if (conn.stillValid && !conn.validating) {
9221
+ conn.validating = true;
9222
+ void credentialStillValid(conn.stillValid).then((valid) => {
9223
+ conn.validating = false;
9224
+ if (!valid && bridges.get(conn.hostAlias) === conn) revokeBridge(conn);
9225
+ });
9226
+ }
9207
9227
  if (now - conn.lastInbound > SILENCE_MS) {
9208
9228
  log.ws.warn("bridge: host silent \u2014 dropping", {
9209
9229
  hostAlias: conn.hostAlias,
@@ -9301,6 +9321,10 @@ function forwardJsonlLine(sessionId, line2) {
9301
9321
  }
9302
9322
  }
9303
9323
  function handleFrame(conn, raw) {
9324
+ if (conn.scope === "status-only" && Buffer.byteLength(raw) > 65536) {
9325
+ revokeBridge(conn);
9326
+ return;
9327
+ }
9304
9328
  conn.lastInbound = Date.now();
9305
9329
  let msg;
9306
9330
  try {
@@ -9308,6 +9332,7 @@ function handleFrame(conn, raw) {
9308
9332
  } catch {
9309
9333
  return;
9310
9334
  }
9335
+ if (!msg || typeof msg !== "object" || Array.isArray(msg) || msg.ev !== void 0 && typeof msg.ev !== "string") return;
9311
9336
  if (conn.hostAlias === PRIMARY_ALIAS && primaryBridgeActivityHandlers.size && Date.now() - primaryBridgeActivityAt >= 15e3) {
9312
9337
  primaryBridgeActivityAt = Date.now();
9313
9338
  for (const handler of primaryBridgeActivityHandlers) {
@@ -9318,6 +9343,10 @@ function handleFrame(conn, raw) {
9318
9343
  }
9319
9344
  }
9320
9345
  }
9346
+ if (conn.scope === "status-only" && msg.ev && msg.ev !== "bridge-ping") {
9347
+ revokeBridge(conn);
9348
+ return;
9349
+ }
9321
9350
  if (msg.ev === "chunk") {
9322
9351
  const whole = conn.chunks.accept(msg);
9323
9352
  if (whole !== null) handleFrame(conn, whole);
@@ -9408,7 +9437,57 @@ function addBridgeConnectedHandler(handler) {
9408
9437
  hostBridgeConnectedHandlers.add(handler);
9409
9438
  return () => hostBridgeConnectedHandlers.delete(handler);
9410
9439
  }
9440
+ async function credentialStillValid(check) {
9441
+ let timer10;
9442
+ try {
9443
+ return await Promise.race([
9444
+ check(),
9445
+ new Promise((resolve) => {
9446
+ timer10 = setTimeout(() => resolve(false), 5e3);
9447
+ })
9448
+ ]);
9449
+ } catch {
9450
+ return false;
9451
+ } finally {
9452
+ if (timer10) clearTimeout(timer10);
9453
+ }
9454
+ }
9455
+ function revokeBridge(conn) {
9456
+ conn.stats.initiator = "none";
9457
+ dropBridge(conn, "credential revoked");
9458
+ try {
9459
+ conn.ws.close(CLOSE_UNAUTHORIZED, "credential revoked");
9460
+ } catch {
9461
+ }
9462
+ }
9463
+ function watchBridgeRevocations() {
9464
+ stopRevokeWatch ??= onCredentialsRevoked((names2) => {
9465
+ const affected = [
9466
+ ...[...preHello].filter(([, waiting3]) => names2.includes(waiting3.deviceName)).map(([ws, waiting3]) => ({ ws, check: waiting3.stillValid })),
9467
+ ...[...bridges.values()].filter((conn) => names2.includes(conn.deviceName)).map((conn) => ({ ws: conn.ws, check: conn.stillValid }))
9468
+ ];
9469
+ for (const { ws, check } of affected) {
9470
+ const closeRevoked = (valid) => {
9471
+ if (valid) return;
9472
+ const waiting3 = preHello.get(ws);
9473
+ if (waiting3) {
9474
+ clearTimeout(waiting3.timer);
9475
+ preHello.delete(ws);
9476
+ }
9477
+ const conn = findConn(ws);
9478
+ if (conn) revokeBridge(conn);
9479
+ else try {
9480
+ ws.close(CLOSE_UNAUTHORIZED, "credential revoked");
9481
+ } catch {
9482
+ }
9483
+ };
9484
+ if (check) void credentialStillValid(check).then(closeRevoked);
9485
+ else closeRevoked(false);
9486
+ }
9487
+ });
9488
+ }
9411
9489
  function attachBridge(ws, deviceName, meta) {
9490
+ watchBridgeRevocations();
9412
9491
  ensureSilenceSweep();
9413
9492
  const stats3 = newBridgeWireStats(meta?.clientIp ?? null);
9414
9493
  const helloTimer = setTimeout(() => {
@@ -9419,22 +9498,67 @@ function attachBridge(ws, deviceName, meta) {
9419
9498
  } catch {
9420
9499
  }
9421
9500
  }, 1e4);
9422
- preHello.set(ws, { deviceName, timer: helloTimer, stats: stats3 });
9501
+ preHello.set(ws, { deviceName, order: ++bridgeOrder, timer: helloTimer, stats: stats3, stillValid: meta?.stillValid, scope: meta?.scope, queued: [], queuedBytes: 0 });
9423
9502
  ws.on("message", (data) => {
9424
9503
  const raw = data.toString();
9425
9504
  noteFrameIn(stats3, Buffer.isBuffer(data) ? data.length : Buffer.byteLength(raw));
9426
9505
  const waiting3 = preHello.get(ws);
9427
9506
  if (waiting3) {
9507
+ if (waiting3.scope === "status-only" && Buffer.byteLength(raw) > 65536) {
9508
+ clearTimeout(waiting3.timer);
9509
+ preHello.delete(ws);
9510
+ try {
9511
+ ws.close(1009, "status frame too large");
9512
+ } catch {
9513
+ }
9514
+ return;
9515
+ }
9516
+ if (waiting3.verifying) {
9517
+ waiting3.queuedBytes += Buffer.byteLength(raw);
9518
+ if (waiting3.queued.length >= 64 || waiting3.queuedBytes > 1048576) {
9519
+ clearTimeout(waiting3.timer);
9520
+ preHello.delete(ws);
9521
+ try {
9522
+ ws.close(1009, "hello buffer full");
9523
+ } catch {
9524
+ }
9525
+ } else {
9526
+ waiting3.queued.push(raw);
9527
+ }
9528
+ return;
9529
+ }
9428
9530
  let msg;
9429
9531
  try {
9430
9532
  msg = JSON.parse(raw);
9431
9533
  } catch {
9432
9534
  return;
9433
9535
  }
9536
+ if (!msg || typeof msg !== "object" || Array.isArray(msg)) return;
9434
9537
  if (msg.ev !== "hello" || typeof msg.hostAlias !== "string") return;
9435
- clearTimeout(waiting3.timer);
9436
- preHello.delete(ws);
9437
- registerBridge(ws, waiting3.deviceName, msg, waiting3.stats);
9538
+ const finishHello = (valid) => {
9539
+ if (preHello.get(ws) !== waiting3) return;
9540
+ clearTimeout(waiting3.timer);
9541
+ preHello.delete(ws);
9542
+ if (!valid || ws.readyState !== void 0 && ws.readyState !== 1) {
9543
+ try {
9544
+ ws.close(CLOSE_UNAUTHORIZED, "credential revoked");
9545
+ } catch {
9546
+ }
9547
+ return;
9548
+ }
9549
+ registerBridge(ws, waiting3.deviceName, msg, waiting3.stats, waiting3.stillValid, waiting3.order, waiting3.scope);
9550
+ const conn2 = findConn(ws);
9551
+ if (conn2) for (const queued of waiting3.queued) {
9552
+ if (findConn(ws) !== conn2) break;
9553
+ handleFrame(conn2, queued);
9554
+ }
9555
+ };
9556
+ if (waiting3.stillValid) {
9557
+ waiting3.verifying = true;
9558
+ void credentialStillValid(waiting3.stillValid).then(finishHello);
9559
+ } else {
9560
+ finishHello(true);
9561
+ }
9438
9562
  return;
9439
9563
  }
9440
9564
  const conn = findConn(ws);
@@ -9470,12 +9594,12 @@ function tokenBoundAlias(deviceName) {
9470
9594
  const alias = deviceName.startsWith("bridge-") ? deviceName.slice("bridge-".length) : deviceName;
9471
9595
  return alias === "local" ? "__local__" : alias;
9472
9596
  }
9473
- function registerBridge(ws, deviceName, hello, stats3) {
9597
+ function registerBridge(ws, deviceName, hello, stats3, stillValid, order, scope) {
9474
9598
  const hostAlias2 = hello.hostAlias;
9475
9599
  if (typeof hello.connId === "string") stats3.connId = hello.connId.slice(0, 80);
9476
9600
  stats3.hostAlias = hostAlias2.slice(0, 120);
9477
9601
  const boundAlias = tokenBoundAlias(deviceName);
9478
- if (hostAlias2 !== boundAlias) {
9602
+ if (hostAlias2 !== boundAlias || scope === "status-only" && hostAlias2 === PRIMARY_ALIAS) {
9479
9603
  log.ws.warn("bridge: hello hostAlias does not match token identity \u2014 rejecting", {
9480
9604
  claimedHostAlias: hostAlias2,
9481
9605
  tokenAlias: boundAlias,
@@ -9489,6 +9613,14 @@ function registerBridge(ws, deviceName, hello, stats3) {
9489
9613
  return;
9490
9614
  }
9491
9615
  const existing = bridges.get(hostAlias2);
9616
+ if (existing && existing.order > order) {
9617
+ stats3.initiator = "replaced";
9618
+ try {
9619
+ ws.close(CLOSE_REPLACED, "superseded");
9620
+ } catch {
9621
+ }
9622
+ return;
9623
+ }
9492
9624
  if (existing) {
9493
9625
  log.ws.info("bridge: replacing existing connection", { hostAlias: hostAlias2 });
9494
9626
  existing.stats.initiator = "replaced";
@@ -9502,6 +9634,9 @@ function registerBridge(ws, deviceName, hello, stats3) {
9502
9634
  ws,
9503
9635
  hostAlias: hostAlias2,
9504
9636
  deviceName,
9637
+ order,
9638
+ scope,
9639
+ stillValid,
9505
9640
  version: hello.version,
9506
9641
  instanceId: hello.instanceId,
9507
9642
  since: Date.now(),
@@ -9524,11 +9659,11 @@ function registerBridge(ws, deviceName, hello, stats3) {
9524
9659
  sids: Array.isArray(hello.sids) ? hello.sids.length : 0,
9525
9660
  connId: stats3.connId
9526
9661
  });
9527
- if (hello.uplink === 1) {
9662
+ if (scope !== "status-only" && hello.uplink === 1) {
9528
9663
  void bridgeRequest(hostAlias2, "bridge.peer", { chunkBytes: REPLICA_CHUNK_BYTES }).catch(() => {
9529
9664
  });
9530
9665
  }
9531
- void reattachInterestedSessions(conn);
9666
+ if (scope !== "status-only") void reattachInterestedSessions(conn);
9532
9667
  if (hostAlias2 === PRIMARY_ALIAS) {
9533
9668
  for (const handler of [primaryBridgeConnectedHandler, ...primaryBridgeConnectedHandlers]) {
9534
9669
  if (!handler) continue;
@@ -9646,8 +9781,8 @@ function waitForBridge(hostAlias2, timeoutMs2) {
9646
9781
  function lastBridgeLossAt(hostAlias2) {
9647
9782
  return bridgeLossAt.get(hostAlias2) ?? null;
9648
9783
  }
9649
- function bridgeHosts() {
9650
- return [...bridges.values()].map((c8) => ({
9784
+ function bridgeHosts(opts2 = {}) {
9785
+ return [...bridges.values()].filter((c8) => !opts2.executionOnly || c8.scope !== "status-only").map((c8) => ({
9651
9786
  hostAlias: c8.hostAlias,
9652
9787
  since: c8.since,
9653
9788
  version: c8.version
@@ -9656,6 +9791,11 @@ function bridgeHosts() {
9656
9791
  function bridgeRequest(hostAlias2, cmd, params = {}, timeoutMs2 = DEFAULT_TIMEOUT_MS) {
9657
9792
  const conn = bridges.get(hostAlias2);
9658
9793
  if (!conn) return Promise.reject(new BridgeOfflineError(hostAlias2));
9794
+ if (conn.scope === "status-only") {
9795
+ if (cmd !== "ping" && cmd !== "status") return Promise.reject(new Error("Status-only bridge refuses this command"));
9796
+ const allowed = cmd === "ping" ? ["nonce", "ackSeq"] : ["nonce"];
9797
+ if (Object.keys(params).some((key) => !allowed.includes(key))) return Promise.reject(new Error("Status-only bridge refuses these parameters"));
9798
+ }
9659
9799
  const id = ++conn.cmdCounter;
9660
9800
  const frame = JSON.stringify({ id, cmd, ...params });
9661
9801
  return new Promise((resolve, reject) => {
@@ -9722,8 +9862,10 @@ function closeAllBridges() {
9722
9862
  clearInterval(silenceSweepTimer);
9723
9863
  silenceSweepTimer = null;
9724
9864
  }
9865
+ stopRevokeWatch?.();
9866
+ stopRevokeWatch = null;
9725
9867
  }
9726
- var PRIMARY_ALIAS, CLOSE_REPLACED, CLOSE_UNAUTHORIZED, DEFAULT_TIMEOUT_MS, SILENCE_MS, SILENCE_SWEEP_MS, bridges, preHello, hostInterest, bridgeWaiters, bridgeLossAt, silenceSweepTimer, lastCliState, mobileEventHandler, gatewayRequestHandler, primaryBridgeConnectedHandler, primaryBridgeConnectedHandlers, primaryBridgeActivityHandlers, primaryBridgeActivityAt, hostBridgeConnectedHandlers, BridgeOfflineError, BridgeAttachRefusedError;
9868
+ var PRIMARY_ALIAS, CLOSE_REPLACED, CLOSE_UNAUTHORIZED, DEFAULT_TIMEOUT_MS, SILENCE_MS, SILENCE_SWEEP_MS, bridges, preHello, bridgeOrder, hostInterest, bridgeWaiters, bridgeLossAt, silenceSweepTimer, lastCliState, mobileEventHandler, gatewayRequestHandler, primaryBridgeConnectedHandler, primaryBridgeConnectedHandlers, primaryBridgeActivityHandlers, primaryBridgeActivityAt, hostBridgeConnectedHandlers, stopRevokeWatch, BridgeOfflineError, BridgeAttachRefusedError;
9727
9869
  var init_bridge_registry = __esm({
9728
9870
  "src/web/ws/bridge-registry.ts"() {
9729
9871
  "use strict";
@@ -9735,6 +9877,7 @@ var init_bridge_registry = __esm({
9735
9877
  init_bridge_read_history();
9736
9878
  init_bridge_wire();
9737
9879
  init_event_loop_monitor();
9880
+ init_device_auth();
9738
9881
  init_bridge_presence();
9739
9882
  PRIMARY_ALIAS = "__local__";
9740
9883
  CLOSE_REPLACED = 4e3;
@@ -9744,6 +9887,7 @@ var init_bridge_registry = __esm({
9744
9887
  SILENCE_SWEEP_MS = 3e4;
9745
9888
  bridges = /* @__PURE__ */ new Map();
9746
9889
  preHello = /* @__PURE__ */ new Map();
9890
+ bridgeOrder = 0;
9747
9891
  hostInterest = /* @__PURE__ */ new Map();
9748
9892
  bridgeWaiters = /* @__PURE__ */ new Map();
9749
9893
  bridgeLossAt = /* @__PURE__ */ new Map();
@@ -9756,6 +9900,7 @@ var init_bridge_registry = __esm({
9756
9900
  primaryBridgeActivityHandlers = /* @__PURE__ */ new Set();
9757
9901
  primaryBridgeActivityAt = 0;
9758
9902
  hostBridgeConnectedHandlers = /* @__PURE__ */ new Set();
9903
+ stopRevokeWatch = null;
9759
9904
  BridgeOfflineError = class extends Error {
9760
9905
  constructor(hostAlias2) {
9761
9906
  super(`No live bridge for host: ${hostAlias2}`);
@@ -11584,7 +11729,7 @@ async function rotateDevice(name, opts2) {
11584
11729
  if (plan && (plan.push.length > 0 || plan.twins.length > 0)) trackRevokeWork(finishRevoke(plan, `device ${name} rotated`));
11585
11730
  return { name: out.name, token: out.token, createdAt: out.createdAt, replaced: out.replaced };
11586
11731
  }
11587
- async function verifyDeviceToken(token) {
11732
+ async function verifyDeviceToken(token, opts2 = {}) {
11588
11733
  if (!token || typeof token !== "string") return null;
11589
11734
  const { devices, revoked } = await readRegistryAndRevoked();
11590
11735
  const matched = recordForToken(devices, token);
@@ -11595,7 +11740,7 @@ async function verifyDeviceToken(token) {
11595
11740
  }
11596
11741
  const now = Date.now();
11597
11742
  const lastWrite = lastUsedWriteAt.get(matched.name) ?? 0;
11598
- if (now - lastWrite >= LAST_USED_WRITE_THROTTLE_MS) {
11743
+ if (opts2.touch !== false && now - lastWrite >= LAST_USED_WRITE_THROTTLE_MS) {
11599
11744
  lastUsedWriteAt.set(matched.name, now);
11600
11745
  const name = matched.name;
11601
11746
  try {
@@ -11612,7 +11757,7 @@ async function verifyDeviceToken(token) {
11612
11757
  });
11613
11758
  }
11614
11759
  }
11615
- return matched.kind ? { name: matched.name, kind: matched.kind } : { name: matched.name };
11760
+ return matched.kind ? { name: matched.name, kind: matched.kind, ...matched.bridgeScope === "status-only" ? { bridgeScope: matched.bridgeScope } : {} } : { name: matched.name };
11616
11761
  }
11617
11762
  async function revokeWith(name, opts2, decide) {
11618
11763
  const intent = { plan: null };
@@ -11792,7 +11937,7 @@ function requestIp(req) {
11792
11937
  }
11793
11938
  async function validateBearerCredential(token) {
11794
11939
  const device = await verifyDeviceToken(token);
11795
- if (device) return { name: device.name, kind: device.kind === "machine" ? "machine" : "device" };
11940
+ if (device) return { name: device.name, kind: device.kind === "machine" ? "machine" : "device", ...device.bridgeScope ? { bridgeScope: device.bridgeScope } : {} };
11796
11941
  const keyName = await validateApiKey(token);
11797
11942
  if (keyName) return { name: keyName, kind: "api_key" };
11798
11943
  return null;
@@ -25710,6 +25855,16 @@ async function resumeForTrigger(home, sid, text, messageId, taskId) {
25710
25855
  if (!current()) throw new Error('a stop request superseded this start');
25711
25856
  return startSessionProcess({ cmd: 'start', sid: sid, args: args, cwd: spawn.cwd, message: text, resume: true, mode: spawn.mode, origin: { home: home, task: taskId } }, undefined, current);
25712
25857
  });
25858
+ // The turn-start marker the server writes after its own resume start
25859
+ // (writeSyntheticUserEvent): the CLI never echoes its stdin, so without it
25860
+ // the stream shows no turn start and the fire never appears in the chat.
25861
+ try {
25862
+ var started = sessions.get(sid);
25863
+ if (started) appendUserMarkerLine(sid, started, text, messageId, false);
25864
+ else logMsg('warn', 'trigger: no turn-start marker for a host resume', { sid: sid, messageId: messageId });
25865
+ } catch (err) {
25866
+ logMsg('warn', 'trigger: no turn-start marker for a host resume', { sid: sid, messageId: messageId, error: err && err.message });
25867
+ }
25713
25868
  logMsg('info', 'trigger: resumed a stopped session on this host', { sid: sid, taskId: taskId, messageId: messageId, mode: spawn.mode });
25714
25869
  return { ok: true };
25715
25870
  } catch (err) {
@@ -37916,6 +38071,9 @@ function refuse2(d8) {
37916
38071
  throw new MachineCredentialRefused(d8.status, d8.code, d8.error);
37917
38072
  }
37918
38073
  async function mintMachineCredential(name, opts2) {
38074
+ if (opts2.bridgeScope === "status-only" && name === PRIMARY_MACHINE_CREDENTIAL) {
38075
+ throw new MachineCredentialRefused(400, "invalid_bridge_scope", "The primary credential cannot be status-only");
38076
+ }
37919
38077
  const out = await updateAuth((auth) => {
37920
38078
  const decision = machineCredentialDecision(auth, { by: opts2.by, name, action: opts2.replace ? "replace" : "mint", proof: opts2.proof });
37921
38079
  if (!decision.ok) refuse2(decision);
@@ -37924,6 +38082,8 @@ async function mintMachineCredential(name, opts2) {
37924
38082
  const me = applyDecision(auth, decision);
37925
38083
  if (!me.tunnelDaemon) me.tunnelDaemon = { key: me.id };
37926
38084
  const { record: record2, token } = newMachineRecord(name, me.id, existing ? existing.daemonKey : daemonKeyFor(me));
38085
+ const bridgeScope = opts2.bridgeScope ?? existing?.bridgeScope;
38086
+ if (bridgeScope) record2.bridgeScope = bridgeScope;
37927
38087
  auth.devices = [...auth.devices.filter((d8) => d8.name !== name), record2];
37928
38088
  return { persist: true, result: { name, token, createdAt: record2.createdAt, replaced: existing !== void 0, owner: me.name, recorded: decision.record?.via ?? null } };
37929
38089
  });
@@ -38002,7 +38162,7 @@ __export(daemon_tunnel_exports, {
38002
38162
  import { WebSocket as WebSocket3, WebSocketServer } from "ws";
38003
38163
  function tunnelAdmission(cred, execEnabled2) {
38004
38164
  if (!cred) return { ok: false, status: 401, refusal: "unauthorized", message: "a machine credential is required" };
38005
- if (cred.kind !== "machine" || cred.name !== PRIMARY_MACHINE_DEVICE) {
38165
+ if (cred.kind !== "machine" || cred.name !== PRIMARY_MACHINE_DEVICE || cred.bridgeScope === "status-only") {
38006
38166
  return { ok: false, status: 403, refusal: "not_primary", message: "only the paired primary may open the daemon tunnel" };
38007
38167
  }
38008
38168
  if (!execEnabled2) {
@@ -38412,7 +38572,14 @@ async function verifyCloudUpgrade(url, request2) {
38412
38572
  return null;
38413
38573
  }
38414
38574
  log.ws.info("cloud ws upgrade authenticated", { name: cred.name, kind: cred.kind });
38415
- return cred;
38575
+ return {
38576
+ ...cred,
38577
+ stillValid: async () => {
38578
+ const { verifyDeviceToken: verifyDeviceToken2 } = await Promise.resolve().then(() => (init_device_auth(), device_auth_exports));
38579
+ const current8 = await verifyDeviceToken2(token, { touch: false });
38580
+ return current8?.name === cred.name && current8.kind === "machine" && current8.bridgeScope === cred.bridgeScope;
38581
+ }
38582
+ };
38416
38583
  }
38417
38584
  function ignoreSocketError() {
38418
38585
  }
@@ -38464,7 +38631,7 @@ function attachWss(server) {
38464
38631
  }
38465
38632
  const clientIp = bridgeClientIp2(request2.headers["x-forwarded-for"], request2.socket.remoteAddress);
38466
38633
  wss.handleUpgrade(request2, socket, head2, (ws) => {
38467
- attachBridge2(ws, cred.name, { clientIp });
38634
+ attachBridge2(ws, cred.name, { clientIp, stillValid: cred.stillValid, scope: cred.bridgeScope });
38468
38635
  });
38469
38636
  }).catch(() => socket.destroy());
38470
38637
  return;
@@ -92926,10 +93093,10 @@ var init_output_mode = __esm({
92926
93093
  init_types();
92927
93094
  init_voice_reply();
92928
93095
  OUTPUT_MODE_INSTRUCTION_MARKER = "[Rich output mode: ";
92929
- RICH_OUTPUT_MODE_ON_INSTRUCTION = `${OUTPUT_MODE_INSTRUCTION_MARKER}ON] Keep writing markdown for what markdown does well (headings, lists, tables, code blocks), and mix raw HTML into the same reply (separated by a blank line) for what markdown cannot express: colour that carries meaning, diagrams (inline <svg>), side-by-side or card layout, and interactivity (<details>, CSS :checked). The client renders it natively while streaming. Set text and background colour together or neither: a custom background with inherited or muted text loses contrast (theme vars --fg, --fg-muted, --bg-secondary, --border always fit). For anything needing <script>, emit a \`\`\`html-app fenced block (rendered in a sandboxed iframe). A short answer needs none of this. Component recipes (steppers, SVG diagrams, animations, islands): \`walnut tools call skill_read '{"dirName":"walnut-rich-output"}'\`.`;
93096
+ RICH_OUTPUT_MODE_ON_INSTRUCTION = `${OUTPUT_MODE_INSTRUCTION_MARKER}ON] Keep writing markdown for what markdown does well (headings, lists, tables, code blocks), and mix raw HTML into the same reply (separated by a blank line) for what markdown cannot express: colour that carries meaning, diagrams (inline <svg>), side-by-side or card layout, and interactivity (<details>, CSS :checked). The client renders it natively while streaming. Write visual HTML directly in your reply, not in a file, screenshot, or HTML code fence, unless the user asks for one. Keep each widget contiguous, without internal blank lines. Set text and background colour together or neither: a custom background with inherited or muted text loses contrast (theme vars --fg, --fg-muted, --bg-secondary, --border always fit). For anything needing <script>, emit a \`\`\`html-app fenced block (rendered in a sandboxed iframe). A short answer needs none of this. Component recipes (steppers, SVG diagrams, animations, islands): \`walnut tools call skill_read '{"dirName":"walnut-rich-output"}'\`.`;
92930
93097
  RICH_OUTPUT_MODE_OFF_INSTRUCTION = `${OUTPUT_MODE_INSTRUCTION_MARKER}OFF] Return to plain markdown replies.`;
92931
93098
  OUTPUT_MODE_REMINDER_MARKER = "[Rich output mode is still on";
92932
- RICH_OUTPUT_MODE_REMINDER = `${OUTPUT_MODE_REMINDER_MARKER} \u2014 markdown first; add HTML only for colour, diagrams, or layout markdown can't do.]`;
93099
+ RICH_OUTPUT_MODE_REMINDER = `${OUTPUT_MODE_REMINDER_MARKER}: markdown first; for colour, diagrams, or layout, write raw HTML directly in this reply. No files, screenshots, or HTML code fences unless requested (html-app for scripts); no blank lines inside a widget.]`;
92933
93100
  MODEL_NATIVE_OUTPUT_MODE = "markdown";
92934
93101
  }
92935
93102
  });
@@ -105471,6 +105638,7 @@ async function applySnapshot(sessionId, snapshot4, source2, canRecoverConnection
105471
105638
  }
105472
105639
  return { outcome: "shadow", diverged, projected };
105473
105640
  }
105641
+ if (sameTurn()) sessionRunner2.findSessionByClaudeId(sessionId)?.setForegroundFromSnapshot?.(snapshot4, epochChanged);
105474
105642
  let reconnectActivityCleared = false;
105475
105643
  if (!recoveringConnection && record2.activity === "Reconnecting to remote host...") {
105476
105644
  const cleared = await updateSessionRecordConditionally3(
@@ -107149,14 +107317,14 @@ async function terminateSession(sessionId, opts2 = {}) {
107149
107317
  if (acpLive) {
107150
107318
  log.session.info("session terminate: stopping ACP session", { sessionId });
107151
107319
  await acpLive.gracefulStop();
107152
- await updateSessionRecord2(sessionId, {
107320
+ const stopped2 = await updateSessionRecord2(sessionId, {
107153
107321
  process_status: "stopped",
107154
107322
  activity: void 0,
107155
107323
  last_status_change: (/* @__PURE__ */ new Date()).toISOString(),
107156
107324
  status_reason: "user_stopped",
107157
107325
  status_changed_by: "user"
107158
- }).catch(() => {
107159
- });
107326
+ }).catch(() => null);
107327
+ if (stopped2) emitSessionStatusChanged2(stopped2, {}, ["*"], { source: "terminate" });
107160
107328
  return { status: "terminated", sessionId, tookMs: Date.now() - startedAt };
107161
107329
  }
107162
107330
  if (!isAcpEngine(record2.engine)) {
@@ -119372,6 +119540,28 @@ function probeAllOverride(env3) {
119372
119540
  function enginePathOverrideVar(engineId) {
119373
119541
  return `WALNUT_${engineId.toUpperCase()}_PATH`;
119374
119542
  }
119543
+ function resolveOverridePath(override2, cwd = process.cwd()) {
119544
+ const literal = path83.resolve(cwd, override2);
119545
+ const segments2 = literal.split(path83.sep);
119546
+ const starAt = segments2.findIndex((segment) => segment.includes("*"));
119547
+ if (starAt <= 0 || segments2.slice(starAt + 1).some((segment) => segment.includes("*"))) return literal;
119548
+ const parent = segments2.slice(0, starAt).join(path83.sep) || path83.sep;
119549
+ const [head2, tail4, ...more] = segments2[starAt].split("*");
119550
+ if (more.length > 0) return literal;
119551
+ let names2;
119552
+ try {
119553
+ names2 = fs56.readdirSync(parent);
119554
+ } catch {
119555
+ return literal;
119556
+ }
119557
+ const rest = segments2.slice(starAt + 1);
119558
+ const matches = names2.filter((name) => name.length >= head2.length + tail4.length && name.startsWith(head2) && name.endsWith(tail4)).sort((a8, b8) => b8.localeCompare(a8, void 0, { numeric: true }));
119559
+ for (const name of matches) {
119560
+ const candidate = path83.join(parent, name, ...rest);
119561
+ if (inspectExecutable(candidate).executable) return candidate;
119562
+ }
119563
+ return literal;
119564
+ }
119375
119565
  function findEngineBinary(binaryName, options = {}) {
119376
119566
  const env3 = options.env ?? process.env;
119377
119567
  const cwd = options.cwd ?? process.cwd();
@@ -119511,7 +119701,7 @@ async function runProbe(caps, options) {
119511
119701
  const override2 = env3[overrideVar];
119512
119702
  let binary;
119513
119703
  if (override2) {
119514
- const verdict = inspectExecutable(path83.resolve(options.cwd ?? process.cwd(), override2));
119704
+ const verdict = inspectExecutable(resolveOverridePath(override2, options.cwd ?? process.cwd()));
119515
119705
  if (!verdict.executable) return { availability: unavailable(overrideReason(overrideVar, override2, verdict.rejection)) };
119516
119706
  binary = verdict.executable;
119517
119707
  } else {
@@ -127867,7 +128057,7 @@ var PRICING_VERSION, DEFAULT_PRICING;
127867
128057
  var init_pricing = __esm({
127868
128058
  "src/core/usage/pricing.ts"() {
127869
128059
  "use strict";
127870
- PRICING_VERSION = "2026-10-07";
128060
+ PRICING_VERSION = "2026-10-12";
127871
128061
  DEFAULT_PRICING = [
127872
128062
  // ── Claude models (Anthropic/Bedrock on-demand) ──
127873
128063
  // Opus 5.5 (2026-09-22 list price). Must sit above 'claude-opus-5', which is a
@@ -127895,6 +128085,16 @@ var init_pricing = __esm({
127895
128085
  { pattern: "claude-haiku-4-5", input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
127896
128086
  { pattern: "claude-3-5-haiku", input: 0.8, output: 4, cacheWrite: 1, cacheRead: 0.08 },
127897
128087
  { pattern: "claude-3-haiku", input: 0.25, output: 1.25, cacheWrite: 0.3, cacheRead: 0.03 },
128088
+ // ── OpenAI GPT on Amazon Bedrock (Global CRIS) ──
128089
+ // GPT-6.1 Sol and GPT-6 Astra have a short and a long tier split at 272K
128090
+ // input tokens; these are the long tier, so an estimate errs high. Specific
128091
+ // ids sit above the shorter ones they contain ('gpt-6-sol' is not in
128092
+ // 'gpt-6.1-sol', but 'gpt-6' is in every GPT-6 id).
128093
+ { pattern: "gpt-6.1-sol", input: 4, output: 15, cacheRead: 0.2 },
128094
+ { pattern: "gpt-6-sol", input: 2, output: 10, cacheRead: 0.2 },
128095
+ { pattern: "gpt-6-luna", input: 0.1, output: 0.5, cacheRead: 0.01 },
128096
+ { pattern: "gpt-6-astra", input: 20, output: 75, cacheRead: 2 },
128097
+ { pattern: "gpt-5.6", input: 8.8, output: 33, cacheRead: 0.88 },
127898
128098
  // ── Zhipu GLM (docs.z.ai international USD pricing) ──
127899
128099
  { pattern: "glm-4.7-flashx", input: 0.07, output: 0.4, cacheRead: 0.01 },
127900
128100
  { pattern: "glm-4.7", input: 0.6, output: 2.2, cacheRead: 0.11 },
@@ -143658,8 +143858,8 @@ import path106 from "path";
143658
143858
  import { fileURLToPath as fileURLToPath8 } from "url";
143659
143859
  function getVersion() {
143660
143860
  if (cachedVersion) return cachedVersion;
143661
- if ("0.6.11") {
143662
- cachedVersion = "0.6.11";
143861
+ if ("0.6.12") {
143862
+ cachedVersion = "0.6.12";
143663
143863
  versionKnown = true;
143664
143864
  return cachedVersion;
143665
143865
  }
@@ -168265,7 +168465,7 @@ async function getBackupLeader() {
168265
168465
  instance3 = createBackupLeader({
168266
168466
  now: () => Date.now(),
168267
168467
  takeoverMs: takeoverMsFromEnv(),
168268
- hosts: () => registry3.bridgeHosts().map((h8) => h8.hostAlias),
168468
+ hosts: () => registry3.bridgeHosts({ executionOnly: true }).map((h8) => h8.hostAlias),
168269
168469
  request: (host, cmd, params, timeoutMs2) => registry3.bridgeRequest(host, cmd, params, timeoutMs2),
168270
168470
  stateFile: path131.join(LEADER_STATE_DIR2, "companion.json")
168271
168471
  });
@@ -199029,7 +199229,14 @@ function normalizeProjectedEvent(event) {
199029
199229
  case "usage":
199030
199230
  return [{
199031
199231
  name: "session:usage-update",
199032
- payload: { used: event.used, size: event.size }
199232
+ // Spoken in the Claude event's vocabulary too, so the composer's
199233
+ // context badge reads an ACP session the same way.
199234
+ payload: {
199235
+ used: event.used,
199236
+ size: event.size,
199237
+ inputTokens: event.used,
199238
+ ...event.size > 0 ? { contextWindow: event.size, contextPercent: Math.round(event.used / event.size * 100) } : {}
199239
+ }
199033
199240
  }];
199034
199241
  case "system":
199035
199242
  return [{
@@ -199084,6 +199291,154 @@ var init_acp_stream_normalizer = __esm({
199084
199291
  }
199085
199292
  });
199086
199293
 
199294
+ // src/core/sessions/acp-turn-speed.ts
199295
+ var AcpTurnSpeedMeter;
199296
+ var init_acp_turn_speed = __esm({
199297
+ "src/core/sessions/acp-turn-speed.ts"() {
199298
+ "use strict";
199299
+ init_pricing();
199300
+ AcpTurnSpeedMeter = class {
199301
+ startedAt;
199302
+ firstChunkAt;
199303
+ model;
199304
+ open;
199305
+ /** Start of the call that was open last (the counted one at turn end). */
199306
+ lastCallStartedAt;
199307
+ lastCallFirstChunkAt;
199308
+ lastCallEndedAt;
199309
+ pendingTools = /* @__PURE__ */ new Set();
199310
+ calls = 0;
199311
+ usageUpdates = 0;
199312
+ partial = false;
199313
+ seenAnything = false;
199314
+ finalSnapshot;
199315
+ get active() {
199316
+ return this.finalSnapshot === void 0 && (this.startedAt !== void 0 || this.seenAnything);
199317
+ }
199318
+ start(ts, model) {
199319
+ this.startedAt = ts;
199320
+ this.firstChunkAt = void 0;
199321
+ this.model = model;
199322
+ this.openCall(ts);
199323
+ this.pendingTools.clear();
199324
+ this.calls = 0;
199325
+ this.usageUpdates = 0;
199326
+ this.partial = false;
199327
+ this.seenAnything = true;
199328
+ this.finalSnapshot = void 0;
199329
+ }
199330
+ /** Text or thought characters streamed by the model. */
199331
+ chunk(ts, chars2) {
199332
+ if (this.finalSnapshot || !(chars2 > 0)) return;
199333
+ this.attachIfNeeded(ts);
199334
+ if (this.firstChunkAt === void 0) this.firstChunkAt = ts;
199335
+ if (!this.open) this.openCall(ts);
199336
+ const open3 = this.open;
199337
+ if (open3.firstChunkAt === void 0) open3.firstChunkAt = ts;
199338
+ open3.chars += chars2;
199339
+ }
199340
+ /** The model asked for a tool: its call ended here. */
199341
+ toolCall(ts, toolCallId) {
199342
+ if (this.finalSnapshot) return;
199343
+ this.attachIfNeeded(ts);
199344
+ if (this.firstChunkAt === void 0) this.firstChunkAt = ts;
199345
+ this.closeCall(ts);
199346
+ this.pendingTools.add(toolCallId);
199347
+ }
199348
+ /** A tool finished; the next model call starts once none is pending. */
199349
+ toolDone(ts, toolCallId) {
199350
+ if (this.finalSnapshot) return;
199351
+ if (!this.pendingTools.delete(toolCallId)) return;
199352
+ if (this.pendingTools.size === 0 && !this.open) this.openCall(ts);
199353
+ }
199354
+ /** One usage_update per finished model call. */
199355
+ usage() {
199356
+ if (this.finalSnapshot) return;
199357
+ this.usageUpdates += 1;
199358
+ }
199359
+ snapshot(now) {
199360
+ if (this.finalSnapshot) return this.finalSnapshot;
199361
+ const open3 = this.open;
199362
+ const streaming = !!open3 && open3.chars > 0;
199363
+ return {
199364
+ ...this.startedAt !== void 0 ? { startedAt: this.startedAt } : {},
199365
+ ttftMs: this.startedAt !== void 0 && this.firstChunkAt !== void 0 ? Math.max(0, this.firstChunkAt - this.startedAt) : null,
199366
+ generationMs: 0,
199367
+ outputTokens: 0,
199368
+ messages: this.calls,
199369
+ ...this.model ? { model: this.model } : {},
199370
+ inFlight: streaming,
199371
+ inFlightChars: streaming ? open3.chars : 0,
199372
+ inFlightMs: streaming ? Math.max(0, now - open3.startedAt) : 0,
199373
+ inFlightVisibleMs: streaming && open3.firstChunkAt !== void 0 ? Math.max(0, now - open3.firstChunkAt) : 0,
199374
+ final: false,
199375
+ ...this.partial ? { partial: true } : {}
199376
+ };
199377
+ }
199378
+ finish(ts, usage, interrupted) {
199379
+ if (this.finalSnapshot) return this.finalSnapshot;
199380
+ if (this.open) this.closeCall(ts);
199381
+ const live5 = this.snapshot(ts);
199382
+ const calls = Math.max(this.calls, this.usageUpdates);
199383
+ const output = usage?.outputTokens;
199384
+ const hiddenReasoning = (usage?.thoughtTokens ?? 0) > 0;
199385
+ const windowStart = hiddenReasoning ? this.lastCallStartedAt : this.lastCallFirstChunkAt ?? this.lastCallStartedAt;
199386
+ const lastCallMs = windowStart !== void 0 && this.lastCallEndedAt !== void 0 ? Math.max(0, this.lastCallEndedAt - windowStart) : 0;
199387
+ const counted = typeof output === "number";
199388
+ const cost = counted && this.model && findPricing(this.model) ? computeCost({
199389
+ model: this.model,
199390
+ input_tokens: usage?.inputTokens ?? 0,
199391
+ output_tokens: output,
199392
+ cache_creation_input_tokens: 0,
199393
+ cache_read_input_tokens: usage?.cachedReadTokens ?? 0
199394
+ }) : 0;
199395
+ this.finalSnapshot = {
199396
+ ...live5,
199397
+ messages: calls,
199398
+ generationMs: counted ? lastCallMs : 0,
199399
+ outputTokens: counted ? output : 0,
199400
+ ...counted ? { turnOutputTokens: output } : {},
199401
+ ...counted && calls > 1 ? { tokenScope: "last-call" } : {},
199402
+ ...typeof usage?.inputTokens === "number" ? { inputTokens: usage.inputTokens } : {},
199403
+ ...typeof usage?.cachedReadTokens === "number" ? { cachedInputTokens: usage.cachedReadTokens } : {},
199404
+ ...typeof usage?.thoughtTokens === "number" && usage.thoughtTokens > 0 ? { reasoningTokens: usage.thoughtTokens } : {},
199405
+ // One call's price is not the turn's: a several-call turn shows no cost.
199406
+ ...cost > 0 && calls <= 1 ? { costUsd: cost, costEstimated: true } : {},
199407
+ ...this.startedAt !== void 0 ? { durationMs: Math.max(0, ts - this.startedAt) } : {},
199408
+ inFlight: false,
199409
+ inFlightChars: 0,
199410
+ inFlightMs: 0,
199411
+ inFlightVisibleMs: 0,
199412
+ final: true,
199413
+ ...interrupted ? { interrupted: true } : {},
199414
+ endedAt: ts
199415
+ };
199416
+ return this.finalSnapshot;
199417
+ }
199418
+ attachIfNeeded(ts) {
199419
+ if (this.startedAt !== void 0 || this.seenAnything) return;
199420
+ this.seenAnything = true;
199421
+ this.partial = true;
199422
+ this.openCall(ts);
199423
+ }
199424
+ openCall(ts) {
199425
+ this.open = { startedAt: ts, chars: 0 };
199426
+ this.lastCallStartedAt = ts;
199427
+ this.lastCallFirstChunkAt = void 0;
199428
+ this.lastCallEndedAt = void 0;
199429
+ }
199430
+ closeCall(ts) {
199431
+ if (!this.open) return;
199432
+ this.calls += 1;
199433
+ this.lastCallStartedAt = this.open.startedAt;
199434
+ this.lastCallFirstChunkAt = this.open.firstChunkAt;
199435
+ this.lastCallEndedAt = ts;
199436
+ this.open = void 0;
199437
+ }
199438
+ };
199439
+ }
199440
+ });
199441
+
199087
199442
  // src/providers/acp-worker/protocol.ts
199088
199443
  function resolveWalnutMcpServers(session2) {
199089
199444
  if (session2?.acp_walnut_mcp !== true) return [];
@@ -199315,7 +199670,7 @@ function resolveSystemCodexPath(options = {}) {
199315
199670
  });
199316
199671
  }
199317
199672
  function inspectExecutableCandidate(candidate, cwd) {
199318
- const executable = path161.resolve(cwd, candidate);
199673
+ const executable = resolveOverridePath(candidate, cwd);
199319
199674
  const verdict = inspectExecutable(executable);
199320
199675
  if (verdict.executable) return { executable: verdict.executable };
199321
199676
  if (verdict.rejection === "npm_injected_path") return { failure: "override_forbidden" };
@@ -199509,6 +199864,7 @@ var init_acp_session = __esm({
199509
199864
  init_daemon_connection();
199510
199865
  init_acp_journal_projector();
199511
199866
  init_acp_stream_normalizer();
199867
+ init_acp_turn_speed();
199512
199868
  init_self_api_root();
199513
199869
  init_protocol2();
199514
199870
  init_session_tracker();
@@ -199583,6 +199939,11 @@ var init_acp_session = __esm({
199583
199939
  _pendingPermissionCommit = Promise.resolve();
199584
199940
  _selfReport = null;
199585
199941
  normalizer;
199942
+ /** The composer's turn speed readout, measured from the journal (see acp-turn-speed.ts). */
199943
+ _speed = new AcpTurnSpeedMeter();
199944
+ _speedEmitAt = 0;
199945
+ /** Latest usage_update, written to the record at turn end (one write per turn). */
199946
+ _lastContext = null;
199586
199947
  constructor(cfg) {
199587
199948
  this.cfg = cfg;
199588
199949
  this.taskId = cfg.taskId;
@@ -199777,6 +200138,7 @@ var init_acp_session = __esm({
199777
200138
  }
199778
200139
  }
199779
200140
  }
200141
+ this.meterTurnSpeed(record2);
199780
200142
  for (const { name, payload } of this.normalizer.normalize(record2)) {
199781
200143
  const full = { ...payload, sessionId: this.trackingId(), taskId: this.taskId };
199782
200144
  if (name === "session:permission-request") {
@@ -199812,6 +200174,78 @@ var init_acp_session = __esm({
199812
200174
  this.commitTerminal(v3, record2);
199813
200175
  }
199814
200176
  }
200177
+ /** Feed one journal record to the turn speed meter; emit its readout. */
200178
+ meterTurnSpeed(record2) {
200179
+ if (record2.kind === "meta") {
200180
+ const event = record2.event;
200181
+ if (event.type === "turn-started") {
200182
+ this._speed.start(record2.ts, this.meterModel());
200183
+ this.emitTurnSpeed(record2.ts, false);
200184
+ } else if (event.type === "turn-ended" || event.type === "turn-interrupted") {
200185
+ if (!this._speed.active) return;
200186
+ const usage = event.type === "turn-ended" ? event.usage : void 0;
200187
+ const interrupted = event.type === "turn-interrupted" || event.type === "turn-ended" && event.stopReason === "cancelled";
200188
+ const speed = this._speed.finish(record2.ts, usage, interrupted);
200189
+ this.emitTurnSpeed(record2.ts, true);
200190
+ const sid = this._providerSessionId;
200191
+ const context = this._lastContext;
200192
+ if (sid) {
200193
+ void updateSessionRecord(sid, {
200194
+ lastTurnSpeed: speed,
200195
+ ...context ? { contextTokens: context.used, ...context.size > 0 ? { modelMaxWindow: context.size } : {} } : {}
200196
+ }).catch(() => {
200197
+ });
200198
+ }
200199
+ }
200200
+ return;
200201
+ }
200202
+ if (record2.source !== void 0 && record2.source !== "live") return;
200203
+ const frame = record2.frame;
200204
+ if (frame?.method !== "session/update") return;
200205
+ const update3 = frame.params?.update;
200206
+ if (!update3) return;
200207
+ switch (update3.sessionUpdate) {
200208
+ case "agent_message_chunk":
200209
+ case "agent_thought_chunk": {
200210
+ const content = update3.content;
200211
+ const chars2 = content?.type === "text" && typeof content.text === "string" ? content.text.length : 0;
200212
+ this._speed.chunk(record2.ts, chars2);
200213
+ if (record2.ts - this._speedEmitAt >= 400) this.emitTurnSpeed(record2.ts, false);
200214
+ return;
200215
+ }
200216
+ case "tool_call":
200217
+ if (typeof update3.toolCallId === "string") this._speed.toolCall(record2.ts, update3.toolCallId);
200218
+ this.emitTurnSpeed(record2.ts, false);
200219
+ return;
200220
+ case "tool_call_update":
200221
+ if (typeof update3.toolCallId === "string" && (update3.status === "completed" || update3.status === "failed")) {
200222
+ this._speed.toolDone(record2.ts, update3.toolCallId);
200223
+ }
200224
+ return;
200225
+ case "usage_update":
200226
+ this._speed.usage();
200227
+ if (typeof update3.used === "number" && typeof update3.size === "number") {
200228
+ this._lastContext = { used: update3.used, size: update3.size };
200229
+ }
200230
+ return;
200231
+ }
200232
+ }
200233
+ /** The model the meter prices and names: the family id, without its effort. */
200234
+ meterModel() {
200235
+ const current8 = this._models.currentModelId;
200236
+ return current8 ? splitAcpModelId(current8).base : void 0;
200237
+ }
200238
+ emitTurnSpeed(now, final) {
200239
+ const sid = this._providerSessionId;
200240
+ if (!sid) return;
200241
+ if (!final && !this._speed.active) return;
200242
+ this._speedEmitAt = now;
200243
+ bus.emit(EventNames.SESSION_TURN_SPEED, {
200244
+ sessionId: sid,
200245
+ taskId: this.taskId,
200246
+ speed: this._speed.snapshot(now)
200247
+ }, ["web-ui"], { source: "session-runner" });
200248
+ }
199815
200249
  /** Records/API key: providerSessionId once known, else runtimeId (pending phase). */
199816
200250
  trackingId() {
199817
200251
  return this._providerSessionId ?? this.runtimeId;
@@ -203975,6 +204409,8 @@ var init_claude_code_session = __esm({
203975
204409
  /** THE authoritative set of background tasks (dynamic workflows / subagents), keyed by
203976
204410
  * task_id, each carrying its latest status. "Is bg work in flight" is derived from this
203977
204411
  * set (see hasActiveBackgroundWork) — there is no parallel scalar counter to desync. */
204412
+ _foregroundActive;
204413
+ _foregroundEvidenceV = 0;
203978
204414
  _bgTasks = /* @__PURE__ */ new Map();
203979
204415
  /** Task ids that have EVER appeared in a `background_tasks_changed` payload — proof of
203980
204416
  * membership in the CLI's level universe. Only a task the CLI itself once listed may be
@@ -204117,6 +204553,7 @@ var init_claude_code_session = __esm({
204117
204553
  * from the init line. */
204118
204554
  _onTurnStartEdge(source2, persist5, sidHint) {
204119
204555
  const sid = this.claudeSessionId ?? sidHint;
204556
+ this.setForegroundActive(true);
204120
204557
  this._turnGen++;
204121
204558
  if (source2 !== "state-running" || this._turnResultEmitted || this._turnEdgeAt === void 0) {
204122
204559
  this._turnEdgeAt = Date.now();
@@ -204174,6 +204611,7 @@ var init_claude_code_session = __esm({
204174
204611
  _completeTurnOnIdle() {
204175
204612
  const sid = this.claudeSessionId;
204176
204613
  if (!sid) return;
204614
+ this.setForegroundActive(false);
204177
204615
  const interrupted = this.consumeInterruptRequest();
204178
204616
  const outcome = this._deferredOutcome;
204179
204617
  this._deferredOutcome = void 0;
@@ -204306,6 +204744,7 @@ var init_claude_code_session = __esm({
204306
204744
  taskId: this.taskId,
204307
204745
  workflowName: this._workflowName,
204308
204746
  inFlight: this._runningBgCount(),
204747
+ foregroundActive: this._foregroundActive,
204309
204748
  tasks: this.backgroundTasks,
204310
204749
  phases: sortedPhases(this._workflowPhases),
204311
204750
  agents: this.workflowAgents,
@@ -204342,6 +204781,12 @@ var init_claude_code_session = __esm({
204342
204781
  });
204343
204782
  return { stopped: true, status: row.status };
204344
204783
  }
204784
+ setForegroundActive(active6) {
204785
+ if (this._currentEventV !== void 0) this._foregroundEvidenceV = Math.max(this._foregroundEvidenceV, this._currentEventV);
204786
+ if (this._foregroundActive === active6) return;
204787
+ this._foregroundActive = active6;
204788
+ if (this.claudeSessionId && this._bgTasks.size > 0) this._emitBackgroundTasksUpdate(this.claudeSessionId);
204789
+ }
204345
204790
  /** Broadcast the current background-task set so the UI can render workflow progress. */
204346
204791
  _emitBackgroundTasksUpdate(sessionId) {
204347
204792
  bus.emit(EventNames.SESSION_BACKGROUND_TASKS, {
@@ -204349,6 +204794,7 @@ var init_claude_code_session = __esm({
204349
204794
  taskId: this.taskId,
204350
204795
  workflowName: this._workflowName,
204351
204796
  inFlight: this._runningBgCount(),
204797
+ foregroundActive: this._foregroundActive,
204352
204798
  tasks: this.backgroundTasks,
204353
204799
  phases: sortedPhases(this._workflowPhases),
204354
204800
  agents: this.workflowAgents,
@@ -204629,6 +205075,15 @@ var init_claude_code_session = __esm({
204629
205075
  setProcessStatusFromReconciler(status2) {
204630
205076
  this._processStatus = status2;
204631
205077
  }
205078
+ setForegroundFromSnapshot(snapshot4, epochChanged = false) {
205079
+ if (epochChanged) this._foregroundEvidenceV = 0;
205080
+ if (snapshot4.v < this._foregroundEvidenceV) return;
205081
+ const active6 = snapshot4.lastResult ? false : snapshot4.turnActive ? true : void 0;
205082
+ this._foregroundEvidenceV = snapshot4.v;
205083
+ if (active6 === void 0 || active6 === this._foregroundActive) return;
205084
+ this._foregroundActive = active6;
205085
+ if (this.claudeSessionId && this._bgTasks.size > 0) this._emitBackgroundTasksUpdate(this.claudeSessionId);
205086
+ }
204632
205087
  /**
204633
205088
  * Epoch-reset the in-memory consumed watermark from the snapshot layer.
204634
205089
  * The ONLY sanctioned backwards move (mirrors the record-side epoch
@@ -204853,6 +205308,8 @@ var init_claude_code_session = __esm({
204853
205308
  }
204854
205309
  this._active = true;
204855
205310
  this._processStatus = initOnly ? "idle" : "running";
205311
+ this._foregroundActive = !initOnly;
205312
+ this._foregroundEvidenceV = 0;
204856
205313
  this._exitCode = null;
204857
205314
  this._exitStderr = void 0;
204858
205315
  this.resultEmitted = false;
@@ -204884,6 +205341,7 @@ var init_claude_code_session = __esm({
204884
205341
  this._bgSeenInLevel.clear();
204885
205342
  this._deferredOutcome = void 0;
204886
205343
  this._resetWorkflowState();
205344
+ if (this.claudeSessionId) this._emitBackgroundTasksUpdate(this.claudeSessionId);
204887
205345
  this._teamActive = false;
204888
205346
  this._cliSessionState = void 0;
204889
205347
  this._transportGeneration++;
@@ -205262,12 +205720,16 @@ var init_claude_code_session = __esm({
205262
205720
  status: status2,
205263
205721
  toolUseId: foldToolUseId ?? prev?.toolUseId,
205264
205722
  isBackgrounded: t3.isBackgrounded || prev?.isBackgrounded,
205723
+ taskType: t3.taskType ?? prev?.taskType,
205724
+ subagentType: t3.subagentType ?? prev?.subagentType,
205725
+ ownedBySubagent: t3.ownedBySubagent ?? prev?.ownedBySubagent,
205265
205726
  // #870: the fold's level verdict rides along so a wedged hold stays
205266
205727
  // un-wedged across an attach (gating skips endedPerLevel entries).
205267
205728
  endedPerLevel: t3.endedPerLevel || prev?.endedPerLevel
205268
205729
  });
205269
205730
  if (t3.endedPerLevel) session2._bgSeenInLevel.add(taskId2);
205270
205731
  }
205732
+ session2._foregroundActive = fold2.lastResult ? false : fold2.cliState === "running" ? true : void 0;
205271
205733
  if (fold2.cliState != null) {
205272
205734
  session2._sessionStateSeen = true;
205273
205735
  session2._cliSessionState = fold2.cliState;
@@ -205529,6 +205991,8 @@ var init_claude_code_session = __esm({
205529
205991
  const before = {
205530
205992
  _processStatus: this._processStatus,
205531
205993
  _activity: this._activity,
205994
+ _foregroundActive: this._foregroundActive,
205995
+ _foregroundEvidenceV: this._foregroundEvidenceV,
205532
205996
  resultEmitted: this.resultEmitted,
205533
205997
  _turnResultEmitted: this._turnResultEmitted,
205534
205998
  _expectedTeardown: this._expectedTeardown,
@@ -205555,6 +206019,7 @@ var init_claude_code_session = __esm({
205555
206019
  undoDispatch = () => {
205556
206020
  if (this._transport === transport && this._turnGen === before._turnGen + 1 && this._lastJsonlEventTs === eventAt && transport.fileSize === offset) {
205557
206021
  Object.assign(this, before);
206022
+ if (this.claudeSessionId) this._emitBackgroundTasksUpdate(this.claudeSessionId);
205558
206023
  if (speedBefore.final) {
205559
206024
  this._speed.restore(speedBefore);
205560
206025
  this.emitTurnSpeed(true);
@@ -205567,6 +206032,8 @@ var init_claude_code_session = __esm({
205567
206032
  this._activity = void 0;
205568
206033
  this.resultEmitted = false;
205569
206034
  this._turnResultEmitted = false;
206035
+ this._foregroundEvidenceV = Math.max(this._foregroundEvidenceV, transport.fileSize + 1);
206036
+ this.setForegroundActive(true);
205570
206037
  this._expectedTeardown = false;
205571
206038
  this._turnGen++;
205572
206039
  this._turnEdgeAt = Date.now();
@@ -206841,6 +207308,7 @@ var init_claude_code_session = __esm({
206841
207308
  this._deferredOutcome = void 0;
206842
207309
  this._onTurnStartEdge("user-message", true);
206843
207310
  }
207311
+ if ((event.type === "assistant" || event.type === "stream_event") && !event.parent_tool_use_id && this._isReplayedByOffset() !== true) this.setForegroundActive(true);
206844
207312
  try {
206845
207313
  switch (event.type) {
206846
207314
  case "system": {
@@ -207182,6 +207650,7 @@ var init_claude_code_session = __esm({
207182
207650
  }
207183
207651
  const wasCompanionIdle = this._idleDebt > 0;
207184
207652
  if (wasCompanionIdle) this._idleDebt--;
207653
+ else this.setForegroundActive(false);
207185
207654
  if (this.hasActiveBackgroundWork()) {
207186
207655
  log.session.info("idle while background work in flight \u2014 staying running, awaiting drain", {
207187
207656
  sessionId: sid,
@@ -207245,6 +207714,7 @@ var init_claude_code_session = __esm({
207245
207714
  // read it here too in case a future CLI stamps it at start. Never
207246
207715
  // un-background: keep a previously-recorded true.
207247
207716
  isBackgrounded: sys.is_backgrounded === true || prevStarted?.isBackgrounded,
207717
+ ownedBySubagent: typeof sys.owned_by_subagent === "boolean" ? sys.owned_by_subagent : prevStarted?.ownedBySubagent,
207248
207718
  // Display-only. toolUseId: within ONE call the first non-empty wins (never
207249
207719
  // clobber with undefined). startedAt is OUR clock at the first start we saw — a
207250
207720
  // replayed task_started must not restart the row's elapsed timer.
@@ -207746,6 +208216,7 @@ var init_claude_code_session = __esm({
207746
208216
  this._turnResultEmitted = true;
207747
208217
  break;
207748
208218
  }
208219
+ this.setForegroundActive(false);
207749
208220
  const resultOrigin = event.origin;
207750
208221
  const isTaskNotificationResult = resultOrigin?.kind === "task-notification";
207751
208222
  if (isTaskNotificationResult) {
@@ -227968,6 +228439,9 @@ function foldSessionTail(content, baseOffset, opts2) {
227968
228439
  fold2.bgTasks[taskId2] = {
227969
228440
  status: !restarted && prev && BG_TERMINAL.has(prev.status) ? prev.status : "running",
227970
228441
  toolUseId: toolUseId ?? prev?.toolUseId,
228442
+ taskType: typeof parsed.task_type === "string" ? parsed.task_type : prev?.taskType,
228443
+ subagentType: typeof parsed.subagent_type === "string" ? parsed.subagent_type : prev?.subagentType,
228444
+ ownedBySubagent: typeof parsed.owned_by_subagent === "boolean" ? parsed.owned_by_subagent : prev?.ownedBySubagent,
227971
228445
  // A run_in_background Bash task carries is_backgrounded:true TOP-LEVEL
227972
228446
  // on its task_started (verified live, local_bash 2026-08-28). Sticky,
227973
228447
  // same as the task_updated patch path. Keep in parity with daemon-fold.
@@ -227977,6 +228451,11 @@ function foldSessionTail(content, baseOffset, opts2) {
227977
228451
  const prev = fold2.bgTasks[taskId2];
227978
228452
  const patch = parsed.patch;
227979
228453
  fold2.bgTasks[taskId2] = {
228454
+ // Keep what task_started said (its type, its owner), but not a level
228455
+ // snapshot's endedPerLevel mark: the task's own event un-marks it, as
228456
+ // daemon-fold's task_updated does (gatingBgCount parity).
228457
+ ...prev,
228458
+ endedPerLevel: void 0,
227980
228459
  status: patch?.status ?? prev?.status ?? "running",
227981
228460
  toolUseId: toolUseId ?? prev?.toolUseId,
227982
228461
  // Sticky: is_backgrounded=true detaches the task from gating forever.
@@ -227985,6 +228464,8 @@ function foldSessionTail(content, baseOffset, opts2) {
227985
228464
  } else if (taskId2 && subtype === "task_notification") {
227986
228465
  const prev = fold2.bgTasks[taskId2];
227987
228466
  fold2.bgTasks[taskId2] = {
228467
+ ...prev,
228468
+ endedPerLevel: void 0,
227988
228469
  status: parsed.status ?? "completed",
227989
228470
  isBackgrounded: prev?.isBackgrounded,
227990
228471
  toolUseId: toolUseId ?? prev?.toolUseId
@@ -246557,6 +247038,11 @@ var init_devices = __esm({
246557
247038
  const existingMachine = CLOUD_MODE && req.body?.kind !== "machine" && (await listDeviceRecords()).some((d8) => d8.name === name && d8.kind === "machine");
246558
247039
  const kind = req.body?.kind === "machine" || existingMachine ? "machine" : void 0;
246559
247040
  const wantsCloud = req.body?.target === "cloud" && !CLOUD_MODE;
247041
+ const bridgeScope = req.body?.bridge_scope;
247042
+ if (bridgeScope !== void 0 && (bridgeScope !== "status-only" || kind !== "machine" || !CLOUD_MODE)) {
247043
+ res.status(400).json({ error: "bridge_scope requires a status-only machine credential on the companion" });
247044
+ return;
247045
+ }
246560
247046
  const replace = req.body?.replace === true;
246561
247047
  if (wantsCloud) {
246562
247048
  if (!await cloudRelayAllowed(req, res, replace ? "rotate" : "create")) return;
@@ -246574,7 +247060,7 @@ var init_devices = __esm({
246574
247060
  const target = CLOUD_MODE ? { origin: consoleOrigin, kind: "cloud" } : localTargets.find((t3) => t3.kind === req.body?.target) ?? localTargets[0];
246575
247061
  const by = actorOf(req);
246576
247062
  if (kind === "machine" && CLOUD_MODE) {
246577
- const minted = await mintMachineCredential(name, { by, replace, proof: machineProof(req) });
247063
+ const minted = await mintMachineCredential(name, { by, replace, proof: machineProof(req), bridgeScope });
246578
247064
  log.web.info("devices: machine credential created via console", { name, caller: callerDevice(req) });
246579
247065
  res.status(201).json({ name, token: minted.token, pairingURI: buildPairingURI(name, minted.token, target?.origin), createdAt: minted.createdAt, target: target?.kind ?? null, server: target?.origin ?? null });
246580
247066
  return;
@@ -264159,6 +264645,7 @@ var init_system = __esm({
264159
264645
 
264160
264646
  // src/web/routes/hosts.ts
264161
264647
  import { Router as Router70 } from "express";
264648
+ import { randomBytes as randomBytes6 } from "crypto";
264162
264649
  async function cloudState(hostKey2) {
264163
264650
  const { bridgeForHost: bridgeForHost2 } = await Promise.resolve().then(() => (init_bridge_registry(), bridge_registry_exports));
264164
264651
  const connected2 = bridgeForHost2(hostKey2).connected;
@@ -264197,6 +264684,37 @@ var init_hosts = __esm({
264197
264684
  next(err2);
264198
264685
  }
264199
264686
  });
264687
+ hostsRouter.get("/:host/status", async (req, res, next) => {
264688
+ const host = String(req.params.host ?? "");
264689
+ if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,79}$/.test(host)) {
264690
+ res.status(400).json({ error: "Invalid host alias" });
264691
+ return;
264692
+ }
264693
+ if (!CLOUD_MODE) {
264694
+ res.status(409).json({ error: "Outbound host status is served by the cloud companion" });
264695
+ return;
264696
+ }
264697
+ try {
264698
+ const { bridgeForHost: bridgeForHost2, bridgeRequest: bridgeRequest2 } = await Promise.resolve().then(() => (init_bridge_registry(), bridge_registry_exports));
264699
+ if (!bridgeForHost2(host).connected) {
264700
+ res.status(503).json({ host, connected: false, code: "bridge_offline" });
264701
+ return;
264702
+ }
264703
+ const nonce = randomBytes6(16).toString("hex");
264704
+ try {
264705
+ const reply2 = await bridgeRequest2(host, "ping", { nonce }, 5e3);
264706
+ if (reply2.ok !== true || reply2.pong !== true || reply2.nonce !== nonce) {
264707
+ res.status(503).json({ host, connected: false, code: "bridge_unconfirmed" });
264708
+ return;
264709
+ }
264710
+ res.json({ host, connected: true, transport: "outbound_bridge" });
264711
+ } catch {
264712
+ res.status(503).json({ host, connected: false, code: "bridge_unavailable" });
264713
+ }
264714
+ } catch (err2) {
264715
+ next(err2);
264716
+ }
264717
+ });
264200
264718
  hostsRouter.post("/:host/connect", async (req, res, next) => {
264201
264719
  try {
264202
264720
  const host = typeof req.params.host === "string" ? req.params.host.trim() : "";
@@ -266314,6 +266832,7 @@ async function runJob2(state13, opts2) {
266314
266832
  state13.error = void 0;
266315
266833
  state13.steps[id] = { status: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString() };
266316
266834
  await persistIfCurrent();
266835
+ if (handle4.cancelled || superseded()) return;
266317
266836
  emitProgress(state13);
266318
266837
  const outcome = await runners[id](ctx);
266319
266838
  if (handle4.cancelled || superseded()) return;
@@ -267597,7 +268116,7 @@ import fs133 from "fs";
267597
268116
  import path237 from "path";
267598
268117
  import { createRequire as createRequire3 } from "module";
267599
268118
  import { spawn as spawn31 } from "child_process";
267600
- import { randomBytes as randomBytes6 } from "crypto";
268119
+ import { randomBytes as randomBytes7 } from "crypto";
267601
268120
  var RECORDING_STATE_FILE, require2, DEFAULT_CHUNK_MINUTES, DEFAULT_SAMPLE_RATE, DEFAULT_CHANNELS, DEFAULT_REFRESH_INTERVAL_SEC, WAV_FORMAT_PCM, AudioCaptureService, audioCaptureService;
267602
268121
  var init_audio_capture = __esm({
267603
268122
  "src/core/audio-capture.ts"() {
@@ -267818,7 +268337,7 @@ var init_audio_capture = __esm({
267818
268337
  throw new Error("Audio capture not available. macOS 13+ with Screen Recording permission required.");
267819
268338
  }
267820
268339
  const { AudioCapture } = require2("screencapturekit-audio-capture");
267821
- this.recordingId = randomBytes6(6).toString("hex");
268340
+ this.recordingId = randomBytes7(6).toString("hex");
267822
268341
  this.source = options.source;
267823
268342
  this.mode = options.mode;
267824
268343
  this.apps = options.apps ?? [];
@@ -286534,6 +287053,7 @@ __export(git_maintenance_exports, {
286534
287053
  DEBRIS_MAX_AGE_MS: () => DEBRIS_MAX_AGE_MS,
286535
287054
  FAILED_GC_BACKOFF_MS: () => FAILED_GC_BACKOFF_MS,
286536
287055
  MAINTENANCE_INTERVAL_DAYS: () => MAINTENANCE_INTERVAL_DAYS,
287056
+ REWRITE_MAINTENANCE_DELAY_MS: () => REWRITE_MAINTENANCE_DELAY_MS,
286537
287057
  SIZE_REGROWTH_RATIO: () => SIZE_REGROWTH_RATIO,
286538
287058
  SIZE_TRIGGER_BYTES: () => SIZE_TRIGGER_BYTES,
286539
287059
  expireCompactionBackups: () => expireCompactionBackups,
@@ -286542,7 +287062,9 @@ __export(git_maintenance_exports, {
286542
287062
  maintenanceDue: () => maintenanceDue,
286543
287063
  packDirBytes: () => packDirBytes,
286544
287064
  readLastRun: () => readLastRun,
287065
+ requestMaintenancePass: () => requestMaintenancePass,
286545
287066
  resolveGitDir: () => resolveGitDir,
287067
+ settleRepoSize: () => settleRepoSize,
286546
287068
  staleCompactionBackups: () => staleCompactionBackups,
286547
287069
  staleQuarantineDirs: () => staleQuarantineDirs,
286548
287070
  startGitMaintenance: () => startGitMaintenance,
@@ -286695,7 +287217,7 @@ function staleCompactionBackups(gitDir, now = Date.now()) {
286695
287217
  return d8 !== null && now - d8 > maxAgeMs;
286696
287218
  }).sort();
286697
287219
  }
286698
- function maintenanceDue(gitDir, now = Date.now()) {
287220
+ function maintenanceDue(gitDir, now = Date.now(), opts2 = {}) {
286699
287221
  const last2 = readLastRun(gitDir);
286700
287222
  const pack = packDirBytes(gitDir);
286701
287223
  const recentlyFailed = last2.failedAt !== void 0 && now - last2.failedAt < FAILED_GC_BACKOFF_MS;
@@ -286705,6 +287227,7 @@ function maintenanceDue(gitDir, now = Date.now()) {
286705
287227
  }
286706
287228
  if (staleQuarantineDirs(gitDir, now) >= DEBRIS_COUNT_TRIGGER) return "debris";
286707
287229
  if (staleCompactionBackups(gitDir, now).length > 0 && !recentlyFailed) return "backup";
287230
+ if (opts2.afterRewrite && !recentlyFailed) return "rewrite";
286708
287231
  if (now - last2.at >= MAINTENANCE_INTERVAL_DAYS * 864e5) return "interval";
286709
287232
  return null;
286710
287233
  }
@@ -286771,7 +287294,7 @@ async function maintainRepo(repoDir, opts2 = {}) {
286771
287294
  packBytesBefore: 0,
286772
287295
  packBytesAfter: 0
286773
287296
  };
286774
- const due2 = opts2.force ? "forced" : maintenanceDue(gitDir);
287297
+ const due2 = opts2.force ? "forced" : maintenanceDue(gitDir, Date.now(), { afterRewrite: opts2.afterRewrite });
286775
287298
  if (!due2) return result;
286776
287299
  result.reason = due2;
286777
287300
  result.packBytesBefore = packDirBytes(gitDir);
@@ -286844,14 +287367,34 @@ function sweepDeployBundles(dir4 = os64.tmpdir(), now = Date.now()) {
286844
287367
  }
286845
287368
  return swept;
286846
287369
  }
287370
+ function requestMaintenancePass() {
287371
+ return activePass ? activePass(false) : null;
287372
+ }
287373
+ async function settleRepoSize(verdict) {
287374
+ if (!verdict.over) return verdict;
287375
+ const pass = requestMaintenancePass();
287376
+ if (!pass) return verdict;
287377
+ const results = await pass;
287378
+ const worktree = results.find((r8) => r8.repo === WALNUT_HOME);
287379
+ if (!worktree?.ran) return verdict;
287380
+ return measureRepoSize(WALNUT_HOME);
287381
+ }
286847
287382
  function startGitMaintenance(opts2 = {}) {
286848
287383
  let stopped2 = false;
286849
287384
  let timer10 = null;
287385
+ let rewriteTimer = null;
287386
+ let rewritePending = false;
287387
+ let inflight7 = null;
286850
287388
  const pass = async (force = false) => {
286851
287389
  const results = [];
287390
+ const afterRewrite = rewritePending;
287391
+ rewritePending = false;
286852
287392
  try {
286853
- results.push(await maintainRepo(WALNUT_HOME, { pauseSync: true, force }));
287393
+ const worktree = await maintainRepo(WALNUT_HOME, { pauseSync: true, force, afterRewrite });
287394
+ results.push(worktree);
287395
+ if (afterRewrite && !worktree.ran) rewritePending = true;
286854
287396
  } catch (err2) {
287397
+ if (afterRewrite) rewritePending = true;
286855
287398
  log.git.warn("git-maintenance worktree pass failed", {
286856
287399
  error: err2 instanceof Error ? err2.message : String(err2)
286857
287400
  });
@@ -286874,9 +287417,32 @@ function startGitMaintenance(opts2 = {}) {
286874
287417
  }
286875
287418
  return results;
286876
287419
  };
287420
+ const runPass = (force = false) => {
287421
+ if (inflight7 && force) return inflight7.then(() => runPass(true));
287422
+ if (inflight7) return inflight7;
287423
+ inflight7 = pass(force).finally(() => {
287424
+ inflight7 = null;
287425
+ });
287426
+ return inflight7;
287427
+ };
287428
+ activePass = runPass;
287429
+ const onRewriteAdopted = () => {
287430
+ if (stopped2) return;
287431
+ rewritePending = true;
287432
+ if (rewriteTimer) return;
287433
+ rewriteTimer = setTimeout(() => {
287434
+ rewriteTimer = null;
287435
+ void (async () => {
287436
+ if (inflight7) await inflight7;
287437
+ if (!stopped2 && rewritePending) await runPass();
287438
+ })();
287439
+ }, opts2.rewriteDelayMs ?? REWRITE_MAINTENANCE_DELAY_MS);
287440
+ rewriteTimer.unref?.();
287441
+ };
287442
+ setRewriteAdoptedListener(onRewriteAdopted);
286877
287443
  const tick = async () => {
286878
287444
  try {
286879
- await pass();
287445
+ await runPass();
286880
287446
  } finally {
286881
287447
  if (!stopped2) {
286882
287448
  timer10 = setTimeout(() => {
@@ -286899,11 +287465,16 @@ function startGitMaintenance(opts2 = {}) {
286899
287465
  stop() {
286900
287466
  stopped2 = true;
286901
287467
  if (timer10) clearTimeout(timer10);
287468
+ if (rewriteTimer) clearTimeout(rewriteTimer);
287469
+ if (activePass === runPass) {
287470
+ activePass = null;
287471
+ setRewriteAdoptedListener(null);
287472
+ }
286902
287473
  },
286903
- runOnce: (force = false) => pass(force)
287474
+ runOnce: (force = false) => runPass(force)
286904
287475
  };
286905
287476
  }
286906
- var MAINTENANCE_INTERVAL_DAYS, SIZE_TRIGGER_BYTES, DEBRIS_MAX_AGE_MS, BACKUP_BRANCH_MAX_AGE_DAYS, COMPACTION_BACKUP_MAX_AGE_DAYS, SIZE_REGROWTH_RATIO, GC_TIMEOUT_MS, CHECK_INTERVAL_MS, LAST_RUN_FILE, FAILED_GC_BACKOFF_MS, DEBRIS_COUNT_TRIGGER, DEPLOY_DEBRIS_RE, DEPLOY_DEBRIS_MAX_AGE_MS;
287477
+ var MAINTENANCE_INTERVAL_DAYS, SIZE_TRIGGER_BYTES, DEBRIS_MAX_AGE_MS, BACKUP_BRANCH_MAX_AGE_DAYS, COMPACTION_BACKUP_MAX_AGE_DAYS, SIZE_REGROWTH_RATIO, GC_TIMEOUT_MS, CHECK_INTERVAL_MS, LAST_RUN_FILE, REWRITE_MAINTENANCE_DELAY_MS, FAILED_GC_BACKOFF_MS, DEBRIS_COUNT_TRIGGER, DEPLOY_DEBRIS_RE, DEPLOY_DEBRIS_MAX_AGE_MS, activePass;
286907
287478
  var init_git_maintenance = __esm({
286908
287479
  "src/integrations/git-maintenance.ts"() {
286909
287480
  "use strict";
@@ -286919,10 +287490,12 @@ var init_git_maintenance = __esm({
286919
287490
  GC_TIMEOUT_MS = 30 * 6e4;
286920
287491
  CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
286921
287492
  LAST_RUN_FILE = "walnut-last-maintenance";
287493
+ REWRITE_MAINTENANCE_DELAY_MS = 6e4;
286922
287494
  FAILED_GC_BACKOFF_MS = 24 * 60 * 60 * 1e3;
286923
287495
  DEBRIS_COUNT_TRIGGER = 10;
286924
287496
  DEPLOY_DEBRIS_RE = /^(?:wn[-.].*\.bundle(?:\.\w+)?|walnut-[\w.-]*\.(?:bundle|tar\.gz|tgz)|deploy-seed[\w.-]*\.sh|wn\.bundle)$/;
286925
287497
  DEPLOY_DEBRIS_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
287498
+ activePass = null;
286926
287499
  }
286927
287500
  });
286928
287501
 
@@ -292805,17 +293378,27 @@ async function startServer(options = {}) {
292805
293378
  recoveryKey: COMPACTION_RECOVERY_KEY
292806
293379
  });
292807
293380
  };
292808
- const attemptCompaction = async (retriesLeft = 2) => {
293381
+ const attemptCompaction = async (retriesLeft = 2, deferralsLeft = 6) => {
292809
293382
  if (!workerPath) {
292810
293383
  reportCompactionFailure("compaction worker build is missing from dist/workers", "missing-worker");
292811
293384
  return;
292812
293385
  }
292813
- const { setCompactionInProgress: setCompactionInProgress2, waitForSyncSettled: waitForSyncSettled2, syncPauseReason: syncPauseReason2 } = await Promise.resolve().then(() => (init_git_sync(), git_sync_exports));
293386
+ const gitSync = await Promise.resolve().then(() => (init_git_sync(), git_sync_exports));
293387
+ const { setCompactionInProgress: setCompactionInProgress2, waitForSyncSettled: waitForSyncSettled2, syncPauseReason: syncPauseReason2 } = gitSync;
292814
293388
  const pausedBy = syncPauseReason2();
292815
293389
  if (pausedBy) {
292816
293390
  log.git.info("git compaction skipped \u2014 operator pause marker active", { reason: pausedBy });
292817
293391
  return;
292818
293392
  }
293393
+ if (gitSync.compactionInProgress) {
293394
+ if (deferralsLeft > 0) {
293395
+ log.git.info("git compaction deferred \u2014 git maintenance holds the repo", { deferralsLeft });
293396
+ setTimeout(() => {
293397
+ void attemptCompaction(retriesLeft, deferralsLeft - 1);
293398
+ }, 10 * 6e4).unref?.();
293399
+ }
293400
+ return;
293401
+ }
292819
293402
  setCompactionInProgress2(true);
292820
293403
  await waitForSyncSettled2();
292821
293404
  const { utilityQosForkExec: utilityQosForkExec2 } = await Promise.resolve().then(() => (init_background_qos(), background_qos_exports));
@@ -294192,23 +294775,33 @@ function startGitAutoCommit() {
294192
294775
  Promise.resolve().then(() => (init_task_outbox(), task_outbox_exports)).then(({ reconcileAfterPull: reconcileAfterPull2 }) => reconcileAfterPull2()).catch(() => {
294193
294776
  });
294194
294777
  }
294195
- const size = checkRepoSize();
294196
- if (size) {
294197
- if (size.sweptBytes > 0) {
294198
- log.git.warn("repo-size sentinel swept dead transfer debris", { sweptBytes: size.sweptBytes, liveBytes: size.liveBytes });
294199
- }
294200
- if (size.over && size.warning) {
294201
- log.git.warn(size.warning);
294202
- void publishErrorNotification({
294203
- title: "Data Repo Growing Too Large",
294204
- body: size.warning,
294205
- dedupScope: "git:repo-size",
294206
- recoveryKey: REPO_SIZE_RECOVERY_KEY
294207
- });
294208
- }
294209
- if (gitRecoveryTracker.observe(REPO_SIZE_RECOVERY_KEY, size.over)) {
294210
- void publishRecovery([REPO_SIZE_RECOVERY_KEY]);
294778
+ const measured2 = checkRepoSize();
294779
+ if (measured2) {
294780
+ if (measured2.sweptBytes > 0) {
294781
+ log.git.warn("repo-size sentinel swept dead transfer debris", { sweptBytes: measured2.sweptBytes, liveBytes: measured2.liveBytes });
294211
294782
  }
294783
+ void Promise.resolve().then(() => (init_git_maintenance(), git_maintenance_exports)).then(({ settleRepoSize: settleRepoSize2 }) => settleRepoSize2(measured2)).then((size) => {
294784
+ if (gitTickStopped) return;
294785
+ if (size.over && size.warning) {
294786
+ log.git.warn(size.warning);
294787
+ void publishErrorNotification({
294788
+ title: "Data Repo Growing Too Large",
294789
+ body: size.warning,
294790
+ dedupScope: "git:repo-size",
294791
+ recoveryKey: REPO_SIZE_RECOVERY_KEY
294792
+ });
294793
+ } else if (measured2.over) {
294794
+ log.git.info("repo-size sentinel: maintenance brought the data repo under the threshold", {
294795
+ liveBytesBefore: measured2.liveBytes,
294796
+ liveBytesAfter: size.liveBytes
294797
+ });
294798
+ }
294799
+ if (gitRecoveryTracker.observe(REPO_SIZE_RECOVERY_KEY, size.over)) {
294800
+ void publishRecovery([REPO_SIZE_RECOVERY_KEY]);
294801
+ }
294802
+ }).catch((err2) => {
294803
+ log.git.warn("repo-size sentinel failed", { error: String(err2) });
294804
+ });
294212
294805
  }
294213
294806
  const guardState = getSyncGuardState();
294214
294807
  if (guardState.safeMode !== health2.safeMode) {