@cmmd-center/forge 0.13.90 → 0.13.92

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.mjs CHANGED
@@ -17793,6 +17793,7 @@ const OrchestrationThread = Struct({
17793
17793
  parentThreadId: NullOr(ThreadId).pipe(withDecodingDefault(succeed(null))),
17794
17794
  status: ThreadStatus.pipe(withDecodingDefault(succeed("idle"))),
17795
17795
  workerRole: NullOr(WorkerRole).pipe(withDecodingDefault(succeed(null))),
17796
+ workSessionId: optionalKey(NullOr(String$1)),
17796
17797
  pendingAutoCreatePrompt: optional$4(NullOr(AutoCreateTaskPrompt)).pipe(withDecodingDefault(succeed(null))),
17797
17798
  goal: NullOr(TrimmedNonEmptyString).pipe(withDecodingDefault(succeed(null))),
17798
17799
  titleRegeneration: NullOr(ThreadTitleRegeneration).pipe(withDecodingDefault(succeed(null))),
@@ -17940,6 +17941,15 @@ const ProjectDeleteCommand = Struct({
17940
17941
  commandId: CommandId,
17941
17942
  projectId: ProjectId
17942
17943
  });
17944
+ /**
17945
+ * The CMMD Work Session id a command may carry (forge#3466 review, nit 9).
17946
+ * CMMD's capability broker requires 1 to 256 characters; bounding it here
17947
+ * rejects a malformed id at the door instead of letting decide-time code find
17948
+ * out. Command-only: the read model and its events keep decoding a plain
17949
+ * string so a row or a journaled event written before this bound existed
17950
+ * still loads.
17951
+ */
17952
+ const CommandWorkSessionId = TrimmedNonEmptyString.check(isMaxLength(256));
17943
17953
  const ThreadCreateCommand = Struct({
17944
17954
  type: Literal("thread.create"),
17945
17955
  commandId: CommandId,
@@ -17953,6 +17963,7 @@ const ThreadCreateCommand = Struct({
17953
17963
  worktreePath: NullOr(TrimmedNonEmptyString),
17954
17964
  anchorKind: optional$4(CmmdAnchorKind),
17955
17965
  anchorCmmdId: optional$4(NullOr(String$1)),
17966
+ workSessionId: optional$4(NullOr(CommandWorkSessionId)),
17956
17967
  goal: optional$4(NullOr(TrimmedNonEmptyString)),
17957
17968
  linkedPullRequest: optional$4(NullOr(LinkedPullRequest)),
17958
17969
  kind: optional$4(ThreadKind),
@@ -18062,7 +18073,8 @@ const ThreadTurnStartCommand = Struct({
18062
18073
  anchorCmmdId: optional$4(NullOr(String$1)),
18063
18074
  kind: optional$4(ThreadKind),
18064
18075
  parentThreadId: optional$4(NullOr(ThreadId)),
18065
- createdAt: IsoDateTime
18076
+ createdAt: IsoDateTime,
18077
+ workSessionId: optional$4(NullOr(CommandWorkSessionId))
18066
18078
  });
18067
18079
  const ClientThreadTurnStartCommand = Struct({
18068
18080
  type: Literal("thread.turn.start"),
@@ -18085,7 +18097,8 @@ const ClientThreadTurnStartCommand = Struct({
18085
18097
  anchorCmmdId: optional$4(NullOr(String$1)),
18086
18098
  kind: optional$4(ThreadKind),
18087
18099
  parentThreadId: optional$4(NullOr(ThreadId)),
18088
- createdAt: IsoDateTime
18100
+ createdAt: IsoDateTime,
18101
+ workSessionId: optional$4(NullOr(CommandWorkSessionId))
18089
18102
  });
18090
18103
  const ThreadTurnInterruptCommand = Struct({
18091
18104
  type: Literal("thread.turn.interrupt"),
@@ -18361,6 +18374,7 @@ const ThreadCreatedPayload$1 = Struct({
18361
18374
  settledOverride: optional$4(SettledOverride),
18362
18375
  anchorKind: CmmdAnchorKind.pipe(withDecodingDefault(succeed("scratch"))),
18363
18376
  anchorCmmdId: NullOr(String$1).pipe(withDecodingDefault(succeed(null))),
18377
+ workSessionId: optionalKey(NullOr(String$1)),
18364
18378
  goal: optionalKey(NullOr(TrimmedNonEmptyString)),
18365
18379
  linkedPullRequest: optionalKey(NullOr(LinkedPullRequest)),
18366
18380
  kind: ThreadKind.pipe(withDecodingDefault(succeed("master"))),
@@ -18402,6 +18416,7 @@ const ThreadMetaUpdatedPayload$1 = Struct({
18402
18416
  status: optional$4(ThreadStatus),
18403
18417
  parentThreadId: optional$4(NullOr(ThreadId)),
18404
18418
  workerRole: optional$4(NullOr(WorkerRole)),
18419
+ workSessionId: optional$4(NullOr(String$1)),
18405
18420
  goal: optional$4(NullOr(TrimmedNonEmptyString)),
18406
18421
  linkedPullRequest: optional$4(NullOr(LinkedPullRequest)),
18407
18422
  regenerateTitle: optional$4(Literal(true)),
@@ -66005,7 +66020,7 @@ function normalizeNumberish(value) {
66005
66020
  }
66006
66021
  //#endregion
66007
66022
  //#region package.json
66008
- var version$1 = "0.13.90";
66023
+ var version$1 = "0.13.92";
66009
66024
  //#endregion
66010
66025
  //#region src/sentry.ts
66011
66026
  const SERVER_APP_NAME = "forge-server";
@@ -87586,7 +87601,8 @@ const brokerScopes = new Set([
87586
87601
  "github",
87587
87602
  "provider",
87588
87603
  "package",
87589
- "scm"
87604
+ "scm",
87605
+ "sidekick"
87590
87606
  ]);
87591
87607
  const defaultOwnerId = "interactive";
87592
87608
  const authorities = /* @__PURE__ */ new Map();
@@ -87740,6 +87756,19 @@ function readRuntimeBrokerAuthorityForRuntime(input) {
87740
87756
  }
87741
87757
  //#endregion
87742
87758
  //#region src/runtimeAuth/runtimeBrokerIdentity.ts
87759
+ /**
87760
+ * Whether this process is a CMMD-managed per-project cloud runtime (a
87761
+ * sprite), never a local Forge desktop or dev process. Shared by
87762
+ * `GitHubBroker.resolveRuntimeGrant` and
87763
+ * `cmmdWorkSessionCapabilityBroker.resolveWorkSessionSidekickRuntimeGrant`
87764
+ * (forge#3466 review, should-fix 4) so the two runtime-grant resolvers gate
87765
+ * on the same runtime-role signal instead of each inferring it separately.
87766
+ * Mirrors the env parsing `providerEgressToken.ts` and `ServerEnvironment.ts`
87767
+ * already use for the same signal.
87768
+ */
87769
+ function isCmmdCloudRuntimeModeEnv() {
87770
+ return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
87771
+ }
87743
87772
  function classifyRuntimeBrokerIdentity(input) {
87744
87773
  const machineId = input.machineId?.trim();
87745
87774
  const credentialEpoch = input.credentialEpoch;
@@ -90764,9 +90793,6 @@ var GitHubBroker = class {
90764
90793
  return validateGitBranchName(branch) ?? branch;
90765
90794
  }
90766
90795
  };
90767
- function isCmmdCloudRuntimeModeEnv() {
90768
- return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
90769
- }
90770
90796
  function inferGitHubBrokerRuntimeRole() {
90771
90797
  if (isCmmdCloudRuntimeModeEnv()) return "user-vm";
90772
90798
  return "local";
@@ -115274,7 +115300,7 @@ const postgresSchemaStatements = [
115274
115300
  "CREATE TABLE IF NOT EXISTS checkpoint_diff_blobs (\n thread_id TEXT NOT NULL,\n from_turn_count INTEGER NOT NULL,\n to_turn_count INTEGER NOT NULL,\n diff TEXT NOT NULL,\n created_at TEXT NOT NULL,\n UNIQUE (thread_id, from_turn_count, to_turn_count)\n )",
115275
115301
  "CREATE TABLE IF NOT EXISTS provider_session_runtime (\n thread_id TEXT PRIMARY KEY,\n provider_name TEXT NOT NULL,\n adapter_key TEXT NOT NULL,\n runtime_mode TEXT NOT NULL DEFAULT 'full-access',\n status TEXT NOT NULL,\n last_seen_at TEXT NOT NULL,\n resume_cursor_json TEXT,\n runtime_payload_json TEXT\n )",
115276
115302
  "CREATE TABLE IF NOT EXISTS projection_projects (\n project_id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n workspace_root TEXT NOT NULL,\n scripts_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n deleted_at TEXT\n , default_model_selection_json TEXT, default_thread_env_mode TEXT, cmmd_registration_json TEXT)",
115277
- "CREATE TABLE IF NOT EXISTS projection_threads (\n thread_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n title TEXT NOT NULL,\n branch TEXT,\n worktree_path TEXT,\n latest_turn_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n deleted_at TEXT\n , runtime_mode TEXT NOT NULL DEFAULT 'full-access', interaction_mode TEXT NOT NULL DEFAULT 'default', model_selection_json TEXT, archived_at TEXT, latest_user_message_at TEXT, pending_approval_count INTEGER NOT NULL DEFAULT 0, pending_user_input_count INTEGER NOT NULL DEFAULT 0, has_actionable_proposed_plan INTEGER NOT NULL DEFAULT 0, kind TEXT NOT NULL DEFAULT 'master', parent_thread_id TEXT, status TEXT NOT NULL DEFAULT 'idle', anchor_kind TEXT NOT NULL DEFAULT 'scratch', anchor_cmmd_id TEXT, worker_role TEXT DEFAULT NULL, goal TEXT, pending_auto_create_prompt_json TEXT, title_regeneration_json TEXT, rate_limit_status_json TEXT, linked_pull_request_json TEXT, settled_at TEXT, settled_override TEXT)",
115303
+ "CREATE TABLE IF NOT EXISTS projection_threads (\n thread_id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n title TEXT NOT NULL,\n branch TEXT,\n worktree_path TEXT,\n latest_turn_id TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL,\n deleted_at TEXT\n , runtime_mode TEXT NOT NULL DEFAULT 'full-access', interaction_mode TEXT NOT NULL DEFAULT 'default', model_selection_json TEXT, archived_at TEXT, latest_user_message_at TEXT, pending_approval_count INTEGER NOT NULL DEFAULT 0, pending_user_input_count INTEGER NOT NULL DEFAULT 0, has_actionable_proposed_plan INTEGER NOT NULL DEFAULT 0, kind TEXT NOT NULL DEFAULT 'master', parent_thread_id TEXT, status TEXT NOT NULL DEFAULT 'idle', anchor_kind TEXT NOT NULL DEFAULT 'scratch', anchor_cmmd_id TEXT, worker_role TEXT DEFAULT NULL, goal TEXT, pending_auto_create_prompt_json TEXT, title_regeneration_json TEXT, rate_limit_status_json TEXT, linked_pull_request_json TEXT, settled_at TEXT, settled_override TEXT, work_session_id TEXT)",
115278
115304
  "CREATE TABLE IF NOT EXISTS projection_thread_messages (\n message_id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n turn_id TEXT,\n role TEXT NOT NULL,\n text TEXT NOT NULL,\n is_streaming INTEGER NOT NULL,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n , attachments_json TEXT)",
115279
115305
  "CREATE TABLE IF NOT EXISTS projection_thread_activities (\n activity_id TEXT PRIMARY KEY,\n thread_id TEXT NOT NULL,\n turn_id TEXT,\n tone TEXT NOT NULL,\n kind TEXT NOT NULL,\n summary TEXT NOT NULL,\n payload_json TEXT NOT NULL,\n created_at TEXT NOT NULL\n , sequence INTEGER)",
115280
115306
  "CREATE TABLE IF NOT EXISTS projection_thread_sessions (\n thread_id TEXT PRIMARY KEY,\n status TEXT NOT NULL,\n provider_name TEXT,\n provider_session_id TEXT,\n provider_thread_id TEXT,\n active_turn_id TEXT,\n last_error TEXT,\n updated_at TEXT NOT NULL\n , runtime_mode TEXT NOT NULL DEFAULT 'full-access')",
@@ -115595,7 +115621,8 @@ const postgresUpgradeMigrations = new Map([
115595
115621
  LIMIT 1
115596
115622
  )
115597
115623
  WHERE model_selection_json IS NULL`
115598
- ]]
115624
+ ]],
115625
+ [77, [`ALTER TABLE projection_threads ADD COLUMN IF NOT EXISTS work_session_id TEXT`]]
115599
115626
  ]);
115600
115627
  function requirePostgresUpgradeStatements(version) {
115601
115628
  const statements = postgresUpgradeMigrations.get(version);
@@ -115621,15 +115648,15 @@ const runPostgresMigrations = fn$1("runPostgresMigrations")(function* () {
115621
115648
  ORDER BY version DESC
115622
115649
  LIMIT 1
115623
115650
  `)[0]?.version ?? 0;
115624
- if (currentVersion > 76) return yield* die$1(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 76.`));
115651
+ if (currentVersion > 77) return yield* die$1(/* @__PURE__ */ new Error(`Forge Postgres schema ${currentVersion} is newer than server schema 77.`));
115625
115652
  if (currentVersion === 0) {
115626
115653
  for (const statement of postgresSchemaStatements) yield* sql.unsafe(statement);
115627
115654
  yield* sql`
115628
115655
  INSERT INTO forge_schema_migrations (version)
115629
- VALUES (${76})
115656
+ VALUES (${77})
115630
115657
  ON CONFLICT (version) DO NOTHING
115631
115658
  `;
115632
- } else for (let version = currentVersion + 1; version <= 76; version += 1) {
115659
+ } else for (let version = currentVersion + 1; version <= 77; version += 1) {
115633
115660
  const statements = yield* sync$1(() => requirePostgresUpgradeStatements(version));
115634
115661
  for (const statement of statements) yield* sql.unsafe(statement);
115635
115662
  yield* sql`
@@ -118413,6 +118440,18 @@ var _076_ProjectionTurnsModelSelection_default = gen(function* () {
118413
118440
  `;
118414
118441
  });
118415
118442
  //#endregion
118443
+ //#region src/persistence/Migrations/077_ProjectionThreadWorkSessionId.ts
118444
+ /** Stores the CMMD Work Session id a kicked thread's root turn belongs to (forge#3466). */
118445
+ var _077_ProjectionThreadWorkSessionId_default = gen(function* () {
118446
+ const sql = yield* SqlClient;
118447
+ if (!(yield* sql`
118448
+ PRAGMA table_info(projection_threads)
118449
+ `).some((column) => column.name === "work_session_id")) yield* sql`
118450
+ ALTER TABLE projection_threads
118451
+ ADD COLUMN work_session_id TEXT
118452
+ `;
118453
+ });
118454
+ //#endregion
118416
118455
  //#region src/persistence/Migrations.ts
118417
118456
  /**
118418
118457
  * MigrationsLive - Migration runner with inline loader
@@ -118813,6 +118852,11 @@ const migrationEntries = [
118813
118852
  76,
118814
118853
  "ProjectionTurnsModelSelection",
118815
118854
  _076_ProjectionTurnsModelSelection_default
118855
+ ],
118856
+ [
118857
+ 77,
118858
+ "ProjectionThreadWorkSessionId",
118859
+ _077_ProjectionThreadWorkSessionId_default
118816
118860
  ]
118817
118861
  ];
118818
118862
  const makeMigrationLoader = (throughId) => fromRecord(Object.fromEntries(migrationEntries.filter(([id]) => throughId === void 0 || id <= throughId).map(([id, name, migration]) => [`${id}_${name}`, migration])));
@@ -152169,10 +152213,10 @@ function getOpenAiProviderEgressProxyConfig(input) {
152169
152213
  message: `Workspace OpenAI provider egress is configured, but OPENAI_BASE_URL does not point at ${OPENAI_PROVIDER_EGRESS_PATH}. Refusing to start Codex with a stale or raw OPENAI_API_KEY.`
152170
152214
  };
152171
152215
  }
152172
- function configuredRuntimeMachineId() {
152216
+ function configuredRuntimeMachineId$1() {
152173
152217
  return process.env.FORGE_CMMD_RUNTIME_MACHINE_ID?.trim() || null;
152174
152218
  }
152175
- function configuredRuntimeCredentialEpoch() {
152219
+ function configuredRuntimeCredentialEpoch$1() {
152176
152220
  const raw = process.env.FORGE_CMMD_RUNTIME_CREDENTIAL_EPOCH?.trim();
152177
152221
  return raw === void 0 ? void 0 : Number(raw);
152178
152222
  }
@@ -152243,15 +152287,15 @@ function resolveBrokerTicketOutcome(input) {
152243
152287
  function providerEgressRequiresOwnerSession() {
152244
152288
  if (!canIssueCmmdRuntimeProviderEgressToken()) return false;
152245
152289
  return classifyRuntimeBrokerIdentity({
152246
- machineId: configuredRuntimeMachineId() ?? void 0,
152247
- credentialEpoch: configuredRuntimeCredentialEpoch(),
152290
+ machineId: configuredRuntimeMachineId$1() ?? void 0,
152291
+ credentialEpoch: configuredRuntimeCredentialEpoch$1(),
152248
152292
  brokerAuthorityMode: process.env.FORGE_CMMD_RUNTIME_BROKER_AUTHORITY_MODE
152249
152293
  }).mode === "exact";
152250
152294
  }
152251
152295
  function runtimeProviderBrokerToken(input) {
152252
152296
  const identity = classifyRuntimeBrokerIdentity({
152253
- machineId: configuredRuntimeMachineId() ?? void 0,
152254
- credentialEpoch: configuredRuntimeCredentialEpoch(),
152297
+ machineId: configuredRuntimeMachineId$1() ?? void 0,
152298
+ credentialEpoch: configuredRuntimeCredentialEpoch$1(),
152255
152299
  brokerAuthorityMode: process.env.FORGE_CMMD_RUNTIME_BROKER_AUTHORITY_MODE
152256
152300
  });
152257
152301
  if (identity.mode !== "exact") return resolveBrokerTicketOutcome({
@@ -152392,8 +152436,8 @@ function runtimeCredentialFromGrant(provider, payload, controlPlaneUrl) {
152392
152436
  return deliveryBoundRuntimeCredential(provider, payload, controlPlaneUrl);
152393
152437
  }
152394
152438
  function deliveryBoundRuntimeCredential(provider, payload, controlPlaneUrl) {
152395
- const runtimeMachineId = configuredRuntimeMachineId();
152396
- const runtimeCredentialEpoch = configuredRuntimeCredentialEpoch();
152439
+ const runtimeMachineId = configuredRuntimeMachineId$1();
152440
+ const runtimeCredentialEpoch = configuredRuntimeCredentialEpoch$1();
152397
152441
  if (!runtimeMachineId || runtimeCredentialEpoch === void 0) throw new Error("Provider proxy token response was missing exact runtime authority.");
152398
152442
  const deliveryReceipt = parseProviderRuntimeDeliveryReceipt(payload.deliveryReceipt, {
152399
152443
  provider,
@@ -211535,7 +211579,7 @@ function createCodexCmmdSidekickDynamicToolHandler(session) {
211535
211579
  //#region src/cmmdData/cmmdDelegatedToolCatalog.ts
211536
211580
  const CATALOG_PATH = "/api/forge/context/sidekick/tools/catalog";
211537
211581
  const EXECUTE_PATH = "/api/forge/context/sidekick/tools/execute";
211538
- const AUTH_REQUIRED = {
211582
+ const AUTH_REQUIRED$1 = {
211539
211583
  ok: false,
211540
211584
  code: "CMMD_AUTH_REQUIRED"
211541
211585
  };
@@ -211543,11 +211587,32 @@ function upstreamErrorCode(error) {
211543
211587
  if (error && typeof error === "object" && "code" in error && typeof error.code === "string") return error.code;
211544
211588
  return "CMMD_REST_ERROR";
211545
211589
  }
211546
- async function listCatalog(resolveJwt, scope) {
211590
+ /**
211591
+ * The Work Session lane, tried before the owner-JWT lane ever runs
211592
+ * (forge#3466 review, should-fix 3). A headless sprite's Work Session thread
211593
+ * never carries an owner JWT (CMMD ADR-015), so trying that lane first only
211594
+ * spends a lookup before falling through to the same broker call every time
211595
+ * — and on a sprite in runtime mode, the owner's browser and CMMD's kick
211596
+ * authenticate into the same synthetic session, so a resolved JWT there is
211597
+ * never legitimately this Work Session's own. Reading fresh on every call,
211598
+ * not once at construction, is what makes blocker 1's fix work: session
211599
+ * start, the ~30-second renewal, and a post-`capability_invalid` re-mint all
211600
+ * land here, so a Retry that rebinds the thread mid-session is picked up on
211601
+ * its very next mint instead of being stuck asking for the session that just
211602
+ * failed. Returns null (not a failure) for a person-started thread, which
211603
+ * supplies no `readWorkSessionId` and keeps today's owner lane byte for byte.
211604
+ */
211605
+ async function resolveViaWorkSession(workSessionCapability, scope) {
211606
+ const workSessionId = workSessionCapability.readWorkSessionId ? await workSessionCapability.readWorkSessionId() : null;
211607
+ if (workSessionId === null) return null;
211608
+ return workSessionCapability.requestCapability ? workSessionCapability.requestCapability(workSessionId, scope.threadId) : AUTH_REQUIRED$1;
211609
+ }
211610
+ async function listCatalog(resolveJwt, workSessionCapability, scope) {
211611
+ const viaWorkSession = await resolveViaWorkSession(workSessionCapability, scope);
211612
+ if (viaWorkSession !== null) return viaWorkSession;
211547
211613
  const sessionId = readThreadSessionId(scope.threadId);
211548
- if (!sessionId) return AUTH_REQUIRED;
211549
- const jwt = await resolveJwt(sessionId);
211550
- if (!jwt) return AUTH_REQUIRED;
211614
+ const jwt = sessionId ? await resolveJwt(sessionId) : null;
211615
+ if (!jwt) return AUTH_REQUIRED$1;
211551
211616
  const body = { forgeThreadId: scope.threadId };
211552
211617
  if (scope.projectId !== void 0) body["projectId"] = scope.projectId;
211553
211618
  if (scope.forgeAgentId !== void 0) body["forgeAgentId"] = scope.forgeAgentId;
@@ -211597,9 +211662,9 @@ async function execute(request) {
211597
211662
  };
211598
211663
  }
211599
211664
  }
211600
- function createCmmdDelegatedToolCatalogClient(resolveJwt) {
211665
+ function createCmmdDelegatedToolCatalogClient(resolveJwt, workSessionCapability = {}) {
211601
211666
  return {
211602
- listCatalog: (scope) => listCatalog(resolveJwt, scope),
211667
+ listCatalog: (scope) => listCatalog(resolveJwt, workSessionCapability, scope),
211603
211668
  execute
211604
211669
  };
211605
211670
  }
@@ -211654,9 +211719,144 @@ function createCmmdSidekickToolSession(client, scope) {
211654
211719
  };
211655
211720
  }
211656
211721
  //#endregion
211722
+ //#region src/cmmdData/cmmdWorkSessionCapabilityBroker.ts
211723
+ /**
211724
+ * CMMD's Work Session Sidekick-capability broker client (forge#3466,
211725
+ * CMMD#5026).
211726
+ *
211727
+ * A headless sprite runs a Work Session's root thread with no owner JWT or
211728
+ * PAT: nobody signed in. `cmmdDelegatedToolCatalog.ts`'s owner-JWT lane falls
211729
+ * through to this one instead, presenting the runtime's OWN grant (never a
211730
+ * JWT, per CMMD ADR-015) plus the Work Session id CMMD stamped on the thread,
211731
+ * and getting back the same delegated Sidekick capability shape the owner
211732
+ * lane mints.
211733
+ */
211734
+ const SIDEKICK_CAPABILITY_TIMEOUT_MS = 1e4;
211735
+ const AUTH_REQUIRED = {
211736
+ ok: false,
211737
+ code: "CMMD_AUTH_REQUIRED"
211738
+ };
211739
+ function configuredRuntimeMachineId() {
211740
+ return process.env.FORGE_CMMD_RUNTIME_MACHINE_ID?.trim() || void 0;
211741
+ }
211742
+ function configuredRuntimeCredentialEpoch() {
211743
+ const raw = process.env.FORGE_CMMD_RUNTIME_CREDENTIAL_EPOCH?.trim();
211744
+ return raw === void 0 ? void 0 : Number(raw);
211745
+ }
211746
+ /**
211747
+ * The runtime's own bearer for the "sidekick" broker scope.
211748
+ *
211749
+ * Follows the same exact-machine / legacy split as
211750
+ * `GitHubBroker.resolveRuntimeGrant`, including its `runtimeRole !== "user-vm"`
211751
+ * gate (forge#3466 review, should-fix 4, via the resolver both share:
211752
+ * `isCmmdCloudRuntimeModeEnv`): an exact-machine runtime presents whatever
211753
+ * ticket CMMD already delivered into the broker-authority cache for this
211754
+ * scope (an inbound request installs it as a side effect of authenticating;
211755
+ * CMMD's runtime ticket carries `sidekick` alongside `provider`/`github`/etc,
211756
+ * so it lands there the same way). A legacy runtime presents its one
211757
+ * portable grant token. A runtime with no id, an identity that is neither
211758
+ * exact nor legacy, or a role that isn't a managed CMMD cloud runtime at
211759
+ * all, has nothing to present — a local Forge with a stray
211760
+ * `FORGE_CMMD_RUNTIME_ID` left over from a legacy config must never present
211761
+ * its portable grant token as though it were a sprite.
211762
+ */
211763
+ function resolveWorkSessionSidekickRuntimeGrant() {
211764
+ if (!isCmmdCloudRuntimeModeEnv()) return null;
211765
+ const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID?.trim();
211766
+ if (!runtimeId) return null;
211767
+ const identity = classifyRuntimeBrokerIdentity({
211768
+ machineId: configuredRuntimeMachineId(),
211769
+ credentialEpoch: configuredRuntimeCredentialEpoch(),
211770
+ brokerAuthorityMode: process.env.FORGE_CMMD_RUNTIME_BROKER_AUTHORITY_MODE
211771
+ });
211772
+ if (identity.mode === "exact") {
211773
+ const authority = readRuntimeBrokerAuthorityForRuntime({
211774
+ runtimeId,
211775
+ machineId: identity.machineId,
211776
+ credentialEpoch: identity.credentialEpoch,
211777
+ requiredScope: "sidekick"
211778
+ });
211779
+ return authority ? {
211780
+ runtimeId,
211781
+ token: authority.token
211782
+ } : null;
211783
+ }
211784
+ if (identity.mode === "invalid") return null;
211785
+ const legacyToken = runtimeGrantToken();
211786
+ return legacyToken ? {
211787
+ runtimeId,
211788
+ token: legacyToken
211789
+ } : null;
211790
+ }
211791
+ function isRawSidekickCapabilityResponse(value) {
211792
+ if (typeof value !== "object" || value === null) return false;
211793
+ const record = value;
211794
+ return Array.isArray(record.tools) && typeof record.catalogVersion === "string" && typeof record.capabilitySessionId === "number" && typeof record.capabilityToken === "string" && typeof record.expiresAt === "string";
211795
+ }
211796
+ /**
211797
+ * Mints a Work Session Sidekick capability straight from CMMD, for a thread
211798
+ * whose owner JWT never resolved.
211799
+ *
211800
+ * The mint route answers a bare JSON body, never CMMD's forge-context
211801
+ * `{ data: ... }` envelope the owner-JWT catalog route uses, so this never
211802
+ * unwraps `.data`. Always resolves, never throws: a missing runtime grant, a
211803
+ * network failure, and a refusal from CMMD (the broker feature flag is off,
211804
+ * the Work Session ended, this runtime is not its starter, ...) all fold to
211805
+ * the same `CMMD_AUTH_REQUIRED` the owner-JWT lane already returns on any
211806
+ * auth failure, so callers need exactly one failure branch. The refusal's
211807
+ * own code is logged, not returned, so an operator can still tell the cases
211808
+ * apart.
211809
+ */
211810
+ async function requestWorkSessionSidekickCapability(workSessionId, threadId, deps = {}) {
211811
+ const grant = (deps.resolveRuntimeGrant ?? resolveWorkSessionSidekickRuntimeGrant)();
211812
+ if (!grant) {
211813
+ console.warn(`[cmmd-work-session-capability] no runtime grant for the sidekick scope (thread ${threadId}, work session ${workSessionId})`);
211814
+ return AUTH_REQUIRED;
211815
+ }
211816
+ const fetchImpl = deps.fetch ?? fetch;
211817
+ const url = resolveCommandAppEndpoint(`/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/sidekick-capability`);
211818
+ let response;
211819
+ try {
211820
+ response = await fetchImpl(url, {
211821
+ method: "POST",
211822
+ headers: {
211823
+ Authorization: `Bearer ${grant.token}`,
211824
+ "Content-Type": "application/json"
211825
+ },
211826
+ body: JSON.stringify({ workSessionId }),
211827
+ signal: AbortSignal.timeout(SIDEKICK_CAPABILITY_TIMEOUT_MS)
211828
+ });
211829
+ } catch (error) {
211830
+ console.warn("[cmmd-work-session-capability] broker request failed:", error instanceof Error ? error.message : String(error));
211831
+ return AUTH_REQUIRED;
211832
+ }
211833
+ if (!response.ok) {
211834
+ const body = await response.json().catch(() => null);
211835
+ const code = body && typeof body === "object" && typeof body.code === "string" ? body.code : `HTTP_${response.status}`;
211836
+ console.warn(`[cmmd-work-session-capability] broker refused the mint (${code})`);
211837
+ return AUTH_REQUIRED;
211838
+ }
211839
+ const data = await response.json().catch(() => null);
211840
+ if (!isRawSidekickCapabilityResponse(data)) {
211841
+ console.warn("[cmmd-work-session-capability] broker returned an unexpected mint response");
211842
+ return AUTH_REQUIRED;
211843
+ }
211844
+ return {
211845
+ ok: true,
211846
+ tools: data.tools,
211847
+ catalogVersion: data.catalogVersion,
211848
+ capabilitySessionId: data.capabilitySessionId,
211849
+ capabilityToken: data.capabilityToken,
211850
+ expiresAt: data.expiresAt
211851
+ };
211852
+ }
211853
+ //#endregion
211657
211854
  //#region src/cmmdData/resolveCmmdSidekickToolCatalog.ts
211658
- async function resolveCmmdSidekickToolCatalog(resolveJwt, threadId) {
211659
- const session = createCmmdSidekickToolSession(createCmmdDelegatedToolCatalogClient(resolveJwt), { threadId });
211855
+ async function resolveCmmdSidekickToolCatalog(resolveJwt, threadId, options = {}) {
211856
+ const session = createCmmdSidekickToolSession(createCmmdDelegatedToolCatalogClient(resolveJwt, {
211857
+ ...options.readWorkSessionId !== void 0 ? { readWorkSessionId: options.readWorkSessionId } : {},
211858
+ requestCapability: options.requestWorkSessionCapability ?? requestWorkSessionSidekickCapability
211859
+ }), { threadId });
211660
211860
  const catalog = await session.getCatalog();
211661
211861
  return {
211662
211862
  session,
@@ -211675,6 +211875,33 @@ async function resolveCmmdSidekickJwt(input) {
211675
211875
  ...binding?.subject ? { subject: binding.subject } : {}
211676
211876
  });
211677
211877
  }
211878
+ const decodeWorkSessionIdRows = decodeUnknownEffect(ArraySchema(Struct({ workSessionId: NullOr(String$1) })));
211879
+ /**
211880
+ * Reads the CMMD Work Session id CMMD stamped on a thread's `thread.create`,
211881
+ * straight from the projected thread row (forge#3466). Null for a
211882
+ * person-started thread, for a thread projected before this field existed,
211883
+ * and for a thread id with no projected row at all.
211884
+ *
211885
+ * A minimal raw-SQL read against `projection_threads`, deliberately not
211886
+ * routed through `ProjectionThreadRepository`: the adapters that need this
211887
+ * (`ClaudeAdapter.ts`, `CodexAdapter.ts`) already capture only
211888
+ * `SqlClient.SqlClient` at session-build time (see `readThreadAuthSession`
211889
+ * in `GitHubAgentToolStore.ts` for the established pattern this follows), and
211890
+ * widening that capture to a second service for one column is not worth the
211891
+ * risk to every other call sharing it.
211892
+ */
211893
+ function readThreadWorkSessionId(threadId) {
211894
+ return gen(function* () {
211895
+ return (yield* decodeWorkSessionIdRows(yield* (yield* SqlClient)`
211896
+ SELECT work_session_id AS "workSessionId"
211897
+ FROM projection_threads
211898
+ WHERE thread_id = ${threadId}
211899
+ LIMIT 1
211900
+ `))[0]?.workSessionId ?? null;
211901
+ }).pipe(tapError((error) => sync$1(() => {
211902
+ console.warn(`[cmmd-work-session-thread-lookup] failed to read work_session_id for thread ${threadId}:`, error instanceof Error ? error.message : String(error));
211903
+ })), orElseSucceed(() => null));
211904
+ }
211678
211905
  //#endregion
211679
211906
  //#region ../../packages/shared/src/computerUsePermission.ts
211680
211907
  /**
@@ -213264,13 +213491,14 @@ const makeCodexAdapter = fn$1("makeCodexAdapter")(function* (options) {
213264
213491
  const jwt = readCommandJwtForSession(binding.sessionId);
213265
213492
  return jwt ? { jwt } : null;
213266
213493
  } });
213494
+ const readCmmdWorkSessionId = () => runWithRuntimeContext(readThreadWorkSessionId(input.threadId));
213267
213495
  const { session: cmmdSidekickToolSession, tools: cmmdSidekickTools } = yield* promise(() => resolveCmmdSidekickToolCatalog((sessionId) => resolveCmmdSidekickJwt({
213268
213496
  threadId: input.threadId,
213269
213497
  sessionId,
213270
213498
  logPrefix: "codex-cmmd-sidekick",
213271
213499
  readThreadAuth: (threadId) => runWithRuntimeContext(readThreadAuthSession(threadId)),
213272
213500
  resolveJwt: (options) => runWithRuntimeContext(resolveCmmdJwtForSession(options))
213273
- }), input.threadId));
213501
+ }), input.threadId, { readWorkSessionId: readCmmdWorkSessionId }));
213274
213502
  const cmmdSidekickToolNames = new Set(cmmdSidekickTools.map((tool) => tool.name));
213275
213503
  const devServerToolDeps = {
213276
213504
  threadDevRuntime,
@@ -215382,6 +215610,21 @@ const SIGNIN_REQUIRED = {
215382
215610
  action: "notify_human",
215383
215611
  message: "Not signed in to CMMD for this session. Continue from repository evidence and tell the user to sign in to CMMD to enable project context."
215384
215612
  };
215613
+ /**
215614
+ * Same failure as {@link SIGNIN_REQUIRED} (no CMMD sign-in exists for this
215615
+ * session), but for a headless Work Session runtime that was never going to
215616
+ * have one: nobody is present to sign in (CMMD ADR-015). Telling the agent
215617
+ * to ask a human to sign in is wrong there. When CMMD's Sidekick capability
215618
+ * broker minted delegated tools for this Work Session (forge#3466,
215619
+ * CMMD#5026), point at them instead of dead-ending.
215620
+ */
215621
+ const WORK_SESSION_CONTEXT_VIA_DELEGATED_TOOLS = {
215622
+ ok: false,
215623
+ code: "signin_required",
215624
+ retryable: false,
215625
+ action: "skip",
215626
+ message: "No CMMD sign-in for this session, but this Work Session has delegated Sidekick tools. Call cmmd_get_agent_plan (or get_task) from cmmd-sidekick-tools for the task packet and current context, then continue from there."
215627
+ };
215385
215628
  /** Map a thrown CMMD client error to a recoverable envelope. */
215386
215629
  function errorEnvelope(err) {
215387
215630
  if (isCmmdMcpAuthError$2(err)) return {
@@ -215444,12 +215687,13 @@ function projectIdToToolArg(projectId) {
215444
215687
  * mutation arg. The `action` arg of CMMD's multiplexed tools is hardcoded to a
215445
215688
  * read inside contextBuilder and never surfaced to the LLM.
215446
215689
  */
215447
- function makeCmmdContextHandlers(resolvePat) {
215690
+ function makeCmmdContextHandlers(resolvePat, options = {}) {
215691
+ const noPatEnvelope = options.hasWorkSessionPacketTool ? WORK_SESSION_CONTEXT_VIA_DELEGATED_TOOLS : SIGNIN_REQUIRED;
215448
215692
  return {
215449
215693
  getCurrentContext: async () => {
215450
215694
  try {
215451
215695
  const pat = await resolvePat();
215452
- if (!pat) return envelope(SIGNIN_REQUIRED);
215696
+ if (!pat) return envelope(noPatEnvelope);
215453
215697
  const snapshot = await buildCurrentContextSnapshot(pat, {
215454
215698
  maxCampaigns: 3,
215455
215699
  maxProjects: 5,
@@ -215482,7 +215726,7 @@ function makeCmmdContextHandlers(resolvePat) {
215482
215726
  message: "Provide a non-empty `query` to search the CMMD knowledge base."
215483
215727
  });
215484
215728
  const pat = await resolvePat();
215485
- if (!pat) return envelope(SIGNIN_REQUIRED);
215729
+ if (!pat) return envelope(noPatEnvelope);
215486
215730
  const effectiveLimit = clampLimit(limit, DEFAULT_BRAIN_LIMIT, MAX_BRAIN_LIMIT);
215487
215731
  const result = await callCmmdTool$2(pat, "search_brain_documents", {
215488
215732
  query: trimmedQuery,
@@ -215536,7 +215780,7 @@ function makeCmmdContextHandlers(resolvePat) {
215536
215780
  message: "Provide a `projectId` (from cmmd_get_current_context data.projects[].id)."
215537
215781
  });
215538
215782
  const pat = await resolvePat();
215539
- if (!pat) return envelope(SIGNIN_REQUIRED);
215783
+ if (!pat) return envelope(noPatEnvelope);
215540
215784
  const result = await callCmmdTool$2(pat, "list_board_tasks", { project_id: projectIdToToolArg(trimmedId) });
215541
215785
  if (result.isError) return envelope(isErrorEnvelope(result));
215542
215786
  const text = extractMcpToolText$2(result);
@@ -215565,8 +215809,8 @@ function makeCmmdContextHandlers(resolvePat) {
215565
215809
  * when the user is not authenticated with CMMD. Called per
215566
215810
  * tool invocation so a mid-session token refresh is picked up.
215567
215811
  */
215568
- function createCmmdContextMcpServer(threadId, resolvePat) {
215569
- const handlers = makeCmmdContextHandlers(resolvePat);
215812
+ function createCmmdContextMcpServer(threadId, resolvePat, options = {}) {
215813
+ const handlers = makeCmmdContextHandlers(resolvePat, options);
215570
215814
  return z1e({
215571
215815
  name: "cmmd-context",
215572
215816
  tools: [
@@ -216089,7 +216333,7 @@ function buildClaudeMcpServers(input) {
216089
216333
  return {
216090
216334
  "forge-browser": createBrowserMcpServer(input.browserManager, input.threadId, browserToolScope(input.browserToolEnvironmentId)),
216091
216335
  "forge-dev-server": createDevServerMcpServer(input.devServerToolDeps, input.threadId),
216092
- "cmmd-context": createCmmdContextMcpServer(input.threadId, input.resolveCmmdPat),
216336
+ "cmmd-context": createCmmdContextMcpServer(input.threadId, input.resolveCmmdPat, { hasWorkSessionPacketTool: input.hasWorkSessionPacketTool }),
216093
216337
  [GITHUB_MCP_SERVER_NAME]: createGitHubMcpServer(input.runBrokeredGitHubTool, input.threadId, input.serverSettings),
216094
216338
  ...input.computerManager ? { [COMPUTER_MCP_SERVER_NAME]: createComputerMcpServer(input.computerManager, input.threadId) } : {},
216095
216339
  ...input.cmmdSidekickTools ? { "cmmd-sidekick-tools": createCmmdSidekickToolsMcpServer(input.cmmdSidekickTools.session, input.cmmdSidekickTools.tools) } : {},
@@ -216133,19 +216377,47 @@ function claudePermissionQueryOptions(permissionMode) {
216133
216377
  function createClaudePromptStream(promptQueue) {
216134
216378
  return fromQueue(promptQueue).pipe(filter$2((item) => item.type === "message"), map$7((item) => item.message), catchCause$1((cause) => hasInterruptsOnly(cause) ? empty$5 : failCause$5(cause)), toAsyncIterable);
216135
216379
  }
216136
- function createCmmdPatResolver(threadId) {
216380
+ function createCmmdPatResolver(threadId, readWorkSessionId) {
216137
216381
  return async () => {
216382
+ if (await readWorkSessionId() !== null) return null;
216138
216383
  const ownerSessionId = readThreadSessionId(threadId);
216139
216384
  if (!ownerSessionId) return null;
216140
216385
  return readCommandAccessTokenForSession(ownerSessionId);
216141
216386
  };
216142
216387
  }
216143
- function resolveCmmdSidekickTools(resolveJwt, threadId) {
216144
- return promise(() => resolveCmmdSidekickToolCatalog(resolveJwt, threadId)).pipe(map$3((resolved) => ({
216388
+ function resolveCmmdSidekickTools(resolveJwt, threadId, readWorkSessionId) {
216389
+ return promise(() => resolveCmmdSidekickToolCatalog(resolveJwt, threadId, { readWorkSessionId })).pipe(map$3((resolved) => ({
216145
216390
  registration: resolved.tools.length > 0 ? resolved : null,
216146
216391
  codewebMcpGrant: resolved.codewebMcp
216147
216392
  })));
216148
216393
  }
216394
+ /**
216395
+ * Name of the delegated tool that returns a Work Session's task packet
216396
+ * (CMMD's Agent Plan family, ADR-029). Forge's Sidekick capability broker
216397
+ * always mounts this for a Work Session (CMMD's `forge-surface.ts` curated
216398
+ * allowlist, verified 2026-09-26), so its presence in the granted catalog is
216399
+ * exactly "a Work Session capability exists for this thread". See
216400
+ * `cmmdContextMcpServer.ts`'s no-PAT branch (forge#3466).
216401
+ */
216402
+ const WORK_SESSION_PACKET_TOOL_NAME = "cmmd_get_agent_plan";
216403
+ /**
216404
+ * Whether `cmmd-context`'s no-PAT branch should point at delegated tools
216405
+ * instead of asking the user to sign in (forge#3466 review, should-fix 5).
216406
+ *
216407
+ * Gating on `workSessionId !== null` alone, without a separate "which lane
216408
+ * minted this" tag, is sound because `cmmdDelegatedToolCatalog.ts`'s
216409
+ * `listCatalog` (should-fix 3) structurally never calls the owner-JWT lane
216410
+ * once a Work Session id is present — it returns straight from the broker
216411
+ * call or `AUTH_REQUIRED`. So a non-null `workSessionId` here can only ever
216412
+ * pair with a broker-minted registration. Before that reorder, the owner-JWT
216413
+ * catalog (the full, unnarrowed Sidekick catalog) also contains
216414
+ * `cmmd_get_agent_plan`, so checking the tool name alone told a
216415
+ * person-started thread with a resolved JWT but no PAT that it had Work
216416
+ * Session tools when it never did.
216417
+ */
216418
+ function hasWorkSessionPacketTool(workSessionId, registration) {
216419
+ return workSessionId !== null && (registration?.tools.some((tool) => tool.name === WORK_SESSION_PACKET_TOOL_NAME) ?? false);
216420
+ }
216149
216421
  function buildDevServerToolDeps(input) {
216150
216422
  return {
216151
216423
  threadDevRuntime: input.threadDevRuntime,
@@ -218321,7 +218593,8 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
218321
218593
  const runPromise = runPromiseWith(runtimeContext);
218322
218594
  const browserToolEnvironmentId = yield* resolveBrowserToolEnvironmentId();
218323
218595
  const computerManager = resolveComputerManager(serverConfig);
218324
- const resolveCmmdPat = createCmmdPatResolver(threadId);
218596
+ const readWorkSessionId = () => runWithSqlRuntimeContext(readThreadWorkSessionId(threadId));
218597
+ const resolveCmmdPat = createCmmdPatResolver(threadId, readWorkSessionId);
218325
218598
  const resolveCmmdSidekickJwtForSession = (sessionId) => resolveCmmdSidekickJwt({
218326
218599
  threadId,
218327
218600
  sessionId,
@@ -218329,7 +218602,8 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
218329
218602
  readThreadAuth: (targetThreadId) => runWithSqlRuntimeContext(readThreadAuthSession(targetThreadId)),
218330
218603
  resolveJwt: (options) => runWithSqlRuntimeContext(resolveCmmdJwtForSession(options))
218331
218604
  });
218332
- const cmmdSidekickResolution = yield* resolveCmmdSidekickTools(resolveCmmdSidekickJwtForSession, threadId);
218605
+ const workSessionId = yield* promise(readWorkSessionId);
218606
+ const cmmdSidekickResolution = yield* resolveCmmdSidekickTools(resolveCmmdSidekickJwtForSession, threadId, readWorkSessionId);
218333
218607
  const devServerToolDeps = buildDevServerToolDeps({
218334
218608
  threadDevRuntime,
218335
218609
  devTunnel,
@@ -218414,6 +218688,7 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
218414
218688
  resolveCmmdPat,
218415
218689
  devServerToolDeps,
218416
218690
  cmmdSidekickTools: cmmdSidekickResolution.registration,
218691
+ hasWorkSessionPacketTool: hasWorkSessionPacketTool(workSessionId, cmmdSidekickResolution.registration),
218417
218692
  codewebMcpInjection,
218418
218693
  runBrokeredGitHubTool,
218419
218694
  serverSettings
@@ -287819,6 +288094,7 @@ const ProjectionThread = Struct({
287819
288094
  parentThreadId: NullOr(ThreadId).pipe(withDecodingDefault(succeed(null))),
287820
288095
  anchorKind: CmmdAnchorKind.pipe(withDecodingDefault(succeed("scratch"))),
287821
288096
  anchorCmmdId: NullOr(String$1).pipe(withDecodingDefault(succeed(null))),
288097
+ workSessionId: optionalKey(NullOr(String$1)),
287822
288098
  linkedPullRequest: optionalKey(NullOr(LinkedPullRequest)),
287823
288099
  status: ThreadStatus.pipe(withDecodingDefault(succeed("idle"))),
287824
288100
  workerRole: NullOr(WorkerRole).pipe(withDecodingDefault(succeed(null))),
@@ -298929,7 +299205,7 @@ function resolveBuildCommitFromEnv(env) {
298929
299205
  * environment descriptor down instead of reporting an honest "unknown".
298930
299206
  */
298931
299207
  function readBakedBuildCommit() {
298932
- return "9608e243c924d4030658e7cc80c7be65887b2073";
299208
+ return "00cc2d653acc035bf8de4ed64006a9af5c4d6362";
298933
299209
  }
298934
299210
  async function resolveServerBuildCommit(input) {
298935
299211
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -302559,6 +302835,7 @@ const decideOrchestrationCommand = fn$1("decideOrchestrationCommand")(function*
302559
302835
  updatedAt: command.createdAt,
302560
302836
  anchorKind: anchor.anchorKind,
302561
302837
  anchorCmmdId: anchor.anchorCmmdId,
302838
+ workSessionId: command.workSessionId ?? null,
302562
302839
  goal: command.goal ?? null,
302563
302840
  linkedPullRequest: command.linkedPullRequest ?? null,
302564
302841
  kind,
@@ -302868,7 +303145,8 @@ const decideOrchestrationCommand = fn$1("decideOrchestrationCommand")(function*
302868
303145
  const providerSwitchSelection = command.modelSelection !== void 0 && command.modelSelection.provider !== targetThread.modelSelection.provider ? command.modelSelection : void 0;
302869
303146
  const accountSelection = turnAccountSelection(targetThread.modelSelection, command.modelSelection);
302870
303147
  const turnModelSelection = providerSwitchSelection ?? accountSelection;
302871
- if (!shouldUpdateAnchor && !turnModelSelection) return [userMessageEvent, turnStartRequestedEvent];
303148
+ const shouldRebindWorkSession = command.workSessionId !== void 0 && command.workSessionId !== null && command.workSessionId !== targetThread.workSessionId;
303149
+ if (!shouldUpdateAnchor && !turnModelSelection && !shouldRebindWorkSession) return [userMessageEvent, turnStartRequestedEvent];
302872
303150
  return [
302873
303151
  {
302874
303152
  ...withEventBase({
@@ -302883,6 +303161,7 @@ const decideOrchestrationCommand = fn$1("decideOrchestrationCommand")(function*
302883
303161
  anchorKind: nextAnchor.anchorKind,
302884
303162
  anchorCmmdId: nextAnchor.anchorCmmdId,
302885
303163
  ...turnModelSelection ? { modelSelection: turnModelSelection } : {},
303164
+ ...shouldRebindWorkSession ? { workSessionId: command.workSessionId } : {},
302886
303165
  updatedAt: command.createdAt
302887
303166
  }
302888
303167
  },
@@ -303406,12 +303685,22 @@ function retainThreadActivitiesAfterRevert(activities, retainedTurnIds) {
303406
303685
  function retainThreadProposedPlansAfterRevert(proposedPlans, retainedTurnIds) {
303407
303686
  return proposedPlans.filter((proposedPlan) => proposedPlan.turnId === null || retainedTurnIds.has(proposedPlan.turnId));
303408
303687
  }
303688
+ /**
303689
+ * Orders an unsequenced activity before a sequenced one, matching the SQL
303690
+ * projection's own `ORDER BY CASE WHEN sequence IS NULL THEN 0 ELSE 1 END
303691
+ * ASC, sequence ASC`. Zero means neither operand settles the order (both
303692
+ * missing, or both present and equal), so the caller falls back to a
303693
+ * timestamp/id tiebreak.
303694
+ */
303695
+ function compareOptionalActivitySequence(left, right) {
303696
+ const leftHasSequence = left !== void 0;
303697
+ const rightHasSequence = right !== void 0;
303698
+ if (leftHasSequence !== rightHasSequence) return leftHasSequence ? 1 : -1;
303699
+ return leftHasSequence && rightHasSequence ? left - right : 0;
303700
+ }
303409
303701
  function compareThreadActivities(left, right) {
303410
- if (left.sequence !== void 0 && right.sequence !== void 0) {
303411
- if (left.sequence !== right.sequence) return left.sequence - right.sequence;
303412
- } else if (left.sequence !== void 0) return 1;
303413
- else if (right.sequence !== void 0) return -1;
303414
- return left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
303702
+ const bySequence = compareOptionalActivitySequence(left.sequence, right.sequence);
303703
+ return bySequence !== 0 ? bySequence : left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id);
303415
303704
  }
303416
303705
  function createEmptyReadModel(nowIso) {
303417
303706
  return {
@@ -303442,6 +303731,7 @@ function projectEvent(model, event) {
303442
303731
  worktreePath: payload.worktreePath,
303443
303732
  anchorKind: payload.anchorKind,
303444
303733
  anchorCmmdId: payload.anchorCmmdId,
303734
+ workSessionId: payload.workSessionId ?? null,
303445
303735
  goal: payload.goal ?? null,
303446
303736
  linkedPullRequest: payload.linkedPullRequest ?? null,
303447
303737
  titleRegeneration: null,
@@ -303522,6 +303812,7 @@ function projectEvent(model, event) {
303522
303812
  ...payload.goal !== void 0 ? { goal: payload.goal } : {},
303523
303813
  ...payload.linkedPullRequest !== void 0 ? { linkedPullRequest: payload.linkedPullRequest } : {},
303524
303814
  ...payload.titleRegeneration !== void 0 ? { titleRegeneration: payload.titleRegeneration } : {},
303815
+ ...payload.workSessionId !== void 0 ? { workSessionId: payload.workSessionId } : {},
303525
303816
  updatedAt: payload.updatedAt
303526
303817
  })
303527
303818
  })));
@@ -304572,6 +304863,7 @@ const ProjectionThreadRepositoryLive = effect(ProjectionThreadRepository, gen(fu
304572
304863
  parent_thread_id,
304573
304864
  anchor_kind,
304574
304865
  anchor_cmmd_id,
304866
+ work_session_id,
304575
304867
  linked_pull_request_json,
304576
304868
  status,
304577
304869
  worker_role,
@@ -304604,6 +304896,7 @@ const ProjectionThreadRepositoryLive = effect(ProjectionThreadRepository, gen(fu
304604
304896
  ${row.parentThreadId},
304605
304897
  ${row.anchorKind},
304606
304898
  ${row.anchorCmmdId},
304899
+ ${row.workSessionId ?? null},
304607
304900
  ${row.linkedPullRequest == null ? null : JSON.stringify(row.linkedPullRequest)},
304608
304901
  ${row.status},
304609
304902
  ${row.workerRole},
@@ -304636,6 +304929,7 @@ const ProjectionThreadRepositoryLive = effect(ProjectionThreadRepository, gen(fu
304636
304929
  parent_thread_id = excluded.parent_thread_id,
304637
304930
  anchor_kind = excluded.anchor_kind,
304638
304931
  anchor_cmmd_id = excluded.anchor_cmmd_id,
304932
+ work_session_id = excluded.work_session_id,
304639
304933
  linked_pull_request_json = excluded.linked_pull_request_json,
304640
304934
  status = excluded.status,
304641
304935
  worker_role = excluded.worker_role,
@@ -304673,6 +304967,7 @@ const ProjectionThreadRepositoryLive = effect(ProjectionThreadRepository, gen(fu
304673
304967
  parent_thread_id AS "parentThreadId",
304674
304968
  anchor_kind AS "anchorKind",
304675
304969
  anchor_cmmd_id AS "anchorCmmdId",
304970
+ work_session_id AS "workSessionId",
304676
304971
  linked_pull_request_json AS "linkedPullRequest",
304677
304972
  status,
304678
304973
  worker_role AS "workerRole",
@@ -304712,6 +305007,7 @@ const ProjectionThreadRepositoryLive = effect(ProjectionThreadRepository, gen(fu
304712
305007
  parent_thread_id AS "parentThreadId",
304713
305008
  anchor_kind AS "anchorKind",
304714
305009
  anchor_cmmd_id AS "anchorCmmdId",
305010
+ work_session_id AS "workSessionId",
304715
305011
  linked_pull_request_json AS "linkedPullRequest",
304716
305012
  status,
304717
305013
  worker_role AS "workerRole",
@@ -304893,6 +305189,14 @@ function collectThreadUserInputAttachmentRelativePaths(threadId, events) {
304893
305189
  }
304894
305190
  return relativePaths;
304895
305191
  }
305192
+ /**
305193
+ * Coalesces an optional Work Session id to the persisted default. Top-level
305194
+ * so the `??` here does not count against `applyThreadsProjection`'s own
305195
+ * complexity budget. See forge#3466.
305196
+ */
305197
+ function projectedWorkSessionId(workSessionId) {
305198
+ return workSessionId ?? null;
305199
+ }
304896
305200
  const runAttachmentSideEffects = fn$1("runAttachmentSideEffects")(function* (sideEffects) {
304897
305201
  const serverConfig = yield* service(ServerConfig);
304898
305202
  const fileSystem = yield* service(FileSystem);
@@ -305055,6 +305359,7 @@ const OrchestrationProjectionPipelineLive = effect(OrchestrationProjectionPipeli
305055
305359
  parentThreadId: event.payload.parentThreadId,
305056
305360
  anchorKind: event.payload.anchorKind,
305057
305361
  anchorCmmdId: event.payload.anchorCmmdId,
305362
+ workSessionId: projectedWorkSessionId(event.payload.workSessionId),
305058
305363
  linkedPullRequest: event.payload.linkedPullRequest ?? null,
305059
305364
  status: event.payload.status,
305060
305365
  workerRole: event.payload.workerRole,
@@ -305092,6 +305397,7 @@ const OrchestrationProjectionPipelineLive = effect(OrchestrationProjectionPipeli
305092
305397
  parentThreadId: thread.parentThreadId,
305093
305398
  anchorKind: thread.anchorKind,
305094
305399
  anchorCmmdId: thread.anchorCmmdId,
305400
+ workSessionId: projectedWorkSessionId(thread.workSessionId),
305095
305401
  linkedPullRequest: thread.linkedPullRequest ?? null,
305096
305402
  status: thread.status,
305097
305403
  workerRole: thread.workerRole,
@@ -305160,6 +305466,7 @@ const OrchestrationProjectionPipelineLive = effect(OrchestrationProjectionPipeli
305160
305466
  ...event.payload.goal !== void 0 ? { goal: event.payload.goal } : {},
305161
305467
  ...event.payload.linkedPullRequest !== void 0 ? { linkedPullRequest: event.payload.linkedPullRequest } : {},
305162
305468
  ...event.payload.titleRegeneration !== void 0 ? { titleRegeneration: event.payload.titleRegeneration } : {},
305469
+ ...event.payload.workSessionId !== void 0 ? { workSessionId: projectedWorkSessionId(event.payload.workSessionId) } : {},
305163
305470
  updatedAt: event.payload.updatedAt
305164
305471
  });
305165
305472
  return;
@@ -306158,6 +306465,16 @@ function probeProjectAvailability(fs, workspaceRoot) {
306158
306465
  function toPersistenceSqlOrDecodeError(sqlOperation, decodeOperation) {
306159
306466
  return (cause) => isSchemaError(cause) ? toPersistenceDecodeError(decodeOperation)(cause) : toPersistenceSqlError(sqlOperation)(cause);
306160
306467
  }
306468
+ /**
306469
+ * `{ workSessionId }` when the row carries one, an empty object when absent,
306470
+ * so the built thread's optionalKey field round-trips without ever getting
306471
+ * assigned an explicit `undefined` (exactOptionalPropertyTypes). Pulled out
306472
+ * of the two thread builders that call it so this file's already-flagged
306473
+ * high-complexity functions do not pick up the branch. See forge#3466.
306474
+ */
306475
+ function workSessionIdField(workSessionId) {
306476
+ return workSessionId !== void 0 ? { workSessionId } : {};
306477
+ }
306161
306478
  const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQuery, gen(function* () {
306162
306479
  const sql = yield* SqlClient;
306163
306480
  const repositoryIdentityResolver = yield* RepositoryIdentityResolver;
@@ -306210,6 +306527,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
306210
306527
  parent_thread_id AS "parentThreadId",
306211
306528
  anchor_kind AS "anchorKind",
306212
306529
  anchor_cmmd_id AS "anchorCmmdId",
306530
+ work_session_id AS "workSessionId",
306213
306531
  linked_pull_request_json AS "linkedPullRequest",
306214
306532
  status,
306215
306533
  worker_role AS "workerRole",
@@ -306248,6 +306566,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
306248
306566
  parent_thread_id AS "parentThreadId",
306249
306567
  anchor_kind AS "anchorKind",
306250
306568
  anchor_cmmd_id AS "anchorCmmdId",
306569
+ work_session_id AS "workSessionId",
306251
306570
  linked_pull_request_json AS "linkedPullRequest",
306252
306571
  status,
306253
306572
  worker_role AS "workerRole",
@@ -306352,6 +306671,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
306352
306671
  parent_thread_id AS "parentThreadId",
306353
306672
  anchor_kind AS "anchorKind",
306354
306673
  anchor_cmmd_id AS "anchorCmmdId",
306674
+ work_session_id AS "workSessionId",
306355
306675
  linked_pull_request_json AS "linkedPullRequest",
306356
306676
  status,
306357
306677
  worker_role AS "workerRole",
@@ -306684,6 +307004,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
306684
307004
  parent_thread_id AS "parentThreadId",
306685
307005
  anchor_kind AS "anchorKind",
306686
307006
  anchor_cmmd_id AS "anchorCmmdId",
307007
+ work_session_id AS "workSessionId",
306687
307008
  linked_pull_request_json AS "linkedPullRequest",
306688
307009
  status,
306689
307010
  worker_role AS "workerRole",
@@ -306996,6 +307317,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
306996
307317
  session: sessionsByThread.get(row.threadId) ?? null,
306997
307318
  anchorKind: row.anchorKind,
306998
307319
  anchorCmmdId: row.anchorCmmdId,
307320
+ ...workSessionIdField(row.workSessionId),
306999
307321
  linkedPullRequest: row.linkedPullRequest,
307000
307322
  kind: row.kind,
307001
307323
  parentThreadId: row.parentThreadId,
@@ -307245,6 +307567,7 @@ const OrchestrationInfrastructureLayerLive = mergeAll(effect(ProjectionSnapshotQ
307245
307567
  session: isSome(sessionRow) ? mapSessionRow(sessionRow.value) : null,
307246
307568
  anchorKind: threadRow.value.anchorKind,
307247
307569
  anchorCmmdId: threadRow.value.anchorCmmdId,
307570
+ ...workSessionIdField(threadRow.value.workSessionId),
307248
307571
  linkedPullRequest: threadRow.value.linkedPullRequest,
307249
307572
  kind: threadRow.value.kind,
307250
307573
  parentThreadId: threadRow.value.parentThreadId,
@@ -322276,6 +322599,11 @@ try {
322276
322599
  }
322277
322600
  }
322278
322601
  `;
322602
+ const toReading = (result) => ({
322603
+ result,
322604
+ probedAt: (/* @__PURE__ */ new Date()).toISOString()
322605
+ });
322606
+ const MAX_LINGERING_PROBES = 2;
322279
322607
  function createRuntimeFilesystemHealthRoute(input) {
322280
322608
  return unwrap(gen(function* () {
322281
322609
  let child = null;
@@ -322283,15 +322611,19 @@ function createRuntimeFilesystemHealthRoute(input) {
322283
322611
  let cached = null;
322284
322612
  let closed = false;
322285
322613
  let shutdown = null;
322614
+ const lingering = /* @__PURE__ */ new Set();
322286
322615
  yield* addFinalizer$2(() => sync$1(() => {
322287
322616
  closed = true;
322288
322617
  shutdown?.();
322289
322618
  }));
322290
322619
  const probe = () => {
322291
- if (closed) return Promise.resolve("unavailable");
322620
+ if (closed) return Promise.resolve(toReading("unavailable"));
322292
322621
  if (pending) return pending;
322293
- if (cached && performance.now() < cached.expiresAt) return Promise.resolve(cached.result);
322294
- if (child) return Promise.resolve("timeout");
322622
+ if (cached && performance.now() < cached.expiresAt) return Promise.resolve(cached.reading);
322623
+ if (lingering.size >= MAX_LINGERING_PROBES) return Promise.resolve({
322624
+ ...toReading("timeout"),
322625
+ lingeringProbes: lingering.size
322626
+ });
322295
322627
  pending = new Promise((resolve) => {
322296
322628
  const current = spawn(process.execPath, [
322297
322629
  "--eval",
@@ -322305,46 +322637,58 @@ function createRuntimeFilesystemHealthRoute(input) {
322305
322637
  current.unref();
322306
322638
  const timer = setTimeout(() => {
322307
322639
  current.kill("SIGKILL");
322308
- resolve("timeout");
322640
+ lingering.add(current);
322641
+ if (child === current) {
322642
+ child = null;
322643
+ shutdown = null;
322644
+ }
322645
+ resolve(toReading("timeout"));
322309
322646
  }, input.timeoutMs ?? 3e3);
322310
322647
  shutdown = () => {
322311
322648
  clearTimeout(timer);
322312
322649
  current.kill("SIGKILL");
322313
- resolve("unavailable");
322650
+ resolve(toReading("unavailable"));
322314
322651
  };
322315
322652
  current.once("error", () => {
322316
322653
  clearTimeout(timer);
322654
+ lingering.delete(current);
322317
322655
  if (!current.pid && child === current) {
322318
322656
  child = null;
322319
322657
  shutdown = null;
322320
322658
  }
322321
- resolve("unavailable");
322659
+ resolve(toReading("unavailable"));
322322
322660
  });
322323
322661
  current.once("exit", (code) => {
322324
322662
  clearTimeout(timer);
322663
+ lingering.delete(current);
322325
322664
  if (child === current) {
322326
322665
  child = null;
322327
322666
  shutdown = null;
322328
322667
  }
322329
- resolve(code === 0 ? "healthy" : "io_error");
322668
+ resolve(toReading(code === 0 ? "healthy" : "io_error"));
322330
322669
  });
322331
- }).catch(() => "unavailable").then((result) => {
322670
+ }).catch(() => toReading("unavailable")).then((settled) => {
322332
322671
  pending = null;
322333
- cached = {
322334
- result,
322672
+ if (settled.result === "healthy") cached = {
322673
+ reading: settled,
322335
322674
  expiresAt: performance.now() + (input.cacheMs ?? 1e4)
322336
322675
  };
322337
- return result;
322676
+ return settled;
322338
322677
  });
322339
322678
  return pending;
322340
322679
  };
322341
322680
  return add$1("GET", "/api/runtime/filesystem-health", gen(function* () {
322342
- const result = yield* promise(probe);
322343
- const healthy = result === "healthy";
322681
+ const outcome = yield* promise(probe);
322682
+ const healthy = outcome.result === "healthy";
322344
322683
  return jsonUnsafe({
322345
322684
  service: "cmmd-forge-runtime",
322346
322685
  filesystem: healthy ? "healthy" : "unhealthy",
322347
- ...healthy ? {} : { reason: result }
322686
+ probedAt: outcome.probedAt,
322687
+ uptimeSeconds: process.uptime(),
322688
+ ...healthy ? {} : {
322689
+ reason: outcome.result,
322690
+ ...outcome.lingeringProbes === void 0 ? {} : { lingeringProbes: outcome.lingeringProbes }
322691
+ }
322348
322692
  }, {
322349
322693
  status: healthy ? 200 : 503,
322350
322694
  headers: { "cache-control": "no-store" }