@cmmd-center/forge 0.13.63 → 0.13.65
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 +384 -210
- package/dist/bin.mjs +384 -210
- package/dist/client/assets/{DiffPanel-Mj56e5Vd.js → DiffPanel-DUE5CUPF.js} +2 -2
- package/dist/client/assets/{DiffPanel.logic-Bws9oedp.js → DiffPanel.logic-DkmY3FZZ.js} +2 -2
- package/dist/client/assets/{DiffPanelShell-CfYAh40Z.js → DiffPanelShell-DH6nYRyX.js} +3 -3
- package/dist/client/assets/{DiffWorkerPoolProvider-CKLNoRVs.js → DiffWorkerPoolProvider-DTK8o_hI.js} +2 -2
- package/dist/client/assets/{PullRequestCodePanel-7AnSD7oo.js → PullRequestCodePanel-BIjtTKuP.js} +2 -2
- package/dist/client/assets/{Virtualizer-nWFuwagg.js → Virtualizer-CPNxYGbV.js} +2 -2
- package/dist/client/assets/{index-DW9f_7Ly.js → index-Df09-gdQ.js} +6 -6
- package/dist/client/index.html +4 -4
- package/dist/forge-build.json +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -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.65";
|
|
65965
65965
|
//#endregion
|
|
65966
65966
|
//#region src/sentry.ts
|
|
65967
65967
|
const SERVER_APP_NAME = "forge-server";
|
|
@@ -88991,6 +88991,62 @@ 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,
|
|
88997
|
+
* CMMD-Center/forge#3498). CMMD delivers the grant proactively on
|
|
88998
|
+
* thread.create and the kick, so a miss on a woken runtime is a cache or
|
|
88999
|
+
* ticket TTL problem, not a missing request: this closes the gap where the
|
|
89000
|
+
* ticket lands in the broker authority cache mid-request, and logs the
|
|
89001
|
+
* outcome on failure so the cause is visible instead of a silent refusal.
|
|
89002
|
+
*
|
|
89003
|
+
* A CMMD cloud runtime (every sprite) must still make this round trip
|
|
89004
|
+
* itself: `status()`'s own grant gate fails closed before ever reaching
|
|
89005
|
+
* the network for that runtime class (ADR-015 — it must never fall
|
|
89006
|
+
* through to the human JWT), so this uses `probeRuntimeGithubGrantDelivery`
|
|
89007
|
+
* instead, which makes the identical round trip without that JWT
|
|
89008
|
+
* fallback.
|
|
89009
|
+
*/
|
|
89010
|
+
async requestRuntimeGithubGrant() {
|
|
89011
|
+
const cached = this.runtimeGrantAuthContext("github");
|
|
89012
|
+
if (cached) return cached;
|
|
89013
|
+
const runtimeId = process.env.FORGE_CMMD_RUNTIME_ID?.trim();
|
|
89014
|
+
if (!runtimeId) return null;
|
|
89015
|
+
if (this.isCmmdCloudRuntimeMode) await this.probeRuntimeGithubGrantDelivery(runtimeId);
|
|
89016
|
+
else await this.status({
|
|
89017
|
+
sessionId: AuthSessionId.make(`cmmd-runtime:${runtimeId}`),
|
|
89018
|
+
subject: `runtime:${runtimeId}`
|
|
89019
|
+
});
|
|
89020
|
+
const grant = this.runtimeGrantAuthContext("github");
|
|
89021
|
+
if (!grant) console.warn(`[github-broker] re-requested the github scope grant for runtime ${runtimeId}: still missing after one network round trip`);
|
|
89022
|
+
return grant;
|
|
89023
|
+
}
|
|
89024
|
+
/**
|
|
89025
|
+
* The cloud-runtime half of `requestRuntimeGithubGrant`'s re-request.
|
|
89026
|
+
*
|
|
89027
|
+
* CMMD's status route always requires a Bearer that already carries the
|
|
89028
|
+
* "github" scope (verified server-side against the presented ticket's own
|
|
89029
|
+
* claims — see `forge-runtime-grants.routes.ts` on CMMD main), and there is
|
|
89030
|
+
* no separate CMMD route that mints a "github"-scoped grant on demand. So
|
|
89031
|
+
* this call's own response can never carry a grant to install; its purpose
|
|
89032
|
+
* is the wait. CMMD delivers a fresh grant on this same runtime's inbound
|
|
89033
|
+
* thread.create/kick requests (`ServerAuth.authenticateHttpRequest` /
|
|
89034
|
+
* `authenticateWebSocketUpgrade`, which install it into the broker
|
|
89035
|
+
* authority cache as a side effect of authenticating each request), and a
|
|
89036
|
+
* delivery already in flight when the caller checked the cache gets this
|
|
89037
|
+
* round trip's worth of extra time to land before the caller gives up.
|
|
89038
|
+
* Bounded by the same timeout as every other runtime-grant call, and
|
|
89039
|
+
* deliberately unauthenticated: sending no Bearer is exactly as
|
|
89040
|
+
* informative to CMMD as sending one lacking the required scope, and
|
|
89041
|
+
* skips resolving one for a request whose own response is never used.
|
|
89042
|
+
*/
|
|
89043
|
+
async probeRuntimeGithubGrantDelivery(runtimeId) {
|
|
89044
|
+
const response = await this.fetchImpl(makeUrl(this.commandAppUrl, `/api/forge/runtimes/${encodeURIComponent(runtimeId)}/github/status`), {
|
|
89045
|
+
method: "GET",
|
|
89046
|
+
signal: AbortSignal.timeout(GITHUB_BROKER_REQUEST_TIMEOUT_MS)
|
|
89047
|
+
}).catch(() => null);
|
|
89048
|
+
if (response !== null) await parseResponseBody(response);
|
|
89049
|
+
}
|
|
88994
89050
|
async workspaceStatus(input) {
|
|
88995
89051
|
const cwdValidation = validateCwd(input.cwd);
|
|
88996
89052
|
if (cwdValidation) return cwdValidation;
|
|
@@ -97458,6 +97514,235 @@ function serializeProviderCatalog(providers) {
|
|
|
97458
97514
|
return catalog;
|
|
97459
97515
|
}
|
|
97460
97516
|
//#endregion
|
|
97517
|
+
//#region src/provider/personalClaudeInVm.ts
|
|
97518
|
+
/**
|
|
97519
|
+
* CMMD writes this marker when it activates a personal Claude subscription
|
|
97520
|
+
* inside a running sprite. Activation always happens after boot, so a
|
|
97521
|
+
* boot-time env var alone cannot reflect it (FOR-36249).
|
|
97522
|
+
*
|
|
97523
|
+
* CMMD names the file in the Service env (FORGE_PERSONAL_CLAUDE_MARKER_PATH).
|
|
97524
|
+
* Without that, the marker sits directly under FORGE_HOME, which is the same
|
|
97525
|
+
* guest home CMMD derives every sprite path from. A server with neither has no
|
|
97526
|
+
* sprite home to look in, and a desktop never has a marker, so it reads none.
|
|
97527
|
+
*/
|
|
97528
|
+
const PERSONAL_CLAUDE_IN_VM_MARKER_NAME = ".forge-personal-claude-in-vm";
|
|
97529
|
+
const PERSONAL_CLAUDE_GENERATION_FILE_NAME = ".forge-personal-claude-generation";
|
|
97530
|
+
function markerPath(env) {
|
|
97531
|
+
if (env.FORGE_PERSONAL_CLAUDE_MARKER_PATH) return env.FORGE_PERSONAL_CLAUDE_MARKER_PATH;
|
|
97532
|
+
if (env.FORGE_HOME) return join(env.FORGE_HOME, PERSONAL_CLAUDE_IN_VM_MARKER_NAME);
|
|
97533
|
+
return null;
|
|
97534
|
+
}
|
|
97535
|
+
/**
|
|
97536
|
+
* True when this runtime should offer the personal Claude subscription lane.
|
|
97537
|
+
*
|
|
97538
|
+
* Accepts either signal: the boot-time env var (backward compatible) or the
|
|
97539
|
+
* activation marker on disk. Reading the marker at call time lets the provider
|
|
97540
|
+
* catalog and the per-turn readiness guard self-heal once CMMD delivers,
|
|
97541
|
+
* without a sprite reboot. Callers still verify the credential is non-empty
|
|
97542
|
+
* before treating it as usable; this only decides whether the lane is active.
|
|
97543
|
+
*/
|
|
97544
|
+
function personalClaudeInVmEnabled(env = process.env) {
|
|
97545
|
+
if (env.FORGE_PERSONAL_CLAUDE_IN_VM === "true") return true;
|
|
97546
|
+
const marker = markerPath(env);
|
|
97547
|
+
if (!marker) return false;
|
|
97548
|
+
try {
|
|
97549
|
+
return existsSync(marker);
|
|
97550
|
+
} catch {
|
|
97551
|
+
return false;
|
|
97552
|
+
}
|
|
97553
|
+
}
|
|
97554
|
+
/** Exported at boot. This is the restart proof CMMD waits on, so it must never read the file. */
|
|
97555
|
+
function bootExportedPersonalClaudeGenerationHash(env = process.env) {
|
|
97556
|
+
return env.FORGE_PERSONAL_CLAUDE_GENERATION_HASH?.trim() ?? "";
|
|
97557
|
+
}
|
|
97558
|
+
/**
|
|
97559
|
+
* The sha256 of the Claude credential generation this runtime holds, or "" when
|
|
97560
|
+
* it holds none. The readiness guard compares it with the generation CMMD
|
|
97561
|
+
* applied. The restart proof must not use it; it uses the boot-exported hash.
|
|
97562
|
+
*
|
|
97563
|
+
* The boot preload exports FORGE_PERSONAL_CLAUDE_GENERATION_HASH, but only when
|
|
97564
|
+
* the marker already exists at boot. Activation in place writes the generation
|
|
97565
|
+
* file and touches the marker without setting any env, so an empty variable
|
|
97566
|
+
* falls back to that file, hashed the way the preload hashes it. CMMD puts the
|
|
97567
|
+
* file beside the marker in the guest home and names no path for it. A marker
|
|
97568
|
+
* with no generation file is a legacy runtime, and gets no hash here either.
|
|
97569
|
+
*/
|
|
97570
|
+
function personalClaudeGenerationHash(env = process.env) {
|
|
97571
|
+
const exported = bootExportedPersonalClaudeGenerationHash(env);
|
|
97572
|
+
if (exported) return exported;
|
|
97573
|
+
const marker = markerPath(env);
|
|
97574
|
+
if (!marker) return "";
|
|
97575
|
+
try {
|
|
97576
|
+
if (!existsSync(marker)) return "";
|
|
97577
|
+
const generation = readFileSync(join(dirname(marker), PERSONAL_CLAUDE_GENERATION_FILE_NAME), "utf8").trim();
|
|
97578
|
+
return generation ? createHash("sha256").update(generation).digest("hex") : "";
|
|
97579
|
+
} catch {
|
|
97580
|
+
return "";
|
|
97581
|
+
}
|
|
97582
|
+
}
|
|
97583
|
+
//#endregion
|
|
97584
|
+
//#region src/provider/providerAuthFailureCopy.ts
|
|
97585
|
+
/**
|
|
97586
|
+
* Rewrite provider auth failures into an action the user can actually take.
|
|
97587
|
+
*
|
|
97588
|
+
* The provider SDKs assume they are running under a human at a terminal, so
|
|
97589
|
+
* their auth errors name CLI verbs: Claude's is literally
|
|
97590
|
+
* `Not logged in · Please run /login`. On Forge Cloud that reaches a browser
|
|
97591
|
+
* user who has no terminal, no shell, and no way to run `/login` — the turn
|
|
97592
|
+
* dies in ~3 s naming an action that does not exist for them (observed live
|
|
97593
|
+
* 2026-08-05).
|
|
97594
|
+
*
|
|
97595
|
+
* CMMD already writes the right shape for the Codex egress path — "Connect
|
|
97596
|
+
* ChatGPT in Settings > Providers, then retry" — which names a destination the
|
|
97597
|
+
* user can click. This gives the provider-SDK failures the same treatment.
|
|
97598
|
+
*
|
|
97599
|
+
* Scope is deliberately narrow. Only messages that clearly identify a
|
|
97600
|
+
* *credential* problem are rewritten; everything else passes through verbatim,
|
|
97601
|
+
* because a rewritten unknown error loses detail an operator needs and risks
|
|
97602
|
+
* telling the user to reconnect an account that was never the problem.
|
|
97603
|
+
*/
|
|
97604
|
+
/**
|
|
97605
|
+
* Signatures of "you have no usable credential", matched case-insensitively.
|
|
97606
|
+
*
|
|
97607
|
+
* Kept as explicit patterns rather than a broad /auth/i so that genuine
|
|
97608
|
+
* mid-turn failures mentioning authorization (a tool hitting a 403 on some
|
|
97609
|
+
* unrelated API, say) are not misreported as "your subscription is
|
|
97610
|
+
* disconnected".
|
|
97611
|
+
*/
|
|
97612
|
+
const CREDENTIAL_FAILURE_PATTERNS = [
|
|
97613
|
+
/\bnot logged in\b/i,
|
|
97614
|
+
/\bplease run\s*\/login\b/i,
|
|
97615
|
+
/\brun\s*\/login\b/i,
|
|
97616
|
+
/\blogin required\b/i,
|
|
97617
|
+
/\bnot authenticated\b/i,
|
|
97618
|
+
/\bno credentials? found\b/i,
|
|
97619
|
+
/\binvalid api key\b/i,
|
|
97620
|
+
/\bauthentication_error\b/i
|
|
97621
|
+
];
|
|
97622
|
+
/**
|
|
97623
|
+
* Claude's words for a delivered credential whose refresh chain is dead.
|
|
97624
|
+
*
|
|
97625
|
+
* Deliberately NOT in CREDENTIAL_FAILURE_PATTERNS. On a runtime whose Claude
|
|
97626
|
+
* credential CMMD delivers, this is not "you are signed out" and reconnecting
|
|
97627
|
+
* in Settings is not the fix: CMMD's authority rotates the refresh token on
|
|
97628
|
+
* every refresh, the snapshot in the guest dies at the next rotation, and the
|
|
97629
|
+
* repair is a re-delivery the next send triggers (FOR-36425, CMMD-36441).
|
|
97630
|
+
* Anywhere else the credential is the user's own local login, so the CLI text
|
|
97631
|
+
* still passes through untouched.
|
|
97632
|
+
*/
|
|
97633
|
+
const DELIVERED_CREDENTIAL_EXPIRED_PATTERN = /\boauth session expired\b/i;
|
|
97634
|
+
const DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY = "The delivered Claude credential expired. CMMD refreshes it on the next send, so retry this turn.";
|
|
97635
|
+
/** True when the message is about a missing/unusable credential, not a tool failure. */
|
|
97636
|
+
function isProviderCredentialFailure(message) {
|
|
97637
|
+
return CREDENTIAL_FAILURE_PATTERNS.some((pattern) => pattern.test(message));
|
|
97638
|
+
}
|
|
97639
|
+
/**
|
|
97640
|
+
* Map a ProviderKind to the account name a user would recognise.
|
|
97641
|
+
*
|
|
97642
|
+
* NOTE the literal is `claudeAgent`, not `claude`. This read `"claude"` and so
|
|
97643
|
+
* never matched, silently degrading every Claude auth failure to the generic
|
|
97644
|
+
* fallback — "Forge Cloud has no **this provider** subscription connected."
|
|
97645
|
+
* Same provider-name drift class as the model-mapping fix (#2103/#2105).
|
|
97646
|
+
*
|
|
97647
|
+
* The names come from the shared PROVIDER_ACCOUNT_NAMES map rather than a local
|
|
97648
|
+
* switch: this file's copy was the ONLY correct provider→label mapping in the
|
|
97649
|
+
* codebase while four client call sites reimplemented it as binary ternaries.
|
|
97650
|
+
* One Record over ProviderKind means a new provider is a compile error in one
|
|
97651
|
+
* place instead of five sentences that read wrong in production.
|
|
97652
|
+
*
|
|
97653
|
+
* Providers that do not sell a personal subscription stay generic on purpose —
|
|
97654
|
+
* "Forge Cloud has no Ollama subscription connected" names something that does
|
|
97655
|
+
* not exist.
|
|
97656
|
+
*/
|
|
97657
|
+
function providerLabel(provider) {
|
|
97658
|
+
return supportsPersonalSubscription(provider) ? PROVIDER_ACCOUNT_NAMES[provider] : "this provider";
|
|
97659
|
+
}
|
|
97660
|
+
/**
|
|
97661
|
+
* Return the message to show for a failed turn.
|
|
97662
|
+
*
|
|
97663
|
+
* `surface` decides the destination: a cloud runtime has no terminal, so it is
|
|
97664
|
+
* pointed at Settings; a local/desktop runtime CAN run the CLI login, so its
|
|
97665
|
+
* copy still offers that as the concrete fix rather than sending someone to a
|
|
97666
|
+
* settings page that is not how they authenticate.
|
|
97667
|
+
*/
|
|
97668
|
+
function rewriteProviderAuthFailure(input) {
|
|
97669
|
+
const original = input.message ?? "";
|
|
97670
|
+
if (original.length === 0) return {
|
|
97671
|
+
message: original,
|
|
97672
|
+
rewritten: false
|
|
97673
|
+
};
|
|
97674
|
+
if (input.deliveredClaudeCredential === true && DELIVERED_CREDENTIAL_EXPIRED_PATTERN.test(original)) return {
|
|
97675
|
+
message: DELIVERED_CLAUDE_CREDENTIAL_EXPIRED_COPY,
|
|
97676
|
+
rewritten: true
|
|
97677
|
+
};
|
|
97678
|
+
if (!isProviderCredentialFailure(original)) return {
|
|
97679
|
+
message: original,
|
|
97680
|
+
rewritten: false
|
|
97681
|
+
};
|
|
97682
|
+
const who = providerLabel(input.provider);
|
|
97683
|
+
return {
|
|
97684
|
+
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.`,
|
|
97685
|
+
rewritten: true
|
|
97686
|
+
};
|
|
97687
|
+
}
|
|
97688
|
+
/**
|
|
97689
|
+
* Convenience for call sites that just want the string to show.
|
|
97690
|
+
*
|
|
97691
|
+
* Exists so adapters do not each re-derive the surface and re-handle the
|
|
97692
|
+
* undefined case inline — that inlining is what pushed
|
|
97693
|
+
* ClaudeAdapter.handleResultMessage over the cyclomatic gate. Returns
|
|
97694
|
+
* `undefined` unchanged so "no error" stays distinguishable from "empty error".
|
|
97695
|
+
*/
|
|
97696
|
+
function providerAuthFailureMessage(message, provider, env = process.env) {
|
|
97697
|
+
if (message === void 0) return void 0;
|
|
97698
|
+
return rewriteProviderAuthFailure({
|
|
97699
|
+
message,
|
|
97700
|
+
provider,
|
|
97701
|
+
surface: env.FORGE_CMMD_RUNTIME_MODE === "true" ? "cloud" : "local",
|
|
97702
|
+
deliveredClaudeCredential: provider === "claudeAgent" && personalClaudeInVmEnabled(env)
|
|
97703
|
+
}).message;
|
|
97704
|
+
}
|
|
97705
|
+
//#endregion
|
|
97706
|
+
//#region src/orchestration/turnFailureReason.ts
|
|
97707
|
+
/**
|
|
97708
|
+
* Machine-readable reasons for a turn that failed before it produced any
|
|
97709
|
+
* assistant output, replicated to CMMD on the `turn_status` event
|
|
97710
|
+
* (CMMD-Center/forge#3496, CMMD-Center/CMMD#5272). Codes match the catalog
|
|
97711
|
+
* CMMD renders actions from (CMMD-Center/CMMD#5289): a code CMMD does not
|
|
97712
|
+
* recognize degrades to its own generic fallback, so a new Forge failure
|
|
97713
|
+
* class is safe to ship before CMMD's catalog knows about it.
|
|
97714
|
+
*/
|
|
97715
|
+
const GITHUB_GRANT_DETAIL_PATTERN = /exact scoped runtime GitHub grant/i;
|
|
97716
|
+
/**
|
|
97717
|
+
* The exact-machine broker's own wording (`providerEgressToken.ts`) for all
|
|
97718
|
+
* three reasons it could not produce a provider token: an incomplete runtime
|
|
97719
|
+
* identity, no owner session bound to the request, or no live broker
|
|
97720
|
+
* authority. Every one of those is a real authentication refusal, not a
|
|
97721
|
+
* workspace or adapter failure, and the prefix is unique to that code path.
|
|
97722
|
+
*/
|
|
97723
|
+
const EXACT_MACHINE_BROKER_AUTHORITY_PATTERN = /provider token issuance requires a live exact-machine broker authority/i;
|
|
97724
|
+
/**
|
|
97725
|
+
* Classifies a `provider.turn.start.failed` activity's detail. This activity
|
|
97726
|
+
* kind also covers non-auth causes (a draining runtime, a missing user
|
|
97727
|
+
* message, a rejected workspace, a start-timeout cleanup failure), so only
|
|
97728
|
+
* text that names a known authentication refusal is reported as one; every
|
|
97729
|
+
* other detail degrades to `unspecified_failure` rather than telling CMMD to
|
|
97730
|
+
* offer a reconnect action credentials cannot fix.
|
|
97731
|
+
*/
|
|
97732
|
+
function classifyProviderTurnStartFailureReason(detail) {
|
|
97733
|
+
const message = detail.trim().length > 0 ? detail : "The provider could not start a session for this run.";
|
|
97734
|
+
return {
|
|
97735
|
+
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",
|
|
97736
|
+
message
|
|
97737
|
+
};
|
|
97738
|
+
}
|
|
97739
|
+
function checkpointTimeoutReason(detail) {
|
|
97740
|
+
return {
|
|
97741
|
+
code: "checkpoint_timeout",
|
|
97742
|
+
message: detail.trim().length > 0 ? detail : "The workspace checkpoint timed out before the turn could start."
|
|
97743
|
+
};
|
|
97744
|
+
}
|
|
97745
|
+
//#endregion
|
|
97461
97746
|
//#region src/runtimeReplication/replicaSerializer.ts
|
|
97462
97747
|
const replicaTurnStatuses = new Set([
|
|
97463
97748
|
"pending",
|
|
@@ -97798,6 +98083,53 @@ function serializeFailureActivity(activity) {
|
|
|
97798
98083
|
}
|
|
97799
98084
|
};
|
|
97800
98085
|
}
|
|
98086
|
+
/** Only the pre-turn baseline capture sets this; a post-turn capture failure
|
|
98087
|
+
* never blocked the turn from starting and must not fail it retroactively. */
|
|
98088
|
+
function blocksTurnStart(activity) {
|
|
98089
|
+
const payload = activity.payload;
|
|
98090
|
+
if (!payload || typeof payload !== "object") return false;
|
|
98091
|
+
return payload.blocksTurnStart === true;
|
|
98092
|
+
}
|
|
98093
|
+
function isCheckpointTimeoutDetail(detail) {
|
|
98094
|
+
return /timed out/i.test(detail);
|
|
98095
|
+
}
|
|
98096
|
+
/**
|
|
98097
|
+
* The structured reason a turn never started, when this activity is one of
|
|
98098
|
+
* the two known causes (CMMD-Center/forge#3496): the provider refused to
|
|
98099
|
+
* start a session, or the pre-turn checkpoint that must precede it timed out.
|
|
98100
|
+
* `null` for every other activity, including a checkpoint failure that did
|
|
98101
|
+
* not block turn start or was not a timeout.
|
|
98102
|
+
*/
|
|
98103
|
+
function turnStartFailureReason(activity) {
|
|
98104
|
+
const detail = failureDetail(activity);
|
|
98105
|
+
if (activity.kind === "provider.turn.start.failed") return classifyProviderTurnStartFailureReason(detail ?? "");
|
|
98106
|
+
if (activity.kind === "checkpoint.capture.failed" && blocksTurnStart(activity) && detail !== null && isCheckpointTimeoutDetail(detail)) return checkpointTimeoutReason(detail);
|
|
98107
|
+
return null;
|
|
98108
|
+
}
|
|
98109
|
+
/**
|
|
98110
|
+
* Replicates a turn-start failure as `turn_status: failed` with a structured
|
|
98111
|
+
* `reason`, alongside (not instead of) the free-text `error_summary` above:
|
|
98112
|
+
* CMMD fails the owning Work Session from this event
|
|
98113
|
+
* (CMMD-Center/CMMD#5272/#5286), which `error_summary` alone cannot drive.
|
|
98114
|
+
*/
|
|
98115
|
+
function serializeTurnStartFailureStatus(activity) {
|
|
98116
|
+
const reason = turnStartFailureReason(activity);
|
|
98117
|
+
if (reason === null) return null;
|
|
98118
|
+
return {
|
|
98119
|
+
sourceSequence: null,
|
|
98120
|
+
event: {
|
|
98121
|
+
eventId: `activity:${activity.id}:turn-status`,
|
|
98122
|
+
occurredAt: canonicalRuntimeEventTimestamp(activity.createdAt),
|
|
98123
|
+
...eventTurn(activity.turnId),
|
|
98124
|
+
kind: "turn_status",
|
|
98125
|
+
status: "failed",
|
|
98126
|
+
reason: {
|
|
98127
|
+
code: reason.code,
|
|
98128
|
+
message: text(reason.message, 2e3) ?? reason.message
|
|
98129
|
+
}
|
|
98130
|
+
}
|
|
98131
|
+
};
|
|
98132
|
+
}
|
|
97801
98133
|
const turnUsageKeys = [
|
|
97802
98134
|
"uncachedInputTokens",
|
|
97803
98135
|
"cachedInputTokens",
|
|
@@ -98146,7 +98478,7 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
98146
98478
|
});
|
|
98147
98479
|
const recordedTurns = serializeRecordedTurns(thread);
|
|
98148
98480
|
const textGenerations = serializeTextGenerations(thread);
|
|
98149
|
-
|
|
98481
|
+
const priorEvents = [
|
|
98150
98482
|
...messages,
|
|
98151
98483
|
...replicaActivities,
|
|
98152
98484
|
...toolActivities,
|
|
@@ -98155,7 +98487,15 @@ function serializeRuntimeThreadEvents(thread) {
|
|
|
98155
98487
|
...failureActivities,
|
|
98156
98488
|
...recordedTurns,
|
|
98157
98489
|
...textGenerations
|
|
98158
|
-
]
|
|
98490
|
+
];
|
|
98491
|
+
const latestPriorOccurredAt = priorEvents.reduce((latest, entry) => latest === null || entry.event.occurredAt > latest ? entry.event.occurredAt : latest, null);
|
|
98492
|
+
const turnStartFailureStatuses = thread.activities.flatMap((activity) => {
|
|
98493
|
+
const event = serializeTurnStartFailureStatus(activity);
|
|
98494
|
+
if (event === null) return [];
|
|
98495
|
+
if (latestPriorOccurredAt !== null && event.event.occurredAt < latestPriorOccurredAt) return [];
|
|
98496
|
+
return [event];
|
|
98497
|
+
});
|
|
98498
|
+
return [...priorEvents, ...turnStartFailureStatuses].toSorted((left, right) => {
|
|
98159
98499
|
if (left.sourceSequence !== null && right.sourceSequence !== null) return left.sourceSequence - right.sourceSequence;
|
|
98160
98500
|
if (left.sourceSequence !== null) return -1;
|
|
98161
98501
|
if (right.sourceSequence !== null) return 1;
|
|
@@ -152071,7 +152411,7 @@ async function prepareWorkspaceRepositoryDependencies(input) {
|
|
|
152071
152411
|
const dependencies = resolveWorkspaceEnvironmentConfig(input.cwd).repositoryDependencies;
|
|
152072
152412
|
if (dependencies.length === 0) return [];
|
|
152073
152413
|
const broker = input.broker ?? new GitHubBroker();
|
|
152074
|
-
const auth = broker.runtimeGrantAuthContext("github");
|
|
152414
|
+
const auth = broker.runtimeGrantAuthContext("github") ?? await broker.requestRuntimeGithubGrant?.() ?? null;
|
|
152075
152415
|
if (auth === null) throw new Error("Repository dependencies require an exact scoped runtime GitHub grant.");
|
|
152076
152416
|
const readOrigin = input.readOrigin ?? readOriginFromGit;
|
|
152077
152417
|
const resolved = resolveRepositoryDependencies(dependencies, input.cloudProjectsRoot);
|
|
@@ -213077,73 +213417,6 @@ function primaryClaudeCredentialMatchesPrepared(env, preparedDirectory) {
|
|
|
213077
213417
|
}
|
|
213078
213418
|
}
|
|
213079
213419
|
//#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
213420
|
//#region src/provider/claudeCredentialContainment.ts
|
|
213148
213421
|
/**
|
|
213149
213422
|
* Containment for provider credentials inherited by the Claude Agent SDK child.
|
|
@@ -215224,128 +215497,6 @@ function createGitHubMcpServer(runBrokeredTool, threadId, settings) {
|
|
|
215224
215497
|
});
|
|
215225
215498
|
}
|
|
215226
215499
|
//#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
215500
|
//#region src/provider/missingWorkspaceCopy.ts
|
|
215350
215501
|
/**
|
|
215351
215502
|
* Refuse a turn whose checkout folder is gone, and say so.
|
|
@@ -233674,6 +233825,8 @@ const CheckpointDiffQueryLive = effect(CheckpointDiffQuery, gen(function* () {
|
|
|
233674
233825
|
* @module CheckpointStoreLive
|
|
233675
233826
|
*/
|
|
233676
233827
|
const CHECKPOINT_PATH_OUTPUT_MAX_BYTES = 16 * 1024 * 1024;
|
|
233828
|
+
const CheckpointCaptureTimeoutConfig = number$2("FORGE_CHECKPOINT_CAPTURE_TIMEOUT_MS").pipe(withDefault$3(6e4));
|
|
233829
|
+
const EFFECT_TIMER_MAX_MS = 2147483647;
|
|
233677
233830
|
function parseNullSeparatedPaths(output) {
|
|
233678
233831
|
return output.split("\0").filter((entry) => entry.length > 0);
|
|
233679
233832
|
}
|
|
@@ -233681,7 +233834,7 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233681
233834
|
const fs = yield* FileSystem;
|
|
233682
233835
|
const path = yield* Path$2;
|
|
233683
233836
|
const git = yield* GitCore;
|
|
233684
|
-
const resolveHeadCommit = (cwd) => git.execute({
|
|
233837
|
+
const resolveHeadCommit = (cwd, timeoutMs) => git.execute({
|
|
233685
233838
|
operation: "CheckpointStore.resolveHeadCommit",
|
|
233686
233839
|
cwd,
|
|
233687
233840
|
args: [
|
|
@@ -233690,7 +233843,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233690
233843
|
"--quiet",
|
|
233691
233844
|
"HEAD^{commit}"
|
|
233692
233845
|
],
|
|
233693
|
-
allowNonZeroExit: true
|
|
233846
|
+
allowNonZeroExit: true,
|
|
233847
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {}
|
|
233694
233848
|
}).pipe(map$3((result) => {
|
|
233695
233849
|
if (result.code !== 0) return null;
|
|
233696
233850
|
const commit = result.stdout.trim();
|
|
@@ -233729,7 +233883,9 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233729
233883
|
}).pipe(map$3((result) => result.code === 0 && result.stdout.trim() === "true"), catch_(() => succeed(false)));
|
|
233730
233884
|
const captureCheckpoint = fn$1("captureCheckpoint")(function* (input) {
|
|
233731
233885
|
const operation = "CheckpointStore.captureCheckpoint";
|
|
233732
|
-
const
|
|
233886
|
+
const captureTimeoutMs = yield* CheckpointCaptureTimeoutConfig.asEffect().pipe(orDie);
|
|
233887
|
+
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}.`));
|
|
233888
|
+
const headCommit = yield* resolveHeadCommit(input.cwd, captureTimeoutMs);
|
|
233733
233889
|
const [trackedPathResult, untrackedPathResult] = yield* all([git.execute({
|
|
233734
233890
|
operation,
|
|
233735
233891
|
cwd: input.cwd,
|
|
@@ -233746,7 +233902,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233746
233902
|
"-z",
|
|
233747
233903
|
"--"
|
|
233748
233904
|
],
|
|
233749
|
-
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES
|
|
233905
|
+
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES,
|
|
233906
|
+
timeoutMs: captureTimeoutMs
|
|
233750
233907
|
}), git.execute({
|
|
233751
233908
|
operation,
|
|
233752
233909
|
cwd: input.cwd,
|
|
@@ -233757,7 +233914,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233757
233914
|
"-z",
|
|
233758
233915
|
"--"
|
|
233759
233916
|
],
|
|
233760
|
-
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES
|
|
233917
|
+
maxOutputBytes: CHECKPOINT_PATH_OUTPUT_MAX_BYTES,
|
|
233918
|
+
timeoutMs: captureTimeoutMs
|
|
233761
233919
|
})], { concurrency: 2 });
|
|
233762
233920
|
const changedPaths = [...new Set([...parseNullSeparatedPaths(trackedPathResult.stdout), ...parseNullSeparatedPaths(untrackedPathResult.stdout)])];
|
|
233763
233921
|
if (headCommit && changedPaths.length === 0) {
|
|
@@ -233768,7 +233926,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233768
233926
|
"update-ref",
|
|
233769
233927
|
input.checkpointRef,
|
|
233770
233928
|
headCommit
|
|
233771
|
-
]
|
|
233929
|
+
],
|
|
233930
|
+
timeoutMs: captureTimeoutMs
|
|
233772
233931
|
});
|
|
233773
233932
|
return;
|
|
233774
233933
|
}
|
|
@@ -233786,13 +233945,15 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233786
233945
|
operation,
|
|
233787
233946
|
cwd: input.cwd,
|
|
233788
233947
|
args: ["read-tree", headCommit],
|
|
233789
|
-
env: commitEnv
|
|
233948
|
+
env: commitEnv,
|
|
233949
|
+
timeoutMs: captureTimeoutMs
|
|
233790
233950
|
});
|
|
233791
233951
|
else yield* git.execute({
|
|
233792
233952
|
operation,
|
|
233793
233953
|
cwd: input.cwd,
|
|
233794
233954
|
args: ["read-tree", "--empty"],
|
|
233795
|
-
env: commitEnv
|
|
233955
|
+
env: commitEnv,
|
|
233956
|
+
timeoutMs: captureTimeoutMs
|
|
233796
233957
|
});
|
|
233797
233958
|
if (changedPaths.length > 0) yield* git.execute({
|
|
233798
233959
|
operation,
|
|
@@ -233805,13 +233966,15 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233805
233966
|
"--pathspec-file-nul"
|
|
233806
233967
|
],
|
|
233807
233968
|
stdin: changedPaths.map((changedPath) => `${changedPath}\0`).join(""),
|
|
233808
|
-
env: commitEnv
|
|
233969
|
+
env: commitEnv,
|
|
233970
|
+
timeoutMs: captureTimeoutMs
|
|
233809
233971
|
});
|
|
233810
233972
|
const treeOid = (yield* git.execute({
|
|
233811
233973
|
operation,
|
|
233812
233974
|
cwd: input.cwd,
|
|
233813
233975
|
args: ["write-tree"],
|
|
233814
|
-
env: commitEnv
|
|
233976
|
+
env: commitEnv,
|
|
233977
|
+
timeoutMs: captureTimeoutMs
|
|
233815
233978
|
})).stdout.trim();
|
|
233816
233979
|
if (treeOid.length === 0) return yield* new GitCommandError({
|
|
233817
233980
|
operation,
|
|
@@ -233829,7 +233992,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233829
233992
|
"-m",
|
|
233830
233993
|
message
|
|
233831
233994
|
],
|
|
233832
|
-
env: commitEnv
|
|
233995
|
+
env: commitEnv,
|
|
233996
|
+
timeoutMs: captureTimeoutMs
|
|
233833
233997
|
})).stdout.trim();
|
|
233834
233998
|
if (commitOid.length === 0) return yield* new GitCommandError({
|
|
233835
233999
|
operation,
|
|
@@ -233844,7 +234008,8 @@ const CheckpointStoreLive = effect(CheckpointStore, gen(function* () {
|
|
|
233844
234008
|
"update-ref",
|
|
233845
234009
|
input.checkpointRef,
|
|
233846
234010
|
commitOid
|
|
233847
|
-
]
|
|
234011
|
+
],
|
|
234012
|
+
timeoutMs: captureTimeoutMs
|
|
233848
234013
|
});
|
|
233849
234014
|
}), (tempDir) => fs.remove(tempDir, { recursive: true })).pipe(catchTags({ PlatformError: (error) => fail(new CheckpointInvariantError({
|
|
233850
234015
|
operation: "CheckpointStore.captureCheckpoint",
|
|
@@ -291680,7 +291845,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
|
|
|
291680
291845
|
summary: "Agent turn blocked",
|
|
291681
291846
|
detail: "Forge runtime is updating after the current turn. New agent turns are paused until the runtime finishes draining.",
|
|
291682
291847
|
turnId: null,
|
|
291683
|
-
createdAt:
|
|
291848
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
291684
291849
|
});
|
|
291685
291850
|
return;
|
|
291686
291851
|
}
|
|
@@ -291914,7 +292079,7 @@ const ProviderCommandReactorLive = effect(ProviderCommandReactor, gen(function*
|
|
|
291914
292079
|
summary: "Agent turn blocked",
|
|
291915
292080
|
detail: workspaceError,
|
|
291916
292081
|
turnId: null,
|
|
291917
|
-
createdAt:
|
|
292082
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
291918
292083
|
});
|
|
291919
292084
|
return;
|
|
291920
292085
|
}
|
|
@@ -293005,7 +293170,10 @@ const CheckpointReactorLive = effect(CheckpointReactor, gen(function* () {
|
|
|
293005
293170
|
tone: "error",
|
|
293006
293171
|
kind: "checkpoint.capture.failed",
|
|
293007
293172
|
summary: "Checkpoint capture failed",
|
|
293008
|
-
payload: {
|
|
293173
|
+
payload: {
|
|
293174
|
+
detail: input.detail,
|
|
293175
|
+
...input.blocksTurnStart ? { blocksTurnStart: true } : {}
|
|
293176
|
+
},
|
|
293009
293177
|
turnId: input.turnId,
|
|
293010
293178
|
createdAt: input.createdAt
|
|
293011
293179
|
},
|
|
@@ -293291,7 +293459,13 @@ const CheckpointReactorLive = effect(CheckpointReactor, gen(function* () {
|
|
|
293291
293459
|
yield* checkpointStore.captureCheckpoint({
|
|
293292
293460
|
cwd: checkpointCwd,
|
|
293293
293461
|
checkpointRef: baselineCheckpointRef
|
|
293294
|
-
})
|
|
293462
|
+
}).pipe(catch_((error) => appendCaptureFailureActivity({
|
|
293463
|
+
threadId,
|
|
293464
|
+
turnId: null,
|
|
293465
|
+
detail: error.message,
|
|
293466
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
293467
|
+
blocksTurnStart: true
|
|
293468
|
+
}).pipe(catch_(() => void_$1))));
|
|
293295
293469
|
yield* receiptBus.publish({
|
|
293296
293470
|
type: "checkpoint.baseline.captured",
|
|
293297
293471
|
threadId,
|
|
@@ -297651,7 +297825,7 @@ function resolveBuildCommitFromEnv(env) {
|
|
|
297651
297825
|
* environment descriptor down instead of reporting an honest "unknown".
|
|
297652
297826
|
*/
|
|
297653
297827
|
function readBakedBuildCommit() {
|
|
297654
|
-
return "
|
|
297828
|
+
return "06c11aad2e4673f13061c7bed6d0a4cb63ff8122";
|
|
297655
297829
|
}
|
|
297656
297830
|
async function resolveServerBuildCommit(input) {
|
|
297657
297831
|
if (isFullCommitSha(input.baked)) return input.baked;
|