@cmmd-center/forge 0.13.2 → 0.13.4
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-VI75aqJw.mjs → BunHttpServer-CDh4Vioe.mjs} +6 -6
- package/dist/{BunHttpServer-BhE7Szci.cjs → BunHttpServer-DKvSpNd1.cjs} +6 -6
- package/dist/{BunPTY-oVISucl_.mjs → BunPTY-DkAIzLIx.mjs} +3 -3
- package/dist/{BunPTY-86ttlTi6.cjs → BunPTY-uyreNRGp.cjs} +3 -3
- package/dist/{BunServices-D0JTDujL.mjs → BunServices-D6PAheqS.mjs} +3 -3
- package/dist/{BunServices-BwE9KexF.cjs → BunServices-DDh7Whyx.cjs} +3 -3
- package/dist/{Cache-p0X77SKw.mjs → Cache-BVilO50s.mjs} +3 -3
- package/dist/{Cache-CWlxNDbC.cjs → Cache-DCF22Dfk.cjs} +3 -3
- package/dist/{Config-AqUR_lhX.mjs → Config-Cdfpphpo.mjs} +2 -2
- package/dist/{Config-CkEzdD9L.cjs → Config-CwKCCk14.cjs} +2 -2
- package/dist/{HttpServer-DNGCTTdk.mjs → HttpServer-ClsGgAq1.mjs} +3 -3
- package/dist/{HttpServer-DGoLR-kU.cjs → HttpServer-D1gSU7NX.cjs} +3 -3
- package/dist/{NodeHttpServer-Dw7ivXfX.mjs → NodeHttpServer-DT0TF8s4.mjs} +7 -7
- package/dist/{NodeHttpServer-BPjxt_DT.cjs → NodeHttpServer-Dfc7nSMq.cjs} +7 -7
- package/dist/{NodePTY-Cps0jsDA.cjs → NodePTY-S4tPEwZc.cjs} +4 -4
- package/dist/{NodePTY-CVH2yNYl.mjs → NodePTY-loxi8Vku.mjs} +4 -4
- package/dist/{NodeServices-CBNXOYLG.mjs → NodeServices-BahzLhbo.mjs} +3 -3
- package/dist/{NodeServices-Cw6SxEbl.cjs → NodeServices-Cb0rXFb2.cjs} +3 -3
- package/dist/{NodeServices-Ddr-tYRu.cjs → NodeServices-DE8jhdcU.cjs} +1 -1
- package/dist/{NodeSqliteClient-DGInbxQ3.mjs → NodeSqliteClient-DqFjB3u_.mjs} +6 -6
- package/dist/{NodeSqliteClient-CmxPzcBf.cjs → NodeSqliteClient-qjBWAOGq.cjs} +6 -6
- package/dist/{NodeTerminal-BEGtoMSS.mjs → NodeTerminal-BPw7bktz.mjs} +3 -3
- package/dist/{NodeTerminal-5iWbO0B2.cjs → NodeTerminal-rYbifme7.cjs} +3 -3
- package/dist/{PTY-BxYDiW3h.cjs → PTY-B4ZctbwQ.cjs} +2 -2
- package/dist/{PTY-rIPf52QR.mjs → PTY-BzdbZ0Ym.mjs} +2 -2
- package/dist/{Path-C6Zb2WHp.mjs → Path-C851t6sC.mjs} +2 -2
- package/dist/{Path-Be7zSwar.cjs → Path-CGCc_dDx.cjs} +2 -2
- package/dist/{Schema-dLi7Qssy.cjs → Schema-CDCJ3xZo.cjs} +7 -1
- package/dist/{Schema-CJyk7ah1.mjs → Schema-DOBWF8gQ.mjs} +2 -2
- package/dist/{SqlError-cyJT2IRY.mjs → SqlError-3aqibZrj.mjs} +3 -3
- package/dist/{SqlError-Bt0Bcd5C.cjs → SqlError-CzB6p9lL.cjs} +3 -3
- package/dist/{SqliteClient-B08FzZM4.cjs → SqliteClient-Cq1peWN1.cjs} +5 -5
- package/dist/{SqliteClient-CIsITMoQ.mjs → SqliteClient-DTWCGkrj.mjs} +5 -5
- package/dist/bin.cjs +1216 -205
- package/dist/bin.mjs +1239 -231
- package/dist/client/assets/{CanvasPanel-BrVen-8G.js → CanvasPanel-DkUmuXgO.js} +3 -3
- package/dist/client/assets/CompositeItem-DG5YBMcI.js +2 -0
- package/dist/client/assets/{DiffPanel-Bh9UFe_U.js → DiffPanel-DLCCD1ht.js} +3 -3
- package/dist/client/assets/DiffPanel.logic-dIDfHUQm.js +21 -0
- package/dist/client/assets/DiffPanelShell-Ba6NA1s7.js +2531 -0
- package/dist/client/assets/{DiffWorkerPoolProvider-CYkhfTpq.js → DiffWorkerPoolProvider-Bqy6DzWP.js} +52 -52
- package/dist/client/assets/PullRequestCodePanel-8SJWZPM5.js +5 -0
- package/dist/client/assets/canvasStateStore-BWZ8xdXr.js +2 -0
- package/dist/client/assets/{cmmdAuthRedirect-sqR8W6vY.js → cmmdAuthRedirect--srSJjFS.js} +2 -2
- package/dist/client/assets/diffTargetResolution-DA0SlQE0.js +2 -0
- package/dist/client/assets/index-C9Upewz-.js +151 -0
- package/dist/client/assets/index-DlyGyVH6.css +1 -0
- package/dist/client/assets/src-DwOYg6gv.js +3 -0
- package/dist/client/assets/toggle-Ozy-3fZ2.js +2 -0
- package/dist/client/assets/useTriggerFocusGuards-FzV_CNjl.js +2 -0
- package/dist/client/index.html +11 -11
- package/dist/forge-build.json +1 -1
- package/dist/{open-FIgTDosf.mjs → open-D8mGyWmt.mjs} +3 -3
- package/package.json +1 -1
- package/dist/client/assets/CompositeItem-BNZfMUvm.js +0 -2
- package/dist/client/assets/DiffPanel.logic-A3r3u_Vr.js +0 -21
- package/dist/client/assets/DiffPanelShell-B30xoHOs.js +0 -2531
- package/dist/client/assets/PullRequestCodePanel-T32DALQE.js +0 -5
- package/dist/client/assets/Virtualizer-DbDh3RHG.js +0 -2
- package/dist/client/assets/canvasStateStore-DN5mPB5B.js +0 -2
- package/dist/client/assets/index-Dk7T0zVg.css +0 -1
- package/dist/client/assets/index-FpNJGy15.js +0 -151
- package/dist/client/assets/src-CFqnisEM.js +0 -3
- package/dist/client/assets/toggle-CLUqZXdO.js +0 -2
- package/dist/client/assets/useTriggerFocusGuards-CaUimC-O.js +0 -2
package/dist/bin.mjs
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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
|
-
import { $ as encodeEffect, $a as make$39, $c as
|
|
5
|
-
import { $ as toAsyncIterable, A as make$45, At as make$44, Bt as publish, C as fromEffect, Ct as make$43, E as fromQueue, Et as end, F as merge$4, Ft as shutdown$1, G as runFold, H as run$2, Ht as subscribe$1, It as sliding, Jt as getCurrent, K as runForEach, Kt as makeUnsafe$7, Lt as take$1, M as mapAccum, Mt as offerAll, N as mapEffect$1, Nt as offerUnsafe, O as fromSubscription, Ot as failCause$4, Q as tick, R as onExit$1, Rt as takeAll, S as fromChannel, T as fromPubSub, Tt as bounded, U as runCollect, Ut as unbounded$1, V as provideContext$1, Vt as shutdown$2, W as runDrain, Y as succeed$4, Yt as interrupt$4, Z as takeWhile, Zt as runIn, _t as mapInput, at as make$46, b as flatMap$3, bt as fromTransform, c as callback$1, d as debounce$1, f as decodeText$1, g as ensuring$1, h as encodeText, it as get$4, j as map$7, jt as offer$1, k as isStream, l as catchCause$1, m as empty$5, mt as forEach$1, ot as get$5, pt as drain, q as runForEachArray, qt as await_, r as FileSystem, rt as unwrap$1, st as make$47, t as Path$2, u as concat$2, v as failCause$5, w as fromIterable$1, wt as makeUnsafe$6, x as fromAsyncIterable, y as filter$2, yt as fromSubscriptionArray, zt as unbounded$2 } from "./Path-
|
|
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-
|
|
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-
|
|
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-
|
|
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 toAsyncIterable, A as make$45, At as make$44, Bt as publish, C as fromEffect, Ct as make$43, E as fromQueue, Et as end, F as merge$4, Ft as shutdown$1, G as runFold, H as run$2, Ht as subscribe$1, It as sliding, Jt as getCurrent, K as runForEach, Kt as makeUnsafe$7, Lt as take$1, M as mapAccum, Mt as offerAll, N as mapEffect$1, Nt as offerUnsafe, O as fromSubscription, Ot as failCause$4, Q as tick, R as onExit$1, Rt as takeAll, S as fromChannel, T as fromPubSub, Tt as bounded, U as runCollect, Ut as unbounded$1, V as provideContext$1, Vt as shutdown$2, W as runDrain, Y as succeed$4, Yt as interrupt$4, Z as takeWhile, Zt as runIn, _t as mapInput, at as make$46, b as flatMap$3, bt as fromTransform, c as callback$1, d as debounce$1, f as decodeText$1, g as ensuring$1, h as encodeText, it as get$4, j as map$7, jt as offer$1, k as isStream, l as catchCause$1, m as empty$5, mt as forEach$1, ot as get$5, pt as drain, q as runForEachArray, qt as await_, r as FileSystem, rt as unwrap$1, st as make$47, t as Path$2, u as concat$2, v as failCause$5, w as fromIterable$1, wt as makeUnsafe$6, x as fromAsyncIterable, y as filter$2, yt as fromSubscriptionArray, zt as unbounded$2 } from "./Path-C851t6sC.mjs";
|
|
6
|
+
import { _ as ChildProcessSpawner, a as Stdio, g as make$48, o as make$49, r as isQuitError } from "./NodeTerminal-BPw7bktz.mjs";
|
|
7
|
+
import { n as layer$7 } from "./NodeServices-BahzLhbo.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-ClsGgAq1.mjs";
|
|
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-BVilO50s.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-3aqibZrj.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
14
|
import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicket, d as setRuntimeGrantToken, f as createReadOnceFileTokenAccessor, i as sendRuntimeControlHeartbeat, l as verifyCmmdRuntimeTicketRefreshProof, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, r as readRuntimeControlAuthority, s as verifyCmmdClaudeUsageTicket, t as bootstrapRuntimeControlAuthority, u as runtimeGrantToken } from "./runtimeControlAuthority-D_zKr_eI.mjs";
|
|
15
|
-
import { n as PtySpawnError, t as PtyAdapter } from "./PTY-
|
|
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";
|
|
18
18
|
import { execFile, execFileSync, spawn, spawnSync } from "node:child_process";
|
|
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
|
|
|
22
22
|
import * as FS$1 from "node:fs";
|
|
23
23
|
import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
24
24
|
import * as NodeOS from "node:os";
|
|
25
|
-
import
|
|
25
|
+
import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
26
26
|
import * as Path$1 from "node:path";
|
|
27
27
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
28
28
|
import { pathToFileURL } from "node:url";
|
|
@@ -16520,7 +16520,7 @@ function makeNetServiceShape(createServer = Net.createServer) {
|
|
|
16520
16520
|
}
|
|
16521
16521
|
//#endregion
|
|
16522
16522
|
//#region ../../packages/shared/src/releaseInfo.generated.ts
|
|
16523
|
-
const FORGE_RELEASE_VERSION = "0.13.
|
|
16523
|
+
const FORGE_RELEASE_VERSION = "0.13.4";
|
|
16524
16524
|
//#endregion
|
|
16525
16525
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16526
16526
|
/**
|
|
@@ -16644,6 +16644,70 @@ const CheckpointRef = makeEntityId("CheckpointRef");
|
|
|
16644
16644
|
*/
|
|
16645
16645
|
const ThreadEnvMode = Literals(["local", "worktree"]);
|
|
16646
16646
|
//#endregion
|
|
16647
|
+
//#region ../../packages/contracts/src/assets.ts
|
|
16648
|
+
/**
|
|
16649
|
+
* Addresses for the bytes a client cannot fetch by guessing.
|
|
16650
|
+
*
|
|
16651
|
+
* Forge already serves the bytes: attachments under `/attachments/*` and
|
|
16652
|
+
* project favicons at `GET /api/project-favicon?cwd=`. What it did not serve
|
|
16653
|
+
* was the question "what is the URL for this thing", which is the only way a
|
|
16654
|
+
* client that holds an attachment id or a workspace root can reach either.
|
|
16655
|
+
* The phone asks that question on the thread list for every project, so
|
|
16656
|
+
* until this existed the answer was an error the phone drew as a spinner.
|
|
16657
|
+
*
|
|
16658
|
+
* These schemas are the Forge side of `assets.*` in the vendored t3 contracts.
|
|
16659
|
+
* They must decode what that package encodes: same tags, same field names.
|
|
16660
|
+
*/
|
|
16661
|
+
const ASSET_PATH_MAX_LENGTH = 1024;
|
|
16662
|
+
const ASSET_ID_MAX_LENGTH = 256;
|
|
16663
|
+
const ASSET_URL_MAX_LENGTH = 4096;
|
|
16664
|
+
const AssetResource = Union([
|
|
16665
|
+
TaggedStruct("workspace-file", {
|
|
16666
|
+
threadId: ThreadId,
|
|
16667
|
+
path: TrimmedNonEmptyString.check(isMaxLength(ASSET_PATH_MAX_LENGTH))
|
|
16668
|
+
}),
|
|
16669
|
+
TaggedStruct("attachment", { attachmentId: TrimmedNonEmptyString.check(isMaxLength(ASSET_ID_MAX_LENGTH)) }),
|
|
16670
|
+
TaggedStruct("project-favicon", {
|
|
16671
|
+
cwd: TrimmedNonEmptyString.check(isMaxLength(ASSET_PATH_MAX_LENGTH)),
|
|
16672
|
+
path: optional$4(TrimmedNonEmptyString.check(isMaxLength(ASSET_PATH_MAX_LENGTH)))
|
|
16673
|
+
})
|
|
16674
|
+
]);
|
|
16675
|
+
const AssetCreateUrlInput = Struct({ resource: AssetResource });
|
|
16676
|
+
const AssetCreateUrlResult = Struct({
|
|
16677
|
+
relativeUrl: TrimmedNonEmptyString.check(isMaxLength(ASSET_URL_MAX_LENGTH)),
|
|
16678
|
+
expiresAt: Number$1,
|
|
16679
|
+
sourcePath: optional$4(TrimmedNonEmptyString.check(isMaxLength(ASSET_PATH_MAX_LENGTH)))
|
|
16680
|
+
});
|
|
16681
|
+
/**
|
|
16682
|
+
* The resource cannot be addressed by URL on this server.
|
|
16683
|
+
*
|
|
16684
|
+
* Tag and message match the vendored client's `AssetAccessError` union so the
|
|
16685
|
+
* phone decodes the failure rather than falling through to a generic transport
|
|
16686
|
+
* error. `workspace-file` is the case that lands here.
|
|
16687
|
+
*/
|
|
16688
|
+
var AssetWorkspaceResolutionError = class extends TaggedErrorClass()("AssetWorkspaceResolutionError", {
|
|
16689
|
+
resource: AssetResource,
|
|
16690
|
+
cause: optional$4(Defect)
|
|
16691
|
+
}) {
|
|
16692
|
+
get message() {
|
|
16693
|
+
return "Failed to resolve workspace.";
|
|
16694
|
+
}
|
|
16695
|
+
};
|
|
16696
|
+
/** The signing key that makes an asset URL self-authenticating could not be read. */
|
|
16697
|
+
var AssetSigningKeyLoadError = class extends TaggedErrorClass()("AssetSigningKeyLoadError", {
|
|
16698
|
+
resource: AssetResource,
|
|
16699
|
+
cause: optional$4(Defect)
|
|
16700
|
+
}) {
|
|
16701
|
+
get message() {
|
|
16702
|
+
return "Failed to load the asset signing key.";
|
|
16703
|
+
}
|
|
16704
|
+
};
|
|
16705
|
+
const AssetAccessError = Union([AssetWorkspaceResolutionError, AssetSigningKeyLoadError]);
|
|
16706
|
+
const AssetsRpcSchemas = { createUrl: {
|
|
16707
|
+
input: AssetCreateUrlInput,
|
|
16708
|
+
output: AssetCreateUrlResult
|
|
16709
|
+
} };
|
|
16710
|
+
//#endregion
|
|
16647
16711
|
//#region ../../node_modules/.bun/effect@4.0.0-beta.51/node_modules/effect/dist/unstable/rpc/RpcMiddleware.js
|
|
16648
16712
|
/**
|
|
16649
16713
|
* @since 4.0.0
|
|
@@ -18144,6 +18208,7 @@ const ExecutionEnvironmentCapabilities = Struct({
|
|
|
18144
18208
|
threadPullRequestLinking: optionalKey(Boolean$1),
|
|
18145
18209
|
cloudMode: Boolean$1.pipe(withDecodingDefault(succeed(false))),
|
|
18146
18210
|
threadSettlement: optionalKey(Boolean$1),
|
|
18211
|
+
threadTitleRegeneration: optionalKey(Boolean$1),
|
|
18147
18212
|
posthogPublicKey: NullOr(String$1).pipe(withDecodingDefault(succeed(null))),
|
|
18148
18213
|
posthogHost: NullOr(String$1).pipe(withDecodingDefault(succeed(null))),
|
|
18149
18214
|
runtime: optionalKey(ExecutionEnvironmentRuntimeCapabilities),
|
|
@@ -19141,6 +19206,51 @@ Struct({
|
|
|
19141
19206
|
headRepositoryNameWithOwner: optional$4(NullOr(TrimmedNonEmptyString)),
|
|
19142
19207
|
headRepositoryOwnerLogin: optional$4(NullOr(TrimmedNonEmptyString))
|
|
19143
19208
|
});
|
|
19209
|
+
const SourceControlCloneProtocol = Literals([
|
|
19210
|
+
"auto",
|
|
19211
|
+
"ssh",
|
|
19212
|
+
"https"
|
|
19213
|
+
]);
|
|
19214
|
+
const SourceControlRepositoryInfo = Struct({
|
|
19215
|
+
provider: SourceControlProviderKind$1,
|
|
19216
|
+
nameWithOwner: TrimmedNonEmptyString,
|
|
19217
|
+
url: TrimmedNonEmptyString,
|
|
19218
|
+
sshUrl: TrimmedNonEmptyString
|
|
19219
|
+
});
|
|
19220
|
+
const SourceControlRepositoryLookupInput = Struct({
|
|
19221
|
+
provider: SourceControlProviderKind$1,
|
|
19222
|
+
repository: TrimmedNonEmptyString,
|
|
19223
|
+
cwd: optional$4(TrimmedNonEmptyString)
|
|
19224
|
+
});
|
|
19225
|
+
/**
|
|
19226
|
+
* Either name the repository (`provider` + `repository`) or paste a clone URL
|
|
19227
|
+
* (`remoteUrl`). Both are optional in the schema because the phone sends one
|
|
19228
|
+
* or the other depending on how the user got here; the server rejects a call
|
|
19229
|
+
* that supplies neither, and rejects a `remoteUrl` it cannot resolve to a
|
|
19230
|
+
* repository it is allowed to fetch a token for.
|
|
19231
|
+
*/
|
|
19232
|
+
const SourceControlCloneRepositoryInput = Struct({
|
|
19233
|
+
provider: optional$4(SourceControlProviderKind$1),
|
|
19234
|
+
repository: optional$4(TrimmedNonEmptyString),
|
|
19235
|
+
remoteUrl: optional$4(TrimmedNonEmptyString),
|
|
19236
|
+
destinationPath: TrimmedNonEmptyString,
|
|
19237
|
+
protocol: optional$4(SourceControlCloneProtocol)
|
|
19238
|
+
});
|
|
19239
|
+
const SourceControlCloneRepositoryResult = Struct({
|
|
19240
|
+
cwd: TrimmedNonEmptyString,
|
|
19241
|
+
remoteUrl: TrimmedNonEmptyString,
|
|
19242
|
+
repository: NullOr(SourceControlRepositoryInfo)
|
|
19243
|
+
});
|
|
19244
|
+
var SourceControlRepositoryError = class extends TaggedErrorClass()("SourceControlRepositoryError", {
|
|
19245
|
+
provider: SourceControlProviderKind$1,
|
|
19246
|
+
operation: String$1,
|
|
19247
|
+
detail: String$1,
|
|
19248
|
+
cause: optional$4(Defect)
|
|
19249
|
+
}) {
|
|
19250
|
+
get message() {
|
|
19251
|
+
return `Source control repository operation ${this.operation} failed for ${this.provider}: ${this.detail}`;
|
|
19252
|
+
}
|
|
19253
|
+
};
|
|
19144
19254
|
//#endregion
|
|
19145
19255
|
//#region ../../packages/contracts/src/orchestration.ts
|
|
19146
19256
|
const ORCHESTRATION_WS_METHODS = {
|
|
@@ -19152,7 +19262,8 @@ const ORCHESTRATION_WS_METHODS = {
|
|
|
19152
19262
|
replayEvents: "orchestration.replayEvents",
|
|
19153
19263
|
getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot",
|
|
19154
19264
|
subscribeShell: "orchestration.subscribeShell",
|
|
19155
|
-
subscribeThread: "orchestration.subscribeThread"
|
|
19265
|
+
subscribeThread: "orchestration.subscribeThread",
|
|
19266
|
+
searchThreads: "orchestration.searchThreads"
|
|
19156
19267
|
};
|
|
19157
19268
|
const ProviderKind = Literals([
|
|
19158
19269
|
"codex",
|
|
@@ -20508,6 +20619,31 @@ const OrchestrationGetFullThreadDiffInput = Struct({
|
|
|
20508
20619
|
const OrchestrationGetFullThreadDiffResult = ThreadTurnDiff;
|
|
20509
20620
|
const OrchestrationReplayEventsInput = Struct({ fromSequenceExclusive: NonNegativeInt });
|
|
20510
20621
|
const OrchestrationReplayEventsResult = ArraySchema(OrchestrationEvent);
|
|
20622
|
+
/**
|
|
20623
|
+
* Which side of the conversation a search hit came from. `null`
|
|
20624
|
+
* `messageCreatedAt` on a match means it came from the thread's own metadata
|
|
20625
|
+
* (its title or its branch) rather than from a message.
|
|
20626
|
+
*/
|
|
20627
|
+
const OrchestrationThreadSearchSource = Literals(["user", "assistant"]);
|
|
20628
|
+
/**
|
|
20629
|
+
* The server's SQLite client is synchronous and single-connection, so both the
|
|
20630
|
+
* scan input and the response size are bounded: a search must not be able to
|
|
20631
|
+
* monopolize the connection every keystroke.
|
|
20632
|
+
*/
|
|
20633
|
+
const OrchestrationSearchThreadsInput = Struct({
|
|
20634
|
+
query: TrimmedString.check(isMinLength(2), isMaxLength(200)),
|
|
20635
|
+
limit: optionalKey(Int$1.check(isBetween({
|
|
20636
|
+
minimum: 1,
|
|
20637
|
+
maximum: 50
|
|
20638
|
+
})))
|
|
20639
|
+
});
|
|
20640
|
+
const OrchestrationSearchThreadsResult = Struct({ matches: ArraySchema(Struct({
|
|
20641
|
+
threadId: ThreadId,
|
|
20642
|
+
projectId: ProjectId,
|
|
20643
|
+
source: OrchestrationThreadSearchSource,
|
|
20644
|
+
snippet: String$1.check(isMaxLength(240)),
|
|
20645
|
+
messageCreatedAt: NullOr(IsoDateTime)
|
|
20646
|
+
})) });
|
|
20511
20647
|
const ProgressPercent = NonNegativeInt.check(isLessThanOrEqualTo(100));
|
|
20512
20648
|
const AgentProtocolMessage = Union([
|
|
20513
20649
|
Struct({
|
|
@@ -20578,6 +20714,10 @@ const OrchestrationRpcSchemas = {
|
|
|
20578
20714
|
getArchivedShellSnapshot: {
|
|
20579
20715
|
input: Struct({}),
|
|
20580
20716
|
output: OrchestrationShellSnapshot
|
|
20717
|
+
},
|
|
20718
|
+
searchThreads: {
|
|
20719
|
+
input: OrchestrationSearchThreadsInput,
|
|
20720
|
+
output: OrchestrationSearchThreadsResult
|
|
20581
20721
|
}
|
|
20582
20722
|
};
|
|
20583
20723
|
var OrchestrationGetSnapshotError = class extends TaggedErrorClass()("OrchestrationGetSnapshotError", {
|
|
@@ -20600,6 +20740,10 @@ var OrchestrationReplayEventsError = class extends TaggedErrorClass()("Orchestra
|
|
|
20600
20740
|
message: TrimmedNonEmptyString,
|
|
20601
20741
|
cause: optional$4(Defect)
|
|
20602
20742
|
}) {};
|
|
20743
|
+
var OrchestrationSearchThreadsError = class extends TaggedErrorClass()("OrchestrationSearchThreadsError", {
|
|
20744
|
+
message: TrimmedNonEmptyString,
|
|
20745
|
+
cause: optional$4(Defect)
|
|
20746
|
+
}) {};
|
|
20603
20747
|
Struct({
|
|
20604
20748
|
provider: ProviderKind,
|
|
20605
20749
|
status: Literals([
|
|
@@ -20659,6 +20803,31 @@ const ProviderRespondToUserInputInput = Struct({
|
|
|
20659
20803
|
requestId: ApprovalRequestId,
|
|
20660
20804
|
answers: ProviderUserInputAnswers
|
|
20661
20805
|
});
|
|
20806
|
+
/**
|
|
20807
|
+
* Sending a thread to the people who maintain Forge.
|
|
20808
|
+
*
|
|
20809
|
+
* The vendored client's `/feedback` slash command calls this. Upstream it
|
|
20810
|
+
* uploads the transcript to the model provider; Forge routes it to the same
|
|
20811
|
+
* CMMD feedback intake the web app's feedback dialog already posts to, so one
|
|
20812
|
+
* report lands in one place no matter which surface filed it.
|
|
20813
|
+
*
|
|
20814
|
+
* `reason` is what the person typed after the command, and is the only part
|
|
20815
|
+
* of the report they wrote. The server fills in the rest.
|
|
20816
|
+
*/
|
|
20817
|
+
const ProviderUploadFeedbackInput = Struct({
|
|
20818
|
+
threadId: ThreadId,
|
|
20819
|
+
reason: optional$4(TrimmedNonEmptyString.check(isMaxLength(4e3)))
|
|
20820
|
+
});
|
|
20821
|
+
const ProviderUploadFeedbackResult = Struct({ feedbackId: TrimmedNonEmptyString });
|
|
20822
|
+
var ProviderUploadFeedbackError = class extends TaggedErrorClass()("ProviderUploadFeedbackError", {
|
|
20823
|
+
threadId: ThreadId,
|
|
20824
|
+
reason: optional$4(TrimmedNonEmptyString.check(isMaxLength(500))),
|
|
20825
|
+
cause: optional$4(Defect)
|
|
20826
|
+
}) {
|
|
20827
|
+
get message() {
|
|
20828
|
+
return this.reason ?? "Feedback could not be sent. Please try again.";
|
|
20829
|
+
}
|
|
20830
|
+
};
|
|
20662
20831
|
Struct({
|
|
20663
20832
|
id: EventId,
|
|
20664
20833
|
kind: Literals([
|
|
@@ -21133,6 +21302,46 @@ const TaskStartedPayload = Struct({
|
|
|
21133
21302
|
description: optional$4(TrimmedNonEmptyStringSchema$2),
|
|
21134
21303
|
taskType: optional$4(TrimmedNonEmptyStringSchema$2)
|
|
21135
21304
|
});
|
|
21305
|
+
/**
|
|
21306
|
+
* Watch-loop task types: Monitor-tool tasks plus background shells. A shell
|
|
21307
|
+
* that outlives its turn is in practice a watch loop, not delegated work.
|
|
21308
|
+
*/
|
|
21309
|
+
const MONITOR_TASK_TYPES = new Set([
|
|
21310
|
+
"monitor",
|
|
21311
|
+
"monitor_mcp",
|
|
21312
|
+
"local_bash",
|
|
21313
|
+
"shell"
|
|
21314
|
+
]);
|
|
21315
|
+
/** Task types that are neither agents nor watch loops (plan-mode bookkeeping). */
|
|
21316
|
+
const INERT_TASK_TYPES = new Set(["plan", "dream"]);
|
|
21317
|
+
/**
|
|
21318
|
+
* Agent-vs-background classification for a task row.
|
|
21319
|
+
*
|
|
21320
|
+
* The mobile client reads `agentKind` off the persisted activity payload and
|
|
21321
|
+
* treats a row without it as background, so a server that never stamps the
|
|
21322
|
+
* field leaves the Agents roster permanently empty however much subagent work
|
|
21323
|
+
* is running (FOR-36646). This is the stamp.
|
|
21324
|
+
*
|
|
21325
|
+
* A denylist, not an allowlist, and deliberately so: the agent-flavoured type
|
|
21326
|
+
* names drift with the SDKs (subagent, local_agent, local_workflow, and
|
|
21327
|
+
* whatever lands next), and an allowlist drops real subagents the day a new
|
|
21328
|
+
* name appears. Only the watch loops and the plan-mode bookkeeping types are
|
|
21329
|
+
* named here; everything else is delegated work.
|
|
21330
|
+
*
|
|
21331
|
+
* A task launched from inside a subagent (`agentId` set) is agent-internal
|
|
21332
|
+
* background work unless it is itself agent-flavoured, because a nested agent
|
|
21333
|
+
* can outlive its parent and has to stay in the roster.
|
|
21334
|
+
*
|
|
21335
|
+
* This mirrors `classifyTaskAgentKind` in the contracts the phone speaks. The
|
|
21336
|
+
* two packages do not import each other, so the sets are duplicated on
|
|
21337
|
+
* purpose; they have to agree, and the tests on both sides pin the same cases.
|
|
21338
|
+
*/
|
|
21339
|
+
function classifyTaskAgentKind(input) {
|
|
21340
|
+
const { taskType, agentId } = input;
|
|
21341
|
+
const nonAgentType = taskType !== void 0 && (MONITOR_TASK_TYPES.has(taskType) || INERT_TASK_TYPES.has(taskType));
|
|
21342
|
+
if (agentId !== void 0 && agentId.trim().length > 0) return taskType === void 0 || nonAgentType ? "background" : "agent";
|
|
21343
|
+
return nonAgentType ? "background" : "agent";
|
|
21344
|
+
}
|
|
21136
21345
|
const TaskProgressPayload = Struct({
|
|
21137
21346
|
taskId: RuntimeTaskId,
|
|
21138
21347
|
description: TrimmedNonEmptyStringSchema$2,
|
|
@@ -25715,6 +25924,8 @@ const WS_METHODS = {
|
|
|
25715
25924
|
projectsListEntries: "projects.listEntries",
|
|
25716
25925
|
serverDiscoverSourceControl: "server.discoverSourceControl",
|
|
25717
25926
|
serverReportClientActivity: "server.reportClientActivity",
|
|
25927
|
+
sourceControlLookupRepository: "sourceControl.lookupRepository",
|
|
25928
|
+
sourceControlCloneRepository: "sourceControl.cloneRepository",
|
|
25718
25929
|
projectsWriteFile: "projects.writeFile",
|
|
25719
25930
|
projectCheckoutDeletionPreview: "projectCheckoutDeletion.preview",
|
|
25720
25931
|
projectCheckoutDeletionExecute: "projectCheckoutDeletion.execute",
|
|
@@ -25818,7 +26029,9 @@ const WS_METHODS = {
|
|
|
25818
26029
|
subscribeServerConfig: "subscribeServerConfig",
|
|
25819
26030
|
subscribeServerLifecycle: "subscribeServerLifecycle",
|
|
25820
26031
|
subscribeAuthAccess: "subscribeAuthAccess",
|
|
25821
|
-
subscribeWorkspaceReadiness: "subscribeWorkspaceReadiness"
|
|
26032
|
+
subscribeWorkspaceReadiness: "subscribeWorkspaceReadiness",
|
|
26033
|
+
assetsCreateUrl: "assets.createUrl",
|
|
26034
|
+
providerUploadFeedback: "provider.uploadFeedback"
|
|
25822
26035
|
};
|
|
25823
26036
|
const WsServerUpsertKeybindingRpc = make$30(WS_METHODS.serverUpsertKeybinding, {
|
|
25824
26037
|
payload: ServerUpsertKeybindingInput,
|
|
@@ -26347,6 +26560,29 @@ const WsOrchestrationGetArchivedShellSnapshotRpc = make$30(ORCHESTRATION_WS_METH
|
|
|
26347
26560
|
success: OrchestrationRpcSchemas.getArchivedShellSnapshot.output,
|
|
26348
26561
|
error: OrchestrationGetSnapshotError
|
|
26349
26562
|
});
|
|
26563
|
+
const WsOrchestrationSearchThreadsRpc = make$30(ORCHESTRATION_WS_METHODS.searchThreads, {
|
|
26564
|
+
payload: OrchestrationSearchThreadsInput,
|
|
26565
|
+
success: OrchestrationRpcSchemas.searchThreads.output,
|
|
26566
|
+
error: OrchestrationSearchThreadsError
|
|
26567
|
+
});
|
|
26568
|
+
/**
|
|
26569
|
+
* Resolve `owner/repo` to the repository the caller is allowed to clone.
|
|
26570
|
+
*
|
|
26571
|
+
* Both of these run against the same CMMD-brokered GitHub credential the web
|
|
26572
|
+
* add-project flow uses, so a lookup that answers is a lookup whose clone can
|
|
26573
|
+
* also be authorized, and a repository the caller cannot reach is a failure
|
|
26574
|
+
* here rather than a clone that dies half way.
|
|
26575
|
+
*/
|
|
26576
|
+
const WsSourceControlLookupRepositoryRpc = make$30(WS_METHODS.sourceControlLookupRepository, {
|
|
26577
|
+
payload: SourceControlRepositoryLookupInput,
|
|
26578
|
+
success: SourceControlRepositoryInfo,
|
|
26579
|
+
error: SourceControlRepositoryError
|
|
26580
|
+
});
|
|
26581
|
+
const WsSourceControlCloneRepositoryRpc = make$30(WS_METHODS.sourceControlCloneRepository, {
|
|
26582
|
+
payload: SourceControlCloneRepositoryInput,
|
|
26583
|
+
success: SourceControlCloneRepositoryResult,
|
|
26584
|
+
error: SourceControlRepositoryError
|
|
26585
|
+
});
|
|
26350
26586
|
const WsOrchestrationSubscribeShellRpc = make$30(ORCHESTRATION_WS_METHODS.subscribeShell, {
|
|
26351
26587
|
payload: OrchestrationRpcSchemas.subscribeShell.input,
|
|
26352
26588
|
success: OrchestrationRpcSchemas.subscribeShell.output,
|
|
@@ -26427,7 +26663,15 @@ const WsRpcGroup = make$29(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
|
|
|
26427
26663
|
payload: UsageSummaryInput,
|
|
26428
26664
|
success: UsageSummary,
|
|
26429
26665
|
error: UsageReadError
|
|
26430
|
-
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsCodeGraphSummaryRpc, WsCodeGraphNeighborhoodRpc, WsCodeGraphSearchRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc
|
|
26666
|
+
}), WsPullRequestsListRpc, WsPullRequestsListStatsRpc, WsPullRequestsDetailRpc, WsPullRequestsActivityRpc, WsPullRequestsThreadCommentsRpc, WsPullRequestsDiffFileContentsRpc, WsPullRequestsRunActionRpc, WsPullRequestsUpdateRpc, WsPullRequestsCommentRpc, WsPullRequestsUpdateCommentRpc, WsPullRequestsSubmitReviewRpc, WsPullRequestsReplyToThreadRpc, WsPullRequestsSetThreadResolutionRpc, WsPullRequestsSetReactionRpc, WsPullRequestsInvalidateRpc, WsPullRequestsReviewerCandidatesRpc, WsPullRequestsRequestReviewersRpc, WsProjectsSearchEntriesRpc, WsProjectsWriteFileRpc, WsProjectCheckoutDeletionPreviewRpc, WsProjectCheckoutDeletionExecuteRpc, WsCodeGraphSummaryRpc, WsCodeGraphNeighborhoodRpc, WsCodeGraphSearchRpc, WsWorkspaceReadFileRpc, WsWorkspaceBeginWriteFileRpc, WsWorkspaceWriteFileChunkRpc, WsWorkspaceCommitWriteFileRpc, WsWorkspaceCancelWriteFileRpc, WsShellOpenInEditorRpc, WsSubscribeGitStatusRpc, WsGitPullRpc, WsGitRefreshStatusRpc, WsGitRunStackedActionRpc, WsGitResolvePullRequestRpc, WsGitPreparePullRequestThreadRpc, WsGitListBranchesRpc, WsVcsListRefsRpc, WsVcsRefreshStatusRpc, WsSubscribeVcsStatusRpc, WsVcsPullRpc, WsVcsCreateWorktreeRpc, WsVcsRemoveWorktreeRpc, WsVcsCreateRefRpc, WsVcsSwitchRefRpc, WsVcsInitRpc, WsGitCreateWorktreeRpc, WsGitRemoveWorktreeRpc, WsGitCreateBranchRpc, WsGitCheckoutRpc, WsGitInitRpc, WsFilesystemBrowseRpc, WsProjectsReadFileRpc, WsReviewGetDiffPreviewRpc, WsCloudListRuntimesRpc, WsTerminalOpenRpc, WsTerminalAttachRpc, WsTerminalWriteRpc, WsTerminalResizeRpc, WsTerminalSignalRpc, WsTerminalClearRpc, WsTerminalRestartRpc, WsTerminalCloseRpc, WsBrowserOpenSessionRpc, WsBrowserListSessionsRpc, WsBrowserCloseSessionRpc, WsBrowserGetSessionRpc, WsBrowserCommandRpc, WsBrowserClearBrowsingDataRpc, WsBrowserAttachDisplayRpc, WsBrowserAddDisplayIceCandidateRpc, WsComputerInputRpc, WsDevTunnelRegisterRpc, WsDevTunnelUnregisterRpc, WsDevTunnelListRpc, WsThreadDevRuntimeGetRpc, WsThreadDevRuntimeAllocatePortRpc, WsThreadDevRuntimeReleaseRpc, WsWorkspaceEnvironmentPreflightRpc, WsWorkspaceEnsureScratchRpc, WsSubscribeTerminalEventsRpc, WsSubscribeTerminalMetadataRpc, WsProjectsListEntriesRpc, WsServerDiscoverSourceControlRpc, WsServerReportClientActivityRpc, WsSubscribeBrowserEventsRpc, WsSubscribeServerConfigRpc, WsSubscribeServerLifecycleRpc, WsSubscribeAuthAccessRpc, WsSubscribeWorkspaceReadinessRpc, WsOrchestrationDispatchCommandRpc, WsOrchestrationGetTurnDiffRpc, WsOrchestrationGetFullThreadDiffRpc, WsOrchestrationStreamTurnDiffRpc, WsOrchestrationStreamFullThreadDiffRpc, WsOrchestrationReplayEventsRpc, WsOrchestrationGetArchivedShellSnapshotRpc, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc, make$30(WS_METHODS.assetsCreateUrl, {
|
|
26667
|
+
payload: AssetsRpcSchemas.createUrl.input,
|
|
26668
|
+
success: AssetsRpcSchemas.createUrl.output,
|
|
26669
|
+
error: AssetAccessError
|
|
26670
|
+
}), make$30(WS_METHODS.providerUploadFeedback, {
|
|
26671
|
+
payload: ProviderUploadFeedbackInput,
|
|
26672
|
+
success: ProviderUploadFeedbackResult,
|
|
26673
|
+
error: ProviderUploadFeedbackError
|
|
26674
|
+
}), WsOrchestrationSearchThreadsRpc, WsSourceControlLookupRepositoryRpc, WsSourceControlCloneRepositoryRpc);
|
|
26431
26675
|
const RelayWsRpcGroup = WsRpcGroup.middleware(RelayAuthorizationMiddleware).middleware(RelayDeliveryMiddleware);
|
|
26432
26676
|
/**
|
|
26433
26677
|
* The direct-WebSocket mount of the same group, with per-method scope
|
|
@@ -31559,6 +31803,31 @@ const RELAY_METHOD_POLICY_BASES = [
|
|
|
31559
31803
|
method: ORCHESTRATION_WS_METHODS.subscribeThread,
|
|
31560
31804
|
requiredScope: "thread.read",
|
|
31561
31805
|
deliveryClass: "subscription"
|
|
31806
|
+
},
|
|
31807
|
+
{
|
|
31808
|
+
method: WS_METHODS.assetsCreateUrl,
|
|
31809
|
+
requiredScope: "thread.read",
|
|
31810
|
+
deliveryClass: "read"
|
|
31811
|
+
},
|
|
31812
|
+
{
|
|
31813
|
+
method: ORCHESTRATION_WS_METHODS.searchThreads,
|
|
31814
|
+
requiredScope: "thread.read",
|
|
31815
|
+
deliveryClass: "read"
|
|
31816
|
+
},
|
|
31817
|
+
{
|
|
31818
|
+
method: WS_METHODS.providerUploadFeedback,
|
|
31819
|
+
requiredScope: "thread.read",
|
|
31820
|
+
deliveryClass: "create-command"
|
|
31821
|
+
},
|
|
31822
|
+
{
|
|
31823
|
+
method: WS_METHODS.sourceControlLookupRepository,
|
|
31824
|
+
requiredScope: "git.read",
|
|
31825
|
+
deliveryClass: "read"
|
|
31826
|
+
},
|
|
31827
|
+
{
|
|
31828
|
+
method: WS_METHODS.sourceControlCloneRepository,
|
|
31829
|
+
requiredScope: "project.write",
|
|
31830
|
+
deliveryClass: "idempotent-command"
|
|
31562
31831
|
}
|
|
31563
31832
|
];
|
|
31564
31833
|
const FRAME_JOURNALED_METHODS = new Set([
|
|
@@ -33726,7 +33995,7 @@ function isRejectedCredential(cause) {
|
|
|
33726
33995
|
//#region src/connect/hiddenInput.ts
|
|
33727
33996
|
/** Ctrl-C arrives as this byte on a raw TTY rather than as a signal. */
|
|
33728
33997
|
const END_OF_TEXT = "";
|
|
33729
|
-
function firstLine(value) {
|
|
33998
|
+
function firstLine$1(value) {
|
|
33730
33999
|
const index = value.indexOf("\n");
|
|
33731
34000
|
const line = index === -1 ? value : value.slice(0, index);
|
|
33732
34001
|
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
@@ -33751,7 +34020,7 @@ function readHiddenLine(input) {
|
|
|
33751
34020
|
settled = true;
|
|
33752
34021
|
detach();
|
|
33753
34022
|
input.output.write("\n");
|
|
33754
|
-
resolve(firstLine(value));
|
|
34023
|
+
resolve(firstLine$1(value));
|
|
33755
34024
|
};
|
|
33756
34025
|
const onData = (chunk) => {
|
|
33757
34026
|
buffer += chunk.toString();
|
|
@@ -35183,7 +35452,7 @@ function stringify(value, fallback = "[unserializable]") {
|
|
|
35183
35452
|
return typeof fallback === "function" ? fallback(value) : fallback;
|
|
35184
35453
|
}
|
|
35185
35454
|
}
|
|
35186
|
-
function truncate$
|
|
35455
|
+
function truncate$2(str, max = 0) {
|
|
35187
35456
|
if (typeof str !== "string" || max === 0) return str;
|
|
35188
35457
|
return str.length <= max ? str : `${str.slice(0, max)}...`;
|
|
35189
35458
|
}
|
|
@@ -35790,7 +36059,7 @@ var Scope = class Scope {
|
|
|
35790
36059
|
const mergedBreadcrumb = {
|
|
35791
36060
|
timestamp: dateTimestampInSeconds(),
|
|
35792
36061
|
...breadcrumb,
|
|
35793
|
-
message: breadcrumb.message ? truncate$
|
|
36062
|
+
message: breadcrumb.message ? truncate$2(breadcrumb.message, 2048) : breadcrumb.message
|
|
35794
36063
|
};
|
|
35795
36064
|
this._breadcrumbs.push(mergedBreadcrumb);
|
|
35796
36065
|
if (this._breadcrumbs.length > maxCrumbs) {
|
|
@@ -38448,9 +38717,9 @@ function applyClientOptions(event, options) {
|
|
|
38448
38717
|
if (!event.release && release) event.release = release;
|
|
38449
38718
|
if (!event.dist && dist) event.dist = dist;
|
|
38450
38719
|
const request = event.request;
|
|
38451
|
-
if (request?.url && maxValueLength) request.url = truncate$
|
|
38720
|
+
if (request?.url && maxValueLength) request.url = truncate$2(request.url, maxValueLength);
|
|
38452
38721
|
if (maxValueLength) event.exception?.values?.forEach((exception) => {
|
|
38453
|
-
if (exception.value) exception.value = truncate$
|
|
38722
|
+
if (exception.value) exception.value = truncate$2(exception.value, maxValueLength);
|
|
38454
38723
|
});
|
|
38455
38724
|
}
|
|
38456
38725
|
function applyDebugIds(event, stackParser) {
|
|
@@ -50194,7 +50463,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
50194
50463
|
const min = forceColor || 0;
|
|
50195
50464
|
if (env.TERM === "dumb") return min;
|
|
50196
50465
|
if (process$1.platform === "win32") {
|
|
50197
|
-
const osRelease =
|
|
50466
|
+
const osRelease = $U.release().split(".");
|
|
50198
50467
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
50199
50468
|
return 1;
|
|
50200
50469
|
}
|
|
@@ -52637,7 +52906,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
52637
52906
|
exports.processDetector = void 0;
|
|
52638
52907
|
const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
|
|
52639
52908
|
const semconv_1 = require_semconv$1();
|
|
52640
|
-
const os$
|
|
52909
|
+
const os$3 = __require("os");
|
|
52641
52910
|
/**
|
|
52642
52911
|
* ProcessDetector will be used to detect the resources related current process running
|
|
52643
52912
|
* and being instrumented from the NodeJS Process module.
|
|
@@ -52659,7 +52928,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
52659
52928
|
};
|
|
52660
52929
|
if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
|
|
52661
52930
|
try {
|
|
52662
|
-
const userInfo = os$
|
|
52931
|
+
const userInfo = os$3.userInfo();
|
|
52663
52932
|
attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
|
|
52664
52933
|
} catch (e) {
|
|
52665
52934
|
api_1.diag.debug(`error obtaining process owner: ${e}`);
|
|
@@ -62067,7 +62336,7 @@ const cacheResponseHook = (span, redisCommand, cmdArgs, response) => {
|
|
|
62067
62336
|
[SEMANTIC_ATTRIBUTE_CACHE_KEY]: safeKey
|
|
62068
62337
|
});
|
|
62069
62338
|
const spanDescription = safeKey.join(", ");
|
|
62070
|
-
span.updateName(_redisOptions.maxCacheKeyLength ? truncate$
|
|
62339
|
+
span.updateName(_redisOptions.maxCacheKeyLength ? truncate$2(spanDescription, _redisOptions.maxCacheKeyLength) : spanDescription);
|
|
62071
62340
|
};
|
|
62072
62341
|
//#endregion
|
|
62073
62342
|
//#region ../../node_modules/.bun/@sentry+node@10.70.0+cfd4f484ffd29da6/node_modules/@sentry/node/build/esm/integrations/tracing/redis/vendored/semconv.js
|
|
@@ -65582,7 +65851,7 @@ function normalizeNumberish(value) {
|
|
|
65582
65851
|
}
|
|
65583
65852
|
//#endregion
|
|
65584
65853
|
//#region package.json
|
|
65585
|
-
var version$1 = "0.13.
|
|
65854
|
+
var version$1 = "0.13.4";
|
|
65586
65855
|
//#endregion
|
|
65587
65856
|
//#region src/sentry.ts
|
|
65588
65857
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -69101,6 +69370,102 @@ var BrowserTraceCollector = class extends Service$1()("forge/observability/Servi
|
|
|
69101
69370
|
*/
|
|
69102
69371
|
var ProjectFaviconResolver = class extends Service$1()("forge/project/Services/ProjectFaviconResolver") {};
|
|
69103
69372
|
//#endregion
|
|
69373
|
+
//#region src/auth/Services/ServerSecretStore.ts
|
|
69374
|
+
var SecretStoreError = class extends TaggedError("SecretStoreError") {};
|
|
69375
|
+
var ServerSecretStore = class extends Service$1()("forge/auth/Services/ServerSecretStore") {};
|
|
69376
|
+
//#endregion
|
|
69377
|
+
//#region src/auth/assetUrlToken.ts
|
|
69378
|
+
/**
|
|
69379
|
+
* A credential an image tag can carry.
|
|
69380
|
+
*
|
|
69381
|
+
* `/attachments/*` and `GET /api/project-favicon` both require an authenticated
|
|
69382
|
+
* request, and the clients that render those bytes hand a bare URL to an image
|
|
69383
|
+
* loader that sends no cookie and no Authorization header. Web gets away with
|
|
69384
|
+
* it because the browser attaches the session cookie itself. The phone holds a
|
|
69385
|
+
* bearer token it has no way to attach, so every asset it fetched would 401.
|
|
69386
|
+
*
|
|
69387
|
+
* So the URL carries its own proof. The token is signed with the same server
|
|
69388
|
+
* signing key as the session and WebSocket credentials, and it is deliberately
|
|
69389
|
+
* weaker than either of those: it names one resource, and presenting it grants
|
|
69390
|
+
* that resource and nothing else. A leaked favicon URL is a leaked favicon.
|
|
69391
|
+
*/
|
|
69392
|
+
const ASSET_TOKEN_KIND = "asset";
|
|
69393
|
+
const ASSET_URL_TOKEN_QUERY_PARAM = "assetToken";
|
|
69394
|
+
/**
|
|
69395
|
+
* An hour, against a client that re-asks every thirty minutes.
|
|
69396
|
+
*
|
|
69397
|
+
* The vendored client refreshes asset URLs on a thirty-minute interval, so a
|
|
69398
|
+
* shorter life would expire URLs the client still believes are good, and the
|
|
69399
|
+
* failure would show up as an image that worked and then stopped.
|
|
69400
|
+
*/
|
|
69401
|
+
const DEFAULT_ASSET_URL_TOKEN_TTL = hours(1);
|
|
69402
|
+
const SIGNING_SECRET_NAME$1 = "server-signing-key";
|
|
69403
|
+
const decodeAssetTokenClaims = decodeUnknownEffect(fromJsonString(Struct({
|
|
69404
|
+
v: Literal(1),
|
|
69405
|
+
kind: Literal(ASSET_TOKEN_KIND),
|
|
69406
|
+
sid: String$1,
|
|
69407
|
+
res: String$1,
|
|
69408
|
+
iat: Number$1,
|
|
69409
|
+
exp: Number$1
|
|
69410
|
+
})));
|
|
69411
|
+
/**
|
|
69412
|
+
* The canonical name of one addressable asset.
|
|
69413
|
+
*
|
|
69414
|
+
* Both the mint and the check compute this from their own side: the RPC from
|
|
69415
|
+
* the resource it was asked about, the route from the request it received. They
|
|
69416
|
+
* have to agree exactly, which is the point. A token minted for one attachment
|
|
69417
|
+
* cannot be replayed against another, or against a favicon.
|
|
69418
|
+
*
|
|
69419
|
+
* The key is built from the address, not the logical id, because the address is
|
|
69420
|
+
* what both sides actually hold. The attachments route reads its id straight
|
|
69421
|
+
* out of the URL path without percent-decoding, so keying on the decoded id
|
|
69422
|
+
* would let the two sides disagree about an id that encodes to something else.
|
|
69423
|
+
*/
|
|
69424
|
+
function assetResourceKey(resource) {
|
|
69425
|
+
return resource.kind === "attachment" ? `attachment:${resource.urlPathSegment}` : `project-favicon:${resource.cwd}`;
|
|
69426
|
+
}
|
|
69427
|
+
const resolveSigningSecret = gen(function* () {
|
|
69428
|
+
const serverConfig = yield* ServerConfig;
|
|
69429
|
+
const secretStore = yield* ServerSecretStore;
|
|
69430
|
+
return resolveConfiguredSigningKey(serverConfig.sessionSigningKey) ?? (yield* secretStore.getOrCreateRandom(SIGNING_SECRET_NAME$1, 32));
|
|
69431
|
+
});
|
|
69432
|
+
const issueAssetUrlToken = (input) => gen(function* () {
|
|
69433
|
+
const secret = yield* resolveSigningSecret;
|
|
69434
|
+
const issuedAt = yield* now;
|
|
69435
|
+
const expiresAt = add$3(issuedAt, { milliseconds: toMillis(input.ttl ?? DEFAULT_ASSET_URL_TOKEN_TTL) });
|
|
69436
|
+
const claims = {
|
|
69437
|
+
v: 1,
|
|
69438
|
+
kind: ASSET_TOKEN_KIND,
|
|
69439
|
+
sid: input.sessionId,
|
|
69440
|
+
res: input.resourceKey,
|
|
69441
|
+
iat: issuedAt.epochMilliseconds,
|
|
69442
|
+
exp: expiresAt.epochMilliseconds
|
|
69443
|
+
};
|
|
69444
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(claims));
|
|
69445
|
+
return {
|
|
69446
|
+
token: `${encodedPayload}.${signPayload(encodedPayload, secret)}`,
|
|
69447
|
+
expiresAt: expiresAt.epochMilliseconds
|
|
69448
|
+
};
|
|
69449
|
+
});
|
|
69450
|
+
/**
|
|
69451
|
+
* Whether this token opens this resource, right now.
|
|
69452
|
+
*
|
|
69453
|
+
* Returns a boolean rather than failing, because every negative answer means
|
|
69454
|
+
* the same thing to the caller: fall through to ordinary request
|
|
69455
|
+
* authentication. A malformed token from a stale client should behave like no
|
|
69456
|
+
* token at all, not like a different error.
|
|
69457
|
+
*/
|
|
69458
|
+
const verifyAssetUrlToken = (input) => gen(function* () {
|
|
69459
|
+
const [encodedPayload, signature] = input.token.split(".");
|
|
69460
|
+
if (!encodedPayload || !signature) return false;
|
|
69461
|
+
if (!timingSafeEqualBase64Url(signature, signPayload(encodedPayload, yield* resolveSigningSecret))) return false;
|
|
69462
|
+
const claims = yield* decodeAssetTokenClaims(base64UrlDecodeUtf8(encodedPayload)).pipe(orElseSucceed(() => null));
|
|
69463
|
+
if (!claims) return false;
|
|
69464
|
+
if (claims.res !== input.resourceKey) return false;
|
|
69465
|
+
const now = yield* currentTimeMillis;
|
|
69466
|
+
return claims.exp > now;
|
|
69467
|
+
}).pipe(orElseSucceed(() => false));
|
|
69468
|
+
//#endregion
|
|
69104
69469
|
//#region src/environment/Services/ServerEnvironment.ts
|
|
69105
69470
|
var ServerEnvironment = class extends Service$1()("forge/environment/Services/ServerEnvironment") {};
|
|
69106
69471
|
//#endregion
|
|
@@ -69375,6 +69740,28 @@ const requireAuthenticatedRequest$1 = gen(function* () {
|
|
|
69375
69740
|
const request = yield* HttpServerRequest;
|
|
69376
69741
|
yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
69377
69742
|
});
|
|
69743
|
+
/**
|
|
69744
|
+
* Authenticate a request for asset bytes.
|
|
69745
|
+
*
|
|
69746
|
+
* Ordinary request authentication first, so the web app's cookie keeps working
|
|
69747
|
+
* exactly as it did and nothing about these routes changes for it. An asset
|
|
69748
|
+
* token is the fallback, for the clients that render these bytes through an
|
|
69749
|
+
* image loader with no way to attach a credential. See `auth/assetUrlToken.ts`
|
|
69750
|
+
* for why the token is scoped to one resource.
|
|
69751
|
+
*/
|
|
69752
|
+
const requireAssetAccess = (resourceKey) => gen(function* () {
|
|
69753
|
+
const request = yield* HttpServerRequest;
|
|
69754
|
+
const serverAuth = yield* ServerAuth;
|
|
69755
|
+
const url = toURL(request);
|
|
69756
|
+
const token = isSome(url) ? url.value.searchParams.get(ASSET_URL_TOKEN_QUERY_PARAM) : null;
|
|
69757
|
+
if (token && token.trim().length > 0) {
|
|
69758
|
+
if (yield* verifyAssetUrlToken({
|
|
69759
|
+
token,
|
|
69760
|
+
resourceKey
|
|
69761
|
+
})) return;
|
|
69762
|
+
}
|
|
69763
|
+
yield* serverAuth.authenticateHttpRequest(request);
|
|
69764
|
+
});
|
|
69378
69765
|
const serverEnvironmentRouteLayer = add$1("GET", "/.well-known/forge/environment", gen(function* () {
|
|
69379
69766
|
return jsonUnsafe(yield* service(ServerEnvironment).pipe(flatMap((serverEnvironment) => serverEnvironment.getDescriptor)), { status: 200 });
|
|
69380
69767
|
}));
|
|
@@ -69443,12 +69830,15 @@ const otlpTracesProxyRouteLayer = add$1("POST", OTLP_TRACES_PROXY_PATH, gen(func
|
|
|
69443
69830
|
captureServerError(cause, { route: "/api/observability/v1/traces" });
|
|
69444
69831
|
}).pipe(andThen(() => logError("otlp-traces unhandled cause", { cause }))))));
|
|
69445
69832
|
const attachmentsRouteLayer = add$1("GET", `${ATTACHMENTS_ROUTE_PREFIX}/*`, gen(function* () {
|
|
69446
|
-
yield* requireAuthenticatedRequest$1;
|
|
69447
69833
|
const url = toURL(yield* HttpServerRequest);
|
|
69448
69834
|
if (isNone(url)) return text$2("Bad Request", { status: 400 });
|
|
69449
69835
|
const config = yield* ServerConfig;
|
|
69450
69836
|
const normalizedRelativePath = normalizeAttachmentRelativePath(url.value.pathname.slice(12));
|
|
69451
69837
|
if (!normalizedRelativePath) return text$2("Invalid attachment path", { status: 400 });
|
|
69838
|
+
yield* requireAssetAccess(assetResourceKey({
|
|
69839
|
+
kind: "attachment",
|
|
69840
|
+
urlPathSegment: normalizedRelativePath
|
|
69841
|
+
}));
|
|
69452
69842
|
const isIdLookup = !normalizedRelativePath.includes("/") && !normalizedRelativePath.includes(".");
|
|
69453
69843
|
const filePath = isIdLookup ? resolveAttachmentPathById({
|
|
69454
69844
|
attachmentsDir: config.attachmentsDir,
|
|
@@ -69466,12 +69856,14 @@ const attachmentsRouteLayer = add$1("GET", `${ATTACHMENTS_ROUTE_PREFIX}/*`, gen(
|
|
|
69466
69856
|
}).pipe(catch_(() => succeed(text$2("Internal Server Error", { status: 500 }))));
|
|
69467
69857
|
}).pipe(catchTag("AuthError", respondToAuthError)));
|
|
69468
69858
|
const projectFaviconRouteLayer = add$1("GET", "/api/project-favicon", gen(function* () {
|
|
69469
|
-
const
|
|
69470
|
-
yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
69471
|
-
const url = toURL(request);
|
|
69859
|
+
const url = toURL(yield* HttpServerRequest);
|
|
69472
69860
|
if (isNone(url)) return text$2("Bad Request", { status: 400 });
|
|
69473
69861
|
const projectCwd = url.value.searchParams.get("cwd");
|
|
69474
69862
|
if (!projectCwd) return text$2("Missing cwd parameter", { status: 400 });
|
|
69863
|
+
yield* requireAssetAccess(assetResourceKey({
|
|
69864
|
+
kind: "project-favicon",
|
|
69865
|
+
cwd: projectCwd
|
|
69866
|
+
}));
|
|
69475
69867
|
const faviconFilePath = yield* (yield* ProjectFaviconResolver).resolvePath(projectCwd);
|
|
69476
69868
|
if (!faviconFilePath) return empty$6({
|
|
69477
69869
|
status: 204,
|
|
@@ -74334,7 +74726,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
74334
74726
|
var fs$6 = __require("fs");
|
|
74335
74727
|
var path$5 = __require("path");
|
|
74336
74728
|
var url$1 = __require("url");
|
|
74337
|
-
var os$
|
|
74729
|
+
var os$2 = __require("os");
|
|
74338
74730
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
74339
74731
|
var vars = process.config && process.config.variables || {};
|
|
74340
74732
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
@@ -74342,8 +74734,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
74342
74734
|
var abi = versions.modules;
|
|
74343
74735
|
if (versions.deno || process.isBun) abi = "unsupported";
|
|
74344
74736
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
74345
|
-
var arch = process.env.npm_config_arch || os$
|
|
74346
|
-
var platform = process.env.npm_config_platform || os$
|
|
74737
|
+
var arch = process.env.npm_config_arch || os$2.arch();
|
|
74738
|
+
var platform = process.env.npm_config_platform || os$2.platform();
|
|
74347
74739
|
var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
|
|
74348
74740
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
74349
74741
|
var uv = (versions.uv || "").split(".")[0];
|
|
@@ -77193,6 +77585,195 @@ succeed$2(TraceDiagnostics, TraceDiagnostics.of({ read: (options) => succeed(mak
|
|
|
77193
77585
|
}
|
|
77194
77586
|
})) }));
|
|
77195
77587
|
//#endregion
|
|
77588
|
+
//#region src/assets/createAssetUrl.ts
|
|
77589
|
+
const PROJECT_FAVICON_ROUTE = "/api/project-favicon";
|
|
77590
|
+
/**
|
|
77591
|
+
* Turn a resource into an address a client can fetch.
|
|
77592
|
+
*
|
|
77593
|
+
* Nothing new is served here. `/attachments/*` and `GET /api/project-favicon`
|
|
77594
|
+
* already return these bytes; what did not exist was the step in between, and
|
|
77595
|
+
* without it a client holding an attachment id or a workspace root has no way
|
|
77596
|
+
* to name the URL. The vendored mobile client asks this for every project on
|
|
77597
|
+
* its thread list, so the gap was not an edge case, it was the most visited
|
|
77598
|
+
* screen.
|
|
77599
|
+
*
|
|
77600
|
+
* The URL is relative so it resolves against whichever base the client is
|
|
77601
|
+
* already talking to, and it carries a token because the clients that render
|
|
77602
|
+
* these bytes hand the URL to an image loader that attaches no credential of
|
|
77603
|
+
* its own. See `auth/assetUrlToken.ts`.
|
|
77604
|
+
*/
|
|
77605
|
+
const createAssetUrl = (sessionId, resource) => gen(function* () {
|
|
77606
|
+
if (resource._tag === "workspace-file") return yield* new AssetWorkspaceResolutionError({
|
|
77607
|
+
resource,
|
|
77608
|
+
cause: "Workspace files are read over projects.readFile, not by URL."
|
|
77609
|
+
});
|
|
77610
|
+
const address = resolveAddress(resource);
|
|
77611
|
+
const issued = yield* issueAssetUrlToken({
|
|
77612
|
+
sessionId,
|
|
77613
|
+
resourceKey: address.resourceKey
|
|
77614
|
+
}).pipe(mapError((cause) => new AssetSigningKeyLoadError({
|
|
77615
|
+
resource,
|
|
77616
|
+
cause
|
|
77617
|
+
})));
|
|
77618
|
+
return {
|
|
77619
|
+
relativeUrl: `${address.path}?${address.query}assetToken=${encodeURIComponent(issued.token)}`,
|
|
77620
|
+
expiresAt: issued.expiresAt,
|
|
77621
|
+
...resource._tag === "project-favicon" && resource.path ? { sourcePath: resource.path } : {}
|
|
77622
|
+
};
|
|
77623
|
+
});
|
|
77624
|
+
function resolveAddress(resource) {
|
|
77625
|
+
if (resource._tag === "attachment") {
|
|
77626
|
+
const urlPathSegment = encodeURIComponent(resource.attachmentId);
|
|
77627
|
+
return {
|
|
77628
|
+
resourceKey: assetResourceKey({
|
|
77629
|
+
kind: "attachment",
|
|
77630
|
+
urlPathSegment
|
|
77631
|
+
}),
|
|
77632
|
+
path: `${ATTACHMENTS_ROUTE_PREFIX}/${urlPathSegment}`,
|
|
77633
|
+
query: ""
|
|
77634
|
+
};
|
|
77635
|
+
}
|
|
77636
|
+
return {
|
|
77637
|
+
resourceKey: assetResourceKey({
|
|
77638
|
+
kind: "project-favicon",
|
|
77639
|
+
cwd: resource.cwd
|
|
77640
|
+
}),
|
|
77641
|
+
path: PROJECT_FAVICON_ROUTE,
|
|
77642
|
+
query: `cwd=${encodeURIComponent(resource.cwd)}&`
|
|
77643
|
+
};
|
|
77644
|
+
}
|
|
77645
|
+
//#endregion
|
|
77646
|
+
//#region src/cmmd/submitFeedback.ts
|
|
77647
|
+
/** The intake the web app's feedback dialog already posts to, reached directly. */
|
|
77648
|
+
const CMMD_FEEDBACK_PATH = "/api/forge/context/feedback";
|
|
77649
|
+
/**
|
|
77650
|
+
* The intake answers with the task it created.
|
|
77651
|
+
*
|
|
77652
|
+
* Web reaches it through the proxy registry, which unwraps `data` before the
|
|
77653
|
+
* caller sees it; this calls the gateway directly and does not, so both shapes
|
|
77654
|
+
* are read here rather than pinning the one that happens to arrive today.
|
|
77655
|
+
*/
|
|
77656
|
+
function readFeedbackId(response) {
|
|
77657
|
+
if (!response || typeof response !== "object") return null;
|
|
77658
|
+
const record = response;
|
|
77659
|
+
const envelope = record["data"];
|
|
77660
|
+
const id = (envelope && typeof envelope === "object" ? envelope : record)["id"];
|
|
77661
|
+
if (typeof id === "number") return String(id);
|
|
77662
|
+
if (typeof id === "string" && id.trim().length > 0) return id.trim();
|
|
77663
|
+
return null;
|
|
77664
|
+
}
|
|
77665
|
+
/**
|
|
77666
|
+
* File feedback against the caller's own CMMD account.
|
|
77667
|
+
*
|
|
77668
|
+
* The session's CMMD JWT is what scopes it. A server with no CMMD connection
|
|
77669
|
+
* has nowhere to send feedback and says so in a sentence, because the caller
|
|
77670
|
+
* draws this text straight into the thread the person is looking at.
|
|
77671
|
+
*/
|
|
77672
|
+
function submitCmmdFeedbackForSession(input) {
|
|
77673
|
+
return gen(function* () {
|
|
77674
|
+
const jwt = yield* resolveCmmdJwtForSession({
|
|
77675
|
+
sessionId: input.sessionId,
|
|
77676
|
+
...input.subject === void 0 ? {} : { subject: input.subject },
|
|
77677
|
+
logPrefix: "cmmd-feedback"
|
|
77678
|
+
}).pipe(orElseSucceed(() => null));
|
|
77679
|
+
if (!jwt) return {
|
|
77680
|
+
ok: false,
|
|
77681
|
+
reason: "This server is not connected to CMMD, so there is nowhere to send feedback."
|
|
77682
|
+
};
|
|
77683
|
+
const body = {
|
|
77684
|
+
type: "bug",
|
|
77685
|
+
title: input.title,
|
|
77686
|
+
source: "forge",
|
|
77687
|
+
priority: "medium",
|
|
77688
|
+
...input.description ? { description: input.description } : {},
|
|
77689
|
+
...input.senderName ? { senderName: input.senderName } : {}
|
|
77690
|
+
};
|
|
77691
|
+
const response = yield* tryPromise(() => cmmdGateway.request({
|
|
77692
|
+
method: "POST",
|
|
77693
|
+
jwt,
|
|
77694
|
+
path: CMMD_FEEDBACK_PATH,
|
|
77695
|
+
body
|
|
77696
|
+
})).pipe(orElseSucceed(() => null));
|
|
77697
|
+
if (response === null) return {
|
|
77698
|
+
ok: false,
|
|
77699
|
+
reason: "The feedback service did not accept the report. Please try again."
|
|
77700
|
+
};
|
|
77701
|
+
const feedbackId = readFeedbackId(response);
|
|
77702
|
+
if (feedbackId === null) return {
|
|
77703
|
+
ok: false,
|
|
77704
|
+
reason: "The feedback service answered without recording a report."
|
|
77705
|
+
};
|
|
77706
|
+
return {
|
|
77707
|
+
ok: true,
|
|
77708
|
+
feedbackId
|
|
77709
|
+
};
|
|
77710
|
+
});
|
|
77711
|
+
}
|
|
77712
|
+
//#endregion
|
|
77713
|
+
//#region src/provider/threadFeedback.ts
|
|
77714
|
+
const FEEDBACK_TITLE_MAX_CHARS = 120;
|
|
77715
|
+
function firstLine(text) {
|
|
77716
|
+
return text.split("\n", 1)[0]?.trim() ?? "";
|
|
77717
|
+
}
|
|
77718
|
+
function truncate$1(text, max) {
|
|
77719
|
+
return text.length <= max ? text : `${text.slice(0, max - 1).trimEnd()}…`;
|
|
77720
|
+
}
|
|
77721
|
+
/**
|
|
77722
|
+
* A title someone triaging the intake can act on.
|
|
77723
|
+
*
|
|
77724
|
+
* What the person typed after `/feedback` first, because it is the only part
|
|
77725
|
+
* they wrote. The thread's title is the fallback, and a bare thread id is the
|
|
77726
|
+
* last resort, since a queue of "Forge feedback" rows tells a triager nothing.
|
|
77727
|
+
*/
|
|
77728
|
+
function feedbackTitle(input) {
|
|
77729
|
+
const reason = input.reason ? firstLine(input.reason) : "";
|
|
77730
|
+
if (reason.length > 0) return truncate$1(reason, FEEDBACK_TITLE_MAX_CHARS);
|
|
77731
|
+
const threadTitle = input.threadTitle?.trim() ?? "";
|
|
77732
|
+
if (threadTitle.length > 0) return truncate$1(`Forge feedback: ${threadTitle}`, FEEDBACK_TITLE_MAX_CHARS);
|
|
77733
|
+
return truncate$1(`Forge feedback: thread ${input.threadId}`, FEEDBACK_TITLE_MAX_CHARS);
|
|
77734
|
+
}
|
|
77735
|
+
/** The body, which always names the thread so the report can be traced back to it. */
|
|
77736
|
+
function feedbackDescription(input) {
|
|
77737
|
+
const lines = [`Filed from Forge thread \`${input.threadId}\`.`];
|
|
77738
|
+
if (input.threadTitle?.trim()) lines.push(`Thread title: ${input.threadTitle.trim()}`);
|
|
77739
|
+
if (input.reason?.trim()) lines.push("", input.reason.trim());
|
|
77740
|
+
return lines.join("\n");
|
|
77741
|
+
}
|
|
77742
|
+
/**
|
|
77743
|
+
* `/feedback`, served.
|
|
77744
|
+
*
|
|
77745
|
+
* The vendored client declared this method and called it; Forge answered
|
|
77746
|
+
* nothing, so the slash command printed the transport's own "unknown request
|
|
77747
|
+
* tag" string into the thread. Routing it to the CMMD intake the web feedback
|
|
77748
|
+
* dialog already uses means one report format and one destination, whichever
|
|
77749
|
+
* surface it came from.
|
|
77750
|
+
*
|
|
77751
|
+
* Every failure carries a sentence rather than a cause, because the client
|
|
77752
|
+
* draws `message` straight into the thread as an assistant reply.
|
|
77753
|
+
*/
|
|
77754
|
+
const uploadThreadFeedback = (orchestrationEngine, sessionId, subject, input) => gen(function* () {
|
|
77755
|
+
const threadTitle = (yield* orchestrationEngine.getReadModel().pipe(orElseSucceed(() => null)))?.threads.find((thread) => thread.id === input.threadId)?.title;
|
|
77756
|
+
const outcome = yield* submitCmmdFeedbackForSession({
|
|
77757
|
+
sessionId,
|
|
77758
|
+
...subject === void 0 ? {} : { subject },
|
|
77759
|
+
title: feedbackTitle({
|
|
77760
|
+
...input,
|
|
77761
|
+
threadId: input.threadId,
|
|
77762
|
+
...threadTitle ? { threadTitle } : {}
|
|
77763
|
+
}),
|
|
77764
|
+
description: feedbackDescription({
|
|
77765
|
+
...input,
|
|
77766
|
+
threadId: input.threadId,
|
|
77767
|
+
...threadTitle ? { threadTitle } : {}
|
|
77768
|
+
})
|
|
77769
|
+
});
|
|
77770
|
+
if (!outcome.ok) return yield* new ProviderUploadFeedbackError({
|
|
77771
|
+
threadId: input.threadId,
|
|
77772
|
+
reason: outcome.reason
|
|
77773
|
+
});
|
|
77774
|
+
return { feedbackId: outcome.feedbackId };
|
|
77775
|
+
});
|
|
77776
|
+
//#endregion
|
|
77196
77777
|
//#region src/cloudRuntime/listCloudRuntimes.ts
|
|
77197
77778
|
/**
|
|
77198
77779
|
* List the caller's Forge Cloud runtimes, through CMMD, as the caller.
|
|
@@ -87222,95 +87803,278 @@ function resolveGitHubCloneDir(baseDir, repoFullName, targetDir) {
|
|
|
87222
87803
|
const repoName = repoFullName.split("/")[1] ?? repoFullName;
|
|
87223
87804
|
return targetDir ?? join(baseDir, "Projects", repoName);
|
|
87224
87805
|
}
|
|
87225
|
-
|
|
87226
|
-
|
|
87227
|
-
|
|
87228
|
-
|
|
87229
|
-
|
|
87230
|
-
|
|
87231
|
-
|
|
87232
|
-
|
|
87233
|
-
|
|
87234
|
-
|
|
87235
|
-
|
|
87236
|
-
|
|
87237
|
-
|
|
87238
|
-
|
|
87239
|
-
|
|
87240
|
-
|
|
87241
|
-
|
|
87242
|
-
|
|
87243
|
-
|
|
87244
|
-
|
|
87245
|
-
|
|
87246
|
-
|
|
87806
|
+
/**
|
|
87807
|
+
* The public clone URLs for a repository.
|
|
87808
|
+
*
|
|
87809
|
+
* Never derived from an authenticated remote: `buildAuthenticatedRemoteUrl`
|
|
87810
|
+
* embeds an installation token in the URL, and a token that reaches a client is
|
|
87811
|
+
* a token that reaches wherever the client writes it down.
|
|
87812
|
+
*/
|
|
87813
|
+
function describeGitHubRepository(nameWithOwner) {
|
|
87814
|
+
return {
|
|
87815
|
+
nameWithOwner,
|
|
87816
|
+
url: `https://github.com/${nameWithOwner}`,
|
|
87817
|
+
sshUrl: `git@github.com:${nameWithOwner}.git`
|
|
87818
|
+
};
|
|
87819
|
+
}
|
|
87820
|
+
/**
|
|
87821
|
+
* Resolve `owner/repo` against the repositories CMMD says this caller may use.
|
|
87822
|
+
*
|
|
87823
|
+
* This asks the same brokered listing the web repository picker is populated
|
|
87824
|
+
* from, rather than github.com directly, so "found" here means the same thing
|
|
87825
|
+
* it means everywhere else in Forge: CMMD will vend this caller a token for it.
|
|
87826
|
+
* A repository that exists but that the caller cannot reach is a 404, which is
|
|
87827
|
+
* the answer they can act on.
|
|
87828
|
+
*/
|
|
87829
|
+
function lookupGitHubRepositoryForSession(input) {
|
|
87830
|
+
return gen(function* () {
|
|
87831
|
+
const requested = input.repository.trim().replace(/^\/+|\/+$/g, "");
|
|
87832
|
+
if (!/^[^/\s]+\/[^/\s]+$/.test(requested)) return {
|
|
87833
|
+
status: 400,
|
|
87834
|
+
repository: null,
|
|
87835
|
+
error: "Enter a repository as owner/name."
|
|
87836
|
+
};
|
|
87837
|
+
const jwt = input.runtimeGrantAuth ? null : yield* resolveJwt(input.sessionId, input.subject);
|
|
87838
|
+
if (!input.runtimeGrantAuth && !jwt) return {
|
|
87839
|
+
status: 401,
|
|
87840
|
+
repository: null,
|
|
87841
|
+
error: "CMMD not connected"
|
|
87842
|
+
};
|
|
87843
|
+
const auth = input.runtimeGrantAuth ?? {
|
|
87247
87844
|
kind: "cmmd-jwt",
|
|
87248
|
-
jwt
|
|
87249
|
-
}
|
|
87250
|
-
|
|
87251
|
-
|
|
87252
|
-
|
|
87253
|
-
|
|
87254
|
-
|
|
87255
|
-
|
|
87256
|
-
|
|
87845
|
+
jwt
|
|
87846
|
+
};
|
|
87847
|
+
const searchTerm = requested.split("/")[1] ?? requested;
|
|
87848
|
+
let result = yield* promise(() => fetchGitHubReposForAuth(auth, {
|
|
87849
|
+
search: searchTerm,
|
|
87850
|
+
organizationId: input.organizationId ?? ""
|
|
87851
|
+
}));
|
|
87852
|
+
if (!input.runtimeGrantAuth && shouldRetryWithRefreshedCmmdJwt(result.status)) {
|
|
87853
|
+
const refreshedJwt = yield* resolveJwt(input.sessionId, input.subject, true);
|
|
87854
|
+
if (refreshedJwt && refreshedJwt !== jwt) result = yield* promise(() => fetchGitHubReposForAuth({
|
|
87855
|
+
kind: "cmmd-jwt",
|
|
87856
|
+
jwt: refreshedJwt
|
|
87857
|
+
}, {
|
|
87858
|
+
search: searchTerm,
|
|
87859
|
+
organizationId: input.organizationId ?? ""
|
|
87860
|
+
}));
|
|
87861
|
+
}
|
|
87862
|
+
if (result.status !== 200) {
|
|
87863
|
+
const body = result.body;
|
|
87864
|
+
return {
|
|
87865
|
+
status: result.status,
|
|
87866
|
+
repository: null,
|
|
87867
|
+
error: typeof body.error === "string" ? body.error : "GitHub repositories are unavailable."
|
|
87868
|
+
};
|
|
87869
|
+
}
|
|
87870
|
+
const match = selectGitHubRepositoryMatch(result.body, requested);
|
|
87871
|
+
if (match === null) return {
|
|
87872
|
+
status: 404,
|
|
87873
|
+
repository: null,
|
|
87874
|
+
error: `Repository ${requested} was not found in the GitHub accounts connected to Forge.`
|
|
87875
|
+
};
|
|
87876
|
+
return {
|
|
87877
|
+
status: 200,
|
|
87878
|
+
repository: describeGitHubRepository(match),
|
|
87879
|
+
error: null
|
|
87880
|
+
};
|
|
87881
|
+
});
|
|
87882
|
+
}
|
|
87883
|
+
/**
|
|
87884
|
+
* Find the one repository in a brokered listing whose full name is the one
|
|
87885
|
+
* asked for.
|
|
87886
|
+
*
|
|
87887
|
+
* Exact and case-insensitive. The listing is a *search*, so it comes back with
|
|
87888
|
+
* near misses in it: asking for `owner/forge` and being handed `someone-else/
|
|
87889
|
+
* forge` because it sorted first is how a person ends up cloning a stranger's
|
|
87890
|
+
* repository, and a prefix match would do exactly that.
|
|
87891
|
+
*/
|
|
87892
|
+
function selectGitHubRepositoryMatch(body, requested) {
|
|
87893
|
+
const repos = body?.repos ?? [];
|
|
87894
|
+
const wanted = requested.trim().toLowerCase();
|
|
87895
|
+
for (const repo of repos) if (typeof repo.full_name === "string" && repo.full_name.toLowerCase() === wanted) return repo.full_name;
|
|
87896
|
+
return null;
|
|
87897
|
+
}
|
|
87898
|
+
/**
|
|
87899
|
+
* The repository a clone request is really for, or `null` if there is not one.
|
|
87900
|
+
*
|
|
87901
|
+
* A caller may name `owner/repo`, or paste a clone URL and let the server work
|
|
87902
|
+
* it out. Anything that does not resolve to a GitHub repository returns `null`
|
|
87903
|
+
* and must be refused rather than cloned: Forge clones through a CMMD-brokered
|
|
87904
|
+
* installation token for a named repository, and "just run `git clone` on this
|
|
87905
|
+
* URL instead" would be a second, unbrokered way to write to disk.
|
|
87906
|
+
*/
|
|
87907
|
+
function resolveGitHubCloneTarget(input) {
|
|
87908
|
+
const named = input.repository?.trim();
|
|
87909
|
+
if (named && /^[^/\s]+\/[^/\s]+$/.test(named)) return named;
|
|
87910
|
+
if (named) return null;
|
|
87911
|
+
const remoteUrl = input.remoteUrl?.trim();
|
|
87912
|
+
return remoteUrl ? parseRepoFullNameFromRemoteUrl(remoteUrl) : null;
|
|
87913
|
+
}
|
|
87914
|
+
/**
|
|
87915
|
+
* Clone `repoFullName` for an authenticated session.
|
|
87916
|
+
*
|
|
87917
|
+
* Authorization is the caller's own CMMD connection: the session's JWT (or a
|
|
87918
|
+
* runtime grant, when this VM is running under one) is exchanged with CMMD for
|
|
87919
|
+
* an installation token scoped to that one repository. A session with no CMMD
|
|
87920
|
+
* connection gets 401 and no clone; a repository CMMD will not issue a token
|
|
87921
|
+
* for gets CMMD's own refusal, not a `git clone` attempt.
|
|
87922
|
+
*/
|
|
87923
|
+
function runGitHubCloneForSession(input) {
|
|
87924
|
+
return gen(function* () {
|
|
87925
|
+
const { config, repoFullName, targetDir } = input;
|
|
87926
|
+
const token = yield* acquireInstallationTokenForSession(input);
|
|
87927
|
+
if (!token.ok) return token.outcome;
|
|
87928
|
+
const cloneDir = resolveGitHubCloneDir(config.baseDir, repoFullName, targetDir);
|
|
87929
|
+
yield* promise(() => mkdir(join(cloneDir, ".."), { recursive: true }).catch(() => void 0));
|
|
87930
|
+
const target = {
|
|
87931
|
+
cloneDir,
|
|
87932
|
+
repoFullName,
|
|
87933
|
+
token: token.token,
|
|
87934
|
+
config
|
|
87935
|
+
};
|
|
87936
|
+
return existsSync(cloneDir) ? yield* adoptExistingGitHubClone(target) : yield* cloneGitHubRepositoryInto(target);
|
|
87937
|
+
});
|
|
87938
|
+
}
|
|
87939
|
+
/**
|
|
87940
|
+
* The installation token for one repository, on the caller's own authority.
|
|
87941
|
+
*
|
|
87942
|
+
* A runtime grant is used as-is. A session resolves its CMMD JWT and retries
|
|
87943
|
+
* once with a refreshed one when CMMD answers 401 or 403, because the cached
|
|
87944
|
+
* JWT may simply have expired. The failure branch is already the HTTP outcome,
|
|
87945
|
+
* so a caller returns it rather than translating it.
|
|
87946
|
+
*/
|
|
87947
|
+
function acquireInstallationTokenForSession(input) {
|
|
87948
|
+
return gen(function* () {
|
|
87949
|
+
const { runtimeGrantAuth, repoFullName } = input;
|
|
87950
|
+
const jwt = runtimeGrantAuth ? null : yield* resolveJwt(input.sessionId, input.subject);
|
|
87951
|
+
if (!runtimeGrantAuth && !jwt) return {
|
|
87952
|
+
ok: false,
|
|
87953
|
+
outcome: {
|
|
87954
|
+
status: 401,
|
|
87955
|
+
body: { error: "CMMD not connected" }
|
|
87956
|
+
}
|
|
87957
|
+
};
|
|
87958
|
+
let token = yield* fetchInstallationTokenForAuth(runtimeGrantAuth ?? {
|
|
87959
|
+
kind: "cmmd-jwt",
|
|
87960
|
+
jwt
|
|
87961
|
+
}, repoFullName);
|
|
87962
|
+
if (!runtimeGrantAuth && !token.ok && shouldRetryWithRefreshedCmmdJwt(token.status)) {
|
|
87963
|
+
const refreshedJwt = yield* resolveJwt(input.sessionId, input.subject, true);
|
|
87964
|
+
if (refreshedJwt && refreshedJwt !== jwt) token = yield* fetchInstallationTokenForAuth({
|
|
87965
|
+
kind: "cmmd-jwt",
|
|
87966
|
+
jwt: refreshedJwt
|
|
87967
|
+
}, repoFullName);
|
|
87968
|
+
}
|
|
87969
|
+
if (!token.ok) return {
|
|
87970
|
+
ok: false,
|
|
87971
|
+
outcome: {
|
|
87972
|
+
status: token.status,
|
|
87973
|
+
body: gitHubTokenFailureBody(token.status, token.error)
|
|
87974
|
+
}
|
|
87975
|
+
};
|
|
87976
|
+
return {
|
|
87977
|
+
ok: true,
|
|
87978
|
+
token: token.token
|
|
87979
|
+
};
|
|
87980
|
+
});
|
|
87981
|
+
}
|
|
87982
|
+
/**
|
|
87983
|
+
* A directory already sits where the clone would go. Adopt it when it is a
|
|
87984
|
+
* checkout of the same repository, so a second "add project" is a no-op with
|
|
87985
|
+
* fresh credentials rather than a failure; refuse otherwise, naming the
|
|
87986
|
+
* folder, because overwriting it would destroy something the person made.
|
|
87987
|
+
*/
|
|
87988
|
+
function adoptExistingGitHubClone(target) {
|
|
87989
|
+
return gen(function* () {
|
|
87990
|
+
const { cloneDir, repoFullName } = target;
|
|
87991
|
+
const git = (args) => promise(() => runProcess("git", [
|
|
87257
87992
|
"-C",
|
|
87258
87993
|
cloneDir,
|
|
87259
|
-
|
|
87260
|
-
"--is-inside-work-tree"
|
|
87994
|
+
...args
|
|
87261
87995
|
], {
|
|
87262
87996
|
timeoutMs: 1e4,
|
|
87263
87997
|
allowNonZeroExit: true,
|
|
87264
87998
|
env: process.env
|
|
87265
87999
|
}));
|
|
87266
|
-
const
|
|
87267
|
-
|
|
87268
|
-
cloneDir,
|
|
88000
|
+
const workTree = yield* git(["rev-parse", "--is-inside-work-tree"]);
|
|
88001
|
+
const remote = yield* git([
|
|
87269
88002
|
"remote",
|
|
87270
88003
|
"get-url",
|
|
87271
88004
|
"origin"
|
|
87272
|
-
]
|
|
87273
|
-
|
|
87274
|
-
|
|
87275
|
-
|
|
87276
|
-
|
|
87277
|
-
|
|
87278
|
-
|
|
87279
|
-
|
|
87280
|
-
|
|
87281
|
-
|
|
87282
|
-
|
|
87283
|
-
|
|
87284
|
-
return json$6({
|
|
88005
|
+
]);
|
|
88006
|
+
if (!(workTree.code === 0 && workTree.stdout.trim() === "true" && remote.code === 0 && isGitHubRemoteForRepo(remote.stdout.trim(), repoFullName))) return {
|
|
88007
|
+
status: 409,
|
|
88008
|
+
body: {
|
|
88009
|
+
error: "A project folder with that name already exists, but it is not the selected GitHub repository. Rename the import, remove the VM folder, or choose a different repository.",
|
|
88010
|
+
local_path: cloneDir
|
|
88011
|
+
}
|
|
88012
|
+
};
|
|
88013
|
+
yield* applyGitCredentialsFor(target);
|
|
88014
|
+
return {
|
|
88015
|
+
status: 200,
|
|
88016
|
+
body: {
|
|
87285
88017
|
local_path: cloneDir,
|
|
87286
88018
|
existing: true
|
|
87287
|
-
}
|
|
87288
|
-
}
|
|
87289
|
-
|
|
87290
|
-
|
|
87291
|
-
|
|
87292
|
-
|
|
87293
|
-
|
|
87294
|
-
|
|
87295
|
-
|
|
87296
|
-
|
|
87297
|
-
|
|
87298
|
-
|
|
87299
|
-
|
|
87300
|
-
|
|
88019
|
+
}
|
|
88020
|
+
};
|
|
88021
|
+
});
|
|
88022
|
+
}
|
|
88023
|
+
function cloneGitHubRepositoryInto(target) {
|
|
88024
|
+
return gen(function* () {
|
|
88025
|
+
const { cloneDir, repoFullName, token } = target;
|
|
88026
|
+
const result = yield* promise(() => runProcess("git", buildAuthenticatedGitCloneArgs(repoFullName, cloneDir), {
|
|
88027
|
+
timeoutMs: 12e4,
|
|
88028
|
+
allowNonZeroExit: true,
|
|
88029
|
+
env: {
|
|
88030
|
+
...process.env,
|
|
88031
|
+
CMMD_FORGE_GIT_TOKEN: token,
|
|
88032
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
88033
|
+
}
|
|
88034
|
+
}));
|
|
88035
|
+
if (result.code !== 0) {
|
|
88036
|
+
const stderr = sanitizeGitCredentialText(result.stderr).trim().slice(0, 500);
|
|
88037
|
+
return {
|
|
88038
|
+
status: 500,
|
|
88039
|
+
body: { error: `git clone failed${stderr.length > 0 ? `: ${stderr}` : ""}` }
|
|
88040
|
+
};
|
|
87301
88041
|
}
|
|
87302
|
-
|
|
87303
|
-
|
|
87304
|
-
|
|
87305
|
-
|
|
88042
|
+
yield* applyGitCredentialsFor(target);
|
|
88043
|
+
return {
|
|
88044
|
+
status: 200,
|
|
88045
|
+
body: { local_path: cloneDir }
|
|
88046
|
+
};
|
|
88047
|
+
});
|
|
88048
|
+
}
|
|
88049
|
+
function applyGitCredentialsFor(target) {
|
|
88050
|
+
return applyGitCredentials({
|
|
88051
|
+
cloneDir: target.cloneDir,
|
|
88052
|
+
repoFullName: target.repoFullName,
|
|
88053
|
+
token: target.token,
|
|
88054
|
+
mode: target.config.forgeGitCredentialCache ? "cache" : "embed"
|
|
88055
|
+
});
|
|
88056
|
+
}
|
|
88057
|
+
const githubCloneRouteLayer = add$1("POST", "/.well-known/forge/github/clone", gen(function* () {
|
|
88058
|
+
const request = yield* HttpServerRequest;
|
|
88059
|
+
const session = yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
88060
|
+
const config = yield* ServerConfig;
|
|
88061
|
+
if (session.method === "proxy-asserted") {
|
|
88062
|
+
const forwardedJwt = normalizeForwardedCmmdJwt(request.headers);
|
|
88063
|
+
if (forwardedJwt !== void 0) yield* setCommandJwtForSession(session.sessionId, forwardedJwt).pipe(ignore({ log: true }));
|
|
87306
88064
|
}
|
|
87307
|
-
yield*
|
|
87308
|
-
|
|
88065
|
+
const rawBody = yield* request.json;
|
|
88066
|
+
const parsed = decodeUnknownExit(CloneBodySchema)(rawBody);
|
|
88067
|
+
if (parsed._tag === "Failure") return json$6({ error: "repo_full_name is required" }, 400);
|
|
88068
|
+
const { repo_full_name, target_dir } = parsed.value;
|
|
88069
|
+
const outcome = yield* runGitHubCloneForSession({
|
|
88070
|
+
config,
|
|
88071
|
+
sessionId: session.sessionId,
|
|
88072
|
+
subject: session.subject,
|
|
88073
|
+
runtimeGrantAuth: resolveRuntimeGitHubGrantAuth(config, request.headers),
|
|
87309
88074
|
repoFullName: repo_full_name,
|
|
87310
|
-
|
|
87311
|
-
mode: config.forgeGitCredentialCache ? "cache" : "embed"
|
|
88075
|
+
targetDir: target_dir
|
|
87312
88076
|
});
|
|
87313
|
-
return json$6(
|
|
88077
|
+
return json$6(outcome.body, outcome.status);
|
|
87314
88078
|
}).pipe(catchTag("AuthError", respondToAuthError)));
|
|
87315
88079
|
const githubCloneJobStartRouteLayer = add$1("POST", "/.well-known/forge/github/clone-jobs", gen(function* () {
|
|
87316
88080
|
const request = yield* HttpServerRequest;
|
|
@@ -92073,7 +92837,7 @@ const launchDetached = (launch) => gen(function* () {
|
|
|
92073
92837
|
});
|
|
92074
92838
|
const OpenLive = effect(Open, gen(function* () {
|
|
92075
92839
|
const open = yield* tryPromise({
|
|
92076
|
-
try: () => import("./open-
|
|
92840
|
+
try: () => import("./open-D8mGyWmt.mjs"),
|
|
92077
92841
|
catch: (cause) => new OpenError$1({
|
|
92078
92842
|
message: "failed to load browser opener",
|
|
92079
92843
|
cause
|
|
@@ -95829,33 +96593,34 @@ function serializeNativeToolActivity(activity) {
|
|
|
95829
96593
|
}
|
|
95830
96594
|
};
|
|
95831
96595
|
}
|
|
95832
|
-
|
|
95833
|
-
|
|
95834
|
-
return activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
96596
|
+
function isReplicatedFailureActivity(activity) {
|
|
96597
|
+
return activity.kind === "runtime.error" || activity.kind.startsWith("provider.") && activity.kind.endsWith(".failed");
|
|
95835
96598
|
}
|
|
95836
96599
|
function failureDetail(activity) {
|
|
95837
96600
|
const payload = activity.payload;
|
|
95838
|
-
if (!payload || typeof payload !== "object"
|
|
95839
|
-
const
|
|
95840
|
-
|
|
96601
|
+
if (!payload || typeof payload !== "object") return null;
|
|
96602
|
+
const record = payload;
|
|
96603
|
+
const detail = typeof record.detail === "string" ? record.detail.trim() : "";
|
|
96604
|
+
if (detail) return detail;
|
|
96605
|
+
return (typeof record.message === "string" ? record.message.trim() : "") || null;
|
|
95841
96606
|
}
|
|
95842
96607
|
/**
|
|
95843
96608
|
* Replicates why a turn failed.
|
|
95844
96609
|
*
|
|
95845
|
-
* Without this a failure never crossed the replication boundary
|
|
95846
|
-
* activities were serialized
|
|
96610
|
+
* Without this a failure never crossed the replication boundary. Only `tool.`
|
|
96611
|
+
* activities were serialized, so durable history could not show one and every
|
|
95847
96612
|
* failure reached the user as an endless "Working" spinner. Measured in
|
|
95848
96613
|
* production on 2026-08-30: zero failure events had EVER been recorded against
|
|
95849
96614
|
* 652 tool_call rows, which is how a workspace-path block, a broker-token
|
|
95850
96615
|
* refusal and a dispatch into a deleted directory all looked identical.
|
|
95851
96616
|
*
|
|
95852
|
-
* `error_summary` is used
|
|
95853
|
-
*
|
|
95854
|
-
*
|
|
95855
|
-
*
|
|
96617
|
+
* `error_summary` is used because both the Forge protocol and CMMD's ingest
|
|
96618
|
+
* schema already accept it. Provider startup failures store `detail`, while
|
|
96619
|
+
* active runtime failures store `message`. The event has no separate detail
|
|
96620
|
+
* field, so either value is joined onto the summary.
|
|
95856
96621
|
*/
|
|
95857
|
-
function
|
|
95858
|
-
if (!
|
|
96622
|
+
function serializeFailureActivity(activity) {
|
|
96623
|
+
if (!isReplicatedFailureActivity(activity)) return null;
|
|
95859
96624
|
const summary = text(activity.summary, 4e3);
|
|
95860
96625
|
if (summary === null) return null;
|
|
95861
96626
|
const detail = failureDetail(activity);
|
|
@@ -96197,7 +96962,7 @@ function isTurnUsageTokens(value) {
|
|
|
96197
96962
|
function serializeRuntimeThreadEvents(thread) {
|
|
96198
96963
|
const messages = thread.messages.flatMap(serializeThreadMessage);
|
|
96199
96964
|
const failureActivities = thread.activities.flatMap((activity) => {
|
|
96200
|
-
const event =
|
|
96965
|
+
const event = serializeFailureActivity(activity);
|
|
96201
96966
|
return event === null ? [] : [event];
|
|
96202
96967
|
});
|
|
96203
96968
|
const replicaActivities = thread.activities.flatMap((activity) => {
|
|
@@ -100459,6 +101224,31 @@ function claudeWriteToken(env = process.env) {
|
|
|
100459
101224
|
return accessor.read(env);
|
|
100460
101225
|
}
|
|
100461
101226
|
//#endregion
|
|
101227
|
+
//#region ../../packages/shared/src/claudeReadinessReason.ts
|
|
101228
|
+
/**
|
|
101229
|
+
* The reason literals Forge acts on, confirmed against CMMD-36441's readiness
|
|
101230
|
+
* service. If the route renames one, the rename lands here as a fall-through
|
|
101231
|
+
* to null, never as the wrong kind.
|
|
101232
|
+
*
|
|
101233
|
+
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
101234
|
+
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
101235
|
+
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
101236
|
+
* `stale` answer that simply has not landed yet. Those want opposite
|
|
101237
|
+
* handling, and both used to get "Reconnect Claude in Settings" because the
|
|
101238
|
+
* guard read only the delivery status.
|
|
101239
|
+
*/
|
|
101240
|
+
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
101241
|
+
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
101242
|
+
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101243
|
+
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
101244
|
+
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
101245
|
+
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
101246
|
+
};
|
|
101247
|
+
function claudeReadinessReasonCode(reason) {
|
|
101248
|
+
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
101249
|
+
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
101250
|
+
}
|
|
101251
|
+
//#endregion
|
|
100462
101252
|
//#region src/egress/providerDeliveryReceipt.ts
|
|
100463
101253
|
const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
100464
101254
|
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;
|
|
@@ -100633,82 +101423,6 @@ function providerDeliveryBindingsEqual(left, right) {
|
|
|
100633
101423
|
return classifyProviderDeliveryBindingChange(left, right) === "unchanged";
|
|
100634
101424
|
}
|
|
100635
101425
|
//#endregion
|
|
100636
|
-
//#region src/provider/claudeReadinessReason.ts
|
|
100637
|
-
/**
|
|
100638
|
-
* The reason literals Forge has copy for, confirmed against CMMD-36441's
|
|
100639
|
-
* readiness service. If the route renames one, the rename lands here as a
|
|
100640
|
-
* fall-through to the generic sentence, never as a wrong sentence.
|
|
100641
|
-
*
|
|
100642
|
-
* Reading the reason is what makes `stale` legible. CMMD-36441 answers
|
|
100643
|
-
* `credential_reconnect_required` on a `stale` answer that still reports
|
|
100644
|
-
* `connected: true`, and the older `credential_generation_not_applied` on a
|
|
100645
|
-
* `stale` answer that simply has not landed yet. Those want opposite
|
|
100646
|
-
* sentences, and both used to get "Reconnect Claude in Settings" because the
|
|
100647
|
-
* guard read only the delivery status.
|
|
100648
|
-
*/
|
|
100649
|
-
const CODE_BY_UPSTREAM_REASON$1 = {
|
|
100650
|
-
credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
|
|
100651
|
-
credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100652
|
-
credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
|
|
100653
|
-
credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
|
|
100654
|
-
runtime_unavailable: "RUNTIME_UNAVAILABLE"
|
|
100655
|
-
};
|
|
100656
|
-
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
100657
|
-
/**
|
|
100658
|
-
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
100659
|
-
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
100660
|
-
* time is the only part of that sentence Forge repeats.
|
|
100661
|
-
*/
|
|
100662
|
-
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
100663
|
-
function claudeReadinessReasonCode(reason) {
|
|
100664
|
-
if (typeof reason !== "string" || !Object.hasOwn(CODE_BY_UPSTREAM_REASON$1, reason)) return null;
|
|
100665
|
-
return CODE_BY_UPSTREAM_REASON$1[reason] ?? null;
|
|
100666
|
-
}
|
|
100667
|
-
function expiredAtFromReadinessMessage(message) {
|
|
100668
|
-
if (typeof message !== "string") return null;
|
|
100669
|
-
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
100670
|
-
}
|
|
100671
|
-
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
100672
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
100673
|
-
}
|
|
100674
|
-
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
100675
|
-
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
100676
|
-
}
|
|
100677
|
-
/**
|
|
100678
|
-
* The last allowlisted readiness reason this process saw.
|
|
100679
|
-
*
|
|
100680
|
-
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
100681
|
-
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
100682
|
-
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
100683
|
-
* from a blank one, so without this the card can only offer the generic
|
|
100684
|
-
* sentence while the guard already knows better.
|
|
100685
|
-
*/
|
|
100686
|
-
let latestVerdict = null;
|
|
100687
|
-
/**
|
|
100688
|
-
* Remember, or clear, what the readiness answer said.
|
|
100689
|
-
*
|
|
100690
|
-
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
100691
|
-
* the card go back to the generic sentence the moment CMMD applies a
|
|
100692
|
-
* credential: an applied answer carries no reason.
|
|
100693
|
-
*/
|
|
100694
|
-
function recordClaudeReadinessVerdict(readiness) {
|
|
100695
|
-
const code = claudeReadinessReasonCode(readiness.reason);
|
|
100696
|
-
latestVerdict = code === null ? null : {
|
|
100697
|
-
code,
|
|
100698
|
-
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
100699
|
-
};
|
|
100700
|
-
}
|
|
100701
|
-
/**
|
|
100702
|
-
* What the provider status card says about a delivered credential it cannot
|
|
100703
|
-
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
100704
|
-
* guessed time would be worse than the generic sentence.
|
|
100705
|
-
*/
|
|
100706
|
-
function claudeNativeCredentialUnusableMessage() {
|
|
100707
|
-
const verdict = latestVerdict;
|
|
100708
|
-
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
100709
|
-
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
100710
|
-
}
|
|
100711
|
-
//#endregion
|
|
100712
101426
|
//#region src/provider/providerReadiness.ts
|
|
100713
101427
|
const GENERATION_HASH_PATTERN$1 = /^[a-f0-9]{64}$/;
|
|
100714
101428
|
const STILL_APPLYING_STATUSES = new Set([
|
|
@@ -100753,10 +101467,18 @@ async function queryProviderReadiness(input) {
|
|
|
100753
101467
|
}
|
|
100754
101468
|
return classifyClaudeSnapshot(snapshot, sprite);
|
|
100755
101469
|
}
|
|
100756
|
-
|
|
100757
|
-
|
|
101470
|
+
/**
|
|
101471
|
+
* Whether CMMD allows a turn to start on this runtime. The delivery status is
|
|
101472
|
+
* not part of it: a refresh that failed over a credential the guest verified
|
|
101473
|
+
* and that has not expired reads as failed with nativeExecutable true, and the
|
|
101474
|
+
* turn is the probe (FOR-3085). The generation fence and the receipt below
|
|
101475
|
+
* still bind the answer.
|
|
101476
|
+
*/
|
|
101477
|
+
function claudeSnapshotAllowsTurn(snapshot) {
|
|
101478
|
+
return snapshot.connected === true && snapshot.nativeExecutable === true && typeof snapshot.generationId === "string" && snapshot.generationId.trim().length > 0;
|
|
100758
101479
|
}
|
|
100759
|
-
|
|
101480
|
+
/** Why a turn may not start, for an answer that does not allow one. */
|
|
101481
|
+
function classifyClaudeTurnRefusal(snapshot) {
|
|
100760
101482
|
const code = claudeReadinessReasonCode(snapshot.reason);
|
|
100761
101483
|
if (code === "RUNTIME_UNAVAILABLE") return {
|
|
100762
101484
|
kind: "not_ready",
|
|
@@ -100784,7 +101506,7 @@ function classifyUnappliedClaudeSnapshot(snapshot) {
|
|
|
100784
101506
|
};
|
|
100785
101507
|
}
|
|
100786
101508
|
function classifyClaudeSnapshot(snapshot, sprite) {
|
|
100787
|
-
if (!
|
|
101509
|
+
if (!claudeSnapshotAllowsTurn(snapshot)) return classifyClaudeTurnRefusal(snapshot);
|
|
100788
101510
|
const generationId = snapshot.generationId.trim();
|
|
100789
101511
|
if (createHash("sha256").update(generationId).digest("hex") !== sprite.localGenerationHash) return {
|
|
100790
101512
|
kind: "not_ready",
|
|
@@ -104126,7 +104848,9 @@ const REJECT_UNCERTAIN_METHODS = new Set([
|
|
|
104126
104848
|
WS_METHODS.pullRequestsComment,
|
|
104127
104849
|
WS_METHODS.pullRequestsSubmitReview,
|
|
104128
104850
|
WS_METHODS.pullRequestsReplyToThread,
|
|
104129
|
-
WS_METHODS.projectCheckoutDeletionExecute
|
|
104851
|
+
WS_METHODS.projectCheckoutDeletionExecute,
|
|
104852
|
+
WS_METHODS.providerUploadFeedback,
|
|
104853
|
+
WS_METHODS.sourceControlCloneRepository
|
|
104130
104854
|
]);
|
|
104131
104855
|
function classifyInterruptedRelayCommand(input) {
|
|
104132
104856
|
if ((input.method === WS_METHODS.gitCreateBranch || input.method === WS_METHODS.vcsCreateRef) && input.deliveryClass === "create-command") return { kind: "reconcile-git-create-branch" };
|
|
@@ -105056,7 +105780,77 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
|
|
|
105056
105780
|
payload: initialSnapshot
|
|
105057
105781
|
}), liveEvents);
|
|
105058
105782
|
}), { "rpc.aggregate": "auth" }),
|
|
105059
|
-
[WS_METHODS.subscribeWorkspaceReadiness]: (_input) => currentUserId === void 0 ? empty$5 : workspaceReadinessState.subscribe(currentUserId)
|
|
105783
|
+
[WS_METHODS.subscribeWorkspaceReadiness]: (_input) => currentUserId === void 0 ? empty$5 : workspaceReadinessState.subscribe(currentUserId),
|
|
105784
|
+
[WS_METHODS.assetsCreateUrl]: (input) => observeRpcEffect(WS_METHODS.assetsCreateUrl, createAssetUrl(currentSessionId, input.resource), { "rpc.aggregate": "assets" }),
|
|
105785
|
+
[WS_METHODS.providerUploadFeedback]: (input) => observeRpcEffect(WS_METHODS.providerUploadFeedback, uploadThreadFeedback(orchestrationEngine, currentSessionId, currentUserId, input), { "rpc.aggregate": "provider" }),
|
|
105786
|
+
[ORCHESTRATION_WS_METHODS.searchThreads]: (input) => observeRpcEffect(ORCHESTRATION_WS_METHODS.searchThreads, gen(function* () {
|
|
105787
|
+
yield* startup.awaitCommandReady;
|
|
105788
|
+
return yield* projectionSnapshotQuery.searchThreads(input);
|
|
105789
|
+
}).pipe(mapError((cause) => new OrchestrationSearchThreadsError({
|
|
105790
|
+
message: "Failed to search threads",
|
|
105791
|
+
cause
|
|
105792
|
+
}))), { "rpc.aggregate": "orchestration" }),
|
|
105793
|
+
[WS_METHODS.sourceControlLookupRepository]: (input) => observeRpcEffect(WS_METHODS.sourceControlLookupRepository, gen(function* () {
|
|
105794
|
+
if (input.provider !== "github") return yield* new SourceControlRepositoryError({
|
|
105795
|
+
provider: input.provider,
|
|
105796
|
+
operation: "lookupRepository",
|
|
105797
|
+
detail: `Forge can only look up GitHub repositories. ${input.provider} is not connected.`
|
|
105798
|
+
});
|
|
105799
|
+
if (currentUserId === void 0) return yield* new SourceControlRepositoryError({
|
|
105800
|
+
provider: "github",
|
|
105801
|
+
operation: "lookupRepository",
|
|
105802
|
+
detail: "Sign in to Forge before looking up a repository."
|
|
105803
|
+
});
|
|
105804
|
+
const outcome = yield* lookupGitHubRepositoryForSession({
|
|
105805
|
+
sessionId: currentSessionId,
|
|
105806
|
+
subject: currentUserId,
|
|
105807
|
+
runtimeGrantAuth: null,
|
|
105808
|
+
repository: input.repository
|
|
105809
|
+
});
|
|
105810
|
+
if (outcome.repository === null) return yield* new SourceControlRepositoryError({
|
|
105811
|
+
provider: "github",
|
|
105812
|
+
operation: "lookupRepository",
|
|
105813
|
+
detail: outcome.error ?? "Repository lookup failed."
|
|
105814
|
+
});
|
|
105815
|
+
return {
|
|
105816
|
+
provider: "github",
|
|
105817
|
+
...outcome.repository
|
|
105818
|
+
};
|
|
105819
|
+
}), { "rpc.aggregate": "sourceControl" }),
|
|
105820
|
+
[WS_METHODS.sourceControlCloneRepository]: (input) => observeRpcEffect(WS_METHODS.sourceControlCloneRepository, gen(function* () {
|
|
105821
|
+
const provider = input.provider ?? "github";
|
|
105822
|
+
const repoFullName = resolveGitHubCloneTarget(input);
|
|
105823
|
+
const refuse = (detail) => new SourceControlRepositoryError({
|
|
105824
|
+
provider,
|
|
105825
|
+
operation: "cloneRepository",
|
|
105826
|
+
detail
|
|
105827
|
+
});
|
|
105828
|
+
if (provider !== "github") return yield* refuse(`Forge can only clone GitHub repositories. ${provider} is not connected.`);
|
|
105829
|
+
if (currentUserId === void 0) return yield* refuse("Sign in to Forge before cloning a repository.");
|
|
105830
|
+
if (repoFullName === null) return yield* refuse("Name the repository as owner/name, or paste a github.com clone URL.");
|
|
105831
|
+
const outcome = yield* runGitHubCloneForSession({
|
|
105832
|
+
config,
|
|
105833
|
+
sessionId: currentSessionId,
|
|
105834
|
+
subject: currentUserId,
|
|
105835
|
+
runtimeGrantAuth: null,
|
|
105836
|
+
repoFullName,
|
|
105837
|
+
targetDir: input.destinationPath
|
|
105838
|
+
});
|
|
105839
|
+
const localPath = outcome.body.local_path;
|
|
105840
|
+
if (outcome.status !== 200 || typeof localPath !== "string") {
|
|
105841
|
+
const detail = outcome.body.error;
|
|
105842
|
+
return yield* refuse(typeof detail === "string" ? detail : "Cloning the repository failed.");
|
|
105843
|
+
}
|
|
105844
|
+
const repository = describeGitHubRepository(repoFullName);
|
|
105845
|
+
return {
|
|
105846
|
+
cwd: localPath,
|
|
105847
|
+
remoteUrl: repository.url,
|
|
105848
|
+
repository: {
|
|
105849
|
+
provider: "github",
|
|
105850
|
+
...repository
|
|
105851
|
+
}
|
|
105852
|
+
};
|
|
105853
|
+
}), { "rpc.aggregate": "sourceControl" })
|
|
105060
105854
|
});
|
|
105061
105855
|
}));
|
|
105062
105856
|
const websocketRpcRouteLayer = unwrap(succeed(mergeAll(add$1("GET", "/ws", gen(function* () {
|
|
@@ -116133,8 +116927,8 @@ effectDiscard(runMigrations());
|
|
|
116133
116927
|
//#endregion
|
|
116134
116928
|
//#region src/persistence/Layers/Sqlite.ts
|
|
116135
116929
|
const defaultSqliteClientLoaders = {
|
|
116136
|
-
bun: () => import("./SqliteClient-
|
|
116137
|
-
node: () => import("./NodeSqliteClient-
|
|
116930
|
+
bun: () => import("./SqliteClient-DTWCGkrj.mjs"),
|
|
116931
|
+
node: () => import("./NodeSqliteClient-DqFjB3u_.mjs")
|
|
116138
116932
|
};
|
|
116139
116933
|
const makeRuntimeSqliteLayer = fn$1("makeRuntimeSqliteLayer")(function* (config) {
|
|
116140
116934
|
const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
|
|
@@ -188985,7 +189779,7 @@ var Vce = {
|
|
|
188985
189779
|
function Ve() {
|
|
188986
189780
|
return Vce;
|
|
188987
189781
|
}
|
|
188988
|
-
var Us =
|
|
189782
|
+
var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
|
|
188989
189783
|
let t = path.join(Us, "Library");
|
|
188990
189784
|
return {
|
|
188991
189785
|
data: path.join(t, "Application Support", e),
|
|
@@ -199185,12 +199979,12 @@ function qZ(e, t) {
|
|
|
199185
199979
|
}
|
|
199186
199980
|
return n;
|
|
199187
199981
|
}
|
|
199188
|
-
var os$
|
|
199982
|
+
var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
199189
199983
|
{
|
|
199190
199984
|
matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
|
|
199191
199985
|
tip: {
|
|
199192
199986
|
suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
|
|
199193
|
-
docLink: `${os$
|
|
199987
|
+
docLink: `${os$1}/iam#permission-modes`
|
|
199194
199988
|
}
|
|
199195
199989
|
},
|
|
199196
199990
|
{
|
|
@@ -199205,7 +199999,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
199205
199999
|
matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
|
|
199206
200000
|
tip: {
|
|
199207
200001
|
suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
|
|
199208
|
-
docLink: `${os$
|
|
200002
|
+
docLink: `${os$1}/settings#environment-variables`
|
|
199209
200003
|
}
|
|
199210
200004
|
},
|
|
199211
200005
|
{
|
|
@@ -199216,14 +200010,14 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
199216
200010
|
matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
|
|
199217
200011
|
tip: {
|
|
199218
200012
|
suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
|
|
199219
|
-
docLink: `${os$
|
|
200013
|
+
docLink: `${os$1}/hooks`
|
|
199220
200014
|
}
|
|
199221
200015
|
},
|
|
199222
200016
|
{
|
|
199223
200017
|
matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
|
|
199224
200018
|
tip: {
|
|
199225
200019
|
suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
|
|
199226
|
-
docLink: `${os$
|
|
200020
|
+
docLink: `${os$1}/hooks#exec-form-and-shell-form`
|
|
199227
200021
|
}
|
|
199228
200022
|
},
|
|
199229
200023
|
{
|
|
@@ -199238,7 +200032,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
199238
200032
|
matches: (e) => e.code === "unrecognized_keys",
|
|
199239
200033
|
tip: {
|
|
199240
200034
|
suggestion: "Check for typos or refer to the documentation for valid fields",
|
|
199241
|
-
docLink: `${os$
|
|
200035
|
+
docLink: `${os$1}/settings`
|
|
199242
200036
|
}
|
|
199243
200037
|
},
|
|
199244
200038
|
{
|
|
@@ -199253,13 +200047,13 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
199253
200047
|
matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
|
|
199254
200048
|
tip: {
|
|
199255
200049
|
suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
|
|
199256
|
-
docLink: `${os$
|
|
200050
|
+
docLink: `${os$1}/iam#working-directories`
|
|
199257
200051
|
}
|
|
199258
200052
|
}
|
|
199259
200053
|
], AKe = {
|
|
199260
|
-
permissions: `${os$
|
|
199261
|
-
env: `${os$
|
|
199262
|
-
hooks: `${os$
|
|
200054
|
+
permissions: `${os$1}/iam#configuring-permissions`,
|
|
200055
|
+
env: `${os$1}/settings#environment-variables`,
|
|
200056
|
+
hooks: `${os$1}/hooks`
|
|
199263
200057
|
};
|
|
199264
200058
|
function GZ(e) {
|
|
199265
200059
|
let t = kKe.find((r) => r.matches(e));
|
|
@@ -211320,6 +212114,83 @@ const makeProviderAuthProvenance = (input) => {
|
|
|
211320
212114
|
};
|
|
211321
212115
|
};
|
|
211322
212116
|
//#endregion
|
|
212117
|
+
//#region src/provider/claudeReadinessReason.ts
|
|
212118
|
+
/**
|
|
212119
|
+
* What Forge says about a delivered Claude credential that expired.
|
|
212120
|
+
*
|
|
212121
|
+
* CMMD's credential authority rotates the refresh token on every refresh, and a
|
|
212122
|
+
* runtime holds a one-time snapshot, so the snapshot's refresh token dies at
|
|
212123
|
+
* the next rotation. Nothing re-delivers on its own, and on 2026-09-04 23:07Z
|
|
212124
|
+
* the turn met a dead credential: the CLI said "OAuth session expired and could
|
|
212125
|
+
* not be refreshed", the thread said Claude was unavailable on this
|
|
212126
|
+
* Environment, and the provider card said the credential was "unavailable or
|
|
212127
|
+
* unusable". Three sentences, none of them the fact, which is that the
|
|
212128
|
+
* credential expired at a known time and a replacement is on its way.
|
|
212129
|
+
*
|
|
212130
|
+
* CMMD-36441 now answers the pre-turn readiness read with a typed reason and
|
|
212131
|
+
* queues a same-generation re-delivery. This module is the one place that
|
|
212132
|
+
* turns those reasons into words.
|
|
212133
|
+
*
|
|
212134
|
+
* Which kind of answer a reason names comes from
|
|
212135
|
+
* `@cmmd-forge/shared/claudeReadinessReason`, which the browser readiness poll
|
|
212136
|
+
* reads too, so the two cannot disagree about a reason (FOR-3085). A reason
|
|
212137
|
+
* that maps to nothing falls through to the generic copy, and every sentence a
|
|
212138
|
+
* person reads is written here rather than forwarded from upstream. Only an
|
|
212139
|
+
* ISO timestamp is ever lifted out of CMMD's message, so no credential detail
|
|
212140
|
+
* in an upstream string can reach a browser.
|
|
212141
|
+
*/
|
|
212142
|
+
const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
|
|
212143
|
+
/**
|
|
212144
|
+
* An ISO 8601 instant, with or without fractional seconds, UTC or offset.
|
|
212145
|
+
* Anchored to nothing on purpose: CMMD wraps the time in a sentence, and the
|
|
212146
|
+
* time is the only part of that sentence Forge repeats.
|
|
212147
|
+
*/
|
|
212148
|
+
const ISO_INSTANT_PATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})/;
|
|
212149
|
+
function expiredAtFromReadinessMessage(message) {
|
|
212150
|
+
if (typeof message !== "string") return null;
|
|
212151
|
+
return ISO_INSTANT_PATTERN.exec(message)?.[0] ?? null;
|
|
212152
|
+
}
|
|
212153
|
+
function claudeCredentialExpiredTurnCopy(expiredAt) {
|
|
212154
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one, so retry this turn in a moment.`;
|
|
212155
|
+
}
|
|
212156
|
+
function claudeCredentialExpiredCardCopy(expiredAt) {
|
|
212157
|
+
return `The delivered Claude credential expired at ${expiredAt}. CMMD is delivering a refreshed one to this runtime.`;
|
|
212158
|
+
}
|
|
212159
|
+
/**
|
|
212160
|
+
* The last allowlisted readiness reason this process saw.
|
|
212161
|
+
*
|
|
212162
|
+
* A server process serves exactly one runtime, so one slot is the whole store.
|
|
212163
|
+
* It holds a reason code and a timestamp, never credential bytes. The provider
|
|
212164
|
+
* catalog reads a local credential file and cannot tell an expired snapshot
|
|
212165
|
+
* from a blank one, so without this the card can only offer the generic
|
|
212166
|
+
* sentence while the guard already knows better.
|
|
212167
|
+
*/
|
|
212168
|
+
let latestVerdict = null;
|
|
212169
|
+
/**
|
|
212170
|
+
* Remember, or clear, what the readiness answer said.
|
|
212171
|
+
*
|
|
212172
|
+
* An answer carrying no allowlisted reason clears the slot, which is what makes
|
|
212173
|
+
* the card go back to the generic sentence the moment CMMD applies a
|
|
212174
|
+
* credential: an applied answer carries no reason.
|
|
212175
|
+
*/
|
|
212176
|
+
function recordClaudeReadinessVerdict(readiness) {
|
|
212177
|
+
const code = claudeReadinessReasonCode(readiness.reason);
|
|
212178
|
+
latestVerdict = code === null ? null : {
|
|
212179
|
+
code,
|
|
212180
|
+
expiredAt: expiredAtFromReadinessMessage(readiness.lastErrorMessage)
|
|
212181
|
+
};
|
|
212182
|
+
}
|
|
212183
|
+
/**
|
|
212184
|
+
* What the provider status card says about a delivered credential it cannot
|
|
212185
|
+
* use. Names the expiry only when a readiness answer supplied the time; a
|
|
212186
|
+
* guessed time would be worse than the generic sentence.
|
|
212187
|
+
*/
|
|
212188
|
+
function claudeNativeCredentialUnusableMessage() {
|
|
212189
|
+
const verdict = latestVerdict;
|
|
212190
|
+
if (verdict?.code === "CREDENTIAL_EXPIRED_REFRESHING" && verdict.expiredAt !== null) return claudeCredentialExpiredCardCopy(verdict.expiredAt);
|
|
212191
|
+
return CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY;
|
|
212192
|
+
}
|
|
212193
|
+
//#endregion
|
|
211323
212194
|
//#region src/provider/claudeCredentialStore.ts
|
|
211324
212195
|
/**
|
|
211325
212196
|
* Which Credential Store the Claude CLI uses on this machine, read the way the
|
|
@@ -265879,7 +266750,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
265879
266750
|
var dgram = __require("dgram");
|
|
265880
266751
|
var thunky = require_thunky();
|
|
265881
266752
|
var events = __require("events");
|
|
265882
|
-
var os
|
|
266753
|
+
var os = __require("os");
|
|
265883
266754
|
var noop = function() {};
|
|
265884
266755
|
module.exports = function(opts) {
|
|
265885
266756
|
if (!opts) opts = {};
|
|
@@ -266010,14 +266881,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
266010
266881
|
return that;
|
|
266011
266882
|
};
|
|
266012
266883
|
function defaultInterface() {
|
|
266013
|
-
var networks = os
|
|
266884
|
+
var networks = os.networkInterfaces();
|
|
266014
266885
|
var names = Object.keys(networks);
|
|
266015
266886
|
for (var i = 0; i < names.length; i++) {
|
|
266016
266887
|
var net = networks[names[i]];
|
|
266017
266888
|
for (var j = 0; j < net.length; j++) {
|
|
266018
266889
|
var iface = net[j];
|
|
266019
266890
|
if (isIPv4(iface.family) && !iface.internal) {
|
|
266020
|
-
if (os
|
|
266891
|
+
if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
|
|
266021
266892
|
return "0.0.0.0";
|
|
266022
266893
|
}
|
|
266023
266894
|
}
|
|
@@ -266025,7 +266896,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
266025
266896
|
return "127.0.0.1";
|
|
266026
266897
|
}
|
|
266027
266898
|
function allInterfaces() {
|
|
266028
|
-
var networks = os
|
|
266899
|
+
var networks = os.networkInterfaces();
|
|
266029
266900
|
var names = Object.keys(networks);
|
|
266030
266901
|
var res = [];
|
|
266031
266902
|
for (var i = 0; i < names.length; i++) {
|
|
@@ -286917,6 +287788,7 @@ function taskStartedToActivities(event) {
|
|
|
286917
287788
|
summary: taskStartedSummary(event.payload.taskType),
|
|
286918
287789
|
payload: {
|
|
286919
287790
|
taskId: event.payload.taskId,
|
|
287791
|
+
agentKind: classifyTaskAgentKind({ taskType: event.payload.taskType }),
|
|
286920
287792
|
...event.payload.taskType ? { taskType: event.payload.taskType } : {},
|
|
286921
287793
|
...event.payload.description ? { detail: truncateDetail(event.payload.description) } : {}
|
|
286922
287794
|
}
|
|
@@ -294724,7 +295596,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
294724
295596
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
294725
295597
|
*/
|
|
294726
295598
|
function readBakedBuildCommit() {
|
|
294727
|
-
return "
|
|
295599
|
+
return "c220fa73834b4651c7fc65f272c171028edc3e81";
|
|
294728
295600
|
}
|
|
294729
295601
|
async function resolveServerBuildCommit(input) {
|
|
294730
295602
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -294841,6 +295713,7 @@ function executionEnvironmentCapabilities(serverConfig) {
|
|
|
294841
295713
|
pullRequests: isExecutionPlane,
|
|
294842
295714
|
threadPullRequestLinking: isExecutionPlane,
|
|
294843
295715
|
threadSettlement: isExecutionPlane,
|
|
295716
|
+
threadTitleRegeneration: isExecutionPlane,
|
|
294844
295717
|
cloudMode: serverConfig.forgeCmmdRuntimeMode === true || isCloudShell,
|
|
294845
295718
|
runtime: {
|
|
294846
295719
|
topology: runtimeTopology(serverConfig),
|
|
@@ -295119,10 +295992,6 @@ const pulseSignalStateUpsertRouteLayer = add$1("POST", "/api/pulse/signals/state
|
|
|
295119
295992
|
});
|
|
295120
295993
|
}).pipe(catchTag("AuthError", respondToAuthError)));
|
|
295121
295994
|
//#endregion
|
|
295122
|
-
//#region src/auth/Services/ServerSecretStore.ts
|
|
295123
|
-
var SecretStoreError = class extends TaggedError("SecretStoreError") {};
|
|
295124
|
-
var ServerSecretStore = class extends Service$1()("forge/auth/Services/ServerSecretStore") {};
|
|
295125
|
-
//#endregion
|
|
295126
295995
|
//#region src/auth/Layers/ServerSecretStore.ts
|
|
295127
295996
|
const OWNED_FILE_RECEIPT_PREFIX = "forge-owned-secret-receipt:v1:";
|
|
295128
295997
|
const LEGACY_FILE_SECRET_NAMESPACE_PREFIX = "forge-secret-namespace:v1:";
|
|
@@ -301461,6 +302330,55 @@ const ProjectionCountsRowSchema = Struct({
|
|
|
301461
302330
|
const WorkspaceRootLookupInput = Struct({ workspaceRoot: String$1 });
|
|
301462
302331
|
const ProjectIdLookupInput = Struct({ projectId: ProjectId });
|
|
301463
302332
|
const ThreadIdLookupInput = Struct({ threadId: ThreadId });
|
|
302333
|
+
const ThreadSearchQueryInput = Struct({
|
|
302334
|
+
pattern: String$1,
|
|
302335
|
+
limit: Number$1
|
|
302336
|
+
});
|
|
302337
|
+
const ThreadSearchMessageRowSchema = Struct({
|
|
302338
|
+
threadId: ThreadId,
|
|
302339
|
+
projectId: ProjectId,
|
|
302340
|
+
role: String$1,
|
|
302341
|
+
text: String$1,
|
|
302342
|
+
createdAt: IsoDateTime
|
|
302343
|
+
});
|
|
302344
|
+
const ThreadSearchMetadataRowSchema = Struct({
|
|
302345
|
+
threadId: ThreadId,
|
|
302346
|
+
projectId: ProjectId,
|
|
302347
|
+
title: String$1,
|
|
302348
|
+
branch: NullOr(String$1)
|
|
302349
|
+
});
|
|
302350
|
+
/** The contract caps a snippet at this length, so the server must too. */
|
|
302351
|
+
const THREAD_SEARCH_SNIPPET_LIMIT = 240;
|
|
302352
|
+
/** How many matches a search returns when the caller does not say. */
|
|
302353
|
+
const THREAD_SEARCH_DEFAULT_LIMIT = 20;
|
|
302354
|
+
/**
|
|
302355
|
+
* `LIKE` treats `%` and `_` as wildcards, so a person searching for a literal
|
|
302356
|
+
* underscore (every other identifier in a codebase) would otherwise match any
|
|
302357
|
+
* character. Escaped with a backslash, declared to SQL as `ESCAPE '\'`, and
|
|
302358
|
+
* lowercased on both sides because Postgres `LIKE` is case-sensitive where
|
|
302359
|
+
* SQLite's is not, and Forge runs on both.
|
|
302360
|
+
*/
|
|
302361
|
+
function toThreadSearchPattern(query) {
|
|
302362
|
+
return `%${query.toLowerCase().replace(/[\\%_]/g, (character) => `\\${character}`)}%`;
|
|
302363
|
+
}
|
|
302364
|
+
/**
|
|
302365
|
+
* A window of the text around the first hit, not the head of it.
|
|
302366
|
+
*
|
|
302367
|
+
* A match 4000 characters into an assistant message is invisible in a snippet
|
|
302368
|
+
* that always starts at character zero, which is the difference between a
|
|
302369
|
+
* result a person can recognize and a result they have to open to understand.
|
|
302370
|
+
*/
|
|
302371
|
+
function buildThreadSearchSnippet(text, lowerQuery) {
|
|
302372
|
+
const collapsed = text.replace(/\s+/g, " ").trim();
|
|
302373
|
+
if (collapsed.length <= THREAD_SEARCH_SNIPPET_LIMIT) return collapsed;
|
|
302374
|
+
const matchIndex = collapsed.toLowerCase().indexOf(lowerQuery);
|
|
302375
|
+
if (matchIndex < 0) return `${collapsed.slice(0, THREAD_SEARCH_SNIPPET_LIMIT - 1)}…`;
|
|
302376
|
+
const leading = Math.floor((THREAD_SEARCH_SNIPPET_LIMIT - lowerQuery.length) / 2);
|
|
302377
|
+
const start = Math.max(0, matchIndex - Math.max(leading, 0));
|
|
302378
|
+
const windowed = collapsed.slice(start, start + THREAD_SEARCH_SNIPPET_LIMIT);
|
|
302379
|
+
const prefixed = start > 0 ? `…${windowed}` : windowed;
|
|
302380
|
+
return prefixed.length > THREAD_SEARCH_SNIPPET_LIMIT ? prefixed.slice(0, THREAD_SEARCH_SNIPPET_LIMIT) : prefixed;
|
|
302381
|
+
}
|
|
301464
302382
|
const ProjectionProjectLookupRowSchema = ProjectionProjectDbRowSchema;
|
|
301465
302383
|
const ProjectionThreadIdLookupRowSchema = Struct({ threadId: ThreadId });
|
|
301466
302384
|
const ProjectionThreadCheckpointContextThreadRowSchema = Struct({
|
|
@@ -301706,6 +302624,61 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
301706
302624
|
AND deleted_at IS NULL
|
|
301707
302625
|
AND archived_at IS NULL
|
|
301708
302626
|
ORDER BY created_at ASC, thread_id ASC
|
|
302627
|
+
`
|
|
302628
|
+
});
|
|
302629
|
+
/**
|
|
302630
|
+
* Message bodies containing the query.
|
|
302631
|
+
*
|
|
302632
|
+
* `role IN ('user', 'assistant')` is not a filter for tidiness: the match
|
|
302633
|
+
* shape on the wire names one of those two as the side the hit came from,
|
|
302634
|
+
* and a system message has no side to report. Ordered newest first because a
|
|
302635
|
+
* capped search should keep what the person is most likely still thinking
|
|
302636
|
+
* about.
|
|
302637
|
+
*/
|
|
302638
|
+
const searchThreadMessageRows = findAll({
|
|
302639
|
+
Request: ThreadSearchQueryInput,
|
|
302640
|
+
Result: ThreadSearchMessageRowSchema,
|
|
302641
|
+
execute: ({ pattern, limit }) => sql`
|
|
302642
|
+
SELECT
|
|
302643
|
+
m.thread_id AS "threadId",
|
|
302644
|
+
t.project_id AS "projectId",
|
|
302645
|
+
m.role AS "role",
|
|
302646
|
+
m.text AS "text",
|
|
302647
|
+
m.created_at AS "createdAt"
|
|
302648
|
+
FROM projection_thread_messages AS m
|
|
302649
|
+
JOIN projection_threads AS t ON t.thread_id = m.thread_id
|
|
302650
|
+
WHERE t.deleted_at IS NULL
|
|
302651
|
+
AND m.role IN ('user', 'assistant')
|
|
302652
|
+
AND LOWER(m.text) LIKE ${pattern} ESCAPE '\\'
|
|
302653
|
+
ORDER BY m.created_at DESC, m.message_id ASC
|
|
302654
|
+
LIMIT ${limit}
|
|
302655
|
+
`
|
|
302656
|
+
});
|
|
302657
|
+
/**
|
|
302658
|
+
* Threads whose own metadata contains the query.
|
|
302659
|
+
*
|
|
302660
|
+
* Title and branch, which is what the web command palette matches on. Title
|
|
302661
|
+
* is duplicated work for a client that already filters its in-memory list,
|
|
302662
|
+
* and harmless because matches are deduplicated by thread; branch is not,
|
|
302663
|
+
* because no client holds every branch.
|
|
302664
|
+
*/
|
|
302665
|
+
const searchThreadMetadataRows = findAll({
|
|
302666
|
+
Request: ThreadSearchQueryInput,
|
|
302667
|
+
Result: ThreadSearchMetadataRowSchema,
|
|
302668
|
+
execute: ({ pattern, limit }) => sql`
|
|
302669
|
+
SELECT
|
|
302670
|
+
thread_id AS "threadId",
|
|
302671
|
+
project_id AS "projectId",
|
|
302672
|
+
title,
|
|
302673
|
+
branch
|
|
302674
|
+
FROM projection_threads
|
|
302675
|
+
WHERE deleted_at IS NULL
|
|
302676
|
+
AND (
|
|
302677
|
+
LOWER(title) LIKE ${pattern} ESCAPE '\\'
|
|
302678
|
+
OR LOWER(branch) LIKE ${pattern} ESCAPE '\\'
|
|
302679
|
+
)
|
|
302680
|
+
ORDER BY updated_at DESC, thread_id ASC
|
|
302681
|
+
LIMIT ${limit}
|
|
301709
302682
|
`
|
|
301710
302683
|
});
|
|
301711
302684
|
/**
|
|
@@ -302452,6 +303425,40 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
302452
303425
|
};
|
|
302453
303426
|
const getShellSnapshot = () => readShellSnapshot("getShellSnapshot", listThreadRows);
|
|
302454
303427
|
const getArchivedShellSnapshot = () => readShellSnapshot("getArchivedShellSnapshot", listArchivedThreadRows);
|
|
303428
|
+
const searchThreads = (input) => {
|
|
303429
|
+
const limit = input.limit ?? THREAD_SEARCH_DEFAULT_LIMIT;
|
|
303430
|
+
const lowerQuery = input.query.toLowerCase();
|
|
303431
|
+
const request = {
|
|
303432
|
+
pattern: toThreadSearchPattern(input.query),
|
|
303433
|
+
limit
|
|
303434
|
+
};
|
|
303435
|
+
return all([searchThreadMessageRows(request).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.searchThreads:messages:query", "ProjectionSnapshotQuery.searchThreads:messages:decodeRows"))), searchThreadMetadataRows(request).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.searchThreads:metadata:query", "ProjectionSnapshotQuery.searchThreads:metadata:decodeRows")))]).pipe(map$3(([messageRows, metadataRows]) => {
|
|
303436
|
+
const byThread = /* @__PURE__ */ new Map();
|
|
303437
|
+
for (const row of messageRows) {
|
|
303438
|
+
if (byThread.has(row.threadId)) continue;
|
|
303439
|
+
byThread.set(row.threadId, {
|
|
303440
|
+
threadId: row.threadId,
|
|
303441
|
+
projectId: row.projectId,
|
|
303442
|
+
source: row.role === "assistant" ? "assistant" : "user",
|
|
303443
|
+
snippet: buildThreadSearchSnippet(row.text, lowerQuery),
|
|
303444
|
+
messageCreatedAt: row.createdAt
|
|
303445
|
+
});
|
|
303446
|
+
}
|
|
303447
|
+
for (const row of metadataRows) {
|
|
303448
|
+
if (byThread.has(row.threadId)) continue;
|
|
303449
|
+
const branchMatched = row.branch?.toLowerCase().includes(lowerQuery) === true;
|
|
303450
|
+
const titleMatched = row.title.toLowerCase().includes(lowerQuery);
|
|
303451
|
+
byThread.set(row.threadId, {
|
|
303452
|
+
threadId: row.threadId,
|
|
303453
|
+
projectId: row.projectId,
|
|
303454
|
+
source: "user",
|
|
303455
|
+
snippet: buildThreadSearchSnippet(titleMatched || !branchMatched ? row.title : row.branch ?? row.title, lowerQuery),
|
|
303456
|
+
messageCreatedAt: null
|
|
303457
|
+
});
|
|
303458
|
+
}
|
|
303459
|
+
return { matches: [...byThread.values()].slice(0, limit) };
|
|
303460
|
+
}));
|
|
303461
|
+
};
|
|
302455
303462
|
const listActiveWorkerShells = () => sql.withTransaction(all([
|
|
302456
303463
|
listActiveWorkerThreadRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:decodeRows"))),
|
|
302457
303464
|
listActiveWorkerSessionRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:decodeRows"))),
|
|
@@ -302619,6 +303626,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
302619
303626
|
getSnapshot,
|
|
302620
303627
|
getShellSnapshot,
|
|
302621
303628
|
getArchivedShellSnapshot,
|
|
303629
|
+
searchThreads,
|
|
302622
303630
|
listActiveWorkerShells,
|
|
302623
303631
|
listStaleRunningSessionShells,
|
|
302624
303632
|
getCounts,
|
|
@@ -317801,13 +318809,13 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", gen(func
|
|
|
317801
318809
|
//#endregion
|
|
317802
318810
|
//#region src/server.ts
|
|
317803
318811
|
const PtyAdapterLive = unwrap(gen(function* () {
|
|
317804
|
-
if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunPTY-
|
|
317805
|
-
else return (yield* promise(() => import("./NodePTY-
|
|
318812
|
+
if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunPTY-DkAIzLIx.mjs"))).layer;
|
|
318813
|
+
else return (yield* promise(() => import("./NodePTY-loxi8Vku.mjs"))).layer;
|
|
317806
318814
|
}));
|
|
317807
318815
|
const HttpServerLive = unwrap(gen(function* () {
|
|
317808
318816
|
const config = yield* ServerConfig;
|
|
317809
|
-
if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunHttpServer-
|
|
317810
|
-
const [NodeHttpServer, NodeHttp] = yield* all([promise(() => import("./NodeHttpServer-
|
|
318817
|
+
if (typeof Bun !== "undefined") return (yield* promise(() => import("./BunHttpServer-CDh4Vioe.mjs"))).layer(bunHttpServerOptions(config));
|
|
318818
|
+
const [NodeHttpServer, NodeHttp] = yield* all([promise(() => import("./NodeHttpServer-DT0TF8s4.mjs")), promise(() => import("node:http"))]);
|
|
317811
318819
|
return NodeHttpServer.layer(NodeHttp.createServer, {
|
|
317812
318820
|
host: config.host,
|
|
317813
318821
|
port: config.port
|
|
@@ -317815,10 +318823,10 @@ const HttpServerLive = unwrap(gen(function* () {
|
|
|
317815
318823
|
}));
|
|
317816
318824
|
const PlatformServicesLive = unwrap(gen(function* () {
|
|
317817
318825
|
if (typeof Bun !== "undefined") {
|
|
317818
|
-
const { layer } = yield* promise(() => import("./BunServices-
|
|
318826
|
+
const { layer } = yield* promise(() => import("./BunServices-D6PAheqS.mjs"));
|
|
317819
318827
|
return layer;
|
|
317820
318828
|
} else {
|
|
317821
|
-
const { layer } = yield* promise(() => import("./NodeServices-
|
|
318829
|
+
const { layer } = yield* promise(() => import("./NodeServices-BahzLhbo.mjs").then((n) => n.t));
|
|
317822
318830
|
return layer;
|
|
317823
318831
|
}
|
|
317824
318832
|
}));
|
|
@@ -318813,7 +319821,7 @@ const connectLoginCommand = make$34("login", {
|
|
|
318813
319821
|
})).json();
|
|
318814
319822
|
},
|
|
318815
319823
|
openBrowser: async (url) => {
|
|
318816
|
-
const open = (await import("./open-
|
|
319824
|
+
const open = (await import("./open-D8mGyWmt.mjs")).default;
|
|
318817
319825
|
await open(url);
|
|
318818
319826
|
return true;
|
|
318819
319827
|
},
|