@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.
- package/dist/{BunHttpServer-C7-1JJ7b.cjs → BunHttpServer-C0oPP_3A.cjs} +4 -4
- package/dist/{BunHttpServer-CVPGkizx.mjs → BunHttpServer-FbbQmsl_.mjs} +4 -4
- package/dist/{BunServices-CPrZSIfE.mjs → BunServices-DC7nhA3w.mjs} +2 -2
- package/dist/{BunServices-Dl-3897e.cjs → BunServices-DkenwD7S.cjs} +2 -2
- package/dist/{Cache-Cf3ARNaS.cjs → Cache-BmGc_2BZ.cjs} +2 -2
- package/dist/{Cache-C9cwdCDt.mjs → Cache-D57GWb9-.mjs} +2 -2
- package/dist/{HttpServer-CggZzScj.cjs → HttpServer-BBzRBmVt.cjs} +2 -2
- package/dist/{HttpServer-COaBeM7-.mjs → HttpServer-Bclwvu0I.mjs} +2 -2
- package/dist/{NodeHttpServer-UQnvZnmu.mjs → NodeHttpServer-BJ4l15f-.mjs} +5 -5
- package/dist/{NodeHttpServer-Bi339XFk.cjs → NodeHttpServer-Cjd3bFrD.cjs} +5 -5
- package/dist/{NodePTY-UOaRyQsj.cjs → NodePTY-BehazUMi.cjs} +2 -2
- package/dist/{NodePTY-B1u6RHXk.mjs → NodePTY-BhezYy9H.mjs} +2 -2
- package/dist/{NodeServices-R0BZsZ72.cjs → NodeServices-BvdIc9Vh.cjs} +1 -1
- package/dist/{NodeServices-CKFIZVu5.cjs → NodeServices-CtC4a-h5.cjs} +2 -2
- package/dist/{NodeServices-z1Ac4lEO.mjs → NodeServices-D3IxOzHl.mjs} +2 -2
- package/dist/{NodeSqliteClient-CyJvo9y9.cjs → NodeSqliteClient-CfsdC3AB.cjs} +4 -4
- package/dist/{NodeSqliteClient-BrzqdOsF.mjs → NodeSqliteClient-md8xd9hY.mjs} +4 -4
- package/dist/{NodeTerminal-pYVVRTiD.mjs → NodeTerminal-9iHZIdt1.mjs} +2 -2
- package/dist/{NodeTerminal-B89MobLp.cjs → NodeTerminal-CO-i-6Je.cjs} +2 -2
- package/dist/{Path-BunGrE8F.cjs → Path-BQOH_fWQ.cjs} +7 -1
- package/dist/{Path-evOm-H0Q.mjs → Path-CZJ1gPPo.mjs} +2 -2
- package/dist/{SqlError-CezTFGvr.mjs → SqlError-BMBkezUO.mjs} +2 -2
- package/dist/{SqlError-1wIMjXAf.cjs → SqlError-g4pLzNWR.cjs} +2 -2
- package/dist/{SqliteClient-D3Yd3ZHn.mjs → SqliteClient-BKB695RF.mjs} +3 -3
- package/dist/{SqliteClient-bo7iui1t.cjs → SqliteClient-O1ABIngw.cjs} +3 -3
- package/dist/bin.cjs +1077 -214
- package/dist/bin.mjs +1072 -217
- package/dist/client/assets/{CanvasPanel-z0F-jdf1.js → CanvasPanel-BjHxCGlb.js} +2 -2
- package/dist/client/assets/{CompositeItem-P9UVXCKv.js → CompositeItem-D4LER3__.js} +2 -2
- package/dist/client/assets/{DiffPanel-aGVgMMDS.js → DiffPanel-D3btZGjW.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-CAL295cZ.js → DiffPanel.logic-DWcwE9Wj.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-BLRSawDM.js → DiffPanelShell-oXHh--ae.js} +109 -109
- package/dist/client/assets/{DiffWorkerPoolProvider-tqcKyIAF.js → DiffWorkerPoolProvider-R9YPaaNy.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-CYLmbiv7.js → PullRequestCodePanel-DTCc2TBl.js} +2 -2
- package/dist/client/assets/{Virtualizer-NYdNfon2.js → Virtualizer-arKGxq_z.js} +2 -2
- package/dist/client/assets/{canvasStateStore-CdQxd2aO.js → canvasStateStore-B5zd1u9r.js} +2 -2
- package/dist/client/assets/cmmdAuthRedirect-DeFAIQpj.js +2 -0
- package/dist/client/assets/index-Bn-ASQRG.css +1 -0
- package/dist/client/assets/index-C7YKDHIe.js +154 -0
- package/dist/client/assets/{popover-CUSlb3eK.js → popover-DM7IA0zJ.js} +2 -2
- package/dist/client/assets/src-LDQtPKQt.js +3 -0
- package/dist/client/assets/{toggle-BREV66nT.js → toggle-DVtS0BJJ.js} +2 -2
- package/dist/client/index.html +11 -11
- package/dist/forge-build.json +1 -1
- package/dist/runtimeBootstrap.cjs +1 -1
- package/dist/runtimeBootstrap.mjs +1 -1
- package/dist/{runtimeControlAuthority-B3kNmYRT.cjs → runtimeControlAuthority-BkVB0uAA.cjs} +17 -1
- package/dist/{runtimeControlAuthority-D_zKr_eI.mjs → runtimeControlAuthority-E_kdeD_P.mjs} +12 -2
- package/package.json +1 -1
- package/dist/client/assets/cmmdAuthRedirect-Bp1uQaQ9.js +0 -2
- package/dist/client/assets/index-Bx4rqeUi.css +0 -1
- package/dist/client/assets/index-fNpsmaLQ.js +0 -154
- 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
|
|
6
|
-
import { _ as ChildProcessSpawner, a as Stdio, g as make$48, o as make$49, r as isQuitError } from "./NodeTerminal-
|
|
7
|
-
import { n as layer$7 } from "./NodeServices-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
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$
|
|
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({
|
|
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([
|
|
18679
|
-
kind: Literal("
|
|
18680
|
-
|
|
18681
|
-
|
|
18682
|
-
|
|
18683
|
-
|
|
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.
|
|
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
|
-
*
|
|
66886
|
-
*
|
|
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)
|
|
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
|
-
|
|
67450
|
-
|
|
67451
|
-
|
|
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
|
|
67550
|
-
return
|
|
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)
|
|
67623
|
-
|
|
67624
|
-
|
|
67625
|
-
|
|
67626
|
-
|
|
67627
|
-
|
|
67628
|
-
|
|
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
|
-
|
|
68202
|
-
|
|
68203
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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 ?
|
|
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*
|
|
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:
|
|
71458
|
-
*
|
|
71459
|
-
*
|
|
71460
|
-
*
|
|
71461
|
-
*
|
|
71462
|
-
*
|
|
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
|
|
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)
|
|
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,
|
|
106118
|
-
|
|
106119
|
-
|
|
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
|
-
})))
|
|
106122
|
-
|
|
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-
|
|
117602
|
-
node: () => import("./NodeSqliteClient-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
/**
|
|
212334
|
-
function
|
|
212335
|
-
|
|
212336
|
-
|
|
212337
|
-
|
|
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
|
|
212754
|
+
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return refuse("operator-override");
|
|
212383
212755
|
const directory = resolveClaudeCredentialDirectory(input);
|
|
212384
|
-
if (!directory) return
|
|
212385
|
-
if (!holdsUsableCredential(directory)) return
|
|
212386
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
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
|
|
289260
|
-
if (
|
|
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
|
-
|
|
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*
|
|
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*
|
|
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 "
|
|
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-
|
|
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-
|
|
319978
|
-
const [NodeHttpServer, NodeHttp] = yield* all([promise(() => import("./NodeHttpServer-
|
|
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-
|
|
320841
|
+
const { layer } = yield* promise(() => import("./BunServices-DC7nhA3w.mjs"));
|
|
319987
320842
|
return layer;
|
|
319988
320843
|
} else {
|
|
319989
|
-
const { layer } = yield* promise(() => import("./NodeServices-
|
|
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 });
|