@cmmd-center/forge 0.9.14 → 0.9.16
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
CHANGED
|
@@ -65123,7 +65123,7 @@ function normalizeNumberish(value) {
|
|
|
65123
65123
|
}
|
|
65124
65124
|
//#endregion
|
|
65125
65125
|
//#region package.json
|
|
65126
|
-
var version$1 = "0.9.
|
|
65126
|
+
var version$1 = "0.9.16";
|
|
65127
65127
|
//#endregion
|
|
65128
65128
|
//#region src/sentry.ts
|
|
65129
65129
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -66945,13 +66945,13 @@ function validateCommandBootstrapPayload(payload) {
|
|
|
66945
66945
|
});
|
|
66946
66946
|
const payloadIdentity = normalizeCmmdUserIdentity(payload.user);
|
|
66947
66947
|
if (payloadIdentity) {
|
|
66948
|
-
const cmmdIdentity = payloadIdentity.avatarUrl ? payloadIdentity : mergeCmmdUserIdentity(payloadIdentity, yield* fetchOptionalCmmdIdentityForAccessToken(payload
|
|
66948
|
+
const cmmdIdentity = payloadIdentity.avatarUrl ? payloadIdentity : mergeCmmdUserIdentity(payloadIdentity, yield* fetchOptionalCmmdIdentityForAccessToken(cmmdUserCredential(payload)));
|
|
66949
66949
|
return {
|
|
66950
66950
|
subjectUserId: cmmdIdentity.id,
|
|
66951
66951
|
cmmdIdentity
|
|
66952
66952
|
};
|
|
66953
66953
|
}
|
|
66954
|
-
const tokenIdentity = yield* fetchCmmdIdentityForAccessToken(payload
|
|
66954
|
+
const tokenIdentity = yield* fetchCmmdIdentityForAccessToken(cmmdUserCredential(payload));
|
|
66955
66955
|
if (tokenIdentity) return {
|
|
66956
66956
|
subjectUserId: tokenIdentity.id,
|
|
66957
66957
|
cmmdIdentity: tokenIdentity
|
|
@@ -67044,6 +67044,23 @@ function isCmmdJwtToken(token) {
|
|
|
67044
67044
|
return token?.startsWith("eyJ") ?? false;
|
|
67045
67045
|
}
|
|
67046
67046
|
/**
|
|
67047
|
+
* The credential CMMD's userinfo accepts.
|
|
67048
|
+
*
|
|
67049
|
+
* Since CMMD's OAuth update, `access_token` is a JWT and the personal access
|
|
67050
|
+
* token arrives separately as `mcp_token`. userinfo authenticates the PAT and
|
|
67051
|
+
* rejects the JWT with `INVALID_OR_NON_USER_TOKEN`, so presenting the JWT
|
|
67052
|
+
* resolves no identity: the bootstrap then finds no subject and answers 401,
|
|
67053
|
+
* even though the sign-in itself succeeded and the caller did nothing wrong.
|
|
67054
|
+
*
|
|
67055
|
+
* `resolveCmmdTokenBundleCredentials` already draws this distinction for the
|
|
67056
|
+
* owner lanes. This is the same rule for the bootstrap lane, which had only ever
|
|
67057
|
+
* seen the pre-update shape where `access_token` was itself the PAT.
|
|
67058
|
+
*/
|
|
67059
|
+
function cmmdUserCredential(payload) {
|
|
67060
|
+
const pat = payload.mcpToken?.trim();
|
|
67061
|
+
return isCmmdJwtToken(payload.accessToken) && pat ? pat : payload.accessToken;
|
|
67062
|
+
}
|
|
67063
|
+
/**
|
|
67047
67064
|
* Read one CMMD token-exchange response into the credential set Forge stores.
|
|
67048
67065
|
*
|
|
67049
67066
|
* Both owner lanes redeem the same `cmmd-forge-web` authorization code — the
|
|
@@ -92262,8 +92279,8 @@ const ProjectionThread = require_Schema$1.Struct({
|
|
|
92262
92279
|
createdAt: IsoDateTime,
|
|
92263
92280
|
updatedAt: IsoDateTime,
|
|
92264
92281
|
archivedAt: require_Schema$1.NullOr(IsoDateTime),
|
|
92265
|
-
settledAt: require_Schema$1.
|
|
92266
|
-
settledOverride: require_Schema$1.
|
|
92282
|
+
settledAt: require_Schema$1.NullOr(IsoDateTime),
|
|
92283
|
+
settledOverride: require_Schema$1.NullOr(require_Schema$1.Literals(["settled", "active"])),
|
|
92267
92284
|
latestUserMessageAt: require_Schema$1.NullOr(IsoDateTime),
|
|
92268
92285
|
pendingApprovalCount: NonNegativeInt,
|
|
92269
92286
|
pendingUserInputCount: NonNegativeInt,
|
|
@@ -92449,16 +92466,23 @@ const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
|
|
|
92449
92466
|
/**
|
|
92450
92467
|
* Resolve the commit this forge checkout actually booted from, so the
|
|
92451
92468
|
* heartbeat can report it and CMMD can tell a stale Sprite from a current
|
|
92452
|
-
* one.
|
|
92453
|
-
*
|
|
92454
|
-
*
|
|
92455
|
-
*
|
|
92456
|
-
*
|
|
92457
|
-
*
|
|
92458
|
-
*
|
|
92459
|
-
*
|
|
92460
|
-
*
|
|
92461
|
-
*
|
|
92469
|
+
* one. The commit cannot change for the life of the process, so resolve it
|
|
92470
|
+
* once and cache it.
|
|
92471
|
+
*
|
|
92472
|
+
* This comment used to say Sprites clone forge at bootstrap and read their
|
|
92473
|
+
* commit from that checkout. They do not. A cloud runtime installs the
|
|
92474
|
+
* published `@cmmd-center/forge` tarball and has no `.git` anywhere near it,
|
|
92475
|
+
* which is why its descriptor reported no commit at all until the server
|
|
92476
|
+
* started baking one into its bundle. See `../environment/buildIdentity.ts`,
|
|
92477
|
+
* which calls this only as its last rung.
|
|
92478
|
+
*
|
|
92479
|
+
* `git rev-parse HEAD` run with the process's own cwd walks up to find `.git`
|
|
92480
|
+
* on its own, so no `-C` path is needed. That also means a cwd inside some
|
|
92481
|
+
* unrelated repository answers with that repository's HEAD, which is one more
|
|
92482
|
+
* reason this is the last rung rather than the first. Returns `undefined`
|
|
92483
|
+
* (never throws) when there is no checkout to read, so the heartbeat body
|
|
92484
|
+
* omits the field exactly like an older Forge build that does not know about
|
|
92485
|
+
* it yet.
|
|
92462
92486
|
*/
|
|
92463
92487
|
let cached$1 = null;
|
|
92464
92488
|
async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$6(command, [...args], { timeout: 5e3 })) {
|
|
@@ -92830,7 +92854,7 @@ function hasValidRuntimeIdentity(value) {
|
|
|
92830
92854
|
}
|
|
92831
92855
|
function decodeRuntime$1(value) {
|
|
92832
92856
|
if (!isRecord$16(value) || !hasValidRuntimeIdentity(value)) return null;
|
|
92833
|
-
if (!nullableString(value.endpointUrl) || value.endpointUrl !== null && !URL.canParse(value.endpointUrl) || !nullableInteger(value.deviceId) || !
|
|
92857
|
+
if (!nullableString(value.endpointUrl) || value.endpointUrl !== null && !URL.canParse(value.endpointUrl) || !nullableInteger(value.deviceId) || !nullableString(value.workspaceRoot) || !nullableString(value.githubRepoFullName) || !(value.lastSeenAt === null || isIsoDateTime(value.lastSeenAt)) || !(value.metadata === null || isRecord$16(value.metadata))) return null;
|
|
92834
92858
|
return value;
|
|
92835
92859
|
}
|
|
92836
92860
|
function decodeProject(value) {
|
|
@@ -93141,8 +93165,6 @@ function replicaRuntimeMatches(left, right) {
|
|
|
93141
93165
|
[left.status, right.status],
|
|
93142
93166
|
[left.endpointUrl, right.endpointUrl],
|
|
93143
93167
|
[left.deviceId, right.deviceId],
|
|
93144
|
-
[left.projectId, right.projectId],
|
|
93145
|
-
[left.projectName, right.projectName],
|
|
93146
93168
|
[left.workspaceRoot, right.workspaceRoot],
|
|
93147
93169
|
[left.githubRepoFullName, right.githubRepoFullName],
|
|
93148
93170
|
[left.lastSeenAt, right.lastSeenAt]
|
|
@@ -206595,7 +206617,7 @@ function createDevServerMcpServer(deps, threadId) {
|
|
|
206595
206617
|
* per provider — this module only adapts descriptor + result shapes.
|
|
206596
206618
|
*
|
|
206597
206619
|
* Without it a Claude agent has no brokered push path at all and falls back to
|
|
206598
|
-
* raw `git`, which
|
|
206620
|
+
* raw `git`, which has no authenticated remote credentials in the agent runtime.
|
|
206599
206621
|
*
|
|
206600
206622
|
* The catalog is filtered through `applyPluginSkillPermissionsToDynamicTools`,
|
|
206601
206623
|
* exactly as the Codex surface filters its dynamic tools. `settings` is required
|
|
@@ -287808,13 +287830,32 @@ const ObservabilityLive = require_Schema$1.unwrap(require_Schema$1.gen(function*
|
|
|
287808
287830
|
//#region ../../packages/shared/src/buildCommit.ts
|
|
287809
287831
|
const COMMIT_SHA_PATTERN$1 = /^[0-9a-f]{40}$/i;
|
|
287810
287832
|
/**
|
|
287833
|
+
* Whether a value is a full commit sha.
|
|
287834
|
+
*
|
|
287835
|
+
* Exported so each surface can keep its own list of env names while sharing one
|
|
287836
|
+
* definition of "valid". Surfaces genuinely differ: Railway injects
|
|
287837
|
+
* RAILWAY_GIT_COMMIT_SHA into the cloud shell build, while the desktop release
|
|
287838
|
+
* workflows pass VITE_SENTRY_COMMIT_SHA. What must not differ is the check.
|
|
287839
|
+
*/
|
|
287840
|
+
function isFullCommitSha(value) {
|
|
287841
|
+
const candidate = value?.trim();
|
|
287842
|
+
return candidate !== void 0 && candidate !== "" && COMMIT_SHA_PATTERN$1.test(candidate);
|
|
287843
|
+
}
|
|
287844
|
+
/**
|
|
287811
287845
|
* Build identity from the environment.
|
|
287812
287846
|
*
|
|
287813
287847
|
* `.dockerignore` excludes `.git`, so `git rev-parse HEAD` cannot resolve inside
|
|
287814
287848
|
* the Railway container — the surface where "which commit is live?" is asked
|
|
287815
287849
|
* most. The commit therefore has to arrive as env there, with git left as the
|
|
287816
|
-
* fallback for surfaces that genuinely are checkouts (
|
|
287817
|
-
*
|
|
287850
|
+
* fallback for surfaces that genuinely are checkouts (desktop and dev run from
|
|
287851
|
+
* a repo).
|
|
287852
|
+
*
|
|
287853
|
+
* A cloud runtime is neither: it installs the published `@cmmd-center/forge`
|
|
287854
|
+
* tarball, so it has no build variables and no `.git`, and both rungs below
|
|
287855
|
+
* return nothing there. That is why the server bakes the commit into its bundle
|
|
287856
|
+
* at build time and reads that first — see `apps/server/src/environment/
|
|
287857
|
+
* buildIdentity.ts`. This comment previously claimed a sprite clones forge at
|
|
287858
|
+
* bootstrap; it does not, and believing it cost a diagnosis.
|
|
287818
287859
|
*
|
|
287819
287860
|
* `RAILWAY_GIT_COMMIT_SHA` is injected by the platform, which is what lets this
|
|
287820
287861
|
* work on the cloud shell with no Dockerfile plumbing. `FORGE_BUILD_COMMIT`
|
|
@@ -287832,6 +287873,30 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
287832
287873
|
}
|
|
287833
287874
|
}
|
|
287834
287875
|
//#endregion
|
|
287876
|
+
//#region src/environment/buildIdentity.ts
|
|
287877
|
+
/**
|
|
287878
|
+
* Which commit this server was built from.
|
|
287879
|
+
*
|
|
287880
|
+
* @module
|
|
287881
|
+
*/
|
|
287882
|
+
/**
|
|
287883
|
+
* The commit baked into this bundle, if it is a bundle at all.
|
|
287884
|
+
*
|
|
287885
|
+
* `typeof` rather than a bare reference: running from source there is no such
|
|
287886
|
+
* binding, and a bare reference is a ReferenceError that would take the whole
|
|
287887
|
+
* environment descriptor down instead of reporting an honest "unknown".
|
|
287888
|
+
*/
|
|
287889
|
+
function readBakedBuildCommit() {
|
|
287890
|
+
return "e65edd4d1b8a361863a52b171b5e73295d691e11";
|
|
287891
|
+
}
|
|
287892
|
+
async function resolveServerBuildCommit(input) {
|
|
287893
|
+
if (isFullCommitSha(input.baked)) return input.baked;
|
|
287894
|
+
const fromEnv = resolveBuildCommitFromEnv(input.env);
|
|
287895
|
+
if (fromEnv !== void 0) return fromEnv;
|
|
287896
|
+
const fromGit = await input.readGitCommit();
|
|
287897
|
+
return isFullCommitSha(fromGit) ? fromGit : void 0;
|
|
287898
|
+
}
|
|
287899
|
+
//#endregion
|
|
287835
287900
|
//#region src/environment/Layers/ServerEnvironmentLabel.ts
|
|
287836
287901
|
function normalizeLabel(value) {
|
|
287837
287902
|
const trimmed = value?.trim();
|
|
@@ -287980,7 +288045,11 @@ const makeServerEnvironment = require_Schema$1.fn("makeServerEnvironment")(funct
|
|
|
287980
288045
|
const initialMetadata = yield* readMetadata;
|
|
287981
288046
|
const displayNameOverrideRef = yield* require_HttpServer.make$3(normalizeDisplayName(initialMetadata.displayName ?? null));
|
|
287982
288047
|
const posthog = yield* PosthogPublicConfig.asEffect();
|
|
287983
|
-
const buildCommit =
|
|
288048
|
+
const buildCommit = yield* require_Schema$1.promise(() => resolveServerBuildCommit({
|
|
288049
|
+
baked: readBakedBuildCommit(),
|
|
288050
|
+
env: process.env,
|
|
288051
|
+
readGitCommit: resolveForgeGitCommit
|
|
288052
|
+
}));
|
|
287984
288053
|
const buildDescriptor = require_Schema$1.gen(function* () {
|
|
287985
288054
|
const displayNameOverride = yield* require_HttpServer.get$1(displayNameOverrideRef);
|
|
287986
288055
|
const os = platformOs();
|
|
@@ -296795,6 +296864,71 @@ function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
|
|
|
296795
296864
|
};
|
|
296796
296865
|
}
|
|
296797
296866
|
//#endregion
|
|
296867
|
+
//#region src/command/claudeUsageRefreshFailure.ts
|
|
296868
|
+
/**
|
|
296869
|
+
* Why a Claude usage refresh failed, in words the Usage page can show.
|
|
296870
|
+
*
|
|
296871
|
+
* CMMD answers a failed refresh with a machine reason (`runtime_not_active`,
|
|
296872
|
+
* `credential_changed`, …). The Shell used to collapse every one of them into
|
|
296873
|
+
* "Claude usage refresh is unavailable.", so the card could only say the
|
|
296874
|
+
* refresh failed, never what to do about it.
|
|
296875
|
+
*
|
|
296876
|
+
* The mapping is a closed allowlist in both directions: a reason CMMD may add
|
|
296877
|
+
* later falls back to the generic code, and the text a client sees is written
|
|
296878
|
+
* here, never forwarded from upstream. That keeps a credential or runtime
|
|
296879
|
+
* detail in a CMMD error body from reaching the browser.
|
|
296880
|
+
*/
|
|
296881
|
+
const FAILURE_TEXT = {
|
|
296882
|
+
CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE: "Refresh needs your Environment running.",
|
|
296883
|
+
CLAUDE_USAGE_CREDENTIAL_NOT_READY: "The Claude credential is not applied to your Environment yet.",
|
|
296884
|
+
CLAUDE_USAGE_REFRESH_IN_PROGRESS: "A refresh for this account is already running.",
|
|
296885
|
+
CLAUDE_USAGE_PROBE_FAILED: "Your Environment could not read Claude usage.",
|
|
296886
|
+
CLAUDE_USAGE_RECONNECT_REQUIRED: "Reconnect this Claude account to refresh usage.",
|
|
296887
|
+
CLAUDE_USAGE_ACCOUNT_NOT_FOUND: "This Claude account is no longer connected.",
|
|
296888
|
+
CLAUDE_USAGE_REFRESH_UNAVAILABLE: "Claude usage refresh is unavailable."
|
|
296889
|
+
};
|
|
296890
|
+
/** Verbatim from CMMD `personal-claude-usage.ts` and `provider-usage-service.ts`. */
|
|
296891
|
+
const CODE_BY_UPSTREAM_REASON = {
|
|
296892
|
+
runtime_not_active: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
|
|
296893
|
+
claude_native_delivery_disabled: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
|
|
296894
|
+
runtime_identity_mismatch: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
|
|
296895
|
+
credential_generation_not_applied: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
|
|
296896
|
+
credential_generation_unavailable: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
|
|
296897
|
+
credential_changed: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
|
|
296898
|
+
refresh_in_progress: "CLAUDE_USAGE_REFRESH_IN_PROGRESS",
|
|
296899
|
+
usage_probe_failed: "CLAUDE_USAGE_PROBE_FAILED",
|
|
296900
|
+
usage_receipt_invalid: "CLAUDE_USAGE_PROBE_FAILED",
|
|
296901
|
+
usage_output_invalid: "CLAUDE_USAGE_PROBE_FAILED"
|
|
296902
|
+
};
|
|
296903
|
+
function failure(code, status) {
|
|
296904
|
+
return {
|
|
296905
|
+
status,
|
|
296906
|
+
body: {
|
|
296907
|
+
error: FAILURE_TEXT[code],
|
|
296908
|
+
code
|
|
296909
|
+
}
|
|
296910
|
+
};
|
|
296911
|
+
}
|
|
296912
|
+
/**
|
|
296913
|
+
* CMMD sends the reason as `{ "error": "<reason>" }`. Anything else, including
|
|
296914
|
+
* a longer sentence that happens to start with an allowlisted word, is treated
|
|
296915
|
+
* as unknown.
|
|
296916
|
+
*/
|
|
296917
|
+
function readAllowlistedReasonCode(body) {
|
|
296918
|
+
if (body === null || typeof body !== "object") return null;
|
|
296919
|
+
const reason = body.error;
|
|
296920
|
+
if (typeof reason !== "string") return null;
|
|
296921
|
+
return Object.hasOwn(CODE_BY_UPSTREAM_REASON, reason) ? CODE_BY_UPSTREAM_REASON[reason] ?? null : null;
|
|
296922
|
+
}
|
|
296923
|
+
function describeClaudeUsageRefreshFailure(error) {
|
|
296924
|
+
if (!(error instanceof CmmdProxyRequestError)) return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", 502);
|
|
296925
|
+
if (error.status === 409) return failure("CLAUDE_USAGE_RECONNECT_REQUIRED", 409);
|
|
296926
|
+
if (error.status === 404) return failure("CLAUDE_USAGE_ACCOUNT_NOT_FOUND", 404);
|
|
296927
|
+
const reasonCode = readAllowlistedReasonCode(error.body);
|
|
296928
|
+
if (reasonCode !== null) return failure(reasonCode, 503);
|
|
296929
|
+
return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", error.status === 503 ? 503 : 502);
|
|
296930
|
+
}
|
|
296931
|
+
//#endregion
|
|
296798
296932
|
//#region src/command/wsToken.ts
|
|
296799
296933
|
/**
|
|
296800
296934
|
* Build the WS token response given an optional PAT and the configured CMMD
|
|
@@ -298534,11 +298668,8 @@ function resolveProviderUsageOwnerUserId(session) {
|
|
|
298534
298668
|
return Number.isSafeInteger(userId) && userId > 0 ? userId : null;
|
|
298535
298669
|
}
|
|
298536
298670
|
function buildClaudeUsageRefreshErrorResponse(error) {
|
|
298537
|
-
const
|
|
298538
|
-
return require_HttpServer.jsonUnsafe({
|
|
298539
|
-
error: "Claude usage refresh is unavailable.",
|
|
298540
|
-
code: "CLAUDE_USAGE_REFRESH_UNAVAILABLE"
|
|
298541
|
-
}, { status });
|
|
298671
|
+
const failure = describeClaudeUsageRefreshFailure(error);
|
|
298672
|
+
return require_HttpServer.jsonUnsafe(failure.body, { status: failure.status });
|
|
298542
298673
|
}
|
|
298543
298674
|
function makeRefreshProviderUsageHandler(dependencies = {}) {
|
|
298544
298675
|
return require_Schema$1.gen(function* () {
|