@cmmd-center/forge 0.9.15 → 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.15";
65126
+ var version$1 = "0.9.16";
65127
65127
  //#endregion
65128
65128
  //#region src/sentry.ts
65129
65129
  const SERVER_APP_NAME = "forge-server";
@@ -92279,8 +92279,8 @@ const ProjectionThread = require_Schema$1.Struct({
92279
92279
  createdAt: IsoDateTime,
92280
92280
  updatedAt: IsoDateTime,
92281
92281
  archivedAt: require_Schema$1.NullOr(IsoDateTime),
92282
- settledAt: require_Schema$1.optional(require_Schema$1.NullOr(IsoDateTime)),
92283
- settledOverride: require_Schema$1.optional(require_Schema$1.NullOr(require_Schema$1.Literals(["settled", "active"]))),
92282
+ settledAt: require_Schema$1.NullOr(IsoDateTime),
92283
+ settledOverride: require_Schema$1.NullOr(require_Schema$1.Literals(["settled", "active"])),
92284
92284
  latestUserMessageAt: require_Schema$1.NullOr(IsoDateTime),
92285
92285
  pendingApprovalCount: NonNegativeInt,
92286
92286
  pendingUserInputCount: NonNegativeInt,
@@ -92466,16 +92466,23 @@ const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
92466
92466
  /**
92467
92467
  * Resolve the commit this forge checkout actually booted from, so the
92468
92468
  * heartbeat can report it and CMMD can tell a stale Sprite from a current
92469
- * one. Sprites clone forge ONLY at bootstrap (see the CMMD-side bootstrap
92470
- * script) and nothing else refreshes them, so this value never changes for
92471
- * the life of the process — resolve it once and cache it.
92472
- *
92473
- * `git rev-parse HEAD` run with the process's own cwd (`apps/server`, a
92474
- * subdirectory of the cloned repo) walks up to find `.git` on its own; no
92475
- * `-C` path is needed. Returns `undefined` (never throws) when there is no
92476
- * git checkout to read local dev run from a tarball, or any environment
92477
- * this simply does not apply to so the heartbeat body omits the field
92478
- * exactly like an older Forge build that doesn't know about it yet.
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.
92479
92486
  */
92480
92487
  let cached$1 = null;
92481
92488
  async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$6(command, [...args], { timeout: 5e3 })) {
@@ -206610,7 +206617,7 @@ function createDevServerMcpServer(deps, threadId) {
206610
206617
  * per provider — this module only adapts descriptor + result shapes.
206611
206618
  *
206612
206619
  * Without it a Claude agent has no brokered push path at all and falls back to
206613
- * raw `git`, which the runtime deliberately blocks via `GIT_ASKPASS=/bin/false`.
206620
+ * raw `git`, which has no authenticated remote credentials in the agent runtime.
206614
206621
  *
206615
206622
  * The catalog is filtered through `applyPluginSkillPermissionsToDynamicTools`,
206616
206623
  * exactly as the Codex surface filters its dynamic tools. `settings` is required
@@ -287823,13 +287830,32 @@ const ObservabilityLive = require_Schema$1.unwrap(require_Schema$1.gen(function*
287823
287830
  //#region ../../packages/shared/src/buildCommit.ts
287824
287831
  const COMMIT_SHA_PATTERN$1 = /^[0-9a-f]{40}$/i;
287825
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
+ /**
287826
287845
  * Build identity from the environment.
287827
287846
  *
287828
287847
  * `.dockerignore` excludes `.git`, so `git rev-parse HEAD` cannot resolve inside
287829
287848
  * the Railway container — the surface where "which commit is live?" is asked
287830
287849
  * most. The commit therefore has to arrive as env there, with git left as the
287831
- * fallback for surfaces that genuinely are checkouts (a sprite clones forge at
287832
- * bootstrap; desktop and dev run from a repo).
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.
287833
287859
  *
287834
287860
  * `RAILWAY_GIT_COMMIT_SHA` is injected by the platform, which is what lets this
287835
287861
  * work on the cloud shell with no Dockerfile plumbing. `FORGE_BUILD_COMMIT`
@@ -287847,6 +287873,30 @@ function resolveBuildCommitFromEnv(env) {
287847
287873
  }
287848
287874
  }
287849
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
287850
287900
  //#region src/environment/Layers/ServerEnvironmentLabel.ts
287851
287901
  function normalizeLabel(value) {
287852
287902
  const trimmed = value?.trim();
@@ -287995,7 +288045,11 @@ const makeServerEnvironment = require_Schema$1.fn("makeServerEnvironment")(funct
287995
288045
  const initialMetadata = yield* readMetadata;
287996
288046
  const displayNameOverrideRef = yield* require_HttpServer.make$3(normalizeDisplayName(initialMetadata.displayName ?? null));
287997
288047
  const posthog = yield* PosthogPublicConfig.asEffect();
287998
- const buildCommit = resolveBuildCommitFromEnv(process.env) ?? (yield* require_Schema$1.promise(() => resolveForgeGitCommit()));
288048
+ const buildCommit = yield* require_Schema$1.promise(() => resolveServerBuildCommit({
288049
+ baked: readBakedBuildCommit(),
288050
+ env: process.env,
288051
+ readGitCommit: resolveForgeGitCommit
288052
+ }));
287999
288053
  const buildDescriptor = require_Schema$1.gen(function* () {
288000
288054
  const displayNameOverride = yield* require_HttpServer.get$1(displayNameOverrideRef);
288001
288055
  const os = platformOs();
@@ -296810,6 +296864,71 @@ function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
296810
296864
  };
296811
296865
  }
296812
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
296813
296932
  //#region src/command/wsToken.ts
296814
296933
  /**
296815
296934
  * Build the WS token response given an optional PAT and the configured CMMD
@@ -298549,11 +298668,8 @@ function resolveProviderUsageOwnerUserId(session) {
298549
298668
  return Number.isSafeInteger(userId) && userId > 0 ? userId : null;
298550
298669
  }
298551
298670
  function buildClaudeUsageRefreshErrorResponse(error) {
298552
- const status = error instanceof CmmdProxyRequestError && error.status === 503 ? 503 : 502;
298553
- return require_HttpServer.jsonUnsafe({
298554
- error: "Claude usage refresh is unavailable.",
298555
- code: "CLAUDE_USAGE_REFRESH_UNAVAILABLE"
298556
- }, { status });
298671
+ const failure = describeClaudeUsageRefreshFailure(error);
298672
+ return require_HttpServer.jsonUnsafe(failure.body, { status: failure.status });
298557
298673
  }
298558
298674
  function makeRefreshProviderUsageHandler(dependencies = {}) {
298559
298675
  return require_Schema$1.gen(function* () {
package/dist/bin.mjs CHANGED
@@ -64870,7 +64870,7 @@ function normalizeNumberish(value) {
64870
64870
  }
64871
64871
  //#endregion
64872
64872
  //#region package.json
64873
- var version$1 = "0.9.15";
64873
+ var version$1 = "0.9.16";
64874
64874
  //#endregion
64875
64875
  //#region src/sentry.ts
64876
64876
  const SERVER_APP_NAME = "forge-server";
@@ -91964,8 +91964,8 @@ const ProjectionThread = Struct({
91964
91964
  createdAt: IsoDateTime,
91965
91965
  updatedAt: IsoDateTime,
91966
91966
  archivedAt: NullOr(IsoDateTime),
91967
- settledAt: optional$4(NullOr(IsoDateTime)),
91968
- settledOverride: optional$4(NullOr(Literals(["settled", "active"]))),
91967
+ settledAt: NullOr(IsoDateTime),
91968
+ settledOverride: NullOr(Literals(["settled", "active"])),
91969
91969
  latestUserMessageAt: NullOr(IsoDateTime),
91970
91970
  pendingApprovalCount: NonNegativeInt,
91971
91971
  pendingUserInputCount: NonNegativeInt,
@@ -92150,16 +92150,23 @@ const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
92150
92150
  /**
92151
92151
  * Resolve the commit this forge checkout actually booted from, so the
92152
92152
  * heartbeat can report it and CMMD can tell a stale Sprite from a current
92153
- * one. Sprites clone forge ONLY at bootstrap (see the CMMD-side bootstrap
92154
- * script) and nothing else refreshes them, so this value never changes for
92155
- * the life of the process — resolve it once and cache it.
92156
- *
92157
- * `git rev-parse HEAD` run with the process's own cwd (`apps/server`, a
92158
- * subdirectory of the cloned repo) walks up to find `.git` on its own; no
92159
- * `-C` path is needed. Returns `undefined` (never throws) when there is no
92160
- * git checkout to read local dev run from a tarball, or any environment
92161
- * this simply does not apply to so the heartbeat body omits the field
92162
- * exactly like an older Forge build that doesn't know about it yet.
92153
+ * one. The commit cannot change for the life of the process, so resolve it
92154
+ * once and cache it.
92155
+ *
92156
+ * This comment used to say Sprites clone forge at bootstrap and read their
92157
+ * commit from that checkout. They do not. A cloud runtime installs the
92158
+ * published `@cmmd-center/forge` tarball and has no `.git` anywhere near it,
92159
+ * which is why its descriptor reported no commit at all until the server
92160
+ * started baking one into its bundle. See `../environment/buildIdentity.ts`,
92161
+ * which calls this only as its last rung.
92162
+ *
92163
+ * `git rev-parse HEAD` run with the process's own cwd walks up to find `.git`
92164
+ * on its own, so no `-C` path is needed. That also means a cwd inside some
92165
+ * unrelated repository answers with that repository's HEAD, which is one more
92166
+ * reason this is the last rung rather than the first. Returns `undefined`
92167
+ * (never throws) when there is no checkout to read, so the heartbeat body
92168
+ * omits the field exactly like an older Forge build that does not know about
92169
+ * it yet.
92163
92170
  */
92164
92171
  let cached$1 = null;
92165
92172
  async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$6(command, [...args], { timeout: 5e3 })) {
@@ -206252,7 +206259,7 @@ function createDevServerMcpServer(deps, threadId) {
206252
206259
  * per provider — this module only adapts descriptor + result shapes.
206253
206260
  *
206254
206261
  * Without it a Claude agent has no brokered push path at all and falls back to
206255
- * raw `git`, which the runtime deliberately blocks via `GIT_ASKPASS=/bin/false`.
206262
+ * raw `git`, which has no authenticated remote credentials in the agent runtime.
206256
206263
  *
206257
206264
  * The catalog is filtered through `applyPluginSkillPermissionsToDynamicTools`,
206258
206265
  * exactly as the Codex surface filters its dynamic tools. `settings` is required
@@ -287328,13 +287335,32 @@ const ObservabilityLive = unwrap(gen(function* () {
287328
287335
  //#region ../../packages/shared/src/buildCommit.ts
287329
287336
  const COMMIT_SHA_PATTERN$1 = /^[0-9a-f]{40}$/i;
287330
287337
  /**
287338
+ * Whether a value is a full commit sha.
287339
+ *
287340
+ * Exported so each surface can keep its own list of env names while sharing one
287341
+ * definition of "valid". Surfaces genuinely differ: Railway injects
287342
+ * RAILWAY_GIT_COMMIT_SHA into the cloud shell build, while the desktop release
287343
+ * workflows pass VITE_SENTRY_COMMIT_SHA. What must not differ is the check.
287344
+ */
287345
+ function isFullCommitSha(value) {
287346
+ const candidate = value?.trim();
287347
+ return candidate !== void 0 && candidate !== "" && COMMIT_SHA_PATTERN$1.test(candidate);
287348
+ }
287349
+ /**
287331
287350
  * Build identity from the environment.
287332
287351
  *
287333
287352
  * `.dockerignore` excludes `.git`, so `git rev-parse HEAD` cannot resolve inside
287334
287353
  * the Railway container — the surface where "which commit is live?" is asked
287335
287354
  * most. The commit therefore has to arrive as env there, with git left as the
287336
- * fallback for surfaces that genuinely are checkouts (a sprite clones forge at
287337
- * bootstrap; desktop and dev run from a repo).
287355
+ * fallback for surfaces that genuinely are checkouts (desktop and dev run from
287356
+ * a repo).
287357
+ *
287358
+ * A cloud runtime is neither: it installs the published `@cmmd-center/forge`
287359
+ * tarball, so it has no build variables and no `.git`, and both rungs below
287360
+ * return nothing there. That is why the server bakes the commit into its bundle
287361
+ * at build time and reads that first — see `apps/server/src/environment/
287362
+ * buildIdentity.ts`. This comment previously claimed a sprite clones forge at
287363
+ * bootstrap; it does not, and believing it cost a diagnosis.
287338
287364
  *
287339
287365
  * `RAILWAY_GIT_COMMIT_SHA` is injected by the platform, which is what lets this
287340
287366
  * work on the cloud shell with no Dockerfile plumbing. `FORGE_BUILD_COMMIT`
@@ -287352,6 +287378,30 @@ function resolveBuildCommitFromEnv(env) {
287352
287378
  }
287353
287379
  }
287354
287380
  //#endregion
287381
+ //#region src/environment/buildIdentity.ts
287382
+ /**
287383
+ * Which commit this server was built from.
287384
+ *
287385
+ * @module
287386
+ */
287387
+ /**
287388
+ * The commit baked into this bundle, if it is a bundle at all.
287389
+ *
287390
+ * `typeof` rather than a bare reference: running from source there is no such
287391
+ * binding, and a bare reference is a ReferenceError that would take the whole
287392
+ * environment descriptor down instead of reporting an honest "unknown".
287393
+ */
287394
+ function readBakedBuildCommit() {
287395
+ return "e65edd4d1b8a361863a52b171b5e73295d691e11";
287396
+ }
287397
+ async function resolveServerBuildCommit(input) {
287398
+ if (isFullCommitSha(input.baked)) return input.baked;
287399
+ const fromEnv = resolveBuildCommitFromEnv(input.env);
287400
+ if (fromEnv !== void 0) return fromEnv;
287401
+ const fromGit = await input.readGitCommit();
287402
+ return isFullCommitSha(fromGit) ? fromGit : void 0;
287403
+ }
287404
+ //#endregion
287355
287405
  //#region src/environment/Layers/ServerEnvironmentLabel.ts
287356
287406
  function normalizeLabel(value) {
287357
287407
  const trimmed = value?.trim();
@@ -287499,7 +287549,11 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
287499
287549
  const writeMetadata = (metadata) => fileSystem.writeFileString(serverConfig.environmentMetadataPath, `${JSON.stringify(metadata, null, 2)}\n`);
287500
287550
  const displayNameOverrideRef = yield* make$47(normalizeDisplayName((yield* readMetadata).displayName ?? null));
287501
287551
  const posthog = yield* PosthogPublicConfig.asEffect();
287502
- const buildCommit = resolveBuildCommitFromEnv(process.env) ?? (yield* promise(() => resolveForgeGitCommit()));
287552
+ const buildCommit = yield* promise(() => resolveServerBuildCommit({
287553
+ baked: readBakedBuildCommit(),
287554
+ env: process.env,
287555
+ readGitCommit: resolveForgeGitCommit
287556
+ }));
287503
287557
  const buildDescriptor = gen(function* () {
287504
287558
  const displayNameOverride = yield* get$5(displayNameOverrideRef);
287505
287559
  const os = platformOs();
@@ -296243,6 +296297,71 @@ function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
296243
296297
  };
296244
296298
  }
296245
296299
  //#endregion
296300
+ //#region src/command/claudeUsageRefreshFailure.ts
296301
+ /**
296302
+ * Why a Claude usage refresh failed, in words the Usage page can show.
296303
+ *
296304
+ * CMMD answers a failed refresh with a machine reason (`runtime_not_active`,
296305
+ * `credential_changed`, …). The Shell used to collapse every one of them into
296306
+ * "Claude usage refresh is unavailable.", so the card could only say the
296307
+ * refresh failed, never what to do about it.
296308
+ *
296309
+ * The mapping is a closed allowlist in both directions: a reason CMMD may add
296310
+ * later falls back to the generic code, and the text a client sees is written
296311
+ * here, never forwarded from upstream. That keeps a credential or runtime
296312
+ * detail in a CMMD error body from reaching the browser.
296313
+ */
296314
+ const FAILURE_TEXT = {
296315
+ CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE: "Refresh needs your Environment running.",
296316
+ CLAUDE_USAGE_CREDENTIAL_NOT_READY: "The Claude credential is not applied to your Environment yet.",
296317
+ CLAUDE_USAGE_REFRESH_IN_PROGRESS: "A refresh for this account is already running.",
296318
+ CLAUDE_USAGE_PROBE_FAILED: "Your Environment could not read Claude usage.",
296319
+ CLAUDE_USAGE_RECONNECT_REQUIRED: "Reconnect this Claude account to refresh usage.",
296320
+ CLAUDE_USAGE_ACCOUNT_NOT_FOUND: "This Claude account is no longer connected.",
296321
+ CLAUDE_USAGE_REFRESH_UNAVAILABLE: "Claude usage refresh is unavailable."
296322
+ };
296323
+ /** Verbatim from CMMD `personal-claude-usage.ts` and `provider-usage-service.ts`. */
296324
+ const CODE_BY_UPSTREAM_REASON = {
296325
+ runtime_not_active: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
296326
+ claude_native_delivery_disabled: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
296327
+ runtime_identity_mismatch: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
296328
+ credential_generation_not_applied: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
296329
+ credential_generation_unavailable: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
296330
+ credential_changed: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
296331
+ refresh_in_progress: "CLAUDE_USAGE_REFRESH_IN_PROGRESS",
296332
+ usage_probe_failed: "CLAUDE_USAGE_PROBE_FAILED",
296333
+ usage_receipt_invalid: "CLAUDE_USAGE_PROBE_FAILED",
296334
+ usage_output_invalid: "CLAUDE_USAGE_PROBE_FAILED"
296335
+ };
296336
+ function failure(code, status) {
296337
+ return {
296338
+ status,
296339
+ body: {
296340
+ error: FAILURE_TEXT[code],
296341
+ code
296342
+ }
296343
+ };
296344
+ }
296345
+ /**
296346
+ * CMMD sends the reason as `{ "error": "<reason>" }`. Anything else, including
296347
+ * a longer sentence that happens to start with an allowlisted word, is treated
296348
+ * as unknown.
296349
+ */
296350
+ function readAllowlistedReasonCode(body) {
296351
+ if (body === null || typeof body !== "object") return null;
296352
+ const reason = body.error;
296353
+ if (typeof reason !== "string") return null;
296354
+ return Object.hasOwn(CODE_BY_UPSTREAM_REASON, reason) ? CODE_BY_UPSTREAM_REASON[reason] ?? null : null;
296355
+ }
296356
+ function describeClaudeUsageRefreshFailure(error) {
296357
+ if (!(error instanceof CmmdProxyRequestError)) return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", 502);
296358
+ if (error.status === 409) return failure("CLAUDE_USAGE_RECONNECT_REQUIRED", 409);
296359
+ if (error.status === 404) return failure("CLAUDE_USAGE_ACCOUNT_NOT_FOUND", 404);
296360
+ const reasonCode = readAllowlistedReasonCode(error.body);
296361
+ if (reasonCode !== null) return failure(reasonCode, 503);
296362
+ return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", error.status === 503 ? 503 : 502);
296363
+ }
296364
+ //#endregion
296246
296365
  //#region src/command/wsToken.ts
296247
296366
  /**
296248
296367
  * Build the WS token response given an optional PAT and the configured CMMD
@@ -297979,10 +298098,8 @@ function resolveProviderUsageOwnerUserId(session) {
297979
298098
  return Number.isSafeInteger(userId) && userId > 0 ? userId : null;
297980
298099
  }
297981
298100
  function buildClaudeUsageRefreshErrorResponse(error) {
297982
- return jsonUnsafe({
297983
- error: "Claude usage refresh is unavailable.",
297984
- code: "CLAUDE_USAGE_REFRESH_UNAVAILABLE"
297985
- }, { status: error instanceof CmmdProxyRequestError && error.status === 503 ? 503 : 502 });
298101
+ const failure = describeClaudeUsageRefreshFailure(error);
298102
+ return jsonUnsafe(failure.body, { status: failure.status });
297986
298103
  }
297987
298104
  function makeRefreshProviderUsageHandler(dependencies = {}) {
297988
298105
  return gen(function* () {