@cmmd-center/forge 0.13.43 → 0.13.45

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
@@ -66243,7 +66243,7 @@ function normalizeNumberish(value) {
66243
66243
  }
66244
66244
  //#endregion
66245
66245
  //#region package.json
66246
- var version$1 = "0.13.43";
66246
+ var version$1 = "0.13.45";
66247
66247
  //#endregion
66248
66248
  //#region src/sentry.ts
66249
66249
  const SERVER_APP_NAME = "forge-server";
@@ -94509,7 +94509,7 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94509
94509
  projectCwd: bootstrapWorktree.projectCwd
94510
94510
  };
94511
94511
  });
94512
- const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(function* () {
94512
+ const dispatchBootstrapTurnStart$1 = (input, command) => require_Schema$1.gen(function* () {
94513
94513
  const { orchestrationEngine, git, gitStatusBroadcaster, projectSetupScriptRunner } = input;
94514
94514
  const bootstrap = command.bootstrap;
94515
94515
  const { bootstrap: _bootstrap, ...finalTurnStartCommand } = command;
@@ -94659,6 +94659,48 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
94659
94659
  })), command.threadId);
94660
94660
  });
94661
94661
  //#endregion
94662
+ //#region src/orchestration/Services/SpriteTaskHoldReactor.ts
94663
+ /**
94664
+ * SpriteTaskHoldReactor - keeps a Fly.io Sprite awake while a turn is running.
94665
+ *
94666
+ * Reacts to provider turn lifecycle events and holds the sprite's run open for
94667
+ * the duration of each turn. See `sprite/spriteTaskHold.ts` for the measurements
94668
+ * and the ordering rule this depends on.
94669
+ *
94670
+ * @module SpriteTaskHoldReactor
94671
+ */
94672
+ /**
94673
+ * SpriteTaskHoldReactor - Service tag for the sprite task hold worker.
94674
+ */
94675
+ var SpriteTaskHoldReactor = class extends require_Schema$1.Service()("forge/orchestration/Services/SpriteTaskHoldReactor") {};
94676
+ //#endregion
94677
+ //#region src/orchestration/bootstrapLaunch.ts
94678
+ /**
94679
+ * The entry point for launching a thread: a bootstrap turn start, held awake.
94680
+ *
94681
+ * A launch creates the thread, adds the worktree and starts the setup script before the first
94682
+ * provider event exists, so no turn hold covers it. On a sprite with nothing connected that
94683
+ * work advances at about 1.7% of wall clock (`sprite/spriteTaskHold.ts`). A person who sends a
94684
+ * first message and closes the tab would otherwise wait most of an hour for a one-minute
94685
+ * install.
94686
+ *
94687
+ * Callers import the dispatch from here. `bootstrapDispatch.ts` stays unaware of sprites.
94688
+ */
94689
+ /**
94690
+ * Runs a launch with the sprite held awake. A launch that fails or is cancelled releases the
94691
+ * hold. One that succeeds keeps it: the reactor hands over to the turn's own hold when the turn
94692
+ * starts, and the hold's refresh budget ends it if the turn never does. Where no reactor is
94693
+ * wired the launch runs unchanged.
94694
+ */
94695
+ const withLaunchHold = (threadId, launch) => require_Schema$1.gen(function* () {
94696
+ const reactor = yield* require_Schema$1.serviceOption(SpriteTaskHoldReactor);
94697
+ if (require_Schema$1.isNone(reactor)) return yield* launch;
94698
+ const release = () => reactor.value.releaseLaunch(threadId);
94699
+ return yield* require_Schema$1.uninterruptible(reactor.value.holdLaunch(threadId)).pipe(require_Schema$1.andThen(require_Schema$1.yieldNow), require_Schema$1.andThen(launch), require_Schema$1.onError(release));
94700
+ });
94701
+ /** `dispatchBootstrapTurnStart` from `bootstrapDispatch.ts`, with the launch held awake. */
94702
+ const dispatchBootstrapTurnStart = (input, command) => withLaunchHold(String(command.threadId), dispatchBootstrapTurnStart$1(input, command));
94703
+ //#endregion
94662
94704
  //#region src/orchestration/Services/OrchestrationEngine.ts
94663
94705
  /**
94664
94706
  * OrchestrationEngineService - Service tag for orchestration engine access.
@@ -211160,7 +211202,8 @@ async function listCatalog(resolveJwt, scope) {
211160
211202
  catalogVersion: data.catalogVersion,
211161
211203
  capabilitySessionId: data.capabilitySessionId,
211162
211204
  capabilityToken: data.capabilityToken,
211163
- expiresAt: data.expiresAt
211205
+ expiresAt: data.expiresAt,
211206
+ ...data.codewebMcp ? { codewebMcp: data.codewebMcp } : {}
211164
211207
  };
211165
211208
  } catch (error) {
211166
211209
  return {
@@ -211255,7 +211298,8 @@ async function resolveCmmdSidekickToolCatalog(resolveJwt, threadId) {
211255
211298
  const catalog = await session.getCatalog();
211256
211299
  return {
211257
211300
  session,
211258
- tools: catalog.ok ? catalog.tools : []
211301
+ tools: catalog.ok ? catalog.tools : [],
211302
+ ...catalog.ok && catalog.codewebMcp ? { codewebMcp: catalog.codewebMcp } : {}
211259
211303
  };
211260
211304
  }
211261
211305
  //#endregion
@@ -214883,6 +214927,18 @@ function createComputerMcpServer(computerManager, threadId) {
214883
214927
  */
214884
214928
  const CODEWEB_MCP_SERVER_NAME = "codeweb";
214885
214929
  const CODEWEB_MCP_SERVER_URL = "https://codeweb.cmmd.ai/mcp";
214930
+ /**
214931
+ * CMMD's grant is the primary control for the CodeWeb injection (ADR-011:
214932
+ * CMMD decides, Forge relays, the same pattern as the Sidekick tools). A
214933
+ * grant present in the catalog response decides, even when it disagrees with
214934
+ * the local setting. Absent grant (CMMD unreachable, auth failure, or an
214935
+ * older CMMD without the field, CMMD#4882) falls back to
214936
+ * `ServerSettings.codewebMcp` so deployments without the grant channel
214937
+ * (local/dev) keep a working switch.
214938
+ */
214939
+ function resolveCodewebMcpEnabled(input) {
214940
+ return input.grant ? input.grant.enabled : input.setting;
214941
+ }
214886
214942
  //#endregion
214887
214943
  //#region src/cmmdData/cmmdContextMcpServer.ts
214888
214944
  /**
@@ -215760,7 +215816,10 @@ function buildClaudeMcpServers(input) {
215760
215816
  [GITHUB_MCP_SERVER_NAME]: createGitHubMcpServer(input.runBrokeredGitHubTool, input.threadId, input.serverSettings),
215761
215817
  ...input.computerManager ? { [COMPUTER_MCP_SERVER_NAME]: createComputerMcpServer(input.computerManager, input.threadId) } : {},
215762
215818
  ...input.cmmdSidekickTools ? { "cmmd-sidekick-tools": createCmmdSidekickToolsMcpServer(input.cmmdSidekickTools.session, input.cmmdSidekickTools.tools) } : {},
215763
- ...input.serverSettings.codewebMcp ? { [CODEWEB_MCP_SERVER_NAME]: {
215819
+ ...resolveCodewebMcpEnabled({
215820
+ grant: input.codewebMcpGrant,
215821
+ setting: input.serverSettings.codewebMcp
215822
+ }) ? { [CODEWEB_MCP_SERVER_NAME]: {
215764
215823
  type: "http",
215765
215824
  url: CODEWEB_MCP_SERVER_URL
215766
215825
  } } : {}
@@ -215788,18 +215847,11 @@ function createCmmdPatResolver(threadId) {
215788
215847
  return readCommandAccessTokenForSession(ownerSessionId);
215789
215848
  };
215790
215849
  }
215791
- /**
215792
- * Fetches CMMD's permitted Sidekick-tool catalog once for this session, via
215793
- * the resolver shared with `CodexAdapter.ts` (`resolveCmmdSidekickToolCatalog`).
215794
- *
215795
- * The MCP server's tool list must be static at construction time, so the
215796
- * catalog is fetched here rather than lazily inside a tool call. A failure
215797
- * (no CMMD session, no permitted tools, upstream error) just means the
215798
- * server is omitted this turn — CMMD remains free to grant a different
215799
- * catalog on the next one.
215800
- */
215801
215850
  function resolveCmmdSidekickTools(resolveJwt, threadId) {
215802
- return require_Schema$1.promise(() => resolveCmmdSidekickToolCatalog(resolveJwt, threadId)).pipe(require_Schema$1.map((resolved) => resolved.tools.length > 0 ? resolved : null));
215851
+ return require_Schema$1.promise(() => resolveCmmdSidekickToolCatalog(resolveJwt, threadId)).pipe(require_Schema$1.map((resolved) => ({
215852
+ registration: resolved.tools.length > 0 ? resolved : null,
215853
+ codewebMcpGrant: resolved.codewebMcp
215854
+ })));
215803
215855
  }
215804
215856
  function buildDevServerToolDeps(input) {
215805
215857
  return {
@@ -217985,7 +218037,7 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217985
218037
  readThreadAuth: (targetThreadId) => runWithSqlRuntimeContext(readThreadAuthSession(targetThreadId)),
217986
218038
  resolveJwt: (options) => runWithSqlRuntimeContext(resolveCmmdJwtForSession(options))
217987
218039
  });
217988
- const cmmdSidekickTools = yield* resolveCmmdSidekickTools(resolveCmmdSidekickJwtForSession, threadId);
218040
+ const cmmdSidekickResolution = yield* resolveCmmdSidekickTools(resolveCmmdSidekickJwtForSession, threadId);
217989
218041
  const devServerToolDeps = buildDevServerToolDeps({
217990
218042
  threadDevRuntime,
217991
218043
  devTunnel,
@@ -218050,7 +218102,8 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
218050
218102
  computerManager,
218051
218103
  resolveCmmdPat,
218052
218104
  devServerToolDeps,
218053
- cmmdSidekickTools,
218105
+ cmmdSidekickTools: cmmdSidekickResolution.registration,
218106
+ codewebMcpGrant: cmmdSidekickResolution.codewebMcpGrant,
218054
218107
  runBrokeredGitHubTool,
218055
218108
  serverSettings
218056
218109
  }), threadId);
@@ -222859,14 +222912,18 @@ function formatConfigOptionValue(value) {
222859
222912
  /**
222860
222913
  * Builds the `mcpServers` payload for session/new and session/load.
222861
222914
  *
222862
- * CodeWeb (ADR-011) is injected only when the setting is relayed as enabled
222863
- * AND the agent advertised `mcpCapabilities.http` in initialize (the schema
222915
+ * CodeWeb (ADR-011) is injected only when CMMD's grant (primary control,
222916
+ * CMMD#4882) or, absent a grant, the relayed local setting enables it, AND
222917
+ * the agent advertised `mcpCapabilities.http` in initialize (the schema
222864
222918
  * defaults it to false). Sending an http entry to an agent that cannot
222865
222919
  * consume it would at best be dropped, at worst error the session, so both
222866
222920
  * gates must pass; anything else stays an empty payload.
222867
222921
  */
222868
222922
  function buildAcpMcpServers(input) {
222869
- if (!input.codewebMcpEnabled) return [];
222923
+ if (!resolveCodewebMcpEnabled({
222924
+ grant: input.codewebMcpGrant,
222925
+ setting: input.codewebMcpEnabled
222926
+ })) return [];
222870
222927
  if (input.agentCapabilities?.mcpCapabilities?.http !== true) return [];
222871
222928
  return [{
222872
222929
  type: "http",
@@ -223022,6 +223079,7 @@ const makeAcpSessionRuntime = (options) => require_Schema$1.gen(function* () {
223022
223079
  let sessionSetupResult;
223023
223080
  const mcpServers = buildAcpMcpServers({
223024
223081
  codewebMcpEnabled: options.codewebMcp === true,
223082
+ ...options.codewebMcpGrant ? { codewebMcpGrant: options.codewebMcpGrant } : {},
223025
223083
  agentCapabilities: initializeResult.agentCapabilities
223026
223084
  });
223027
223085
  if (options.resumeSessionId) {
@@ -240983,8 +241041,13 @@ var SpriteTaskHoldManager = class {
240983
241041
  activeTurnIds() {
240984
241042
  return [...this.#holds.keys()];
240985
241043
  }
240986
- async acquire(turnId, threadId = null) {
241044
+ /**
241045
+ * `maxRefreshes` bounds a hold that has no terminal event of its own, such as a launch
241046
+ * whose turn may never start. When the budget is spent the hold releases itself.
241047
+ */
241048
+ async acquire(turnId, threadId = null, options = {}) {
240987
241049
  if (this.#holds.has(turnId)) return;
241050
+ let refreshesLeft = options.maxRefreshes ?? Number.POSITIVE_INFINITY;
240988
241051
  const name = spriteTaskHoldName(this.#config.namePrefix, turnId);
240989
241052
  const hold = {
240990
241053
  name,
@@ -241008,6 +241071,11 @@ var SpriteTaskHoldManager = class {
241008
241071
  if (hold.released || this.#holds.get(turnId) !== hold) return;
241009
241072
  hold.refresher = this.#timer.setInterval(() => {
241010
241073
  if (hold.inFlight.size > 0) return;
241074
+ if (refreshesLeft <= 0) {
241075
+ if (this.#holds.get(turnId) === hold) this.release(turnId);
241076
+ return;
241077
+ }
241078
+ refreshesLeft -= 1;
241011
241079
  const refresh = this.#transport.put(name, 300).catch((cause) => this.#onError(`failed to refresh sprite task hold for turn ${turnId}`, cause));
241012
241080
  hold.inFlight.add(refresh);
241013
241081
  refresh.finally(() => hold.inFlight.delete(refresh));
@@ -287071,21 +287139,6 @@ var ProviderRuntimeIngestionService = class extends require_Schema$1.Service()("
287071
287139
  //#region src/orchestration/Services/PlanTaskCreationReactor.ts
287072
287140
  var PlanTaskCreationReactor = class extends require_Schema$1.Service()("forge/orchestration/Services/PlanTaskCreationReactor") {};
287073
287141
  //#endregion
287074
- //#region src/orchestration/Services/SpriteTaskHoldReactor.ts
287075
- /**
287076
- * SpriteTaskHoldReactor - keeps a Fly.io Sprite awake while a turn is running.
287077
- *
287078
- * Reacts to provider turn lifecycle events and holds the sprite's run open for
287079
- * the duration of each turn. See `sprite/spriteTaskHold.ts` for the measurements
287080
- * and the ordering rule this depends on.
287081
- *
287082
- * @module SpriteTaskHoldReactor
287083
- */
287084
- /**
287085
- * SpriteTaskHoldReactor - Service tag for the sprite task hold worker.
287086
- */
287087
- var SpriteTaskHoldReactor = class extends require_Schema$1.Service()("forge/orchestration/Services/SpriteTaskHoldReactor") {};
287088
- //#endregion
287089
287142
  //#region src/persistence/Services/ProjectionProjects.ts
287090
287143
  /**
287091
287144
  * ProjectionProjectRepository - Projection repository interface for projects.
@@ -293635,34 +293688,67 @@ const CheckpointReactorLive = require_Schema$1.effect(CheckpointReactor, make$7)
293635
293688
  * it is provider-agnostic: codex, claude and every other adapter emit
293636
293689
  * `turn.started` / `turn.completed` through `ProviderService.streamEvents`.
293637
293690
  */
293691
+ /**
293692
+ * A launch hold refreshes every 60 s, so this budget is about ten minutes. A launch whose turn
293693
+ * never starts, because the provider refused it or the process died first, has no event to
293694
+ * release it. The budget is what stops that hold from keeping the sprite billing.
293695
+ */
293696
+ const LAUNCH_HOLD_MAX_REFRESHES = 10;
293697
+ /** The hold id of a thread's launch. One launch per thread, so the thread names it. */
293698
+ const launchHoldId = (threadId) => `launch-${threadId}`;
293638
293699
  const make$6 = require_Schema$1.gen(function* () {
293639
293700
  const providerService = yield* ProviderService;
293640
293701
  const manager = makeSpriteTaskHoldManager(process.env, (message, cause) => {
293641
293702
  require_Schema$1.runFork(require_Schema$1.logWarning(message, { cause: cause instanceof Error ? cause.message : cause }));
293642
293703
  });
293704
+ const launchCallers = /* @__PURE__ */ new Map();
293705
+ const start = require_Schema$1.fn("start")(function* () {
293706
+ if (manager === null) return;
293707
+ yield* require_Schema$1.forkScoped(require_Path.runForEach(providerService.streamEvents, (event) => {
293708
+ if (event.type === "turn.started") {
293709
+ const turnId = event.turnId;
293710
+ if (turnId === void 0) return require_Schema$1.void_;
293711
+ return require_Schema$1.promise(async () => {
293712
+ await manager.acquire(String(turnId), String(event.threadId));
293713
+ launchCallers.delete(String(event.threadId));
293714
+ await manager.release(launchHoldId(String(event.threadId)));
293715
+ });
293716
+ }
293717
+ if (event.type === "turn.completed") {
293718
+ const turnId = event.turnId;
293719
+ if (turnId === void 0) return require_Schema$1.void_;
293720
+ return require_Schema$1.promise(() => manager.release(String(turnId)));
293721
+ }
293722
+ if (event.type === "turn.aborted") {
293723
+ const turnId = event.turnId;
293724
+ if (turnId === void 0) return require_Schema$1.void_;
293725
+ return require_Schema$1.promise(() => manager.release(String(turnId)));
293726
+ }
293727
+ if (event.type === "session.exited") {
293728
+ launchCallers.delete(String(event.threadId));
293729
+ return require_Schema$1.promise(() => manager.releaseThread(String(event.threadId)));
293730
+ }
293731
+ return require_Schema$1.void_;
293732
+ }));
293733
+ });
293734
+ /** Takes the launch hold for one more caller. The budget bounds a launch that never starts a turn. */
293735
+ const holdLaunch = (threadId) => require_Schema$1.promise(async () => {
293736
+ if (manager === null) return;
293737
+ launchCallers.set(threadId, (launchCallers.get(threadId) ?? 0) + 1);
293738
+ await manager.acquire(launchHoldId(threadId), threadId, { maxRefreshes: LAUNCH_HOLD_MAX_REFRESHES });
293739
+ });
293740
+ /** Lets one caller go. The hold itself is released when the last caller has let go. */
293741
+ const releaseLaunch = (threadId) => require_Schema$1.promise(async () => {
293742
+ if (manager === null) return;
293743
+ const remaining = (launchCallers.get(threadId) ?? 1) - 1;
293744
+ if (remaining > 0) return void launchCallers.set(threadId, remaining);
293745
+ launchCallers.delete(threadId);
293746
+ await manager.release(launchHoldId(threadId));
293747
+ });
293643
293748
  return {
293644
- start: require_Schema$1.fn("start")(function* () {
293645
- if (manager === null) return;
293646
- yield* require_Schema$1.forkScoped(require_Path.runForEach(providerService.streamEvents, (event) => {
293647
- if (event.type === "turn.started") {
293648
- const turnId = event.turnId;
293649
- if (turnId === void 0) return require_Schema$1.void_;
293650
- return require_Schema$1.promise(() => manager.acquire(String(turnId), String(event.threadId)));
293651
- }
293652
- if (event.type === "turn.completed") {
293653
- const turnId = event.turnId;
293654
- if (turnId === void 0) return require_Schema$1.void_;
293655
- return require_Schema$1.promise(() => manager.release(String(turnId)));
293656
- }
293657
- if (event.type === "turn.aborted") {
293658
- const turnId = event.turnId;
293659
- if (turnId === void 0) return require_Schema$1.void_;
293660
- return require_Schema$1.promise(() => manager.release(String(turnId)));
293661
- }
293662
- if (event.type === "session.exited") return require_Schema$1.promise(() => manager.releaseThread(String(event.threadId)));
293663
- return require_Schema$1.void_;
293664
- }));
293665
- }),
293749
+ start,
293750
+ holdLaunch,
293751
+ releaseLaunch,
293666
293752
  releaseAll: require_Schema$1.promise(() => manager === null ? Promise.resolve() : manager.releaseAll())
293667
293753
  };
293668
293754
  });
@@ -297808,7 +297894,7 @@ function resolveBuildCommitFromEnv(env) {
297808
297894
  * environment descriptor down instead of reporting an honest "unknown".
297809
297895
  */
297810
297896
  function readBakedBuildCommit() {
297811
- return "f42e124b2bff6bb73499fe42a9e44411a8961e1c";
297897
+ return "8ada8bfccfb8a4b0827961f304821e7b11249865";
297812
297898
  }
297813
297899
  async function resolveServerBuildCommit(input) {
297814
297900
  if (isFullCommitSha(input.baked)) return input.baked;