@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.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.9";
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,9 +90131,9 @@ var GitHubBroker = class {
90103
90131
  };
90104
90132
  }
90105
90133
  async resolveInstallationToken(auth, repoFullName) {
90106
- const runtimeGrant = this.resolveRuntimeGrant();
90134
+ const runtimeGrant = this.resolveRuntimeGrant("scm");
90107
90135
  if (runtimeGrant) {
90108
- const token = await this.fetchRuntimeInstallationToken(runtimeGrant, repoFullName);
90136
+ const token = await this.fetchRuntimeScmCredential(runtimeGrant, repoFullName);
90109
90137
  if (!token.ok) return token;
90110
90138
  return token.token ?? invalidInstallationTokenResult();
90111
90139
  }
@@ -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,33 @@ 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 fetchRuntimeScmCredential(grant, repoFullName) {
90604
+ return this.fetchRuntimeCredential(grant, {
90605
+ url: makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(grant.runtimeId)}/scm/credential`),
90606
+ requestBody: {
90607
+ provider: "github",
90608
+ host: "github.com",
90609
+ repository_path: repoFullName
90610
+ },
90611
+ responseField: "credential",
90612
+ label: "GitHub source-control credential"
90613
+ });
90614
+ }
90615
+ async fetchRuntimeCredential(grant, input) {
90616
+ const response = await this.fetchImpl(input.url, {
90569
90617
  method: "POST",
90570
90618
  headers: {
90571
90619
  Authorization: `Bearer ${grant.token}`,
90572
90620
  "Content-Type": "application/json"
90573
90621
  },
90574
- body: JSON.stringify({ repo_full_name: repoFullName }),
90622
+ body: JSON.stringify(input.requestBody),
90575
90623
  signal: AbortSignal.timeout(GITHUB_BROKER_REQUEST_TIMEOUT_MS)
90576
90624
  });
90577
90625
  const body = await parseResponseBody(response);
@@ -90580,10 +90628,10 @@ var GitHubBroker = class {
90580
90628
  status: response.status,
90581
90629
  code: response.status === 401 ? "GITHUB_AUTH_REQUIRED" : "GITHUB_BROKER_REQUEST_FAILED",
90582
90630
  ...response.status === 401 ? { userActionRequired: "connect_github" } : {},
90583
- error: errorFromBody(body, `GitHub installation token request failed with status ${response.status}.`),
90631
+ error: errorFromBody(body, `${input.label} request failed with status ${response.status}.`),
90584
90632
  data: body
90585
90633
  };
90586
- const token = typeof body === "object" && body !== null ? body.token : null;
90634
+ const token = typeof body === "object" && body !== null ? body[input.responseField] : null;
90587
90635
  if (typeof token !== "string" || token.trim().length === 0) return invalidInstallationTokenResult();
90588
90636
  return {
90589
90637
  ok: true,
@@ -90602,6 +90650,25 @@ var GitHubBroker = class {
90602
90650
  const branch = result.result.stdout.trim();
90603
90651
  return branch.length > 0 ? branch : null;
90604
90652
  }
90653
+ async resolveRemoteDefaultBranch(cwd) {
90654
+ const result = await this.runGit([
90655
+ "-C",
90656
+ cwd,
90657
+ "ls-remote",
90658
+ "--symref",
90659
+ "origin",
90660
+ "HEAD"
90661
+ ], { allowNonZeroExit: true });
90662
+ if (!isGitRunResult(result)) return result;
90663
+ if (result.result.code !== 0) return gitCommandFailure("GITHUB_DEFAULT_BRANCH_LOOKUP_FAILED", result.result.stderr, "Could not determine the repository default branch.");
90664
+ const branch = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/m.exec(result.result.stdout)?.[1];
90665
+ if (branch === void 0) return {
90666
+ ok: false,
90667
+ code: "GITHUB_DEFAULT_BRANCH_RESPONSE_INVALID",
90668
+ error: "GitHub did not return a valid default branch for the repository."
90669
+ };
90670
+ return validateGitBranchName(branch) ?? branch;
90671
+ }
90605
90672
  };
90606
90673
  function isCmmdCloudRuntimeModeEnv() {
90607
90674
  return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
@@ -90686,20 +90753,40 @@ function validateCloneDestination(destination) {
90686
90753
  code: "GITHUB_CLONE_DESTINATION_INVALID",
90687
90754
  error: "An absolute clone destination is required."
90688
90755
  };
90689
- if ((0, node_fs.existsSync)(destination)) return {
90756
+ if ((0, node_fs.existsSync)(destination)) return cloneDestinationExistsResult();
90757
+ return null;
90758
+ }
90759
+ function cloneDestinationExistsResult() {
90760
+ return {
90690
90761
  ok: false,
90691
90762
  code: "GITHUB_CLONE_DESTINATION_EXISTS",
90692
90763
  error: "Clone destination already exists; choose a new empty path."
90693
90764
  };
90694
- return null;
90765
+ }
90766
+ function isNodeErrorWithCode(error, code) {
90767
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
90768
+ }
90769
+ async function removeFailedCloneDestination(destination, failure) {
90770
+ try {
90771
+ await (0, node_fs_promises.rm)(destination, {
90772
+ recursive: true,
90773
+ force: true
90774
+ });
90775
+ return failure;
90776
+ } catch {
90777
+ return {
90778
+ ...failure,
90779
+ error: `${failure.error ?? "Clone failed."} The incomplete clone destination could not be removed.`
90780
+ };
90781
+ }
90695
90782
  }
90696
90783
  function validateCloneInput(input) {
90697
90784
  const repoFullNameValidation = validateRepoFullName(input.repoFullName);
90698
90785
  if (repoFullNameValidation) return repoFullNameValidation;
90699
90786
  const destinationValidation = validateCloneDestination(input.destination);
90700
90787
  if (destinationValidation) return destinationValidation;
90701
- const branch = input.branch?.trim() || "main";
90702
- return validateGitBranchName(branch) ?? { branch };
90788
+ const branch = input.branch?.trim() || null;
90789
+ return branch === null ? { branch } : validateGitBranchName(branch) ?? { branch };
90703
90790
  }
90704
90791
  function validateGitBranchName(branch) {
90705
90792
  if (branch.length === 0 || branch.startsWith("-") || Array.from(branch).some((character) => {
@@ -93872,6 +93959,17 @@ const admitNormalizedDispatchCommand = (command, admit) => require_Schema$1.unin
93872
93959
  //#region src/orchestration/bootstrapCommandCoordinator.ts
93873
93960
  const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
93874
93961
  const commandsByEngine = /* @__PURE__ */ new WeakMap();
93962
+ const sealBootstrapCommand = (engine, commandId) => {
93963
+ const entry = commandsByEngine.get(engine)?.get(commandId);
93964
+ if (entry) entry.cancellable = false;
93965
+ };
93966
+ const cancelBootstrapCommand = (engine, threadId) => require_Schema$1.promise(async () => {
93967
+ const entry = [...commandsByEngine.get(engine)?.values() ?? []].find((candidate) => candidate.threadId === threadId && !candidate.completed && candidate.cancellable);
93968
+ if (!entry) return false;
93969
+ entry.controller.abort();
93970
+ await entry.promise.catch(() => void 0);
93971
+ return true;
93972
+ });
93875
93973
  const toBootstrapError = (error) => require_Schema$1.is(OrchestrationDispatchCommandError)(error) ? error : new OrchestrationDispatchCommandError({
93876
93974
  message: error instanceof Error ? error.message : "Bootstrap command failed.",
93877
93975
  cause: error
@@ -93891,7 +93989,7 @@ const pruneCompletedCommands = (commands) => {
93891
93989
  * the registry to one server runtime, so reconnects share work while separate
93892
93990
  * runtimes remain isolated.
93893
93991
  */
93894
- const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93992
+ const runBootstrapCommand = (engine, commandId, bootstrapEffect, threadId) => {
93895
93993
  let commands = commandsByEngine.get(engine);
93896
93994
  if (commands === void 0) {
93897
93995
  commands = /* @__PURE__ */ new Map();
@@ -93904,16 +94002,21 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93904
94002
  });
93905
94003
  pruneCompletedCommands(commands);
93906
94004
  let entry;
93907
- const promise = require_Schema$1.runPromise(bootstrapEffect).then((result) => {
94005
+ const controller = new AbortController();
94006
+ const promise = Promise.resolve().then(() => require_Schema$1.runPromise(bootstrapEffect, { signal: controller.signal })).then((result) => {
93908
94007
  entry.completed = true;
93909
94008
  return result;
93910
94009
  }, (error) => {
93911
- commands.delete(commandId);
94010
+ entry.completed = true;
94011
+ if (!controller.signal.aborted) commands.delete(commandId);
93912
94012
  throw error;
93913
94013
  });
93914
94014
  entry = {
93915
94015
  promise,
93916
- completed: false
94016
+ completed: false,
94017
+ threadId,
94018
+ controller,
94019
+ cancellable: true
93917
94020
  };
93918
94021
  commands.set(commandId, entry);
93919
94022
  return require_Schema$1.tryPromise({
@@ -93923,6 +94026,10 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93923
94026
  };
93924
94027
  //#endregion
93925
94028
  //#region src/orchestration/bootstrapDispatch.ts
94029
+ const interruptBootstrapTurnStart = (engine, command) => require_Schema$1.gen(function* () {
94030
+ if (yield* cancelBootstrapCommand(engine, command.threadId)) return { sequence: (yield* engine.getReadModel()).snapshotSequence };
94031
+ return yield* engine.dispatch(command);
94032
+ });
93926
94033
  const MAX_BOOTSTRAP_OUTPUT_LINE_LENGTH = 1e3;
93927
94034
  const toBootstrapDispatchCommandCauseError = (cause) => {
93928
94035
  const error = require_Schema$1.squash(cause);
@@ -94009,6 +94116,7 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94009
94116
  const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
94010
94117
  const baseBranchLabel = bootstrapWorktree.baseBranch ?? "<default>";
94011
94118
  const requestedPayload = {
94119
+ turnRequestedAt: input.command.createdAt,
94012
94120
  projectCwd: bootstrapWorktree.projectCwd,
94013
94121
  baseBranch: bootstrapWorktree.baseBranch,
94014
94122
  branch: bootstrapWorktree.branch,
@@ -94029,6 +94137,16 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94029
94137
  branch: bootstrapWorktree.baseBranch,
94030
94138
  newBranch: bootstrapWorktree.branch,
94031
94139
  path: null,
94140
+ onStage: (update) => appendWorktreeActivity({
94141
+ orchestrationEngine: input.orchestrationEngine,
94142
+ threadId: input.command.threadId,
94143
+ commandId: CommandId.make(`server:worktree-stage:${input.command.commandId}:${crypto.randomUUID()}`),
94144
+ kind: "worktree.stage",
94145
+ summary: "Preparing worktree",
94146
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94147
+ payload: update,
94148
+ tone: "info"
94149
+ }).pipe(require_Schema$1.asVoid, require_Schema$1.ignore),
94032
94150
  progress: {
94033
94151
  onStdoutLine: (line) => {
94034
94152
  const detail = truncateBootstrapOutputLine(line);
@@ -94068,6 +94186,7 @@ const prepareBootstrapWorktree = (input) => require_Schema$1.gen(function* () {
94068
94186
  }
94069
94187
  }
94070
94188
  }).pipe(require_Schema$1.catchCause((cause) => {
94189
+ if (require_Schema$1.hasInterruptsOnly(cause)) return require_Schema$1.failCause(cause);
94071
94190
  const error = require_Schema$1.squash(cause);
94072
94191
  const detail = error instanceof Error ? error.message : "Unknown worktree failure.";
94073
94192
  return appendWorktreeActivity({
@@ -94194,7 +94313,16 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
94194
94313
  worktreePath
94195
94314
  }),
94196
94315
  onSuccess: (setupResult) => {
94197
- if (setupResult.status !== "started") return require_Schema$1.void_;
94316
+ if (setupResult.status !== "started") return appendSetupScriptActivity({
94317
+ orchestrationEngine,
94318
+ threadId: command.threadId,
94319
+ commandId: bootstrapCommandId(command, "setup-script-skipped"),
94320
+ kind: "setup-script.skipped",
94321
+ summary: "No setup script configured",
94322
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94323
+ payload: {},
94324
+ tone: "info"
94325
+ }).pipe(require_Schema$1.asVoid, require_Schema$1.ignore);
94198
94326
  return recordSetupScriptStarted({
94199
94327
  requestedAt,
94200
94328
  worktreePath,
@@ -94224,13 +94352,33 @@ const dispatchBootstrapTurnStart = (input, command) => require_Schema$1.gen(func
94224
94352
  targetProjectCwd = preparedWorktree.projectCwd;
94225
94353
  }
94226
94354
  yield* runSetupProgram();
94355
+ if (bootstrap?.prepareWorktree) yield* appendWorktreeActivity({
94356
+ orchestrationEngine,
94357
+ threadId: command.threadId,
94358
+ commandId: bootstrapCommandId(command, "launch-agent-starting"),
94359
+ kind: "launch.agent-starting",
94360
+ summary: "Starting agent",
94361
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94362
+ payload: { setupRequested: bootstrap.runSetupScript === true },
94363
+ tone: "info"
94364
+ }).pipe(require_Schema$1.ignore);
94365
+ sealBootstrapCommand(orchestrationEngine, command.commandId);
94227
94366
  return yield* orchestrationEngine.dispatch(finalTurnStartCommand);
94228
94367
  });
94229
- return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(require_Schema$1.catchCause((cause) => {
94368
+ return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(require_Schema$1.onInterrupt(() => appendWorktreeActivity({
94369
+ orchestrationEngine,
94370
+ threadId: command.threadId,
94371
+ commandId: bootstrapCommandId(command, "launch-cancelled"),
94372
+ kind: "launch.cancelled",
94373
+ summary: "Launch cancelled",
94374
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94375
+ payload: {},
94376
+ tone: "info"
94377
+ }).pipe(require_Schema$1.ignore)), require_Schema$1.catchCause((cause) => {
94230
94378
  const dispatchError = toBootstrapDispatchCommandCauseError(cause);
94231
94379
  if (require_Schema$1.hasInterruptsOnly(cause)) return require_Schema$1.fail(dispatchError);
94232
94380
  return cleanupCreatedThread().pipe(require_Schema$1.flatMap(() => require_Schema$1.fail(dispatchError)));
94233
- })));
94381
+ })), command.threadId);
94234
94382
  });
94235
94383
  //#endregion
94236
94384
  //#region src/orchestration/Services/OrchestrationEngine.ts
@@ -100946,6 +101094,7 @@ function emptyConfig(source, path) {
100946
101094
  terminals: [],
100947
101095
  systemPackages: [],
100948
101096
  secrets: [],
101097
+ repositoryDependencies: [],
100949
101098
  issues: []
100950
101099
  };
100951
101100
  }
@@ -101031,6 +101180,7 @@ function readConfigFile(source, path) {
101031
101180
  terminals: terminalsFromValue(parsed.terminals),
101032
101181
  systemPackages: stringArray(parsed.systemPackages ?? parsed.aptPackages ?? parsed.packages),
101033
101182
  secrets: stringArray(parsed.secrets ?? parsed.envSecrets),
101183
+ repositoryDependencies: stringArray(parsed.repositoryDependencies),
101034
101184
  issues: []
101035
101185
  };
101036
101186
  }
@@ -101443,6 +101593,7 @@ const EMPTY_WORKSPACE_ENVIRONMENT_CONFIG = {
101443
101593
  terminals: [],
101444
101594
  systemPackages: [],
101445
101595
  secrets: [],
101596
+ repositoryDependencies: [],
101446
101597
  issues: []
101447
101598
  };
101448
101599
  function buildCwdCheck(cwdExists) {
@@ -106250,7 +106401,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(r
106250
106401
  git,
106251
106402
  gitStatusBroadcaster,
106252
106403
  projectSetupScriptRunner
106253
- }, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106404
+ }, 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
106405
  return startup.enqueueCommand(dispatchEffect).pipe(require_Schema$1.mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
106255
106406
  };
106256
106407
  const loadServerConfig = require_Schema$1.gen(function* () {
@@ -151620,6 +151771,86 @@ async function attestCodexLocalAccountIdentity(input) {
151620
151771
  return identity;
151621
151772
  }
151622
151773
  //#endregion
151774
+ //#region src/workspace/workspaceRepositoryDependencies.ts
151775
+ function repositoryFullName(value) {
151776
+ return value.trim().match(/^(?:https?:\/\/)?github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i)?.[1] ?? null;
151777
+ }
151778
+ function dependencyDestination(cloudProjectsRoot, repoFullName) {
151779
+ const root = (0, node_path.resolve)(cloudProjectsRoot);
151780
+ const destination = (0, node_path.resolve)((0, node_path.join)(root, (0, node_path.basename)(repoFullName)));
151781
+ const pathFromRoot = (0, node_path.relative)(root, destination);
151782
+ if (pathFromRoot.startsWith("..") || pathFromRoot === "") throw new Error(`Repository dependency ${repoFullName} has an unsafe destination.`);
151783
+ return destination;
151784
+ }
151785
+ async function readOriginFromGit(destination) {
151786
+ const result = await runProcess("git", [
151787
+ "-C",
151788
+ destination,
151789
+ "remote",
151790
+ "get-url",
151791
+ "origin"
151792
+ ], {
151793
+ allowNonZeroExit: true,
151794
+ timeoutMs: 1e4
151795
+ });
151796
+ return result.code === 0 ? result.stdout.trim() : null;
151797
+ }
151798
+ function resolveRepositoryDependencies(declarations, cloudProjectsRoot) {
151799
+ const destinations = /* @__PURE__ */ new Set();
151800
+ return declarations.map((declaration) => {
151801
+ const repoFullName = repositoryFullName(declaration);
151802
+ if (repoFullName === null) throw new Error(`Unsupported repository dependency: ${declaration}`);
151803
+ const destination = dependencyDestination(cloudProjectsRoot, repoFullName);
151804
+ if (destinations.has(destination)) throw new Error(`Repository dependencies collide at ${destination}.`);
151805
+ destinations.add(destination);
151806
+ return {
151807
+ repoFullName,
151808
+ destination
151809
+ };
151810
+ });
151811
+ }
151812
+ async function prepareRepositoryDependency(input) {
151813
+ const { repoFullName, destination } = input.dependency;
151814
+ if ((0, node_fs.existsSync)(destination)) {
151815
+ const origin = await input.readOrigin(destination);
151816
+ const existingRepo = origin === null ? null : parseRepoFullNameFromRemoteUrl(origin);
151817
+ if (existingRepo?.toLowerCase() !== repoFullName.toLowerCase()) throw new Error(`Repository dependency destination ${destination} contains ${existingRepo ?? "no GitHub repository"} instead of ${repoFullName}.`);
151818
+ return {
151819
+ repoFullName,
151820
+ destination,
151821
+ outcome: "existing"
151822
+ };
151823
+ }
151824
+ const result = await input.broker.cloneRepository(input.auth, {
151825
+ repoFullName,
151826
+ destination
151827
+ });
151828
+ if (!result.ok) throw new Error(`Could not prepare repository dependency ${repoFullName}: ${result.error ?? result.code ?? "clone failed"}`);
151829
+ return {
151830
+ repoFullName,
151831
+ destination,
151832
+ outcome: "cloned"
151833
+ };
151834
+ }
151835
+ async function prepareWorkspaceRepositoryDependencies(input) {
151836
+ if (!input.runtimeMode) return [];
151837
+ const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
151838
+ if (dependencies.length === 0) return [];
151839
+ const broker = input.broker ?? new GitHubBroker();
151840
+ const auth = broker.runtimeGrantAuthContext("scm");
151841
+ if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime SCM grant.");
151842
+ const readOrigin = input.readOrigin ?? readOriginFromGit;
151843
+ const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
151844
+ const prepared = [];
151845
+ for (const dependency of resolved) prepared.push(await prepareRepositoryDependency({
151846
+ dependency,
151847
+ broker,
151848
+ auth,
151849
+ readOrigin
151850
+ }));
151851
+ return prepared;
151852
+ }
151853
+ //#endregion
151623
151854
  //#region src/codexAppServerManager.ts
151624
151855
  const CODEX_LOGIN_STATUS_TIMEOUT_MS = 5e3;
151625
151856
  const CODEX_SKILLS_LIST_TIMEOUT_MS = 4e3;
@@ -151954,6 +152185,11 @@ var CodexAppServerManager = class extends node_events.EventEmitter {
151954
152185
  let context;
151955
152186
  try {
151956
152187
  const resolvedCwd = input.cwd ?? process.cwd();
152188
+ await prepareWorkspaceRepositoryDependencies({
152189
+ cwd: resolvedCwd,
152190
+ cloudProjectsRoot: process.env.FORGE_CLOUD_PROJECTS_ROOT?.trim() || "/projects",
152191
+ runtimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true"
152192
+ });
151957
152193
  let workspacePreflightReport = runWorkspaceEnvironmentPreflight({
151958
152194
  cwd: resolvedCwd,
151959
152195
  env: process.env
@@ -217040,6 +217276,19 @@ const makeClaudeAdapter = require_Schema$1.fn("makeClaudeAdapter")(function* (op
217040
217276
  const resumeState = readClaudeResumeState(input.resumeCursor);
217041
217277
  const threadId = input.threadId;
217042
217278
  const { existingResumeSessionId, newSessionId, sessionId } = yield* resolveClaudeStartSessionIds(resumeState);
217279
+ const workspaceCwd = input.cwd;
217280
+ if (workspaceCwd) yield* require_Schema$1.tryPromise({
217281
+ try: () => prepareWorkspaceRepositoryDependencies({
217282
+ cwd: workspaceCwd,
217283
+ cloudProjectsRoot: serverConfig.cloudProjectsRoot ?? "/projects",
217284
+ runtimeMode: serverConfig.forgeCmmdRuntimeMode === true
217285
+ }),
217286
+ catch: (cause) => new ProviderAdapterValidationError({
217287
+ provider: PROVIDER$11,
217288
+ operation: "startSession",
217289
+ issue: cause instanceof Error ? cause.message : "Could not prepare declared repository dependencies."
217290
+ })
217291
+ });
217043
217292
  const runtimeContext = yield* require_Schema$1.context();
217044
217293
  const runFork = require_Schema$1.runForkWith(runtimeContext);
217045
217294
  const runPromise = require_Schema$1.runPromiseWith(runtimeContext);
@@ -233379,12 +233628,12 @@ const collectOutput = require_Schema$1.fn("collectOutput")(function* (input, str
233379
233628
  let lineBuffer = "";
233380
233629
  let truncated = false;
233381
233630
  const emitCompleteLines = require_Schema$1.fn("emitCompleteLines")(function* (flush) {
233382
- let newlineIndex = lineBuffer.indexOf("\n");
233631
+ let newlineIndex = lineBuffer.search(/[\r\n]/);
233383
233632
  while (newlineIndex >= 0) {
233384
233633
  const line = lineBuffer.slice(0, newlineIndex).replace(/\r$/, "");
233385
233634
  lineBuffer = lineBuffer.slice(newlineIndex + 1);
233386
233635
  if (line.length > 0 && onLine) yield* onLine(line);
233387
- newlineIndex = lineBuffer.indexOf("\n");
233636
+ newlineIndex = lineBuffer.search(/[\r\n]/);
233388
233637
  }
233389
233638
  if (flush) {
233390
233639
  const trailing = lineBuffer.replace(/\r$/, "");
@@ -233533,6 +233782,7 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
233533
233782
  operation,
233534
233783
  cwd,
233535
233784
  args,
233785
+ ...options.env ? { env: options.env } : {},
233536
233786
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
233537
233787
  allowNonZeroExit: true,
233538
233788
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
@@ -234256,12 +234506,19 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234256
234506
  };
234257
234507
  });
234258
234508
  const createWorktree = require_Schema$1.fn("createWorktree")(function* (input) {
234509
+ const report = (stage, status, detail) => input.onStage?.({
234510
+ stage,
234511
+ status,
234512
+ ...detail ? { detail } : {}
234513
+ }) ?? require_Schema$1.void_;
234514
+ yield* report("resolve", "running");
234259
234515
  const base = input.branch ?? (yield* resolveDefaultBaseBranch(input.cwd));
234260
234516
  const startPoint = yield* resolveWorktreeStartPoint({
234261
234517
  cwd: input.cwd,
234262
234518
  base,
234263
234519
  creatingNewBranch: input.newBranch !== void 0
234264
234520
  });
234521
+ yield* report("resolve", "done", startPoint);
234265
234522
  const targetBranch = input.newBranch ?? base;
234266
234523
  const sanitizedBranch = targetBranch.replace(/\//g, "-");
234267
234524
  const repoName = path.basename(input.cwd);
@@ -234273,7 +234530,10 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234273
234530
  limit: 200
234274
234531
  })).branches.find((branch) => branch.isRemote !== true && branch.name === targetBranch && branch.worktreePath !== null && node_path.default.resolve(branch.worktreePath) === canonicalWorktreePath);
234275
234532
  if (existingBranch?.worktreePath) {
234533
+ yield* report("checkout", "skipped", "Existing worktree");
234534
+ yield* report("dependencies", "running");
234276
234535
  yield* setupSharedWorktreeDependencies(input.cwd, existingBranch.worktreePath);
234536
+ yield* report("dependencies", "done");
234277
234537
  return { worktree: {
234278
234538
  path: worktreePath,
234279
234539
  branch: targetBranch
@@ -234292,11 +234552,19 @@ const makeGitCore = require_Schema$1.fn("makeGitCore")(function* (options) {
234292
234552
  worktreePath,
234293
234553
  startPoint
234294
234554
  ];
234555
+ yield* report("checkout", "running");
234295
234556
  yield* executeGit("GitCore.createWorktree", input.cwd, args, {
234296
234557
  fallbackErrorMessage: "git worktree add failed",
234558
+ env: {
234559
+ GIT_PROGRESS_DELAY: "0",
234560
+ LC_ALL: "C"
234561
+ },
234297
234562
  ...input.progress ? { progress: input.progress } : {}
234298
234563
  });
234564
+ yield* report("checkout", "done");
234565
+ yield* report("dependencies", "running");
234299
234566
  yield* setupSharedWorktreeDependencies(input.cwd, worktreePath);
234567
+ yield* report("dependencies", "done");
234300
234568
  return { worktree: {
234301
234569
  path: worktreePath,
234302
234570
  branch: targetBranch
@@ -297109,7 +297377,7 @@ function resolveBuildCommitFromEnv(env) {
297109
297377
  * environment descriptor down instead of reporting an honest "unknown".
297110
297378
  */
297111
297379
  function readBakedBuildCommit() {
297112
- return "ab022129e97b79359ffb5998de13084bb9f28c79";
297380
+ return "339f0da718657526651ad84cacb841446f1bb38b";
297113
297381
  }
297114
297382
  async function resolveServerBuildCommit(input) {
297115
297383
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -298935,6 +299203,10 @@ const AuthSessionRepositoryLive = require_Schema$1.effect(AuthSessionRepository,
298935
299203
  const SIGNING_SECRET_NAME = "server-signing-key";
298936
299204
  const DEFAULT_SESSION_TTL = require_Schema$1.days(30);
298937
299205
  const DEFAULT_WEBSOCKET_TOKEN_TTL = require_Schema$1.minutes(5);
299206
+ const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
299207
+ message,
299208
+ cause
299209
+ });
298938
299210
  const SessionClaims = require_Schema$1.Struct({
298939
299211
  v: require_Schema$1.Literal(1),
298940
299212
  kind: require_Schema$1.Literal("session"),
@@ -298991,10 +299263,6 @@ const makeSessionCredentialService = require_Schema$1.gen(function* () {
298991
299263
  port: serverConfig.port,
298992
299264
  host: serverConfig.host
298993
299265
  });
298994
- const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298995
- message,
298996
- cause
298997
- });
298998
299266
  const emitUpsert = (clientSession) => require_Path.publish(changesPubSub, {
298999
299267
  type: "clientUpserted",
299000
299268
  clientSession
@@ -299155,15 +299423,28 @@ const makeSessionCredentialService = require_Schema$1.gen(function* () {
299155
299423
  if (require_Schema$1.isSome(row)) {
299156
299424
  if (row.value.revokedAt !== null) return yield* new SessionCredentialError({ message: "Websocket session revoked." });
299157
299425
  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." });
299426
+ } else {
299427
+ const cloudOwnerSession = serverConfig.forgeCloudShellMode === true && claims.method === "proxy-asserted" && forgeCloudOwnerSessionExpiresAt(claims.sid) !== null;
299428
+ if (cloudOwnerSession && isExpiredForgeCloudOwnerSession(claims.sid, now)) return yield* new SessionCredentialError({ message: "Websocket session expired." });
299429
+ if (statelessCookieSessions && (claims.method === "browser-session-cookie" || cloudOwnerSession)) return {
299430
+ sessionId: claims.sid,
299431
+ token,
299432
+ method: claims.method,
299433
+ client: createDefaultClientMetadata(),
299434
+ expiresAt: require_Schema$1.makeUnsafe(claims.exp),
299435
+ subject: claims.sub,
299436
+ role: claims.role
299437
+ };
299438
+ return yield* new SessionCredentialError({ message: "Unknown websocket session." });
299439
+ }
299159
299440
  return {
299160
299441
  sessionId: claims.sid,
299161
299442
  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
299443
+ method: row.value.method,
299444
+ client: toClientMetadata(row.value.client),
299445
+ expiresAt: row.value.expiresAt,
299446
+ subject: row.value.subject,
299447
+ role: row.value.role
299167
299448
  };
299168
299449
  }).pipe(require_Schema$1.mapError((cause) => cause instanceof SessionCredentialError ? cause : new SessionCredentialError({
299169
299450
  message: "Failed to verify websocket token.",
@@ -299441,6 +299722,18 @@ function verifyForgeCloudOwnerCallbackAssertion(assertion, config) {
299441
299722
  //#endregion
299442
299723
  //#region src/auth/Layers/ServerAuth.ts
299443
299724
  const AUTHORIZATION_PREFIX = "Bearer ";
299725
+ function classifyWebSocketTokenFailure(cause) {
299726
+ switch (cause.message) {
299727
+ case "Malformed websocket token.": return "malformed";
299728
+ case "Invalid websocket token signature.": return "invalid_signature";
299729
+ case "Invalid websocket token payload.": return "invalid_payload";
299730
+ case "Websocket token expired.": return "expired_token";
299731
+ case "Websocket session revoked.": return "revoked_parent_session";
299732
+ case "Websocket session expired.": return "expired_parent_session";
299733
+ case "Unknown websocket session.": return "unknown_parent_session";
299734
+ default: return "verification_failed";
299735
+ }
299736
+ }
299444
299737
  function toBootstrapExchangeAuthError(cause) {
299445
299738
  if (cause.status === 500) return new AuthError({
299446
299739
  message: "Failed to validate bootstrap credential.",
@@ -299743,7 +300036,6 @@ const makeServerAuth = require_Schema$1.gen(function* () {
299743
300036
  expiresAt: require_Schema$1.toUtc(issued.expiresAt)
299744
300037
  })));
299745
300038
  const authenticateWebSocketUpgrade = (request) => require_Schema$1.gen(function* () {
299746
- if (serverConfig.forgeCloudShellMode === true) return yield* authenticateRequest(request);
299747
300039
  if (serverConfig.forgeCmmdRuntimeMode) {
299748
300040
  if (serverConfig.forgeCmmdAuthRuntime) {
299749
300041
  const authConfig = readCmmdAuthRuntimeConfig(serverConfig);
@@ -299800,7 +300092,7 @@ const makeServerAuth = require_Schema$1.gen(function* () {
299800
300092
  const requestUrl = require_HttpServer.toURL(request);
299801
300093
  if (require_Schema$1.isSome(requestUrl)) {
299802
300094
  const websocketToken = readWebSocketUpgradeToken(requestUrl.value.searchParams);
299803
- if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(require_Schema$1.map((session) => ({
300095
+ 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
300096
  sessionId: session.sessionId,
299805
300097
  subject: session.subject,
299806
300098
  method: session.method,
@@ -305889,7 +306181,7 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
305889
306181
  }).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
305890
306182
  message: "Failed to dispatch orchestration command.",
305891
306183
  cause
305892
- }))) : orchestrationEngine.dispatch(normalizedCommand).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
306184
+ }))) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(require_Schema$1.mapError((cause) => new OrchestrationDispatchCommandError({
305893
306185
  message: "Failed to dispatch orchestration command.",
305894
306186
  cause
305895
306187
  })));