@cmmd-center/forge 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +852 -656
- package/dist/bin.mjs +887 -691
- package/dist/client/assets/{CanvasPanel-Cogtmutb.js → CanvasPanel-CL4T3ptl.js} +2 -2
- package/dist/client/assets/{CompositeItem-D6h2WFmU.js → CompositeItem-NATmsDHX.js} +2 -2
- package/dist/client/assets/{DiffPanel-B1yCvMgl.js → DiffPanel-29UmSyE3.js} +3 -3
- package/dist/client/assets/{DiffPanel.logic-DS-ZTg-9.js → DiffPanel.logic-D5Bn5tgT.js} +2 -2
- package/dist/client/assets/DiffPanelShell-ClKPX_Pu.js +2531 -0
- package/dist/client/assets/{DiffWorkerPoolProvider-Q7qqna90.js → DiffWorkerPoolProvider-BMZL3CsO.js} +9 -9
- package/dist/client/assets/{PullRequestCodePanel-CO19fx8r.js → PullRequestCodePanel-Cp75delh.js} +3 -3
- package/dist/client/assets/{Virtualizer-nnC3ZYoF.js → Virtualizer-uq0d5TwY.js} +2 -2
- package/dist/client/assets/{canvasStateStore-DUZyBLSv.js → canvasStateStore-DtidOKbV.js} +2 -2
- package/dist/client/assets/{cmmdAuthRedirect-Bkc6Luqb.js → cmmdAuthRedirect-CzACd5XJ.js} +2 -2
- package/dist/client/assets/index-BOI5P7rG.js +151 -0
- package/dist/client/assets/src-BlFkWEsP.js +3 -0
- package/dist/client/assets/{toggle-CWmPePti.js → toggle-pwklbW9N.js} +2 -2
- package/dist/client/assets/{useTriggerFocusGuards-O7DUCec0.js → useTriggerFocusGuards-CPLQ8_CW.js} +2 -2
- package/dist/client/index.html +10 -10
- package/dist/forge-build.json +1 -1
- package/dist/runtimeBootstrap.cjs +1 -1
- package/dist/runtimeBootstrap.mjs +1 -1
- package/dist/{runtimeControlAuthority-BdIovv-r.cjs → runtimeControlAuthority-B3kNmYRT.cjs} +4 -2
- package/dist/{runtimeControlAuthority-COHmz_WT.mjs → runtimeControlAuthority-D_zKr_eI.mjs} +4 -2
- package/package.json +1 -1
- package/dist/client/assets/DiffPanelShell-BFklbpRe.js +0 -2531
- package/dist/client/assets/index-ChfmpOIo.js +0 -151
- package/dist/client/assets/src-BlF9iHm-.js +0 -3
package/dist/bin.mjs
CHANGED
|
@@ -5,13 +5,13 @@ import { $ as encodeEffect, $a as make$39, $c as fromUndefinedOr, $i as spaced,
|
|
|
5
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-C6Zb2WHp.mjs";
|
|
6
6
|
import { _ as ChildProcessSpawner, a as Stdio, g as make$48, o as make$49, r as isQuitError } from "./NodeTerminal-BEGtoMSS.mjs";
|
|
7
7
|
import { n as layer$7 } from "./NodeServices-CBNXOYLG.mjs";
|
|
8
|
-
import { $ as file, A as SocketErrorReason, At as merge$5, Bt as set$
|
|
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-DNGCTTdk.mjs";
|
|
9
9
|
import { _ as withDefault$3, a as all$1, c as logLevel, d as option$1, f as port, g as url$2, i as TrueValues, l as map$8, m as string$7, n as FalseValues, o as boolean$5, p as schema$2, r as Record$1, s as int$2, t as Boolean$2, u as number$2 } from "./Config-AqUR_lhX.mjs";
|
|
10
|
-
import { a as make$51, i as keys, n as getOption, o as makeWith$1, r as invalidate, s as set$
|
|
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-p0X77SKw.mjs";
|
|
11
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-cyJT2IRY.mjs";
|
|
12
12
|
import { r as wrapper_default, t as Mime_default } from "./Mime-Dq9IoNiV.mjs";
|
|
13
13
|
import { a as diag, c as SpanKind$1, d as TraceFlags, f as ROOT_CONTEXT, i as propagation, l as isSpanContextValid, m as DiagLogLevel, n as init_esm$1, o as context$1, p as createContextKey, r as trace, s as SpanStatusCode, t as esm_exports$1 } from "./esm-wLIF-u3N.mjs";
|
|
14
|
-
import { a as verifyCmmdAuthRuntimeTicket, c as verifyCmmdRuntimeTicket, d as setRuntimeGrantToken, f as createReadOnceFileTokenAccessor, i as sendRuntimeControlHeartbeat, l as verifyCmmdRuntimeTicketRefreshProof, n as bootstrapRuntimeControlAuthorityFromNarrowGrant, o as verifyCmmdClaudeReadinessTicket, r as readRuntimeControlAuthority, s as verifyCmmdClaudeUsageTicket, t as bootstrapRuntimeControlAuthority, u as runtimeGrantToken } from "./runtimeControlAuthority-
|
|
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
15
|
import { n as PtySpawnError, t as PtyAdapter } from "./PTY-rIPf52QR.mjs";
|
|
16
16
|
import { createRequire } from "node:module";
|
|
17
17
|
import * as HE from "node:child_process";
|
|
@@ -15750,7 +15750,7 @@ const get$1 = (self) => modify$1(self, (current) => [current, current]);
|
|
|
15750
15750
|
* })
|
|
15751
15751
|
* ```
|
|
15752
15752
|
*/
|
|
15753
|
-
const set$
|
|
15753
|
+
const set$2 = /* @__PURE__ */ dual(2, (self, value) => update$1(self, () => value));
|
|
15754
15754
|
//#endregion
|
|
15755
15755
|
//#region ../../node_modules/.bun/effect@4.0.0-beta.51/node_modules/effect/dist/TxChunk.js
|
|
15756
15756
|
/**
|
|
@@ -15939,7 +15939,7 @@ const get = (self) => get$1(self.ref);
|
|
|
15939
15939
|
* @since 4.0.0
|
|
15940
15940
|
* @category combinators
|
|
15941
15941
|
*/
|
|
15942
|
-
const set = /* @__PURE__ */ dual(2, (self, chunk) => set$
|
|
15942
|
+
const set$1 = /* @__PURE__ */ dual(2, (self, chunk) => set$2(self.ref, chunk));
|
|
15943
15943
|
/**
|
|
15944
15944
|
* Appends an element to the end of the `TxChunk`.
|
|
15945
15945
|
*
|
|
@@ -16187,7 +16187,7 @@ const take = (self) => gen(function* () {
|
|
|
16187
16187
|
const head$3 = head(yield* get(self.items));
|
|
16188
16188
|
if (isNone(head$3)) return yield* txRetry;
|
|
16189
16189
|
yield* drop(self.items, 1);
|
|
16190
|
-
if (state._tag === "Closing" && (yield* isEmpty(self))) yield* set$
|
|
16190
|
+
if (state._tag === "Closing" && (yield* isEmpty(self))) yield* set$2(self.stateRef, {
|
|
16191
16191
|
_tag: "Done",
|
|
16192
16192
|
cause: state.cause
|
|
16193
16193
|
});
|
|
@@ -16285,11 +16285,11 @@ const interrupt = (self) => withFiber$1((fiber) => failCause(self, interrupt$2(f
|
|
|
16285
16285
|
*/
|
|
16286
16286
|
const failCause = /* @__PURE__ */ dual(2, (self, cause) => gen(function* () {
|
|
16287
16287
|
if ((yield* get$1(self.stateRef))._tag !== "Open") return false;
|
|
16288
|
-
if (yield* isEmpty(self)) yield* set$
|
|
16288
|
+
if (yield* isEmpty(self)) yield* set$2(self.stateRef, {
|
|
16289
16289
|
_tag: "Done",
|
|
16290
16290
|
cause
|
|
16291
16291
|
});
|
|
16292
|
-
else yield* set$
|
|
16292
|
+
else yield* set$2(self.stateRef, {
|
|
16293
16293
|
_tag: "Closing",
|
|
16294
16294
|
cause
|
|
16295
16295
|
});
|
|
@@ -16331,7 +16331,7 @@ const clear = (self) => gen(function* () {
|
|
|
16331
16331
|
return yield* failCause$1(state.cause);
|
|
16332
16332
|
}
|
|
16333
16333
|
const chunk = yield* get(self.items);
|
|
16334
|
-
yield* set(self.items, empty$2());
|
|
16334
|
+
yield* set$1(self.items, empty$2());
|
|
16335
16335
|
return toArray(chunk);
|
|
16336
16336
|
}).pipe(tx$1);
|
|
16337
16337
|
/**
|
|
@@ -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.
|
|
16523
|
+
const FORGE_RELEASE_VERSION = "0.12.0";
|
|
16524
16524
|
//#endregion
|
|
16525
16525
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16526
16526
|
/**
|
|
@@ -19116,6 +19116,7 @@ const ORCHESTRATION_WS_METHODS = {
|
|
|
19116
19116
|
streamTurnDiff: "orchestration.streamTurnDiff",
|
|
19117
19117
|
streamFullThreadDiff: "orchestration.streamFullThreadDiff",
|
|
19118
19118
|
replayEvents: "orchestration.replayEvents",
|
|
19119
|
+
getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot",
|
|
19119
19120
|
subscribeShell: "orchestration.subscribeShell",
|
|
19120
19121
|
subscribeThread: "orchestration.subscribeThread"
|
|
19121
19122
|
};
|
|
@@ -20551,6 +20552,10 @@ const OrchestrationRpcSchemas = {
|
|
|
20551
20552
|
subscribeShell: {
|
|
20552
20553
|
input: Struct({}),
|
|
20553
20554
|
output: OrchestrationShellStreamItem
|
|
20555
|
+
},
|
|
20556
|
+
getArchivedShellSnapshot: {
|
|
20557
|
+
input: Struct({}),
|
|
20558
|
+
output: OrchestrationShellSnapshot
|
|
20554
20559
|
}
|
|
20555
20560
|
};
|
|
20556
20561
|
var OrchestrationGetSnapshotError = class extends TaggedErrorClass()("OrchestrationGetSnapshotError", {
|
|
@@ -26292,6 +26297,11 @@ const WsOrchestrationReplayEventsRpc = make$30(ORCHESTRATION_WS_METHODS.replayEv
|
|
|
26292
26297
|
success: OrchestrationRpcSchemas.replayEvents.output,
|
|
26293
26298
|
error: OrchestrationReplayEventsError
|
|
26294
26299
|
});
|
|
26300
|
+
const WsOrchestrationGetArchivedShellSnapshotRpc = make$30(ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, {
|
|
26301
|
+
payload: OrchestrationRpcSchemas.getArchivedShellSnapshot.input,
|
|
26302
|
+
success: OrchestrationRpcSchemas.getArchivedShellSnapshot.output,
|
|
26303
|
+
error: OrchestrationGetSnapshotError
|
|
26304
|
+
});
|
|
26295
26305
|
const WsOrchestrationSubscribeShellRpc = make$30(ORCHESTRATION_WS_METHODS.subscribeShell, {
|
|
26296
26306
|
payload: OrchestrationRpcSchemas.subscribeShell.input,
|
|
26297
26307
|
success: OrchestrationRpcSchemas.subscribeShell.output,
|
|
@@ -26372,7 +26382,7 @@ const WsRpcGroup = make$29(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
|
|
|
26372
26382
|
payload: UsageSummaryInput,
|
|
26373
26383
|
success: UsageSummary,
|
|
26374
26384
|
error: UsageReadError
|
|
26375
|
-
}), 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, WsOrchestrationSubscribeShellRpc, WsOrchestrationSubscribeThreadRpc);
|
|
26385
|
+
}), 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);
|
|
26376
26386
|
const RelayWsRpcGroup = WsRpcGroup.middleware(RelayAuthorizationMiddleware).middleware(RelayDeliveryMiddleware);
|
|
26377
26387
|
/**
|
|
26378
26388
|
* The direct-WebSocket mount of the same group, with per-method scope
|
|
@@ -26406,6 +26416,29 @@ function encodeCmmdRuntimeThreadLifecycleMetadata(lifecycle) {
|
|
|
26406
26416
|
} };
|
|
26407
26417
|
}
|
|
26408
26418
|
//#endregion
|
|
26419
|
+
//#region ../../packages/contracts/src/cmmdRuntimeThreadWorkspace.ts
|
|
26420
|
+
function encodeCmmdRuntimeThreadWorkspaceMetadata(workspace) {
|
|
26421
|
+
return { forgeWorkspace: {
|
|
26422
|
+
schemaVersion: 1,
|
|
26423
|
+
worktreePath: workspace.worktreePath
|
|
26424
|
+
} };
|
|
26425
|
+
}
|
|
26426
|
+
//#endregion
|
|
26427
|
+
//#region ../../packages/contracts/src/cmmdRuntimeToolActivity.ts
|
|
26428
|
+
function isRecord$22(value) {
|
|
26429
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26430
|
+
}
|
|
26431
|
+
function encodeCmmdRuntimeToolActivityPayload(kind, payload) {
|
|
26432
|
+
return {
|
|
26433
|
+
...isRecord$22(payload) ? payload : { sourcePayload: payload },
|
|
26434
|
+
forgeToolActivity: {
|
|
26435
|
+
schemaVersion: 1,
|
|
26436
|
+
source: "native",
|
|
26437
|
+
kind
|
|
26438
|
+
}
|
|
26439
|
+
};
|
|
26440
|
+
}
|
|
26441
|
+
//#endregion
|
|
26409
26442
|
//#region ../../packages/contracts/src/cmmdProjectPatch.ts
|
|
26410
26443
|
const CMMD_PROJECT_PATCH_FIELDS = {
|
|
26411
26444
|
title: "requiredText",
|
|
@@ -31467,6 +31500,11 @@ const RELAY_METHOD_POLICY_BASES = [
|
|
|
31467
31500
|
requiredScope: "thread.read",
|
|
31468
31501
|
deliveryClass: "subscription"
|
|
31469
31502
|
},
|
|
31503
|
+
{
|
|
31504
|
+
method: ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot,
|
|
31505
|
+
requiredScope: "thread.read",
|
|
31506
|
+
deliveryClass: "read"
|
|
31507
|
+
},
|
|
31470
31508
|
{
|
|
31471
31509
|
method: ORCHESTRATION_WS_METHODS.subscribeShell,
|
|
31472
31510
|
requiredScope: "thread.read",
|
|
@@ -65495,7 +65533,7 @@ function normalizeNumberish(value) {
|
|
|
65495
65533
|
}
|
|
65496
65534
|
//#endregion
|
|
65497
65535
|
//#region package.json
|
|
65498
|
-
var version$1 = "0.
|
|
65536
|
+
var version$1 = "0.12.0";
|
|
65499
65537
|
//#endregion
|
|
65500
65538
|
//#region src/sentry.ts
|
|
65501
65539
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -69266,6 +69304,17 @@ const browserApiCorsLayer = middleware(cors({
|
|
|
69266
69304
|
credentials: true,
|
|
69267
69305
|
maxAge: 600
|
|
69268
69306
|
}), { global: true });
|
|
69307
|
+
/**
|
|
69308
|
+
* An /api path that reaches the static catch-all is not implemented by this
|
|
69309
|
+
* server: every real API route is registered ahead of it. Serving the web app's
|
|
69310
|
+
* index.html for those made API clients decode an HTML document as JSON, which
|
|
69311
|
+
* is how the phone's shell snapshot fetch failed with a JSON parse error on
|
|
69312
|
+
* every connect (FOR-36611). Callers can act on a 404; they cannot act on a
|
|
69313
|
+
* document that claims to be their payload.
|
|
69314
|
+
*/
|
|
69315
|
+
function isApiRequestPath(pathname) {
|
|
69316
|
+
return pathname === "/api" || pathname.startsWith("/api/");
|
|
69317
|
+
}
|
|
69269
69318
|
function resolveDevRedirectUrl(devUrl, requestUrl) {
|
|
69270
69319
|
const redirectUrl = new URL(devUrl.toString());
|
|
69271
69320
|
redirectUrl.pathname = requestUrl.pathname;
|
|
@@ -69387,6 +69436,10 @@ const projectFaviconRouteLayer = add$1("GET", "/api/project-favicon", gen(functi
|
|
|
69387
69436
|
const staticAndDevRouteLayer = add$1("GET", "*", gen(function* () {
|
|
69388
69437
|
const url = toURL(yield* HttpServerRequest);
|
|
69389
69438
|
if (isNone(url)) return text$2("Bad Request", { status: 400 });
|
|
69439
|
+
if (isApiRequestPath(url.value.pathname)) return jsonUnsafe({
|
|
69440
|
+
error: "not_found",
|
|
69441
|
+
message: `This server does not serve ${url.value.pathname}.`
|
|
69442
|
+
}, { status: 404 });
|
|
69390
69443
|
const config = yield* ServerConfig;
|
|
69391
69444
|
if (config.devUrl && isLoopbackHostname(url.value.hostname)) return redirect(resolveDevRedirectUrl(config.devUrl, url.value), { status: 302 });
|
|
69392
69445
|
const staticDir = config.staticDir ?? (config.devUrl ? yield* resolveStaticDir() : void 0);
|
|
@@ -82637,7 +82690,7 @@ var ServerSettingsService = class ServerSettingsService extends Service$1()("for
|
|
|
82637
82690
|
start: void_$1,
|
|
82638
82691
|
ready: void_$1,
|
|
82639
82692
|
getSettings: get$6(currentSettingsRef),
|
|
82640
|
-
updateSettings: (patch) => get$6(currentSettingsRef).pipe(map$3((currentSettings) => deepMerge$1(currentSettings, patch)), tap((nextSettings) => set$
|
|
82693
|
+
updateSettings: (patch) => get$6(currentSettingsRef).pipe(map$3((currentSettings) => deepMerge$1(currentSettings, patch)), tap((nextSettings) => set$3(currentSettingsRef, nextSettings))),
|
|
82641
82694
|
streamChanges: empty$5
|
|
82642
82695
|
};
|
|
82643
82696
|
}));
|
|
@@ -82777,7 +82830,7 @@ const ServerSettingsLive = effect(ServerSettingsService, gen(function* () {
|
|
|
82777
82830
|
cause
|
|
82778
82831
|
})));
|
|
82779
82832
|
yield* writeSettingsAtomically(next);
|
|
82780
|
-
yield* set$
|
|
82833
|
+
yield* set$4(settingsCache, cacheKey, next);
|
|
82781
82834
|
yield* emitChange(next);
|
|
82782
82835
|
return resolveTextGenerationProvider(next);
|
|
82783
82836
|
})),
|
|
@@ -91707,7 +91760,7 @@ const KeybindingsLive = effect(Keybindings, gen(function* () {
|
|
|
91707
91760
|
return {
|
|
91708
91761
|
start: gen(function* () {
|
|
91709
91762
|
if (yield* get$6(startedRef)) return yield* _await(startedDeferred);
|
|
91710
|
-
yield* set$
|
|
91763
|
+
yield* set$3(startedRef, true);
|
|
91711
91764
|
const startupExit = yield* exit(gen(function* () {
|
|
91712
91765
|
yield* startWatcher;
|
|
91713
91766
|
yield* syncDefaultKeybindingsOnStartup;
|
|
@@ -91736,7 +91789,7 @@ const KeybindingsLive = effect(Keybindings, gen(function* () {
|
|
|
91736
91789
|
});
|
|
91737
91790
|
yield* writeConfigAtomically(cappedConfig);
|
|
91738
91791
|
const nextResolved = mergeWithDefaultKeybindings(compileResolvedKeybindingsConfig(cappedConfig));
|
|
91739
|
-
yield* set$
|
|
91792
|
+
yield* set$4(resolvedConfigCache, resolvedConfigCacheKey, {
|
|
91740
91793
|
keybindings: nextResolved,
|
|
91741
91794
|
issues: []
|
|
91742
91795
|
});
|
|
@@ -91750,7 +91803,7 @@ const KeybindingsLive = effect(Keybindings, gen(function* () {
|
|
|
91750
91803
|
const nextConfig = (yield* loadWritableCustomKeybindingsConfig()).filter((entry) => entry.command !== command);
|
|
91751
91804
|
yield* writeConfigAtomically(nextConfig);
|
|
91752
91805
|
const nextResolved = mergeWithDefaultKeybindings(compileResolvedKeybindingsConfig(nextConfig));
|
|
91753
|
-
yield* set$
|
|
91806
|
+
yield* set$4(resolvedConfigCache, resolvedConfigCacheKey, {
|
|
91754
91807
|
keybindings: nextResolved,
|
|
91755
91808
|
issues: []
|
|
91756
91809
|
});
|
|
@@ -92685,6 +92738,15 @@ async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync
|
|
|
92685
92738
|
}
|
|
92686
92739
|
return cached$1;
|
|
92687
92740
|
}
|
|
92741
|
+
//#endregion
|
|
92742
|
+
//#region src/environment/serverVersion.ts
|
|
92743
|
+
const installedForgeServerVersion = version$1;
|
|
92744
|
+
const SEMVER_NUMERIC_IDENTIFIER = "(?:0|[1-9][0-9]*)";
|
|
92745
|
+
const SEMVER_PRERELEASE_IDENTIFIER = "(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)";
|
|
92746
|
+
const EXACT_FORGE_SERVER_VERSION_PATTERN = new RegExp(`^${SEMVER_NUMERIC_IDENTIFIER}[.]${SEMVER_NUMERIC_IDENTIFIER}[.]${SEMVER_NUMERIC_IDENTIFIER}(?:-${SEMVER_PRERELEASE_IDENTIFIER}(?:[.]${SEMVER_PRERELEASE_IDENTIFIER})*)?(?:[+][0-9A-Za-z-]+(?:[.][0-9A-Za-z-]+)*)?\$`);
|
|
92747
|
+
function isExactForgeServerVersion(value) {
|
|
92748
|
+
return typeof value === "string" && EXACT_FORGE_SERVER_VERSION_PATTERN.test(value);
|
|
92749
|
+
}
|
|
92688
92750
|
const decodeSessions = decodeUnknownSync(ArraySchema(Struct({
|
|
92689
92751
|
sessionId: AuthSessionId,
|
|
92690
92752
|
subject: String$1
|
|
@@ -94299,7 +94361,8 @@ const registerCloudRuntimeForReplicaHydration = gen(function* () {
|
|
|
94299
94361
|
credentialEpoch,
|
|
94300
94362
|
activeTurnCount: 0,
|
|
94301
94363
|
activeSessionCount: 0,
|
|
94302
|
-
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {}
|
|
94364
|
+
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
94365
|
+
forgeServerVersion: installedForgeServerVersion
|
|
94303
94366
|
}),
|
|
94304
94367
|
catch: (cause) => replicaHydrationFailure("runtime registration heartbeat failed", cause)
|
|
94305
94368
|
})) === null) return yield* replicaHydrationFailure("runtime control authority is unavailable");
|
|
@@ -95620,10 +95683,13 @@ function serializeRuntimeInventory(snapshot, providerInventory) {
|
|
|
95620
95683
|
workerRole: thread.workerRole,
|
|
95621
95684
|
anchorKind: thread.anchorKind,
|
|
95622
95685
|
anchorCmmdId: thread.anchorCmmdId,
|
|
95623
|
-
metadata:
|
|
95624
|
-
|
|
95625
|
-
|
|
95626
|
-
|
|
95686
|
+
metadata: {
|
|
95687
|
+
...encodeCmmdRuntimeThreadLifecycleMetadata({
|
|
95688
|
+
settledAt: thread.settledAt ?? null,
|
|
95689
|
+
settledOverride: thread.settledOverride ?? null
|
|
95690
|
+
}),
|
|
95691
|
+
...encodeCmmdRuntimeThreadWorkspaceMetadata({ worktreePath: thread.worktreePath })
|
|
95692
|
+
}
|
|
95627
95693
|
})),
|
|
95628
95694
|
metadata: {
|
|
95629
95695
|
source: "forge-server-projection",
|
|
@@ -95690,7 +95756,7 @@ function serializeNativeToolActivity(activity) {
|
|
|
95690
95756
|
kind: "tool_call",
|
|
95691
95757
|
summary,
|
|
95692
95758
|
status: activityStatus(activity),
|
|
95693
|
-
|
|
95759
|
+
payload: encodeCmmdRuntimeToolActivityPayload(activity.kind, activity.payload)
|
|
95694
95760
|
}
|
|
95695
95761
|
};
|
|
95696
95762
|
}
|
|
@@ -97852,8 +97918,8 @@ const runUsageScanWarmup = (options) => gen(function* () {
|
|
|
97852
97918
|
return yield* forever$1(gen(function* () {
|
|
97853
97919
|
if (yield* get$6(scanning)) yield* logInfo("usage scan warm-up skipped; the previous scan is still running");
|
|
97854
97920
|
else {
|
|
97855
|
-
yield* set$
|
|
97856
|
-
yield* forkChild(scanOnce(options, now).pipe(ensuring(set$
|
|
97921
|
+
yield* set$3(scanning, true);
|
|
97922
|
+
yield* forkChild(scanOnce(options, now).pipe(ensuring(set$3(scanning, false))));
|
|
97857
97923
|
}
|
|
97858
97924
|
yield* sleep(intervalMs);
|
|
97859
97925
|
}));
|
|
@@ -97871,11 +97937,11 @@ const makeCommandGate = gen(function* () {
|
|
|
97871
97937
|
return {
|
|
97872
97938
|
awaitCommandReady: _await(commandReady),
|
|
97873
97939
|
signalCommandReady: gen(function* () {
|
|
97874
|
-
yield* set$
|
|
97940
|
+
yield* set$3(commandReadinessState, "ready");
|
|
97875
97941
|
yield* succeed$3(commandReady, void 0).pipe(orDie);
|
|
97876
97942
|
}),
|
|
97877
97943
|
failCommandReady: (error) => gen(function* () {
|
|
97878
|
-
yield* set$
|
|
97944
|
+
yield* set$3(commandReadinessState, error);
|
|
97879
97945
|
yield* fail$3(commandReady, error).pipe(orDie);
|
|
97880
97946
|
}),
|
|
97881
97947
|
enqueueCommand: (effect) => gen(function* () {
|
|
@@ -98256,413 +98322,23 @@ function browseDirectory(input) {
|
|
|
98256
98322
|
});
|
|
98257
98323
|
}
|
|
98258
98324
|
//#endregion
|
|
98259
|
-
//#region src/
|
|
98325
|
+
//#region src/pathWithinRoot.ts
|
|
98260
98326
|
/**
|
|
98261
|
-
*
|
|
98327
|
+
* True when `candidate` is `root` itself or lies below it, judged on the path
|
|
98328
|
+
* strings alone. The path from `root` to `candidate` is taken with
|
|
98329
|
+
* `path.relative` and normalised to forward slashes, and the candidate is
|
|
98330
|
+
* outside when that path climbs out (`..` or `../...`) or is absolute, which is
|
|
98331
|
+
* what `relative` returns across Windows drives. A child whose name only starts
|
|
98332
|
+
* with two dots, such as `..cache`, is inside. Symlinks are not followed, so a
|
|
98333
|
+
* caller that cares where a link points has to compare resolved paths.
|
|
98262
98334
|
*
|
|
98263
|
-
*
|
|
98264
|
-
*
|
|
98265
|
-
*
|
|
98266
|
-
* @module WorkspaceEntries
|
|
98267
|
-
*/
|
|
98268
|
-
var WorkspaceEntriesError = class extends TaggedErrorClass()("WorkspaceEntriesError", {
|
|
98269
|
-
cwd: String$1,
|
|
98270
|
-
operation: String$1,
|
|
98271
|
-
detail: String$1,
|
|
98272
|
-
cause: optional$4(Defect)
|
|
98273
|
-
}) {};
|
|
98274
|
-
/**
|
|
98275
|
-
* WorkspaceEntries - Service tag for cached workspace entry search.
|
|
98276
|
-
*/
|
|
98277
|
-
var WorkspaceEntries = class extends Service$1()("forge/workspace/Services/WorkspaceEntries") {};
|
|
98278
|
-
//#endregion
|
|
98279
|
-
//#region src/workspace/Services/WorkspaceBulkTransfer.ts
|
|
98280
|
-
var WorkspaceBulkTransfer = class extends Service$1()("forge/workspace/Services/WorkspaceBulkTransfer") {};
|
|
98281
|
-
//#endregion
|
|
98282
|
-
//#region src/workspace/Layers/WorkspaceBulkTransfer.ts
|
|
98283
|
-
const TRANSFER_TTL_MS = 1440 * 60 * 1e3;
|
|
98284
|
-
const TRANSFER_LOCK_STRIPE_COUNT = 64;
|
|
98285
|
-
function transferError(input) {
|
|
98286
|
-
return new BulkTransferError({
|
|
98287
|
-
code: input.code,
|
|
98288
|
-
message: input.message,
|
|
98289
|
-
transferId: input.transferId ?? null,
|
|
98290
|
-
retryable: input.retryable ?? false,
|
|
98291
|
-
...input.cause === void 0 ? {} : { cause: input.cause }
|
|
98292
|
-
});
|
|
98293
|
-
}
|
|
98294
|
-
function mapFailure(operation, transferId) {
|
|
98295
|
-
return (cause) => is$1(BulkTransferError)(cause) ? cause : transferError({
|
|
98296
|
-
code: "io_error",
|
|
98297
|
-
message: `Workspace bulk transfer failed during ${operation}.`,
|
|
98298
|
-
...transferId === void 0 ? {} : { transferId },
|
|
98299
|
-
retryable: true,
|
|
98300
|
-
cause
|
|
98301
|
-
});
|
|
98302
|
-
}
|
|
98303
|
-
/**
|
|
98304
|
-
* Exported so the unary file read in `projectFile/readProjectFile.ts` uses this
|
|
98305
|
-
* containment check rather than a second copy of it. Two copies of a security
|
|
98306
|
-
* rule in one repo is how they drift, and the drift is silent.
|
|
98335
|
+
* Only a type import, so a module that has to stay free of Effect services can
|
|
98336
|
+
* use it without importing a Layer.
|
|
98307
98337
|
*/
|
|
98308
98338
|
function isPathWithinRoot(path, root, candidate) {
|
|
98309
98339
|
const relative = path.relative(root, candidate).replaceAll("\\", "/");
|
|
98310
98340
|
return relative === "" || !relative.startsWith("../") && relative !== ".." && !path.isAbsolute(relative);
|
|
98311
98341
|
}
|
|
98312
|
-
function transferLockStripe(transferId) {
|
|
98313
|
-
let hash = 2166136261;
|
|
98314
|
-
for (let index = 0; index < transferId.length; index += 1) hash = Math.imul(hash ^ transferId.charCodeAt(index), 16777619);
|
|
98315
|
-
return (hash >>> 0) % TRANSFER_LOCK_STRIPE_COUNT;
|
|
98316
|
-
}
|
|
98317
|
-
const makeWorkspaceBulkTransfer = (options = {}) => gen(function* () {
|
|
98318
|
-
const fileSystem = yield* FileSystem;
|
|
98319
|
-
const path = yield* Path$2;
|
|
98320
|
-
const sql = yield* SqlClient;
|
|
98321
|
-
const workspacePaths = yield* WorkspacePaths;
|
|
98322
|
-
const workspaceEntries = yield* WorkspaceEntries;
|
|
98323
|
-
const fault = options.fault ?? (() => void_$1);
|
|
98324
|
-
const transferLocks = yield* forEach(Array.from({ length: TRANSFER_LOCK_STRIPE_COUNT }), () => make$43(1));
|
|
98325
|
-
const withTransferLock = (transferId, effect) => transferLocks[transferLockStripe(transferId)].withPermit(effect);
|
|
98326
|
-
const findTransfer = (transferId) => sql`
|
|
98327
|
-
SELECT transfer_id AS "transferId", workspace_root AS "workspaceRoot",
|
|
98328
|
-
relative_path AS "relativePath", target_path AS "targetPath", temp_path AS "tempPath",
|
|
98329
|
-
total_bytes AS "totalBytes", whole_sha256 AS "wholeSha256",
|
|
98330
|
-
next_offset AS "nextOffset", state, expires_at AS "expiresAt"
|
|
98331
|
-
FROM workspace_bulk_transfers WHERE transfer_id = ${transferId} LIMIT 1
|
|
98332
|
-
`.pipe(map$3((rows) => rows[0] ?? null));
|
|
98333
|
-
const requireTransfer = (transferId) => findTransfer(transferId).pipe(flatMap((row) => row === null ? fail(transferError({
|
|
98334
|
-
code: "transfer_not_found",
|
|
98335
|
-
message: "Bulk transfer does not exist.",
|
|
98336
|
-
transferId
|
|
98337
|
-
})) : succeed(row)));
|
|
98338
|
-
const requireOpenTransfer = (transferId) => requireTransfer(transferId).pipe(flatMap((row) => {
|
|
98339
|
-
if (row.state === "cancelled") return fail(transferError({
|
|
98340
|
-
code: "transfer_cancelled",
|
|
98341
|
-
message: "Bulk transfer was cancelled.",
|
|
98342
|
-
transferId
|
|
98343
|
-
}));
|
|
98344
|
-
if (row.state === "expired" || Date.parse(row.expiresAt) <= Date.now()) return fail(transferError({
|
|
98345
|
-
code: "transfer_expired",
|
|
98346
|
-
message: "Bulk transfer expired.",
|
|
98347
|
-
transferId
|
|
98348
|
-
}));
|
|
98349
|
-
return succeed(row);
|
|
98350
|
-
}));
|
|
98351
|
-
const resolveTarget = fn$1("WorkspaceBulkTransfer.resolveTarget")(function* (input) {
|
|
98352
|
-
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd);
|
|
98353
|
-
const resolved = yield* workspacePaths.resolveRelativePathWithinRoot({
|
|
98354
|
-
workspaceRoot,
|
|
98355
|
-
relativePath: input.relativePath
|
|
98356
|
-
});
|
|
98357
|
-
const canonicalRoot = yield* fileSystem.realPath(workspaceRoot);
|
|
98358
|
-
const parentSegments = resolved.relativePath.split("/").slice(0, -1);
|
|
98359
|
-
let canonicalParent = canonicalRoot;
|
|
98360
|
-
for (const segment of parentSegments) {
|
|
98361
|
-
const candidate = path.join(canonicalParent, segment);
|
|
98362
|
-
const canonicalCandidate = yield* fileSystem.realPath(candidate).pipe(catch_(() => input.createParent ? fileSystem.makeDirectory(candidate).pipe(flatMap(() => fileSystem.realPath(candidate))) : fileSystem.realPath(candidate)));
|
|
98363
|
-
if (!isPathWithinRoot(path, canonicalRoot, canonicalCandidate)) return yield* transferError({
|
|
98364
|
-
code: "symlink_escape",
|
|
98365
|
-
message: "Workspace file path resolves outside the project root."
|
|
98366
|
-
});
|
|
98367
|
-
canonicalParent = canonicalCandidate;
|
|
98368
|
-
}
|
|
98369
|
-
const targetPath = path.join(canonicalParent, path.basename(resolved.relativePath));
|
|
98370
|
-
const targetRealPath = yield* fileSystem.realPath(targetPath).pipe(catch_(() => succeed(null)));
|
|
98371
|
-
if (targetRealPath !== null && !isPathWithinRoot(path, canonicalRoot, targetRealPath)) return yield* transferError({
|
|
98372
|
-
code: "symlink_escape",
|
|
98373
|
-
message: "Workspace file target resolves outside the project root."
|
|
98374
|
-
});
|
|
98375
|
-
return {
|
|
98376
|
-
workspaceRoot: canonicalRoot,
|
|
98377
|
-
relativePath: resolved.relativePath,
|
|
98378
|
-
targetPath,
|
|
98379
|
-
canonicalParent
|
|
98380
|
-
};
|
|
98381
|
-
});
|
|
98382
|
-
const readFile = (input) => unwrap$1(gen(function* () {
|
|
98383
|
-
const target = yield* resolveTarget({
|
|
98384
|
-
...input,
|
|
98385
|
-
createParent: false
|
|
98386
|
-
});
|
|
98387
|
-
const info = yield* fileSystem.stat(target.targetPath);
|
|
98388
|
-
const maxBytes = input.maxBytes ?? 16777216;
|
|
98389
|
-
if (Number(info.size) > maxBytes) return yield* transferError({
|
|
98390
|
-
code: "size_limit_exceeded",
|
|
98391
|
-
message: `Workspace file exceeds the ${maxBytes}-byte read limit.`
|
|
98392
|
-
});
|
|
98393
|
-
const bytes = yield* fileSystem.readFile(target.targetPath);
|
|
98394
|
-
const transfer = yield* promise(() => chunkBulkTransfer(bytes));
|
|
98395
|
-
const transferId = transfer.chunks[0]?.transferId ?? crypto.randomUUID().replaceAll("-", "_");
|
|
98396
|
-
return fromIterable$1([
|
|
98397
|
-
{
|
|
98398
|
-
kind: "started",
|
|
98399
|
-
transferId,
|
|
98400
|
-
totalBytes: transfer.totalBytes,
|
|
98401
|
-
sha256: transfer.sha256
|
|
98402
|
-
},
|
|
98403
|
-
...transfer.chunks.map((chunk) => ({
|
|
98404
|
-
...chunk,
|
|
98405
|
-
transferId
|
|
98406
|
-
})),
|
|
98407
|
-
{
|
|
98408
|
-
kind: "completed",
|
|
98409
|
-
transferId,
|
|
98410
|
-
totalBytes: transfer.totalBytes,
|
|
98411
|
-
sha256: transfer.sha256
|
|
98412
|
-
}
|
|
98413
|
-
]);
|
|
98414
|
-
}).pipe(mapError(mapFailure("read file"))));
|
|
98415
|
-
const resumeWrite = fn$1("WorkspaceBulkTransfer.resumeWrite")(function* (input, existing) {
|
|
98416
|
-
const target = yield* resolveTarget({
|
|
98417
|
-
...input,
|
|
98418
|
-
createParent: true
|
|
98419
|
-
});
|
|
98420
|
-
if (!(existing.workspaceRoot === target.workspaceRoot && existing.relativePath === target.relativePath && existing.totalBytes === input.totalBytes && existing.wholeSha256 === input.sha256)) return yield* transferError({
|
|
98421
|
-
code: "transfer_identity_conflict",
|
|
98422
|
-
message: "Bulk transfer ID is already bound to different content or a different path.",
|
|
98423
|
-
transferId: input.transferId
|
|
98424
|
-
});
|
|
98425
|
-
if (existing.state === "cancelled") return yield* transferError({
|
|
98426
|
-
code: "transfer_cancelled",
|
|
98427
|
-
message: "Bulk transfer was cancelled.",
|
|
98428
|
-
transferId: input.transferId
|
|
98429
|
-
});
|
|
98430
|
-
if (existing.state === "expired" || Date.parse(existing.expiresAt) <= Date.now()) return yield* transferError({
|
|
98431
|
-
code: "transfer_expired",
|
|
98432
|
-
message: "Bulk transfer expired.",
|
|
98433
|
-
transferId: input.transferId
|
|
98434
|
-
});
|
|
98435
|
-
return {
|
|
98436
|
-
transferId: input.transferId,
|
|
98437
|
-
nextOffset: existing.nextOffset,
|
|
98438
|
-
state: existing.state === "completed" ? "completed" : "open"
|
|
98439
|
-
};
|
|
98440
|
-
});
|
|
98441
|
-
const createWrite = fn$1("WorkspaceBulkTransfer.createWrite")(function* (input) {
|
|
98442
|
-
const target = yield* resolveTarget({
|
|
98443
|
-
...input,
|
|
98444
|
-
createParent: true
|
|
98445
|
-
}).pipe(mapError((cause) => is$1(WorkspacePathOutsideRootError)(cause) ? transferError({
|
|
98446
|
-
code: "path_outside_workspace",
|
|
98447
|
-
message: "Workspace file path must stay within the project root.",
|
|
98448
|
-
transferId: input.transferId,
|
|
98449
|
-
cause
|
|
98450
|
-
}) : cause));
|
|
98451
|
-
const tempPath = path.join(target.canonicalParent, `.forge-transfer-${input.transferId}.part`);
|
|
98452
|
-
yield* scoped(fileSystem.open(tempPath, {
|
|
98453
|
-
flag: "wx",
|
|
98454
|
-
mode: 384
|
|
98455
|
-
}).pipe(flatMap((file) => file.sync), catch_(() => fileSystem.stat(tempPath).pipe(flatMap((stat) => Number(stat.size) === 0 ? void_$1 : fail(transferError({
|
|
98456
|
-
code: "transfer_identity_conflict",
|
|
98457
|
-
message: "Bulk transfer temporary path already contains data.",
|
|
98458
|
-
transferId: input.transferId
|
|
98459
|
-
})))))));
|
|
98460
|
-
const now = /* @__PURE__ */ new Date();
|
|
98461
|
-
yield* sql`
|
|
98462
|
-
INSERT INTO workspace_bulk_transfers (
|
|
98463
|
-
transfer_id, workspace_root, relative_path, target_path, temp_path, total_bytes,
|
|
98464
|
-
whole_sha256, next_offset, state, expires_at, created_at, updated_at
|
|
98465
|
-
) VALUES (
|
|
98466
|
-
${input.transferId}, ${target.workspaceRoot}, ${target.relativePath}, ${target.targetPath},
|
|
98467
|
-
${tempPath}, ${input.totalBytes}, ${input.sha256}, 0, 'open',
|
|
98468
|
-
${new Date(now.getTime() + TRANSFER_TTL_MS).toISOString()}, ${now.toISOString()},
|
|
98469
|
-
${now.toISOString()}
|
|
98470
|
-
)
|
|
98471
|
-
`;
|
|
98472
|
-
return {
|
|
98473
|
-
transferId: input.transferId,
|
|
98474
|
-
nextOffset: 0,
|
|
98475
|
-
state: "open"
|
|
98476
|
-
};
|
|
98477
|
-
});
|
|
98478
|
-
const beginWriteEffect = fn$1("WorkspaceBulkTransfer.beginWrite")(function* (input) {
|
|
98479
|
-
const existing = yield* findTransfer(input.transferId);
|
|
98480
|
-
return yield* existing === null ? createWrite(input) : resumeWrite(input, existing);
|
|
98481
|
-
});
|
|
98482
|
-
const beginWrite = (input) => withTransferLock(input.transferId, beginWriteEffect(input)).pipe(mapError(mapFailure("begin write", input.transferId)));
|
|
98483
|
-
const acceptRetriedChunk = fn$1("WorkspaceBulkTransfer.acceptRetriedChunk")(function* (input, row) {
|
|
98484
|
-
const accepted = (yield* sql`
|
|
98485
|
-
SELECT chunk_offset AS "chunkOffset", chunk_bytes AS "chunkBytes",
|
|
98486
|
-
chunk_sha256 AS "chunkSha256"
|
|
98487
|
-
FROM workspace_bulk_transfer_chunks
|
|
98488
|
-
WHERE transfer_id = ${input.transferId} AND chunk_offset = ${input.offset}
|
|
98489
|
-
LIMIT 1
|
|
98490
|
-
`)[0];
|
|
98491
|
-
if (!(accepted !== void 0 && accepted.chunkBytes === input.bytes && accepted.chunkSha256 === input.sha256)) return yield* transferError({
|
|
98492
|
-
code: "integrity_mismatch",
|
|
98493
|
-
message: "Retried bulk transfer chunk differs from the accepted chunk.",
|
|
98494
|
-
transferId: input.transferId
|
|
98495
|
-
});
|
|
98496
|
-
return {
|
|
98497
|
-
transferId: input.transferId,
|
|
98498
|
-
nextOffset: row.nextOffset,
|
|
98499
|
-
duplicate: true
|
|
98500
|
-
};
|
|
98501
|
-
});
|
|
98502
|
-
const validateChunkRange = fn$1("WorkspaceBulkTransfer.validateChunkRange")(function* (input, row, byteLength) {
|
|
98503
|
-
if (input.offset !== row.nextOffset) return yield* transferError({
|
|
98504
|
-
code: "offset_mismatch",
|
|
98505
|
-
message: `Expected chunk offset ${row.nextOffset}, received ${input.offset}.`,
|
|
98506
|
-
transferId: input.transferId,
|
|
98507
|
-
retryable: true
|
|
98508
|
-
});
|
|
98509
|
-
const nextOffset = input.offset + byteLength;
|
|
98510
|
-
if (nextOffset > row.totalBytes) return yield* transferError({
|
|
98511
|
-
code: "size_limit_exceeded",
|
|
98512
|
-
message: "Bulk transfer chunk exceeds the declared total byte count.",
|
|
98513
|
-
transferId: input.transferId
|
|
98514
|
-
});
|
|
98515
|
-
return nextOffset;
|
|
98516
|
-
});
|
|
98517
|
-
const reconcileChunkBytes = fn$1("WorkspaceBulkTransfer.reconcileChunkBytes")(function* (input, row, bytes, nextOffset) {
|
|
98518
|
-
const tempInfo = yield* fileSystem.stat(row.tempPath);
|
|
98519
|
-
if (Number(tempInfo.size) > row.nextOffset) {
|
|
98520
|
-
if (Number(tempInfo.size) !== nextOffset) return yield* transferError({
|
|
98521
|
-
code: "integrity_mismatch",
|
|
98522
|
-
message: "Bulk transfer temporary file cannot be reconciled with the next chunk.",
|
|
98523
|
-
transferId: input.transferId
|
|
98524
|
-
});
|
|
98525
|
-
const recovered = (yield* fileSystem.readFile(row.tempPath)).slice(input.offset, nextOffset);
|
|
98526
|
-
if ((yield* promise(() => sha256Hex(recovered))) !== input.sha256) return yield* transferError({
|
|
98527
|
-
code: "integrity_mismatch",
|
|
98528
|
-
message: "Bulk transfer temporary bytes differ from the retried chunk.",
|
|
98529
|
-
transferId: input.transferId
|
|
98530
|
-
});
|
|
98531
|
-
return;
|
|
98532
|
-
}
|
|
98533
|
-
yield* fault("before-byte-write");
|
|
98534
|
-
yield* scoped(fileSystem.open(row.tempPath, { flag: "r+" }).pipe(flatMap((file) => file.seek(input.offset, "start").pipe(flatMap(() => file.writeAll(bytes)), flatMap(() => file.sync)))));
|
|
98535
|
-
yield* fault("after-byte-write-before-metadata");
|
|
98536
|
-
});
|
|
98537
|
-
const persistAcceptedChunk = fn$1("WorkspaceBulkTransfer.persistAcceptedChunk")(function* (input, nextOffset) {
|
|
98538
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
98539
|
-
yield* sql.withTransaction(sql`
|
|
98540
|
-
INSERT INTO workspace_bulk_transfer_chunks (
|
|
98541
|
-
transfer_id, chunk_offset, chunk_bytes, chunk_sha256
|
|
98542
|
-
) VALUES (${input.transferId}, ${input.offset}, ${input.bytes}, ${input.sha256})
|
|
98543
|
-
ON CONFLICT DO NOTHING
|
|
98544
|
-
`.pipe(flatMap(() => sql`
|
|
98545
|
-
UPDATE workspace_bulk_transfers
|
|
98546
|
-
SET next_offset = ${nextOffset}, updated_at = ${updatedAt}
|
|
98547
|
-
WHERE transfer_id = ${input.transferId} AND next_offset = ${input.offset} AND state = 'open'
|
|
98548
|
-
`)));
|
|
98549
|
-
yield* fault("after-metadata-advance");
|
|
98550
|
-
});
|
|
98551
|
-
const writeChunkEffect = fn$1("WorkspaceBulkTransfer.writeChunk")(function* (input) {
|
|
98552
|
-
const bytes = yield* promise(() => verifyBulkTransferChunk({
|
|
98553
|
-
...input,
|
|
98554
|
-
kind: "chunk"
|
|
98555
|
-
}));
|
|
98556
|
-
const row = yield* requireOpenTransfer(input.transferId);
|
|
98557
|
-
if (row.state === "completed") return yield* transferError({
|
|
98558
|
-
code: "offset_mismatch",
|
|
98559
|
-
message: "Bulk transfer is already complete.",
|
|
98560
|
-
transferId: input.transferId
|
|
98561
|
-
});
|
|
98562
|
-
if (input.offset < row.nextOffset) return yield* acceptRetriedChunk(input, row);
|
|
98563
|
-
const nextOffset = yield* validateChunkRange(input, row, bytes.byteLength);
|
|
98564
|
-
yield* reconcileChunkBytes(input, row, bytes, nextOffset);
|
|
98565
|
-
yield* persistAcceptedChunk(input, nextOffset);
|
|
98566
|
-
return {
|
|
98567
|
-
transferId: input.transferId,
|
|
98568
|
-
nextOffset,
|
|
98569
|
-
duplicate: false
|
|
98570
|
-
};
|
|
98571
|
-
});
|
|
98572
|
-
const writeChunk = (input) => withTransferLock(input.transferId, writeChunkEffect(input)).pipe(mapError(mapFailure("write chunk", input.transferId)));
|
|
98573
|
-
const commitWriteEffect = fn$1("WorkspaceBulkTransfer.commitWrite")(function* (input) {
|
|
98574
|
-
const row = yield* requireOpenTransfer(input.transferId);
|
|
98575
|
-
if (row.state === "completed") return {
|
|
98576
|
-
transferId: row.transferId,
|
|
98577
|
-
relativePath: row.relativePath,
|
|
98578
|
-
totalBytes: row.totalBytes,
|
|
98579
|
-
sha256: row.wholeSha256
|
|
98580
|
-
};
|
|
98581
|
-
if (row.nextOffset !== row.totalBytes) return yield* transferError({
|
|
98582
|
-
code: "offset_mismatch",
|
|
98583
|
-
message: `Bulk transfer is incomplete at byte ${row.nextOffset} of ${row.totalBytes}.`,
|
|
98584
|
-
transferId: row.transferId,
|
|
98585
|
-
retryable: true
|
|
98586
|
-
});
|
|
98587
|
-
if ((yield* resolveTarget({
|
|
98588
|
-
cwd: row.workspaceRoot,
|
|
98589
|
-
relativePath: row.relativePath,
|
|
98590
|
-
createParent: false
|
|
98591
|
-
})).targetPath !== row.targetPath) return yield* transferError({
|
|
98592
|
-
code: "symlink_escape",
|
|
98593
|
-
message: "Workspace file destination changed during transfer.",
|
|
98594
|
-
transferId: row.transferId
|
|
98595
|
-
});
|
|
98596
|
-
const tempInfo = yield* fileSystem.stat(row.tempPath).pipe(catch_(() => succeed(null)));
|
|
98597
|
-
const candidatePath = tempInfo === null ? row.targetPath : row.tempPath;
|
|
98598
|
-
const candidate = yield* fileSystem.readFile(candidatePath);
|
|
98599
|
-
if (candidate.byteLength !== row.totalBytes || (yield* promise(() => sha256Hex(candidate))) !== row.wholeSha256) return yield* transferError({
|
|
98600
|
-
code: "integrity_mismatch",
|
|
98601
|
-
message: "Bulk transfer payload does not match its declared size and digest.",
|
|
98602
|
-
transferId: row.transferId
|
|
98603
|
-
});
|
|
98604
|
-
if (tempInfo !== null) {
|
|
98605
|
-
yield* fileSystem.rename(row.tempPath, row.targetPath);
|
|
98606
|
-
yield* fault("after-atomic-rename");
|
|
98607
|
-
}
|
|
98608
|
-
yield* sql`
|
|
98609
|
-
UPDATE workspace_bulk_transfers SET state = 'completed', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
98610
|
-
WHERE transfer_id = ${row.transferId}
|
|
98611
|
-
`;
|
|
98612
|
-
yield* workspaceEntries.invalidate(row.workspaceRoot);
|
|
98613
|
-
return {
|
|
98614
|
-
transferId: row.transferId,
|
|
98615
|
-
relativePath: row.relativePath,
|
|
98616
|
-
totalBytes: row.totalBytes,
|
|
98617
|
-
sha256: row.wholeSha256
|
|
98618
|
-
};
|
|
98619
|
-
});
|
|
98620
|
-
const commitWrite = (input) => withTransferLock(input.transferId, commitWriteEffect(input)).pipe(mapError(mapFailure("commit write", input.transferId)));
|
|
98621
|
-
const cancelWriteEffect = fn$1("WorkspaceBulkTransfer.cancelWrite")(function* (input) {
|
|
98622
|
-
const row = yield* requireTransfer(input.transferId);
|
|
98623
|
-
if (row.state !== "completed") {
|
|
98624
|
-
yield* fileSystem.remove(row.tempPath, { force: true });
|
|
98625
|
-
yield* sql`
|
|
98626
|
-
UPDATE workspace_bulk_transfers SET state = 'cancelled', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
98627
|
-
WHERE transfer_id = ${input.transferId}
|
|
98628
|
-
`;
|
|
98629
|
-
}
|
|
98630
|
-
return {
|
|
98631
|
-
transferId: input.transferId,
|
|
98632
|
-
state: row.state === "completed" ? "completed" : "cancelled"
|
|
98633
|
-
};
|
|
98634
|
-
});
|
|
98635
|
-
const cancelWrite = (input) => withTransferLock(input.transferId, cancelWriteEffect(input)).pipe(mapError(mapFailure("cancel write", input.transferId)));
|
|
98636
|
-
const cleanupExpiredEffect = fn$1("WorkspaceBulkTransfer.cleanupExpired")(function* () {
|
|
98637
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
98638
|
-
const rows = yield* sql`
|
|
98639
|
-
SELECT transfer_id AS "transferId", temp_path AS "tempPath"
|
|
98640
|
-
FROM workspace_bulk_transfers WHERE state = 'open' AND expires_at <= ${now}
|
|
98641
|
-
`;
|
|
98642
|
-
let expiredCount = 0;
|
|
98643
|
-
for (const row of rows) if (yield* withTransferLock(row.transferId, gen(function* () {
|
|
98644
|
-
const current = yield* findTransfer(row.transferId);
|
|
98645
|
-
if (current === null || current.state !== "open" || Date.parse(current.expiresAt) > Date.parse(now)) return false;
|
|
98646
|
-
yield* fileSystem.remove(current.tempPath, { force: true });
|
|
98647
|
-
yield* sql`
|
|
98648
|
-
UPDATE workspace_bulk_transfers SET state = 'expired', updated_at = ${now}
|
|
98649
|
-
WHERE transfer_id = ${row.transferId} AND state = 'open' AND expires_at <= ${now}
|
|
98650
|
-
`;
|
|
98651
|
-
return true;
|
|
98652
|
-
}))) expiredCount += 1;
|
|
98653
|
-
return expiredCount;
|
|
98654
|
-
});
|
|
98655
|
-
const cleanupExpired = () => cleanupExpiredEffect().pipe(mapError(mapFailure("expiry cleanup")));
|
|
98656
|
-
return {
|
|
98657
|
-
readFile,
|
|
98658
|
-
beginWrite,
|
|
98659
|
-
writeChunk,
|
|
98660
|
-
commitWrite,
|
|
98661
|
-
cancelWrite,
|
|
98662
|
-
cleanupExpired
|
|
98663
|
-
};
|
|
98664
|
-
});
|
|
98665
|
-
const WorkspaceBulkTransferLive = effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
|
|
98666
98342
|
//#endregion
|
|
98667
98343
|
//#region src/projectFile/readProjectFile.ts
|
|
98668
98344
|
/**
|
|
@@ -99464,6 +99140,26 @@ const DevTunnelLive = effect(DevTunnel, gen(function* () {
|
|
|
99464
99140
|
};
|
|
99465
99141
|
}));
|
|
99466
99142
|
//#endregion
|
|
99143
|
+
//#region src/workspace/Services/WorkspaceEntries.ts
|
|
99144
|
+
/**
|
|
99145
|
+
* WorkspaceEntries - Effect service contract for cached workspace entry search.
|
|
99146
|
+
*
|
|
99147
|
+
* Owns indexed workspace entry search plus cache invalidation for workspace
|
|
99148
|
+
* roots when the underlying filesystem changes.
|
|
99149
|
+
*
|
|
99150
|
+
* @module WorkspaceEntries
|
|
99151
|
+
*/
|
|
99152
|
+
var WorkspaceEntriesError = class extends TaggedErrorClass()("WorkspaceEntriesError", {
|
|
99153
|
+
cwd: String$1,
|
|
99154
|
+
operation: String$1,
|
|
99155
|
+
detail: String$1,
|
|
99156
|
+
cause: optional$4(Defect)
|
|
99157
|
+
}) {};
|
|
99158
|
+
/**
|
|
99159
|
+
* WorkspaceEntries - Service tag for cached workspace entry search.
|
|
99160
|
+
*/
|
|
99161
|
+
var WorkspaceEntries = class extends Service$1()("forge/workspace/Services/WorkspaceEntries") {};
|
|
99162
|
+
//#endregion
|
|
99467
99163
|
//#region src/workspace/Services/WorkspaceFileSystem.ts
|
|
99468
99164
|
/**
|
|
99469
99165
|
* WorkspaceFileSystem - Effect service contract for workspace file mutations.
|
|
@@ -99485,6 +99181,9 @@ var WorkspaceFileSystemError = class extends TaggedErrorClass()("WorkspaceFileSy
|
|
|
99485
99181
|
*/
|
|
99486
99182
|
var WorkspaceFileSystem = class extends Service$1()("forge/workspace/Services/WorkspaceFileSystem") {};
|
|
99487
99183
|
//#endregion
|
|
99184
|
+
//#region src/workspace/Services/WorkspaceBulkTransfer.ts
|
|
99185
|
+
var WorkspaceBulkTransfer = class extends Service$1()("forge/workspace/Services/WorkspaceBulkTransfer") {};
|
|
99186
|
+
//#endregion
|
|
99488
99187
|
//#region src/workspace/Services/WorkspaceReadinessState.ts
|
|
99489
99188
|
var WorkspaceReadinessState = class extends Service$1()("forge/workspace/Services/WorkspaceReadinessState") {};
|
|
99490
99189
|
//#endregion
|
|
@@ -104941,6 +104640,13 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
|
|
|
104941
104640
|
message: "Failed to replay orchestration events",
|
|
104942
104641
|
cause
|
|
104943
104642
|
}))), { "rpc.aggregate": "orchestration" }),
|
|
104643
|
+
[ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot]: (_input) => observeRpcEffect(ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, gen(function* () {
|
|
104644
|
+
yield* startup.awaitCommandReady;
|
|
104645
|
+
return yield* projectionSnapshotQuery.getArchivedShellSnapshot();
|
|
104646
|
+
}).pipe(mapError((cause) => new OrchestrationGetSnapshotError({
|
|
104647
|
+
message: "Failed to load archived orchestration shell snapshot",
|
|
104648
|
+
cause
|
|
104649
|
+
}))), { "rpc.aggregate": "orchestration" }),
|
|
104944
104650
|
[ORCHESTRATION_WS_METHODS.subscribeShell]: (_input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeShell, succeed(makeSnapshotThenLiveStream({
|
|
104945
104651
|
loadSnapshot: loadInitialShellSnapshot({
|
|
104946
104652
|
startup,
|
|
@@ -210937,56 +210643,169 @@ function hasConfiguredApexCredential(profiles) {
|
|
|
210937
210643
|
}));
|
|
210938
210644
|
}
|
|
210939
210645
|
//#endregion
|
|
210940
|
-
//#region src/provider/
|
|
210646
|
+
//#region src/provider/claudeCredentialDirectory.ts
|
|
210941
210647
|
/**
|
|
210942
|
-
*
|
|
210648
|
+
* Per-account credential directories for Claude turns.
|
|
210943
210649
|
*
|
|
210944
|
-
*
|
|
210945
|
-
*
|
|
210946
|
-
*
|
|
210947
|
-
*
|
|
210650
|
+
* Claude Code keeps conversation transcripts under
|
|
210651
|
+
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
210652
|
+
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
210653
|
+
* credential file, so each Provider Account can hold its own login while every
|
|
210654
|
+
* session file stays in one place and `--resume` keeps finding its target. See
|
|
210655
|
+
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
210948
210656
|
*
|
|
210949
|
-
*
|
|
210950
|
-
*
|
|
210951
|
-
*
|
|
210952
|
-
*
|
|
210657
|
+
* That variable is not documented. Under
|
|
210658
|
+
* `docs/adr/unpublished-provider-surface-needs-a-fallback.md` the dependency is
|
|
210659
|
+
* only allowed because absence degrades to a named behaviour that already
|
|
210660
|
+
* works: a thread with no account runs on the Environment's own login. This
|
|
210661
|
+
* module sets the variable only when the resolved directory holds a credential
|
|
210662
|
+
* the CLI can use, because a missing file, or one whose access token a refusal
|
|
210663
|
+
* has blanked, makes the CLI report logged out and a live turn would fail.
|
|
210953
210664
|
*
|
|
210954
|
-
*
|
|
210955
|
-
*
|
|
210956
|
-
*
|
|
210957
|
-
*
|
|
210958
|
-
* /api/provider-egress/*, leaving the fail-closed billing gate inert.
|
|
210665
|
+
* Nothing writes these directories yet. #3049 adds the writer and keys each
|
|
210666
|
+
* directory on the account identity CMMD writes on the delivery receipt it
|
|
210667
|
+
* signs (`docs/adr/provider-account-identity-is-issued-by-cmmd.md`). Until then
|
|
210668
|
+
* every runtime takes the `no-account` skip.
|
|
210959
210669
|
*
|
|
210960
|
-
*
|
|
210961
|
-
*
|
|
210962
|
-
*
|
|
210670
|
+
* Free of Effect services on purpose, for the same reason as
|
|
210671
|
+
* `claudeCredentialContainment.ts`: ClaudeAdapter drags in Effect platform
|
|
210672
|
+
* services, the browser manager and the server config, so a unit test that
|
|
210673
|
+
* imports it cannot run in a worktree. This rule decides which account pays for
|
|
210674
|
+
* a turn, so it has to stay directly testable.
|
|
210963
210675
|
*/
|
|
210964
|
-
/**
|
|
210965
|
-
const
|
|
210676
|
+
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
210677
|
+
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
210678
|
+
/** The one file Claude Code writes into the credential directory. */
|
|
210679
|
+
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
210966
210680
|
/**
|
|
210967
|
-
*
|
|
210968
|
-
*
|
|
210969
|
-
*
|
|
210970
|
-
*
|
|
210971
|
-
*
|
|
210972
|
-
*
|
|
210681
|
+
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
210682
|
+
* belong to. Nothing in Forge or CMMD sets it, so no runtime names an account
|
|
210683
|
+
* and each keeps its one login.
|
|
210684
|
+
*
|
|
210685
|
+
* It is a bridge that will not get a writer. #3073 settled the key in
|
|
210686
|
+
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`: the directory name
|
|
210687
|
+
* comes from the identity CMMD issues and writes on the delivery receipt it
|
|
210688
|
+
* signs, and this variable goes away. #3049 makes that change. The credential
|
|
210689
|
+
* generation was ruled out as a key: it rotates on every reconnect, and CMMD
|
|
210690
|
+
* uses it as the primary account's id, so keying on it would strand every
|
|
210691
|
+
* thread pinned to that account on a directory that stops receiving deliveries.
|
|
210692
|
+
*
|
|
210693
|
+
* Tests reach the `credentialDirectory` parameter on
|
|
210694
|
+
* `ClaudeAdapter.buildClaudeQueryEnv` directly.
|
|
210973
210695
|
*/
|
|
210974
|
-
const
|
|
210696
|
+
const CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV = "FORGE_CLAUDE_PROVIDER_ACCOUNT_IDENTITY";
|
|
210697
|
+
/** Directory under the server's base directory that holds one directory per account. */
|
|
210698
|
+
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
210699
|
+
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
210700
|
+
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
210701
|
+
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
210975
210702
|
/**
|
|
210976
|
-
*
|
|
210977
|
-
*
|
|
210978
|
-
|
|
210979
|
-
|
|
210703
|
+
* Directory name for an account. A hash, so no email or display name reaches a
|
|
210704
|
+
* filesystem path and no path-illegal character can appear in one either.
|
|
210705
|
+
*/
|
|
210706
|
+
function claudeCredentialDirectoryName(accountIdentity) {
|
|
210707
|
+
return `account-${createHash("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
210708
|
+
}
|
|
210709
|
+
/** The account this runtime's Claude turns belong to, or null while the variable is unset. */
|
|
210710
|
+
function resolveClaudeProviderAccountIdentity(env) {
|
|
210711
|
+
const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
|
|
210712
|
+
if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
|
|
210713
|
+
return identity;
|
|
210714
|
+
}
|
|
210715
|
+
/**
|
|
210716
|
+
* Where per-account credential directories live: `claude-credentials` under the
|
|
210717
|
+
* server's base directory, `ServerConfig.baseDir`, which is `FORGE_HOME` or
|
|
210718
|
+
* `~/.forge`. A delivery writer gets the same `ServerConfig`, so reader and
|
|
210719
|
+
* writer cannot disagree about the root. Always outside `CLAUDE_CONFIG_DIR`, so
|
|
210720
|
+
* one of these can never be mistaken for the main credential store, and a stray
|
|
210721
|
+
* read of the config directory cannot walk into another account's login.
|
|
210722
|
+
*/
|
|
210723
|
+
function claudeCredentialDirectoryRoot(env, baseDir) {
|
|
210724
|
+
if (!isAbsolute(baseDir)) return null;
|
|
210725
|
+
const root = resolve(baseDir, CREDENTIAL_ROOT_DIRECTORY_NAME);
|
|
210726
|
+
const configDirectory = claudeConfigDirectory(env);
|
|
210727
|
+
if (configDirectory && isPathWithinRoot({
|
|
210728
|
+
relative,
|
|
210729
|
+
isAbsolute
|
|
210730
|
+
}, configDirectory, root)) return null;
|
|
210731
|
+
return root;
|
|
210732
|
+
}
|
|
210733
|
+
/** Absolute credential directory for one account, or null when either half is missing. */
|
|
210734
|
+
function resolveClaudeCredentialDirectory(input) {
|
|
210735
|
+
if (!input.accountIdentity) return null;
|
|
210736
|
+
const root = claudeCredentialDirectoryRoot(input.env, input.baseDir);
|
|
210737
|
+
if (!root) return null;
|
|
210738
|
+
return join(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
210739
|
+
}
|
|
210740
|
+
/**
|
|
210741
|
+
* Decide whether this turn may point at a per-account credential directory.
|
|
210980
210742
|
*
|
|
210981
|
-
*
|
|
210982
|
-
*
|
|
210983
|
-
*
|
|
210743
|
+
* The cheap checks run first and the capability probe runs last, so a runtime
|
|
210744
|
+
* with no usable credential never spawns the CLI. The probe's error channel is
|
|
210745
|
+
* empty, so nothing here catches.
|
|
210746
|
+
*
|
|
210747
|
+
* A skip is not a fallback onto the default account. With no account the turn
|
|
210748
|
+
* runs on the Environment's own login, unchanged. A thread pinned to an account
|
|
210749
|
+
* whose credential is missing is refused by name under
|
|
210750
|
+
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`, and #3049 turns
|
|
210751
|
+
* `no-usable-credential`, and `capability-not-supported` for a pinned account,
|
|
210752
|
+
* into those refusals. Until #3049 nothing names an account, so `no-account` is
|
|
210753
|
+
* the only skip a runtime sees.
|
|
210754
|
+
*/
|
|
210755
|
+
function decideClaudeCredentialDirectory(input) {
|
|
210756
|
+
return gen(function* () {
|
|
210757
|
+
if (!input.accountIdentity) return skip("no-account");
|
|
210758
|
+
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
|
|
210759
|
+
const directory = resolveClaudeCredentialDirectory(input);
|
|
210760
|
+
if (!directory) return skip("no-root");
|
|
210761
|
+
if (!holdsUsableCredential(directory)) return skip("no-usable-credential");
|
|
210762
|
+
return (yield* input.probeSupport) === "supported" ? set(directory) : skip("capability-not-supported");
|
|
210763
|
+
});
|
|
210764
|
+
}
|
|
210765
|
+
/**
|
|
210766
|
+
* True when `.credentials.json` parses and carries a non-empty access token.
|
|
210767
|
+
*
|
|
210768
|
+
* File presence is not readiness. A refused credential is rewritten in place
|
|
210769
|
+
* with `accessToken: ""`, so a file that exists can still leave the CLI logged
|
|
210770
|
+
* out. Only the token's length is checked. The value is never logged, returned
|
|
210771
|
+
* or kept.
|
|
210984
210772
|
*/
|
|
210985
|
-
function
|
|
210986
|
-
const
|
|
210987
|
-
|
|
210988
|
-
|
|
210989
|
-
|
|
210773
|
+
function holdsUsableCredential(directory) {
|
|
210774
|
+
const file = join(directory, CLAUDE_CREDENTIAL_FILE_NAME);
|
|
210775
|
+
try {
|
|
210776
|
+
if (!statSync(file).isFile()) return false;
|
|
210777
|
+
return hasAccessToken(JSON.parse(readFileSync(file, "utf8")));
|
|
210778
|
+
} catch {
|
|
210779
|
+
return false;
|
|
210780
|
+
}
|
|
210781
|
+
}
|
|
210782
|
+
function hasAccessToken(credential) {
|
|
210783
|
+
if (typeof credential !== "object" || credential === null) return false;
|
|
210784
|
+
const oauth = credential.claudeAiOauth;
|
|
210785
|
+
if (typeof oauth !== "object" || oauth === null) return false;
|
|
210786
|
+
const token = oauth.accessToken;
|
|
210787
|
+
return typeof token === "string" && token.length > 0;
|
|
210788
|
+
}
|
|
210789
|
+
function set(directory) {
|
|
210790
|
+
return {
|
|
210791
|
+
outcome: "set",
|
|
210792
|
+
directory
|
|
210793
|
+
};
|
|
210794
|
+
}
|
|
210795
|
+
function skip(reason) {
|
|
210796
|
+
return {
|
|
210797
|
+
outcome: "skipped",
|
|
210798
|
+
reason
|
|
210799
|
+
};
|
|
210800
|
+
}
|
|
210801
|
+
function claudeConfigDirectory(env) {
|
|
210802
|
+
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
210803
|
+
if (configured) return configured;
|
|
210804
|
+
try {
|
|
210805
|
+
return join(homedir(), DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME);
|
|
210806
|
+
} catch {
|
|
210807
|
+
return null;
|
|
210808
|
+
}
|
|
210990
210809
|
}
|
|
210991
210810
|
//#endregion
|
|
210992
210811
|
//#region src/provider/personalClaudeInVm.ts
|
|
@@ -211056,160 +210875,76 @@ function personalClaudeGenerationHash(env = process.env) {
|
|
|
211056
210875
|
}
|
|
211057
210876
|
}
|
|
211058
210877
|
//#endregion
|
|
211059
|
-
//#region src/provider/
|
|
210878
|
+
//#region src/provider/claudeCredentialContainment.ts
|
|
211060
210879
|
/**
|
|
211061
|
-
*
|
|
211062
|
-
*
|
|
211063
|
-
* Claude Code keeps conversation transcripts under
|
|
211064
|
-
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
211065
|
-
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
211066
|
-
* credential file, so each Provider Account can hold its own login while every
|
|
211067
|
-
* session file stays in one place and `--resume` keeps finding its target. See
|
|
211068
|
-
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
210880
|
+
* Containment for provider credentials inherited by the Claude Agent SDK child.
|
|
211069
210881
|
*
|
|
211070
|
-
*
|
|
211071
|
-
*
|
|
211072
|
-
*
|
|
211073
|
-
*
|
|
211074
|
-
* the gate that keeps the degrade honest. It sets the variable only when a
|
|
211075
|
-
* readable `.credentials.json` is already sitting in the resolved directory,
|
|
211076
|
-
* because a missing or empty directory makes the CLI report logged out and a
|
|
211077
|
-
* live turn would fail. Nothing writes these directories yet, so every runtime
|
|
211078
|
-
* today takes the skip path.
|
|
210882
|
+
* Free of Effect services on purpose: ClaudeAdapter pulls in Effect platform
|
|
210883
|
+
* services, the browser manager, and the server config, so a unit test that
|
|
210884
|
+
* imports it cannot run in a worktree. This rule is cost-critical and must stay
|
|
210885
|
+
* directly testable.
|
|
211079
210886
|
*
|
|
211080
|
-
*
|
|
211081
|
-
*
|
|
211082
|
-
*
|
|
211083
|
-
*
|
|
211084
|
-
* a turn, so it has to stay directly testable.
|
|
211085
|
-
*/
|
|
211086
|
-
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
211087
|
-
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
211088
|
-
/** The one file Claude Code writes into the credential directory. */
|
|
211089
|
-
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
211090
|
-
/**
|
|
211091
|
-
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
211092
|
-
* belong to.
|
|
210887
|
+
* Audit finding S1 (2026-07-25). The Claude Agent SDK reads ANTHROPIC_API_KEY /
|
|
210888
|
+
* ANTHROPIC_AUTH_TOKEN from its own environment, and those TAKE PRECEDENCE over
|
|
210889
|
+
* the user's claude.ai OAuth login — that precedence is exactly what the
|
|
210890
|
+
* adapter relies on to redirect billing at the proxy.
|
|
211093
210891
|
*
|
|
211094
|
-
*
|
|
211095
|
-
*
|
|
211096
|
-
*
|
|
211097
|
-
*
|
|
211098
|
-
*
|
|
211099
|
-
* each time and defeat the point.
|
|
210892
|
+
* So on a plain Claude turn (no endpoint profile, no proxy credential), which
|
|
210893
|
+
* is supposed to run on the personal subscription, an ANTHROPIC_* key inherited
|
|
210894
|
+
* from the server's environment would send the turn straight to
|
|
210895
|
+
* api.anthropic.com on a metered account — never reaching
|
|
210896
|
+
* /api/provider-egress/*, leaving the fail-closed billing gate inert.
|
|
211100
210897
|
*
|
|
211101
|
-
*
|
|
211102
|
-
*
|
|
211103
|
-
*
|
|
211104
|
-
* free to replace it with something better rather than adopt it.
|
|
210898
|
+
* Forge Cloud sprites were only incidentally safe (no Anthropic key present on
|
|
210899
|
+
* the box). This makes containment an enforced property rather than an
|
|
210900
|
+
* assumption about what the deployment environment happens to contain.
|
|
211105
210901
|
*/
|
|
211106
|
-
|
|
211107
|
-
|
|
211108
|
-
const CLAUDE_CREDENTIAL_ROOT_ENV = "FORGE_CLAUDE_CREDENTIAL_ROOT";
|
|
211109
|
-
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
211110
|
-
const FALLBACK_ROOT_DIRECTORY_NAME = ".forge-claude-credentials";
|
|
211111
|
-
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
211112
|
-
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
211113
|
-
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
210902
|
+
/** Credentials the Claude Agent SDK will prefer over the user's OAuth login. */
|
|
210903
|
+
const INHERITABLE_ANTHROPIC_CREDENTIAL_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
211114
210904
|
/**
|
|
211115
|
-
*
|
|
211116
|
-
*
|
|
210905
|
+
* Routing override the SDK would obey instead of the login's default endpoint.
|
|
210906
|
+
* A CMMD cloud runtime exports it to point at the workspace provider-egress
|
|
210907
|
+
* proxy; on a PERSONAL-Claude-in-VM runtime that must be dropped so the turn
|
|
210908
|
+
* spends the on-box ~/.claude subscription (card 1359 Phase 2). Stripped ONLY
|
|
210909
|
+
* when the runtime signals in-VM delivery — otherwise existing behavior (the
|
|
210910
|
+
* inherited base URL survives) is preserved exactly.
|
|
211117
210911
|
*/
|
|
211118
|
-
|
|
211119
|
-
return `account-${createHash("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
211120
|
-
}
|
|
211121
|
-
/** The account this runtime's Claude turns belong to, or null on every runtime today. */
|
|
211122
|
-
function resolveClaudeProviderAccountIdentity(env) {
|
|
211123
|
-
const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
|
|
211124
|
-
if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
|
|
211125
|
-
return identity;
|
|
211126
|
-
}
|
|
210912
|
+
const ANTHROPIC_ENDPOINT_OVERRIDE_KEY = "ANTHROPIC_BASE_URL";
|
|
211127
210913
|
/**
|
|
211128
|
-
*
|
|
211129
|
-
*
|
|
211130
|
-
*
|
|
211131
|
-
*
|
|
210914
|
+
* Remove inherited Anthropic credentials so the child falls back to the user's
|
|
210915
|
+
* personal claude.ai login. Call this ONLY on the path where no base URL
|
|
210916
|
+
* resolved; when the adapter has a profile or proxy credential it sets these
|
|
210917
|
+
* variables deliberately, and that assignment must win.
|
|
210918
|
+
*
|
|
210919
|
+
* `stripEndpointOverride` additionally drops ANTHROPIC_BASE_URL — pass it only on
|
|
210920
|
+
* a personal-Claude-in-VM runtime (gated), never by default, so existing turns
|
|
210921
|
+
* that rely on an inherited base URL are untouched.
|
|
211132
210922
|
*/
|
|
211133
|
-
function
|
|
211134
|
-
const
|
|
211135
|
-
|
|
211136
|
-
|
|
211137
|
-
|
|
211138
|
-
/** Absolute credential directory for one account, or null when either half is missing. */
|
|
211139
|
-
function resolveClaudeCredentialDirectory(input) {
|
|
211140
|
-
if (!input.accountIdentity) return null;
|
|
211141
|
-
const root = claudeCredentialDirectoryRoot(input.env);
|
|
211142
|
-
if (!root) return null;
|
|
211143
|
-
return join(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
210923
|
+
function withoutInheritedAnthropicCredentials(env, options) {
|
|
210924
|
+
const contained = { ...env };
|
|
210925
|
+
for (const key of INHERITABLE_ANTHROPIC_CREDENTIAL_KEYS) delete contained[key];
|
|
210926
|
+
if (options?.stripEndpointOverride) delete contained[ANTHROPIC_ENDPOINT_OVERRIDE_KEY];
|
|
210927
|
+
return contained;
|
|
211144
210928
|
}
|
|
211145
210929
|
/**
|
|
211146
|
-
*
|
|
210930
|
+
* The environment a Claude child gets when it runs on the personal
|
|
210931
|
+
* subscription. Server-only secrets are scrubbed, inherited Anthropic keys are
|
|
210932
|
+
* removed so they cannot outrank the login, and the credential directory is set
|
|
210933
|
+
* only when one was decided. The turn's personal-subscription branch and the
|
|
210934
|
+
* capability probe both build their environment here, so the probe asks about
|
|
210935
|
+
* the setup the turn will run with.
|
|
211147
210936
|
*
|
|
211148
|
-
* The
|
|
211149
|
-
*
|
|
211150
|
-
*
|
|
211151
|
-
|
|
211152
|
-
|
|
211153
|
-
|
|
211154
|
-
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
|
|
211155
|
-
const directory = resolveClaudeCredentialDirectory(input);
|
|
211156
|
-
if (!directory) return skip("no-root");
|
|
211157
|
-
if (!(input.credentialFilePresent ?? hasReadableCredentialFile)(directory)) return skip("no-credential-file");
|
|
211158
|
-
return await probeSupportSafely(input.probeSupport) === "supported" ? {
|
|
211159
|
-
outcome: "set",
|
|
211160
|
-
directory
|
|
211161
|
-
} : skip("capability-not-supported");
|
|
211162
|
-
}
|
|
211163
|
-
/** True when `.credentials.json` is in the directory and readable right now. */
|
|
211164
|
-
function hasReadableCredentialFile(directory) {
|
|
211165
|
-
const file = join(directory, CLAUDE_CREDENTIAL_FILE_NAME);
|
|
211166
|
-
try {
|
|
211167
|
-
if (!statSync(file).isFile()) return false;
|
|
211168
|
-
accessSync(file, constants.R_OK);
|
|
211169
|
-
return true;
|
|
211170
|
-
} catch {
|
|
211171
|
-
return false;
|
|
211172
|
-
}
|
|
211173
|
-
}
|
|
211174
|
-
function skip(reason) {
|
|
210937
|
+
* The endpoint override goes only when `personalClaudeInVmEnabled` says this
|
|
210938
|
+
* runtime is on the personal Claude lane. That check also reads the activation
|
|
210939
|
+
* marker CMMD writes on disk (#3081), so a Sprite switched on in place, which
|
|
210940
|
+
* has the marker and no env var until it restarts, drops the override too.
|
|
210941
|
+
*/
|
|
210942
|
+
function claudeSubscriptionChildEnv(processEnv, credentialDirectory) {
|
|
211175
210943
|
return {
|
|
211176
|
-
|
|
211177
|
-
|
|
210944
|
+
...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: personalClaudeInVmEnabled(processEnv) }),
|
|
210945
|
+
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
211178
210946
|
};
|
|
211179
210947
|
}
|
|
211180
|
-
/** `inconclusive` is treated as `unsupported`, and so is a probe that throws. */
|
|
211181
|
-
async function probeSupportSafely(probeSupport) {
|
|
211182
|
-
try {
|
|
211183
|
-
return await probeSupport();
|
|
211184
|
-
} catch {
|
|
211185
|
-
return "inconclusive";
|
|
211186
|
-
}
|
|
211187
|
-
}
|
|
211188
|
-
function credentialRootCandidate(env) {
|
|
211189
|
-
const explicit = env[CLAUDE_CREDENTIAL_ROOT_ENV]?.trim();
|
|
211190
|
-
if (explicit) return explicit;
|
|
211191
|
-
const forgeHome = env.FORGE_HOME?.trim();
|
|
211192
|
-
if (forgeHome) return join(forgeHome, CREDENTIAL_ROOT_DIRECTORY_NAME);
|
|
211193
|
-
try {
|
|
211194
|
-
return join(homedir(), FALLBACK_ROOT_DIRECTORY_NAME);
|
|
211195
|
-
} catch {
|
|
211196
|
-
return null;
|
|
211197
|
-
}
|
|
211198
|
-
}
|
|
211199
|
-
function claudeConfigDirectory(env) {
|
|
211200
|
-
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
211201
|
-
if (configured) return configured;
|
|
211202
|
-
try {
|
|
211203
|
-
return join(homedir(), DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME);
|
|
211204
|
-
} catch {
|
|
211205
|
-
return null;
|
|
211206
|
-
}
|
|
211207
|
-
}
|
|
211208
|
-
function isInside(candidate, parent) {
|
|
211209
|
-
if (!parent) return false;
|
|
211210
|
-
const step = relative(resolve(parent), resolve(candidate));
|
|
211211
|
-
return step === "" || !step.startsWith("..") && !isAbsolute(step);
|
|
211212
|
-
}
|
|
211213
210948
|
//#endregion
|
|
211214
210949
|
//#region src/provider/claudeCredentialDirectoryProbe.ts
|
|
211215
210950
|
/**
|
|
@@ -211222,6 +210957,11 @@ function isInside(candidate, parent) {
|
|
|
211222
210957
|
* A runtime with no credential at all is logged out either way and cannot
|
|
211223
210958
|
* discriminate, which is `inconclusive`, and callers read that as unsupported.
|
|
211224
210959
|
*
|
|
210960
|
+
* An answer only counts for the binary and environment the turn will use. The
|
|
210961
|
+
* probe spawns the `binaryPath` the turn hands the SDK, and builds its
|
|
210962
|
+
* environment with `claudeSubscriptionChildEnv`, the same builder as the turn's
|
|
210963
|
+
* personal-subscription branch.
|
|
210964
|
+
*
|
|
211225
210965
|
* `claude auth status` is a pure read. It sends no prompt and leaves
|
|
211226
210966
|
* `.credentials.json` byte-identical, so probing spends no subscription usage.
|
|
211227
210967
|
* It prints JSON on stdout and exits 1 when logged out, so the exit code is not
|
|
@@ -211237,38 +210977,62 @@ const VERSION_ARGS = ["--version"];
|
|
|
211237
210977
|
* hung or unkillable process costs a few seconds and an `inconclusive`.
|
|
211238
210978
|
*/
|
|
211239
210979
|
const PROBE_TIMEOUT_MS = 5e3;
|
|
210980
|
+
const PROBE_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
211240
210981
|
const PROBE_DIRECTORY_PREFIX = "forge-claude-capability-";
|
|
211241
210982
|
const VERSION_PATTERN = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/;
|
|
211242
210983
|
function makeClaudeCredentialDirectoryProbe(deps) {
|
|
211243
210984
|
const answers = /* @__PURE__ */ new Map();
|
|
211244
|
-
|
|
210985
|
+
const answer = async () => {
|
|
211245
210986
|
const version = await readVersionSafely(deps);
|
|
211246
210987
|
if (version === null) return runProbe(deps);
|
|
211247
210988
|
const cached = answers.get(version);
|
|
211248
210989
|
if (cached) return cached;
|
|
211249
210990
|
const pending = runProbe(deps);
|
|
211250
210991
|
answers.set(version, pending);
|
|
210992
|
+
const forget = () => {
|
|
210993
|
+
if (answers.get(version) === pending) answers.delete(version);
|
|
210994
|
+
};
|
|
210995
|
+
pending.then((result) => {
|
|
210996
|
+
if (result === "inconclusive") forget();
|
|
210997
|
+
}, forget);
|
|
211251
210998
|
return pending;
|
|
211252
|
-
}
|
|
210999
|
+
};
|
|
211000
|
+
return { support: tryPromise(answer).pipe(orElseSucceed(() => "inconclusive")) };
|
|
211253
211001
|
}
|
|
211254
211002
|
/**
|
|
211255
|
-
* One probe per binary path for the life of the process.
|
|
211256
|
-
*
|
|
211003
|
+
* One probe per binary path for the life of the process. A conclusive answer is
|
|
211004
|
+
* cached per binary version, so the two `auth status` spawns run once per path
|
|
211005
|
+
* and version. `claude --version` still spawns on every call, which is how an
|
|
211006
|
+
* in-place CLI upgrade gets a fresh answer.
|
|
211257
211007
|
*/
|
|
211258
211008
|
const probesByBinaryPath = /* @__PURE__ */ new Map();
|
|
211009
|
+
const INCONCLUSIVE_PROBE = { support: succeed("inconclusive") };
|
|
211010
|
+
/**
|
|
211011
|
+
* The probe for the binary a turn with this `binaryPath` setting runs.
|
|
211012
|
+
*
|
|
211013
|
+
* `buildClaudeQueryOptions` hands the setting to the SDK as
|
|
211014
|
+
* `pathToClaudeCodeExecutable`, and the SDK spawns it as given, so the default
|
|
211015
|
+
* `claude` goes through PATH for the probe exactly as for the turn. The SDK
|
|
211016
|
+
* picks a bundled build of its own only when the path is empty, and the
|
|
211017
|
+
* settings schema turns an empty value into `claude`. Should one arrive empty
|
|
211018
|
+
* anyway, the answer is `inconclusive` rather than an answer about another
|
|
211019
|
+
* binary.
|
|
211020
|
+
*/
|
|
211259
211021
|
function claudeCredentialDirectoryProbeFor(binaryPath) {
|
|
211260
|
-
|
|
211261
|
-
const existing = probesByBinaryPath.get(
|
|
211022
|
+
if (!binaryPath) return INCONCLUSIVE_PROBE;
|
|
211023
|
+
const existing = probesByBinaryPath.get(binaryPath);
|
|
211262
211024
|
if (existing) return existing;
|
|
211263
|
-
const probe = makeClaudeCredentialDirectoryProbe(claudeCredentialDirectoryProbeDeps(
|
|
211264
|
-
probesByBinaryPath.set(
|
|
211025
|
+
const probe = makeClaudeCredentialDirectoryProbe(claudeCredentialDirectoryProbeDeps(binaryPath));
|
|
211026
|
+
probesByBinaryPath.set(binaryPath, probe);
|
|
211265
211027
|
return probe;
|
|
211266
211028
|
}
|
|
211267
|
-
|
|
211029
|
+
const spawnClaude = (file, args, options) => execFileAsync$3(file, [...args], { ...options });
|
|
211030
|
+
function claudeCredentialDirectoryProbeDeps(binaryPath, options = {}) {
|
|
211031
|
+
const run = (args, credentialDirectory) => runClaudeCommand$1(options.execFile ?? spawnClaude, binaryPath, args, claudeSubscriptionChildEnv(options.baseEnv ?? process.env, credentialDirectory));
|
|
211268
211032
|
return {
|
|
211269
|
-
runAuthStatus: ({ credentialDirectory }) =>
|
|
211033
|
+
runAuthStatus: ({ credentialDirectory }) => run(AUTH_STATUS_ARGS, credentialDirectory),
|
|
211270
211034
|
readBinaryVersion: async () => {
|
|
211271
|
-
const { stdout } = await
|
|
211035
|
+
const { stdout } = await run(VERSION_ARGS, null);
|
|
211272
211036
|
return VERSION_PATTERN.exec(stdout)?.[0] ?? null;
|
|
211273
211037
|
},
|
|
211274
211038
|
makeProbeDirectory: () => mkdtemp(join(tmpdir(), PROBE_DIRECTORY_PREFIX)),
|
|
@@ -211278,6 +211042,7 @@ function claudeCredentialDirectoryProbeDeps(binaryPath) {
|
|
|
211278
211042
|
})
|
|
211279
211043
|
};
|
|
211280
211044
|
}
|
|
211045
|
+
/** Rejects when the probe could not tell. `support` turns that into `inconclusive`. */
|
|
211281
211046
|
async function runProbe(deps) {
|
|
211282
211047
|
let probeDirectory = null;
|
|
211283
211048
|
try {
|
|
@@ -211287,8 +211052,6 @@ async function runProbe(deps) {
|
|
|
211287
211052
|
const redirected = parseLoggedIn(await runBounded(deps, () => deps.runAuthStatus({ credentialDirectory: directory })));
|
|
211288
211053
|
if (redirected === false) return "supported";
|
|
211289
211054
|
return redirected === true ? "unsupported" : "inconclusive";
|
|
211290
|
-
} catch {
|
|
211291
|
-
return "inconclusive";
|
|
211292
211055
|
} finally {
|
|
211293
211056
|
if (probeDirectory !== null) await deps.removeProbeDirectory(probeDirectory).catch(() => void 0);
|
|
211294
211057
|
}
|
|
@@ -211310,15 +211073,15 @@ async function runBounded(deps, operation) {
|
|
|
211310
211073
|
let timer;
|
|
211311
211074
|
try {
|
|
211312
211075
|
return await Promise.race([operation(), new Promise((_resolve, reject) => {
|
|
211313
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("claude
|
|
211076
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("claude probe call timed out")), timeoutMs);
|
|
211314
211077
|
})]);
|
|
211315
211078
|
} finally {
|
|
211316
211079
|
if (timer !== void 0) clearTimeout(timer);
|
|
211317
211080
|
}
|
|
211318
211081
|
}
|
|
211319
211082
|
/** The `loggedIn` field, or null when the output does not carry one. */
|
|
211320
|
-
function parseLoggedIn(
|
|
211321
|
-
const parsed = parseJsonObject$1(
|
|
211083
|
+
function parseLoggedIn(output) {
|
|
211084
|
+
const parsed = parseJsonObject$1(output.stdout);
|
|
211322
211085
|
return typeof parsed?.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
211323
211086
|
}
|
|
211324
211087
|
function parseJsonObject$1(output) {
|
|
@@ -211333,27 +211096,18 @@ function parseJsonObject$1(output) {
|
|
|
211333
211096
|
return null;
|
|
211334
211097
|
}
|
|
211335
211098
|
}
|
|
211336
|
-
async function runClaudeCommand$1(binaryPath, args,
|
|
211099
|
+
async function runClaudeCommand$1(execFileImpl, binaryPath, args, env) {
|
|
211337
211100
|
try {
|
|
211338
|
-
const { stdout } = await
|
|
211339
|
-
env
|
|
211340
|
-
|
|
211341
|
-
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
211342
|
-
},
|
|
211343
|
-
maxBuffer: 1024 * 1024,
|
|
211101
|
+
const { stdout } = await execFileImpl(binaryPath, args, {
|
|
211102
|
+
env,
|
|
211103
|
+
maxBuffer: PROBE_OUTPUT_LIMIT_BYTES,
|
|
211344
211104
|
timeout: PROBE_TIMEOUT_MS
|
|
211345
211105
|
});
|
|
211346
|
-
return {
|
|
211347
|
-
stdout,
|
|
211348
|
-
exitCode: 0
|
|
211349
|
-
};
|
|
211106
|
+
return { stdout };
|
|
211350
211107
|
} catch (cause) {
|
|
211351
211108
|
const failure = cause;
|
|
211352
211109
|
if (typeof failure.stdout !== "string") throw cause;
|
|
211353
|
-
return {
|
|
211354
|
-
stdout: failure.stdout,
|
|
211355
|
-
exitCode: typeof failure.code === "number" ? failure.code : null
|
|
211356
|
-
};
|
|
211110
|
+
return { stdout: failure.stdout };
|
|
211357
211111
|
}
|
|
211358
211112
|
}
|
|
211359
211113
|
//#endregion
|
|
@@ -211368,12 +211122,12 @@ const makeManagedServerProvider = fn$1("makeManagedServerProvider")(function* (i
|
|
|
211368
211122
|
const forceRefresh = options?.forceRefresh === true;
|
|
211369
211123
|
const previousSettings = yield* get$6(settingsRef);
|
|
211370
211124
|
if (!forceRefresh && !input.haveSettingsChanged(previousSettings, nextSettings)) {
|
|
211371
|
-
yield* set$
|
|
211125
|
+
yield* set$3(settingsRef, nextSettings);
|
|
211372
211126
|
return yield* get$6(snapshotRef);
|
|
211373
211127
|
}
|
|
211374
211128
|
const nextSnapshot = yield* input.checkProvider;
|
|
211375
|
-
yield* set$
|
|
211376
|
-
yield* set$
|
|
211129
|
+
yield* set$3(settingsRef, nextSettings);
|
|
211130
|
+
yield* set$3(snapshotRef, nextSnapshot);
|
|
211377
211131
|
yield* publish(changesPubSub, nextSnapshot);
|
|
211378
211132
|
return nextSnapshot;
|
|
211379
211133
|
});
|
|
@@ -211393,7 +211147,7 @@ const makeManagedServerProvider = fn$1("makeManagedServerProvider")(function* (i
|
|
|
211393
211147
|
failureCount: failureCount + 1,
|
|
211394
211148
|
nextDelayMs: Math.min(baseMs * Math.pow(2, failureCount + 1), 3e5)
|
|
211395
211149
|
});
|
|
211396
|
-
} else yield* set$
|
|
211150
|
+
} else yield* set$3(refreshFailureCount, 0);
|
|
211397
211151
|
})).pipe(forkScoped);
|
|
211398
211152
|
return {
|
|
211399
211153
|
getSnapshot: input.getSettings.pipe(flatMap(applySnapshot), tapError(logError), orDie),
|
|
@@ -212867,10 +212621,12 @@ function claudeForgeAttributionPrompt() {
|
|
|
212867
212621
|
* `credentialDirectory` points this turn at one Provider Account's own
|
|
212868
212622
|
* `.credentials.json` without moving the transcripts, which is what lets an
|
|
212869
212623
|
* account switch keep `--resume` working
|
|
212870
|
-
* (`docs/adr/claude-accounts-split-credentials-not-sessions.md`). It
|
|
212871
|
-
*
|
|
212872
|
-
*
|
|
212873
|
-
*
|
|
212624
|
+
* (`docs/adr/claude-accounts-split-credentials-not-sessions.md`). It stays null
|
|
212625
|
+
* until #3049 gives the directories a writer, keyed on the account identity on
|
|
212626
|
+
* the delivery receipt CMMD signs. `decideClaudeCredentialDirectory` only hands
|
|
212627
|
+
* back a directory that already holds a usable credential, and while none
|
|
212628
|
+
* exists the environment is byte-for-byte what it would be without this
|
|
212629
|
+
* parameter.
|
|
212874
212630
|
*
|
|
212875
212631
|
* It applies to the personal-subscription branch below and to nothing else. On
|
|
212876
212632
|
* the profile / proxy branch the credential file is not what authenticates the
|
|
@@ -212880,10 +212636,7 @@ function claudeForgeAttributionPrompt() {
|
|
|
212880
212636
|
function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory) {
|
|
212881
212637
|
const baseEnv = scrubForgeProviderChildEnvSecrets(baseProcessEnv);
|
|
212882
212638
|
const effectiveBaseUrl = resolveClaudeQueryBaseUrl(endpointProfile, claudeSettings, providerProxyCredential);
|
|
212883
|
-
if (!effectiveBaseUrl) return
|
|
212884
|
-
...withoutInheritedAnthropicCredentials(baseEnv, { stripEndpointOverride: personalClaudeInVmEnabled(baseProcessEnv) }),
|
|
212885
|
-
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
212886
|
-
};
|
|
212639
|
+
if (!effectiveBaseUrl) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory);
|
|
212887
212640
|
const effectiveApiKey = resolveClaudeQueryApiKey(endpointProfile, claudeSettings, providerProxyCredential?.token ?? null);
|
|
212888
212641
|
return {
|
|
212889
212642
|
...baseEnv,
|
|
@@ -215151,11 +214904,17 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
|
|
|
215151
214904
|
})));
|
|
215152
214905
|
const claudeSettings = serverSettings.providers.claudeAgent;
|
|
215153
214906
|
const claudeBinaryPath = claudeSettings.binaryPath;
|
|
215154
|
-
const
|
|
214907
|
+
const credentialDirectoryDecision = yield* decideClaudeCredentialDirectory({
|
|
215155
214908
|
env: process.env,
|
|
214909
|
+
baseDir: serverConfig.baseDir,
|
|
215156
214910
|
accountIdentity: resolveClaudeProviderAccountIdentity(process.env),
|
|
215157
|
-
probeSupport:
|
|
215158
|
-
})
|
|
214911
|
+
probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support
|
|
214912
|
+
});
|
|
214913
|
+
if (credentialDirectoryDecision.outcome === "skipped") {
|
|
214914
|
+
const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
|
|
214915
|
+
yield* credentialDirectoryDecision.reason === "no-account" ? logDebug(skipLine) : logWarning$1(skipLine);
|
|
214916
|
+
}
|
|
214917
|
+
const claudeCredentialDirectory = credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null;
|
|
215159
214918
|
const modelConfig = resolveClaudeSessionModelConfig(input.modelSelection, claudeSettings.endpointProfiles);
|
|
215160
214919
|
const providerProxyCredential = yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
|
|
215161
214920
|
const permissionMode = permissionModeForRuntimeMode(input.runtimeMode);
|
|
@@ -215217,7 +214976,7 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
|
|
|
215217
214976
|
effectiveApiModelId: modelConfig.effectiveApiModelId,
|
|
215218
214977
|
effectiveEffort: modelConfig.effectiveEffort
|
|
215219
214978
|
});
|
|
215220
|
-
yield* set$
|
|
214979
|
+
yield* set$3(contextRef, context$4);
|
|
215221
214980
|
sessions.set(threadId, context$4);
|
|
215222
214981
|
yield* emitClaudeSessionStartupEvents({
|
|
215223
214982
|
threadId,
|
|
@@ -220098,7 +219857,7 @@ const makeAcpSessionRuntime = (options) => gen(function* () {
|
|
|
220098
219857
|
}
|
|
220099
219858
|
});
|
|
220100
219859
|
});
|
|
220101
|
-
const updateConfigOptions = (response) => set$
|
|
219860
|
+
const updateConfigOptions = (response) => set$3(configOptionsRef, sessionConfigOptionsFromSetup(response));
|
|
220102
219861
|
const updateCurrentModeId = (modeId) => update$4(modeStateRef, (current) => current ? {
|
|
220103
219862
|
...current,
|
|
220104
219863
|
currentModeId: modeId
|
|
@@ -220157,8 +219916,8 @@ const makeAcpSessionRuntime = (options) => gen(function* () {
|
|
|
220157
219916
|
sessionId = created.sessionId;
|
|
220158
219917
|
sessionSetupResult = created;
|
|
220159
219918
|
}
|
|
220160
|
-
yield* set$
|
|
220161
|
-
yield* set$
|
|
219919
|
+
yield* set$3(modeStateRef, parseSessionModeState(sessionSetupResult));
|
|
219920
|
+
yield* set$3(configOptionsRef, sessionConfigOptionsFromSetup(sessionSetupResult));
|
|
220162
219921
|
return {
|
|
220163
219922
|
sessionId,
|
|
220164
219923
|
initializeResult,
|
|
@@ -220352,10 +220111,10 @@ const makeStartCoordinator = (startStateRef, startOnce) => gen(function* () {
|
|
|
220352
220111
|
switch (state._tag) {
|
|
220353
220112
|
case "Started": return [succeed(state.result), state];
|
|
220354
220113
|
case "Starting": return [_await(state.deferred), state];
|
|
220355
|
-
case "NotStarted": return [startOnce.pipe(tap((result) => set$
|
|
220114
|
+
case "NotStarted": return [startOnce.pipe(tap((result) => set$3(startStateRef, {
|
|
220356
220115
|
_tag: "Started",
|
|
220357
220116
|
result
|
|
220358
|
-
}).pipe(andThen(succeed$3(deferred, result)))), onError$1((cause) => failCause$3(deferred, cause).pipe(andThen(set$
|
|
220117
|
+
}).pipe(andThen(succeed$3(deferred, result)))), onError$1((cause) => failCause$3(deferred, cause).pipe(andThen(set$3(startStateRef, { _tag: "NotStarted" }))))), {
|
|
220359
220118
|
_tag: "Starting",
|
|
220360
220119
|
deferred
|
|
220361
220120
|
}];
|
|
@@ -284032,7 +283791,7 @@ const GitManagerLive = effect(GitManager, fn$1("makeGitManager")(function* () {
|
|
|
284032
283791
|
let preResolvedCommitSuggestion = void 0;
|
|
284033
283792
|
const modelSelection = yield* serverSettingsService.getSettings.pipe(map$3((settings) => settings.textGenerationModelSelection), mapError((cause) => gitManagerError("runStackedAction", "Failed to get server settings.", cause)));
|
|
284034
283793
|
if (input.featureBranch) {
|
|
284035
|
-
yield* set$
|
|
283794
|
+
yield* set$3(currentPhase, some("branch"));
|
|
284036
283795
|
yield* progress.emit({
|
|
284037
283796
|
kind: "phase_started",
|
|
284038
283797
|
phase: "branch",
|
|
@@ -284045,12 +283804,12 @@ const GitManagerLive = effect(GitManager, fn$1("makeGitManager")(function* () {
|
|
|
284045
283804
|
} else branchStep = { status: "skipped_not_requested" };
|
|
284046
283805
|
const currentBranch = branchStep.name ?? initialStatus.branch;
|
|
284047
283806
|
const commitAction = isCommitAction(input.action) ? input.action : null;
|
|
284048
|
-
const commit = commitAction ? yield* set$
|
|
283807
|
+
const commit = commitAction ? yield* set$3(currentPhase, some("commit")).pipe(flatMap(() => runCommitStep(modelSelection, input.cwd, commitAction, currentBranch, commitMessageForStep, preResolvedCommitSuggestion, input.filePaths, options?.progressReporter, progress.actionId, input.threadId))) : { status: "skipped_not_requested" };
|
|
284049
283808
|
const push = wantsPush ? yield* progress.emit({
|
|
284050
283809
|
kind: "phase_started",
|
|
284051
283810
|
phase: "push",
|
|
284052
283811
|
label: "Pushing..."
|
|
284053
|
-
}).pipe(tap(() => set$
|
|
283812
|
+
}).pipe(tap(() => set$3(currentPhase, some("push"))), flatMap(() => {
|
|
284054
283813
|
const emit = (event) => progress.emit(event);
|
|
284055
283814
|
const hookProgress = createHookProgressEmitter(emit);
|
|
284056
283815
|
return gitCore.pushCurrentBranch(input.cwd, currentBranch, { progress: hookProgress.progress }).pipe(tap(() => {
|
|
@@ -284068,11 +283827,11 @@ const GitManagerLive = effect(GitManager, fn$1("makeGitManager")(function* () {
|
|
|
284068
283827
|
kind: "phase_started",
|
|
284069
283828
|
phase: "pr",
|
|
284070
283829
|
label: "Preparing PR merge..."
|
|
284071
|
-
}).pipe(tap(() => set$
|
|
283830
|
+
}).pipe(tap(() => set$3(currentPhase, some("pr"))), flatMap(() => runMergePrStep(input.cwd, currentBranch, progress.emit))) : wantsPr ? yield* progress.emit({
|
|
284072
283831
|
kind: "phase_started",
|
|
284073
283832
|
phase: "pr",
|
|
284074
283833
|
label: "Preparing PR..."
|
|
284075
|
-
}).pipe(tap(() => set$
|
|
283834
|
+
}).pipe(tap(() => set$3(currentPhase, some("pr"))), flatMap(() => runPrStep(modelSelection, input.cwd, currentBranch, progress.emit, input.threadId))) : { status: "skipped_not_requested" };
|
|
284076
283835
|
const toast = yield* buildCompletionToast(input.cwd, {
|
|
284077
283836
|
action: input.action,
|
|
284078
283837
|
branch: branchStep,
|
|
@@ -287344,7 +287103,8 @@ const makeRuntimeDrainReporter = gen(function* () {
|
|
|
287344
287103
|
...config,
|
|
287345
287104
|
activeTurnCount: snapshot.activeTurnCount,
|
|
287346
287105
|
activeSessionCount: snapshot.activeSessionCount,
|
|
287347
|
-
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {}
|
|
287106
|
+
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
287107
|
+
forgeServerVersion: installedForgeServerVersion
|
|
287348
287108
|
});
|
|
287349
287109
|
});
|
|
287350
287110
|
const applyControlPlaneState = (runtime) => drainState.applyControlPlaneState({
|
|
@@ -287928,7 +287688,7 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, fn$
|
|
|
287928
287688
|
createdAt: ""
|
|
287929
287689
|
})
|
|
287930
287690
|
});
|
|
287931
|
-
const rememberAssistantMessageId = (threadId, turnId, messageId) => getOption(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId)).pipe(flatMap((existingIds) => set$
|
|
287691
|
+
const rememberAssistantMessageId = (threadId, turnId, messageId) => getOption(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId)).pipe(flatMap((existingIds) => set$4(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId), match(existingIds, {
|
|
287932
287692
|
onNone: () => new Set([messageId]),
|
|
287933
287693
|
onSome: (ids) => {
|
|
287934
287694
|
const nextIds = new Set(ids);
|
|
@@ -287942,7 +287702,7 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, fn$
|
|
|
287942
287702
|
const nextIds = new Set(ids);
|
|
287943
287703
|
nextIds.delete(messageId);
|
|
287944
287704
|
if (nextIds.size === 0) return invalidate(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId));
|
|
287945
|
-
return set$
|
|
287705
|
+
return set$4(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId), nextIds);
|
|
287946
287706
|
}
|
|
287947
287707
|
})));
|
|
287948
287708
|
const getAssistantMessageIdsForTurn = (threadId, turnId) => getOption(turnMessageIdsByTurnKey, providerTurnKey(threadId, turnId)).pipe(map$3((existingIds) => getOrElse(existingIds, () => /* @__PURE__ */ new Set())));
|
|
@@ -287953,7 +287713,7 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, fn$
|
|
|
287953
287713
|
onSome: (text) => `${text}${delta}`
|
|
287954
287714
|
});
|
|
287955
287715
|
if (nextText.length <= MAX_BUFFERED_ASSISTANT_CHARS) {
|
|
287956
|
-
yield* set$
|
|
287716
|
+
yield* set$4(bufferedAssistantTextByMessageId, messageId, nextText);
|
|
287957
287717
|
return "";
|
|
287958
287718
|
}
|
|
287959
287719
|
yield* invalidate(bufferedAssistantTextByMessageId, messageId);
|
|
@@ -287963,7 +287723,7 @@ const ProviderRuntimeIngestionLive = effect(ProviderRuntimeIngestionService, fn$
|
|
|
287963
287723
|
const clearBufferedAssistantText = (messageId) => invalidate(bufferedAssistantTextByMessageId, messageId);
|
|
287964
287724
|
const appendBufferedProposedPlan = (planId, delta, createdAt) => getOption(bufferedProposedPlanById, planId).pipe(flatMap((existingEntry) => {
|
|
287965
287725
|
const existing = getOrUndefined(existingEntry);
|
|
287966
|
-
return set$
|
|
287726
|
+
return set$4(bufferedProposedPlanById, planId, {
|
|
287967
287727
|
text: `${existing?.text ?? ""}${delta}`,
|
|
287968
287728
|
createdAt: existing?.createdAt && existing.createdAt.length > 0 ? existing.createdAt : createdAt
|
|
287969
287729
|
});
|
|
@@ -288841,7 +288601,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
|
|
|
288841
288601
|
timeToLive: HANDLED_TURN_START_KEY_TTL,
|
|
288842
288602
|
lookup: () => succeed(true)
|
|
288843
288603
|
});
|
|
288844
|
-
const hasHandledTurnStartRecently = (key) => getOption(handledTurnStartKeys, key).pipe(flatMap((cached) => set$
|
|
288604
|
+
const hasHandledTurnStartRecently = (key) => getOption(handledTurnStartKeys, key).pipe(flatMap((cached) => set$4(handledTurnStartKeys, key, true).pipe(as$3(isSome(cached)))));
|
|
288845
288605
|
const threadModelSelections = /* @__PURE__ */ new Map();
|
|
288846
288606
|
/**
|
|
288847
288607
|
* The providerAccountId each LIVE provider session was actually started with.
|
|
@@ -292684,6 +292444,382 @@ const WorkspaceFileSystemLive = effect(WorkspaceFileSystem, gen(function* () {
|
|
|
292684
292444
|
}) };
|
|
292685
292445
|
}));
|
|
292686
292446
|
//#endregion
|
|
292447
|
+
//#region src/workspace/Layers/WorkspaceBulkTransfer.ts
|
|
292448
|
+
const TRANSFER_TTL_MS = 1440 * 60 * 1e3;
|
|
292449
|
+
const TRANSFER_LOCK_STRIPE_COUNT = 64;
|
|
292450
|
+
function transferError(input) {
|
|
292451
|
+
return new BulkTransferError({
|
|
292452
|
+
code: input.code,
|
|
292453
|
+
message: input.message,
|
|
292454
|
+
transferId: input.transferId ?? null,
|
|
292455
|
+
retryable: input.retryable ?? false,
|
|
292456
|
+
...input.cause === void 0 ? {} : { cause: input.cause }
|
|
292457
|
+
});
|
|
292458
|
+
}
|
|
292459
|
+
function mapFailure(operation, transferId) {
|
|
292460
|
+
return (cause) => is$1(BulkTransferError)(cause) ? cause : transferError({
|
|
292461
|
+
code: "io_error",
|
|
292462
|
+
message: `Workspace bulk transfer failed during ${operation}.`,
|
|
292463
|
+
...transferId === void 0 ? {} : { transferId },
|
|
292464
|
+
retryable: true,
|
|
292465
|
+
cause
|
|
292466
|
+
});
|
|
292467
|
+
}
|
|
292468
|
+
function transferLockStripe(transferId) {
|
|
292469
|
+
let hash = 2166136261;
|
|
292470
|
+
for (let index = 0; index < transferId.length; index += 1) hash = Math.imul(hash ^ transferId.charCodeAt(index), 16777619);
|
|
292471
|
+
return (hash >>> 0) % TRANSFER_LOCK_STRIPE_COUNT;
|
|
292472
|
+
}
|
|
292473
|
+
const makeWorkspaceBulkTransfer = (options = {}) => gen(function* () {
|
|
292474
|
+
const fileSystem = yield* FileSystem;
|
|
292475
|
+
const path = yield* Path$2;
|
|
292476
|
+
const sql = yield* SqlClient;
|
|
292477
|
+
const workspacePaths = yield* WorkspacePaths;
|
|
292478
|
+
const workspaceEntries = yield* WorkspaceEntries;
|
|
292479
|
+
const fault = options.fault ?? (() => void_$1);
|
|
292480
|
+
const transferLocks = yield* forEach(Array.from({ length: TRANSFER_LOCK_STRIPE_COUNT }), () => make$43(1));
|
|
292481
|
+
const withTransferLock = (transferId, effect) => transferLocks[transferLockStripe(transferId)].withPermit(effect);
|
|
292482
|
+
const findTransfer = (transferId) => sql`
|
|
292483
|
+
SELECT transfer_id AS "transferId", workspace_root AS "workspaceRoot",
|
|
292484
|
+
relative_path AS "relativePath", target_path AS "targetPath", temp_path AS "tempPath",
|
|
292485
|
+
total_bytes AS "totalBytes", whole_sha256 AS "wholeSha256",
|
|
292486
|
+
next_offset AS "nextOffset", state, expires_at AS "expiresAt"
|
|
292487
|
+
FROM workspace_bulk_transfers WHERE transfer_id = ${transferId} LIMIT 1
|
|
292488
|
+
`.pipe(map$3((rows) => rows[0] ?? null));
|
|
292489
|
+
const requireTransfer = (transferId) => findTransfer(transferId).pipe(flatMap((row) => row === null ? fail(transferError({
|
|
292490
|
+
code: "transfer_not_found",
|
|
292491
|
+
message: "Bulk transfer does not exist.",
|
|
292492
|
+
transferId
|
|
292493
|
+
})) : succeed(row)));
|
|
292494
|
+
const requireOpenTransfer = (transferId) => requireTransfer(transferId).pipe(flatMap((row) => {
|
|
292495
|
+
if (row.state === "cancelled") return fail(transferError({
|
|
292496
|
+
code: "transfer_cancelled",
|
|
292497
|
+
message: "Bulk transfer was cancelled.",
|
|
292498
|
+
transferId
|
|
292499
|
+
}));
|
|
292500
|
+
if (row.state === "expired" || Date.parse(row.expiresAt) <= Date.now()) return fail(transferError({
|
|
292501
|
+
code: "transfer_expired",
|
|
292502
|
+
message: "Bulk transfer expired.",
|
|
292503
|
+
transferId
|
|
292504
|
+
}));
|
|
292505
|
+
return succeed(row);
|
|
292506
|
+
}));
|
|
292507
|
+
const resolveTarget = fn$1("WorkspaceBulkTransfer.resolveTarget")(function* (input) {
|
|
292508
|
+
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd);
|
|
292509
|
+
const resolved = yield* workspacePaths.resolveRelativePathWithinRoot({
|
|
292510
|
+
workspaceRoot,
|
|
292511
|
+
relativePath: input.relativePath
|
|
292512
|
+
});
|
|
292513
|
+
const canonicalRoot = yield* fileSystem.realPath(workspaceRoot);
|
|
292514
|
+
const parentSegments = resolved.relativePath.split("/").slice(0, -1);
|
|
292515
|
+
let canonicalParent = canonicalRoot;
|
|
292516
|
+
for (const segment of parentSegments) {
|
|
292517
|
+
const candidate = path.join(canonicalParent, segment);
|
|
292518
|
+
const canonicalCandidate = yield* fileSystem.realPath(candidate).pipe(catch_(() => input.createParent ? fileSystem.makeDirectory(candidate).pipe(flatMap(() => fileSystem.realPath(candidate))) : fileSystem.realPath(candidate)));
|
|
292519
|
+
if (!isPathWithinRoot(path, canonicalRoot, canonicalCandidate)) return yield* transferError({
|
|
292520
|
+
code: "symlink_escape",
|
|
292521
|
+
message: "Workspace file path resolves outside the project root."
|
|
292522
|
+
});
|
|
292523
|
+
canonicalParent = canonicalCandidate;
|
|
292524
|
+
}
|
|
292525
|
+
const targetPath = path.join(canonicalParent, path.basename(resolved.relativePath));
|
|
292526
|
+
const targetRealPath = yield* fileSystem.realPath(targetPath).pipe(catch_(() => succeed(null)));
|
|
292527
|
+
if (targetRealPath !== null && !isPathWithinRoot(path, canonicalRoot, targetRealPath)) return yield* transferError({
|
|
292528
|
+
code: "symlink_escape",
|
|
292529
|
+
message: "Workspace file target resolves outside the project root."
|
|
292530
|
+
});
|
|
292531
|
+
return {
|
|
292532
|
+
workspaceRoot: canonicalRoot,
|
|
292533
|
+
relativePath: resolved.relativePath,
|
|
292534
|
+
targetPath,
|
|
292535
|
+
canonicalParent
|
|
292536
|
+
};
|
|
292537
|
+
});
|
|
292538
|
+
const readFile = (input) => unwrap$1(gen(function* () {
|
|
292539
|
+
const target = yield* resolveTarget({
|
|
292540
|
+
...input,
|
|
292541
|
+
createParent: false
|
|
292542
|
+
});
|
|
292543
|
+
const info = yield* fileSystem.stat(target.targetPath);
|
|
292544
|
+
const maxBytes = input.maxBytes ?? 16777216;
|
|
292545
|
+
if (Number(info.size) > maxBytes) return yield* transferError({
|
|
292546
|
+
code: "size_limit_exceeded",
|
|
292547
|
+
message: `Workspace file exceeds the ${maxBytes}-byte read limit.`
|
|
292548
|
+
});
|
|
292549
|
+
const bytes = yield* fileSystem.readFile(target.targetPath);
|
|
292550
|
+
const transfer = yield* promise(() => chunkBulkTransfer(bytes));
|
|
292551
|
+
const transferId = transfer.chunks[0]?.transferId ?? crypto.randomUUID().replaceAll("-", "_");
|
|
292552
|
+
return fromIterable$1([
|
|
292553
|
+
{
|
|
292554
|
+
kind: "started",
|
|
292555
|
+
transferId,
|
|
292556
|
+
totalBytes: transfer.totalBytes,
|
|
292557
|
+
sha256: transfer.sha256
|
|
292558
|
+
},
|
|
292559
|
+
...transfer.chunks.map((chunk) => ({
|
|
292560
|
+
...chunk,
|
|
292561
|
+
transferId
|
|
292562
|
+
})),
|
|
292563
|
+
{
|
|
292564
|
+
kind: "completed",
|
|
292565
|
+
transferId,
|
|
292566
|
+
totalBytes: transfer.totalBytes,
|
|
292567
|
+
sha256: transfer.sha256
|
|
292568
|
+
}
|
|
292569
|
+
]);
|
|
292570
|
+
}).pipe(mapError(mapFailure("read file"))));
|
|
292571
|
+
const resumeWrite = fn$1("WorkspaceBulkTransfer.resumeWrite")(function* (input, existing) {
|
|
292572
|
+
const target = yield* resolveTarget({
|
|
292573
|
+
...input,
|
|
292574
|
+
createParent: true
|
|
292575
|
+
});
|
|
292576
|
+
if (!(existing.workspaceRoot === target.workspaceRoot && existing.relativePath === target.relativePath && existing.totalBytes === input.totalBytes && existing.wholeSha256 === input.sha256)) return yield* transferError({
|
|
292577
|
+
code: "transfer_identity_conflict",
|
|
292578
|
+
message: "Bulk transfer ID is already bound to different content or a different path.",
|
|
292579
|
+
transferId: input.transferId
|
|
292580
|
+
});
|
|
292581
|
+
if (existing.state === "cancelled") return yield* transferError({
|
|
292582
|
+
code: "transfer_cancelled",
|
|
292583
|
+
message: "Bulk transfer was cancelled.",
|
|
292584
|
+
transferId: input.transferId
|
|
292585
|
+
});
|
|
292586
|
+
if (existing.state === "expired" || Date.parse(existing.expiresAt) <= Date.now()) return yield* transferError({
|
|
292587
|
+
code: "transfer_expired",
|
|
292588
|
+
message: "Bulk transfer expired.",
|
|
292589
|
+
transferId: input.transferId
|
|
292590
|
+
});
|
|
292591
|
+
return {
|
|
292592
|
+
transferId: input.transferId,
|
|
292593
|
+
nextOffset: existing.nextOffset,
|
|
292594
|
+
state: existing.state === "completed" ? "completed" : "open"
|
|
292595
|
+
};
|
|
292596
|
+
});
|
|
292597
|
+
const createWrite = fn$1("WorkspaceBulkTransfer.createWrite")(function* (input) {
|
|
292598
|
+
const target = yield* resolveTarget({
|
|
292599
|
+
...input,
|
|
292600
|
+
createParent: true
|
|
292601
|
+
}).pipe(mapError((cause) => is$1(WorkspacePathOutsideRootError)(cause) ? transferError({
|
|
292602
|
+
code: "path_outside_workspace",
|
|
292603
|
+
message: "Workspace file path must stay within the project root.",
|
|
292604
|
+
transferId: input.transferId,
|
|
292605
|
+
cause
|
|
292606
|
+
}) : cause));
|
|
292607
|
+
const tempPath = path.join(target.canonicalParent, `.forge-transfer-${input.transferId}.part`);
|
|
292608
|
+
yield* scoped(fileSystem.open(tempPath, {
|
|
292609
|
+
flag: "wx",
|
|
292610
|
+
mode: 384
|
|
292611
|
+
}).pipe(flatMap((file) => file.sync), catch_(() => fileSystem.stat(tempPath).pipe(flatMap((stat) => Number(stat.size) === 0 ? void_$1 : fail(transferError({
|
|
292612
|
+
code: "transfer_identity_conflict",
|
|
292613
|
+
message: "Bulk transfer temporary path already contains data.",
|
|
292614
|
+
transferId: input.transferId
|
|
292615
|
+
})))))));
|
|
292616
|
+
const now = /* @__PURE__ */ new Date();
|
|
292617
|
+
yield* sql`
|
|
292618
|
+
INSERT INTO workspace_bulk_transfers (
|
|
292619
|
+
transfer_id, workspace_root, relative_path, target_path, temp_path, total_bytes,
|
|
292620
|
+
whole_sha256, next_offset, state, expires_at, created_at, updated_at
|
|
292621
|
+
) VALUES (
|
|
292622
|
+
${input.transferId}, ${target.workspaceRoot}, ${target.relativePath}, ${target.targetPath},
|
|
292623
|
+
${tempPath}, ${input.totalBytes}, ${input.sha256}, 0, 'open',
|
|
292624
|
+
${new Date(now.getTime() + TRANSFER_TTL_MS).toISOString()}, ${now.toISOString()},
|
|
292625
|
+
${now.toISOString()}
|
|
292626
|
+
)
|
|
292627
|
+
`;
|
|
292628
|
+
return {
|
|
292629
|
+
transferId: input.transferId,
|
|
292630
|
+
nextOffset: 0,
|
|
292631
|
+
state: "open"
|
|
292632
|
+
};
|
|
292633
|
+
});
|
|
292634
|
+
const beginWriteEffect = fn$1("WorkspaceBulkTransfer.beginWrite")(function* (input) {
|
|
292635
|
+
const existing = yield* findTransfer(input.transferId);
|
|
292636
|
+
return yield* existing === null ? createWrite(input) : resumeWrite(input, existing);
|
|
292637
|
+
});
|
|
292638
|
+
const beginWrite = (input) => withTransferLock(input.transferId, beginWriteEffect(input)).pipe(mapError(mapFailure("begin write", input.transferId)));
|
|
292639
|
+
const acceptRetriedChunk = fn$1("WorkspaceBulkTransfer.acceptRetriedChunk")(function* (input, row) {
|
|
292640
|
+
const accepted = (yield* sql`
|
|
292641
|
+
SELECT chunk_offset AS "chunkOffset", chunk_bytes AS "chunkBytes",
|
|
292642
|
+
chunk_sha256 AS "chunkSha256"
|
|
292643
|
+
FROM workspace_bulk_transfer_chunks
|
|
292644
|
+
WHERE transfer_id = ${input.transferId} AND chunk_offset = ${input.offset}
|
|
292645
|
+
LIMIT 1
|
|
292646
|
+
`)[0];
|
|
292647
|
+
if (!(accepted !== void 0 && accepted.chunkBytes === input.bytes && accepted.chunkSha256 === input.sha256)) return yield* transferError({
|
|
292648
|
+
code: "integrity_mismatch",
|
|
292649
|
+
message: "Retried bulk transfer chunk differs from the accepted chunk.",
|
|
292650
|
+
transferId: input.transferId
|
|
292651
|
+
});
|
|
292652
|
+
return {
|
|
292653
|
+
transferId: input.transferId,
|
|
292654
|
+
nextOffset: row.nextOffset,
|
|
292655
|
+
duplicate: true
|
|
292656
|
+
};
|
|
292657
|
+
});
|
|
292658
|
+
const validateChunkRange = fn$1("WorkspaceBulkTransfer.validateChunkRange")(function* (input, row, byteLength) {
|
|
292659
|
+
if (input.offset !== row.nextOffset) return yield* transferError({
|
|
292660
|
+
code: "offset_mismatch",
|
|
292661
|
+
message: `Expected chunk offset ${row.nextOffset}, received ${input.offset}.`,
|
|
292662
|
+
transferId: input.transferId,
|
|
292663
|
+
retryable: true
|
|
292664
|
+
});
|
|
292665
|
+
const nextOffset = input.offset + byteLength;
|
|
292666
|
+
if (nextOffset > row.totalBytes) return yield* transferError({
|
|
292667
|
+
code: "size_limit_exceeded",
|
|
292668
|
+
message: "Bulk transfer chunk exceeds the declared total byte count.",
|
|
292669
|
+
transferId: input.transferId
|
|
292670
|
+
});
|
|
292671
|
+
return nextOffset;
|
|
292672
|
+
});
|
|
292673
|
+
const reconcileChunkBytes = fn$1("WorkspaceBulkTransfer.reconcileChunkBytes")(function* (input, row, bytes, nextOffset) {
|
|
292674
|
+
const tempInfo = yield* fileSystem.stat(row.tempPath);
|
|
292675
|
+
if (Number(tempInfo.size) > row.nextOffset) {
|
|
292676
|
+
if (Number(tempInfo.size) !== nextOffset) return yield* transferError({
|
|
292677
|
+
code: "integrity_mismatch",
|
|
292678
|
+
message: "Bulk transfer temporary file cannot be reconciled with the next chunk.",
|
|
292679
|
+
transferId: input.transferId
|
|
292680
|
+
});
|
|
292681
|
+
const recovered = (yield* fileSystem.readFile(row.tempPath)).slice(input.offset, nextOffset);
|
|
292682
|
+
if ((yield* promise(() => sha256Hex(recovered))) !== input.sha256) return yield* transferError({
|
|
292683
|
+
code: "integrity_mismatch",
|
|
292684
|
+
message: "Bulk transfer temporary bytes differ from the retried chunk.",
|
|
292685
|
+
transferId: input.transferId
|
|
292686
|
+
});
|
|
292687
|
+
return;
|
|
292688
|
+
}
|
|
292689
|
+
yield* fault("before-byte-write");
|
|
292690
|
+
yield* scoped(fileSystem.open(row.tempPath, { flag: "r+" }).pipe(flatMap((file) => file.seek(input.offset, "start").pipe(flatMap(() => file.writeAll(bytes)), flatMap(() => file.sync)))));
|
|
292691
|
+
yield* fault("after-byte-write-before-metadata");
|
|
292692
|
+
});
|
|
292693
|
+
const persistAcceptedChunk = fn$1("WorkspaceBulkTransfer.persistAcceptedChunk")(function* (input, nextOffset) {
|
|
292694
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
292695
|
+
yield* sql.withTransaction(sql`
|
|
292696
|
+
INSERT INTO workspace_bulk_transfer_chunks (
|
|
292697
|
+
transfer_id, chunk_offset, chunk_bytes, chunk_sha256
|
|
292698
|
+
) VALUES (${input.transferId}, ${input.offset}, ${input.bytes}, ${input.sha256})
|
|
292699
|
+
ON CONFLICT DO NOTHING
|
|
292700
|
+
`.pipe(flatMap(() => sql`
|
|
292701
|
+
UPDATE workspace_bulk_transfers
|
|
292702
|
+
SET next_offset = ${nextOffset}, updated_at = ${updatedAt}
|
|
292703
|
+
WHERE transfer_id = ${input.transferId} AND next_offset = ${input.offset} AND state = 'open'
|
|
292704
|
+
`)));
|
|
292705
|
+
yield* fault("after-metadata-advance");
|
|
292706
|
+
});
|
|
292707
|
+
const writeChunkEffect = fn$1("WorkspaceBulkTransfer.writeChunk")(function* (input) {
|
|
292708
|
+
const bytes = yield* promise(() => verifyBulkTransferChunk({
|
|
292709
|
+
...input,
|
|
292710
|
+
kind: "chunk"
|
|
292711
|
+
}));
|
|
292712
|
+
const row = yield* requireOpenTransfer(input.transferId);
|
|
292713
|
+
if (row.state === "completed") return yield* transferError({
|
|
292714
|
+
code: "offset_mismatch",
|
|
292715
|
+
message: "Bulk transfer is already complete.",
|
|
292716
|
+
transferId: input.transferId
|
|
292717
|
+
});
|
|
292718
|
+
if (input.offset < row.nextOffset) return yield* acceptRetriedChunk(input, row);
|
|
292719
|
+
const nextOffset = yield* validateChunkRange(input, row, bytes.byteLength);
|
|
292720
|
+
yield* reconcileChunkBytes(input, row, bytes, nextOffset);
|
|
292721
|
+
yield* persistAcceptedChunk(input, nextOffset);
|
|
292722
|
+
return {
|
|
292723
|
+
transferId: input.transferId,
|
|
292724
|
+
nextOffset,
|
|
292725
|
+
duplicate: false
|
|
292726
|
+
};
|
|
292727
|
+
});
|
|
292728
|
+
const writeChunk = (input) => withTransferLock(input.transferId, writeChunkEffect(input)).pipe(mapError(mapFailure("write chunk", input.transferId)));
|
|
292729
|
+
const commitWriteEffect = fn$1("WorkspaceBulkTransfer.commitWrite")(function* (input) {
|
|
292730
|
+
const row = yield* requireOpenTransfer(input.transferId);
|
|
292731
|
+
if (row.state === "completed") return {
|
|
292732
|
+
transferId: row.transferId,
|
|
292733
|
+
relativePath: row.relativePath,
|
|
292734
|
+
totalBytes: row.totalBytes,
|
|
292735
|
+
sha256: row.wholeSha256
|
|
292736
|
+
};
|
|
292737
|
+
if (row.nextOffset !== row.totalBytes) return yield* transferError({
|
|
292738
|
+
code: "offset_mismatch",
|
|
292739
|
+
message: `Bulk transfer is incomplete at byte ${row.nextOffset} of ${row.totalBytes}.`,
|
|
292740
|
+
transferId: row.transferId,
|
|
292741
|
+
retryable: true
|
|
292742
|
+
});
|
|
292743
|
+
if ((yield* resolveTarget({
|
|
292744
|
+
cwd: row.workspaceRoot,
|
|
292745
|
+
relativePath: row.relativePath,
|
|
292746
|
+
createParent: false
|
|
292747
|
+
})).targetPath !== row.targetPath) return yield* transferError({
|
|
292748
|
+
code: "symlink_escape",
|
|
292749
|
+
message: "Workspace file destination changed during transfer.",
|
|
292750
|
+
transferId: row.transferId
|
|
292751
|
+
});
|
|
292752
|
+
const tempInfo = yield* fileSystem.stat(row.tempPath).pipe(catch_(() => succeed(null)));
|
|
292753
|
+
const candidatePath = tempInfo === null ? row.targetPath : row.tempPath;
|
|
292754
|
+
const candidate = yield* fileSystem.readFile(candidatePath);
|
|
292755
|
+
if (candidate.byteLength !== row.totalBytes || (yield* promise(() => sha256Hex(candidate))) !== row.wholeSha256) return yield* transferError({
|
|
292756
|
+
code: "integrity_mismatch",
|
|
292757
|
+
message: "Bulk transfer payload does not match its declared size and digest.",
|
|
292758
|
+
transferId: row.transferId
|
|
292759
|
+
});
|
|
292760
|
+
if (tempInfo !== null) {
|
|
292761
|
+
yield* fileSystem.rename(row.tempPath, row.targetPath);
|
|
292762
|
+
yield* fault("after-atomic-rename");
|
|
292763
|
+
}
|
|
292764
|
+
yield* sql`
|
|
292765
|
+
UPDATE workspace_bulk_transfers SET state = 'completed', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
292766
|
+
WHERE transfer_id = ${row.transferId}
|
|
292767
|
+
`;
|
|
292768
|
+
yield* workspaceEntries.invalidate(row.workspaceRoot);
|
|
292769
|
+
return {
|
|
292770
|
+
transferId: row.transferId,
|
|
292771
|
+
relativePath: row.relativePath,
|
|
292772
|
+
totalBytes: row.totalBytes,
|
|
292773
|
+
sha256: row.wholeSha256
|
|
292774
|
+
};
|
|
292775
|
+
});
|
|
292776
|
+
const commitWrite = (input) => withTransferLock(input.transferId, commitWriteEffect(input)).pipe(mapError(mapFailure("commit write", input.transferId)));
|
|
292777
|
+
const cancelWriteEffect = fn$1("WorkspaceBulkTransfer.cancelWrite")(function* (input) {
|
|
292778
|
+
const row = yield* requireTransfer(input.transferId);
|
|
292779
|
+
if (row.state !== "completed") {
|
|
292780
|
+
yield* fileSystem.remove(row.tempPath, { force: true });
|
|
292781
|
+
yield* sql`
|
|
292782
|
+
UPDATE workspace_bulk_transfers SET state = 'cancelled', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
292783
|
+
WHERE transfer_id = ${input.transferId}
|
|
292784
|
+
`;
|
|
292785
|
+
}
|
|
292786
|
+
return {
|
|
292787
|
+
transferId: input.transferId,
|
|
292788
|
+
state: row.state === "completed" ? "completed" : "cancelled"
|
|
292789
|
+
};
|
|
292790
|
+
});
|
|
292791
|
+
const cancelWrite = (input) => withTransferLock(input.transferId, cancelWriteEffect(input)).pipe(mapError(mapFailure("cancel write", input.transferId)));
|
|
292792
|
+
const cleanupExpiredEffect = fn$1("WorkspaceBulkTransfer.cleanupExpired")(function* () {
|
|
292793
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
292794
|
+
const rows = yield* sql`
|
|
292795
|
+
SELECT transfer_id AS "transferId", temp_path AS "tempPath"
|
|
292796
|
+
FROM workspace_bulk_transfers WHERE state = 'open' AND expires_at <= ${now}
|
|
292797
|
+
`;
|
|
292798
|
+
let expiredCount = 0;
|
|
292799
|
+
for (const row of rows) if (yield* withTransferLock(row.transferId, gen(function* () {
|
|
292800
|
+
const current = yield* findTransfer(row.transferId);
|
|
292801
|
+
if (current === null || current.state !== "open" || Date.parse(current.expiresAt) > Date.parse(now)) return false;
|
|
292802
|
+
yield* fileSystem.remove(current.tempPath, { force: true });
|
|
292803
|
+
yield* sql`
|
|
292804
|
+
UPDATE workspace_bulk_transfers SET state = 'expired', updated_at = ${now}
|
|
292805
|
+
WHERE transfer_id = ${row.transferId} AND state = 'open' AND expires_at <= ${now}
|
|
292806
|
+
`;
|
|
292807
|
+
return true;
|
|
292808
|
+
}))) expiredCount += 1;
|
|
292809
|
+
return expiredCount;
|
|
292810
|
+
});
|
|
292811
|
+
const cleanupExpired = () => cleanupExpiredEffect().pipe(mapError(mapFailure("expiry cleanup")));
|
|
292812
|
+
return {
|
|
292813
|
+
readFile,
|
|
292814
|
+
beginWrite,
|
|
292815
|
+
writeChunk,
|
|
292816
|
+
commitWrite,
|
|
292817
|
+
cancelWrite,
|
|
292818
|
+
cleanupExpired
|
|
292819
|
+
};
|
|
292820
|
+
});
|
|
292821
|
+
const WorkspaceBulkTransferLive = effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
|
|
292822
|
+
//#endregion
|
|
292687
292823
|
//#region src/workspace/Layers/WorkspacePaths.ts
|
|
292688
292824
|
function toPosixRelativePath(input) {
|
|
292689
292825
|
return input.replaceAll("\\", "/");
|
|
@@ -294277,7 +294413,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
294277
294413
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
294278
294414
|
*/
|
|
294279
294415
|
function readBakedBuildCommit() {
|
|
294280
|
-
return "
|
|
294416
|
+
return "7695a8221bc664caedd33aa411ac7bcdbb0a3594";
|
|
294281
294417
|
}
|
|
294282
294418
|
async function resolveServerBuildCommit(input) {
|
|
294283
294419
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -294469,7 +294605,7 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
|
|
|
294469
294605
|
os,
|
|
294470
294606
|
arch: platformArch()
|
|
294471
294607
|
},
|
|
294472
|
-
serverVersion:
|
|
294608
|
+
serverVersion: installedForgeServerVersion,
|
|
294473
294609
|
...buildCommit !== void 0 ? { buildCommit } : {},
|
|
294474
294610
|
...deviceRuntimeId !== void 0 ? { deviceRuntimeId } : {},
|
|
294475
294611
|
capabilities: {
|
|
@@ -294485,7 +294621,7 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
|
|
|
294485
294621
|
updateDisplayName: (displayName) => gen(function* () {
|
|
294486
294622
|
const normalized = normalizeDisplayName(displayName);
|
|
294487
294623
|
yield* writeMetadata({ displayName: normalized }).pipe(orDie);
|
|
294488
|
-
yield* set$
|
|
294624
|
+
yield* set$3(displayNameOverrideRef, normalized);
|
|
294489
294625
|
const descriptor = yield* buildDescriptor;
|
|
294490
294626
|
yield* publish(changesPubSub, descriptor);
|
|
294491
294627
|
return descriptor;
|
|
@@ -301218,6 +301354,54 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
301218
301354
|
AND deleted_at IS NULL
|
|
301219
301355
|
AND archived_at IS NULL
|
|
301220
301356
|
ORDER BY created_at ASC, thread_id ASC
|
|
301357
|
+
`
|
|
301358
|
+
});
|
|
301359
|
+
/**
|
|
301360
|
+
* The archive's own rows.
|
|
301361
|
+
*
|
|
301362
|
+
* `listThreadRows` deliberately carries archived threads too, because the
|
|
301363
|
+
* active list needs `archivedAt` to know what to hide and the web archive
|
|
301364
|
+
* panel filters that same snapshot client side. This query is the opposite
|
|
301365
|
+
* selection, for the clients that ask for the archive directly.
|
|
301366
|
+
*/
|
|
301367
|
+
const listArchivedThreadRows = findAll({
|
|
301368
|
+
Request: Void,
|
|
301369
|
+
Result: ProjectionThreadDbRowSchema,
|
|
301370
|
+
execute: () => sql`
|
|
301371
|
+
SELECT
|
|
301372
|
+
thread_id AS "threadId",
|
|
301373
|
+
project_id AS "projectId",
|
|
301374
|
+
title,
|
|
301375
|
+
model_selection_json AS "modelSelection",
|
|
301376
|
+
runtime_mode AS "runtimeMode",
|
|
301377
|
+
interaction_mode AS "interactionMode",
|
|
301378
|
+
branch,
|
|
301379
|
+
worktree_path AS "worktreePath",
|
|
301380
|
+
latest_turn_id AS "latestTurnId",
|
|
301381
|
+
created_at AS "createdAt",
|
|
301382
|
+
updated_at AS "updatedAt",
|
|
301383
|
+
archived_at AS "archivedAt",
|
|
301384
|
+
settled_at AS "settledAt",
|
|
301385
|
+
settled_override AS "settledOverride",
|
|
301386
|
+
latest_user_message_at AS "latestUserMessageAt",
|
|
301387
|
+
pending_approval_count AS "pendingApprovalCount",
|
|
301388
|
+
pending_user_input_count AS "pendingUserInputCount",
|
|
301389
|
+
has_actionable_proposed_plan AS "hasActionableProposedPlan",
|
|
301390
|
+
deleted_at AS "deletedAt",
|
|
301391
|
+
kind,
|
|
301392
|
+
parent_thread_id AS "parentThreadId",
|
|
301393
|
+
anchor_kind AS "anchorKind",
|
|
301394
|
+
anchor_cmmd_id AS "anchorCmmdId",
|
|
301395
|
+
linked_pull_request_json AS "linkedPullRequest",
|
|
301396
|
+
status,
|
|
301397
|
+
worker_role AS "workerRole",
|
|
301398
|
+
goal,
|
|
301399
|
+
pending_auto_create_prompt_json AS "pendingAutoCreatePrompt",
|
|
301400
|
+
COALESCE(rate_limit_status_json, '[]') AS "rateLimitStatus"
|
|
301401
|
+
FROM projection_threads
|
|
301402
|
+
WHERE archived_at IS NOT NULL
|
|
301403
|
+
AND deleted_at IS NULL
|
|
301404
|
+
ORDER BY archived_at DESC, thread_id ASC
|
|
301221
301405
|
`
|
|
301222
301406
|
});
|
|
301223
301407
|
const listThreadMessageRows = findAll({
|
|
@@ -301872,13 +302056,22 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
301872
302056
|
if (isPersistenceError(error)) return error;
|
|
301873
302057
|
return toPersistenceSqlError("ProjectionSnapshotQuery.getSnapshot:query")(error);
|
|
301874
302058
|
}));
|
|
301875
|
-
|
|
302059
|
+
/**
|
|
302060
|
+
* One snapshot assembly, two selections of threads.
|
|
302061
|
+
*
|
|
302062
|
+
* The active list and the archive differ only in which thread rows they
|
|
302063
|
+
* read, so they share this body. Keeping them on one mapper is the point:
|
|
302064
|
+
* FOR-36394 was a projected column that one path carried and another
|
|
302065
|
+
* dropped, and a second hand-copied assembly here would invite the same
|
|
302066
|
+
* failure for anyone who only remembered to update the active list.
|
|
302067
|
+
*/
|
|
302068
|
+
const readShellSnapshot = (operation, listThreads) => {
|
|
301876
302069
|
return sql.withTransaction(all([
|
|
301877
|
-
listProjectRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(
|
|
301878
|
-
|
|
301879
|
-
listThreadSessionRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(
|
|
301880
|
-
listLatestTurnRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(
|
|
301881
|
-
listProjectionStateRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(
|
|
302070
|
+
listProjectRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listProjects:query`, `ProjectionSnapshotQuery.${operation}:listProjects:decodeRows`))),
|
|
302071
|
+
listThreads(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listThreads:query`, `ProjectionSnapshotQuery.${operation}:listThreads:decodeRows`))),
|
|
302072
|
+
listThreadSessionRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listThreadSessions:query`, `ProjectionSnapshotQuery.${operation}:listThreadSessions:decodeRows`))),
|
|
302073
|
+
listLatestTurnRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listLatestTurns:query`, `ProjectionSnapshotQuery.${operation}:listLatestTurns:decodeRows`))),
|
|
302074
|
+
listProjectionStateRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listProjectionState:query`, `ProjectionSnapshotQuery.${operation}:listProjectionState:decodeRows`)))
|
|
301882
302075
|
])).pipe(flatMap(([projectRows, threadRows, sessionRows, latestTurnRows, stateRows]) => gen(function* () {
|
|
301883
302076
|
let updatedAt = null;
|
|
301884
302077
|
for (const row of projectRows) updatedAt = maxIso(updatedAt, row.updatedAt);
|
|
@@ -301899,12 +302092,14 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
301899
302092
|
projects: projectRows.filter((row) => row.deletedAt === null).map((row) => mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null, projectAvailabilities.get(row.projectId) ?? "unknown")),
|
|
301900
302093
|
threads: threadShells.filter((thread) => projectIds.has(thread.projectId)),
|
|
301901
302094
|
updatedAt: updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
301902
|
-
}).pipe(mapError(toPersistenceDecodeError(
|
|
302095
|
+
}).pipe(mapError(toPersistenceDecodeError(`ProjectionSnapshotQuery.${operation}:decodeShellSnapshot`)));
|
|
301903
302096
|
})), mapError((error) => {
|
|
301904
302097
|
if (isPersistenceError(error)) return error;
|
|
301905
|
-
return toPersistenceSqlError(
|
|
302098
|
+
return toPersistenceSqlError(`ProjectionSnapshotQuery.${operation}:query`)(error);
|
|
301906
302099
|
}));
|
|
301907
302100
|
};
|
|
302101
|
+
const getShellSnapshot = () => readShellSnapshot("getShellSnapshot", listThreadRows);
|
|
302102
|
+
const getArchivedShellSnapshot = () => readShellSnapshot("getArchivedShellSnapshot", listArchivedThreadRows);
|
|
301908
302103
|
const listActiveWorkerShells = () => sql.withTransaction(all([
|
|
301909
302104
|
listActiveWorkerThreadRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:decodeRows"))),
|
|
301910
302105
|
listActiveWorkerSessionRows(void 0).pipe(mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:decodeRows"))),
|
|
@@ -302071,6 +302266,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
|
|
|
302071
302266
|
return {
|
|
302072
302267
|
getSnapshot,
|
|
302073
302268
|
getShellSnapshot,
|
|
302269
|
+
getArchivedShellSnapshot,
|
|
302074
302270
|
listActiveWorkerShells,
|
|
302075
302271
|
listStaleRunningSessionShells,
|
|
302076
302272
|
getCounts,
|
|
@@ -306060,7 +306256,6 @@ const cmmdUpdateProjectAgentMemberAvatarRouteLayer = registerProxyRoute({
|
|
|
306060
306256
|
});
|
|
306061
306257
|
//#endregion
|
|
306062
306258
|
//#region src/command/runtimeProviderAuthorityUpgradeModel.ts
|
|
306063
|
-
const GIT_COMMIT = /^[0-9a-f]{40}$/u;
|
|
306064
306259
|
const AUTHORITY_UPGRADE_OPERATION_KEYS = new Set([
|
|
306065
306260
|
"operationId",
|
|
306066
306261
|
"runtimeId",
|
|
@@ -306070,7 +306265,7 @@ const AUTHORITY_UPGRADE_OPERATION_KEYS = new Set([
|
|
|
306070
306265
|
"attemptCount",
|
|
306071
306266
|
"expectedCredentialEpoch",
|
|
306072
306267
|
"resultingCredentialEpoch",
|
|
306073
|
-
"
|
|
306268
|
+
"targetForgeServerVersion",
|
|
306074
306269
|
"retryAt",
|
|
306075
306270
|
"completedAt",
|
|
306076
306271
|
"failureCode"
|
|
@@ -306138,7 +306333,7 @@ function parseRuntimeAuthorityUpgradeOperation(value, expected) {
|
|
|
306138
306333
|
Number.isSafeInteger(operation.attemptCount) && operation.attemptCount >= 0,
|
|
306139
306334
|
operation.expectedCredentialEpoch === 0,
|
|
306140
306335
|
operation.resultingCredentialEpoch === 1,
|
|
306141
|
-
|
|
306336
|
+
isExactForgeServerVersion(operation.targetForgeServerVersion),
|
|
306142
306337
|
isNullableDateTime(operation.retryAt),
|
|
306143
306338
|
isNullableDateTime(operation.completedAt),
|
|
306144
306339
|
operation.failureCode === null || typeof operation.failureCode === "string",
|
|
@@ -306155,6 +306350,7 @@ function isLegacyRuntimeConnection(value, expectedRuntimeId) {
|
|
|
306155
306350
|
runtime.brokerAuthorityAttestedImageRef === null,
|
|
306156
306351
|
runtime.brokerAuthorityAttestedImageVersion === null,
|
|
306157
306352
|
runtime.brokerAuthorityAttestedForgeGitCommit === null,
|
|
306353
|
+
runtime.brokerAuthorityAttestedForgeServerVersion === null,
|
|
306158
306354
|
runtime.brokerAuthorityAttestedMachineId === null,
|
|
306159
306355
|
runtime.brokerAuthorityAttestedCredentialEpoch === null,
|
|
306160
306356
|
runtime.brokerAuthorityAttestedAt === null
|
|
@@ -306318,13 +306514,13 @@ function hasTrustedRuntimeRoute(url, trustedBase) {
|
|
|
306318
306514
|
const relativePath = url.pathname.slice(trustedPrefix.length);
|
|
306319
306515
|
return url.origin === trustedBase.origin && url.username === "" && url.password === "" && url.port === "" && url.search === "" && url.hash === "" && url.pathname.startsWith(`${trustedPrefix}/r/`) && RUNTIME_ROUTE_PATH.test(relativePath);
|
|
306320
306516
|
}
|
|
306321
|
-
function hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId,
|
|
306322
|
-
return runtime.runtimeId === expectedRuntimeId && runtime.runtimeCapabilityMode === "exact_machine_v2" && isNonEmptyString(runtime.brokerAuthorityAttestedMachineId) && isSafeCredentialEpoch(runtime.brokerAuthorityAttestedCredentialEpoch) && (
|
|
306517
|
+
function hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId, expectedAttestedForgeServerVersion) {
|
|
306518
|
+
return runtime.runtimeId === expectedRuntimeId && runtime.runtimeCapabilityMode === "exact_machine_v2" && isNonEmptyString(runtime.brokerAuthorityAttestedMachineId) && isSafeCredentialEpoch(runtime.brokerAuthorityAttestedCredentialEpoch) && (expectedAttestedForgeServerVersion === void 0 || runtime.brokerAuthorityAttestedForgeServerVersion === expectedAttestedForgeServerVersion) && isNonEmptyString(data.httpBaseUrl) && isNonEmptyString(data.bearerToken);
|
|
306323
306519
|
}
|
|
306324
|
-
function parseRuntimeConnection(value, expectedRuntimeId, trustedRuntimeRouterBaseUrl,
|
|
306520
|
+
function parseRuntimeConnection(value, expectedRuntimeId, trustedRuntimeRouterBaseUrl, expectedAttestedForgeServerVersion) {
|
|
306325
306521
|
const data = asRecord$1(unwrapData(value));
|
|
306326
306522
|
const runtime = asRecord$1(data?.runtime);
|
|
306327
|
-
if (!data || !runtime || !hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId,
|
|
306523
|
+
if (!data || !runtime || !hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId, expectedAttestedForgeServerVersion)) return null;
|
|
306328
306524
|
const trustedBase = parseTrustedRuntimeRouterBaseUrl(trustedRuntimeRouterBaseUrl);
|
|
306329
306525
|
if (!trustedBase) return null;
|
|
306330
306526
|
let httpBaseUrl;
|
|
@@ -306504,7 +306700,7 @@ function classifyInitialConnection(outcome, runtimeId, trustedRuntimeRouterBaseU
|
|
|
306504
306700
|
}
|
|
306505
306701
|
};
|
|
306506
306702
|
}
|
|
306507
|
-
function reconnectExactRuntime(input,
|
|
306703
|
+
function reconnectExactRuntime(input, targetForgeServerVersion) {
|
|
306508
306704
|
return gen(function* () {
|
|
306509
306705
|
const connectPath = `/api/forge/context/runtimes/${encodeURIComponent(input.runtimeId)}/connect`;
|
|
306510
306706
|
const reconnected = yield* postRuntimeProviderCmmd({
|
|
@@ -306521,7 +306717,7 @@ function reconnectExactRuntime(input, targetForgeGitCommit) {
|
|
|
306521
306717
|
error: reconnected.error,
|
|
306522
306718
|
stage: "reconnect"
|
|
306523
306719
|
};
|
|
306524
|
-
const exact = parseRuntimeConnection(reconnected.value, input.runtimeId, input.trustedRuntimeRouterBaseUrl,
|
|
306720
|
+
const exact = parseRuntimeConnection(reconnected.value, input.runtimeId, input.trustedRuntimeRouterBaseUrl, targetForgeServerVersion);
|
|
306525
306721
|
return exact ? {
|
|
306526
306722
|
ok: true,
|
|
306527
306723
|
value: exact
|
|
@@ -306532,7 +306728,7 @@ function reconnectExactRuntime(input, targetForgeGitCommit) {
|
|
|
306532
306728
|
});
|
|
306533
306729
|
}
|
|
306534
306730
|
function resolveOperation(input, operation) {
|
|
306535
|
-
return operation.status === "completed" ? reconnectExactRuntime(input, operation.
|
|
306731
|
+
return operation.status === "completed" ? reconnectExactRuntime(input, operation.targetForgeServerVersion) : succeed(pendingOperation(operation));
|
|
306536
306732
|
}
|
|
306537
306733
|
function beginExactRuntimeConnection(input) {
|
|
306538
306734
|
return gen(function* () {
|
|
@@ -311400,7 +311596,7 @@ const PipelineRunCoordinatorLive = effect(PipelineRunCoordinator, gen(function*
|
|
|
311400
311596
|
contextHash: preparedHandoff.authority.contextHash,
|
|
311401
311597
|
contextSnapshot: snapshot.run.contextSnapshot
|
|
311402
311598
|
}).pipe(mapError((cause) => coordinatorError("registerHandoff", "PIPELINE_CONTEXT_ARTIFACT_FAILED", "The immutable CMMD run context could not be materialized for the provider.", cause)));
|
|
311403
|
-
const latestPreparedAuthority = yield* heartbeatPublicationSemaphore.withPermits(1)(set$
|
|
311599
|
+
const latestPreparedAuthority = yield* heartbeatPublicationSemaphore.withPermits(1)(set$3(acceptingHeartbeatRef, false).pipe(andThen(get$6(preparedAuthorityRef))));
|
|
311404
311600
|
const persistedAuthority = yield* persistAuthoritySecret({
|
|
311405
311601
|
runId: input.runId,
|
|
311406
311602
|
organizationId: latestPreparedAuthority.authority.organizationId,
|
|
@@ -311487,7 +311683,7 @@ const PipelineRunCoordinatorLive = effect(PipelineRunCoordinator, gen(function*
|
|
|
311487
311683
|
});
|
|
311488
311684
|
yield* heartbeatPublicationSemaphore.withPermits(1)(gen(function* () {
|
|
311489
311685
|
if (!(yield* get$6(acceptingHeartbeatRef))) return;
|
|
311490
|
-
yield* set$
|
|
311686
|
+
yield* set$3(preparedAuthorityRef, {
|
|
311491
311687
|
authority: heartbeat.refreshedAuthority,
|
|
311492
311688
|
preparation
|
|
311493
311689
|
});
|
|
@@ -316261,7 +316457,7 @@ const WorkspaceReadinessStateLive = effect(WorkspaceReadinessState, gen(function
|
|
|
316261
316457
|
const slot = yield* getOrCreateSlot(userId);
|
|
316262
316458
|
const previous = yield* get$6(slot.snapshotRef);
|
|
316263
316459
|
if (previous !== null && event.ts < previous.ts) return;
|
|
316264
|
-
yield* set$
|
|
316460
|
+
yield* set$3(slot.snapshotRef, event);
|
|
316265
316461
|
yield* publish(slot.pubsub, event);
|
|
316266
316462
|
if (event.phase === "app-ready") yield* schedulePrune(userId, slot);
|
|
316267
316463
|
});
|