@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.mjs CHANGED
@@ -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.10";
66006
66006
  //#endregion
66007
66007
  //#region src/sentry.ts
66008
66008
  const SERVER_APP_NAME = "forge-server";
@@ -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,7 +89785,7 @@ var GitHubBroker = class {
89757
89785
  };
89758
89786
  }
89759
89787
  async resolveInstallationToken(auth, repoFullName) {
89760
- const runtimeGrant = this.resolveRuntimeGrant();
89788
+ const runtimeGrant = this.resolveRuntimeGrant("github");
89761
89789
  if (runtimeGrant) {
89762
89790
  const token = await this.fetchRuntimeInstallationToken(runtimeGrant, repoFullName);
89763
89791
  if (!token.ok) return token;
@@ -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,21 @@ 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 fetchRuntimeCredential(grant, input) {
90258
+ const response = await this.fetchImpl(input.url, {
90223
90259
  method: "POST",
90224
90260
  headers: {
90225
90261
  Authorization: `Bearer ${grant.token}`,
90226
90262
  "Content-Type": "application/json"
90227
90263
  },
90228
- body: JSON.stringify({ repo_full_name: repoFullName }),
90264
+ body: JSON.stringify(input.requestBody),
90229
90265
  signal: AbortSignal.timeout(GITHUB_BROKER_REQUEST_TIMEOUT_MS)
90230
90266
  });
90231
90267
  const body = await parseResponseBody(response);
@@ -90234,10 +90270,10 @@ var GitHubBroker = class {
90234
90270
  status: response.status,
90235
90271
  code: response.status === 401 ? "GITHUB_AUTH_REQUIRED" : "GITHUB_BROKER_REQUEST_FAILED",
90236
90272
  ...response.status === 401 ? { userActionRequired: "connect_github" } : {},
90237
- error: errorFromBody(body, `GitHub installation token request failed with status ${response.status}.`),
90273
+ error: errorFromBody(body, `${input.label} request failed with status ${response.status}.`),
90238
90274
  data: body
90239
90275
  };
90240
- const token = typeof body === "object" && body !== null ? body.token : null;
90276
+ const token = typeof body === "object" && body !== null ? body[input.responseField] : null;
90241
90277
  if (typeof token !== "string" || token.trim().length === 0) return invalidInstallationTokenResult();
90242
90278
  return {
90243
90279
  ok: true,
@@ -90256,6 +90292,25 @@ var GitHubBroker = class {
90256
90292
  const branch = result.result.stdout.trim();
90257
90293
  return branch.length > 0 ? branch : null;
90258
90294
  }
90295
+ async resolveRemoteDefaultBranch(cwd) {
90296
+ const result = await this.runGit([
90297
+ "-C",
90298
+ cwd,
90299
+ "ls-remote",
90300
+ "--symref",
90301
+ "origin",
90302
+ "HEAD"
90303
+ ], { allowNonZeroExit: true });
90304
+ if (!isGitRunResult(result)) return result;
90305
+ if (result.result.code !== 0) return gitCommandFailure("GITHUB_DEFAULT_BRANCH_LOOKUP_FAILED", result.result.stderr, "Could not determine the repository default branch.");
90306
+ const branch = /^ref:\s+refs\/heads\/([^\s]+)\s+HEAD$/m.exec(result.result.stdout)?.[1];
90307
+ if (branch === void 0) return {
90308
+ ok: false,
90309
+ code: "GITHUB_DEFAULT_BRANCH_RESPONSE_INVALID",
90310
+ error: "GitHub did not return a valid default branch for the repository."
90311
+ };
90312
+ return validateGitBranchName(branch) ?? branch;
90313
+ }
90259
90314
  };
90260
90315
  function isCmmdCloudRuntimeModeEnv() {
90261
90316
  return process.env.FORGE_CMMD_RUNTIME_MODE === "true" || process.env.FORGE_CMMD_RUNTIME_MODE === "1";
@@ -90340,20 +90395,40 @@ function validateCloneDestination(destination) {
90340
90395
  code: "GITHUB_CLONE_DESTINATION_INVALID",
90341
90396
  error: "An absolute clone destination is required."
90342
90397
  };
90343
- if (existsSync(destination)) return {
90398
+ if (existsSync(destination)) return cloneDestinationExistsResult();
90399
+ return null;
90400
+ }
90401
+ function cloneDestinationExistsResult() {
90402
+ return {
90344
90403
  ok: false,
90345
90404
  code: "GITHUB_CLONE_DESTINATION_EXISTS",
90346
90405
  error: "Clone destination already exists; choose a new empty path."
90347
90406
  };
90348
- return null;
90407
+ }
90408
+ function isNodeErrorWithCode(error, code) {
90409
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
90410
+ }
90411
+ async function removeFailedCloneDestination(destination, failure) {
90412
+ try {
90413
+ await rm(destination, {
90414
+ recursive: true,
90415
+ force: true
90416
+ });
90417
+ return failure;
90418
+ } catch {
90419
+ return {
90420
+ ...failure,
90421
+ error: `${failure.error ?? "Clone failed."} The incomplete clone destination could not be removed.`
90422
+ };
90423
+ }
90349
90424
  }
90350
90425
  function validateCloneInput(input) {
90351
90426
  const repoFullNameValidation = validateRepoFullName(input.repoFullName);
90352
90427
  if (repoFullNameValidation) return repoFullNameValidation;
90353
90428
  const destinationValidation = validateCloneDestination(input.destination);
90354
90429
  if (destinationValidation) return destinationValidation;
90355
- const branch = input.branch?.trim() || "main";
90356
- return validateGitBranchName(branch) ?? { branch };
90430
+ const branch = input.branch?.trim() || null;
90431
+ return branch === null ? { branch } : validateGitBranchName(branch) ?? { branch };
90357
90432
  }
90358
90433
  function validateGitBranchName(branch) {
90359
90434
  if (branch.length === 0 || branch.startsWith("-") || Array.from(branch).some((character) => {
@@ -93518,6 +93593,17 @@ const admitNormalizedDispatchCommand = (command, admit) => uninterruptibleMask$1
93518
93593
  //#region src/orchestration/bootstrapCommandCoordinator.ts
93519
93594
  const MAX_COMPLETED_COMMANDS_PER_ENGINE = 256;
93520
93595
  const commandsByEngine = /* @__PURE__ */ new WeakMap();
93596
+ const sealBootstrapCommand = (engine, commandId) => {
93597
+ const entry = commandsByEngine.get(engine)?.get(commandId);
93598
+ if (entry) entry.cancellable = false;
93599
+ };
93600
+ const cancelBootstrapCommand = (engine, threadId) => promise(async () => {
93601
+ const entry = [...commandsByEngine.get(engine)?.values() ?? []].find((candidate) => candidate.threadId === threadId && !candidate.completed && candidate.cancellable);
93602
+ if (!entry) return false;
93603
+ entry.controller.abort();
93604
+ await entry.promise.catch(() => void 0);
93605
+ return true;
93606
+ });
93521
93607
  const toBootstrapError = (error) => is$1(OrchestrationDispatchCommandError)(error) ? error : new OrchestrationDispatchCommandError({
93522
93608
  message: error instanceof Error ? error.message : "Bootstrap command failed.",
93523
93609
  cause: error
@@ -93537,7 +93623,7 @@ const pruneCompletedCommands = (commands) => {
93537
93623
  * the registry to one server runtime, so reconnects share work while separate
93538
93624
  * runtimes remain isolated.
93539
93625
  */
93540
- const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93626
+ const runBootstrapCommand = (engine, commandId, bootstrapEffect, threadId) => {
93541
93627
  let commands = commandsByEngine.get(engine);
93542
93628
  if (commands === void 0) {
93543
93629
  commands = /* @__PURE__ */ new Map();
@@ -93550,16 +93636,21 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93550
93636
  });
93551
93637
  pruneCompletedCommands(commands);
93552
93638
  let entry;
93553
- const promise = runPromise(bootstrapEffect).then((result) => {
93639
+ const controller = new AbortController();
93640
+ const promise = Promise.resolve().then(() => runPromise(bootstrapEffect, { signal: controller.signal })).then((result) => {
93554
93641
  entry.completed = true;
93555
93642
  return result;
93556
93643
  }, (error) => {
93557
- commands.delete(commandId);
93644
+ entry.completed = true;
93645
+ if (!controller.signal.aborted) commands.delete(commandId);
93558
93646
  throw error;
93559
93647
  });
93560
93648
  entry = {
93561
93649
  promise,
93562
- completed: false
93650
+ completed: false,
93651
+ threadId,
93652
+ controller,
93653
+ cancellable: true
93563
93654
  };
93564
93655
  commands.set(commandId, entry);
93565
93656
  return tryPromise({
@@ -93569,6 +93660,10 @@ const runBootstrapCommand = (engine, commandId, bootstrapEffect) => {
93569
93660
  };
93570
93661
  //#endregion
93571
93662
  //#region src/orchestration/bootstrapDispatch.ts
93663
+ const interruptBootstrapTurnStart = (engine, command) => gen(function* () {
93664
+ if (yield* cancelBootstrapCommand(engine, command.threadId)) return { sequence: (yield* engine.getReadModel()).snapshotSequence };
93665
+ return yield* engine.dispatch(command);
93666
+ });
93572
93667
  const MAX_BOOTSTRAP_OUTPUT_LINE_LENGTH = 1e3;
93573
93668
  const toBootstrapDispatchCommandCauseError = (cause) => {
93574
93669
  const error = squash(cause);
@@ -93655,6 +93750,7 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93655
93750
  const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
93656
93751
  const baseBranchLabel = bootstrapWorktree.baseBranch ?? "<default>";
93657
93752
  const requestedPayload = {
93753
+ turnRequestedAt: input.command.createdAt,
93658
93754
  projectCwd: bootstrapWorktree.projectCwd,
93659
93755
  baseBranch: bootstrapWorktree.baseBranch,
93660
93756
  branch: bootstrapWorktree.branch,
@@ -93675,6 +93771,16 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93675
93771
  branch: bootstrapWorktree.baseBranch,
93676
93772
  newBranch: bootstrapWorktree.branch,
93677
93773
  path: null,
93774
+ onStage: (update) => appendWorktreeActivity({
93775
+ orchestrationEngine: input.orchestrationEngine,
93776
+ threadId: input.command.threadId,
93777
+ commandId: CommandId.make(`server:worktree-stage:${input.command.commandId}:${crypto.randomUUID()}`),
93778
+ kind: "worktree.stage",
93779
+ summary: "Preparing worktree",
93780
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
93781
+ payload: update,
93782
+ tone: "info"
93783
+ }).pipe(asVoid, ignore),
93678
93784
  progress: {
93679
93785
  onStdoutLine: (line) => {
93680
93786
  const detail = truncateBootstrapOutputLine(line);
@@ -93714,6 +93820,7 @@ const prepareBootstrapWorktree = (input) => gen(function* () {
93714
93820
  }
93715
93821
  }
93716
93822
  }).pipe(catchCause((cause) => {
93823
+ if (hasInterruptsOnly(cause)) return failCause$1(cause);
93717
93824
  const error = squash(cause);
93718
93825
  const detail = error instanceof Error ? error.message : "Unknown worktree failure.";
93719
93826
  return appendWorktreeActivity({
@@ -93840,7 +93947,16 @@ const dispatchBootstrapTurnStart = (input, command) => gen(function* () {
93840
93947
  worktreePath
93841
93948
  }),
93842
93949
  onSuccess: (setupResult) => {
93843
- if (setupResult.status !== "started") return void_$1;
93950
+ if (setupResult.status !== "started") return appendSetupScriptActivity({
93951
+ orchestrationEngine,
93952
+ threadId: command.threadId,
93953
+ commandId: bootstrapCommandId(command, "setup-script-skipped"),
93954
+ kind: "setup-script.skipped",
93955
+ summary: "No setup script configured",
93956
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
93957
+ payload: {},
93958
+ tone: "info"
93959
+ }).pipe(asVoid, ignore);
93844
93960
  return recordSetupScriptStarted({
93845
93961
  requestedAt,
93846
93962
  worktreePath,
@@ -93870,13 +93986,33 @@ const dispatchBootstrapTurnStart = (input, command) => gen(function* () {
93870
93986
  targetProjectCwd = preparedWorktree.projectCwd;
93871
93987
  }
93872
93988
  yield* runSetupProgram();
93989
+ if (bootstrap?.prepareWorktree) yield* appendWorktreeActivity({
93990
+ orchestrationEngine,
93991
+ threadId: command.threadId,
93992
+ commandId: bootstrapCommandId(command, "launch-agent-starting"),
93993
+ kind: "launch.agent-starting",
93994
+ summary: "Starting agent",
93995
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
93996
+ payload: { setupRequested: bootstrap.runSetupScript === true },
93997
+ tone: "info"
93998
+ }).pipe(ignore);
93999
+ sealBootstrapCommand(orchestrationEngine, command.commandId);
93873
94000
  return yield* orchestrationEngine.dispatch(finalTurnStartCommand);
93874
94001
  });
93875
- return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(catchCause((cause) => {
94002
+ return yield* runBootstrapCommand(orchestrationEngine, command.commandId, bootstrapProgram.pipe(onInterrupt(() => appendWorktreeActivity({
94003
+ orchestrationEngine,
94004
+ threadId: command.threadId,
94005
+ commandId: bootstrapCommandId(command, "launch-cancelled"),
94006
+ kind: "launch.cancelled",
94007
+ summary: "Launch cancelled",
94008
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
94009
+ payload: {},
94010
+ tone: "info"
94011
+ }).pipe(ignore)), catchCause((cause) => {
93876
94012
  const dispatchError = toBootstrapDispatchCommandCauseError(cause);
93877
94013
  if (hasInterruptsOnly(cause)) return fail(dispatchError);
93878
94014
  return cleanupCreatedThread().pipe(flatMap(() => fail(dispatchError)));
93879
- })));
94015
+ })), command.threadId);
93880
94016
  });
93881
94017
  //#endregion
93882
94018
  //#region src/orchestration/Services/OrchestrationEngine.ts
@@ -100555,6 +100691,7 @@ function emptyConfig(source, path) {
100555
100691
  terminals: [],
100556
100692
  systemPackages: [],
100557
100693
  secrets: [],
100694
+ repositoryDependencies: [],
100558
100695
  issues: []
100559
100696
  };
100560
100697
  }
@@ -100640,6 +100777,7 @@ function readConfigFile(source, path) {
100640
100777
  terminals: terminalsFromValue(parsed.terminals),
100641
100778
  systemPackages: stringArray(parsed.systemPackages ?? parsed.aptPackages ?? parsed.packages),
100642
100779
  secrets: stringArray(parsed.secrets ?? parsed.envSecrets),
100780
+ repositoryDependencies: stringArray(parsed.repositoryDependencies),
100643
100781
  issues: []
100644
100782
  };
100645
100783
  }
@@ -101052,6 +101190,7 @@ const EMPTY_WORKSPACE_ENVIRONMENT_CONFIG = {
101052
101190
  terminals: [],
101053
101191
  systemPackages: [],
101054
101192
  secrets: [],
101193
+ repositoryDependencies: [],
101055
101194
  issues: []
101056
101195
  };
101057
101196
  function buildCwdCheck(cwdExists) {
@@ -105854,7 +105993,7 @@ const makeWsRpcLayer = (currentSessionId, currentUserId) => WsRpcGroup.toLayer(g
105854
105993
  git,
105855
105994
  gitStatusBroadcaster,
105856
105995
  projectSetupScriptRunner
105857
- }, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105996
+ }, normalizedCommand) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105858
105997
  return startup.enqueueCommand(dispatchEffect).pipe(mapError((cause) => toDispatchCommandError(cause, "Failed to dispatch orchestration command")));
105859
105998
  };
105860
105999
  const loadServerConfig = gen(function* () {
@@ -151203,6 +151342,86 @@ async function attestCodexLocalAccountIdentity(input) {
151203
151342
  return identity;
151204
151343
  }
151205
151344
  //#endregion
151345
+ //#region src/workspace/workspaceRepositoryDependencies.ts
151346
+ function repositoryFullName(value) {
151347
+ return value.trim().match(/^(?:https?:\/\/)?github\.com\/([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?\/?$/i)?.[1] ?? null;
151348
+ }
151349
+ function dependencyDestination(cloudProjectsRoot, repoFullName) {
151350
+ const root = resolve(cloudProjectsRoot);
151351
+ const destination = resolve(join(root, basename(repoFullName)));
151352
+ const pathFromRoot = relative(root, destination);
151353
+ if (pathFromRoot.startsWith("..") || pathFromRoot === "") throw new Error(`Repository dependency ${repoFullName} has an unsafe destination.`);
151354
+ return destination;
151355
+ }
151356
+ async function readOriginFromGit(destination) {
151357
+ const result = await runProcess("git", [
151358
+ "-C",
151359
+ destination,
151360
+ "remote",
151361
+ "get-url",
151362
+ "origin"
151363
+ ], {
151364
+ allowNonZeroExit: true,
151365
+ timeoutMs: 1e4
151366
+ });
151367
+ return result.code === 0 ? result.stdout.trim() : null;
151368
+ }
151369
+ function resolveRepositoryDependencies(declarations, cloudProjectsRoot) {
151370
+ const destinations = /* @__PURE__ */ new Set();
151371
+ return declarations.map((declaration) => {
151372
+ const repoFullName = repositoryFullName(declaration);
151373
+ if (repoFullName === null) throw new Error(`Unsupported repository dependency: ${declaration}`);
151374
+ const destination = dependencyDestination(cloudProjectsRoot, repoFullName);
151375
+ if (destinations.has(destination)) throw new Error(`Repository dependencies collide at ${destination}.`);
151376
+ destinations.add(destination);
151377
+ return {
151378
+ repoFullName,
151379
+ destination
151380
+ };
151381
+ });
151382
+ }
151383
+ async function prepareRepositoryDependency(input) {
151384
+ const { repoFullName, destination } = input.dependency;
151385
+ if (existsSync(destination)) {
151386
+ const origin = await input.readOrigin(destination);
151387
+ const existingRepo = origin === null ? null : parseRepoFullNameFromRemoteUrl(origin);
151388
+ if (existingRepo?.toLowerCase() !== repoFullName.toLowerCase()) throw new Error(`Repository dependency destination ${destination} contains ${existingRepo ?? "no GitHub repository"} instead of ${repoFullName}.`);
151389
+ return {
151390
+ repoFullName,
151391
+ destination,
151392
+ outcome: "existing"
151393
+ };
151394
+ }
151395
+ const result = await input.broker.cloneRepository(input.auth, {
151396
+ repoFullName,
151397
+ destination
151398
+ });
151399
+ if (!result.ok) throw new Error(`Could not prepare repository dependency ${repoFullName}: ${result.error ?? result.code ?? "clone failed"}`);
151400
+ return {
151401
+ repoFullName,
151402
+ destination,
151403
+ outcome: "cloned"
151404
+ };
151405
+ }
151406
+ async function prepareWorkspaceRepositoryDependencies(input) {
151407
+ if (!input.runtimeMode) return [];
151408
+ const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
151409
+ if (dependencies.length === 0) return [];
151410
+ const broker = input.broker ?? new GitHubBroker();
151411
+ const auth = broker.runtimeGrantAuthContext("github");
151412
+ if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime GitHub grant.");
151413
+ const readOrigin = input.readOrigin ?? readOriginFromGit;
151414
+ const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
151415
+ const prepared = [];
151416
+ for (const dependency of resolved) prepared.push(await prepareRepositoryDependency({
151417
+ dependency,
151418
+ broker,
151419
+ auth,
151420
+ readOrigin
151421
+ }));
151422
+ return prepared;
151423
+ }
151424
+ //#endregion
151206
151425
  //#region src/codexAppServerManager.ts
151207
151426
  const CODEX_LOGIN_STATUS_TIMEOUT_MS = 5e3;
151208
151427
  const CODEX_SKILLS_LIST_TIMEOUT_MS = 4e3;
@@ -151537,6 +151756,11 @@ var CodexAppServerManager = class extends EventEmitter$1 {
151537
151756
  let context;
151538
151757
  try {
151539
151758
  const resolvedCwd = input.cwd ?? process.cwd();
151759
+ await prepareWorkspaceRepositoryDependencies({
151760
+ cwd: resolvedCwd,
151761
+ cloudProjectsRoot: process.env.FORGE_CLOUD_PROJECTS_ROOT?.trim() || "/projects",
151762
+ runtimeMode: process.env.FORGE_CMMD_RUNTIME_MODE === "true"
151763
+ });
151540
151764
  let workspacePreflightReport = runWorkspaceEnvironmentPreflight({
151541
151765
  cwd: resolvedCwd,
151542
151766
  env: process.env
@@ -216617,6 +216841,19 @@ const makeClaudeAdapter = fn$1("makeClaudeAdapter")(function* (options) {
216617
216841
  const resumeState = readClaudeResumeState(input.resumeCursor);
216618
216842
  const threadId = input.threadId;
216619
216843
  const { existingResumeSessionId, newSessionId, sessionId } = yield* resolveClaudeStartSessionIds(resumeState);
216844
+ const workspaceCwd = input.cwd;
216845
+ if (workspaceCwd) yield* tryPromise({
216846
+ try: () => prepareWorkspaceRepositoryDependencies({
216847
+ cwd: workspaceCwd,
216848
+ cloudProjectsRoot: serverConfig.cloudProjectsRoot ?? "/projects",
216849
+ runtimeMode: serverConfig.forgeCmmdRuntimeMode === true
216850
+ }),
216851
+ catch: (cause) => new ProviderAdapterValidationError({
216852
+ provider: PROVIDER$11,
216853
+ operation: "startSession",
216854
+ issue: cause instanceof Error ? cause.message : "Could not prepare declared repository dependencies."
216855
+ })
216856
+ });
216620
216857
  const runtimeContext = yield* context();
216621
216858
  const runFork = runForkWith(runtimeContext);
216622
216859
  const runPromise = runPromiseWith(runtimeContext);
@@ -232923,12 +233160,12 @@ const collectOutput = fn$1("collectOutput")(function* (input, stream, maxOutputB
232923
233160
  let lineBuffer = "";
232924
233161
  let truncated = false;
232925
233162
  const emitCompleteLines = fn$1("emitCompleteLines")(function* (flush) {
232926
- let newlineIndex = lineBuffer.indexOf("\n");
233163
+ let newlineIndex = lineBuffer.search(/[\r\n]/);
232927
233164
  while (newlineIndex >= 0) {
232928
233165
  const line = lineBuffer.slice(0, newlineIndex).replace(/\r$/, "");
232929
233166
  lineBuffer = lineBuffer.slice(newlineIndex + 1);
232930
233167
  if (line.length > 0 && onLine) yield* onLine(line);
232931
- newlineIndex = lineBuffer.indexOf("\n");
233168
+ newlineIndex = lineBuffer.search(/[\r\n]/);
232932
233169
  }
232933
233170
  if (flush) {
232934
233171
  const trailing = lineBuffer.replace(/\r$/, "");
@@ -233076,6 +233313,7 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233076
233313
  operation,
233077
233314
  cwd,
233078
233315
  args,
233316
+ ...options.env ? { env: options.env } : {},
233079
233317
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {},
233080
233318
  allowNonZeroExit: true,
233081
233319
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
@@ -233797,12 +234035,19 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233797
234035
  };
233798
234036
  });
233799
234037
  const createWorktree = fn$1("createWorktree")(function* (input) {
234038
+ const report = (stage, status, detail) => input.onStage?.({
234039
+ stage,
234040
+ status,
234041
+ ...detail ? { detail } : {}
234042
+ }) ?? void_$1;
234043
+ yield* report("resolve", "running");
233800
234044
  const base = input.branch ?? (yield* resolveDefaultBaseBranch(input.cwd));
233801
234045
  const startPoint = yield* resolveWorktreeStartPoint({
233802
234046
  cwd: input.cwd,
233803
234047
  base,
233804
234048
  creatingNewBranch: input.newBranch !== void 0
233805
234049
  });
234050
+ yield* report("resolve", "done", startPoint);
233806
234051
  const targetBranch = input.newBranch ?? base;
233807
234052
  const sanitizedBranch = targetBranch.replace(/\//g, "-");
233808
234053
  const repoName = path$10.basename(input.cwd);
@@ -233814,7 +234059,10 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233814
234059
  limit: 200
233815
234060
  })).branches.find((branch) => branch.isRemote !== true && branch.name === targetBranch && branch.worktreePath !== null && path.resolve(branch.worktreePath) === canonicalWorktreePath);
233816
234061
  if (existingBranch?.worktreePath) {
234062
+ yield* report("checkout", "skipped", "Existing worktree");
234063
+ yield* report("dependencies", "running");
233817
234064
  yield* setupSharedWorktreeDependencies(input.cwd, existingBranch.worktreePath);
234065
+ yield* report("dependencies", "done");
233818
234066
  return { worktree: {
233819
234067
  path: worktreePath,
233820
234068
  branch: targetBranch
@@ -233833,11 +234081,19 @@ const GitCoreLive = effect(GitCore, fn$1("makeGitCore")(function* (options) {
233833
234081
  worktreePath,
233834
234082
  startPoint
233835
234083
  ];
234084
+ yield* report("checkout", "running");
233836
234085
  yield* executeGit("GitCore.createWorktree", input.cwd, args, {
233837
234086
  fallbackErrorMessage: "git worktree add failed",
234087
+ env: {
234088
+ GIT_PROGRESS_DELAY: "0",
234089
+ LC_ALL: "C"
234090
+ },
233838
234091
  ...input.progress ? { progress: input.progress } : {}
233839
234092
  });
234093
+ yield* report("checkout", "done");
234094
+ yield* report("dependencies", "running");
233840
234095
  yield* setupSharedWorktreeDependencies(input.cwd, worktreePath);
234096
+ yield* report("dependencies", "done");
233841
234097
  return { worktree: {
233842
234098
  path: worktreePath,
233843
234099
  branch: targetBranch
@@ -296549,7 +296805,7 @@ function resolveBuildCommitFromEnv(env) {
296549
296805
  * environment descriptor down instead of reporting an honest "unknown".
296550
296806
  */
296551
296807
  function readBakedBuildCommit() {
296552
- return "ab022129e97b79359ffb5998de13084bb9f28c79";
296808
+ return "d9a524e82e63dd163e8f06083c14b5913b6fdf88";
296553
296809
  }
296554
296810
  async function resolveServerBuildCommit(input) {
296555
296811
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -298361,6 +298617,10 @@ const AuthSessionRepositoryLive = effect(AuthSessionRepository, gen(function* ()
298361
298617
  const SIGNING_SECRET_NAME = "server-signing-key";
298362
298618
  const DEFAULT_SESSION_TTL = days(30);
298363
298619
  const DEFAULT_WEBSOCKET_TOKEN_TTL = minutes(5);
298620
+ const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298621
+ message,
298622
+ cause
298623
+ });
298364
298624
  const SessionClaims = Struct({
298365
298625
  v: Literal(1),
298366
298626
  kind: Literal("session"),
@@ -298417,10 +298677,6 @@ const SessionCredentialServiceLive = effect(SessionCredentialService, gen(functi
298417
298677
  port: serverConfig.port,
298418
298678
  host: serverConfig.host
298419
298679
  });
298420
- const toSessionCredentialError = (message) => (cause) => new SessionCredentialError({
298421
- message,
298422
- cause
298423
- });
298424
298680
  const emitUpsert = (clientSession) => publish(changesPubSub, {
298425
298681
  type: "clientUpserted",
298426
298682
  clientSession
@@ -298581,15 +298837,28 @@ const SessionCredentialServiceLive = effect(SessionCredentialService, gen(functi
298581
298837
  if (isSome(row)) {
298582
298838
  if (row.value.revokedAt !== null) return yield* new SessionCredentialError({ message: "Websocket session revoked." });
298583
298839
  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." });
298840
+ } else {
298841
+ const cloudOwnerSession = serverConfig.forgeCloudShellMode === true && claims.method === "proxy-asserted" && forgeCloudOwnerSessionExpiresAt(claims.sid) !== null;
298842
+ if (cloudOwnerSession && isExpiredForgeCloudOwnerSession(claims.sid, now)) return yield* new SessionCredentialError({ message: "Websocket session expired." });
298843
+ if (statelessCookieSessions && (claims.method === "browser-session-cookie" || cloudOwnerSession)) return {
298844
+ sessionId: claims.sid,
298845
+ token,
298846
+ method: claims.method,
298847
+ client: createDefaultClientMetadata(),
298848
+ expiresAt: makeUnsafe$5(claims.exp),
298849
+ subject: claims.sub,
298850
+ role: claims.role
298851
+ };
298852
+ return yield* new SessionCredentialError({ message: "Unknown websocket session." });
298853
+ }
298585
298854
  return {
298586
298855
  sessionId: claims.sid,
298587
298856
  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
298857
+ method: row.value.method,
298858
+ client: toClientMetadata(row.value.client),
298859
+ expiresAt: row.value.expiresAt,
298860
+ subject: row.value.subject,
298861
+ role: row.value.role
298593
298862
  };
298594
298863
  }).pipe(mapError((cause) => cause instanceof SessionCredentialError ? cause : new SessionCredentialError({
298595
298864
  message: "Failed to verify websocket token.",
@@ -298866,6 +299135,18 @@ function verifyForgeCloudOwnerCallbackAssertion(assertion, config) {
298866
299135
  //#endregion
298867
299136
  //#region src/auth/Layers/ServerAuth.ts
298868
299137
  const AUTHORIZATION_PREFIX = "Bearer ";
299138
+ function classifyWebSocketTokenFailure(cause) {
299139
+ switch (cause.message) {
299140
+ case "Malformed websocket token.": return "malformed";
299141
+ case "Invalid websocket token signature.": return "invalid_signature";
299142
+ case "Invalid websocket token payload.": return "invalid_payload";
299143
+ case "Websocket token expired.": return "expired_token";
299144
+ case "Websocket session revoked.": return "revoked_parent_session";
299145
+ case "Websocket session expired.": return "expired_parent_session";
299146
+ case "Unknown websocket session.": return "unknown_parent_session";
299147
+ default: return "verification_failed";
299148
+ }
299149
+ }
298869
299150
  function toBootstrapExchangeAuthError(cause) {
298870
299151
  if (cause.status === 500) return new AuthError({
298871
299152
  message: "Failed to validate bootstrap credential.",
@@ -299168,7 +299449,6 @@ const ServerAuthLive = effect(ServerAuth, gen(function* () {
299168
299449
  expiresAt: toUtc(issued.expiresAt)
299169
299450
  })));
299170
299451
  const authenticateWebSocketUpgrade = (request) => gen(function* () {
299171
- if (serverConfig.forgeCloudShellMode === true) return yield* authenticateRequest(request);
299172
299452
  if (serverConfig.forgeCmmdRuntimeMode) {
299173
299453
  if (serverConfig.forgeCmmdAuthRuntime) {
299174
299454
  const authConfig = readCmmdAuthRuntimeConfig(serverConfig);
@@ -299225,7 +299505,7 @@ const ServerAuthLive = effect(ServerAuth, gen(function* () {
299225
299505
  const requestUrl = toURL(request);
299226
299506
  if (isSome(requestUrl)) {
299227
299507
  const websocketToken = readWebSocketUpgradeToken(requestUrl.value.searchParams);
299228
- if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(map$3((session) => ({
299508
+ if (websocketToken) return yield* sessions.verifyWebSocketToken(websocketToken).pipe(tapError((cause) => logWarning$1("Rejected websocket token.").pipe(annotateLogs({ category: classifyWebSocketTokenFailure(cause) }))), map$3((session) => ({
299229
299509
  sessionId: session.sessionId,
299230
299510
  subject: session.subject,
299231
299511
  method: session.method,
@@ -305264,7 +305544,7 @@ const orchestrationDispatchRouteLayer = add$1("POST", "/api/orchestration/dispat
305264
305544
  }).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305265
305545
  message: "Failed to dispatch orchestration command.",
305266
305546
  cause
305267
- }))) : orchestrationEngine.dispatch(normalizedCommand).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305547
+ }))) : (normalizedCommand.type === "thread.turn.interrupt" ? interruptBootstrapTurnStart(orchestrationEngine, normalizedCommand) : orchestrationEngine.dispatch(normalizedCommand)).pipe(mapError((cause) => new OrchestrationDispatchCommandError({
305268
305548
  message: "Failed to dispatch orchestration command.",
305269
305549
  cause
305270
305550
  })));