@cmmd-center/forge 0.13.10 → 0.13.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 (53) hide show
  1. package/dist/{BunHttpServer-C7-1JJ7b.cjs → BunHttpServer-C0oPP_3A.cjs} +4 -4
  2. package/dist/{BunHttpServer-CVPGkizx.mjs → BunHttpServer-FbbQmsl_.mjs} +4 -4
  3. package/dist/{BunServices-CPrZSIfE.mjs → BunServices-DC7nhA3w.mjs} +2 -2
  4. package/dist/{BunServices-Dl-3897e.cjs → BunServices-DkenwD7S.cjs} +2 -2
  5. package/dist/{Cache-Cf3ARNaS.cjs → Cache-BmGc_2BZ.cjs} +2 -2
  6. package/dist/{Cache-C9cwdCDt.mjs → Cache-D57GWb9-.mjs} +2 -2
  7. package/dist/{HttpServer-CggZzScj.cjs → HttpServer-BBzRBmVt.cjs} +2 -2
  8. package/dist/{HttpServer-COaBeM7-.mjs → HttpServer-Bclwvu0I.mjs} +2 -2
  9. package/dist/{NodeHttpServer-UQnvZnmu.mjs → NodeHttpServer-BJ4l15f-.mjs} +5 -5
  10. package/dist/{NodeHttpServer-Bi339XFk.cjs → NodeHttpServer-Cjd3bFrD.cjs} +5 -5
  11. package/dist/{NodePTY-UOaRyQsj.cjs → NodePTY-BehazUMi.cjs} +2 -2
  12. package/dist/{NodePTY-B1u6RHXk.mjs → NodePTY-BhezYy9H.mjs} +2 -2
  13. package/dist/{NodeServices-R0BZsZ72.cjs → NodeServices-BvdIc9Vh.cjs} +1 -1
  14. package/dist/{NodeServices-CKFIZVu5.cjs → NodeServices-CtC4a-h5.cjs} +2 -2
  15. package/dist/{NodeServices-z1Ac4lEO.mjs → NodeServices-D3IxOzHl.mjs} +2 -2
  16. package/dist/{NodeSqliteClient-CyJvo9y9.cjs → NodeSqliteClient-CfsdC3AB.cjs} +4 -4
  17. package/dist/{NodeSqliteClient-BrzqdOsF.mjs → NodeSqliteClient-md8xd9hY.mjs} +4 -4
  18. package/dist/{NodeTerminal-pYVVRTiD.mjs → NodeTerminal-9iHZIdt1.mjs} +2 -2
  19. package/dist/{NodeTerminal-B89MobLp.cjs → NodeTerminal-CO-i-6Je.cjs} +2 -2
  20. package/dist/{Path-BunGrE8F.cjs → Path-BQOH_fWQ.cjs} +7 -1
  21. package/dist/{Path-evOm-H0Q.mjs → Path-CZJ1gPPo.mjs} +2 -2
  22. package/dist/{SqlError-CezTFGvr.mjs → SqlError-BMBkezUO.mjs} +2 -2
  23. package/dist/{SqlError-1wIMjXAf.cjs → SqlError-g4pLzNWR.cjs} +2 -2
  24. package/dist/{SqliteClient-D3Yd3ZHn.mjs → SqliteClient-BKB695RF.mjs} +3 -3
  25. package/dist/{SqliteClient-bo7iui1t.cjs → SqliteClient-O1ABIngw.cjs} +3 -3
  26. package/dist/bin.cjs +1077 -214
  27. package/dist/bin.mjs +1072 -217
  28. package/dist/client/assets/{CanvasPanel-z0F-jdf1.js → CanvasPanel-BjHxCGlb.js} +2 -2
  29. package/dist/client/assets/{CompositeItem-P9UVXCKv.js → CompositeItem-D4LER3__.js} +2 -2
  30. package/dist/client/assets/{DiffPanel-aGVgMMDS.js → DiffPanel-D3btZGjW.js} +2 -2
  31. package/dist/client/assets/{DiffPanel.logic-CAL295cZ.js → DiffPanel.logic-DWcwE9Wj.js} +2 -2
  32. package/dist/client/assets/{DiffPanelShell-BLRSawDM.js → DiffPanelShell-oXHh--ae.js} +109 -109
  33. package/dist/client/assets/{DiffWorkerPoolProvider-tqcKyIAF.js → DiffWorkerPoolProvider-R9YPaaNy.js} +2 -2
  34. package/dist/client/assets/{PullRequestCodePanel-CYLmbiv7.js → PullRequestCodePanel-DTCc2TBl.js} +2 -2
  35. package/dist/client/assets/{Virtualizer-NYdNfon2.js → Virtualizer-arKGxq_z.js} +2 -2
  36. package/dist/client/assets/{canvasStateStore-CdQxd2aO.js → canvasStateStore-B5zd1u9r.js} +2 -2
  37. package/dist/client/assets/cmmdAuthRedirect-DeFAIQpj.js +2 -0
  38. package/dist/client/assets/index-Bn-ASQRG.css +1 -0
  39. package/dist/client/assets/index-C7YKDHIe.js +154 -0
  40. package/dist/client/assets/{popover-CUSlb3eK.js → popover-DM7IA0zJ.js} +2 -2
  41. package/dist/client/assets/src-LDQtPKQt.js +3 -0
  42. package/dist/client/assets/{toggle-BREV66nT.js → toggle-DVtS0BJJ.js} +2 -2
  43. package/dist/client/index.html +11 -11
  44. package/dist/forge-build.json +1 -1
  45. package/dist/runtimeBootstrap.cjs +1 -1
  46. package/dist/runtimeBootstrap.mjs +1 -1
  47. package/dist/{runtimeControlAuthority-B3kNmYRT.cjs → runtimeControlAuthority-BkVB0uAA.cjs} +17 -1
  48. package/dist/{runtimeControlAuthority-D_zKr_eI.mjs → runtimeControlAuthority-E_kdeD_P.mjs} +12 -2
  49. package/package.json +1 -1
  50. package/dist/client/assets/cmmdAuthRedirect-Bp1uQaQ9.js +0 -2
  51. package/dist/client/assets/index-Bx4rqeUi.css +0 -1
  52. package/dist/client/assets/index-fNpsmaLQ.js +0 -154
  53. package/dist/client/assets/src-BL-mOkT1.js +0 -3
package/dist/bin.mjs CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-C5ckVeoz.mjs";
4
4
  import { $ as encodeEffect, $a as make$39, $c as fromNullishOr, $i as spaced, $l as constTrue, $n as filterMapOrFail, $r as runPromise, $s as millis, $t as stringifyJson, Aa as Scope$1, Ai as void_$1, Al as format, An as cached$3, Ar as match$1, As as uninterruptibleMask, At as withConstructorDefault, Ba as CurrentLogSpans, Bc as add$2, Bi as counter, Bl as compose$1, Bn as catch_, Bo as addFinalizer, Br as orDie, Bs as make$42, Bt as head, C as Record, Ca as mergeAll, Ci as tx$1, Cn as andThen, Co as hasDies, Cr as logInfo, Cs as sleep$3, Ct as optionalKey, D as TaggedStruct, Da as succeedContext, Di as uninterruptibleMask$1, Dn as as$3, Do as interruptors, Ds as suspend, Dt as toCodecStringTree, E as TaggedErrorClass, Ea as succeed$2, Ei as uninterruptible, En as annotateSpans, Eo as interrupt$2, Er as map$3, Et as toCodecJson, F as UndefinedOr, Fa as forkUnsafe, Fc as isSuccess$1, Fi as withParentSpan, Fn as catchFilter, Fr as onError$1, Fs as MinimumTraceLevel, G as decodeExit, Ga as done, Gc as getUnsafe$1, Gi as withAttributes, Gl as isNullish, Gn as die$1, Gr as provideService, Gs as format$1, Gt as Transformation, H as declare, Hc as get$3, Hi as snapshotUnsafe, Hl as isNotNull, Hn as contextWith, Ho as as$2, Hr as promise, Ht as size$2, I as Union, Ia as make$37, Ii as withSpan, In as catchIf, Io as ConsoleRef, Ir as onExit, Is as NativeSpan, It as assign, J as decodeUnknownEffect, Ja as failCause$3, Jc as merge$2, Ji as exponential, Jn as exit, Jr as repeat, Js as hours, Jt as transformOrFail, K as decodeSync, Kc as make$35, Ki as addDelay, Kl as isObject$1, Ko as consolePretty$1, Kr as race, Ks as fromInput, L as Unknown, La as makeUnsafe$4, Lc as succeed$1, Li as withTracerEnabled, Ln as catchReason, Lo as CurrentLoggers$1, Ls as ParentSpan, Lt as append$1, M as URLFromString, Ma as addFinalizerExit, Mi as whileLoop, Mn as callback, Mo as pretty, Ms as void_$2, Na as close, Ni as withFiber$1, Nn as catchCause, No as prettyErrors, Nr as matchEffect, O as TemplateLiteral, Oa as sync$2, Ol as NodeInspectSymbol, On as asSome, Or as mapError, Os as sync, Ot as toJsonSchemaDocument, P as Undefined, Pa as closeUnsafe, Pc as isFailure$1, Pl as equals, Pn as catchDefect, Po as squash, Pr as never$1, Q as encode$4, Qc as flatten$1, Qi as recurs, Qn as filter$1, Qr as runForkWith, Qs as max, R as UnknownFromJsonString$1, Ra as provide$2, Rc as Reference$1, Ri as yieldNow, Rn as catchTag, Rr as onInterrupt, Rs as Tracer, Rt as drop$1, S as Option, Sa as merge$3, Sc as sort, Si as try_, Sn as all, Sr as logFatal, Ss as scopeAddFinalizerExit, St as optional$4, T as Struct, Ta as provideMerge, Ti as undefined_, Tl as withFiber, Tn as annotateLogs, To as hasInterruptsOnly, Tr as makeSpanScoped, Tt as tag, Ua as TracerTimingEnabled, Ui as timer, Ul as isNotNullish, Un as currentSpan, Ur as provide, Ut as toArray, V as brand, Va as MinimumLogLevel, Vc as empty$1, Vi as gauge, Vl as hasProperty, Vn as context, Vo as andThen$1, Vr as orElseSucceed, Vt as isEmpty$2, W as decodeEffect, Wa as _await, Wi as update$3, Wl as isNotUndefined, Wr as provideContext, Ws as days, X as decodeUnknownOption, Xc as firstSomeOf, Xl as cast, Xn as failCause$1, Xr as retry, Xt as onSome, Y as decodeUnknownExit, Yi as forever$2, Yn as fail, Yr as result, Z as decodeUnknownSync, Za as isDeferred, Zc as flatMap$2, Zi as passthrough$1, Zl as compose, Zr as runFork, Zt as parseJson$1, _ as NonEmptyArray, _a as effectContext, _i as tapErrorTag, _l as YieldableProto, _n as toUtc, _t as isSchemaError, a as DateTimeUtc, aa as Random$1, ac as toSeconds, al as isSome, an as value, ao as interrupt$3, ar as fnUntraced, at as isBetween, au as identity, b as NullOr, bi as timeoutOrElse, bn as acquireUseRelease, br as logDebug, bs as reportCauseUnsafe, bt as makeFilter, c as Defect, ca as Clock, cc as empty$3, ci as sleep$4, cl as none$4, cn as add$3, co as match$2, cr as forkChild, cs as flatMap$1, ct as isGreaterThanOrEqualTo, d as Exit, dc as flatMapNullishOr, di as succeedSome, dl as make$41, dn as isGreaterThanOrEqualTo$1, dr as forkScoped, ds as forkDetach, dt as isMaxLength, ea as catchDone, ec as min, el as fromUndefinedOr, er as filterOrFail, et as encodeSync, eu as constUndefined, fa as TaggedError, fc as fromIterable, fi as suspend$1, fn as make$38, fr as gen, ft as isMaxProperties, g as Never, ga as effect, gi as tapError, gl as PipeInspectableProto, gn as toEpochMillis, gr as interruptible, gt as isSchema, h as Literals, ha as buildWithMemoMap, hc as isReadonlyArrayNonEmpty, hi as tapCause, hn as toDate, hr as interrupt$1, ht as isPattern, i as Class, ic as toMillis, ii as scoped, il as isNone, in as make$40, io as failCause$2, ir as fn$1, it as is$1, ja as addFinalizer$1, jc as fail$1, jn as cachedWithTTL, jo as map$6, jr as matchCauseEffect, jt as withDecodingDefault, k as Trim, ka as unwrap, ki as useSpan, kl as toJson, kn as asVoid, ks as tracerLogger$1, kt as toType, l as Duration, la as currentTimeMillis, lc as ensure, li as succeed, ll as some, ln as distance, lr as forkDetach$1, ls as fnUntraced$1, lt as isInt, m as Literal, ma as build, mc as isArrayNonEmpty, mi as tap, mn as now, mo as StackTrace, mr as ignoreCause, ms as loggerMake, mt as isNonEmpty, n as ArraySchema, nc as seconds, nl as getOrNull, no as die$2, nt as encodeUnknownExit, nu as constant, o as DateTimeUtcFromString, oa as clamp, oc as zero, oi as service, ol as map$5, oo as isFailure, or as forEach, ot as isFinite$1, ou as pipe$1, p as Int$1, pa as CurrentMemoMap, pi as sync$1, pn as makeUnsafe$5, po as NoSuchElementError, pr as ignore, pt as isMinLength, q as decodeTo, qa as fail$3, qc as makeUnsafe$3, qi as both, ql as isTagged, qn as ensuring, qr as raceFirst, qs as fromInputUnsafe, r as Boolean$1, ra as isDoneCause, rc as times, ri as scope$1, rl as getOrUndefined, rn as makeFormatterDefault, ro as fail$2, rr as flatten$2, rt as fromJsonString, ru as dual, sa as parse$6, sc as allocate, si as serviceOption, sl as match, sn as encodeBase64Url$2, so as isSuccess, sr as forever$1, st as isGreaterThan, su as pipeArguments, t as Any, tc as minutes, ti as runPromiseWith, tl as getOrElse, tn as InvalidValue$1, to as succeed$3, tr as flatMap, ts as fiberInterrupt, tt as encodeUnknownEffect, tu as constVoid, u as ErrorClass, ua as Class$1, uc as filter, ui as succeedNone, ul as Number$2, un as formatIso, uo as void_$3, ur as forkIn, us as forever, ut as isLessThanOrEqualTo, v as NonEmptyString, va as effectDiscard, vi as timeout$1, vn as Transaction$1, vo as done$1, vr as isEffect, w as String$1, wa as provide$1, wi as txRetry, wl as isEffect$1, wn as annotateCurrentSpan, wo as hasInterrupts, wr as logWarning$1, wt as suspend$2, x as Number$1, xa as launch$1, xc as reverse, xi as tryPromise, xn as addFinalizer$2, xr as logError, xs as scope, xt as mutable, y as Null, ya as empty$4, yc as map$4, yi as timeoutOption, yn as acquireRelease, yo as fail$4, yr as log$4, yt as make$36, z as Void, za as CurrentLogAnnotations, zc as Service$1, zi as zipWith, zn as catchTags, zr as option, zs as externalSpan, zt as empty$2 } from "./Schema-DOBWF8gQ.mjs";
5
- import { $ as tick, $t as runIn, A as isStream, At as failCause$4, Bt as takeAll, C as fromEffect, D as fromQueue, Dt as bounded, E as fromPull, Et as makeUnsafe$6, Ft as offerUnsafe, G as runDrain, Gt as unbounded$1, H as provideContext$1, Ht as publish, I as merge$4, J as runForEachArray, Jt as makeUnsafe$7, K as runFold, Lt as shutdown$1, M as map$7, Mt as make$45, N as mapAccum, Nt as offer$1, Ot as end, P as mapEffect$1, Pt as offerAll, Q as takeWhile, Rt as sliding, S as fromChannel, St as fromTransform, T as fromPubSub, Tt as make$44, U as run$2, Ut as shutdown$2, Vt as unbounded$2, W as runCollect, Wt as subscribe$1, X as succeed$4, Xt as getCurrent, Yt as await_, Zt as interrupt$4, at as unwrap$1, b as flatMap$3, c as callback$1, ct as get$5, d as debounce$1, et as toAsyncIterable, f as decodeText$1, g as ensuring$1, gt as forEach$1, h as encodeText, ht as drain, j as make$43, k as fromSubscription, l as catchCause$1, lt as make$47, m as empty$5, ot as get$4, q as runForEach, r as FileSystem, st as make$46, t as Path$2, tt as toPull, u as concat$2, v as failCause$5, w as fromIterable$1, x as fromAsyncIterable, xt as fromSubscriptionArray, y as filter$2, yt as mapInput, z as onExit$1, zt as take$1 } from "./Path-evOm-H0Q.mjs";
6
- import { _ as ChildProcessSpawner, a as Stdio, g as make$48, o as make$49, r as isQuitError } from "./NodeTerminal-pYVVRTiD.mjs";
7
- import { n as layer$7 } from "./NodeServices-z1Ac4lEO.mjs";
8
- import { $ as file, A as SocketErrorReason, At as merge$5, Bt as set$3, Ct as HttpClientErrorSchema, Dt as empty$7, Ft as getAndSet, G as TracerPropagationEnabled, Ht as updateAndGet, I as HttpServerError, It as getUnsafe$2, J as transformResponse, K as filterStatusOk$1, Lt as make$50, Ot as fromInput$1, Pt as get$6, Q as empty$6, Rt as makeUnsafe$8, St as setBody, Tt as jsonUnsafe$1, U as layer$8, Vt as update$4, W as HttpClient, Z as symbol$1, _ as schemaBodyUrlParams, _t as acceptJson, a as withLogAddress, at as setCookie, bt as get$7, ct as stream, d as logger$1, dt as filterStatusOk, et as isHttpServerResponse, f as withLoggerDisabled, ft as matchStatus, g as schemaBodyJson, gt as schemaBodyJson$1, i as serve$1, it as schemaJson, kt as fromRecordUnsafe, lt as text$2, m as ParsedSearchParams, nt as raw, ot as setCookies, p as HttpServerRequest, q as retryTransient, rt as redirect, st as setHeader, t as HttpServer, tt as jsonUnsafe, u as cors, ut as uint8Array, v as schemaHeaders, vt as bearerToken, wt as isHttpClientError, xt as post, y as toURL, yt as bodyJson, z as RouteNotFound, zt as modify$3 } from "./HttpServer-COaBeM7-.mjs";
5
+ import { $ as takeWhile, A as fromSubscription, B as onExit$1, Bt as take$1, C as fromChannel, Ct as fromTransform, D as fromPull, Dt as makeUnsafe$6, E as fromPubSub, Et as make$44, F as mapEffect$1, Ft as offerAll, G as runCollect, Gt as subscribe$1, Ht as unbounded$2, I as mapError$1, It as offerUnsafe, J as runForEach, K as runDrain, Kt as unbounded$1, L as merge$4, M as make$43, N as map$7, Nt as make$45, O as fromQueue, Ot as bounded, P as mapAccum, Pt as offer$1, Qt as interrupt$4, Rt as shutdown$1, S as fromAsyncIterable, St as fromSubscriptionArray, T as fromIterable$1, U as provideContext$1, Ut as publish, Vt as takeAll, W as run$2, Wt as shutdown$2, Xt as await_, Y as runForEachArray, Yt as makeUnsafe$7, Z as succeed$4, Zt as getCurrent, _t as forEach$1, b as flatMap$3, bt as mapInput, c as callback$1, ct as make$46, d as debounce$1, en as runIn, et as tick, f as decodeText$1, g as ensuring$1, gt as drain, h as encodeText, j as isStream, jt as failCause$4, kt as end, l as catchCause$1, lt as get$5, m as empty$5, nt as toPull, ot as unwrap$1, q as runFold, r as FileSystem, st as get$4, t as Path$2, tt as toAsyncIterable, u as concat$2, ut as make$47, v as failCause$5, w as fromEffect, x as fromArray, y as filter$2, zt as sliding } from "./Path-CZJ1gPPo.mjs";
6
+ import { _ as ChildProcessSpawner, a as Stdio, g as make$48, o as make$49, r as isQuitError } from "./NodeTerminal-9iHZIdt1.mjs";
7
+ import { n as layer$7 } from "./NodeServices-D3IxOzHl.mjs";
8
+ import { $ as file, A as SocketErrorReason, At as merge$5, Bt as set$3, Ct as HttpClientErrorSchema, Dt as empty$7, Ft as getAndSet, G as TracerPropagationEnabled, Ht as updateAndGet, I as HttpServerError, It as getUnsafe$2, J as transformResponse, K as filterStatusOk$1, Lt as make$50, Ot as fromInput$1, Pt as get$6, Q as empty$6, Rt as makeUnsafe$8, St as setBody, Tt as jsonUnsafe$1, U as layer$8, Vt as update$4, W as HttpClient, Z as symbol$1, _ as schemaBodyUrlParams, _t as acceptJson, a as withLogAddress, at as setCookie, bt as get$7, ct as stream, d as logger$1, dt as filterStatusOk, et as isHttpServerResponse, f as withLoggerDisabled, ft as matchStatus, g as schemaBodyJson, gt as schemaBodyJson$1, i as serve$1, it as schemaJson, kt as fromRecordUnsafe, lt as text$2, m as ParsedSearchParams, nt as raw, ot as setCookies, p as HttpServerRequest, q as retryTransient, rt as redirect, st as setHeader, t as HttpServer, tt as jsonUnsafe, u as cors, ut as uint8Array, v as schemaHeaders, vt as bearerToken, wt as isHttpClientError, xt as post, y as toURL, yt as bodyJson, z as RouteNotFound, zt as modify$3 } from "./HttpServer-Bclwvu0I.mjs";
9
9
  import { _ as withDefault$3, a as all$1, c as logLevel, d as option$1, f as port, g as url$2, i as TrueValues, l as map$8, m as string$7, n as FalseValues, o as boolean$5, p as schema$2, r as Record$1, s as int$2, t as Boolean$2, u as number$2 } from "./Config-Cdfpphpo.mjs";
10
- import { a as make$51, i as keys, n as getOption, o as makeWith$1, r as invalidate, s as set$4, t as get$8 } from "./Cache-C9cwdCDt.mjs";
11
- import { S as layer$9, _ as defaultEscape, a as DeadlockError, b as makeCompiler$1, c as SqlError, d as UnknownError, g as custom, h as make$52, i as ConstraintError, l as SqlSyntaxError, m as SqlClient, n as AuthorizationError, o as LockTimeoutError, r as ConnectionError, s as SerializationError, t as AuthenticationError, u as StatementTimeoutError, v as defaultTransforms, y as fragment } from "./SqlError-CezTFGvr.mjs";
10
+ import { a as make$51, i as keys, n as getOption, o as makeWith$1, r as invalidate, s as set$4, t as get$8 } from "./Cache-D57GWb9-.mjs";
11
+ import { S as layer$9, _ as defaultEscape, a as DeadlockError, b as makeCompiler$1, c as SqlError, d as UnknownError, g as custom, h as make$52, i as ConstraintError, l as SqlSyntaxError, m as SqlClient, n as AuthorizationError, o as LockTimeoutError, r as ConnectionError, s as SerializationError, t as AuthenticationError, u as StatementTimeoutError, v as defaultTransforms, y as fragment } from "./SqlError-BMBkezUO.mjs";
12
12
  import { r as wrapper_default, t as Mime_default } from "./Mime-Dq9IoNiV.mjs";
13
13
  import { a as diag, c as SpanKind$1, d as TraceFlags, f as ROOT_CONTEXT, i as propagation, l as isSpanContextValid, m as DiagLogLevel, n as init_esm$1, o as context$1, p as createContextKey, r as trace, s as SpanStatusCode, t as esm_exports$1 } from "./esm-wLIF-u3N.mjs";
14
- import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicket, d as setRuntimeGrantToken, f as createReadOnceFileTokenAccessor, i as sendRuntimeControlHeartbeat, l as verifyCmmdRuntimeTicketRefreshProof, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, r as readRuntimeControlAuthority, s as verifyCmmdClaudeUsageTicket, t as bootstrapRuntimeControlAuthority, u as runtimeGrantToken } from "./runtimeControlAuthority-D_zKr_eI.mjs";
14
+ import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicket, d as runtimeGrantToken, f as setRuntimeGrantToken, i as sendRuntimeControlHeartbeat, l as verifyCmmdRuntimeTicketRefreshProof, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, p as createReadOnceFileTokenAccessor, r as readRuntimeControlAuthority, s as verifyCmmdClaudeUsageTicket, t as bootstrapRuntimeControlAuthority, u as verifyCmmdScopedRuntimePayload } from "./runtimeControlAuthority-E_kdeD_P.mjs";
15
15
  import { n as PtySpawnError, t as PtyAdapter } from "./PTY-BzdbZ0Ym.mjs";
16
16
  import { createRequire } from "node:module";
17
17
  import * as HE from "node:child_process";
@@ -65,12 +65,12 @@ import * as tls from "node:tls";
65
65
  import { performance as performance$1 } from "perf_hooks";
66
66
  import { execFile as execFile$1, spawn as spawn$1 } from "child_process";
67
67
  import * as NodeFSP from "node:fs/promises";
68
- import fs$1, { link, lstat, mkdir, mkdtemp, readFile as readFile$1, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
68
+ import fs$1, { chmod, link, lstat, mkdir, mkdtemp, readFile as readFile$1, readdir as readdir$1, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
69
69
  import { StringDecoder } from "string_decoder";
70
70
  import * as timers from "node:timers/promises";
71
71
  import { pipeline } from "node:stream/promises";
72
72
  import z$ from "http2";
73
- import { access, appendFile, chmod, copyFile, link as link$1, lstat as lstat$1, mkdir as mkdir$1, open, readFile as readFile$2, readdir as readdir$1, readlink, realpath as realpath$1, rename as rename$1, rm as rm$1, rmdir, stat as stat$1, symlink, unlink as unlink$1, writeFile as writeFile$1 } from "fs/promises";
73
+ import { access, appendFile, chmod as chmod$1, copyFile, link as link$1, lstat as lstat$1, mkdir as mkdir$1, open, readFile as readFile$2, readdir as readdir$2, readlink, realpath as realpath$1, rename as rename$1, rm as rm$1, rmdir, stat as stat$1, symlink, unlink as unlink$1, writeFile as writeFile$1 } from "fs/promises";
74
74
  import { createInterface as createInterface$1 } from "readline";
75
75
  import { AsyncLocalStorage as AsyncLocalStorage$1 } from "async_hooks";
76
76
  import { chromium } from "playwright";
@@ -17868,10 +17868,24 @@ const OrchestrationShellStreamItem = Union([Struct({
17868
17868
  kind: Literal("snapshot"),
17869
17869
  snapshot: OrchestrationShellSnapshot
17870
17870
  }), OrchestrationShellStreamEvent]);
17871
- const OrchestrationSubscribeThreadInput = Struct({ threadId: ThreadId });
17871
+ const OrchestrationSubscribeThreadInput = Struct({
17872
+ threadId: ThreadId,
17873
+ afterSequence: optionalKey(NonNegativeInt),
17874
+ requestCompletionMarker: optionalKey(Boolean$1),
17875
+ turnLimit: optionalKey(PositiveInt)
17876
+ });
17877
+ Struct({
17878
+ turnLimit: optionalKey(PositiveInt),
17879
+ beforeCursor: optionalKey(TrimmedNonEmptyString)
17880
+ });
17872
17881
  const OrchestrationThreadDetailSnapshot = Struct({
17873
17882
  snapshotSequence: NonNegativeInt,
17874
- thread: OrchestrationThread
17883
+ thread: OrchestrationThread,
17884
+ page: optional$4(Struct({
17885
+ beforeCursor: NullOr(TrimmedNonEmptyString),
17886
+ hasMore: Boolean$1,
17887
+ snapshotSequence: NonNegativeInt
17888
+ }))
17875
17889
  });
17876
17890
  const ProjectCreateCommand = Struct({
17877
17891
  type: Literal("project.create"),
@@ -18675,13 +18689,17 @@ const OrchestrationEvent = Union([
18675
18689
  payload: ThreadRateLimitSetPayload$1
18676
18690
  })
18677
18691
  ]);
18678
- const OrchestrationThreadStreamItem = Union([Struct({
18679
- kind: Literal("snapshot"),
18680
- snapshot: OrchestrationThreadDetailSnapshot
18681
- }), Struct({
18682
- kind: Literal("event"),
18683
- event: OrchestrationEvent
18684
- })]);
18692
+ const OrchestrationThreadStreamItem = Union([
18693
+ Struct({ kind: Literal("synchronized") }),
18694
+ Struct({
18695
+ kind: Literal("snapshot"),
18696
+ snapshot: OrchestrationThreadDetailSnapshot
18697
+ }),
18698
+ Struct({
18699
+ kind: Literal("event"),
18700
+ event: OrchestrationEvent
18701
+ })
18702
+ ]);
18685
18703
  const OrchestrationCommandReceiptStatus = Literals(["accepted", "rejected"]);
18686
18704
  const TurnCountRange = Struct({
18687
18705
  fromTurnCount: NonNegativeInt,
@@ -22792,6 +22810,8 @@ const ServerConfig$1 = Struct({
22792
22810
  availableEditors: ArraySchema(EditorId),
22793
22811
  shellRevealInFileManager: optionalKey(Boolean$1),
22794
22812
  shellRevealInFileManagerKind: optionalKey(FileManagerRevealKind),
22813
+ threadResumeCompletionMarker: optionalKey(Boolean$1),
22814
+ threadSnapshotPagination: optionalKey(Boolean$1),
22795
22815
  observability: ServerObservability,
22796
22816
  settings: ServerSettings
22797
22817
  });
@@ -66002,7 +66022,7 @@ function normalizeNumberish(value) {
66002
66022
  }
66003
66023
  //#endregion
66004
66024
  //#region package.json
66005
- var version$1 = "0.13.10";
66025
+ var version$1 = "0.13.12";
66006
66026
  //#endregion
66007
66027
  //#region src/sentry.ts
66008
66028
  const SERVER_APP_NAME = "forge-server";
@@ -66429,6 +66449,29 @@ function accessTokenExpiresInSeconds(remainingMillis) {
66429
66449
  return Math.max(0, Math.floor(remainingMillis / 1e3));
66430
66450
  }
66431
66451
  //#endregion
66452
+ //#region src/auth/commandBootstrapClient.ts
66453
+ const BOOTSTRAP_CLIENT_LABELS = {
66454
+ desktop: "Forge Desktop Cloud",
66455
+ mobile: "Forge Mobile Cloud"
66456
+ };
66457
+ /**
66458
+ * Client metadata for a bearer bootstrap session.
66459
+ *
66460
+ * The declared device type wins over the one inferred from the user agent. A
66461
+ * React Native fetch sends whatever the platform's networking stack sends, and
66462
+ * on iOS that is a CFNetwork string no user-agent heuristic reads as a phone.
66463
+ */
66464
+ function resolveCommandBootstrapClient(input) {
66465
+ const deviceType = input.clientDeviceType ?? "desktop";
66466
+ return {
66467
+ ...deriveAuthClientMetadata({
66468
+ request: input.request,
66469
+ label: BOOTSTRAP_CLIENT_LABELS[deviceType]
66470
+ }),
66471
+ deviceType
66472
+ };
66473
+ }
66474
+ //#endregion
66432
66475
  //#region ../../packages/shared/src/cmmdIdentity.ts
66433
66476
  const MAX_CMMD_AVATAR_URL_LENGTH = 256 * 1024;
66434
66477
  const DATA_IMAGE_AVATAR_PATTERN = /^data:image\/(?:avif|gif|jpeg|jpg|png|webp);base64,[a-z0-9+/=]+$/i;
@@ -66882,14 +66925,26 @@ function extractJwtExpiresAt(jwtToken) {
66882
66925
  }
66883
66926
  }
66884
66927
  /**
66885
- * Returns true when a stored JWT is past its expiry or has no decodable expiry.
66886
- * CMMD REST JWTs are short-lived bearer tokens, so unknown expiry is not safe.
66928
+ * How long before `exp` a stored JWT is already treated as spent.
66929
+ *
66930
+ * Comparing against `exp` exactly leaves a window where Forge sends a token it
66931
+ * believes is good and CMMD has already stopped accepting it: the clocks differ by a
66932
+ * little, the request spends time in flight, and the answer comes back 401 on a
66933
+ * session that had a perfectly good refresh token sitting next to it. Two
66934
+ * minutes is far inside the JWT's own lifetime, so this costs one early
66935
+ * rotation and removes the race.
66936
+ */
66937
+ const JWT_EXPIRY_SKEW_MS = 2 * 6e4;
66938
+ /**
66939
+ * Returns true when a stored JWT is at or near its expiry, or has no decodable
66940
+ * expiry. CMMD REST JWTs are short-lived bearer tokens, so unknown expiry is
66941
+ * not safe.
66887
66942
  */
66888
66943
  function isJwtExpiredForSession(sessionId) {
66889
66944
  if (!jwtCache.has(sessionId)) return false;
66890
66945
  const expiresAt = jwtExpiryCache.get(sessionId);
66891
66946
  if (!expiresAt) return true;
66892
- return new Date(expiresAt) <= /* @__PURE__ */ new Date();
66947
+ return new Date(expiresAt).getTime() - JWT_EXPIRY_SKEW_MS <= Date.now();
66893
66948
  }
66894
66949
  /**
66895
66950
  * Hydrate the in-memory cache from the configured persistence layer. Call once at server startup
@@ -67445,10 +67500,27 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId) => gen(function* () {
67445
67500
  * Retire refresh credentials copied into historical desktop bearer sessions.
67446
67501
  * Electron owns rotation for native desktop OAuth; Forge Local keeps only the
67447
67502
  * derived access authority needed by the current bearer session.
67448
- */
67449
- const clearDesktopBearerRefreshTokensForSubject = (subject) => gen(function* () {
67450
- const normalizedSubject = subject.trim();
67451
- if (!normalizedSubject) return;
67503
+ *
67504
+ * Scoped to the bootstrapping client, not to the whole subject. The phone and
67505
+ * Electron sign in as the same `user:<id>`, so a subject-wide sweep let one
67506
+ * desktop launch null out a phone's refresh token and end its session a day
67507
+ * later. Two clauses keep that from happening:
67508
+ *
67509
+ * - `client_device_type = 'desktop'`, which now excludes phones outright,
67510
+ * because the mobile app declares itself at bootstrap.
67511
+ * - the same recorded `client_user_agent` as the bootstrap doing the
67512
+ * retirement, which is the only thing that separates a phone row written
67513
+ * before that stamp existed from a real Electron row. Both are stamped
67514
+ * `desktop`; neither carries any other mark. An empty or unmatched user agent
67515
+ * retires nothing, which is the direction that keeps a live credential.
67516
+ *
67517
+ * Electron re-bootstraps under a stable user agent, so its own historical rows
67518
+ * still match and still get swept.
67519
+ */
67520
+ const clearDesktopBearerRefreshTokensForSubject = (input) => gen(function* () {
67521
+ const normalizedSubject = input.subject.trim();
67522
+ const normalizedUserAgent = input.clientUserAgent?.trim() ?? "";
67523
+ if (!normalizedSubject || !normalizedUserAgent) return;
67452
67524
  const rows = yield* (yield* SqlClient)`
67453
67525
  UPDATE command_access_tokens
67454
67526
  SET
@@ -67461,6 +67533,7 @@ const clearDesktopBearerRefreshTokensForSubject = (subject) => gen(function* ()
67461
67533
  AND role = 'owner'
67462
67534
  AND method = 'bearer-session-token'
67463
67535
  AND client_device_type = 'desktop'
67536
+ AND client_user_agent = ${normalizedUserAgent}
67464
67537
  )
67465
67538
  RETURNING session_id AS "sessionId"
67466
67539
  `.pipe(mapError(toPersistenceSqlError("CommandAccessTokenStore.clearDesktopBearerRefresh:query")));
@@ -67483,6 +67556,17 @@ const OAUTH_STATE_COOKIE = "forge_oauth_state";
67483
67556
  const OAUTH_VERIFIER_COOKIE = "forge_oauth_verifier";
67484
67557
  //#endregion
67485
67558
  //#region src/auth/http/cloudOwnerOAuth.ts
67559
+ const FORGE_CLOUD_SESSION_GRANT_COOKIE = "forge_cloud_session_grant";
67560
+ function resolveCloudOwnerSessionGrantCookie(input) {
67561
+ const token = input.token?.trim();
67562
+ if (!token || !input.expiresAt) return null;
67563
+ const expiresAt = new Date(input.expiresAt);
67564
+ if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= Date.now()) return null;
67565
+ return {
67566
+ token,
67567
+ expiresAt
67568
+ };
67569
+ }
67486
67570
  function validateCloudOwnerCallbackAssertion(input) {
67487
67571
  if (input.serverAuth.isForgeCloudShell?.() !== true) return succeed(null);
67488
67572
  if (!input.assertionToken || !input.serverAuth.verifyForgeCloudOwnerAssertion) return fail(new AuthError({
@@ -67546,8 +67630,19 @@ function cloudOwnerAssertionCookieOptions(assertion) {
67546
67630
  secure: true
67547
67631
  };
67548
67632
  }
67549
- function cloudOwnerOAuthResponse(homeUrl, assertion) {
67550
- return redirect(homeUrl, { status: 302 }).pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)), flatMap(setCookie(OAUTH_STATE_COOKIE, "", expiredCallbackCookie)), flatMap(setCookie(OAUTH_VERIFIER_COOKIE, "", expiredCallbackCookie)));
67633
+ function cloudOwnerSessionGrantCookieOptions(grant) {
67634
+ return {
67635
+ expires: grant.expiresAt,
67636
+ httpOnly: true,
67637
+ path: "/api/auth",
67638
+ sameSite: "lax",
67639
+ secure: true
67640
+ };
67641
+ }
67642
+ function cloudOwnerOAuthResponse(homeUrl, assertion, sessionGrant) {
67643
+ let response = redirect(homeUrl, { status: 302 }).pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
67644
+ if (sessionGrant) response = response.pipe(flatMap(setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, sessionGrant.token, cloudOwnerSessionGrantCookieOptions(sessionGrant))));
67645
+ return response.pipe(flatMap(setCookie(OAUTH_STATE_COOKIE, "", expiredCallbackCookie)), flatMap(setCookie(OAUTH_VERIFIER_COOKIE, "", expiredCallbackCookie)));
67551
67646
  }
67552
67647
  //#endregion
67553
67648
  //#region src/auth/http.ts
@@ -67597,6 +67692,56 @@ const authSessionRouteLayer = add$1("GET", "/api/auth/session", gen(function* ()
67597
67692
  }));
67598
67693
  return response;
67599
67694
  }));
67695
+ const authSessionRenewRouteLayer = add$1("POST", "/api/auth/session/renew", gen(function* () {
67696
+ const request = yield* HttpServerRequest;
67697
+ const serverAuth = yield* ServerAuth;
67698
+ if (serverAuth.isForgeCloudShell?.() !== true) return yield* new AuthError({
67699
+ message: "Session renewal is unavailable.",
67700
+ status: 401
67701
+ });
67702
+ const sessionGrant = request.cookies[FORGE_CLOUD_SESSION_GRANT_COOKIE]?.trim();
67703
+ if (!sessionGrant) return yield* new AuthError({
67704
+ message: "Session renewal is unavailable.",
67705
+ status: 401
67706
+ });
67707
+ const credentials = yield* resolveCmmdTokenBundleCredentials(yield* readCmmdJsonResponse(yield* tryPromise({
67708
+ try: () => fetch(resolveCommandAppEndpoint("/api/mcp-server/oauth/token"), {
67709
+ method: "POST",
67710
+ headers: { "content-type": "application/json" },
67711
+ body: JSON.stringify({
67712
+ grant_type: "forge_session_grant",
67713
+ grant_token: sessionGrant,
67714
+ client_id: "cmmd-forge-web"
67715
+ })
67716
+ }),
67717
+ catch: (cause) => new AuthError({
67718
+ message: "Failed to renew the Forge Cloud session with CMMD.",
67719
+ status: 500,
67720
+ cause
67721
+ })
67722
+ }), "Failed to renew the Forge Cloud session with CMMD."));
67723
+ const { cmmdIdentity, userId, patToStore } = credentials;
67724
+ if (!cmmdIdentity || !userId || !patToStore) return yield* new AuthError({
67725
+ message: "CMMD did not return a complete renewed Forge session.",
67726
+ status: 500
67727
+ });
67728
+ const assertion = yield* validateCloudOwnerCallbackAssertion({
67729
+ serverAuth,
67730
+ assertionToken: credentials.forgeOwnerAssertion,
67731
+ authenticatedUserId: userId
67732
+ });
67733
+ if (!assertion) return yield* die$1(/* @__PURE__ */ new Error("Expected a renewed Forge Cloud owner assertion."));
67734
+ yield* persistCloudOwnerArtifacts({
67735
+ assertion,
67736
+ accessToken: patToStore,
67737
+ identity: cmmdIdentity,
67738
+ jwtToken: credentials.jwtToStore,
67739
+ refreshToken: credentials.refreshToken,
67740
+ sessionGrant,
67741
+ sessionGrantExpiresAt: void 0
67742
+ });
67743
+ return yield* jsonUnsafe({ renewed: true }, { status: 200 }).pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
67744
+ }).pipe(catchTag("AuthError", (error) => respondToAuthError(error))));
67600
67745
  const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", gen(function* () {
67601
67746
  const request = yield* HttpServerRequest;
67602
67747
  const serverAuth = yield* ServerAuth;
@@ -67619,14 +67764,24 @@ const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", gen(function* ()
67619
67764
  sameSite: "lax",
67620
67765
  secure
67621
67766
  }));
67622
- if (cloudShell) response = yield* response.pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, "", {
67623
- expires: /* @__PURE__ */ new Date(0),
67624
- httpOnly: true,
67625
- maxAge: 0,
67626
- path: "/",
67627
- sameSite: "lax",
67628
- secure: true
67629
- }));
67767
+ if (cloudShell) {
67768
+ response = yield* response.pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, "", {
67769
+ expires: /* @__PURE__ */ new Date(0),
67770
+ httpOnly: true,
67771
+ maxAge: 0,
67772
+ path: "/",
67773
+ sameSite: "lax",
67774
+ secure: true
67775
+ }));
67776
+ response = yield* response.pipe(setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, "", {
67777
+ expires: /* @__PURE__ */ new Date(0),
67778
+ httpOnly: true,
67779
+ maxAge: 0,
67780
+ path: "/api/auth",
67781
+ sameSite: "lax",
67782
+ secure: true
67783
+ }));
67784
+ }
67630
67785
  response = yield* pipeExpireLegacySessionCookie(sessions.cookieName, succeed(response), { secure });
67631
67786
  if (sessions.cookieName !== "forge_session") response = yield* response.pipe(setCookie(BASE_SESSION_COOKIE_NAME, "", {
67632
67787
  expires: /* @__PURE__ */ new Date(0),
@@ -67649,7 +67804,8 @@ const CommandBootstrapInput = Struct({
67649
67804
  refreshToken: optionalKey(String$1),
67650
67805
  userId: optionalKey(Number$1),
67651
67806
  displayName: optionalKey(String$1),
67652
- user: optionalKey(Unknown)
67807
+ user: optionalKey(Unknown),
67808
+ clientDeviceType: optionalKey(Literals(["desktop", "mobile"]))
67653
67809
  });
67654
67810
  const CommandOAuthDeviceInput = Struct({
67655
67811
  client_id: String$1,
@@ -68197,13 +68353,10 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68197
68353
  method: "bearer-session-token",
68198
68354
  role: "owner",
68199
68355
  subject: sessionSubject,
68200
- client: {
68201
- ...deriveAuthClientMetadata({
68202
- request,
68203
- label: "Forge Desktop Cloud"
68204
- }),
68205
- deviceType: "desktop"
68206
- }
68356
+ client: resolveCommandBootstrapClient({
68357
+ request,
68358
+ clientDeviceType: payload.clientDeviceType
68359
+ })
68207
68360
  }).pipe(mapError((cause) => new AuthError({
68208
68361
  message: "Failed to issue authenticated bearer session.",
68209
68362
  cause
@@ -68213,7 +68366,10 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
68213
68366
  payload,
68214
68367
  cmmdIdentity: validated.cmmdIdentity
68215
68368
  });
68216
- if (!payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject(sessionSubject).pipe(mapError((cause) => new AuthError({
68369
+ if ((payload.clientDeviceType ?? "desktop") === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
68370
+ subject: sessionSubject,
68371
+ clientUserAgent: request.headers["user-agent"]
68372
+ }).pipe(mapError((cause) => new AuthError({
68217
68373
  message: "Failed to retire legacy desktop refresh credentials.",
68218
68374
  status: 500,
68219
68375
  cause
@@ -68281,6 +68437,14 @@ function completeCloudOwnerPasswordLogin(input) {
68281
68437
  authenticatedUserId: userId
68282
68438
  });
68283
68439
  if (!assertion) return yield* die$1(/* @__PURE__ */ new Error("Expected a Forge Cloud owner assertion in cloud shell mode."));
68440
+ const sessionGrant = resolveCloudOwnerSessionGrantCookie({
68441
+ token: credentials.sessionGrant,
68442
+ expiresAt: credentials.sessionGrantExpiresAt
68443
+ });
68444
+ if (!sessionGrant) return yield* new AuthError({
68445
+ message: "CMMD did not return a durable Forge Cloud session grant.",
68446
+ status: 500
68447
+ });
68284
68448
  yield* persistCloudOwnerArtifacts({
68285
68449
  assertion,
68286
68450
  accessToken: patToStore,
@@ -68290,10 +68454,12 @@ function completeCloudOwnerPasswordLogin(input) {
68290
68454
  sessionGrant: credentials.sessionGrant,
68291
68455
  sessionGrantExpiresAt: credentials.sessionGrantExpiresAt
68292
68456
  });
68293
- return yield* jsonUnsafe({
68457
+ let response = jsonUnsafe({
68294
68458
  authenticated: true,
68295
68459
  cmmdIdentity
68296
68460
  }, { status: 200 }).pipe(setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
68461
+ if (sessionGrant) response = response.pipe(flatMap(setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, sessionGrant.token, cloudOwnerSessionGrantCookieOptions(sessionGrant))));
68462
+ return yield* response;
68297
68463
  });
68298
68464
  }
68299
68465
  const authCmmdPasswordTokenRouteLayer = add$1("POST", "/api/auth/cmmd-password/token", gen(function* () {
@@ -68467,6 +68633,14 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", gen(functio
68467
68633
  assertionToken: forgeOwnerAssertion,
68468
68634
  authenticatedUserId: userId
68469
68635
  });
68636
+ const cloudSessionGrant = cloudOwnerAssertion ? resolveCloudOwnerSessionGrantCookie({
68637
+ token: sessionGrant,
68638
+ expiresAt: sessionGrantExpiresAt
68639
+ }) : null;
68640
+ if (cloudOwnerAssertion && !cloudSessionGrant) return yield* new AuthError({
68641
+ message: "CMMD did not return a durable Forge Cloud session grant.",
68642
+ status: 500
68643
+ });
68470
68644
  const sessionSubject = resolveCommandOAuthSubject(userId);
68471
68645
  const issued = cloudOwnerAssertion ? null : yield* sessions.issue({
68472
68646
  method: "browser-session-cookie",
@@ -68498,7 +68672,7 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", gen(functio
68498
68672
  else yield* persistLocalOAuthArtifacts(persistenceInput);
68499
68673
  const homeUrl = `${publicUrl}/`;
68500
68674
  const secureCookie = shouldUseSecureCookie(publicUrl);
68501
- if (cloudOwnerAssertion) return yield* cloudOwnerOAuthResponse(homeUrl, cloudOwnerAssertion);
68675
+ if (cloudOwnerAssertion) return yield* cloudOwnerOAuthResponse(homeUrl, cloudOwnerAssertion, cloudSessionGrant);
68502
68676
  if (!issued) return yield* die$1(/* @__PURE__ */ new Error("Expected a local session outside Forge Cloud."));
68503
68677
  return yield* pipeExpireLegacySessionCookie(sessions.cookieName, redirect(homeUrl, { status: 302 }).pipe(setCookie(sessions.cookieName, issued.token, {
68504
68678
  expires: toDate(issued.expiresAt),
@@ -70518,6 +70692,11 @@ function redeemCmmdSessionGrant(grantToken) {
70518
70692
  };
70519
70693
  }).catch(() => null);
70520
70694
  }
70695
+ const REJECTED_OAUTH_ERROR_CODES = new Set([
70696
+ "invalid_grant",
70697
+ "invalid_client",
70698
+ "invalid_request"
70699
+ ]);
70521
70700
  /**
70522
70701
  * Rotate a CMMD refresh token into a fresh access JWT.
70523
70702
  *
@@ -70530,7 +70709,7 @@ function redeemCmmdSessionGrant(grantToken) {
70530
70709
  * code are what identify the failure, and `invalid_grant` in particular means
70531
70710
  * the token was already spent, which is a different fix from a 500.
70532
70711
  */
70533
- function refreshCmmdJwt(refreshToken) {
70712
+ function refreshCmmdJwt(refreshToken, observer) {
70534
70713
  return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
70535
70714
  method: "POST",
70536
70715
  headers: { "Content-Type": "application/json" },
@@ -70541,7 +70720,9 @@ function refreshCmmdJwt(refreshToken) {
70541
70720
  })
70542
70721
  }).then(async (response) => {
70543
70722
  if (!response.ok) {
70544
- console.warn(`[cmmd-auth] CMMD refused a JWT rotation with ${response.status}${await readOAuthError(response)}`);
70723
+ const errorCode = await readOAuthErrorCode(response);
70724
+ if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
70725
+ console.warn(`[cmmd-auth] CMMD refused a JWT rotation with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
70545
70726
  return null;
70546
70727
  }
70547
70728
  const parsed = parseCmmdOAuthRefreshResponse(await response.json());
@@ -70553,20 +70734,73 @@ function refreshCmmdJwt(refreshToken) {
70553
70734
  });
70554
70735
  }
70555
70736
  /**
70556
- * The OAuth `error` code from a refusal, as a loggable suffix.
70737
+ * The OAuth `error` code from a refusal, lowercased, or null when there is none.
70557
70738
  *
70558
70739
  * Best effort by design: a body that is not JSON, or is already consumed, must
70559
- * not turn a logged failure into an unlogged one.
70740
+ * not turn a logged failure into an unlogged one. Null also reads correctly as
70741
+ * "no evidence the credential is dead", which is the safe answer for a caller
70742
+ * deciding whether to keep a refresh token.
70560
70743
  */
70561
- async function readOAuthError(response) {
70744
+ async function readOAuthErrorCode(response) {
70562
70745
  try {
70563
70746
  const code = (await response.json())["error"];
70564
- return typeof code === "string" && code.trim().length > 0 ? ` (${code.trim()})` : "";
70747
+ return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
70565
70748
  } catch {
70566
- return "";
70749
+ return null;
70567
70750
  }
70568
70751
  }
70569
70752
  //#endregion
70753
+ //#region src/command/cmmdRotationFence.ts
70754
+ /**
70755
+ * True when Forge must not rotate this session's CMMD credentials.
70756
+ *
70757
+ * Fails closed: if the session row cannot be read, assume the fence applies.
70758
+ * Spending a token Forge had no right to spend is worse than one extra
70759
+ * reconnect.
70760
+ */
70761
+ function isRotationFencedSession(sessionId) {
70762
+ if (readCommandRefreshTokenForSession(sessionId)) return succeed(false);
70763
+ return isDesktopBearerAuthSession(sessionId).pipe(orElseSucceed(() => true));
70764
+ }
70765
+ //#endregion
70766
+ //#region src/cmmdConnection/cmmdCredentialRejection.ts
70767
+ /**
70768
+ * How long a recorded rejection stays usable.
70769
+ *
70770
+ * Long enough to span the resolver ladder and the response that follows it,
70771
+ * short enough that it cannot outlive the credential it describes.
70772
+ */
70773
+ const REJECTION_TTL_MS = 6e4;
70774
+ const rejectedAt = /* @__PURE__ */ new Map();
70775
+ /** Record that CMMD refused this session's credential outright. */
70776
+ function noteCmmdCredentialRejected(sessionId) {
70777
+ rejectedAt.set(sessionId, Date.now());
70778
+ }
70779
+ /** Whether CMMD refused this session's credential recently enough to act on. */
70780
+ function hasCmmdCredentialRejection(sessionId) {
70781
+ const observedAt = rejectedAt.get(sessionId);
70782
+ if (observedAt === void 0) return false;
70783
+ if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
70784
+ rejectedAt.delete(sessionId);
70785
+ return false;
70786
+ }
70787
+ /** Drop a recorded rejection, once it has been acted on or superseded. */
70788
+ function forgetCmmdCredentialRejection(sessionId) {
70789
+ rejectedAt.delete(sessionId);
70790
+ }
70791
+ /**
70792
+ * Clear a session's CMMD credentials, but only if CMMD rejected them.
70793
+ *
70794
+ * This is the only door the 401 path may use. A resolver that returned null
70795
+ * because it was not allowed to try is a policy refusal and leaves the stored
70796
+ * credential exactly where it is, so the next request can rotate it.
70797
+ */
70798
+ const clearCmmdAuthAfterCmmdRejection = (sessionId) => gen(function* () {
70799
+ if (!hasCmmdCredentialRejection(sessionId)) return;
70800
+ yield* clearUnrecoverableCmmdAuthForSession(sessionId);
70801
+ forgetCmmdCredentialRejection(sessionId);
70802
+ });
70803
+ //#endregion
70570
70804
  //#region src/command/cmmdJwtResolver.ts
70571
70805
  function usableCmmdUserJwt(token) {
70572
70806
  return isCmmdUserAccessJwt(token) ? token : null;
@@ -70579,7 +70813,7 @@ function refreshOnceForSession(sessionId, refreshToken) {
70579
70813
  return promise(() => {
70580
70814
  const existing = inFlightRefreshes.get(sessionId);
70581
70815
  if (existing) return existing;
70582
- const pending = refreshCmmdJwt(refreshToken).catch(() => null).finally(() => {
70816
+ const pending = refreshCmmdJwt(refreshToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
70583
70817
  inFlightRefreshes.delete(sessionId);
70584
70818
  });
70585
70819
  inFlightRefreshes.set(sessionId, pending);
@@ -70695,7 +70929,7 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
70695
70929
  const normalizedSubject = subject?.trim() || null;
70696
70930
  if (!normalizedSubject && !forceRefresh) return succeed(null);
70697
70931
  return gen(function* () {
70698
- if (yield* isDesktopBearerAuthSession(sessionId).pipe(orElseSucceed(() => true))) return null;
70932
+ if (yield* isRotationFencedSession(sessionId)) return null;
70699
70933
  if (forceRefresh) {
70700
70934
  const forced = yield* tryOwnCredentialRecovery(sessionId, logPrefix);
70701
70935
  if (forced) return forced;
@@ -71454,15 +71688,25 @@ function clearCmmdAuthAfterFailure(sessionId) {
71454
71688
  })));
71455
71689
  }
71456
71690
  /**
71457
- * The single "no usable CMMD credential" response: clears whatever
71458
- * unrecoverable auth state this session is holding, then returns the
71459
- * canonical `CMMD_AUTH_REQUIRED` body. Every prologue path that decides the
71460
- * session has no way to reach CMMD — a missing PAT, a JWT that fails to
71461
- * resolve, a bespoke gate elsewhere in http.ts — emits this same response so
71462
- * the Shell only ever has one `CMMD_AUTH_REQUIRED` body to branch on.
71691
+ * The single "no usable CMMD credential" response: returns the canonical
71692
+ * `CMMD_AUTH_REQUIRED` body. Every prologue path that decides the session has
71693
+ * no way to reach CMMD — a missing PAT, a JWT that fails to resolve, a bespoke
71694
+ * gate elsewhere in http.ts — emits this same response so the Shell only ever
71695
+ * has one `CMMD_AUTH_REQUIRED` body to branch on.
71696
+ *
71697
+ * It used to delete the session's stored credentials on the way out, and that
71698
+ * was the wrong reading of its own argument. Most of the paths that reach here
71699
+ * are refusals, not rejections: the resolver was fenced, the subject was
71700
+ * missing, a gate said no. A phone would arrive holding a refresh token good
71701
+ * for the next three months, be refused by a rule, and have that token deleted
71702
+ * as "unrecoverable". The clear now happens only where CMMD has actually
71703
+ * refused the credential, which `clearCmmdAuthAfterCmmdRejection` checks.
71463
71704
  */
71464
71705
  function respondCmmdConnectionRequired(sessionId) {
71465
- return clearCmmdAuthAfterFailure(sessionId).pipe(as$3(buildProxyErrorResponse(new CmmdProxyAuthenticationRequiredError("CMMD connection required. Reconnect CMMD to continue."))));
71706
+ return clearCmmdAuthAfterCmmdRejection(sessionId).pipe(catch_((error) => logWarning$1("Failed to clear CMMD auth that CMMD itself rejected.", {
71707
+ cause: error,
71708
+ sessionId
71709
+ })), as$3(buildProxyErrorResponse(new CmmdProxyAuthenticationRequiredError("CMMD connection required. Reconnect CMMD to continue."))));
71466
71710
  }
71467
71711
  async function dispatchToProxy(method, jwt, cmmdPath, rawBody, orgId, searchParams) {
71468
71712
  return cmmdGateway.request({
@@ -83346,12 +83590,12 @@ const makePullRequestService = gen(function* () {
83346
83590
  }));
83347
83591
  const submitReview = (input) => requireProject(input).pipe(flatMap((project) => {
83348
83592
  const review = project.api.capabilities.review;
83349
- if (!review.verdicts.includes(input.verdict)) return refuse(`This host cannot ${VERDICT_LABELS[input.verdict]} a change request.`);
83350
- if (input.comments.length > 0 && !review.inlineComment) return refuse("This host cannot comment on a line of a change request.");
83351
- if (input.verdict !== "approve" && input.body.trim().length === 0 && input.comments.length === 0) return refuse("A review needs a summary or at least one comment.");
83593
+ if (!review.verdicts.includes(input.verdict)) return refuse$1(`This host cannot ${VERDICT_LABELS[input.verdict]} a change request.`);
83594
+ if (input.comments.length > 0 && !review.inlineComment) return refuse$1("This host cannot comment on a line of a change request.");
83595
+ if (input.verdict !== "approve" && input.body.trim().length === 0 && input.comments.length === 0) return refuse$1("A review needs a summary or at least one comment.");
83352
83596
  return viewerPermissionsOf(project, input, "submitReview").pipe(flatMap((viewer) => {
83353
- if (!viewer.verdicts.includes(input.verdict)) return refuse(`You need write access on this repository to ${VERDICT_LABELS[input.verdict]} a change request.`);
83354
- if (input.comments.length > 0 && !viewer.comment) return refuse("You need write access on this repository to comment on a line of a change request.");
83597
+ if (!viewer.verdicts.includes(input.verdict)) return refuse$1(`You need write access on this repository to ${VERDICT_LABELS[input.verdict]} a change request.`);
83598
+ if (input.comments.length > 0 && !viewer.comment) return refuse$1("You need write access on this repository to comment on a line of a change request.");
83355
83599
  return project.api.submitReview({
83356
83600
  projectId: project.project.id,
83357
83601
  cwd: project.project.workspaceRoot,
@@ -83748,7 +83992,7 @@ const toEntry = (input) => {
83748
83992
  ...input.item.reviewDecision === void 0 || input.item.reviewDecision === null ? {} : { reviewDecision: input.item.reviewDecision }
83749
83993
  };
83750
83994
  };
83751
- const refuse = (detail) => fail(new PullRequestOperationError({
83995
+ const refuse$1 = (detail) => fail(new PullRequestOperationError({
83752
83996
  operation: "submitReview",
83753
83997
  detail
83754
83998
  }));
@@ -93590,6 +93834,166 @@ const admitNormalizedDispatchCommand = (command, admit) => uninterruptibleMask$1
93590
93834
  };
93591
93835
  }));
93592
93836
  //#endregion
93837
+ //#region src/orchestration/threadDetailWindow.ts
93838
+ function earliest(current, candidate) {
93839
+ return current === void 0 || candidate < current ? candidate : current;
93840
+ }
93841
+ /**
93842
+ * Every turn in the thread, oldest first.
93843
+ *
93844
+ * Ordering is by the earliest timestamp across all four windowed collections
93845
+ * rather than by message order alone: a turn can open with an activity (a tool
93846
+ * call before any text) and a checkpoint-only turn has no message at all.
93847
+ * Ties break on turn id so the order is total and a cursor means the same thing
93848
+ * on every read.
93849
+ */
93850
+ function orderThreadTurns(thread) {
93851
+ const anchors = /* @__PURE__ */ new Map();
93852
+ const touch = (turnId, at, isUserMessage) => {
93853
+ if (turnId === null) return;
93854
+ const existing = anchors.get(turnId);
93855
+ anchors.set(turnId, {
93856
+ at: earliest(existing?.at, at),
93857
+ hasUserMessage: (existing?.hasUserMessage ?? false) || isUserMessage
93858
+ });
93859
+ };
93860
+ for (const message of thread.messages) touch(message.turnId, message.createdAt, message.role === "user");
93861
+ for (const activity of thread.activities) touch(activity.turnId, activity.createdAt, false);
93862
+ for (const plan of thread.proposedPlans) touch(plan.turnId, plan.createdAt, false);
93863
+ for (const checkpoint of thread.checkpoints) touch(checkpoint.turnId, checkpoint.completedAt, false);
93864
+ return Array.from(anchors, ([turnId, anchor]) => ({
93865
+ turnId,
93866
+ ...anchor
93867
+ })).toSorted((left, right) => left.at === right.at ? left.turnId.localeCompare(right.turnId) : left.at < right.at ? -1 : 1);
93868
+ }
93869
+ /**
93870
+ * Index of the first turn to include, counting back `turnLimit` user-anchored
93871
+ * turns from the end of `ordered`. Returns 0 when the thread holds fewer user
93872
+ * turns than the limit, which is how a short thread comes back whole.
93873
+ */
93874
+ function windowStartIndex(ordered, turnLimit) {
93875
+ let userTurns = 0;
93876
+ for (let index = ordered.length - 1; index >= 0; index -= 1) {
93877
+ const anchor = ordered[index];
93878
+ if (anchor !== void 0 && anchor.hasUserMessage) {
93879
+ userTurns += 1;
93880
+ if (userTurns === turnLimit) return index;
93881
+ }
93882
+ }
93883
+ return 0;
93884
+ }
93885
+ /**
93886
+ * The turns a windowed read returns.
93887
+ *
93888
+ * `beforeCursor` bounds the read to turns strictly older than a cursor an
93889
+ * earlier read handed out, which is what "load earlier" sends back. A cursor
93890
+ * naming a turn this thread no longer holds, after a revert, say, yields an
93891
+ * empty page rather than the whole thread: returning everything would be a
93892
+ * silent full download at the exact moment the client asked for less.
93893
+ */
93894
+ function selectWindowTurnIds(ordered, window) {
93895
+ const cursorIndex = window.beforeCursor === void 0 ? ordered.length : ordered.findIndex((anchor) => anchor.turnId === window.beforeCursor);
93896
+ if (cursorIndex < 0) return {
93897
+ turnIds: /* @__PURE__ */ new Set(),
93898
+ beforeCursor: null,
93899
+ hasMore: false,
93900
+ includeUnanchored: false
93901
+ };
93902
+ const candidates = ordered.slice(0, cursorIndex);
93903
+ const startIndex = window.turnLimit === void 0 ? 0 : windowStartIndex(candidates, window.turnLimit);
93904
+ const included = candidates.slice(startIndex);
93905
+ const oldest = included[0];
93906
+ return {
93907
+ turnIds: new Set(included.map((anchor) => anchor.turnId)),
93908
+ beforeCursor: oldest?.turnId ?? null,
93909
+ hasMore: startIndex > 0,
93910
+ includeUnanchored: startIndex === 0
93911
+ };
93912
+ }
93913
+ function keepsTurn(selection, turnId) {
93914
+ return turnId === null ? selection.includeUnanchored : selection.turnIds.has(turnId);
93915
+ }
93916
+ /**
93917
+ * Applies a turn window to a full thread detail read.
93918
+ *
93919
+ * A request with neither field is a full read and comes back untouched with no
93920
+ * page, which is exactly what a pre-pagination client already receives. Only
93921
+ * the four turn-scoped collections are filtered; thread metadata, the session,
93922
+ * and `latestTurn` describe the thread as a whole and stay as they are, so a
93923
+ * windowed snapshot still renders a correct header and a correct running state.
93924
+ */
93925
+ function windowThreadDetail(input) {
93926
+ const { thread, window, snapshotSequence } = input;
93927
+ if (window.turnLimit === void 0 && window.beforeCursor === void 0) return {
93928
+ thread,
93929
+ page: void 0
93930
+ };
93931
+ const selection = selectWindowTurnIds(orderThreadTurns(thread), window);
93932
+ return {
93933
+ thread: {
93934
+ ...thread,
93935
+ messages: thread.messages.filter((row) => keepsTurn(selection, row.turnId)),
93936
+ activities: thread.activities.filter((row) => keepsTurn(selection, row.turnId)),
93937
+ proposedPlans: thread.proposedPlans.filter((row) => keepsTurn(selection, row.turnId)),
93938
+ checkpoints: thread.checkpoints.filter((row) => keepsTurn(selection, row.turnId)),
93939
+ ...thread.turnModelSelections === void 0 ? {} : { turnModelSelections: thread.turnModelSelections.filter((row) => keepsTurn(selection, row.turnId)) }
93940
+ },
93941
+ page: {
93942
+ beforeCursor: selection.hasMore ? selection.beforeCursor : null,
93943
+ hasMore: selection.hasMore,
93944
+ snapshotSequence
93945
+ }
93946
+ };
93947
+ }
93948
+ /** Whether a subscriber's cursor can be served by replay rather than a snapshot. */
93949
+ function canResumeFromSequence(input) {
93950
+ if (input.afterSequence > input.currentSequence) return false;
93951
+ return input.currentSequence - input.afterSequence <= (input.maxGap ?? 2e3);
93952
+ }
93953
+ function snapshotCatchUp(snapshot) {
93954
+ return {
93955
+ items: [{
93956
+ kind: "snapshot",
93957
+ snapshot
93958
+ }],
93959
+ watermark: snapshot.snapshotSequence
93960
+ };
93961
+ }
93962
+ function replayCatchUp(events, afterSequence) {
93963
+ return {
93964
+ items: events.map((event) => ({
93965
+ kind: "event",
93966
+ event
93967
+ })),
93968
+ watermark: events.at(-1)?.sequence ?? afterSequence
93969
+ };
93970
+ }
93971
+ /**
93972
+ * Builds the frames that precede live events: either the replayed catch-up or
93973
+ * the snapshot, plus the completion marker when one was asked for.
93974
+ */
93975
+ const buildCatchUp = fn$1("threadSubscription.buildCatchUp")(function* (input, sources) {
93976
+ const afterSequence = input.afterSequence;
93977
+ if (afterSequence === void 0) return snapshotCatchUp(yield* sources.loadSnapshot);
93978
+ if (!canResumeFromSequence({
93979
+ afterSequence,
93980
+ currentSequence: yield* sources.currentSequence
93981
+ })) return snapshotCatchUp(yield* sources.loadSnapshot);
93982
+ const replayed = yield* runCollect(sources.replayEvents(afterSequence));
93983
+ return replayCatchUp(Array.from(replayed), afterSequence);
93984
+ });
93985
+ function makeThreadSubscriptionStream(input, sources) {
93986
+ return unwrap$1(gen(function* () {
93987
+ const liveEvents = yield* sources.subscribeLiveEvents;
93988
+ const catchUp = yield* buildCatchUp(input, sources);
93989
+ const marker = input.requestCompletionMarker === true ? [{ kind: "synchronized" }] : [];
93990
+ return concat$2(fromArray([...catchUp.items, ...marker]), liveEvents.pipe(filter$2((event) => event.sequence > catchUp.watermark), map$7((event) => ({
93991
+ kind: "event",
93992
+ event
93993
+ }))));
93994
+ }));
93995
+ }
93996
+ //#endregion
93593
93997
  //#region src/orchestration/bootstrapCommandCoordinator.ts
93594
93998
  const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
93595
93999
  const commandsByEngine = /* @__PURE__ */ new WeakMap();
@@ -101904,7 +102308,7 @@ function claudeReadinessReasonCode(reason) {
101904
102308
  //#endregion
101905
102309
  //#region src/egress/providerDeliveryReceipt.ts
101906
102310
  const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
101907
- const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
102311
+ const UUID$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
101908
102312
  const RFC3339$1 = /^(\d{4})-(\d{2})-(\d{2})T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
101909
102313
  const RECEIPT_KEYS = new Set([
101910
102314
  "receiptId",
@@ -101987,7 +102391,7 @@ function claudeAccountFields(value) {
101987
102391
  }
101988
102392
  function parseClaudeDeliveryIdentity(value) {
101989
102393
  const account = claudeAccountFields(value.providerAccountIdentity);
101990
- if (!account || !hasAllowedKeys(value, CLAUDE_IDENTITY_KEYS, CLAUDE_IDENTITY_OPTIONAL_KEYS) || value.kind !== "credential_generation" || value.provider !== "claude" || typeof value.credentialGenerationId !== "string" || !UUID.test(value.credentialGenerationId)) return null;
102394
+ if (!account || !hasAllowedKeys(value, CLAUDE_IDENTITY_KEYS, CLAUDE_IDENTITY_OPTIONAL_KEYS) || value.kind !== "credential_generation" || value.provider !== "claude" || typeof value.credentialGenerationId !== "string" || !UUID$1.test(value.credentialGenerationId)) return null;
101991
102395
  return {
101992
102396
  kind: "credential_generation",
101993
102397
  provider: "claude",
@@ -102754,7 +103158,10 @@ function extractClaudeProviderAccountId(response) {
102754
103158
  if (typeof response !== "object" || response === null || !("data" in response)) return null;
102755
103159
  const data = response.data;
102756
103160
  if (typeof data !== "object" || data === null) return null;
102757
- if ("connected" in data && data.connected === true) return extractClaudeCredentialGenerationId(response);
103161
+ if ("connected" in data && data.connected === true) {
103162
+ if ("providerAccountId" in data) return asNonEmptyString$1(data.providerAccountId) ?? null;
103163
+ return extractClaudeCredentialGenerationId(response);
103164
+ }
102758
103165
  if (!("account" in data)) return null;
102759
103166
  const account = data.account;
102760
103167
  if (typeof account !== "object" || account === null || !("providerAccountId" in account) || typeof account.providerAccountId !== "string") return null;
@@ -105726,6 +106133,14 @@ function makeRelayCommandInboxRepository(sql) {
105726
106133
  function isThreadDetailEvent(event) {
105727
106134
  return event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set" || event.type === "thread.rate-limit-set";
105728
106135
  }
106136
+ /**
106137
+ * Narrows the domain-event feed to one thread's stream-visible events. The same
106138
+ * predicate guards the live feed and the resume replay, so a subscriber cannot
106139
+ * be handed a different set of events depending on which path served it.
106140
+ */
106141
+ function isThreadDetailEventFor(threadId) {
106142
+ return (event) => event.aggregateKind === "thread" && event.aggregateId === threadId && isThreadDetailEvent(event);
106143
+ }
105729
106144
  const PROVIDER_STATUS_DEBOUNCE_MS = 200;
105730
106145
  function streamBoundedText(contents) {
105731
106146
  if (Buffer.byteLength(contents, "utf8") > 16777216) return fail(new BulkTransferError({
@@ -105996,6 +106411,32 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
105996
106411
  }, normalizedCommand) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105997
106412
  return startup.enqueueCommand(dispatchEffect).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105998
106413
  };
106414
+ /**
106415
+ * The thread snapshot a subscription falls back to, windowed when the
106416
+ * subscriber asked for a window. A missing thread is a failure rather
106417
+ * than an empty snapshot: the subscriber asked about a thread this server
106418
+ * does not have, and saying so is the only honest answer.
106419
+ */
106420
+ const loadThreadDetailSnapshot = (threadId, turnLimit) => gen(function* () {
106421
+ const [threadDetail, snapshotSequence] = yield* all([projectionSnapshotQuery.getThreadDetailById(threadId).pipe(mapError((cause) => new OrchestrationGetSnapshotError({
106422
+ message: `Failed to load thread ${threadId}`,
106423
+ cause
106424
+ }))), orchestrationEngine.getReadModel().pipe(map$3((readModel) => readModel.snapshotSequence))]);
106425
+ if (isNone(threadDetail)) return yield* new OrchestrationGetSnapshotError({
106426
+ message: `Thread ${threadId} was not found`,
106427
+ cause: threadId
106428
+ });
106429
+ const windowed = windowThreadDetail({
106430
+ thread: threadDetail.value,
106431
+ window: turnLimit === void 0 ? {} : { turnLimit },
106432
+ snapshotSequence
106433
+ });
106434
+ return {
106435
+ snapshotSequence,
106436
+ thread: windowed.thread,
106437
+ ...windowed.page === void 0 ? {} : { page: windowed.page }
106438
+ };
106439
+ });
105999
106440
  const loadServerConfig = gen(function* () {
106000
106441
  const keybindingsConfig = yield* keybindings.loadConfigState;
106001
106442
  const providers = yield* providerRegistry.getProviders;
@@ -106017,6 +106458,8 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
106017
106458
  shellRevealInFileManager: true,
106018
106459
  shellRevealInFileManagerKind: fileManagerRevealKind
106019
106460
  },
106461
+ threadResumeCompletionMarker: true,
106462
+ threadSnapshotPagination: true,
106020
106463
  observability: {
106021
106464
  logsDirectoryPath: config.logsDir,
106022
106465
  localTracingEnabled: true,
@@ -106114,27 +106557,15 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
106114
106557
  projectionSnapshotQuery
106115
106558
  })), flatMap$3((event) => isSome(event) ? succeed$4(event.value) : empty$5))))
106116
106559
  })), { "rpc.aggregate": "orchestration" }),
106117
- [ORCHESTRATION_WS_METHODS.subscribeThread]: (input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeThread, gen(function* () {
106118
- const [threadDetail, snapshotSequence] = yield* all([projectionSnapshotQuery.getThreadDetailById(input.threadId).pipe(mapError((cause) => new OrchestrationGetSnapshotError({
106119
- message: `Failed to load thread ${input.threadId}`,
106560
+ [ORCHESTRATION_WS_METHODS.subscribeThread]: (input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeThread, succeed(makeThreadSubscriptionStream(input, {
106561
+ subscribeLiveEvents: orchestrationEngine.subscribeDomainEvents().pipe(map$3((events) => events.pipe(filter$2(isThreadDetailEventFor(input.threadId))))),
106562
+ currentSequence: orchestrationEngine.getReadModel().pipe(map$3((readModel) => readModel.snapshotSequence)),
106563
+ loadSnapshot: loadThreadDetailSnapshot(input.threadId, input.turnLimit),
106564
+ replayEvents: (afterSequence) => orchestrationEngine.readEvents(afterSequence).pipe(filter$2(isThreadDetailEventFor(input.threadId)), mapError$1((cause) => new OrchestrationGetSnapshotError({
106565
+ message: `Failed to replay thread ${input.threadId}`,
106120
106566
  cause
106121
- }))), orchestrationEngine.getReadModel().pipe(map$3((readModel) => readModel.snapshotSequence))]);
106122
- if (isNone(threadDetail)) return yield* new OrchestrationGetSnapshotError({
106123
- message: `Thread ${input.threadId} was not found`,
106124
- cause: input.threadId
106125
- });
106126
- const liveStream = orchestrationEngine.streamDomainEvents.pipe(filter$2((event) => event.aggregateKind === "thread" && event.aggregateId === input.threadId && isThreadDetailEvent(event)), map$7((event) => ({
106127
- kind: "event",
106128
- event
106129
- })));
106130
- return concat$2(make$43({
106131
- kind: "snapshot",
106132
- snapshot: {
106133
- snapshotSequence,
106134
- thread: threadDetail.value
106135
- }
106136
- }), liveStream);
106137
- }), { "rpc.aggregate": "orchestration" }),
106567
+ })))
106568
+ })), { "rpc.aggregate": "orchestration" }),
106138
106569
  [WS_METHODS.serverGetConfig]: (_input) => observeRpcEffect(WS_METHODS.serverGetConfig, loadServerConfig, { "rpc.aggregate": "server" }),
106139
106570
  [WS_METHODS.serverRefreshProviders]: (_input) => observeRpcEffect(WS_METHODS.serverRefreshProviders, providerRegistry.refresh().pipe(map$3((providers) => ({ providers }))), { "rpc.aggregate": "server" }),
106140
106571
  [WS_METHODS.serverStartProviderAuth]: (input) => observeRpcEffect(WS_METHODS.serverStartProviderAuth, gen(function* () {
@@ -117598,8 +118029,8 @@ effectDiscard(runMigrations());
117598
118029
  //#endregion
117599
118030
  //#region src/persistence/Layers/Sqlite.ts
117600
118031
  const defaultSqliteClientLoaders = {
117601
- bun: () => import("./SqliteClient-D3Yd3ZHn.mjs"),
117602
- node: () => import("./NodeSqliteClient-BrzqdOsF.mjs")
118032
+ bun: () => import("./SqliteClient-BKB695RF.mjs"),
118033
+ node: () => import("./NodeSqliteClient-md8xd9hY.mjs")
117603
118034
  };
117604
118035
  const makeRuntimeSqliteLayer = fn$1("makeRuntimeSqliteLayer")(function* (config) {
117605
118036
  const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
@@ -183040,7 +183471,7 @@ var AM = class {
183040
183471
  return unlink$1(e);
183041
183472
  }
183042
183473
  list(e) {
183043
- return readdir$1(e);
183474
+ return readdir$2(e);
183044
183475
  }
183045
183476
  append(e, t, n) {
183046
183477
  return appendFile(e, t, {
@@ -183065,7 +183496,7 @@ var AM = class {
183065
183496
  return { mtimeMs: (await stat$1(e)).mtimeMs };
183066
183497
  }
183067
183498
  async listEntries(e) {
183068
- return (await readdir$1(e, { withFileTypes: !0 })).map((n) => ({
183499
+ return (await readdir$2(e, { withFileTypes: !0 })).map((n) => ({
183069
183500
  name: n.name,
183070
183501
  isDirectory: n.isDirectory(),
183071
183502
  isFile: n.isFile()
@@ -190295,7 +190726,7 @@ var Vce = {
190295
190726
  return lstat$1(e);
190296
190727
  },
190297
190728
  async readdir(e) {
190298
- return readdir$1(e, { withFileTypes: !0 });
190729
+ return readdir$2(e, { withFileTypes: !0 });
190299
190730
  },
190300
190731
  async unlink(e) {
190301
190732
  return unlink$1(e);
@@ -190352,7 +190783,7 @@ var Vce = {
190352
190783
  return link$1(e, t);
190353
190784
  },
190354
190785
  async chmod(e, t) {
190355
- return chmod(e, t);
190786
+ return chmod$1(e, t);
190356
190787
  },
190357
190788
  statSync(e) {
190358
190789
  let n = [];
@@ -190871,7 +191302,7 @@ async function hme(e) {
190871
191302
  await Promise.all(o.map(async (s) => {
190872
191303
  let i;
190873
191304
  try {
190874
- i = await readdir$1(s, { withFileTypes: !0 });
191305
+ i = await readdir$2(s, { withFileTypes: !0 });
190875
191306
  } catch {
190876
191307
  return;
190877
191308
  }
@@ -212268,60 +212699,14 @@ function hasConfiguredApexCredential(profiles) {
212268
212699
  }
212269
212700
  //#endregion
212270
212701
  //#region src/provider/claudeCredentialDirectory.ts
212271
- /**
212272
- * Per-account credential directories for Claude turns.
212273
- *
212274
- * Claude Code keeps conversation transcripts under
212275
- * `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
212276
- * under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
212277
- * credential file, so each Provider Account can hold its own login while every
212278
- * session file stays in one place and `--resume` keeps finding its target. See
212279
- * `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
212280
- *
212281
- * That variable is not documented. Under
212282
- * `docs/adr/unpublished-provider-surface-needs-a-fallback.md` the dependency is
212283
- * only allowed because absence degrades to a named behaviour that already
212284
- * works: a thread with no account runs on the Environment's own login. This
212285
- * module sets the variable only when the resolved directory holds a credential
212286
- * the CLI can use, because a missing file, or one whose access token a refusal
212287
- * has blanked, makes the CLI report logged out and a live turn would fail.
212288
- *
212289
- * Nothing writes these directories yet. #3049 adds the writer and keys each
212290
- * directory on the account identity CMMD writes on the delivery receipt it
212291
- * signs (`docs/adr/provider-account-identity-is-issued-by-cmmd.md`). Until then
212292
- * every runtime takes the `no-account` skip.
212293
- *
212294
- * Free of Effect services on purpose, for the same reason as
212295
- * `claudeCredentialContainment.ts`: ClaudeAdapter drags in Effect platform
212296
- * services, the browser manager and the server config, so a unit test that
212297
- * imports it cannot run in a worktree. This rule decides which account pays for
212298
- * a turn, so it has to stay directly testable.
212299
- */
212702
+ /** Per-account credentials share the existing Claude transcript directory. */
212300
212703
  /** Repoints `.credentials.json` without moving the transcripts. */
212301
212704
  const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
212302
212705
  /** The one file Claude Code writes into the credential directory. */
212303
212706
  const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
212304
- /**
212305
- * Runtime env var naming the Provider Account this runtime's Claude turns
212306
- * belong to. Nothing in Forge or CMMD sets it, so no runtime names an account
212307
- * and each keeps its one login.
212308
- *
212309
- * It is a bridge that will not get a writer. #3073 settled the key in
212310
- * `docs/adr/provider-account-identity-is-issued-by-cmmd.md`: the directory name
212311
- * comes from the identity CMMD issues and writes on the delivery receipt it
212312
- * signs, and this variable goes away. #3049 makes that change. The credential
212313
- * generation was ruled out as a key: it rotates on every reconnect, and CMMD
212314
- * uses it as the primary account's id, so keying on it would strand every
212315
- * thread pinned to that account on a directory that stops receiving deliveries.
212316
- *
212317
- * Tests reach the `credentialDirectory` parameter on
212318
- * `ClaudeAdapter.buildClaudeQueryEnv` directly.
212319
- */
212320
- const CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV = "FORGE_CLAUDE_PROVIDER_ACCOUNT_IDENTITY";
212321
212707
  /** Directory under the server's base directory that holds one directory per account. */
212322
212708
  const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
212323
212709
  const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
212324
- const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
212325
212710
  const DIRECTORY_NAME_HASH_LENGTH = 32;
212326
212711
  /**
212327
212712
  * Directory name for an account. A hash, so no email or display name reaches a
@@ -212330,11 +212715,12 @@ const DIRECTORY_NAME_HASH_LENGTH = 32;
212330
212715
  function claudeCredentialDirectoryName(accountIdentity) {
212331
212716
  return `account-${createHash("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
212332
212717
  }
212333
- /** The account this runtime's Claude turns belong to, or null while the variable is unset. */
212334
- function resolveClaudeProviderAccountIdentity(env) {
212335
- const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
212336
- if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
212337
- return identity;
212718
+ /** Only a matching CMMD delivery can select a credential directory. */
212719
+ function resolveSelectedClaudeAccountIdentity(selectedAccountId, binding) {
212720
+ if (selectedAccountId === void 0) return null;
212721
+ const identity = binding?.identity;
212722
+ if (identity?.kind !== "credential_generation" || identity.provider !== "claude" || identity.providerAccountIdentity !== selectedAccountId) throw new Error("The selected Claude account has no matching credential delivery. Reconnect that account and try again.");
212723
+ return identity.providerAccountIdentity;
212338
212724
  }
212339
212725
  /**
212340
212726
  * Where per-account credential directories live: `claude-credentials` under the
@@ -212361,29 +212747,19 @@ function resolveClaudeCredentialDirectory(input) {
212361
212747
  if (!root) return null;
212362
212748
  return join(root, claudeCredentialDirectoryName(input.accountIdentity));
212363
212749
  }
212364
- /**
212365
- * Decide whether this turn may point at a per-account credential directory.
212366
- *
212367
- * The cheap checks run first and the capability probe runs last, so a runtime
212368
- * with no usable credential never spawns the CLI. The probe's error channel is
212369
- * empty, so nothing here catches.
212370
- *
212371
- * A skip is not a fallback onto the default account. With no account the turn
212372
- * runs on the Environment's own login, unchanged. A thread pinned to an account
212373
- * whose credential is missing is refused by name under
212374
- * `docs/adr/provider-account-identity-is-issued-by-cmmd.md`, and #3049 turns
212375
- * `no-usable-credential`, and `capability-not-supported` for a pinned account,
212376
- * into those refusals. Until #3049 nothing names an account, so `no-account` is
212377
- * the only skip a runtime sees.
212378
- */
212750
+ /** Refuse selected accounts that cannot use their own credential directory. */
212379
212751
  function decideClaudeCredentialDirectory(input) {
212380
212752
  return gen(function* () {
212381
212753
  if (!input.accountIdentity) return skip("no-account");
212382
- if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
212754
+ if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return refuse("operator-override");
212383
212755
  const directory = resolveClaudeCredentialDirectory(input);
212384
- if (!directory) return skip("no-root");
212385
- if (!holdsUsableCredential(directory)) return skip("no-usable-credential");
212386
- return (yield* input.probeSupport) === "supported" ? set(directory) : skip("capability-not-supported");
212756
+ if (!directory) return refuse("no-root");
212757
+ if (!holdsUsableCredential(directory)) return refuse("no-usable-credential");
212758
+ if ((yield* input.probeSupport) === "supported") return set(directory);
212759
+ return input.primaryCredentialMatchesSelected === true ? {
212760
+ outcome: "skipped",
212761
+ reason: "same-primary-account"
212762
+ } : refuse("capability-not-supported");
212387
212763
  });
212388
212764
  }
212389
212765
  /**
@@ -212422,6 +212798,12 @@ function skip(reason) {
212422
212798
  reason
212423
212799
  };
212424
212800
  }
212801
+ function refuse(reason) {
212802
+ return {
212803
+ outcome: "refused",
212804
+ reason
212805
+ };
212806
+ }
212425
212807
  function claudeConfigDirectory(env) {
212426
212808
  const configured = env.CLAUDE_CONFIG_DIR?.trim();
212427
212809
  if (configured) return configured;
@@ -212431,6 +212813,17 @@ function claudeConfigDirectory(env) {
212431
212813
  return null;
212432
212814
  }
212433
212815
  }
212816
+ /** The fallback is allowed only for the exact credential bytes CMMD signed. */
212817
+ function primaryClaudeCredentialMatchesRevision(env, revision) {
212818
+ const directory = claudeConfigDirectory(env);
212819
+ if (!directory || !/^[a-f0-9]{64}$/.test(revision)) return false;
212820
+ try {
212821
+ const credential = readFileSync(join(directory, CLAUDE_CREDENTIAL_FILE_NAME), "utf8");
212822
+ return createHash("sha256").update(credential).digest("hex") === revision && hasAccessToken(JSON.parse(credential));
212823
+ } catch {
212824
+ return false;
212825
+ }
212826
+ }
212434
212827
  //#endregion
212435
212828
  //#region src/provider/personalClaudeInVm.ts
212436
212829
  /**
@@ -212563,13 +212956,106 @@ function withoutInheritedAnthropicCredentials(env, options) {
212563
212956
  * marker CMMD writes on disk (#3081), so a Sprite switched on in place, which
212564
212957
  * has the marker and no env var until it restarts, drops the override too.
212565
212958
  */
212566
- function claudeSubscriptionChildEnv(processEnv, credentialDirectory) {
212959
+ function claudeSubscriptionChildEnv(processEnv, credentialDirectory, selectedSubscription = false) {
212567
212960
  return {
212568
- ...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: personalClaudeInVmEnabled(processEnv) }),
212961
+ ...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: selectedSubscription || credentialDirectory !== null || personalClaudeInVmEnabled(processEnv) }),
212569
212962
  ...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
212570
212963
  };
212571
212964
  }
212572
212965
  //#endregion
212966
+ //#region src/provider/claudeSelectedCredentialStore.ts
212967
+ async function ensurePrivateDirectory(path) {
212968
+ await mkdir(path, {
212969
+ recursive: true,
212970
+ mode: 448
212971
+ });
212972
+ const info = await lstat(path);
212973
+ if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Unsafe credential directory.");
212974
+ await chmod(path, 448);
212975
+ }
212976
+ async function makeCapacity(root, target, active, maximum) {
212977
+ const entries = (await readdir$1(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^account-[a-f0-9]{32}$/.test(entry.name));
212978
+ if (entries.some((entry) => join(root, entry.name) === target)) return;
212979
+ const candidates = await Promise.all(entries.filter((entry) => !active.has(join(root, entry.name))).map(async (entry) => ({
212980
+ path: join(root, entry.name),
212981
+ modified: (await lstat(join(root, entry.name))).mtimeMs
212982
+ })));
212983
+ candidates.sort((a, b) => a.modified - b.modified);
212984
+ const required = entries.length - maximum + 1;
212985
+ if (required > candidates.length) throw new Error("All Claude credential slots are active. Stop a session before choosing another account.");
212986
+ for (const candidate of candidates.slice(0, Math.max(0, required))) await rm(candidate.path, { recursive: true });
212987
+ }
212988
+ function createClaudeSelectedCredentialStore(baseDir, options = {}) {
212989
+ const sessions = /* @__PURE__ */ new Map();
212990
+ const maximum = options.maxAccounts ?? 8;
212991
+ if (!Number.isSafeInteger(maximum) || maximum < 1) throw new Error("Invalid credential capacity.");
212992
+ let queue = Promise.resolve();
212993
+ async function install(threadId, acquired) {
212994
+ if (acquired.claims.threadId !== threadId) throw new Error("Credential thread mismatch.");
212995
+ const directory = resolveClaudeCredentialDirectory({
212996
+ env: process.env,
212997
+ baseDir,
212998
+ accountIdentity: acquired.claims.providerAccountId
212999
+ });
213000
+ if (!directory) throw new Error("Unsafe credential directory.");
213001
+ const binding = {
213002
+ runtimeId: acquired.claims.runtimeId,
213003
+ machineId: acquired.claims.machineId,
213004
+ credentialEpoch: acquired.claims.credentialEpoch,
213005
+ identity: {
213006
+ kind: "credential_generation",
213007
+ provider: "claude",
213008
+ credentialGenerationId: acquired.claims.generationId,
213009
+ providerAccountIdentity: acquired.claims.providerAccountId
213010
+ }
213011
+ };
213012
+ for (const prepared of sessions.values()) if (prepared.directory === directory && (!providerDeliveryBindingsEqual(prepared.binding, binding) || prepared.credentialRevisionHash !== acquired.claims.credentialRevisionHash)) throw new Error("The Claude account has an active session. Stop it before replacing its credentials.");
213013
+ const root = dirname(directory);
213014
+ await ensurePrivateDirectory(root);
213015
+ await makeCapacity(root, directory, new Set([...sessions.values()].map((entry) => entry.directory)), maximum);
213016
+ await ensurePrivateDirectory(directory);
213017
+ const temporary = join(directory, `.credential-${randomUUID()}.tmp`);
213018
+ try {
213019
+ await writeFile(temporary, acquired.credentialJson, {
213020
+ mode: 384,
213021
+ flag: "wx"
213022
+ });
213023
+ await rename(temporary, join(directory, ".credentials.json"));
213024
+ } finally {
213025
+ await rm(temporary, { force: true });
213026
+ }
213027
+ sessions.set(threadId, {
213028
+ directory,
213029
+ binding,
213030
+ accountKind: acquired.claims.accountKind,
213031
+ credentialRevisionHash: acquired.claims.credentialRevisionHash
213032
+ });
213033
+ return binding;
213034
+ }
213035
+ return {
213036
+ prepare(threadId, acquired) {
213037
+ const operation = queue.then(() => install(threadId, acquired));
213038
+ queue = operation.then(() => void 0, () => void 0);
213039
+ return operation;
213040
+ },
213041
+ read(threadId, binding) {
213042
+ const prepared = sessions.get(threadId);
213043
+ return prepared && providerDeliveryBindingsEqual(prepared.binding, binding) ? prepared : null;
213044
+ },
213045
+ release(threadId) {
213046
+ sessions.delete(threadId);
213047
+ }
213048
+ };
213049
+ }
213050
+ const stores = /* @__PURE__ */ new Map();
213051
+ function selectedClaudeCredentialStore(baseDir) {
213052
+ const existing = stores.get(baseDir);
213053
+ if (existing) return existing;
213054
+ const store = createClaudeSelectedCredentialStore(baseDir);
213055
+ stores.set(baseDir, store);
213056
+ return store;
213057
+ }
213058
+ //#endregion
212573
213059
  //#region src/provider/claudeCredentialDirectoryProbe.ts
212574
213060
  /**
212575
213061
  * Capability probe for `CLAUDE_SECURESTORAGE_CONFIG_DIR`.
@@ -214611,7 +215097,8 @@ function claudeForgeAttributionPrompt() {
214611
215097
  * turn, an API key is, so pointing the variable there would add a second source
214612
215098
  * of truth that a later reader has to rule out.
214613
215099
  */
214614
- function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory) {
215100
+ function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory, selectedSubscription = false) {
215101
+ if (selectedSubscription) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory, true);
214615
215102
  const baseEnv = scrubForgeProviderChildEnvSecrets(baseProcessEnv);
214616
215103
  const effectiveBaseUrl = resolveClaudeQueryBaseUrl(endpointProfile, claudeSettings, providerProxyCredential);
214617
215104
  if (!effectiveBaseUrl) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory);
@@ -216524,6 +217011,7 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216524
217011
  if (streamFiber && streamFiber.pollUnsafe() === void 0) yield* interrupt$4(streamFiber);
216525
217012
  try {
216526
217013
  context.query.close();
217014
+ selectedClaudeCredentialStore(serverConfig.baseDir).release(context.session.threadId);
216527
217015
  } catch (cause) {
216528
217016
  yield* emitRuntimeError(context, "Failed to close Claude runtime query.", cause);
216529
217017
  }
@@ -216835,6 +217323,43 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216835
217323
  sessionId: existingResumeSessionId ?? newSessionId
216836
217324
  };
216837
217325
  });
217326
+ const resolveSelectedCredentialDirectory = fn$1("resolveSelectedCredentialDirectory")(function* (input, resumeState, claudeBinaryPath) {
217327
+ const requestedAccountId = input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.providerAccountId : void 0;
217328
+ const selectedAccountIdentity = yield* try_({
217329
+ try: () => resolveSelectedClaudeAccountIdentity(requestedAccountId, resumeState?.providerDeliveryBinding),
217330
+ catch: () => new ProviderAdapterValidationError({
217331
+ provider: PROVIDER$11,
217332
+ operation: "startSession",
217333
+ issue: `The selected Claude account ${requestedAccountId} has no matching credential delivery. Reconnect that account and try again.`
217334
+ })
217335
+ });
217336
+ const preparedCredential = resumeState?.providerDeliveryBinding ? selectedClaudeCredentialStore(serverConfig.baseDir).read(input.threadId, resumeState.providerDeliveryBinding) : null;
217337
+ if (selectedAccountIdentity !== null && preparedCredential === null) return yield* new ProviderAdapterValidationError({
217338
+ provider: PROVIDER$11,
217339
+ operation: "startSession",
217340
+ issue: `The selected Claude account ${selectedAccountIdentity} has no prepared credential. Try again to reconnect it.`
217341
+ });
217342
+ const credentialDirectoryDecision = yield* decideClaudeCredentialDirectory({
217343
+ env: process.env,
217344
+ baseDir: serverConfig.baseDir,
217345
+ accountIdentity: selectedAccountIdentity,
217346
+ probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support,
217347
+ primaryCredentialMatchesSelected: preparedCredential?.accountKind === "primary" && primaryClaudeCredentialMatchesRevision(process.env, preparedCredential.credentialRevisionHash)
217348
+ });
217349
+ if (credentialDirectoryDecision.outcome === "refused") return yield* new ProviderAdapterValidationError({
217350
+ provider: PROVIDER$11,
217351
+ operation: "startSession",
217352
+ issue: `The selected Claude account ${selectedAccountIdentity} cannot run on this environment: ${credentialDirectoryDecision.reason}. Reconnect the account or choose another environment.`
217353
+ });
217354
+ if (credentialDirectoryDecision.outcome === "skipped") {
217355
+ const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
217356
+ yield* credentialDirectoryDecision.reason === "no-account" ? logDebug(skipLine) : logWarning$1(skipLine);
217357
+ }
217358
+ return {
217359
+ selectedAccountIdentity,
217360
+ claudeCredentialDirectory: credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null
217361
+ };
217362
+ });
216838
217363
  const startSession = fn$1("startSession")(function* (input) {
216839
217364
  const sessionProvider = yield* validateClaudeStartSessionProvider(input.provider);
216840
217365
  const startedAt = yield* nowIso;
@@ -216895,19 +217420,14 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216895
217420
  })));
216896
217421
  const claudeSettings = serverSettings.providers.claudeAgent;
216897
217422
  const claudeBinaryPath = claudeSettings.binaryPath;
216898
- const credentialDirectoryDecision = yield* decideClaudeCredentialDirectory({
216899
- env: process.env,
216900
- baseDir: serverConfig.baseDir,
216901
- accountIdentity: resolveClaudeProviderAccountIdentity(process.env),
216902
- probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support
216903
- });
216904
- if (credentialDirectoryDecision.outcome === "skipped") {
216905
- const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
216906
- yield* credentialDirectoryDecision.reason === "no-account" ? logDebug(skipLine) : logWarning$1(skipLine);
216907
- }
216908
- const claudeCredentialDirectory = credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null;
217423
+ const { selectedAccountIdentity, claudeCredentialDirectory } = yield* resolveSelectedCredentialDirectory(input, resumeState, claudeBinaryPath);
216909
217424
  const modelConfig = resolveClaudeSessionModelConfig(input.modelSelection, claudeSettings.endpointProfiles);
216910
- const providerProxyCredential = yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
217425
+ if (selectedAccountIdentity !== null && modelConfig.endpointProfile !== void 0) return yield* new ProviderAdapterValidationError({
217426
+ provider: PROVIDER$11,
217427
+ operation: "startSession",
217428
+ issue: "The selected Claude account cannot be used with a custom API endpoint. Choose a subscription model."
217429
+ });
217430
+ const providerProxyCredential = selectedAccountIdentity !== null ? null : yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
216911
217431
  const permissionMode = permissionModeForRuntimeMode(input.runtimeMode);
216912
217432
  const settings = claudeQuerySettings(modelConfig.thinking, modelConfig.fastMode);
216913
217433
  const workspacePreflight = input.cwd ? formatWorkspaceEnvironmentPreflightForAgent(runWorkspaceEnvironmentPreflight({ cwd: input.cwd })) : void 0;
@@ -216930,7 +217450,7 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216930
217450
  existingResumeSessionId,
216931
217451
  newSessionId,
216932
217452
  canUseTool,
216933
- env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential, claudeCredentialDirectory),
217453
+ env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential, claudeCredentialDirectory, selectedAccountIdentity !== null),
216934
217454
  browserManager,
216935
217455
  threadId,
216936
217456
  browserToolEnvironmentId,
@@ -216982,7 +217502,9 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216982
217502
  });
216983
217503
  attachClaudeStreamFiber(context$4, runFork);
216984
217504
  return { ...session };
216985
- });
217505
+ }, (effect, input) => effect.pipe(onExit((exit) => sync$1(() => {
217506
+ if (exit._tag === "Failure") selectedClaudeCredentialStore(serverConfig.baseDir).release(input.threadId);
217507
+ }))));
216986
217508
  const sendTurn = fn$1("sendTurn")(function* (input) {
216987
217509
  const context = yield* requireSession(input.threadId);
216988
217510
  const modelSelection = input.modelSelection?.provider === "claudeAgent" || input.modelSelection?.provider === "apex" || input.modelSelection?.provider === "cmmd" ? input.modelSelection : void 0;
@@ -231264,6 +231786,9 @@ const ProviderRollbackConversationInput = Struct({
231264
231786
  threadId: ThreadId,
231265
231787
  numTurns: NonNegativeInt
231266
231788
  });
231789
+ function selectedAccountGuardFields(providerAccountId) {
231790
+ return providerAccountId === void 0 ? {} : { providerAccountId };
231791
+ }
231267
231792
  function cursorRecord(value) {
231268
231793
  return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : {};
231269
231794
  }
@@ -231356,9 +231881,13 @@ const makeProviderService = fn$1("makeProviderService")(function* (options) {
231356
231881
  const runtimeEventPubSub = yield* unbounded$1();
231357
231882
  const assertProviderExecutable = options?.assertProviderExecutable ?? (() => succeed(null));
231358
231883
  const assertCurrentProviderDelivery = fn$1("assertCurrentProviderDelivery")(function* (input) {
231359
- const currentBinding = yield* assertProviderExecutable(input);
231360
- if (!currentBinding) return;
231361
231884
  const persistedBinding = getOrUndefined(yield* directory.getBinding(input.threadId));
231885
+ const providerAccountId = readPersistedProviderAccountId(persistedBinding?.runtimePayload);
231886
+ const currentBinding = yield* assertProviderExecutable({
231887
+ ...input,
231888
+ ...selectedAccountGuardFields(providerAccountId)
231889
+ });
231890
+ if (!currentBinding) return;
231362
231891
  if (!persistedBinding || persistedBinding.provider !== input.provider) return yield* toValidationError(input.operation, `Cannot verify the delivered provider identity for thread '${input.threadId}'.`);
231363
231892
  yield* bindProviderDeliveryToResumeCursor({
231364
231893
  operation: input.operation,
@@ -231413,10 +231942,12 @@ const makeProviderService = fn$1("makeProviderService")(function* (options) {
231413
231942
  if (!hasResumeCursor) return yield* toValidationError(input.operation, `Cannot recover thread '${input.binding.threadId}' because no provider resume state is persisted.`);
231414
231943
  const persistedCwd = readPersistedCwd(input.binding.runtimePayload);
231415
231944
  const persistedModelSelection = readPersistedModelSelection(input.binding.runtimePayload);
231945
+ const persistedProviderAccountId = readProviderAccountIdFromModelSelection(persistedModelSelection);
231416
231946
  const currentDeliveryBinding = yield* assertProviderExecutable({
231417
231947
  threadId: input.binding.threadId,
231418
231948
  provider: input.binding.provider,
231419
- operation: input.operation
231949
+ operation: input.operation,
231950
+ ...selectedAccountGuardFields(persistedProviderAccountId)
231420
231951
  });
231421
231952
  const boundResumeCursor = yield* bindProviderDeliveryToResumeCursor({
231422
231953
  operation: input.operation,
@@ -231541,7 +232072,8 @@ const makeProviderService = fn$1("makeProviderService")(function* (options) {
231541
232072
  currentBinding: yield* assertProviderExecutable({
231542
232073
  threadId,
231543
232074
  provider: effectiveProvider,
231544
- operation: "ProviderService.startSession"
232075
+ operation: "ProviderService.startSession",
232076
+ ...selectedAccountGuardFields(requestedAccountId)
231545
232077
  })
231546
232078
  });
231547
232079
  const session = yield* adapter.startSession({
@@ -231607,6 +232139,8 @@ const makeProviderService = fn$1("makeProviderService")(function* (options) {
231607
232139
  allowRecovery: true
231608
232140
  });
231609
232141
  const persistedBinding = getOrUndefined(yield* directory.getBinding(input.threadId));
232142
+ const persistedModelSelection = readPersistedModelSelection(persistedBinding?.runtimePayload);
232143
+ const nextModelSelection = input.modelSelection ?? persistedModelSelection;
231610
232144
  yield* assertProviderAccountBoundary({
231611
232145
  operation: "ProviderService.sendTurn",
231612
232146
  threadId: input.threadId,
@@ -231631,7 +232165,7 @@ const makeProviderService = fn$1("makeProviderService")(function* (options) {
231631
232165
  status: "running",
231632
232166
  ...turn.resumeCursor !== void 0 ? { resumeCursor: turn.resumeCursor } : {},
231633
232167
  runtimePayload: {
231634
- ...input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {},
232168
+ ...nextModelSelection !== void 0 ? { modelSelection: nextModelSelection } : {},
231635
232169
  activeTurnId: turn.turnId,
231636
232170
  lastRuntimeEvent: "provider.sendTurn",
231637
232171
  lastRuntimeEventAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -231887,6 +232421,201 @@ function makeProviderServiceLive(options) {
231887
232421
  return effect(ProviderService, makeProviderService(options));
231888
232422
  }
231889
232423
  //#endregion
232424
+ //#region src/runtimeAuth/cmmdClaudeSelectedTicket.ts
232425
+ const ACCOUNT = /^pai_v1_[A-Za-z0-9_-]{43}$/;
232426
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
232427
+ const REVISION_HASH = /^[a-f0-9]{64}$/;
232428
+ function hasValidTicketLifetime(claims, payload) {
232429
+ const now = Math.floor(Date.now() / 1e3);
232430
+ const issuedAt = payload.iat;
232431
+ const notBefore = payload.nbf;
232432
+ return typeof issuedAt === "number" && Number.isSafeInteger(issuedAt) && issuedAt <= now + 5 && typeof notBefore === "number" && Number.isSafeInteger(notBefore) && notBefore <= now && Number.isSafeInteger(claims.expiresAtSeconds) && claims.expiresAtSeconds - issuedAt <= 900;
232433
+ }
232434
+ function hasSelectedCredentialScope(claims) {
232435
+ return claims.scope.length === 1 && claims.scope[0] === "claude-credential-read";
232436
+ }
232437
+ function hasExpectedSelectedBinding(payload, expected) {
232438
+ return payload.provider_account_id === expected.providerAccountId && ACCOUNT.test(expected.providerAccountId) && payload.thread_id === expected.threadId && payload.operation_id === expected.operationId && UUID.test(expected.operationId);
232439
+ }
232440
+ function selectedCredentialClaims(payload) {
232441
+ const jti = payload.jti;
232442
+ const generationId = payload.credential_generation_id;
232443
+ const credentialRevisionHash = payload.credential_revision_hash;
232444
+ const accountKind = payload.account_kind;
232445
+ if (typeof jti !== "string" || !jti.trim() || typeof generationId !== "string" || !UUID.test(generationId) || typeof credentialRevisionHash !== "string" || !REVISION_HASH.test(credentialRevisionHash) || accountKind !== "primary" && accountKind !== "linked") return null;
232446
+ return {
232447
+ jti,
232448
+ generationId,
232449
+ credentialRevisionHash,
232450
+ accountKind
232451
+ };
232452
+ }
232453
+ function verifyCmmdClaudeSelectedTicket(token, expected) {
232454
+ const verified = verifyCmmdScopedRuntimePayload(token, expected, "forge-claude-credential-read");
232455
+ if (!verified) return null;
232456
+ const { claims, payload } = verified;
232457
+ if (!hasValidTicketLifetime(claims, payload)) return null;
232458
+ if (!hasSelectedCredentialScope(claims)) return null;
232459
+ if (!hasExpectedSelectedBinding(payload, expected)) return null;
232460
+ const selected = selectedCredentialClaims(payload);
232461
+ if (!selected) return null;
232462
+ return {
232463
+ ...claims,
232464
+ jti: selected.jti,
232465
+ providerAccountId: expected.providerAccountId,
232466
+ generationId: selected.generationId,
232467
+ credentialRevisionHash: selected.credentialRevisionHash,
232468
+ accountKind: selected.accountKind,
232469
+ threadId: expected.threadId,
232470
+ operationId: expected.operationId
232471
+ };
232472
+ }
232473
+ //#endregion
232474
+ //#region src/provider/claudeSelectedCredentialAcquisition.ts
232475
+ function readToken(response) {
232476
+ if (typeof response !== "object" || response === null || !("data" in response)) return null;
232477
+ const data = response.data;
232478
+ return typeof data === "object" && data !== null && "token" in data && typeof data.token === "string" ? data.token : null;
232479
+ }
232480
+ async function acquireSelectedClaudeCredential(expected, transport) {
232481
+ try {
232482
+ const token = readToken(await transport.requestTicket());
232483
+ const claims = token === null ? null : verifyCmmdClaudeSelectedTicket(token, expected);
232484
+ if (!token || !claims) throw new Error("invalid_ticket");
232485
+ const credentialJson = await transport.readCredential(token);
232486
+ if (Buffer.byteLength(credentialJson, "utf8") > 512e3 || createHash("sha256").update(credentialJson).digest("hex") !== claims.credentialRevisionHash || !hasUsableClaudeCredentialBlob(credentialJson)) throw new Error("invalid_credential");
232487
+ return {
232488
+ claims,
232489
+ credentialJson
232490
+ };
232491
+ } catch {
232492
+ throw new Error("The selected Claude account could not be prepared. Reconnect the account and try again.");
232493
+ }
232494
+ }
232495
+ //#endregion
232496
+ //#region src/provider/claudeSelectedCredentialPreparer.ts
232497
+ const REQUEST_TIMEOUT_MS = 1e4;
232498
+ const OWNER_TICKET_PATH = "/api/forge/context/providers/claude/runtime-read-ticket";
232499
+ const CREDENTIAL_READ_PATH = "/api/forge/context/auth-runtime-claude-credential";
232500
+ function isRuntimeTicketSession$1(sessionId) {
232501
+ return String(sessionId).startsWith("cmmd-runtime:");
232502
+ }
232503
+ function nonEmptyConfigString(value) {
232504
+ return value?.trim() || null;
232505
+ }
232506
+ function selectedRuntimeStrings(config) {
232507
+ const values = [
232508
+ nonEmptyConfigString(config.forgeCmmdRuntimeId),
232509
+ nonEmptyConfigString(config.forgeCmmdRuntimeMachineId),
232510
+ nonEmptyConfigString(config.forgeCmmdRuntimeOrgId),
232511
+ nonEmptyConfigString(config.forgeCmmdRuntimeUserId),
232512
+ nonEmptyConfigString(config.forgeCmmdRuntimePublicKey)
232513
+ ];
232514
+ if (values.includes(null)) return null;
232515
+ return {
232516
+ runtimeId: values[0],
232517
+ machineId: values[1],
232518
+ orgId: values[2],
232519
+ userId: values[3],
232520
+ publicKeyPem: values[4]
232521
+ };
232522
+ }
232523
+ function selectedCredentialEpoch(config) {
232524
+ const credentialEpoch = config.forgeCmmdRuntimeCredentialEpoch;
232525
+ return typeof credentialEpoch === "number" && Number.isSafeInteger(credentialEpoch) && credentialEpoch >= 0 ? credentialEpoch : null;
232526
+ }
232527
+ function selectedConfig(config) {
232528
+ if (config.forgeCmmdRuntimeMode !== true) return null;
232529
+ const strings = selectedRuntimeStrings(config);
232530
+ const credentialEpoch = selectedCredentialEpoch(config);
232531
+ if (strings === null || credentialEpoch === null) return null;
232532
+ return {
232533
+ ...strings,
232534
+ credentialEpoch
232535
+ };
232536
+ }
232537
+ async function boundedRuntimeTicketRequest(grant, runtimeId, request) {
232538
+ const response = await fetch(resolveCommandAppEndpoint(`/api/forge/runtimes/${encodeURIComponent(runtimeId)}/provider/claude/read-ticket`), {
232539
+ method: "POST",
232540
+ headers: {
232541
+ authorization: `Bearer ${grant}`,
232542
+ "content-type": "application/json"
232543
+ },
232544
+ body: JSON.stringify(request),
232545
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
232546
+ });
232547
+ if (!response.ok) throw new Error("selected_ticket_unavailable");
232548
+ return await response.json();
232549
+ }
232550
+ async function boundedCredentialRead(token) {
232551
+ const response = await fetch(resolveCommandAppEndpoint(CREDENTIAL_READ_PATH), {
232552
+ headers: {
232553
+ authorization: `Bearer ${token}`,
232554
+ accept: "application/json"
232555
+ },
232556
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
232557
+ });
232558
+ if (!response.ok) throw new Error("selected_credential_unavailable");
232559
+ return await response.text();
232560
+ }
232561
+ function missingAuthority() {
232562
+ throw new Error("selected_authority_unavailable");
232563
+ }
232564
+ /**
232565
+ * Acquires one signed, account-scoped Claude credential for the thread that
232566
+ * selected it. Owner and runtime-ticket sessions use different CMMD lanes.
232567
+ */
232568
+ function makeClaudeSelectedCredentialPreparer(config, dependencies) {
232569
+ const readRuntimeAuthority = dependencies.readRuntimeAuthority ?? readRuntimeBrokerAuthority;
232570
+ const requestOwnerTicket = dependencies.requestOwnerTicket ?? ((jwt, request) => proxyPost(jwt, OWNER_TICKET_PATH, request, void 0, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }));
232571
+ const requestRuntimeTicket = dependencies.requestRuntimeTicket ?? boundedRuntimeTicketRequest;
232572
+ const readCredential = dependencies.readCredential ?? boundedCredentialRead;
232573
+ const prepare = dependencies.prepare ?? ((threadId, acquired) => {
232574
+ if (!config.baseDir) return Promise.reject(/* @__PURE__ */ new Error("selected_store_unavailable"));
232575
+ return selectedClaudeCredentialStore(config.baseDir).prepare(threadId, acquired);
232576
+ });
232577
+ const createOperationId = dependencies.createOperationId ?? randomUUID;
232578
+ return async (input) => {
232579
+ const sealed = selectedConfig(config);
232580
+ const sessionId = input.executionSessionId ?? dependencies.readSessionId(input.threadId);
232581
+ if (sealed === null || sessionId === null) return missingAuthority();
232582
+ const operationId = createOperationId();
232583
+ const request = {
232584
+ providerAccountId: input.providerAccountId,
232585
+ threadId: String(input.threadId),
232586
+ operationId
232587
+ };
232588
+ const acquired = await acquireSelectedClaudeCredential({
232589
+ ...sealed,
232590
+ providerAccountId: input.providerAccountId,
232591
+ threadId: String(input.threadId),
232592
+ operationId
232593
+ }, {
232594
+ requestTicket: async () => {
232595
+ if (isRuntimeTicketSession$1(sessionId)) {
232596
+ const grant = readRuntimeAuthority({
232597
+ sessionId,
232598
+ runtimeId: sealed.runtimeId,
232599
+ machineId: sealed.machineId,
232600
+ credentialEpoch: sealed.credentialEpoch,
232601
+ requiredScope: "provider"
232602
+ });
232603
+ if (grant === null) return missingAuthority();
232604
+ return { data: await requestRuntimeTicket(grant, sealed.runtimeId, request) };
232605
+ }
232606
+ const jwt = await dependencies.resolveJwt(sessionId, { forceRefresh: false });
232607
+ if (jwt === null) return missingAuthority();
232608
+ return await requestOwnerTicket(jwt, {
232609
+ ...request,
232610
+ runtimeId: sealed.runtimeId
232611
+ });
232612
+ },
232613
+ readCredential
232614
+ });
232615
+ return await prepare(String(input.threadId), acquired);
232616
+ };
232617
+ }
232618
+ //#endregion
231890
232619
  //#region src/provider/claudeRuntimeReadinessGuard.ts
231891
232620
  const GENERATION_HASH_PATTERN = /^[a-f0-9]{64}$/;
231892
232621
  const RUNTIME_READINESS_TIMEOUT_MS$1 = 1e4;
@@ -232155,7 +232884,22 @@ function makeClaudeRuntimeReadinessGuard(config, dependencies) {
232155
232884
  return (input) => claudeReadinessGuardEffect(config, dependencies, resolved, input);
232156
232885
  }
232157
232886
  function claudeReadinessGuardEffect(config, dependencies, resolved, input) {
232158
- if (!requiresNativeClaudeReadiness(config, input.provider)) return succeed(null);
232887
+ if (!requiresNativeClaudeReadiness(config, input.provider, input.providerAccountId)) return succeed(null);
232888
+ if (input.providerAccountId !== void 0) {
232889
+ const prepareSelectedCredential = dependencies.prepareSelectedCredential ?? makeClaudeSelectedCredentialPreparer(config, {
232890
+ resolveJwt: dependencies.resolveJwt,
232891
+ readSessionId: resolved.readSessionId,
232892
+ readRuntimeAuthority: resolved.readRuntimeAuthority
232893
+ });
232894
+ const selectedInput = {
232895
+ ...input,
232896
+ providerAccountId: input.providerAccountId
232897
+ };
232898
+ return tryPromise({
232899
+ try: () => prepareSelectedCredential(selectedInput),
232900
+ catch: () => selectedCredentialValidationError(input.operation, selectedInput.providerAccountId)
232901
+ });
232902
+ }
232159
232903
  const authority = resolveGuardAuthority(config, resolved.readSessionId, input);
232160
232904
  if (authority === null) return invalidOwnerSessionEffect(input.operation);
232161
232905
  return tryPromise({
@@ -232163,8 +232907,9 @@ function claudeReadinessGuardEffect(config, dependencies, resolved, input) {
232163
232907
  catch: (cause) => readinessValidationError(input.operation, cause)
232164
232908
  });
232165
232909
  }
232166
- function requiresNativeClaudeReadiness(config, provider) {
232167
- return provider === "claudeAgent" && config.forgeCmmdRuntimeMode === true && personalClaudeInVmEnabled();
232910
+ function requiresNativeClaudeReadiness(config, provider, providerAccountId) {
232911
+ if (provider !== "claudeAgent" || config.forgeCmmdRuntimeMode !== true) return false;
232912
+ return providerAccountId !== void 0 || personalClaudeInVmEnabled();
232168
232913
  }
232169
232914
  function resolveGuardAuthority(config, readSessionId, input) {
232170
232915
  const runtimeId = config.forgeCmmdRuntimeId?.trim();
@@ -232201,6 +232946,12 @@ async function verifyGuardAuthority(config, dependencies, resolved, authority) {
232201
232946
  fetchReadiness: resolved.fetchReadiness
232202
232947
  });
232203
232948
  }
232949
+ function selectedCredentialValidationError(operation, accountId) {
232950
+ return new ProviderValidationError({
232951
+ operation,
232952
+ issue: `The selected Claude account ${accountId} could not be prepared. Reconnect the account and try again.`
232953
+ });
232954
+ }
232204
232955
  function readinessValidationError(operation, cause) {
232205
232956
  return new ProviderValidationError({
232206
232957
  operation,
@@ -289177,6 +289928,7 @@ const RuntimeDrainStateLive = effect(RuntimeDrainState, makeRuntimeDrainState);
289177
289928
  //#region src/runtimeDrain/RuntimeDrainReporter.ts
289178
289929
  const HEARTBEAT_INTERVAL = seconds(30);
289179
289930
  const HEARTBEAT_TICK_TIMEOUT = seconds(60);
289931
+ const FORCE_FORGE_APP_UPDATE_DRAIN_REASON = "forge_app_force_update";
289180
289932
  function runtimeHeartbeatConfig(config) {
289181
289933
  if (config.forgeCmmdRuntimeMode !== true) return null;
289182
289934
  const runtimeId = config.forgeCmmdRuntimeId?.trim();
@@ -289211,6 +289963,9 @@ function hasDrainUpdateTarget(runtime) {
289211
289963
  function isReadyForRuntimeUpdateActivation(runtime, snapshot) {
289212
289964
  return runtime.draining === true && snapshot.activeTurnCount === 0 && snapshot.activeSessionCount === 0 && hasDrainUpdateTarget(runtime);
289213
289965
  }
289966
+ function forceStopsResumableTurns(runtime) {
289967
+ return runtime.drainForceRequired === true && runtime.drainReason?.startsWith(`${FORCE_FORGE_APP_UPDATE_DRAIN_REASON}:`) === true;
289968
+ }
289214
289969
  const terminateForRuntimeUpdate = (runtime) => gen(function* () {
289215
289970
  yield* logWarning$1("CMMD runtime drain is clean and targets a Forge app update; terminating for supervisor activation.", {
289216
289971
  drainReason: runtime.drainReason,
@@ -289221,6 +289976,16 @@ const terminateForRuntimeUpdate = (runtime) => gen(function* () {
289221
289976
  process.kill(process.pid, "SIGTERM");
289222
289977
  });
289223
289978
  });
289979
+ const postRuntimeHeartbeat = (config, snapshot) => promise(async () => {
289980
+ const forgeGitCommit = await resolveForgeGitCommit();
289981
+ return sendRuntimeControlHeartbeat({
289982
+ ...config,
289983
+ activeTurnCount: snapshot.activeTurnCount,
289984
+ activeSessionCount: snapshot.activeSessionCount,
289985
+ ...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
289986
+ forgeServerVersion: installedForgeServerVersion
289987
+ });
289988
+ });
289224
289989
  const makeRuntimeDrainReporter = gen(function* () {
289225
289990
  const drainState = yield* RuntimeDrainState;
289226
289991
  const providerService = yield* ProviderService;
@@ -289228,16 +289993,6 @@ const makeRuntimeDrainReporter = gen(function* () {
289228
289993
  const spriteTaskHoldReactor = yield* SpriteTaskHoldReactor;
289229
289994
  const reportSemaphore = yield* make$44(1);
289230
289995
  let heartbeatSilent = false;
289231
- const postHeartbeat = (config, snapshot) => promise(async () => {
289232
- const forgeGitCommit = await resolveForgeGitCommit();
289233
- return sendRuntimeControlHeartbeat({
289234
- ...config,
289235
- activeTurnCount: snapshot.activeTurnCount,
289236
- activeSessionCount: snapshot.activeSessionCount,
289237
- ...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
289238
- forgeServerVersion: installedForgeServerVersion
289239
- });
289240
- });
289241
289996
  const applyControlPlaneState = (runtime) => drainState.applyControlPlaneState({
289242
289997
  draining: runtime.draining,
289243
289998
  drainReason: runtime.drainReason,
@@ -289256,11 +290011,35 @@ const makeRuntimeDrainReporter = gen(function* () {
289256
290011
  })
289257
290012
  }).pipe(catch_((cause) => logError("CMMD runtime provisioning grant refresh failed — this runtime will go dark once its current grant expires", { cause })));
289258
290013
  });
289259
- const completeDrainIfRequested = (config, runtime) => gen(function* () {
289260
- if (runtime.draining !== true) return;
290014
+ const settleActiveDrainTurns = (runtime, initialSessions, initialSnapshot) => gen(function* () {
290015
+ if (initialSnapshot.activeTurnCount === 0) return {
290016
+ ready: true,
290017
+ sessions: initialSessions,
290018
+ snapshot: initialSnapshot
290019
+ };
290020
+ if (!forceStopsResumableTurns(runtime)) return {
290021
+ ready: false,
290022
+ sessions: initialSessions,
290023
+ snapshot: initialSnapshot
290024
+ };
290025
+ yield* forEach(initialSessions.filter((session) => session.activeTurnId !== void 0), (session) => providerService.interruptTurn({
290026
+ threadId: session.threadId,
290027
+ turnId: session.activeTurnId
290028
+ }), { concurrency: 1 });
289261
290029
  const sessions = yield* providerService.listSessions();
289262
290030
  const snapshot = yield* drainState.reconcileProviderSessions(sessions);
289263
- if (snapshot.activeTurnCount > 0) return;
290031
+ return {
290032
+ ready: snapshot.activeTurnCount === 0,
290033
+ sessions,
290034
+ snapshot
290035
+ };
290036
+ });
290037
+ const completeDrainIfRequested = (config, runtime) => gen(function* () {
290038
+ if (runtime.draining !== true) return;
290039
+ const initialSessions = yield* providerService.listSessions();
290040
+ const settled = yield* settleActiveDrainTurns(runtime, initialSessions, yield* drainState.reconcileProviderSessions(initialSessions));
290041
+ if (!settled.ready) return;
290042
+ const { sessions, snapshot } = settled;
289264
290043
  yield* spriteTaskHoldReactor.releaseAll;
289265
290044
  const wasAlreadySessionless = snapshot.activeSessionCount === 0;
289266
290045
  const drainedSnapshot = wasAlreadySessionless ? snapshot : yield* gen(function* () {
@@ -289272,7 +290051,7 @@ const makeRuntimeDrainReporter = gen(function* () {
289272
290051
  return yield* drainState.reconcileProviderSessions(remainingSessions);
289273
290052
  });
289274
290053
  if (wasAlreadySessionless && !hasDrainUpdateTarget(runtime)) return;
289275
- const drainedRuntime = yield* postHeartbeat(config, drainedSnapshot);
290054
+ const drainedRuntime = yield* postRuntimeHeartbeat(config, drainedSnapshot);
289276
290055
  if (drainedRuntime) {
289277
290056
  yield* applyControlPlaneState(drainedRuntime);
289278
290057
  if (isReadyForRuntimeUpdateActivation(drainedRuntime, drainedSnapshot)) yield* terminateForRuntimeUpdate(drainedRuntime);
@@ -289283,7 +290062,7 @@ const makeRuntimeDrainReporter = gen(function* () {
289283
290062
  const config = runtimeHeartbeatConfig(serverConfig);
289284
290063
  if (!config) return;
289285
290064
  const initialSessions = yield* providerService.listSessions();
289286
- const runtime = yield* postHeartbeat(config, yield* drainState.reconcileProviderSessions(initialSessions));
290065
+ const runtime = yield* postRuntimeHeartbeat(config, yield* drainState.reconcileProviderSessions(initialSessions));
289287
290066
  if (!runtime) {
289288
290067
  if (!heartbeatSilent) {
289289
290068
  heartbeatSilent = true;
@@ -296805,7 +297584,7 @@ function resolveBuildCommitFromEnv(env) {
296805
297584
  * environment descriptor down instead of reporting an honest "unknown".
296806
297585
  */
296807
297586
  function readBakedBuildCommit() {
296808
- return "d9a524e82e63dd163e8f06083c14b5913b6fdf88";
297587
+ return "e51896589531a57a458ca5c2430dfd54cdc16476";
296809
297588
  }
296810
297589
  async function resolveServerBuildCommit(input) {
296811
297590
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -305651,6 +306430,82 @@ const orchestrationAutomationTerminalReconcileRouteLayer = add$1("POST", "/api/o
305651
306430
  }, { status: 200 });
305652
306431
  }).pipe(catchTag("OrchestrationDispatchCommandError", respondToWorkRunEventError)));
305653
306432
  //#endregion
306433
+ //#region src/orchestration/threadSnapshotRoute.ts
306434
+ /**
306435
+ * `GET /api/orchestration/threads/:threadId`, one thread's detail snapshot,
306436
+ * optionally windowed to a page of recent turns.
306437
+ *
306438
+ * Forge never served this route. The mobile client has asked for it on every
306439
+ * thread open since it shipped, fell through to the socket-embedded snapshot on
306440
+ * the 404, and its "load earlier" control had nowhere to fetch from, so a long
306441
+ * thread was only ever loadable in full. Serving it here gives the initial read
306442
+ * a body the transport can gzip, off the RPC socket, and gives older turns a
306443
+ * cursor to arrive on.
306444
+ *
306445
+ * The window is applied by the same pure function the WebSocket fallback
306446
+ * snapshot uses, so the two reads cannot disagree about what a page is.
306447
+ */
306448
+ /**
306449
+ * Reads the window out of the query string.
306450
+ *
306451
+ * Returns null for a malformed value rather than ignoring it. A client that
306452
+ * asked for 10 turns and silently received 4,000 has no way to tell, and the
306453
+ * failure would show up as a slow app rather than as an error.
306454
+ */
306455
+ function parseThreadSnapshotWindow(searchParams) {
306456
+ const rawTurnLimit = searchParams.get("turnLimit");
306457
+ const rawBeforeCursor = searchParams.get("beforeCursor");
306458
+ let turnLimit;
306459
+ if (rawTurnLimit !== null) {
306460
+ const parsed = Number(rawTurnLimit);
306461
+ if (!Number.isSafeInteger(parsed) || parsed < 1) return null;
306462
+ turnLimit = parsed;
306463
+ }
306464
+ let beforeCursor;
306465
+ if (rawBeforeCursor !== null) {
306466
+ const trimmed = rawBeforeCursor.trim();
306467
+ if (trimmed.length === 0) return null;
306468
+ beforeCursor = trimmed;
306469
+ }
306470
+ return {
306471
+ ...turnLimit === void 0 ? {} : { turnLimit },
306472
+ ...beforeCursor === void 0 ? {} : { beforeCursor }
306473
+ };
306474
+ }
306475
+ const orchestrationThreadSnapshotRouteLayer = add$1("GET", "/api/orchestration/threads/:threadId", gen(function* () {
306476
+ const request = yield* HttpServerRequest;
306477
+ yield* (yield* ServerAuth).authenticateHttpRequest(request);
306478
+ const rawThreadId = (yield* params)["threadId"];
306479
+ if (rawThreadId === void 0 || rawThreadId.length === 0) return jsonUnsafe({ error: "Missing thread id." }, { status: 400 });
306480
+ const url = toURL(request);
306481
+ const window = isNone(url) ? {} : parseThreadSnapshotWindow(url.value.searchParams);
306482
+ if (window === null) return jsonUnsafe({ error: "Invalid turnLimit or beforeCursor." }, { status: 400 });
306483
+ const threadId = ThreadId.make(decodeURIComponent(rawThreadId));
306484
+ const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
306485
+ const orchestrationEngine = yield* OrchestrationEngineService;
306486
+ const [threadDetail, snapshotSequence] = yield* all([projectionSnapshotQuery.getThreadDetailById(threadId).pipe(mapError((cause) => new OrchestrationGetSnapshotError({
306487
+ message: `Failed to load thread ${threadId}`,
306488
+ cause
306489
+ }))), orchestrationEngine.getReadModel().pipe(map$3((readModel) => readModel.snapshotSequence))]);
306490
+ if (isNone(threadDetail)) return jsonUnsafe({ error: "Thread not found." }, { status: 404 });
306491
+ const windowed = windowThreadDetail({
306492
+ thread: threadDetail.value,
306493
+ window,
306494
+ snapshotSequence
306495
+ });
306496
+ return jsonUnsafe({
306497
+ snapshotSequence,
306498
+ thread: windowed.thread,
306499
+ ...windowed.page === void 0 ? {} : { page: windowed.page }
306500
+ }, { status: 200 });
306501
+ }).pipe(catchTag("AuthError", respondToAuthError), catchTag("OrchestrationGetSnapshotError", (error) => gen(function* () {
306502
+ yield* logError("thread snapshot route failed", {
306503
+ message: error.message,
306504
+ cause: error.cause
306505
+ });
306506
+ return jsonUnsafe({ error: error.message }, { status: 500 });
306507
+ }))));
306508
+ //#endregion
305654
306509
  //#region src/localAutomations/scheduling.ts
305655
306510
  const MINUTE_MS = 6e4;
305656
306511
  const HOUR_MS = 60 * MINUTE_MS;
@@ -319970,12 +320825,12 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", gen(func
319970
320825
  //#region src/server.ts
319971
320826
  const PtyAdapterLive = unwrap(gen(function* () {
319972
320827
  if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunPTY-DkAIzLIx.mjs"))).layer;
319973
- else return (yield* promise(() => import("./NodePTY-B1u6RHXk.mjs"))).layer;
320828
+ else return (yield* promise(() => import("./NodePTY-BhezYy9H.mjs"))).layer;
319974
320829
  }));
319975
320830
  const HttpServerLive = unwrap(gen(function* () {
319976
320831
  const config = yield* ServerConfig;
319977
- if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunHttpServer-CVPGkizx.mjs"))).layer(bunHttpServerOptions(config));
319978
- const [NodeHttpServer, NodeHttp] = yield* all([promise(() => import("./NodeHttpServer-UQnvZnmu.mjs")), promise(() => import("node:http"))]);
320832
+ if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunHttpServer-FbbQmsl_.mjs"))).layer(bunHttpServerOptions(config));
320833
+ const [NodeHttpServer, NodeHttp] = yield* all([promise(() => import("./NodeHttpServer-BJ4l15f-.mjs")), promise(() => import("node:http"))]);
319979
320834
  return NodeHttpServer.layer(NodeHttp.createServer, {
319980
320835
  host: config.host,
319981
320836
  port: config.port
@@ -319983,10 +320838,10 @@ const HttpServerLive = unwrap(gen(function* () {
319983
320838
  }));
319984
320839
  const PlatformServicesLive = unwrap(gen(function* () {
319985
320840
  if (typeof Bun !== "undefined") {
319986
- const { layer } = yield* promise(() => import("./BunServices-CPrZSIfE.mjs"));
320841
+ const { layer } = yield* promise(() => import("./BunServices-DC7nhA3w.mjs"));
319987
320842
  return layer;
319988
320843
  } else {
319989
- const { layer } = yield* promise(() => import("./NodeServices-z1Ac4lEO.mjs").then((n) => n.t));
320844
+ const { layer } = yield* promise(() => import("./NodeServices-D3IxOzHl.mjs").then((n) => n.t));
319990
320845
  return layer;
319991
320846
  }
319992
320847
  }));
@@ -320051,7 +320906,7 @@ const MiscRuntimeLayerLive = mergeAll(AnalyticsServiceLayerLive, OpenLive, Serve
320051
320906
  const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(provideMerge(CheckpointingLayerLive), provideMerge(GitLayerLive), provideMerge(OrchestrationLayerLive), provideMerge(ProviderLayerLive), provideMerge(TerminalLayerLive), provideMerge(AgentBrowserManagerLive), provideMerge(mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(provide$1(ThreadDevRuntimeLive)))), provideMerge(PersistenceLayerLive), provideMerge(KeybindingsLive), provideMerge(ProviderRegistryLive), provideMerge(mergeAll(ServerSettingsLive, UsageServiceLive.pipe(provide$1(ServerSettingsLive), provide$1(UsageSessionLookupLive), provide$1(UsageRecordedSourceLive.pipe(provide$1(OrchestrationLayerLive), provide$1(RuntimeReplicationOutboxLive), provide$1(ProviderSessionRuntimeRepositoryLive), provide$1(TextGenerationSessionRepositoryLive)))))), provideMerge(WorkspaceLayerLive), provideMerge(WorkspaceReadinessStateLive), provideMerge(ProjectFaviconResolverLive), provideMerge(RepositoryIdentityResolverLive), provideMerge(ServerEnvironmentLive), provideMerge(AuthLayerLive), provideMerge(ServerSecretStoreLayerLive), provideMerge(mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(provide$1(PersistenceLayerLive)))), provideMerge(MiscRuntimeLayerLive));
320052
320907
  const RuntimeDependenciesLive = pullRequestServiceFactoryLayer.pipe(provideMerge(RuntimeDependenciesWithoutPullRequestsLive), provideMerge(cmmdProjectIdentityResolverLive.pipe(provide$1(RuntimeDependenciesWithoutPullRequestsLive))), provideMerge(pullRequestProviderRuntimeLive));
320053
320908
  const RuntimeServicesLive = ServerRuntimeStartupLive.pipe(provideMerge(RuntimeDependenciesLive));
320054
- const makeRoutesLayer = mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(provide$1(browserApiCorsLayer));
320909
+ const makeRoutesLayer = mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, orchestrationThreadSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(provide$1(browserApiCorsLayer));
320055
320910
  const runServer = launch$1(unwrap(gen(function* () {
320056
320911
  const config = yield* ServerConfig;
320057
320912
  fixPath({ preferProcessPath: config.forgeCmmdRuntimeMode === true });