@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.cjs
CHANGED
|
@@ -12,7 +12,7 @@ const require_Cache = require("./Cache-CWlxNDbC.cjs");
|
|
|
12
12
|
const require_SqlError = require("./SqlError-Bt0Bcd5C.cjs");
|
|
13
13
|
const require_Mime = require("./Mime-Bilnh8zE.cjs");
|
|
14
14
|
const require_esm = require("./esm-BZhXwX5p.cjs");
|
|
15
|
-
const require_runtimeControlAuthority = require("./runtimeControlAuthority-
|
|
15
|
+
const require_runtimeControlAuthority = require("./runtimeControlAuthority-B3kNmYRT.cjs");
|
|
16
16
|
const require_PTY = require("./PTY-BxYDiW3h.cjs");
|
|
17
17
|
let node_child_process = require("node:child_process");
|
|
18
18
|
node_child_process = require_chunk.__toESM(node_child_process);
|
|
@@ -15773,7 +15773,7 @@ const get$1 = (self) => modify$1(self, (current) => [current, current]);
|
|
|
15773
15773
|
* })
|
|
15774
15774
|
* ```
|
|
15775
15775
|
*/
|
|
15776
|
-
const set$
|
|
15776
|
+
const set$2 = /* @__PURE__ */ require_Schema$1.dual(2, (self, value) => update$1(self, () => value));
|
|
15777
15777
|
//#endregion
|
|
15778
15778
|
//#region ../../node_modules/.bun/effect@4.0.0-beta.51/node_modules/effect/dist/TxChunk.js
|
|
15779
15779
|
/**
|
|
@@ -15962,7 +15962,7 @@ const get = (self) => get$1(self.ref);
|
|
|
15962
15962
|
* @since 4.0.0
|
|
15963
15963
|
* @category combinators
|
|
15964
15964
|
*/
|
|
15965
|
-
const set = /* @__PURE__ */ require_Schema$1.dual(2, (self, chunk) => set$
|
|
15965
|
+
const set$1 = /* @__PURE__ */ require_Schema$1.dual(2, (self, chunk) => set$2(self.ref, chunk));
|
|
15966
15966
|
/**
|
|
15967
15967
|
* Appends an element to the end of the `TxChunk`.
|
|
15968
15968
|
*
|
|
@@ -16211,7 +16211,7 @@ const take = (self) => require_Schema$1.gen(function* () {
|
|
|
16211
16211
|
const head$3 = require_Schema$1.head(chunk);
|
|
16212
16212
|
if (require_Schema$1.isNone(head$3)) return yield* require_Schema$1.txRetry;
|
|
16213
16213
|
yield* drop(self.items, 1);
|
|
16214
|
-
if (state._tag === "Closing" && (yield* isEmpty(self))) yield* set$
|
|
16214
|
+
if (state._tag === "Closing" && (yield* isEmpty(self))) yield* set$2(self.stateRef, {
|
|
16215
16215
|
_tag: "Done",
|
|
16216
16216
|
cause: state.cause
|
|
16217
16217
|
});
|
|
@@ -16309,11 +16309,11 @@ const interrupt = (self) => require_Schema$1.withFiber((fiber) => failCause(self
|
|
|
16309
16309
|
*/
|
|
16310
16310
|
const failCause = /* @__PURE__ */ require_Schema$1.dual(2, (self, cause) => require_Schema$1.gen(function* () {
|
|
16311
16311
|
if ((yield* get$1(self.stateRef))._tag !== "Open") return false;
|
|
16312
|
-
if (yield* isEmpty(self)) yield* set$
|
|
16312
|
+
if (yield* isEmpty(self)) yield* set$2(self.stateRef, {
|
|
16313
16313
|
_tag: "Done",
|
|
16314
16314
|
cause
|
|
16315
16315
|
});
|
|
16316
|
-
else yield* set$
|
|
16316
|
+
else yield* set$2(self.stateRef, {
|
|
16317
16317
|
_tag: "Closing",
|
|
16318
16318
|
cause
|
|
16319
16319
|
});
|
|
@@ -16355,7 +16355,7 @@ const clear = (self) => require_Schema$1.gen(function* () {
|
|
|
16355
16355
|
return yield* require_Schema$1.failCause(state.cause);
|
|
16356
16356
|
}
|
|
16357
16357
|
const chunk = yield* get(self.items);
|
|
16358
|
-
yield* set(self.items, require_Schema$1.empty());
|
|
16358
|
+
yield* set$1(self.items, require_Schema$1.empty());
|
|
16359
16359
|
return require_Schema$1.toArray(chunk);
|
|
16360
16360
|
}).pipe(require_Schema$1.tx);
|
|
16361
16361
|
/**
|
|
@@ -16544,7 +16544,7 @@ function makeNetServiceShape(createServer = node_net.createServer) {
|
|
|
16544
16544
|
}
|
|
16545
16545
|
//#endregion
|
|
16546
16546
|
//#region ../../packages/shared/src/releaseInfo.generated.ts
|
|
16547
|
-
const FORGE_RELEASE_VERSION = "0.
|
|
16547
|
+
const FORGE_RELEASE_VERSION = "0.12.0";
|
|
16548
16548
|
//#endregion
|
|
16549
16549
|
//#region ../../packages/shared/src/hostProcess.ts
|
|
16550
16550
|
/**
|
|
@@ -19200,6 +19200,7 @@ const ORCHESTRATION_WS_METHODS = {
|
|
|
19200
19200
|
streamTurnDiff: "orchestration.streamTurnDiff",
|
|
19201
19201
|
streamFullThreadDiff: "orchestration.streamFullThreadDiff",
|
|
19202
19202
|
replayEvents: "orchestration.replayEvents",
|
|
19203
|
+
getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot",
|
|
19203
19204
|
subscribeShell: "orchestration.subscribeShell",
|
|
19204
19205
|
subscribeThread: "orchestration.subscribeThread"
|
|
19205
19206
|
};
|
|
@@ -20669,6 +20670,10 @@ const OrchestrationRpcSchemas = {
|
|
|
20669
20670
|
subscribeShell: {
|
|
20670
20671
|
input: require_Schema$1.Struct({}),
|
|
20671
20672
|
output: OrchestrationShellStreamItem
|
|
20673
|
+
},
|
|
20674
|
+
getArchivedShellSnapshot: {
|
|
20675
|
+
input: require_Schema$1.Struct({}),
|
|
20676
|
+
output: OrchestrationShellSnapshot
|
|
20672
20677
|
}
|
|
20673
20678
|
};
|
|
20674
20679
|
var OrchestrationGetSnapshotError = class extends require_Schema$1.TaggedErrorClass()("OrchestrationGetSnapshotError", {
|
|
@@ -26564,6 +26569,11 @@ const WsOrchestrationReplayEventsRpc = make$30(ORCHESTRATION_WS_METHODS.replayEv
|
|
|
26564
26569
|
success: OrchestrationRpcSchemas.replayEvents.output,
|
|
26565
26570
|
error: OrchestrationReplayEventsError
|
|
26566
26571
|
});
|
|
26572
|
+
const WsOrchestrationGetArchivedShellSnapshotRpc = make$30(ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, {
|
|
26573
|
+
payload: OrchestrationRpcSchemas.getArchivedShellSnapshot.input,
|
|
26574
|
+
success: OrchestrationRpcSchemas.getArchivedShellSnapshot.output,
|
|
26575
|
+
error: OrchestrationGetSnapshotError
|
|
26576
|
+
});
|
|
26567
26577
|
const WsOrchestrationSubscribeShellRpc = make$30(ORCHESTRATION_WS_METHODS.subscribeShell, {
|
|
26568
26578
|
payload: OrchestrationRpcSchemas.subscribeShell.input,
|
|
26569
26579
|
success: OrchestrationRpcSchemas.subscribeShell.output,
|
|
@@ -26644,7 +26654,7 @@ const WsRpcGroup = make$29(WsServerGetConfigRpc, WsServerRefreshProvidersRpc, Ws
|
|
|
26644
26654
|
payload: UsageSummaryInput,
|
|
26645
26655
|
success: UsageSummary,
|
|
26646
26656
|
error: UsageReadError
|
|
26647
|
-
}), 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);
|
|
26657
|
+
}), 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);
|
|
26648
26658
|
const RelayWsRpcGroup = WsRpcGroup.middleware(RelayAuthorizationMiddleware).middleware(RelayDeliveryMiddleware);
|
|
26649
26659
|
/**
|
|
26650
26660
|
* The direct-WebSocket mount of the same group, with per-method scope
|
|
@@ -26678,6 +26688,29 @@ function encodeCmmdRuntimeThreadLifecycleMetadata(lifecycle) {
|
|
|
26678
26688
|
} };
|
|
26679
26689
|
}
|
|
26680
26690
|
//#endregion
|
|
26691
|
+
//#region ../../packages/contracts/src/cmmdRuntimeThreadWorkspace.ts
|
|
26692
|
+
function encodeCmmdRuntimeThreadWorkspaceMetadata(workspace) {
|
|
26693
|
+
return { forgeWorkspace: {
|
|
26694
|
+
schemaVersion: 1,
|
|
26695
|
+
worktreePath: workspace.worktreePath
|
|
26696
|
+
} };
|
|
26697
|
+
}
|
|
26698
|
+
//#endregion
|
|
26699
|
+
//#region ../../packages/contracts/src/cmmdRuntimeToolActivity.ts
|
|
26700
|
+
function isRecord$22(value) {
|
|
26701
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
26702
|
+
}
|
|
26703
|
+
function encodeCmmdRuntimeToolActivityPayload(kind, payload) {
|
|
26704
|
+
return {
|
|
26705
|
+
...isRecord$22(payload) ? payload : { sourcePayload: payload },
|
|
26706
|
+
forgeToolActivity: {
|
|
26707
|
+
schemaVersion: 1,
|
|
26708
|
+
source: "native",
|
|
26709
|
+
kind
|
|
26710
|
+
}
|
|
26711
|
+
};
|
|
26712
|
+
}
|
|
26713
|
+
//#endregion
|
|
26681
26714
|
//#region ../../packages/contracts/src/cmmdProjectPatch.ts
|
|
26682
26715
|
const CMMD_PROJECT_PATCH_FIELDS = {
|
|
26683
26716
|
title: "requiredText",
|
|
@@ -31747,6 +31780,11 @@ const RELAY_METHOD_POLICY_BASES = [
|
|
|
31747
31780
|
requiredScope: "thread.read",
|
|
31748
31781
|
deliveryClass: "subscription"
|
|
31749
31782
|
},
|
|
31783
|
+
{
|
|
31784
|
+
method: ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot,
|
|
31785
|
+
requiredScope: "thread.read",
|
|
31786
|
+
deliveryClass: "read"
|
|
31787
|
+
},
|
|
31750
31788
|
{
|
|
31751
31789
|
method: ORCHESTRATION_WS_METHODS.subscribeShell,
|
|
31752
31790
|
requiredScope: "thread.read",
|
|
@@ -65775,7 +65813,7 @@ function normalizeNumberish(value) {
|
|
|
65775
65813
|
}
|
|
65776
65814
|
//#endregion
|
|
65777
65815
|
//#region package.json
|
|
65778
|
-
var version$1 = "0.
|
|
65816
|
+
var version$1 = "0.12.0";
|
|
65779
65817
|
//#endregion
|
|
65780
65818
|
//#region src/sentry.ts
|
|
65781
65819
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -69563,6 +69601,17 @@ const browserApiCorsLayer = middleware(require_HttpServer.cors({
|
|
|
69563
69601
|
credentials: true,
|
|
69564
69602
|
maxAge: 600
|
|
69565
69603
|
}), { global: true });
|
|
69604
|
+
/**
|
|
69605
|
+
* An /api path that reaches the static catch-all is not implemented by this
|
|
69606
|
+
* server: every real API route is registered ahead of it. Serving the web app's
|
|
69607
|
+
* index.html for those made API clients decode an HTML document as JSON, which
|
|
69608
|
+
* is how the phone's shell snapshot fetch failed with a JSON parse error on
|
|
69609
|
+
* every connect (FOR-36611). Callers can act on a 404; they cannot act on a
|
|
69610
|
+
* document that claims to be their payload.
|
|
69611
|
+
*/
|
|
69612
|
+
function isApiRequestPath(pathname) {
|
|
69613
|
+
return pathname === "/api" || pathname.startsWith("/api/");
|
|
69614
|
+
}
|
|
69566
69615
|
function resolveDevRedirectUrl(devUrl, requestUrl) {
|
|
69567
69616
|
const redirectUrl = new URL(devUrl.toString());
|
|
69568
69617
|
redirectUrl.pathname = requestUrl.pathname;
|
|
@@ -69687,6 +69736,10 @@ const staticAndDevRouteLayer = add$1("GET", "*", require_Schema$1.gen(function*
|
|
|
69687
69736
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
69688
69737
|
const url = require_HttpServer.toURL(request);
|
|
69689
69738
|
if (require_Schema$1.isNone(url)) return require_HttpServer.text("Bad Request", { status: 400 });
|
|
69739
|
+
if (isApiRequestPath(url.value.pathname)) return require_HttpServer.jsonUnsafe({
|
|
69740
|
+
error: "not_found",
|
|
69741
|
+
message: `This server does not serve ${url.value.pathname}.`
|
|
69742
|
+
}, { status: 404 });
|
|
69690
69743
|
const config = yield* ServerConfig;
|
|
69691
69744
|
if (config.devUrl && isLoopbackHostname(url.value.hostname)) return require_HttpServer.redirect(resolveDevRedirectUrl(config.devUrl, url.value), { status: 302 });
|
|
69692
69745
|
const staticDir = config.staticDir ?? (config.devUrl ? yield* resolveStaticDir() : void 0);
|
|
@@ -93028,6 +93081,15 @@ async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync
|
|
|
93028
93081
|
return cached$1;
|
|
93029
93082
|
}
|
|
93030
93083
|
//#endregion
|
|
93084
|
+
//#region src/environment/serverVersion.ts
|
|
93085
|
+
const installedForgeServerVersion = version$1;
|
|
93086
|
+
const SEMVER_NUMERIC_IDENTIFIER = "(?:0|[1-9][0-9]*)";
|
|
93087
|
+
const SEMVER_PRERELEASE_IDENTIFIER = "(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)";
|
|
93088
|
+
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-]+)*)?\$`);
|
|
93089
|
+
function isExactForgeServerVersion(value) {
|
|
93090
|
+
return typeof value === "string" && EXACT_FORGE_SERVER_VERSION_PATTERN.test(value);
|
|
93091
|
+
}
|
|
93092
|
+
//#endregion
|
|
93031
93093
|
//#region src/cmmd/ownerSessionCredential.ts
|
|
93032
93094
|
/**
|
|
93033
93095
|
* The signed-in owner's CMMD credential, for the calls a device makes as
|
|
@@ -94671,7 +94733,8 @@ const registerCloudRuntimeForReplicaHydration = require_Schema$1.gen(function* (
|
|
|
94671
94733
|
credentialEpoch,
|
|
94672
94734
|
activeTurnCount: 0,
|
|
94673
94735
|
activeSessionCount: 0,
|
|
94674
|
-
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {}
|
|
94736
|
+
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
94737
|
+
forgeServerVersion: installedForgeServerVersion
|
|
94675
94738
|
}),
|
|
94676
94739
|
catch: (cause) => replicaHydrationFailure("runtime registration heartbeat failed", cause)
|
|
94677
94740
|
})) === null) return yield* replicaHydrationFailure("runtime control authority is unavailable");
|
|
@@ -95993,10 +96056,13 @@ function serializeRuntimeInventory(snapshot, providerInventory) {
|
|
|
95993
96056
|
workerRole: thread.workerRole,
|
|
95994
96057
|
anchorKind: thread.anchorKind,
|
|
95995
96058
|
anchorCmmdId: thread.anchorCmmdId,
|
|
95996
|
-
metadata:
|
|
95997
|
-
|
|
95998
|
-
|
|
95999
|
-
|
|
96059
|
+
metadata: {
|
|
96060
|
+
...encodeCmmdRuntimeThreadLifecycleMetadata({
|
|
96061
|
+
settledAt: thread.settledAt ?? null,
|
|
96062
|
+
settledOverride: thread.settledOverride ?? null
|
|
96063
|
+
}),
|
|
96064
|
+
...encodeCmmdRuntimeThreadWorkspaceMetadata({ worktreePath: thread.worktreePath })
|
|
96065
|
+
}
|
|
96000
96066
|
})),
|
|
96001
96067
|
metadata: {
|
|
96002
96068
|
source: "forge-server-projection",
|
|
@@ -96063,7 +96129,7 @@ function serializeNativeToolActivity(activity) {
|
|
|
96063
96129
|
kind: "tool_call",
|
|
96064
96130
|
summary,
|
|
96065
96131
|
status: activityStatus(activity),
|
|
96066
|
-
|
|
96132
|
+
payload: encodeCmmdRuntimeToolActivityPayload(activity.kind, activity.payload)
|
|
96067
96133
|
}
|
|
96068
96134
|
};
|
|
96069
96135
|
}
|
|
@@ -98633,413 +98699,23 @@ function browseDirectory(input) {
|
|
|
98633
98699
|
});
|
|
98634
98700
|
}
|
|
98635
98701
|
//#endregion
|
|
98636
|
-
//#region src/
|
|
98702
|
+
//#region src/pathWithinRoot.ts
|
|
98637
98703
|
/**
|
|
98638
|
-
*
|
|
98704
|
+
* True when `candidate` is `root` itself or lies below it, judged on the path
|
|
98705
|
+
* strings alone. The path from `root` to `candidate` is taken with
|
|
98706
|
+
* `path.relative` and normalised to forward slashes, and the candidate is
|
|
98707
|
+
* outside when that path climbs out (`..` or `../...`) or is absolute, which is
|
|
98708
|
+
* what `relative` returns across Windows drives. A child whose name only starts
|
|
98709
|
+
* with two dots, such as `..cache`, is inside. Symlinks are not followed, so a
|
|
98710
|
+
* caller that cares where a link points has to compare resolved paths.
|
|
98639
98711
|
*
|
|
98640
|
-
*
|
|
98641
|
-
*
|
|
98642
|
-
*
|
|
98643
|
-
* @module WorkspaceEntries
|
|
98644
|
-
*/
|
|
98645
|
-
var WorkspaceEntriesError = class extends require_Schema$1.TaggedErrorClass()("WorkspaceEntriesError", {
|
|
98646
|
-
cwd: require_Schema$1.String,
|
|
98647
|
-
operation: require_Schema$1.String,
|
|
98648
|
-
detail: require_Schema$1.String,
|
|
98649
|
-
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
98650
|
-
}) {};
|
|
98651
|
-
/**
|
|
98652
|
-
* WorkspaceEntries - Service tag for cached workspace entry search.
|
|
98653
|
-
*/
|
|
98654
|
-
var WorkspaceEntries = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceEntries") {};
|
|
98655
|
-
//#endregion
|
|
98656
|
-
//#region src/workspace/Services/WorkspaceBulkTransfer.ts
|
|
98657
|
-
var WorkspaceBulkTransfer = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceBulkTransfer") {};
|
|
98658
|
-
//#endregion
|
|
98659
|
-
//#region src/workspace/Layers/WorkspaceBulkTransfer.ts
|
|
98660
|
-
const TRANSFER_TTL_MS = 1440 * 60 * 1e3;
|
|
98661
|
-
const TRANSFER_LOCK_STRIPE_COUNT = 64;
|
|
98662
|
-
function transferError(input) {
|
|
98663
|
-
return new BulkTransferError({
|
|
98664
|
-
code: input.code,
|
|
98665
|
-
message: input.message,
|
|
98666
|
-
transferId: input.transferId ?? null,
|
|
98667
|
-
retryable: input.retryable ?? false,
|
|
98668
|
-
...input.cause === void 0 ? {} : { cause: input.cause }
|
|
98669
|
-
});
|
|
98670
|
-
}
|
|
98671
|
-
function mapFailure(operation, transferId) {
|
|
98672
|
-
return (cause) => require_Schema$1.is(BulkTransferError)(cause) ? cause : transferError({
|
|
98673
|
-
code: "io_error",
|
|
98674
|
-
message: `Workspace bulk transfer failed during ${operation}.`,
|
|
98675
|
-
...transferId === void 0 ? {} : { transferId },
|
|
98676
|
-
retryable: true,
|
|
98677
|
-
cause
|
|
98678
|
-
});
|
|
98679
|
-
}
|
|
98680
|
-
/**
|
|
98681
|
-
* Exported so the unary file read in `projectFile/readProjectFile.ts` uses this
|
|
98682
|
-
* containment check rather than a second copy of it. Two copies of a security
|
|
98683
|
-
* rule in one repo is how they drift, and the drift is silent.
|
|
98712
|
+
* Only a type import, so a module that has to stay free of Effect services can
|
|
98713
|
+
* use it without importing a Layer.
|
|
98684
98714
|
*/
|
|
98685
98715
|
function isPathWithinRoot(path, root, candidate) {
|
|
98686
98716
|
const relative = path.relative(root, candidate).replaceAll("\\", "/");
|
|
98687
98717
|
return relative === "" || !relative.startsWith("../") && relative !== ".." && !path.isAbsolute(relative);
|
|
98688
98718
|
}
|
|
98689
|
-
function transferLockStripe(transferId) {
|
|
98690
|
-
let hash = 2166136261;
|
|
98691
|
-
for (let index = 0; index < transferId.length; index += 1) hash = Math.imul(hash ^ transferId.charCodeAt(index), 16777619);
|
|
98692
|
-
return (hash >>> 0) % TRANSFER_LOCK_STRIPE_COUNT;
|
|
98693
|
-
}
|
|
98694
|
-
const makeWorkspaceBulkTransfer = (options = {}) => require_Schema$1.gen(function* () {
|
|
98695
|
-
const fileSystem = yield* require_Path.FileSystem;
|
|
98696
|
-
const path = yield* require_Path.Path;
|
|
98697
|
-
const sql = yield* require_SqlError.SqlClient;
|
|
98698
|
-
const workspacePaths = yield* WorkspacePaths;
|
|
98699
|
-
const workspaceEntries = yield* WorkspaceEntries;
|
|
98700
|
-
const fault = options.fault ?? (() => require_Schema$1.void_);
|
|
98701
|
-
const transferLocks = yield* require_Schema$1.forEach(Array.from({ length: TRANSFER_LOCK_STRIPE_COUNT }), () => require_Path.make$5(1));
|
|
98702
|
-
const withTransferLock = (transferId, effect) => transferLocks[transferLockStripe(transferId)].withPermit(effect);
|
|
98703
|
-
const findTransfer = (transferId) => sql`
|
|
98704
|
-
SELECT transfer_id AS "transferId", workspace_root AS "workspaceRoot",
|
|
98705
|
-
relative_path AS "relativePath", target_path AS "targetPath", temp_path AS "tempPath",
|
|
98706
|
-
total_bytes AS "totalBytes", whole_sha256 AS "wholeSha256",
|
|
98707
|
-
next_offset AS "nextOffset", state, expires_at AS "expiresAt"
|
|
98708
|
-
FROM workspace_bulk_transfers WHERE transfer_id = ${transferId} LIMIT 1
|
|
98709
|
-
`.pipe(require_Schema$1.map((rows) => rows[0] ?? null));
|
|
98710
|
-
const requireTransfer = (transferId) => findTransfer(transferId).pipe(require_Schema$1.flatMap((row) => row === null ? require_Schema$1.fail(transferError({
|
|
98711
|
-
code: "transfer_not_found",
|
|
98712
|
-
message: "Bulk transfer does not exist.",
|
|
98713
|
-
transferId
|
|
98714
|
-
})) : require_Schema$1.succeed(row)));
|
|
98715
|
-
const requireOpenTransfer = (transferId) => requireTransfer(transferId).pipe(require_Schema$1.flatMap((row) => {
|
|
98716
|
-
if (row.state === "cancelled") return require_Schema$1.fail(transferError({
|
|
98717
|
-
code: "transfer_cancelled",
|
|
98718
|
-
message: "Bulk transfer was cancelled.",
|
|
98719
|
-
transferId
|
|
98720
|
-
}));
|
|
98721
|
-
if (row.state === "expired" || Date.parse(row.expiresAt) <= Date.now()) return require_Schema$1.fail(transferError({
|
|
98722
|
-
code: "transfer_expired",
|
|
98723
|
-
message: "Bulk transfer expired.",
|
|
98724
|
-
transferId
|
|
98725
|
-
}));
|
|
98726
|
-
return require_Schema$1.succeed(row);
|
|
98727
|
-
}));
|
|
98728
|
-
const resolveTarget = require_Schema$1.fn("WorkspaceBulkTransfer.resolveTarget")(function* (input) {
|
|
98729
|
-
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd);
|
|
98730
|
-
const resolved = yield* workspacePaths.resolveRelativePathWithinRoot({
|
|
98731
|
-
workspaceRoot,
|
|
98732
|
-
relativePath: input.relativePath
|
|
98733
|
-
});
|
|
98734
|
-
const canonicalRoot = yield* fileSystem.realPath(workspaceRoot);
|
|
98735
|
-
const parentSegments = resolved.relativePath.split("/").slice(0, -1);
|
|
98736
|
-
let canonicalParent = canonicalRoot;
|
|
98737
|
-
for (const segment of parentSegments) {
|
|
98738
|
-
const candidate = path.join(canonicalParent, segment);
|
|
98739
|
-
const canonicalCandidate = yield* fileSystem.realPath(candidate).pipe(require_Schema$1.catch_(() => input.createParent ? fileSystem.makeDirectory(candidate).pipe(require_Schema$1.flatMap(() => fileSystem.realPath(candidate))) : fileSystem.realPath(candidate)));
|
|
98740
|
-
if (!isPathWithinRoot(path, canonicalRoot, canonicalCandidate)) return yield* transferError({
|
|
98741
|
-
code: "symlink_escape",
|
|
98742
|
-
message: "Workspace file path resolves outside the project root."
|
|
98743
|
-
});
|
|
98744
|
-
canonicalParent = canonicalCandidate;
|
|
98745
|
-
}
|
|
98746
|
-
const targetPath = path.join(canonicalParent, path.basename(resolved.relativePath));
|
|
98747
|
-
const targetRealPath = yield* fileSystem.realPath(targetPath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
|
|
98748
|
-
if (targetRealPath !== null && !isPathWithinRoot(path, canonicalRoot, targetRealPath)) return yield* transferError({
|
|
98749
|
-
code: "symlink_escape",
|
|
98750
|
-
message: "Workspace file target resolves outside the project root."
|
|
98751
|
-
});
|
|
98752
|
-
return {
|
|
98753
|
-
workspaceRoot: canonicalRoot,
|
|
98754
|
-
relativePath: resolved.relativePath,
|
|
98755
|
-
targetPath,
|
|
98756
|
-
canonicalParent
|
|
98757
|
-
};
|
|
98758
|
-
});
|
|
98759
|
-
const readFile = (input) => require_Path.unwrap(require_Schema$1.gen(function* () {
|
|
98760
|
-
const target = yield* resolveTarget({
|
|
98761
|
-
...input,
|
|
98762
|
-
createParent: false
|
|
98763
|
-
});
|
|
98764
|
-
const info = yield* fileSystem.stat(target.targetPath);
|
|
98765
|
-
const maxBytes = input.maxBytes ?? 16777216;
|
|
98766
|
-
if (Number(info.size) > maxBytes) return yield* transferError({
|
|
98767
|
-
code: "size_limit_exceeded",
|
|
98768
|
-
message: `Workspace file exceeds the ${maxBytes}-byte read limit.`
|
|
98769
|
-
});
|
|
98770
|
-
const bytes = yield* fileSystem.readFile(target.targetPath);
|
|
98771
|
-
const transfer = yield* require_Schema$1.promise(() => chunkBulkTransfer(bytes));
|
|
98772
|
-
const transferId = transfer.chunks[0]?.transferId ?? crypto.randomUUID().replaceAll("-", "_");
|
|
98773
|
-
return require_Path.fromIterable([
|
|
98774
|
-
{
|
|
98775
|
-
kind: "started",
|
|
98776
|
-
transferId,
|
|
98777
|
-
totalBytes: transfer.totalBytes,
|
|
98778
|
-
sha256: transfer.sha256
|
|
98779
|
-
},
|
|
98780
|
-
...transfer.chunks.map((chunk) => ({
|
|
98781
|
-
...chunk,
|
|
98782
|
-
transferId
|
|
98783
|
-
})),
|
|
98784
|
-
{
|
|
98785
|
-
kind: "completed",
|
|
98786
|
-
transferId,
|
|
98787
|
-
totalBytes: transfer.totalBytes,
|
|
98788
|
-
sha256: transfer.sha256
|
|
98789
|
-
}
|
|
98790
|
-
]);
|
|
98791
|
-
}).pipe(require_Schema$1.mapError(mapFailure("read file"))));
|
|
98792
|
-
const resumeWrite = require_Schema$1.fn("WorkspaceBulkTransfer.resumeWrite")(function* (input, existing) {
|
|
98793
|
-
const target = yield* resolveTarget({
|
|
98794
|
-
...input,
|
|
98795
|
-
createParent: true
|
|
98796
|
-
});
|
|
98797
|
-
if (!(existing.workspaceRoot === target.workspaceRoot && existing.relativePath === target.relativePath && existing.totalBytes === input.totalBytes && existing.wholeSha256 === input.sha256)) return yield* transferError({
|
|
98798
|
-
code: "transfer_identity_conflict",
|
|
98799
|
-
message: "Bulk transfer ID is already bound to different content or a different path.",
|
|
98800
|
-
transferId: input.transferId
|
|
98801
|
-
});
|
|
98802
|
-
if (existing.state === "cancelled") return yield* transferError({
|
|
98803
|
-
code: "transfer_cancelled",
|
|
98804
|
-
message: "Bulk transfer was cancelled.",
|
|
98805
|
-
transferId: input.transferId
|
|
98806
|
-
});
|
|
98807
|
-
if (existing.state === "expired" || Date.parse(existing.expiresAt) <= Date.now()) return yield* transferError({
|
|
98808
|
-
code: "transfer_expired",
|
|
98809
|
-
message: "Bulk transfer expired.",
|
|
98810
|
-
transferId: input.transferId
|
|
98811
|
-
});
|
|
98812
|
-
return {
|
|
98813
|
-
transferId: input.transferId,
|
|
98814
|
-
nextOffset: existing.nextOffset,
|
|
98815
|
-
state: existing.state === "completed" ? "completed" : "open"
|
|
98816
|
-
};
|
|
98817
|
-
});
|
|
98818
|
-
const createWrite = require_Schema$1.fn("WorkspaceBulkTransfer.createWrite")(function* (input) {
|
|
98819
|
-
const target = yield* resolveTarget({
|
|
98820
|
-
...input,
|
|
98821
|
-
createParent: true
|
|
98822
|
-
}).pipe(require_Schema$1.mapError((cause) => require_Schema$1.is(WorkspacePathOutsideRootError)(cause) ? transferError({
|
|
98823
|
-
code: "path_outside_workspace",
|
|
98824
|
-
message: "Workspace file path must stay within the project root.",
|
|
98825
|
-
transferId: input.transferId,
|
|
98826
|
-
cause
|
|
98827
|
-
}) : cause));
|
|
98828
|
-
const tempPath = path.join(target.canonicalParent, `.forge-transfer-${input.transferId}.part`);
|
|
98829
|
-
yield* require_Schema$1.scoped(fileSystem.open(tempPath, {
|
|
98830
|
-
flag: "wx",
|
|
98831
|
-
mode: 384
|
|
98832
|
-
}).pipe(require_Schema$1.flatMap((file) => file.sync), require_Schema$1.catch_(() => fileSystem.stat(tempPath).pipe(require_Schema$1.flatMap((stat) => Number(stat.size) === 0 ? require_Schema$1.void_ : require_Schema$1.fail(transferError({
|
|
98833
|
-
code: "transfer_identity_conflict",
|
|
98834
|
-
message: "Bulk transfer temporary path already contains data.",
|
|
98835
|
-
transferId: input.transferId
|
|
98836
|
-
})))))));
|
|
98837
|
-
const now = /* @__PURE__ */ new Date();
|
|
98838
|
-
yield* sql`
|
|
98839
|
-
INSERT INTO workspace_bulk_transfers (
|
|
98840
|
-
transfer_id, workspace_root, relative_path, target_path, temp_path, total_bytes,
|
|
98841
|
-
whole_sha256, next_offset, state, expires_at, created_at, updated_at
|
|
98842
|
-
) VALUES (
|
|
98843
|
-
${input.transferId}, ${target.workspaceRoot}, ${target.relativePath}, ${target.targetPath},
|
|
98844
|
-
${tempPath}, ${input.totalBytes}, ${input.sha256}, 0, 'open',
|
|
98845
|
-
${new Date(now.getTime() + TRANSFER_TTL_MS).toISOString()}, ${now.toISOString()},
|
|
98846
|
-
${now.toISOString()}
|
|
98847
|
-
)
|
|
98848
|
-
`;
|
|
98849
|
-
return {
|
|
98850
|
-
transferId: input.transferId,
|
|
98851
|
-
nextOffset: 0,
|
|
98852
|
-
state: "open"
|
|
98853
|
-
};
|
|
98854
|
-
});
|
|
98855
|
-
const beginWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.beginWrite")(function* (input) {
|
|
98856
|
-
const existing = yield* findTransfer(input.transferId);
|
|
98857
|
-
return yield* existing === null ? createWrite(input) : resumeWrite(input, existing);
|
|
98858
|
-
});
|
|
98859
|
-
const beginWrite = (input) => withTransferLock(input.transferId, beginWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("begin write", input.transferId)));
|
|
98860
|
-
const acceptRetriedChunk = require_Schema$1.fn("WorkspaceBulkTransfer.acceptRetriedChunk")(function* (input, row) {
|
|
98861
|
-
const accepted = (yield* sql`
|
|
98862
|
-
SELECT chunk_offset AS "chunkOffset", chunk_bytes AS "chunkBytes",
|
|
98863
|
-
chunk_sha256 AS "chunkSha256"
|
|
98864
|
-
FROM workspace_bulk_transfer_chunks
|
|
98865
|
-
WHERE transfer_id = ${input.transferId} AND chunk_offset = ${input.offset}
|
|
98866
|
-
LIMIT 1
|
|
98867
|
-
`)[0];
|
|
98868
|
-
if (!(accepted !== void 0 && accepted.chunkBytes === input.bytes && accepted.chunkSha256 === input.sha256)) return yield* transferError({
|
|
98869
|
-
code: "integrity_mismatch",
|
|
98870
|
-
message: "Retried bulk transfer chunk differs from the accepted chunk.",
|
|
98871
|
-
transferId: input.transferId
|
|
98872
|
-
});
|
|
98873
|
-
return {
|
|
98874
|
-
transferId: input.transferId,
|
|
98875
|
-
nextOffset: row.nextOffset,
|
|
98876
|
-
duplicate: true
|
|
98877
|
-
};
|
|
98878
|
-
});
|
|
98879
|
-
const validateChunkRange = require_Schema$1.fn("WorkspaceBulkTransfer.validateChunkRange")(function* (input, row, byteLength) {
|
|
98880
|
-
if (input.offset !== row.nextOffset) return yield* transferError({
|
|
98881
|
-
code: "offset_mismatch",
|
|
98882
|
-
message: `Expected chunk offset ${row.nextOffset}, received ${input.offset}.`,
|
|
98883
|
-
transferId: input.transferId,
|
|
98884
|
-
retryable: true
|
|
98885
|
-
});
|
|
98886
|
-
const nextOffset = input.offset + byteLength;
|
|
98887
|
-
if (nextOffset > row.totalBytes) return yield* transferError({
|
|
98888
|
-
code: "size_limit_exceeded",
|
|
98889
|
-
message: "Bulk transfer chunk exceeds the declared total byte count.",
|
|
98890
|
-
transferId: input.transferId
|
|
98891
|
-
});
|
|
98892
|
-
return nextOffset;
|
|
98893
|
-
});
|
|
98894
|
-
const reconcileChunkBytes = require_Schema$1.fn("WorkspaceBulkTransfer.reconcileChunkBytes")(function* (input, row, bytes, nextOffset) {
|
|
98895
|
-
const tempInfo = yield* fileSystem.stat(row.tempPath);
|
|
98896
|
-
if (Number(tempInfo.size) > row.nextOffset) {
|
|
98897
|
-
if (Number(tempInfo.size) !== nextOffset) return yield* transferError({
|
|
98898
|
-
code: "integrity_mismatch",
|
|
98899
|
-
message: "Bulk transfer temporary file cannot be reconciled with the next chunk.",
|
|
98900
|
-
transferId: input.transferId
|
|
98901
|
-
});
|
|
98902
|
-
const recovered = (yield* fileSystem.readFile(row.tempPath)).slice(input.offset, nextOffset);
|
|
98903
|
-
if ((yield* require_Schema$1.promise(() => sha256Hex(recovered))) !== input.sha256) return yield* transferError({
|
|
98904
|
-
code: "integrity_mismatch",
|
|
98905
|
-
message: "Bulk transfer temporary bytes differ from the retried chunk.",
|
|
98906
|
-
transferId: input.transferId
|
|
98907
|
-
});
|
|
98908
|
-
return;
|
|
98909
|
-
}
|
|
98910
|
-
yield* fault("before-byte-write");
|
|
98911
|
-
yield* require_Schema$1.scoped(fileSystem.open(row.tempPath, { flag: "r+" }).pipe(require_Schema$1.flatMap((file) => file.seek(input.offset, "start").pipe(require_Schema$1.flatMap(() => file.writeAll(bytes)), require_Schema$1.flatMap(() => file.sync)))));
|
|
98912
|
-
yield* fault("after-byte-write-before-metadata");
|
|
98913
|
-
});
|
|
98914
|
-
const persistAcceptedChunk = require_Schema$1.fn("WorkspaceBulkTransfer.persistAcceptedChunk")(function* (input, nextOffset) {
|
|
98915
|
-
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
98916
|
-
yield* sql.withTransaction(sql`
|
|
98917
|
-
INSERT INTO workspace_bulk_transfer_chunks (
|
|
98918
|
-
transfer_id, chunk_offset, chunk_bytes, chunk_sha256
|
|
98919
|
-
) VALUES (${input.transferId}, ${input.offset}, ${input.bytes}, ${input.sha256})
|
|
98920
|
-
ON CONFLICT DO NOTHING
|
|
98921
|
-
`.pipe(require_Schema$1.flatMap(() => sql`
|
|
98922
|
-
UPDATE workspace_bulk_transfers
|
|
98923
|
-
SET next_offset = ${nextOffset}, updated_at = ${updatedAt}
|
|
98924
|
-
WHERE transfer_id = ${input.transferId} AND next_offset = ${input.offset} AND state = 'open'
|
|
98925
|
-
`)));
|
|
98926
|
-
yield* fault("after-metadata-advance");
|
|
98927
|
-
});
|
|
98928
|
-
const writeChunkEffect = require_Schema$1.fn("WorkspaceBulkTransfer.writeChunk")(function* (input) {
|
|
98929
|
-
const bytes = yield* require_Schema$1.promise(() => verifyBulkTransferChunk({
|
|
98930
|
-
...input,
|
|
98931
|
-
kind: "chunk"
|
|
98932
|
-
}));
|
|
98933
|
-
const row = yield* requireOpenTransfer(input.transferId);
|
|
98934
|
-
if (row.state === "completed") return yield* transferError({
|
|
98935
|
-
code: "offset_mismatch",
|
|
98936
|
-
message: "Bulk transfer is already complete.",
|
|
98937
|
-
transferId: input.transferId
|
|
98938
|
-
});
|
|
98939
|
-
if (input.offset < row.nextOffset) return yield* acceptRetriedChunk(input, row);
|
|
98940
|
-
const nextOffset = yield* validateChunkRange(input, row, bytes.byteLength);
|
|
98941
|
-
yield* reconcileChunkBytes(input, row, bytes, nextOffset);
|
|
98942
|
-
yield* persistAcceptedChunk(input, nextOffset);
|
|
98943
|
-
return {
|
|
98944
|
-
transferId: input.transferId,
|
|
98945
|
-
nextOffset,
|
|
98946
|
-
duplicate: false
|
|
98947
|
-
};
|
|
98948
|
-
});
|
|
98949
|
-
const writeChunk = (input) => withTransferLock(input.transferId, writeChunkEffect(input)).pipe(require_Schema$1.mapError(mapFailure("write chunk", input.transferId)));
|
|
98950
|
-
const commitWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.commitWrite")(function* (input) {
|
|
98951
|
-
const row = yield* requireOpenTransfer(input.transferId);
|
|
98952
|
-
if (row.state === "completed") return {
|
|
98953
|
-
transferId: row.transferId,
|
|
98954
|
-
relativePath: row.relativePath,
|
|
98955
|
-
totalBytes: row.totalBytes,
|
|
98956
|
-
sha256: row.wholeSha256
|
|
98957
|
-
};
|
|
98958
|
-
if (row.nextOffset !== row.totalBytes) return yield* transferError({
|
|
98959
|
-
code: "offset_mismatch",
|
|
98960
|
-
message: `Bulk transfer is incomplete at byte ${row.nextOffset} of ${row.totalBytes}.`,
|
|
98961
|
-
transferId: row.transferId,
|
|
98962
|
-
retryable: true
|
|
98963
|
-
});
|
|
98964
|
-
if ((yield* resolveTarget({
|
|
98965
|
-
cwd: row.workspaceRoot,
|
|
98966
|
-
relativePath: row.relativePath,
|
|
98967
|
-
createParent: false
|
|
98968
|
-
})).targetPath !== row.targetPath) return yield* transferError({
|
|
98969
|
-
code: "symlink_escape",
|
|
98970
|
-
message: "Workspace file destination changed during transfer.",
|
|
98971
|
-
transferId: row.transferId
|
|
98972
|
-
});
|
|
98973
|
-
const tempInfo = yield* fileSystem.stat(row.tempPath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
|
|
98974
|
-
const candidatePath = tempInfo === null ? row.targetPath : row.tempPath;
|
|
98975
|
-
const candidate = yield* fileSystem.readFile(candidatePath);
|
|
98976
|
-
if (candidate.byteLength !== row.totalBytes || (yield* require_Schema$1.promise(() => sha256Hex(candidate))) !== row.wholeSha256) return yield* transferError({
|
|
98977
|
-
code: "integrity_mismatch",
|
|
98978
|
-
message: "Bulk transfer payload does not match its declared size and digest.",
|
|
98979
|
-
transferId: row.transferId
|
|
98980
|
-
});
|
|
98981
|
-
if (tempInfo !== null) {
|
|
98982
|
-
yield* fileSystem.rename(row.tempPath, row.targetPath);
|
|
98983
|
-
yield* fault("after-atomic-rename");
|
|
98984
|
-
}
|
|
98985
|
-
yield* sql`
|
|
98986
|
-
UPDATE workspace_bulk_transfers SET state = 'completed', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
98987
|
-
WHERE transfer_id = ${row.transferId}
|
|
98988
|
-
`;
|
|
98989
|
-
yield* workspaceEntries.invalidate(row.workspaceRoot);
|
|
98990
|
-
return {
|
|
98991
|
-
transferId: row.transferId,
|
|
98992
|
-
relativePath: row.relativePath,
|
|
98993
|
-
totalBytes: row.totalBytes,
|
|
98994
|
-
sha256: row.wholeSha256
|
|
98995
|
-
};
|
|
98996
|
-
});
|
|
98997
|
-
const commitWrite = (input) => withTransferLock(input.transferId, commitWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("commit write", input.transferId)));
|
|
98998
|
-
const cancelWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.cancelWrite")(function* (input) {
|
|
98999
|
-
const row = yield* requireTransfer(input.transferId);
|
|
99000
|
-
if (row.state !== "completed") {
|
|
99001
|
-
yield* fileSystem.remove(row.tempPath, { force: true });
|
|
99002
|
-
yield* sql`
|
|
99003
|
-
UPDATE workspace_bulk_transfers SET state = 'cancelled', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
99004
|
-
WHERE transfer_id = ${input.transferId}
|
|
99005
|
-
`;
|
|
99006
|
-
}
|
|
99007
|
-
return {
|
|
99008
|
-
transferId: input.transferId,
|
|
99009
|
-
state: row.state === "completed" ? "completed" : "cancelled"
|
|
99010
|
-
};
|
|
99011
|
-
});
|
|
99012
|
-
const cancelWrite = (input) => withTransferLock(input.transferId, cancelWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("cancel write", input.transferId)));
|
|
99013
|
-
const cleanupExpiredEffect = require_Schema$1.fn("WorkspaceBulkTransfer.cleanupExpired")(function* () {
|
|
99014
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
99015
|
-
const rows = yield* sql`
|
|
99016
|
-
SELECT transfer_id AS "transferId", temp_path AS "tempPath"
|
|
99017
|
-
FROM workspace_bulk_transfers WHERE state = 'open' AND expires_at <= ${now}
|
|
99018
|
-
`;
|
|
99019
|
-
let expiredCount = 0;
|
|
99020
|
-
for (const row of rows) if (yield* withTransferLock(row.transferId, require_Schema$1.gen(function* () {
|
|
99021
|
-
const current = yield* findTransfer(row.transferId);
|
|
99022
|
-
if (current === null || current.state !== "open" || Date.parse(current.expiresAt) > Date.parse(now)) return false;
|
|
99023
|
-
yield* fileSystem.remove(current.tempPath, { force: true });
|
|
99024
|
-
yield* sql`
|
|
99025
|
-
UPDATE workspace_bulk_transfers SET state = 'expired', updated_at = ${now}
|
|
99026
|
-
WHERE transfer_id = ${row.transferId} AND state = 'open' AND expires_at <= ${now}
|
|
99027
|
-
`;
|
|
99028
|
-
return true;
|
|
99029
|
-
}))) expiredCount += 1;
|
|
99030
|
-
return expiredCount;
|
|
99031
|
-
});
|
|
99032
|
-
const cleanupExpired = () => cleanupExpiredEffect().pipe(require_Schema$1.mapError(mapFailure("expiry cleanup")));
|
|
99033
|
-
return {
|
|
99034
|
-
readFile,
|
|
99035
|
-
beginWrite,
|
|
99036
|
-
writeChunk,
|
|
99037
|
-
commitWrite,
|
|
99038
|
-
cancelWrite,
|
|
99039
|
-
cleanupExpired
|
|
99040
|
-
};
|
|
99041
|
-
});
|
|
99042
|
-
const WorkspaceBulkTransferLive = require_Schema$1.effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
|
|
99043
98719
|
//#endregion
|
|
99044
98720
|
//#region src/projectFile/readProjectFile.ts
|
|
99045
98721
|
/**
|
|
@@ -99843,6 +99519,26 @@ const DevTunnelLive = require_Schema$1.effect(DevTunnel, require_Schema$1.gen(fu
|
|
|
99843
99519
|
};
|
|
99844
99520
|
}));
|
|
99845
99521
|
//#endregion
|
|
99522
|
+
//#region src/workspace/Services/WorkspaceEntries.ts
|
|
99523
|
+
/**
|
|
99524
|
+
* WorkspaceEntries - Effect service contract for cached workspace entry search.
|
|
99525
|
+
*
|
|
99526
|
+
* Owns indexed workspace entry search plus cache invalidation for workspace
|
|
99527
|
+
* roots when the underlying filesystem changes.
|
|
99528
|
+
*
|
|
99529
|
+
* @module WorkspaceEntries
|
|
99530
|
+
*/
|
|
99531
|
+
var WorkspaceEntriesError = class extends require_Schema$1.TaggedErrorClass()("WorkspaceEntriesError", {
|
|
99532
|
+
cwd: require_Schema$1.String,
|
|
99533
|
+
operation: require_Schema$1.String,
|
|
99534
|
+
detail: require_Schema$1.String,
|
|
99535
|
+
cause: require_Schema$1.optional(require_Schema$1.Defect)
|
|
99536
|
+
}) {};
|
|
99537
|
+
/**
|
|
99538
|
+
* WorkspaceEntries - Service tag for cached workspace entry search.
|
|
99539
|
+
*/
|
|
99540
|
+
var WorkspaceEntries = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceEntries") {};
|
|
99541
|
+
//#endregion
|
|
99846
99542
|
//#region src/workspace/Services/WorkspaceFileSystem.ts
|
|
99847
99543
|
/**
|
|
99848
99544
|
* WorkspaceFileSystem - Effect service contract for workspace file mutations.
|
|
@@ -99864,6 +99560,9 @@ var WorkspaceFileSystemError = class extends require_Schema$1.TaggedErrorClass()
|
|
|
99864
99560
|
*/
|
|
99865
99561
|
var WorkspaceFileSystem = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceFileSystem") {};
|
|
99866
99562
|
//#endregion
|
|
99563
|
+
//#region src/workspace/Services/WorkspaceBulkTransfer.ts
|
|
99564
|
+
var WorkspaceBulkTransfer = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceBulkTransfer") {};
|
|
99565
|
+
//#endregion
|
|
99867
99566
|
//#region src/workspace/Services/WorkspaceReadinessState.ts
|
|
99868
99567
|
var WorkspaceReadinessState = class extends require_Schema$1.Service()("forge/workspace/Services/WorkspaceReadinessState") {};
|
|
99869
99568
|
//#endregion
|
|
@@ -105325,6 +105024,13 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
105325
105024
|
message: "Failed to replay orchestration events",
|
|
105326
105025
|
cause
|
|
105327
105026
|
}))), { "rpc.aggregate": "orchestration" }),
|
|
105027
|
+
[ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot]: (_input) => observeRpcEffect(ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, require_Schema$1.gen(function* () {
|
|
105028
|
+
yield* startup.awaitCommandReady;
|
|
105029
|
+
return yield* projectionSnapshotQuery.getArchivedShellSnapshot();
|
|
105030
|
+
}).pipe(require_Schema$1.mapError((cause) => new OrchestrationGetSnapshotError({
|
|
105031
|
+
message: "Failed to load archived orchestration shell snapshot",
|
|
105032
|
+
cause
|
|
105033
|
+
}))), { "rpc.aggregate": "orchestration" }),
|
|
105328
105034
|
[ORCHESTRATION_WS_METHODS.subscribeShell]: (_input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeShell, require_Schema$1.succeed(makeSnapshotThenLiveStream({
|
|
105329
105035
|
loadSnapshot: loadInitialShellSnapshot({
|
|
105330
105036
|
startup,
|
|
@@ -211344,56 +211050,169 @@ function hasConfiguredApexCredential(profiles) {
|
|
|
211344
211050
|
}));
|
|
211345
211051
|
}
|
|
211346
211052
|
//#endregion
|
|
211347
|
-
//#region src/provider/
|
|
211053
|
+
//#region src/provider/claudeCredentialDirectory.ts
|
|
211348
211054
|
/**
|
|
211349
|
-
*
|
|
211055
|
+
* Per-account credential directories for Claude turns.
|
|
211350
211056
|
*
|
|
211351
|
-
*
|
|
211352
|
-
*
|
|
211353
|
-
*
|
|
211354
|
-
*
|
|
211057
|
+
* Claude Code keeps conversation transcripts under
|
|
211058
|
+
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
211059
|
+
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
211060
|
+
* credential file, so each Provider Account can hold its own login while every
|
|
211061
|
+
* session file stays in one place and `--resume` keeps finding its target. See
|
|
211062
|
+
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
211355
211063
|
*
|
|
211356
|
-
*
|
|
211357
|
-
*
|
|
211358
|
-
*
|
|
211359
|
-
*
|
|
211064
|
+
* That variable is not documented. Under
|
|
211065
|
+
* `docs/adr/unpublished-provider-surface-needs-a-fallback.md` the dependency is
|
|
211066
|
+
* only allowed because absence degrades to a named behaviour that already
|
|
211067
|
+
* works: a thread with no account runs on the Environment's own login. This
|
|
211068
|
+
* module sets the variable only when the resolved directory holds a credential
|
|
211069
|
+
* the CLI can use, because a missing file, or one whose access token a refusal
|
|
211070
|
+
* has blanked, makes the CLI report logged out and a live turn would fail.
|
|
211360
211071
|
*
|
|
211361
|
-
*
|
|
211362
|
-
*
|
|
211363
|
-
*
|
|
211364
|
-
*
|
|
211365
|
-
* /api/provider-egress/*, leaving the fail-closed billing gate inert.
|
|
211072
|
+
* Nothing writes these directories yet. #3049 adds the writer and keys each
|
|
211073
|
+
* directory on the account identity CMMD writes on the delivery receipt it
|
|
211074
|
+
* signs (`docs/adr/provider-account-identity-is-issued-by-cmmd.md`). Until then
|
|
211075
|
+
* every runtime takes the `no-account` skip.
|
|
211366
211076
|
*
|
|
211367
|
-
*
|
|
211368
|
-
*
|
|
211369
|
-
*
|
|
211077
|
+
* Free of Effect services on purpose, for the same reason as
|
|
211078
|
+
* `claudeCredentialContainment.ts`: ClaudeAdapter drags in Effect platform
|
|
211079
|
+
* services, the browser manager and the server config, so a unit test that
|
|
211080
|
+
* imports it cannot run in a worktree. This rule decides which account pays for
|
|
211081
|
+
* a turn, so it has to stay directly testable.
|
|
211370
211082
|
*/
|
|
211371
|
-
/**
|
|
211372
|
-
const
|
|
211083
|
+
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
211084
|
+
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
211085
|
+
/** The one file Claude Code writes into the credential directory. */
|
|
211086
|
+
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
211373
211087
|
/**
|
|
211374
|
-
*
|
|
211375
|
-
*
|
|
211376
|
-
*
|
|
211377
|
-
*
|
|
211378
|
-
*
|
|
211379
|
-
*
|
|
211088
|
+
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
211089
|
+
* belong to. Nothing in Forge or CMMD sets it, so no runtime names an account
|
|
211090
|
+
* and each keeps its one login.
|
|
211091
|
+
*
|
|
211092
|
+
* It is a bridge that will not get a writer. #3073 settled the key in
|
|
211093
|
+
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`: the directory name
|
|
211094
|
+
* comes from the identity CMMD issues and writes on the delivery receipt it
|
|
211095
|
+
* signs, and this variable goes away. #3049 makes that change. The credential
|
|
211096
|
+
* generation was ruled out as a key: it rotates on every reconnect, and CMMD
|
|
211097
|
+
* uses it as the primary account's id, so keying on it would strand every
|
|
211098
|
+
* thread pinned to that account on a directory that stops receiving deliveries.
|
|
211099
|
+
*
|
|
211100
|
+
* Tests reach the `credentialDirectory` parameter on
|
|
211101
|
+
* `ClaudeAdapter.buildClaudeQueryEnv` directly.
|
|
211380
211102
|
*/
|
|
211381
|
-
const
|
|
211103
|
+
const CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV = "FORGE_CLAUDE_PROVIDER_ACCOUNT_IDENTITY";
|
|
211104
|
+
/** Directory under the server's base directory that holds one directory per account. */
|
|
211105
|
+
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
211106
|
+
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
211107
|
+
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
211108
|
+
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
211382
211109
|
/**
|
|
211383
|
-
*
|
|
211384
|
-
*
|
|
211385
|
-
|
|
211386
|
-
|
|
211110
|
+
* Directory name for an account. A hash, so no email or display name reaches a
|
|
211111
|
+
* filesystem path and no path-illegal character can appear in one either.
|
|
211112
|
+
*/
|
|
211113
|
+
function claudeCredentialDirectoryName(accountIdentity) {
|
|
211114
|
+
return `account-${(0, node_crypto.createHash)("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
211115
|
+
}
|
|
211116
|
+
/** The account this runtime's Claude turns belong to, or null while the variable is unset. */
|
|
211117
|
+
function resolveClaudeProviderAccountIdentity(env) {
|
|
211118
|
+
const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
|
|
211119
|
+
if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
|
|
211120
|
+
return identity;
|
|
211121
|
+
}
|
|
211122
|
+
/**
|
|
211123
|
+
* Where per-account credential directories live: `claude-credentials` under the
|
|
211124
|
+
* server's base directory, `ServerConfig.baseDir`, which is `FORGE_HOME` or
|
|
211125
|
+
* `~/.forge`. A delivery writer gets the same `ServerConfig`, so reader and
|
|
211126
|
+
* writer cannot disagree about the root. Always outside `CLAUDE_CONFIG_DIR`, so
|
|
211127
|
+
* one of these can never be mistaken for the main credential store, and a stray
|
|
211128
|
+
* read of the config directory cannot walk into another account's login.
|
|
211129
|
+
*/
|
|
211130
|
+
function claudeCredentialDirectoryRoot(env, baseDir) {
|
|
211131
|
+
if (!(0, node_path.isAbsolute)(baseDir)) return null;
|
|
211132
|
+
const root = (0, node_path.resolve)(baseDir, CREDENTIAL_ROOT_DIRECTORY_NAME);
|
|
211133
|
+
const configDirectory = claudeConfigDirectory(env);
|
|
211134
|
+
if (configDirectory && isPathWithinRoot({
|
|
211135
|
+
relative: node_path.relative,
|
|
211136
|
+
isAbsolute: node_path.isAbsolute
|
|
211137
|
+
}, configDirectory, root)) return null;
|
|
211138
|
+
return root;
|
|
211139
|
+
}
|
|
211140
|
+
/** Absolute credential directory for one account, or null when either half is missing. */
|
|
211141
|
+
function resolveClaudeCredentialDirectory(input) {
|
|
211142
|
+
if (!input.accountIdentity) return null;
|
|
211143
|
+
const root = claudeCredentialDirectoryRoot(input.env, input.baseDir);
|
|
211144
|
+
if (!root) return null;
|
|
211145
|
+
return (0, node_path.join)(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
211146
|
+
}
|
|
211147
|
+
/**
|
|
211148
|
+
* Decide whether this turn may point at a per-account credential directory.
|
|
211387
211149
|
*
|
|
211388
|
-
*
|
|
211389
|
-
*
|
|
211390
|
-
*
|
|
211150
|
+
* The cheap checks run first and the capability probe runs last, so a runtime
|
|
211151
|
+
* with no usable credential never spawns the CLI. The probe's error channel is
|
|
211152
|
+
* empty, so nothing here catches.
|
|
211153
|
+
*
|
|
211154
|
+
* A skip is not a fallback onto the default account. With no account the turn
|
|
211155
|
+
* runs on the Environment's own login, unchanged. A thread pinned to an account
|
|
211156
|
+
* whose credential is missing is refused by name under
|
|
211157
|
+
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`, and #3049 turns
|
|
211158
|
+
* `no-usable-credential`, and `capability-not-supported` for a pinned account,
|
|
211159
|
+
* into those refusals. Until #3049 nothing names an account, so `no-account` is
|
|
211160
|
+
* the only skip a runtime sees.
|
|
211161
|
+
*/
|
|
211162
|
+
function decideClaudeCredentialDirectory(input) {
|
|
211163
|
+
return require_Schema$1.gen(function* () {
|
|
211164
|
+
if (!input.accountIdentity) return skip("no-account");
|
|
211165
|
+
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
|
|
211166
|
+
const directory = resolveClaudeCredentialDirectory(input);
|
|
211167
|
+
if (!directory) return skip("no-root");
|
|
211168
|
+
if (!holdsUsableCredential(directory)) return skip("no-usable-credential");
|
|
211169
|
+
return (yield* input.probeSupport) === "supported" ? set(directory) : skip("capability-not-supported");
|
|
211170
|
+
});
|
|
211171
|
+
}
|
|
211172
|
+
/**
|
|
211173
|
+
* True when `.credentials.json` parses and carries a non-empty access token.
|
|
211174
|
+
*
|
|
211175
|
+
* File presence is not readiness. A refused credential is rewritten in place
|
|
211176
|
+
* with `accessToken: ""`, so a file that exists can still leave the CLI logged
|
|
211177
|
+
* out. Only the token's length is checked. The value is never logged, returned
|
|
211178
|
+
* or kept.
|
|
211391
211179
|
*/
|
|
211392
|
-
function
|
|
211393
|
-
const
|
|
211394
|
-
|
|
211395
|
-
|
|
211396
|
-
|
|
211180
|
+
function holdsUsableCredential(directory) {
|
|
211181
|
+
const file = (0, node_path.join)(directory, CLAUDE_CREDENTIAL_FILE_NAME);
|
|
211182
|
+
try {
|
|
211183
|
+
if (!(0, node_fs.statSync)(file).isFile()) return false;
|
|
211184
|
+
return hasAccessToken(JSON.parse((0, node_fs.readFileSync)(file, "utf8")));
|
|
211185
|
+
} catch {
|
|
211186
|
+
return false;
|
|
211187
|
+
}
|
|
211188
|
+
}
|
|
211189
|
+
function hasAccessToken(credential) {
|
|
211190
|
+
if (typeof credential !== "object" || credential === null) return false;
|
|
211191
|
+
const oauth = credential.claudeAiOauth;
|
|
211192
|
+
if (typeof oauth !== "object" || oauth === null) return false;
|
|
211193
|
+
const token = oauth.accessToken;
|
|
211194
|
+
return typeof token === "string" && token.length > 0;
|
|
211195
|
+
}
|
|
211196
|
+
function set(directory) {
|
|
211197
|
+
return {
|
|
211198
|
+
outcome: "set",
|
|
211199
|
+
directory
|
|
211200
|
+
};
|
|
211201
|
+
}
|
|
211202
|
+
function skip(reason) {
|
|
211203
|
+
return {
|
|
211204
|
+
outcome: "skipped",
|
|
211205
|
+
reason
|
|
211206
|
+
};
|
|
211207
|
+
}
|
|
211208
|
+
function claudeConfigDirectory(env) {
|
|
211209
|
+
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
211210
|
+
if (configured) return configured;
|
|
211211
|
+
try {
|
|
211212
|
+
return (0, node_path.join)((0, node_os.homedir)(), DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME);
|
|
211213
|
+
} catch {
|
|
211214
|
+
return null;
|
|
211215
|
+
}
|
|
211397
211216
|
}
|
|
211398
211217
|
//#endregion
|
|
211399
211218
|
//#region src/provider/personalClaudeInVm.ts
|
|
@@ -211463,160 +211282,76 @@ function personalClaudeGenerationHash(env = process.env) {
|
|
|
211463
211282
|
}
|
|
211464
211283
|
}
|
|
211465
211284
|
//#endregion
|
|
211466
|
-
//#region src/provider/
|
|
211285
|
+
//#region src/provider/claudeCredentialContainment.ts
|
|
211467
211286
|
/**
|
|
211468
|
-
*
|
|
211469
|
-
*
|
|
211470
|
-
* Claude Code keeps conversation transcripts under
|
|
211471
|
-
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
211472
|
-
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
211473
|
-
* credential file, so each Provider Account can hold its own login while every
|
|
211474
|
-
* session file stays in one place and `--resume` keeps finding its target. See
|
|
211475
|
-
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
211287
|
+
* Containment for provider credentials inherited by the Claude Agent SDK child.
|
|
211476
211288
|
*
|
|
211477
|
-
*
|
|
211478
|
-
*
|
|
211479
|
-
*
|
|
211480
|
-
*
|
|
211481
|
-
* the gate that keeps the degrade honest. It sets the variable only when a
|
|
211482
|
-
* readable `.credentials.json` is already sitting in the resolved directory,
|
|
211483
|
-
* because a missing or empty directory makes the CLI report logged out and a
|
|
211484
|
-
* live turn would fail. Nothing writes these directories yet, so every runtime
|
|
211485
|
-
* today takes the skip path.
|
|
211289
|
+
* Free of Effect services on purpose: ClaudeAdapter pulls in Effect platform
|
|
211290
|
+
* services, the browser manager, and the server config, so a unit test that
|
|
211291
|
+
* imports it cannot run in a worktree. This rule is cost-critical and must stay
|
|
211292
|
+
* directly testable.
|
|
211486
211293
|
*
|
|
211487
|
-
*
|
|
211488
|
-
*
|
|
211489
|
-
*
|
|
211490
|
-
*
|
|
211491
|
-
* a turn, so it has to stay directly testable.
|
|
211492
|
-
*/
|
|
211493
|
-
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
211494
|
-
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
211495
|
-
/** The one file Claude Code writes into the credential directory. */
|
|
211496
|
-
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
211497
|
-
/**
|
|
211498
|
-
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
211499
|
-
* belong to.
|
|
211294
|
+
* Audit finding S1 (2026-07-25). The Claude Agent SDK reads ANTHROPIC_API_KEY /
|
|
211295
|
+
* ANTHROPIC_AUTH_TOKEN from its own environment, and those TAKE PRECEDENCE over
|
|
211296
|
+
* the user's claude.ai OAuth login — that precedence is exactly what the
|
|
211297
|
+
* adapter relies on to redirect billing at the proxy.
|
|
211500
211298
|
*
|
|
211501
|
-
*
|
|
211502
|
-
*
|
|
211503
|
-
*
|
|
211504
|
-
*
|
|
211505
|
-
*
|
|
211506
|
-
* each time and defeat the point.
|
|
211299
|
+
* So on a plain Claude turn (no endpoint profile, no proxy credential), which
|
|
211300
|
+
* is supposed to run on the personal subscription, an ANTHROPIC_* key inherited
|
|
211301
|
+
* from the server's environment would send the turn straight to
|
|
211302
|
+
* api.anthropic.com on a metered account — never reaching
|
|
211303
|
+
* /api/provider-egress/*, leaving the fail-closed billing gate inert.
|
|
211507
211304
|
*
|
|
211508
|
-
*
|
|
211509
|
-
*
|
|
211510
|
-
*
|
|
211511
|
-
* free to replace it with something better rather than adopt it.
|
|
211305
|
+
* Forge Cloud sprites were only incidentally safe (no Anthropic key present on
|
|
211306
|
+
* the box). This makes containment an enforced property rather than an
|
|
211307
|
+
* assumption about what the deployment environment happens to contain.
|
|
211512
211308
|
*/
|
|
211513
|
-
|
|
211514
|
-
|
|
211515
|
-
const CLAUDE_CREDENTIAL_ROOT_ENV = "FORGE_CLAUDE_CREDENTIAL_ROOT";
|
|
211516
|
-
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
211517
|
-
const FALLBACK_ROOT_DIRECTORY_NAME = ".forge-claude-credentials";
|
|
211518
|
-
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
211519
|
-
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
211520
|
-
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
211309
|
+
/** Credentials the Claude Agent SDK will prefer over the user's OAuth login. */
|
|
211310
|
+
const INHERITABLE_ANTHROPIC_CREDENTIAL_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"];
|
|
211521
211311
|
/**
|
|
211522
|
-
*
|
|
211523
|
-
*
|
|
211312
|
+
* Routing override the SDK would obey instead of the login's default endpoint.
|
|
211313
|
+
* A CMMD cloud runtime exports it to point at the workspace provider-egress
|
|
211314
|
+
* proxy; on a PERSONAL-Claude-in-VM runtime that must be dropped so the turn
|
|
211315
|
+
* spends the on-box ~/.claude subscription (card 1359 Phase 2). Stripped ONLY
|
|
211316
|
+
* when the runtime signals in-VM delivery — otherwise existing behavior (the
|
|
211317
|
+
* inherited base URL survives) is preserved exactly.
|
|
211524
211318
|
*/
|
|
211525
|
-
|
|
211526
|
-
return `account-${(0, node_crypto.createHash)("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
211527
|
-
}
|
|
211528
|
-
/** The account this runtime's Claude turns belong to, or null on every runtime today. */
|
|
211529
|
-
function resolveClaudeProviderAccountIdentity(env) {
|
|
211530
|
-
const identity = env[CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV]?.trim();
|
|
211531
|
-
if (!identity || identity.length > MAX_ACCOUNT_IDENTITY_LENGTH) return null;
|
|
211532
|
-
return identity;
|
|
211533
|
-
}
|
|
211319
|
+
const ANTHROPIC_ENDPOINT_OVERRIDE_KEY = "ANTHROPIC_BASE_URL";
|
|
211534
211320
|
/**
|
|
211535
|
-
*
|
|
211536
|
-
*
|
|
211537
|
-
*
|
|
211538
|
-
*
|
|
211321
|
+
* Remove inherited Anthropic credentials so the child falls back to the user's
|
|
211322
|
+
* personal claude.ai login. Call this ONLY on the path where no base URL
|
|
211323
|
+
* resolved; when the adapter has a profile or proxy credential it sets these
|
|
211324
|
+
* variables deliberately, and that assignment must win.
|
|
211325
|
+
*
|
|
211326
|
+
* `stripEndpointOverride` additionally drops ANTHROPIC_BASE_URL — pass it only on
|
|
211327
|
+
* a personal-Claude-in-VM runtime (gated), never by default, so existing turns
|
|
211328
|
+
* that rely on an inherited base URL are untouched.
|
|
211539
211329
|
*/
|
|
211540
|
-
function
|
|
211541
|
-
const
|
|
211542
|
-
|
|
211543
|
-
|
|
211544
|
-
|
|
211545
|
-
/** Absolute credential directory for one account, or null when either half is missing. */
|
|
211546
|
-
function resolveClaudeCredentialDirectory(input) {
|
|
211547
|
-
if (!input.accountIdentity) return null;
|
|
211548
|
-
const root = claudeCredentialDirectoryRoot(input.env);
|
|
211549
|
-
if (!root) return null;
|
|
211550
|
-
return (0, node_path.join)(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
211330
|
+
function withoutInheritedAnthropicCredentials(env, options) {
|
|
211331
|
+
const contained = { ...env };
|
|
211332
|
+
for (const key of INHERITABLE_ANTHROPIC_CREDENTIAL_KEYS) delete contained[key];
|
|
211333
|
+
if (options?.stripEndpointOverride) delete contained[ANTHROPIC_ENDPOINT_OVERRIDE_KEY];
|
|
211334
|
+
return contained;
|
|
211551
211335
|
}
|
|
211552
211336
|
/**
|
|
211553
|
-
*
|
|
211337
|
+
* The environment a Claude child gets when it runs on the personal
|
|
211338
|
+
* subscription. Server-only secrets are scrubbed, inherited Anthropic keys are
|
|
211339
|
+
* removed so they cannot outrank the login, and the credential directory is set
|
|
211340
|
+
* only when one was decided. The turn's personal-subscription branch and the
|
|
211341
|
+
* capability probe both build their environment here, so the probe asks about
|
|
211342
|
+
* the setup the turn will run with.
|
|
211554
211343
|
*
|
|
211555
|
-
* The
|
|
211556
|
-
*
|
|
211557
|
-
*
|
|
211558
|
-
|
|
211559
|
-
|
|
211560
|
-
|
|
211561
|
-
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return skip("operator-override");
|
|
211562
|
-
const directory = resolveClaudeCredentialDirectory(input);
|
|
211563
|
-
if (!directory) return skip("no-root");
|
|
211564
|
-
if (!(input.credentialFilePresent ?? hasReadableCredentialFile)(directory)) return skip("no-credential-file");
|
|
211565
|
-
return await probeSupportSafely(input.probeSupport) === "supported" ? {
|
|
211566
|
-
outcome: "set",
|
|
211567
|
-
directory
|
|
211568
|
-
} : skip("capability-not-supported");
|
|
211569
|
-
}
|
|
211570
|
-
/** True when `.credentials.json` is in the directory and readable right now. */
|
|
211571
|
-
function hasReadableCredentialFile(directory) {
|
|
211572
|
-
const file = (0, node_path.join)(directory, CLAUDE_CREDENTIAL_FILE_NAME);
|
|
211573
|
-
try {
|
|
211574
|
-
if (!(0, node_fs.statSync)(file).isFile()) return false;
|
|
211575
|
-
(0, node_fs.accessSync)(file, node_fs.constants.R_OK);
|
|
211576
|
-
return true;
|
|
211577
|
-
} catch {
|
|
211578
|
-
return false;
|
|
211579
|
-
}
|
|
211580
|
-
}
|
|
211581
|
-
function skip(reason) {
|
|
211344
|
+
* The endpoint override goes only when `personalClaudeInVmEnabled` says this
|
|
211345
|
+
* runtime is on the personal Claude lane. That check also reads the activation
|
|
211346
|
+
* marker CMMD writes on disk (#3081), so a Sprite switched on in place, which
|
|
211347
|
+
* has the marker and no env var until it restarts, drops the override too.
|
|
211348
|
+
*/
|
|
211349
|
+
function claudeSubscriptionChildEnv(processEnv, credentialDirectory) {
|
|
211582
211350
|
return {
|
|
211583
|
-
|
|
211584
|
-
|
|
211351
|
+
...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: personalClaudeInVmEnabled(processEnv) }),
|
|
211352
|
+
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
211585
211353
|
};
|
|
211586
211354
|
}
|
|
211587
|
-
/** `inconclusive` is treated as `unsupported`, and so is a probe that throws. */
|
|
211588
|
-
async function probeSupportSafely(probeSupport) {
|
|
211589
|
-
try {
|
|
211590
|
-
return await probeSupport();
|
|
211591
|
-
} catch {
|
|
211592
|
-
return "inconclusive";
|
|
211593
|
-
}
|
|
211594
|
-
}
|
|
211595
|
-
function credentialRootCandidate(env) {
|
|
211596
|
-
const explicit = env[CLAUDE_CREDENTIAL_ROOT_ENV]?.trim();
|
|
211597
|
-
if (explicit) return explicit;
|
|
211598
|
-
const forgeHome = env.FORGE_HOME?.trim();
|
|
211599
|
-
if (forgeHome) return (0, node_path.join)(forgeHome, CREDENTIAL_ROOT_DIRECTORY_NAME);
|
|
211600
|
-
try {
|
|
211601
|
-
return (0, node_path.join)((0, node_os.homedir)(), FALLBACK_ROOT_DIRECTORY_NAME);
|
|
211602
|
-
} catch {
|
|
211603
|
-
return null;
|
|
211604
|
-
}
|
|
211605
|
-
}
|
|
211606
|
-
function claudeConfigDirectory(env) {
|
|
211607
|
-
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
211608
|
-
if (configured) return configured;
|
|
211609
|
-
try {
|
|
211610
|
-
return (0, node_path.join)((0, node_os.homedir)(), DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME);
|
|
211611
|
-
} catch {
|
|
211612
|
-
return null;
|
|
211613
|
-
}
|
|
211614
|
-
}
|
|
211615
|
-
function isInside(candidate, parent) {
|
|
211616
|
-
if (!parent) return false;
|
|
211617
|
-
const step = (0, node_path.relative)((0, node_path.resolve)(parent), (0, node_path.resolve)(candidate));
|
|
211618
|
-
return step === "" || !step.startsWith("..") && !(0, node_path.isAbsolute)(step);
|
|
211619
|
-
}
|
|
211620
211355
|
//#endregion
|
|
211621
211356
|
//#region src/provider/claudeCredentialDirectoryProbe.ts
|
|
211622
211357
|
/**
|
|
@@ -211629,6 +211364,11 @@ function isInside(candidate, parent) {
|
|
|
211629
211364
|
* A runtime with no credential at all is logged out either way and cannot
|
|
211630
211365
|
* discriminate, which is `inconclusive`, and callers read that as unsupported.
|
|
211631
211366
|
*
|
|
211367
|
+
* An answer only counts for the binary and environment the turn will use. The
|
|
211368
|
+
* probe spawns the `binaryPath` the turn hands the SDK, and builds its
|
|
211369
|
+
* environment with `claudeSubscriptionChildEnv`, the same builder as the turn's
|
|
211370
|
+
* personal-subscription branch.
|
|
211371
|
+
*
|
|
211632
211372
|
* `claude auth status` is a pure read. It sends no prompt and leaves
|
|
211633
211373
|
* `.credentials.json` byte-identical, so probing spends no subscription usage.
|
|
211634
211374
|
* It prints JSON on stdout and exits 1 when logged out, so the exit code is not
|
|
@@ -211644,38 +211384,62 @@ const VERSION_ARGS = ["--version"];
|
|
|
211644
211384
|
* hung or unkillable process costs a few seconds and an `inconclusive`.
|
|
211645
211385
|
*/
|
|
211646
211386
|
const PROBE_TIMEOUT_MS = 5e3;
|
|
211387
|
+
const PROBE_OUTPUT_LIMIT_BYTES = 1024 * 1024;
|
|
211647
211388
|
const PROBE_DIRECTORY_PREFIX = "forge-claude-capability-";
|
|
211648
211389
|
const VERSION_PATTERN = /\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/;
|
|
211649
211390
|
function makeClaudeCredentialDirectoryProbe(deps) {
|
|
211650
211391
|
const answers = /* @__PURE__ */ new Map();
|
|
211651
|
-
|
|
211392
|
+
const answer = async () => {
|
|
211652
211393
|
const version = await readVersionSafely(deps);
|
|
211653
211394
|
if (version === null) return runProbe(deps);
|
|
211654
211395
|
const cached = answers.get(version);
|
|
211655
211396
|
if (cached) return cached;
|
|
211656
211397
|
const pending = runProbe(deps);
|
|
211657
211398
|
answers.set(version, pending);
|
|
211399
|
+
const forget = () => {
|
|
211400
|
+
if (answers.get(version) === pending) answers.delete(version);
|
|
211401
|
+
};
|
|
211402
|
+
pending.then((result) => {
|
|
211403
|
+
if (result === "inconclusive") forget();
|
|
211404
|
+
}, forget);
|
|
211658
211405
|
return pending;
|
|
211659
|
-
}
|
|
211406
|
+
};
|
|
211407
|
+
return { support: require_Schema$1.tryPromise(answer).pipe(require_Schema$1.orElseSucceed(() => "inconclusive")) };
|
|
211660
211408
|
}
|
|
211661
211409
|
/**
|
|
211662
|
-
* One probe per binary path for the life of the process.
|
|
211663
|
-
*
|
|
211410
|
+
* One probe per binary path for the life of the process. A conclusive answer is
|
|
211411
|
+
* cached per binary version, so the two `auth status` spawns run once per path
|
|
211412
|
+
* and version. `claude --version` still spawns on every call, which is how an
|
|
211413
|
+
* in-place CLI upgrade gets a fresh answer.
|
|
211664
211414
|
*/
|
|
211665
211415
|
const probesByBinaryPath = /* @__PURE__ */ new Map();
|
|
211416
|
+
const INCONCLUSIVE_PROBE = { support: require_Schema$1.succeed("inconclusive") };
|
|
211417
|
+
/**
|
|
211418
|
+
* The probe for the binary a turn with this `binaryPath` setting runs.
|
|
211419
|
+
*
|
|
211420
|
+
* `buildClaudeQueryOptions` hands the setting to the SDK as
|
|
211421
|
+
* `pathToClaudeCodeExecutable`, and the SDK spawns it as given, so the default
|
|
211422
|
+
* `claude` goes through PATH for the probe exactly as for the turn. The SDK
|
|
211423
|
+
* picks a bundled build of its own only when the path is empty, and the
|
|
211424
|
+
* settings schema turns an empty value into `claude`. Should one arrive empty
|
|
211425
|
+
* anyway, the answer is `inconclusive` rather than an answer about another
|
|
211426
|
+
* binary.
|
|
211427
|
+
*/
|
|
211666
211428
|
function claudeCredentialDirectoryProbeFor(binaryPath) {
|
|
211667
|
-
|
|
211668
|
-
const existing = probesByBinaryPath.get(
|
|
211429
|
+
if (!binaryPath) return INCONCLUSIVE_PROBE;
|
|
211430
|
+
const existing = probesByBinaryPath.get(binaryPath);
|
|
211669
211431
|
if (existing) return existing;
|
|
211670
|
-
const probe = makeClaudeCredentialDirectoryProbe(claudeCredentialDirectoryProbeDeps(
|
|
211671
|
-
probesByBinaryPath.set(
|
|
211432
|
+
const probe = makeClaudeCredentialDirectoryProbe(claudeCredentialDirectoryProbeDeps(binaryPath));
|
|
211433
|
+
probesByBinaryPath.set(binaryPath, probe);
|
|
211672
211434
|
return probe;
|
|
211673
211435
|
}
|
|
211674
|
-
|
|
211436
|
+
const spawnClaude = (file, args, options) => execFileAsync$3(file, [...args], { ...options });
|
|
211437
|
+
function claudeCredentialDirectoryProbeDeps(binaryPath, options = {}) {
|
|
211438
|
+
const run = (args, credentialDirectory) => runClaudeCommand$1(options.execFile ?? spawnClaude, binaryPath, args, claudeSubscriptionChildEnv(options.baseEnv ?? process.env, credentialDirectory));
|
|
211675
211439
|
return {
|
|
211676
|
-
runAuthStatus: ({ credentialDirectory }) =>
|
|
211440
|
+
runAuthStatus: ({ credentialDirectory }) => run(AUTH_STATUS_ARGS, credentialDirectory),
|
|
211677
211441
|
readBinaryVersion: async () => {
|
|
211678
|
-
const { stdout } = await
|
|
211442
|
+
const { stdout } = await run(VERSION_ARGS, null);
|
|
211679
211443
|
return VERSION_PATTERN.exec(stdout)?.[0] ?? null;
|
|
211680
211444
|
},
|
|
211681
211445
|
makeProbeDirectory: () => (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), PROBE_DIRECTORY_PREFIX)),
|
|
@@ -211685,6 +211449,7 @@ function claudeCredentialDirectoryProbeDeps(binaryPath) {
|
|
|
211685
211449
|
})
|
|
211686
211450
|
};
|
|
211687
211451
|
}
|
|
211452
|
+
/** Rejects when the probe could not tell. `support` turns that into `inconclusive`. */
|
|
211688
211453
|
async function runProbe(deps) {
|
|
211689
211454
|
let probeDirectory = null;
|
|
211690
211455
|
try {
|
|
@@ -211694,8 +211459,6 @@ async function runProbe(deps) {
|
|
|
211694
211459
|
const redirected = parseLoggedIn(await runBounded(deps, () => deps.runAuthStatus({ credentialDirectory: directory })));
|
|
211695
211460
|
if (redirected === false) return "supported";
|
|
211696
211461
|
return redirected === true ? "unsupported" : "inconclusive";
|
|
211697
|
-
} catch {
|
|
211698
|
-
return "inconclusive";
|
|
211699
211462
|
} finally {
|
|
211700
211463
|
if (probeDirectory !== null) await deps.removeProbeDirectory(probeDirectory).catch(() => void 0);
|
|
211701
211464
|
}
|
|
@@ -211717,15 +211480,15 @@ async function runBounded(deps, operation) {
|
|
|
211717
211480
|
let timer;
|
|
211718
211481
|
try {
|
|
211719
211482
|
return await Promise.race([operation(), new Promise((_resolve, reject) => {
|
|
211720
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("claude
|
|
211483
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error("claude probe call timed out")), timeoutMs);
|
|
211721
211484
|
})]);
|
|
211722
211485
|
} finally {
|
|
211723
211486
|
if (timer !== void 0) clearTimeout(timer);
|
|
211724
211487
|
}
|
|
211725
211488
|
}
|
|
211726
211489
|
/** The `loggedIn` field, or null when the output does not carry one. */
|
|
211727
|
-
function parseLoggedIn(
|
|
211728
|
-
const parsed = parseJsonObject$1(
|
|
211490
|
+
function parseLoggedIn(output) {
|
|
211491
|
+
const parsed = parseJsonObject$1(output.stdout);
|
|
211729
211492
|
return typeof parsed?.loggedIn === "boolean" ? parsed.loggedIn : null;
|
|
211730
211493
|
}
|
|
211731
211494
|
function parseJsonObject$1(output) {
|
|
@@ -211740,27 +211503,18 @@ function parseJsonObject$1(output) {
|
|
|
211740
211503
|
return null;
|
|
211741
211504
|
}
|
|
211742
211505
|
}
|
|
211743
|
-
async function runClaudeCommand$1(binaryPath, args,
|
|
211506
|
+
async function runClaudeCommand$1(execFileImpl, binaryPath, args, env) {
|
|
211744
211507
|
try {
|
|
211745
|
-
const { stdout } = await
|
|
211746
|
-
env
|
|
211747
|
-
|
|
211748
|
-
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
211749
|
-
},
|
|
211750
|
-
maxBuffer: 1024 * 1024,
|
|
211508
|
+
const { stdout } = await execFileImpl(binaryPath, args, {
|
|
211509
|
+
env,
|
|
211510
|
+
maxBuffer: PROBE_OUTPUT_LIMIT_BYTES,
|
|
211751
211511
|
timeout: PROBE_TIMEOUT_MS
|
|
211752
211512
|
});
|
|
211753
|
-
return {
|
|
211754
|
-
stdout,
|
|
211755
|
-
exitCode: 0
|
|
211756
|
-
};
|
|
211513
|
+
return { stdout };
|
|
211757
211514
|
} catch (cause) {
|
|
211758
211515
|
const failure = cause;
|
|
211759
211516
|
if (typeof failure.stdout !== "string") throw cause;
|
|
211760
|
-
return {
|
|
211761
|
-
stdout: failure.stdout,
|
|
211762
|
-
exitCode: typeof failure.code === "number" ? failure.code : null
|
|
211763
|
-
};
|
|
211517
|
+
return { stdout: failure.stdout };
|
|
211764
211518
|
}
|
|
211765
211519
|
}
|
|
211766
211520
|
//#endregion
|
|
@@ -213277,10 +213031,12 @@ function claudeForgeAttributionPrompt() {
|
|
|
213277
213031
|
* `credentialDirectory` points this turn at one Provider Account's own
|
|
213278
213032
|
* `.credentials.json` without moving the transcripts, which is what lets an
|
|
213279
213033
|
* account switch keep `--resume` working
|
|
213280
|
-
* (`docs/adr/claude-accounts-split-credentials-not-sessions.md`). It
|
|
213281
|
-
*
|
|
213282
|
-
*
|
|
213283
|
-
*
|
|
213034
|
+
* (`docs/adr/claude-accounts-split-credentials-not-sessions.md`). It stays null
|
|
213035
|
+
* until #3049 gives the directories a writer, keyed on the account identity on
|
|
213036
|
+
* the delivery receipt CMMD signs. `decideClaudeCredentialDirectory` only hands
|
|
213037
|
+
* back a directory that already holds a usable credential, and while none
|
|
213038
|
+
* exists the environment is byte-for-byte what it would be without this
|
|
213039
|
+
* parameter.
|
|
213284
213040
|
*
|
|
213285
213041
|
* It applies to the personal-subscription branch below and to nothing else. On
|
|
213286
213042
|
* the profile / proxy branch the credential file is not what authenticates the
|
|
@@ -213290,10 +213046,7 @@ function claudeForgeAttributionPrompt() {
|
|
|
213290
213046
|
function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory) {
|
|
213291
213047
|
const baseEnv = scrubForgeProviderChildEnvSecrets(baseProcessEnv);
|
|
213292
213048
|
const effectiveBaseUrl = resolveClaudeQueryBaseUrl(endpointProfile, claudeSettings, providerProxyCredential);
|
|
213293
|
-
if (!effectiveBaseUrl) return
|
|
213294
|
-
...withoutInheritedAnthropicCredentials(baseEnv, { stripEndpointOverride: personalClaudeInVmEnabled(baseProcessEnv) }),
|
|
213295
|
-
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
213296
|
-
};
|
|
213049
|
+
if (!effectiveBaseUrl) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory);
|
|
213297
213050
|
const effectiveApiKey = resolveClaudeQueryApiKey(endpointProfile, claudeSettings, providerProxyCredential?.token ?? null);
|
|
213298
213051
|
return {
|
|
213299
213052
|
...baseEnv,
|
|
@@ -215562,11 +215315,17 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
215562
215315
|
})));
|
|
215563
215316
|
const claudeSettings = serverSettings.providers.claudeAgent;
|
|
215564
215317
|
const claudeBinaryPath = claudeSettings.binaryPath;
|
|
215565
|
-
const
|
|
215318
|
+
const credentialDirectoryDecision = yield* decideClaudeCredentialDirectory({
|
|
215566
215319
|
env: process.env,
|
|
215320
|
+
baseDir: serverConfig.baseDir,
|
|
215567
215321
|
accountIdentity: resolveClaudeProviderAccountIdentity(process.env),
|
|
215568
|
-
probeSupport:
|
|
215569
|
-
})
|
|
215322
|
+
probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support
|
|
215323
|
+
});
|
|
215324
|
+
if (credentialDirectoryDecision.outcome === "skipped") {
|
|
215325
|
+
const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
|
|
215326
|
+
yield* credentialDirectoryDecision.reason === "no-account" ? require_Schema$1.logDebug(skipLine) : require_Schema$1.logWarning(skipLine);
|
|
215327
|
+
}
|
|
215328
|
+
const claudeCredentialDirectory = credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null;
|
|
215570
215329
|
const modelConfig = resolveClaudeSessionModelConfig(input.modelSelection, claudeSettings.endpointProfiles);
|
|
215571
215330
|
const providerProxyCredential = yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
|
|
215572
215331
|
const permissionMode = permissionModeForRuntimeMode(input.runtimeMode);
|
|
@@ -287840,7 +287599,8 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
287840
287599
|
...config,
|
|
287841
287600
|
activeTurnCount: snapshot.activeTurnCount,
|
|
287842
287601
|
activeSessionCount: snapshot.activeSessionCount,
|
|
287843
|
-
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {}
|
|
287602
|
+
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
287603
|
+
forgeServerVersion: installedForgeServerVersion
|
|
287844
287604
|
});
|
|
287845
287605
|
});
|
|
287846
287606
|
const applyControlPlaneState = (runtime) => drainState.applyControlPlaneState({
|
|
@@ -293213,6 +292973,382 @@ const makeWorkspaceFileSystem = require_Schema$1.gen(function* () {
|
|
|
293213
292973
|
});
|
|
293214
292974
|
const WorkspaceFileSystemLive = require_Schema$1.effect(WorkspaceFileSystem, makeWorkspaceFileSystem);
|
|
293215
292975
|
//#endregion
|
|
292976
|
+
//#region src/workspace/Layers/WorkspaceBulkTransfer.ts
|
|
292977
|
+
const TRANSFER_TTL_MS = 1440 * 60 * 1e3;
|
|
292978
|
+
const TRANSFER_LOCK_STRIPE_COUNT = 64;
|
|
292979
|
+
function transferError(input) {
|
|
292980
|
+
return new BulkTransferError({
|
|
292981
|
+
code: input.code,
|
|
292982
|
+
message: input.message,
|
|
292983
|
+
transferId: input.transferId ?? null,
|
|
292984
|
+
retryable: input.retryable ?? false,
|
|
292985
|
+
...input.cause === void 0 ? {} : { cause: input.cause }
|
|
292986
|
+
});
|
|
292987
|
+
}
|
|
292988
|
+
function mapFailure(operation, transferId) {
|
|
292989
|
+
return (cause) => require_Schema$1.is(BulkTransferError)(cause) ? cause : transferError({
|
|
292990
|
+
code: "io_error",
|
|
292991
|
+
message: `Workspace bulk transfer failed during ${operation}.`,
|
|
292992
|
+
...transferId === void 0 ? {} : { transferId },
|
|
292993
|
+
retryable: true,
|
|
292994
|
+
cause
|
|
292995
|
+
});
|
|
292996
|
+
}
|
|
292997
|
+
function transferLockStripe(transferId) {
|
|
292998
|
+
let hash = 2166136261;
|
|
292999
|
+
for (let index = 0; index < transferId.length; index += 1) hash = Math.imul(hash ^ transferId.charCodeAt(index), 16777619);
|
|
293000
|
+
return (hash >>> 0) % TRANSFER_LOCK_STRIPE_COUNT;
|
|
293001
|
+
}
|
|
293002
|
+
const makeWorkspaceBulkTransfer = (options = {}) => require_Schema$1.gen(function* () {
|
|
293003
|
+
const fileSystem = yield* require_Path.FileSystem;
|
|
293004
|
+
const path = yield* require_Path.Path;
|
|
293005
|
+
const sql = yield* require_SqlError.SqlClient;
|
|
293006
|
+
const workspacePaths = yield* WorkspacePaths;
|
|
293007
|
+
const workspaceEntries = yield* WorkspaceEntries;
|
|
293008
|
+
const fault = options.fault ?? (() => require_Schema$1.void_);
|
|
293009
|
+
const transferLocks = yield* require_Schema$1.forEach(Array.from({ length: TRANSFER_LOCK_STRIPE_COUNT }), () => require_Path.make$5(1));
|
|
293010
|
+
const withTransferLock = (transferId, effect) => transferLocks[transferLockStripe(transferId)].withPermit(effect);
|
|
293011
|
+
const findTransfer = (transferId) => sql`
|
|
293012
|
+
SELECT transfer_id AS "transferId", workspace_root AS "workspaceRoot",
|
|
293013
|
+
relative_path AS "relativePath", target_path AS "targetPath", temp_path AS "tempPath",
|
|
293014
|
+
total_bytes AS "totalBytes", whole_sha256 AS "wholeSha256",
|
|
293015
|
+
next_offset AS "nextOffset", state, expires_at AS "expiresAt"
|
|
293016
|
+
FROM workspace_bulk_transfers WHERE transfer_id = ${transferId} LIMIT 1
|
|
293017
|
+
`.pipe(require_Schema$1.map((rows) => rows[0] ?? null));
|
|
293018
|
+
const requireTransfer = (transferId) => findTransfer(transferId).pipe(require_Schema$1.flatMap((row) => row === null ? require_Schema$1.fail(transferError({
|
|
293019
|
+
code: "transfer_not_found",
|
|
293020
|
+
message: "Bulk transfer does not exist.",
|
|
293021
|
+
transferId
|
|
293022
|
+
})) : require_Schema$1.succeed(row)));
|
|
293023
|
+
const requireOpenTransfer = (transferId) => requireTransfer(transferId).pipe(require_Schema$1.flatMap((row) => {
|
|
293024
|
+
if (row.state === "cancelled") return require_Schema$1.fail(transferError({
|
|
293025
|
+
code: "transfer_cancelled",
|
|
293026
|
+
message: "Bulk transfer was cancelled.",
|
|
293027
|
+
transferId
|
|
293028
|
+
}));
|
|
293029
|
+
if (row.state === "expired" || Date.parse(row.expiresAt) <= Date.now()) return require_Schema$1.fail(transferError({
|
|
293030
|
+
code: "transfer_expired",
|
|
293031
|
+
message: "Bulk transfer expired.",
|
|
293032
|
+
transferId
|
|
293033
|
+
}));
|
|
293034
|
+
return require_Schema$1.succeed(row);
|
|
293035
|
+
}));
|
|
293036
|
+
const resolveTarget = require_Schema$1.fn("WorkspaceBulkTransfer.resolveTarget")(function* (input) {
|
|
293037
|
+
const workspaceRoot = yield* workspacePaths.normalizeWorkspaceRoot(input.cwd);
|
|
293038
|
+
const resolved = yield* workspacePaths.resolveRelativePathWithinRoot({
|
|
293039
|
+
workspaceRoot,
|
|
293040
|
+
relativePath: input.relativePath
|
|
293041
|
+
});
|
|
293042
|
+
const canonicalRoot = yield* fileSystem.realPath(workspaceRoot);
|
|
293043
|
+
const parentSegments = resolved.relativePath.split("/").slice(0, -1);
|
|
293044
|
+
let canonicalParent = canonicalRoot;
|
|
293045
|
+
for (const segment of parentSegments) {
|
|
293046
|
+
const candidate = path.join(canonicalParent, segment);
|
|
293047
|
+
const canonicalCandidate = yield* fileSystem.realPath(candidate).pipe(require_Schema$1.catch_(() => input.createParent ? fileSystem.makeDirectory(candidate).pipe(require_Schema$1.flatMap(() => fileSystem.realPath(candidate))) : fileSystem.realPath(candidate)));
|
|
293048
|
+
if (!isPathWithinRoot(path, canonicalRoot, canonicalCandidate)) return yield* transferError({
|
|
293049
|
+
code: "symlink_escape",
|
|
293050
|
+
message: "Workspace file path resolves outside the project root."
|
|
293051
|
+
});
|
|
293052
|
+
canonicalParent = canonicalCandidate;
|
|
293053
|
+
}
|
|
293054
|
+
const targetPath = path.join(canonicalParent, path.basename(resolved.relativePath));
|
|
293055
|
+
const targetRealPath = yield* fileSystem.realPath(targetPath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
|
|
293056
|
+
if (targetRealPath !== null && !isPathWithinRoot(path, canonicalRoot, targetRealPath)) return yield* transferError({
|
|
293057
|
+
code: "symlink_escape",
|
|
293058
|
+
message: "Workspace file target resolves outside the project root."
|
|
293059
|
+
});
|
|
293060
|
+
return {
|
|
293061
|
+
workspaceRoot: canonicalRoot,
|
|
293062
|
+
relativePath: resolved.relativePath,
|
|
293063
|
+
targetPath,
|
|
293064
|
+
canonicalParent
|
|
293065
|
+
};
|
|
293066
|
+
});
|
|
293067
|
+
const readFile = (input) => require_Path.unwrap(require_Schema$1.gen(function* () {
|
|
293068
|
+
const target = yield* resolveTarget({
|
|
293069
|
+
...input,
|
|
293070
|
+
createParent: false
|
|
293071
|
+
});
|
|
293072
|
+
const info = yield* fileSystem.stat(target.targetPath);
|
|
293073
|
+
const maxBytes = input.maxBytes ?? 16777216;
|
|
293074
|
+
if (Number(info.size) > maxBytes) return yield* transferError({
|
|
293075
|
+
code: "size_limit_exceeded",
|
|
293076
|
+
message: `Workspace file exceeds the ${maxBytes}-byte read limit.`
|
|
293077
|
+
});
|
|
293078
|
+
const bytes = yield* fileSystem.readFile(target.targetPath);
|
|
293079
|
+
const transfer = yield* require_Schema$1.promise(() => chunkBulkTransfer(bytes));
|
|
293080
|
+
const transferId = transfer.chunks[0]?.transferId ?? crypto.randomUUID().replaceAll("-", "_");
|
|
293081
|
+
return require_Path.fromIterable([
|
|
293082
|
+
{
|
|
293083
|
+
kind: "started",
|
|
293084
|
+
transferId,
|
|
293085
|
+
totalBytes: transfer.totalBytes,
|
|
293086
|
+
sha256: transfer.sha256
|
|
293087
|
+
},
|
|
293088
|
+
...transfer.chunks.map((chunk) => ({
|
|
293089
|
+
...chunk,
|
|
293090
|
+
transferId
|
|
293091
|
+
})),
|
|
293092
|
+
{
|
|
293093
|
+
kind: "completed",
|
|
293094
|
+
transferId,
|
|
293095
|
+
totalBytes: transfer.totalBytes,
|
|
293096
|
+
sha256: transfer.sha256
|
|
293097
|
+
}
|
|
293098
|
+
]);
|
|
293099
|
+
}).pipe(require_Schema$1.mapError(mapFailure("read file"))));
|
|
293100
|
+
const resumeWrite = require_Schema$1.fn("WorkspaceBulkTransfer.resumeWrite")(function* (input, existing) {
|
|
293101
|
+
const target = yield* resolveTarget({
|
|
293102
|
+
...input,
|
|
293103
|
+
createParent: true
|
|
293104
|
+
});
|
|
293105
|
+
if (!(existing.workspaceRoot === target.workspaceRoot && existing.relativePath === target.relativePath && existing.totalBytes === input.totalBytes && existing.wholeSha256 === input.sha256)) return yield* transferError({
|
|
293106
|
+
code: "transfer_identity_conflict",
|
|
293107
|
+
message: "Bulk transfer ID is already bound to different content or a different path.",
|
|
293108
|
+
transferId: input.transferId
|
|
293109
|
+
});
|
|
293110
|
+
if (existing.state === "cancelled") return yield* transferError({
|
|
293111
|
+
code: "transfer_cancelled",
|
|
293112
|
+
message: "Bulk transfer was cancelled.",
|
|
293113
|
+
transferId: input.transferId
|
|
293114
|
+
});
|
|
293115
|
+
if (existing.state === "expired" || Date.parse(existing.expiresAt) <= Date.now()) return yield* transferError({
|
|
293116
|
+
code: "transfer_expired",
|
|
293117
|
+
message: "Bulk transfer expired.",
|
|
293118
|
+
transferId: input.transferId
|
|
293119
|
+
});
|
|
293120
|
+
return {
|
|
293121
|
+
transferId: input.transferId,
|
|
293122
|
+
nextOffset: existing.nextOffset,
|
|
293123
|
+
state: existing.state === "completed" ? "completed" : "open"
|
|
293124
|
+
};
|
|
293125
|
+
});
|
|
293126
|
+
const createWrite = require_Schema$1.fn("WorkspaceBulkTransfer.createWrite")(function* (input) {
|
|
293127
|
+
const target = yield* resolveTarget({
|
|
293128
|
+
...input,
|
|
293129
|
+
createParent: true
|
|
293130
|
+
}).pipe(require_Schema$1.mapError((cause) => require_Schema$1.is(WorkspacePathOutsideRootError)(cause) ? transferError({
|
|
293131
|
+
code: "path_outside_workspace",
|
|
293132
|
+
message: "Workspace file path must stay within the project root.",
|
|
293133
|
+
transferId: input.transferId,
|
|
293134
|
+
cause
|
|
293135
|
+
}) : cause));
|
|
293136
|
+
const tempPath = path.join(target.canonicalParent, `.forge-transfer-${input.transferId}.part`);
|
|
293137
|
+
yield* require_Schema$1.scoped(fileSystem.open(tempPath, {
|
|
293138
|
+
flag: "wx",
|
|
293139
|
+
mode: 384
|
|
293140
|
+
}).pipe(require_Schema$1.flatMap((file) => file.sync), require_Schema$1.catch_(() => fileSystem.stat(tempPath).pipe(require_Schema$1.flatMap((stat) => Number(stat.size) === 0 ? require_Schema$1.void_ : require_Schema$1.fail(transferError({
|
|
293141
|
+
code: "transfer_identity_conflict",
|
|
293142
|
+
message: "Bulk transfer temporary path already contains data.",
|
|
293143
|
+
transferId: input.transferId
|
|
293144
|
+
})))))));
|
|
293145
|
+
const now = /* @__PURE__ */ new Date();
|
|
293146
|
+
yield* sql`
|
|
293147
|
+
INSERT INTO workspace_bulk_transfers (
|
|
293148
|
+
transfer_id, workspace_root, relative_path, target_path, temp_path, total_bytes,
|
|
293149
|
+
whole_sha256, next_offset, state, expires_at, created_at, updated_at
|
|
293150
|
+
) VALUES (
|
|
293151
|
+
${input.transferId}, ${target.workspaceRoot}, ${target.relativePath}, ${target.targetPath},
|
|
293152
|
+
${tempPath}, ${input.totalBytes}, ${input.sha256}, 0, 'open',
|
|
293153
|
+
${new Date(now.getTime() + TRANSFER_TTL_MS).toISOString()}, ${now.toISOString()},
|
|
293154
|
+
${now.toISOString()}
|
|
293155
|
+
)
|
|
293156
|
+
`;
|
|
293157
|
+
return {
|
|
293158
|
+
transferId: input.transferId,
|
|
293159
|
+
nextOffset: 0,
|
|
293160
|
+
state: "open"
|
|
293161
|
+
};
|
|
293162
|
+
});
|
|
293163
|
+
const beginWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.beginWrite")(function* (input) {
|
|
293164
|
+
const existing = yield* findTransfer(input.transferId);
|
|
293165
|
+
return yield* existing === null ? createWrite(input) : resumeWrite(input, existing);
|
|
293166
|
+
});
|
|
293167
|
+
const beginWrite = (input) => withTransferLock(input.transferId, beginWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("begin write", input.transferId)));
|
|
293168
|
+
const acceptRetriedChunk = require_Schema$1.fn("WorkspaceBulkTransfer.acceptRetriedChunk")(function* (input, row) {
|
|
293169
|
+
const accepted = (yield* sql`
|
|
293170
|
+
SELECT chunk_offset AS "chunkOffset", chunk_bytes AS "chunkBytes",
|
|
293171
|
+
chunk_sha256 AS "chunkSha256"
|
|
293172
|
+
FROM workspace_bulk_transfer_chunks
|
|
293173
|
+
WHERE transfer_id = ${input.transferId} AND chunk_offset = ${input.offset}
|
|
293174
|
+
LIMIT 1
|
|
293175
|
+
`)[0];
|
|
293176
|
+
if (!(accepted !== void 0 && accepted.chunkBytes === input.bytes && accepted.chunkSha256 === input.sha256)) return yield* transferError({
|
|
293177
|
+
code: "integrity_mismatch",
|
|
293178
|
+
message: "Retried bulk transfer chunk differs from the accepted chunk.",
|
|
293179
|
+
transferId: input.transferId
|
|
293180
|
+
});
|
|
293181
|
+
return {
|
|
293182
|
+
transferId: input.transferId,
|
|
293183
|
+
nextOffset: row.nextOffset,
|
|
293184
|
+
duplicate: true
|
|
293185
|
+
};
|
|
293186
|
+
});
|
|
293187
|
+
const validateChunkRange = require_Schema$1.fn("WorkspaceBulkTransfer.validateChunkRange")(function* (input, row, byteLength) {
|
|
293188
|
+
if (input.offset !== row.nextOffset) return yield* transferError({
|
|
293189
|
+
code: "offset_mismatch",
|
|
293190
|
+
message: `Expected chunk offset ${row.nextOffset}, received ${input.offset}.`,
|
|
293191
|
+
transferId: input.transferId,
|
|
293192
|
+
retryable: true
|
|
293193
|
+
});
|
|
293194
|
+
const nextOffset = input.offset + byteLength;
|
|
293195
|
+
if (nextOffset > row.totalBytes) return yield* transferError({
|
|
293196
|
+
code: "size_limit_exceeded",
|
|
293197
|
+
message: "Bulk transfer chunk exceeds the declared total byte count.",
|
|
293198
|
+
transferId: input.transferId
|
|
293199
|
+
});
|
|
293200
|
+
return nextOffset;
|
|
293201
|
+
});
|
|
293202
|
+
const reconcileChunkBytes = require_Schema$1.fn("WorkspaceBulkTransfer.reconcileChunkBytes")(function* (input, row, bytes, nextOffset) {
|
|
293203
|
+
const tempInfo = yield* fileSystem.stat(row.tempPath);
|
|
293204
|
+
if (Number(tempInfo.size) > row.nextOffset) {
|
|
293205
|
+
if (Number(tempInfo.size) !== nextOffset) return yield* transferError({
|
|
293206
|
+
code: "integrity_mismatch",
|
|
293207
|
+
message: "Bulk transfer temporary file cannot be reconciled with the next chunk.",
|
|
293208
|
+
transferId: input.transferId
|
|
293209
|
+
});
|
|
293210
|
+
const recovered = (yield* fileSystem.readFile(row.tempPath)).slice(input.offset, nextOffset);
|
|
293211
|
+
if ((yield* require_Schema$1.promise(() => sha256Hex(recovered))) !== input.sha256) return yield* transferError({
|
|
293212
|
+
code: "integrity_mismatch",
|
|
293213
|
+
message: "Bulk transfer temporary bytes differ from the retried chunk.",
|
|
293214
|
+
transferId: input.transferId
|
|
293215
|
+
});
|
|
293216
|
+
return;
|
|
293217
|
+
}
|
|
293218
|
+
yield* fault("before-byte-write");
|
|
293219
|
+
yield* require_Schema$1.scoped(fileSystem.open(row.tempPath, { flag: "r+" }).pipe(require_Schema$1.flatMap((file) => file.seek(input.offset, "start").pipe(require_Schema$1.flatMap(() => file.writeAll(bytes)), require_Schema$1.flatMap(() => file.sync)))));
|
|
293220
|
+
yield* fault("after-byte-write-before-metadata");
|
|
293221
|
+
});
|
|
293222
|
+
const persistAcceptedChunk = require_Schema$1.fn("WorkspaceBulkTransfer.persistAcceptedChunk")(function* (input, nextOffset) {
|
|
293223
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
293224
|
+
yield* sql.withTransaction(sql`
|
|
293225
|
+
INSERT INTO workspace_bulk_transfer_chunks (
|
|
293226
|
+
transfer_id, chunk_offset, chunk_bytes, chunk_sha256
|
|
293227
|
+
) VALUES (${input.transferId}, ${input.offset}, ${input.bytes}, ${input.sha256})
|
|
293228
|
+
ON CONFLICT DO NOTHING
|
|
293229
|
+
`.pipe(require_Schema$1.flatMap(() => sql`
|
|
293230
|
+
UPDATE workspace_bulk_transfers
|
|
293231
|
+
SET next_offset = ${nextOffset}, updated_at = ${updatedAt}
|
|
293232
|
+
WHERE transfer_id = ${input.transferId} AND next_offset = ${input.offset} AND state = 'open'
|
|
293233
|
+
`)));
|
|
293234
|
+
yield* fault("after-metadata-advance");
|
|
293235
|
+
});
|
|
293236
|
+
const writeChunkEffect = require_Schema$1.fn("WorkspaceBulkTransfer.writeChunk")(function* (input) {
|
|
293237
|
+
const bytes = yield* require_Schema$1.promise(() => verifyBulkTransferChunk({
|
|
293238
|
+
...input,
|
|
293239
|
+
kind: "chunk"
|
|
293240
|
+
}));
|
|
293241
|
+
const row = yield* requireOpenTransfer(input.transferId);
|
|
293242
|
+
if (row.state === "completed") return yield* transferError({
|
|
293243
|
+
code: "offset_mismatch",
|
|
293244
|
+
message: "Bulk transfer is already complete.",
|
|
293245
|
+
transferId: input.transferId
|
|
293246
|
+
});
|
|
293247
|
+
if (input.offset < row.nextOffset) return yield* acceptRetriedChunk(input, row);
|
|
293248
|
+
const nextOffset = yield* validateChunkRange(input, row, bytes.byteLength);
|
|
293249
|
+
yield* reconcileChunkBytes(input, row, bytes, nextOffset);
|
|
293250
|
+
yield* persistAcceptedChunk(input, nextOffset);
|
|
293251
|
+
return {
|
|
293252
|
+
transferId: input.transferId,
|
|
293253
|
+
nextOffset,
|
|
293254
|
+
duplicate: false
|
|
293255
|
+
};
|
|
293256
|
+
});
|
|
293257
|
+
const writeChunk = (input) => withTransferLock(input.transferId, writeChunkEffect(input)).pipe(require_Schema$1.mapError(mapFailure("write chunk", input.transferId)));
|
|
293258
|
+
const commitWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.commitWrite")(function* (input) {
|
|
293259
|
+
const row = yield* requireOpenTransfer(input.transferId);
|
|
293260
|
+
if (row.state === "completed") return {
|
|
293261
|
+
transferId: row.transferId,
|
|
293262
|
+
relativePath: row.relativePath,
|
|
293263
|
+
totalBytes: row.totalBytes,
|
|
293264
|
+
sha256: row.wholeSha256
|
|
293265
|
+
};
|
|
293266
|
+
if (row.nextOffset !== row.totalBytes) return yield* transferError({
|
|
293267
|
+
code: "offset_mismatch",
|
|
293268
|
+
message: `Bulk transfer is incomplete at byte ${row.nextOffset} of ${row.totalBytes}.`,
|
|
293269
|
+
transferId: row.transferId,
|
|
293270
|
+
retryable: true
|
|
293271
|
+
});
|
|
293272
|
+
if ((yield* resolveTarget({
|
|
293273
|
+
cwd: row.workspaceRoot,
|
|
293274
|
+
relativePath: row.relativePath,
|
|
293275
|
+
createParent: false
|
|
293276
|
+
})).targetPath !== row.targetPath) return yield* transferError({
|
|
293277
|
+
code: "symlink_escape",
|
|
293278
|
+
message: "Workspace file destination changed during transfer.",
|
|
293279
|
+
transferId: row.transferId
|
|
293280
|
+
});
|
|
293281
|
+
const tempInfo = yield* fileSystem.stat(row.tempPath).pipe(require_Schema$1.catch_(() => require_Schema$1.succeed(null)));
|
|
293282
|
+
const candidatePath = tempInfo === null ? row.targetPath : row.tempPath;
|
|
293283
|
+
const candidate = yield* fileSystem.readFile(candidatePath);
|
|
293284
|
+
if (candidate.byteLength !== row.totalBytes || (yield* require_Schema$1.promise(() => sha256Hex(candidate))) !== row.wholeSha256) return yield* transferError({
|
|
293285
|
+
code: "integrity_mismatch",
|
|
293286
|
+
message: "Bulk transfer payload does not match its declared size and digest.",
|
|
293287
|
+
transferId: row.transferId
|
|
293288
|
+
});
|
|
293289
|
+
if (tempInfo !== null) {
|
|
293290
|
+
yield* fileSystem.rename(row.tempPath, row.targetPath);
|
|
293291
|
+
yield* fault("after-atomic-rename");
|
|
293292
|
+
}
|
|
293293
|
+
yield* sql`
|
|
293294
|
+
UPDATE workspace_bulk_transfers SET state = 'completed', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
293295
|
+
WHERE transfer_id = ${row.transferId}
|
|
293296
|
+
`;
|
|
293297
|
+
yield* workspaceEntries.invalidate(row.workspaceRoot);
|
|
293298
|
+
return {
|
|
293299
|
+
transferId: row.transferId,
|
|
293300
|
+
relativePath: row.relativePath,
|
|
293301
|
+
totalBytes: row.totalBytes,
|
|
293302
|
+
sha256: row.wholeSha256
|
|
293303
|
+
};
|
|
293304
|
+
});
|
|
293305
|
+
const commitWrite = (input) => withTransferLock(input.transferId, commitWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("commit write", input.transferId)));
|
|
293306
|
+
const cancelWriteEffect = require_Schema$1.fn("WorkspaceBulkTransfer.cancelWrite")(function* (input) {
|
|
293307
|
+
const row = yield* requireTransfer(input.transferId);
|
|
293308
|
+
if (row.state !== "completed") {
|
|
293309
|
+
yield* fileSystem.remove(row.tempPath, { force: true });
|
|
293310
|
+
yield* sql`
|
|
293311
|
+
UPDATE workspace_bulk_transfers SET state = 'cancelled', updated_at = ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
293312
|
+
WHERE transfer_id = ${input.transferId}
|
|
293313
|
+
`;
|
|
293314
|
+
}
|
|
293315
|
+
return {
|
|
293316
|
+
transferId: input.transferId,
|
|
293317
|
+
state: row.state === "completed" ? "completed" : "cancelled"
|
|
293318
|
+
};
|
|
293319
|
+
});
|
|
293320
|
+
const cancelWrite = (input) => withTransferLock(input.transferId, cancelWriteEffect(input)).pipe(require_Schema$1.mapError(mapFailure("cancel write", input.transferId)));
|
|
293321
|
+
const cleanupExpiredEffect = require_Schema$1.fn("WorkspaceBulkTransfer.cleanupExpired")(function* () {
|
|
293322
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
293323
|
+
const rows = yield* sql`
|
|
293324
|
+
SELECT transfer_id AS "transferId", temp_path AS "tempPath"
|
|
293325
|
+
FROM workspace_bulk_transfers WHERE state = 'open' AND expires_at <= ${now}
|
|
293326
|
+
`;
|
|
293327
|
+
let expiredCount = 0;
|
|
293328
|
+
for (const row of rows) if (yield* withTransferLock(row.transferId, require_Schema$1.gen(function* () {
|
|
293329
|
+
const current = yield* findTransfer(row.transferId);
|
|
293330
|
+
if (current === null || current.state !== "open" || Date.parse(current.expiresAt) > Date.parse(now)) return false;
|
|
293331
|
+
yield* fileSystem.remove(current.tempPath, { force: true });
|
|
293332
|
+
yield* sql`
|
|
293333
|
+
UPDATE workspace_bulk_transfers SET state = 'expired', updated_at = ${now}
|
|
293334
|
+
WHERE transfer_id = ${row.transferId} AND state = 'open' AND expires_at <= ${now}
|
|
293335
|
+
`;
|
|
293336
|
+
return true;
|
|
293337
|
+
}))) expiredCount += 1;
|
|
293338
|
+
return expiredCount;
|
|
293339
|
+
});
|
|
293340
|
+
const cleanupExpired = () => cleanupExpiredEffect().pipe(require_Schema$1.mapError(mapFailure("expiry cleanup")));
|
|
293341
|
+
return {
|
|
293342
|
+
readFile,
|
|
293343
|
+
beginWrite,
|
|
293344
|
+
writeChunk,
|
|
293345
|
+
commitWrite,
|
|
293346
|
+
cancelWrite,
|
|
293347
|
+
cleanupExpired
|
|
293348
|
+
};
|
|
293349
|
+
});
|
|
293350
|
+
const WorkspaceBulkTransferLive = require_Schema$1.effect(WorkspaceBulkTransfer, makeWorkspaceBulkTransfer());
|
|
293351
|
+
//#endregion
|
|
293216
293352
|
//#region src/workspace/Layers/WorkspacePaths.ts
|
|
293217
293353
|
function toPosixRelativePath(input) {
|
|
293218
293354
|
return input.replaceAll("\\", "/");
|
|
@@ -294825,7 +294961,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
294825
294961
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
294826
294962
|
*/
|
|
294827
294963
|
function readBakedBuildCommit() {
|
|
294828
|
-
return "
|
|
294964
|
+
return "7695a8221bc664caedd33aa411ac7bcdbb0a3594";
|
|
294829
294965
|
}
|
|
294830
294966
|
async function resolveServerBuildCommit(input) {
|
|
294831
294967
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -295018,7 +295154,7 @@ const makeServerEnvironment = require_Schema$1.fn("makeServerEnvironment")(funct
|
|
|
295018
295154
|
os,
|
|
295019
295155
|
arch: platformArch()
|
|
295020
295156
|
},
|
|
295021
|
-
serverVersion:
|
|
295157
|
+
serverVersion: installedForgeServerVersion,
|
|
295022
295158
|
...buildCommit !== void 0 ? { buildCommit } : {},
|
|
295023
295159
|
...deviceRuntimeId !== void 0 ? { deviceRuntimeId } : {},
|
|
295024
295160
|
capabilities: {
|
|
@@ -301807,6 +301943,54 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
301807
301943
|
AND deleted_at IS NULL
|
|
301808
301944
|
AND archived_at IS NULL
|
|
301809
301945
|
ORDER BY created_at ASC, thread_id ASC
|
|
301946
|
+
`
|
|
301947
|
+
});
|
|
301948
|
+
/**
|
|
301949
|
+
* The archive's own rows.
|
|
301950
|
+
*
|
|
301951
|
+
* `listThreadRows` deliberately carries archived threads too, because the
|
|
301952
|
+
* active list needs `archivedAt` to know what to hide and the web archive
|
|
301953
|
+
* panel filters that same snapshot client side. This query is the opposite
|
|
301954
|
+
* selection, for the clients that ask for the archive directly.
|
|
301955
|
+
*/
|
|
301956
|
+
const listArchivedThreadRows = findAll({
|
|
301957
|
+
Request: require_Schema$1.Void,
|
|
301958
|
+
Result: ProjectionThreadDbRowSchema,
|
|
301959
|
+
execute: () => sql`
|
|
301960
|
+
SELECT
|
|
301961
|
+
thread_id AS "threadId",
|
|
301962
|
+
project_id AS "projectId",
|
|
301963
|
+
title,
|
|
301964
|
+
model_selection_json AS "modelSelection",
|
|
301965
|
+
runtime_mode AS "runtimeMode",
|
|
301966
|
+
interaction_mode AS "interactionMode",
|
|
301967
|
+
branch,
|
|
301968
|
+
worktree_path AS "worktreePath",
|
|
301969
|
+
latest_turn_id AS "latestTurnId",
|
|
301970
|
+
created_at AS "createdAt",
|
|
301971
|
+
updated_at AS "updatedAt",
|
|
301972
|
+
archived_at AS "archivedAt",
|
|
301973
|
+
settled_at AS "settledAt",
|
|
301974
|
+
settled_override AS "settledOverride",
|
|
301975
|
+
latest_user_message_at AS "latestUserMessageAt",
|
|
301976
|
+
pending_approval_count AS "pendingApprovalCount",
|
|
301977
|
+
pending_user_input_count AS "pendingUserInputCount",
|
|
301978
|
+
has_actionable_proposed_plan AS "hasActionableProposedPlan",
|
|
301979
|
+
deleted_at AS "deletedAt",
|
|
301980
|
+
kind,
|
|
301981
|
+
parent_thread_id AS "parentThreadId",
|
|
301982
|
+
anchor_kind AS "anchorKind",
|
|
301983
|
+
anchor_cmmd_id AS "anchorCmmdId",
|
|
301984
|
+
linked_pull_request_json AS "linkedPullRequest",
|
|
301985
|
+
status,
|
|
301986
|
+
worker_role AS "workerRole",
|
|
301987
|
+
goal,
|
|
301988
|
+
pending_auto_create_prompt_json AS "pendingAutoCreatePrompt",
|
|
301989
|
+
COALESCE(rate_limit_status_json, '[]') AS "rateLimitStatus"
|
|
301990
|
+
FROM projection_threads
|
|
301991
|
+
WHERE archived_at IS NOT NULL
|
|
301992
|
+
AND deleted_at IS NULL
|
|
301993
|
+
ORDER BY archived_at DESC, thread_id ASC
|
|
301810
301994
|
`
|
|
301811
301995
|
});
|
|
301812
301996
|
const listThreadMessageRows = findAll({
|
|
@@ -302461,13 +302645,22 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
302461
302645
|
if (isPersistenceError(error)) return error;
|
|
302462
302646
|
return toPersistenceSqlError("ProjectionSnapshotQuery.getSnapshot:query")(error);
|
|
302463
302647
|
}));
|
|
302464
|
-
|
|
302648
|
+
/**
|
|
302649
|
+
* One snapshot assembly, two selections of threads.
|
|
302650
|
+
*
|
|
302651
|
+
* The active list and the archive differ only in which thread rows they
|
|
302652
|
+
* read, so they share this body. Keeping them on one mapper is the point:
|
|
302653
|
+
* FOR-36394 was a projected column that one path carried and another
|
|
302654
|
+
* dropped, and a second hand-copied assembly here would invite the same
|
|
302655
|
+
* failure for anyone who only remembered to update the active list.
|
|
302656
|
+
*/
|
|
302657
|
+
const readShellSnapshot = (operation, listThreads) => {
|
|
302465
302658
|
return sql.withTransaction(require_Schema$1.all([
|
|
302466
|
-
listProjectRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(
|
|
302467
|
-
|
|
302468
|
-
listThreadSessionRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(
|
|
302469
|
-
listLatestTurnRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(
|
|
302470
|
-
listProjectionStateRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(
|
|
302659
|
+
listProjectRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listProjects:query`, `ProjectionSnapshotQuery.${operation}:listProjects:decodeRows`))),
|
|
302660
|
+
listThreads(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listThreads:query`, `ProjectionSnapshotQuery.${operation}:listThreads:decodeRows`))),
|
|
302661
|
+
listThreadSessionRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listThreadSessions:query`, `ProjectionSnapshotQuery.${operation}:listThreadSessions:decodeRows`))),
|
|
302662
|
+
listLatestTurnRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listLatestTurns:query`, `ProjectionSnapshotQuery.${operation}:listLatestTurns:decodeRows`))),
|
|
302663
|
+
listProjectionStateRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError(`ProjectionSnapshotQuery.${operation}:listProjectionState:query`, `ProjectionSnapshotQuery.${operation}:listProjectionState:decodeRows`)))
|
|
302471
302664
|
])).pipe(require_Schema$1.flatMap(([projectRows, threadRows, sessionRows, latestTurnRows, stateRows]) => require_Schema$1.gen(function* () {
|
|
302472
302665
|
let updatedAt = null;
|
|
302473
302666
|
for (const row of projectRows) updatedAt = maxIso(updatedAt, row.updatedAt);
|
|
@@ -302488,12 +302681,14 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
302488
302681
|
projects: projectRows.filter((row) => row.deletedAt === null).map((row) => mapProjectShellRow(row, repositoryIdentities.get(row.projectId) ?? null, projectAvailabilities.get(row.projectId) ?? "unknown")),
|
|
302489
302682
|
threads: threadShells.filter((thread) => projectIds.has(thread.projectId)),
|
|
302490
302683
|
updatedAt: updatedAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
302491
|
-
}).pipe(require_Schema$1.mapError(toPersistenceDecodeError(
|
|
302684
|
+
}).pipe(require_Schema$1.mapError(toPersistenceDecodeError(`ProjectionSnapshotQuery.${operation}:decodeShellSnapshot`)));
|
|
302492
302685
|
})), require_Schema$1.mapError((error) => {
|
|
302493
302686
|
if (isPersistenceError(error)) return error;
|
|
302494
|
-
return toPersistenceSqlError(
|
|
302687
|
+
return toPersistenceSqlError(`ProjectionSnapshotQuery.${operation}:query`)(error);
|
|
302495
302688
|
}));
|
|
302496
302689
|
};
|
|
302690
|
+
const getShellSnapshot = () => readShellSnapshot("getShellSnapshot", listThreadRows);
|
|
302691
|
+
const getArchivedShellSnapshot = () => readShellSnapshot("getArchivedShellSnapshot", listArchivedThreadRows);
|
|
302497
302692
|
const listActiveWorkerShells = () => sql.withTransaction(require_Schema$1.all([
|
|
302498
302693
|
listActiveWorkerThreadRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreads:decodeRows"))),
|
|
302499
302694
|
listActiveWorkerSessionRows(void 0).pipe(require_Schema$1.mapError(toPersistenceSqlOrDecodeError("ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:query", "ProjectionSnapshotQuery.listActiveWorkerShells:listThreadSessions:decodeRows"))),
|
|
@@ -302661,6 +302856,7 @@ const makeProjectionSnapshotQuery = require_Schema$1.gen(function* () {
|
|
|
302661
302856
|
return {
|
|
302662
302857
|
getSnapshot,
|
|
302663
302858
|
getShellSnapshot,
|
|
302859
|
+
getArchivedShellSnapshot,
|
|
302664
302860
|
listActiveWorkerShells,
|
|
302665
302861
|
listStaleRunningSessionShells,
|
|
302666
302862
|
getCounts,
|
|
@@ -306682,7 +306878,6 @@ const cmmdUpdateProjectAgentMemberAvatarRouteLayer = registerProxyRoute({
|
|
|
306682
306878
|
});
|
|
306683
306879
|
//#endregion
|
|
306684
306880
|
//#region src/command/runtimeProviderAuthorityUpgradeModel.ts
|
|
306685
|
-
const GIT_COMMIT = /^[0-9a-f]{40}$/u;
|
|
306686
306881
|
const AUTHORITY_UPGRADE_OPERATION_KEYS = new Set([
|
|
306687
306882
|
"operationId",
|
|
306688
306883
|
"runtimeId",
|
|
@@ -306692,7 +306887,7 @@ const AUTHORITY_UPGRADE_OPERATION_KEYS = new Set([
|
|
|
306692
306887
|
"attemptCount",
|
|
306693
306888
|
"expectedCredentialEpoch",
|
|
306694
306889
|
"resultingCredentialEpoch",
|
|
306695
|
-
"
|
|
306890
|
+
"targetForgeServerVersion",
|
|
306696
306891
|
"retryAt",
|
|
306697
306892
|
"completedAt",
|
|
306698
306893
|
"failureCode"
|
|
@@ -306760,7 +306955,7 @@ function parseRuntimeAuthorityUpgradeOperation(value, expected) {
|
|
|
306760
306955
|
Number.isSafeInteger(operation.attemptCount) && operation.attemptCount >= 0,
|
|
306761
306956
|
operation.expectedCredentialEpoch === 0,
|
|
306762
306957
|
operation.resultingCredentialEpoch === 1,
|
|
306763
|
-
|
|
306958
|
+
isExactForgeServerVersion(operation.targetForgeServerVersion),
|
|
306764
306959
|
isNullableDateTime(operation.retryAt),
|
|
306765
306960
|
isNullableDateTime(operation.completedAt),
|
|
306766
306961
|
operation.failureCode === null || typeof operation.failureCode === "string",
|
|
@@ -306777,6 +306972,7 @@ function isLegacyRuntimeConnection(value, expectedRuntimeId) {
|
|
|
306777
306972
|
runtime.brokerAuthorityAttestedImageRef === null,
|
|
306778
306973
|
runtime.brokerAuthorityAttestedImageVersion === null,
|
|
306779
306974
|
runtime.brokerAuthorityAttestedForgeGitCommit === null,
|
|
306975
|
+
runtime.brokerAuthorityAttestedForgeServerVersion === null,
|
|
306780
306976
|
runtime.brokerAuthorityAttestedMachineId === null,
|
|
306781
306977
|
runtime.brokerAuthorityAttestedCredentialEpoch === null,
|
|
306782
306978
|
runtime.brokerAuthorityAttestedAt === null
|
|
@@ -306940,13 +307136,13 @@ function hasTrustedRuntimeRoute(url, trustedBase) {
|
|
|
306940
307136
|
const relativePath = url.pathname.slice(trustedPrefix.length);
|
|
306941
307137
|
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);
|
|
306942
307138
|
}
|
|
306943
|
-
function hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId,
|
|
306944
|
-
return runtime.runtimeId === expectedRuntimeId && runtime.runtimeCapabilityMode === "exact_machine_v2" && isNonEmptyString(runtime.brokerAuthorityAttestedMachineId) && isSafeCredentialEpoch(runtime.brokerAuthorityAttestedCredentialEpoch) && (
|
|
307139
|
+
function hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId, expectedAttestedForgeServerVersion) {
|
|
307140
|
+
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);
|
|
306945
307141
|
}
|
|
306946
|
-
function parseRuntimeConnection(value, expectedRuntimeId, trustedRuntimeRouterBaseUrl,
|
|
307142
|
+
function parseRuntimeConnection(value, expectedRuntimeId, trustedRuntimeRouterBaseUrl, expectedAttestedForgeServerVersion) {
|
|
306947
307143
|
const data = asRecord$1(unwrapData(value));
|
|
306948
307144
|
const runtime = asRecord$1(data?.runtime);
|
|
306949
|
-
if (!data || !runtime || !hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId,
|
|
307145
|
+
if (!data || !runtime || !hasExactRuntimeConnectionFields(data, runtime, expectedRuntimeId, expectedAttestedForgeServerVersion)) return null;
|
|
306950
307146
|
const trustedBase = parseTrustedRuntimeRouterBaseUrl(trustedRuntimeRouterBaseUrl);
|
|
306951
307147
|
if (!trustedBase) return null;
|
|
306952
307148
|
let httpBaseUrl;
|
|
@@ -307126,7 +307322,7 @@ function classifyInitialConnection(outcome, runtimeId, trustedRuntimeRouterBaseU
|
|
|
307126
307322
|
}
|
|
307127
307323
|
};
|
|
307128
307324
|
}
|
|
307129
|
-
function reconnectExactRuntime(input,
|
|
307325
|
+
function reconnectExactRuntime(input, targetForgeServerVersion) {
|
|
307130
307326
|
return require_Schema$1.gen(function* () {
|
|
307131
307327
|
const connectPath = `/api/forge/context/runtimes/${encodeURIComponent(input.runtimeId)}/connect`;
|
|
307132
307328
|
const reconnected = yield* postRuntimeProviderCmmd({
|
|
@@ -307143,7 +307339,7 @@ function reconnectExactRuntime(input, targetForgeGitCommit) {
|
|
|
307143
307339
|
error: reconnected.error,
|
|
307144
307340
|
stage: "reconnect"
|
|
307145
307341
|
};
|
|
307146
|
-
const exact = parseRuntimeConnection(reconnected.value, input.runtimeId, input.trustedRuntimeRouterBaseUrl,
|
|
307342
|
+
const exact = parseRuntimeConnection(reconnected.value, input.runtimeId, input.trustedRuntimeRouterBaseUrl, targetForgeServerVersion);
|
|
307147
307343
|
return exact ? {
|
|
307148
307344
|
ok: true,
|
|
307149
307345
|
value: exact
|
|
@@ -307154,7 +307350,7 @@ function reconnectExactRuntime(input, targetForgeGitCommit) {
|
|
|
307154
307350
|
});
|
|
307155
307351
|
}
|
|
307156
307352
|
function resolveOperation(input, operation) {
|
|
307157
|
-
return operation.status === "completed" ? reconnectExactRuntime(input, operation.
|
|
307353
|
+
return operation.status === "completed" ? reconnectExactRuntime(input, operation.targetForgeServerVersion) : require_Schema$1.succeed(pendingOperation(operation));
|
|
307158
307354
|
}
|
|
307159
307355
|
function beginExactRuntimeConnection(input) {
|
|
307160
307356
|
return require_Schema$1.gen(function* () {
|