@cmmd-center/forge 0.13.63 → 0.13.64
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 +350 -210
- package/dist/bin.mjs +374 -234
- package/dist/client/assets/{DiffPanel-Mj56e5Vd.js → DiffPanel-CMYTuX9T.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-Bws9oedp.js → DiffPanel.logic-Bo8iewty.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-CfYAh40Z.js → DiffPanelShell-BOfiU8pF.js} +3 -3
- package/dist/client/assets/{DiffWorkerPoolProvider-CKLNoRVs.js → DiffWorkerPoolProvider-BJM597MG.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-7AnSD7oo.js → PullRequestCodePanel-Dp0Gub8R.js} +2 -2
- package/dist/client/assets/{Virtualizer-nWFuwagg.js → Virtualizer-DA9X_YiJ.js} +2 -2
- package/dist/client/assets/{index-DW9f_7Ly.js → index-unGMBg6C.js} +6 -6
- package/dist/client/index.html +4 -4
- package/dist/forge-build.json +1 -1
- package/dist/{open-yHTUkd2Y.mjs → open-2-reBrpv.mjs} +3 -3
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -23,7 +23,7 @@ import { createCipheriv, createDecipheriv, createHash, createHmac, createPublicK
|
|
|
23
23
|
import * as FS$1 from "node:fs";
|
|
24
24
|
import fs, { accessSync, chmodSync, constants, createReadStream, existsSync, mkdirSync, mkdtempSync, promises, readFile, readFileSync, readdir, readdirSync, realpathSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
25
25
|
import * as NodeOS from "node:os";
|
|
26
|
-
import
|
|
26
|
+
import $U, { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
27
27
|
import * as Path$1 from "node:path";
|
|
28
28
|
import path, { basename, delimiter, dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
29
29
|
import { pathToFileURL } from "node:url";
|
|
@@ -50573,7 +50573,7 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
|
50573
50573
|
const min = forceColor || 0;
|
|
50574
50574
|
if (env.TERM === "dumb") return min;
|
|
50575
50575
|
if (process$1.platform === "win32") {
|
|
50576
|
-
const osRelease =
|
|
50576
|
+
const osRelease = $U.release().split(".");
|
|
50577
50577
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
50578
50578
|
return 1;
|
|
50579
50579
|
}
|
|
@@ -53016,7 +53016,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
53016
53016
|
exports.processDetector = void 0;
|
|
53017
53017
|
const api_1 = (init_esm$1(), __toCommonJS(esm_exports$1));
|
|
53018
53018
|
const semconv_1 = require_semconv$1();
|
|
53019
|
-
const os$
|
|
53019
|
+
const os$3 = __require("os");
|
|
53020
53020
|
/**
|
|
53021
53021
|
* ProcessDetector will be used to detect the resources related current process running
|
|
53022
53022
|
* and being instrumented from the NodeJS Process module.
|
|
@@ -53038,7 +53038,7 @@ var require_ProcessDetector = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
53038
53038
|
};
|
|
53039
53039
|
if (process.argv.length > 1) attributes[semconv_1.ATTR_PROCESS_COMMAND] = process.argv[1];
|
|
53040
53040
|
try {
|
|
53041
|
-
const userInfo = os$
|
|
53041
|
+
const userInfo = os$3.userInfo();
|
|
53042
53042
|
attributes[semconv_1.ATTR_PROCESS_OWNER] = userInfo.username;
|
|
53043
53043
|
} catch (e) {
|
|
53044
53044
|
api_1.diag.debug(`error obtaining process owner: ${e}`);
|
|
@@ -65961,7 +65961,7 @@ function normalizeNumberish(value) {
|
|
|
65961
65961
|
}
|
|
65962
65962
|
//#endregion
|
|
65963
65963
|
//#region package.json
|
|
65964
|
-
var version$1 = "0.13.
|
|
65964
|
+
var version$1 = "0.13.64";
|
|
65965
65965
|
//#endregion
|
|
65966
65966
|
//#region src/sentry.ts
|
|
65967
65967
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -75844,7 +75844,7 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
75844
75844
|
var fs$6 = __require("fs");
|
|
75845
75845
|
var path$5 = __require("path");
|
|
75846
75846
|
var url$1 = __require("url");
|
|
75847
|
-
var os$
|
|
75847
|
+
var os$2 = __require("os");
|
|
75848
75848
|
var runtimeRequire = typeof __webpack_require__ === "function" ? __non_webpack_require__ : __require;
|
|
75849
75849
|
var vars = process.config && process.config.variables || {};
|
|
75850
75850
|
var prebuildsOnly = !!process.env.PREBUILDS_ONLY;
|
|
@@ -75852,8 +75852,8 @@ var require_node_gyp_build = /* @__PURE__ */ __commonJSMin(((exports, module) =>
|
|
|
75852
75852
|
var abi = versions.modules;
|
|
75853
75853
|
if (versions.deno || process.isBun) abi = "unsupported";
|
|
75854
75854
|
var runtime = isElectron() ? "electron" : isNwjs() ? "node-webkit" : "node";
|
|
75855
|
-
var arch = process.env.npm_config_arch || os$
|
|
75856
|
-
var platform = process.env.npm_config_platform || os$
|
|
75855
|
+
var arch = process.env.npm_config_arch || os$2.arch();
|
|
75856
|
+
var platform = process.env.npm_config_platform || os$2.platform();
|
|
75857
75857
|
var libc = process.env.LIBC || (isMusl(platform) ? "musl" : "glibc");
|
|
75858
75858
|
var armv = process.env.ARM_VERSION || (arch === "arm64" ? "8" : vars.arm_version) || "";
|
|
75859
75859
|
var uv = (versions.uv || "").split(".")[0];
|
|
@@ -88991,6 +88991,28 @@ var GitHubBroker = class {
|
|
|
88991
88991
|
runtimeResource: "status"
|
|
88992
88992
|
});
|
|
88993
88993
|
}
|
|
88994
|
+
/**
|
|
88995
|
+
* On a "github" scope cache miss, re-requests it once through the existing
|
|
88996
|
+
* CMMD client before the caller refuses (CMMD-Center/forge#3496). CMMD
|
|
88997
|
+
* delivers the grant proactively on thread.create and the kick, so a miss
|
|
88998
|
+
* on a woken runtime is a cache or ticket TTL problem, not a missing
|
|
88999
|
+
* request: this closes the gap where the ticket lands in the broker
|
|
89000
|
+
* authority cache mid-request, and logs the outcome either way so the
|
|
89001
|
+
* cause is visible instead of a silent refusal.
|
|
89002
|
+
*/
|
|
89003
|
+
async requestRuntimeGithubGrant() {
|
|
89004
|
+
const cached = this.runtimeGrantAuthContext("github");
|
|
89005
|
+
if (cached) return cached;
|
|
89006
|
+
const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID?.trim();
|
|
89007
|
+
if (!runtimeId) return null;
|
|
89008
|
+
const result = await this.status({
|
|
89009
|
+
sessionId: AuthSessionId.make(`cmmd-runtime:${runtimeId}`),
|
|
89010
|
+
subject: `runtime:${runtimeId}`
|
|
89011
|
+
});
|
|
89012
|
+
const grant = this.runtimeGrantAuthContext("github");
|
|
89013
|
+
console.warn(`[github-broker] re-requested the github scope grant for runtime ${runtimeId}: ${grant ? "cache populated" : `still missing (broker answered ${result.ok ? "ok" : result.code ?? "no grant"})`}`);
|
|
89014
|
+
return grant;
|
|
89015
|
+
}
|
|
88994
89016
|
async workspaceStatus(input) {
|
|
88995
89017
|
const cwdValidation = validateCwd(input.cwd);
|
|
88996
89018
|
if (cwdValidation) return cwdValidation;
|
|
@@ -93606,7 +93628,7 @@ const launchDetached = (launch) => gen(function* () {
|
|
|
93606
93628
|
});
|
|
93607
93629
|
const OpenLive = effect(Open, gen(function* () {
|
|
93608
93630
|
const open = yield* tryPromise({
|
|
93609
|
-
try: () => import("./open-
|
|
93631
|
+
try: () => import("./open-2-reBrpv.mjs"),
|
|
93610
93632
|
catch: (cause) => new OpenError$1({
|
|
93611
93633
|
message: "failed to load browser opener",
|
|
93612
93634
|
cause
|
|
@@ -97458,6 +97480,235 @@ function serializeProviderCatalog(providers) {
|
|
|
97458
97480
|
return catalog;
|
|
97459
97481
|
}
|
|
97460
97482
|
//#endregion
|
|
97483
|
+
//#region src/provider/personalClaudeInVm.ts
|
|
97484
|
+
/**
|
|
97485
|
+
* CMMD writes this marker when it activates a personal Claude subscription
|
|
97486
|
+
* inside a running sprite. Activation always happens after boot, so a
|
|
97487
|
+
* boot-time env var alone cannot reflect it (FOR-36249).
|
|
97488
|
+
*
|
|
97489
|
+
* CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
|
|
97490
|
+
* Without that, the marker sits directly under FORGE_HOME, which is the same
|
|
97491
|
+
* guest home CMMD derives every sprite path from. A server with neither has no
|
|
97492
|
+
* sprite home to look in, and a desktop never has a marker, so it reads none.
|
|
97493
|
+
*/
|
|
97494
|
+
const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
|
|
97495
|
+
const PERSONAL_CLAUDE_GENERATION_FILE_NAME = ".forge-personal-claude-generation";
|
|
97496
|
+
function markerPath(env) {
|
|
97497
|
+
if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
|
|
97498
|
+
if (env.FORGE_HOME) return join(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
|
|
97499
|
+
return null;
|
|
97500
|
+
}
|
|
97501
|
+
/**
|
|
97502
|
+
* True when this runtime should offer the personal Claude subscription lane.
|
|
97503
|
+
*
|
|
97504
|
+
* Accepts either signal: the boot-time env var (backward compatible) or the
|
|
97505
|
+
* activation marker on disk. Reading the marker at call time lets the provider
|
|
97506
|
+
* catalog and the per-turn readiness guard self-heal once CMMD delivers,
|
|
97507
|
+
* without a sprite reboot. Callers still verify the credential is non-empty
|
|
97508
|
+
* before treating it as usable; this only decides whether the lane is active.
|
|
97509
|
+
*/
|
|
97510
|
+
function personalClaudeInVmEnabled(env = process.env) {
|
|
97511
|
+
if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
|
|
97512
|
+
const marker = markerPath(env);
|
|
97513
|
+
if (!marker) return false;
|
|
97514
|
+
try {
|
|
97515
|
+
return existsSync(marker);
|
|
97516
|
+
} catch {
|
|
97517
|
+
return false;
|
|
97518
|
+
}
|
|
97519
|
+
}
|
|
97520
|
+
/** Exported at boot. This is the restart proof CMMD waits on, so it must never read the file. */
|
|
97521
|
+
function bootExportedPersonalClaudeGenerationHash(env = process.env) {
|
|
97522
|
+
return env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
97523
|
+
}
|
|
97524
|
+
/**
|
|
97525
|
+
* The sha256 of the Claude credential generation this runtime holds, or "" when
|
|
97526
|
+
* it holds none. The readiness guard compares it with the generation CMMD
|
|
97527
|
+
* applied. The restart proof must not use it; it uses the boot-exported hash.
|
|
97528
|
+
*
|
|
97529
|
+
* The boot preload exports FORGE_PERSONAL_CLAUDE_GENERATION_HASH, but only when
|
|
97530
|
+
* the marker already exists at boot. Activation in place writes the generation
|
|
97531
|
+
* file and touches the marker without setting any env, so an empty variable
|
|
97532
|
+
* falls back to that file, hashed the way the preload hashes it. CMMD puts the
|
|
97533
|
+
* file beside the marker in the guest home and names no path for it. A marker
|
|
97534
|
+
* with no generation file is a legacy runtime, and gets no hash here either.
|
|
97535
|
+
*/
|
|
97536
|
+
function personalClaudeGenerationHash(env = process.env) {
|
|
97537
|
+
const exported = bootExportedPersonalClaudeGenerationHash(env);
|
|
97538
|
+
if (exported) return exported;
|
|
97539
|
+
const marker = markerPath(env);
|
|
97540
|
+
if (!marker) return "";
|
|
97541
|
+
try {
|
|
97542
|
+
if (!existsSync(marker)) return "";
|
|
97543
|
+
const generation = readFileSync(join(dirname(marker), PERSONAL_CLAUDE_GENERATION_FILE_NAME), "utf8").trim();
|
|
97544
|
+
return generation ? createHash("sha256").update(generation).digest("hex") : "";
|
|
97545
|
+
} catch {
|
|
97546
|
+
return "";
|
|
97547
|
+
}
|
|
97548
|
+
}
|
|
97549
|
+
//#endregion
|
|
97550
|
+
//#region src/provider/providerAuthFailureCopy.ts
|
|
97551
|
+
/**
|
|
97552
|
+
* Rewrite provider auth failures into an action the user can actually take.
|
|
97553
|
+
*
|
|
97554
|
+
* The provider SDKs assume they are running under a human at a terminal, so
|
|
97555
|
+
* their auth errors name CLI verbs: Claude's is literally
|
|
97556
|
+
* `Not logged in · Please run /login`. On Forge Cloud that reaches a browser
|
|
97557
|
+
* user who has no terminal, no shell, and no way to run `/login` — the turn
|
|
97558
|
+
* dies in ~3 s naming an action that does not exist for them (observed live
|
|
97559
|
+
* 2026-08-05).
|
|
97560
|
+
*
|
|
97561
|
+
* CMMD already writes the right shape for the Codex egress path — "Connect
|
|
97562
|
+
* ChatGPT in Settings > Providers, then retry" — which names a destination the
|
|
97563
|
+
* user can click. This gives the provider-SDK failures the same treatment.
|
|
97564
|
+
*
|
|
97565
|
+
* Scope is deliberately narrow. Only messages that clearly identify a
|
|
97566
|
+
* *credential* problem are rewritten; everything else passes through verbatim,
|
|
97567
|
+
* because a rewritten unknown error loses detail an operator needs and risks
|
|
97568
|
+
* telling the user to reconnect an account that was never the problem.
|
|
97569
|
+
*/
|
|
97570
|
+
/**
|
|
97571
|
+
* Signatures of "you have no usable credential", matched case-insensitively.
|
|
97572
|
+
*
|
|
97573
|
+
* Kept as explicit patterns rather than a broad /auth/i so that genuine
|
|
97574
|
+
* mid-turn failures mentioning authorization (a tool hitting a 403 on some
|
|
97575
|
+
* unrelated API, say) are not misreported as "your subscription is
|
|
97576
|
+
* disconnected".
|
|
97577
|
+
*/
|
|
97578
|
+
const CREDENTIAL_FAILURE_PATTERNS = [
|
|
97579
|
+
/\bnot logged in\b/i,
|
|
97580
|
+
/\bplease run\s*\/login\b/i,
|
|
97581
|
+
/\brun\s*\/login\b/i,
|
|
97582
|
+
/\blogin required\b/i,
|
|
97583
|
+
/\bnot authenticated\b/i,
|
|
97584
|
+
/\bno credentials? found\b/i,
|
|
97585
|
+
/\binvalid api key\b/i,
|
|
97586
|
+
/\bauthentication_error\b/i
|
|
97587
|
+
];
|
|
97588
|
+
/**
|
|
97589
|
+
* Claude's words for a delivered credential whose refresh chain is dead.
|
|
97590
|
+
*
|
|
97591
|
+
* Deliberately NOT in CREDENTIAL_FAILURE_PATTERNS. On a runtime whose Claude
|
|
97592
|
+
* credential CMMD delivers, this is not "you are signed out" and reconnecting
|
|
97593
|
+
* in Settings is not the fix: CMMD's authority rotates the refresh token on
|
|
97594
|
+
* every refresh, the snapshot in the guest dies at the next rotation, and the
|
|
97595
|
+
* repair is a re-delivery the next send triggers (FOR-36425, CMMD-36441).
|
|
97596
|
+
* Anywhere else the credential is the user's own local login, so the CLI text
|
|
97597
|
+
* still passes through untouched.
|
|
97598
|
+
*/
|
|
97599
|
+
const DELIVERED_CREDENTIAL_EXPIRED_PATTERN = /\boauth session expired\b/i;
|
|
97600
|
+
const DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY = "The delivered Claude credential expired. CMMD refreshes it on the next send, so retry this turn.";
|
|
97601
|
+
/** True when the message is about a missing/unusable credential, not a tool failure. */
|
|
97602
|
+
function isProviderCredentialFailure(message) {
|
|
97603
|
+
return CREDENTIAL_FAILURE_PATTERNS.some((pattern) => pattern.test(message));
|
|
97604
|
+
}
|
|
97605
|
+
/**
|
|
97606
|
+
* Map a ProviderKind to the account name a user would recognise.
|
|
97607
|
+
*
|
|
97608
|
+
* NOTE the literal is `claudeAgent`, not `claude`. This read `"claude"` and so
|
|
97609
|
+
* never matched, silently degrading every Claude auth failure to the generic
|
|
97610
|
+
* fallback — "Forge Cloud has no **this provider** subscription connected."
|
|
97611
|
+
* Same provider-name drift class as the model-mapping fix (#2103/#2105).
|
|
97612
|
+
*
|
|
97613
|
+
* The names come from the shared PROVIDER_ACCOUNT_NAMES map rather than a local
|
|
97614
|
+
* switch: this file's copy was the ONLY correct provider→label mapping in the
|
|
97615
|
+
* codebase while four client call sites reimplemented it as binary ternaries.
|
|
97616
|
+
* One Record over ProviderKind means a new provider is a compile error in one
|
|
97617
|
+
* place instead of five sentences that read wrong in production.
|
|
97618
|
+
*
|
|
97619
|
+
* Providers that do not sell a personal subscription stay generic on purpose —
|
|
97620
|
+
* "Forge Cloud has no Ollama subscription connected" names something that does
|
|
97621
|
+
* not exist.
|
|
97622
|
+
*/
|
|
97623
|
+
function providerLabel(provider) {
|
|
97624
|
+
return supportsPersonalSubscription(provider) ? PROVIDER_ACCOUNT_NAMES[provider] : "this provider";
|
|
97625
|
+
}
|
|
97626
|
+
/**
|
|
97627
|
+
* Return the message to show for a failed turn.
|
|
97628
|
+
*
|
|
97629
|
+
* `surface` decides the destination: a cloud runtime has no terminal, so it is
|
|
97630
|
+
* pointed at Settings; a local/desktop runtime CAN run the CLI login, so its
|
|
97631
|
+
* copy still offers that as the concrete fix rather than sending someone to a
|
|
97632
|
+
* settings page that is not how they authenticate.
|
|
97633
|
+
*/
|
|
97634
|
+
function rewriteProviderAuthFailure(input) {
|
|
97635
|
+
const original = input.message ?? "";
|
|
97636
|
+
if (original.length === 0) return {
|
|
97637
|
+
message: original,
|
|
97638
|
+
rewritten: false
|
|
97639
|
+
};
|
|
97640
|
+
if (input.deliveredClaudeCredential === true && DELIVERED_CREDENTIAL_EXPIRED_PATTERN.test(original)) return {
|
|
97641
|
+
message: DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY,
|
|
97642
|
+
rewritten: true
|
|
97643
|
+
};
|
|
97644
|
+
if (!isProviderCredentialFailure(original)) return {
|
|
97645
|
+
message: original,
|
|
97646
|
+
rewritten: false
|
|
97647
|
+
};
|
|
97648
|
+
const who = providerLabel(input.provider);
|
|
97649
|
+
return {
|
|
97650
|
+
message: input.surface === "cloud" ? `Forge Cloud has no ${who} subscription connected for your account. Open Settings > Providers to connect one, then retry this turn.` : `This runtime is not signed in to ${who}. Connect it from Settings > Providers, then retry this turn.`,
|
|
97651
|
+
rewritten: true
|
|
97652
|
+
};
|
|
97653
|
+
}
|
|
97654
|
+
/**
|
|
97655
|
+
* Convenience for call sites that just want the string to show.
|
|
97656
|
+
*
|
|
97657
|
+
* Exists so adapters do not each re-derive the surface and re-handle the
|
|
97658
|
+
* undefined case inline — that inlining is what pushed
|
|
97659
|
+
* ClaudeAdapter.handleResultMessage over the cyclomatic gate. Returns
|
|
97660
|
+
* `undefined` unchanged so "no error" stays distinguishable from "empty error".
|
|
97661
|
+
*/
|
|
97662
|
+
function providerAuthFailureMessage(message, provider, env = process.env) {
|
|
97663
|
+
if (message === void 0) return void 0;
|
|
97664
|
+
return rewriteProviderAuthFailure({
|
|
97665
|
+
message,
|
|
97666
|
+
provider,
|
|
97667
|
+
surface: env.FORGE_CMMD_RUNTIME_MODE === "true" ? "cloud" : "local",
|
|
97668
|
+
deliveredClaudeCredential: provider === "claudeAgent" && personalClaudeInVmEnabled(env)
|
|
97669
|
+
}).message;
|
|
97670
|
+
}
|
|
97671
|
+
//#endregion
|
|
97672
|
+
//#region src/orchestration/turnFailureReason.ts
|
|
97673
|
+
/**
|
|
97674
|
+
* Machine-readable reasons for a turn that failed before it produced any
|
|
97675
|
+
* assistant output, replicated to CMMD on the `turn_status` event
|
|
97676
|
+
* (CMMD-Center/forge#3496, CMMD-Center/CMMD#5272). Codes match the catalog
|
|
97677
|
+
* CMMD renders actions from (CMMD-Center/CMMD#5289): a code CMMD does not
|
|
97678
|
+
* recognize degrades to its own generic fallback, so a new Forge failure
|
|
97679
|
+
* class is safe to ship before CMMD's catalog knows about it.
|
|
97680
|
+
*/
|
|
97681
|
+
const GITHUB_GRANT_DETAIL_PATTERN = /exact scoped runtime GitHub grant/i;
|
|
97682
|
+
/**
|
|
97683
|
+
* The exact-machine broker's own wording (`providerEgressToken.ts`) for all
|
|
97684
|
+
* three reasons it could not produce a provider token: an incomplete runtime
|
|
97685
|
+
* identity, no owner session bound to the request, or no live broker
|
|
97686
|
+
* authority. Every one of those is a real authentication refusal, not a
|
|
97687
|
+
* workspace or adapter failure, and the prefix is unique to that code path.
|
|
97688
|
+
*/
|
|
97689
|
+
const EXACT_MACHINE_BROKER_AUTHORITY_PATTERN = /provider token issuance requires a live exact-machine broker authority/i;
|
|
97690
|
+
/**
|
|
97691
|
+
* Classifies a `provider.turn.start.failed` activity's detail. This activity
|
|
97692
|
+
* kind also covers non-auth causes (a draining runtime, a missing user
|
|
97693
|
+
* message, a rejected workspace, a start-timeout cleanup failure), so only
|
|
97694
|
+
* text that names a known authentication refusal is reported as one; every
|
|
97695
|
+
* other detail degrades to `unspecified_failure` rather than telling CMMD to
|
|
97696
|
+
* offer a reconnect action credentials cannot fix.
|
|
97697
|
+
*/
|
|
97698
|
+
function classifyProviderTurnStartFailureReason(detail) {
|
|
97699
|
+
const message = detail.trim().length > 0 ? detail : "The provider could not start a session for this run.";
|
|
97700
|
+
return {
|
|
97701
|
+
code: GITHUB_GRANT_DETAIL_PATTERN.test(detail) ? "provider_start_refused_github_grant" : EXACT_MACHINE_BROKER_AUTHORITY_PATTERN.test(detail) || isProviderCredentialFailure(detail) ? "provider_start_refused_auth" : "unspecified_failure",
|
|
97702
|
+
message
|
|
97703
|
+
};
|
|
97704
|
+
}
|
|
97705
|
+
function checkpointTimeoutReason(detail) {
|
|
97706
|
+
return {
|
|
97707
|
+
code: "checkpoint_timeout",
|
|
97708
|
+
message: detail.trim().length > 0 ? detail : "The workspace checkpoint timed out before the turn could start."
|
|
97709
|
+
};
|
|
97710
|
+
}
|
|
97711
|
+
//#endregion
|
|
97461
97712
|
//#region src/runtimeReplication/replicaSerializer.ts
|
|
97462
97713
|
const replicaTurnStatuses = new Set([
|
|
97463
97714
|
"pending",
|
|
@@ -97798,6 +98049,53 @@ function serializeFailureActivity(activity) {
|
|
|
97798
98049
|
}
|
|
97799
98050
|
};
|
|
97800
98051
|
}
|
|
98052
|
+
/** Only the pre-turn baseline capture sets this; a post-turn capture failure
|
|
98053
|
+
* never blocked the turn from starting and must not fail it retroactively. */
|
|
98054
|
+
function blocksTurnStart(activity) {
|
|
98055
|
+
const payload = activity.payload;
|
|
98056
|
+
if (!payload || typeof payload !== "object") return false;
|
|
98057
|
+
return payload.blocksTurnStart === true;
|
|
98058
|
+
}
|
|
98059
|
+
function isCheckpointTimeoutDetail(detail) {
|
|
98060
|
+
return /timed out/i.test(detail);
|
|
98061
|
+
}
|
|
98062
|
+
/**
|
|
98063
|
+
* The structured reason a turn never started, when this activity is one of
|
|
98064
|
+
* the two known causes (CMMD-Center/forge#3496): the provider refused to
|
|
98065
|
+
* start a session, or the pre-turn checkpoint that must precede it timed out.
|
|
98066
|
+
* `null` for every other activity, including a checkpoint failure that did
|
|
98067
|
+
* not block turn start or was not a timeout.
|
|
98068
|
+
*/
|
|
98069
|
+
function turnStartFailureReason(activity) {
|
|
98070
|
+
const detail = failureDetail(activity);
|
|
98071
|
+
if (activity.kind === "provider.turn.start.failed") return classifyProviderTurnStartFailureReason(detail ?? "");
|
|
98072
|
+
if (activity.kind === "checkpoint.capture.failed" && blocksTurnStart(activity) && detail !== null && isCheckpointTimeoutDetail(detail)) return checkpointTimeoutReason(detail);
|
|
98073
|
+
return null;
|
|
98074
|
+
}
|
|
98075
|
+
/**
|
|
98076
|
+
* Replicates a turn-start failure as `turn_status: failed` with a structured
|
|
98077
|
+
* `reason`, alongside (not instead of) the free-text `error_summary` above:
|
|
98078
|
+
* CMMD fails the owning Work Session from this event
|
|
98079
|
+
* (CMMD-Center/CMMD#5272/#5286), which `error_summary` alone cannot drive.
|
|
98080
|
+
*/
|
|
98081
|
+
function serializeTurnStartFailureStatus(activity) {
|
|
98082
|
+
const reason = turnStartFailureReason(activity);
|
|
98083
|
+
if (reason === null) return null;
|
|
98084
|
+
return {
|
|
98085
|
+
sourceSequence: null,
|
|
98086
|
+
event: {
|
|
98087
|
+
eventId: `activity:${activity.id}:turn-status`,
|
|
98088
|
+
occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
|
|
98089
|
+
...eventTurn(activity.turnId),
|
|
98090
|
+
kind: "turn_status",
|
|
98091
|
+
status: "failed",
|
|
98092
|
+
reason: {
|
|
98093
|
+
code: reason.code,
|
|
98094
|
+
message: text(reason.message, 2e3) ?? reason.message
|
|
98095
|
+
}
|
|
98096
|
+
}
|
|
98097
|
+
};
|
|
98098
|
+
}
|
|
97801
98099
|
const turnUsageKeys = [
|
|
97802
98100
|
"uncachedInputTokens",
|
|
97803
98101
|
"cachedInputTokens",
|
|
@@ -98146,7 +98444,7 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
98146
98444
|
});
|
|
98147
98445
|
const recordedTurns = serializeRecordedTurns(thread);
|
|
98148
98446
|
const textGenerations = serializeTextGenerations(thread);
|
|
98149
|
-
|
|
98447
|
+
const priorEvents = [
|
|
98150
98448
|
...messages,
|
|
98151
98449
|
...replicaActivities,
|
|
98152
98450
|
...toolActivities,
|
|
@@ -98155,7 +98453,15 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
98155
98453
|
...failureActivities,
|
|
98156
98454
|
...recordedTurns,
|
|
98157
98455
|
...textGenerations
|
|
98158
|
-
]
|
|
98456
|
+
];
|
|
98457
|
+
const latestPriorOccurredAt = priorEvents.reduce((latest, entry) => latest === null || entry.event.occurredAt > latest ? entry.event.occurredAt : latest, null);
|
|
98458
|
+
const turnStartFailureStatuses = thread.activities.flatMap((activity) => {
|
|
98459
|
+
const event = serializeTurnStartFailureStatus(activity);
|
|
98460
|
+
if (event === null) return [];
|
|
98461
|
+
if (latestPriorOccurredAt !== null && event.event.occurredAt < latestPriorOccurredAt) return [];
|
|
98462
|
+
return [event];
|
|
98463
|
+
});
|
|
98464
|
+
return [...priorEvents, ...turnStartFailureStatuses].toSorted((left, right) => {
|
|
98159
98465
|
if (left.sourceSequence !== null && right.sourceSequence !== null) return left.sourceSequence - right.sourceSequence;
|
|
98160
98466
|
if (left.sourceSequence !== null) return -1;
|
|
98161
98467
|
if (right.sourceSequence !== null) return 1;
|
|
@@ -152071,7 +152377,7 @@ async function prepareWorkspaceRepositoryDependencies(input) {
|
|
|
152071
152377
|
const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
|
|
152072
152378
|
if (dependencies.length === 0) return [];
|
|
152073
152379
|
const broker = input.broker ?? new GitHubBroker();
|
|
152074
|
-
const auth = broker.runtimeGrantAuthContext("github");
|
|
152380
|
+
const auth = broker.runtimeGrantAuthContext("github") ?? await broker.requestRuntimeGithubGrant?.() ?? null;
|
|
152075
152381
|
if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime GitHub grant.");
|
|
152076
152382
|
const readOrigin = input.readOrigin ?? readOriginFromGit;
|
|
152077
152383
|
const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
|
|
@@ -191200,7 +191506,7 @@ var Vce = {
|
|
|
191200
191506
|
function Ve() {
|
|
191201
191507
|
return Vce;
|
|
191202
191508
|
}
|
|
191203
|
-
var Us =
|
|
191509
|
+
var Us = $U.homedir(), lk = $U.tmpdir(), { env: Vc } = process$1, Yce = (e) => {
|
|
191204
191510
|
let t = path.join(Us, "Library");
|
|
191205
191511
|
return {
|
|
191206
191512
|
data: path.join(t, "Application Support", e),
|
|
@@ -201400,12 +201706,12 @@ function qZ(e, t) {
|
|
|
201400
201706
|
}
|
|
201401
201707
|
return n;
|
|
201402
201708
|
}
|
|
201403
|
-
var os$
|
|
201709
|
+
var os$1 = "https://code.claude.com/docs/en", kKe = [
|
|
201404
201710
|
{
|
|
201405
201711
|
matches: (e) => e.path === "permissions.defaultMode" && e.code === "invalid_value",
|
|
201406
201712
|
tip: {
|
|
201407
201713
|
suggestion: "Valid modes: \"acceptEdits\" (ask before file changes), \"plan\" (analysis only), \"bypassPermissions\" (auto-accept all), or \"default\" (standard behavior)",
|
|
201408
|
-
docLink: `${os$
|
|
201714
|
+
docLink: `${os$1}/iam#permission-modes`
|
|
201409
201715
|
}
|
|
201410
201716
|
},
|
|
201411
201717
|
{
|
|
@@ -201420,7 +201726,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201420
201726
|
matches: (e) => e.path.startsWith("env.") && e.code === "invalid_type",
|
|
201421
201727
|
tip: {
|
|
201422
201728
|
suggestion: "Environment variables must be strings. Wrap numbers and booleans in quotes. Example: \"DEBUG\": \"true\", \"PORT\": \"3000\"",
|
|
201423
|
-
docLink: `${os$
|
|
201729
|
+
docLink: `${os$1}/settings#environment-variables`
|
|
201424
201730
|
}
|
|
201425
201731
|
},
|
|
201426
201732
|
{
|
|
@@ -201431,14 +201737,14 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201431
201737
|
matches: (e) => e.path.startsWith("hooks.") && e.code === "invalid_key",
|
|
201432
201738
|
tip: {
|
|
201433
201739
|
suggestion: "Not a recognized hook event. Common events: PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, SessionEnd, Stop. Check spelling and capitalization.",
|
|
201434
|
-
docLink: `${os$
|
|
201740
|
+
docLink: `${os$1}/hooks`
|
|
201435
201741
|
}
|
|
201436
201742
|
},
|
|
201437
201743
|
{
|
|
201438
201744
|
matches: (e) => /\.hooks\.\d+\.command$/.test(e.path) && e.code === "invalid_type" && e.received === "undefined",
|
|
201439
201745
|
tip: {
|
|
201440
201746
|
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\"}.",
|
|
201441
|
-
docLink: `${os$
|
|
201747
|
+
docLink: `${os$1}/hooks#exec-form-and-shell-form`
|
|
201442
201748
|
}
|
|
201443
201749
|
},
|
|
201444
201750
|
{
|
|
@@ -201453,7 +201759,7 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201453
201759
|
matches: (e) => e.code === "unrecognized_keys",
|
|
201454
201760
|
tip: {
|
|
201455
201761
|
suggestion: "Check for typos or refer to the documentation for valid fields",
|
|
201456
|
-
docLink: `${os$
|
|
201762
|
+
docLink: `${os$1}/settings`
|
|
201457
201763
|
}
|
|
201458
201764
|
},
|
|
201459
201765
|
{
|
|
@@ -201468,13 +201774,13 @@ var os$2 = "https://code.claude.com/docs/en", kKe = [
|
|
|
201468
201774
|
matches: (e) => e.path === "permissions.additionalDirectories" && e.code === "invalid_type",
|
|
201469
201775
|
tip: {
|
|
201470
201776
|
suggestion: "Must be an array of directory paths. Example: [\"~/projects\", \"/tmp/workspace\"]. You can also use --add-dir flag or /add-dir command",
|
|
201471
|
-
docLink: `${os$
|
|
201777
|
+
docLink: `${os$1}/iam#working-directories`
|
|
201472
201778
|
}
|
|
201473
201779
|
}
|
|
201474
201780
|
], AKe = {
|
|
201475
|
-
permissions: `${os$
|
|
201476
|
-
env: `${os$
|
|
201477
|
-
hooks: `${os$
|
|
201781
|
+
permissions: `${os$1}/iam#configuring-permissions`,
|
|
201782
|
+
env: `${os$1}/settings#environment-variables`,
|
|
201783
|
+
hooks: `${os$1}/hooks`
|
|
201478
201784
|
};
|
|
201479
201785
|
function GZ(e) {
|
|
201480
201786
|
let t = kKe.find((r) => r.matches(e));
|
|
@@ -213077,73 +213383,6 @@ function primaryClaudeCredentialMatchesPrepared(env, preparedDirectory) {
|
|
|
213077
213383
|
}
|
|
213078
213384
|
}
|
|
213079
213385
|
//#endregion
|
|
213080
|
-
//#region src/provider/personalClaudeInVm.ts
|
|
213081
|
-
/**
|
|
213082
|
-
* CMMD writes this marker when it activates a personal Claude subscription
|
|
213083
|
-
* inside a running sprite. Activation always happens after boot, so a
|
|
213084
|
-
* boot-time env var alone cannot reflect it (FOR-36249).
|
|
213085
|
-
*
|
|
213086
|
-
* CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
|
|
213087
|
-
* Without that, the marker sits directly under FORGE_HOME, which is the same
|
|
213088
|
-
* guest home CMMD derives every sprite path from. A server with neither has no
|
|
213089
|
-
* sprite home to look in, and a desktop never has a marker, so it reads none.
|
|
213090
|
-
*/
|
|
213091
|
-
const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
|
|
213092
|
-
const PERSONAL_CLAUDE_GENERATION_FILE_NAME = ".forge-personal-claude-generation";
|
|
213093
|
-
function markerPath(env) {
|
|
213094
|
-
if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
|
|
213095
|
-
if (env.FORGE_HOME) return join(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
|
|
213096
|
-
return null;
|
|
213097
|
-
}
|
|
213098
|
-
/**
|
|
213099
|
-
* True when this runtime should offer the personal Claude subscription lane.
|
|
213100
|
-
*
|
|
213101
|
-
* Accepts either signal: the boot-time env var (backward compatible) or the
|
|
213102
|
-
* activation marker on disk. Reading the marker at call time lets the provider
|
|
213103
|
-
* catalog and the per-turn readiness guard self-heal once CMMD delivers,
|
|
213104
|
-
* without a sprite reboot. Callers still verify the credential is non-empty
|
|
213105
|
-
* before treating it as usable; this only decides whether the lane is active.
|
|
213106
|
-
*/
|
|
213107
|
-
function personalClaudeInVmEnabled(env = process.env) {
|
|
213108
|
-
if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
|
|
213109
|
-
const marker = markerPath(env);
|
|
213110
|
-
if (!marker) return false;
|
|
213111
|
-
try {
|
|
213112
|
-
return existsSync(marker);
|
|
213113
|
-
} catch {
|
|
213114
|
-
return false;
|
|
213115
|
-
}
|
|
213116
|
-
}
|
|
213117
|
-
/** Exported at boot. This is the restart proof CMMD waits on, so it must never read the file. */
|
|
213118
|
-
function bootExportedPersonalClaudeGenerationHash(env = process.env) {
|
|
213119
|
-
return env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
213120
|
-
}
|
|
213121
|
-
/**
|
|
213122
|
-
* The sha256 of the Claude credential generation this runtime holds, or "" when
|
|
213123
|
-
* it holds none. The readiness guard compares it with the generation CMMD
|
|
213124
|
-
* applied. The restart proof must not use it; it uses the boot-exported hash.
|
|
213125
|
-
*
|
|
213126
|
-
* The boot preload exports FORGE_PERSONAL_CLAUDE_GENERATION_HASH, but only when
|
|
213127
|
-
* the marker already exists at boot. Activation in place writes the generation
|
|
213128
|
-
* file and touches the marker without setting any env, so an empty variable
|
|
213129
|
-
* falls back to that file, hashed the way the preload hashes it. CMMD puts the
|
|
213130
|
-
* file beside the marker in the guest home and names no path for it. A marker
|
|
213131
|
-
* with no generation file is a legacy runtime, and gets no hash here either.
|
|
213132
|
-
*/
|
|
213133
|
-
function personalClaudeGenerationHash(env = process.env) {
|
|
213134
|
-
const exported = bootExportedPersonalClaudeGenerationHash(env);
|
|
213135
|
-
if (exported) return exported;
|
|
213136
|
-
const marker = markerPath(env);
|
|
213137
|
-
if (!marker) return "";
|
|
213138
|
-
try {
|
|
213139
|
-
if (!existsSync(marker)) return "";
|
|
213140
|
-
const generation = readFileSync(join(dirname(marker), PERSONAL_CLAUDE_GENERATION_FILE_NAME), "utf8").trim();
|
|
213141
|
-
return generation ? createHash("sha256").update(generation).digest("hex") : "";
|
|
213142
|
-
} catch {
|
|
213143
|
-
return "";
|
|
213144
|
-
}
|
|
213145
|
-
}
|
|
213146
|
-
//#endregion
|
|
213147
213386
|
//#region src/provider/claudeCredentialContainment.ts
|
|
213148
213387
|
/**
|
|
213149
213388
|
* Containment for provider credentials inherited by the Claude Agent SDK child.
|
|
@@ -215224,128 +215463,6 @@ function createGitHubMcpServer(runBrokeredTool, threadId, settings) {
|
|
|
215224
215463
|
});
|
|
215225
215464
|
}
|
|
215226
215465
|
//#endregion
|
|
215227
|
-
//#region src/provider/providerAuthFailureCopy.ts
|
|
215228
|
-
/**
|
|
215229
|
-
* Rewrite provider auth failures into an action the user can actually take.
|
|
215230
|
-
*
|
|
215231
|
-
* The provider SDKs assume they are running under a human at a terminal, so
|
|
215232
|
-
* their auth errors name CLI verbs: Claude's is literally
|
|
215233
|
-
* `Not logged in · Please run /login`. On Forge Cloud that reaches a browser
|
|
215234
|
-
* user who has no terminal, no shell, and no way to run `/login` — the turn
|
|
215235
|
-
* dies in ~3 s naming an action that does not exist for them (observed live
|
|
215236
|
-
* 2026-08-05).
|
|
215237
|
-
*
|
|
215238
|
-
* CMMD already writes the right shape for the Codex egress path — "Connect
|
|
215239
|
-
* ChatGPT in Settings > Providers, then retry" — which names a destination the
|
|
215240
|
-
* user can click. This gives the provider-SDK failures the same treatment.
|
|
215241
|
-
*
|
|
215242
|
-
* Scope is deliberately narrow. Only messages that clearly identify a
|
|
215243
|
-
* *credential* problem are rewritten; everything else passes through verbatim,
|
|
215244
|
-
* because a rewritten unknown error loses detail an operator needs and risks
|
|
215245
|
-
* telling the user to reconnect an account that was never the problem.
|
|
215246
|
-
*/
|
|
215247
|
-
/**
|
|
215248
|
-
* Signatures of "you have no usable credential", matched case-insensitively.
|
|
215249
|
-
*
|
|
215250
|
-
* Kept as explicit patterns rather than a broad /auth/i so that genuine
|
|
215251
|
-
* mid-turn failures mentioning authorization (a tool hitting a 403 on some
|
|
215252
|
-
* unrelated API, say) are not misreported as "your subscription is
|
|
215253
|
-
* disconnected".
|
|
215254
|
-
*/
|
|
215255
|
-
const CREDENTIAL_FAILURE_PATTERNS = [
|
|
215256
|
-
/\bnot logged in\b/i,
|
|
215257
|
-
/\bplease run\s*\/login\b/i,
|
|
215258
|
-
/\brun\s*\/login\b/i,
|
|
215259
|
-
/\blogin required\b/i,
|
|
215260
|
-
/\bnot authenticated\b/i,
|
|
215261
|
-
/\bno credentials? found\b/i,
|
|
215262
|
-
/\binvalid api key\b/i,
|
|
215263
|
-
/\bauthentication_error\b/i
|
|
215264
|
-
];
|
|
215265
|
-
/**
|
|
215266
|
-
* Claude's words for a delivered credential whose refresh chain is dead.
|
|
215267
|
-
*
|
|
215268
|
-
* Deliberately NOT in CREDENTIAL_FAILURE_PATTERNS. On a runtime whose Claude
|
|
215269
|
-
* credential CMMD delivers, this is not "you are signed out" and reconnecting
|
|
215270
|
-
* in Settings is not the fix: CMMD's authority rotates the refresh token on
|
|
215271
|
-
* every refresh, the snapshot in the guest dies at the next rotation, and the
|
|
215272
|
-
* repair is a re-delivery the next send triggers (FOR-36425, CMMD-36441).
|
|
215273
|
-
* Anywhere else the credential is the user's own local login, so the CLI text
|
|
215274
|
-
* still passes through untouched.
|
|
215275
|
-
*/
|
|
215276
|
-
const DELIVERED_CREDENTIAL_EXPIRED_PATTERN = /\boauth session expired\b/i;
|
|
215277
|
-
const DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY = "The delivered Claude credential expired. CMMD refreshes it on the next send, so retry this turn.";
|
|
215278
|
-
/** True when the message is about a missing/unusable credential, not a tool failure. */
|
|
215279
|
-
function isProviderCredentialFailure(message) {
|
|
215280
|
-
return CREDENTIAL_FAILURE_PATTERNS.some((pattern) => pattern.test(message));
|
|
215281
|
-
}
|
|
215282
|
-
/**
|
|
215283
|
-
* Map a ProviderKind to the account name a user would recognise.
|
|
215284
|
-
*
|
|
215285
|
-
* NOTE the literal is `claudeAgent`, not `claude`. This read `"claude"` and so
|
|
215286
|
-
* never matched, silently degrading every Claude auth failure to the generic
|
|
215287
|
-
* fallback — "Forge Cloud has no **this provider** subscription connected."
|
|
215288
|
-
* Same provider-name drift class as the model-mapping fix (#2103/#2105).
|
|
215289
|
-
*
|
|
215290
|
-
* The names come from the shared PROVIDER_ACCOUNT_NAMES map rather than a local
|
|
215291
|
-
* switch: this file's copy was the ONLY correct provider→label mapping in the
|
|
215292
|
-
* codebase while four client call sites reimplemented it as binary ternaries.
|
|
215293
|
-
* One Record over ProviderKind means a new provider is a compile error in one
|
|
215294
|
-
* place instead of five sentences that read wrong in production.
|
|
215295
|
-
*
|
|
215296
|
-
* Providers that do not sell a personal subscription stay generic on purpose —
|
|
215297
|
-
* "Forge Cloud has no Ollama subscription connected" names something that does
|
|
215298
|
-
* not exist.
|
|
215299
|
-
*/
|
|
215300
|
-
function providerLabel(provider) {
|
|
215301
|
-
return supportsPersonalSubscription(provider) ? PROVIDER_ACCOUNT_NAMES[provider] : "this provider";
|
|
215302
|
-
}
|
|
215303
|
-
/**
|
|
215304
|
-
* Return the message to show for a failed turn.
|
|
215305
|
-
*
|
|
215306
|
-
* `surface` decides the destination: a cloud runtime has no terminal, so it is
|
|
215307
|
-
* pointed at Settings; a local/desktop runtime CAN run the CLI login, so its
|
|
215308
|
-
* copy still offers that as the concrete fix rather than sending someone to a
|
|
215309
|
-
* settings page that is not how they authenticate.
|
|
215310
|
-
*/
|
|
215311
|
-
function rewriteProviderAuthFailure(input) {
|
|
215312
|
-
const original = input.message ?? "";
|
|
215313
|
-
if (original.length === 0) return {
|
|
215314
|
-
message: original,
|
|
215315
|
-
rewritten: false
|
|
215316
|
-
};
|
|
215317
|
-
if (input.deliveredClaudeCredential === true && DELIVERED_CREDENTIAL_EXPIRED_PATTERN.test(original)) return {
|
|
215318
|
-
message: DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY,
|
|
215319
|
-
rewritten: true
|
|
215320
|
-
};
|
|
215321
|
-
if (!isProviderCredentialFailure(original)) return {
|
|
215322
|
-
message: original,
|
|
215323
|
-
rewritten: false
|
|
215324
|
-
};
|
|
215325
|
-
const who = providerLabel(input.provider);
|
|
215326
|
-
return {
|
|
215327
|
-
message: input.surface === "cloud" ? `Forge Cloud has no ${who} subscription connected for your account. Open Settings > Providers to connect one, then retry this turn.` : `This runtime is not signed in to ${who}. Connect it from Settings > Providers, then retry this turn.`,
|
|
215328
|
-
rewritten: true
|
|
215329
|
-
};
|
|
215330
|
-
}
|
|
215331
|
-
/**
|
|
215332
|
-
* Convenience for call sites that just want the string to show.
|
|
215333
|
-
*
|
|
215334
|
-
* Exists so adapters do not each re-derive the surface and re-handle the
|
|
215335
|
-
* undefined case inline — that inlining is what pushed
|
|
215336
|
-
* ClaudeAdapter.handleResultMessage over the cyclomatic gate. Returns
|
|
215337
|
-
* `undefined` unchanged so "no error" stays distinguishable from "empty error".
|
|
215338
|
-
*/
|
|
215339
|
-
function providerAuthFailureMessage(message, provider, env = process.env) {
|
|
215340
|
-
if (message === void 0) return void 0;
|
|
215341
|
-
return rewriteProviderAuthFailure({
|
|
215342
|
-
message,
|
|
215343
|
-
provider,
|
|
215344
|
-
surface: env.FORGE_CMMD_RUNTIME_MODE === "true" ? "cloud" : "local",
|
|
215345
|
-
deliveredClaudeCredential: provider === "claudeAgent" && personalClaudeInVmEnabled(env)
|
|
215346
|
-
}).message;
|
|
215347
|
-
}
|
|
215348
|
-
//#endregion
|
|
215349
215466
|
//#region src/provider/missingWorkspaceCopy.ts
|
|
215350
215467
|
/**
|
|
215351
215468
|
* Refuse a turn whose checkout folder is gone, and say so.
|
|
@@ -233674,6 +233791,8 @@ const CheckpointDiffQueryLive = effect(CheckpointDiffQuery, gen(function* () {
|
|
|
233674
233791
|
* @module CheckpointStoreLive
|
|
233675
233792
|
*/
|
|
233676
233793
|
const CHECKPOINT_PATH_OUTPUT_MAX_BYTES = 16 * 1024 * 1024;
|
|
233794
|
+
const CheckpointCaptureTimeoutConfig = number$2("FORGE_CHECKPOINT_CAPTURE_TIMEOUT_MS").pipe(withDefault$3(6e4));
|
|
233795
|
+
const EFFECT_TIMER_MAX_MS = 2147483647;
|
|
233677
233796
|
function parseNullSeparatedPaths(output) {
|
|
233678
233797
|
return output.split("\0").filter((entry) => entry.length > 0);
|
|
233679
233798
|
}
|
|
@@ -233681,7 +233800,7 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233681
233800
|
const fs = yield* FileSystem;
|
|
233682
233801
|
const path = yield* Path$2;
|
|
233683
233802
|
const git = yield* GitCore;
|
|
233684
|
-
const resolveHeadCommit = (cwd) => git.execute({
|
|
233803
|
+
const resolveHeadCommit = (cwd, timeoutMs) => git.execute({
|
|
233685
233804
|
operation: "CheckpointStore.resolveHeadCommit",
|
|
233686
233805
|
cwd,
|
|
233687
233806
|
args: [
|
|
@@ -233690,7 +233809,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233690
233809
|
"--quiet",
|
|
233691
233810
|
"HEAD^{commit}"
|
|
233692
233811
|
],
|
|
233693
|
-
allowNonZeroExit: true
|
|
233812
|
+
allowNonZeroExit: true,
|
|
233813
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
233694
233814
|
}).pipe(map$3((result) => {
|
|
233695
233815
|
if (result.code !== 0) return null;
|
|
233696
233816
|
const commit = result.stdout.trim();
|
|
@@ -233729,7 +233849,9 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233729
233849
|
}).pipe(map$3((result) => result.code === 0 && result.stdout.trim() === "true"), catch_(() => succeed(false)));
|
|
233730
233850
|
const captureCheckpoint = fn$1("captureCheckpoint")(function* (input) {
|
|
233731
233851
|
const operation = "CheckpointStore.captureCheckpoint";
|
|
233732
|
-
const
|
|
233852
|
+
const captureTimeoutMs = yield* CheckpointCaptureTimeoutConfig.asEffect().pipe(orDie);
|
|
233853
|
+
if (!Number.isFinite(captureTimeoutMs) || captureTimeoutMs <= 0 || captureTimeoutMs > EFFECT_TIMER_MAX_MS) return yield* die$1(/* @__PURE__ */ new Error(`FORGE_CHECKPOINT_CAPTURE_TIMEOUT_MS must be a finite number greater than zero and at most ${EFFECT_TIMER_MAX_MS}, got ${captureTimeoutMs}.`));
|
|
233854
|
+
const headCommit = yield* resolveHeadCommit(input.cwd, captureTimeoutMs);
|
|
233733
233855
|
const [trackedPathResult, untrackedPathResult] = yield* all([git.execute({
|
|
233734
233856
|
operation,
|
|
233735
233857
|
cwd: input.cwd,
|
|
@@ -233746,7 +233868,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233746
233868
|
"-z",
|
|
233747
233869
|
"--"
|
|
233748
233870
|
],
|
|
233749
|
-
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES
|
|
233871
|
+
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES,
|
|
233872
|
+
timeoutMs: captureTimeoutMs
|
|
233750
233873
|
}), git.execute({
|
|
233751
233874
|
operation,
|
|
233752
233875
|
cwd: input.cwd,
|
|
@@ -233757,7 +233880,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233757
233880
|
"-z",
|
|
233758
233881
|
"--"
|
|
233759
233882
|
],
|
|
233760
|
-
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES
|
|
233883
|
+
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES,
|
|
233884
|
+
timeoutMs: captureTimeoutMs
|
|
233761
233885
|
})], { concurrency: 2 });
|
|
233762
233886
|
const changedPaths = [...new Set([...parseNullSeparatedPaths(trackedPathResult.stdout), ...parseNullSeparatedPaths(untrackedPathResult.stdout)])];
|
|
233763
233887
|
if (headCommit && changedPaths.length === 0) {
|
|
@@ -233768,7 +233892,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233768
233892
|
"update-ref",
|
|
233769
233893
|
input.checkpointRef,
|
|
233770
233894
|
headCommit
|
|
233771
|
-
]
|
|
233895
|
+
],
|
|
233896
|
+
timeoutMs: captureTimeoutMs
|
|
233772
233897
|
});
|
|
233773
233898
|
return;
|
|
233774
233899
|
}
|
|
@@ -233786,13 +233911,15 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233786
233911
|
operation,
|
|
233787
233912
|
cwd: input.cwd,
|
|
233788
233913
|
args: ["read-tree", headCommit],
|
|
233789
|
-
env: commitEnv
|
|
233914
|
+
env: commitEnv,
|
|
233915
|
+
timeoutMs: captureTimeoutMs
|
|
233790
233916
|
});
|
|
233791
233917
|
else yield* git.execute({
|
|
233792
233918
|
operation,
|
|
233793
233919
|
cwd: input.cwd,
|
|
233794
233920
|
args: ["read-tree", "--empty"],
|
|
233795
|
-
env: commitEnv
|
|
233921
|
+
env: commitEnv,
|
|
233922
|
+
timeoutMs: captureTimeoutMs
|
|
233796
233923
|
});
|
|
233797
233924
|
if (changedPaths.length > 0) yield* git.execute({
|
|
233798
233925
|
operation,
|
|
@@ -233805,13 +233932,15 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233805
233932
|
"--pathspec-file-nul"
|
|
233806
233933
|
],
|
|
233807
233934
|
stdin: changedPaths.map((changedPath) => `${changedPath}\0`).join(""),
|
|
233808
|
-
env: commitEnv
|
|
233935
|
+
env: commitEnv,
|
|
233936
|
+
timeoutMs: captureTimeoutMs
|
|
233809
233937
|
});
|
|
233810
233938
|
const treeOid = (yield* git.execute({
|
|
233811
233939
|
operation,
|
|
233812
233940
|
cwd: input.cwd,
|
|
233813
233941
|
args: ["write-tree"],
|
|
233814
|
-
env: commitEnv
|
|
233942
|
+
env: commitEnv,
|
|
233943
|
+
timeoutMs: captureTimeoutMs
|
|
233815
233944
|
})).stdout.trim();
|
|
233816
233945
|
if (treeOid.length === 0) return yield* new GitCommandError({
|
|
233817
233946
|
operation,
|
|
@@ -233829,7 +233958,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233829
233958
|
"-m",
|
|
233830
233959
|
message
|
|
233831
233960
|
],
|
|
233832
|
-
env: commitEnv
|
|
233961
|
+
env: commitEnv,
|
|
233962
|
+
timeoutMs: captureTimeoutMs
|
|
233833
233963
|
})).stdout.trim();
|
|
233834
233964
|
if (commitOid.length === 0) return yield* new GitCommandError({
|
|
233835
233965
|
operation,
|
|
@@ -233844,7 +233974,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233844
233974
|
"update-ref",
|
|
233845
233975
|
input.checkpointRef,
|
|
233846
233976
|
commitOid
|
|
233847
|
-
]
|
|
233977
|
+
],
|
|
233978
|
+
timeoutMs: captureTimeoutMs
|
|
233848
233979
|
});
|
|
233849
233980
|
}), (tempDir) => fs.remove(tempDir, { recursive: true })).pipe(catchTags({ PlatformError: (error) => fail(new CheckpointInvariantError({
|
|
233850
233981
|
operation: "CheckpointStore.captureCheckpoint",
|
|
@@ -269601,7 +269732,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
269601
269732
|
var dgram = __require("dgram");
|
|
269602
269733
|
var thunky = require_thunky();
|
|
269603
269734
|
var events = __require("events");
|
|
269604
|
-
var os
|
|
269735
|
+
var os = __require("os");
|
|
269605
269736
|
var noop = function() {};
|
|
269606
269737
|
module.exports = function(opts) {
|
|
269607
269738
|
if (!opts) opts = {};
|
|
@@ -269732,14 +269863,14 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
269732
269863
|
return that;
|
|
269733
269864
|
};
|
|
269734
269865
|
function defaultInterface() {
|
|
269735
|
-
var networks = os
|
|
269866
|
+
var networks = os.networkInterfaces();
|
|
269736
269867
|
var names = Object.keys(networks);
|
|
269737
269868
|
for (var i = 0; i < names.length; i++) {
|
|
269738
269869
|
var net = networks[names[i]];
|
|
269739
269870
|
for (var j = 0; j < net.length; j++) {
|
|
269740
269871
|
var iface = net[j];
|
|
269741
269872
|
if (isIPv4(iface.family) && !iface.internal) {
|
|
269742
|
-
if (os
|
|
269873
|
+
if (os.platform() === "darwin" && names[i] === "en0") return iface.address;
|
|
269743
269874
|
return "0.0.0.0";
|
|
269744
269875
|
}
|
|
269745
269876
|
}
|
|
@@ -269747,7 +269878,7 @@ var import_multicast_dns = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMi
|
|
|
269747
269878
|
return "127.0.0.1";
|
|
269748
269879
|
}
|
|
269749
269880
|
function allInterfaces() {
|
|
269750
|
-
var networks = os
|
|
269881
|
+
var networks = os.networkInterfaces();
|
|
269751
269882
|
var names = Object.keys(networks);
|
|
269752
269883
|
var res = [];
|
|
269753
269884
|
for (var i = 0; i < names.length; i++) {
|
|
@@ -291680,7 +291811,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
|
|
|
291680
291811
|
summary: "Agent turn blocked",
|
|
291681
291812
|
detail: "Forge runtime is updating after the current turn. New agent turns are paused until the runtime finishes draining.",
|
|
291682
291813
|
turnId: null,
|
|
291683
|
-
createdAt:
|
|
291814
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
291684
291815
|
});
|
|
291685
291816
|
return;
|
|
291686
291817
|
}
|
|
@@ -291914,7 +292045,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
|
|
|
291914
292045
|
summary: "Agent turn blocked",
|
|
291915
292046
|
detail: workspaceError,
|
|
291916
292047
|
turnId: null,
|
|
291917
|
-
createdAt:
|
|
292048
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
291918
292049
|
});
|
|
291919
292050
|
return;
|
|
291920
292051
|
}
|
|
@@ -293005,7 +293136,10 @@ const CheckpointReactorLive = effect(CheckpointReactor, gen(function* () {
|
|
|
293005
293136
|
tone: "error",
|
|
293006
293137
|
kind: "checkpoint.capture.failed",
|
|
293007
293138
|
summary: "Checkpoint capture failed",
|
|
293008
|
-
payload: {
|
|
293139
|
+
payload: {
|
|
293140
|
+
detail: input.detail,
|
|
293141
|
+
...input.blocksTurnStart ? { blocksTurnStart: true } : {}
|
|
293142
|
+
},
|
|
293009
293143
|
turnId: input.turnId,
|
|
293010
293144
|
createdAt: input.createdAt
|
|
293011
293145
|
},
|
|
@@ -293291,7 +293425,13 @@ const CheckpointReactorLive = effect(CheckpointReactor, gen(function* () {
|
|
|
293291
293425
|
yield* checkpointStore.captureCheckpoint({
|
|
293292
293426
|
cwd: checkpointCwd,
|
|
293293
293427
|
checkpointRef: baselineCheckpointRef
|
|
293294
|
-
})
|
|
293428
|
+
}).pipe(catch_((error) => appendCaptureFailureActivity({
|
|
293429
|
+
threadId,
|
|
293430
|
+
turnId: null,
|
|
293431
|
+
detail: error.message,
|
|
293432
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
293433
|
+
blocksTurnStart: true
|
|
293434
|
+
}).pipe(catch_(() => void_$1))));
|
|
293295
293435
|
yield* receiptBus.publish({
|
|
293296
293436
|
type: "checkpoint.baseline.captured",
|
|
293297
293437
|
threadId,
|
|
@@ -297651,7 +297791,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
297651
297791
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
297652
297792
|
*/
|
|
297653
297793
|
function readBakedBuildCommit() {
|
|
297654
|
-
return "
|
|
297794
|
+
return "589c5f6a52f5cd82c598399da0ece13acd43d50d";
|
|
297655
297795
|
}
|
|
297656
297796
|
async function resolveServerBuildCommit(input) {
|
|
297657
297797
|
if (isFullCommitSha(input.baked)) return input.baked;
|
|
@@ -322247,7 +322387,7 @@ const connectLoginCommand = make$34("login", {
|
|
|
322247
322387
|
})).json();
|
|
322248
322388
|
},
|
|
322249
322389
|
openBrowser: async (url) => {
|
|
322250
|
-
const open = (await import("./open-
|
|
322390
|
+
const open = (await import("./open-2-reBrpv.mjs")).default;
|
|
322251
322391
|
await open(url);
|
|
322252
322392
|
return true;
|
|
322253
322393
|
},
|