@cmmd-center/forge 0.13.124 → 0.13.126
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.cjs +365 -65
- package/dist/bin.mjs +394 -94
- package/dist/client/assets/{CanvasPanel-BOh2xUZv.js → CanvasPanel-Db1Iz4JN.js} +2 -2
- package/dist/client/assets/CompositeItem-BxPZ_9BJ.js +2 -0
- package/dist/client/assets/DiffPanel-CNil6OV-.js +64 -0
- package/dist/client/assets/{DiffPanel.logic-DK4RDoiD.js → DiffPanel.logic-yEOXCs0u.js} +2 -2
- package/dist/client/assets/{DiffWorkerPoolProvider-orOnVCAZ.js → DiffWorkerPoolProvider-BF-pZpUl.js} +11 -11
- package/dist/client/assets/PullRequestCodePanel-eldrSbZ3.js +5 -0
- package/dist/client/assets/{ThemeEditorPanel-v2mnY18G.js → ThemeEditorPanel-DTfrlPBc.js} +2 -2
- package/dist/client/assets/{Virtualizer-D5o0Becp.js → Virtualizer-BvsCWf4E.js} +2 -2
- package/dist/client/assets/WorkerPoolContext-CaivT_VA.js +1533 -0
- package/dist/client/assets/{canvasStateStore-xaZAenqH.js → canvasStateStore-DKtQcBVc.js} +2 -2
- package/dist/client/assets/cmmdForgeRuntimeListResource-CRVF6pin.js +1008 -0
- package/dist/client/assets/currentForgeWorkspaceStore-DH-DGTJm.js +2 -0
- package/dist/client/assets/{index-DB8H2s1H.js → index-CIM7fDFV.js} +68 -68
- package/dist/client/assets/popover-Cvb1Axgx.js +2 -0
- package/dist/client/assets/scroll-area-DzReJmNn.js +2 -0
- package/dist/client/assets/switch-RuRK1onH.js +2 -0
- package/dist/client/assets/{themePalette-eOA8OvYM.js → themePalette-gr6ZYaOx.js} +2 -2
- package/dist/client/assets/toggle-D8xaJAgP.js +2 -0
- package/dist/client/assets/toggle-group-CwSbXP07.js +2 -0
- package/dist/client/assets/useOpenInteractionType-B983ff5H.js +2 -0
- package/dist/client/assets/utils-B4G-Q0s7.js +2 -0
- package/dist/client/index.html +16 -13
- package/dist/forge-build.json +1 -1
- package/dist/{open-2-reBrpv.mjs → open-yHTUkd2Y.mjs} +3 -3
- package/package.json +1 -1
- package/dist/client/assets/CompositeItem-DNVaubQt.js +0 -2
- package/dist/client/assets/DiffPanel-C1le0UaP.js +0 -64
- package/dist/client/assets/PullRequestCodePanel-Br2r3lbU.js +0 -5
- package/dist/client/assets/WorkerPoolContext-CpM1XKxV.js +0 -2538
- package/dist/client/assets/popover-Cxnw5kve.js +0 -2
- package/dist/client/assets/scroll-area-DDcW71H4.js +0 -2
- package/dist/client/assets/switch-Wi-2Dkh9.js +0 -2
- package/dist/client/assets/toggle-group-Cqh7gA8I.js +0 -2
- package/dist/client/assets/toggle-m8ncTpRB.js +0 -2
- package/dist/client/assets/useOpenInteractionType-CN3hnnfp.js +0 -2
package/dist/bin.cjs
CHANGED
|
@@ -30767,7 +30767,7 @@ function frameClassByte(frameClass) {
|
|
|
30767
30767
|
if (frameClass === "application") return Uint8Array.of(1);
|
|
30768
30768
|
return Uint8Array.of(2);
|
|
30769
30769
|
}
|
|
30770
|
-
function encodeBase64Url$
|
|
30770
|
+
function encodeBase64Url$2(value) {
|
|
30771
30771
|
let output = "";
|
|
30772
30772
|
for (let index = 0; index < value.byteLength; index += 3) {
|
|
30773
30773
|
const first = value[index] ?? 0;
|
|
@@ -30797,7 +30797,7 @@ function decodeBase64Url$1(value) {
|
|
|
30797
30797
|
outputIndex += 1;
|
|
30798
30798
|
}
|
|
30799
30799
|
}
|
|
30800
|
-
if (encodeBase64Url$
|
|
30800
|
+
if (encodeBase64Url$2(output) !== value) throw new RelayPayloadChannelFailure("channel_authentication_failed", "Encrypted relay frame has a non-canonical base64url encoding.");
|
|
30801
30801
|
return output;
|
|
30802
30802
|
}
|
|
30803
30803
|
function isLowOrderX25519PublicKey(value) {
|
|
@@ -30836,7 +30836,7 @@ async function createRelayPayloadBinding(input, digestProvider = defaultDigestPr
|
|
|
30836
30836
|
};
|
|
30837
30837
|
}
|
|
30838
30838
|
function relayPayloadTicketDigestBase64Url(binding) {
|
|
30839
|
-
return encodeBase64Url$
|
|
30839
|
+
return encodeBase64Url$2(binding.ticketDigest);
|
|
30840
30840
|
}
|
|
30841
30841
|
function relayPayloadInfo(binding) {
|
|
30842
30842
|
return concat([
|
|
@@ -30873,7 +30873,7 @@ async function generateRelayEndpointKeyPair(randomSource = defaultRandomSource)
|
|
|
30873
30873
|
}
|
|
30874
30874
|
async function serializeRelayEndpointPublicKey(publicKey) {
|
|
30875
30875
|
try {
|
|
30876
|
-
return encodeBase64Url$
|
|
30876
|
+
return encodeBase64Url$2(bytes(await suite.kem.serializePublicKey(publicKey)));
|
|
30877
30877
|
} catch (error) {
|
|
30878
30878
|
throw new RelayPayloadChannelFailure("endpoint_key_invalid", error instanceof Error ? error.message : "Endpoint public-key serialization failed.");
|
|
30879
30879
|
}
|
|
@@ -30928,7 +30928,7 @@ var HpkeRelayPayloadSender = class {
|
|
|
30928
30928
|
constructor(binding, context) {
|
|
30929
30929
|
this.binding = binding;
|
|
30930
30930
|
this.context = context;
|
|
30931
|
-
this.encapsulatedKey = encodeBase64Url$
|
|
30931
|
+
this.encapsulatedKey = encodeBase64Url$2(bytes(context.enc));
|
|
30932
30932
|
}
|
|
30933
30933
|
seal(input) {
|
|
30934
30934
|
return this.queue.run(async () => {
|
|
@@ -30958,7 +30958,7 @@ var HpkeRelayPayloadSender = class {
|
|
|
30958
30958
|
direction: this.binding.direction,
|
|
30959
30959
|
frameClass: input.frameClass,
|
|
30960
30960
|
sequence,
|
|
30961
|
-
ciphertext: encodeBase64Url$
|
|
30961
|
+
ciphertext: encodeBase64Url$2(ciphertext)
|
|
30962
30962
|
};
|
|
30963
30963
|
});
|
|
30964
30964
|
}
|
|
@@ -66337,7 +66337,7 @@ function normalizeNumberish(value) {
|
|
|
66337
66337
|
}
|
|
66338
66338
|
//#endregion
|
|
66339
66339
|
//#region package.json
|
|
66340
|
-
var version$1 = "0.13.
|
|
66340
|
+
var version$1 = "0.13.126";
|
|
66341
66341
|
//#endregion
|
|
66342
66342
|
//#region src/sentry.ts
|
|
66343
66343
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -71414,6 +71414,19 @@ function mintCmmdShortLivedToken(grantToken) {
|
|
|
71414
71414
|
});
|
|
71415
71415
|
}
|
|
71416
71416
|
/**
|
|
71417
|
+
* The redemption request body. `popPublicKeyPem` is the server's PoP identity
|
|
71418
|
+
* (forge#3534): presented when bound sessions are enabled so CMMD stamps the
|
|
71419
|
+
* returned JWT with this server's key (`cnf.jkt`), omitted otherwise.
|
|
71420
|
+
*/
|
|
71421
|
+
function buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem) {
|
|
71422
|
+
return {
|
|
71423
|
+
grant_type: "forge_session_grant",
|
|
71424
|
+
grant_token: grantToken,
|
|
71425
|
+
client_id: "cmmd-forge-web",
|
|
71426
|
+
...popPublicKeyPem ? { pop_public_key: popPublicKeyPem } : {}
|
|
71427
|
+
};
|
|
71428
|
+
}
|
|
71429
|
+
/**
|
|
71417
71430
|
* Redeem a durable CMMD session grant for a fresh credential set.
|
|
71418
71431
|
*
|
|
71419
71432
|
* The grant is CMMD's designed recovery path for exactly the case a rotating
|
|
@@ -71425,15 +71438,11 @@ function mintCmmdShortLivedToken(grantToken) {
|
|
|
71425
71438
|
* Returns the new MCP PAT alongside the JWT/refresh pair, because CMMD mints a
|
|
71426
71439
|
* fresh PAT on redemption and the old one is not carried over.
|
|
71427
71440
|
*/
|
|
71428
|
-
function redeemCmmdSessionGrant(grantToken) {
|
|
71441
|
+
function redeemCmmdSessionGrant(grantToken, popPublicKeyPem) {
|
|
71429
71442
|
return fetch(new URL("/api/mcp-server/oauth/token", resolveCommandAppUrl()).toString(), {
|
|
71430
71443
|
method: "POST",
|
|
71431
71444
|
headers: { "Content-Type": "application/json" },
|
|
71432
|
-
body: JSON.stringify(
|
|
71433
|
-
grant_type: "forge_session_grant",
|
|
71434
|
-
grant_token: grantToken,
|
|
71435
|
-
client_id: "cmmd-forge-web"
|
|
71436
|
-
})
|
|
71445
|
+
body: JSON.stringify(buildSessionGrantRedemptionBody(grantToken, popPublicKeyPem))
|
|
71437
71446
|
}).then(async (response) => {
|
|
71438
71447
|
if (!response.ok) return null;
|
|
71439
71448
|
const payload = await response.json();
|
|
@@ -71504,6 +71513,140 @@ async function readOAuthErrorCode(response) {
|
|
|
71504
71513
|
}
|
|
71505
71514
|
}
|
|
71506
71515
|
//#endregion
|
|
71516
|
+
//#region ../../packages/shared/src/cmmdSessionPop.ts
|
|
71517
|
+
/**
|
|
71518
|
+
* CMMD session proof-of-possession client protocol (forge#3517).
|
|
71519
|
+
*
|
|
71520
|
+
* A CMMD session issued to a Forge client can be bound to the client's Ed25519
|
|
71521
|
+
* device key: the public key travels with the login (`pop_public_key`), CMMD
|
|
71522
|
+
* stamps its thumbprint into the token (`cnf.jkt`), and every authenticated
|
|
71523
|
+
* request must then carry a detached Ed25519 signature over a canonical
|
|
71524
|
+
* request binding (method + path + timestamp + nonce). A stolen bearer token
|
|
71525
|
+
* alone is unusable.
|
|
71526
|
+
*
|
|
71527
|
+
* This module is the client half of the scheme CMMD verifies in
|
|
71528
|
+
* `server/services/auth/session-pop-binding.ts`. The protocol deliberately
|
|
71529
|
+
* reuses CMMD's existing crypto shapes rather than inventing a new scheme: the
|
|
71530
|
+
* thumbprint is the same sha256-over-SPKI-DER base64url digest as the forge
|
|
71531
|
+
* device registry and the desktop relay identity, and the signature covers
|
|
71532
|
+
* `<purpose>.<base64url payload>`, the forge relay challenge shape, with
|
|
71533
|
+
* Ed25519. It is not a DPoP port: proofs are plain headers, not JWTs.
|
|
71534
|
+
*/
|
|
71535
|
+
const HEADER_KEY = "x-cmmd-pop-key";
|
|
71536
|
+
const HEADER_INPUT = "x-cmmd-pop-input";
|
|
71537
|
+
const HEADER_SIGNATURE = "x-cmmd-pop-signature";
|
|
71538
|
+
const CMMD_SESSION_POP_PURPOSE = "cmmd-session-pop-v1";
|
|
71539
|
+
function encodeBase64Url$1(bytes) {
|
|
71540
|
+
let binary = "";
|
|
71541
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
71542
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/u, "");
|
|
71543
|
+
}
|
|
71544
|
+
function buildCmmdSessionPopSigningInput(payload) {
|
|
71545
|
+
return `${CMMD_SESSION_POP_PURPOSE}.${encodeBase64Url$1(new TextEncoder().encode(JSON.stringify(payload)))}`;
|
|
71546
|
+
}
|
|
71547
|
+
function randomNonce() {
|
|
71548
|
+
const bytes = new Uint8Array(16);
|
|
71549
|
+
crypto.getRandomValues(bytes);
|
|
71550
|
+
return encodeBase64Url$1(bytes);
|
|
71551
|
+
}
|
|
71552
|
+
/**
|
|
71553
|
+
* Build the three proof headers for one request. Each call produces a fresh
|
|
71554
|
+
* nonce; callers must not reuse the returned headers across attempts
|
|
71555
|
+
* (retries included), because CMMD rejects replayed nonces.
|
|
71556
|
+
*/
|
|
71557
|
+
async function buildCmmdSessionPopProofHeaders(input) {
|
|
71558
|
+
const payload = {
|
|
71559
|
+
version: 1,
|
|
71560
|
+
htm: input.method.toUpperCase(),
|
|
71561
|
+
htu: input.path,
|
|
71562
|
+
ts: input.nowSeconds ?? Math.floor(Date.now() / 1e3),
|
|
71563
|
+
nonce: input.nonce ?? randomNonce()
|
|
71564
|
+
};
|
|
71565
|
+
const signingInput = buildCmmdSessionPopSigningInput(payload);
|
|
71566
|
+
const signature = await input.signer.sign(signingInput);
|
|
71567
|
+
const spkiDer = decodePemSpkiDer(input.signer.publicKeyPem);
|
|
71568
|
+
return {
|
|
71569
|
+
[HEADER_KEY]: encodeBase64Url$1(spkiDer),
|
|
71570
|
+
[HEADER_INPUT]: encodeBase64Url$1(new TextEncoder().encode(JSON.stringify(payload))),
|
|
71571
|
+
[HEADER_SIGNATURE]: encodeBase64Url$1(signature)
|
|
71572
|
+
};
|
|
71573
|
+
}
|
|
71574
|
+
function decodePemSpkiDer(pem) {
|
|
71575
|
+
const base64 = pem.replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "").replace(/\s+/gu, "").replace(/-/g, "+").replace(/_/g, "/");
|
|
71576
|
+
const padded = base64 + "=".repeat((4 - base64.length % 4) % 4);
|
|
71577
|
+
const binary = atob(padded);
|
|
71578
|
+
const bytes = new Uint8Array(binary.length);
|
|
71579
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
71580
|
+
return bytes;
|
|
71581
|
+
}
|
|
71582
|
+
//#endregion
|
|
71583
|
+
//#region src/cmmd/cmmdSessionPopServer.ts
|
|
71584
|
+
/**
|
|
71585
|
+
* The Forge server's own CMMD session proof-of-possession identity (forge#3534,
|
|
71586
|
+
* D3): the server holds an Ed25519 key, presents its SPKI PEM as
|
|
71587
|
+
* `pop_public_key` when it redeems a CMMD session grant, and signs every
|
|
71588
|
+
* CMMD-origin REST call it makes with detached proofs, so a stolen bearer JWT
|
|
71589
|
+
* alone is unusable. CMMD verifies the scheme in
|
|
71590
|
+
* `server/services/auth/session-pop-binding.ts`.
|
|
71591
|
+
*
|
|
71592
|
+
* Rollout order (docs/forge-cmmd-auth-lanes.md): CMMD deploys first; Forge
|
|
71593
|
+
* flips `FORGE_CMMD_SESSION_POP_ENABLED` afterwards; CMMD's
|
|
71594
|
+
* `JWT_REQUIRE_BOUND_SESSIONS=true` is the contract phase. While the flag is
|
|
71595
|
+
* off this module produces no keys and no headers, and CMMD treats tokens as
|
|
71596
|
+
* plain bearers.
|
|
71597
|
+
*
|
|
71598
|
+
* The identity lives in memory for the process lifetime. After a restart the
|
|
71599
|
+
* server mints a new identity; CMMD then refuses proofs from the old one, and
|
|
71600
|
+
* the JWT resolver's recovery ladder re-mints the affected credentials. The
|
|
71601
|
+
* persistence duty (a stable identity across restarts) lands with the flag
|
|
71602
|
+
* rollout, not with this plumbing.
|
|
71603
|
+
*/
|
|
71604
|
+
const ENABLED_FLAG = "FORGE_CMMD_SESSION_POP_ENABLED";
|
|
71605
|
+
/** Enables session proofs only when the rollout flag is exactly "true". */
|
|
71606
|
+
function isCmmdSessionPopEnabled() {
|
|
71607
|
+
return process.env[ENABLED_FLAG] === "true";
|
|
71608
|
+
}
|
|
71609
|
+
let cachedSigner = null;
|
|
71610
|
+
/** Creates an in-memory Ed25519 identity with a public SPKI PEM and a detached signer. */
|
|
71611
|
+
function createServerSigner() {
|
|
71612
|
+
const { publicKey, privateKey } = (0, node_crypto.generateKeyPairSync)("ed25519");
|
|
71613
|
+
const exported = publicKey.export({
|
|
71614
|
+
type: "spki",
|
|
71615
|
+
format: "pem"
|
|
71616
|
+
});
|
|
71617
|
+
return {
|
|
71618
|
+
publicKeyPem: typeof exported === "string" ? exported : exported.toString("utf8"),
|
|
71619
|
+
sign: async (signingInput) => new Uint8Array((0, node_crypto.sign)(null, Buffer.from(signingInput, "utf8"), privateKey))
|
|
71620
|
+
};
|
|
71621
|
+
}
|
|
71622
|
+
/**
|
|
71623
|
+
* The server's PoP identity, created on first use under the flag and stable
|
|
71624
|
+
* for the process lifetime. Null while the flag is off.
|
|
71625
|
+
*/
|
|
71626
|
+
function resolveCmmdSessionPopSigner() {
|
|
71627
|
+
if (!isCmmdSessionPopEnabled()) return null;
|
|
71628
|
+
cachedSigner ??= createServerSigner();
|
|
71629
|
+
return cachedSigner;
|
|
71630
|
+
}
|
|
71631
|
+
/** The `pop_public_key` value to present at issuance, or null while disabled. */
|
|
71632
|
+
function cmmdSessionPopPublicKey() {
|
|
71633
|
+
return resolveCmmdSessionPopSigner()?.publicKeyPem ?? null;
|
|
71634
|
+
}
|
|
71635
|
+
/**
|
|
71636
|
+
* The three proof headers for one CMMD-origin request, or no headers while
|
|
71637
|
+
* disabled. Each call carries a fresh nonce; never reuse the result across
|
|
71638
|
+
* attempts, because CMMD rejects replayed nonces.
|
|
71639
|
+
*/
|
|
71640
|
+
async function cmmdSessionPopRequestHeaders(input) {
|
|
71641
|
+
const signer = resolveCmmdSessionPopSigner();
|
|
71642
|
+
if (!signer) return {};
|
|
71643
|
+
return buildCmmdSessionPopProofHeaders({
|
|
71644
|
+
signer,
|
|
71645
|
+
method: input.method,
|
|
71646
|
+
path: input.path
|
|
71647
|
+
});
|
|
71648
|
+
}
|
|
71649
|
+
//#endregion
|
|
71507
71650
|
//#region src/command/cmmdTokenMintLane.ts
|
|
71508
71651
|
/**
|
|
71509
71652
|
* The CMMD token-mint lane (forge#3518, expand phase).
|
|
@@ -71756,7 +71899,7 @@ function tryGrantRedemption(sessionId, context) {
|
|
|
71756
71899
|
return require_Schema$1.gen(function* () {
|
|
71757
71900
|
yield* logCmmdJwtRung(context, "grant-redemption", "attempted", "redeeming this session's durable CMMD session grant");
|
|
71758
71901
|
yield* recordCmmdCredentialRefreshMetric("grant-redemption", "attempted");
|
|
71759
|
-
const redeemed = yield* require_Schema$1.promise(() => redeemCmmdSessionGrant(grantToken)).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
71902
|
+
const redeemed = yield* require_Schema$1.promise(() => redeemCmmdSessionGrant(grantToken, cmmdSessionPopPublicKey())).pipe(require_Schema$1.orElseSucceed(() => null));
|
|
71760
71903
|
const accessToken = usableCmmdUserJwt(redeemed?.accessToken);
|
|
71761
71904
|
if (!accessToken) {
|
|
71762
71905
|
yield* logCmmdJwtRung(context, "grant-redemption", "failed", "session grant redemption did not return a usable user access JWT");
|
|
@@ -72045,11 +72188,25 @@ async function requireCmmdProxySuccess(response, signal) {
|
|
|
72045
72188
|
* Uses the stored JWT (set after OAuth auth-code flow with post-update CMMD)
|
|
72046
72189
|
* to make authenticated REST calls on behalf of the user. A missing delegated
|
|
72047
72190
|
* JWT is an authentication-required state, not a CMMD availability failure.
|
|
72191
|
+
*
|
|
72192
|
+
* Every request this module sends can carry the server's session
|
|
72193
|
+
* proof-of-possession headers (forge#3534): while
|
|
72194
|
+
* `FORGE_CMMD_SESSION_POP_ENABLED` is true, `authHeaders` attaches detached
|
|
72195
|
+
* Ed25519 proofs over each request's method and path, which CMMD requires for
|
|
72196
|
+
* tokens bound with `cnf.jkt` and ignores otherwise. Each call gets a fresh
|
|
72197
|
+
* nonce, so retries re-sign rather than replay.
|
|
72048
72198
|
*/
|
|
72049
|
-
|
|
72199
|
+
/** Builds owner JWT and optional organization headers with fresh, flag-gated session proofs. */
|
|
72200
|
+
async function authHeaders(jwt, orgId, method, path) {
|
|
72050
72201
|
const headers = { Authorization: `Bearer ${jwt}` };
|
|
72051
72202
|
if (orgId) headers["X-Organization-ID"] = orgId;
|
|
72052
|
-
return
|
|
72203
|
+
return {
|
|
72204
|
+
...headers,
|
|
72205
|
+
...await cmmdSessionPopRequestHeaders({
|
|
72206
|
+
method,
|
|
72207
|
+
path
|
|
72208
|
+
})
|
|
72209
|
+
};
|
|
72053
72210
|
}
|
|
72054
72211
|
async function fetchResponse(url, init, options) {
|
|
72055
72212
|
try {
|
|
@@ -72061,18 +72218,20 @@ async function fetchResponse(url, init, options) {
|
|
|
72061
72218
|
rethrowCmmdProxyTimeout(error, options?.signal);
|
|
72062
72219
|
}
|
|
72063
72220
|
}
|
|
72221
|
+
/** Fetches a CMMD JSON resource with optional query parameters and caller cancellation. */
|
|
72064
72222
|
async function proxyGet(jwt, path, searchParams, orgId, options) {
|
|
72065
72223
|
const cmmdBase = resolveCommandAppUrl();
|
|
72066
72224
|
const url = new URL(path, cmmdBase);
|
|
72067
72225
|
if (searchParams) for (const [k, v] of Object.entries(searchParams)) url.searchParams.set(k, v);
|
|
72068
|
-
return handleCmmdProxyResponse(await fetchResponse(url.toString(), { headers: authHeaders(jwt, orgId) }, options), options?.signal);
|
|
72226
|
+
return handleCmmdProxyResponse(await fetchResponse(url.toString(), { headers: await authHeaders(jwt, orgId, "GET", path) }, options), options?.signal);
|
|
72069
72227
|
}
|
|
72228
|
+
/** Posts JSON to CMMD on the owner JWT lane and decodes the response or upstream error. */
|
|
72070
72229
|
async function proxyPost(jwt, path, body, orgId, options) {
|
|
72071
72230
|
const cmmdBase = resolveCommandAppUrl();
|
|
72072
72231
|
return handleCmmdProxyResponse(await fetchResponse(new URL(path, cmmdBase).toString(), {
|
|
72073
72232
|
method: "POST",
|
|
72074
72233
|
headers: {
|
|
72075
|
-
...authHeaders(jwt, orgId),
|
|
72234
|
+
...await authHeaders(jwt, orgId, "POST", path),
|
|
72076
72235
|
"Content-Type": "application/json"
|
|
72077
72236
|
},
|
|
72078
72237
|
body: JSON.stringify(body)
|
|
@@ -72086,44 +72245,49 @@ async function responseWithMetadata(response, signal) {
|
|
|
72086
72245
|
retryAfter: response.headers.get("retry-after")
|
|
72087
72246
|
};
|
|
72088
72247
|
}
|
|
72248
|
+
/** Fetches a CMMD resource while preserving status, Location, and Retry-After metadata. */
|
|
72089
72249
|
async function proxyGetWithMetadata(jwt, path, orgId, options) {
|
|
72090
|
-
return responseWithMetadata(await fetchResponse(new URL(path, resolveCommandAppUrl()).toString(), { headers: authHeaders(jwt, orgId) }, options), options?.signal);
|
|
72250
|
+
return responseWithMetadata(await fetchResponse(new URL(path, resolveCommandAppUrl()).toString(), { headers: await authHeaders(jwt, orgId, "GET", path) }, options), options?.signal);
|
|
72091
72251
|
}
|
|
72252
|
+
/** Posts JSON to CMMD while preserving status, Location, and Retry-After metadata. */
|
|
72092
72253
|
async function proxyPostWithMetadata(jwt, path, body, orgId, options) {
|
|
72093
72254
|
const cmmdBase = resolveCommandAppUrl();
|
|
72094
72255
|
return responseWithMetadata(await fetchResponse(new URL(path, cmmdBase).toString(), {
|
|
72095
72256
|
method: "POST",
|
|
72096
72257
|
headers: {
|
|
72097
|
-
...authHeaders(jwt, orgId),
|
|
72258
|
+
...await authHeaders(jwt, orgId, "POST", path),
|
|
72098
72259
|
"Content-Type": "application/json"
|
|
72099
72260
|
},
|
|
72100
72261
|
body: JSON.stringify(body)
|
|
72101
72262
|
}, options), options?.signal);
|
|
72102
72263
|
}
|
|
72264
|
+
/** Applies a JSON patch request to CMMD using the owner JWT and optional organization. */
|
|
72103
72265
|
async function proxyPatch(jwt, path, body, orgId) {
|
|
72104
72266
|
const cmmdBase = resolveCommandAppUrl();
|
|
72105
72267
|
const url = new URL(path, cmmdBase);
|
|
72106
72268
|
return handleCmmdProxyResponse(await fetch(url.toString(), {
|
|
72107
72269
|
method: "PATCH",
|
|
72108
72270
|
headers: {
|
|
72109
|
-
...authHeaders(jwt, orgId),
|
|
72271
|
+
...await authHeaders(jwt, orgId, "PATCH", path),
|
|
72110
72272
|
"Content-Type": "application/json"
|
|
72111
72273
|
},
|
|
72112
72274
|
body: JSON.stringify(body)
|
|
72113
72275
|
}));
|
|
72114
72276
|
}
|
|
72277
|
+
/** Sends a JSON replacement request to CMMD and decodes its response or upstream error. */
|
|
72115
72278
|
async function proxyPut(jwt, path, body, orgId) {
|
|
72116
72279
|
const cmmdBase = resolveCommandAppUrl();
|
|
72117
72280
|
const url = new URL(path, cmmdBase);
|
|
72118
72281
|
return handleCmmdProxyResponse(await fetch(url.toString(), {
|
|
72119
72282
|
method: "PUT",
|
|
72120
72283
|
headers: {
|
|
72121
|
-
...authHeaders(jwt, orgId),
|
|
72284
|
+
...await authHeaders(jwt, orgId, "PUT", path),
|
|
72122
72285
|
"Content-Type": "application/json"
|
|
72123
72286
|
},
|
|
72124
72287
|
body: JSON.stringify(body)
|
|
72125
72288
|
}));
|
|
72126
72289
|
}
|
|
72290
|
+
/** Deletes a CMMD resource with optional query parameters and JSON body; 204 returns undefined. */
|
|
72127
72291
|
async function proxyDelete(jwt, path, searchParams, orgId, body) {
|
|
72128
72292
|
const cmmdBase = resolveCommandAppUrl();
|
|
72129
72293
|
const url = new URL(path, cmmdBase);
|
|
@@ -72131,7 +72295,7 @@ async function proxyDelete(jwt, path, searchParams, orgId, body) {
|
|
|
72131
72295
|
const response = await fetch(url.toString(), {
|
|
72132
72296
|
method: "DELETE",
|
|
72133
72297
|
headers: {
|
|
72134
|
-
...authHeaders(jwt, orgId),
|
|
72298
|
+
...await authHeaders(jwt, orgId, "DELETE", path),
|
|
72135
72299
|
...body === void 0 ? {} : { "Content-Type": "application/json" }
|
|
72136
72300
|
},
|
|
72137
72301
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
@@ -72139,13 +72303,14 @@ async function proxyDelete(jwt, path, searchParams, orgId, body) {
|
|
|
72139
72303
|
if (!response.ok && response.status !== 204) await requireCmmdProxySuccess(response);
|
|
72140
72304
|
return response.status === 204 ? void 0 : handleCmmdProxyResponse(response);
|
|
72141
72305
|
}
|
|
72306
|
+
/** Forwards multipart bytes to CMMD, preserving the caller's content type and boundary. */
|
|
72142
72307
|
async function proxyMultipart(jwt, path, body, contentType, orgId) {
|
|
72143
72308
|
const cmmdBase = resolveCommandAppUrl();
|
|
72144
72309
|
const url = new URL(path, cmmdBase);
|
|
72145
72310
|
return handleCmmdProxyResponse(await fetch(url.toString(), {
|
|
72146
72311
|
method: "POST",
|
|
72147
72312
|
headers: {
|
|
72148
|
-
...authHeaders(jwt, orgId),
|
|
72313
|
+
...await authHeaders(jwt, orgId, "POST", path),
|
|
72149
72314
|
"Content-Type": contentType
|
|
72150
72315
|
},
|
|
72151
72316
|
body
|
|
@@ -72469,20 +72634,25 @@ const legacyCmmdToolBridge = {
|
|
|
72469
72634
|
};
|
|
72470
72635
|
//#endregion
|
|
72471
72636
|
//#region src/command/workIntakeCache.ts
|
|
72472
|
-
const
|
|
72637
|
+
const cacheByKey = /* @__PURE__ */ new Map();
|
|
72638
|
+
function cacheKeyFor(sessionId, orgId) {
|
|
72639
|
+
return `${sessionId}\u0000${orgId ?? ""}`;
|
|
72640
|
+
}
|
|
72473
72641
|
function resolveCmmdWorkIntakeCacheIdentity(subject, cmmdUserId) {
|
|
72474
72642
|
return cmmdUserId ? `cmmd-user:${cmmdUserId}` : `forge-subject:${subject}`;
|
|
72475
72643
|
}
|
|
72476
72644
|
function fingerprintToken(token) {
|
|
72477
72645
|
return (0, node_crypto.createHash)("sha256").update(token).digest("hex");
|
|
72478
72646
|
}
|
|
72479
|
-
async function fetchBlockingSnapshot(input, identityKey, tokenFingerprint, existing) {
|
|
72647
|
+
async function fetchBlockingSnapshot(input, cacheKey, identityKey, tokenFingerprint, existing) {
|
|
72480
72648
|
if (existing?.inFlight) return existing.inFlight;
|
|
72481
72649
|
const nowMs = input.nowMs ?? Date.now;
|
|
72482
72650
|
let promise;
|
|
72483
72651
|
promise = input.fetchFresh().then((snapshot) => {
|
|
72484
|
-
const current =
|
|
72485
|
-
if (current?.tokenFingerprint === tokenFingerprint && current.inFlight === promise)
|
|
72652
|
+
const current = cacheByKey.get(cacheKey);
|
|
72653
|
+
if (current?.tokenFingerprint === tokenFingerprint && current.inFlight === promise) cacheByKey.set(cacheKey, {
|
|
72654
|
+
sessionId: input.sessionId,
|
|
72655
|
+
orgId: input.orgId,
|
|
72486
72656
|
identityKey,
|
|
72487
72657
|
tokenFingerprint,
|
|
72488
72658
|
snapshot,
|
|
@@ -72491,12 +72661,14 @@ async function fetchBlockingSnapshot(input, identityKey, tokenFingerprint, exist
|
|
|
72491
72661
|
});
|
|
72492
72662
|
return snapshot;
|
|
72493
72663
|
}).catch((error) => {
|
|
72494
|
-
const current =
|
|
72495
|
-
if (current?.tokenFingerprint === tokenFingerprint && current.inFlight === promise) if (legacyCmmdToolBridge.isAuthError(error))
|
|
72664
|
+
const current = cacheByKey.get(cacheKey);
|
|
72665
|
+
if (current?.tokenFingerprint === tokenFingerprint && current.inFlight === promise) if (legacyCmmdToolBridge.isAuthError(error)) cacheByKey.delete(cacheKey);
|
|
72496
72666
|
else current.inFlight = null;
|
|
72497
72667
|
throw error;
|
|
72498
72668
|
});
|
|
72499
|
-
|
|
72669
|
+
cacheByKey.set(cacheKey, {
|
|
72670
|
+
sessionId: input.sessionId,
|
|
72671
|
+
orgId: input.orgId,
|
|
72500
72672
|
identityKey,
|
|
72501
72673
|
tokenFingerprint,
|
|
72502
72674
|
snapshot: existing?.snapshot ?? null,
|
|
@@ -72512,9 +72684,9 @@ async function fetchBlockingSnapshot(input, identityKey, tokenFingerprint, exist
|
|
|
72512
72684
|
* opportunistically on every read.
|
|
72513
72685
|
*/
|
|
72514
72686
|
function sweepExpiredWorkIntakeEntries(nowMs) {
|
|
72515
|
-
for (const [
|
|
72687
|
+
for (const [key, entry] of cacheByKey) {
|
|
72516
72688
|
if (entry.inFlight) continue;
|
|
72517
|
-
if (nowMs - entry.cachedAtMs > 3e5)
|
|
72689
|
+
if (nowMs - entry.cachedAtMs > 3e5) cacheByKey.delete(key);
|
|
72518
72690
|
}
|
|
72519
72691
|
}
|
|
72520
72692
|
async function readCachedCmmdWorkIntakeForSession(input) {
|
|
@@ -72523,17 +72695,18 @@ async function readCachedCmmdWorkIntakeForSession(input) {
|
|
|
72523
72695
|
const identityKey = input.identityKey ?? input.sessionId;
|
|
72524
72696
|
if (input.forceFresh) invalidateCachedCmmdWorkIntakeForIdentity(identityKey);
|
|
72525
72697
|
const tokenFingerprint = fingerprintToken(input.token);
|
|
72526
|
-
const
|
|
72527
|
-
|
|
72528
|
-
|
|
72529
|
-
|
|
72698
|
+
const cacheKey = cacheKeyFor(input.sessionId, input.orgId);
|
|
72699
|
+
const existing = cacheByKey.get(cacheKey);
|
|
72700
|
+
if (existing && (existing.identityKey !== identityKey || existing.tokenFingerprint !== tokenFingerprint)) cacheByKey.delete(cacheKey);
|
|
72701
|
+
const current = cacheByKey.get(cacheKey);
|
|
72702
|
+
if (!current?.snapshot) return fetchBlockingSnapshot(input, cacheKey, identityKey, tokenFingerprint, current);
|
|
72530
72703
|
const ageMs = Math.max(0, nowMs() - current.cachedAtMs);
|
|
72531
72704
|
if (ageMs <= 12e4) return current.snapshot;
|
|
72532
72705
|
if (ageMs <= 3e5) {
|
|
72533
72706
|
if (!current.inFlight) {
|
|
72534
72707
|
let refreshPromise;
|
|
72535
72708
|
refreshPromise = input.fetchFresh().then((snapshot) => {
|
|
72536
|
-
const latest =
|
|
72709
|
+
const latest = cacheByKey.get(cacheKey);
|
|
72537
72710
|
if (latest?.tokenFingerprint === tokenFingerprint && latest.inFlight === refreshPromise) {
|
|
72538
72711
|
latest.snapshot = snapshot;
|
|
72539
72712
|
latest.cachedAtMs = nowMs();
|
|
@@ -72541,25 +72714,25 @@ async function readCachedCmmdWorkIntakeForSession(input) {
|
|
|
72541
72714
|
return snapshot;
|
|
72542
72715
|
}).catch((error) => {
|
|
72543
72716
|
if (legacyCmmdToolBridge.isAuthError(error)) {
|
|
72544
|
-
const latest =
|
|
72545
|
-
if (latest?.tokenFingerprint === tokenFingerprint && latest.inFlight === refreshPromise)
|
|
72717
|
+
const latest = cacheByKey.get(cacheKey);
|
|
72718
|
+
if (latest?.tokenFingerprint === tokenFingerprint && latest.inFlight === refreshPromise) cacheByKey.delete(cacheKey);
|
|
72546
72719
|
}
|
|
72547
72720
|
return current.snapshot;
|
|
72548
72721
|
}).finally(() => {
|
|
72549
|
-
const latest =
|
|
72722
|
+
const latest = cacheByKey.get(cacheKey);
|
|
72550
72723
|
if (latest?.tokenFingerprint === tokenFingerprint && latest.inFlight === refreshPromise) latest.inFlight = null;
|
|
72551
72724
|
});
|
|
72552
72725
|
current.inFlight = refreshPromise;
|
|
72553
72726
|
}
|
|
72554
72727
|
return current.snapshot;
|
|
72555
72728
|
}
|
|
72556
|
-
return fetchBlockingSnapshot(input, identityKey, tokenFingerprint, current);
|
|
72729
|
+
return fetchBlockingSnapshot(input, cacheKey, identityKey, tokenFingerprint, current);
|
|
72557
72730
|
}
|
|
72558
72731
|
function invalidateCachedCmmdWorkIntakeForSession(sessionId) {
|
|
72559
|
-
|
|
72732
|
+
for (const [key, entry] of cacheByKey) if (entry.sessionId === sessionId) cacheByKey.delete(key);
|
|
72560
72733
|
}
|
|
72561
72734
|
function invalidateCachedCmmdWorkIntakeForIdentity(identityKey) {
|
|
72562
|
-
for (const [
|
|
72735
|
+
for (const [key, entry] of cacheByKey) if (entry.identityKey === identityKey) cacheByKey.delete(key);
|
|
72563
72736
|
}
|
|
72564
72737
|
//#endregion
|
|
72565
72738
|
//#region src/cmmdData/proxyRouteRegistry.ts
|
|
@@ -105066,6 +105239,29 @@ function warn$1(label, cause) {
|
|
|
105066
105239
|
return `${label} fetch failed: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
105067
105240
|
}
|
|
105068
105241
|
/**
|
|
105242
|
+
* Reads the workspace catalog off a settled bootstrap call. No explicit org
|
|
105243
|
+
* means CMMD answered `activeWorkspace` with its own default-organization
|
|
105244
|
+
* guess, not a confirmed fact -- leave it null rather than publish it
|
|
105245
|
+
* (forge#3636).
|
|
105246
|
+
*/
|
|
105247
|
+
function resolveWorkspacesFromBootstrap(bootstrapResult, orgId, warnings) {
|
|
105248
|
+
if (bootstrapResult.status !== "fulfilled") {
|
|
105249
|
+
warnings.push(warn$1("Workspaces (bootstrap)", bootstrapResult.reason));
|
|
105250
|
+
return {
|
|
105251
|
+
workspaces: [],
|
|
105252
|
+
activeWorkspace: null
|
|
105253
|
+
};
|
|
105254
|
+
}
|
|
105255
|
+
const bootstrap = unwrapRestObject(bootstrapResult.value);
|
|
105256
|
+
const rawWorkspaces = bootstrap?.["workspaces"];
|
|
105257
|
+
const workspaces = Array.isArray(rawWorkspaces) ? rawWorkspaces.map(adaptBootstrapWorkspace) : [];
|
|
105258
|
+
const rawActive = orgId !== void 0 ? bootstrap?.["activeWorkspace"] : void 0;
|
|
105259
|
+
return {
|
|
105260
|
+
workspaces,
|
|
105261
|
+
activeWorkspace: rawActive && typeof rawActive === "object" ? adaptBootstrapWorkspace(rawActive) : null
|
|
105262
|
+
};
|
|
105263
|
+
}
|
|
105264
|
+
/**
|
|
105069
105265
|
* Build the CMMD work-intake snapshot via the JWT REST API:
|
|
105070
105266
|
* bootstrap (workspaces) + campaigns + projects + center-board (tasks WITH deps).
|
|
105071
105267
|
* `identity` is supplied by the session (resolved at login), matching the MCP builder.
|
|
@@ -105073,21 +105269,13 @@ function warn$1(label, cause) {
|
|
|
105073
105269
|
async function fetchCmmdWorkIntakeViaRest(jwt, identity, deps = {}) {
|
|
105074
105270
|
const get = deps.proxyGet ?? proxyGet;
|
|
105075
105271
|
const [bootstrapResult, campaignsResult, projectsResult, boardResult] = await Promise.allSettled([
|
|
105076
|
-
get(jwt, "/api/forge/context/bootstrap"),
|
|
105272
|
+
get(jwt, "/api/forge/context/bootstrap", void 0, deps.orgId),
|
|
105077
105273
|
get(jwt, "/api/forge/context/campaigns"),
|
|
105078
105274
|
fetchAllProjects(get, jwt),
|
|
105079
105275
|
get(jwt, "/api/forge/context/work-items/board")
|
|
105080
105276
|
]);
|
|
105081
105277
|
const warnings = [];
|
|
105082
|
-
|
|
105083
|
-
let activeWorkspace = null;
|
|
105084
|
-
if (bootstrapResult.status === "fulfilled") {
|
|
105085
|
-
const bootstrap = unwrapRestObject(bootstrapResult.value);
|
|
105086
|
-
const rawWorkspaces = bootstrap?.["workspaces"];
|
|
105087
|
-
if (Array.isArray(rawWorkspaces)) workspaces = rawWorkspaces.map(adaptBootstrapWorkspace);
|
|
105088
|
-
const rawActive = bootstrap?.["activeWorkspace"];
|
|
105089
|
-
if (rawActive && typeof rawActive === "object") activeWorkspace = adaptBootstrapWorkspace(rawActive);
|
|
105090
|
-
} else warnings.push(warn$1("Workspaces (bootstrap)", bootstrapResult.reason));
|
|
105278
|
+
const { workspaces, activeWorkspace } = resolveWorkspacesFromBootstrap(bootstrapResult, deps.orgId, warnings);
|
|
105091
105279
|
if (projectsResult.status === "rejected") throw projectsResult.reason;
|
|
105092
105280
|
if (campaignsResult.status === "rejected") warnings.push(warn$1("Campaigns", campaignsResult.reason));
|
|
105093
105281
|
if (boardResult.status === "rejected") warnings.push(warn$1("Tasks (board)", boardResult.reason));
|
|
@@ -300407,7 +300595,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
300407
300595
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
300408
300596
|
*/
|
|
300409
300597
|
function readBakedBuildCommit() {
|
|
300410
|
-
return "
|
|
300598
|
+
return "8a4057a4a58eeb014b73143ffbb1bb091089d750";
|
|
300411
300599
|
}
|
|
300412
300600
|
async function resolveServerBuildCommit(input) {
|
|
300413
300601
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -310827,6 +311015,24 @@ function logWorkIntakeFailure(cause) {
|
|
|
310827
311015
|
}
|
|
310828
311016
|
console.error("[work-intake] fetch failed:", cause);
|
|
310829
311017
|
}
|
|
311018
|
+
/**
|
|
311019
|
+
* `?orgId=` present but empty (a stripped or malformed query string) is not
|
|
311020
|
+
* an explicit org. Treat it the same as an absent param rather than sending
|
|
311021
|
+
* CMMD an empty `X-Organization-ID` header (forge#3636).
|
|
311022
|
+
*/
|
|
311023
|
+
function normalizeOrgIdQueryParam(raw) {
|
|
311024
|
+
const trimmed = raw?.trim();
|
|
311025
|
+
return trimmed ? trimmed : void 0;
|
|
311026
|
+
}
|
|
311027
|
+
/**
|
|
311028
|
+
* `{ orgId }` when present, `{}` when not -- the `exactOptionalPropertyTypes`
|
|
311029
|
+
* shape every `orgId?: string` deps object here needs, since spreading
|
|
311030
|
+
* `{ orgId: possiblyUndefined }` inline assigns `undefined` to a property
|
|
311031
|
+
* the type says is only ever absent or a string.
|
|
311032
|
+
*/
|
|
311033
|
+
function orgIdDeps(orgId) {
|
|
311034
|
+
return orgId !== void 0 ? { orgId } : {};
|
|
311035
|
+
}
|
|
310830
311036
|
function fetchRestFallbackWorkIntakeForSession(session, dependencies = {}) {
|
|
310831
311037
|
return require_Schema$1.gen(function* () {
|
|
310832
311038
|
const jwt = yield* (dependencies.resolveJwt ?? resolveCmmdJwtForSession)({
|
|
@@ -310838,7 +311044,7 @@ function fetchRestFallbackWorkIntakeForSession(session, dependencies = {}) {
|
|
|
310838
311044
|
const identity = (dependencies.readIdentity ?? readCmmdUserIdentityForSession)(session.sessionId);
|
|
310839
311045
|
const fetchFallback = dependencies.fetchFallback ?? fetchCmmdWorkIntakeViaRest;
|
|
310840
311046
|
return yield* require_Schema$1.tryPromise({
|
|
310841
|
-
try: () => fetchFallback(jwt, identity),
|
|
311047
|
+
try: () => fetchFallback(jwt, identity, orgIdDeps(dependencies.orgId)),
|
|
310842
311048
|
catch: (cause) => new WorkIntakeSnapshotReadError({
|
|
310843
311049
|
detail: "Failed to build CMMD REST work-intake fallback.",
|
|
310844
311050
|
cause
|
|
@@ -310950,7 +311156,9 @@ function unwrapForgeEnvelopeJsonText(text) {
|
|
|
310950
311156
|
const cmmdWorkIntakeRouteLayer = add$1("GET", "/api/cmmd/work-intake", require_Schema$1.gen(function* () {
|
|
310951
311157
|
const request = yield* require_HttpServer.HttpServerRequest;
|
|
310952
311158
|
const session = yield* (yield* ServerAuth).authenticateHttpRequest(request);
|
|
310953
|
-
const
|
|
311159
|
+
const requestUrl = new URL(request.url, "http://localhost");
|
|
311160
|
+
const forceFresh = requestUrl.searchParams.get("fresh") === "1";
|
|
311161
|
+
const orgId = normalizeOrgIdQueryParam(requestUrl.searchParams.get("orgId"));
|
|
310954
311162
|
let accessToken = readCommandAccessTokenForSession(session.sessionId);
|
|
310955
311163
|
if (!accessToken && session.role === "owner" && (session.subject === "owner-bootstrap" || session.subject === "desktop-bootstrap")) accessToken = (yield* promoteMostRecentOwnerCommandAuthToSession(session.sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to promote owner CMMD auth for work intake.", {
|
|
310956
311164
|
cause: error,
|
|
@@ -310995,8 +311203,9 @@ const cmmdWorkIntakeRouteLayer = add$1("GET", "/api/cmmd/work-intake", require_S
|
|
|
310995
311203
|
sessionId: session.sessionId,
|
|
310996
311204
|
identityKey: workIntakeCacheIdentity,
|
|
310997
311205
|
token: accessToken,
|
|
311206
|
+
...orgIdDeps(orgId),
|
|
310998
311207
|
forceFresh,
|
|
310999
|
-
fetchFresh: () => cmmdJwt ? fetchCmmdWorkIntakeViaRest(cmmdJwt, sessionIdentity) : Promise.reject(/* @__PURE__ */ new Error("CMMD JWT unavailable for work-intake fetch."))
|
|
311208
|
+
fetchFresh: () => cmmdJwt ? fetchCmmdWorkIntakeViaRest(cmmdJwt, sessionIdentity, orgIdDeps(orgId)) : Promise.reject(/* @__PURE__ */ new Error("CMMD JWT unavailable for work-intake fetch."))
|
|
311000
311209
|
}),
|
|
311001
311210
|
catch: (cause) => new WorkIntakeSnapshotReadError({
|
|
311002
311211
|
detail: "Failed to read CMMD work intake snapshot.",
|
|
@@ -311014,7 +311223,7 @@ const cmmdWorkIntakeRouteLayer = add$1("GET", "/api/cmmd/work-intake", require_S
|
|
|
311014
311223
|
if (isCmmdMcpAuthError(cause)) {
|
|
311015
311224
|
console.error("[WORK_INTAKE] CMMD_AUTH_EXPIRED mcp_error:", cause instanceof Error ? cause.message : String(cause));
|
|
311016
311225
|
invalidateCachedCmmdWorkIntakeForSession(session.sessionId);
|
|
311017
|
-
const fallback = yield* fetchRestFallbackWorkIntakeForSession(session);
|
|
311226
|
+
const fallback = yield* fetchRestFallbackWorkIntakeForSession(session, orgIdDeps(orgId));
|
|
311018
311227
|
yield* clearCommandAccessTokenForSession(session.sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear expired CMMD token after MCP auth failure.", {
|
|
311019
311228
|
cause: error,
|
|
311020
311229
|
sessionId: session.sessionId
|
|
@@ -311039,7 +311248,7 @@ const cmmdWorkIntakeRouteLayer = add$1("GET", "/api/cmmd/work-intake", require_S
|
|
|
311039
311248
|
connected: snapshot.connected
|
|
311040
311249
|
});
|
|
311041
311250
|
invalidateCachedCmmdWorkIntakeForSession(session.sessionId);
|
|
311042
|
-
const fallback = yield* fetchRestFallbackWorkIntakeForSession(session);
|
|
311251
|
+
const fallback = yield* fetchRestFallbackWorkIntakeForSession(session, orgIdDeps(orgId));
|
|
311043
311252
|
yield* clearCommandAccessTokenForSession(session.sessionId).pipe(require_Schema$1.catch_((error) => require_Schema$1.logWarning("Failed to clear expired CMMD token after auth warning.", {
|
|
311044
311253
|
cause: error,
|
|
311045
311254
|
sessionId: session.sessionId
|
|
@@ -312395,12 +312604,65 @@ registerProxyRoute({
|
|
|
312395
312604
|
responseShape: "unwrapData",
|
|
312396
312605
|
requestBody: "none"
|
|
312397
312606
|
});
|
|
312398
|
-
|
|
312607
|
+
function parseCmmdBootstrapRequest(request) {
|
|
312608
|
+
return require_Schema$1.sync(() => {
|
|
312609
|
+
return {
|
|
312610
|
+
ok: true,
|
|
312611
|
+
value: normalizeOrgIdQueryParam(new URL(request.url, "http://localhost").searchParams.get("orgId"))
|
|
312612
|
+
};
|
|
312613
|
+
});
|
|
312614
|
+
}
|
|
312615
|
+
/**
|
|
312616
|
+
* Nulls `activeWorkspace` on a bootstrap body, wherever it sits. CMMD answers
|
|
312617
|
+
* `GET /api/forge/context/bootstrap` either flat (`{ activeWorkspace, ... }`)
|
|
312618
|
+
* or enveloped (`{ data: { activeWorkspace, ... } }`) -- `workIntakeRest.ts`'s
|
|
312619
|
+
* `unwrapRestObject` already treats both as live shapes for this exact
|
|
312620
|
+
* endpoint. Nulling only the top level left `data.activeWorkspace` (CMMD's
|
|
312621
|
+
* guess) untouched on the enveloped shape.
|
|
312622
|
+
*/
|
|
312623
|
+
function nullActiveWorkspace(body) {
|
|
312624
|
+
const data = body["data"];
|
|
312625
|
+
const nulledData = typeof data === "object" && data !== null && !Array.isArray(data) ? {
|
|
312626
|
+
...data,
|
|
312627
|
+
activeWorkspace: null
|
|
312628
|
+
} : data;
|
|
312629
|
+
return {
|
|
312630
|
+
...body,
|
|
312631
|
+
activeWorkspace: null,
|
|
312632
|
+
...data !== void 0 ? { data: nulledData } : {}
|
|
312633
|
+
};
|
|
312634
|
+
}
|
|
312635
|
+
/**
|
|
312636
|
+
* CMMD resolves `activeWorkspace` to the caller's own default organization
|
|
312637
|
+
* whenever the request carries no `X-Organization-ID` header (forge#3636:
|
|
312638
|
+
* the owner working in org 2119 kept getting org 2142, CMMD's default, back).
|
|
312639
|
+
* Passing the client's explicit `?orgId=` scopes CMMD's answer to that
|
|
312640
|
+
* workspace; without one, CMMD still guesses, so `activeWorkspace` is nulled
|
|
312641
|
+
* here rather than forwarded as fact. Everything else in the bootstrap
|
|
312642
|
+
* response passes through untouched.
|
|
312643
|
+
*/
|
|
312644
|
+
function runCmmdBootstrap(orgId, { session }) {
|
|
312645
|
+
return runCmmdRestProxyAsHandlerResult(session, (jwt) => cmmdGateway.request({
|
|
312646
|
+
method: "GET",
|
|
312647
|
+
jwt,
|
|
312648
|
+
path: "/api/forge/context/bootstrap",
|
|
312649
|
+
...orgId !== void 0 ? { orgId } : {}
|
|
312650
|
+
}), (value) => {
|
|
312651
|
+
if (orgId !== void 0 || value === null || typeof value !== "object") return {
|
|
312652
|
+
status: 200,
|
|
312653
|
+
body: value
|
|
312654
|
+
};
|
|
312655
|
+
return {
|
|
312656
|
+
status: 200,
|
|
312657
|
+
body: nullActiveWorkspace(value)
|
|
312658
|
+
};
|
|
312659
|
+
});
|
|
312660
|
+
}
|
|
312661
|
+
registerProxyHandler({
|
|
312399
312662
|
forgePath: "/api/cmmd/bootstrap",
|
|
312400
312663
|
method: "GET",
|
|
312401
|
-
|
|
312402
|
-
|
|
312403
|
-
responseShape: "passthrough"
|
|
312664
|
+
parse: parseCmmdBootstrapRequest,
|
|
312665
|
+
run: runCmmdBootstrap
|
|
312404
312666
|
});
|
|
312405
312667
|
registerProxyRoute({
|
|
312406
312668
|
forgePath: "/api/cmmd/account/preferences",
|
|
@@ -313335,6 +313597,44 @@ const cmmdUpdateProjectAgentMemberAvatarRouteLayer = registerProxyRoute({
|
|
|
313335
313597
|
responseShape: "unwrapData"
|
|
313336
313598
|
});
|
|
313337
313599
|
//#endregion
|
|
313600
|
+
//#region src/command/cmmdPushProxyRoutes.ts
|
|
313601
|
+
/**
|
|
313602
|
+
* The signed-in phone's push registration, forwarded to CMMD's push
|
|
313603
|
+
* device-token API on the owner's stored CMMD JWT (forge#3534).
|
|
313604
|
+
*
|
|
313605
|
+
* CMMD owns push delivery: the `device_push_tokens` table, the Expo transport,
|
|
313606
|
+
* and the `mobile_push_v1` delivery flag are all CMMD-side, and its
|
|
313607
|
+
* registration API lives at `/api/push`, not under `/api/forge/context`. The
|
|
313608
|
+
* mobile push delivery spec (docs/specs/2026-09-09-mobile-push-delivery.md,
|
|
313609
|
+
* stage 2) recommends reusing that API over adding a forge-context doorway
|
|
313610
|
+
* first: the Forge server already holds the phone's CMMD credential, and the
|
|
313611
|
+
* registry prologue here provides the same session auth, CMMD-connection
|
|
313612
|
+
* gate, and `CMMD_AUTH_REQUIRED` mapping every `/api/cmmd` route gets. The
|
|
313613
|
+
* deny-listed legacy owner control paths are untouched. If review wants a
|
|
313614
|
+
* forge-context doorway first, these two routes are the only call sites to
|
|
313615
|
+
* repoint.
|
|
313616
|
+
*
|
|
313617
|
+
* `orgIdSource` is `none` (the organization comes from the JWT, like the
|
|
313618
|
+
* settings and device-registry routes): a phone registers against its default
|
|
313619
|
+
* organization and has no workspace context to pass.
|
|
313620
|
+
*/
|
|
313621
|
+
const cmmdRegisterMobilePushDeviceRouteLayer = registerProxyRoute({
|
|
313622
|
+
forgePath: "/api/cmmd/mobile/push/registration",
|
|
313623
|
+
method: "POST",
|
|
313624
|
+
cmmdPath: "/api/push/device-tokens",
|
|
313625
|
+
orgIdSource: "none",
|
|
313626
|
+
responseShape: "unwrapData",
|
|
313627
|
+
successStatus: 201
|
|
313628
|
+
});
|
|
313629
|
+
const cmmdDeleteMobilePushDeviceRouteLayer = registerProxyRoute({
|
|
313630
|
+
forgePath: "/api/cmmd/mobile/push/registration",
|
|
313631
|
+
method: "DELETE",
|
|
313632
|
+
cmmdPath: "/api/push/device-tokens",
|
|
313633
|
+
orgIdSource: "none",
|
|
313634
|
+
responseShape: "unwrapData",
|
|
313635
|
+
requestBody: "json"
|
|
313636
|
+
});
|
|
313637
|
+
//#endregion
|
|
313338
313638
|
//#region src/command/runtimeProviderAuthorityUpgradeModel.ts
|
|
313339
313639
|
const AUTHORITY_UPGRADE_OPERATION_KEYS = new Set([
|
|
313340
313640
|
"operationId",
|
|
@@ -324954,7 +325254,7 @@ const MiscRuntimeLayerLive = require_Schema$1.mergeAll(AnalyticsServiceLayerLive
|
|
|
324954
325254
|
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));
|
|
324955
325255
|
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));
|
|
324956
325256
|
const RuntimeServicesLive = ServerRuntimeStartupLive.pipe(require_Schema$1.provideMerge(RuntimeDependenciesLive));
|
|
324957
|
-
const makeRoutesLayer = require_Schema$1.mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authCmmdEmbedExchangeRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, cloudOwnerSessionRedeemRouteLayer, cloudOwnerSessionRevokeRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeEnvironmentsEventsRouteLayer, 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));
|
|
325257
|
+
const makeRoutesLayer = require_Schema$1.mergeAll(attachmentUploadRouteLayer, authBearerBootstrapRouteLayer, authBootstrapRouteLayer, authCommandBearerBootstrapRouteLayer, authCommandBootstrapRouteLayer, authCmmdEmbedExchangeRouteLayer, authOAuthTokenExchangeRouteLayer, authWebSocketTicketRouteLayer, authCmmdOAuthDeviceRouteLayer, authCmmdOAuthTokenRouteLayer, authCmmdPasswordTokenRouteLayer, authClientsRevokeOthersRouteLayer, authClientsRevokeRouteLayer, authClientsRouteLayer, authLogoutRouteLayer, authPairingLinksRevokeRouteLayer, authPairingLinksRouteLayer, authPairingCredentialRouteLayer, authSessionRenewRouteLayer, authSessionRouteLayer, authWebSocketTokenRouteLayer, authWebLoginRouteLayer, authWebCallbackRouteLayer, cloudOwnerSessionRedeemRouteLayer, cloudOwnerSessionRevokeRouteLayer, desktopLocalWebBridgePublishRouteLayer, desktopLocalWebBridgeBootstrapRouteLayer, desktopRelayControlPlaneRouteLayer, apiDocsHtmlRouteLayer, apiDocsOpenApiRouteLayer, pulseSignalStateListRouteLayer, pulseSignalStateUpsertRouteLayer, cmmdProfileRouteLayer, ...cmmdProxyRouteLayers, cmmdWorkIntakeRouteLayer, cmmdWsTokenRouteLayer, cmmdGetProjectCommentsRouteLayer, cmmdAddProjectCommentRouteLayer, cmmdUpdateProjectRouteLayer, cmmdUploadTaskAttachmentRouteLayer, cmmdUpdateTaskCommentRouteLayer, cmmdListWorkspaceMembersRouteLayer, cmmdAddProjectMemberRouteLayer, cmmdUpdateProjectAgentMemberAvatarRouteLayer, runtimeReplicationStatusRouteLayer, cmmdRefreshProviderUsageRouteLayer, cmmdForgeCloudProjectsListRouteLayer, cmmdForgeCloudProjectsEventsRouteLayer, cmmdForgeEnvironmentsEventsRouteLayer, cmmdForgeRuntimesEnsureRouteLayer, cmmdForgeRuntimesConnectRouteLayer, ownerRuntimeCodexReadinessRouteLayer, cmmdRegisterMobilePushDeviceRouteLayer, cmmdDeleteMobilePushDeviceRouteLayer, 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));
|
|
324958
325258
|
const makeServerLayer = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
|
|
324959
325259
|
const config = yield* ServerConfig;
|
|
324960
325260
|
fixPath({ preferProcessPath: config.forgeCmmdRuntimeMode === true });
|