@cmmd-center/forge 0.9.15 → 0.9.17

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
@@ -22345,6 +22345,40 @@ const ServerProviderAuth = require_Schema$1.Struct({
22345
22345
  accountEmail: require_Schema$1.optional(TrimmedNonEmptyString),
22346
22346
  provenance: require_Schema$1.optional(ProviderAuthProvenance)
22347
22347
  });
22348
+ /**
22349
+ * Whether this Environment will let a person select the provider.
22350
+ *
22351
+ * Published for every provider the build knows about. A provider is never
22352
+ * dropped from the list: absence is not a state, and four different reasons
22353
+ * once collapsed into one "Coming soon." string because the Shell had to infer
22354
+ * meaning from what was missing. See
22355
+ * `docs/adr/withheld-providers-carry-their-reason.md`.
22356
+ */
22357
+ const ProviderAvailability = require_Schema$1.Literals(["available", "unavailable"]);
22358
+ /**
22359
+ * Why a Withheld Provider is withheld. The four members are the glossary's,
22360
+ * in `CONTEXT.md` section "Provider availability": Unbuilt, Not Offered, Not
22361
+ * Usable, Not on this Environment. Only Unbuilt may read "Coming soon."
22362
+ *
22363
+ * An Environment can only ever report Unbuilt, Not Usable, or Not on this
22364
+ * Environment. Not Offered is a CMMD fact about the organization's Provider
22365
+ * Offer, which the Shell adds when it joins.
22366
+ */
22367
+ const ProviderUnavailableReason = require_Schema$1.Literals([
22368
+ "unbuilt",
22369
+ "not-offered",
22370
+ "not-usable",
22371
+ "not-on-this-environment"
22372
+ ]);
22373
+ /**
22374
+ * The Not Usable sub-case. It picks a second line of copy, not a fifth state,
22375
+ * which is why it is a separate field rather than three more reasons.
22376
+ */
22377
+ const ProviderUnavailableDetail = require_Schema$1.Literals([
22378
+ "no-connection",
22379
+ "delivery-pending",
22380
+ "reconnect-required"
22381
+ ]);
22348
22382
  const ServerProviderModel = require_Schema$1.Struct({
22349
22383
  slug: TrimmedNonEmptyString,
22350
22384
  name: TrimmedNonEmptyString,
@@ -22376,6 +22410,9 @@ const ServerProvider = require_Schema$1.Struct({
22376
22410
  checkedAt: IsoDateTime,
22377
22411
  message: require_Schema$1.optional(TrimmedNonEmptyString),
22378
22412
  models: require_Schema$1.ArraySchema(ServerProviderModel),
22413
+ availability: require_Schema$1.optional(ProviderAvailability),
22414
+ unavailableReason: require_Schema$1.optional(ProviderUnavailableReason),
22415
+ unavailableDetail: require_Schema$1.optional(ProviderUnavailableDetail),
22379
22416
  slashCommands: require_Schema$1.ArraySchema(ServerProviderSlashCommand).pipe(require_Schema$1.withDecodingDefault(require_Schema$1.succeed([]))),
22380
22417
  skills: require_Schema$1.ArraySchema(ServerProviderSkill).pipe(require_Schema$1.withDecodingDefault(require_Schema$1.succeed([])))
22381
22418
  });
@@ -65123,7 +65160,7 @@ function normalizeNumberish(value) {
65123
65160
  }
65124
65161
  //#endregion
65125
65162
  //#region package.json
65126
- var version$1 = "0.9.15";
65163
+ var version$1 = "0.9.17";
65127
65164
  //#endregion
65128
65165
  //#region src/sentry.ts
65129
65166
  const SERVER_APP_NAME = "forge-server";
@@ -92279,8 +92316,8 @@ const ProjectionThread = require_Schema$1.Struct({
92279
92316
  createdAt: IsoDateTime,
92280
92317
  updatedAt: IsoDateTime,
92281
92318
  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"]))),
92319
+ settledAt: require_Schema$1.NullOr(IsoDateTime),
92320
+ settledOverride: require_Schema$1.NullOr(require_Schema$1.Literals(["settled", "active"])),
92284
92321
  latestUserMessageAt: require_Schema$1.NullOr(IsoDateTime),
92285
92322
  pendingApprovalCount: NonNegativeInt,
92286
92323
  pendingUserInputCount: NonNegativeInt,
@@ -92466,16 +92503,23 @@ const FORGE_GIT_COMMIT_PATTERN = /^[0-9a-f]{7,40}$/i;
92466
92503
  /**
92467
92504
  * Resolve the commit this forge checkout actually booted from, so the
92468
92505
  * 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.
92506
+ * one. The commit cannot change for the life of the process, so resolve it
92507
+ * once and cache it.
92508
+ *
92509
+ * This comment used to say Sprites clone forge at bootstrap and read their
92510
+ * commit from that checkout. They do not. A cloud runtime installs the
92511
+ * published `@cmmd-center/forge` tarball and has no `.git` anywhere near it,
92512
+ * which is why its descriptor reported no commit at all until the server
92513
+ * started baking one into its bundle. See `../environment/buildIdentity.ts`,
92514
+ * which calls this only as its last rung.
92515
+ *
92516
+ * `git rev-parse HEAD` run with the process's own cwd walks up to find `.git`
92517
+ * on its own, so no `-C` path is needed. That also means a cwd inside some
92518
+ * unrelated repository answers with that repository's HEAD, which is one more
92519
+ * reason this is the last rung rather than the first. Returns `undefined`
92520
+ * (never throws) when there is no checkout to read, so the heartbeat body
92521
+ * omits the field exactly like an older Forge build that does not know about
92522
+ * it yet.
92479
92523
  */
92480
92524
  let cached$1 = null;
92481
92525
  async function resolveForgeGitCommit(execImpl = (command, args) => execFileAsync$6(command, [...args], { timeout: 5e3 })) {
@@ -206610,7 +206654,7 @@ function createDevServerMcpServer(deps, threadId) {
206610
206654
  * per provider — this module only adapts descriptor + result shapes.
206611
206655
  *
206612
206656
  * 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`.
206657
+ * raw `git`, which has no authenticated remote credentials in the agent runtime.
206614
206658
  *
206615
206659
  * The catalog is filtered through `applyPluginSkillPermissionsToDynamicTools`,
206616
206660
  * exactly as the Codex surface filters its dynamic tools. `settings` is required
@@ -285036,14 +285080,87 @@ const writeProviderStatusCache = (input) => {
285036
285080
  })));
285037
285081
  };
285038
285082
  //#endregion
285039
- //#region src/provider/Layers/ProviderRegistry.ts
285083
+ //#region ../../packages/shared/src/providerExecution.ts
285084
+ /**
285085
+ * Canonical provider-readiness gate for starting work.
285086
+ *
285087
+ * Cloud user VMs can execute through Forge's scoped provider proxy without a
285088
+ * provider-local login. In that case the provider reports unauthenticated but
285089
+ * its credential provenance is explicitly `proxied`.
285090
+ */
285091
+ function isServerProviderExecutable(provider) {
285092
+ const hasExecutionAuthority = provider.auth.status !== "unauthenticated" || provider.auth.provenance?.credentialScope === "proxied";
285093
+ return provider.enabled && provider.installed && provider.status === "ready" && hasExecutionAuthority && provider.models.length > 0;
285094
+ }
285095
+ //#endregion
285096
+ //#region src/provider/providerAvailability.ts
285097
+ /**
285098
+ * Derive what this Environment knows about a provider's availability.
285099
+ *
285100
+ * WHY THIS EXISTS
285101
+ *
285102
+ * Four different states (Unbuilt, Not Offered, Not Usable, Not on this
285103
+ * Environment) used to render as one "Coming soon." string, because consumers
285104
+ * were handed a filtered list and had to infer meaning from what was missing.
285105
+ * Every provider is now published with an explicit availability and, when
285106
+ * withheld, its reason. See `docs/adr/withheld-providers-carry-their-reason.md`
285107
+ * and the glossary in `CONTEXT.md` section "Provider availability".
285108
+ *
285109
+ * WHAT AN ENVIRONMENT MAY SAY
285110
+ *
285111
+ * Three of the four reasons only. Not Offered is a fact about the CMMD
285112
+ * organization's Provider Offer, and delivery-pending is a fact about a
285113
+ * credential in flight; neither is visible from inside an Environment, so the
285114
+ * Shell adds them when it joins. Nothing here may invent them.
285115
+ *
285116
+ * This reads the snapshot a provider Layer already produced. It runs no probe
285117
+ * and changes no probe: `isServerProviderExecutable` stays the single gate on
285118
+ * whether a turn can be dispatched, and this restates its answer in the
285119
+ * vocabulary a person sees.
285120
+ */
285040
285121
  /**
285041
- * Providers surfaced in the UI but lacking a dedicated detection Layer.
285042
- * Every provider now has a real Layer; this array stays as a (currently
285043
- * empty) hook for any future provider that ships in the UI before its
285044
- * server-side detection lands.
285122
+ * Providers Forge ships in the UI but cannot execute, published as Unbuilt.
285123
+ *
285124
+ * Empty in this build: codex, claudeAgent, ollama, cmmd, apex, cursor and
285125
+ * opencode all have a real detection Layer and a registered adapter. The list
285126
+ * stays as the one place a provider that ships ahead of its server-side
285127
+ * implementation is named, so "Coming soon." is a deliberate entry rather than
285128
+ * a fallback something fell into.
285045
285129
  */
285046
- const STUB_PROVIDERS = [];
285130
+ const UNBUILT_PROVIDERS = [];
285131
+ function deriveProviderAvailability(provider, unbuiltProviders = UNBUILT_PROVIDERS) {
285132
+ if (unbuiltProviders.includes(provider.provider)) return {
285133
+ availability: "unavailable",
285134
+ unavailableReason: "unbuilt"
285135
+ };
285136
+ if (isServerProviderExecutable(provider)) return { availability: "available" };
285137
+ const hasProxiedCredential = provider.auth.provenance?.credentialScope === "proxied";
285138
+ if (provider.installed && provider.auth.status === "unauthenticated" && !hasProxiedCredential) return {
285139
+ availability: "unavailable",
285140
+ unavailableReason: "not-usable",
285141
+ unavailableDetail: "no-connection"
285142
+ };
285143
+ return {
285144
+ availability: "unavailable",
285145
+ unavailableReason: "not-on-this-environment"
285146
+ };
285147
+ }
285148
+ /**
285149
+ * Stamp a snapshot with its derived availability.
285150
+ *
285151
+ * The previous fields are dropped before the new ones are applied. Merging
285152
+ * would leave a provider that just connected reporting `available` while still
285153
+ * carrying the reason it was withheld for.
285154
+ */
285155
+ function withProviderAvailability(provider, unbuiltProviders = UNBUILT_PROVIDERS) {
285156
+ const { availability: _availability, unavailableReason: _unavailableReason, unavailableDetail: _unavailableDetail, ...rest } = provider;
285157
+ return {
285158
+ ...rest,
285159
+ ...deriveProviderAvailability(provider, unbuiltProviders)
285160
+ };
285161
+ }
285162
+ //#endregion
285163
+ //#region src/provider/Layers/ProviderRegistry.ts
285047
285164
  const loadProviders = (codexProvider, claudeProvider, ollamaProvider, cmmdProvider, apexProvider, cursorProvider, opencodeProvider) => require_Schema$1.all([
285048
285165
  codexProvider.getSnapshot,
285049
285166
  claudeProvider.getSnapshot,
@@ -285080,9 +285197,9 @@ const ProviderRegistryLive = require_Schema$1.effect(ProviderRegistry, require_S
285080
285197
  cachedProvider,
285081
285198
  fallbackProvider
285082
285199
  });
285083
- return STUB_PROVIDERS.includes(provider) ? fallbackProvider : void 0;
285200
+ return UNBUILT_PROVIDERS.includes(provider) ? fallbackProvider : void 0;
285084
285201
  }));
285085
- }, { concurrency: "unbounded" }).pipe(require_Schema$1.map((providers) => orderProviderSnapshots(providers.filter((provider) => provider !== void 0))));
285202
+ }, { concurrency: "unbounded" }).pipe(require_Schema$1.map((providers) => orderProviderSnapshots(providers.filter((provider) => provider !== void 0).map((provider) => withProviderAvailability(provider)))));
285086
285203
  const providersRef = yield* require_HttpServer.make$3(cachedProviders);
285087
285204
  const persistProvider = (provider) => writeProviderStatusCache({
285088
285205
  filePath: cachePathByProvider.get(provider.provider),
@@ -285091,7 +285208,8 @@ const ProviderRegistryLive = require_Schema$1.effect(ProviderRegistry, require_S
285091
285208
  provider: provider.provider,
285092
285209
  error
285093
285210
  })), require_Schema$1.ignore);
285094
- const upsertProviders = require_Schema$1.fn("upsertProviders")(function* (nextProviders, options) {
285211
+ const upsertProviders = require_Schema$1.fn("upsertProviders")(function* (incomingProviders, options) {
285212
+ const nextProviders = incomingProviders.map((provider) => withProviderAvailability(provider));
285095
285213
  const [previousProviders, providers] = yield* require_HttpServer.modify(providersRef, (previousProviders) => {
285096
285214
  const mergedProviders = new Map(previousProviders.map((provider) => [provider.provider, provider]));
285097
285215
  for (const provider of nextProviders) mergedProviders.set(provider.provider, provider);
@@ -287823,13 +287941,32 @@ const ObservabilityLive = require_Schema$1.unwrap(require_Schema$1.gen(function*
287823
287941
  //#region ../../packages/shared/src/buildCommit.ts
287824
287942
  const COMMIT_SHA_PATTERN$1 = /^[0-9a-f]{40}$/i;
287825
287943
  /**
287944
+ * Whether a value is a full commit sha.
287945
+ *
287946
+ * Exported so each surface can keep its own list of env names while sharing one
287947
+ * definition of "valid". Surfaces genuinely differ: Railway injects
287948
+ * RAILWAY_GIT_COMMIT_SHA into the cloud shell build, while the desktop release
287949
+ * workflows pass VITE_SENTRY_COMMIT_SHA. What must not differ is the check.
287950
+ */
287951
+ function isFullCommitSha(value) {
287952
+ const candidate = value?.trim();
287953
+ return candidate !== void 0 && candidate !== "" && COMMIT_SHA_PATTERN$1.test(candidate);
287954
+ }
287955
+ /**
287826
287956
  * Build identity from the environment.
287827
287957
  *
287828
287958
  * `.dockerignore` excludes `.git`, so `git rev-parse HEAD` cannot resolve inside
287829
287959
  * the Railway container — the surface where "which commit is live?" is asked
287830
287960
  * 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).
287961
+ * fallback for surfaces that genuinely are checkouts (desktop and dev run from
287962
+ * a repo).
287963
+ *
287964
+ * A cloud runtime is neither: it installs the published `@cmmd-center/forge`
287965
+ * tarball, so it has no build variables and no `.git`, and both rungs below
287966
+ * return nothing there. That is why the server bakes the commit into its bundle
287967
+ * at build time and reads that first — see `apps/server/src/environment/
287968
+ * buildIdentity.ts`. This comment previously claimed a sprite clones forge at
287969
+ * bootstrap; it does not, and believing it cost a diagnosis.
287833
287970
  *
287834
287971
  * `RAILWAY_GIT_COMMIT_SHA` is injected by the platform, which is what lets this
287835
287972
  * work on the cloud shell with no Dockerfile plumbing. `FORGE_BUILD_COMMIT`
@@ -287847,6 +287984,30 @@ function resolveBuildCommitFromEnv(env) {
287847
287984
  }
287848
287985
  }
287849
287986
  //#endregion
287987
+ //#region src/environment/buildIdentity.ts
287988
+ /**
287989
+ * Which commit this server was built from.
287990
+ *
287991
+ * @module
287992
+ */
287993
+ /**
287994
+ * The commit baked into this bundle, if it is a bundle at all.
287995
+ *
287996
+ * `typeof` rather than a bare reference: running from source there is no such
287997
+ * binding, and a bare reference is a ReferenceError that would take the whole
287998
+ * environment descriptor down instead of reporting an honest "unknown".
287999
+ */
288000
+ function readBakedBuildCommit() {
288001
+ return "86c71b55bb7a5dfbbad063d6e4c082570759f006";
288002
+ }
288003
+ async function resolveServerBuildCommit(input) {
288004
+ if (isFullCommitSha(input.baked)) return input.baked;
288005
+ const fromEnv = resolveBuildCommitFromEnv(input.env);
288006
+ if (fromEnv !== void 0) return fromEnv;
288007
+ const fromGit = await input.readGitCommit();
288008
+ return isFullCommitSha(fromGit) ? fromGit : void 0;
288009
+ }
288010
+ //#endregion
287850
288011
  //#region src/environment/Layers/ServerEnvironmentLabel.ts
287851
288012
  function normalizeLabel(value) {
287852
288013
  const trimmed = value?.trim();
@@ -287995,7 +288156,11 @@ const makeServerEnvironment = require_Schema$1.fn("makeServerEnvironment")(funct
287995
288156
  const initialMetadata = yield* readMetadata;
287996
288157
  const displayNameOverrideRef = yield* require_HttpServer.make$3(normalizeDisplayName(initialMetadata.displayName ?? null));
287997
288158
  const posthog = yield* PosthogPublicConfig.asEffect();
287998
- const buildCommit = resolveBuildCommitFromEnv(process.env) ?? (yield* require_Schema$1.promise(() => resolveForgeGitCommit()));
288159
+ const buildCommit = yield* require_Schema$1.promise(() => resolveServerBuildCommit({
288160
+ baked: readBakedBuildCommit(),
288161
+ env: process.env,
288162
+ readGitCommit: resolveForgeGitCommit
288163
+ }));
287999
288164
  const buildDescriptor = require_Schema$1.gen(function* () {
288000
288165
  const displayNameOverride = yield* require_HttpServer.get$1(displayNameOverrideRef);
288001
288166
  const os = platformOs();
@@ -296810,6 +296975,71 @@ function selectProviderUsageEnvironment(rawCatalog, ownerUserId) {
296810
296975
  };
296811
296976
  }
296812
296977
  //#endregion
296978
+ //#region src/command/claudeUsageRefreshFailure.ts
296979
+ /**
296980
+ * Why a Claude usage refresh failed, in words the Usage page can show.
296981
+ *
296982
+ * CMMD answers a failed refresh with a machine reason (`runtime_not_active`,
296983
+ * `credential_changed`, …). The Shell used to collapse every one of them into
296984
+ * "Claude usage refresh is unavailable.", so the card could only say the
296985
+ * refresh failed, never what to do about it.
296986
+ *
296987
+ * The mapping is a closed allowlist in both directions: a reason CMMD may add
296988
+ * later falls back to the generic code, and the text a client sees is written
296989
+ * here, never forwarded from upstream. That keeps a credential or runtime
296990
+ * detail in a CMMD error body from reaching the browser.
296991
+ */
296992
+ const FAILURE_TEXT = {
296993
+ CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE: "Refresh needs your Environment running.",
296994
+ CLAUDE_USAGE_CREDENTIAL_NOT_READY: "The Claude credential is not applied to your Environment yet.",
296995
+ CLAUDE_USAGE_REFRESH_IN_PROGRESS: "A refresh for this account is already running.",
296996
+ CLAUDE_USAGE_PROBE_FAILED: "Your Environment could not read Claude usage.",
296997
+ CLAUDE_USAGE_RECONNECT_REQUIRED: "Reconnect this Claude account to refresh usage.",
296998
+ CLAUDE_USAGE_ACCOUNT_NOT_FOUND: "This Claude account is no longer connected.",
296999
+ CLAUDE_USAGE_REFRESH_UNAVAILABLE: "Claude usage refresh is unavailable."
297000
+ };
297001
+ /** Verbatim from CMMD `personal-claude-usage.ts` and `provider-usage-service.ts`. */
297002
+ const CODE_BY_UPSTREAM_REASON = {
297003
+ runtime_not_active: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
297004
+ claude_native_delivery_disabled: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
297005
+ runtime_identity_mismatch: "CLAUDE_USAGE_ENVIRONMENT_UNAVAILABLE",
297006
+ credential_generation_not_applied: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
297007
+ credential_generation_unavailable: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
297008
+ credential_changed: "CLAUDE_USAGE_CREDENTIAL_NOT_READY",
297009
+ refresh_in_progress: "CLAUDE_USAGE_REFRESH_IN_PROGRESS",
297010
+ usage_probe_failed: "CLAUDE_USAGE_PROBE_FAILED",
297011
+ usage_receipt_invalid: "CLAUDE_USAGE_PROBE_FAILED",
297012
+ usage_output_invalid: "CLAUDE_USAGE_PROBE_FAILED"
297013
+ };
297014
+ function failure(code, status) {
297015
+ return {
297016
+ status,
297017
+ body: {
297018
+ error: FAILURE_TEXT[code],
297019
+ code
297020
+ }
297021
+ };
297022
+ }
297023
+ /**
297024
+ * CMMD sends the reason as `{ "error": "<reason>" }`. Anything else, including
297025
+ * a longer sentence that happens to start with an allowlisted word, is treated
297026
+ * as unknown.
297027
+ */
297028
+ function readAllowlistedReasonCode(body) {
297029
+ if (body === null || typeof body !== "object") return null;
297030
+ const reason = body.error;
297031
+ if (typeof reason !== "string") return null;
297032
+ return Object.hasOwn(CODE_BY_UPSTREAM_REASON, reason) ? CODE_BY_UPSTREAM_REASON[reason] ?? null : null;
297033
+ }
297034
+ function describeClaudeUsageRefreshFailure(error) {
297035
+ if (!(error instanceof CmmdProxyRequestError)) return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", 502);
297036
+ if (error.status === 409) return failure("CLAUDE_USAGE_RECONNECT_REQUIRED", 409);
297037
+ if (error.status === 404) return failure("CLAUDE_USAGE_ACCOUNT_NOT_FOUND", 404);
297038
+ const reasonCode = readAllowlistedReasonCode(error.body);
297039
+ if (reasonCode !== null) return failure(reasonCode, 503);
297040
+ return failure("CLAUDE_USAGE_REFRESH_UNAVAILABLE", error.status === 503 ? 503 : 502);
297041
+ }
297042
+ //#endregion
296813
297043
  //#region src/command/wsToken.ts
296814
297044
  /**
296815
297045
  * Build the WS token response given an optional PAT and the configured CMMD
@@ -298549,11 +298779,8 @@ function resolveProviderUsageOwnerUserId(session) {
298549
298779
  return Number.isSafeInteger(userId) && userId > 0 ? userId : null;
298550
298780
  }
298551
298781
  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 });
298782
+ const failure = describeClaudeUsageRefreshFailure(error);
298783
+ return require_HttpServer.jsonUnsafe(failure.body, { status: failure.status });
298557
298784
  }
298558
298785
  function makeRefreshProviderUsageHandler(dependencies = {}) {
298559
298786
  return require_Schema$1.gen(function* () {
@@ -300509,21 +300736,24 @@ function processRuntimeReplicationOutboxItem(item, dependencies) {
300509
300736
  //#endregion
300510
300737
  //#region src/runtimeReplication/Services/ActiveHourMeter.ts
300511
300738
  var ActiveHourMeter = class extends require_Schema$1.Service()("forge/runtimeReplication/Services/ActiveHourMeter") {};
300512
- //#endregion
300513
- //#region ../../packages/shared/src/providerExecution.ts
300739
+ /** `slug` and `name` are `.min(1).max(512)` on the receiving schema. */
300740
+ const FIELD_MAX_LENGTH = 512;
300741
+ /** `version` is `.max(128)` on the receiving schema, with no length floor. */
300742
+ const VERSION_MAX_LENGTH = 128;
300743
+ /** `binaryPath` is `.max(2048)` on the receiving schema, with no length floor. */
300744
+ const BINARY_PATH_MAX_LENGTH = 2048;
300514
300745
  /**
300515
- * Canonical provider-readiness gate for starting work.
300746
+ * Trim to the receiving schema's maximum, or null.
300516
300747
  *
300517
- * Cloud user VMs can execute through Forge's scoped provider proxy without a
300518
- * provider-local login. In that case the provider reports unauthenticated but
300519
- * its credential provenance is explicitly `proxied`.
300748
+ * Neither field has a length floor, so an empty string would be legal — but it
300749
+ * is not an answer. A version nobody resolved and a binary nobody found are
300750
+ * both null, which is what the control plane stores and what a reader can tell
300751
+ * apart from a real value.
300520
300752
  */
300521
- function isServerProviderExecutable(provider) {
300522
- const hasExecutionAuthority = provider.auth.status !== "unauthenticated" || provider.auth.provenance?.credentialScope === "proxied";
300523
- return provider.enabled && provider.installed && provider.status === "ready" && hasExecutionAuthority && provider.models.length > 0;
300753
+ function cappedText(value, max) {
300754
+ const normalized = value?.trim().slice(0, max);
300755
+ return normalized ? normalized : null;
300524
300756
  }
300525
- /** `slug` and `name` are `.min(1).max(512)` on the receiving schema. */
300526
- const FIELD_MAX_LENGTH = 512;
300527
300757
  function serializeModel(model) {
300528
300758
  const slug = model.slug.trim().slice(0, FIELD_MAX_LENGTH);
300529
300759
  const name = model.name.trim().slice(0, FIELD_MAX_LENGTH);
@@ -300537,20 +300767,48 @@ function serializeModel(model) {
300537
300767
  name
300538
300768
  };
300539
300769
  }
300770
+ /**
300771
+ * The availability to publish for one provider.
300772
+ *
300773
+ * The registry stamps every snapshot before it leaves, so this normally copies.
300774
+ * Deriving over a stamped snapshot would lose the one fact only the registry
300775
+ * holds — which providers this build cannot run at all — and republish every
300776
+ * Unbuilt provider as Not on this Environment. The fallback covers a snapshot
300777
+ * that reached the outbox from some other path.
300778
+ *
300779
+ * Built by spread so a key with no value is ABSENT rather than `undefined`: the
300780
+ * receiving object is `.strict()`, `undefined` is not a legal enum member, and
300781
+ * the outbox retries the resulting 400 forever.
300782
+ */
300783
+ function publishedAvailability(provider) {
300784
+ if (provider.availability === void 0) return deriveProviderAvailability(provider);
300785
+ const reason = provider.unavailableReason;
300786
+ const detail = provider.unavailableDetail;
300787
+ return {
300788
+ availability: provider.availability,
300789
+ ...reason === void 0 ? {} : { unavailableReason: reason },
300790
+ ...detail === void 0 ? {} : { unavailableDetail: detail }
300791
+ };
300792
+ }
300793
+ function serializeProvider(provider) {
300794
+ const models = [];
300795
+ for (const model of provider.models) {
300796
+ if (models.length >= 256) break;
300797
+ const serialized = serializeModel(model);
300798
+ if (serialized) models.push(serialized);
300799
+ }
300800
+ return {
300801
+ provider: provider.provider,
300802
+ models,
300803
+ ...publishedAvailability(provider),
300804
+ version: cappedText(provider.version, VERSION_MAX_LENGTH),
300805
+ binaryPath: cappedText(provider.auth.provenance?.binaryPath, BINARY_PATH_MAX_LENGTH)
300806
+ };
300807
+ }
300540
300808
  function serializeProviderCatalog(providers) {
300541
300809
  const catalog = [];
300542
300810
  for (const provider of providers) {
300543
- if (!isServerProviderExecutable(provider)) continue;
300544
- const models = [];
300545
- for (const model of provider.models) {
300546
- if (models.length >= 256) break;
300547
- const serialized = serializeModel(model);
300548
- if (serialized) models.push(serialized);
300549
- }
300550
- catalog.push({
300551
- provider: provider.provider,
300552
- models
300553
- });
300811
+ catalog.push(serializeProvider(provider));
300554
300812
  if (catalog.length >= 16) break;
300555
300813
  }
300556
300814
  return catalog;