lody 0.105.0 → 0.106.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,13 +6,13 @@ import fs__default$1, { createReadStream, existsSync, realpathSync, statSync, cr
6
6
  import * as readline$1 from "node:readline";
7
7
  import readline__default, { createInterface } from "node:readline";
8
8
  import require$$5$3, { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
9
- import { g as getLodyDataDir, r as resolvePlatformKind, a as requireMs, b as getLogger, f as formatSafeLogFields, L as LODY_LOG_DIR, c as rootLogger, t as toShared, d as LOCAL_USER_ID_PREFIX, e as LOCAL_WORKSPACE_ID_PREFIX, h as formatErrorMessage, i as getInstallationProfile, j as registerLoroSyncErrorFormatter, l as listDailyLogFiles, k as readDailyLogFile, m as formatErrorWithCauses, n as truncateLogText, w as withSpawn, p as platformLayer, o as LodyDataDirUnavailableError, q as ensureLodyDataDir, C as CloudflaredError, u as routeLoggerToStderr, v as createHybridLogger, x as readLatestLogTail, y as createFileLogger } from "./chunks/cloudflared-native-CT-eRa_M.js";
9
+ import { g as getLodyDataDir, r as resolvePlatformKind, a as requireMs, b as getLogger, f as formatSafeLogFields, L as LODY_LOG_DIR, c as rootLogger, t as toShared, d as LOCAL_USER_ID_PREFIX, e as LOCAL_WORKSPACE_ID_PREFIX, h as formatErrorMessage, i as getInstallationProfile, j as registerLoroSyncErrorFormatter, l as listDailyLogFiles, k as readDailyLogFile, m as formatErrorWithCauses, n as truncateLogText, w as withSpawn, p as platformLayer, o as LodyDataDirUnavailableError, q as ensureLodyDataDir, C as CloudflaredError, u as routeLoggerToStderr, v as createHybridLogger, x as readLatestLogTail, y as createFileLogger } from "./chunks/cloudflared-native-CJVH1-0P.js";
10
10
  import * as fs from "fs";
11
11
  import fs__default, { promises, readFileSync, writeFileSync, mkdirSync as mkdirSync$1, lstatSync as lstatSync$1, unlinkSync, realpathSync as realpathSync$1, statSync as statSync$1, accessSync as accessSync$1, constants as constants$8, existsSync as existsSync$1 } from "fs";
12
12
  import * as crypto$4 from "crypto";
13
13
  import crypto__default, { randomUUID as randomUUID$1, createHash as createHash$1, randomBytes as randomBytes$4, webcrypto, createHmac, createSign, createCipheriv, createDecipheriv, timingSafeEqual as timingSafeEqual$1 } from "crypto";
14
14
  import { j as _coercedNumber, Z as ZodNumber, o as object, s as string$4, l as literal, d as discriminatedUnion, _ as _enum, a as array$1, n as number$3, b as boolean$1, r as record$5, c as unknown$1, p as preprocess$1, u as union$1, g as _null, k as custom, m as ZodObject, q as ZodLiteral, v as json, w as ZodError, x as safeParse, y as ZodOptional, z as ZodNullable, A as ZodPipe, B as ZodUnion, C as ZodArray, D as ZodNever, E as ZodDiscriminatedUnion, F as _instanceof, G as uuid, N as NEVER, H as lazy, h as looseObject, e as intersection, I as optional$2, J as datetime, f as url, K as any, L as toJSONSchema, M as date, O as email, P as xor, Q as never$2 } from "./chunks/schemas-Bue2bhN8.js";
15
- import { T as Tuple, y as dual, z as assignProperty, A as none, B as some, D as identity$2, E as hash$1, F as symbol, G as pipe$2, H as isNone, I as equals, J as symbol$1, K as optimize, N as NodeInspectSymbol, L as toJson, O as format$2, P as pipeArguments, Q as hasProperty, R as string$5, S as backEdges, U as combine$1, V as BaseProto, W as seconds, X as PipeInspectableProto, Y as isString$1, Z as structure, _ as redact$1, $ as symbolRedactable, a0 as get$2, a1 as Reference, a2 as strictEqual, a3 as fromIterable$1, a4 as array$2, a5 as make$c, a6 as makeEquivalence$2, a7 as memoize, a8 as succeed$1, a9 as isReadonlyArrayNonEmpty, aa as map$2, ab as formatPath, ac as isFailReason, ad as Prototype, ae as Class, af as resolveConcurrency, ag as fail, ah as effectIsExit, ai as die$1, aj as iterateConcurrent, ak as fnUntracedEager, al as flatMap$1, am as exit$1, an as flatMapEager, ao as void_, ap as args$1, aq as fail$1, ar as append, as as iterateEager, at as failCause, au as map$3, av as memoizeIdempotent, aw as isFunction$2, ax as isPropertyKey, ay as isNotNullish, az as empty$7, aA as isArrayNonEmpty, aB as formatPropertyKey, aC as suspend, aD as isObjectKeyword, aE as catchCause, aF as succeed$2, aG as isUnknown, aH as isNumber$1, aI as isBoolean, aJ as mapEager, aK as failCauseSync, aL as isSuccess, aM as runSyncExit, aN as die$2, aO as succeed$3, aP as fail$2, aQ as mapError, aR as TaggedError, aS as setStackTraceLimit, aT as getStackTraceLimit, aU as Service, aV as try_, aW as badArgument, aX as unwrap$1, aY as map$4, aZ as forEach, a_ as fnUntraced, a$ as unwrap$2, b0 as fromPull, b1 as done, b2 as match$1, b3 as of$1, b4 as catchTag, b5 as as, b6 as forkUnsafe, b7 as runIn, b8 as makeUnsafe, b as runPromise, b9 as runPromiseWith, ba as runPromiseExit, bb as runPromiseExitWith, bc as runSync$1, bd as runSyncWith, be as runSyncExitWith, bf as runCallback, bg as runCallbackWith, bh as runFork, bi as runForkWith, bj as withFiber, bk as scopeRemoveFinalizerUnsafe, bl as close, bm as tap$1, bn as buildWithMemoMap, bo as sync, bp as flatten, bq as await_, br as makeMemoMapUnsafe, bs as provideContext, bt as gen, bu as forEach$1, bv as ensuring, bw as isFailure, bx as failCause$1, by as combine$2, bz as callback, bA as void_$1, bB as fail$3, bC as raceFirst, bD as interruptible, d as sleep$5, bE as andThen, bF as make$d, bG as make$e, bH as hasInterruptsOnly, bI as hasDies, bJ as uninterruptibleMask, bK as forkIn, bL as join, bM as timeout$1, bN as uninterruptible, bO as die$3, bP as done$1, bQ as _await, bR as makeUnsafe$1, bS as isDoneUnsafe, bT as addFinalizer, bU as tapCause, i as effect, bV as isInterruptReason, bW as isDieReason, bX as isCause, bY as isTimeoutError, bZ as provide$1, b_ as pretty, b$ as catch_, c0 as currentTimeMillis, c1 as sync$1, o as getDefaultExportFromCjs, c2 as makeUnsafe$2, c3 as modify$1, c4 as set$4, c5 as getUnsafe, c6 as fork, c7 as scope, c8 as make$f, c9 as get$3, ca as make$g, cb as interrupt, cc as ProcessReleaseFailed, cd as squash, ce as timeoutOrElse, cf as asVoid, cg as provideMerge, q as merge$3, g as processLayer, ch as ChildProcessSpawner, ci as toMillis, cj as CommandTimedOut, ck as runCommandOk, cl as READ_ONLY_ABANDON_POLICY, cm as provideService, cn as findErrorOption, co as isSome, cp as CommandFailed, cq as SpawnFailed, cr as errnoCode, v as succeed$4, cs as squashProcessFailure, ct as try_$1, cu as fromNullishOr, cv as getOrUndefined, cw as isSuccess$1, cx as Class$1, cy as fromReadableStream, cz as fail$4, cA as tryPromise, cB as cached, cC as constFalse, cD as isFailure$1, cE as DisablePropagation, cF as matchCauseEffect, cG as hasInterrupts, cH as useSpan, cI as withParentSpan, cJ as TracerTimingEnabled, cK as Clock, cL as constTrue, cM as onInterrupt, cN as suspend$1, cO as ensuring$1, cP as interrupt$1, cQ as fail$5, cR as mapError$1, cS as never$1, cT as yieldNow, cU as doneUnsafe, cV as forkDetach, cW as orDie, p as promise, cX as empty$8, cY as logDebug, e as provide$2, cZ as provide$3, c_ as Scope, c$ as context, d0 as timeoutOption, d1 as TimeoutError$3, d2 as forever, d3 as unbounded, d4 as offerUnsafe, d5 as take$1, n as commonjsGlobal, m as getAugmentedNamespace, d6 as fromReasons, s as startProcessLegacy, d7 as systemError, d8 as serviceOption, d9 as acquireRelease, da as flatMap$2, db as getOrElse, dc as effectify, dd as callback$1, de as matchEffect, df as offer, dg as failCauseUnsafe, dh as endUnsafe, di as mergeAll$1, dj as NodeProcess, x as nodeProcessLive, dk as probePid, dl as update, dm as acquireUseRelease, dn as toStepWithMetadata, dp as forever$1, dq as modifyDelay, dr as getAndUpdate, ds as match$2, dt as make$h, du as shutdown, dv as makeDieReason, dw as makeUnsafe$3, r as runCommandTextLegacy, dx as all$1, f as runCommand$1, dy as result, dz as isPidAliveSyncLegacy, t as terminateChildTreeLegacy, dA as scoped, dB as ProcessCleanupFailed, dC as runPromiseSquashedLegacy, dD as fn, dE as tap$2, dF as map$5, dG as filter$1, dH as runHead, dI as getOrThrow, dJ as tapError, dK as spawn, dL as terminateTree, dM as succeed$5, dN as exists$1, dO as logWarning, dP as spawnProcess, dQ as TerminationFailed, dR as makeProcessRunnerLegacy, dS as unwrapSpawnFailure, a as runCommandTextSyncLegacy, dT as addFinalizer$1, dU as repeat$1, dV as spaced, dW as delay$4, dX as min$1, dY as exponential, dZ as minutes, d_ as jittered, d$ as while_, e0 as retry$2, e1 as takeBetween, e2 as forkScoped, e3 as unwrap$3, e4 as dropping, e5 as makeUnsafe$4, e6 as CommandOutputTooLarge, e7 as isEffect, e8 as take$2, e9 as release$1, ea as withPermits, eb as posixGroupTree, ec as waitUntilGone, ed as Effect, ee as childProcessTree, ef as hasExited, eg as isDone } from "./chunks/process-22-gqNSA.js";
15
+ import { T as Tuple, y as dual, z as assignProperty, A as none, B as some, D as identity$2, E as hash$1, F as symbol, G as pipe$2, H as isNone, I as equals, J as symbol$1, K as optimize, N as NodeInspectSymbol, L as toJson, O as format$2, P as pipeArguments, Q as hasProperty, R as string$5, S as backEdges, U as combine$1, V as BaseProto, W as seconds, X as PipeInspectableProto, Y as isString$1, Z as structure, _ as redact$1, $ as symbolRedactable, a0 as get$2, a1 as Reference, a2 as strictEqual, a3 as fromIterable$1, a4 as array$2, a5 as make$c, a6 as makeEquivalence$2, a7 as memoize, a8 as succeed$1, a9 as isReadonlyArrayNonEmpty, aa as map$2, ab as formatPath, ac as isFailReason, ad as Prototype, ae as Class, af as resolveConcurrency, ag as fail, ah as effectIsExit, ai as die$1, aj as iterateConcurrent, ak as fnUntracedEager, al as flatMap$1, am as exit$1, an as flatMapEager, ao as void_, ap as args$1, aq as fail$1, ar as append, as as iterateEager, at as failCause, au as map$3, av as memoizeIdempotent, aw as isFunction$2, ax as isPropertyKey, ay as isNotNullish, az as empty$7, aA as isArrayNonEmpty, aB as formatPropertyKey, aC as suspend, aD as isObjectKeyword, aE as catchCause, aF as succeed$2, aG as isUnknown, aH as isNumber$1, aI as isBoolean, aJ as mapEager, aK as failCauseSync, aL as isSuccess, aM as runSyncExit, aN as die$2, aO as succeed$3, aP as fail$2, aQ as mapError, aR as TaggedError, aS as setStackTraceLimit, aT as getStackTraceLimit, aU as Service, aV as try_, aW as badArgument, aX as unwrap$1, aY as map$4, aZ as forEach, a_ as fnUntraced, a$ as unwrap$2, b0 as fromPull, b1 as done, b2 as match$1, b3 as of$1, b4 as catchTag, b5 as as, b6 as forkUnsafe, b7 as runIn, b8 as makeUnsafe, b as runPromise, b9 as runPromiseWith, ba as runPromiseExit, bb as runPromiseExitWith, bc as runSync$1, bd as runSyncWith, be as runSyncExitWith, bf as runCallback, bg as runCallbackWith, bh as runFork, bi as runForkWith, bj as withFiber, bk as scopeRemoveFinalizerUnsafe, bl as close, bm as tap$1, bn as buildWithMemoMap, bo as sync, bp as flatten, bq as await_, br as makeMemoMapUnsafe, bs as provideContext, bt as gen, bu as forEach$1, bv as ensuring, bw as isFailure, bx as failCause$1, by as combine$2, bz as callback, bA as void_$1, bB as fail$3, bC as raceFirst, bD as interruptible, d as sleep$5, bE as andThen, bF as make$d, bG as make$e, bH as hasInterruptsOnly, bI as hasDies, bJ as uninterruptibleMask, bK as forkIn, bL as join, bM as timeout$1, bN as uninterruptible, bO as die$3, bP as done$1, bQ as _await, bR as makeUnsafe$1, bS as isDoneUnsafe, bT as addFinalizer, bU as tapCause, i as effect, bV as isInterruptReason, bW as isDieReason, bX as isCause, bY as isTimeoutError, bZ as provide$1, b_ as pretty, b$ as catch_, c0 as currentTimeMillis, c1 as sync$1, o as getDefaultExportFromCjs, c2 as makeUnsafe$2, c3 as modify$1, c4 as set$4, c5 as getUnsafe, c6 as fork, c7 as scope, c8 as make$f, c9 as get$3, ca as make$g, cb as interrupt, cc as ProcessReleaseFailed, cd as squash, ce as timeoutOrElse, cf as asVoid, cg as provideMerge, q as merge$3, g as processLayer, ch as ChildProcessSpawner, ci as toMillis, cj as CommandTimedOut, ck as runCommandOk, cl as READ_ONLY_ABANDON_POLICY, cm as provideService, cn as findErrorOption, co as isSome, cp as CommandFailed, cq as SpawnFailed, cr as errnoCode, v as succeed$4, cs as squashProcessFailure, ct as try_$1, cu as fromNullishOr, cv as getOrUndefined, cw as isSuccess$1, cx as Class$1, cy as fromReadableStream, cz as fail$4, cA as tryPromise, cB as cached, cC as constFalse, cD as isFailure$1, cE as DisablePropagation, cF as matchCauseEffect, cG as hasInterrupts, cH as useSpan, cI as withParentSpan, cJ as TracerTimingEnabled, cK as Clock, cL as constTrue, cM as onInterrupt, cN as suspend$1, cO as ensuring$1, cP as interrupt$1, cQ as fail$5, cR as mapError$1, cS as never$1, cT as yieldNow, cU as doneUnsafe, cV as forkDetach, cW as orDie, p as promise, cX as empty$8, cY as logDebug, e as provide$2, cZ as provide$3, c_ as Scope, c$ as context, d0 as timeoutOption, d1 as TimeoutError$3, d2 as forever, d3 as unbounded, d4 as offerUnsafe, d5 as take$1, n as commonjsGlobal, m as getAugmentedNamespace, d6 as fromReasons, s as startProcessLegacy, d7 as systemError, d8 as serviceOption, d9 as acquireRelease, da as flatMap$2, db as getOrElse, dc as effectify, dd as callback$1, de as matchEffect, df as offer, dg as failCauseUnsafe, dh as endUnsafe, di as mergeAll$1, dj as NodeProcess, x as nodeProcessLive, dk as probePid, dl as update, dm as acquireUseRelease, dn as toStepWithMetadata, dp as forever$1, dq as modifyDelay, dr as getAndUpdate, ds as match$2, dt as make$h, du as shutdown, dv as makeDieReason, dw as makeUnsafe$3, r as runCommandTextLegacy, dx as all$1, f as runCommand$1, dy as result, dz as isPidAliveSyncLegacy, t as terminateChildTreeLegacy, dA as scoped, dB as ProcessCleanupFailed, dC as runPromiseSquashedLegacy, dD as fn, dE as tap$2, dF as map$5, dG as filter$1, dH as runHead, dI as getOrThrow, dJ as tapError, dK as spawn, dL as terminateTree, dM as succeed$5, dN as exists$1, dO as logWarning, dP as spawnProcess, dQ as TerminationFailed, dR as makeProcessRunnerLegacy, dS as unwrapSpawnFailure, a as runCommandTextSyncLegacy, dT as addFinalizer$1, dU as repeat$1, dV as spaced, dW as delay$4, dX as min$1, dY as exponential, dZ as minutes, d_ as jittered, d$ as while_, e0 as retry$2, e1 as takeBetween, e2 as forkScoped, e3 as unwrap$3, e4 as dropping, e5 as makeUnsafe$4, e6 as CommandOutputTooLarge, e7 as isEffect, e8 as take$2, e9 as release$1, ea as withPermits, eb as posixGroupTree, ec as waitUntilGone, ed as Effect, ee as childProcessTree, ef as hasExited, eg as isDone } from "./chunks/process-DZ2dHvHL.js";
16
16
  import require$$0$3, { once, EventEmitter as EventEmitter$1 } from "node:events";
17
17
  import NodeChildProcess, { execFileSync } from "node:child_process";
18
18
  import * as path from "node:path";
@@ -63,7 +63,7 @@ import * as fs$3 from "node:fs/promises";
63
63
  import fs__default$3, { realpath, mkdir as mkdir$1, writeFile as writeFile$1, rename as rename$1, rm, symlink as symlink$1, mkdtemp, chmod as chmod$1, readFile as readFile$1, readdir, stat as stat$2, lstat, unlink, open as open$1, opendir, link as link$1 } from "node:fs/promises";
64
64
  import { a as parseWorkspaceWatchChildMessage, C as CODE_COLLAB_IGNORED_DIRECTORY_NAMES } from "./chunks/workspace-watch-path-policy-BRzhvEkG.js";
65
65
  import { StringDecoder } from "node:string_decoder";
66
- import { C as CodeCollabV2FileIndexRequestSchema, d as CodeCollabV2OpenTextRequestSchema, e as CodeCollabV2RefreshTextRequestSchema, f as CodeCollabV2SaveTextRequestSchema, g as CodeCollabV2OpenCurrentDiffRequestSchema, h as CodeCollabV2OpenAllChangesDiffRequestSchema, k as CodeCollabV2OpenTurnDiffRequestSchema, l as CodeCollabV2InitDirectoryRequestSchema, m as CodeCollabV2FileIndexSnapshotSchema, n as CodeCollabV2OpenTextOkSchema, o as CodeCollabV2RefreshTextResponseSchema, q as CodeCollabV2SaveTextResponseSchema, r as CodeCollabV2OpenCurrentDiffResponseSchema, t as CodeCollabV2OpenAllChangesDiffResponseSchema, u as CodeCollabV2OpenTurnDiffResponseSchema, v as CodeCollabV2InitDirectoryOkSchema, w as CodeCollabV2LspUnsupportedSchema, x as CodeCollabV2ErrorSchema, y as CodeCollabV2RpcContentEnvelopeSchema, z as deriveCodeCollabV2ContentKeyId, A as CodeCollabV2ErrorCodeSchema, B as CodeCollabV2RpcResponseSchema, D as deriveCodeCollabV2ContentKeyBytes, E as readCodeCollabFileIndexSignalFromFlock, F as writeCodeCollabFileIndexSignalToFlock, G as writeCodeCollabFileIndexToFlock, H as gitDiffBaseRefCandidates, b as buildCodeCollabFileIndexState, I as CODE_COLLAB_V2_TEXT_LIMITS, J as CODE_COLLAB_V2_ALL_CHANGES_DIFF_LIMITS, K as resolveAllChangesDiffBase, c as computeAllChanges, L as codeCollabFileIndexStatesEqual, s as scanGitDirectoryEntries, a as closeDirectoryQuietly } from "./chunks/directory-handle-BNSLkXDU.js";
66
+ import { C as CodeCollabV2FileIndexRequestSchema, d as CodeCollabV2OpenTextRequestSchema, e as CodeCollabV2RefreshTextRequestSchema, f as CodeCollabV2SaveTextRequestSchema, g as CodeCollabV2OpenCurrentDiffRequestSchema, h as CodeCollabV2OpenAllChangesDiffRequestSchema, k as CodeCollabV2OpenTurnDiffRequestSchema, l as CodeCollabV2InitDirectoryRequestSchema, m as CodeCollabV2FileIndexSnapshotSchema, n as CodeCollabV2OpenTextOkSchema, o as CodeCollabV2RefreshTextResponseSchema, q as CodeCollabV2SaveTextResponseSchema, r as CodeCollabV2OpenCurrentDiffResponseSchema, t as CodeCollabV2OpenAllChangesDiffResponseSchema, u as CodeCollabV2OpenTurnDiffResponseSchema, v as CodeCollabV2InitDirectoryOkSchema, w as CodeCollabV2LspUnsupportedSchema, x as CodeCollabV2ErrorSchema, y as CodeCollabV2RpcContentEnvelopeSchema, z as deriveCodeCollabV2ContentKeyId, A as CodeCollabV2ErrorCodeSchema, B as CodeCollabV2RpcResponseSchema, D as deriveCodeCollabV2ContentKeyBytes, E as readCodeCollabFileIndexSignalFromFlock, F as writeCodeCollabFileIndexSignalToFlock, G as writeCodeCollabFileIndexToFlock, H as gitDiffBaseRefCandidates, b as buildCodeCollabFileIndexState, I as CODE_COLLAB_V2_TEXT_LIMITS, J as CODE_COLLAB_V2_ALL_CHANGES_DIFF_LIMITS, K as resolveAllChangesDiffBase, c as computeAllChanges, L as codeCollabFileIndexStatesEqual, s as scanGitDirectoryEntries, a as closeDirectoryQuietly } from "./chunks/directory-handle-DssOMLsI.js";
67
67
  import { performance as performance$1 } from "perf_hooks";
68
68
  import require$$0$e, { EventEmitter as EventEmitter$2 } from "events";
69
69
  import require$$0$d, { Transform as Transform$1, Readable as Readable$1 } from "stream";
@@ -81,8 +81,8 @@ import { setTimeout as setTimeout$1 } from "timers/promises";
81
81
  import * as timers$2 from "node:timers/promises";
82
82
  import { setTimeout as setTimeout$2 } from "node:timers/promises";
83
83
  import { RoostHostError, RoostNativeClient } from "@loro-dev/roost-node";
84
- import { c as computeLineCounts, a as countTextLines } from "./chunks/diff-line-counts-Cr-lqn_D.js";
85
- import { r as runDiffWorkerTask } from "./chunks/diff-worker-task-BuDrFtkp.js";
84
+ import { c as computeLineCounts, a as countTextLines } from "./chunks/diff-line-counts-D6bAVmNB.js";
85
+ import { r as runDiffWorkerTask } from "./chunks/diff-worker-task-rnxgvZHM.js";
86
86
  import { createRequire as createRequire$1 } from "node:module";
87
87
  import v8 from "node:v8";
88
88
  import require$$0$f from "child_process";
@@ -4315,7 +4315,7 @@ class PostHog extends PostHogBackendClient {
4315
4315
  }
4316
4316
  const name$3 = "lody";
4317
4317
  const name$2 = "@lody/cli-cloud";
4318
- const version$g = "0.105.0";
4318
+ const version$g = "0.106.0";
4319
4319
  const type = "module";
4320
4320
  const scripts = { "dev": "node dev.mjs", "dev:staging": "LODY_ENV=staging NODE_ENV=production node dev.mjs", "build": "node build.mjs", "prepare:publish": "node prepare-publish.js", "test": "node --test pnpm-runner.test.mjs vite-config.test.mjs nightly-npm.test.mjs" };
4321
4321
  const devDependencies = { "dotenv": "^16.6.1", "vite": "^7.0.0", "vite-plugin-wasm": "catalog:" };
@@ -116452,6 +116452,94 @@ const redactUrlAuth = (raw2) => {
116452
116452
  return raw2;
116453
116453
  }
116454
116454
  };
116455
+ const GITHUB_TOKEN_CLOUD_TIMEOUT_MS = 15e3;
116456
+ const GITHUB_BROKER_TOKEN_TIMEOUT_MS = GITHUB_TOKEN_CLOUD_TIMEOUT_MS + 5e3;
116457
+ const githubCredentialRuntime = String.raw`
116458
+ const credentialError = (code, details = {}) => Object.assign(new Error(code), { code, ...details });
116459
+ const errorCode = error => {
116460
+ if (error?.name === 'TimeoutError' || error?.name === 'AbortError') return 'timeout';
116461
+ const code = [error?.code, error?.cause?.code].find(value => typeof value === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(value));
116462
+ return typeof code === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(code) ? code : 'unexpected_error';
116463
+ };
116464
+ const diagnostic = (source, stage, error) => {
116465
+ const detail = { source, stage, code: errorCode(error) };
116466
+ if (Number.isInteger(error?.status)) detail.status = error.status;
116467
+ if (typeof error?.requestId === 'string' && /^[a-f0-9-]{1,64}$/.test(error.requestId)) detail.requestId = error.requestId;
116468
+ console.error('[Lody GitHub] ' + JSON.stringify(detail));
116469
+ return detail;
116470
+ };
116471
+ // This file is authored by the host, never inferred from a child's token/env.
116472
+ // A pinned context token names an immutable snapshot for host-side clone/fetch.
116473
+ const readCredentialContext = (fs, statePath, env) => {
116474
+ const file = env.LODY_GIT_CRED_CONTEXT_FILE || (env.LODY_GIT_CRED_CONTEXT_TOKEN && statePath + '.contexts/' + env.LODY_GIT_CRED_CONTEXT_TOKEN + '.json');
116475
+ if (!file) throw credentialError('context_missing');
116476
+ let context;
116477
+ try { context = JSON.parse(fs.readFileSync(file, 'utf8')); }
116478
+ catch (cause) { throw credentialError('context_unreadable', { cause }); }
116479
+ if (context?.version !== 1 || typeof context.contextToken !== 'string' || !context.contextToken || typeof context.allowLocalAuth !== 'boolean') throw credentialError('context_invalid');
116480
+ return context;
116481
+ };
116482
+ const readCredentialPolicy = () => {
116483
+ const context = getContext();
116484
+ return { allowLocalAuth: context.allowLocalAuth, personalEnabled: true };
116485
+ };
116486
+ const readManagedCandidate = async (repoFullName, source) => {
116487
+ const response = await requestBroker('/github-token', {
116488
+ repoFullName, source, contextToken: getContext().contextToken,
116489
+ }, ${GITHUB_BROKER_TOKEN_TIMEOUT_MS});
116490
+ if (!response) throw credentialError('broker_unavailable');
116491
+ let body;
116492
+ try { body = await response.json(); }
116493
+ catch (cause) { throw credentialError(errorCode(cause) === 'timeout' ? 'timeout' : 'invalid_response', { cause, status: response.status }); }
116494
+ if (!response.ok) throw credentialError(typeof body.error === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(body.error) ? body.error : 'broker_http_error', { status: response.status, requestId: body.requestId });
116495
+ if (body.available === false) throw credentialError(typeof body.reason === 'string' && /^[a-z_]{1,64}$/.test(body.reason) ? body.reason : 'credential_unavailable');
116496
+ if (typeof body.token !== 'string' || !body.token || /[\r\n]/.test(body.token) || body.tokenSource !== source) throw credentialError('invalid_credential_response');
116497
+ return { token: body.token };
116498
+ };
116499
+ // One acquisition per source. Consumers can continue after a provably safe
116500
+ // failure of the actual operation; never restart this iterator to retry a source.
116501
+ async function* githubCredentials(repo, policy, localCandidate, anonymous = false) {
116502
+ const failures = [];
116503
+ for (const source of ['personal', 'local', 'app', ...(anonymous ? ['anonymous'] : [])]) {
116504
+ if (source === 'personal' && policy.personalEnabled === false) continue;
116505
+ if (source === 'local' && !policy.allowLocalAuth) continue;
116506
+ try {
116507
+ const candidate = source === 'anonymous' ? { token: null } : source === 'local' ? await localCandidate() : await readManagedCandidate(repo, source);
116508
+ if (!candidate) throw credentialError('credential_missing');
116509
+ yield { ...candidate, source };
116510
+ } catch (error) {
116511
+ failures.push(diagnostic(source, 'acquire', error));
116512
+ }
116513
+ }
116514
+ throw credentialError('credentials_exhausted', { failures });
116515
+ }
116516
+ const selectGitHubCredential = async (repo, policy, localCandidate, requireWrite, anonymousCandidate, verify = async () => true) => {
116517
+ const failures = [];
116518
+ try {
116519
+ for await (const candidate of githubCredentials(repo, policy, localCandidate, !requireWrite && !!anonymousCandidate)) {
116520
+ try {
116521
+ if (candidate.source === 'anonymous' && !await anonymousCandidate()) throw credentialError('anonymous_unavailable');
116522
+ if (!await verify(candidate)) throw credentialError('access_denied');
116523
+ console.error('[Lody GitHub] ' + JSON.stringify({ source: candidate.source, stage: 'selected', repo }));
116524
+ return candidate;
116525
+ } catch (error) { failures.push(diagnostic(candidate.source, 'access', error)); }
116526
+ }
116527
+ } catch (error) {
116528
+ if (error.code === 'credentials_exhausted') error.failures.push(...failures);
116529
+ throw error;
116530
+ }
116531
+ };
116532
+ `;
116533
+ const withRequestDeadline = (timeoutMs, fetchImpl = fetch) => (input2, init2) => {
116534
+ const deadline = new AbortController();
116535
+ const timer2 = setTimeout(
116536
+ () => deadline.abort(new DOMException("GitHub token request timed out", "TimeoutError")),
116537
+ timeoutMs
116538
+ );
116539
+ timer2.unref?.();
116540
+ const signal = init2?.signal ? AbortSignal.any([init2.signal, deadline.signal]) : deadline.signal;
116541
+ return fetchImpl(input2, { ...init2, signal });
116542
+ };
116455
116543
  const GitHubTokenSuccessSchema = object({
116456
116544
  success: literal(true),
116457
116545
  token: string$4(),
@@ -116555,7 +116643,7 @@ class GitHubTokenManager {
116555
116643
  this.logger = options2.logger ?? getLogger("github-token");
116556
116644
  this.client = new ConvexHttpClient(options2.serverUrl, {
116557
116645
  logger: false,
116558
- fetch: (input2, init2) => fetch(input2, { ...init2, signal: AbortSignal.timeout(2500) })
116646
+ fetch: withRequestDeadline(GITHUB_TOKEN_CLOUD_TIMEOUT_MS)
116559
116647
  });
116560
116648
  this.cliToken = options2.cliToken;
116561
116649
  this.workspaceId = options2.workspaceId;
@@ -138086,82 +138174,6 @@ const clearGitHubTokenEnv = (env2) => {
138086
138174
  if (GITHUB_CREDENTIAL_ENV_KEYS.includes(key2.toUpperCase())) delete env2[key2];
138087
138175
  }
138088
138176
  };
138089
- const githubCredentialRuntime = String.raw`
138090
- const credentialError = (code, details = {}) => Object.assign(new Error(code), { code, ...details });
138091
- const errorCode = error => {
138092
- if (error?.name === 'TimeoutError' || error?.name === 'AbortError') return 'timeout';
138093
- const code = [error?.code, error?.cause?.code].find(value => typeof value === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(value));
138094
- return typeof code === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(code) ? code : 'unexpected_error';
138095
- };
138096
- const diagnostic = (source, stage, error) => {
138097
- const detail = { source, stage, code: errorCode(error) };
138098
- if (Number.isInteger(error?.status)) detail.status = error.status;
138099
- if (typeof error?.requestId === 'string' && /^[a-f0-9-]{1,64}$/.test(error.requestId)) detail.requestId = error.requestId;
138100
- console.error('[Lody GitHub] ' + JSON.stringify(detail));
138101
- return detail;
138102
- };
138103
- // This file is authored by the host, never inferred from a child's token/env.
138104
- // A pinned context token names an immutable snapshot for host-side clone/fetch.
138105
- const readCredentialContext = (fs, statePath, env) => {
138106
- const file = env.LODY_GIT_CRED_CONTEXT_FILE || (env.LODY_GIT_CRED_CONTEXT_TOKEN && statePath + '.contexts/' + env.LODY_GIT_CRED_CONTEXT_TOKEN + '.json');
138107
- if (!file) throw credentialError('context_missing');
138108
- let context;
138109
- try { context = JSON.parse(fs.readFileSync(file, 'utf8')); }
138110
- catch (cause) { throw credentialError('context_unreadable', { cause }); }
138111
- if (context?.version !== 1 || typeof context.contextToken !== 'string' || !context.contextToken || typeof context.allowLocalAuth !== 'boolean') throw credentialError('context_invalid');
138112
- return context;
138113
- };
138114
- const readCredentialPolicy = () => {
138115
- const context = getContext();
138116
- return { allowLocalAuth: context.allowLocalAuth, personalEnabled: true };
138117
- };
138118
- const readManagedCandidate = async (repoFullName, source) => {
138119
- const response = await requestBroker('/github-token', {
138120
- repoFullName, source, contextToken: getContext().contextToken,
138121
- }, 3000);
138122
- if (!response) throw credentialError('broker_unavailable');
138123
- let body;
138124
- try { body = await response.json(); }
138125
- catch (cause) { throw credentialError(errorCode(cause) === 'timeout' ? 'timeout' : 'invalid_response', { cause, status: response.status }); }
138126
- if (!response.ok) throw credentialError(typeof body.error === 'string' && /^[A-Za-z0-9_]{1,64}$/.test(body.error) ? body.error : 'broker_http_error', { status: response.status, requestId: body.requestId });
138127
- if (body.available === false) throw credentialError(typeof body.reason === 'string' && /^[a-z_]{1,64}$/.test(body.reason) ? body.reason : 'credential_unavailable');
138128
- if (typeof body.token !== 'string' || !body.token || /[\r\n]/.test(body.token) || body.tokenSource !== source) throw credentialError('invalid_credential_response');
138129
- return { token: body.token };
138130
- };
138131
- // One acquisition per source. Consumers can continue after a provably safe
138132
- // failure of the actual operation; never restart this iterator to retry a source.
138133
- async function* githubCredentials(repo, policy, localCandidate, anonymous = false) {
138134
- const failures = [];
138135
- for (const source of ['personal', 'local', 'app', ...(anonymous ? ['anonymous'] : [])]) {
138136
- if (source === 'personal' && policy.personalEnabled === false) continue;
138137
- if (source === 'local' && !policy.allowLocalAuth) continue;
138138
- try {
138139
- const candidate = source === 'anonymous' ? { token: null } : source === 'local' ? await localCandidate() : await readManagedCandidate(repo, source);
138140
- if (!candidate) throw credentialError('credential_missing');
138141
- yield { ...candidate, source };
138142
- } catch (error) {
138143
- failures.push(diagnostic(source, 'acquire', error));
138144
- }
138145
- }
138146
- throw credentialError('credentials_exhausted', { failures });
138147
- }
138148
- const selectGitHubCredential = async (repo, policy, localCandidate, requireWrite, anonymousCandidate, verify = async () => true) => {
138149
- const failures = [];
138150
- try {
138151
- for await (const candidate of githubCredentials(repo, policy, localCandidate, !requireWrite && !!anonymousCandidate)) {
138152
- try {
138153
- if (candidate.source === 'anonymous' && !await anonymousCandidate()) throw credentialError('anonymous_unavailable');
138154
- if (!await verify(candidate)) throw credentialError('access_denied');
138155
- console.error('[Lody GitHub] ' + JSON.stringify({ source: candidate.source, stage: 'selected', repo }));
138156
- return candidate;
138157
- } catch (error) { failures.push(diagnostic(candidate.source, 'access', error)); }
138158
- }
138159
- } catch (error) {
138160
- if (error.code === 'credentials_exhausted') error.failures.push(...failures);
138161
- throw error;
138162
- }
138163
- };
138164
- `;
138165
138177
  const ghPrCreateScript = String.raw`
138166
138178
  const createPrViaRest = async (ghCommand, target, parsed, env) => {
138167
138179
  const values = parsed.values;
@@ -138387,7 +138399,7 @@ const main = async () => {
138387
138399
  if (token) return { token };
138388
138400
  throw credentialError('unsupported_local_header');
138389
138401
  }
138390
- const fill = spawnSync(REAL_GIT, ['credential', 'fill'], { env: { ...env, LODY_GIT_LOCAL_DELEGATE: '1' }, input: 'protocol=https\nhost=github.com\npath=' + repo + '.git\n\n', encoding: 'utf8', timeout: 3000 });
138402
+ const fill = spawnSync(REAL_GIT, ['credential', 'fill'], { env: { ...env, LODY_GIT_LOCAL_DELEGATE: '1' }, input: 'protocol=https\nhost=github.com\npath=' + repo + '.git\n\n', encoding: 'utf8', timeout: 10_000 });
138391
138403
  if (fill.error) throw fill.error;
138392
138404
  const token = /^password=(.*)$/m.exec(fill.stdout || '')?.[1];
138393
138405
  if (token) return { token };
@@ -138398,14 +138410,14 @@ const main = async () => {
138398
138410
  const env = candidate.ssh ? nativeEnv() : authenticatedEnv(httpsUrl, candidate.token);
138399
138411
  if (candidate.ssh) {
138400
138412
  const configured = spawnSync(REAL_GIT, ['config', '--get', 'core.sshCommand'], { env, encoding: 'utf8' });
138401
- env.GIT_SSH_COMMAND = (env.GIT_SSH_COMMAND || configured.stdout?.trim() || (env.GIT_SSH ? quote(env.GIT_SSH) : 'ssh')) + sshPort + ' -oBatchMode=yes -oConnectTimeout=3';
138413
+ env.GIT_SSH_COMMAND = (env.GIT_SSH_COMMAND || configured.stdout?.trim() || (env.GIT_SSH ? quote(env.GIT_SSH) : 'ssh')) + sshPort + ' -oBatchMode=yes -oConnectTimeout=10';
138402
138414
  }
138403
138415
  // Native upload/receive-pack advertisement has no write effects. It lets
138404
138416
  // denied credentials fall through without replaying an uncertain push.
138405
138417
  const target = candidate.ssh ? (sshPort ? 'ssh://git@ssh.github.com:443/' : 'git@github.com:') + repo + '.git' : httpsUrl;
138406
138418
  const args = candidate.ssh ? ['ls-remote', ...(write ? ['--upload-pack=git-receive-pack'] : []), target] : ['remote-https', 'lody-advertisement', target];
138407
138419
  const result = spawnSync(REAL_GIT, args, {
138408
- env, input: candidate.ssh ? undefined : (write ? 'list for-push\n\n' : 'list\n\n'), encoding: 'utf8', timeout: 5000, maxBuffer: 1024 * 1024, stdio: ['pipe', 'ignore', 'pipe'],
138420
+ env, input: candidate.ssh ? undefined : (write ? 'list for-push\n\n' : 'list\n\n'), encoding: 'utf8', timeout: 15_000, maxBuffer: 1024 * 1024, stdio: ['pipe', 'ignore', 'pipe'],
138409
138421
  });
138410
138422
  if (result.error) throw result.error;
138411
138423
  if (result.status !== 0) {
@@ -139144,7 +139156,7 @@ async function* ghEnvironments(ghCommand, args) {
139144
139156
  const token = env.GH_TOKEN || env.GITHUB_TOKEN;
139145
139157
  if (token) return { token };
139146
139158
  const result = await runGhCommand(ghCommand, ['auth', 'token', '--hostname', target.host], {
139147
- env, stdio: ['ignore', 'pipe', 'pipe'], timeout: 3000,
139159
+ env, stdio: ['ignore', 'pipe', 'pipe'], timeout: 10_000,
139148
139160
  });
139149
139161
  if (result.error) throw result.error;
139150
139162
  if (result.status !== 0) throw credentialError('local_auth_missing');
@@ -145537,6 +145549,16 @@ const CatalogAccessSnapshotSchema = object({
145537
145549
  ownerUserId: string$4(),
145538
145550
  verifiedAt: string$4()
145539
145551
  });
145552
+ const MEMBER_ACCESS_SNAPSHOT_TTL_MS = 60 * 60 * 1e3;
145553
+ const CatalogMemberAccessSnapshotSchema = object({
145554
+ requesterUserId: string$4(),
145555
+ localProjectId: string$4().optional(),
145556
+ // Optional only so an unbound entry parses (and never matches) instead of
145557
+ // making the whole catalog corrupt.
145558
+ ownerUserId: string$4().optional(),
145559
+ machineId: string$4().optional(),
145560
+ verifiedAt: string$4()
145561
+ });
145540
145562
  const CatalogWorkspaceSchema = object({
145541
145563
  workspaceId: string$4(),
145542
145564
  name: string$4(),
@@ -145548,7 +145570,8 @@ const CatalogWorkspaceSchema = object({
145548
145570
  machine: CatalogMachineSchema.extend({
145549
145571
  cachedAt: number$3()
145550
145572
  }).optional(),
145551
- accessSnapshot: CatalogAccessSnapshotSchema.optional()
145573
+ accessSnapshot: CatalogAccessSnapshotSchema.optional(),
145574
+ memberAccessSnapshots: array$1(CatalogMemberAccessSnapshotSchema).optional()
145552
145575
  });
145553
145576
  const CatalogSessionSchema = object({
145554
145577
  sessionId: string$4(),
@@ -145571,6 +145594,10 @@ class CatalogCorruptError extends TaggedError("CatalogCorruptError") {
145571
145594
  }
145572
145595
  class CatalogPermissionError extends TaggedError("CatalogPermissionError") {
145573
145596
  }
145597
+ function isMemberAccessSnapshotFresh(snapshot2, nowMs2) {
145598
+ const ageMs = nowMs2 - Date.parse(snapshot2.verifiedAt);
145599
+ return ageMs >= 0 && ageMs < MEMBER_ACCESS_SNAPSHOT_TTL_MS;
145600
+ }
145574
145601
  const emptyCatalog = () => ({
145575
145602
  version: CATALOG_VERSION,
145576
145603
  identity: null,
@@ -145721,6 +145748,7 @@ function makeLocalWorkspaceCatalog(options2 = {}) {
145721
145748
  cacheRemoteWorkspaces: (input2) => mutate((snapshot2) => {
145722
145749
  const now2 = getServerNow();
145723
145750
  const remoteIds = new Set(input2.workspaces.map((workspace) => workspace.id));
145751
+ const keepMemberGrants = snapshot2.identity?.userId === input2.identity.userId && snapshot2.machine?.machineId === input2.machine.machineId;
145724
145752
  const previousById = new Map(
145725
145753
  snapshot2.workspaces.map((workspace) => [workspace.workspaceId, workspace])
145726
145754
  );
@@ -145737,7 +145765,8 @@ function makeLocalWorkspaceCatalog(options2 = {}) {
145737
145765
  ...input2.machine,
145738
145766
  cachedAt: now2
145739
145767
  },
145740
- ...previous2?.accessSnapshot ? { accessSnapshot: previous2.accessSnapshot } : {}
145768
+ ...previous2?.accessSnapshot ? { accessSnapshot: previous2.accessSnapshot } : {},
145769
+ ...keepMemberGrants && previous2?.memberAccessSnapshots ? { memberAccessSnapshots: previous2.memberAccessSnapshots } : {}
145741
145770
  };
145742
145771
  });
145743
145772
  const remoteMissing = snapshot2.workspaces.filter((workspace) => !remoteIds.has(workspace.workspaceId)).map((workspace) => ({
@@ -145765,6 +145794,32 @@ function makeLocalWorkspaceCatalog(options2 = {}) {
145765
145794
  return { ...workspace, accessSnapshot: input2.accessSnapshot };
145766
145795
  })
145767
145796
  })),
145797
+ recordMemberAccessSnapshot: (input2) => mutate((snapshot2) => {
145798
+ const nowMs2 = clock2();
145799
+ return {
145800
+ ...snapshot2,
145801
+ workspaces: snapshot2.workspaces.map((workspace) => {
145802
+ if (workspace.workspaceId !== input2.workspaceId) {
145803
+ return workspace;
145804
+ }
145805
+ const others = (workspace.memberAccessSnapshots ?? []).filter(
145806
+ (entry) => (entry.requesterUserId !== input2.requesterUserId || entry.localProjectId !== input2.localProjectId) && isMemberAccessSnapshotFresh(entry, nowMs2)
145807
+ );
145808
+ const next = input2.verifiedAt === null ? others : [
145809
+ ...others,
145810
+ {
145811
+ requesterUserId: input2.requesterUserId,
145812
+ ...input2.localProjectId ? { localProjectId: input2.localProjectId } : {},
145813
+ ownerUserId: input2.ownerUserId,
145814
+ machineId: input2.machineId,
145815
+ verifiedAt: input2.verifiedAt
145816
+ }
145817
+ ].slice(-64);
145818
+ const { memberAccessSnapshots: _previous, ...rest } = workspace;
145819
+ return next.length > 0 ? { ...rest, memberAccessSnapshots: next } : rest;
145820
+ })
145821
+ };
145822
+ }),
145768
145823
  upsertSession: (session2) => mutate((snapshot2) => {
145769
145824
  const now2 = getServerNow();
145770
145825
  const nextSession = { ...session2, cachedAt: now2 };
@@ -221442,13 +221497,15 @@ const defaultSandboxDeps = () => ({
221442
221497
  configureExecutionProcess: async (pid, logger2) => {
221443
221498
  await applyExecutionProcessResourceProfile(pid, logger2);
221444
221499
  },
221445
- killPid: (pid, signal) => nodeProcessLive.kill(pid, signal ?? "SIGTERM")
221500
+ killPid: (pid, signal) => nodeProcessLive.kill(pid, signal ?? "SIGTERM"),
221501
+ listProcessGroup: nodeProcessLive.listProcessGroup
221446
221502
  });
221447
221503
  const toNodeProcess = (deps) => ({
221448
221504
  platform: deps.platform,
221449
221505
  spawn: deps.spawnProcess,
221450
221506
  spawnSync: nodeProcessLive.spawnSync,
221451
- kill: (pid, signal) => deps.killPid(pid, signal)
221507
+ kill: (pid, signal) => deps.killPid(pid, signal),
221508
+ ...deps.listProcessGroup ? { listProcessGroup: deps.listProcessGroup } : {}
221452
221509
  });
221453
221510
  const configureProcessBestEffort = (deps, logger2, prefix = "") => (pid) => tryPromise({
221454
221511
  try: () => deps.configureExecutionProcess(pid, logger2),
@@ -221463,6 +221520,8 @@ const configureProcessBestEffort = (deps, logger2, prefix = "") => (pid) => tryP
221463
221520
  function createSessionSandboxFactory(options2) {
221464
221521
  const deps = {
221465
221522
  ...defaultSandboxDeps(),
221523
+ // A substituted process table must not be judged by the host's.
221524
+ ...options2.deps?.killPid ? { listProcessGroup: void 0 } : {},
221466
221525
  ...options2.deps ?? {}
221467
221526
  };
221468
221527
  const facade = { logger: options2.logger, nodeProcess: toNodeProcess(deps) };
@@ -222127,15 +222186,11 @@ class Session2 extends EventEmitter {
222127
222186
  const failures = [];
222128
222187
  for (const outcome of await Promise.allSettled([
222129
222188
  activeProcess?.terminate(termination.force),
222130
- agentProcess?.terminate(termination.force)
222189
+ agentProcess?.terminate(termination.force),
222190
+ this.sandbox.terminate(termination.force)
222131
222191
  ])) {
222132
222192
  if (outcome.status === "rejected") failures.push(outcome.reason);
222133
222193
  }
222134
- try {
222135
- await this.sandbox.terminate(termination.force);
222136
- } catch (error2) {
222137
- failures.push(error2);
222138
- }
222139
222194
  await terminalsDisposed;
222140
222195
  for (const failure2 of failures) {
222141
222196
  this.logger.error(
@@ -225875,7 +225930,7 @@ async function applyTitleConfigOptions(options2) {
225875
225930
  );
225876
225931
  }
225877
225932
  }
225878
- const extractTitleChunkFromNotification = (notification, agentType) => {
225933
+ const extractTitleChunkFromNotification = (notification) => {
225879
225934
  const update2 = notification.update;
225880
225935
  if (update2.sessionUpdate !== "agent_message_chunk") {
225881
225936
  return null;
@@ -225885,12 +225940,6 @@ const extractTitleChunkFromNotification = (notification, agentType) => {
225885
225940
  return null;
225886
225941
  }
225887
225942
  const phase = parseLodyMessagePhase(update2._meta);
225888
- if (agentType === "codex") {
225889
- if (phase !== "final_answer") {
225890
- return null;
225891
- }
225892
- return content2.text;
225893
- }
225894
225943
  if (phase !== void 0 && phase !== "final_answer") {
225895
225944
  return null;
225896
225945
  }
@@ -225990,6 +226039,7 @@ const generateTitleIsolated = async (options2) => {
225990
226039
  const sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
225991
226040
  const tryGenerateWithArgs = async (extraArgs) => {
225992
226041
  let collectedText = "";
226042
+ let unphasedText = "";
225993
226043
  const startupStartedAt = Date.now();
225994
226044
  options2.logger.debug(
225995
226045
  `[title-generator] Starting isolated title ACP agent (cliType=${options2.cliType} agentType=${options2.agentType})`
@@ -226008,9 +226058,13 @@ const generateTitleIsolated = async (options2) => {
226008
226058
  terminalManager: noopTerminalManager,
226009
226059
  terminalEnabled: false,
226010
226060
  onUpdateMessage: (msg) => {
226011
- const text2 = extractTitleChunkFromNotification(msg, options2.agentType);
226061
+ const text2 = extractTitleChunkFromNotification(msg);
226012
226062
  if (text2) {
226013
- collectedText += text2;
226063
+ if (parseLodyMessagePhase(msg.update._meta) === "final_answer") {
226064
+ collectedText += text2;
226065
+ } else {
226066
+ unphasedText += text2;
226067
+ }
226014
226068
  }
226015
226069
  },
226016
226070
  onRequestPermission: async (_requestId, _request) => ({ outcome: { outcome: "cancelled" } }),
@@ -226040,16 +226094,19 @@ const generateTitleIsolated = async (options2) => {
226040
226094
  }
226041
226095
  }
226042
226096
  collectedText = "";
226097
+ unphasedText = "";
226043
226098
  options2.logger.debug(`[title-generator] Sending title prompt (acpSessionId=${acpSessionId})`);
226044
226099
  const response2 = await client2?.prompt(acpSessionId, prompt2);
226045
226100
  options2.logger.debug(
226046
226101
  `[title-generator] Title prompt returned (acpSessionId=${acpSessionId})`
226047
226102
  );
226048
226103
  const deadline = Date.now() + 1e4;
226049
- while (Date.now() < deadline && collectedText.trim() === "") {
226104
+ while (Date.now() < deadline && collectedText.trim() === "" && unphasedText.trim() === "") {
226050
226105
  await sleep2(100);
226051
226106
  }
226052
- const fromStream = sanitizeGeneratedTitle(collectedText);
226107
+ const fromStream = sanitizeGeneratedTitle(
226108
+ collectedText.trim() ? collectedText : unphasedText
226109
+ );
226053
226110
  if (fromStream) {
226054
226111
  return fromStream;
226055
226112
  }
@@ -229950,6 +230007,7 @@ class SessionExecutionService {
229950
230007
  }
229951
230008
  if (access2.outcome === "indeterminate") {
229952
230009
  outcome = "access-indeterminate";
230010
+ ctx.runtime.withdrawInboxExecution = true;
229953
230011
  yield* self2.tryPromise(async () => await options2.onAccessIndeterminate(access2));
229954
230012
  return void 0;
229955
230013
  }
@@ -230417,6 +230475,23 @@ class SessionExecutionService {
230417
230475
  await options2.sessionDoc.setStatus(SessionStatusFactory.idle());
230418
230476
  this.captureStatusChanged(options2.sessionId, "idle", void 0, "turn_failed");
230419
230477
  }
230478
+ /**
230479
+ * Fail a still-pending user turn whose Inbox input already ended in an earlier
230480
+ * execution. Lody never replays such an input, so the user resends instead.
230481
+ */
230482
+ async retireEndedInboxInputTurn(sessionId, sessionDoc, userTurnId) {
230483
+ const read2 = await (await createSessionBackend(sessionDoc)).readTurn(userTurnId);
230484
+ if (read2.state === "ready" && (read2.turn.role !== "user" || ["handled", "failed", "canceled", "delivery_unknown"].includes(read2.turn.status ?? ""))) {
230485
+ return;
230486
+ }
230487
+ await this.recordKnownChatFailure({
230488
+ sessionId,
230489
+ sessionDoc,
230490
+ userTurnId,
230491
+ reason: "turn_pre_prompt_failed",
230492
+ message: "This message was not started because an earlier attempt to run it already ended. Lody does not replay messages automatically; send it again to retry."
230493
+ });
230494
+ }
230420
230495
  formatMemoryPressureFailureMessage(result2) {
230421
230496
  if (result2.pressureReason === "darwin_pressure_critical") {
230422
230497
  return "The machine is at critical memory pressure — macOS is reclaiming memory and terminating processes to keep up. The turn was not started; free memory and retry.";
@@ -230974,6 +231049,15 @@ class SessionExecutionService {
230974
231049
  "Inbox preparation and release failed"
230975
231050
  );
230976
231051
  }
231052
+ if (!runtime.releaseFailure && userTurnId && inboxInput && !options2.inboxBatch && inbox.inputEnded(this.deps.workspaceId, sessionId, inboxInput.inputId)) {
231053
+ await this.retireEndedInboxInputTurn(sessionId, sessionDoc, userTurnId).catch(
231054
+ (retireCause) => this.deps.logger.warn(
231055
+ `[${sessionId}] Failed to retire ended Inbox input ${userTurnId}: ${formatErrorMessage(retireCause)}`
231056
+ )
231057
+ );
231058
+ span.end({ outcome: "inbox-input-ended" });
231059
+ return "inbox-input-ended";
231060
+ }
230977
231061
  span.end({ outcome: "inbox-prepare-failed" });
230978
231062
  throw cause;
230979
231063
  }
@@ -231394,7 +231478,13 @@ class SessionExecutionService {
231394
231478
  }
231395
231479
  span.end({ outcome, turnId });
231396
231480
  }
231397
- if (inboxInput) {
231481
+ const withdrawn = inboxInput && runtime.withdrawInboxExecution && !runtime.promptStarted && !runtime.cancelRequested && !runtime.releaseFailure && await runtime.inboxOwner.withdraw().catch((cause) => {
231482
+ this.deps.logger.warn(
231483
+ `[${sessionId}] Inbox withdraw failed; releasing instead: ${formatErrorMessage(cause)}`
231484
+ );
231485
+ return false;
231486
+ });
231487
+ if (inboxInput && !withdrawn) {
231398
231488
  if (inboxBatch)
231399
231489
  await (await createSessionBackend(sessionDoc)).applyHistoryAction({
231400
231490
  kind: "operation-batch-status",
@@ -235416,11 +235506,14 @@ class SessionDispatchWatcher {
235416
235506
  });
235417
235507
  let executionAccessPromise = accessPromise;
235418
235508
  let ownerRecheckStarted = false;
235509
+ this.recordMemberAccessVerdict(sessionId, requesterUserId, localProjectId, accessPromise);
235419
235510
  const localAccess = this.deps.accessPolicy && this.deps.currentUserId ? await runPromise(
235420
235511
  this.deps.accessPolicy.decide({
235421
235512
  workspaceId: this.deps.workspaceId,
235422
235513
  currentUserId: this.deps.currentUserId,
235423
- requesterUserId
235514
+ requesterUserId,
235515
+ machineId: this.deps.machineId,
235516
+ ...localProjectId ? { localProjectId } : {}
235424
235517
  })
235425
235518
  ) : { outcome: "remote" };
235426
235519
  if (!isActive()) {
@@ -235544,6 +235637,31 @@ class SessionDispatchWatcher {
235544
235637
  );
235545
235638
  });
235546
235639
  }
235640
+ /**
235641
+ * Maintain the member optimistic-allow cache from a backend verdict for a
235642
+ * requester who is not the machine owner. Only definitive verdicts write;
235643
+ * fire-and-forget like the owner recheck, so it never affects dispatch.
235644
+ */
235645
+ recordMemberAccessVerdict(sessionId, requesterUserId, localProjectId, verdict) {
235646
+ const record2 = this.deps.recordMemberAccessSnapshot;
235647
+ if (!record2 || !this.deps.currentUserId || requesterUserId === this.deps.currentUserId) {
235648
+ void verdict.catch(() => void 0);
235649
+ return;
235650
+ }
235651
+ void (async () => {
235652
+ const access2 = await verdict;
235653
+ if (access2.outcome === "indeterminate") return;
235654
+ await record2({
235655
+ requesterUserId,
235656
+ ...localProjectId ? { localProjectId } : {},
235657
+ verdict: access2.outcome
235658
+ });
235659
+ })().catch((error2) => {
235660
+ this.deps.logger.debug(
235661
+ `[${sessionId}] Failed to record member access verdict: ${formatErrorMessage(error2)}`
235662
+ );
235663
+ });
235664
+ }
235547
235665
  /**
235548
235666
  * Fork a background verify-with-retry for a transient access failure on `turnId`.
235549
235667
  *
@@ -235582,6 +235700,12 @@ class SessionDispatchWatcher {
235582
235700
  }).pipe(
235583
235701
  flatMap$1(
235584
235702
  () => sync(() => {
235703
+ this.recordMemberAccessVerdict(
235704
+ sessionId,
235705
+ verifyArgs.requesterUserId,
235706
+ verifyArgs.localProjectId,
235707
+ Promise.resolve({ outcome: "allowed" })
235708
+ );
235585
235709
  this.clearAccessFiberIf(sessionId, turnId);
235586
235710
  void this.enqueueSessionCheck(sessionId);
235587
235711
  })
@@ -235590,6 +235714,12 @@ class SessionDispatchWatcher {
235590
235714
  "AccessDenied",
235591
235715
  (denied) => promise(async () => {
235592
235716
  this.clearAccessFiberIf(sessionId, turnId);
235717
+ this.recordMemberAccessVerdict(
235718
+ sessionId,
235719
+ verifyArgs.requesterUserId,
235720
+ verifyArgs.localProjectId,
235721
+ Promise.resolve({ outcome: "denied", reason: denied.reason })
235722
+ );
235593
235723
  await this.markDispatchAccessDenied(sessionId, sessionDoc, turnId, denied.reason).catch(
235594
235724
  (error2) => {
235595
235725
  this.deps.logger.error(
@@ -238916,6 +239046,9 @@ class OperationInbox extends Service()("lody/orchestration/OperationInbox") {
238916
239046
  begin: fn("OperationInbox.begin")(function* (s2, id2) {
238917
239047
  return yield* withGate(s2, repo.begin(s2, id2, owner2));
238918
239048
  }),
239049
+ withdraw: fn("OperationInbox.withdraw")(function* (i, id2) {
239050
+ return yield* withGate(i, repo.withdraw(i, id2));
239051
+ }),
238919
239052
  pause: fn("OperationInbox.pause")(function* (s2, id2, ids, cancelId) {
238920
239053
  return yield* withGate(
238921
239054
  s2,
@@ -239113,6 +239246,19 @@ const makeExecutionOwner = fnUntraced(function* (initial, id2, initialBatch) {
239113
239246
  return start2();
239114
239247
  }).pipe(tap$1(() => batch ? inbox.settle(batch, "submitted") : void_$1)),
239115
239248
  finish: (outcome, cause) => releaseGate.withPermit(finishWork(outcome, cause)),
239249
+ /** Return an unsubmitted, resource-free execution's input to pending; false if ineligible. */
239250
+ withdraw: releaseGate.withPermit(
239251
+ gen(function* () {
239252
+ if (firstFinish || phase !== "prepared" || resources.size > 0 || rawRequests.length > 0)
239253
+ return false;
239254
+ if (!(yield* inbox.withdraw(input2, id2))) return false;
239255
+ firstFinish = void_;
239256
+ phase = "released";
239257
+ yield* detach;
239258
+ yield* succeed$5(released, void 0);
239259
+ return true;
239260
+ }).pipe(uninterruptible)
239261
+ ),
239116
239262
  awaitReleased: _await(released),
239117
239263
  detach,
239118
239264
  recover: (prepare, beforeRelease) => releaseGate.withPermit(recoverWork(prepare, beforeRelease).pipe(uninterruptible))
@@ -239136,6 +239282,7 @@ const makeInboxQueries = (sql) => {
239136
239282
  return {
239137
239283
  preferredHumanInput: (w2, s2) => io(() => sql.preferredHumanInput(w2, s2)),
239138
239284
  inputCancellationReason: (w2, s2, id2) => io(() => sql.inputCancellationReason(w2, s2, id2)),
239285
+ inputEnded: (w2, s2, id2) => io(() => sql.inputEnded(w2, s2, id2)),
239139
239286
  inputCanceled: (w2, s2, id2) => io(() => sql.inputCanceled(w2, s2, id2)),
239140
239287
  inputKind: (s2, id2) => io(() => sql.inputKind(s2, id2)),
239141
239288
  pauseTarget: (w2, s2, id2) => io(() => sql.pauseTarget(w2, s2, id2))
@@ -239161,6 +239308,7 @@ const inboxRepositoryLayer = (sql, ownedConnection = false) => {
239161
239308
  canSubmit: (s2, id2, o) => io(() => sql.canSubmit(s2, id2, o)),
239162
239309
  accept: (i) => io(() => sql.accept(i)),
239163
239310
  begin: (s2, id2, owner2) => io(() => sql.begin(s2, id2, owner2)),
239311
+ withdraw: (i, id2) => io(() => sql.withdraw(i, id2)),
239164
239312
  pause: (s2, id2, ids, cancelId) => io(() => sql.pause(s2, id2, ids, cancelId)),
239165
239313
  release: (e) => io(() => sql.release(e)),
239166
239314
  prepare: (i, o, id2) => io(() => sql.prepare(i, o, id2)),
@@ -239505,6 +239653,7 @@ const operationInboxLegacy = (options2) => {
239505
239653
  return {
239506
239654
  preferredHumanInput: (w2, s2) => runSync$1(queries.preferredHumanInput(w2, s2)),
239507
239655
  inputCancellationReason: (w2, s2, id2) => runSync$1(queries.inputCancellationReason(w2, s2, id2)),
239656
+ inputEnded: (w2, s2, id2) => runSync$1(queries.inputEnded(w2, s2, id2)),
239508
239657
  inputCanceled: (w2, s2, id2) => runSync$1(queries.inputCanceled(w2, s2, id2)),
239509
239658
  inputKind: (s2, id2) => runSync$1(queries.inputKind(s2, id2)),
239510
239659
  pauseTarget: (w2, s2, id2) => runSync$1(queries.pauseTarget(w2, s2, id2)),
@@ -239531,6 +239680,7 @@ const operationInboxLegacy = (options2) => {
239531
239680
  steer: (start2) => runtime.runPromise(owner2.steer(start2)),
239532
239681
  submit: (start2) => runtime.runPromise(owner2.submit(start2)),
239533
239682
  finish: (outcome, cause) => runtime.runPromise(owner2.finish(outcome, cause)),
239683
+ withdraw: () => runtime.runPromise(owner2.withdraw),
239534
239684
  detach: () => runtime.runSync(owner2.detach),
239535
239685
  retain: (resource) => runtime.runSync(owner2.retain(resource)),
239536
239686
  recover: (prepare, beforeRelease) => runtime.runPromise(
@@ -240038,6 +240188,13 @@ class InboxSqlite {
240038
240188
  ).get(workspaceId, sessionId, inputId);
240039
240189
  return row2?.cancellation_reason === "user_stop" ? "user_stop" : void 0;
240040
240190
  }
240191
+ /** True once an input finished or was canceled; `begin` never admits it again. */
240192
+ inputEnded(workspaceId, sessionId, inputId) {
240193
+ const row2 = this.db.prepare(
240194
+ "SELECT canceled,finished FROM inbox_inputs WHERE workspace_id=? AND session_id=? AND input_id=?"
240195
+ ).get(workspaceId, sessionId, inputId);
240196
+ return row2?.canceled === 1 || row2?.finished === 1;
240197
+ }
240041
240198
  inputCanceled(workspaceId, sessionId, inputId) {
240042
240199
  const row2 = this.db.prepare(
240043
240200
  "SELECT canceled FROM inbox_inputs WHERE workspace_id=? AND session_id=? AND input_id=?"
@@ -240158,6 +240315,38 @@ class InboxSqlite {
240158
240315
  ).run(executionId, "inputId" in s2 ? s2.inputId : null, s2.workspaceId, s2.sessionId, s2.userId).changes === 1;
240159
240316
  });
240160
240317
  }
240318
+ /**
240319
+ * Undo `begin` for an execution that submitted nothing and acquired nothing,
240320
+ * such as a turn whose machine access is still unverified. No release receipt is
240321
+ * written and the input stays unfinished, so the same execution id may begin it
240322
+ * again; prepared batch claims stay with the input.
240323
+ */
240324
+ withdraw(s2, executionId) {
240325
+ return this.transaction(() => {
240326
+ const current2 = this.state(s2);
240327
+ if (current2.active_execution !== executionId || current2.active_input !== s2.inputId)
240328
+ return false;
240329
+ const used = this.db.prepare(
240330
+ `SELECT 1 FROM inbox_releases WHERE workspace_id=? AND session_id=? AND execution_id=?
240331
+ UNION ALL SELECT 1 FROM inbox_resources WHERE workspace_id=? AND session_id=? AND execution_id=?
240332
+ UNION ALL SELECT 1 FROM inbox_batches WHERE workspace_id=? AND session_id=? AND input_id=? AND state NOT IN ('prepared','superseded')`
240333
+ ).get(
240334
+ s2.workspaceId,
240335
+ s2.sessionId,
240336
+ executionId,
240337
+ s2.workspaceId,
240338
+ s2.sessionId,
240339
+ executionId,
240340
+ s2.workspaceId,
240341
+ s2.sessionId,
240342
+ s2.inputId
240343
+ );
240344
+ if (used) return false;
240345
+ return this.db.prepare(
240346
+ "UPDATE inbox_sessions SET active_execution=NULL WHERE workspace_id=? AND session_id=? AND active_execution=?"
240347
+ ).run(s2.workspaceId, s2.sessionId, executionId).changes === 1;
240348
+ });
240349
+ }
240161
240350
  release(event) {
240162
240351
  const prior = this.db.prepare(
240163
240352
  "SELECT outcome,user_id FROM inbox_releases WHERE workspace_id=? AND session_id=? AND execution_id=?"
@@ -249825,7 +250014,7 @@ const session = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePrope
249825
250014
  withBuiltinDefaultTurnMode
249826
250015
  }, Symbol.toStringTag, { value: "Module" }));
249827
250016
  const LOCAL_POLICY_DENY_REASON = "not_visible";
249828
- function decideSessionAccessFromCatalog(snapshot2, input2) {
250017
+ function decideSessionAccessFromCatalog(snapshot2, input2, nowMs2) {
249829
250018
  const workspace = snapshot2.workspaces.find((item) => item.workspaceId === input2.workspaceId);
249830
250019
  if (!workspace) {
249831
250020
  return { outcome: "remote" };
@@ -249836,6 +250025,12 @@ function decideSessionAccessFromCatalog(snapshot2, input2) {
249836
250025
  if (workspace.state === "remote_missing") {
249837
250026
  return { outcome: "deny", reason: LOCAL_POLICY_DENY_REASON, source: "remote_missing" };
249838
250027
  }
250028
+ if (input2.requesterUserId !== input2.currentUserId) {
250029
+ const member2 = workspace.memberAccessSnapshots?.find(
250030
+ (entry) => entry.requesterUserId === input2.requesterUserId && entry.localProjectId === input2.localProjectId && entry.ownerUserId === input2.currentUserId && input2.machineId !== void 0 && entry.machineId === input2.machineId
250031
+ );
250032
+ return member2 && isMemberAccessSnapshotFresh(member2, nowMs2) ? { outcome: "allow", source: "member-cached" } : { outcome: "remote" };
250033
+ }
249839
250034
  const accessSnapshot = workspace.accessSnapshot;
249840
250035
  if (!accessSnapshot) {
249841
250036
  return { outcome: "remote" };
@@ -249843,15 +250038,12 @@ function decideSessionAccessFromCatalog(snapshot2, input2) {
249843
250038
  if (accessSnapshot.ownerUserId !== input2.currentUserId) {
249844
250039
  return { outcome: "remote" };
249845
250040
  }
249846
- if (input2.requesterUserId !== input2.currentUserId) {
249847
- return { outcome: "remote" };
249848
- }
249849
250041
  return { outcome: "allow", source: "owner-cached" };
249850
250042
  }
249851
- function makeSessionAccessPolicy(catalog) {
250043
+ function makeSessionAccessPolicy(catalog, now2 = getServerNow) {
249852
250044
  return {
249853
250045
  decide: (input2) => catalog.read().pipe(
249854
- map$4((snapshot2) => decideSessionAccessFromCatalog(snapshot2, input2)),
250046
+ map$4((snapshot2) => decideSessionAccessFromCatalog(snapshot2, input2, now2())),
249855
250047
  // Missing/corrupt catalogs already self-recover inside read(). Anything
249856
250048
  // that still fails (e.g. EACCES on ~/.lody) must not strand the turn in
249857
250049
  // a dispatch check that errors forever: degrade to the remote
@@ -256300,6 +256492,18 @@ class MessageHandler {
256300
256492
  }
256301
256493
  await this.recordWorkspaceAccessSnapshot(null);
256302
256494
  },
256495
+ // Members get a TTL-bounded cached allow refreshed by every confirmed
256496
+ // backend allow and cleared by a definitive deny.
256497
+ recordMemberAccessSnapshot: async ({ requesterUserId, localProjectId, verdict }) => await fileLocksLegacy.runPromise(
256498
+ this.localWorkspaceCatalog.recordMemberAccessSnapshot({
256499
+ workspaceId: this.workspaceId,
256500
+ requesterUserId,
256501
+ ...localProjectId ? { localProjectId } : {},
256502
+ ownerUserId: this.userId,
256503
+ machineId: this.machineId,
256504
+ verifiedAt: verdict === "allowed" ? new Date(getServerNow()).toISOString() : null
256505
+ })
256506
+ ),
256303
256507
  recordChatFailure: async (sessionDoc, reason, message) => await this.recordChatFailure(sessionDoc, reason, message),
256304
256508
  onStartupBootstrapComplete: () => {
256305
256509
  void this.resetMachineDisconnectedSessionsToIdle().catch((error2) => {
@@ -260446,7 +260650,7 @@ class MessageHandler {
260446
260650
  const { sessionId, name: name2, arguments: args2 } = request2.params;
260447
260651
  if (request2.workspaceId !== this.workspaceId || request2.machineId !== this.machineId || request2.ownerSessionId !== sessionId)
260448
260652
  throw new Error("Session tool scope mismatch");
260449
- const { executeDaemonSessionTool } = await import("./chunks/daemon-session-tools-DtsKbbQ9.js");
260653
+ const { executeDaemonSessionTool } = await import("./chunks/daemon-session-tools-Bgq2Cj2S.js");
260450
260654
  const sourceWorkdir = this.resolveSessionWorkspaceRoot(sessionId);
260451
260655
  if (!sourceWorkdir && args2 && typeof args2 === "object" && "attachments" in args2)
260452
260656
  throw new Error("Requester Session workspace is unavailable");
@@ -265106,6 +265310,7 @@ const LODY_GIT_CRED_CONTEXT_TOKEN_ENV = "LODY_GIT_CRED_CONTEXT_TOKEN";
265106
265310
  const LODY_GIT_CRED_CONTEXT_FILE_ENV = "LODY_GIT_CRED_CONTEXT_FILE";
265107
265311
  class GitCredentialContextError extends TaggedError("GitCredentialContextError") {
265108
265312
  }
265313
+ const SLOW_TOKEN_LOG_THRESHOLD_MS = 2e3;
265109
265314
  const createGitCredentialBrokerHandler = (options2) => {
265110
265315
  const handleRequest2 = async (req, res) => {
265111
265316
  const requestId = randomBytes$4(8).toString("hex");
@@ -265210,6 +265415,12 @@ const createGitCredentialBrokerHandler = (options2) => {
265210
265415
  typeof obj?.invalidatedPersonalToken === "string" ? obj.invalidatedPersonalToken : void 0
265211
265416
  );
265212
265417
  if (!isContextCurrent()) return;
265418
+ const elapsedMs = Date.now() - startedAt;
265419
+ if (elapsedMs >= SLOW_TOKEN_LOG_THRESHOLD_MS) {
265420
+ options2.logger.debug(
265421
+ `[git-cred-broker] ${JSON.stringify({ requestId, path: req.url, source: source2, code: "slow_ok", elapsedMs })}`
265422
+ );
265423
+ }
265213
265424
  res.writeHead(200, { "Content-Type": "application/json" });
265214
265425
  res.end(
265215
265426
  JSON.stringify(
@@ -265701,9 +265912,28 @@ function makePreparationControlLegacy(hardTtlMs, onExpired) {
265701
265912
  };
265702
265913
  }
265703
265914
  class SessionPreparationCleanupError extends Error {
265704
- constructor(cause) {
265915
+ retry;
265916
+ constructor(cause, retry2) {
265705
265917
  super(`Session preparation cleanup failed: ${formatErrorMessage(cause)}`, { cause });
265706
265918
  this.name = "SessionPreparationCleanupError";
265919
+ this.retry = retry2;
265920
+ }
265921
+ }
265922
+ class Retirement {
265923
+ constructor(release2) {
265924
+ this.release = release2;
265925
+ }
265926
+ attempt = null;
265927
+ failed = false;
265928
+ run() {
265929
+ if (this.attempt && !this.failed) return this.attempt;
265930
+ this.failed = false;
265931
+ const attempt2 = Promise.resolve().then(this.release);
265932
+ this.attempt = attempt2;
265933
+ attempt2.catch(() => {
265934
+ if (this.attempt === attempt2) this.failed = true;
265935
+ });
265936
+ return attempt2;
265707
265937
  }
265708
265938
  }
265709
265939
  class SessionPreparationService {
@@ -265720,7 +265950,7 @@ class SessionPreparationService {
265720
265950
  }
265721
265951
  if (current2 && current2.requesterUserId !== args2.requesterUserId) return "busy";
265722
265952
  let disposition = "accepted";
265723
- let predecessorCleanup = this.retiring.get(args2.sessionId) ?? null;
265953
+ let predecessorCleanup = this.retirementOf(args2.sessionId);
265724
265954
  const requesterRecord = Array.from(this.records.values()).find(
265725
265955
  (record22) => record22.requesterUserId === args2.requesterUserId
265726
265956
  );
@@ -265798,7 +266028,7 @@ class SessionPreparationService {
265798
266028
  }
265799
266029
  discard(sessionId) {
265800
266030
  const record2 = this.records.get(sessionId);
265801
- if (!record2) return this.retiring.get(sessionId) ?? null;
266031
+ if (!record2) return this.retirementOf(sessionId);
265802
266032
  return this.expire(record2, "expired");
265803
266033
  }
265804
266034
  peek(args2) {
@@ -265811,7 +266041,7 @@ class SessionPreparationService {
265811
266041
  claim(args2) {
265812
266042
  const record2 = this.records.get(args2.sessionId);
265813
266043
  if (!record2) {
265814
- return { status: "miss", cleanup: this.retiring.get(args2.sessionId) ?? null };
266044
+ return { status: "miss", cleanup: this.retirementOf(args2.sessionId) };
265815
266045
  }
265816
266046
  if (record2.requesterUserId !== args2.requesterUserId || record2.claimKey !== args2.claimKey || !record2.resource) {
265817
266047
  return { status: "miss", cleanup: this.expire(record2, "expired") };
@@ -265839,7 +266069,7 @@ class SessionPreparationService {
265839
266069
  for (const record2 of records) {
265840
266070
  void this.expire(record2, "expired");
265841
266071
  }
265842
- await Promise.allSettled(Array.from(this.retiring.values()));
266072
+ await Promise.allSettled(Array.from(this.retiring.values(), (retirement) => retirement.run()));
265843
266073
  }
265844
266074
  advance(record2, state2) {
265845
266075
  if (this.records.get(record2.sessionId) !== record2 || record2.state === "claimed") {
@@ -265859,28 +266089,32 @@ class SessionPreparationService {
265859
266089
  );
265860
266090
  void this.expire(record2, "failed");
265861
266091
  }
266092
+ /** The session's pending retirement, retrying it first if its last attempt failed. */
266093
+ retirementOf(sessionId) {
266094
+ return this.retiring.get(sessionId)?.run() ?? null;
266095
+ }
265862
266096
  expire(record2, state2) {
265863
- if (record2.retirementPromise) return record2.retirementPromise;
266097
+ if (record2.retirement) return record2.retirement.run();
265864
266098
  if (this.records.get(record2.sessionId) === record2) {
265865
266099
  this.records.delete(record2.sessionId);
265866
266100
  }
265867
266101
  record2.state = state2;
265868
266102
  const controlClosed = record2.control.close();
265869
266103
  const predecessor = this.retiring.get(record2.sessionId);
265870
- const cleanup = Promise.all([
265871
- predecessor,
265872
- controlClosed,
265873
- Promise.resolve().then(() => this.waitForCleanup(record2))
265874
- ]).then(() => void 0).then(() => {
265875
- if (this.retiring.get(record2.sessionId) === cleanup) {
266104
+ const retirement = new Retirement(async () => {
266105
+ try {
266106
+ await Promise.all([predecessor?.run(), controlClosed, this.waitForCleanup(record2)]);
266107
+ } catch (error2) {
266108
+ this.logger.debug(`Session preparation retirement failed: ${formatErrorMessage(error2)}`);
266109
+ throw error2;
266110
+ }
266111
+ if (this.retiring.get(record2.sessionId) === retirement) {
265876
266112
  this.retiring.delete(record2.sessionId);
265877
266113
  }
265878
266114
  });
265879
- void cleanup.catch((error2) => {
265880
- this.logger.debug(`Session preparation retirement failed: ${formatErrorMessage(error2)}`);
265881
- });
265882
- record2.retirementPromise = cleanup;
265883
- this.retiring.set(record2.sessionId, cleanup);
266115
+ record2.retirement = retirement;
266116
+ this.retiring.set(record2.sessionId, retirement);
266117
+ const cleanup = retirement.run();
265884
266118
  record2.abortController.abort();
265885
266119
  return cleanup;
265886
266120
  }
@@ -265888,17 +266122,26 @@ class SessionPreparationService {
265888
266122
  if (record2.cleanupPromise) return record2.cleanupPromise;
265889
266123
  const cleanup = Promise.resolve().then(() => resource.dispose());
265890
266124
  void cleanup.catch((error2) => {
266125
+ if (record2.cleanupPromise === cleanup) record2.cleanupPromise = void 0;
265891
266126
  this.logger.debug(`Failed to dispose session preparation: ${formatErrorMessage(error2)}`);
265892
266127
  });
265893
266128
  record2.cleanupPromise = cleanup;
265894
266129
  return cleanup;
265895
266130
  }
265896
266131
  async waitForCleanup(record2) {
265897
- const resource = await record2.resourcePromise.catch((error2) => {
265898
- if (error2 instanceof SessionPreparationCleanupError) throw error2;
265899
- return void 0;
265900
- });
265901
- if (resource) await this.scheduleCleanup(record2, resource);
266132
+ let resource;
266133
+ try {
266134
+ resource = await record2.resourcePromise;
266135
+ } catch (error2) {
266136
+ if (!(error2 instanceof SessionPreparationCleanupError)) return;
266137
+ if (record2.rollbackFailure !== error2 || !error2.retry) {
266138
+ record2.rollbackFailure = error2;
266139
+ throw error2;
266140
+ }
266141
+ await error2.retry();
266142
+ return;
266143
+ }
266144
+ await this.scheduleCleanup(record2, resource);
265902
266145
  }
265903
266146
  }
265904
266147
  const SPECULATIVE_WORKTREE_MARKER_VERSION = 1;
@@ -266744,6 +266987,7 @@ class SessionManager extends EventEmitter {
266744
266987
  let adoptionPromise = null;
266745
266988
  let started = false;
266746
266989
  let cleanupPromise = null;
266990
+ let sandboxCleanupFailed = false;
266747
266991
  const initialized2 = createDeferred();
266748
266992
  const sessionStart = createDeferred();
266749
266993
  const workspaceReady = createDeferred();
@@ -266780,7 +267024,10 @@ class SessionManager extends EventEmitter {
266780
267024
  await session2.terminate(true);
266781
267025
  } else if (sandbox) {
266782
267026
  await sandbox.terminate(true);
266783
- await sandbox.cleanup();
267027
+ const recover = sandboxCleanupFailed ? sandbox.recoverCleanup?.bind(sandbox) : void 0;
267028
+ sandboxCleanupFailed = true;
267029
+ await (recover ? recover() : sandbox.cleanup());
267030
+ sandboxCleanupFailed = false;
266784
267031
  }
266785
267032
  } catch (error2) {
266786
267033
  await credentials.release();
@@ -266801,7 +267048,8 @@ class SessionManager extends EventEmitter {
266801
267048
  await rm(provisionalWorkdir, { recursive: true, force: true });
266802
267049
  }
266803
267050
  })().catch((error2) => {
266804
- throw new SessionPreparationCleanupError(error2);
267051
+ cleanupPromise = null;
267052
+ throw new SessionPreparationCleanupError(error2, cleanup);
266805
267053
  });
266806
267054
  return cleanupPromise;
266807
267055
  };
@@ -271971,7 +272219,7 @@ const createNodeServicesForLease = async (sessionDoc, lease) => {
271971
272219
  };
271972
272220
  }
271973
272221
  };
271974
- const { createRoostSessionData: createRoostSessionData2 } = await import("./chunks/index-CU8Ghgdl.js");
272222
+ const { createRoostSessionData: createRoostSessionData2 } = await import("./chunks/index-a0nEYQPa.js");
271975
272223
  syncModelSummary = async () => {
271976
272224
  const read2 = latestModelTurnId ? await history.readTurn(latestModelTurnId) : { state: "missing" };
271977
272225
  const turn = read2.state === "ready" ? [read2.turn] : [];
@@ -306177,7 +306425,7 @@ async function runRender(file2, options2) {
306177
306425
  }
306178
306426
  data = parsed;
306179
306427
  } else {
306180
- const { resolveReviewBundle } = await import("./chunks/index-CtXoAGVz.js");
306428
+ const { resolveReviewBundle } = await import("./chunks/index-DnWlNrUf.js");
306181
306429
  const { collectBundleDiagnostics, hasErrorDiagnostics, createReviewBundleSnapshot } = await import("./chunks/index-DG2Ikd5A.js");
306182
306430
  const bundle = await resolveReviewBundle({
306183
306431
  reviewFilePath: inputPath,
@@ -306196,7 +306444,7 @@ async function runRender(file2, options2) {
306196
306444
  data = createReviewBundleSnapshot(bundle);
306197
306445
  }
306198
306446
  const { injectReviewSnapshot } = await import("./chunks/index-BeRTvYK1.js");
306199
- const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-DS24MU0Q.js");
306447
+ const { resolveReviewViewerTemplate } = await import("./chunks/review-viewer-Cm1OZZZc.js");
306200
306448
  const template = await resolveReviewViewerTemplate();
306201
306449
  const html = injectReviewSnapshot(template, data);
306202
306450
  const outputPath = options2.output ? path__default$1.resolve(options2.output) : defaultHtmlOutputPath(inputPath);