@cmmd-center/forge 0.13.8 → 0.13.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.cjs CHANGED
@@ -66289,7 +66289,7 @@ function normalizeNumberish(value) {
66289
66289
  }
66290
66290
  //#endregion
66291
66291
  //#region package.json
66292
- var version$1 = "0.13.8";
66292
+ var version$1 = "0.13.10";
66293
66293
  //#endregion
66294
66294
  //#region src/sentry.ts
66295
66295
  const SERVER_APP_NAME = "forge-server";
@@ -88064,14 +88064,18 @@ function resolveRuntimeGitHubGrantAuthFromEnv(headers) {
88064
88064
  }
88065
88065
  function buildCmmdRuntimeGrantFetchRequest(auth, repoFullName) {
88066
88066
  return {
88067
- url: new URL(`/api/forge/runtimes/${encodeURIComponent(auth.runtimeId)}/github/installation-token`, resolveCommandAppUrl()).toString(),
88067
+ url: new URL(`/api/forge/runtimes/${encodeURIComponent(auth.runtimeId)}/scm/credential`, resolveCommandAppUrl()).toString(),
88068
88068
  init: {
88069
88069
  method: "POST",
88070
88070
  headers: {
88071
88071
  Authorization: `Bearer ${auth.token}`,
88072
88072
  "Content-Type": "application/json"
88073
88073
  },
88074
- body: JSON.stringify({ repo_full_name: repoFullName })
88074
+ body: JSON.stringify({
88075
+ provider: "github",
88076
+ host: "github.com",
88077
+ repository_path: repoFullName
88078
+ })
88075
88079
  }
88076
88080
  };
88077
88081
  }
@@ -88533,10 +88537,19 @@ function fetchInstallationTokenForAuth(auth, repoFullName) {
88533
88537
  ok: false,
88534
88538
  ...lastError
88535
88539
  };
88536
- if (tokenResp.ok) return {
88537
- ok: true,
88538
- token: (yield* require_Schema$1.promise(() => tokenResp.json())).token
88539
- };
88540
+ if (tokenResp.ok) {
88541
+ const tokenData = yield* require_Schema$1.promise(() => tokenResp.json());
88542
+ const token = auth.kind === "cmmd-runtime-ticket" ? tokenData.credential : tokenData.token;
88543
+ if (typeof token === "string" && token.trim().length > 0) return {
88544
+ ok: true,
88545
+ token: token.trim()
88546
+ };
88547
+ return {
88548
+ ok: false,
88549
+ status: 502,
88550
+ error: "CMMD returned an invalid source-control credential response."
88551
+ };
88552
+ }
88540
88553
  const err = yield* require_Schema$1.promise(() => tokenResp.json().catch(() => ({})));
88541
88554
  lastError = {
88542
88555
  status: tokenResp.status,
@@ -89458,23 +89471,38 @@ var GitHubBroker = class {
89458
89471
  async cloneRepository(auth, input) {
89459
89472
  const cloneInput = validateCloneInput(input);
89460
89473
  if ("ok" in cloneInput) return cloneInput;
89461
- const { branch } = cloneInput;
89474
+ let { branch } = cloneInput;
89462
89475
  const gitToken = await this.resolveInstallationToken(auth, input.repoFullName);
89463
89476
  if (typeof gitToken !== "string") return gitToken;
89464
- const initFailure = await this.runCloneGitStep([
89477
+ try {
89478
+ await (0, node_fs_promises.mkdir)(input.destination);
89479
+ } catch (error) {
89480
+ if (isNodeErrorWithCode(error, "EEXIST")) return cloneDestinationExistsResult();
89481
+ return {
89482
+ ok: false,
89483
+ code: "GITHUB_CLONE_DESTINATION_CREATE_FAILED",
89484
+ error: "Could not create the clone destination."
89485
+ };
89486
+ }
89487
+ const initFailure = await this.runCloneGitStep(branch === null ? ["init", input.destination] : [
89465
89488
  "init",
89466
89489
  "-b",
89467
89490
  branch,
89468
89491
  input.destination
89469
89492
  ], "GITHUB_CLONE_INIT_FAILED", "Could not initialize the clone destination.");
89470
- if (initFailure) return initFailure;
89493
+ if (initFailure) return removeFailedCloneDestination(input.destination, initFailure);
89471
89494
  const configured = await this.configureProjectGitCredential({
89472
89495
  cwd: input.destination,
89473
89496
  repoFullName: input.repoFullName,
89474
89497
  token: gitToken,
89475
89498
  remoteMode: "add"
89476
89499
  });
89477
- if (!configured.ok) return configured;
89500
+ if (!configured.ok) return removeFailedCloneDestination(input.destination, configured);
89501
+ if (branch === null) {
89502
+ const defaultBranch = await this.resolveRemoteDefaultBranch(input.destination);
89503
+ if (typeof defaultBranch !== "string") return removeFailedCloneDestination(input.destination, defaultBranch);
89504
+ branch = defaultBranch;
89505
+ }
89478
89506
  const fetchFailure = await this.runCloneGitStep([
89479
89507
  "-C",
89480
89508
  input.destination,
@@ -89484,7 +89512,7 @@ var GitHubBroker = class {
89484
89512
  "origin",
89485
89513
  branch
89486
89514
  ], "GITHUB_CLONE_FETCH_FAILED", "Could not fetch the requested repository branch.", { timeoutMs: 12e4 });
89487
- if (fetchFailure) return fetchFailure;
89515
+ if (fetchFailure) return removeFailedCloneDestination(input.destination, fetchFailure);
89488
89516
  const checkoutFailure = await this.runCloneGitStep([
89489
89517
  "-C",
89490
89518
  input.destination,
@@ -89493,7 +89521,7 @@ var GitHubBroker = class {
89493
89521
  branch,
89494
89522
  "FETCH_HEAD"
89495
89523
  ], "GITHUB_CLONE_CHECKOUT_FAILED", "Could not check out the requested repository branch.");
89496
- if (checkoutFailure) return checkoutFailure;
89524
+ if (checkoutFailure) return removeFailedCloneDestination(input.destination, checkoutFailure);
89497
89525
  return {
89498
89526
  ok: true,
89499
89527
  data: {
@@ -90103,7 +90131,7 @@ var GitHubBroker = class {
90103
90131
  };
90104
90132
  }
90105
90133
  async resolveInstallationToken(auth, repoFullName) {
90106
- const runtimeGrant = this.resolveRuntimeGrant();
90134
+ const runtimeGrant = this.resolveRuntimeGrant("github");
90107
90135
  if (runtimeGrant) {
90108
90136
  const token = await this.fetchRuntimeInstallationToken(runtimeGrant, repoFullName);
90109
90137
  if (!token.ok) return token;
@@ -90488,7 +90516,7 @@ var GitHubBroker = class {
90488
90516
  repoFullName
90489
90517
  };
90490
90518
  }
90491
- resolveRuntimeGrant() {
90519
+ resolveRuntimeGrant(requiredScope = "github") {
90492
90520
  if (this.runtimeRole !== "user-vm") return null;
90493
90521
  const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID?.trim();
90494
90522
  if (!runtimeId) return null;
@@ -90498,7 +90526,7 @@ var GitHubBroker = class {
90498
90526
  runtimeId,
90499
90527
  machineId: identity.machineId,
90500
90528
  credentialEpoch: identity.credentialEpoch,
90501
- requiredScope: "github"
90529
+ requiredScope
90502
90530
  });
90503
90531
  return authority ? {
90504
90532
  runtimeId,
@@ -90512,8 +90540,8 @@ var GitHubBroker = class {
90512
90540
  token: legacyToken
90513
90541
  } : null;
90514
90542
  }
90515
- runtimeGrantAuthContext() {
90516
- const grant = this.resolveRuntimeGrant();
90543
+ runtimeGrantAuthContext(requiredScope = "github") {
90544
+ const grant = this.resolveRuntimeGrant(requiredScope);
90517
90545
  if (grant === null) return null;
90518
90546
  return {
90519
90547
  sessionId: grant.sessionId ?? AuthSessionId.make(`cmmd-runtime:${grant.runtimeId}`),
@@ -90565,13 +90593,21 @@ var GitHubBroker = class {
90565
90593
  };
90566
90594
  }
90567
90595
  async fetchRuntimeInstallationToken(grant, repoFullName) {
90568
- const response = await this.fetchImpl(makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/github/installation-token`), {
90596
+ return this.fetchRuntimeCredential(grant, {
90597
+ url: makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/github/installation-token`),
90598
+ requestBody: { repo_full_name: repoFullName },
90599
+ responseField: "token",
90600
+ label: "GitHub installation token"
90601
+ });
90602
+ }
90603
+ async fetchRuntimeCredential(grant, input) {
90604
+ const response = await this.fetchImpl(input.url, {
90569
90605
  method: "POST",
90570
90606
  headers: {
90571
90607
  Authorization: `Bearer ${grant.token}`,
90572
90608
  "Content-Type": "application/json"
90573
90609
  },
90574
- body: JSON.stringify({ repo_full_name: repoFullName }),
90610
+ body: JSON.stringify(input.requestBody),
90575
90611
  signal: AbortSignal.timeout(GITHUB_BROKER_REQUEST_TIMEOUT_MS)
90576
90612
  });
90577
90613
  const body = await parseResponseBody(response);
@@ -90580,10 +90616,10 @@ var GitHubBroker = class {
90580
90616
  status: response.status,
90581
90617
  code: response.status === 401 ? "GITHUB_AUTH_REQUIRED" : "GITHUB_BROKER_REQUEST_FAILED",
90582
90618
  ...response.status === 401 ? { userActionRequired: "connect_github" } : {},
90583
- error: errorFromBody(body, `GitHub installation token request failed with status ${response.status}.`),
90619
+ error: errorFromBody(body, `${input.label} request failed with status ${response.status}.`),
90584
90620
  data: body
90585
90621
  };
90586
- const token = typeof body === "object" && body !== null ? body.token : null;
90622
+ const token = typeof body === "object" && body !== null ? body[input.responseField] : null;
90587
90623
  if (typeof token !== "string" || token.trim().length === 0) return invalidInstallationTokenResult();
90588
90624
  return {
90589
90625
  ok: true,
@@ -90602,6 +90638,25 @@ var GitHubBroker = class {
90602
90638
  const branch = result.result.stdout.trim();
90603
90639
  return branch.length > 0 ? branch : null;
90604
90640
  }
90641
+ async resolveRemoteDefaultBranch(cwd) {
90642
+ const result = await this.runGit([
90643
+ "-C",
90644
+ cwd,
90645
+ "ls-remote",
90646
+ "--symref",
90647
+ "origin",
90648
+ "HEAD"
90649
+ ], { allowNonZeroExit: true });
90650
+ if (!isGitRunResult(result)) return result;
90651
+ if (result.result.code !== 0) return gitCommandFailure("GITHUB_DEFAULT_BRANCH_LOOKUP_FAILED", result.result.stderr, "Could not determine the repository default branch.");
90652
+ const branch = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/m.exec(result.result.stdout)?.[1];
90653
+ if (branch === void 0) return {
90654
+ ok: false,
90655
+ code: "GITHUB_DEFAULT_BRANCH_RESPONSE_INVALID",
90656
+ error: "GitHub did not return a valid default branch for the repository."
90657
+ };
90658
+ return validateGitBranchName(branch) ?? branch;
90659
+ }
90605
90660
  };
90606
90661
  function isCmmdCloudRuntimeModeEnv() {
90607
90662
  return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
@@ -90686,20 +90741,40 @@ function validateCloneDestination(destination) {
90686
90741
  code: "GITHUB_CLONE_DESTINATION_INVALID",
90687
90742
  error: "An absolute clone destination is required."
90688
90743
  };
90689
- if ((0, node_fs.existsSync)(destination)) return {
90744
+ if ((0, node_fs.existsSync)(destination)) return cloneDestinationExistsResult();
90745
+ return null;
90746
+ }
90747
+ function cloneDestinationExistsResult() {
90748
+ return {
90690
90749
  ok: false,
90691
90750
  code: "GITHUB_CLONE_DESTINATION_EXISTS",
90692
90751
  error: "Clone destination already exists; choose a new empty path."
90693
90752
  };
90694
- return null;
90753
+ }
90754
+ function isNodeErrorWithCode(error, code) {
90755
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
90756
+ }
90757
+ async function removeFailedCloneDestination(destination, failure) {
90758
+ try {
90759
+ await (0, node_fs_promises.rm)(destination, {
90760
+ recursive: true,
90761
+ force: true
90762
+ });
90763
+ return failure;
90764
+ } catch {
90765
+ return {
90766
+ ...failure,
90767
+ error: `${failure.error ?? "Clone failed."} The incomplete clone destination could not be removed.`
90768
+ };
90769
+ }
90695
90770
  }
90696
90771
  function validateCloneInput(input) {
90697
90772
  const repoFullNameValidation = validateRepoFullName(input.repoFullName);
90698
90773
  if (repoFullNameValidation) return repoFullNameValidation;
90699
90774
  const destinationValidation = validateCloneDestination(input.destination);
90700
90775
  if (destinationValidation) return destinationValidation;
90701
- const branch = input.branch?.trim() || "main";
90702
- return validateGitBranchName(branch) ?? { branch };
90776
+ const branch = input.branch?.trim() || null;
90777
+ return branch === null ? { branch } : validateGitBranchName(branch) ?? { branch };
90703
90778
  }
90704
90779
  function validateGitBranchName(branch) {
90705
90780
  if (branch.length === 0 || branch.startsWith("-") || Array.from(branch).some((character) => {
@@ -93872,6 +93947,17 @@ const admitNormalizedDispatchCommand = (command, admit) => require_Schema$1.unin
93872
93947
  //#region src/orchestration/bootstrapCommandCoordinator.ts
93873
93948
  const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
93874
93949
  const commandsByEngine = /* @__PURE__ */ new WeakMap();
93950
+ const sealBootstrapCommand = (engine, commandId) => {
93951
+ const entry = commandsByEngine.get(engine)?.get(commandId);
93952
+ if (entry) entry.cancellable = false;
93953
+ };
93954
+ const cancelBootstrapCommand = (engine, threadId) => require_Schema$1.promise(async () => {
93955
+ const entry = [...commandsByEngine.get(engine)?.values() ?? []].find((candidate) => candidate.threadId === threadId && !candidate.completed && candidate.cancellable);
93956
+ if (!entry) return false;
93957
+ entry.controller.abort();
93958
+ await entry.promise.catch(() => void 0);
93959
+ return true;
93960
+ });
93875
93961
  const toBootstrapError = (error) => require_Schema$1.is(OrchestrationDispatchCommandError)(error) ? error : new OrchestrationDispatchCommandError({
93876
93962
  message: error instanceof Error ? error.message : "Bootstrap command failed.",
93877
93963
  cause: error
@@ -93891,7 +93977,7 @@ const pruneCompletedCommands = (commands) => {
93891
93977
  * the registry to one server runtime, so reconnects share work while separate
93892
93978
  * runtimes remain isolated.
93893
93979
  */
93894
- const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93980
+ const runBootstrapCommand = (engine, commandId, bootstrapEffect, threadId) => {
93895
93981
  let commands = commandsByEngine.get(engine);
93896
93982
  if (commands === void 0) {
93897
93983
  commands = /* @__PURE__ */ new Map();
@@ -93904,16 +93990,21 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93904
93990
  });
93905
93991
  pruneCompletedCommands(commands);
93906
93992
  let entry;
93907
- const promise = require_Schema$1.runPromise(bootstrapEffect).then((result) => {
93993
+ const controller = new AbortController();
93994
+ const promise = Promise.resolve().then(() => require_Schema$1.runPromise(bootstrapEffect, { signal: controller.signal })).then((result) => {
93908
93995
  entry.completed = true;
93909
93996
  return result;
93910
93997
  }, (error) => {
93911
- commands.delete(commandId);
93998
+ entry.completed = true;
93999
+ if (!controller.signal.aborted) commands.delete(commandId);
93912
94000
  throw error;
93913
94001
  });
93914
94002
  entry = {
93915
94003
  promise,
93916
- completed: false
94004
+ completed: false,
94005
+ threadId,
94006
+ controller,
94007
+ cancellable: true
93917
94008
  };
93918
94009
  commands.set(commandId, entry);
93919
94010
  return require_Schema$1.tryPromise({
@@ -93923,6 +94014,10 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93923
94014
  };
93924
94015
  //#endregion
93925
94016
  //#region src/orchestration/bootstrapDispatch.ts
94017
+ const interruptBootstrapTurnStart = (engine, command) => require_Schema$1.gen(function* () {
94018
+ if (yield* cancelBootstrapCommand(engine, command.threadId)) return { sequence: (yield* engine.getReadModel()).snapshotSequence };
94019
+ return yield* engine.dispatch(command);
94020
+ });
93926
94021
  const MAX_BOOTSTRAP_OUTPUT_LINE_LENGTH = 1e3;
93927
94022
  const toBootstrapDispatchCommandCauseError = (cause) => {
93928
94023
  const error = require_Schema$1.squash(cause);
@@ -94009,6 +94104,7 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94009
94104
  const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
94010
94105
  const baseBranchLabel = bootstrapWorktree.baseBranch ?? "<default>";
94011
94106
  const requestedPayload = {
94107
+ turnRequestedAt: input.command.createdAt,
94012
94108
  projectCwd: bootstrapWorktree.projectCwd,
94013
94109
  baseBranch: bootstrapWorktree.baseBranch,
94014
94110
  branch: bootstrapWorktree.branch,
@@ -94029,6 +94125,16 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94029
94125
  branch: bootstrapWorktree.baseBranch,
94030
94126
  newBranch: bootstrapWorktree.branch,
94031
94127
  path: null,
94128
+ onStage: (update) => appendWorktreeActivity({
94129
+ orchestrationEngine: input.orchestrationEngine,
94130
+ threadId: input.command.threadId,
94131
+ commandId: CommandId.make(`server:worktree-stage:${input.command.commandId}:${crypto.randomUUID()}`),
94132
+ kind: "worktree.stage",
94133
+ summary: "Preparing worktree",
94134
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94135
+ payload: update,
94136
+ tone: "info"
94137
+ }).pipe(require_Schema$1.asVoid, require_Schema$1.ignore),
94032
94138
  progress: {
94033
94139
  onStdoutLine: (line) => {
94034
94140
  const detail = truncateBootstrapOutputLine(line);
@@ -94068,6 +94174,7 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94068
94174
  }
94069
94175
  }
94070
94176
  }).pipe(require_Schema$1.catchCause((cause) => {
94177
+ if (require_Schema$1.hasInterruptsOnly(cause)) return require_Schema$1.failCause(cause);
94071
94178
  const error = require_Schema$1.squash(cause);
94072
94179
  const detail = error instanceof Error ? error.message : "Unknown worktree failure.";
94073
94180
  return appendWorktreeActivity({
@@ -94194,7 +94301,16 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
94194
94301
  worktreePath
94195
94302
  }),
94196
94303
  onSuccess: (setupResult) => {
94197
- if (setupResult.status !== "started") return require_Schema$1.void_;
94304
+ if (setupResult.status !== "started") return appendSetupScriptActivity({
94305
+ orchestrationEngine,
94306
+ threadId: command.threadId,
94307
+ commandId: bootstrapCommandId(command, "setup-script-skipped"),
94308
+ kind: "setup-script.skipped",
94309
+ summary: "No setup script configured",
94310
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94311
+ payload: {},
94312
+ tone: "info"
94313
+ }).pipe(require_Schema$1.asVoid, require_Schema$1.ignore);
94198
94314
  return recordSetupScriptStarted({
94199
94315
  requestedAt,
94200
94316
  worktreePath,
@@ -94224,13 +94340,33 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
94224
94340
  targetProjectCwd = preparedWorktree.projectCwd;
94225
94341
  }
94226
94342
  yield* runSetupProgram();
94343
+ if (bootstrap?.prepareWorktree) yield* appendWorktreeActivity({
94344
+ orchestrationEngine,
94345
+ threadId: command.threadId,
94346
+ commandId: bootstrapCommandId(command, "launch-agent-starting"),
94347
+ kind: "launch.agent-starting",
94348
+ summary: "Starting agent",
94349
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94350
+ payload: { setupRequested: bootstrap.runSetupScript === true },
94351
+ tone: "info"
94352
+ }).pipe(require_Schema$1.ignore);
94353
+ sealBootstrapCommand(orchestrationEngine, command.commandId);
94227
94354
  return yield* orchestrationEngine.dispatch(finalTurnStartCommand);
94228
94355
  });
94229
- return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(require_Schema$1.catchCause((cause) => {
94356
+ return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(require_Schema$1.onInterrupt(() => appendWorktreeActivity({
94357
+ orchestrationEngine,
94358
+ threadId: command.threadId,
94359
+ commandId: bootstrapCommandId(command, "launch-cancelled"),
94360
+ kind: "launch.cancelled",
94361
+ summary: "Launch cancelled",
94362
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94363
+ payload: {},
94364
+ tone: "info"
94365
+ }).pipe(require_Schema$1.ignore)), require_Schema$1.catchCause((cause) => {
94230
94366
  const dispatchError = toBootstrapDispatchCommandCauseError(cause);
94231
94367
  if (require_Schema$1.hasInterruptsOnly(cause)) return require_Schema$1.fail(dispatchError);
94232
94368
  return cleanupCreatedThread().pipe(require_Schema$1.flatMap(() => require_Schema$1.fail(dispatchError)));
94233
- })));
94369
+ })), command.threadId);
94234
94370
  });
94235
94371
  //#endregion
94236
94372
  //#region src/orchestration/Services/OrchestrationEngine.ts
@@ -100946,6 +101082,7 @@ function emptyConfig(source, path) {
100946
101082
  terminals: [],
100947
101083
  systemPackages: [],
100948
101084
  secrets: [],
101085
+ repositoryDependencies: [],
100949
101086
  issues: []
100950
101087
  };
100951
101088
  }
@@ -101031,6 +101168,7 @@ function readConfigFile(source, path) {
101031
101168
  terminals: terminalsFromValue(parsed.terminals),
101032
101169
  systemPackages: stringArray(parsed.systemPackages ?? parsed.aptPackages ?? parsed.packages),
101033
101170
  secrets: stringArray(parsed.secrets ?? parsed.envSecrets),
101171
+ repositoryDependencies: stringArray(parsed.repositoryDependencies),
101034
101172
  issues: []
101035
101173
  };
101036
101174
  }
@@ -101443,6 +101581,7 @@ const EMPTY_WORKSPACE_ENVIRONMENT_CONFIG = {
101443
101581
  terminals: [],
101444
101582
  systemPackages: [],
101445
101583
  secrets: [],
101584
+ repositoryDependencies: [],
101446
101585
  issues: []
101447
101586
  };
101448
101587
  function buildCwdCheck(cwdExists) {
@@ -106250,7 +106389,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
106250
106389
  git,
106251
106390
  gitStatusBroadcaster,
106252
106391
  projectSetupScriptRunner
106253
- }, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106392
+ }, normalizedCommand) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106254
106393
  return startup.enqueueCommand(dispatchEffect).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106255
106394
  };
106256
106395
  const loadServerConfig = require_Schema$1.gen(function* () {
@@ -151620,6 +151759,86 @@ async function attestCodexLocalAccountIdentity(input) {
151620
151759
  return identity;
151621
151760
  }
151622
151761
  //#endregion
151762
+ //#region src/workspace/workspaceRepositoryDependencies.ts
151763
+ function repositoryFullName(value) {
151764
+ return value.trim().match(/^(?:https?:\/\/)?github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i)?.[1] ?? null;
151765
+ }
151766
+ function dependencyDestination(cloudProjectsRoot, repoFullName) {
151767
+ const root = (0, node_path.resolve)(cloudProjectsRoot);
151768
+ const destination = (0, node_path.resolve)((0, node_path.join)(root, (0, node_path.basename)(repoFullName)));
151769
+ const pathFromRoot = (0, node_path.relative)(root, destination);
151770
+ if (pathFromRoot.startsWith("..") || pathFromRoot === "") throw new Error(`Repository dependency ${repoFullName} has an unsafe destination.`);
151771
+ return destination;
151772
+ }
151773
+ async function readOriginFromGit(destination) {
151774
+ const result = await runProcess("git", [
151775
+ "-C",
151776
+ destination,
151777
+ "remote",
151778
+ "get-url",
151779
+ "origin"
151780
+ ], {
151781
+ allowNonZeroExit: true,
151782
+ timeoutMs: 1e4
151783
+ });
151784
+ return result.code === 0 ? result.stdout.trim() : null;
151785
+ }
151786
+ function resolveRepositoryDependencies(declarations, cloudProjectsRoot) {
151787
+ const destinations = /* @__PURE__ */ new Set();
151788
+ return declarations.map((declaration) => {
151789
+ const repoFullName = repositoryFullName(declaration);
151790
+ if (repoFullName === null) throw new Error(`Unsupported repository dependency: ${declaration}`);
151791
+ const destination = dependencyDestination(cloudProjectsRoot, repoFullName);
151792
+ if (destinations.has(destination)) throw new Error(`Repository dependencies collide at ${destination}.`);
151793
+ destinations.add(destination);
151794
+ return {
151795
+ repoFullName,
151796
+ destination
151797
+ };
151798
+ });
151799
+ }
151800
+ async function prepareRepositoryDependency(input) {
151801
+ const { repoFullName, destination } = input.dependency;
151802
+ if ((0, node_fs.existsSync)(destination)) {
151803
+ const origin = await input.readOrigin(destination);
151804
+ const existingRepo = origin === null ? null : parseRepoFullNameFromRemoteUrl(origin);
151805
+ if (existingRepo?.toLowerCase() !== repoFullName.toLowerCase()) throw new Error(`Repository dependency destination ${destination} contains ${existingRepo ?? "no GitHub repository"} instead of ${repoFullName}.`);
151806
+ return {
151807
+ repoFullName,
151808
+ destination,
151809
+ outcome: "existing"
151810
+ };
151811
+ }
151812
+ const result = await input.broker.cloneRepository(input.auth, {
151813
+ repoFullName,
151814
+ destination
151815
+ });
151816
+ if (!result.ok) throw new Error(`Could not prepare repository dependency ${repoFullName}: ${result.error ?? result.code ?? "clone failed"}`);
151817
+ return {
151818
+ repoFullName,
151819
+ destination,
151820
+ outcome: "cloned"
151821
+ };
151822
+ }
151823
+ async function prepareWorkspaceRepositoryDependencies(input) {
151824
+ if (!input.runtimeMode) return [];
151825
+ const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
151826
+ if (dependencies.length === 0) return [];
151827
+ const broker = input.broker ?? new GitHubBroker();
151828
+ const auth = broker.runtimeGrantAuthContext("github");
151829
+ if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime GitHub grant.");
151830
+ const readOrigin = input.readOrigin ?? readOriginFromGit;
151831
+ const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
151832
+ const prepared = [];
151833
+ for (const dependency of resolved) prepared.push(await prepareRepositoryDependency({
151834
+ dependency,
151835
+ broker,
151836
+ auth,
151837
+ readOrigin
151838
+ }));
151839
+ return prepared;
151840
+ }
151841
+ //#endregion
151623
151842
  //#region src/codexAppServerManager.ts
151624
151843
  const CODEX_LOGIN_STATUS_TIMEOUT_MS = 5e3;
151625
151844
  const CODEX_SKILLS_LIST_TIMEOUT_MS = 4e3;
@@ -151954,6 +152173,11 @@ var CodexAppServerManager = class extends node_events.EventEmitter {
151954
152173
  let context;
151955
152174
  try {
151956
152175
  const resolvedCwd = input.cwd ?? process.cwd();
152176
+ await prepareWorkspaceRepositoryDependencies({
152177
+ cwd: resolvedCwd,
152178
+ cloudProjectsRoot: process.env.FORGE_CLOUD_PROJECTS_ROOT?.trim() || "/projects",
152179
+ runtimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true"
152180
+ });
151957
152181
  let workspacePreflightReport = runWorkspaceEnvironmentPreflight({
151958
152182
  cwd: resolvedCwd,
151959
152183
  env: process.env
@@ -217040,6 +217264,19 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217040
217264
  const resumeState = readClaudeResumeState(input.resumeCursor);
217041
217265
  const threadId = input.threadId;
217042
217266
  const { existingResumeSessionId, newSessionId, sessionId } = yield* resolveClaudeStartSessionIds(resumeState);
217267
+ const workspaceCwd = input.cwd;
217268
+ if (workspaceCwd) yield* require_Schema$1.tryPromise({
217269
+ try: () => prepareWorkspaceRepositoryDependencies({
217270
+ cwd: workspaceCwd,
217271
+ cloudProjectsRoot: serverConfig.cloudProjectsRoot ?? "/projects",
217272
+ runtimeMode: serverConfig.forgeCmmdRuntimeMode === true
217273
+ }),
217274
+ catch: (cause) => new ProviderAdapterValidationError({
217275
+ provider: PROVIDER$11,
217276
+ operation: "startSession",
217277
+ issue: cause instanceof Error ? cause.message : "Could not prepare declared repository dependencies."
217278
+ })
217279
+ });
217043
217280
  const runtimeContext = yield* require_Schema$1.context();
217044
217281
  const runFork = require_Schema$1.runForkWith(runtimeContext);
217045
217282
  const runPromise = require_Schema$1.runPromiseWith(runtimeContext);
@@ -233379,12 +233616,12 @@ const collectOutput = require_Schema$1.fn("collectOutput")(function* (input, str
233379
233616
  let lineBuffer = "";
233380
233617
  let truncated = false;
233381
233618
  const emitCompleteLines = require_Schema$1.fn("emitCompleteLines")(function* (flush) {
233382
- let newlineIndex = lineBuffer.indexOf("\n");
233619
+ let newlineIndex = lineBuffer.search(/[\r\n]/);
233383
233620
  while (newlineIndex >= 0) {
233384
233621
  const line = lineBuffer.slice(0, newlineIndex).replace(/\r$/, "");
233385
233622
  lineBuffer = lineBuffer.slice(newlineIndex + 1);
233386
233623
  if (line.length > 0 && onLine) yield* onLine(line);
233387
- newlineIndex = lineBuffer.indexOf("\n");
233624
+ newlineIndex = lineBuffer.search(/[\r\n]/);
233388
233625
  }
233389
233626
  if (flush) {
233390
233627
  const trailing = lineBuffer.replace(/\r$/, "");
@@ -233533,6 +233770,7 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
233533
233770
  operation,
233534
233771
  cwd,
233535
233772
  args,
233773
+ ...options.env ? { env: options.env } : {},
233536
233774
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
233537
233775
  allowNonZeroExit: true,
233538
233776
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
@@ -234256,12 +234494,19 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234256
234494
  };
234257
234495
  });
234258
234496
  const createWorktree = require_Schema$1.fn("createWorktree")(function* (input) {
234497
+ const report = (stage, status, detail) => input.onStage?.({
234498
+ stage,
234499
+ status,
234500
+ ...detail ? { detail } : {}
234501
+ }) ?? require_Schema$1.void_;
234502
+ yield* report("resolve", "running");
234259
234503
  const base = input.branch ?? (yield* resolveDefaultBaseBranch(input.cwd));
234260
234504
  const startPoint = yield* resolveWorktreeStartPoint({
234261
234505
  cwd: input.cwd,
234262
234506
  base,
234263
234507
  creatingNewBranch: input.newBranch !== void 0
234264
234508
  });
234509
+ yield* report("resolve", "done", startPoint);
234265
234510
  const targetBranch = input.newBranch ?? base;
234266
234511
  const sanitizedBranch = targetBranch.replace(/\//g, "-");
234267
234512
  const repoName = path.basename(input.cwd);
@@ -234273,7 +234518,10 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234273
234518
  limit: 200
234274
234519
  })).branches.find((branch) => branch.isRemote !== true && branch.name === targetBranch && branch.worktreePath !== null && node_path.default.resolve(branch.worktreePath) === canonicalWorktreePath);
234275
234520
  if (existingBranch?.worktreePath) {
234521
+ yield* report("checkout", "skipped", "Existing worktree");
234522
+ yield* report("dependencies", "running");
234276
234523
  yield* setupSharedWorktreeDependencies(input.cwd, existingBranch.worktreePath);
234524
+ yield* report("dependencies", "done");
234277
234525
  return { worktree: {
234278
234526
  path: worktreePath,
234279
234527
  branch: targetBranch
@@ -234292,11 +234540,19 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234292
234540
  worktreePath,
234293
234541
  startPoint
234294
234542
  ];
234543
+ yield* report("checkout", "running");
234295
234544
  yield* executeGit("GitCore.createWorktree", input.cwd, args, {
234296
234545
  fallbackErrorMessage: "git worktree add failed",
234546
+ env: {
234547
+ GIT_PROGRESS_DELAY: "0",
234548
+ LC_ALL: "C"
234549
+ },
234297
234550
  ...input.progress ? { progress: input.progress } : {}
234298
234551
  });
234552
+ yield* report("checkout", "done");
234553
+ yield* report("dependencies", "running");
234299
234554
  yield* setupSharedWorktreeDependencies(input.cwd, worktreePath);
234555
+ yield* report("dependencies", "done");
234300
234556
  return { worktree: {
234301
234557
  path: worktreePath,
234302
234558
  branch: targetBranch
@@ -297109,7 +297365,7 @@ function resolveBuildCommitFromEnv(env) {
297109
297365
  * environment descriptor down instead of reporting an honest "unknown".
297110
297366
  */
297111
297367
  function readBakedBuildCommit() {
297112
- return "ab022129e97b79359ffb5998de13084bb9f28c79";
297368
+ return "d9a524e82e63dd163e8f06083c14b5913b6fdf88";
297113
297369
  }
297114
297370
  async function resolveServerBuildCommit(input) {
297115
297371
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -298935,6 +299191,10 @@ const AuthSessionRepositoryLive = require_Schema$1.effect(AuthSessionRepository,
298935
299191
  const SIGNING_SECRET_NAME = "server-signing-key";
298936
299192
  const DEFAULT_SESSION_TTL = require_Schema$1.days(30);
298937
299193
  const DEFAULT_WEBSOCKET_TOKEN_TTL = require_Schema$1.minutes(5);
299194
+ const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
299195
+ message,
299196
+ cause
299197
+ });
298938
299198
  const SessionClaims = require_Schema$1.Struct({
298939
299199
  v: require_Schema$1.Literal(1),
298940
299200
  kind: require_Schema$1.Literal("session"),
@@ -298991,10 +299251,6 @@ const makeSessionCredentialService = require_Schema$1.gen(function* () {
298991
299251
  port: serverConfig.port,
298992
299252
  host: serverConfig.host
298993
299253
  });
298994
- const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298995
- message,
298996
- cause
298997
- });
298998
299254
  const emitUpsert = (clientSession) => require_Path.publish(changesPubSub, {
298999
299255
  type: "clientUpserted",
299000
299256
  clientSession
@@ -299155,15 +299411,28 @@ const makeSessionCredentialService = require_Schema$1.gen(function* () {
299155
299411
  if (require_Schema$1.isSome(row)) {
299156
299412
  if (row.value.revokedAt !== null) return yield* new SessionCredentialError({ message: "Websocket session revoked." });
299157
299413
  if (row.value.expiresAt.epochMilliseconds <= now) return yield* new SessionCredentialError({ message: "Websocket session expired." });
299158
- } else if (!(statelessCookieSessions && claims.method === "browser-session-cookie")) return yield* new SessionCredentialError({ message: "Unknown websocket session." });
299414
+ } else {
299415
+ const cloudOwnerSession = serverConfig.forgeCloudShellMode === true && claims.method === "proxy-asserted" && forgeCloudOwnerSessionExpiresAt(claims.sid) !== null;
299416
+ if (cloudOwnerSession && isExpiredForgeCloudOwnerSession(claims.sid, now)) return yield* new SessionCredentialError({ message: "Websocket session expired." });
299417
+ if (statelessCookieSessions && (claims.method === "browser-session-cookie" || cloudOwnerSession)) return {
299418
+ sessionId: claims.sid,
299419
+ token,
299420
+ method: claims.method,
299421
+ client: createDefaultClientMetadata(),
299422
+ expiresAt: require_Schema$1.makeUnsafe(claims.exp),
299423
+ subject: claims.sub,
299424
+ role: claims.role
299425
+ };
299426
+ return yield* new SessionCredentialError({ message: "Unknown websocket session." });
299427
+ }
299159
299428
  return {
299160
299429
  sessionId: claims.sid,
299161
299430
  token,
299162
- method: require_Schema$1.isSome(row) ? row.value.method : claims.method,
299163
- client: require_Schema$1.isSome(row) ? toClientMetadata(row.value.client) : createDefaultClientMetadata(),
299164
- expiresAt: require_Schema$1.isSome(row) ? row.value.expiresAt : require_Schema$1.makeUnsafe(claims.exp),
299165
- subject: require_Schema$1.isSome(row) ? row.value.subject : claims.sub,
299166
- role: require_Schema$1.isSome(row) ? row.value.role : claims.role
299431
+ method: row.value.method,
299432
+ client: toClientMetadata(row.value.client),
299433
+ expiresAt: row.value.expiresAt,
299434
+ subject: row.value.subject,
299435
+ role: row.value.role
299167
299436
  };
299168
299437
  }).pipe(require_Schema$1.mapError((cause) => cause instanceof SessionCredentialError ? cause : new SessionCredentialError({
299169
299438
  message: "Failed to verify websocket token.",
@@ -299441,6 +299710,18 @@ function verifyForgeCloudOwnerCallbackAssertion(assertion, config) {
299441
299710
  //#endregion
299442
299711
  //#region src/auth/Layers/ServerAuth.ts
299443
299712
  const AUTHORIZATION_PREFIX = "Bearer ";
299713
+ function classifyWebSocketTokenFailure(cause) {
299714
+ switch (cause.message) {
299715
+ case "Malformed websocket token.": return "malformed";
299716
+ case "Invalid websocket token signature.": return "invalid_signature";
299717
+ case "Invalid websocket token payload.": return "invalid_payload";
299718
+ case "Websocket token expired.": return "expired_token";
299719
+ case "Websocket session revoked.": return "revoked_parent_session";
299720
+ case "Websocket session expired.": return "expired_parent_session";
299721
+ case "Unknown websocket session.": return "unknown_parent_session";
299722
+ default: return "verification_failed";
299723
+ }
299724
+ }
299444
299725
  function toBootstrapExchangeAuthError(cause) {
299445
299726
  if (cause.status === 500) return new AuthError({
299446
299727
  message: "Failed to validate bootstrap credential.",
@@ -299743,7 +300024,6 @@ const makeServerAuth = require_Schema$1.gen(function* () {
299743
300024
  expiresAt: require_Schema$1.toUtc(issued.expiresAt)
299744
300025
  })));
299745
300026
  const authenticateWebSocketUpgrade = (request) => require_Schema$1.gen(function* () {
299746
- if (serverConfig.forgeCloudShellMode === true) return yield* authenticateRequest(request);
299747
300027
  if (serverConfig.forgeCmmdRuntimeMode) {
299748
300028
  if (serverConfig.forgeCmmdAuthRuntime) {
299749
300029
  const authConfig = readCmmdAuthRuntimeConfig(serverConfig);
@@ -299800,7 +300080,7 @@ const makeServerAuth = require_Schema$1.gen(function* () {
299800
300080
  const requestUrl = require_HttpServer.toURL(request);
299801
300081
  if (require_Schema$1.isSome(requestUrl)) {
299802
300082
  const websocketToken = readWebSocketUpgradeToken(requestUrl.value.searchParams);
299803
- if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(require_Schema$1.map((session) => ({
300083
+ if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(require_Schema$1.tapError((cause) => require_Schema$1.logWarning("Rejected websocket token.").pipe(require_Schema$1.annotateLogs({ category: classifyWebSocketTokenFailure(cause) }))), require_Schema$1.map((session) => ({
299804
300084
  sessionId: session.sessionId,
299805
300085
  subject: session.subject,
299806
300086
  method: session.method,
@@ -305889,7 +306169,7 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
305889
306169
  }).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
305890
306170
  message: "Failed to dispatch orchestration command.",
305891
306171
  cause
305892
- }))) : orchestrationEngine.dispatch(normalizedCommand).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
306172
+ }))) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
305893
306173
  message: "Failed to dispatch orchestration command.",
305894
306174
  cause
305895
306175
  })));