@cmmd-center/forge 0.13.8 → 0.13.9

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
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
22
22
  import * as FS$1 from "node:fs";
23
23
  import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
24
24
  import * as NodeOS from "node:os";
25
- import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
25
+ import os, { homedir, networkInterfaces, tmpdir } from "node:os";
26
26
  import * as Path$1 from "node:path";
27
27
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
28
28
  import { pathToFileURL } from "node:url";
@@ -50614,7 +50614,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
50614
50614
  const min = forceColor || 0;
50615
50615
  if (env.TERM === "dumb") return min;
50616
50616
  if (process$1.platform === "win32") {
50617
- const osRelease = $U.release().split(".");
50617
+ const osRelease = os.release().split(".");
50618
50618
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
50619
50619
  return 1;
50620
50620
  }
@@ -53057,7 +53057,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
53057
53057
  exports.processDetector = void 0;
53058
53058
  const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
53059
53059
  const semconv_1 = require_semconv$1();
53060
- const os$3 = __require("os");
53060
+ const os$4 = __require("os");
53061
53061
  /**
53062
53062
  * ProcessDetector will be used to detect the resources related current process running
53063
53063
  * and being instrumented from the NodeJS Process module.
@@ -53079,7 +53079,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
53079
53079
  };
53080
53080
  if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
53081
53081
  try {
53082
- const userInfo = os$3.userInfo();
53082
+ const userInfo = os$4.userInfo();
53083
53083
  attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
53084
53084
  } catch (e) {
53085
53085
  api_1.diag.debug(`error obtaining process owner: ${e}`);
@@ -66002,7 +66002,7 @@ function normalizeNumberish(value) {
66002
66002
  }
66003
66003
  //#endregion
66004
66004
  //#region package.json
66005
- var version$1 = "0.13.8";
66005
+ var version$1 = "0.13.9";
66006
66006
  //#endregion
66007
66007
  //#region src/sentry.ts
66008
66008
  const SERVER_APP_NAME = "forge-server";
@@ -75215,7 +75215,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
75215
75215
  var fs$6 = __require("fs");
75216
75216
  var path$5 = __require("path");
75217
75217
  var url$1 = __require("url");
75218
- var os$2 = __require("os");
75218
+ var os$3 = __require("os");
75219
75219
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
75220
75220
  var vars = process.config && process.config.variables || {};
75221
75221
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
@@ -75223,8 +75223,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
75223
75223
  var abi = versions.modules;
75224
75224
  if (versions.deno || process.isBun) abi = "unsupported";
75225
75225
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
75226
- var arch = process.env.npm_config_arch || os$2.arch();
75227
- var platform = process.env.npm_config_platform || os$2.platform();
75226
+ var arch = process.env.npm_config_arch || os$3.arch();
75227
+ var platform = process.env.npm_config_platform || os$3.platform();
75228
75228
  var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
75229
75229
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
75230
75230
  var uv = (versions.uv || "").split(".")[0];
@@ -87718,14 +87718,18 @@ function resolveRuntimeGitHubGrantAuthFromEnv(headers) {
87718
87718
  }
87719
87719
  function buildCmmdRuntimeGrantFetchRequest(auth, repoFullName) {
87720
87720
  return {
87721
- url: new URL(`/api/forge/runtimes/${encodeURIComponent(auth.runtimeId)}/github/installation-token`, resolveCommandAppUrl()).toString(),
87721
+ url: new URL(`/api/forge/runtimes/${encodeURIComponent(auth.runtimeId)}/scm/credential`, resolveCommandAppUrl()).toString(),
87722
87722
  init: {
87723
87723
  method: "POST",
87724
87724
  headers: {
87725
87725
  Authorization: `Bearer ${auth.token}`,
87726
87726
  "Content-Type": "application/json"
87727
87727
  },
87728
- body: JSON.stringify({ repo_full_name: repoFullName })
87728
+ body: JSON.stringify({
87729
+ provider: "github",
87730
+ host: "github.com",
87731
+ repository_path: repoFullName
87732
+ })
87729
87733
  }
87730
87734
  };
87731
87735
  }
@@ -88187,10 +88191,19 @@ function fetchInstallationTokenForAuth(auth, repoFullName) {
88187
88191
  ok: false,
88188
88192
  ...lastError
88189
88193
  };
88190
- if (tokenResp.ok) return {
88191
- ok: true,
88192
- token: (yield* promise(() => tokenResp.json())).token
88193
- };
88194
+ if (tokenResp.ok) {
88195
+ const tokenData = yield* promise(() => tokenResp.json());
88196
+ const token = auth.kind === "cmmd-runtime-ticket" ? tokenData.credential : tokenData.token;
88197
+ if (typeof token === "string" && token.trim().length > 0) return {
88198
+ ok: true,
88199
+ token: token.trim()
88200
+ };
88201
+ return {
88202
+ ok: false,
88203
+ status: 502,
88204
+ error: "CMMD returned an invalid source-control credential response."
88205
+ };
88206
+ }
88194
88207
  const err = yield* promise(() => tokenResp.json().catch(() => ({})));
88195
88208
  lastError = {
88196
88209
  status: tokenResp.status,
@@ -89112,23 +89125,38 @@ var GitHubBroker = class {
89112
89125
  async cloneRepository(auth, input) {
89113
89126
  const cloneInput = validateCloneInput(input);
89114
89127
  if ("ok" in cloneInput) return cloneInput;
89115
- const { branch } = cloneInput;
89128
+ let { branch } = cloneInput;
89116
89129
  const gitToken = await this.resolveInstallationToken(auth, input.repoFullName);
89117
89130
  if (typeof gitToken !== "string") return gitToken;
89118
- const initFailure = await this.runCloneGitStep([
89131
+ try {
89132
+ await mkdir(input.destination);
89133
+ } catch (error) {
89134
+ if (isNodeErrorWithCode(error, "EEXIST")) return cloneDestinationExistsResult();
89135
+ return {
89136
+ ok: false,
89137
+ code: "GITHUB_CLONE_DESTINATION_CREATE_FAILED",
89138
+ error: "Could not create the clone destination."
89139
+ };
89140
+ }
89141
+ const initFailure = await this.runCloneGitStep(branch === null ? ["init", input.destination] : [
89119
89142
  "init",
89120
89143
  "-b",
89121
89144
  branch,
89122
89145
  input.destination
89123
89146
  ], "GITHUB_CLONE_INIT_FAILED", "Could not initialize the clone destination.");
89124
- if (initFailure) return initFailure;
89147
+ if (initFailure) return removeFailedCloneDestination(input.destination, initFailure);
89125
89148
  const configured = await this.configureProjectGitCredential({
89126
89149
  cwd: input.destination,
89127
89150
  repoFullName: input.repoFullName,
89128
89151
  token: gitToken,
89129
89152
  remoteMode: "add"
89130
89153
  });
89131
- if (!configured.ok) return configured;
89154
+ if (!configured.ok) return removeFailedCloneDestination(input.destination, configured);
89155
+ if (branch === null) {
89156
+ const defaultBranch = await this.resolveRemoteDefaultBranch(input.destination);
89157
+ if (typeof defaultBranch !== "string") return removeFailedCloneDestination(input.destination, defaultBranch);
89158
+ branch = defaultBranch;
89159
+ }
89132
89160
  const fetchFailure = await this.runCloneGitStep([
89133
89161
  "-C",
89134
89162
  input.destination,
@@ -89138,7 +89166,7 @@ var GitHubBroker = class {
89138
89166
  "origin",
89139
89167
  branch
89140
89168
  ], "GITHUB_CLONE_FETCH_FAILED", "Could not fetch the requested repository branch.", { timeoutMs: 12e4 });
89141
- if (fetchFailure) return fetchFailure;
89169
+ if (fetchFailure) return removeFailedCloneDestination(input.destination, fetchFailure);
89142
89170
  const checkoutFailure = await this.runCloneGitStep([
89143
89171
  "-C",
89144
89172
  input.destination,
@@ -89147,7 +89175,7 @@ var GitHubBroker = class {
89147
89175
  branch,
89148
89176
  "FETCH_HEAD"
89149
89177
  ], "GITHUB_CLONE_CHECKOUT_FAILED", "Could not check out the requested repository branch.");
89150
- if (checkoutFailure) return checkoutFailure;
89178
+ if (checkoutFailure) return removeFailedCloneDestination(input.destination, checkoutFailure);
89151
89179
  return {
89152
89180
  ok: true,
89153
89181
  data: {
@@ -89757,9 +89785,9 @@ var GitHubBroker = class {
89757
89785
  };
89758
89786
  }
89759
89787
  async resolveInstallationToken(auth, repoFullName) {
89760
- const runtimeGrant = this.resolveRuntimeGrant();
89788
+ const runtimeGrant = this.resolveRuntimeGrant("scm");
89761
89789
  if (runtimeGrant) {
89762
- const token = await this.fetchRuntimeInstallationToken(runtimeGrant, repoFullName);
89790
+ const token = await this.fetchRuntimeScmCredential(runtimeGrant, repoFullName);
89763
89791
  if (!token.ok) return token;
89764
89792
  return token.token ?? invalidInstallationTokenResult();
89765
89793
  }
@@ -90142,7 +90170,7 @@ var GitHubBroker = class {
90142
90170
  repoFullName
90143
90171
  };
90144
90172
  }
90145
- resolveRuntimeGrant() {
90173
+ resolveRuntimeGrant(requiredScope = "github") {
90146
90174
  if (this.runtimeRole !== "user-vm") return null;
90147
90175
  const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID?.trim();
90148
90176
  if (!runtimeId) return null;
@@ -90152,7 +90180,7 @@ var GitHubBroker = class {
90152
90180
  runtimeId,
90153
90181
  machineId: identity.machineId,
90154
90182
  credentialEpoch: identity.credentialEpoch,
90155
- requiredScope: "github"
90183
+ requiredScope
90156
90184
  });
90157
90185
  return authority ? {
90158
90186
  runtimeId,
@@ -90166,8 +90194,8 @@ var GitHubBroker = class {
90166
90194
  token: legacyToken
90167
90195
  } : null;
90168
90196
  }
90169
- runtimeGrantAuthContext() {
90170
- const grant = this.resolveRuntimeGrant();
90197
+ runtimeGrantAuthContext(requiredScope = "github") {
90198
+ const grant = this.resolveRuntimeGrant(requiredScope);
90171
90199
  if (grant === null) return null;
90172
90200
  return {
90173
90201
  sessionId: grant.sessionId ?? AuthSessionId.make(`cmmd-runtime:${grant.runtimeId}`),
@@ -90219,13 +90247,33 @@ var GitHubBroker = class {
90219
90247
  };
90220
90248
  }
90221
90249
  async fetchRuntimeInstallationToken(grant, repoFullName) {
90222
- const response = await this.fetchImpl(makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/github/installation-token`), {
90250
+ return this.fetchRuntimeCredential(grant, {
90251
+ url: makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/github/installation-token`),
90252
+ requestBody: { repo_full_name: repoFullName },
90253
+ responseField: "token",
90254
+ label: "GitHub installation token"
90255
+ });
90256
+ }
90257
+ async fetchRuntimeScmCredential(grant, repoFullName) {
90258
+ return this.fetchRuntimeCredential(grant, {
90259
+ url: makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/scm/credential`),
90260
+ requestBody: {
90261
+ provider: "github",
90262
+ host: "github.com",
90263
+ repository_path: repoFullName
90264
+ },
90265
+ responseField: "credential",
90266
+ label: "GitHub source-control credential"
90267
+ });
90268
+ }
90269
+ async fetchRuntimeCredential(grant, input) {
90270
+ const response = await this.fetchImpl(input.url, {
90223
90271
  method: "POST",
90224
90272
  headers: {
90225
90273
  Authorization: `Bearer ${grant.token}`,
90226
90274
  "Content-Type": "application/json"
90227
90275
  },
90228
- body: JSON.stringify({ repo_full_name: repoFullName }),
90276
+ body: JSON.stringify(input.requestBody),
90229
90277
  signal: AbortSignal.timeout(GITHUB_BROKER_REQUEST_TIMEOUT_MS)
90230
90278
  });
90231
90279
  const body = await parseResponseBody(response);
@@ -90234,10 +90282,10 @@ var GitHubBroker = class {
90234
90282
  status: response.status,
90235
90283
  code: response.status === 401 ? "GITHUB_AUTH_REQUIRED" : "GITHUB_BROKER_REQUEST_FAILED",
90236
90284
  ...response.status === 401 ? { userActionRequired: "connect_github" } : {},
90237
- error: errorFromBody(body, `GitHub installation token request failed with status ${response.status}.`),
90285
+ error: errorFromBody(body, `${input.label} request failed with status ${response.status}.`),
90238
90286
  data: body
90239
90287
  };
90240
- const token = typeof body === "object" && body !== null ? body.token : null;
90288
+ const token = typeof body === "object" && body !== null ? body[input.responseField] : null;
90241
90289
  if (typeof token !== "string" || token.trim().length === 0) return invalidInstallationTokenResult();
90242
90290
  return {
90243
90291
  ok: true,
@@ -90256,6 +90304,25 @@ var GitHubBroker = class {
90256
90304
  const branch = result.result.stdout.trim();
90257
90305
  return branch.length > 0 ? branch : null;
90258
90306
  }
90307
+ async resolveRemoteDefaultBranch(cwd) {
90308
+ const result = await this.runGit([
90309
+ "-C",
90310
+ cwd,
90311
+ "ls-remote",
90312
+ "--symref",
90313
+ "origin",
90314
+ "HEAD"
90315
+ ], { allowNonZeroExit: true });
90316
+ if (!isGitRunResult(result)) return result;
90317
+ if (result.result.code !== 0) return gitCommandFailure("GITHUB_DEFAULT_BRANCH_LOOKUP_FAILED", result.result.stderr, "Could not determine the repository default branch.");
90318
+ const branch = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/m.exec(result.result.stdout)?.[1];
90319
+ if (branch === void 0) return {
90320
+ ok: false,
90321
+ code: "GITHUB_DEFAULT_BRANCH_RESPONSE_INVALID",
90322
+ error: "GitHub did not return a valid default branch for the repository."
90323
+ };
90324
+ return validateGitBranchName(branch) ?? branch;
90325
+ }
90259
90326
  };
90260
90327
  function isCmmdCloudRuntimeModeEnv() {
90261
90328
  return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
@@ -90340,20 +90407,40 @@ function validateCloneDestination(destination) {
90340
90407
  code: "GITHUB_CLONE_DESTINATION_INVALID",
90341
90408
  error: "An absolute clone destination is required."
90342
90409
  };
90343
- if (existsSync(destination)) return {
90410
+ if (existsSync(destination)) return cloneDestinationExistsResult();
90411
+ return null;
90412
+ }
90413
+ function cloneDestinationExistsResult() {
90414
+ return {
90344
90415
  ok: false,
90345
90416
  code: "GITHUB_CLONE_DESTINATION_EXISTS",
90346
90417
  error: "Clone destination already exists; choose a new empty path."
90347
90418
  };
90348
- return null;
90419
+ }
90420
+ function isNodeErrorWithCode(error, code) {
90421
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
90422
+ }
90423
+ async function removeFailedCloneDestination(destination, failure) {
90424
+ try {
90425
+ await rm(destination, {
90426
+ recursive: true,
90427
+ force: true
90428
+ });
90429
+ return failure;
90430
+ } catch {
90431
+ return {
90432
+ ...failure,
90433
+ error: `${failure.error ?? "Clone failed."} The incomplete clone destination could not be removed.`
90434
+ };
90435
+ }
90349
90436
  }
90350
90437
  function validateCloneInput(input) {
90351
90438
  const repoFullNameValidation = validateRepoFullName(input.repoFullName);
90352
90439
  if (repoFullNameValidation) return repoFullNameValidation;
90353
90440
  const destinationValidation = validateCloneDestination(input.destination);
90354
90441
  if (destinationValidation) return destinationValidation;
90355
- const branch = input.branch?.trim() || "main";
90356
- return validateGitBranchName(branch) ?? { branch };
90442
+ const branch = input.branch?.trim() || null;
90443
+ return branch === null ? { branch } : validateGitBranchName(branch) ?? { branch };
90357
90444
  }
90358
90445
  function validateGitBranchName(branch) {
90359
90446
  if (branch.length === 0 || branch.startsWith("-") || Array.from(branch).some((character) => {
@@ -93331,7 +93418,7 @@ const launchDetached = (launch) => gen(function* () {
93331
93418
  });
93332
93419
  const OpenLive = effect(Open, gen(function* () {
93333
93420
  const open = yield* tryPromise({
93334
- try: () => import("./open-D8mGyWmt.mjs"),
93421
+ try: () => import("./open-FIgTDosf.mjs"),
93335
93422
  catch: (cause) => new OpenError$1({
93336
93423
  message: "failed to load browser opener",
93337
93424
  cause
@@ -93518,6 +93605,17 @@ const admitNormalizedDispatchCommand = (command, admit) => uninterruptibleMask$1
93518
93605
  //#region src/orchestration/bootstrapCommandCoordinator.ts
93519
93606
  const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
93520
93607
  const commandsByEngine = /* @__PURE__ */ new WeakMap();
93608
+ const sealBootstrapCommand = (engine, commandId) => {
93609
+ const entry = commandsByEngine.get(engine)?.get(commandId);
93610
+ if (entry) entry.cancellable = false;
93611
+ };
93612
+ const cancelBootstrapCommand = (engine, threadId) => promise(async () => {
93613
+ const entry = [...commandsByEngine.get(engine)?.values() ?? []].find((candidate) => candidate.threadId === threadId && !candidate.completed && candidate.cancellable);
93614
+ if (!entry) return false;
93615
+ entry.controller.abort();
93616
+ await entry.promise.catch(() => void 0);
93617
+ return true;
93618
+ });
93521
93619
  const toBootstrapError = (error) => is$1(OrchestrationDispatchCommandError)(error) ? error : new OrchestrationDispatchCommandError({
93522
93620
  message: error instanceof Error ? error.message : "Bootstrap command failed.",
93523
93621
  cause: error
@@ -93537,7 +93635,7 @@ const pruneCompletedCommands = (commands) => {
93537
93635
  * the registry to one server runtime, so reconnects share work while separate
93538
93636
  * runtimes remain isolated.
93539
93637
  */
93540
- const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93638
+ const runBootstrapCommand = (engine, commandId, bootstrapEffect, threadId) => {
93541
93639
  let commands = commandsByEngine.get(engine);
93542
93640
  if (commands === void 0) {
93543
93641
  commands = /* @__PURE__ */ new Map();
@@ -93550,16 +93648,21 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93550
93648
  });
93551
93649
  pruneCompletedCommands(commands);
93552
93650
  let entry;
93553
- const promise = runPromise(bootstrapEffect).then((result) => {
93651
+ const controller = new AbortController();
93652
+ const promise = Promise.resolve().then(() => runPromise(bootstrapEffect, { signal: controller.signal })).then((result) => {
93554
93653
  entry.completed = true;
93555
93654
  return result;
93556
93655
  }, (error) => {
93557
- commands.delete(commandId);
93656
+ entry.completed = true;
93657
+ if (!controller.signal.aborted) commands.delete(commandId);
93558
93658
  throw error;
93559
93659
  });
93560
93660
  entry = {
93561
93661
  promise,
93562
- completed: false
93662
+ completed: false,
93663
+ threadId,
93664
+ controller,
93665
+ cancellable: true
93563
93666
  };
93564
93667
  commands.set(commandId, entry);
93565
93668
  return tryPromise({
@@ -93569,6 +93672,10 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93569
93672
  };
93570
93673
  //#endregion
93571
93674
  //#region src/orchestration/bootstrapDispatch.ts
93675
+ const interruptBootstrapTurnStart = (engine, command) => gen(function* () {
93676
+ if (yield* cancelBootstrapCommand(engine, command.threadId)) return { sequence: (yield* engine.getReadModel()).snapshotSequence };
93677
+ return yield* engine.dispatch(command);
93678
+ });
93572
93679
  const MAX_BOOTSTRAP_OUTPUT_LINE_LENGTH = 1e3;
93573
93680
  const toBootstrapDispatchCommandCauseError = (cause) => {
93574
93681
  const error = squash(cause);
@@ -93655,6 +93762,7 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93655
93762
  const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
93656
93763
  const baseBranchLabel = bootstrapWorktree.baseBranch ?? "<default>";
93657
93764
  const requestedPayload = {
93765
+ turnRequestedAt: input.command.createdAt,
93658
93766
  projectCwd: bootstrapWorktree.projectCwd,
93659
93767
  baseBranch: bootstrapWorktree.baseBranch,
93660
93768
  branch: bootstrapWorktree.branch,
@@ -93675,6 +93783,16 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93675
93783
  branch: bootstrapWorktree.baseBranch,
93676
93784
  newBranch: bootstrapWorktree.branch,
93677
93785
  path: null,
93786
+ onStage: (update) => appendWorktreeActivity({
93787
+ orchestrationEngine: input.orchestrationEngine,
93788
+ threadId: input.command.threadId,
93789
+ commandId: CommandId.make(`server:worktree-stage:${input.command.commandId}:${crypto.randomUUID()}`),
93790
+ kind: "worktree.stage",
93791
+ summary: "Preparing worktree",
93792
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
93793
+ payload: update,
93794
+ tone: "info"
93795
+ }).pipe(asVoid, ignore),
93678
93796
  progress: {
93679
93797
  onStdoutLine: (line) => {
93680
93798
  const detail = truncateBootstrapOutputLine(line);
@@ -93714,6 +93832,7 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93714
93832
  }
93715
93833
  }
93716
93834
  }).pipe(catchCause((cause) => {
93835
+ if (hasInterruptsOnly(cause)) return failCause$1(cause);
93717
93836
  const error = squash(cause);
93718
93837
  const detail = error instanceof Error ? error.message : "Unknown worktree failure.";
93719
93838
  return appendWorktreeActivity({
@@ -93840,7 +93959,16 @@ const dispatchBootstrapTurnStart = (input, command) => gen(function* () {
93840
93959
  worktreePath
93841
93960
  }),
93842
93961
  onSuccess: (setupResult) => {
93843
- if (setupResult.status !== "started") return void_$1;
93962
+ if (setupResult.status !== "started") return appendSetupScriptActivity({
93963
+ orchestrationEngine,
93964
+ threadId: command.threadId,
93965
+ commandId: bootstrapCommandId(command, "setup-script-skipped"),
93966
+ kind: "setup-script.skipped",
93967
+ summary: "No setup script configured",
93968
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
93969
+ payload: {},
93970
+ tone: "info"
93971
+ }).pipe(asVoid, ignore);
93844
93972
  return recordSetupScriptStarted({
93845
93973
  requestedAt,
93846
93974
  worktreePath,
@@ -93870,13 +93998,33 @@ const dispatchBootstrapTurnStart = (input, command) => gen(function* () {
93870
93998
  targetProjectCwd = preparedWorktree.projectCwd;
93871
93999
  }
93872
94000
  yield* runSetupProgram();
94001
+ if (bootstrap?.prepareWorktree) yield* appendWorktreeActivity({
94002
+ orchestrationEngine,
94003
+ threadId: command.threadId,
94004
+ commandId: bootstrapCommandId(command, "launch-agent-starting"),
94005
+ kind: "launch.agent-starting",
94006
+ summary: "Starting agent",
94007
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94008
+ payload: { setupRequested: bootstrap.runSetupScript === true },
94009
+ tone: "info"
94010
+ }).pipe(ignore);
94011
+ sealBootstrapCommand(orchestrationEngine, command.commandId);
93873
94012
  return yield* orchestrationEngine.dispatch(finalTurnStartCommand);
93874
94013
  });
93875
- return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(catchCause((cause) => {
94014
+ return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(onInterrupt(() => appendWorktreeActivity({
94015
+ orchestrationEngine,
94016
+ threadId: command.threadId,
94017
+ commandId: bootstrapCommandId(command, "launch-cancelled"),
94018
+ kind: "launch.cancelled",
94019
+ summary: "Launch cancelled",
94020
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94021
+ payload: {},
94022
+ tone: "info"
94023
+ }).pipe(ignore)), catchCause((cause) => {
93876
94024
  const dispatchError = toBootstrapDispatchCommandCauseError(cause);
93877
94025
  if (hasInterruptsOnly(cause)) return fail(dispatchError);
93878
94026
  return cleanupCreatedThread().pipe(flatMap(() => fail(dispatchError)));
93879
- })));
94027
+ })), command.threadId);
93880
94028
  });
93881
94029
  //#endregion
93882
94030
  //#region src/orchestration/Services/OrchestrationEngine.ts
@@ -100555,6 +100703,7 @@ function emptyConfig(source, path) {
100555
100703
  terminals: [],
100556
100704
  systemPackages: [],
100557
100705
  secrets: [],
100706
+ repositoryDependencies: [],
100558
100707
  issues: []
100559
100708
  };
100560
100709
  }
@@ -100640,6 +100789,7 @@ function readConfigFile(source, path) {
100640
100789
  terminals: terminalsFromValue(parsed.terminals),
100641
100790
  systemPackages: stringArray(parsed.systemPackages ?? parsed.aptPackages ?? parsed.packages),
100642
100791
  secrets: stringArray(parsed.secrets ?? parsed.envSecrets),
100792
+ repositoryDependencies: stringArray(parsed.repositoryDependencies),
100643
100793
  issues: []
100644
100794
  };
100645
100795
  }
@@ -101052,6 +101202,7 @@ const EMPTY_WORKSPACE_ENVIRONMENT_CONFIG = {
101052
101202
  terminals: [],
101053
101203
  systemPackages: [],
101054
101204
  secrets: [],
101205
+ repositoryDependencies: [],
101055
101206
  issues: []
101056
101207
  };
101057
101208
  function buildCwdCheck(cwdExists) {
@@ -105854,7 +106005,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
105854
106005
  git,
105855
106006
  gitStatusBroadcaster,
105856
106007
  projectSetupScriptRunner
105857
- }, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106008
+ }, normalizedCommand) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105858
106009
  return startup.enqueueCommand(dispatchEffect).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105859
106010
  };
105860
106011
  const loadServerConfig = gen(function* () {
@@ -151203,6 +151354,86 @@ async function attestCodexLocalAccountIdentity(input) {
151203
151354
  return identity;
151204
151355
  }
151205
151356
  //#endregion
151357
+ //#region src/workspace/workspaceRepositoryDependencies.ts
151358
+ function repositoryFullName(value) {
151359
+ return value.trim().match(/^(?:https?:\/\/)?github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i)?.[1] ?? null;
151360
+ }
151361
+ function dependencyDestination(cloudProjectsRoot, repoFullName) {
151362
+ const root = resolve(cloudProjectsRoot);
151363
+ const destination = resolve(join(root, basename(repoFullName)));
151364
+ const pathFromRoot = relative(root, destination);
151365
+ if (pathFromRoot.startsWith("..") || pathFromRoot === "") throw new Error(`Repository dependency ${repoFullName} has an unsafe destination.`);
151366
+ return destination;
151367
+ }
151368
+ async function readOriginFromGit(destination) {
151369
+ const result = await runProcess("git", [
151370
+ "-C",
151371
+ destination,
151372
+ "remote",
151373
+ "get-url",
151374
+ "origin"
151375
+ ], {
151376
+ allowNonZeroExit: true,
151377
+ timeoutMs: 1e4
151378
+ });
151379
+ return result.code === 0 ? result.stdout.trim() : null;
151380
+ }
151381
+ function resolveRepositoryDependencies(declarations, cloudProjectsRoot) {
151382
+ const destinations = /* @__PURE__ */ new Set();
151383
+ return declarations.map((declaration) => {
151384
+ const repoFullName = repositoryFullName(declaration);
151385
+ if (repoFullName === null) throw new Error(`Unsupported repository dependency: ${declaration}`);
151386
+ const destination = dependencyDestination(cloudProjectsRoot, repoFullName);
151387
+ if (destinations.has(destination)) throw new Error(`Repository dependencies collide at ${destination}.`);
151388
+ destinations.add(destination);
151389
+ return {
151390
+ repoFullName,
151391
+ destination
151392
+ };
151393
+ });
151394
+ }
151395
+ async function prepareRepositoryDependency(input) {
151396
+ const { repoFullName, destination } = input.dependency;
151397
+ if (existsSync(destination)) {
151398
+ const origin = await input.readOrigin(destination);
151399
+ const existingRepo = origin === null ? null : parseRepoFullNameFromRemoteUrl(origin);
151400
+ if (existingRepo?.toLowerCase() !== repoFullName.toLowerCase()) throw new Error(`Repository dependency destination ${destination} contains ${existingRepo ?? "no GitHub repository"} instead of ${repoFullName}.`);
151401
+ return {
151402
+ repoFullName,
151403
+ destination,
151404
+ outcome: "existing"
151405
+ };
151406
+ }
151407
+ const result = await input.broker.cloneRepository(input.auth, {
151408
+ repoFullName,
151409
+ destination
151410
+ });
151411
+ if (!result.ok) throw new Error(`Could not prepare repository dependency ${repoFullName}: ${result.error ?? result.code ?? "clone failed"}`);
151412
+ return {
151413
+ repoFullName,
151414
+ destination,
151415
+ outcome: "cloned"
151416
+ };
151417
+ }
151418
+ async function prepareWorkspaceRepositoryDependencies(input) {
151419
+ if (!input.runtimeMode) return [];
151420
+ const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
151421
+ if (dependencies.length === 0) return [];
151422
+ const broker = input.broker ?? new GitHubBroker();
151423
+ const auth = broker.runtimeGrantAuthContext("scm");
151424
+ if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime SCM grant.");
151425
+ const readOrigin = input.readOrigin ?? readOriginFromGit;
151426
+ const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
151427
+ const prepared = [];
151428
+ for (const dependency of resolved) prepared.push(await prepareRepositoryDependency({
151429
+ dependency,
151430
+ broker,
151431
+ auth,
151432
+ readOrigin
151433
+ }));
151434
+ return prepared;
151435
+ }
151436
+ //#endregion
151206
151437
  //#region src/codexAppServerManager.ts
151207
151438
  const CODEX_LOGIN_STATUS_TIMEOUT_MS = 5e3;
151208
151439
  const CODEX_SKILLS_LIST_TIMEOUT_MS = 4e3;
@@ -151537,6 +151768,11 @@ var CodexAppServerManager = class extends EventEmitter$1 {
151537
151768
  let context;
151538
151769
  try {
151539
151770
  const resolvedCwd = input.cwd ?? process.cwd();
151771
+ await prepareWorkspaceRepositoryDependencies({
151772
+ cwd: resolvedCwd,
151773
+ cloudProjectsRoot: process.env.FORGE_CLOUD_PROJECTS_ROOT?.trim() || "/projects",
151774
+ runtimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true"
151775
+ });
151540
151776
  let workspacePreflightReport = runWorkspaceEnvironmentPreflight({
151541
151777
  cwd: resolvedCwd,
151542
151778
  env: process.env
@@ -190311,7 +190547,7 @@ var Vce = {
190311
190547
  function Ve() {
190312
190548
  return Vce;
190313
190549
  }
190314
- var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
190550
+ var Us = os.homedir(), lk = os.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
190315
190551
  let t = path.join(Us, "Library");
190316
190552
  return {
190317
190553
  data: path.join(t, "Application Support", e),
@@ -200511,12 +200747,12 @@ function qZ(e, t) {
200511
200747
  }
200512
200748
  return n;
200513
200749
  }
200514
- var os$1 = "https://code.claude.com/docs/en", kKe = [
200750
+ var os$2 = "https://code.claude.com/docs/en", kKe = [
200515
200751
  {
200516
200752
  matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
200517
200753
  tip: {
200518
200754
  suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
200519
- docLink: `${os$1}/iam#permission-modes`
200755
+ docLink: `${os$2}/iam#permission-modes`
200520
200756
  }
200521
200757
  },
200522
200758
  {
@@ -200531,7 +200767,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
200531
200767
  matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
200532
200768
  tip: {
200533
200769
  suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
200534
- docLink: `${os$1}/settings#environment-variables`
200770
+ docLink: `${os$2}/settings#environment-variables`
200535
200771
  }
200536
200772
  },
200537
200773
  {
@@ -200542,14 +200778,14 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
200542
200778
  matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
200543
200779
  tip: {
200544
200780
  suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
200545
- docLink: `${os$1}/hooks`
200781
+ docLink: `${os$2}/hooks`
200546
200782
  }
200547
200783
  },
200548
200784
  {
200549
200785
  matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
200550
200786
  tip: {
200551
200787
  suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
200552
- docLink: `${os$1}/hooks#exec-form-and-shell-form`
200788
+ docLink: `${os$2}/hooks#exec-form-and-shell-form`
200553
200789
  }
200554
200790
  },
200555
200791
  {
@@ -200564,7 +200800,7 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
200564
200800
  matches: (e) => e.code === "unrecognized_keys",
200565
200801
  tip: {
200566
200802
  suggestion: "Check for typos or refer to the documentation for valid fields",
200567
- docLink: `${os$1}/settings`
200803
+ docLink: `${os$2}/settings`
200568
200804
  }
200569
200805
  },
200570
200806
  {
@@ -200579,13 +200815,13 @@ var os$1 = "https://code.claude.com/docs/en", kKe = [
200579
200815
  matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
200580
200816
  tip: {
200581
200817
  suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
200582
- docLink: `${os$1}/iam#working-directories`
200818
+ docLink: `${os$2}/iam#working-directories`
200583
200819
  }
200584
200820
  }
200585
200821
  ], AKe = {
200586
- permissions: `${os$1}/iam#configuring-permissions`,
200587
- env: `${os$1}/settings#environment-variables`,
200588
- hooks: `${os$1}/hooks`
200822
+ permissions: `${os$2}/iam#configuring-permissions`,
200823
+ env: `${os$2}/settings#environment-variables`,
200824
+ hooks: `${os$2}/hooks`
200589
200825
  };
200590
200826
  function GZ(e) {
200591
200827
  let t = kKe.find((r) => r.matches(e));
@@ -216617,6 +216853,19 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216617
216853
  const resumeState = readClaudeResumeState(input.resumeCursor);
216618
216854
  const threadId = input.threadId;
216619
216855
  const { existingResumeSessionId, newSessionId, sessionId } = yield* resolveClaudeStartSessionIds(resumeState);
216856
+ const workspaceCwd = input.cwd;
216857
+ if (workspaceCwd) yield* tryPromise({
216858
+ try: () => prepareWorkspaceRepositoryDependencies({
216859
+ cwd: workspaceCwd,
216860
+ cloudProjectsRoot: serverConfig.cloudProjectsRoot ?? "/projects",
216861
+ runtimeMode: serverConfig.forgeCmmdRuntimeMode === true
216862
+ }),
216863
+ catch: (cause) => new ProviderAdapterValidationError({
216864
+ provider: PROVIDER$11,
216865
+ operation: "startSession",
216866
+ issue: cause instanceof Error ? cause.message : "Could not prepare declared repository dependencies."
216867
+ })
216868
+ });
216620
216869
  const runtimeContext = yield* context();
216621
216870
  const runFork = runForkWith(runtimeContext);
216622
216871
  const runPromise = runPromiseWith(runtimeContext);
@@ -232923,12 +233172,12 @@ const collectOutput = fn$1("collectOutput")(function* (input, stream, maxOutputB
232923
233172
  let lineBuffer = "";
232924
233173
  let truncated = false;
232925
233174
  const emitCompleteLines = fn$1("emitCompleteLines")(function* (flush) {
232926
- let newlineIndex = lineBuffer.indexOf("\n");
233175
+ let newlineIndex = lineBuffer.search(/[\r\n]/);
232927
233176
  while (newlineIndex >= 0) {
232928
233177
  const line = lineBuffer.slice(0, newlineIndex).replace(/\r$/, "");
232929
233178
  lineBuffer = lineBuffer.slice(newlineIndex + 1);
232930
233179
  if (line.length > 0 && onLine) yield* onLine(line);
232931
- newlineIndex = lineBuffer.indexOf("\n");
233180
+ newlineIndex = lineBuffer.search(/[\r\n]/);
232932
233181
  }
232933
233182
  if (flush) {
232934
233183
  const trailing = lineBuffer.replace(/\r$/, "");
@@ -233076,6 +233325,7 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233076
233325
  operation,
233077
233326
  cwd,
233078
233327
  args,
233328
+ ...options.env ? { env: options.env } : {},
233079
233329
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
233080
233330
  allowNonZeroExit: true,
233081
233331
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
@@ -233797,12 +234047,19 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233797
234047
  };
233798
234048
  });
233799
234049
  const createWorktree = fn$1("createWorktree")(function* (input) {
234050
+ const report = (stage, status, detail) => input.onStage?.({
234051
+ stage,
234052
+ status,
234053
+ ...detail ? { detail } : {}
234054
+ }) ?? void_$1;
234055
+ yield* report("resolve", "running");
233800
234056
  const base = input.branch ?? (yield* resolveDefaultBaseBranch(input.cwd));
233801
234057
  const startPoint = yield* resolveWorktreeStartPoint({
233802
234058
  cwd: input.cwd,
233803
234059
  base,
233804
234060
  creatingNewBranch: input.newBranch !== void 0
233805
234061
  });
234062
+ yield* report("resolve", "done", startPoint);
233806
234063
  const targetBranch = input.newBranch ?? base;
233807
234064
  const sanitizedBranch = targetBranch.replace(/\//g, "-");
233808
234065
  const repoName = path$10.basename(input.cwd);
@@ -233814,7 +234071,10 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233814
234071
  limit: 200
233815
234072
  })).branches.find((branch) => branch.isRemote !== true && branch.name === targetBranch && branch.worktreePath !== null && path.resolve(branch.worktreePath) === canonicalWorktreePath);
233816
234073
  if (existingBranch?.worktreePath) {
234074
+ yield* report("checkout", "skipped", "Existing worktree");
234075
+ yield* report("dependencies", "running");
233817
234076
  yield* setupSharedWorktreeDependencies(input.cwd, existingBranch.worktreePath);
234077
+ yield* report("dependencies", "done");
233818
234078
  return { worktree: {
233819
234079
  path: worktreePath,
233820
234080
  branch: targetBranch
@@ -233833,11 +234093,19 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233833
234093
  worktreePath,
233834
234094
  startPoint
233835
234095
  ];
234096
+ yield* report("checkout", "running");
233836
234097
  yield* executeGit("GitCore.createWorktree", input.cwd, args, {
233837
234098
  fallbackErrorMessage: "git worktree add failed",
234099
+ env: {
234100
+ GIT_PROGRESS_DELAY: "0",
234101
+ LC_ALL: "C"
234102
+ },
233838
234103
  ...input.progress ? { progress: input.progress } : {}
233839
234104
  });
234105
+ yield* report("checkout", "done");
234106
+ yield* report("dependencies", "running");
233840
234107
  yield* setupSharedWorktreeDependencies(input.cwd, worktreePath);
234108
+ yield* report("dependencies", "done");
233841
234109
  return { worktree: {
233842
234110
  path: worktreePath,
233843
234111
  branch: targetBranch
@@ -267442,7 +267710,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
267442
267710
  var dgram = __require("dgram");
267443
267711
  var thunky = require_thunky();
267444
267712
  var events = __require("events");
267445
- var os = __require("os");
267713
+ var os$1 = __require("os");
267446
267714
  var noop = function() {};
267447
267715
  module.exports = function(opts) {
267448
267716
  if (!opts) opts = {};
@@ -267573,14 +267841,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
267573
267841
  return that;
267574
267842
  };
267575
267843
  function defaultInterface() {
267576
- var networks = os.networkInterfaces();
267844
+ var networks = os$1.networkInterfaces();
267577
267845
  var names = Object.keys(networks);
267578
267846
  for (var i = 0; i < names.length; i++) {
267579
267847
  var net = networks[names[i]];
267580
267848
  for (var j = 0; j < net.length; j++) {
267581
267849
  var iface = net[j];
267582
267850
  if (isIPv4(iface.family) && !iface.internal) {
267583
- if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
267851
+ if (os$1.platform() === "darwin" && names[i] === "en0") return iface.address;
267584
267852
  return "0.0.0.0";
267585
267853
  }
267586
267854
  }
@@ -267588,7 +267856,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
267588
267856
  return "127.0.0.1";
267589
267857
  }
267590
267858
  function allInterfaces() {
267591
- var networks = os.networkInterfaces();
267859
+ var networks = os$1.networkInterfaces();
267592
267860
  var names = Object.keys(networks);
267593
267861
  var res = [];
267594
267862
  for (var i = 0; i < names.length; i++) {
@@ -296549,7 +296817,7 @@ function resolveBuildCommitFromEnv(env) {
296549
296817
  * environment descriptor down instead of reporting an honest "unknown".
296550
296818
  */
296551
296819
  function readBakedBuildCommit() {
296552
- return "ab022129e97b79359ffb5998de13084bb9f28c79";
296820
+ return "339f0da718657526651ad84cacb841446f1bb38b";
296553
296821
  }
296554
296822
  async function resolveServerBuildCommit(input) {
296555
296823
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -298361,6 +298629,10 @@ const AuthSessionRepositoryLive = effect(AuthSessionRepository, gen(function* ()
298361
298629
  const SIGNING_SECRET_NAME = "server-signing-key";
298362
298630
  const DEFAULT_SESSION_TTL = days(30);
298363
298631
  const DEFAULT_WEBSOCKET_TOKEN_TTL = minutes(5);
298632
+ const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298633
+ message,
298634
+ cause
298635
+ });
298364
298636
  const SessionClaims = Struct({
298365
298637
  v: Literal(1),
298366
298638
  kind: Literal("session"),
@@ -298417,10 +298689,6 @@ const SessionCredentialServiceLive = effect(SessionCredentialService, gen(functi
298417
298689
  port: serverConfig.port,
298418
298690
  host: serverConfig.host
298419
298691
  });
298420
- const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298421
- message,
298422
- cause
298423
- });
298424
298692
  const emitUpsert = (clientSession) => publish(changesPubSub, {
298425
298693
  type: "clientUpserted",
298426
298694
  clientSession
@@ -298581,15 +298849,28 @@ const SessionCredentialServiceLive = effect(SessionCredentialService, gen(functi
298581
298849
  if (isSome(row)) {
298582
298850
  if (row.value.revokedAt !== null) return yield* new SessionCredentialError({ message: "Websocket session revoked." });
298583
298851
  if (row.value.expiresAt.epochMilliseconds <= now) return yield* new SessionCredentialError({ message: "Websocket session expired." });
298584
- } else if (!(statelessCookieSessions && claims.method === "browser-session-cookie")) return yield* new SessionCredentialError({ message: "Unknown websocket session." });
298852
+ } else {
298853
+ const cloudOwnerSession = serverConfig.forgeCloudShellMode === true && claims.method === "proxy-asserted" && forgeCloudOwnerSessionExpiresAt(claims.sid) !== null;
298854
+ if (cloudOwnerSession && isExpiredForgeCloudOwnerSession(claims.sid, now)) return yield* new SessionCredentialError({ message: "Websocket session expired." });
298855
+ if (statelessCookieSessions && (claims.method === "browser-session-cookie" || cloudOwnerSession)) return {
298856
+ sessionId: claims.sid,
298857
+ token,
298858
+ method: claims.method,
298859
+ client: createDefaultClientMetadata(),
298860
+ expiresAt: makeUnsafe$5(claims.exp),
298861
+ subject: claims.sub,
298862
+ role: claims.role
298863
+ };
298864
+ return yield* new SessionCredentialError({ message: "Unknown websocket session." });
298865
+ }
298585
298866
  return {
298586
298867
  sessionId: claims.sid,
298587
298868
  token,
298588
- method: isSome(row) ? row.value.method : claims.method,
298589
- client: isSome(row) ? toClientMetadata(row.value.client) : createDefaultClientMetadata(),
298590
- expiresAt: isSome(row) ? row.value.expiresAt : makeUnsafe$5(claims.exp),
298591
- subject: isSome(row) ? row.value.subject : claims.sub,
298592
- role: isSome(row) ? row.value.role : claims.role
298869
+ method: row.value.method,
298870
+ client: toClientMetadata(row.value.client),
298871
+ expiresAt: row.value.expiresAt,
298872
+ subject: row.value.subject,
298873
+ role: row.value.role
298593
298874
  };
298594
298875
  }).pipe(mapError((cause) => cause instanceof SessionCredentialError ? cause : new SessionCredentialError({
298595
298876
  message: "Failed to verify websocket token.",
@@ -298866,6 +299147,18 @@ function verifyForgeCloudOwnerCallbackAssertion(assertion, config) {
298866
299147
  //#endregion
298867
299148
  //#region src/auth/Layers/ServerAuth.ts
298868
299149
  const AUTHORIZATION_PREFIX = "Bearer ";
299150
+ function classifyWebSocketTokenFailure(cause) {
299151
+ switch (cause.message) {
299152
+ case "Malformed websocket token.": return "malformed";
299153
+ case "Invalid websocket token signature.": return "invalid_signature";
299154
+ case "Invalid websocket token payload.": return "invalid_payload";
299155
+ case "Websocket token expired.": return "expired_token";
299156
+ case "Websocket session revoked.": return "revoked_parent_session";
299157
+ case "Websocket session expired.": return "expired_parent_session";
299158
+ case "Unknown websocket session.": return "unknown_parent_session";
299159
+ default: return "verification_failed";
299160
+ }
299161
+ }
298869
299162
  function toBootstrapExchangeAuthError(cause) {
298870
299163
  if (cause.status === 500) return new AuthError({
298871
299164
  message: "Failed to validate bootstrap credential.",
@@ -299168,7 +299461,6 @@ const ServerAuthLive = effect(ServerAuth, gen(function* () {
299168
299461
  expiresAt: toUtc(issued.expiresAt)
299169
299462
  })));
299170
299463
  const authenticateWebSocketUpgrade = (request) => gen(function* () {
299171
- if (serverConfig.forgeCloudShellMode === true) return yield* authenticateRequest(request);
299172
299464
  if (serverConfig.forgeCmmdRuntimeMode) {
299173
299465
  if (serverConfig.forgeCmmdAuthRuntime) {
299174
299466
  const authConfig = readCmmdAuthRuntimeConfig(serverConfig);
@@ -299225,7 +299517,7 @@ const ServerAuthLive = effect(ServerAuth, gen(function* () {
299225
299517
  const requestUrl = toURL(request);
299226
299518
  if (isSome(requestUrl)) {
299227
299519
  const websocketToken = readWebSocketUpgradeToken(requestUrl.value.searchParams);
299228
- if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(map$3((session) => ({
299520
+ if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(tapError((cause) => logWarning$1("Rejected websocket token.").pipe(annotateLogs({ category: classifyWebSocketTokenFailure(cause) }))), map$3((session) => ({
299229
299521
  sessionId: session.sessionId,
299230
299522
  subject: session.subject,
299231
299523
  method: session.method,
@@ -305264,7 +305556,7 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
305264
305556
  }).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305265
305557
  message: "Failed to dispatch orchestration command.",
305266
305558
  cause
305267
- }))) : orchestrationEngine.dispatch(normalizedCommand).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305559
+ }))) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305268
305560
  message: "Failed to dispatch orchestration command.",
305269
305561
  cause
305270
305562
  })));
@@ -320701,7 +320993,7 @@ const connectLoginCommand = make$34("login", {
320701
320993
  })).json();
320702
320994
  },
320703
320995
  openBrowser: async (url) => {
320704
- const open = (await import("./open-D8mGyWmt.mjs")).default;
320996
+ const open = (await import("./open-FIgTDosf.mjs")).default;
320705
320997
  await open(url);
320706
320998
  return true;
320707
320999
  },