@cmmd-center/forge 0.10.4 → 0.10.6

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.
Files changed (25) hide show
  1. package/dist/bin.cjs +106 -37
  2. package/dist/bin.mjs +129 -60
  3. package/dist/client/assets/{CanvasPanel-BYsK8tAq.js → CanvasPanel-Dr4I7MnM.js} +2 -2
  4. package/dist/client/assets/{CompositeItem-khilqBJh.js → CompositeItem-Cj6_oAlz.js} +2 -2
  5. package/dist/client/assets/{DiffPanel-CrG7-Qe5.js → DiffPanel-njtnlFro.js} +3 -3
  6. package/dist/client/assets/{DiffPanel.logic--xX71t7M.js → DiffPanel.logic-BiWRNb4k.js} +2 -2
  7. package/dist/client/assets/{DiffPanelShell-CSeG4SSQ.js → DiffPanelShell-AKUK0L-g.js} +33 -33
  8. package/dist/client/assets/{DiffWorkerPoolProvider-C1s-wxqS.js → DiffWorkerPoolProvider-DWrja16U.js} +51 -51
  9. package/dist/client/assets/PullRequestCodePanel-CAaq9dk8.js +5 -0
  10. package/dist/client/assets/{Virtualizer-BaEpKPBi.js → Virtualizer-BXpiamky.js} +2 -2
  11. package/dist/client/assets/{canvasStateStore-CVTcDwc2.js → canvasStateStore-DncdOQ4J.js} +2 -2
  12. package/dist/client/assets/{cmmdAuthRedirect-B6WFFIw6.js → cmmdAuthRedirect-DWiMDVzN.js} +2 -2
  13. package/dist/client/assets/index-DBIIL0ML.js +151 -0
  14. package/dist/client/assets/index-T36rURmx.css +1 -0
  15. package/dist/client/assets/src-CtyKlVph.js +3 -0
  16. package/dist/client/assets/{toggle-DnYrc2U3.js → toggle-pTm2fPLg.js} +2 -2
  17. package/dist/client/assets/{useTriggerFocusGuards-CVcUEajo.js → useTriggerFocusGuards-B4Ejoaox.js} +2 -2
  18. package/dist/client/index.html +11 -11
  19. package/dist/forge-build.json +1 -1
  20. package/dist/{open-FIgTDosf.mjs → open-D8mGyWmt.mjs} +3 -3
  21. package/package.json +1 -1
  22. package/dist/client/assets/PullRequestCodePanel-CDmYMNEt.js +0 -5
  23. package/dist/client/assets/index-DB-C-45R.css +0 -1
  24. package/dist/client/assets/index-zQmFiQS7.js +0 -151
  25. package/dist/client/assets/src-BSCF6BY3.js +0 -3
package/dist/bin.mjs CHANGED
@@ -22,7 +22,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
22
22
  import * as FS$1 from "node:fs";
23
23
  import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
24
24
  import * as NodeOS from "node:os";
25
- import os, { homedir, networkInterfaces, tmpdir } from "node:os";
25
+ import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
26
26
  import * as Path$1 from "node:path";
27
27
  import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
28
28
  import { pathToFileURL } from "node:url";
@@ -16520,7 +16520,7 @@ function makeNetServiceShape(createServer = Net.createServer) {
16520
16520
  }
16521
16521
  //#endregion
16522
16522
  //#region ../../packages/shared/src/releaseInfo.generated.ts
16523
- const FORGE_RELEASE_VERSION = "0.10.3";
16523
+ const FORGE_RELEASE_VERSION = "0.10.4";
16524
16524
  //#endregion
16525
16525
  //#region ../../packages/shared/src/hostProcess.ts
16526
16526
  /**
@@ -18157,6 +18157,7 @@ const ExecutionEnvironmentDescriptor = Struct({
18157
18157
  platform: ExecutionEnvironmentPlatform,
18158
18158
  serverVersion: TrimmedNonEmptyString,
18159
18159
  buildCommit: optional$4(TrimmedNonEmptyString),
18160
+ deviceRuntimeId: optional$4(TrimmedNonEmptyString),
18160
18161
  capabilities: ExecutionEnvironmentCapabilities
18161
18162
  });
18162
18163
  Literals([
@@ -21906,6 +21907,7 @@ const DEFAULT_CLIENT_SETTINGS = decodeSync(Struct({
21906
21907
  confirmThreadArchive: Boolean$1.pipe(withDecodingDefault(succeed(true))),
21907
21908
  confirmThreadDelete: Boolean$1.pipe(withDecodingDefault(succeed(true))),
21908
21909
  showInteractionModeToggle: Boolean$1.pipe(withDecodingDefault(succeed(false))),
21910
+ composerCollapseOnBlur: Boolean$1.pipe(withDecodingDefault(succeed(true))),
21909
21911
  diffWordWrap: Boolean$1.pipe(withDecodingDefault(succeed(true))),
21910
21912
  previewAutoOpen: Boolean$1.pipe(withDecodingDefault(succeed(false))),
21911
21913
  previewRememberLastUrl: Boolean$1.pipe(withDecodingDefault(succeed(true))),
@@ -49730,7 +49732,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
49730
49732
  const min = forceColor || 0;
49731
49733
  if (env.TERM === "dumb") return min;
49732
49734
  if (process$1.platform === "win32") {
49733
- const osRelease = os.release().split(".");
49735
+ const osRelease = $U.release().split(".");
49734
49736
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
49735
49737
  return 1;
49736
49738
  }
@@ -52173,7 +52175,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
52173
52175
  exports.processDetector = void 0;
52174
52176
  const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
52175
52177
  const semconv_1 = require_semconv$1();
52176
- const os$4 = __require("os");
52178
+ const os$3 = __require("os");
52177
52179
  /**
52178
52180
  * ProcessDetector will be used to detect the resources related current process running
52179
52181
  * and being instrumented from the NodeJS Process module.
@@ -52195,7 +52197,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
52195
52197
  };
52196
52198
  if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
52197
52199
  try {
52198
- const userInfo = os$4.userInfo();
52200
+ const userInfo = os$3.userInfo();
52199
52201
  attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
52200
52202
  } catch (e) {
52201
52203
  api_1.diag.debug(`error obtaining process owner: ${e}`);
@@ -65118,7 +65120,7 @@ function normalizeNumberish(value) {
65118
65120
  }
65119
65121
  //#endregion
65120
65122
  //#region package.json
65121
- var version$1 = "0.10.4";
65123
+ var version$1 = "0.10.6";
65122
65124
  //#endregion
65123
65125
  //#region src/sentry.ts
65124
65126
  const SERVER_APP_NAME = "forge-server";
@@ -67886,6 +67888,15 @@ function parsePersistedServerObservabilitySettings(raw) {
67886
67888
  * @module ServerConfig
67887
67889
  */
67888
67890
  const DEFAULT_PORT = 13773;
67891
+ /**
67892
+ * Where a managed cloud runtime keeps the user's checkouts when
67893
+ * FORGE_CLOUD_PROJECTS_ROOT is not set. CMMD seeds every sprite checkout under
67894
+ * /projects (forge-guest-layout.ts on the CMMD side) and passes that value in
67895
+ * the Service env, so this default only matters for a server started without
67896
+ * it. It used to be a path under the Fly-era mounted volume; on a sprite the
67897
+ * two repos disagreed and every local-checkout turn was refused (FOR-36484).
67898
+ */
67899
+ const DEFAULT_CLOUD_PROJECTS_ROOT = "/projects";
67889
67900
  const RuntimeMode = Literals(["web", "desktop"]);
67890
67901
  Literals(["browser", "headless"]);
67891
67902
  const deriveServerPaths = fn$1(function* (baseDir, _devUrl, _mode) {
@@ -68011,7 +68022,7 @@ var ServerConfig = class ServerConfig extends Service$1()("forge/config/ServerCo
68011
68022
  forgeCodexHomePath: void 0,
68012
68023
  forgeClaudeConfigPath: void 0,
68013
68024
  forgeApexBaseUrl: void 0,
68014
- cloudProjectsRoot: "/data/Projects",
68025
+ cloudProjectsRoot: DEFAULT_CLOUD_PROJECTS_ROOT,
68015
68026
  ...overrides
68016
68027
  };
68017
68028
  }));
@@ -73756,7 +73767,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
73756
73767
  var fs$6 = __require("fs");
73757
73768
  var path$5 = __require("path");
73758
73769
  var url$1 = __require("url");
73759
- var os$3 = __require("os");
73770
+ var os$2 = __require("os");
73760
73771
  var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
73761
73772
  var vars = process.config && process.config.variables || {};
73762
73773
  var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
@@ -73764,8 +73775,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
73764
73775
  var abi = versions.modules;
73765
73776
  if (versions.deno || process.isBun) abi = "unsupported";
73766
73777
  var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
73767
- var arch = process.env.npm_config_arch || os$3.arch();
73768
- var platform = process.env.npm_config_platform || os$3.platform();
73778
+ var arch = process.env.npm_config_arch || os$2.arch();
73779
+ var platform = process.env.npm_config_platform || os$2.platform();
73769
73780
  var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
73770
73781
  var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
73771
73782
  var uv = (versions.uv || "").split(".")[0];
@@ -91423,7 +91434,7 @@ const launchDetached = (launch) => gen(function* () {
91423
91434
  });
91424
91435
  const OpenLive = effect(Open, gen(function* () {
91425
91436
  const open = yield* tryPromise({
91426
- try: () => import("./open-FIgTDosf.mjs"),
91437
+ try: () => import("./open-D8mGyWmt.mjs"),
91427
91438
  catch: (cause) => new OpenError$1({
91428
91439
  message: "failed to load browser opener",
91429
91440
  cause
@@ -97136,7 +97147,7 @@ const resolveAutoBootstrapWelcomeTargets = gen(function* () {
97136
97147
  /**
97137
97148
  * Cloud-mode project recovery — runs only on cloud execution runtimes.
97138
97149
  * Scans `cloudProjectsRoot` (default
97139
- * /data/Projects) for git-repo subdirectories and registers any not yet in
97150
+ * /projects) for git-repo subdirectories and registers any not yet in
97140
97151
  * the orchestration engine. This is the cloud-mode replacement for the
97141
97152
  * gated `autoBootstrapProjectFromCwd` path: managed runtimes whose volume
97142
97153
  * already holds cloned repos surface them in the sidebar after restart.
@@ -149395,13 +149406,40 @@ var CodexAppServerManager = class extends EventEmitter$1 {
149395
149406
  if (!providerConversationId) return;
149396
149407
  return context.collabReceiverTurns.get(providerConversationId);
149397
149408
  }
149409
+ /**
149410
+ * The child threads one thread item says the turn spawned.
149411
+ *
149412
+ * Two item shapes name a child, and which one arrives depends on the Codex
149413
+ * build. `CollabAgentToolCallThreadItem` lists `receiverThreadIds`, which
149414
+ * "in case of spawn operation ... corresponds to the newly spawned agent".
149415
+ * `SubAgentActivityThreadItem` names a single `agentThreadId` and is what a
149416
+ * `spawn_agent` call reports on codex-cli 0.152/0.153: measured on the
149417
+ * packaged desktop on 2026-09-06, the spawn arrived as `SubAgentActivity`
149418
+ * (`kind: started`) and the only collab tool call that followed was the
149419
+ * `wait`, whose `receiverThreadIds` was empty. Reading only the collab item
149420
+ * left the child unmapped, so its spend was published as a second turn row
149421
+ * under the child's own turn id instead of a subagent row under the parent's.
149422
+ *
149423
+ * `SubAgentActivityThreadItem` has no `model` field, so a `completed` report
149424
+ * for a child a collab call already named must not blank the model out; the
149425
+ * caller's `model ?? known` merge is what keeps it.
149426
+ */
149427
+ readSpawnedThreadIds(item, itemType) {
149428
+ if (itemType === "subAgentActivity") {
149429
+ const agentThreadId = this.readString(item, "agentThreadId");
149430
+ return agentThreadId ? [agentThreadId] : [];
149431
+ }
149432
+ if (itemType !== "collabAgentToolCall") return [];
149433
+ return this.readArray(item, "receiverThreadIds")?.map((value) => typeof value === "string" ? value : null).filter((value) => value !== null) ?? [];
149434
+ }
149398
149435
  rememberCollabReceiverTurns(context, params, parentTurnId) {
149399
149436
  if (!parentTurnId) return;
149400
149437
  const payload = this.readObject(params);
149401
149438
  const item = this.readObject(payload, "item") ?? payload;
149402
- if ((this.readString(item, "type") ?? this.readString(item, "kind")) !== "collabAgentToolCall") return;
149439
+ const itemType = this.readString(item, "type") ?? this.readString(item, "kind");
149440
+ const receiverThreadIds = this.readSpawnedThreadIds(item, itemType);
149441
+ if (receiverThreadIds.length === 0) return;
149403
149442
  const model = this.readString(item, "model");
149404
- const receiverThreadIds = this.readArray(item, "receiverThreadIds")?.map((value) => typeof value === "string" ? value : null).filter((value) => value !== null) ?? [];
149405
149443
  for (const receiverThreadId of receiverThreadIds) {
149406
149444
  const known = context.collabReceiverTurns.get(receiverThreadId)?.model;
149407
149445
  const resolved = model ?? known;
@@ -149417,8 +149455,8 @@ var CodexAppServerManager = class extends EventEmitter$1 {
149417
149455
  * Codex reports a child thread's tokens on the parent's own connection:
149418
149456
  * `ThreadTokenUsageUpdatedNotification` requires `threadId` and `turnId` and
149419
149457
  * names the child thread for a child, and `Thread.parentThreadId` is "only
149420
- * set if this thread is a subagent" (codex 0.144.6 app-server protocol
149421
- * schema). Every other child notification is suppressed so it cannot replace
149458
+ * set if this thread is a subagent" (still true of the codex-cli 0.153
149459
+ * app-server protocol schema). Every other child notification is suppressed so it cannot replace
149422
149460
  * the parent turn; this one is not, because the tokens belong on the ledger.
149423
149461
  * The method is Forge's own rather than the protocol's, because the payload
149424
149462
  * carries what the Usage row needs rather than what the notification held.
@@ -187273,7 +187311,7 @@ var Vce = {
187273
187311
  function Ve() {
187274
187312
  return Vce;
187275
187313
  }
187276
- var Us = os.homedir(), lk = os.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
187314
+ var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
187277
187315
  let t = path.join(Us, "Library");
187278
187316
  return {
187279
187317
  data: path.join(t, "Application Support", e),
@@ -197473,12 +197511,12 @@ function qZ(e, t) {
197473
197511
  }
197474
197512
  return n;
197475
197513
  }
197476
- var os$2 = "https://code.claude.com/docs/en", kKe = [
197514
+ var os$1 = "https://code.claude.com/docs/en", kKe = [
197477
197515
  {
197478
197516
  matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
197479
197517
  tip: {
197480
197518
  suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
197481
- docLink: `${os$2}/iam#permission-modes`
197519
+ docLink: `${os$1}/iam#permission-modes`
197482
197520
  }
197483
197521
  },
197484
197522
  {
@@ -197493,7 +197531,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
197493
197531
  matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
197494
197532
  tip: {
197495
197533
  suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
197496
- docLink: `${os$2}/settings#environment-variables`
197534
+ docLink: `${os$1}/settings#environment-variables`
197497
197535
  }
197498
197536
  },
197499
197537
  {
@@ -197504,14 +197542,14 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
197504
197542
  matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
197505
197543
  tip: {
197506
197544
  suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
197507
- docLink: `${os$2}/hooks`
197545
+ docLink: `${os$1}/hooks`
197508
197546
  }
197509
197547
  },
197510
197548
  {
197511
197549
  matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
197512
197550
  tip: {
197513
197551
  suggestion: "Command hooks require `command`. For exec form (no shell), set `command` to the executable and `args` to its arguments: {\"type\": \"command\", \"command\": \"echo\", \"args\": [\"hi\"]}. For shell form, set `command` to the full shell string: {\"type\": \"command\", \"command\": \"echo hi\"}.",
197514
- docLink: `${os$2}/hooks#exec-form-and-shell-form`
197552
+ docLink: `${os$1}/hooks#exec-form-and-shell-form`
197515
197553
  }
197516
197554
  },
197517
197555
  {
@@ -197526,7 +197564,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
197526
197564
  matches: (e) => e.code === "unrecognized_keys",
197527
197565
  tip: {
197528
197566
  suggestion: "Check for typos or refer to the documentation for valid fields",
197529
- docLink: `${os$2}/settings`
197567
+ docLink: `${os$1}/settings`
197530
197568
  }
197531
197569
  },
197532
197570
  {
@@ -197541,13 +197579,13 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
197541
197579
  matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
197542
197580
  tip: {
197543
197581
  suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
197544
- docLink: `${os$2}/iam#working-directories`
197582
+ docLink: `${os$1}/iam#working-directories`
197545
197583
  }
197546
197584
  }
197547
197585
  ], AKe = {
197548
- permissions: `${os$2}/iam#configuring-permissions`,
197549
- env: `${os$2}/settings#environment-variables`,
197550
- hooks: `${os$2}/hooks`
197586
+ permissions: `${os$1}/iam#configuring-permissions`,
197587
+ env: `${os$1}/settings#environment-variables`,
197588
+ hooks: `${os$1}/hooks`
197551
197589
  };
197552
197590
  function GZ(e) {
197553
197591
  let t = kKe.find((r) => r.matches(e));
@@ -207214,11 +207252,14 @@ function mapCodexThreadTokenUsageEvent(event, canonicalThreadId, payload) {
207214
207252
  *
207215
207253
  * The source is the live app-server protocol, not a rollout read: Codex sends
207216
207254
  * `thread/tokenUsage/updated` for a child thread on the parent's own
207217
- * connection (0.144.6 schema: `ThreadTokenUsageUpdatedNotification` requires
207218
- * `threadId` and `turnId`, `Thread.parentThreadId` is set only for a subagent,
207219
- * and `CollabAgentToolCallThreadItem` names `receiverThreadIds` and the
207220
- * spawned agent's `model`), and `codexAppServerManager` already tracked those
207221
- * child threads to route their notifications onto the parent turn.
207255
+ * connection (`ThreadTokenUsageUpdatedNotification` requires `threadId` and
207256
+ * `turnId`, and `Thread.parentThreadId` is set only for a subagent), and
207257
+ * `codexAppServerManager` already tracked those child threads to route their
207258
+ * notifications onto the parent turn. It learns a child from whichever item
207259
+ * the running Codex build uses to announce a spawn: the
207260
+ * `receiverThreadIds` of a `CollabAgentToolCallThreadItem`, which also names
207261
+ * the spawned agent's `model`, or the `agentThreadId` of a
207262
+ * `SubAgentActivityThreadItem`, which names no model.
207222
207263
  *
207223
207264
  * The row carries no context-meter snapshot and no `turnUsage`: the parent's
207224
207265
  * own row is never re-summed with its children. It carries the call's delta
@@ -208100,7 +208141,8 @@ const CODE_BY_UPSTREAM_REASON$1 = {
208100
208141
  credential_expired_refreshing: "CREDENTIAL_EXPIRED_REFRESHING",
208101
208142
  credential_reconnect_required: "CREDENTIAL_RECONNECT_REQUIRED",
208102
208143
  credential_not_connected: "CREDENTIAL_RECONNECT_REQUIRED",
208103
- credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED"
208144
+ credential_generation_not_applied: "CREDENTIAL_GENERATION_NOT_APPLIED",
208145
+ runtime_unavailable: "RUNTIME_UNAVAILABLE"
208104
208146
  };
208105
208147
  const CLAUDE_NATIVE_CREDENTIAL_UNUSABLE_COPY = "The delivered Claude credential is unavailable or unusable in this runtime.";
208106
208148
  /**
@@ -208163,10 +208205,17 @@ function claudeNativeCredentialUnusableMessage() {
208163
208205
  * CMMD writes this marker when it activates a personal Claude subscription
208164
208206
  * inside a running sprite. Activation always happens after boot, so a
208165
208207
  * boot-time env var alone cannot reflect it (FOR-36249).
208208
+ *
208209
+ * CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
208210
+ * Without that, the marker sits directly under FORGE_HOME, which is the same
208211
+ * guest home CMMD derives every sprite path from. A server with neither has no
208212
+ * sprite home to look in, and a desktop never has a marker, so it reads none.
208166
208213
  */
208167
- const PERSONAL_CLAUDE_IN_VM_MARKER_PATH = "/data/.forge-personal-claude-in-vm";
208214
+ const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
208168
208215
  function markerPath(env) {
208169
- return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH || PERSONAL_CLAUDE_IN_VM_MARKER_PATH;
208216
+ if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
208217
+ if (env.FORGE_HOME) return join(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
208218
+ return null;
208170
208219
  }
208171
208220
  /**
208172
208221
  * True when this runtime should offer the personal Claude subscription lane.
@@ -208179,8 +208228,10 @@ function markerPath(env) {
208179
208228
  */
208180
208229
  function personalClaudeInVmEnabled(env = process.env) {
208181
208230
  if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
208231
+ const marker = markerPath(env);
208232
+ if (!marker) return false;
208182
208233
  try {
208183
- return existsSync(markerPath(env));
208234
+ return existsSync(marker);
208184
208235
  } catch {
208185
208236
  return false;
208186
208237
  }
@@ -226760,7 +226811,8 @@ const CLAUDE_READINESS_COPY = {
226760
226811
  failed: "Claude native runtime setup could not be applied. Reconnect Claude in Settings.",
226761
226812
  invalid: "Claude native runtime readiness is no longer valid. Reconnect Claude in Settings.",
226762
226813
  unavailable: "Claude native runtime readiness could not be verified.",
226763
- generationMismatch: "Claude native runtime generation does not match CMMD readiness."
226814
+ generationMismatch: "Claude native runtime generation does not match CMMD readiness.",
226815
+ environmentUnavailable: "This Environment is not running, so Claude cannot run on it. Restart the Environment, then try again."
226764
226816
  };
226765
226817
  var ClaudeRuntimeReadinessError = class extends Error {
226766
226818
  constructor(safeIssue, retryable = false, retryProfile = "default") {
@@ -226830,6 +226882,7 @@ async function withReadinessRetry(attempt) {
226830
226882
  */
226831
226883
  function readinessReasonIssue(readiness) {
226832
226884
  const code = claudeReadinessReasonCode(readiness.reason);
226885
+ if (code === "RUNTIME_UNAVAILABLE") return CLAUDE_READINESS_COPY.environmentUnavailable;
226833
226886
  if (code === "CREDENTIAL_RECONNECT_REQUIRED") return CLAUDE_READINESS_COPY.invalid;
226834
226887
  if (code === "CREDENTIAL_GENERATION_NOT_APPLIED") return CLAUDE_READINESS_COPY.applying;
226835
226888
  if (code !== "CREDENTIAL_EXPIRED_REFRESHING") return null;
@@ -226840,6 +226893,10 @@ function readinessReasonIssue(readiness) {
226840
226893
  function isCredentialRefreshInFlight(readiness) {
226841
226894
  return claudeReadinessReasonCode(readiness.reason) === "CREDENTIAL_EXPIRED_REFRESHING";
226842
226895
  }
226896
+ /** True when CMMD says the runtime itself cannot take a delivery (CMMD-36473). */
226897
+ function isRuntimeUnavailable(readiness) {
226898
+ return claudeReadinessReasonCode(readiness.reason) === "RUNTIME_UNAVAILABLE";
226899
+ }
226843
226900
  function readinessFailureIssue(readiness) {
226844
226901
  const reasonIssue = readinessReasonIssue(readiness);
226845
226902
  if (reasonIssue !== null) return reasonIssue;
@@ -226879,11 +226936,12 @@ function verifiedClaudeDeliveryBinding(readiness, authority) {
226879
226936
  function appliedClaudeGeneration(readiness) {
226880
226937
  if (readiness.connected !== true || readiness.deliveryStatus !== "applied" || readiness.nativeExecutable !== true || typeof readiness.generationId !== "string" || readiness.generationId.trim().length === 0) {
226881
226938
  const refreshing = isCredentialRefreshInFlight(readiness);
226882
- throw new ClaudeRuntimeReadinessError(readinessFailureIssue(readiness), refreshing || [
226939
+ const retryable = !isRuntimeUnavailable(readiness) && (refreshing || [
226883
226940
  "pending",
226884
226941
  "claimed",
226885
226942
  "activating"
226886
- ].includes(readiness.deliveryStatus), refreshing ? "credentialRefresh" : "default");
226943
+ ].includes(readiness.deliveryStatus));
226944
+ throw new ClaudeRuntimeReadinessError(readinessFailureIssue(readiness), retryable, refreshing ? "credentialRefresh" : "default");
226887
226945
  }
226888
226946
  return readiness.generationId;
226889
226947
  }
@@ -228037,15 +228095,15 @@ const collectOutput = fn$1("collectOutput")(function* (input, stream, maxOutputB
228037
228095
  /**
228038
228096
  * Adds a `safe.directory` exception to a git child environment.
228039
228097
  *
228040
- * Forge runs git against workspaces on a mounted volume (`/data/Projects/...` on
228041
- * Fly). A volume mount replaces the build-time ownership of its mountpoint, so a
228098
+ * Forge runs git against workspaces on a mounted volume (`<cloudProjectsRoot>/...`
228099
+ * on Fly). A volume mount replaces the build-time ownership of its mountpoint, so a
228042
228100
  * repository there can be owned by a uid other than the server's, and git then
228043
228101
  * refuses EVERY command against it with "detected dubious ownership" — the
228044
228102
  * FORGE-2T failure in `CheckpointStore.captureCheckpoint`.
228045
228103
  *
228046
228104
  * The exception has to be `*`, verified against git 2.43:
228047
228105
  * - git compares `safe.directory` entries literally, so a prefix such as
228048
- * `/data/Projects/*` does not match the repository at all.
228106
+ * `/projects/*` does not match the repository at all.
228049
228107
  * - the value must name the DISCOVERED REPOSITORY ROOT, which is an ancestor of
228050
228108
  * `cwd` whenever a command runs from a subdirectory. Resolving that root means
228051
228109
  * asking git, which is the command being refused.
@@ -262514,7 +262572,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
262514
262572
  var dgram = __require("dgram");
262515
262573
  var thunky = require_thunky();
262516
262574
  var events = __require("events");
262517
- var os$1 = __require("os");
262575
+ var os = __require("os");
262518
262576
  var noop = function() {};
262519
262577
  module.exports = function(opts) {
262520
262578
  if (!opts) opts = {};
@@ -262645,14 +262703,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
262645
262703
  return that;
262646
262704
  };
262647
262705
  function defaultInterface() {
262648
- var networks = os$1.networkInterfaces();
262706
+ var networks = os.networkInterfaces();
262649
262707
  var names = Object.keys(networks);
262650
262708
  for (var i = 0; i < names.length; i++) {
262651
262709
  var net = networks[names[i]];
262652
262710
  for (var j = 0; j < net.length; j++) {
262653
262711
  var iface = net[j];
262654
262712
  if (isIPv4(iface.family) && !iface.internal) {
262655
- if (os$1.platform() === "darwin" && names[i] === "en0") return iface.address;
262713
+ if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
262656
262714
  return "0.0.0.0";
262657
262715
  }
262658
262716
  }
@@ -262660,7 +262718,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
262660
262718
  return "127.0.0.1";
262661
262719
  }
262662
262720
  function allInterfaces() {
262663
- var networks = os$1.networkInterfaces();
262721
+ var networks = os.networkInterfaces();
262664
262722
  var names = Object.keys(networks);
262665
262723
  var res = [];
262666
262724
  for (var i = 0; i < names.length; i++) {
@@ -290889,7 +290947,7 @@ const ServerLoggerLive = gen(function* () {
290889
290947
  * Observed live on a Forge Cloud sprite (2026-07-25): the browser opens
290890
290948
  * `wss://<sprite>.sprites.app/ws?routeKey=…&runtimeTicket=<JWT>`, and the HTTP
290891
290949
  * instrumentation records `url.full` / `url.query` verbatim. The ticket then
290892
- * lands in `/data/userdata/logs/server.trace.ndjson` at mode 644 — readable by
290950
+ * lands in `<FORGE_HOME>/userdata/logs/server.trace.ndjson` at mode 644 — readable by
290893
290951
  * the same user the agent runs as — and the URL also crosses fly.io's edge,
290894
290952
  * where infrastructure we do not control may log it.
290895
290953
  *
@@ -291181,7 +291239,7 @@ function resolveBuildCommitFromEnv(env) {
291181
291239
  * environment descriptor down instead of reporting an honest "unknown".
291182
291240
  */
291183
291241
  function readBakedBuildCommit() {
291184
- return "986939d02dd54d5adfd34ed23f2d67fece201f9a";
291242
+ return "5f0060a9696edd41f61f06cbf24450a5994fed33";
291185
291243
  }
291186
291244
  async function resolveServerBuildCommit(input) {
291187
291245
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -291270,6 +291328,25 @@ function runtimeTopology(serverConfig) {
291270
291328
  if (serverConfig.mode === "desktop") return "desktop-local";
291271
291329
  return "local-web";
291272
291330
  }
291331
+ /**
291332
+ * The CMMD device runtime id to publish on this server's descriptor.
291333
+ *
291334
+ * A client cannot work this out for itself. Its only other source is page
291335
+ * storage, which holds this machine's id when the page runs inside the desktop
291336
+ * shell and a freshly minted browser id otherwise, so opening the same local
291337
+ * server at `127.0.0.1` in Safari made the desktop look like a remote runtime
291338
+ * (FOR-36439). The server was told the id at boot, so the server says it.
291339
+ *
291340
+ * The cloud shell is excluded on purpose. It is a control plane, not an
291341
+ * execution runtime, and its own provider list is empty; publishing a runtime
291342
+ * id there would let the SPA resolve some target as "the server I am talking
291343
+ * to" and read models off a surface that runs nothing. Everything else with no
291344
+ * registration (dev checkouts, plain `forge serve`) already answers undefined.
291345
+ */
291346
+ function resolveDescriptorDeviceRuntimeId(config) {
291347
+ if (config.forgeCloudShellMode === true) return void 0;
291348
+ return resolveRuntimeReplicationRuntimeId(config);
291349
+ }
291273
291350
  function executionEnvironmentCapabilities(serverConfig) {
291274
291351
  const isCloudShell = serverConfig.forgeCloudShellMode === true;
291275
291352
  const isRuntime = serverConfig.forgeCmmdRuntimeMode === true && !isCloudShell;
@@ -291343,6 +291420,7 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
291343
291420
  env: process.env,
291344
291421
  readGitCommit: resolveForgeGitCommit
291345
291422
  }));
291423
+ const deviceRuntimeId = resolveDescriptorDeviceRuntimeId(serverConfig);
291346
291424
  const buildDescriptor = gen(function* () {
291347
291425
  const displayNameOverride = yield* get$5(displayNameOverrideRef);
291348
291426
  const os = platformOs();
@@ -291355,6 +291433,7 @@ const ServerEnvironmentLive = effect(ServerEnvironment, fn$1("makeServerEnvironm
291355
291433
  },
291356
291434
  serverVersion: version$1,
291357
291435
  ...buildCommit !== void 0 ? { buildCommit } : {},
291436
+ ...deviceRuntimeId !== void 0 ? { deviceRuntimeId } : {},
291358
291437
  capabilities: {
291359
291438
  ...executionEnvironmentCapabilities(serverConfig),
291360
291439
  posthogPublicKey: posthog.posthogPublicKey,
@@ -299149,16 +299228,6 @@ function describeDispatchCause(cause) {
299149
299228
  if (deepest === void 0 || deepest.length <= DISPATCH_CAUSE_DETAIL_LIMIT) return deepest;
299150
299229
  return `${deepest.slice(0, DISPATCH_CAUSE_DETAIL_LIMIT)}...`;
299151
299230
  }
299152
- /**
299153
- * Where automation-created projects live when the caller does not say.
299154
- *
299155
- * This is the documented default of the `cloudProjectsRoot` setting
299156
- * (FORGE_CLOUD_PROJECTS_ROOT, see cli.ts) and exists only for direct callers
299157
- * such as tests. Every HTTP route passes the CONFIGURED value instead: reading
299158
- * the default here is what made a runtime configured with any other root create
299159
- * its automation projects in a directory nothing else read.
299160
- */
299161
- const DEFAULT_CLOUD_PROJECTS_ROOT = "/data/Projects";
299162
299231
  const respondToOrchestrationHttpError = (error) => gen(function* () {
299163
299232
  if (error._tag === "OrchestrationGetSnapshotError") {
299164
299233
  yield* logError("orchestration http route failed", {
@@ -314070,7 +314139,7 @@ const resolveServerConfig = (flags, cliLogLevel, options) => gen(function* () {
314070
314139
  forgeCodexHomePath: env.forgeCodexHomePath,
314071
314140
  forgeClaudeConfigPath: env.forgeClaudeConfigPath,
314072
314141
  forgeApexBaseUrl: env.forgeApexBaseUrl,
314073
- cloudProjectsRoot: env.cloudProjectsRoot ?? "/data/Projects"
314142
+ cloudProjectsRoot: env.cloudProjectsRoot ?? "/projects"
314074
314143
  };
314075
314144
  if (config.forgeCloudShellMode === true && (!config.forgeCloudOwnerAssertionPublicKey?.trim() || !config.forgeCloudOwnerAssertionKeyId?.trim())) return yield* die$1(/* @__PURE__ */ new Error("FORGE_CMMD_CLOUD_OWNER_ASSERTION_PUBLIC_KEY and FORGE_CMMD_CLOUD_OWNER_ASSERTION_KEY_ID are required in cloud-shell mode; runtime ticket keys are not accepted for Cloud owner assertions."));
314076
314145
  if (config.forgeCloudShellMode === true && !isEd25519PublicKey(config.forgeCloudOwnerAssertionPublicKey ?? "")) return yield* die$1(/* @__PURE__ */ new Error("FORGE_CMMD_CLOUD_OWNER_ASSERTION_PUBLIC_KEY must be a valid Ed25519 public key in cloud-shell mode."));
@@ -314488,7 +314557,7 @@ const connectLoginCommand = make$34("login", {
314488
314557
  })).json();
314489
314558
  },
314490
314559
  openBrowser: async (url) => {
314491
- const open = (await import("./open-FIgTDosf.mjs")).default;
314560
+ const open = (await import("./open-D8mGyWmt.mjs")).default;
314492
314561
  await open(url);
314493
314562
  return true;
314494
314563
  },