@cmmd-center/forge 0.13.10 → 0.13.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BunHttpServer-C7-1JJ7b.cjs → BunHttpServer-C0oPP_3A.cjs} +4 -4
- package/dist/{BunHttpServer-CVPGkizx.mjs → BunHttpServer-FbbQmsl_.mjs} +4 -4
- package/dist/{BunServices-CPrZSIfE.mjs → BunServices-DC7nhA3w.mjs} +2 -2
- package/dist/{BunServices-Dl-3897e.cjs → BunServices-DkenwD7S.cjs} +2 -2
- package/dist/{Cache-Cf3ARNaS.cjs → Cache-BmGc_2BZ.cjs} +2 -2
- package/dist/{Cache-C9cwdCDt.mjs → Cache-D57GWb9-.mjs} +2 -2
- package/dist/{HttpServer-CggZzScj.cjs → HttpServer-BBzRBmVt.cjs} +2 -2
- package/dist/{HttpServer-COaBeM7-.mjs → HttpServer-Bclwvu0I.mjs} +2 -2
- package/dist/{NodeHttpServer-UQnvZnmu.mjs → NodeHttpServer-BJ4l15f-.mjs} +5 -5
- package/dist/{NodeHttpServer-Bi339XFk.cjs → NodeHttpServer-Cjd3bFrD.cjs} +5 -5
- package/dist/{NodePTY-UOaRyQsj.cjs → NodePTY-BehazUMi.cjs} +2 -2
- package/dist/{NodePTY-B1u6RHXk.mjs → NodePTY-BhezYy9H.mjs} +2 -2
- package/dist/{NodeServices-R0BZsZ72.cjs → NodeServices-BvdIc9Vh.cjs} +1 -1
- package/dist/{NodeServices-CKFIZVu5.cjs → NodeServices-CtC4a-h5.cjs} +2 -2
- package/dist/{NodeServices-z1Ac4lEO.mjs → NodeServices-D3IxOzHl.mjs} +2 -2
- package/dist/{NodeSqliteClient-CyJvo9y9.cjs → NodeSqliteClient-CfsdC3AB.cjs} +4 -4
- package/dist/{NodeSqliteClient-BrzqdOsF.mjs → NodeSqliteClient-md8xd9hY.mjs} +4 -4
- package/dist/{NodeTerminal-pYVVRTiD.mjs → NodeTerminal-9iHZIdt1.mjs} +2 -2
- package/dist/{NodeTerminal-B89MobLp.cjs → NodeTerminal-CO-i-6Je.cjs} +2 -2
- package/dist/{Path-BunGrE8F.cjs → Path-BQOH_fWQ.cjs} +7 -1
- package/dist/{Path-evOm-H0Q.mjs → Path-CZJ1gPPo.mjs} +2 -2
- package/dist/{SqlError-CezTFGvr.mjs → SqlError-BMBkezUO.mjs} +2 -2
- package/dist/{SqlError-1wIMjXAf.cjs → SqlError-g4pLzNWR.cjs} +2 -2
- package/dist/{SqliteClient-D3Yd3ZHn.mjs → SqliteClient-BKB695RF.mjs} +3 -3
- package/dist/{SqliteClient-bo7iui1t.cjs → SqliteClient-O1ABIngw.cjs} +3 -3
- package/dist/bin.cjs +1077 -214
- package/dist/bin.mjs +1072 -217
- package/dist/client/assets/{CanvasPanel-z0F-jdf1.js → CanvasPanel-BjHxCGlb.js} +2 -2
- package/dist/client/assets/{CompositeItem-P9UVXCKv.js → CompositeItem-D4LER3__.js} +2 -2
- package/dist/client/assets/{DiffPanel-aGVgMMDS.js → DiffPanel-D3btZGjW.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-CAL295cZ.js → DiffPanel.logic-DWcwE9Wj.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-BLRSawDM.js → DiffPanelShell-oXHh--ae.js} +109 -109
- package/dist/client/assets/{DiffWorkerPoolProvider-tqcKyIAF.js → DiffWorkerPoolProvider-R9YPaaNy.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-CYLmbiv7.js → PullRequestCodePanel-DTCc2TBl.js} +2 -2
- package/dist/client/assets/{Virtualizer-NYdNfon2.js → Virtualizer-arKGxq_z.js} +2 -2
- package/dist/client/assets/{canvasStateStore-CdQxd2aO.js → canvasStateStore-B5zd1u9r.js} +2 -2
- package/dist/client/assets/cmmdAuthRedirect-DeFAIQpj.js +2 -0
- package/dist/client/assets/index-Bn-ASQRG.css +1 -0
- package/dist/client/assets/index-C7YKDHIe.js +154 -0
- package/dist/client/assets/{popover-CUSlb3eK.js → popover-DM7IA0zJ.js} +2 -2
- package/dist/client/assets/src-LDQtPKQt.js +3 -0
- package/dist/client/assets/{toggle-BREV66nT.js → toggle-DVtS0BJJ.js} +2 -2
- package/dist/client/index.html +11 -11
- package/dist/forge-build.json +1 -1
- package/dist/runtimeBootstrap.cjs +1 -1
- package/dist/runtimeBootstrap.mjs +1 -1
- package/dist/{runtimeControlAuthority-B3kNmYRT.cjs → runtimeControlAuthority-BkVB0uAA.cjs} +17 -1
- package/dist/{runtimeControlAuthority-D_zKr_eI.mjs → runtimeControlAuthority-E_kdeD_P.mjs} +12 -2
- package/package.json +1 -1
- package/dist/client/assets/cmmdAuthRedirect-Bp1uQaQ9.js +0 -2
- package/dist/client/assets/index-Bx4rqeUi.css +0 -1
- package/dist/client/assets/index-fNpsmaLQ.js +0 -154
- package/dist/client/assets/src-BL-mOkT1.js +0 -3
package/dist/bin.cjs
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
4
|
const require_chunk = require("./chunk-BhaIgcZK.cjs");
|
|
5
5
|
const require_Schema$1 = require("./Schema-CDCJ3xZo.cjs");
|
|
6
|
-
const require_Path = require("./Path-
|
|
7
|
-
const require_NodeTerminal = require("./NodeTerminal-
|
|
8
|
-
const require_NodeServices = require("./NodeServices-
|
|
9
|
-
const require_HttpServer = require("./HttpServer-
|
|
6
|
+
const require_Path = require("./Path-BQOH_fWQ.cjs");
|
|
7
|
+
const require_NodeTerminal = require("./NodeTerminal-CO-i-6Je.cjs");
|
|
8
|
+
const require_NodeServices = require("./NodeServices-CtC4a-h5.cjs");
|
|
9
|
+
const require_HttpServer = require("./HttpServer-BBzRBmVt.cjs");
|
|
10
10
|
const require_Config = require("./Config-CwKCCk14.cjs");
|
|
11
|
-
const require_Cache = require("./Cache-
|
|
12
|
-
const require_SqlError = require("./SqlError-
|
|
11
|
+
const require_Cache = require("./Cache-BmGc_2BZ.cjs");
|
|
12
|
+
const require_SqlError = require("./SqlError-g4pLzNWR.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-BkVB0uAA.cjs");
|
|
16
16
|
const require_PTY = require("./PTY-B4ZctbwQ.cjs");
|
|
17
17
|
let node_child_process = require("node:child_process");
|
|
18
18
|
node_child_process = require_chunk.__toESM(node_child_process);
|
|
@@ -17920,10 +17920,32 @@ const OrchestrationShellStreamItem = require_Schema$1.Union([require_Schema$1.St
|
|
|
17920
17920
|
kind: require_Schema$1.Literal("snapshot"),
|
|
17921
17921
|
snapshot: OrchestrationShellSnapshot
|
|
17922
17922
|
}), OrchestrationShellStreamEvent]);
|
|
17923
|
-
const OrchestrationSubscribeThreadInput = require_Schema$1.Struct({
|
|
17923
|
+
const OrchestrationSubscribeThreadInput = require_Schema$1.Struct({
|
|
17924
|
+
threadId: ThreadId,
|
|
17925
|
+
afterSequence: require_Schema$1.optionalKey(NonNegativeInt),
|
|
17926
|
+
requestCompletionMarker: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
17927
|
+
turnLimit: require_Schema$1.optionalKey(PositiveInt)
|
|
17928
|
+
});
|
|
17929
|
+
require_Schema$1.Struct({
|
|
17930
|
+
turnLimit: require_Schema$1.optionalKey(PositiveInt),
|
|
17931
|
+
beforeCursor: require_Schema$1.optionalKey(TrimmedNonEmptyString)
|
|
17932
|
+
});
|
|
17933
|
+
/**
|
|
17934
|
+
* Page metadata for a windowed thread detail read. `beforeCursor` is opaque and
|
|
17935
|
+
* exclusive: passing it back returns the adjacent slice of older turns. `null`
|
|
17936
|
+
* means nothing older is left. `snapshotSequence` mirrors the top-level
|
|
17937
|
+
* sequence so a history page can be sequence-checked against live state before
|
|
17938
|
+
* a client merges it.
|
|
17939
|
+
*/
|
|
17940
|
+
const OrchestrationThreadDetailPage = require_Schema$1.Struct({
|
|
17941
|
+
beforeCursor: require_Schema$1.NullOr(TrimmedNonEmptyString),
|
|
17942
|
+
hasMore: require_Schema$1.Boolean,
|
|
17943
|
+
snapshotSequence: NonNegativeInt
|
|
17944
|
+
});
|
|
17924
17945
|
const OrchestrationThreadDetailSnapshot = require_Schema$1.Struct({
|
|
17925
17946
|
snapshotSequence: NonNegativeInt,
|
|
17926
|
-
thread: OrchestrationThread
|
|
17947
|
+
thread: OrchestrationThread,
|
|
17948
|
+
page: require_Schema$1.optional(OrchestrationThreadDetailPage)
|
|
17927
17949
|
});
|
|
17928
17950
|
const ProjectCreateCommand = require_Schema$1.Struct({
|
|
17929
17951
|
type: require_Schema$1.Literal("project.create"),
|
|
@@ -18747,13 +18769,17 @@ const OrchestrationEvent = require_Schema$1.Union([
|
|
|
18747
18769
|
payload: ThreadRateLimitSetPayload$1
|
|
18748
18770
|
})
|
|
18749
18771
|
]);
|
|
18750
|
-
const OrchestrationThreadStreamItem = require_Schema$1.Union([
|
|
18751
|
-
kind: require_Schema$1.Literal("
|
|
18752
|
-
|
|
18753
|
-
|
|
18754
|
-
|
|
18755
|
-
|
|
18756
|
-
|
|
18772
|
+
const OrchestrationThreadStreamItem = require_Schema$1.Union([
|
|
18773
|
+
require_Schema$1.Struct({ kind: require_Schema$1.Literal("synchronized") }),
|
|
18774
|
+
require_Schema$1.Struct({
|
|
18775
|
+
kind: require_Schema$1.Literal("snapshot"),
|
|
18776
|
+
snapshot: OrchestrationThreadDetailSnapshot
|
|
18777
|
+
}),
|
|
18778
|
+
require_Schema$1.Struct({
|
|
18779
|
+
kind: require_Schema$1.Literal("event"),
|
|
18780
|
+
event: OrchestrationEvent
|
|
18781
|
+
})
|
|
18782
|
+
]);
|
|
18757
18783
|
const OrchestrationCommandReceiptStatus = require_Schema$1.Literals(["accepted", "rejected"]);
|
|
18758
18784
|
const TurnCountRange = require_Schema$1.Struct({
|
|
18759
18785
|
fromTurnCount: NonNegativeInt,
|
|
@@ -22978,6 +23004,8 @@ const ServerConfig$1 = require_Schema$1.Struct({
|
|
|
22978
23004
|
availableEditors: require_Schema$1.ArraySchema(EditorId),
|
|
22979
23005
|
shellRevealInFileManager: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
22980
23006
|
shellRevealInFileManagerKind: require_Schema$1.optionalKey(FileManagerRevealKind),
|
|
23007
|
+
threadResumeCompletionMarker: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
23008
|
+
threadSnapshotPagination: require_Schema$1.optionalKey(require_Schema$1.Boolean),
|
|
22981
23009
|
observability: ServerObservability,
|
|
22982
23010
|
settings: ServerSettings
|
|
22983
23011
|
});
|
|
@@ -41486,7 +41514,7 @@ function splitPath(filename) {
|
|
|
41486
41514
|
const parts = splitPathRe.exec(truncated);
|
|
41487
41515
|
return parts ? parts.slice(1) : [];
|
|
41488
41516
|
}
|
|
41489
|
-
function dirname$
|
|
41517
|
+
function dirname$8(path) {
|
|
41490
41518
|
const result = splitPath(path);
|
|
41491
41519
|
const root = result[0] || "";
|
|
41492
41520
|
let dir = result[1];
|
|
@@ -58801,7 +58829,7 @@ function getSpotlightConfig(optionsSpotlight) {
|
|
|
58801
58829
|
function normalizeWindowsPath(path) {
|
|
58802
58830
|
return path.replace(/^[A-Z]:/, "").replace(/\\/g, "/");
|
|
58803
58831
|
}
|
|
58804
|
-
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname$
|
|
58832
|
+
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname$8(process.argv[1]) : process.cwd(), isWindows = node_path.sep === "\\") {
|
|
58805
58833
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
58806
58834
|
return (filename) => {
|
|
58807
58835
|
if (!filename) return;
|
|
@@ -66289,7 +66317,7 @@ function normalizeNumberish(value) {
|
|
|
66289
66317
|
}
|
|
66290
66318
|
//#endregion
|
|
66291
66319
|
//#region package.json
|
|
66292
|
-
var version$1 = "0.13.
|
|
66320
|
+
var version$1 = "0.13.12";
|
|
66293
66321
|
//#endregion
|
|
66294
66322
|
//#region src/sentry.ts
|
|
66295
66323
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -66716,6 +66744,29 @@ function accessTokenExpiresInSeconds(remainingMillis) {
|
|
|
66716
66744
|
return Math.max(0, Math.floor(remainingMillis / 1e3));
|
|
66717
66745
|
}
|
|
66718
66746
|
//#endregion
|
|
66747
|
+
//#region src/auth/commandBootstrapClient.ts
|
|
66748
|
+
const BOOTSTRAP_CLIENT_LABELS = {
|
|
66749
|
+
desktop: "Forge Desktop Cloud",
|
|
66750
|
+
mobile: "Forge Mobile Cloud"
|
|
66751
|
+
};
|
|
66752
|
+
/**
|
|
66753
|
+
* Client metadata for a bearer bootstrap session.
|
|
66754
|
+
*
|
|
66755
|
+
* The declared device type wins over the one inferred from the user agent. A
|
|
66756
|
+
* React Native fetch sends whatever the platform's networking stack sends, and
|
|
66757
|
+
* on iOS that is a CFNetwork string no user-agent heuristic reads as a phone.
|
|
66758
|
+
*/
|
|
66759
|
+
function resolveCommandBootstrapClient(input) {
|
|
66760
|
+
const deviceType = input.clientDeviceType ?? "desktop";
|
|
66761
|
+
return {
|
|
66762
|
+
...deriveAuthClientMetadata({
|
|
66763
|
+
request: input.request,
|
|
66764
|
+
label: BOOTSTRAP_CLIENT_LABELS[deviceType]
|
|
66765
|
+
}),
|
|
66766
|
+
deviceType
|
|
66767
|
+
};
|
|
66768
|
+
}
|
|
66769
|
+
//#endregion
|
|
66719
66770
|
//#region ../../packages/shared/src/cmmdIdentity.ts
|
|
66720
66771
|
const MAX_CMMD_AVATAR_URL_LENGTH = 256 * 1024;
|
|
66721
66772
|
const DATA_IMAGE_AVATAR_PATTERN = /^data:image\/(?:avif|gif|jpeg|jpg|png|webp);base64,[a-z0-9+/=]+$/i;
|
|
@@ -67169,14 +67220,26 @@ function extractJwtExpiresAt(jwtToken) {
|
|
|
67169
67220
|
}
|
|
67170
67221
|
}
|
|
67171
67222
|
/**
|
|
67172
|
-
*
|
|
67173
|
-
*
|
|
67223
|
+
* How long before `exp` a stored JWT is already treated as spent.
|
|
67224
|
+
*
|
|
67225
|
+
* Comparing against `exp` exactly leaves a window where Forge sends a token it
|
|
67226
|
+
* believes is good and CMMD has already stopped accepting it: the clocks differ by a
|
|
67227
|
+
* little, the request spends time in flight, and the answer comes back 401 on a
|
|
67228
|
+
* session that had a perfectly good refresh token sitting next to it. Two
|
|
67229
|
+
* minutes is far inside the JWT's own lifetime, so this costs one early
|
|
67230
|
+
* rotation and removes the race.
|
|
67231
|
+
*/
|
|
67232
|
+
const JWT_EXPIRY_SKEW_MS = 2 * 6e4;
|
|
67233
|
+
/**
|
|
67234
|
+
* Returns true when a stored JWT is at or near its expiry, or has no decodable
|
|
67235
|
+
* expiry. CMMD REST JWTs are short-lived bearer tokens, so unknown expiry is
|
|
67236
|
+
* not safe.
|
|
67174
67237
|
*/
|
|
67175
67238
|
function isJwtExpiredForSession(sessionId) {
|
|
67176
67239
|
if (!jwtCache.has(sessionId)) return false;
|
|
67177
67240
|
const expiresAt = jwtExpiryCache.get(sessionId);
|
|
67178
67241
|
if (!expiresAt) return true;
|
|
67179
|
-
return new Date(expiresAt)
|
|
67242
|
+
return new Date(expiresAt).getTime() - JWT_EXPIRY_SKEW_MS <= Date.now();
|
|
67180
67243
|
}
|
|
67181
67244
|
/**
|
|
67182
67245
|
* Hydrate the in-memory cache from the configured persistence layer. Call once at server startup
|
|
@@ -67732,10 +67795,27 @@ const clearUnrecoverableCmmdAuthForSession = (sessionId) => require_Schema$1.gen
|
|
|
67732
67795
|
* Retire refresh credentials copied into historical desktop bearer sessions.
|
|
67733
67796
|
* Electron owns rotation for native desktop OAuth; Forge Local keeps only the
|
|
67734
67797
|
* derived access authority needed by the current bearer session.
|
|
67735
|
-
|
|
67736
|
-
|
|
67737
|
-
|
|
67738
|
-
|
|
67798
|
+
*
|
|
67799
|
+
* Scoped to the bootstrapping client, not to the whole subject. The phone and
|
|
67800
|
+
* Electron sign in as the same `user:<id>`, so a subject-wide sweep let one
|
|
67801
|
+
* desktop launch null out a phone's refresh token and end its session a day
|
|
67802
|
+
* later. Two clauses keep that from happening:
|
|
67803
|
+
*
|
|
67804
|
+
* - `client_device_type = 'desktop'`, which now excludes phones outright,
|
|
67805
|
+
* because the mobile app declares itself at bootstrap.
|
|
67806
|
+
* - the same recorded `client_user_agent` as the bootstrap doing the
|
|
67807
|
+
* retirement, which is the only thing that separates a phone row written
|
|
67808
|
+
* before that stamp existed from a real Electron row. Both are stamped
|
|
67809
|
+
* `desktop`; neither carries any other mark. An empty or unmatched user agent
|
|
67810
|
+
* retires nothing, which is the direction that keeps a live credential.
|
|
67811
|
+
*
|
|
67812
|
+
* Electron re-bootstraps under a stable user agent, so its own historical rows
|
|
67813
|
+
* still match and still get swept.
|
|
67814
|
+
*/
|
|
67815
|
+
const clearDesktopBearerRefreshTokensForSubject = (input) => require_Schema$1.gen(function* () {
|
|
67816
|
+
const normalizedSubject = input.subject.trim();
|
|
67817
|
+
const normalizedUserAgent = input.clientUserAgent?.trim() ?? "";
|
|
67818
|
+
if (!normalizedSubject || !normalizedUserAgent) return;
|
|
67739
67819
|
const rows = yield* (yield* require_SqlError.SqlClient)`
|
|
67740
67820
|
UPDATE command_access_tokens
|
|
67741
67821
|
SET
|
|
@@ -67748,6 +67828,7 @@ const clearDesktopBearerRefreshTokensForSubject = (subject) => require_Schema$1.
|
|
|
67748
67828
|
AND role = 'owner'
|
|
67749
67829
|
AND method = 'bearer-session-token'
|
|
67750
67830
|
AND client_device_type = 'desktop'
|
|
67831
|
+
AND client_user_agent = ${normalizedUserAgent}
|
|
67751
67832
|
)
|
|
67752
67833
|
RETURNING session_id AS "sessionId"
|
|
67753
67834
|
`.pipe(require_Schema$1.mapError(toPersistenceSqlError("CommandAccessTokenStore.clearDesktopBearerRefresh:query")));
|
|
@@ -67770,6 +67851,17 @@ const OAUTH_STATE_COOKIE = "forge_oauth_state";
|
|
|
67770
67851
|
const OAUTH_VERIFIER_COOKIE = "forge_oauth_verifier";
|
|
67771
67852
|
//#endregion
|
|
67772
67853
|
//#region src/auth/http/cloudOwnerOAuth.ts
|
|
67854
|
+
const FORGE_CLOUD_SESSION_GRANT_COOKIE = "forge_cloud_session_grant";
|
|
67855
|
+
function resolveCloudOwnerSessionGrantCookie(input) {
|
|
67856
|
+
const token = input.token?.trim();
|
|
67857
|
+
if (!token || !input.expiresAt) return null;
|
|
67858
|
+
const expiresAt = new Date(input.expiresAt);
|
|
67859
|
+
if (!Number.isFinite(expiresAt.getTime()) || expiresAt.getTime() <= Date.now()) return null;
|
|
67860
|
+
return {
|
|
67861
|
+
token,
|
|
67862
|
+
expiresAt
|
|
67863
|
+
};
|
|
67864
|
+
}
|
|
67773
67865
|
function validateCloudOwnerCallbackAssertion(input) {
|
|
67774
67866
|
if (input.serverAuth.isForgeCloudShell?.() !== true) return require_Schema$1.succeed(null);
|
|
67775
67867
|
if (!input.assertionToken || !input.serverAuth.verifyForgeCloudOwnerAssertion) return require_Schema$1.fail(new AuthError({
|
|
@@ -67833,8 +67925,19 @@ function cloudOwnerAssertionCookieOptions(assertion) {
|
|
|
67833
67925
|
secure: true
|
|
67834
67926
|
};
|
|
67835
67927
|
}
|
|
67836
|
-
function
|
|
67837
|
-
return
|
|
67928
|
+
function cloudOwnerSessionGrantCookieOptions(grant) {
|
|
67929
|
+
return {
|
|
67930
|
+
expires: grant.expiresAt,
|
|
67931
|
+
httpOnly: true,
|
|
67932
|
+
path: "/api/auth",
|
|
67933
|
+
sameSite: "lax",
|
|
67934
|
+
secure: true
|
|
67935
|
+
};
|
|
67936
|
+
}
|
|
67937
|
+
function cloudOwnerOAuthResponse(homeUrl, assertion, sessionGrant) {
|
|
67938
|
+
let response = require_HttpServer.redirect(homeUrl, { status: 302 }).pipe(require_HttpServer.setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
|
|
67939
|
+
if (sessionGrant) response = response.pipe(require_Schema$1.flatMap(require_HttpServer.setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, sessionGrant.token, cloudOwnerSessionGrantCookieOptions(sessionGrant))));
|
|
67940
|
+
return response.pipe(require_Schema$1.flatMap(require_HttpServer.setCookie(OAUTH_STATE_COOKIE, "", expiredCallbackCookie)), require_Schema$1.flatMap(require_HttpServer.setCookie(OAUTH_VERIFIER_COOKIE, "", expiredCallbackCookie)));
|
|
67838
67941
|
}
|
|
67839
67942
|
//#endregion
|
|
67840
67943
|
//#region src/auth/http.ts
|
|
@@ -67884,6 +67987,56 @@ const authSessionRouteLayer = add$1("GET", "/api/auth/session", require_Schema$1
|
|
|
67884
67987
|
}));
|
|
67885
67988
|
return response;
|
|
67886
67989
|
}));
|
|
67990
|
+
const authSessionRenewRouteLayer = add$1("POST", "/api/auth/session/renew", require_Schema$1.gen(function* () {
|
|
67991
|
+
const request = yield* require_HttpServer.HttpServerRequest;
|
|
67992
|
+
const serverAuth = yield* ServerAuth;
|
|
67993
|
+
if (serverAuth.isForgeCloudShell?.() !== true) return yield* new AuthError({
|
|
67994
|
+
message: "Session renewal is unavailable.",
|
|
67995
|
+
status: 401
|
|
67996
|
+
});
|
|
67997
|
+
const sessionGrant = request.cookies[FORGE_CLOUD_SESSION_GRANT_COOKIE]?.trim();
|
|
67998
|
+
if (!sessionGrant) return yield* new AuthError({
|
|
67999
|
+
message: "Session renewal is unavailable.",
|
|
68000
|
+
status: 401
|
|
68001
|
+
});
|
|
68002
|
+
const credentials = yield* resolveCmmdTokenBundleCredentials(yield* readCmmdJsonResponse(yield* require_Schema$1.tryPromise({
|
|
68003
|
+
try: () => fetch(resolveCommandAppEndpoint("/api/mcp-server/oauth/token"), {
|
|
68004
|
+
method: "POST",
|
|
68005
|
+
headers: { "content-type": "application/json" },
|
|
68006
|
+
body: JSON.stringify({
|
|
68007
|
+
grant_type: "forge_session_grant",
|
|
68008
|
+
grant_token: sessionGrant,
|
|
68009
|
+
client_id: "cmmd-forge-web"
|
|
68010
|
+
})
|
|
68011
|
+
}),
|
|
68012
|
+
catch: (cause) => new AuthError({
|
|
68013
|
+
message: "Failed to renew the Forge Cloud session with CMMD.",
|
|
68014
|
+
status: 500,
|
|
68015
|
+
cause
|
|
68016
|
+
})
|
|
68017
|
+
}), "Failed to renew the Forge Cloud session with CMMD."));
|
|
68018
|
+
const { cmmdIdentity, userId, patToStore } = credentials;
|
|
68019
|
+
if (!cmmdIdentity || !userId || !patToStore) return yield* new AuthError({
|
|
68020
|
+
message: "CMMD did not return a complete renewed Forge session.",
|
|
68021
|
+
status: 500
|
|
68022
|
+
});
|
|
68023
|
+
const assertion = yield* validateCloudOwnerCallbackAssertion({
|
|
68024
|
+
serverAuth,
|
|
68025
|
+
assertionToken: credentials.forgeOwnerAssertion,
|
|
68026
|
+
authenticatedUserId: userId
|
|
68027
|
+
});
|
|
68028
|
+
if (!assertion) return yield* require_Schema$1.die(/* @__PURE__ */ new Error("Expected a renewed Forge Cloud owner assertion."));
|
|
68029
|
+
yield* persistCloudOwnerArtifacts({
|
|
68030
|
+
assertion,
|
|
68031
|
+
accessToken: patToStore,
|
|
68032
|
+
identity: cmmdIdentity,
|
|
68033
|
+
jwtToken: credentials.jwtToStore,
|
|
68034
|
+
refreshToken: credentials.refreshToken,
|
|
68035
|
+
sessionGrant,
|
|
68036
|
+
sessionGrantExpiresAt: void 0
|
|
68037
|
+
});
|
|
68038
|
+
return yield* require_HttpServer.jsonUnsafe({ renewed: true }, { status: 200 }).pipe(require_HttpServer.setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
|
|
68039
|
+
}).pipe(require_Schema$1.catchTag("AuthError", (error) => respondToAuthError(error))));
|
|
67887
68040
|
const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", require_Schema$1.gen(function* () {
|
|
67888
68041
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
67889
68042
|
const serverAuth = yield* ServerAuth;
|
|
@@ -67906,14 +68059,24 @@ const authLogoutRouteLayer = add$1("POST", "/api/auth/logout", require_Schema$1.
|
|
|
67906
68059
|
sameSite: "lax",
|
|
67907
68060
|
secure
|
|
67908
68061
|
}));
|
|
67909
|
-
if (cloudShell)
|
|
67910
|
-
|
|
67911
|
-
|
|
67912
|
-
|
|
67913
|
-
|
|
67914
|
-
|
|
67915
|
-
|
|
67916
|
-
|
|
68062
|
+
if (cloudShell) {
|
|
68063
|
+
response = yield* response.pipe(require_HttpServer.setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, "", {
|
|
68064
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
68065
|
+
httpOnly: true,
|
|
68066
|
+
maxAge: 0,
|
|
68067
|
+
path: "/",
|
|
68068
|
+
sameSite: "lax",
|
|
68069
|
+
secure: true
|
|
68070
|
+
}));
|
|
68071
|
+
response = yield* response.pipe(require_HttpServer.setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, "", {
|
|
68072
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
68073
|
+
httpOnly: true,
|
|
68074
|
+
maxAge: 0,
|
|
68075
|
+
path: "/api/auth",
|
|
68076
|
+
sameSite: "lax",
|
|
68077
|
+
secure: true
|
|
68078
|
+
}));
|
|
68079
|
+
}
|
|
67917
68080
|
response = yield* pipeExpireLegacySessionCookie(sessions.cookieName, require_Schema$1.succeed(response), { secure });
|
|
67918
68081
|
if (sessions.cookieName !== "forge_session") response = yield* response.pipe(require_HttpServer.setCookie(BASE_SESSION_COOKIE_NAME, "", {
|
|
67919
68082
|
expires: /* @__PURE__ */ new Date(0),
|
|
@@ -67936,7 +68099,8 @@ const CommandBootstrapInput = require_Schema$1.Struct({
|
|
|
67936
68099
|
refreshToken: require_Schema$1.optionalKey(require_Schema$1.String),
|
|
67937
68100
|
userId: require_Schema$1.optionalKey(require_Schema$1.Number),
|
|
67938
68101
|
displayName: require_Schema$1.optionalKey(require_Schema$1.String),
|
|
67939
|
-
user: require_Schema$1.optionalKey(require_Schema$1.Unknown)
|
|
68102
|
+
user: require_Schema$1.optionalKey(require_Schema$1.Unknown),
|
|
68103
|
+
clientDeviceType: require_Schema$1.optionalKey(require_Schema$1.Literals(["desktop", "mobile"]))
|
|
67940
68104
|
});
|
|
67941
68105
|
const CommandOAuthDeviceInput = require_Schema$1.Struct({
|
|
67942
68106
|
client_id: require_Schema$1.String,
|
|
@@ -68484,13 +68648,10 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
|
|
|
68484
68648
|
method: "bearer-session-token",
|
|
68485
68649
|
role: "owner",
|
|
68486
68650
|
subject: sessionSubject,
|
|
68487
|
-
client: {
|
|
68488
|
-
|
|
68489
|
-
|
|
68490
|
-
|
|
68491
|
-
}),
|
|
68492
|
-
deviceType: "desktop"
|
|
68493
|
-
}
|
|
68651
|
+
client: resolveCommandBootstrapClient({
|
|
68652
|
+
request,
|
|
68653
|
+
clientDeviceType: payload.clientDeviceType
|
|
68654
|
+
})
|
|
68494
68655
|
}).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
68495
68656
|
message: "Failed to issue authenticated bearer session.",
|
|
68496
68657
|
cause
|
|
@@ -68500,7 +68661,10 @@ const authCommandBearerBootstrapRouteLayer = add$1("POST", "/api/auth/bootstrap/
|
|
|
68500
68661
|
payload,
|
|
68501
68662
|
cmmdIdentity: validated.cmmdIdentity
|
|
68502
68663
|
});
|
|
68503
|
-
if (!payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject(
|
|
68664
|
+
if ((payload.clientDeviceType ?? "desktop") === "desktop" && !payload.refreshToken?.trim()) yield* clearDesktopBearerRefreshTokensForSubject({
|
|
68665
|
+
subject: sessionSubject,
|
|
68666
|
+
clientUserAgent: request.headers["user-agent"]
|
|
68667
|
+
}).pipe(require_Schema$1.mapError((cause) => new AuthError({
|
|
68504
68668
|
message: "Failed to retire legacy desktop refresh credentials.",
|
|
68505
68669
|
status: 500,
|
|
68506
68670
|
cause
|
|
@@ -68568,6 +68732,14 @@ function completeCloudOwnerPasswordLogin(input) {
|
|
|
68568
68732
|
authenticatedUserId: userId
|
|
68569
68733
|
});
|
|
68570
68734
|
if (!assertion) return yield* require_Schema$1.die(/* @__PURE__ */ new Error("Expected a Forge Cloud owner assertion in cloud shell mode."));
|
|
68735
|
+
const sessionGrant = resolveCloudOwnerSessionGrantCookie({
|
|
68736
|
+
token: credentials.sessionGrant,
|
|
68737
|
+
expiresAt: credentials.sessionGrantExpiresAt
|
|
68738
|
+
});
|
|
68739
|
+
if (!sessionGrant) return yield* new AuthError({
|
|
68740
|
+
message: "CMMD did not return a durable Forge Cloud session grant.",
|
|
68741
|
+
status: 500
|
|
68742
|
+
});
|
|
68571
68743
|
yield* persistCloudOwnerArtifacts({
|
|
68572
68744
|
assertion,
|
|
68573
68745
|
accessToken: patToStore,
|
|
@@ -68577,10 +68749,12 @@ function completeCloudOwnerPasswordLogin(input) {
|
|
|
68577
68749
|
sessionGrant: credentials.sessionGrant,
|
|
68578
68750
|
sessionGrantExpiresAt: credentials.sessionGrantExpiresAt
|
|
68579
68751
|
});
|
|
68580
|
-
|
|
68752
|
+
let response = require_HttpServer.jsonUnsafe({
|
|
68581
68753
|
authenticated: true,
|
|
68582
68754
|
cmmdIdentity
|
|
68583
68755
|
}, { status: 200 }).pipe(require_HttpServer.setCookie(FORGE_CLOUD_OWNER_ASSERTION_COOKIE, assertion.token, cloudOwnerAssertionCookieOptions(assertion)));
|
|
68756
|
+
if (sessionGrant) response = response.pipe(require_Schema$1.flatMap(require_HttpServer.setCookie(FORGE_CLOUD_SESSION_GRANT_COOKIE, sessionGrant.token, cloudOwnerSessionGrantCookieOptions(sessionGrant))));
|
|
68757
|
+
return yield* response;
|
|
68584
68758
|
});
|
|
68585
68759
|
}
|
|
68586
68760
|
const authCmmdPasswordTokenRouteLayer = add$1("POST", "/api/auth/cmmd-password/token", require_Schema$1.gen(function* () {
|
|
@@ -68754,6 +68928,14 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
|
|
|
68754
68928
|
assertionToken: forgeOwnerAssertion,
|
|
68755
68929
|
authenticatedUserId: userId
|
|
68756
68930
|
});
|
|
68931
|
+
const cloudSessionGrant = cloudOwnerAssertion ? resolveCloudOwnerSessionGrantCookie({
|
|
68932
|
+
token: sessionGrant,
|
|
68933
|
+
expiresAt: sessionGrantExpiresAt
|
|
68934
|
+
}) : null;
|
|
68935
|
+
if (cloudOwnerAssertion && !cloudSessionGrant) return yield* new AuthError({
|
|
68936
|
+
message: "CMMD did not return a durable Forge Cloud session grant.",
|
|
68937
|
+
status: 500
|
|
68938
|
+
});
|
|
68757
68939
|
const sessionSubject = resolveCommandOAuthSubject(userId);
|
|
68758
68940
|
const issued = cloudOwnerAssertion ? null : yield* sessions.issue({
|
|
68759
68941
|
method: "browser-session-cookie",
|
|
@@ -68785,7 +68967,7 @@ const authWebCallbackRouteLayer = add$1("GET", "/api/auth/callback", require_Sch
|
|
|
68785
68967
|
else yield* persistLocalOAuthArtifacts(persistenceInput);
|
|
68786
68968
|
const homeUrl = `${publicUrl}/`;
|
|
68787
68969
|
const secureCookie = shouldUseSecureCookie(publicUrl);
|
|
68788
|
-
if (cloudOwnerAssertion) return yield* cloudOwnerOAuthResponse(homeUrl, cloudOwnerAssertion);
|
|
68970
|
+
if (cloudOwnerAssertion) return yield* cloudOwnerOAuthResponse(homeUrl, cloudOwnerAssertion, cloudSessionGrant);
|
|
68789
68971
|
if (!issued) return yield* require_Schema$1.die(/* @__PURE__ */ new Error("Expected a local session outside Forge Cloud."));
|
|
68790
68972
|
return yield* pipeExpireLegacySessionCookie(sessions.cookieName, require_HttpServer.redirect(homeUrl, { status: 302 }).pipe(require_HttpServer.setCookie(sessions.cookieName, issued.token, {
|
|
68791
68973
|
expires: require_Schema$1.toDate(issued.expiresAt),
|
|
@@ -70829,6 +71011,11 @@ function redeemCmmdSessionGrant(grantToken) {
|
|
|
70829
71011
|
};
|
|
70830
71012
|
}).catch(() => null);
|
|
70831
71013
|
}
|
|
71014
|
+
const REJECTED_OAUTH_ERROR_CODES = new Set([
|
|
71015
|
+
"invalid_grant",
|
|
71016
|
+
"invalid_client",
|
|
71017
|
+
"invalid_request"
|
|
71018
|
+
]);
|
|
70832
71019
|
/**
|
|
70833
71020
|
* Rotate a CMMD refresh token into a fresh access JWT.
|
|
70834
71021
|
*
|
|
@@ -70841,7 +71028,7 @@ function redeemCmmdSessionGrant(grantToken) {
|
|
|
70841
71028
|
* code are what identify the failure, and `invalid_grant` in particular means
|
|
70842
71029
|
* the token was already spent, which is a different fix from a 500.
|
|
70843
71030
|
*/
|
|
70844
|
-
function refreshCmmdJwt(refreshToken) {
|
|
71031
|
+
function refreshCmmdJwt(refreshToken, observer) {
|
|
70845
71032
|
return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
|
|
70846
71033
|
method: "POST",
|
|
70847
71034
|
headers: { "Content-Type": "application/json" },
|
|
@@ -70852,7 +71039,9 @@ function refreshCmmdJwt(refreshToken) {
|
|
|
70852
71039
|
})
|
|
70853
71040
|
}).then(async (response) => {
|
|
70854
71041
|
if (!response.ok) {
|
|
70855
|
-
|
|
71042
|
+
const errorCode = await readOAuthErrorCode(response);
|
|
71043
|
+
if (errorCode !== null && REJECTED_OAUTH_ERROR_CODES.has(errorCode)) observer?.onCredentialRejected?.();
|
|
71044
|
+
console.warn(`[cmmd-auth] CMMD refused a JWT rotation with ${response.status}${errorCode === null ? "" : ` (${errorCode})`}`);
|
|
70856
71045
|
return null;
|
|
70857
71046
|
}
|
|
70858
71047
|
const parsed = parseCmmdOAuthRefreshResponse(await response.json());
|
|
@@ -70864,20 +71053,73 @@ function refreshCmmdJwt(refreshToken) {
|
|
|
70864
71053
|
});
|
|
70865
71054
|
}
|
|
70866
71055
|
/**
|
|
70867
|
-
* The OAuth `error` code from a refusal,
|
|
71056
|
+
* The OAuth `error` code from a refusal, lowercased, or null when there is none.
|
|
70868
71057
|
*
|
|
70869
71058
|
* Best effort by design: a body that is not JSON, or is already consumed, must
|
|
70870
|
-
* not turn a logged failure into an unlogged one.
|
|
71059
|
+
* not turn a logged failure into an unlogged one. Null also reads correctly as
|
|
71060
|
+
* "no evidence the credential is dead", which is the safe answer for a caller
|
|
71061
|
+
* deciding whether to keep a refresh token.
|
|
70871
71062
|
*/
|
|
70872
|
-
async function
|
|
71063
|
+
async function readOAuthErrorCode(response) {
|
|
70873
71064
|
try {
|
|
70874
71065
|
const code = (await response.json())["error"];
|
|
70875
|
-
return typeof code === "string" && code.trim().length > 0 ?
|
|
71066
|
+
return typeof code === "string" && code.trim().length > 0 ? code.trim().toLowerCase() : null;
|
|
70876
71067
|
} catch {
|
|
70877
|
-
return
|
|
71068
|
+
return null;
|
|
70878
71069
|
}
|
|
70879
71070
|
}
|
|
70880
71071
|
//#endregion
|
|
71072
|
+
//#region src/command/cmmdRotationFence.ts
|
|
71073
|
+
/**
|
|
71074
|
+
* True when Forge must not rotate this session's CMMD credentials.
|
|
71075
|
+
*
|
|
71076
|
+
* Fails closed: if the session row cannot be read, assume the fence applies.
|
|
71077
|
+
* Spending a token Forge had no right to spend is worse than one extra
|
|
71078
|
+
* reconnect.
|
|
71079
|
+
*/
|
|
71080
|
+
function isRotationFencedSession(sessionId) {
|
|
71081
|
+
if (readCommandRefreshTokenForSession(sessionId)) return require_Schema$1.succeed(false);
|
|
71082
|
+
return isDesktopBearerAuthSession(sessionId).pipe(require_Schema$1.orElseSucceed(() => true));
|
|
71083
|
+
}
|
|
71084
|
+
//#endregion
|
|
71085
|
+
//#region src/cmmdConnection/cmmdCredentialRejection.ts
|
|
71086
|
+
/**
|
|
71087
|
+
* How long a recorded rejection stays usable.
|
|
71088
|
+
*
|
|
71089
|
+
* Long enough to span the resolver ladder and the response that follows it,
|
|
71090
|
+
* short enough that it cannot outlive the credential it describes.
|
|
71091
|
+
*/
|
|
71092
|
+
const REJECTION_TTL_MS = 6e4;
|
|
71093
|
+
const rejectedAt = /* @__PURE__ */ new Map();
|
|
71094
|
+
/** Record that CMMD refused this session's credential outright. */
|
|
71095
|
+
function noteCmmdCredentialRejected(sessionId) {
|
|
71096
|
+
rejectedAt.set(sessionId, Date.now());
|
|
71097
|
+
}
|
|
71098
|
+
/** Whether CMMD refused this session's credential recently enough to act on. */
|
|
71099
|
+
function hasCmmdCredentialRejection(sessionId) {
|
|
71100
|
+
const observedAt = rejectedAt.get(sessionId);
|
|
71101
|
+
if (observedAt === void 0) return false;
|
|
71102
|
+
if (Date.now() - observedAt <= REJECTION_TTL_MS) return true;
|
|
71103
|
+
rejectedAt.delete(sessionId);
|
|
71104
|
+
return false;
|
|
71105
|
+
}
|
|
71106
|
+
/** Drop a recorded rejection, once it has been acted on or superseded. */
|
|
71107
|
+
function forgetCmmdCredentialRejection(sessionId) {
|
|
71108
|
+
rejectedAt.delete(sessionId);
|
|
71109
|
+
}
|
|
71110
|
+
/**
|
|
71111
|
+
* Clear a session's CMMD credentials, but only if CMMD rejected them.
|
|
71112
|
+
*
|
|
71113
|
+
* This is the only door the 401 path may use. A resolver that returned null
|
|
71114
|
+
* because it was not allowed to try is a policy refusal and leaves the stored
|
|
71115
|
+
* credential exactly where it is, so the next request can rotate it.
|
|
71116
|
+
*/
|
|
71117
|
+
const clearCmmdAuthAfterCmmdRejection = (sessionId) => require_Schema$1.gen(function* () {
|
|
71118
|
+
if (!hasCmmdCredentialRejection(sessionId)) return;
|
|
71119
|
+
yield* clearUnrecoverableCmmdAuthForSession(sessionId);
|
|
71120
|
+
forgetCmmdCredentialRejection(sessionId);
|
|
71121
|
+
});
|
|
71122
|
+
//#endregion
|
|
70881
71123
|
//#region src/command/cmmdJwtResolver.ts
|
|
70882
71124
|
function usableCmmdUserJwt(token) {
|
|
70883
71125
|
return isCmmdUserAccessJwt(token) ? token : null;
|
|
@@ -70890,7 +71132,7 @@ function refreshOnceForSession(sessionId, refreshToken) {
|
|
|
70890
71132
|
return require_Schema$1.promise(() => {
|
|
70891
71133
|
const existing = inFlightRefreshes.get(sessionId);
|
|
70892
71134
|
if (existing) return existing;
|
|
70893
|
-
const pending = refreshCmmdJwt(refreshToken).catch(() => null).finally(() => {
|
|
71135
|
+
const pending = refreshCmmdJwt(refreshToken, { onCredentialRejected: () => noteCmmdCredentialRejected(sessionId) }).catch(() => null).finally(() => {
|
|
70894
71136
|
inFlightRefreshes.delete(sessionId);
|
|
70895
71137
|
});
|
|
70896
71138
|
inFlightRefreshes.set(sessionId, pending);
|
|
@@ -71006,7 +71248,7 @@ function resolveCmmdJwtForSession({ sessionId, subject, logPrefix = "cmmd-auth",
|
|
|
71006
71248
|
const normalizedSubject = subject?.trim() || null;
|
|
71007
71249
|
if (!normalizedSubject && !forceRefresh) return require_Schema$1.succeed(null);
|
|
71008
71250
|
return require_Schema$1.gen(function* () {
|
|
71009
|
-
if (yield*
|
|
71251
|
+
if (yield* isRotationFencedSession(sessionId)) return null;
|
|
71010
71252
|
if (forceRefresh) {
|
|
71011
71253
|
const forced = yield* tryOwnCredentialRecovery(sessionId, logPrefix);
|
|
71012
71254
|
if (forced) return forced;
|
|
@@ -71765,15 +72007,25 @@ function clearCmmdAuthAfterFailure(sessionId) {
|
|
|
71765
72007
|
})));
|
|
71766
72008
|
}
|
|
71767
72009
|
/**
|
|
71768
|
-
* The single "no usable CMMD credential" response:
|
|
71769
|
-
*
|
|
71770
|
-
*
|
|
71771
|
-
*
|
|
71772
|
-
*
|
|
71773
|
-
*
|
|
72010
|
+
* The single "no usable CMMD credential" response: returns the canonical
|
|
72011
|
+
* `CMMD_AUTH_REQUIRED` body. Every prologue path that decides the session has
|
|
72012
|
+
* no way to reach CMMD — a missing PAT, a JWT that fails to resolve, a bespoke
|
|
72013
|
+
* gate elsewhere in http.ts — emits this same response so the Shell only ever
|
|
72014
|
+
* has one `CMMD_AUTH_REQUIRED` body to branch on.
|
|
72015
|
+
*
|
|
72016
|
+
* It used to delete the session's stored credentials on the way out, and that
|
|
72017
|
+
* was the wrong reading of its own argument. Most of the paths that reach here
|
|
72018
|
+
* are refusals, not rejections: the resolver was fenced, the subject was
|
|
72019
|
+
* missing, a gate said no. A phone would arrive holding a refresh token good
|
|
72020
|
+
* for the next three months, be refused by a rule, and have that token deleted
|
|
72021
|
+
* as "unrecoverable". The clear now happens only where CMMD has actually
|
|
72022
|
+
* refused the credential, which `clearCmmdAuthAfterCmmdRejection` checks.
|
|
71774
72023
|
*/
|
|
71775
72024
|
function respondCmmdConnectionRequired(sessionId) {
|
|
71776
|
-
return
|
|
72025
|
+
return clearCmmdAuthAfterCmmdRejection(sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear CMMD auth that CMMD itself rejected.", {
|
|
72026
|
+
cause: error,
|
|
72027
|
+
sessionId
|
|
72028
|
+
})), require_Schema$1.as(buildProxyErrorResponse(new CmmdProxyAuthenticationRequiredError("CMMD connection required. Reconnect CMMD to continue."))));
|
|
71777
72029
|
}
|
|
71778
72030
|
async function dispatchToProxy(method, jwt, cmmdPath, rawBody, orgId, searchParams) {
|
|
71779
72031
|
return cmmdGateway.request({
|
|
@@ -83674,12 +83926,12 @@ const makePullRequestService = require_Schema$1.gen(function* () {
|
|
|
83674
83926
|
}));
|
|
83675
83927
|
const submitReview = (input) => requireProject(input).pipe(require_Schema$1.flatMap((project) => {
|
|
83676
83928
|
const review = project.api.capabilities.review;
|
|
83677
|
-
if (!review.verdicts.includes(input.verdict)) return refuse(`This host cannot ${VERDICT_LABELS[input.verdict]} a change request.`);
|
|
83678
|
-
if (input.comments.length > 0 && !review.inlineComment) return refuse("This host cannot comment on a line of a change request.");
|
|
83679
|
-
if (input.verdict !== "approve" && input.body.trim().length === 0 && input.comments.length === 0) return refuse("A review needs a summary or at least one comment.");
|
|
83929
|
+
if (!review.verdicts.includes(input.verdict)) return refuse$1(`This host cannot ${VERDICT_LABELS[input.verdict]} a change request.`);
|
|
83930
|
+
if (input.comments.length > 0 && !review.inlineComment) return refuse$1("This host cannot comment on a line of a change request.");
|
|
83931
|
+
if (input.verdict !== "approve" && input.body.trim().length === 0 && input.comments.length === 0) return refuse$1("A review needs a summary or at least one comment.");
|
|
83680
83932
|
return viewerPermissionsOf(project, input, "submitReview").pipe(require_Schema$1.flatMap((viewer) => {
|
|
83681
|
-
if (!viewer.verdicts.includes(input.verdict)) return refuse(`You need write access on this repository to ${VERDICT_LABELS[input.verdict]} a change request.`);
|
|
83682
|
-
if (input.comments.length > 0 && !viewer.comment) return refuse("You need write access on this repository to comment on a line of a change request.");
|
|
83933
|
+
if (!viewer.verdicts.includes(input.verdict)) return refuse$1(`You need write access on this repository to ${VERDICT_LABELS[input.verdict]} a change request.`);
|
|
83934
|
+
if (input.comments.length > 0 && !viewer.comment) return refuse$1("You need write access on this repository to comment on a line of a change request.");
|
|
83683
83935
|
return project.api.submitReview({
|
|
83684
83936
|
projectId: project.project.id,
|
|
83685
83937
|
cwd: project.project.workspaceRoot,
|
|
@@ -84077,7 +84329,7 @@ const toEntry = (input) => {
|
|
|
84077
84329
|
...input.item.reviewDecision === void 0 || input.item.reviewDecision === null ? {} : { reviewDecision: input.item.reviewDecision }
|
|
84078
84330
|
};
|
|
84079
84331
|
};
|
|
84080
|
-
const refuse = (detail) => require_Schema$1.fail(new PullRequestOperationError({
|
|
84332
|
+
const refuse$1 = (detail) => require_Schema$1.fail(new PullRequestOperationError({
|
|
84081
84333
|
operation: "submitReview",
|
|
84082
84334
|
detail
|
|
84083
84335
|
}));
|
|
@@ -93944,6 +94196,166 @@ const admitNormalizedDispatchCommand = (command, admit) => require_Schema$1.unin
|
|
|
93944
94196
|
};
|
|
93945
94197
|
}));
|
|
93946
94198
|
//#endregion
|
|
94199
|
+
//#region src/orchestration/threadDetailWindow.ts
|
|
94200
|
+
function earliest(current, candidate) {
|
|
94201
|
+
return current === void 0 || candidate < current ? candidate : current;
|
|
94202
|
+
}
|
|
94203
|
+
/**
|
|
94204
|
+
* Every turn in the thread, oldest first.
|
|
94205
|
+
*
|
|
94206
|
+
* Ordering is by the earliest timestamp across all four windowed collections
|
|
94207
|
+
* rather than by message order alone: a turn can open with an activity (a tool
|
|
94208
|
+
* call before any text) and a checkpoint-only turn has no message at all.
|
|
94209
|
+
* Ties break on turn id so the order is total and a cursor means the same thing
|
|
94210
|
+
* on every read.
|
|
94211
|
+
*/
|
|
94212
|
+
function orderThreadTurns(thread) {
|
|
94213
|
+
const anchors = /* @__PURE__ */ new Map();
|
|
94214
|
+
const touch = (turnId, at, isUserMessage) => {
|
|
94215
|
+
if (turnId === null) return;
|
|
94216
|
+
const existing = anchors.get(turnId);
|
|
94217
|
+
anchors.set(turnId, {
|
|
94218
|
+
at: earliest(existing?.at, at),
|
|
94219
|
+
hasUserMessage: (existing?.hasUserMessage ?? false) || isUserMessage
|
|
94220
|
+
});
|
|
94221
|
+
};
|
|
94222
|
+
for (const message of thread.messages) touch(message.turnId, message.createdAt, message.role === "user");
|
|
94223
|
+
for (const activity of thread.activities) touch(activity.turnId, activity.createdAt, false);
|
|
94224
|
+
for (const plan of thread.proposedPlans) touch(plan.turnId, plan.createdAt, false);
|
|
94225
|
+
for (const checkpoint of thread.checkpoints) touch(checkpoint.turnId, checkpoint.completedAt, false);
|
|
94226
|
+
return Array.from(anchors, ([turnId, anchor]) => ({
|
|
94227
|
+
turnId,
|
|
94228
|
+
...anchor
|
|
94229
|
+
})).toSorted((left, right) => left.at === right.at ? left.turnId.localeCompare(right.turnId) : left.at < right.at ? -1 : 1);
|
|
94230
|
+
}
|
|
94231
|
+
/**
|
|
94232
|
+
* Index of the first turn to include, counting back `turnLimit` user-anchored
|
|
94233
|
+
* turns from the end of `ordered`. Returns 0 when the thread holds fewer user
|
|
94234
|
+
* turns than the limit, which is how a short thread comes back whole.
|
|
94235
|
+
*/
|
|
94236
|
+
function windowStartIndex(ordered, turnLimit) {
|
|
94237
|
+
let userTurns = 0;
|
|
94238
|
+
for (let index = ordered.length - 1; index >= 0; index -= 1) {
|
|
94239
|
+
const anchor = ordered[index];
|
|
94240
|
+
if (anchor !== void 0 && anchor.hasUserMessage) {
|
|
94241
|
+
userTurns += 1;
|
|
94242
|
+
if (userTurns === turnLimit) return index;
|
|
94243
|
+
}
|
|
94244
|
+
}
|
|
94245
|
+
return 0;
|
|
94246
|
+
}
|
|
94247
|
+
/**
|
|
94248
|
+
* The turns a windowed read returns.
|
|
94249
|
+
*
|
|
94250
|
+
* `beforeCursor` bounds the read to turns strictly older than a cursor an
|
|
94251
|
+
* earlier read handed out, which is what "load earlier" sends back. A cursor
|
|
94252
|
+
* naming a turn this thread no longer holds, after a revert, say, yields an
|
|
94253
|
+
* empty page rather than the whole thread: returning everything would be a
|
|
94254
|
+
* silent full download at the exact moment the client asked for less.
|
|
94255
|
+
*/
|
|
94256
|
+
function selectWindowTurnIds(ordered, window) {
|
|
94257
|
+
const cursorIndex = window.beforeCursor === void 0 ? ordered.length : ordered.findIndex((anchor) => anchor.turnId === window.beforeCursor);
|
|
94258
|
+
if (cursorIndex < 0) return {
|
|
94259
|
+
turnIds: /* @__PURE__ */ new Set(),
|
|
94260
|
+
beforeCursor: null,
|
|
94261
|
+
hasMore: false,
|
|
94262
|
+
includeUnanchored: false
|
|
94263
|
+
};
|
|
94264
|
+
const candidates = ordered.slice(0, cursorIndex);
|
|
94265
|
+
const startIndex = window.turnLimit === void 0 ? 0 : windowStartIndex(candidates, window.turnLimit);
|
|
94266
|
+
const included = candidates.slice(startIndex);
|
|
94267
|
+
const oldest = included[0];
|
|
94268
|
+
return {
|
|
94269
|
+
turnIds: new Set(included.map((anchor) => anchor.turnId)),
|
|
94270
|
+
beforeCursor: oldest?.turnId ?? null,
|
|
94271
|
+
hasMore: startIndex > 0,
|
|
94272
|
+
includeUnanchored: startIndex === 0
|
|
94273
|
+
};
|
|
94274
|
+
}
|
|
94275
|
+
function keepsTurn(selection, turnId) {
|
|
94276
|
+
return turnId === null ? selection.includeUnanchored : selection.turnIds.has(turnId);
|
|
94277
|
+
}
|
|
94278
|
+
/**
|
|
94279
|
+
* Applies a turn window to a full thread detail read.
|
|
94280
|
+
*
|
|
94281
|
+
* A request with neither field is a full read and comes back untouched with no
|
|
94282
|
+
* page, which is exactly what a pre-pagination client already receives. Only
|
|
94283
|
+
* the four turn-scoped collections are filtered; thread metadata, the session,
|
|
94284
|
+
* and `latestTurn` describe the thread as a whole and stay as they are, so a
|
|
94285
|
+
* windowed snapshot still renders a correct header and a correct running state.
|
|
94286
|
+
*/
|
|
94287
|
+
function windowThreadDetail(input) {
|
|
94288
|
+
const { thread, window, snapshotSequence } = input;
|
|
94289
|
+
if (window.turnLimit === void 0 && window.beforeCursor === void 0) return {
|
|
94290
|
+
thread,
|
|
94291
|
+
page: void 0
|
|
94292
|
+
};
|
|
94293
|
+
const selection = selectWindowTurnIds(orderThreadTurns(thread), window);
|
|
94294
|
+
return {
|
|
94295
|
+
thread: {
|
|
94296
|
+
...thread,
|
|
94297
|
+
messages: thread.messages.filter((row) => keepsTurn(selection, row.turnId)),
|
|
94298
|
+
activities: thread.activities.filter((row) => keepsTurn(selection, row.turnId)),
|
|
94299
|
+
proposedPlans: thread.proposedPlans.filter((row) => keepsTurn(selection, row.turnId)),
|
|
94300
|
+
checkpoints: thread.checkpoints.filter((row) => keepsTurn(selection, row.turnId)),
|
|
94301
|
+
...thread.turnModelSelections === void 0 ? {} : { turnModelSelections: thread.turnModelSelections.filter((row) => keepsTurn(selection, row.turnId)) }
|
|
94302
|
+
},
|
|
94303
|
+
page: {
|
|
94304
|
+
beforeCursor: selection.hasMore ? selection.beforeCursor : null,
|
|
94305
|
+
hasMore: selection.hasMore,
|
|
94306
|
+
snapshotSequence
|
|
94307
|
+
}
|
|
94308
|
+
};
|
|
94309
|
+
}
|
|
94310
|
+
/** Whether a subscriber's cursor can be served by replay rather than a snapshot. */
|
|
94311
|
+
function canResumeFromSequence(input) {
|
|
94312
|
+
if (input.afterSequence > input.currentSequence) return false;
|
|
94313
|
+
return input.currentSequence - input.afterSequence <= (input.maxGap ?? 2e3);
|
|
94314
|
+
}
|
|
94315
|
+
function snapshotCatchUp(snapshot) {
|
|
94316
|
+
return {
|
|
94317
|
+
items: [{
|
|
94318
|
+
kind: "snapshot",
|
|
94319
|
+
snapshot
|
|
94320
|
+
}],
|
|
94321
|
+
watermark: snapshot.snapshotSequence
|
|
94322
|
+
};
|
|
94323
|
+
}
|
|
94324
|
+
function replayCatchUp(events, afterSequence) {
|
|
94325
|
+
return {
|
|
94326
|
+
items: events.map((event) => ({
|
|
94327
|
+
kind: "event",
|
|
94328
|
+
event
|
|
94329
|
+
})),
|
|
94330
|
+
watermark: events.at(-1)?.sequence ?? afterSequence
|
|
94331
|
+
};
|
|
94332
|
+
}
|
|
94333
|
+
/**
|
|
94334
|
+
* Builds the frames that precede live events: either the replayed catch-up or
|
|
94335
|
+
* the snapshot, plus the completion marker when one was asked for.
|
|
94336
|
+
*/
|
|
94337
|
+
const buildCatchUp = require_Schema$1.fn("threadSubscription.buildCatchUp")(function* (input, sources) {
|
|
94338
|
+
const afterSequence = input.afterSequence;
|
|
94339
|
+
if (afterSequence === void 0) return snapshotCatchUp(yield* sources.loadSnapshot);
|
|
94340
|
+
if (!canResumeFromSequence({
|
|
94341
|
+
afterSequence,
|
|
94342
|
+
currentSequence: yield* sources.currentSequence
|
|
94343
|
+
})) return snapshotCatchUp(yield* sources.loadSnapshot);
|
|
94344
|
+
const replayed = yield* require_Path.runCollect(sources.replayEvents(afterSequence));
|
|
94345
|
+
return replayCatchUp(Array.from(replayed), afterSequence);
|
|
94346
|
+
});
|
|
94347
|
+
function makeThreadSubscriptionStream(input, sources) {
|
|
94348
|
+
return require_Path.unwrap(require_Schema$1.gen(function* () {
|
|
94349
|
+
const liveEvents = yield* sources.subscribeLiveEvents;
|
|
94350
|
+
const catchUp = yield* buildCatchUp(input, sources);
|
|
94351
|
+
const marker = input.requestCompletionMarker === true ? [{ kind: "synchronized" }] : [];
|
|
94352
|
+
return require_Path.concat(require_Path.fromArray([...catchUp.items, ...marker]), liveEvents.pipe(require_Path.filter((event) => event.sequence > catchUp.watermark), require_Path.map((event) => ({
|
|
94353
|
+
kind: "event",
|
|
94354
|
+
event
|
|
94355
|
+
}))));
|
|
94356
|
+
}));
|
|
94357
|
+
}
|
|
94358
|
+
//#endregion
|
|
93947
94359
|
//#region src/orchestration/bootstrapCommandCoordinator.ts
|
|
93948
94360
|
const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
|
|
93949
94361
|
const commandsByEngine = /* @__PURE__ */ new WeakMap();
|
|
@@ -102295,7 +102707,7 @@ function claudeReadinessReasonCode(reason) {
|
|
|
102295
102707
|
//#endregion
|
|
102296
102708
|
//#region src/egress/providerDeliveryReceipt.ts
|
|
102297
102709
|
const PROVIDER_ACCOUNT_IDENTITY$2 = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
102298
|
-
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
102710
|
+
const UUID$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
102299
102711
|
const RFC3339$1 = /^(\d{4})-(\d{2})-(\d{2})T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/;
|
|
102300
102712
|
const RECEIPT_KEYS = new Set([
|
|
102301
102713
|
"receiptId",
|
|
@@ -102378,7 +102790,7 @@ function claudeAccountFields(value) {
|
|
|
102378
102790
|
}
|
|
102379
102791
|
function parseClaudeDeliveryIdentity(value) {
|
|
102380
102792
|
const account = claudeAccountFields(value.providerAccountIdentity);
|
|
102381
|
-
if (!account || !hasAllowedKeys(value, CLAUDE_IDENTITY_KEYS, CLAUDE_IDENTITY_OPTIONAL_KEYS) || value.kind !== "credential_generation" || value.provider !== "claude" || typeof value.credentialGenerationId !== "string" || !UUID.test(value.credentialGenerationId)) return null;
|
|
102793
|
+
if (!account || !hasAllowedKeys(value, CLAUDE_IDENTITY_KEYS, CLAUDE_IDENTITY_OPTIONAL_KEYS) || value.kind !== "credential_generation" || value.provider !== "claude" || typeof value.credentialGenerationId !== "string" || !UUID$1.test(value.credentialGenerationId)) return null;
|
|
102382
102794
|
return {
|
|
102383
102795
|
kind: "credential_generation",
|
|
102384
102796
|
provider: "claude",
|
|
@@ -103145,7 +103557,10 @@ function extractClaudeProviderAccountId(response) {
|
|
|
103145
103557
|
if (typeof response !== "object" || response === null || !("data" in response)) return null;
|
|
103146
103558
|
const data = response.data;
|
|
103147
103559
|
if (typeof data !== "object" || data === null) return null;
|
|
103148
|
-
if ("connected" in data && data.connected === true)
|
|
103560
|
+
if ("connected" in data && data.connected === true) {
|
|
103561
|
+
if ("providerAccountId" in data) return asNonEmptyString$1(data.providerAccountId) ?? null;
|
|
103562
|
+
return extractClaudeCredentialGenerationId(response);
|
|
103563
|
+
}
|
|
103149
103564
|
if (!("account" in data)) return null;
|
|
103150
103565
|
const account = data.account;
|
|
103151
103566
|
if (typeof account !== "object" || account === null || !("providerAccountId" in account) || typeof account.providerAccountId !== "string") return null;
|
|
@@ -106121,6 +106536,14 @@ function makeRelayCommandInboxRepository(sql) {
|
|
|
106121
106536
|
function isThreadDetailEvent(event) {
|
|
106122
106537
|
return event.type === "thread.message-sent" || event.type === "thread.proposed-plan-upserted" || event.type === "thread.activity-appended" || event.type === "thread.turn-diff-completed" || event.type === "thread.reverted" || event.type === "thread.session-set" || event.type === "thread.rate-limit-set";
|
|
106123
106538
|
}
|
|
106539
|
+
/**
|
|
106540
|
+
* Narrows the domain-event feed to one thread's stream-visible events. The same
|
|
106541
|
+
* predicate guards the live feed and the resume replay, so a subscriber cannot
|
|
106542
|
+
* be handed a different set of events depending on which path served it.
|
|
106543
|
+
*/
|
|
106544
|
+
function isThreadDetailEventFor(threadId) {
|
|
106545
|
+
return (event) => event.aggregateKind === "thread" && event.aggregateId === threadId && isThreadDetailEvent(event);
|
|
106546
|
+
}
|
|
106124
106547
|
const PROVIDER_STATUS_DEBOUNCE_MS = 200;
|
|
106125
106548
|
function streamBoundedText(contents) {
|
|
106126
106549
|
if (Buffer.byteLength(contents, "utf8") > 16777216) return require_Schema$1.fail(new BulkTransferError({
|
|
@@ -106392,6 +106815,32 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
106392
106815
|
}, normalizedCommand) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
|
|
106393
106816
|
return startup.enqueueCommand(dispatchEffect).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
|
|
106394
106817
|
};
|
|
106818
|
+
/**
|
|
106819
|
+
* The thread snapshot a subscription falls back to, windowed when the
|
|
106820
|
+
* subscriber asked for a window. A missing thread is a failure rather
|
|
106821
|
+
* than an empty snapshot: the subscriber asked about a thread this server
|
|
106822
|
+
* does not have, and saying so is the only honest answer.
|
|
106823
|
+
*/
|
|
106824
|
+
const loadThreadDetailSnapshot = (threadId, turnLimit) => require_Schema$1.gen(function* () {
|
|
106825
|
+
const [threadDetail, snapshotSequence] = yield* require_Schema$1.all([projectionSnapshotQuery.getThreadDetailById(threadId).pipe(require_Schema$1.mapError((cause) => new OrchestrationGetSnapshotError({
|
|
106826
|
+
message: `Failed to load thread ${threadId}`,
|
|
106827
|
+
cause
|
|
106828
|
+
}))), orchestrationEngine.getReadModel().pipe(require_Schema$1.map((readModel) => readModel.snapshotSequence))]);
|
|
106829
|
+
if (require_Schema$1.isNone(threadDetail)) return yield* new OrchestrationGetSnapshotError({
|
|
106830
|
+
message: `Thread ${threadId} was not found`,
|
|
106831
|
+
cause: threadId
|
|
106832
|
+
});
|
|
106833
|
+
const windowed = windowThreadDetail({
|
|
106834
|
+
thread: threadDetail.value,
|
|
106835
|
+
window: turnLimit === void 0 ? {} : { turnLimit },
|
|
106836
|
+
snapshotSequence
|
|
106837
|
+
});
|
|
106838
|
+
return {
|
|
106839
|
+
snapshotSequence,
|
|
106840
|
+
thread: windowed.thread,
|
|
106841
|
+
...windowed.page === void 0 ? {} : { page: windowed.page }
|
|
106842
|
+
};
|
|
106843
|
+
});
|
|
106395
106844
|
const loadServerConfig = require_Schema$1.gen(function* () {
|
|
106396
106845
|
const keybindingsConfig = yield* keybindings.loadConfigState;
|
|
106397
106846
|
const providers = yield* providerRegistry.getProviders;
|
|
@@ -106413,6 +106862,8 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
106413
106862
|
shellRevealInFileManager: true,
|
|
106414
106863
|
shellRevealInFileManagerKind: fileManagerRevealKind
|
|
106415
106864
|
},
|
|
106865
|
+
threadResumeCompletionMarker: true,
|
|
106866
|
+
threadSnapshotPagination: true,
|
|
106416
106867
|
observability: {
|
|
106417
106868
|
logsDirectoryPath: config.logsDir,
|
|
106418
106869
|
localTracingEnabled: true,
|
|
@@ -106510,27 +106961,15 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
|
|
|
106510
106961
|
projectionSnapshotQuery
|
|
106511
106962
|
})), require_Path.flatMap((event) => require_Schema$1.isSome(event) ? require_Path.succeed(event.value) : require_Path.empty))))
|
|
106512
106963
|
})), { "rpc.aggregate": "orchestration" }),
|
|
106513
|
-
[ORCHESTRATION_WS_METHODS.subscribeThread]: (input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeThread, require_Schema$1.
|
|
106514
|
-
|
|
106515
|
-
|
|
106964
|
+
[ORCHESTRATION_WS_METHODS.subscribeThread]: (input) => observeRpcStreamEffect(ORCHESTRATION_WS_METHODS.subscribeThread, require_Schema$1.succeed(makeThreadSubscriptionStream(input, {
|
|
106965
|
+
subscribeLiveEvents: orchestrationEngine.subscribeDomainEvents().pipe(require_Schema$1.map((events) => events.pipe(require_Path.filter(isThreadDetailEventFor(input.threadId))))),
|
|
106966
|
+
currentSequence: orchestrationEngine.getReadModel().pipe(require_Schema$1.map((readModel) => readModel.snapshotSequence)),
|
|
106967
|
+
loadSnapshot: loadThreadDetailSnapshot(input.threadId, input.turnLimit),
|
|
106968
|
+
replayEvents: (afterSequence) => orchestrationEngine.readEvents(afterSequence).pipe(require_Path.filter(isThreadDetailEventFor(input.threadId)), require_Path.mapError((cause) => new OrchestrationGetSnapshotError({
|
|
106969
|
+
message: `Failed to replay thread ${input.threadId}`,
|
|
106516
106970
|
cause
|
|
106517
|
-
})))
|
|
106518
|
-
|
|
106519
|
-
message: `Thread ${input.threadId} was not found`,
|
|
106520
|
-
cause: input.threadId
|
|
106521
|
-
});
|
|
106522
|
-
const liveStream = orchestrationEngine.streamDomainEvents.pipe(require_Path.filter((event) => event.aggregateKind === "thread" && event.aggregateId === input.threadId && isThreadDetailEvent(event)), require_Path.map((event) => ({
|
|
106523
|
-
kind: "event",
|
|
106524
|
-
event
|
|
106525
|
-
})));
|
|
106526
|
-
return require_Path.concat(require_Path.make$1({
|
|
106527
|
-
kind: "snapshot",
|
|
106528
|
-
snapshot: {
|
|
106529
|
-
snapshotSequence,
|
|
106530
|
-
thread: threadDetail.value
|
|
106531
|
-
}
|
|
106532
|
-
}), liveStream);
|
|
106533
|
-
}), { "rpc.aggregate": "orchestration" }),
|
|
106971
|
+
})))
|
|
106972
|
+
})), { "rpc.aggregate": "orchestration" }),
|
|
106534
106973
|
[WS_METHODS.serverGetConfig]: (_input) => observeRpcEffect(WS_METHODS.serverGetConfig, loadServerConfig, { "rpc.aggregate": "server" }),
|
|
106535
106974
|
[WS_METHODS.serverRefreshProviders]: (_input) => observeRpcEffect(WS_METHODS.serverRefreshProviders, providerRegistry.refresh().pipe(require_Schema$1.map((providers) => ({ providers }))), { "rpc.aggregate": "server" }),
|
|
106536
106975
|
[WS_METHODS.serverStartProviderAuth]: (input) => observeRpcEffect(WS_METHODS.serverStartProviderAuth, require_Schema$1.gen(function* () {
|
|
@@ -118006,8 +118445,8 @@ require_Schema$1.effectDiscard(runMigrations());
|
|
|
118006
118445
|
//#endregion
|
|
118007
118446
|
//#region src/persistence/Layers/Sqlite.ts
|
|
118008
118447
|
const defaultSqliteClientLoaders = {
|
|
118009
|
-
bun: () => Promise.resolve().then(() => require("./SqliteClient-
|
|
118010
|
-
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-
|
|
118448
|
+
bun: () => Promise.resolve().then(() => require("./SqliteClient-O1ABIngw.cjs")),
|
|
118449
|
+
node: () => Promise.resolve().then(() => require("./NodeSqliteClient-CfsdC3AB.cjs"))
|
|
118011
118450
|
};
|
|
118012
118451
|
const makeRuntimeSqliteLayer = require_Schema$1.fn("makeRuntimeSqliteLayer")(function* (config) {
|
|
118013
118452
|
const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
|
|
@@ -178324,7 +178763,7 @@ function em(e) {
|
|
|
178324
178763
|
BO ??= new Int32Array(new SharedArrayBuffer(4)), Atomics.wait(BO, 0, 0, SY);
|
|
178325
178764
|
}
|
|
178326
178765
|
}
|
|
178327
|
-
function rm$
|
|
178766
|
+
function rm$7(e, t) {
|
|
178328
178767
|
let n = (0, path.resolve)(t).toLowerCase(), r = (0, path.dirname)((0, path.resolve)(e)).toLowerCase();
|
|
178329
178768
|
if (r === n || KO(r, n) || qO(r, n, "lexical")) return !0;
|
|
178330
178769
|
let o = em(t)?.toLowerCase();
|
|
@@ -191174,7 +191613,7 @@ function Wo() {
|
|
|
191174
191613
|
}
|
|
191175
191614
|
function sle(e) {
|
|
191176
191615
|
let t = process.cwd();
|
|
191177
|
-
return e.filter((n) => !rm$
|
|
191616
|
+
return e.filter((n) => !rm$7(n, t));
|
|
191178
191617
|
}
|
|
191179
191618
|
function ZU(e) {
|
|
191180
191619
|
let t = Bun.which(e);
|
|
@@ -212687,60 +213126,14 @@ function hasConfiguredApexCredential(profiles) {
|
|
|
212687
213126
|
}
|
|
212688
213127
|
//#endregion
|
|
212689
213128
|
//#region src/provider/claudeCredentialDirectory.ts
|
|
212690
|
-
/**
|
|
212691
|
-
* Per-account credential directories for Claude turns.
|
|
212692
|
-
*
|
|
212693
|
-
* Claude Code keeps conversation transcripts under
|
|
212694
|
-
* `${CLAUDE_CONFIG_DIR}/projects/<cwd-slug>/<sessionId>.jsonl` and its login
|
|
212695
|
-
* under `.credentials.json`. `CLAUDE_SECURESTORAGE_CONFIG_DIR` moves only the
|
|
212696
|
-
* credential file, so each Provider Account can hold its own login while every
|
|
212697
|
-
* session file stays in one place and `--resume` keeps finding its target. See
|
|
212698
|
-
* `docs/adr/claude-accounts-split-credentials-not-sessions.md`.
|
|
212699
|
-
*
|
|
212700
|
-
* That variable is not documented. Under
|
|
212701
|
-
* `docs/adr/unpublished-provider-surface-needs-a-fallback.md` the dependency is
|
|
212702
|
-
* only allowed because absence degrades to a named behaviour that already
|
|
212703
|
-
* works: a thread with no account runs on the Environment's own login. This
|
|
212704
|
-
* module sets the variable only when the resolved directory holds a credential
|
|
212705
|
-
* the CLI can use, because a missing file, or one whose access token a refusal
|
|
212706
|
-
* has blanked, makes the CLI report logged out and a live turn would fail.
|
|
212707
|
-
*
|
|
212708
|
-
* Nothing writes these directories yet. #3049 adds the writer and keys each
|
|
212709
|
-
* directory on the account identity CMMD writes on the delivery receipt it
|
|
212710
|
-
* signs (`docs/adr/provider-account-identity-is-issued-by-cmmd.md`). Until then
|
|
212711
|
-
* every runtime takes the `no-account` skip.
|
|
212712
|
-
*
|
|
212713
|
-
* Free of Effect services on purpose, for the same reason as
|
|
212714
|
-
* `claudeCredentialContainment.ts`: ClaudeAdapter drags in Effect platform
|
|
212715
|
-
* services, the browser manager and the server config, so a unit test that
|
|
212716
|
-
* imports it cannot run in a worktree. This rule decides which account pays for
|
|
212717
|
-
* a turn, so it has to stay directly testable.
|
|
212718
|
-
*/
|
|
213129
|
+
/** Per-account credentials share the existing Claude transcript directory. */
|
|
212719
213130
|
/** Repoints `.credentials.json` without moving the transcripts. */
|
|
212720
213131
|
const CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
212721
213132
|
/** The one file Claude Code writes into the credential directory. */
|
|
212722
213133
|
const CLAUDE_CREDENTIAL_FILE_NAME = ".credentials.json";
|
|
212723
|
-
/**
|
|
212724
|
-
* Runtime env var naming the Provider Account this runtime's Claude turns
|
|
212725
|
-
* belong to. Nothing in Forge or CMMD sets it, so no runtime names an account
|
|
212726
|
-
* and each keeps its one login.
|
|
212727
|
-
*
|
|
212728
|
-
* It is a bridge that will not get a writer. #3073 settled the key in
|
|
212729
|
-
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`: the directory name
|
|
212730
|
-
* comes from the identity CMMD issues and writes on the delivery receipt it
|
|
212731
|
-
* signs, and this variable goes away. #3049 makes that change. The credential
|
|
212732
|
-
* generation was ruled out as a key: it rotates on every reconnect, and CMMD
|
|
212733
|
-
* uses it as the primary account's id, so keying on it would strand every
|
|
212734
|
-
* thread pinned to that account on a directory that stops receiving deliveries.
|
|
212735
|
-
*
|
|
212736
|
-
* Tests reach the `credentialDirectory` parameter on
|
|
212737
|
-
* `ClaudeAdapter.buildClaudeQueryEnv` directly.
|
|
212738
|
-
*/
|
|
212739
|
-
const CLAUDE_PROVIDER_ACCOUNT_IDENTITY_ENV = "FORGE_CLAUDE_PROVIDER_ACCOUNT_IDENTITY";
|
|
212740
213134
|
/** Directory under the server's base directory that holds one directory per account. */
|
|
212741
213135
|
const CREDENTIAL_ROOT_DIRECTORY_NAME = "claude-credentials";
|
|
212742
213136
|
const DEFAULT_CLAUDE_CONFIG_DIRECTORY_NAME = ".claude";
|
|
212743
|
-
const MAX_ACCOUNT_IDENTITY_LENGTH = 256;
|
|
212744
213137
|
const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
212745
213138
|
/**
|
|
212746
213139
|
* Directory name for an account. A hash, so no email or display name reaches a
|
|
@@ -212749,11 +213142,12 @@ const DIRECTORY_NAME_HASH_LENGTH = 32;
|
|
|
212749
213142
|
function claudeCredentialDirectoryName(accountIdentity) {
|
|
212750
213143
|
return `account-${(0, node_crypto.createHash)("sha256").update(accountIdentity, "utf8").digest("hex").slice(0, DIRECTORY_NAME_HASH_LENGTH)}`;
|
|
212751
213144
|
}
|
|
212752
|
-
/**
|
|
212753
|
-
function
|
|
212754
|
-
|
|
212755
|
-
|
|
212756
|
-
|
|
213145
|
+
/** Only a matching CMMD delivery can select a credential directory. */
|
|
213146
|
+
function resolveSelectedClaudeAccountIdentity(selectedAccountId, binding) {
|
|
213147
|
+
if (selectedAccountId === void 0) return null;
|
|
213148
|
+
const identity = binding?.identity;
|
|
213149
|
+
if (identity?.kind !== "credential_generation" || identity.provider !== "claude" || identity.providerAccountIdentity !== selectedAccountId) throw new Error("The selected Claude account has no matching credential delivery. Reconnect that account and try again.");
|
|
213150
|
+
return identity.providerAccountIdentity;
|
|
212757
213151
|
}
|
|
212758
213152
|
/**
|
|
212759
213153
|
* Where per-account credential directories live: `claude-credentials` under the
|
|
@@ -212780,29 +213174,19 @@ function resolveClaudeCredentialDirectory(input) {
|
|
|
212780
213174
|
if (!root) return null;
|
|
212781
213175
|
return (0, node_path.join)(root, claudeCredentialDirectoryName(input.accountIdentity));
|
|
212782
213176
|
}
|
|
212783
|
-
/**
|
|
212784
|
-
* Decide whether this turn may point at a per-account credential directory.
|
|
212785
|
-
*
|
|
212786
|
-
* The cheap checks run first and the capability probe runs last, so a runtime
|
|
212787
|
-
* with no usable credential never spawns the CLI. The probe's error channel is
|
|
212788
|
-
* empty, so nothing here catches.
|
|
212789
|
-
*
|
|
212790
|
-
* A skip is not a fallback onto the default account. With no account the turn
|
|
212791
|
-
* runs on the Environment's own login, unchanged. A thread pinned to an account
|
|
212792
|
-
* whose credential is missing is refused by name under
|
|
212793
|
-
* `docs/adr/provider-account-identity-is-issued-by-cmmd.md`, and #3049 turns
|
|
212794
|
-
* `no-usable-credential`, and `capability-not-supported` for a pinned account,
|
|
212795
|
-
* into those refusals. Until #3049 nothing names an account, so `no-account` is
|
|
212796
|
-
* the only skip a runtime sees.
|
|
212797
|
-
*/
|
|
213177
|
+
/** Refuse selected accounts that cannot use their own credential directory. */
|
|
212798
213178
|
function decideClaudeCredentialDirectory(input) {
|
|
212799
213179
|
return require_Schema$1.gen(function* () {
|
|
212800
213180
|
if (!input.accountIdentity) return skip("no-account");
|
|
212801
|
-
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return
|
|
213181
|
+
if (input.env["CLAUDE_SECURESTORAGE_CONFIG_DIR"]?.trim()) return refuse("operator-override");
|
|
212802
213182
|
const directory = resolveClaudeCredentialDirectory(input);
|
|
212803
|
-
if (!directory) return
|
|
212804
|
-
if (!holdsUsableCredential(directory)) return
|
|
212805
|
-
|
|
213183
|
+
if (!directory) return refuse("no-root");
|
|
213184
|
+
if (!holdsUsableCredential(directory)) return refuse("no-usable-credential");
|
|
213185
|
+
if ((yield* input.probeSupport) === "supported") return set(directory);
|
|
213186
|
+
return input.primaryCredentialMatchesSelected === true ? {
|
|
213187
|
+
outcome: "skipped",
|
|
213188
|
+
reason: "same-primary-account"
|
|
213189
|
+
} : refuse("capability-not-supported");
|
|
212806
213190
|
});
|
|
212807
213191
|
}
|
|
212808
213192
|
/**
|
|
@@ -212841,6 +213225,12 @@ function skip(reason) {
|
|
|
212841
213225
|
reason
|
|
212842
213226
|
};
|
|
212843
213227
|
}
|
|
213228
|
+
function refuse(reason) {
|
|
213229
|
+
return {
|
|
213230
|
+
outcome: "refused",
|
|
213231
|
+
reason
|
|
213232
|
+
};
|
|
213233
|
+
}
|
|
212844
213234
|
function claudeConfigDirectory(env) {
|
|
212845
213235
|
const configured = env.CLAUDE_CONFIG_DIR?.trim();
|
|
212846
213236
|
if (configured) return configured;
|
|
@@ -212850,6 +213240,17 @@ function claudeConfigDirectory(env) {
|
|
|
212850
213240
|
return null;
|
|
212851
213241
|
}
|
|
212852
213242
|
}
|
|
213243
|
+
/** The fallback is allowed only for the exact credential bytes CMMD signed. */
|
|
213244
|
+
function primaryClaudeCredentialMatchesRevision(env, revision) {
|
|
213245
|
+
const directory = claudeConfigDirectory(env);
|
|
213246
|
+
if (!directory || !/^[a-f0-9]{64}$/.test(revision)) return false;
|
|
213247
|
+
try {
|
|
213248
|
+
const credential = (0, node_fs.readFileSync)((0, node_path.join)(directory, CLAUDE_CREDENTIAL_FILE_NAME), "utf8");
|
|
213249
|
+
return (0, node_crypto.createHash)("sha256").update(credential).digest("hex") === revision && hasAccessToken(JSON.parse(credential));
|
|
213250
|
+
} catch {
|
|
213251
|
+
return false;
|
|
213252
|
+
}
|
|
213253
|
+
}
|
|
212853
213254
|
//#endregion
|
|
212854
213255
|
//#region src/provider/personalClaudeInVm.ts
|
|
212855
213256
|
/**
|
|
@@ -212982,13 +213383,106 @@ function withoutInheritedAnthropicCredentials(env, options) {
|
|
|
212982
213383
|
* marker CMMD writes on disk (#3081), so a Sprite switched on in place, which
|
|
212983
213384
|
* has the marker and no env var until it restarts, drops the override too.
|
|
212984
213385
|
*/
|
|
212985
|
-
function claudeSubscriptionChildEnv(processEnv, credentialDirectory) {
|
|
213386
|
+
function claudeSubscriptionChildEnv(processEnv, credentialDirectory, selectedSubscription = false) {
|
|
212986
213387
|
return {
|
|
212987
|
-
...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: personalClaudeInVmEnabled(processEnv) }),
|
|
213388
|
+
...withoutInheritedAnthropicCredentials(scrubForgeProviderChildEnvSecrets(processEnv), { stripEndpointOverride: selectedSubscription || credentialDirectory !== null || personalClaudeInVmEnabled(processEnv) }),
|
|
212988
213389
|
...credentialDirectory ? { [CLAUDE_SECURESTORAGE_CONFIG_DIR_ENV]: credentialDirectory } : {}
|
|
212989
213390
|
};
|
|
212990
213391
|
}
|
|
212991
213392
|
//#endregion
|
|
213393
|
+
//#region src/provider/claudeSelectedCredentialStore.ts
|
|
213394
|
+
async function ensurePrivateDirectory(path) {
|
|
213395
|
+
await (0, node_fs_promises.mkdir)(path, {
|
|
213396
|
+
recursive: true,
|
|
213397
|
+
mode: 448
|
|
213398
|
+
});
|
|
213399
|
+
const info = await (0, node_fs_promises.lstat)(path);
|
|
213400
|
+
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error("Unsafe credential directory.");
|
|
213401
|
+
await (0, node_fs_promises.chmod)(path, 448);
|
|
213402
|
+
}
|
|
213403
|
+
async function makeCapacity(root, target, active, maximum) {
|
|
213404
|
+
const entries = (await (0, node_fs_promises.readdir)(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && /^account-[a-f0-9]{32}$/.test(entry.name));
|
|
213405
|
+
if (entries.some((entry) => (0, node_path.join)(root, entry.name) === target)) return;
|
|
213406
|
+
const candidates = await Promise.all(entries.filter((entry) => !active.has((0, node_path.join)(root, entry.name))).map(async (entry) => ({
|
|
213407
|
+
path: (0, node_path.join)(root, entry.name),
|
|
213408
|
+
modified: (await (0, node_fs_promises.lstat)((0, node_path.join)(root, entry.name))).mtimeMs
|
|
213409
|
+
})));
|
|
213410
|
+
candidates.sort((a, b) => a.modified - b.modified);
|
|
213411
|
+
const required = entries.length - maximum + 1;
|
|
213412
|
+
if (required > candidates.length) throw new Error("All Claude credential slots are active. Stop a session before choosing another account.");
|
|
213413
|
+
for (const candidate of candidates.slice(0, Math.max(0, required))) await (0, node_fs_promises.rm)(candidate.path, { recursive: true });
|
|
213414
|
+
}
|
|
213415
|
+
function createClaudeSelectedCredentialStore(baseDir, options = {}) {
|
|
213416
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
213417
|
+
const maximum = options.maxAccounts ?? 8;
|
|
213418
|
+
if (!Number.isSafeInteger(maximum) || maximum < 1) throw new Error("Invalid credential capacity.");
|
|
213419
|
+
let queue = Promise.resolve();
|
|
213420
|
+
async function install(threadId, acquired) {
|
|
213421
|
+
if (acquired.claims.threadId !== threadId) throw new Error("Credential thread mismatch.");
|
|
213422
|
+
const directory = resolveClaudeCredentialDirectory({
|
|
213423
|
+
env: process.env,
|
|
213424
|
+
baseDir,
|
|
213425
|
+
accountIdentity: acquired.claims.providerAccountId
|
|
213426
|
+
});
|
|
213427
|
+
if (!directory) throw new Error("Unsafe credential directory.");
|
|
213428
|
+
const binding = {
|
|
213429
|
+
runtimeId: acquired.claims.runtimeId,
|
|
213430
|
+
machineId: acquired.claims.machineId,
|
|
213431
|
+
credentialEpoch: acquired.claims.credentialEpoch,
|
|
213432
|
+
identity: {
|
|
213433
|
+
kind: "credential_generation",
|
|
213434
|
+
provider: "claude",
|
|
213435
|
+
credentialGenerationId: acquired.claims.generationId,
|
|
213436
|
+
providerAccountIdentity: acquired.claims.providerAccountId
|
|
213437
|
+
}
|
|
213438
|
+
};
|
|
213439
|
+
for (const prepared of sessions.values()) if (prepared.directory === directory && (!providerDeliveryBindingsEqual(prepared.binding, binding) || prepared.credentialRevisionHash !== acquired.claims.credentialRevisionHash)) throw new Error("The Claude account has an active session. Stop it before replacing its credentials.");
|
|
213440
|
+
const root = (0, node_path.dirname)(directory);
|
|
213441
|
+
await ensurePrivateDirectory(root);
|
|
213442
|
+
await makeCapacity(root, directory, new Set([...sessions.values()].map((entry) => entry.directory)), maximum);
|
|
213443
|
+
await ensurePrivateDirectory(directory);
|
|
213444
|
+
const temporary = (0, node_path.join)(directory, `.credential-${(0, node_crypto.randomUUID)()}.tmp`);
|
|
213445
|
+
try {
|
|
213446
|
+
await (0, node_fs_promises.writeFile)(temporary, acquired.credentialJson, {
|
|
213447
|
+
mode: 384,
|
|
213448
|
+
flag: "wx"
|
|
213449
|
+
});
|
|
213450
|
+
await (0, node_fs_promises.rename)(temporary, (0, node_path.join)(directory, ".credentials.json"));
|
|
213451
|
+
} finally {
|
|
213452
|
+
await (0, node_fs_promises.rm)(temporary, { force: true });
|
|
213453
|
+
}
|
|
213454
|
+
sessions.set(threadId, {
|
|
213455
|
+
directory,
|
|
213456
|
+
binding,
|
|
213457
|
+
accountKind: acquired.claims.accountKind,
|
|
213458
|
+
credentialRevisionHash: acquired.claims.credentialRevisionHash
|
|
213459
|
+
});
|
|
213460
|
+
return binding;
|
|
213461
|
+
}
|
|
213462
|
+
return {
|
|
213463
|
+
prepare(threadId, acquired) {
|
|
213464
|
+
const operation = queue.then(() => install(threadId, acquired));
|
|
213465
|
+
queue = operation.then(() => void 0, () => void 0);
|
|
213466
|
+
return operation;
|
|
213467
|
+
},
|
|
213468
|
+
read(threadId, binding) {
|
|
213469
|
+
const prepared = sessions.get(threadId);
|
|
213470
|
+
return prepared && providerDeliveryBindingsEqual(prepared.binding, binding) ? prepared : null;
|
|
213471
|
+
},
|
|
213472
|
+
release(threadId) {
|
|
213473
|
+
sessions.delete(threadId);
|
|
213474
|
+
}
|
|
213475
|
+
};
|
|
213476
|
+
}
|
|
213477
|
+
const stores = /* @__PURE__ */ new Map();
|
|
213478
|
+
function selectedClaudeCredentialStore(baseDir) {
|
|
213479
|
+
const existing = stores.get(baseDir);
|
|
213480
|
+
if (existing) return existing;
|
|
213481
|
+
const store = createClaudeSelectedCredentialStore(baseDir);
|
|
213482
|
+
stores.set(baseDir, store);
|
|
213483
|
+
return store;
|
|
213484
|
+
}
|
|
213485
|
+
//#endregion
|
|
212992
213486
|
//#region src/provider/claudeCredentialDirectoryProbe.ts
|
|
212993
213487
|
/**
|
|
212994
213488
|
* Capability probe for `CLAUDE_SECURESTORAGE_CONFIG_DIR`.
|
|
@@ -215033,7 +215527,8 @@ function claudeForgeAttributionPrompt() {
|
|
|
215033
215527
|
* turn, an API key is, so pointing the variable there would add a second source
|
|
215034
215528
|
* of truth that a later reader has to rule out.
|
|
215035
215529
|
*/
|
|
215036
|
-
function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory) {
|
|
215530
|
+
function buildClaudeQueryEnv(baseProcessEnv, endpointProfile, claudeSettings, providerProxyCredential, credentialDirectory, selectedSubscription = false) {
|
|
215531
|
+
if (selectedSubscription) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory, true);
|
|
215037
215532
|
const baseEnv = scrubForgeProviderChildEnvSecrets(baseProcessEnv);
|
|
215038
215533
|
const effectiveBaseUrl = resolveClaudeQueryBaseUrl(endpointProfile, claudeSettings, providerProxyCredential);
|
|
215039
215534
|
if (!effectiveBaseUrl) return claudeSubscriptionChildEnv(baseProcessEnv, credentialDirectory);
|
|
@@ -216947,6 +217442,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
216947
217442
|
if (streamFiber && streamFiber.pollUnsafe() === void 0) yield* require_Path.interrupt(streamFiber);
|
|
216948
217443
|
try {
|
|
216949
217444
|
context.query.close();
|
|
217445
|
+
selectedClaudeCredentialStore(serverConfig.baseDir).release(context.session.threadId);
|
|
216950
217446
|
} catch (cause) {
|
|
216951
217447
|
yield* emitRuntimeError(context, "Failed to close Claude runtime query.", cause);
|
|
216952
217448
|
}
|
|
@@ -217258,6 +217754,43 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
217258
217754
|
sessionId: existingResumeSessionId ?? newSessionId
|
|
217259
217755
|
};
|
|
217260
217756
|
});
|
|
217757
|
+
const resolveSelectedCredentialDirectory = require_Schema$1.fn("resolveSelectedCredentialDirectory")(function* (input, resumeState, claudeBinaryPath) {
|
|
217758
|
+
const requestedAccountId = input.modelSelection?.provider === "claudeAgent" ? input.modelSelection.providerAccountId : void 0;
|
|
217759
|
+
const selectedAccountIdentity = yield* require_Schema$1.try_({
|
|
217760
|
+
try: () => resolveSelectedClaudeAccountIdentity(requestedAccountId, resumeState?.providerDeliveryBinding),
|
|
217761
|
+
catch: () => new ProviderAdapterValidationError({
|
|
217762
|
+
provider: PROVIDER$11,
|
|
217763
|
+
operation: "startSession",
|
|
217764
|
+
issue: `The selected Claude account ${requestedAccountId} has no matching credential delivery. Reconnect that account and try again.`
|
|
217765
|
+
})
|
|
217766
|
+
});
|
|
217767
|
+
const preparedCredential = resumeState?.providerDeliveryBinding ? selectedClaudeCredentialStore(serverConfig.baseDir).read(input.threadId, resumeState.providerDeliveryBinding) : null;
|
|
217768
|
+
if (selectedAccountIdentity !== null && preparedCredential === null) return yield* new ProviderAdapterValidationError({
|
|
217769
|
+
provider: PROVIDER$11,
|
|
217770
|
+
operation: "startSession",
|
|
217771
|
+
issue: `The selected Claude account ${selectedAccountIdentity} has no prepared credential. Try again to reconnect it.`
|
|
217772
|
+
});
|
|
217773
|
+
const credentialDirectoryDecision = yield* decideClaudeCredentialDirectory({
|
|
217774
|
+
env: process.env,
|
|
217775
|
+
baseDir: serverConfig.baseDir,
|
|
217776
|
+
accountIdentity: selectedAccountIdentity,
|
|
217777
|
+
probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support,
|
|
217778
|
+
primaryCredentialMatchesSelected: preparedCredential?.accountKind === "primary" && primaryClaudeCredentialMatchesRevision(process.env, preparedCredential.credentialRevisionHash)
|
|
217779
|
+
});
|
|
217780
|
+
if (credentialDirectoryDecision.outcome === "refused") return yield* new ProviderAdapterValidationError({
|
|
217781
|
+
provider: PROVIDER$11,
|
|
217782
|
+
operation: "startSession",
|
|
217783
|
+
issue: `The selected Claude account ${selectedAccountIdentity} cannot run on this environment: ${credentialDirectoryDecision.reason}. Reconnect the account or choose another environment.`
|
|
217784
|
+
});
|
|
217785
|
+
if (credentialDirectoryDecision.outcome === "skipped") {
|
|
217786
|
+
const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
|
|
217787
|
+
yield* credentialDirectoryDecision.reason === "no-account" ? require_Schema$1.logDebug(skipLine) : require_Schema$1.logWarning(skipLine);
|
|
217788
|
+
}
|
|
217789
|
+
return {
|
|
217790
|
+
selectedAccountIdentity,
|
|
217791
|
+
claudeCredentialDirectory: credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null
|
|
217792
|
+
};
|
|
217793
|
+
});
|
|
217261
217794
|
const startSession = require_Schema$1.fn("startSession")(function* (input) {
|
|
217262
217795
|
const sessionProvider = yield* validateClaudeStartSessionProvider(input.provider);
|
|
217263
217796
|
const startedAt = yield* nowIso;
|
|
@@ -217318,19 +217851,14 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
217318
217851
|
})));
|
|
217319
217852
|
const claudeSettings = serverSettings.providers.claudeAgent;
|
|
217320
217853
|
const claudeBinaryPath = claudeSettings.binaryPath;
|
|
217321
|
-
const
|
|
217322
|
-
env: process.env,
|
|
217323
|
-
baseDir: serverConfig.baseDir,
|
|
217324
|
-
accountIdentity: resolveClaudeProviderAccountIdentity(process.env),
|
|
217325
|
-
probeSupport: claudeCredentialDirectoryProbeFor(claudeBinaryPath).support
|
|
217326
|
-
});
|
|
217327
|
-
if (credentialDirectoryDecision.outcome === "skipped") {
|
|
217328
|
-
const skipLine = `Claude credential directory skipped: ${credentialDirectoryDecision.reason}`;
|
|
217329
|
-
yield* credentialDirectoryDecision.reason === "no-account" ? require_Schema$1.logDebug(skipLine) : require_Schema$1.logWarning(skipLine);
|
|
217330
|
-
}
|
|
217331
|
-
const claudeCredentialDirectory = credentialDirectoryDecision.outcome === "set" ? credentialDirectoryDecision.directory : null;
|
|
217854
|
+
const { selectedAccountIdentity, claudeCredentialDirectory } = yield* resolveSelectedCredentialDirectory(input, resumeState, claudeBinaryPath);
|
|
217332
217855
|
const modelConfig = resolveClaudeSessionModelConfig(input.modelSelection, claudeSettings.endpointProfiles);
|
|
217333
|
-
|
|
217856
|
+
if (selectedAccountIdentity !== null && modelConfig.endpointProfile !== void 0) return yield* new ProviderAdapterValidationError({
|
|
217857
|
+
provider: PROVIDER$11,
|
|
217858
|
+
operation: "startSession",
|
|
217859
|
+
issue: "The selected Claude account cannot be used with a custom API endpoint. Choose a subscription model."
|
|
217860
|
+
});
|
|
217861
|
+
const providerProxyCredential = selectedAccountIdentity !== null ? null : yield* issueClaudeProviderProxyCredential(modelConfig.endpointProfile, modelConfig.modelSlug ?? modelConfig.effectiveApiModelId ?? "unknown", threadId);
|
|
217334
217862
|
const permissionMode = permissionModeForRuntimeMode(input.runtimeMode);
|
|
217335
217863
|
const settings = claudeQuerySettings(modelConfig.thinking, modelConfig.fastMode);
|
|
217336
217864
|
const workspacePreflight = input.cwd ? formatWorkspaceEnvironmentPreflightForAgent(runWorkspaceEnvironmentPreflight({ cwd: input.cwd })) : void 0;
|
|
@@ -217353,7 +217881,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
217353
217881
|
existingResumeSessionId,
|
|
217354
217882
|
newSessionId,
|
|
217355
217883
|
canUseTool,
|
|
217356
|
-
env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential, claudeCredentialDirectory),
|
|
217884
|
+
env: buildClaudeQueryEnv(process.env, modelConfig.endpointProfile, claudeSettings, providerProxyCredential, claudeCredentialDirectory, selectedAccountIdentity !== null),
|
|
217357
217885
|
browserManager,
|
|
217358
217886
|
threadId,
|
|
217359
217887
|
browserToolEnvironmentId,
|
|
@@ -217405,7 +217933,9 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
|
|
|
217405
217933
|
});
|
|
217406
217934
|
attachClaudeStreamFiber(context$4, runFork);
|
|
217407
217935
|
return { ...session };
|
|
217408
|
-
})
|
|
217936
|
+
}, (effect, input) => effect.pipe(require_Schema$1.onExit((exit) => require_Schema$1.sync(() => {
|
|
217937
|
+
if (exit._tag === "Failure") selectedClaudeCredentialStore(serverConfig.baseDir).release(input.threadId);
|
|
217938
|
+
}))));
|
|
217409
217939
|
const sendTurn = require_Schema$1.fn("sendTurn")(function* (input) {
|
|
217410
217940
|
const context = yield* requireSession(input.threadId);
|
|
217411
217941
|
const modelSelection = input.modelSelection?.provider === "claudeAgent" || input.modelSelection?.provider === "apex" || input.modelSelection?.provider === "cmmd" ? input.modelSelection : void 0;
|
|
@@ -231714,6 +232244,9 @@ const ProviderRollbackConversationInput = require_Schema$1.Struct({
|
|
|
231714
232244
|
threadId: ThreadId,
|
|
231715
232245
|
numTurns: NonNegativeInt
|
|
231716
232246
|
});
|
|
232247
|
+
function selectedAccountGuardFields(providerAccountId) {
|
|
232248
|
+
return providerAccountId === void 0 ? {} : { providerAccountId };
|
|
232249
|
+
}
|
|
231717
232250
|
function cursorRecord(value) {
|
|
231718
232251
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : {};
|
|
231719
232252
|
}
|
|
@@ -231806,9 +232339,13 @@ const makeProviderService = require_Schema$1.fn("makeProviderService")(function*
|
|
|
231806
232339
|
const runtimeEventPubSub = yield* require_Path.unbounded$1();
|
|
231807
232340
|
const assertProviderExecutable = options?.assertProviderExecutable ?? (() => require_Schema$1.succeed(null));
|
|
231808
232341
|
const assertCurrentProviderDelivery = require_Schema$1.fn("assertCurrentProviderDelivery")(function* (input) {
|
|
231809
|
-
const currentBinding = yield* assertProviderExecutable(input);
|
|
231810
|
-
if (!currentBinding) return;
|
|
231811
232342
|
const persistedBinding = require_Schema$1.getOrUndefined$1(yield* directory.getBinding(input.threadId));
|
|
232343
|
+
const providerAccountId = readPersistedProviderAccountId(persistedBinding?.runtimePayload);
|
|
232344
|
+
const currentBinding = yield* assertProviderExecutable({
|
|
232345
|
+
...input,
|
|
232346
|
+
...selectedAccountGuardFields(providerAccountId)
|
|
232347
|
+
});
|
|
232348
|
+
if (!currentBinding) return;
|
|
231812
232349
|
if (!persistedBinding || persistedBinding.provider !== input.provider) return yield* toValidationError(input.operation, `Cannot verify the delivered provider identity for thread '${input.threadId}'.`);
|
|
231813
232350
|
yield* bindProviderDeliveryToResumeCursor({
|
|
231814
232351
|
operation: input.operation,
|
|
@@ -231864,10 +232401,12 @@ const makeProviderService = require_Schema$1.fn("makeProviderService")(function*
|
|
|
231864
232401
|
if (!hasResumeCursor) return yield* toValidationError(input.operation, `Cannot recover thread '${input.binding.threadId}' because no provider resume state is persisted.`);
|
|
231865
232402
|
const persistedCwd = readPersistedCwd(input.binding.runtimePayload);
|
|
231866
232403
|
const persistedModelSelection = readPersistedModelSelection(input.binding.runtimePayload);
|
|
232404
|
+
const persistedProviderAccountId = readProviderAccountIdFromModelSelection(persistedModelSelection);
|
|
231867
232405
|
const currentDeliveryBinding = yield* assertProviderExecutable({
|
|
231868
232406
|
threadId: input.binding.threadId,
|
|
231869
232407
|
provider: input.binding.provider,
|
|
231870
|
-
operation: input.operation
|
|
232408
|
+
operation: input.operation,
|
|
232409
|
+
...selectedAccountGuardFields(persistedProviderAccountId)
|
|
231871
232410
|
});
|
|
231872
232411
|
const boundResumeCursor = yield* bindProviderDeliveryToResumeCursor({
|
|
231873
232412
|
operation: input.operation,
|
|
@@ -231993,7 +232532,8 @@ const makeProviderService = require_Schema$1.fn("makeProviderService")(function*
|
|
|
231993
232532
|
currentBinding: yield* assertProviderExecutable({
|
|
231994
232533
|
threadId,
|
|
231995
232534
|
provider: effectiveProvider,
|
|
231996
|
-
operation: "ProviderService.startSession"
|
|
232535
|
+
operation: "ProviderService.startSession",
|
|
232536
|
+
...selectedAccountGuardFields(requestedAccountId)
|
|
231997
232537
|
})
|
|
231998
232538
|
});
|
|
231999
232539
|
const session = yield* adapter.startSession({
|
|
@@ -232059,6 +232599,8 @@ const makeProviderService = require_Schema$1.fn("makeProviderService")(function*
|
|
|
232059
232599
|
allowRecovery: true
|
|
232060
232600
|
});
|
|
232061
232601
|
const persistedBinding = require_Schema$1.getOrUndefined$1(yield* directory.getBinding(input.threadId));
|
|
232602
|
+
const persistedModelSelection = readPersistedModelSelection(persistedBinding?.runtimePayload);
|
|
232603
|
+
const nextModelSelection = input.modelSelection ?? persistedModelSelection;
|
|
232062
232604
|
yield* assertProviderAccountBoundary({
|
|
232063
232605
|
operation: "ProviderService.sendTurn",
|
|
232064
232606
|
threadId: input.threadId,
|
|
@@ -232083,7 +232625,7 @@ const makeProviderService = require_Schema$1.fn("makeProviderService")(function*
|
|
|
232083
232625
|
status: "running",
|
|
232084
232626
|
...turn.resumeCursor !== void 0 ? { resumeCursor: turn.resumeCursor } : {},
|
|
232085
232627
|
runtimePayload: {
|
|
232086
|
-
...
|
|
232628
|
+
...nextModelSelection !== void 0 ? { modelSelection: nextModelSelection } : {},
|
|
232087
232629
|
activeTurnId: turn.turnId,
|
|
232088
232630
|
lastRuntimeEvent: "provider.sendTurn",
|
|
232089
232631
|
lastRuntimeEventAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -232340,6 +232882,201 @@ function makeProviderServiceLive(options) {
|
|
|
232340
232882
|
return require_Schema$1.effect(ProviderService, makeProviderService(options));
|
|
232341
232883
|
}
|
|
232342
232884
|
//#endregion
|
|
232885
|
+
//#region src/runtimeAuth/cmmdClaudeSelectedTicket.ts
|
|
232886
|
+
const ACCOUNT = /^pai_v1_[A-Za-z0-9_-]{43}$/;
|
|
232887
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
232888
|
+
const REVISION_HASH = /^[a-f0-9]{64}$/;
|
|
232889
|
+
function hasValidTicketLifetime(claims, payload) {
|
|
232890
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
232891
|
+
const issuedAt = payload.iat;
|
|
232892
|
+
const notBefore = payload.nbf;
|
|
232893
|
+
return typeof issuedAt === "number" && Number.isSafeInteger(issuedAt) && issuedAt <= now + 5 && typeof notBefore === "number" && Number.isSafeInteger(notBefore) && notBefore <= now && Number.isSafeInteger(claims.expiresAtSeconds) && claims.expiresAtSeconds - issuedAt <= 900;
|
|
232894
|
+
}
|
|
232895
|
+
function hasSelectedCredentialScope(claims) {
|
|
232896
|
+
return claims.scope.length === 1 && claims.scope[0] === "claude-credential-read";
|
|
232897
|
+
}
|
|
232898
|
+
function hasExpectedSelectedBinding(payload, expected) {
|
|
232899
|
+
return payload.provider_account_id === expected.providerAccountId && ACCOUNT.test(expected.providerAccountId) && payload.thread_id === expected.threadId && payload.operation_id === expected.operationId && UUID.test(expected.operationId);
|
|
232900
|
+
}
|
|
232901
|
+
function selectedCredentialClaims(payload) {
|
|
232902
|
+
const jti = payload.jti;
|
|
232903
|
+
const generationId = payload.credential_generation_id;
|
|
232904
|
+
const credentialRevisionHash = payload.credential_revision_hash;
|
|
232905
|
+
const accountKind = payload.account_kind;
|
|
232906
|
+
if (typeof jti !== "string" || !jti.trim() || typeof generationId !== "string" || !UUID.test(generationId) || typeof credentialRevisionHash !== "string" || !REVISION_HASH.test(credentialRevisionHash) || accountKind !== "primary" && accountKind !== "linked") return null;
|
|
232907
|
+
return {
|
|
232908
|
+
jti,
|
|
232909
|
+
generationId,
|
|
232910
|
+
credentialRevisionHash,
|
|
232911
|
+
accountKind
|
|
232912
|
+
};
|
|
232913
|
+
}
|
|
232914
|
+
function verifyCmmdClaudeSelectedTicket(token, expected) {
|
|
232915
|
+
const verified = require_runtimeControlAuthority.verifyCmmdScopedRuntimePayload(token, expected, "forge-claude-credential-read");
|
|
232916
|
+
if (!verified) return null;
|
|
232917
|
+
const { claims, payload } = verified;
|
|
232918
|
+
if (!hasValidTicketLifetime(claims, payload)) return null;
|
|
232919
|
+
if (!hasSelectedCredentialScope(claims)) return null;
|
|
232920
|
+
if (!hasExpectedSelectedBinding(payload, expected)) return null;
|
|
232921
|
+
const selected = selectedCredentialClaims(payload);
|
|
232922
|
+
if (!selected) return null;
|
|
232923
|
+
return {
|
|
232924
|
+
...claims,
|
|
232925
|
+
jti: selected.jti,
|
|
232926
|
+
providerAccountId: expected.providerAccountId,
|
|
232927
|
+
generationId: selected.generationId,
|
|
232928
|
+
credentialRevisionHash: selected.credentialRevisionHash,
|
|
232929
|
+
accountKind: selected.accountKind,
|
|
232930
|
+
threadId: expected.threadId,
|
|
232931
|
+
operationId: expected.operationId
|
|
232932
|
+
};
|
|
232933
|
+
}
|
|
232934
|
+
//#endregion
|
|
232935
|
+
//#region src/provider/claudeSelectedCredentialAcquisition.ts
|
|
232936
|
+
function readToken(response) {
|
|
232937
|
+
if (typeof response !== "object" || response === null || !("data" in response)) return null;
|
|
232938
|
+
const data = response.data;
|
|
232939
|
+
return typeof data === "object" && data !== null && "token" in data && typeof data.token === "string" ? data.token : null;
|
|
232940
|
+
}
|
|
232941
|
+
async function acquireSelectedClaudeCredential(expected, transport) {
|
|
232942
|
+
try {
|
|
232943
|
+
const token = readToken(await transport.requestTicket());
|
|
232944
|
+
const claims = token === null ? null : verifyCmmdClaudeSelectedTicket(token, expected);
|
|
232945
|
+
if (!token || !claims) throw new Error("invalid_ticket");
|
|
232946
|
+
const credentialJson = await transport.readCredential(token);
|
|
232947
|
+
if (Buffer.byteLength(credentialJson, "utf8") > 512e3 || (0, node_crypto.createHash)("sha256").update(credentialJson).digest("hex") !== claims.credentialRevisionHash || !hasUsableClaudeCredentialBlob(credentialJson)) throw new Error("invalid_credential");
|
|
232948
|
+
return {
|
|
232949
|
+
claims,
|
|
232950
|
+
credentialJson
|
|
232951
|
+
};
|
|
232952
|
+
} catch {
|
|
232953
|
+
throw new Error("The selected Claude account could not be prepared. Reconnect the account and try again.");
|
|
232954
|
+
}
|
|
232955
|
+
}
|
|
232956
|
+
//#endregion
|
|
232957
|
+
//#region src/provider/claudeSelectedCredentialPreparer.ts
|
|
232958
|
+
const REQUEST_TIMEOUT_MS = 1e4;
|
|
232959
|
+
const OWNER_TICKET_PATH = "/api/forge/context/providers/claude/runtime-read-ticket";
|
|
232960
|
+
const CREDENTIAL_READ_PATH = "/api/forge/context/auth-runtime-claude-credential";
|
|
232961
|
+
function isRuntimeTicketSession$1(sessionId) {
|
|
232962
|
+
return String(sessionId).startsWith("cmmd-runtime:");
|
|
232963
|
+
}
|
|
232964
|
+
function nonEmptyConfigString(value) {
|
|
232965
|
+
return value?.trim() || null;
|
|
232966
|
+
}
|
|
232967
|
+
function selectedRuntimeStrings(config) {
|
|
232968
|
+
const values = [
|
|
232969
|
+
nonEmptyConfigString(config.forgeCmmdRuntimeId),
|
|
232970
|
+
nonEmptyConfigString(config.forgeCmmdRuntimeMachineId),
|
|
232971
|
+
nonEmptyConfigString(config.forgeCmmdRuntimeOrgId),
|
|
232972
|
+
nonEmptyConfigString(config.forgeCmmdRuntimeUserId),
|
|
232973
|
+
nonEmptyConfigString(config.forgeCmmdRuntimePublicKey)
|
|
232974
|
+
];
|
|
232975
|
+
if (values.includes(null)) return null;
|
|
232976
|
+
return {
|
|
232977
|
+
runtimeId: values[0],
|
|
232978
|
+
machineId: values[1],
|
|
232979
|
+
orgId: values[2],
|
|
232980
|
+
userId: values[3],
|
|
232981
|
+
publicKeyPem: values[4]
|
|
232982
|
+
};
|
|
232983
|
+
}
|
|
232984
|
+
function selectedCredentialEpoch(config) {
|
|
232985
|
+
const credentialEpoch = config.forgeCmmdRuntimeCredentialEpoch;
|
|
232986
|
+
return typeof credentialEpoch === "number" && Number.isSafeInteger(credentialEpoch) && credentialEpoch >= 0 ? credentialEpoch : null;
|
|
232987
|
+
}
|
|
232988
|
+
function selectedConfig(config) {
|
|
232989
|
+
if (config.forgeCmmdRuntimeMode !== true) return null;
|
|
232990
|
+
const strings = selectedRuntimeStrings(config);
|
|
232991
|
+
const credentialEpoch = selectedCredentialEpoch(config);
|
|
232992
|
+
if (strings === null || credentialEpoch === null) return null;
|
|
232993
|
+
return {
|
|
232994
|
+
...strings,
|
|
232995
|
+
credentialEpoch
|
|
232996
|
+
};
|
|
232997
|
+
}
|
|
232998
|
+
async function boundedRuntimeTicketRequest(grant, runtimeId, request) {
|
|
232999
|
+
const response = await fetch(resolveCommandAppEndpoint(`/api/forge/runtimes/${encodeURIComponent(runtimeId)}/provider/claude/read-ticket`), {
|
|
233000
|
+
method: "POST",
|
|
233001
|
+
headers: {
|
|
233002
|
+
authorization: `Bearer ${grant}`,
|
|
233003
|
+
"content-type": "application/json"
|
|
233004
|
+
},
|
|
233005
|
+
body: JSON.stringify(request),
|
|
233006
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
233007
|
+
});
|
|
233008
|
+
if (!response.ok) throw new Error("selected_ticket_unavailable");
|
|
233009
|
+
return await response.json();
|
|
233010
|
+
}
|
|
233011
|
+
async function boundedCredentialRead(token) {
|
|
233012
|
+
const response = await fetch(resolveCommandAppEndpoint(CREDENTIAL_READ_PATH), {
|
|
233013
|
+
headers: {
|
|
233014
|
+
authorization: `Bearer ${token}`,
|
|
233015
|
+
accept: "application/json"
|
|
233016
|
+
},
|
|
233017
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
233018
|
+
});
|
|
233019
|
+
if (!response.ok) throw new Error("selected_credential_unavailable");
|
|
233020
|
+
return await response.text();
|
|
233021
|
+
}
|
|
233022
|
+
function missingAuthority() {
|
|
233023
|
+
throw new Error("selected_authority_unavailable");
|
|
233024
|
+
}
|
|
233025
|
+
/**
|
|
233026
|
+
* Acquires one signed, account-scoped Claude credential for the thread that
|
|
233027
|
+
* selected it. Owner and runtime-ticket sessions use different CMMD lanes.
|
|
233028
|
+
*/
|
|
233029
|
+
function makeClaudeSelectedCredentialPreparer(config, dependencies) {
|
|
233030
|
+
const readRuntimeAuthority = dependencies.readRuntimeAuthority ?? readRuntimeBrokerAuthority;
|
|
233031
|
+
const requestOwnerTicket = dependencies.requestOwnerTicket ?? ((jwt, request) => proxyPost(jwt, OWNER_TICKET_PATH, request, void 0, { signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) }));
|
|
233032
|
+
const requestRuntimeTicket = dependencies.requestRuntimeTicket ?? boundedRuntimeTicketRequest;
|
|
233033
|
+
const readCredential = dependencies.readCredential ?? boundedCredentialRead;
|
|
233034
|
+
const prepare = dependencies.prepare ?? ((threadId, acquired) => {
|
|
233035
|
+
if (!config.baseDir) return Promise.reject(/* @__PURE__ */ new Error("selected_store_unavailable"));
|
|
233036
|
+
return selectedClaudeCredentialStore(config.baseDir).prepare(threadId, acquired);
|
|
233037
|
+
});
|
|
233038
|
+
const createOperationId = dependencies.createOperationId ?? node_crypto.randomUUID;
|
|
233039
|
+
return async (input) => {
|
|
233040
|
+
const sealed = selectedConfig(config);
|
|
233041
|
+
const sessionId = input.executionSessionId ?? dependencies.readSessionId(input.threadId);
|
|
233042
|
+
if (sealed === null || sessionId === null) return missingAuthority();
|
|
233043
|
+
const operationId = createOperationId();
|
|
233044
|
+
const request = {
|
|
233045
|
+
providerAccountId: input.providerAccountId,
|
|
233046
|
+
threadId: String(input.threadId),
|
|
233047
|
+
operationId
|
|
233048
|
+
};
|
|
233049
|
+
const acquired = await acquireSelectedClaudeCredential({
|
|
233050
|
+
...sealed,
|
|
233051
|
+
providerAccountId: input.providerAccountId,
|
|
233052
|
+
threadId: String(input.threadId),
|
|
233053
|
+
operationId
|
|
233054
|
+
}, {
|
|
233055
|
+
requestTicket: async () => {
|
|
233056
|
+
if (isRuntimeTicketSession$1(sessionId)) {
|
|
233057
|
+
const grant = readRuntimeAuthority({
|
|
233058
|
+
sessionId,
|
|
233059
|
+
runtimeId: sealed.runtimeId,
|
|
233060
|
+
machineId: sealed.machineId,
|
|
233061
|
+
credentialEpoch: sealed.credentialEpoch,
|
|
233062
|
+
requiredScope: "provider"
|
|
233063
|
+
});
|
|
233064
|
+
if (grant === null) return missingAuthority();
|
|
233065
|
+
return { data: await requestRuntimeTicket(grant, sealed.runtimeId, request) };
|
|
233066
|
+
}
|
|
233067
|
+
const jwt = await dependencies.resolveJwt(sessionId, { forceRefresh: false });
|
|
233068
|
+
if (jwt === null) return missingAuthority();
|
|
233069
|
+
return await requestOwnerTicket(jwt, {
|
|
233070
|
+
...request,
|
|
233071
|
+
runtimeId: sealed.runtimeId
|
|
233072
|
+
});
|
|
233073
|
+
},
|
|
233074
|
+
readCredential
|
|
233075
|
+
});
|
|
233076
|
+
return await prepare(String(input.threadId), acquired);
|
|
233077
|
+
};
|
|
233078
|
+
}
|
|
233079
|
+
//#endregion
|
|
232343
233080
|
//#region src/provider/claudeRuntimeReadinessGuard.ts
|
|
232344
233081
|
const GENERATION_HASH_PATTERN = /^[a-f0-9]{64}$/;
|
|
232345
233082
|
const RUNTIME_READINESS_TIMEOUT_MS$1 = 1e4;
|
|
@@ -232608,7 +233345,22 @@ function makeClaudeRuntimeReadinessGuard(config, dependencies) {
|
|
|
232608
233345
|
return (input) => claudeReadinessGuardEffect(config, dependencies, resolved, input);
|
|
232609
233346
|
}
|
|
232610
233347
|
function claudeReadinessGuardEffect(config, dependencies, resolved, input) {
|
|
232611
|
-
if (!requiresNativeClaudeReadiness(config, input.provider)) return require_Schema$1.succeed(null);
|
|
233348
|
+
if (!requiresNativeClaudeReadiness(config, input.provider, input.providerAccountId)) return require_Schema$1.succeed(null);
|
|
233349
|
+
if (input.providerAccountId !== void 0) {
|
|
233350
|
+
const prepareSelectedCredential = dependencies.prepareSelectedCredential ?? makeClaudeSelectedCredentialPreparer(config, {
|
|
233351
|
+
resolveJwt: dependencies.resolveJwt,
|
|
233352
|
+
readSessionId: resolved.readSessionId,
|
|
233353
|
+
readRuntimeAuthority: resolved.readRuntimeAuthority
|
|
233354
|
+
});
|
|
233355
|
+
const selectedInput = {
|
|
233356
|
+
...input,
|
|
233357
|
+
providerAccountId: input.providerAccountId
|
|
233358
|
+
};
|
|
233359
|
+
return require_Schema$1.tryPromise({
|
|
233360
|
+
try: () => prepareSelectedCredential(selectedInput),
|
|
233361
|
+
catch: () => selectedCredentialValidationError(input.operation, selectedInput.providerAccountId)
|
|
233362
|
+
});
|
|
233363
|
+
}
|
|
232612
233364
|
const authority = resolveGuardAuthority(config, resolved.readSessionId, input);
|
|
232613
233365
|
if (authority === null) return invalidOwnerSessionEffect(input.operation);
|
|
232614
233366
|
return require_Schema$1.tryPromise({
|
|
@@ -232616,8 +233368,9 @@ function claudeReadinessGuardEffect(config, dependencies, resolved, input) {
|
|
|
232616
233368
|
catch: (cause) => readinessValidationError(input.operation, cause)
|
|
232617
233369
|
});
|
|
232618
233370
|
}
|
|
232619
|
-
function requiresNativeClaudeReadiness(config, provider) {
|
|
232620
|
-
|
|
233371
|
+
function requiresNativeClaudeReadiness(config, provider, providerAccountId) {
|
|
233372
|
+
if (provider !== "claudeAgent" || config.forgeCmmdRuntimeMode !== true) return false;
|
|
233373
|
+
return providerAccountId !== void 0 || personalClaudeInVmEnabled();
|
|
232621
233374
|
}
|
|
232622
233375
|
function resolveGuardAuthority(config, readSessionId, input) {
|
|
232623
233376
|
const runtimeId = config.forgeCmmdRuntimeId?.trim();
|
|
@@ -232654,6 +233407,12 @@ async function verifyGuardAuthority(config, dependencies, resolved, authority) {
|
|
|
232654
233407
|
fetchReadiness: resolved.fetchReadiness
|
|
232655
233408
|
});
|
|
232656
233409
|
}
|
|
233410
|
+
function selectedCredentialValidationError(operation, accountId) {
|
|
233411
|
+
return new ProviderValidationError({
|
|
233412
|
+
operation,
|
|
233413
|
+
issue: `The selected Claude account ${accountId} could not be prepared. Reconnect the account and try again.`
|
|
233414
|
+
});
|
|
233415
|
+
}
|
|
232657
233416
|
function readinessValidationError(operation, cause) {
|
|
232658
233417
|
return new ProviderValidationError({
|
|
232659
233418
|
operation,
|
|
@@ -289685,6 +290444,7 @@ const RuntimeDrainStateLive = require_Schema$1.effect(RuntimeDrainState, makeRun
|
|
|
289685
290444
|
//#region src/runtimeDrain/RuntimeDrainReporter.ts
|
|
289686
290445
|
const HEARTBEAT_INTERVAL = require_Schema$1.seconds(30);
|
|
289687
290446
|
const HEARTBEAT_TICK_TIMEOUT = require_Schema$1.seconds(60);
|
|
290447
|
+
const FORCE_FORGE_APP_UPDATE_DRAIN_REASON = "forge_app_force_update";
|
|
289688
290448
|
function runtimeHeartbeatConfig(config) {
|
|
289689
290449
|
if (config.forgeCmmdRuntimeMode !== true) return null;
|
|
289690
290450
|
const runtimeId = config.forgeCmmdRuntimeId?.trim();
|
|
@@ -289719,6 +290479,9 @@ function hasDrainUpdateTarget(runtime) {
|
|
|
289719
290479
|
function isReadyForRuntimeUpdateActivation(runtime, snapshot) {
|
|
289720
290480
|
return runtime.draining === true && snapshot.activeTurnCount === 0 && snapshot.activeSessionCount === 0 && hasDrainUpdateTarget(runtime);
|
|
289721
290481
|
}
|
|
290482
|
+
function forceStopsResumableTurns(runtime) {
|
|
290483
|
+
return runtime.drainForceRequired === true && runtime.drainReason?.startsWith(`${FORCE_FORGE_APP_UPDATE_DRAIN_REASON}:`) === true;
|
|
290484
|
+
}
|
|
289722
290485
|
const terminateForRuntimeUpdate = (runtime) => require_Schema$1.gen(function* () {
|
|
289723
290486
|
yield* require_Schema$1.logWarning("CMMD runtime drain is clean and targets a Forge app update; terminating for supervisor activation.", {
|
|
289724
290487
|
drainReason: runtime.drainReason,
|
|
@@ -289729,6 +290492,16 @@ const terminateForRuntimeUpdate = (runtime) => require_Schema$1.gen(function* ()
|
|
|
289729
290492
|
process.kill(process.pid, "SIGTERM");
|
|
289730
290493
|
});
|
|
289731
290494
|
});
|
|
290495
|
+
const postRuntimeHeartbeat = (config, snapshot) => require_Schema$1.promise(async () => {
|
|
290496
|
+
const forgeGitCommit = await resolveForgeGitCommit();
|
|
290497
|
+
return require_runtimeControlAuthority.sendRuntimeControlHeartbeat({
|
|
290498
|
+
...config,
|
|
290499
|
+
activeTurnCount: snapshot.activeTurnCount,
|
|
290500
|
+
activeSessionCount: snapshot.activeSessionCount,
|
|
290501
|
+
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
290502
|
+
forgeServerVersion: installedForgeServerVersion
|
|
290503
|
+
});
|
|
290504
|
+
});
|
|
289732
290505
|
const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
289733
290506
|
const drainState = yield* RuntimeDrainState;
|
|
289734
290507
|
const providerService = yield* ProviderService;
|
|
@@ -289736,16 +290509,6 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
289736
290509
|
const spriteTaskHoldReactor = yield* SpriteTaskHoldReactor;
|
|
289737
290510
|
const reportSemaphore = yield* require_Path.make$5(1);
|
|
289738
290511
|
let heartbeatSilent = false;
|
|
289739
|
-
const postHeartbeat = (config, snapshot) => require_Schema$1.promise(async () => {
|
|
289740
|
-
const forgeGitCommit = await resolveForgeGitCommit();
|
|
289741
|
-
return require_runtimeControlAuthority.sendRuntimeControlHeartbeat({
|
|
289742
|
-
...config,
|
|
289743
|
-
activeTurnCount: snapshot.activeTurnCount,
|
|
289744
|
-
activeSessionCount: snapshot.activeSessionCount,
|
|
289745
|
-
...forgeGitCommit !== void 0 ? { forgeGitCommit } : {},
|
|
289746
|
-
forgeServerVersion: installedForgeServerVersion
|
|
289747
|
-
});
|
|
289748
|
-
});
|
|
289749
290512
|
const applyControlPlaneState = (runtime) => drainState.applyControlPlaneState({
|
|
289750
290513
|
draining: runtime.draining,
|
|
289751
290514
|
drainReason: runtime.drainReason,
|
|
@@ -289764,11 +290527,35 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
289764
290527
|
})
|
|
289765
290528
|
}).pipe(require_Schema$1.catch_((cause) => require_Schema$1.logError("CMMD runtime provisioning grant refresh failed — this runtime will go dark once its current grant expires", { cause })));
|
|
289766
290529
|
});
|
|
289767
|
-
const
|
|
289768
|
-
if (
|
|
290530
|
+
const settleActiveDrainTurns = (runtime, initialSessions, initialSnapshot) => require_Schema$1.gen(function* () {
|
|
290531
|
+
if (initialSnapshot.activeTurnCount === 0) return {
|
|
290532
|
+
ready: true,
|
|
290533
|
+
sessions: initialSessions,
|
|
290534
|
+
snapshot: initialSnapshot
|
|
290535
|
+
};
|
|
290536
|
+
if (!forceStopsResumableTurns(runtime)) return {
|
|
290537
|
+
ready: false,
|
|
290538
|
+
sessions: initialSessions,
|
|
290539
|
+
snapshot: initialSnapshot
|
|
290540
|
+
};
|
|
290541
|
+
yield* require_Schema$1.forEach(initialSessions.filter((session) => session.activeTurnId !== void 0), (session) => providerService.interruptTurn({
|
|
290542
|
+
threadId: session.threadId,
|
|
290543
|
+
turnId: session.activeTurnId
|
|
290544
|
+
}), { concurrency: 1 });
|
|
289769
290545
|
const sessions = yield* providerService.listSessions();
|
|
289770
290546
|
const snapshot = yield* drainState.reconcileProviderSessions(sessions);
|
|
289771
|
-
|
|
290547
|
+
return {
|
|
290548
|
+
ready: snapshot.activeTurnCount === 0,
|
|
290549
|
+
sessions,
|
|
290550
|
+
snapshot
|
|
290551
|
+
};
|
|
290552
|
+
});
|
|
290553
|
+
const completeDrainIfRequested = (config, runtime) => require_Schema$1.gen(function* () {
|
|
290554
|
+
if (runtime.draining !== true) return;
|
|
290555
|
+
const initialSessions = yield* providerService.listSessions();
|
|
290556
|
+
const settled = yield* settleActiveDrainTurns(runtime, initialSessions, yield* drainState.reconcileProviderSessions(initialSessions));
|
|
290557
|
+
if (!settled.ready) return;
|
|
290558
|
+
const { sessions, snapshot } = settled;
|
|
289772
290559
|
yield* spriteTaskHoldReactor.releaseAll;
|
|
289773
290560
|
const wasAlreadySessionless = snapshot.activeSessionCount === 0;
|
|
289774
290561
|
const drainedSnapshot = wasAlreadySessionless ? snapshot : yield* require_Schema$1.gen(function* () {
|
|
@@ -289780,7 +290567,7 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
289780
290567
|
return yield* drainState.reconcileProviderSessions(remainingSessions);
|
|
289781
290568
|
});
|
|
289782
290569
|
if (wasAlreadySessionless && !hasDrainUpdateTarget(runtime)) return;
|
|
289783
|
-
const drainedRuntime = yield*
|
|
290570
|
+
const drainedRuntime = yield* postRuntimeHeartbeat(config, drainedSnapshot);
|
|
289784
290571
|
if (drainedRuntime) {
|
|
289785
290572
|
yield* applyControlPlaneState(drainedRuntime);
|
|
289786
290573
|
if (isReadyForRuntimeUpdateActivation(drainedRuntime, drainedSnapshot)) yield* terminateForRuntimeUpdate(drainedRuntime);
|
|
@@ -289791,7 +290578,7 @@ const makeRuntimeDrainReporter = require_Schema$1.gen(function* () {
|
|
|
289791
290578
|
const config = runtimeHeartbeatConfig(serverConfig);
|
|
289792
290579
|
if (!config) return;
|
|
289793
290580
|
const initialSessions = yield* providerService.listSessions();
|
|
289794
|
-
const runtime = yield*
|
|
290581
|
+
const runtime = yield* postRuntimeHeartbeat(config, yield* drainState.reconcileProviderSessions(initialSessions));
|
|
289795
290582
|
if (!runtime) {
|
|
289796
290583
|
if (!heartbeatSilent) {
|
|
289797
290584
|
heartbeatSilent = true;
|
|
@@ -297365,7 +298152,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
297365
298152
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
297366
298153
|
*/
|
|
297367
298154
|
function readBakedBuildCommit() {
|
|
297368
|
-
return "
|
|
298155
|
+
return "e51896589531a57a458ca5c2430dfd54cdc16476";
|
|
297369
298156
|
}
|
|
297370
298157
|
async function resolveServerBuildCommit(input) {
|
|
297371
298158
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -306279,6 +307066,82 @@ const orchestrationAutomationTerminalReconcileRouteLayer = add$1("POST", "/api/o
|
|
|
306279
307066
|
}, { status: 200 });
|
|
306280
307067
|
}).pipe(require_Schema$1.catchTag("OrchestrationDispatchCommandError", respondToWorkRunEventError)));
|
|
306281
307068
|
//#endregion
|
|
307069
|
+
//#region src/orchestration/threadSnapshotRoute.ts
|
|
307070
|
+
/**
|
|
307071
|
+
* `GET /api/orchestration/threads/:threadId`, one thread's detail snapshot,
|
|
307072
|
+
* optionally windowed to a page of recent turns.
|
|
307073
|
+
*
|
|
307074
|
+
* Forge never served this route. The mobile client has asked for it on every
|
|
307075
|
+
* thread open since it shipped, fell through to the socket-embedded snapshot on
|
|
307076
|
+
* the 404, and its "load earlier" control had nowhere to fetch from, so a long
|
|
307077
|
+
* thread was only ever loadable in full. Serving it here gives the initial read
|
|
307078
|
+
* a body the transport can gzip, off the RPC socket, and gives older turns a
|
|
307079
|
+
* cursor to arrive on.
|
|
307080
|
+
*
|
|
307081
|
+
* The window is applied by the same pure function the WebSocket fallback
|
|
307082
|
+
* snapshot uses, so the two reads cannot disagree about what a page is.
|
|
307083
|
+
*/
|
|
307084
|
+
/**
|
|
307085
|
+
* Reads the window out of the query string.
|
|
307086
|
+
*
|
|
307087
|
+
* Returns null for a malformed value rather than ignoring it. A client that
|
|
307088
|
+
* asked for 10 turns and silently received 4,000 has no way to tell, and the
|
|
307089
|
+
* failure would show up as a slow app rather than as an error.
|
|
307090
|
+
*/
|
|
307091
|
+
function parseThreadSnapshotWindow(searchParams) {
|
|
307092
|
+
const rawTurnLimit = searchParams.get("turnLimit");
|
|
307093
|
+
const rawBeforeCursor = searchParams.get("beforeCursor");
|
|
307094
|
+
let turnLimit;
|
|
307095
|
+
if (rawTurnLimit !== null) {
|
|
307096
|
+
const parsed = Number(rawTurnLimit);
|
|
307097
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) return null;
|
|
307098
|
+
turnLimit = parsed;
|
|
307099
|
+
}
|
|
307100
|
+
let beforeCursor;
|
|
307101
|
+
if (rawBeforeCursor !== null) {
|
|
307102
|
+
const trimmed = rawBeforeCursor.trim();
|
|
307103
|
+
if (trimmed.length === 0) return null;
|
|
307104
|
+
beforeCursor = trimmed;
|
|
307105
|
+
}
|
|
307106
|
+
return {
|
|
307107
|
+
...turnLimit === void 0 ? {} : { turnLimit },
|
|
307108
|
+
...beforeCursor === void 0 ? {} : { beforeCursor }
|
|
307109
|
+
};
|
|
307110
|
+
}
|
|
307111
|
+
const orchestrationThreadSnapshotRouteLayer = add$1("GET", "/api/orchestration/threads/:threadId", require_Schema$1.gen(function* () {
|
|
307112
|
+
const request = yield* require_HttpServer.HttpServerRequest;
|
|
307113
|
+
yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
307114
|
+
const rawThreadId = (yield* params)["threadId"];
|
|
307115
|
+
if (rawThreadId === void 0 || rawThreadId.length === 0) return require_HttpServer.jsonUnsafe({ error: "Missing thread id." }, { status: 400 });
|
|
307116
|
+
const url = require_HttpServer.toURL(request);
|
|
307117
|
+
const window = require_Schema$1.isNone(url) ? {} : parseThreadSnapshotWindow(url.value.searchParams);
|
|
307118
|
+
if (window === null) return require_HttpServer.jsonUnsafe({ error: "Invalid turnLimit or beforeCursor." }, { status: 400 });
|
|
307119
|
+
const threadId = ThreadId.make(decodeURIComponent(rawThreadId));
|
|
307120
|
+
const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;
|
|
307121
|
+
const orchestrationEngine = yield* OrchestrationEngineService;
|
|
307122
|
+
const [threadDetail, snapshotSequence] = yield* require_Schema$1.all([projectionSnapshotQuery.getThreadDetailById(threadId).pipe(require_Schema$1.mapError((cause) => new OrchestrationGetSnapshotError({
|
|
307123
|
+
message: `Failed to load thread ${threadId}`,
|
|
307124
|
+
cause
|
|
307125
|
+
}))), orchestrationEngine.getReadModel().pipe(require_Schema$1.map((readModel) => readModel.snapshotSequence))]);
|
|
307126
|
+
if (require_Schema$1.isNone(threadDetail)) return require_HttpServer.jsonUnsafe({ error: "Thread not found." }, { status: 404 });
|
|
307127
|
+
const windowed = windowThreadDetail({
|
|
307128
|
+
thread: threadDetail.value,
|
|
307129
|
+
window,
|
|
307130
|
+
snapshotSequence
|
|
307131
|
+
});
|
|
307132
|
+
return require_HttpServer.jsonUnsafe({
|
|
307133
|
+
snapshotSequence,
|
|
307134
|
+
thread: windowed.thread,
|
|
307135
|
+
...windowed.page === void 0 ? {} : { page: windowed.page }
|
|
307136
|
+
}, { status: 200 });
|
|
307137
|
+
}).pipe(require_Schema$1.catchTag("AuthError", respondToAuthError), require_Schema$1.catchTag("OrchestrationGetSnapshotError", (error) => require_Schema$1.gen(function* () {
|
|
307138
|
+
yield* require_Schema$1.logError("thread snapshot route failed", {
|
|
307139
|
+
message: error.message,
|
|
307140
|
+
cause: error.cause
|
|
307141
|
+
});
|
|
307142
|
+
return require_HttpServer.jsonUnsafe({ error: error.message }, { status: 500 });
|
|
307143
|
+
}))));
|
|
307144
|
+
//#endregion
|
|
306282
307145
|
//#region src/localAutomations/scheduling.ts
|
|
306283
307146
|
const MINUTE_MS = 6e4;
|
|
306284
307147
|
const HOUR_MS = 60 * MINUTE_MS;
|
|
@@ -320653,12 +321516,12 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", require_
|
|
|
320653
321516
|
//#region src/server.ts
|
|
320654
321517
|
const PtyAdapterLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
320655
321518
|
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-uyreNRGp.cjs")))).layer;
|
|
320656
|
-
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-
|
|
321519
|
+
else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-BehazUMi.cjs")))).layer;
|
|
320657
321520
|
}));
|
|
320658
321521
|
const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
320659
321522
|
const config = yield* ServerConfig;
|
|
320660
|
-
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-
|
|
320661
|
-
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-
|
|
321523
|
+
if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-C0oPP_3A.cjs")))).layer(bunHttpServerOptions(config));
|
|
321524
|
+
const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-Cjd3bFrD.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
|
|
320662
321525
|
return NodeHttpServer.layer(NodeHttp.createServer, {
|
|
320663
321526
|
host: config.host,
|
|
320664
321527
|
port: config.port
|
|
@@ -320666,10 +321529,10 @@ const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* ()
|
|
|
320666
321529
|
}));
|
|
320667
321530
|
const PlatformServicesLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
320668
321531
|
if (typeof Bun !== "undefined") {
|
|
320669
|
-
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-
|
|
321532
|
+
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-DkenwD7S.cjs")));
|
|
320670
321533
|
return layer;
|
|
320671
321534
|
} else {
|
|
320672
|
-
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-
|
|
321535
|
+
const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-BvdIc9Vh.cjs")));
|
|
320673
321536
|
return layer;
|
|
320674
321537
|
}
|
|
320675
321538
|
}));
|
|
@@ -320736,7 +321599,7 @@ const MiscRuntimeLayerLive = require_Schema$1.mergeAll(AnalyticsServiceLayerLive
|
|
|
320736
321599
|
const RuntimeDependenciesWithoutPullRequestsLive = ReactorLayerLive.pipe(require_Schema$1.provideMerge(CheckpointingLayerLive), require_Schema$1.provideMerge(GitLayerLive), require_Schema$1.provideMerge(OrchestrationLayerLive), require_Schema$1.provideMerge(ProviderLayerLive), require_Schema$1.provideMerge(TerminalLayerLive), require_Schema$1.provideMerge(AgentBrowserManagerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ThreadDevRuntimeLive, DevTunnelLive.pipe(require_Schema$1.provide$1(ThreadDevRuntimeLive)))), require_Schema$1.provideMerge(PersistenceLayerLive), require_Schema$1.provideMerge(KeybindingsLive), require_Schema$1.provideMerge(ProviderRegistryLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(ServerSettingsLive, UsageServiceLive.pipe(require_Schema$1.provide$1(ServerSettingsLive), require_Schema$1.provide$1(UsageSessionLookupLive), require_Schema$1.provide$1(UsageRecordedSourceLive.pipe(require_Schema$1.provide$1(OrchestrationLayerLive), require_Schema$1.provide$1(RuntimeReplicationOutboxLive), require_Schema$1.provide$1(ProviderSessionRuntimeRepositoryLive), require_Schema$1.provide$1(TextGenerationSessionRepositoryLive)))))), require_Schema$1.provideMerge(WorkspaceLayerLive), require_Schema$1.provideMerge(WorkspaceReadinessStateLive), require_Schema$1.provideMerge(ProjectFaviconResolverLive), require_Schema$1.provideMerge(RepositoryIdentityResolverLive), require_Schema$1.provideMerge(ServerEnvironmentLive), require_Schema$1.provideMerge(AuthLayerLive), require_Schema$1.provideMerge(ServerSecretStoreLayerLive), require_Schema$1.provideMerge(require_Schema$1.mergeAll(CloudRedisCoordinationLive, CloudStateSyncLive.pipe(require_Schema$1.provide$1(PersistenceLayerLive)))), require_Schema$1.provideMerge(MiscRuntimeLayerLive));
|
|
320737
321600
|
const RuntimeDependenciesLive = pullRequestServiceFactoryLayer.pipe(require_Schema$1.provideMerge(RuntimeDependenciesWithoutPullRequestsLive), require_Schema$1.provideMerge(cmmdProjectIdentityResolverLive.pipe(require_Schema$1.provide$1(RuntimeDependenciesWithoutPullRequestsLive))), require_Schema$1.provideMerge(pullRequestProviderRuntimeLive));
|
|
320738
321601
|
const RuntimeServicesLive = ServerRuntimeStartupLive.pipe(require_Schema$1.provideMerge(RuntimeDependenciesLive));
|
|
320739
|
-
const makeRoutesLayer = require_Schema$1.mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(require_Schema$1.provide$1(browserApiCorsLayer));
|
|
321602
|
+
const makeRoutesLayer = require_Schema$1.mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdForgeRuntimesStatusRouteLayer, cmmdForgeRuntimesStartRouteLayer, cmmdForgeRuntimesStopRouteLayer, cmmdForgeRuntimesRecycleRouteLayer, cmmdForgeRuntimesUpdateCommitRouteLayer, cmmdForgeRuntimesDestroyRouteLayer, mobileProviderCatalogRouteLayer, attachmentsRouteLayer, orchestrationDispatchRouteLayer, orchestrationCmmdWorkRunEventRouteLayer, orchestrationAutomationStartThreadRouteLayer, orchestrationAutomationRebindRouteLayer, orchestrationAutomationTerminalReconcileRouteLayer, mobileGitRunStackedActionRouteLayer, pullRequestDiffRouteLayer, orchestrationSnapshotRouteLayer, orchestrationThreadSnapshotRouteLayer, localAutomationListRouteLayer, localAutomationCreateRouteLayer, localAutomationUpdateRouteLayer, localAutomationDeleteRouteLayer, localAutomationRunRouteLayer, otlpTracesProxyRouteLayer, devTunnelProxyRouteLayer, devTunnelLocalPortProxyRouteLayer, projectFaviconRouteLayer, serverEnvironmentRouteLayer, browserWebRtcReadinessRouteLayer, staticAndDevRouteLayer, githubStatusRouteLayer, githubAuthorizeUrlRouteLayer, githubDisconnectRouteLayer, githubReposRouteLayer, githubCloneRouteLayer, githubCloneJobStartRouteLayer, githubCloneJobStatusRouteLayer, githubRefreshCredentialsRouteLayer, githubToolConfirmationApproveRouteLayer, projectLinkGetRouteLayer, projectLinkListRouteLayer, projectLinkSetRouteLayer, projectLinkDeleteRouteLayer, cmmdWorkspacesRouteLayer, cmmdCampaignsRouteLayer, cmmdProjectLinksRouteLayer, cmmdEnsureGitHubProjectContextRouteLayer, cmmdProjectRepositoryEnsureRouteLayer, cmmdProjectsGetRouteLayer, cmmdProjectsCreateRouteLayer, workspaceReadinessRouteLayer, runtimeLivenessRouteLayer, runtimeFilesystemHealthRouteLayer, claudeNativeGenerationProofRouteLayer, claudeUsageRouteLayer, codexRuntimeProviderReadinessRouteLayer, runtimeDiagnosticsRouteLayer, cloudFoldersRouteLayer, cloudProjectCreateRouteLayer, gitUrlCloneRouteLayer, websocketRpcRouteLayer).pipe(require_Schema$1.provide$1(browserApiCorsLayer));
|
|
320740
321603
|
const makeServerLayer = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
320741
321604
|
const config = yield* ServerConfig;
|
|
320742
321605
|
fixPath({ preferProcessPath: config.forgeCmmdRuntimeMode === true });
|