@runfusion/fusion 0.17.0 → 0.17.2
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.js +1729 -789
- package/dist/client/assets/{AgentDetailView-DGqT1oDt.js → AgentDetailView-17J-F0Rl.js} +3 -3
- package/dist/client/assets/{AgentsView-BmemrfrO.js → AgentsView-sbBkb7Wd.js} +45 -45
- package/dist/client/assets/ChatView-BR5cvK_B.js +1 -0
- package/dist/client/assets/{DevServerView-C3Q0XqDA.js → DevServerView-GFFVXHVP.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BZWVA9ND.js → DirectoryPicker-WPDSBdT6.js} +1 -1
- package/dist/client/assets/{DocumentsView-DO48ivSq.js → DocumentsView-BHpDsIIt.js} +1 -1
- package/dist/client/assets/{InsightsView-CAngTfMf.js → InsightsView-Bxu0TJkt.js} +1 -1
- package/dist/client/assets/{MemoryView-B3rNcAOW.js → MemoryView-CmnzZorw.js} +2 -2
- package/dist/client/assets/{NodesView-BnV1LWa8.js → NodesView-CO9_4hCr.js} +4 -4
- package/dist/client/assets/{PiExtensionsManager-C3_Lw4sa.js → PiExtensionsManager-4e3MlD62.js} +3 -3
- package/dist/client/assets/{PluginManager-Vv3nzrJ1.js → PluginManager-DGN2rvOY.js} +1 -1
- package/dist/client/assets/ResearchView-Dsa6Gykl.js +1 -0
- package/dist/client/assets/{RoadmapsView-BiIpE-b8.js → RoadmapsView-jHTOK0RQ.js} +2 -2
- package/dist/client/assets/{SettingsModal-CK4w8Ztb.js → SettingsModal-4Z8ZJMzD.js} +1 -1
- package/dist/client/assets/SettingsModal-D0kuJpBA.js +31 -0
- package/dist/client/assets/{SetupWizardModal-Dw6N4UvY.js → SetupWizardModal-Bhumd4Rf.js} +1 -1
- package/dist/client/assets/{SkillsView-C1196wgA.js → SkillsView-MHweJTz4.js} +1 -1
- package/dist/client/assets/{folder-open-WVtgE4k3.js → folder-open-BNQW9dE9.js} +1 -1
- package/dist/client/assets/{index-BIJgrHEn.css → index-DEVBHvyW.css} +1 -1
- package/dist/client/assets/index-k_85J1DS.js +682 -0
- package/dist/client/assets/{star-MSImEC8V.js → star-7L86NZrT.js} +1 -1
- package/dist/client/assets/{upload-Dmvy3xXd.js → upload-DsAS6tno.js} +1 -1
- package/dist/client/assets/{users-CncYvHNf.js → users-D3u6f2Rz.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +1239 -527
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/package.json +1 -1
- package/dist/client/assets/ChatView-CZQUBFlV.js +0 -1
- package/dist/client/assets/ResearchView-Dfdsuc21.js +0 -1
- package/dist/client/assets/SettingsModal-BN00HYJ2.js +0 -31
- package/dist/client/assets/index-Bv0TGzDH.js +0 -682
package/dist/extension.js
CHANGED
|
@@ -1389,7 +1389,14 @@ Note: Refs (@e1, @e2) are invalidated after page navigation. Re-snapshot after c
|
|
|
1389
1389
|
toolMode: "readonly",
|
|
1390
1390
|
prompt: `You are a UX design reviewer. Verify frontend changes maintain visual polish and consistency with existing UI patterns and design tokens.
|
|
1391
1391
|
|
|
1392
|
-
|
|
1392
|
+
FAST-BAIL RULE (check this FIRST):
|
|
1393
|
+
- The task harness gives you a "Diff Scope" listing the files this task actually changed.
|
|
1394
|
+
- If that list contains NO frontend/UI files (no .tsx/.jsx/.ts/.js component files, no .css/.scss/.sass/.styl, no .html/.vue/.svelte/.astro, no design-token/theme files), respond IMMEDIATELY with a single short line such as "No UI changes in scope \u2014 approved." and STOP.
|
|
1395
|
+
- Do NOT explore the worktree looking for related-looking UI code to critique. If this task didn't change a UI file, your review is a no-op by definition.
|
|
1396
|
+
|
|
1397
|
+
Otherwise, restrict your review to the UI files actually present in the diff scope.
|
|
1398
|
+
|
|
1399
|
+
Design System Review (only for UI files in the diff scope):
|
|
1393
1400
|
1. **Visual Hierarchy** \u2014 Check that the changes maintain consistent heading levels, content flow, and information architecture
|
|
1394
1401
|
2. **Spacing and Typography** \u2014 Verify consistent spacing (margins, padding, gaps) and typography scale usage
|
|
1395
1402
|
3. **Color and Token Consistency** \u2014 Check that CSS custom properties and design tokens are used correctly; no hardcoded color values that bypass the design system
|
|
@@ -1397,15 +1404,16 @@ Design System Review:
|
|
|
1397
1404
|
5. **Responsive Behavior** \u2014 Check that layouts adapt properly across viewport sizes and maintain usability on mobile
|
|
1398
1405
|
6. **Fit with Design Language** \u2014 Verify the visual style matches existing patterns (border radius, shadows, transitions, icon style, etc.)
|
|
1399
1406
|
|
|
1400
|
-
Files to Review:
|
|
1407
|
+
Files to Review (only those that appear in the Diff Scope):
|
|
1401
1408
|
- Modified UI components (React, Vue, Angular, HTML)
|
|
1402
1409
|
- CSS/SCSS/styled-component files
|
|
1403
1410
|
- Design token or theme configuration files
|
|
1404
1411
|
|
|
1405
1412
|
Output Requirements:
|
|
1406
|
-
- If design is consistent and polished
|
|
1407
|
-
- If issues found: describe each finding with specific file paths and suggested corrections
|
|
1408
|
-
- Prioritize issues by impact: layout breaks > visual inconsistency > style preferences
|
|
1413
|
+
- If design is consistent and polished (or there are no UI files in scope): respond with a brief approval line and stop.
|
|
1414
|
+
- If issues found: start your response with "REQUEST REVISION" and describe each finding with specific file paths and suggested corrections.
|
|
1415
|
+
- Prioritize issues by impact: layout breaks > visual inconsistency > style preferences.
|
|
1416
|
+
- Do NOT spend time on stylistic nits when no real issues exist.`
|
|
1409
1417
|
}
|
|
1410
1418
|
];
|
|
1411
1419
|
DOCUMENT_KEY_RE = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
@@ -29610,8 +29618,8 @@ var require_CronFileParser = __commonJS({
|
|
|
29610
29618
|
* @throws If file cannot be read
|
|
29611
29619
|
*/
|
|
29612
29620
|
static parseFileSync(filePath) {
|
|
29613
|
-
const { readFileSync:
|
|
29614
|
-
const data =
|
|
29621
|
+
const { readFileSync: readFileSync12 } = __require("fs");
|
|
29622
|
+
const data = readFileSync12(filePath, "utf8");
|
|
29615
29623
|
return _CronFileParser.#parseContent(data);
|
|
29616
29624
|
}
|
|
29617
29625
|
/**
|
|
@@ -52657,6 +52665,193 @@ var init_chat_store = __esm({
|
|
|
52657
52665
|
}
|
|
52658
52666
|
});
|
|
52659
52667
|
|
|
52668
|
+
// ../core/src/oauth-credential-interop.ts
|
|
52669
|
+
import { existsSync as existsSync19, readFileSync as readFileSync6 } from "node:fs";
|
|
52670
|
+
import { homedir as homedir4 } from "node:os";
|
|
52671
|
+
import { join as join22 } from "node:path";
|
|
52672
|
+
function getHomeDir4() {
|
|
52673
|
+
return process.env.HOME || process.env.USERPROFILE || homedir4();
|
|
52674
|
+
}
|
|
52675
|
+
function getCodexCliAuthPath(home = getHomeDir4()) {
|
|
52676
|
+
return join22(home, ".codex", "auth.json");
|
|
52677
|
+
}
|
|
52678
|
+
function parseJwtPayload(token) {
|
|
52679
|
+
try {
|
|
52680
|
+
const [, payload = ""] = token.split(".", 3);
|
|
52681
|
+
if (!payload) {
|
|
52682
|
+
return null;
|
|
52683
|
+
}
|
|
52684
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
|
|
52685
|
+
} catch {
|
|
52686
|
+
return null;
|
|
52687
|
+
}
|
|
52688
|
+
}
|
|
52689
|
+
function getJwtExpiryMs(token) {
|
|
52690
|
+
if (!token) {
|
|
52691
|
+
return void 0;
|
|
52692
|
+
}
|
|
52693
|
+
const payload = parseJwtPayload(token);
|
|
52694
|
+
const exp = payload?.exp;
|
|
52695
|
+
if (typeof exp !== "number" || !Number.isFinite(exp)) {
|
|
52696
|
+
return void 0;
|
|
52697
|
+
}
|
|
52698
|
+
return exp * 1e3;
|
|
52699
|
+
}
|
|
52700
|
+
function getCodexAccountId(accessToken, fallbackAccountId) {
|
|
52701
|
+
const payload = parseJwtPayload(accessToken);
|
|
52702
|
+
const authClaim = payload?.[OPENAI_AUTH_CLAIM];
|
|
52703
|
+
const claimAccountId = authClaim && typeof authClaim === "object" ? authClaim.chatgpt_account_id : void 0;
|
|
52704
|
+
if (typeof claimAccountId === "string" && claimAccountId.trim().length > 0) {
|
|
52705
|
+
return claimAccountId;
|
|
52706
|
+
}
|
|
52707
|
+
if (typeof fallbackAccountId === "string" && fallbackAccountId.trim().length > 0) {
|
|
52708
|
+
return fallbackAccountId;
|
|
52709
|
+
}
|
|
52710
|
+
return void 0;
|
|
52711
|
+
}
|
|
52712
|
+
function getLastRefreshFallbackExpiryMs(lastRefresh) {
|
|
52713
|
+
if (typeof lastRefresh !== "string" || lastRefresh.trim().length === 0) {
|
|
52714
|
+
return void 0;
|
|
52715
|
+
}
|
|
52716
|
+
const parsed = Date.parse(lastRefresh);
|
|
52717
|
+
if (!Number.isFinite(parsed)) {
|
|
52718
|
+
return void 0;
|
|
52719
|
+
}
|
|
52720
|
+
return parsed + CODEX_REFRESH_FALLBACK_WINDOW_MS;
|
|
52721
|
+
}
|
|
52722
|
+
function isStoredAuthCredential(value) {
|
|
52723
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
52724
|
+
return false;
|
|
52725
|
+
}
|
|
52726
|
+
const record = value;
|
|
52727
|
+
return record.type === "oauth" || record.type === "api_key";
|
|
52728
|
+
}
|
|
52729
|
+
function isValidOauthCredential(credential) {
|
|
52730
|
+
return credential?.type === "oauth" && typeof credential.access === "string" && credential.access.length > 0 && typeof credential.refresh === "string" && credential.refresh.length > 0 && typeof credential.expires === "number" && Number.isFinite(credential.expires) && Date.now() < credential.expires;
|
|
52731
|
+
}
|
|
52732
|
+
function isRefreshableOauthCredential(credential) {
|
|
52733
|
+
return credential?.type === "oauth" && typeof credential.refresh === "string" && credential.refresh.length > 0 && typeof credential.expires === "number" && Number.isFinite(credential.expires);
|
|
52734
|
+
}
|
|
52735
|
+
function compareStoredCredentials(left, right) {
|
|
52736
|
+
if (!left && !right) {
|
|
52737
|
+
return 0;
|
|
52738
|
+
}
|
|
52739
|
+
if (left && !right) {
|
|
52740
|
+
return 1;
|
|
52741
|
+
}
|
|
52742
|
+
if (!left && right) {
|
|
52743
|
+
return -1;
|
|
52744
|
+
}
|
|
52745
|
+
if (left?.type === "api_key" && right?.type !== "api_key") {
|
|
52746
|
+
return 1;
|
|
52747
|
+
}
|
|
52748
|
+
if (right?.type === "api_key" && left?.type !== "api_key") {
|
|
52749
|
+
return -1;
|
|
52750
|
+
}
|
|
52751
|
+
if (left?.type === "oauth" && right?.type === "oauth") {
|
|
52752
|
+
const leftValid = isValidOauthCredential(left);
|
|
52753
|
+
const rightValid = isValidOauthCredential(right);
|
|
52754
|
+
if (leftValid !== rightValid) {
|
|
52755
|
+
return leftValid ? 1 : -1;
|
|
52756
|
+
}
|
|
52757
|
+
const leftRefreshable = isRefreshableOauthCredential(left);
|
|
52758
|
+
const rightRefreshable = isRefreshableOauthCredential(right);
|
|
52759
|
+
if (leftRefreshable !== rightRefreshable) {
|
|
52760
|
+
return leftRefreshable ? 1 : -1;
|
|
52761
|
+
}
|
|
52762
|
+
const leftExpiry = typeof left.expires === "number" && Number.isFinite(left.expires) ? left.expires : -Infinity;
|
|
52763
|
+
const rightExpiry = typeof right.expires === "number" && Number.isFinite(right.expires) ? right.expires : -Infinity;
|
|
52764
|
+
if (leftExpiry !== rightExpiry) {
|
|
52765
|
+
return leftExpiry > rightExpiry ? 1 : -1;
|
|
52766
|
+
}
|
|
52767
|
+
const leftAccessLength = typeof left.access === "string" ? left.access.length : 0;
|
|
52768
|
+
const rightAccessLength = typeof right.access === "string" ? right.access.length : 0;
|
|
52769
|
+
if (leftAccessLength !== rightAccessLength) {
|
|
52770
|
+
return leftAccessLength > rightAccessLength ? 1 : -1;
|
|
52771
|
+
}
|
|
52772
|
+
}
|
|
52773
|
+
return 0;
|
|
52774
|
+
}
|
|
52775
|
+
function choosePreferredStoredCredential(...credentials) {
|
|
52776
|
+
let best;
|
|
52777
|
+
for (const credential of credentials) {
|
|
52778
|
+
if (compareStoredCredentials(credential, best) > 0) {
|
|
52779
|
+
best = credential;
|
|
52780
|
+
}
|
|
52781
|
+
}
|
|
52782
|
+
return best;
|
|
52783
|
+
}
|
|
52784
|
+
function shouldHydrateStoredCredential(current, candidate) {
|
|
52785
|
+
if (!candidate || candidate.type !== "oauth") {
|
|
52786
|
+
return false;
|
|
52787
|
+
}
|
|
52788
|
+
if (current?.type === "api_key") {
|
|
52789
|
+
return false;
|
|
52790
|
+
}
|
|
52791
|
+
return compareStoredCredentials(candidate, current) > 0;
|
|
52792
|
+
}
|
|
52793
|
+
function extractCodexCliStoredCredential(raw) {
|
|
52794
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
52795
|
+
return void 0;
|
|
52796
|
+
}
|
|
52797
|
+
const record = raw;
|
|
52798
|
+
const tokens = record.tokens;
|
|
52799
|
+
if (!tokens || typeof tokens !== "object" || Array.isArray(tokens)) {
|
|
52800
|
+
return void 0;
|
|
52801
|
+
}
|
|
52802
|
+
const tokenRecord = tokens;
|
|
52803
|
+
const access7 = typeof tokenRecord.access_token === "string" ? tokenRecord.access_token : void 0;
|
|
52804
|
+
const refresh = typeof tokenRecord.refresh_token === "string" ? tokenRecord.refresh_token : void 0;
|
|
52805
|
+
if (!access7 || !refresh) {
|
|
52806
|
+
return void 0;
|
|
52807
|
+
}
|
|
52808
|
+
const expires = getJwtExpiryMs(access7) ?? getJwtExpiryMs(typeof tokenRecord.id_token === "string" ? tokenRecord.id_token : void 0) ?? getLastRefreshFallbackExpiryMs(record.last_refresh);
|
|
52809
|
+
if (typeof expires !== "number" || !Number.isFinite(expires)) {
|
|
52810
|
+
return void 0;
|
|
52811
|
+
}
|
|
52812
|
+
const accountId = getCodexAccountId(access7, tokenRecord.account_id);
|
|
52813
|
+
return {
|
|
52814
|
+
type: "oauth",
|
|
52815
|
+
access: access7,
|
|
52816
|
+
refresh,
|
|
52817
|
+
expires,
|
|
52818
|
+
...accountId ? { accountId } : {}
|
|
52819
|
+
};
|
|
52820
|
+
}
|
|
52821
|
+
function readStoredCredentialsFromAuthFile(authPath) {
|
|
52822
|
+
if (!existsSync19(authPath)) {
|
|
52823
|
+
return {};
|
|
52824
|
+
}
|
|
52825
|
+
try {
|
|
52826
|
+
const parsed = JSON.parse(readFileSync6(authPath, "utf-8"));
|
|
52827
|
+
const codexCliCredential = extractCodexCliStoredCredential(parsed);
|
|
52828
|
+
if (codexCliCredential) {
|
|
52829
|
+
return { "openai-codex": codexCliCredential };
|
|
52830
|
+
}
|
|
52831
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
52832
|
+
return {};
|
|
52833
|
+
}
|
|
52834
|
+
const credentials = {};
|
|
52835
|
+
for (const [providerId, value] of Object.entries(parsed)) {
|
|
52836
|
+
if (!isStoredAuthCredential(value)) {
|
|
52837
|
+
continue;
|
|
52838
|
+
}
|
|
52839
|
+
credentials[providerId] = value;
|
|
52840
|
+
}
|
|
52841
|
+
return credentials;
|
|
52842
|
+
} catch {
|
|
52843
|
+
return {};
|
|
52844
|
+
}
|
|
52845
|
+
}
|
|
52846
|
+
var OPENAI_AUTH_CLAIM, CODEX_REFRESH_FALLBACK_WINDOW_MS;
|
|
52847
|
+
var init_oauth_credential_interop = __esm({
|
|
52848
|
+
"../core/src/oauth-credential-interop.ts"() {
|
|
52849
|
+
"use strict";
|
|
52850
|
+
OPENAI_AUTH_CLAIM = "https://api.openai.com/auth";
|
|
52851
|
+
CODEX_REFRESH_FALLBACK_WINDOW_MS = 55 * 60 * 1e3;
|
|
52852
|
+
}
|
|
52853
|
+
});
|
|
52854
|
+
|
|
52660
52855
|
// ../core/src/index.ts
|
|
52661
52856
|
var src_exports = {};
|
|
52662
52857
|
__export(src_exports, {
|
|
@@ -52813,6 +53008,7 @@ __export(src_exports, {
|
|
|
52813
53008
|
buildTriageMemoryInstructions: () => buildTriageMemoryInstructions,
|
|
52814
53009
|
canTransition: () => canTransition,
|
|
52815
53010
|
checkRateLimit: () => checkRateLimit,
|
|
53011
|
+
choosePreferredStoredCredential: () => choosePreferredStoredCredential,
|
|
52816
53012
|
classifyInsightRunError: () => classifyInsightRunError,
|
|
52817
53013
|
clearOverrides: () => clearOverrides,
|
|
52818
53014
|
collectSystemMetrics: () => collectSystemMetrics,
|
|
@@ -52843,6 +53039,7 @@ __export(src_exports, {
|
|
|
52843
53039
|
executeInsightRunLifecycle: () => executeInsightRunLifecycle,
|
|
52844
53040
|
exportAgentsToDirectory: () => exportAgentsToDirectory,
|
|
52845
53041
|
exportSettings: () => exportSettings,
|
|
53042
|
+
extractCodexCliStoredCredential: () => extractCodexCliStoredCredential,
|
|
52846
53043
|
extractDreamProcessorResult: () => extractDreamProcessorResult,
|
|
52847
53044
|
formatPiExtensionSource: () => formatPiExtensionSource,
|
|
52848
53045
|
fromJson: () => fromJson,
|
|
@@ -52853,6 +53050,7 @@ __export(src_exports, {
|
|
|
52853
53050
|
generateMemoryAudit: () => generateMemoryAudit,
|
|
52854
53051
|
getAppVersion: () => getAppVersion,
|
|
52855
53052
|
getAvailableTemplates: () => getAvailableTemplates,
|
|
53053
|
+
getCodexCliAuthPath: () => getCodexCliAuthPath,
|
|
52856
53054
|
getCurrentRepo: () => getCurrentRepo,
|
|
52857
53055
|
getDefaultDailyMemoryScaffold: () => getDefaultDailyMemoryScaffold,
|
|
52858
53056
|
getDefaultDreamsScaffold: () => getDefaultDreamsScaffold,
|
|
@@ -52950,6 +53148,7 @@ __export(src_exports, {
|
|
|
52950
53148
|
readProjectMemoryFile: () => readProjectMemoryFile,
|
|
52951
53149
|
readProjectMemoryFileContent: () => readProjectMemoryFileContent,
|
|
52952
53150
|
readProjectMemoryWithBackend: () => readProjectMemoryWithBackend,
|
|
53151
|
+
readStoredCredentialsFromAuthFile: () => readStoredCredentialsFromAuthFile,
|
|
52953
53152
|
readWorkingMemory: () => readWorkingMemory,
|
|
52954
53153
|
reconcileClaudeCliPaths: () => reconcileClaudeCliPaths,
|
|
52955
53154
|
reconcileDroidCliPaths: () => reconcileDroidCliPaths,
|
|
@@ -52985,6 +53184,7 @@ __export(src_exports, {
|
|
|
52985
53184
|
scheduleQmdProjectMemoryRefresh: () => scheduleQmdProjectMemoryRefresh,
|
|
52986
53185
|
searchProjectMemory: () => searchProjectMemory,
|
|
52987
53186
|
setCreateFnAgent: () => setCreateFnAgent,
|
|
53187
|
+
shouldHydrateStoredCredential: () => shouldHydrateStoredCredential,
|
|
52988
53188
|
shouldSkipBackgroundQmdRefresh: () => shouldSkipBackgroundQmdRefresh,
|
|
52989
53189
|
shouldTriggerExtraction: () => shouldTriggerExtraction,
|
|
52990
53190
|
slugify: () => slugify,
|
|
@@ -53089,6 +53289,7 @@ var init_src = __esm({
|
|
|
53089
53289
|
init_agent_companies_parser();
|
|
53090
53290
|
init_agent_companies_exporter();
|
|
53091
53291
|
init_chat_store();
|
|
53292
|
+
init_oauth_credential_interop();
|
|
53092
53293
|
init_error_message();
|
|
53093
53294
|
}
|
|
53094
53295
|
});
|
|
@@ -54278,12 +54479,12 @@ var init_github_provider = __esm({
|
|
|
54278
54479
|
});
|
|
54279
54480
|
|
|
54280
54481
|
// ../engine/src/skill-resolver.ts
|
|
54281
|
-
import { existsSync as
|
|
54282
|
-
import { dirname as dirname8, join as
|
|
54482
|
+
import { existsSync as existsSync20, readFileSync as readFileSync7 } from "node:fs";
|
|
54483
|
+
import { dirname as dirname8, join as join23, resolve as resolve11 } from "node:path";
|
|
54283
54484
|
function resolveProjectRoot(cwd) {
|
|
54284
54485
|
let current = resolve11(cwd);
|
|
54285
54486
|
while (true) {
|
|
54286
|
-
if (
|
|
54487
|
+
if (existsSync20(join23(current, ".fusion"))) {
|
|
54287
54488
|
return current;
|
|
54288
54489
|
}
|
|
54289
54490
|
const parent = dirname8(current);
|
|
@@ -54294,19 +54495,19 @@ function resolveProjectRoot(cwd) {
|
|
|
54294
54495
|
}
|
|
54295
54496
|
}
|
|
54296
54497
|
function readJsonObject(path2) {
|
|
54297
|
-
if (!
|
|
54498
|
+
if (!existsSync20(path2)) {
|
|
54298
54499
|
return {};
|
|
54299
54500
|
}
|
|
54300
54501
|
try {
|
|
54301
|
-
const parsed = JSON.parse(
|
|
54502
|
+
const parsed = JSON.parse(readFileSync7(path2, "utf-8"));
|
|
54302
54503
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
54303
54504
|
} catch {
|
|
54304
54505
|
return {};
|
|
54305
54506
|
}
|
|
54306
54507
|
}
|
|
54307
54508
|
function readProjectSettings(projectRootDir) {
|
|
54308
|
-
const fusionSettings =
|
|
54309
|
-
if (
|
|
54509
|
+
const fusionSettings = join23(projectRootDir, ".fusion", "settings.json");
|
|
54510
|
+
if (existsSync20(fusionSettings)) {
|
|
54310
54511
|
const parsed = readJsonObject(fusionSettings);
|
|
54311
54512
|
return {
|
|
54312
54513
|
skills: Array.isArray(parsed.skills) ? parsed.skills : void 0,
|
|
@@ -54531,51 +54732,51 @@ var init_context_limit_detector = __esm({
|
|
|
54531
54732
|
});
|
|
54532
54733
|
|
|
54533
54734
|
// ../engine/src/auth-storage.ts
|
|
54534
|
-
import { existsSync as
|
|
54535
|
-
import { homedir as
|
|
54536
|
-
import { join as
|
|
54735
|
+
import { existsSync as existsSync21, readFileSync as readFileSync8 } from "node:fs";
|
|
54736
|
+
import { homedir as homedir5 } from "node:os";
|
|
54737
|
+
import { join as join24 } from "node:path";
|
|
54537
54738
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
54538
54739
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
54539
|
-
function
|
|
54540
|
-
return process.env.HOME || process.env.USERPROFILE ||
|
|
54740
|
+
function getHomeDir5() {
|
|
54741
|
+
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
54541
54742
|
}
|
|
54542
|
-
function getFusionAuthPath(home =
|
|
54543
|
-
return
|
|
54743
|
+
function getFusionAuthPath(home = getHomeDir5()) {
|
|
54744
|
+
return join24(home, ".fusion", "agent", "auth.json");
|
|
54544
54745
|
}
|
|
54545
|
-
function getFusionModelsPath(home =
|
|
54546
|
-
return
|
|
54746
|
+
function getFusionModelsPath(home = getHomeDir5()) {
|
|
54747
|
+
return join24(home, ".fusion", "agent", "models.json");
|
|
54547
54748
|
}
|
|
54548
|
-
function getLegacyAuthPaths(home =
|
|
54749
|
+
function getLegacyAuthPaths(home = getHomeDir5()) {
|
|
54549
54750
|
return [
|
|
54550
|
-
|
|
54551
|
-
|
|
54751
|
+
join24(home, ".pi", "agent", "auth.json"),
|
|
54752
|
+
join24(home, ".pi", "auth.json")
|
|
54552
54753
|
];
|
|
54553
54754
|
}
|
|
54554
|
-
function
|
|
54755
|
+
function getSupplementalAuthPaths(home = getHomeDir5()) {
|
|
54555
54756
|
return [
|
|
54556
|
-
|
|
54557
|
-
|
|
54757
|
+
...getLegacyAuthPaths(home),
|
|
54758
|
+
getCodexCliAuthPath(home)
|
|
54558
54759
|
];
|
|
54559
54760
|
}
|
|
54560
|
-
function
|
|
54761
|
+
function getLegacyModelsPaths(home = getHomeDir5()) {
|
|
54762
|
+
return [
|
|
54763
|
+
join24(home, ".pi", "agent", "models.json"),
|
|
54764
|
+
join24(home, ".pi", "models.json")
|
|
54765
|
+
];
|
|
54766
|
+
}
|
|
54767
|
+
function getModelRegistryModelsPath(home = getHomeDir5()) {
|
|
54561
54768
|
const fusionModelsPath = getFusionModelsPath(home);
|
|
54562
|
-
if (
|
|
54769
|
+
if (existsSync21(fusionModelsPath)) {
|
|
54563
54770
|
return fusionModelsPath;
|
|
54564
54771
|
}
|
|
54565
|
-
return getLegacyModelsPaths(home).find((modelsPath) =>
|
|
54772
|
+
return getLegacyModelsPaths(home).find((modelsPath) => existsSync21(modelsPath)) ?? fusionModelsPath;
|
|
54566
54773
|
}
|
|
54567
|
-
function
|
|
54774
|
+
function readSupplementalCredentials(authPaths = getSupplementalAuthPaths()) {
|
|
54568
54775
|
const credentials = {};
|
|
54569
54776
|
for (const authPath of authPaths) {
|
|
54570
|
-
|
|
54571
|
-
|
|
54572
|
-
|
|
54573
|
-
try {
|
|
54574
|
-
const parsed = JSON.parse(readFileSync7(authPath, "utf-8"));
|
|
54575
|
-
for (const [provider, credential] of Object.entries(parsed)) {
|
|
54576
|
-
credentials[provider] ??= credential;
|
|
54577
|
-
}
|
|
54578
|
-
} catch {
|
|
54777
|
+
const parsed = readStoredCredentialsFromAuthFile(authPath);
|
|
54778
|
+
for (const [provider, credential] of Object.entries(parsed)) {
|
|
54779
|
+
credentials[provider] = choosePreferredStoredCredential(credentials[provider], credential) ?? credential;
|
|
54579
54780
|
}
|
|
54580
54781
|
}
|
|
54581
54782
|
return credentials;
|
|
@@ -54599,14 +54800,14 @@ function resolveStoredCredentialApiKey(providerId, credential) {
|
|
|
54599
54800
|
}
|
|
54600
54801
|
return void 0;
|
|
54601
54802
|
}
|
|
54602
|
-
function readModelsJsonApiKeys(home =
|
|
54803
|
+
function readModelsJsonApiKeys(home = getHomeDir5()) {
|
|
54603
54804
|
const apiKeys = /* @__PURE__ */ new Map();
|
|
54604
54805
|
const modelsPath = getModelRegistryModelsPath(home);
|
|
54605
|
-
if (!
|
|
54806
|
+
if (!existsSync21(modelsPath)) {
|
|
54606
54807
|
return apiKeys;
|
|
54607
54808
|
}
|
|
54608
54809
|
try {
|
|
54609
|
-
const parsed = JSON.parse(
|
|
54810
|
+
const parsed = JSON.parse(readFileSync8(modelsPath, "utf-8"));
|
|
54610
54811
|
const providers = parsed?.providers;
|
|
54611
54812
|
if (providers) {
|
|
54612
54813
|
for (const [providerId, config] of Object.entries(providers)) {
|
|
@@ -54621,8 +54822,20 @@ function readModelsJsonApiKeys(home = getHomeDir4()) {
|
|
|
54621
54822
|
}
|
|
54622
54823
|
function createFusionAuthStorage() {
|
|
54623
54824
|
const primary = AuthStorage.create(getFusionAuthPath());
|
|
54624
|
-
let
|
|
54825
|
+
let supplementalCredentials = readSupplementalCredentials();
|
|
54625
54826
|
let modelsJsonApiKeys = readModelsJsonApiKeys();
|
|
54827
|
+
const syncSupplementalOauthCredentials = () => {
|
|
54828
|
+
for (const [provider, credential] of Object.entries(supplementalCredentials)) {
|
|
54829
|
+
const current = primary.get(provider);
|
|
54830
|
+
if (!shouldHydrateStoredCredential(current, credential)) {
|
|
54831
|
+
continue;
|
|
54832
|
+
}
|
|
54833
|
+
if (credential.type === "oauth" || credential.type === "api_key") {
|
|
54834
|
+
primary.set(provider, credential);
|
|
54835
|
+
}
|
|
54836
|
+
}
|
|
54837
|
+
};
|
|
54838
|
+
syncSupplementalOauthCredentials();
|
|
54626
54839
|
return new Proxy(primary, {
|
|
54627
54840
|
// Forward property writes to the target so that methods like
|
|
54628
54841
|
// `setFallbackResolver` (called by ModelRegistry) correctly update the
|
|
@@ -54636,31 +54849,51 @@ function createFusionAuthStorage() {
|
|
|
54636
54849
|
if (prop === "reload") {
|
|
54637
54850
|
return () => {
|
|
54638
54851
|
target.reload();
|
|
54639
|
-
|
|
54852
|
+
supplementalCredentials = readSupplementalCredentials();
|
|
54853
|
+
syncSupplementalOauthCredentials();
|
|
54640
54854
|
modelsJsonApiKeys = readModelsJsonApiKeys();
|
|
54641
54855
|
};
|
|
54642
54856
|
}
|
|
54643
54857
|
if (prop === "get") {
|
|
54644
|
-
return (provider) =>
|
|
54858
|
+
return (provider) => choosePreferredStoredCredential(
|
|
54859
|
+
target.get(provider),
|
|
54860
|
+
supplementalCredentials[provider]
|
|
54861
|
+
);
|
|
54645
54862
|
}
|
|
54646
54863
|
if (prop === "has") {
|
|
54647
|
-
return (provider) => target.has(provider) || provider in
|
|
54864
|
+
return (provider) => target.has(provider) || provider in supplementalCredentials || modelsJsonApiKeys.has(provider);
|
|
54648
54865
|
}
|
|
54649
54866
|
if (prop === "hasAuth") {
|
|
54650
|
-
return (provider) => target.hasAuth(provider) || Boolean(
|
|
54867
|
+
return (provider) => target.hasAuth(provider) || Boolean(supplementalCredentials[provider]) || modelsJsonApiKeys.has(provider);
|
|
54651
54868
|
}
|
|
54652
54869
|
if (prop === "getAll") {
|
|
54653
|
-
return () =>
|
|
54870
|
+
return () => {
|
|
54871
|
+
const providerIds = /* @__PURE__ */ new Set([
|
|
54872
|
+
...Object.keys(supplementalCredentials),
|
|
54873
|
+
...Object.keys(target.getAll())
|
|
54874
|
+
]);
|
|
54875
|
+
const merged = {};
|
|
54876
|
+
for (const providerId of providerIds) {
|
|
54877
|
+
const credential = choosePreferredStoredCredential(
|
|
54878
|
+
target.get(providerId),
|
|
54879
|
+
supplementalCredentials[providerId]
|
|
54880
|
+
);
|
|
54881
|
+
if (credential) {
|
|
54882
|
+
merged[providerId] = credential;
|
|
54883
|
+
}
|
|
54884
|
+
}
|
|
54885
|
+
return merged;
|
|
54886
|
+
};
|
|
54654
54887
|
}
|
|
54655
54888
|
if (prop === "list") {
|
|
54656
|
-
return () => Array.from(/* @__PURE__ */ new Set([...Object.keys(
|
|
54889
|
+
return () => Array.from(/* @__PURE__ */ new Set([...Object.keys(supplementalCredentials), ...target.list(), ...modelsJsonApiKeys.keys()]));
|
|
54657
54890
|
}
|
|
54658
54891
|
if (prop === "getApiKey") {
|
|
54659
54892
|
return async (provider) => {
|
|
54660
54893
|
const primaryKey = await target.getApiKey(provider);
|
|
54661
54894
|
if (primaryKey) return primaryKey;
|
|
54662
|
-
const
|
|
54663
|
-
if (
|
|
54895
|
+
const supplementalKey = resolveStoredCredentialApiKey(provider, supplementalCredentials[provider]);
|
|
54896
|
+
if (supplementalKey) return supplementalKey;
|
|
54664
54897
|
return modelsJsonApiKeys.get(provider);
|
|
54665
54898
|
};
|
|
54666
54899
|
}
|
|
@@ -54671,17 +54904,18 @@ function createFusionAuthStorage() {
|
|
|
54671
54904
|
var init_auth_storage = __esm({
|
|
54672
54905
|
"../engine/src/auth-storage.ts"() {
|
|
54673
54906
|
"use strict";
|
|
54907
|
+
init_src();
|
|
54674
54908
|
}
|
|
54675
54909
|
});
|
|
54676
54910
|
|
|
54677
54911
|
// ../engine/src/custom-providers.ts
|
|
54678
|
-
import { readFileSync as
|
|
54679
|
-
import { homedir as
|
|
54680
|
-
import { join as
|
|
54912
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
54913
|
+
import { homedir as homedir6 } from "node:os";
|
|
54914
|
+
import { join as join25 } from "node:path";
|
|
54681
54915
|
function readCustomProviders() {
|
|
54682
54916
|
try {
|
|
54683
|
-
const settingsPath =
|
|
54684
|
-
const raw =
|
|
54917
|
+
const settingsPath = join25(homedir6(), ".fusion", "settings.json");
|
|
54918
|
+
const raw = readFileSync9(settingsPath, "utf-8");
|
|
54685
54919
|
const parsed = JSON.parse(raw);
|
|
54686
54920
|
return Array.isArray(parsed.customProviders) ? parsed.customProviders : [];
|
|
54687
54921
|
} catch {
|
|
@@ -54695,11 +54929,11 @@ var init_custom_providers = __esm({
|
|
|
54695
54929
|
});
|
|
54696
54930
|
|
|
54697
54931
|
// ../engine/src/pi.ts
|
|
54698
|
-
import { existsSync as
|
|
54932
|
+
import { existsSync as existsSync22, readFileSync as readFileSync10 } from "node:fs";
|
|
54699
54933
|
import { exec as exec2 } from "node:child_process";
|
|
54700
54934
|
import { promisify as promisify3 } from "node:util";
|
|
54701
54935
|
import { createRequire as createRequire2 } from "node:module";
|
|
54702
|
-
import { basename as basename7, dirname as dirname9, join as
|
|
54936
|
+
import { basename as basename7, dirname as dirname9, join as join26, relative as relative3, isAbsolute as isAbsolute7, resolve as resolve12 } from "node:path";
|
|
54703
54937
|
import {
|
|
54704
54938
|
createAgentSession,
|
|
54705
54939
|
createBashTool,
|
|
@@ -54953,11 +55187,11 @@ function isRetryableModelSelectionError(message) {
|
|
|
54953
55187
|
return normalized.includes("rate limit") || normalized.includes("too many requests") || normalized.includes("429") || normalized.includes("401") || normalized.includes("403") || normalized.includes("unauthorized") || normalized.includes("forbidden") || normalized.includes("authentication") || normalized.includes("invalid api key") || normalized.includes("invalid key") || normalized.includes("api key") || normalized.includes("overloaded") || normalized.includes("quota") || normalized.includes("capacity") || normalized.includes("temporarily unavailable") || normalized.includes("invalid temperature");
|
|
54954
55188
|
}
|
|
54955
55189
|
function readJsonObject2(path2) {
|
|
54956
|
-
if (!
|
|
55190
|
+
if (!existsSync22(path2)) {
|
|
54957
55191
|
return {};
|
|
54958
55192
|
}
|
|
54959
55193
|
try {
|
|
54960
|
-
const parsed = JSON.parse(
|
|
55194
|
+
const parsed = JSON.parse(readFileSync10(path2, "utf-8"));
|
|
54961
55195
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
54962
55196
|
} catch {
|
|
54963
55197
|
return {};
|
|
@@ -55094,17 +55328,17 @@ function siblingAgentDir(agentDir, siblingRoot) {
|
|
|
55094
55328
|
if (basename7(agentDir) !== "agent") {
|
|
55095
55329
|
return void 0;
|
|
55096
55330
|
}
|
|
55097
|
-
return
|
|
55331
|
+
return join26(dirname9(dirname9(agentDir)), siblingRoot, "agent");
|
|
55098
55332
|
}
|
|
55099
55333
|
function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
55100
55334
|
const projectRoot = resolvePiExtensionProjectRoot(cwd);
|
|
55101
|
-
const fusionAgentDir = agentDir.includes(`${
|
|
55102
|
-
const legacyAgentDir = agentDir.includes(`${
|
|
55103
|
-
const legacyGlobalSettings = legacyAgentDir ? readJsonObject2(
|
|
55104
|
-
const fusionGlobalSettings = fusionAgentDir ? readJsonObject2(
|
|
55105
|
-
const directGlobalSettings = readJsonObject2(
|
|
55335
|
+
const fusionAgentDir = agentDir.includes(`${join26(".fusion", "agent")}`) ? agentDir : siblingAgentDir(agentDir, ".fusion");
|
|
55336
|
+
const legacyAgentDir = agentDir.includes(`${join26(".pi", "agent")}`) ? agentDir : siblingAgentDir(agentDir, ".pi");
|
|
55337
|
+
const legacyGlobalSettings = legacyAgentDir ? readJsonObject2(join26(legacyAgentDir, "settings.json")) : {};
|
|
55338
|
+
const fusionGlobalSettings = fusionAgentDir ? readJsonObject2(join26(fusionAgentDir, "settings.json")) : {};
|
|
55339
|
+
const directGlobalSettings = readJsonObject2(join26(agentDir, "settings.json"));
|
|
55106
55340
|
const globalSettings = { ...legacyGlobalSettings, ...directGlobalSettings, ...fusionGlobalSettings };
|
|
55107
|
-
const fusionProjectSettings = readJsonObject2(
|
|
55341
|
+
const fusionProjectSettings = readJsonObject2(join26(projectRoot, ".fusion", "settings.json"));
|
|
55108
55342
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
55109
55343
|
return {
|
|
55110
55344
|
getGlobalSettings: () => structuredClone(globalSettings),
|
|
@@ -55115,27 +55349,27 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
55115
55349
|
function getPackageManagerAgentDir() {
|
|
55116
55350
|
const fusionAgentDir = getFusionAgentDir();
|
|
55117
55351
|
const legacyAgentDir = getLegacyPiAgentDir();
|
|
55118
|
-
const fusionSettings = readJsonObject2(
|
|
55119
|
-
const legacySettings = readJsonObject2(
|
|
55120
|
-
if (hasPackageManagerSettings(fusionSettings) || !
|
|
55352
|
+
const fusionSettings = readJsonObject2(join26(fusionAgentDir, "settings.json"));
|
|
55353
|
+
const legacySettings = readJsonObject2(join26(legacyAgentDir, "settings.json"));
|
|
55354
|
+
if (hasPackageManagerSettings(fusionSettings) || !existsSync22(legacyAgentDir)) {
|
|
55121
55355
|
return fusionAgentDir;
|
|
55122
55356
|
}
|
|
55123
55357
|
if (hasPackageManagerSettings(legacySettings)) {
|
|
55124
55358
|
return legacyAgentDir;
|
|
55125
55359
|
}
|
|
55126
|
-
return
|
|
55360
|
+
return existsSync22(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
|
|
55127
55361
|
}
|
|
55128
55362
|
function resolveVendoredClaudeCliEntry() {
|
|
55129
55363
|
try {
|
|
55130
55364
|
const require_ = createRequire2(import.meta.url);
|
|
55131
55365
|
const pkgJsonPath = require_.resolve("@fusion/pi-claude-cli/package.json");
|
|
55132
|
-
const pkgJson = JSON.parse(
|
|
55366
|
+
const pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
55133
55367
|
const extensions = pkgJson.pi?.extensions;
|
|
55134
55368
|
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
55135
55369
|
const entry = extensions[0];
|
|
55136
55370
|
if (typeof entry !== "string" || entry.length === 0) return null;
|
|
55137
55371
|
const path2 = resolve12(dirname9(pkgJsonPath), entry);
|
|
55138
|
-
return
|
|
55372
|
+
return existsSync22(path2) ? path2 : null;
|
|
55139
55373
|
} catch {
|
|
55140
55374
|
return null;
|
|
55141
55375
|
}
|
|
@@ -55144,13 +55378,13 @@ function resolveVendoredDroidCliEntry() {
|
|
|
55144
55378
|
try {
|
|
55145
55379
|
const require_ = createRequire2(import.meta.url);
|
|
55146
55380
|
const pkgJsonPath = require_.resolve("@fusion/droid-cli/package.json");
|
|
55147
|
-
const pkgJson = JSON.parse(
|
|
55381
|
+
const pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf-8"));
|
|
55148
55382
|
const extensions = pkgJson.pi?.extensions;
|
|
55149
55383
|
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
55150
55384
|
const entry = extensions[0];
|
|
55151
55385
|
if (typeof entry !== "string" || entry.length === 0) return null;
|
|
55152
55386
|
const path2 = resolve12(dirname9(pkgJsonPath), entry);
|
|
55153
|
-
return
|
|
55387
|
+
return existsSync22(path2) ? path2 : null;
|
|
55154
55388
|
} catch {
|
|
55155
55389
|
return null;
|
|
55156
55390
|
}
|
|
@@ -55178,7 +55412,7 @@ async function registerExtensionProviders(cwd, modelRegistry) {
|
|
|
55178
55412
|
const extensionsResult = await discoverAndLoadExtensions(
|
|
55179
55413
|
doubleReconciledPaths,
|
|
55180
55414
|
cwd,
|
|
55181
|
-
|
|
55415
|
+
join26(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
|
|
55182
55416
|
);
|
|
55183
55417
|
for (const { path: path2, error } of extensionsResult.errors) {
|
|
55184
55418
|
extensionsLog.warn(`Failed to load ${path2}: ${error}`);
|
|
@@ -55233,10 +55467,10 @@ async function isCompleteGitWorktree(worktreePath) {
|
|
|
55233
55467
|
}
|
|
55234
55468
|
}
|
|
55235
55469
|
async function assertValidWorktreeSession(cwd, projectRoot) {
|
|
55236
|
-
if (!
|
|
55470
|
+
if (!existsSync22(cwd)) {
|
|
55237
55471
|
throw new Error(`Refusing to start coding agent in missing worktree: ${cwd}`);
|
|
55238
55472
|
}
|
|
55239
|
-
if (!
|
|
55473
|
+
if (!existsSync22(join26(cwd, ".git")) || !await isCompleteGitWorktree(cwd)) {
|
|
55240
55474
|
throw new Error(`Refusing to start coding agent in incomplete worktree: ${cwd}`);
|
|
55241
55475
|
}
|
|
55242
55476
|
if (!await isRegisteredGitWorktree(projectRoot, cwd)) {
|
|
@@ -55817,7 +56051,7 @@ ${source.content ?? ""}`;
|
|
|
55817
56051
|
|
|
55818
56052
|
// ../engine/src/research/providers/local-docs-provider.ts
|
|
55819
56053
|
import { promises as fs } from "node:fs";
|
|
55820
|
-
import { extname as extname2, join as
|
|
56054
|
+
import { extname as extname2, join as join27, relative as relative4, resolve as resolve13 } from "node:path";
|
|
55821
56055
|
function buildExcerpt(content, terms) {
|
|
55822
56056
|
const lower = content.toLowerCase();
|
|
55823
56057
|
const first = terms.find((term) => lower.includes(term));
|
|
@@ -55946,7 +56180,7 @@ var init_local_docs_provider = __esm({
|
|
|
55946
56180
|
const rootEntries = await fs.readdir(this.projectRoot, { withFileTypes: true });
|
|
55947
56181
|
for (const entry of rootEntries) {
|
|
55948
56182
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
55949
|
-
files.push(
|
|
56183
|
+
files.push(join27(this.projectRoot, entry.name));
|
|
55950
56184
|
}
|
|
55951
56185
|
}
|
|
55952
56186
|
return [...new Set(files)];
|
|
@@ -55956,7 +56190,7 @@ var init_local_docs_provider = __esm({
|
|
|
55956
56190
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
55957
56191
|
for (const entry of entries) {
|
|
55958
56192
|
this.throwIfAborted(signal);
|
|
55959
|
-
const fullPath =
|
|
56193
|
+
const fullPath = join27(dir, entry.name);
|
|
55960
56194
|
const relPath = relative4(this.projectRoot, fullPath).replace(/\\/g, "/");
|
|
55961
56195
|
if (matchesGitignore(relPath, ignorePatterns)) continue;
|
|
55962
56196
|
if (entry.isDirectory()) {
|
|
@@ -55981,7 +56215,7 @@ var init_local_docs_provider = __esm({
|
|
|
55981
56215
|
}
|
|
55982
56216
|
async readGitignore() {
|
|
55983
56217
|
try {
|
|
55984
|
-
const content = await fs.readFile(
|
|
56218
|
+
const content = await fs.readFile(join27(this.projectRoot, ".gitignore"), "utf-8");
|
|
55985
56219
|
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
55986
56220
|
} catch {
|
|
55987
56221
|
return [];
|
|
@@ -56591,9 +56825,9 @@ var init_research_step_runner = __esm({
|
|
|
56591
56825
|
|
|
56592
56826
|
// ../engine/src/agent-tools.ts
|
|
56593
56827
|
import { appendFile as appendFile3, mkdir as mkdir11, readFile as readFile12, readdir as readdir7, stat as stat4, writeFile as writeFile10 } from "node:fs/promises";
|
|
56594
|
-
import { existsSync as
|
|
56828
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
56595
56829
|
import { createHash as createHash4 } from "node:crypto";
|
|
56596
|
-
import { join as
|
|
56830
|
+
import { join as join28 } from "node:path";
|
|
56597
56831
|
import { Type } from "@mariozechner/pi-ai";
|
|
56598
56832
|
function sanitizeAgentMemoryId(agentId) {
|
|
56599
56833
|
return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
@@ -56605,16 +56839,16 @@ function agentDreamsDisplayPath(agentId) {
|
|
|
56605
56839
|
return `${AGENT_MEMORY_ROOT2}/${sanitizeAgentMemoryId(agentId)}/${AGENT_DREAMS_FILENAME2}`;
|
|
56606
56840
|
}
|
|
56607
56841
|
function agentMemoryDirectory(rootDir, agentId) {
|
|
56608
|
-
return
|
|
56842
|
+
return join28(rootDir, AGENT_MEMORY_ROOT2, sanitizeAgentMemoryId(agentId));
|
|
56609
56843
|
}
|
|
56610
56844
|
function agentMemoryFilePath(rootDir, agentId) {
|
|
56611
|
-
return
|
|
56845
|
+
return join28(agentMemoryDirectory(rootDir, agentId), AGENT_MEMORY_FILENAME2);
|
|
56612
56846
|
}
|
|
56613
56847
|
function agentDreamsFilePath(rootDir, agentId) {
|
|
56614
|
-
return
|
|
56848
|
+
return join28(agentMemoryDirectory(rootDir, agentId), AGENT_DREAMS_FILENAME2);
|
|
56615
56849
|
}
|
|
56616
56850
|
function agentDailyFilePath(rootDir, agentId, date = /* @__PURE__ */ new Date()) {
|
|
56617
|
-
return
|
|
56851
|
+
return join28(agentMemoryDirectory(rootDir, agentId), `${date.toISOString().slice(0, 10)}.md`);
|
|
56618
56852
|
}
|
|
56619
56853
|
function qmdAgentMemoryCollectionName(rootDir, agentId) {
|
|
56620
56854
|
const hash = createHash4("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
|
|
@@ -56650,7 +56884,7 @@ async function syncAgentMemoryFile(rootDir, agentMemory) {
|
|
|
56650
56884
|
const dir = agentMemoryDirectory(rootDir, agentMemory.agentId);
|
|
56651
56885
|
await mkdir11(dir, { recursive: true });
|
|
56652
56886
|
const longTermPath = agentMemoryFilePath(rootDir, agentMemory.agentId);
|
|
56653
|
-
if (!
|
|
56887
|
+
if (!existsSync23(longTermPath)) {
|
|
56654
56888
|
const title = agentMemory.agentName?.trim() ? `# Agent Memory: ${agentMemory.agentName.trim()}` : "# Agent Memory";
|
|
56655
56889
|
const fileContent = `${title}
|
|
56656
56890
|
|
|
@@ -56661,11 +56895,11 @@ ${content || ""}
|
|
|
56661
56895
|
await writeFile10(longTermPath, fileContent, "utf-8");
|
|
56662
56896
|
}
|
|
56663
56897
|
const dreamsPath = agentDreamsFilePath(rootDir, agentMemory.agentId);
|
|
56664
|
-
if (!
|
|
56898
|
+
if (!existsSync23(dreamsPath)) {
|
|
56665
56899
|
await writeFile10(dreamsPath, "# Agent Memory Dreams\n\n<!-- Synthesized patterns from this agent's daily notes. -->\n", "utf-8");
|
|
56666
56900
|
}
|
|
56667
56901
|
const dailyPath = agentDailyFilePath(rootDir, agentMemory.agentId);
|
|
56668
|
-
if (!
|
|
56902
|
+
if (!existsSync23(dailyPath)) {
|
|
56669
56903
|
await writeFile10(dailyPath, `# Agent Daily Memory ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}
|
|
56670
56904
|
|
|
56671
56905
|
<!-- Running observations for this agent. -->
|
|
@@ -56689,7 +56923,7 @@ async function listAgentMemoryFiles2(rootDir, agentMemory) {
|
|
|
56689
56923
|
}
|
|
56690
56924
|
for (const entry of entries) {
|
|
56691
56925
|
if (!DAILY_AGENT_MEMORY_RE2.test(entry)) continue;
|
|
56692
|
-
const absPath =
|
|
56926
|
+
const absPath = join28(dir, entry);
|
|
56693
56927
|
const fileStat = await stat4(absPath);
|
|
56694
56928
|
if (fileStat.isFile()) {
|
|
56695
56929
|
files.push({
|
|
@@ -56851,7 +57085,7 @@ function resolveAgentMemoryPath(rootDir, agentId, path2) {
|
|
|
56851
57085
|
return null;
|
|
56852
57086
|
}
|
|
56853
57087
|
return {
|
|
56854
|
-
absPath:
|
|
57088
|
+
absPath: join28(agentMemoryDirectory(rootDir, agentId), filename),
|
|
56855
57089
|
displayPath: `${prefix}${filename}`
|
|
56856
57090
|
};
|
|
56857
57091
|
}
|
|
@@ -57866,6 +58100,7 @@ __export(agent_session_helpers_exports, {
|
|
|
57866
58100
|
createResolvedAgentSession: () => createResolvedAgentSession,
|
|
57867
58101
|
describeAgentModel: () => describeAgentModel,
|
|
57868
58102
|
extractRuntimeHint: () => extractRuntimeHint,
|
|
58103
|
+
extractRuntimeModel: () => extractRuntimeModel,
|
|
57869
58104
|
promptWithAutoRetry: () => promptWithAutoRetry
|
|
57870
58105
|
});
|
|
57871
58106
|
function extractRuntimeHint(runtimeConfig) {
|
|
@@ -57876,6 +58111,24 @@ function extractRuntimeHint(runtimeConfig) {
|
|
|
57876
58111
|
const normalizedHint = hint.trim();
|
|
57877
58112
|
return normalizedHint.length > 0 ? normalizedHint : void 0;
|
|
57878
58113
|
}
|
|
58114
|
+
function extractRuntimeModel(runtimeConfig) {
|
|
58115
|
+
const combined = typeof runtimeConfig?.model === "string" ? runtimeConfig.model.trim() : "";
|
|
58116
|
+
if (combined) {
|
|
58117
|
+
const slashIdx = combined.indexOf("/");
|
|
58118
|
+
if (slashIdx > 0 && slashIdx < combined.length - 1) {
|
|
58119
|
+
return {
|
|
58120
|
+
provider: combined.slice(0, slashIdx).trim() || void 0,
|
|
58121
|
+
modelId: combined.slice(slashIdx + 1).trim() || void 0
|
|
58122
|
+
};
|
|
58123
|
+
}
|
|
58124
|
+
}
|
|
58125
|
+
const provider = typeof runtimeConfig?.modelProvider === "string" ? runtimeConfig.modelProvider.trim() : "";
|
|
58126
|
+
const modelId = typeof runtimeConfig?.modelId === "string" ? runtimeConfig.modelId.trim() : "";
|
|
58127
|
+
return {
|
|
58128
|
+
provider: provider || void 0,
|
|
58129
|
+
modelId: modelId || void 0
|
|
58130
|
+
};
|
|
58131
|
+
}
|
|
57879
58132
|
async function createResolvedAgentSession(options) {
|
|
57880
58133
|
const { sessionPurpose, pluginRunner, runtimeHint, ...runtimeOptions } = options;
|
|
57881
58134
|
const context = buildRuntimeResolutionContext(sessionPurpose, pluginRunner, runtimeHint);
|
|
@@ -59129,6 +59382,38 @@ var init_notifier = __esm({
|
|
|
59129
59382
|
}
|
|
59130
59383
|
});
|
|
59131
59384
|
|
|
59385
|
+
// ../engine/src/fallback-model-observer.ts
|
|
59386
|
+
function buildFallbackLogMessage(label, payload) {
|
|
59387
|
+
return `[fallback] ${label} switched from ${payload.primaryModel} to ${payload.fallbackModel} (${payload.triggerPoint})`;
|
|
59388
|
+
}
|
|
59389
|
+
function createFallbackModelObserver(options) {
|
|
59390
|
+
return async (payload) => {
|
|
59391
|
+
const taskId = options.taskId ?? payload.taskId;
|
|
59392
|
+
const taskTitle = options.taskTitle ?? payload.taskTitle;
|
|
59393
|
+
const message = buildFallbackLogMessage(options.label, payload);
|
|
59394
|
+
if (taskId && options.store?.logEntry) {
|
|
59395
|
+
await options.store.logEntry(taskId, message).catch(() => void 0);
|
|
59396
|
+
}
|
|
59397
|
+
if (taskId && options.store?.appendAgentLog) {
|
|
59398
|
+
await options.store.appendAgentLog(taskId, message, "text", void 0, options.agent).catch(() => void 0);
|
|
59399
|
+
}
|
|
59400
|
+
await notifyFallbackUsed({
|
|
59401
|
+
primaryModel: payload.primaryModel,
|
|
59402
|
+
fallbackModel: payload.fallbackModel,
|
|
59403
|
+
triggerPoint: payload.triggerPoint,
|
|
59404
|
+
taskId,
|
|
59405
|
+
taskTitle,
|
|
59406
|
+
timestamp: payload.timestamp
|
|
59407
|
+
});
|
|
59408
|
+
};
|
|
59409
|
+
}
|
|
59410
|
+
var init_fallback_model_observer = __esm({
|
|
59411
|
+
"../engine/src/fallback-model-observer.ts"() {
|
|
59412
|
+
"use strict";
|
|
59413
|
+
init_notifier();
|
|
59414
|
+
}
|
|
59415
|
+
});
|
|
59416
|
+
|
|
59132
59417
|
// ../engine/src/reviewer.ts
|
|
59133
59418
|
async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptContent, baseline, options = {}) {
|
|
59134
59419
|
let liveSettings = options.settings;
|
|
@@ -59259,7 +59544,13 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
59259
59544
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
59260
59545
|
taskId: options.taskId,
|
|
59261
59546
|
taskTitle: options.taskTitle,
|
|
59262
|
-
onFallbackModelUsed:
|
|
59547
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
59548
|
+
agent: "reviewer",
|
|
59549
|
+
label: "reviewer",
|
|
59550
|
+
store: options.store,
|
|
59551
|
+
taskId: options.taskId,
|
|
59552
|
+
taskTitle: options.taskTitle
|
|
59553
|
+
}),
|
|
59263
59554
|
beforeSpawnSession: async () => {
|
|
59264
59555
|
if (!options.store) return;
|
|
59265
59556
|
let finalSettings;
|
|
@@ -59446,7 +59737,7 @@ var init_reviewer = __esm({
|
|
|
59446
59737
|
init_logger2();
|
|
59447
59738
|
init_usage_limit_detector();
|
|
59448
59739
|
init_agent_instructions();
|
|
59449
|
-
|
|
59740
|
+
init_fallback_model_observer();
|
|
59450
59741
|
init_agent_tools();
|
|
59451
59742
|
REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
|
59452
59743
|
|
|
@@ -59807,7 +60098,7 @@ var init_recovery_policy = __esm({
|
|
|
59807
60098
|
// ../engine/src/triage.ts
|
|
59808
60099
|
import { Type as Type2 } from "@mariozechner/pi-ai";
|
|
59809
60100
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
59810
|
-
import { join as
|
|
60101
|
+
import { join as join29 } from "node:path";
|
|
59811
60102
|
function extractPromptDeclaredTitle(prompt, taskId) {
|
|
59812
60103
|
const headingMatch = prompt.match(/^#\s+Task:\s+([A-Z]+-\d+)\s+-\s+(.+)$/m);
|
|
59813
60104
|
if (!headingMatch) return null;
|
|
@@ -59836,9 +60127,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
59836
60127
|
return { attachmentContents, imageContents };
|
|
59837
60128
|
}
|
|
59838
60129
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
59839
|
-
const { join:
|
|
60130
|
+
const { join: join43 } = await import("node:path");
|
|
59840
60131
|
for (const att of attachments) {
|
|
59841
|
-
const filePath =
|
|
60132
|
+
const filePath = join43(
|
|
59842
60133
|
rootDir,
|
|
59843
60134
|
".fusion",
|
|
59844
60135
|
"tasks",
|
|
@@ -60063,7 +60354,7 @@ var init_triage = __esm({
|
|
|
60063
60354
|
init_concurrency();
|
|
60064
60355
|
init_agent_logger();
|
|
60065
60356
|
init_agent_instructions();
|
|
60066
|
-
|
|
60357
|
+
init_fallback_model_observer();
|
|
60067
60358
|
init_logger2();
|
|
60068
60359
|
init_usage_limit_detector();
|
|
60069
60360
|
init_transient_error_detector();
|
|
@@ -60673,7 +60964,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60673
60964
|
return false;
|
|
60674
60965
|
}
|
|
60675
60966
|
const settings = await this.store.getSettings();
|
|
60676
|
-
const promptPath =
|
|
60967
|
+
const promptPath = join29(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
60677
60968
|
const written = await readFile14(promptPath, "utf-8").catch((err) => {
|
|
60678
60969
|
const msg = err instanceof Error ? err.message : String(err);
|
|
60679
60970
|
planLog.warn(`${task.id}: failed to read PROMPT.md during approved-spec recovery (${promptPath}): ${msg}`);
|
|
@@ -60903,7 +61194,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60903
61194
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
60904
61195
|
taskId: task.id,
|
|
60905
61196
|
taskTitle: task.title,
|
|
60906
|
-
onFallbackModelUsed:
|
|
61197
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61198
|
+
agent: "triage",
|
|
61199
|
+
label: "triage",
|
|
61200
|
+
store: this.store,
|
|
61201
|
+
taskId: task.id,
|
|
61202
|
+
taskTitle: task.title
|
|
61203
|
+
})
|
|
60907
61204
|
});
|
|
60908
61205
|
const modelDesc = describeModel(session);
|
|
60909
61206
|
planLog.log(`${task.id}: using model ${modelDesc}`);
|
|
@@ -61046,7 +61343,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61046
61343
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
61047
61344
|
taskId: task.id,
|
|
61048
61345
|
taskTitle: task.title,
|
|
61049
|
-
onFallbackModelUsed:
|
|
61346
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61347
|
+
agent: "triage",
|
|
61348
|
+
label: "triage",
|
|
61349
|
+
store: this.store,
|
|
61350
|
+
taskId: task.id,
|
|
61351
|
+
taskTitle: task.title
|
|
61352
|
+
})
|
|
61050
61353
|
});
|
|
61051
61354
|
session = fallbackResult.session;
|
|
61052
61355
|
const fallbackModelDesc = describeModel(session);
|
|
@@ -61131,7 +61434,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61131
61434
|
return;
|
|
61132
61435
|
}
|
|
61133
61436
|
const written = await readFile14(
|
|
61134
|
-
|
|
61437
|
+
join29(this.rootDir, promptPath),
|
|
61135
61438
|
"utf-8"
|
|
61136
61439
|
).catch((err) => {
|
|
61137
61440
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61450,9 +61753,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
61450
61753
|
}
|
|
61451
61754
|
try {
|
|
61452
61755
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
61453
|
-
const { join:
|
|
61756
|
+
const { join: join43 } = await import("node:path");
|
|
61454
61757
|
const promptContent = await readFile20(
|
|
61455
|
-
|
|
61758
|
+
join43(rootDir, promptPath),
|
|
61456
61759
|
"utf-8"
|
|
61457
61760
|
).catch((err) => {
|
|
61458
61761
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61680,160 +61983,8 @@ Take a completely different approach to writing this specification. Do NOT repea
|
|
|
61680
61983
|
}
|
|
61681
61984
|
});
|
|
61682
61985
|
|
|
61683
|
-
// ../engine/src/
|
|
61684
|
-
|
|
61685
|
-
__export(session_token_usage_exports, {
|
|
61686
|
-
accumulateSessionTokenUsage: () => accumulateSessionTokenUsage
|
|
61687
|
-
});
|
|
61688
|
-
function readSessionStats(session) {
|
|
61689
|
-
const accessor = session.getSessionStats;
|
|
61690
|
-
if (typeof accessor !== "function") return void 0;
|
|
61691
|
-
try {
|
|
61692
|
-
return accessor.call(session);
|
|
61693
|
-
} catch {
|
|
61694
|
-
return void 0;
|
|
61695
|
-
}
|
|
61696
|
-
}
|
|
61697
|
-
async function accumulateSessionTokenUsage(store, taskId, session) {
|
|
61698
|
-
try {
|
|
61699
|
-
const stats = readSessionStats(session);
|
|
61700
|
-
const tokens = stats?.tokens;
|
|
61701
|
-
if (!tokens) return;
|
|
61702
|
-
const currentInput = (tokens.input ?? 0) + (tokens.cacheWrite ?? 0);
|
|
61703
|
-
const currentOutput = tokens.output ?? 0;
|
|
61704
|
-
const currentCached = tokens.cacheRead ?? 0;
|
|
61705
|
-
const baseline = sessionBaselines.get(session) ?? { input: 0, output: 0, cached: 0 };
|
|
61706
|
-
const inputDelta = Math.max(0, currentInput - baseline.input);
|
|
61707
|
-
const outputDelta = Math.max(0, currentOutput - baseline.output);
|
|
61708
|
-
const cachedDelta = Math.max(0, currentCached - baseline.cached);
|
|
61709
|
-
sessionBaselines.set(session, {
|
|
61710
|
-
input: currentInput,
|
|
61711
|
-
output: currentOutput,
|
|
61712
|
-
cached: currentCached
|
|
61713
|
-
});
|
|
61714
|
-
if (inputDelta === 0 && outputDelta === 0 && cachedDelta === 0) return;
|
|
61715
|
-
const task = await store.getTask(taskId);
|
|
61716
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
61717
|
-
const newInput = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
|
|
61718
|
-
const newOutput = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
|
|
61719
|
-
const newCached = (task.tokenUsage?.cachedTokens ?? 0) + cachedDelta;
|
|
61720
|
-
await store.updateTask(taskId, {
|
|
61721
|
-
tokenUsage: {
|
|
61722
|
-
inputTokens: newInput,
|
|
61723
|
-
outputTokens: newOutput,
|
|
61724
|
-
cachedTokens: newCached,
|
|
61725
|
-
totalTokens: newInput + newOutput + newCached,
|
|
61726
|
-
firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
|
|
61727
|
-
lastUsedAt: now
|
|
61728
|
-
}
|
|
61729
|
-
});
|
|
61730
|
-
} catch (err) {
|
|
61731
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
61732
|
-
log14.warn(`${taskId}: session token usage accumulate failed: ${message}`);
|
|
61733
|
-
}
|
|
61734
|
-
}
|
|
61735
|
-
var log14, sessionBaselines;
|
|
61736
|
-
var init_session_token_usage = __esm({
|
|
61737
|
-
"../engine/src/session-token-usage.ts"() {
|
|
61738
|
-
"use strict";
|
|
61739
|
-
init_logger2();
|
|
61740
|
-
log14 = createLogger2("session-token-usage");
|
|
61741
|
-
sessionBaselines = /* @__PURE__ */ new WeakMap();
|
|
61742
|
-
}
|
|
61743
|
-
});
|
|
61744
|
-
|
|
61745
|
-
// ../engine/src/run-audit.ts
|
|
61746
|
-
function createRunAuditor(store, context) {
|
|
61747
|
-
if (!context) {
|
|
61748
|
-
return {
|
|
61749
|
-
git: async () => {
|
|
61750
|
-
},
|
|
61751
|
-
database: async () => {
|
|
61752
|
-
},
|
|
61753
|
-
filesystem: async () => {
|
|
61754
|
-
}
|
|
61755
|
-
};
|
|
61756
|
-
}
|
|
61757
|
-
const hasRecordAuditEvent = typeof store.recordRunAuditEvent === "function";
|
|
61758
|
-
if (!hasRecordAuditEvent) {
|
|
61759
|
-
return {
|
|
61760
|
-
git: async () => {
|
|
61761
|
-
},
|
|
61762
|
-
database: async () => {
|
|
61763
|
-
},
|
|
61764
|
-
filesystem: async () => {
|
|
61765
|
-
}
|
|
61766
|
-
};
|
|
61767
|
-
}
|
|
61768
|
-
return {
|
|
61769
|
-
git: async (input) => {
|
|
61770
|
-
const eventInput = {
|
|
61771
|
-
taskId: context.taskId,
|
|
61772
|
-
agentId: context.agentId,
|
|
61773
|
-
runId: context.runId,
|
|
61774
|
-
domain: "git",
|
|
61775
|
-
mutationType: input.type,
|
|
61776
|
-
target: input.target,
|
|
61777
|
-
metadata: {
|
|
61778
|
-
phase: context.phase,
|
|
61779
|
-
...context.source ? { source: context.source } : {},
|
|
61780
|
-
...input.metadata
|
|
61781
|
-
}
|
|
61782
|
-
};
|
|
61783
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61784
|
-
},
|
|
61785
|
-
database: async (input) => {
|
|
61786
|
-
const inferredTaskId = input.target.startsWith("FN-") || input.target.startsWith("KB-") ? input.target : context.taskId;
|
|
61787
|
-
const eventInput = {
|
|
61788
|
-
taskId: inferredTaskId,
|
|
61789
|
-
agentId: context.agentId,
|
|
61790
|
-
runId: context.runId,
|
|
61791
|
-
domain: "database",
|
|
61792
|
-
mutationType: input.type,
|
|
61793
|
-
target: input.target,
|
|
61794
|
-
metadata: {
|
|
61795
|
-
phase: context.phase,
|
|
61796
|
-
...context.source ? { source: context.source } : {},
|
|
61797
|
-
...input.metadata
|
|
61798
|
-
}
|
|
61799
|
-
};
|
|
61800
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61801
|
-
},
|
|
61802
|
-
filesystem: async (input) => {
|
|
61803
|
-
const eventInput = {
|
|
61804
|
-
taskId: context.taskId,
|
|
61805
|
-
agentId: context.agentId,
|
|
61806
|
-
runId: context.runId,
|
|
61807
|
-
domain: "filesystem",
|
|
61808
|
-
mutationType: input.type,
|
|
61809
|
-
target: input.target,
|
|
61810
|
-
metadata: {
|
|
61811
|
-
phase: context.phase,
|
|
61812
|
-
...context.source ? { source: context.source } : {},
|
|
61813
|
-
...input.metadata
|
|
61814
|
-
}
|
|
61815
|
-
};
|
|
61816
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61817
|
-
}
|
|
61818
|
-
};
|
|
61819
|
-
}
|
|
61820
|
-
function generateSyntheticRunId(prefix, taskId) {
|
|
61821
|
-
const timestamp = Date.now();
|
|
61822
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
61823
|
-
return `${prefix}-${taskId}-${timestamp}-${random}`;
|
|
61824
|
-
}
|
|
61825
|
-
var init_run_audit = __esm({
|
|
61826
|
-
"../engine/src/run-audit.ts"() {
|
|
61827
|
-
"use strict";
|
|
61828
|
-
}
|
|
61829
|
-
});
|
|
61830
|
-
|
|
61831
|
-
// ../engine/src/merger.ts
|
|
61832
|
-
import { execSync, exec as exec3, spawn as spawn3 } from "node:child_process";
|
|
61833
|
-
import { promisify as promisify4 } from "node:util";
|
|
61834
|
-
import { existsSync as existsSync23 } from "node:fs";
|
|
61835
|
-
import { join as join29 } from "node:path";
|
|
61836
|
-
import { Type as Type3 } from "typebox";
|
|
61986
|
+
// ../engine/src/verification-utils.ts
|
|
61987
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
61837
61988
|
async function execWithProcessGroup(command, options) {
|
|
61838
61989
|
return new Promise((resolve20, reject) => {
|
|
61839
61990
|
if (options.signal?.aborted) {
|
|
@@ -61945,6 +62096,11 @@ function truncateWithEllipsis(text, maxChars) {
|
|
|
61945
62096
|
return `${text.slice(0, maxChars)}
|
|
61946
62097
|
... (truncated)`;
|
|
61947
62098
|
}
|
|
62099
|
+
function truncateOutput(output) {
|
|
62100
|
+
if (output.length <= VERIFICATION_LOG_MAX_CHARS) return output;
|
|
62101
|
+
return `... output truncated to last ${VERIFICATION_LOG_MAX_CHARS} characters ...
|
|
62102
|
+
${output.slice(-VERIFICATION_LOG_MAX_CHARS)}`;
|
|
62103
|
+
}
|
|
61948
62104
|
function summarizeVerificationOutput(output, type) {
|
|
61949
62105
|
const lines = output.split("\n");
|
|
61950
62106
|
let summaryLine = null;
|
|
@@ -62010,6 +62166,13 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62010
62166
|
failureNames.add(truncated);
|
|
62011
62167
|
}
|
|
62012
62168
|
const footer = "(full output available in engine logs)";
|
|
62169
|
+
if (type === "build") {
|
|
62170
|
+
const buildError = output.length > 500 ? `${output.slice(0, 500)}
|
|
62171
|
+
... (truncated)` : output;
|
|
62172
|
+
return `Build output:
|
|
62173
|
+
${buildError}
|
|
62174
|
+
${footer}`;
|
|
62175
|
+
}
|
|
62013
62176
|
const parts = [];
|
|
62014
62177
|
if (summaryLine) {
|
|
62015
62178
|
parts.push(summaryLine);
|
|
@@ -62027,29 +62190,292 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62027
62190
|
parts.push(` \u2022 ... and ${names.length - 5} more failures`);
|
|
62028
62191
|
}
|
|
62029
62192
|
}
|
|
62030
|
-
if (parts.length
|
|
62031
|
-
|
|
62032
|
-
|
|
62033
|
-
|
|
62034
|
-
|
|
62035
|
-
|
|
62036
|
-
return `Verification command failed with no output
|
|
62193
|
+
if (parts.length === 0) {
|
|
62194
|
+
if (output.trim().length === 0) {
|
|
62195
|
+
return `no output
|
|
62196
|
+
${footer}`;
|
|
62197
|
+
}
|
|
62198
|
+
return `${truncateOutput(output)}
|
|
62037
62199
|
${footer}`;
|
|
62038
62200
|
}
|
|
62039
|
-
|
|
62040
|
-
return `${trimmed}
|
|
62201
|
+
return parts.join("\n") + `
|
|
62041
62202
|
${footer}`;
|
|
62203
|
+
}
|
|
62204
|
+
async function runVerificationCommand(store, rootDir, taskId, command, type, signal, log18, agentLabel) {
|
|
62205
|
+
const logger2 = log18 ?? { log: console.log, error: console.error, warn: console.warn };
|
|
62206
|
+
const label = agentLabel ?? "merger";
|
|
62207
|
+
if (signal?.aborted) {
|
|
62208
|
+
throw Object.assign(
|
|
62209
|
+
new Error(`Command aborted before start: ${command}`),
|
|
62210
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62211
|
+
);
|
|
62042
62212
|
}
|
|
62043
|
-
|
|
62044
|
-
|
|
62045
|
-
|
|
62046
|
-
|
|
62047
|
-
|
|
62213
|
+
logger2.log(`${taskId}: running ${type} command: ${command}`);
|
|
62214
|
+
await store.logEntry(taskId, `[verification] Running ${type} command: ${command}`);
|
|
62215
|
+
await store.appendAgentLog(taskId, `Running ${type} command`, "tool", command, label);
|
|
62216
|
+
const result = {
|
|
62217
|
+
command,
|
|
62218
|
+
exitCode: null,
|
|
62219
|
+
stdout: "",
|
|
62220
|
+
stderr: "",
|
|
62221
|
+
success: false
|
|
62222
|
+
};
|
|
62223
|
+
const verificationStartedAt = Date.now();
|
|
62224
|
+
try {
|
|
62225
|
+
const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
|
|
62226
|
+
cwd: rootDir,
|
|
62227
|
+
timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
|
|
62228
|
+
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
|
62229
|
+
signal
|
|
62230
|
+
});
|
|
62231
|
+
if (signal?.aborted) {
|
|
62232
|
+
throw Object.assign(
|
|
62233
|
+
new Error(`Command aborted: ${command}`),
|
|
62234
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62235
|
+
);
|
|
62048
62236
|
}
|
|
62237
|
+
result.stdout = stdout?.toString?.() || "";
|
|
62238
|
+
result.stderr = stderr?.toString?.() || "";
|
|
62239
|
+
result.exitCode = 0;
|
|
62240
|
+
result.success = true;
|
|
62241
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62242
|
+
const timingDetail = `${verificationDurationMs}ms`;
|
|
62243
|
+
if (bufferOverflow) {
|
|
62244
|
+
logger2.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62245
|
+
await store.logEntry(
|
|
62246
|
+
taskId,
|
|
62247
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62248
|
+
);
|
|
62249
|
+
await store.appendAgentLog(
|
|
62250
|
+
taskId,
|
|
62251
|
+
`${type} command succeeded (exit 0)`,
|
|
62252
|
+
"tool_result",
|
|
62253
|
+
timingDetail,
|
|
62254
|
+
label
|
|
62255
|
+
);
|
|
62256
|
+
} else {
|
|
62257
|
+
logger2.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
62258
|
+
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
62259
|
+
await store.appendAgentLog(
|
|
62260
|
+
taskId,
|
|
62261
|
+
`${type} command succeeded (exit 0)`,
|
|
62262
|
+
"tool_result",
|
|
62263
|
+
timingDetail,
|
|
62264
|
+
label
|
|
62265
|
+
);
|
|
62266
|
+
}
|
|
62267
|
+
return result;
|
|
62268
|
+
} catch (error) {
|
|
62269
|
+
if (signal?.aborted) {
|
|
62270
|
+
throw Object.assign(
|
|
62271
|
+
new Error(`Command aborted: ${command}`),
|
|
62272
|
+
{ code: "ABORT_ERR", aborted: true }
|
|
62273
|
+
);
|
|
62274
|
+
}
|
|
62275
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62276
|
+
const err = error;
|
|
62277
|
+
result.stdout = err?.stdout?.toString?.() || "";
|
|
62278
|
+
result.stderr = err?.stderr?.toString?.() || "";
|
|
62279
|
+
result.exitCode = typeof err?.status === "number" ? err.status : typeof err?.code === "number" ? err.code : null;
|
|
62280
|
+
const maxBufferExceeded = err?.code === "ENOBUFS" || err?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(err?.message ?? "").includes("maxBuffer");
|
|
62281
|
+
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
62282
|
+
if (result.success) {
|
|
62283
|
+
logger2.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62284
|
+
await store.logEntry(
|
|
62285
|
+
taskId,
|
|
62286
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62287
|
+
);
|
|
62288
|
+
await store.appendAgentLog(
|
|
62289
|
+
taskId,
|
|
62290
|
+
`${type} command succeeded (exit 0)`,
|
|
62291
|
+
"tool_result",
|
|
62292
|
+
`${verificationDurationMs}ms`,
|
|
62293
|
+
label
|
|
62294
|
+
);
|
|
62295
|
+
return result;
|
|
62296
|
+
}
|
|
62297
|
+
const output = result.stderr || result.stdout || err?.message || "Unknown error";
|
|
62298
|
+
const summary = summarizeVerificationOutput(output, type);
|
|
62299
|
+
logger2.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
62300
|
+
await store.logEntry(
|
|
62301
|
+
taskId,
|
|
62302
|
+
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
62303
|
+
${summary}`
|
|
62304
|
+
);
|
|
62305
|
+
await store.appendAgentLog(
|
|
62306
|
+
taskId,
|
|
62307
|
+
`${type} command failed (exit ${result.exitCode})`,
|
|
62308
|
+
"tool_error",
|
|
62309
|
+
summary,
|
|
62310
|
+
label
|
|
62311
|
+
);
|
|
62312
|
+
}
|
|
62313
|
+
return result;
|
|
62314
|
+
}
|
|
62315
|
+
var VERIFICATION_COMMAND_MAX_BUFFER, VERIFICATION_COMMAND_TIMEOUT_MS, VERIFICATION_LOG_MAX_CHARS;
|
|
62316
|
+
var init_verification_utils = __esm({
|
|
62317
|
+
"../engine/src/verification-utils.ts"() {
|
|
62318
|
+
"use strict";
|
|
62319
|
+
VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
|
|
62320
|
+
VERIFICATION_COMMAND_TIMEOUT_MS = 6e5;
|
|
62321
|
+
VERIFICATION_LOG_MAX_CHARS = 2e4;
|
|
62322
|
+
}
|
|
62323
|
+
});
|
|
62324
|
+
|
|
62325
|
+
// ../engine/src/session-token-usage.ts
|
|
62326
|
+
var session_token_usage_exports = {};
|
|
62327
|
+
__export(session_token_usage_exports, {
|
|
62328
|
+
accumulateSessionTokenUsage: () => accumulateSessionTokenUsage
|
|
62329
|
+
});
|
|
62330
|
+
function readSessionStats(session) {
|
|
62331
|
+
const accessor = session.getSessionStats;
|
|
62332
|
+
if (typeof accessor !== "function") return void 0;
|
|
62333
|
+
try {
|
|
62334
|
+
return accessor.call(session);
|
|
62335
|
+
} catch {
|
|
62336
|
+
return void 0;
|
|
62337
|
+
}
|
|
62338
|
+
}
|
|
62339
|
+
async function accumulateSessionTokenUsage(store, taskId, session) {
|
|
62340
|
+
try {
|
|
62341
|
+
const stats = readSessionStats(session);
|
|
62342
|
+
const tokens = stats?.tokens;
|
|
62343
|
+
if (!tokens) return;
|
|
62344
|
+
const currentInput = (tokens.input ?? 0) + (tokens.cacheWrite ?? 0);
|
|
62345
|
+
const currentOutput = tokens.output ?? 0;
|
|
62346
|
+
const currentCached = tokens.cacheRead ?? 0;
|
|
62347
|
+
const baseline = sessionBaselines.get(session) ?? { input: 0, output: 0, cached: 0 };
|
|
62348
|
+
const inputDelta = Math.max(0, currentInput - baseline.input);
|
|
62349
|
+
const outputDelta = Math.max(0, currentOutput - baseline.output);
|
|
62350
|
+
const cachedDelta = Math.max(0, currentCached - baseline.cached);
|
|
62351
|
+
sessionBaselines.set(session, {
|
|
62352
|
+
input: currentInput,
|
|
62353
|
+
output: currentOutput,
|
|
62354
|
+
cached: currentCached
|
|
62355
|
+
});
|
|
62356
|
+
if (inputDelta === 0 && outputDelta === 0 && cachedDelta === 0) return;
|
|
62357
|
+
const task = await store.getTask(taskId);
|
|
62358
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
62359
|
+
const newInput = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
|
|
62360
|
+
const newOutput = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
|
|
62361
|
+
const newCached = (task.tokenUsage?.cachedTokens ?? 0) + cachedDelta;
|
|
62362
|
+
await store.updateTask(taskId, {
|
|
62363
|
+
tokenUsage: {
|
|
62364
|
+
inputTokens: newInput,
|
|
62365
|
+
outputTokens: newOutput,
|
|
62366
|
+
cachedTokens: newCached,
|
|
62367
|
+
totalTokens: newInput + newOutput + newCached,
|
|
62368
|
+
firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
|
|
62369
|
+
lastUsedAt: now
|
|
62370
|
+
}
|
|
62371
|
+
});
|
|
62372
|
+
} catch (err) {
|
|
62373
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62374
|
+
log14.warn(`${taskId}: session token usage accumulate failed: ${message}`);
|
|
62049
62375
|
}
|
|
62050
|
-
return `${trimmed.slice(0, cutoff)}...
|
|
62051
|
-
${footer}`;
|
|
62052
62376
|
}
|
|
62377
|
+
var log14, sessionBaselines;
|
|
62378
|
+
var init_session_token_usage = __esm({
|
|
62379
|
+
"../engine/src/session-token-usage.ts"() {
|
|
62380
|
+
"use strict";
|
|
62381
|
+
init_logger2();
|
|
62382
|
+
log14 = createLogger2("session-token-usage");
|
|
62383
|
+
sessionBaselines = /* @__PURE__ */ new WeakMap();
|
|
62384
|
+
}
|
|
62385
|
+
});
|
|
62386
|
+
|
|
62387
|
+
// ../engine/src/run-audit.ts
|
|
62388
|
+
function createRunAuditor(store, context) {
|
|
62389
|
+
if (!context) {
|
|
62390
|
+
return {
|
|
62391
|
+
git: async () => {
|
|
62392
|
+
},
|
|
62393
|
+
database: async () => {
|
|
62394
|
+
},
|
|
62395
|
+
filesystem: async () => {
|
|
62396
|
+
}
|
|
62397
|
+
};
|
|
62398
|
+
}
|
|
62399
|
+
const hasRecordAuditEvent = typeof store.recordRunAuditEvent === "function";
|
|
62400
|
+
if (!hasRecordAuditEvent) {
|
|
62401
|
+
return {
|
|
62402
|
+
git: async () => {
|
|
62403
|
+
},
|
|
62404
|
+
database: async () => {
|
|
62405
|
+
},
|
|
62406
|
+
filesystem: async () => {
|
|
62407
|
+
}
|
|
62408
|
+
};
|
|
62409
|
+
}
|
|
62410
|
+
return {
|
|
62411
|
+
git: async (input) => {
|
|
62412
|
+
const eventInput = {
|
|
62413
|
+
taskId: context.taskId,
|
|
62414
|
+
agentId: context.agentId,
|
|
62415
|
+
runId: context.runId,
|
|
62416
|
+
domain: "git",
|
|
62417
|
+
mutationType: input.type,
|
|
62418
|
+
target: input.target,
|
|
62419
|
+
metadata: {
|
|
62420
|
+
phase: context.phase,
|
|
62421
|
+
...context.source ? { source: context.source } : {},
|
|
62422
|
+
...input.metadata
|
|
62423
|
+
}
|
|
62424
|
+
};
|
|
62425
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62426
|
+
},
|
|
62427
|
+
database: async (input) => {
|
|
62428
|
+
const inferredTaskId = input.target.startsWith("FN-") || input.target.startsWith("KB-") ? input.target : context.taskId;
|
|
62429
|
+
const eventInput = {
|
|
62430
|
+
taskId: inferredTaskId,
|
|
62431
|
+
agentId: context.agentId,
|
|
62432
|
+
runId: context.runId,
|
|
62433
|
+
domain: "database",
|
|
62434
|
+
mutationType: input.type,
|
|
62435
|
+
target: input.target,
|
|
62436
|
+
metadata: {
|
|
62437
|
+
phase: context.phase,
|
|
62438
|
+
...context.source ? { source: context.source } : {},
|
|
62439
|
+
...input.metadata
|
|
62440
|
+
}
|
|
62441
|
+
};
|
|
62442
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62443
|
+
},
|
|
62444
|
+
filesystem: async (input) => {
|
|
62445
|
+
const eventInput = {
|
|
62446
|
+
taskId: context.taskId,
|
|
62447
|
+
agentId: context.agentId,
|
|
62448
|
+
runId: context.runId,
|
|
62449
|
+
domain: "filesystem",
|
|
62450
|
+
mutationType: input.type,
|
|
62451
|
+
target: input.target,
|
|
62452
|
+
metadata: {
|
|
62453
|
+
phase: context.phase,
|
|
62454
|
+
...context.source ? { source: context.source } : {},
|
|
62455
|
+
...input.metadata
|
|
62456
|
+
}
|
|
62457
|
+
};
|
|
62458
|
+
await store.recordRunAuditEvent(eventInput);
|
|
62459
|
+
}
|
|
62460
|
+
};
|
|
62461
|
+
}
|
|
62462
|
+
function generateSyntheticRunId(prefix, taskId) {
|
|
62463
|
+
const timestamp = Date.now();
|
|
62464
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
62465
|
+
return `${prefix}-${taskId}-${timestamp}-${random}`;
|
|
62466
|
+
}
|
|
62467
|
+
var init_run_audit = __esm({
|
|
62468
|
+
"../engine/src/run-audit.ts"() {
|
|
62469
|
+
"use strict";
|
|
62470
|
+
}
|
|
62471
|
+
});
|
|
62472
|
+
|
|
62473
|
+
// ../engine/src/merger.ts
|
|
62474
|
+
import { execSync, exec as exec3 } from "node:child_process";
|
|
62475
|
+
import { promisify as promisify4 } from "node:util";
|
|
62476
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
62477
|
+
import { join as join30 } from "node:path";
|
|
62478
|
+
import { Type as Type3 } from "typebox";
|
|
62053
62479
|
function truncateWorkflowScriptOutput(output) {
|
|
62054
62480
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS) return output;
|
|
62055
62481
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS} characters ...
|
|
@@ -62093,7 +62519,7 @@ async function getStagedFiles(cwd) {
|
|
|
62093
62519
|
}
|
|
62094
62520
|
}
|
|
62095
62521
|
function hasInstallState(rootDir) {
|
|
62096
|
-
return
|
|
62522
|
+
return existsSync24(join30(rootDir, "node_modules")) || existsSync24(join30(rootDir, ".pnp.cjs"));
|
|
62097
62523
|
}
|
|
62098
62524
|
function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
62099
62525
|
if (!installStatePresent) return true;
|
|
@@ -62102,10 +62528,10 @@ function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
|
62102
62528
|
);
|
|
62103
62529
|
}
|
|
62104
62530
|
function getDependencySyncCommand(rootDir) {
|
|
62105
|
-
if (
|
|
62106
|
-
if (
|
|
62107
|
-
if (
|
|
62108
|
-
if (
|
|
62531
|
+
if (existsSync24(join30(rootDir, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
|
|
62532
|
+
if (existsSync24(join30(rootDir, "package-lock.json"))) return "npm install";
|
|
62533
|
+
if (existsSync24(join30(rootDir, "yarn.lock"))) return "yarn install --frozen-lockfile";
|
|
62534
|
+
if (existsSync24(join30(rootDir, "bun.lock")) || existsSync24(join30(rootDir, "bun.lockb"))) {
|
|
62109
62535
|
return "bun install --frozen-lockfile";
|
|
62110
62536
|
}
|
|
62111
62537
|
return null;
|
|
@@ -62138,8 +62564,8 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62138
62564
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62139
62565
|
};
|
|
62140
62566
|
}
|
|
62141
|
-
if (
|
|
62142
|
-
if (
|
|
62567
|
+
if (existsSync24(join30(rootDir, "pnpm-lock.yaml"))) {
|
|
62568
|
+
if (existsSync24(join30(rootDir, "pnpm-workspace.yaml"))) {
|
|
62143
62569
|
mergerLog.warn(
|
|
62144
62570
|
`Inferred test command "pnpm test" in a pnpm workspace (${rootDir}). This runs the full monorepo suite on every merge. Consider setting an explicit scoped testCommand in project settings, e.g. \`pnpm -r --filter "...[main]" test\`.`
|
|
62145
62571
|
);
|
|
@@ -62150,21 +62576,21 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62150
62576
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62151
62577
|
};
|
|
62152
62578
|
}
|
|
62153
|
-
if (
|
|
62579
|
+
if (existsSync24(join30(rootDir, "yarn.lock"))) {
|
|
62154
62580
|
return {
|
|
62155
62581
|
command: "yarn test",
|
|
62156
62582
|
testSource: "inferred",
|
|
62157
62583
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62158
62584
|
};
|
|
62159
62585
|
}
|
|
62160
|
-
if (
|
|
62586
|
+
if (existsSync24(join30(rootDir, "bun.lock")) || existsSync24(join30(rootDir, "bun.lockb"))) {
|
|
62161
62587
|
return {
|
|
62162
62588
|
command: "bun test",
|
|
62163
62589
|
testSource: "inferred",
|
|
62164
62590
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62165
62591
|
};
|
|
62166
62592
|
}
|
|
62167
|
-
if (
|
|
62593
|
+
if (existsSync24(join30(rootDir, "package-lock.json"))) {
|
|
62168
62594
|
return {
|
|
62169
62595
|
command: "npm test",
|
|
62170
62596
|
testSource: "inferred",
|
|
@@ -62201,7 +62627,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62201
62627
|
await store.logEntry(taskId, deterministicVerificationMessage);
|
|
62202
62628
|
await store.appendAgentLog(taskId, deterministicVerificationMessage, "text", void 0, "merger");
|
|
62203
62629
|
if (hasTestCommand) {
|
|
62204
|
-
const testResult = await
|
|
62630
|
+
const testResult = await runVerificationCommand2(
|
|
62205
62631
|
store,
|
|
62206
62632
|
rootDir,
|
|
62207
62633
|
taskId,
|
|
@@ -62232,7 +62658,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62232
62658
|
}
|
|
62233
62659
|
}
|
|
62234
62660
|
if (hasBuildCommand) {
|
|
62235
|
-
const buildResult = await
|
|
62661
|
+
const buildResult = await runVerificationCommand2(
|
|
62236
62662
|
store,
|
|
62237
62663
|
rootDir,
|
|
62238
62664
|
taskId,
|
|
@@ -62267,98 +62693,9 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62267
62693
|
await store.appendAgentLog(taskId, "Deterministic merge verification passed", "text", void 0, "merger");
|
|
62268
62694
|
return result;
|
|
62269
62695
|
}
|
|
62270
|
-
async function
|
|
62696
|
+
async function runVerificationCommand2(store, rootDir, taskId, command, type, signal) {
|
|
62271
62697
|
throwIfAborted(signal, taskId);
|
|
62272
|
-
|
|
62273
|
-
await store.logEntry(taskId, `[verification] Running ${type} command: ${command}`);
|
|
62274
|
-
await store.appendAgentLog(taskId, `Running ${type} command`, "tool", command, "merger");
|
|
62275
|
-
const result = {
|
|
62276
|
-
command,
|
|
62277
|
-
exitCode: null,
|
|
62278
|
-
stdout: "",
|
|
62279
|
-
stderr: "",
|
|
62280
|
-
success: false
|
|
62281
|
-
};
|
|
62282
|
-
const verificationStartedAt = Date.now();
|
|
62283
|
-
try {
|
|
62284
|
-
const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
|
|
62285
|
-
cwd: rootDir,
|
|
62286
|
-
timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
|
|
62287
|
-
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
|
62288
|
-
signal
|
|
62289
|
-
});
|
|
62290
|
-
throwIfAborted(signal, taskId);
|
|
62291
|
-
result.stdout = stdout?.toString?.() || "";
|
|
62292
|
-
result.stderr = stderr?.toString?.() || "";
|
|
62293
|
-
result.exitCode = 0;
|
|
62294
|
-
result.success = true;
|
|
62295
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62296
|
-
const timingDetail = `${verificationDurationMs}ms`;
|
|
62297
|
-
if (bufferOverflow) {
|
|
62298
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62299
|
-
await store.logEntry(
|
|
62300
|
-
taskId,
|
|
62301
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62302
|
-
);
|
|
62303
|
-
await store.appendAgentLog(
|
|
62304
|
-
taskId,
|
|
62305
|
-
`${type} command succeeded (exit 0)`,
|
|
62306
|
-
"tool_result",
|
|
62307
|
-
timingDetail,
|
|
62308
|
-
"merger"
|
|
62309
|
-
);
|
|
62310
|
-
} else {
|
|
62311
|
-
mergerLog.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
62312
|
-
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
62313
|
-
await store.appendAgentLog(
|
|
62314
|
-
taskId,
|
|
62315
|
-
`${type} command succeeded (exit 0)`,
|
|
62316
|
-
"tool_result",
|
|
62317
|
-
timingDetail,
|
|
62318
|
-
"merger"
|
|
62319
|
-
);
|
|
62320
|
-
}
|
|
62321
|
-
return result;
|
|
62322
|
-
} catch (error) {
|
|
62323
|
-
throwIfAborted(signal, taskId);
|
|
62324
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62325
|
-
result.stdout = error?.stdout?.toString?.() || "";
|
|
62326
|
-
result.stderr = error?.stderr?.toString?.() || "";
|
|
62327
|
-
result.exitCode = typeof error?.status === "number" ? error.status : typeof error?.code === "number" ? error.code : null;
|
|
62328
|
-
const maxBufferExceeded = error?.code === "ENOBUFS" || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(error?.message ?? "").includes("maxBuffer");
|
|
62329
|
-
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
62330
|
-
if (result.success) {
|
|
62331
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62332
|
-
await store.logEntry(
|
|
62333
|
-
taskId,
|
|
62334
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62335
|
-
);
|
|
62336
|
-
await store.appendAgentLog(
|
|
62337
|
-
taskId,
|
|
62338
|
-
`${type} command succeeded (exit 0)`,
|
|
62339
|
-
"tool_result",
|
|
62340
|
-
`${verificationDurationMs}ms`,
|
|
62341
|
-
"merger"
|
|
62342
|
-
);
|
|
62343
|
-
return result;
|
|
62344
|
-
}
|
|
62345
|
-
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
|
62346
|
-
const summary = summarizeVerificationOutput(output, type);
|
|
62347
|
-
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
62348
|
-
await store.logEntry(
|
|
62349
|
-
taskId,
|
|
62350
|
-
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
62351
|
-
${summary}`
|
|
62352
|
-
);
|
|
62353
|
-
await store.appendAgentLog(
|
|
62354
|
-
taskId,
|
|
62355
|
-
`${type} command failed (exit ${result.exitCode})`,
|
|
62356
|
-
"tool_error",
|
|
62357
|
-
summary,
|
|
62358
|
-
"merger"
|
|
62359
|
-
);
|
|
62360
|
-
}
|
|
62361
|
-
return result;
|
|
62698
|
+
return runVerificationCommand(store, rootDir, taskId, command, type, signal, mergerLog, "merger");
|
|
62362
62699
|
}
|
|
62363
62700
|
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, mergeRunContext, fixAttemptNumber, _testCommand, _buildCommand) {
|
|
62364
62701
|
try {
|
|
@@ -62421,9 +62758,20 @@ Do not refactor, rename broadly, or make opportunistic improvements.
|
|
|
62421
62758
|
onToolEnd: logger2.onToolEnd,
|
|
62422
62759
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
62423
62760
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
62761
|
+
fallbackProvider: settings.fallbackProvider,
|
|
62762
|
+
fallbackModelId: settings.fallbackModelId,
|
|
62424
62763
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
62425
62764
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
62426
|
-
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
62765
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
62766
|
+
taskId,
|
|
62767
|
+
taskTitle: taskForSkillContext?.title,
|
|
62768
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
62769
|
+
agent: "merger",
|
|
62770
|
+
label: "merge verification fix agent",
|
|
62771
|
+
store,
|
|
62772
|
+
taskId,
|
|
62773
|
+
taskTitle: taskForSkillContext?.title
|
|
62774
|
+
})
|
|
62427
62775
|
});
|
|
62428
62776
|
const runId = mergeRunContext?.runId;
|
|
62429
62777
|
const agentId = mergeRunContext?.agentId ?? "merger";
|
|
@@ -62476,7 +62824,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
62476
62824
|
void 0,
|
|
62477
62825
|
"merger"
|
|
62478
62826
|
);
|
|
62479
|
-
const reRunResult = await
|
|
62827
|
+
const reRunResult = await runVerificationCommand2(
|
|
62480
62828
|
store,
|
|
62481
62829
|
rootDir,
|
|
62482
62830
|
taskId,
|
|
@@ -63218,9 +63566,16 @@ You are assisting with a paused \`git pull --rebase\`.
|
|
|
63218
63566
|
onToolEnd: agentLogger.onToolEnd,
|
|
63219
63567
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
63220
63568
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
63569
|
+
fallbackProvider: settings.fallbackProvider,
|
|
63570
|
+
fallbackModelId: settings.fallbackModelId,
|
|
63221
63571
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
63222
63572
|
taskId,
|
|
63223
|
-
onFallbackModelUsed:
|
|
63573
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
63574
|
+
agent: "merger",
|
|
63575
|
+
label: "rebase conflict resolver",
|
|
63576
|
+
store,
|
|
63577
|
+
taskId
|
|
63578
|
+
})
|
|
63224
63579
|
});
|
|
63225
63580
|
const prompt = [
|
|
63226
63581
|
`Resolve rebase conflicts for task ${taskId}.`,
|
|
@@ -63412,7 +63767,7 @@ async function pushToRemoteAfterMerge(store, rootDir, taskId, settings, options)
|
|
|
63412
63767
|
}
|
|
63413
63768
|
async function createPostMergeWorktree(rootDir, taskId) {
|
|
63414
63769
|
const randomSuffix = Math.random().toString(36).slice(2, 10);
|
|
63415
|
-
const postMergeWorktree =
|
|
63770
|
+
const postMergeWorktree = join30(rootDir, ".worktrees", `post-merge-${taskId}-${randomSuffix}`);
|
|
63416
63771
|
try {
|
|
63417
63772
|
await execAsync2(`git worktree add ${quoteArg(postMergeWorktree)} HEAD`, { cwd: rootDir });
|
|
63418
63773
|
return postMergeWorktree;
|
|
@@ -64353,7 +64708,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
64353
64708
|
}
|
|
64354
64709
|
}
|
|
64355
64710
|
throwIfAborted(options.signal, taskId);
|
|
64356
|
-
if (worktreePath &&
|
|
64711
|
+
if (worktreePath && existsSync24(worktreePath)) {
|
|
64357
64712
|
const otherUser = await findWorktreeUser(store, worktreePath, taskId);
|
|
64358
64713
|
if (otherUser) {
|
|
64359
64714
|
mergerLog.log(`Worktree retained \u2014 still needed by ${otherUser}`);
|
|
@@ -65007,9 +65362,20 @@ async function runAiAgentForCommit(params) {
|
|
|
65007
65362
|
onToolEnd: agentLogger.onToolEnd,
|
|
65008
65363
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
65009
65364
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
65365
|
+
fallbackProvider: settings.fallbackProvider,
|
|
65366
|
+
fallbackModelId: settings.fallbackModelId,
|
|
65010
65367
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65011
65368
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65012
|
-
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
65369
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
65370
|
+
taskId,
|
|
65371
|
+
taskTitle: taskForSkillContext?.title,
|
|
65372
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
65373
|
+
agent: "merger",
|
|
65374
|
+
label: "merge agent",
|
|
65375
|
+
store,
|
|
65376
|
+
taskId,
|
|
65377
|
+
taskTitle: taskForSkillContext?.title
|
|
65378
|
+
})
|
|
65013
65379
|
});
|
|
65014
65380
|
options.onSession?.(session);
|
|
65015
65381
|
try {
|
|
@@ -65440,7 +65806,14 @@ If issues are found that need attention, describe them clearly and include concr
|
|
|
65440
65806
|
fallbackModelId: settings.fallbackModelId,
|
|
65441
65807
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65442
65808
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65443
|
-
...postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {}
|
|
65809
|
+
...postMergeSkillContext?.skillSelectionContext ? { skillSelection: postMergeSkillContext.skillSelectionContext } : {},
|
|
65810
|
+
taskId,
|
|
65811
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
65812
|
+
agent: "merger",
|
|
65813
|
+
label: `post-merge workflow step '${workflowStep.name}'`,
|
|
65814
|
+
store,
|
|
65815
|
+
taskId
|
|
65816
|
+
})
|
|
65444
65817
|
});
|
|
65445
65818
|
mergerLog.log(`${taskId}: [post-merge] workflow step '${workflowStep.name}' using model ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
65446
65819
|
await store.logEntry(taskId, `[post-merge] Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
@@ -65476,15 +65849,17 @@ async function completeTask(store, taskId, result) {
|
|
|
65476
65849
|
result.task = task;
|
|
65477
65850
|
store.emit("task:merged", result);
|
|
65478
65851
|
}
|
|
65479
|
-
var execAsync2, LOCKFILE_PATTERNS, GENERATED_PATTERNS, DEPENDENCY_SYNC_TRIGGER_PATTERNS,
|
|
65852
|
+
var execAsync2, LOCKFILE_PATTERNS, GENERATED_PATTERNS, DEPENDENCY_SYNC_TRIGGER_PATTERNS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, PULL_REBASE_TIMEOUT_MS, PUSH_TIMEOUT_MS, MERGE_COMMIT_LOG_MAX_CHARS, MERGE_DIFF_STAT_MAX_CHARS, VerificationError, MergeAbortedError, FUSION_TASK_ID_TRAILER_KEY;
|
|
65480
65853
|
var init_merger = __esm({
|
|
65481
65854
|
"../engine/src/merger.ts"() {
|
|
65482
65855
|
"use strict";
|
|
65856
|
+
init_verification_utils();
|
|
65857
|
+
init_verification_utils();
|
|
65483
65858
|
init_src();
|
|
65484
65859
|
init_pi();
|
|
65485
65860
|
init_session_token_usage();
|
|
65486
65861
|
init_agent_session_helpers();
|
|
65487
|
-
|
|
65862
|
+
init_fallback_model_observer();
|
|
65488
65863
|
init_session_skill_context();
|
|
65489
65864
|
init_agent_logger();
|
|
65490
65865
|
init_logger2();
|
|
@@ -65530,9 +65905,6 @@ var init_merger = __esm({
|
|
|
65530
65905
|
"bun.lock",
|
|
65531
65906
|
"packages/*/package.json"
|
|
65532
65907
|
];
|
|
65533
|
-
VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
|
|
65534
|
-
VERIFICATION_COMMAND_TIMEOUT_MS = 6e5;
|
|
65535
|
-
VERIFICATION_LOG_MAX_CHARS = 2e4;
|
|
65536
65908
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS = 4e3;
|
|
65537
65909
|
PULL_REBASE_TIMEOUT_MS = 12e4;
|
|
65538
65910
|
PUSH_TIMEOUT_MS = 6e4;
|
|
@@ -65557,8 +65929,8 @@ var init_merger = __esm({
|
|
|
65557
65929
|
|
|
65558
65930
|
// ../engine/src/worktree-names.ts
|
|
65559
65931
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
65560
|
-
import { join as
|
|
65561
|
-
import { existsSync as
|
|
65932
|
+
import { join as join31 } from "node:path";
|
|
65933
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
65562
65934
|
function slugify2(str) {
|
|
65563
65935
|
return str.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
65564
65936
|
}
|
|
@@ -65569,7 +65941,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65569
65941
|
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
65570
65942
|
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
65571
65943
|
const baseName = `${adjective}-${noun}`;
|
|
65572
|
-
const worktreesDir =
|
|
65944
|
+
const worktreesDir = join31(rootDir, ".worktrees");
|
|
65573
65945
|
const existing = getExistingWorktreeNames(worktreesDir);
|
|
65574
65946
|
for (const reserved of reservedNames) {
|
|
65575
65947
|
existing.add(reserved);
|
|
@@ -65584,7 +65956,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65584
65956
|
return `${baseName}-${suffix}`;
|
|
65585
65957
|
}
|
|
65586
65958
|
function getExistingWorktreeNames(worktreesDir) {
|
|
65587
|
-
if (!
|
|
65959
|
+
if (!existsSync25(worktreesDir)) {
|
|
65588
65960
|
return /* @__PURE__ */ new Set();
|
|
65589
65961
|
}
|
|
65590
65962
|
try {
|
|
@@ -65721,8 +66093,8 @@ __export(worktree_pool_exports, {
|
|
|
65721
66093
|
});
|
|
65722
66094
|
import { exec as exec4 } from "node:child_process";
|
|
65723
66095
|
import { promisify as promisify5 } from "node:util";
|
|
65724
|
-
import { existsSync as
|
|
65725
|
-
import { join as
|
|
66096
|
+
import { existsSync as existsSync26, lstatSync, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
66097
|
+
import { join as join32, relative as relative6, resolve as resolve15, isAbsolute as isAbsolute9 } from "node:path";
|
|
65726
66098
|
function getExecStdout(result) {
|
|
65727
66099
|
if (typeof result === "string") return result;
|
|
65728
66100
|
if (result && typeof result === "object" && "stdout" in result) {
|
|
@@ -65768,10 +66140,10 @@ async function isRegisteredGitWorktree2(rootDir, worktreePath) {
|
|
|
65768
66140
|
return (await getRegisteredWorktreePaths(rootDir)).has(resolve15(worktreePath));
|
|
65769
66141
|
}
|
|
65770
66142
|
function hasRequiredWorktreeFiles(worktreePath) {
|
|
65771
|
-
return
|
|
66143
|
+
return existsSync26(join32(worktreePath, ".git")) && existsSync26(join32(worktreePath, "package.json"));
|
|
65772
66144
|
}
|
|
65773
66145
|
async function isUsableTaskWorktree(rootDir, worktreePath) {
|
|
65774
|
-
return
|
|
66146
|
+
return existsSync26(worktreePath) && await isRegisteredGitWorktree2(rootDir, worktreePath) && hasRequiredWorktreeFiles(worktreePath);
|
|
65775
66147
|
}
|
|
65776
66148
|
function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
65777
66149
|
const worktreesDir = resolve15(rootDir, ".worktrees");
|
|
@@ -65780,14 +66152,14 @@ function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
|
65780
66152
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute9(rel);
|
|
65781
66153
|
}
|
|
65782
66154
|
async function scanIdleWorktrees(rootDir, store) {
|
|
65783
|
-
const worktreesDir =
|
|
65784
|
-
if (!
|
|
66155
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66156
|
+
if (!existsSync26(worktreesDir)) {
|
|
65785
66157
|
return [];
|
|
65786
66158
|
}
|
|
65787
66159
|
let dirs;
|
|
65788
66160
|
try {
|
|
65789
66161
|
const entries = readdirSync4(worktreesDir, { withFileTypes: true });
|
|
65790
|
-
dirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
66162
|
+
dirs = entries.filter((e) => e.isDirectory()).map((e) => join32(worktreesDir, e.name));
|
|
65791
66163
|
} catch (err) {
|
|
65792
66164
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65793
66165
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory: ${errorMessage}`);
|
|
@@ -65810,16 +66182,16 @@ async function scanIdleWorktrees(rootDir, store) {
|
|
|
65810
66182
|
return registeredDirs.filter((dir) => !activeWorktrees.has(resolve15(dir)));
|
|
65811
66183
|
}
|
|
65812
66184
|
async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
65813
|
-
const worktreesDir =
|
|
65814
|
-
if (!
|
|
66185
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66186
|
+
if (!existsSync26(worktreesDir)) {
|
|
65815
66187
|
return 0;
|
|
65816
66188
|
}
|
|
65817
66189
|
const orphaned = await scanIdleWorktrees(rootDir, store);
|
|
65818
66190
|
const registeredWorktrees = await getRegisteredWorktreePaths(rootDir);
|
|
65819
66191
|
let dirs = [];
|
|
65820
|
-
if (
|
|
66192
|
+
if (existsSync26(worktreesDir)) {
|
|
65821
66193
|
try {
|
|
65822
|
-
dirs = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
66194
|
+
dirs = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join32(worktreesDir, e.name));
|
|
65823
66195
|
} catch (err) {
|
|
65824
66196
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65825
66197
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory for cleanup: ${errorMessage}`);
|
|
@@ -65851,8 +66223,8 @@ async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
|
65851
66223
|
return cleaned;
|
|
65852
66224
|
}
|
|
65853
66225
|
async function reapOrphanWorktrees(projectRoot) {
|
|
65854
|
-
const worktreesDir =
|
|
65855
|
-
if (!
|
|
66226
|
+
const worktreesDir = join32(projectRoot, ".worktrees");
|
|
66227
|
+
if (!existsSync26(worktreesDir)) {
|
|
65856
66228
|
return 0;
|
|
65857
66229
|
}
|
|
65858
66230
|
let entries;
|
|
@@ -65860,11 +66232,11 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65860
66232
|
entries = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => {
|
|
65861
66233
|
if (!e.isDirectory()) return false;
|
|
65862
66234
|
try {
|
|
65863
|
-
return lstatSync(
|
|
66235
|
+
return lstatSync(join32(worktreesDir, e.name)).isDirectory() && !lstatSync(join32(worktreesDir, e.name)).isSymbolicLink();
|
|
65864
66236
|
} catch {
|
|
65865
66237
|
return false;
|
|
65866
66238
|
}
|
|
65867
|
-
}).map((e) => ({ name: e.name, fullPath:
|
|
66239
|
+
}).map((e) => ({ name: e.name, fullPath: join32(worktreesDir, e.name) }));
|
|
65868
66240
|
} catch (err) {
|
|
65869
66241
|
const msg = err instanceof Error ? err.message : String(err);
|
|
65870
66242
|
worktreePoolLog.warn(`reapOrphanWorktrees: failed to read .worktrees/ \u2014 ${msg}`);
|
|
@@ -65883,8 +66255,8 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65883
66255
|
if (registered.has(resolvedFull)) {
|
|
65884
66256
|
continue;
|
|
65885
66257
|
}
|
|
65886
|
-
const dotGit =
|
|
65887
|
-
if (
|
|
66258
|
+
const dotGit = join32(resolvedFull, ".git");
|
|
66259
|
+
if (existsSync26(dotGit)) {
|
|
65888
66260
|
worktreePoolLog.log(`reapOrphanWorktrees: skipping ${name} (has .git entry but not in registered list \u2014 may be partially registered)`);
|
|
65889
66261
|
continue;
|
|
65890
66262
|
}
|
|
@@ -65944,7 +66316,7 @@ var init_worktree_pool = __esm({
|
|
|
65944
66316
|
acquire() {
|
|
65945
66317
|
for (const path2 of this.idle) {
|
|
65946
66318
|
this.idle.delete(path2);
|
|
65947
|
-
if (
|
|
66319
|
+
if (existsSync26(path2)) {
|
|
65948
66320
|
return path2;
|
|
65949
66321
|
}
|
|
65950
66322
|
worktreePoolLog.log(`Pruned stale entry: ${path2}`);
|
|
@@ -65991,7 +66363,7 @@ var init_worktree_pool = __esm({
|
|
|
65991
66363
|
*/
|
|
65992
66364
|
rehydrate(idlePaths) {
|
|
65993
66365
|
for (const path2 of idlePaths) {
|
|
65994
|
-
if (
|
|
66366
|
+
if (existsSync26(path2)) {
|
|
65995
66367
|
this.idle.add(path2);
|
|
65996
66368
|
} else {
|
|
65997
66369
|
worktreePoolLog.log(`Rehydrate skipped (not on disk): ${path2}`);
|
|
@@ -66047,7 +66419,7 @@ var init_worktree_pool = __esm({
|
|
|
66047
66419
|
throw err;
|
|
66048
66420
|
}
|
|
66049
66421
|
const conflictingPath = match[1];
|
|
66050
|
-
if (!
|
|
66422
|
+
if (!existsSync26(conflictingPath)) {
|
|
66051
66423
|
await execAsync3("git worktree prune", { cwd: worktreePath });
|
|
66052
66424
|
await execAsync3(checkoutCmd, { cwd: worktreePath });
|
|
66053
66425
|
return branchName;
|
|
@@ -66124,8 +66496,8 @@ var init_token_cap_detector = __esm({
|
|
|
66124
66496
|
// ../engine/src/step-session-executor.ts
|
|
66125
66497
|
import { exec as exec5 } from "node:child_process";
|
|
66126
66498
|
import { promisify as promisify6 } from "node:util";
|
|
66127
|
-
import { existsSync as
|
|
66128
|
-
import { join as
|
|
66499
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
66500
|
+
import { join as join33 } from "node:path";
|
|
66129
66501
|
function parseStepFileScopes(prompt) {
|
|
66130
66502
|
const result = /* @__PURE__ */ new Map();
|
|
66131
66503
|
if (!prompt) return result;
|
|
@@ -66412,7 +66784,7 @@ var init_step_session_executor = __esm({
|
|
|
66412
66784
|
init_worktree_names();
|
|
66413
66785
|
init_agent_logger();
|
|
66414
66786
|
init_logger2();
|
|
66415
|
-
|
|
66787
|
+
init_fallback_model_observer();
|
|
66416
66788
|
init_context_limit_detector();
|
|
66417
66789
|
init_usage_limit_detector();
|
|
66418
66790
|
init_agent_tools();
|
|
@@ -66529,7 +66901,7 @@ var init_step_session_executor = __esm({
|
|
|
66529
66901
|
}
|
|
66530
66902
|
for (const [stepIdx, worktreePath] of this.parallelWorktrees) {
|
|
66531
66903
|
try {
|
|
66532
|
-
if (
|
|
66904
|
+
if (existsSync27(worktreePath)) {
|
|
66533
66905
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66534
66906
|
cwd: this.options.rootDir
|
|
66535
66907
|
});
|
|
@@ -66694,7 +67066,13 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66694
67066
|
...this.options.skillSelection ? { skillSelection: this.options.skillSelection } : {},
|
|
66695
67067
|
taskId: taskDetail.id,
|
|
66696
67068
|
taskTitle: taskDetail.title,
|
|
66697
|
-
onFallbackModelUsed:
|
|
67069
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
67070
|
+
agent: "executor",
|
|
67071
|
+
label: "workflow step agent",
|
|
67072
|
+
store: this.store,
|
|
67073
|
+
taskId: taskDetail.id,
|
|
67074
|
+
taskTitle: taskDetail.title
|
|
67075
|
+
})
|
|
66698
67076
|
});
|
|
66699
67077
|
session = createResult.session;
|
|
66700
67078
|
const handle = {
|
|
@@ -66874,7 +67252,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66874
67252
|
for (const [stepIdx, worktreePath] of worktreePaths) {
|
|
66875
67253
|
if (worktreePath !== this.options.worktreePath) {
|
|
66876
67254
|
try {
|
|
66877
|
-
if (
|
|
67255
|
+
if (existsSync27(worktreePath)) {
|
|
66878
67256
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66879
67257
|
cwd: this.options.rootDir
|
|
66880
67258
|
});
|
|
@@ -66904,7 +67282,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66904
67282
|
async createStepWorktree(stepIndex) {
|
|
66905
67283
|
const { rootDir } = this.options;
|
|
66906
67284
|
const name = generateWorktreeName(rootDir);
|
|
66907
|
-
const worktreePath =
|
|
67285
|
+
const worktreePath = join33(rootDir, ".worktrees", name);
|
|
66908
67286
|
const branchName = `fusion/step-${stepIndex}-${name}`;
|
|
66909
67287
|
stepExecLog.log(`Creating worktree for step ${stepIndex}: ${worktreePath} (branch: ${branchName})`);
|
|
66910
67288
|
try {
|
|
@@ -66979,7 +67357,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66979
67357
|
|
|
66980
67358
|
// ../engine/src/spec-staleness.ts
|
|
66981
67359
|
import { stat as stat5 } from "node:fs/promises";
|
|
66982
|
-
import { join as
|
|
67360
|
+
import { join as join34 } from "node:path";
|
|
66983
67361
|
async function evaluateSpecStaleness(options) {
|
|
66984
67362
|
const { settings, promptPath, nowMs } = options;
|
|
66985
67363
|
if (settings.specStalenessEnabled !== true) {
|
|
@@ -67019,7 +67397,7 @@ async function evaluateSpecStaleness(options) {
|
|
|
67019
67397
|
};
|
|
67020
67398
|
}
|
|
67021
67399
|
function getPromptPath(tasksDir, taskId) {
|
|
67022
|
-
return
|
|
67400
|
+
return join34(tasksDir, taskId, "PROMPT.md");
|
|
67023
67401
|
}
|
|
67024
67402
|
var DEFAULT_SPEC_STALENESS_MAX_AGE_MS;
|
|
67025
67403
|
var init_spec_staleness = __esm({
|
|
@@ -67050,8 +67428,8 @@ var init_task_completion = __esm({
|
|
|
67050
67428
|
|
|
67051
67429
|
// ../engine/src/run-verification-tool.ts
|
|
67052
67430
|
import { spawn as spawn4 } from "node:child_process";
|
|
67053
|
-
import { existsSync as
|
|
67054
|
-
import { isAbsolute as isAbsolute10, join as
|
|
67431
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
67432
|
+
import { isAbsolute as isAbsolute10, join as join35 } from "node:path";
|
|
67055
67433
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
67056
67434
|
function createBuffer() {
|
|
67057
67435
|
return { headChunks: [], headBytes: 0, tailChunks: [], tailBytes: 0, totalBytes: 0 };
|
|
@@ -67084,7 +67462,7 @@ function flattenBuffer(buf) {
|
|
|
67084
67462
|
|
|
67085
67463
|
` + tail;
|
|
67086
67464
|
}
|
|
67087
|
-
async function
|
|
67465
|
+
async function runVerificationCommand3(opts) {
|
|
67088
67466
|
const { command, cwd, timeoutMs, expectFailure = false, onHeartbeat, onLine } = opts;
|
|
67089
67467
|
const startMs = Date.now();
|
|
67090
67468
|
const warnings = [];
|
|
@@ -67224,7 +67602,7 @@ function createRunVerificationTool(opts) {
|
|
|
67224
67602
|
if (params.cwd && isAbsolute10(params.cwd)) {
|
|
67225
67603
|
resolvedCwd = params.cwd;
|
|
67226
67604
|
} else if (params.cwd) {
|
|
67227
|
-
resolvedCwd =
|
|
67605
|
+
resolvedCwd = join35(worktreePath, params.cwd);
|
|
67228
67606
|
} else {
|
|
67229
67607
|
resolvedCwd = worktreePath;
|
|
67230
67608
|
}
|
|
@@ -67239,8 +67617,8 @@ function createRunVerificationTool(opts) {
|
|
|
67239
67617
|
}
|
|
67240
67618
|
let effectiveCommand = command;
|
|
67241
67619
|
if (command.trimStart().startsWith("pnpm --filter")) {
|
|
67242
|
-
const modulesYaml =
|
|
67243
|
-
if (!
|
|
67620
|
+
const modulesYaml = join35(rootDir, "node_modules", ".modules.yaml");
|
|
67621
|
+
if (!existsSync28(modulesYaml)) {
|
|
67244
67622
|
const installCmd = "pnpm install --prefer-offline";
|
|
67245
67623
|
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
67246
67624
|
warnings.push(msg);
|
|
@@ -67251,7 +67629,7 @@ function createRunVerificationTool(opts) {
|
|
|
67251
67629
|
log18.info(
|
|
67252
67630
|
`[fn_run_verification] ${taskId}: scope=${scope} timeout=${timeoutSec}s cwd=${resolvedCwd} cmd=${effectiveCommand}`
|
|
67253
67631
|
);
|
|
67254
|
-
const result = await
|
|
67632
|
+
const result = await runVerificationCommand3({
|
|
67255
67633
|
command: effectiveCommand,
|
|
67256
67634
|
cwd: resolvedCwd,
|
|
67257
67635
|
timeoutMs,
|
|
@@ -67351,8 +67729,8 @@ var init_run_verification_tool = __esm({
|
|
|
67351
67729
|
// ../engine/src/executor.ts
|
|
67352
67730
|
import { exec as exec6 } from "node:child_process";
|
|
67353
67731
|
import { promisify as promisify7 } from "node:util";
|
|
67354
|
-
import { isAbsolute as isAbsolute11, join as
|
|
67355
|
-
import { existsSync as
|
|
67732
|
+
import { isAbsolute as isAbsolute11, join as join36, relative as relative7, resolve as resolvePath } from "node:path";
|
|
67733
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
67356
67734
|
import { readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
67357
67735
|
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
67358
67736
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
@@ -67646,6 +68024,7 @@ var init_executor = __esm({
|
|
|
67646
68024
|
"use strict";
|
|
67647
68025
|
init_src();
|
|
67648
68026
|
init_merger();
|
|
68027
|
+
init_verification_utils();
|
|
67649
68028
|
init_worktree_names();
|
|
67650
68029
|
init_pi();
|
|
67651
68030
|
init_session_token_usage();
|
|
@@ -67670,7 +68049,7 @@ var init_executor = __esm({
|
|
|
67670
68049
|
init_task_completion();
|
|
67671
68050
|
init_auth_storage();
|
|
67672
68051
|
init_run_verification_tool();
|
|
67673
|
-
|
|
68052
|
+
init_fallback_model_observer();
|
|
67674
68053
|
init_agent_logger();
|
|
67675
68054
|
init_agent_tools();
|
|
67676
68055
|
execAsync5 = promisify7(exec6);
|
|
@@ -68817,7 +69196,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68817
69196
|
);
|
|
68818
69197
|
return false;
|
|
68819
69198
|
}
|
|
68820
|
-
if (task.worktree &&
|
|
69199
|
+
if (task.worktree && existsSync29(task.worktree)) {
|
|
68821
69200
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
68822
69201
|
if (modifiedFiles.length > 0) {
|
|
68823
69202
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
@@ -68997,7 +69376,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68997
69376
|
if (task.dependencies.length === 0) return null;
|
|
68998
69377
|
for (const depId of task.dependencies) {
|
|
68999
69378
|
const dep = allTasks.find((t) => t.id === depId);
|
|
69000
|
-
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") &&
|
|
69379
|
+
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") && existsSync29(dep.worktree)) {
|
|
69001
69380
|
return dep.worktree;
|
|
69002
69381
|
}
|
|
69003
69382
|
}
|
|
@@ -69048,7 +69427,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69048
69427
|
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
69049
69428
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
69050
69429
|
if (!isActiveTask) {
|
|
69051
|
-
const tasksDir =
|
|
69430
|
+
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
69052
69431
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
69053
69432
|
const staleness = await evaluateSpecStaleness({ settings, promptPath });
|
|
69054
69433
|
if (staleness.isStale) {
|
|
@@ -69095,7 +69474,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69095
69474
|
worktreeName = generateWorktreeName(this.rootDir);
|
|
69096
69475
|
break;
|
|
69097
69476
|
}
|
|
69098
|
-
worktreePath =
|
|
69477
|
+
worktreePath = join36(this.rootDir, ".worktrees", worktreeName);
|
|
69099
69478
|
}
|
|
69100
69479
|
let stuckRequeue = null;
|
|
69101
69480
|
let taskDone = false;
|
|
@@ -69119,7 +69498,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69119
69498
|
);
|
|
69120
69499
|
}
|
|
69121
69500
|
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
69122
|
-
let isResume =
|
|
69501
|
+
let isResume = existsSync29(worktreePath);
|
|
69123
69502
|
let acquiredFromPool = false;
|
|
69124
69503
|
const baseBranch = task.baseBranch || null;
|
|
69125
69504
|
if (task.worktree && isResume && !await isUsableTaskWorktree(this.rootDir, worktreePath)) {
|
|
@@ -69132,8 +69511,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69132
69511
|
this.currentRunContext
|
|
69133
69512
|
);
|
|
69134
69513
|
await this.store.updateTask(task.id, { worktree: null, branch: null });
|
|
69135
|
-
worktreePath =
|
|
69136
|
-
isResume =
|
|
69514
|
+
worktreePath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
69515
|
+
isResume = existsSync29(worktreePath);
|
|
69137
69516
|
}
|
|
69138
69517
|
if (!isResume) {
|
|
69139
69518
|
if (this.options.pool && settings.recycleWorktrees) {
|
|
@@ -69359,6 +69738,84 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69359
69738
|
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
|
|
69360
69739
|
return;
|
|
69361
69740
|
}
|
|
69741
|
+
if (executionMode !== "fast") {
|
|
69742
|
+
if (settings.testCommand?.trim() || settings.buildCommand?.trim()) {
|
|
69743
|
+
const verificationResult = await this.runExecutorDeterministicVerification(task, worktreePath, settings);
|
|
69744
|
+
if (!verificationResult.allPassed) {
|
|
69745
|
+
const failedType = verificationResult.failedCommand === "testCommand" ? "test" : "build";
|
|
69746
|
+
const failedResult = failedType === "test" ? verificationResult.testResult : verificationResult.buildResult;
|
|
69747
|
+
const failedCommand = failedResult.command;
|
|
69748
|
+
const failureOutput = failedResult.stderr || failedResult.stdout || "Unknown error";
|
|
69749
|
+
const summary = summarizeVerificationOutput(failureOutput, failedType);
|
|
69750
|
+
executorLog.log(`${task.id}: [verification] ${failedType} failed \u2014 attempting fix agent`);
|
|
69751
|
+
await this.store.logEntry(
|
|
69752
|
+
task.id,
|
|
69753
|
+
`[verification] ${failedType} command failed (exit ${failedResult.exitCode}). Attempting fix agent...`,
|
|
69754
|
+
summary,
|
|
69755
|
+
this.currentRunContext
|
|
69756
|
+
);
|
|
69757
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
69758
|
+
if (maxFixRetries === 0) {
|
|
69759
|
+
executorLog.log(`${task.id}: [verification] fix retries set to 0 \u2014 sending task back immediately`);
|
|
69760
|
+
await this.sendTaskBackForFix(
|
|
69761
|
+
task,
|
|
69762
|
+
worktreePath,
|
|
69763
|
+
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}):
|
|
69764
|
+
${summary}`,
|
|
69765
|
+
`Verification (${failedType})`,
|
|
69766
|
+
`Deterministic verification failed (${failedType})`
|
|
69767
|
+
);
|
|
69768
|
+
return;
|
|
69769
|
+
}
|
|
69770
|
+
let fixSucceeded = false;
|
|
69771
|
+
for (let attempt = 1; attempt <= maxFixRetries; attempt++) {
|
|
69772
|
+
const fixed = await this.attemptExecutorVerificationFix(
|
|
69773
|
+
task,
|
|
69774
|
+
worktreePath,
|
|
69775
|
+
{
|
|
69776
|
+
command: failedCommand,
|
|
69777
|
+
exitCode: failedResult.exitCode,
|
|
69778
|
+
output: failureOutput,
|
|
69779
|
+
type: failedType
|
|
69780
|
+
},
|
|
69781
|
+
settings,
|
|
69782
|
+
attempt,
|
|
69783
|
+
maxFixRetries
|
|
69784
|
+
);
|
|
69785
|
+
if (fixed) {
|
|
69786
|
+
fixSucceeded = true;
|
|
69787
|
+
executorLog.log(`${task.id}: [verification] fix agent succeeded on attempt ${attempt}/${maxFixRetries}`);
|
|
69788
|
+
await this.store.logEntry(
|
|
69789
|
+
task.id,
|
|
69790
|
+
`[verification] Fix agent succeeded on attempt ${attempt}/${maxFixRetries}. Verification now passing.`,
|
|
69791
|
+
void 0,
|
|
69792
|
+
this.currentRunContext
|
|
69793
|
+
);
|
|
69794
|
+
break;
|
|
69795
|
+
}
|
|
69796
|
+
executorLog.log(`${task.id}: [verification] fix agent attempt ${attempt}/${maxFixRetries} failed`);
|
|
69797
|
+
await this.store.logEntry(
|
|
69798
|
+
task.id,
|
|
69799
|
+
`[verification] Fix agent attempt ${attempt}/${maxFixRetries} failed`,
|
|
69800
|
+
void 0,
|
|
69801
|
+
this.currentRunContext
|
|
69802
|
+
);
|
|
69803
|
+
}
|
|
69804
|
+
if (!fixSucceeded) {
|
|
69805
|
+
executorLog.log(`${task.id}: [verification] all fix attempts exhausted (${maxFixRetries}/${maxFixRetries}) \u2014 sending task back`);
|
|
69806
|
+
await this.sendTaskBackForFix(
|
|
69807
|
+
task,
|
|
69808
|
+
worktreePath,
|
|
69809
|
+
`${failedType} command \`${failedCommand}\` failed (exit ${failedResult.exitCode}) after ${maxFixRetries} fix attempts:
|
|
69810
|
+
${summary}`,
|
|
69811
|
+
`Verification (${failedType})`,
|
|
69812
|
+
`Deterministic verification failed after ${maxFixRetries} fix attempts`
|
|
69813
|
+
);
|
|
69814
|
+
return;
|
|
69815
|
+
}
|
|
69816
|
+
}
|
|
69817
|
+
}
|
|
69818
|
+
}
|
|
69362
69819
|
if (executionMode !== "fast") {
|
|
69363
69820
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
69364
69821
|
if (workflowResult === "deferred-paused") {
|
|
@@ -69447,7 +69904,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69447
69904
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
69448
69905
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
69449
69906
|
}
|
|
69450
|
-
if (worktreePath &&
|
|
69907
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69451
69908
|
try {
|
|
69452
69909
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69453
69910
|
await audit.git({ type: "worktree:remove", target: worktreePath });
|
|
@@ -69506,7 +69963,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69506
69963
|
try {
|
|
69507
69964
|
const latestTask = await this.store.getTask(task.id);
|
|
69508
69965
|
await this.resetStepsIfWorkLost(latestTask);
|
|
69509
|
-
if (worktreePath &&
|
|
69966
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69510
69967
|
try {
|
|
69511
69968
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69512
69969
|
} catch (wtErr) {
|
|
@@ -69618,7 +70075,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69618
70075
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
69619
70076
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
69620
70077
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
69621
|
-
const isResuming = !!task.sessionFile &&
|
|
70078
|
+
const isResuming = !!task.sessionFile && existsSync29(task.sessionFile);
|
|
69622
70079
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
69623
70080
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
69624
70081
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -69648,7 +70105,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69648
70105
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
69649
70106
|
taskId: task.id,
|
|
69650
70107
|
taskTitle: detail.title,
|
|
69651
|
-
onFallbackModelUsed:
|
|
70108
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
70109
|
+
agent: "executor",
|
|
70110
|
+
label: "executor",
|
|
70111
|
+
store: this.store,
|
|
70112
|
+
taskId: task.id,
|
|
70113
|
+
taskTitle: detail.title
|
|
70114
|
+
})
|
|
69652
70115
|
});
|
|
69653
70116
|
if (isResuming) {
|
|
69654
70117
|
executorLog.log(`${task.id}: resumed session from ${task.sessionFile}`);
|
|
@@ -70082,7 +70545,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70082
70545
|
this.options.onComplete?.(task);
|
|
70083
70546
|
} else {
|
|
70084
70547
|
executorLog.log(`${task.id} paused \u2014 moving to todo`);
|
|
70085
|
-
if (worktreePath &&
|
|
70548
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70086
70549
|
try {
|
|
70087
70550
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70088
70551
|
executorLog.log(`Removed old worktree for paused task: ${worktreePath}`);
|
|
@@ -70178,7 +70641,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70178
70641
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
70179
70642
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
70180
70643
|
}
|
|
70181
|
-
if (worktreePath &&
|
|
70644
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70182
70645
|
try {
|
|
70183
70646
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70184
70647
|
executorLog.log(`Removed old worktree for transient retry: ${worktreePath}`);
|
|
@@ -70233,7 +70696,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70233
70696
|
try {
|
|
70234
70697
|
const latestTask = await this.store.getTask(task.id);
|
|
70235
70698
|
await this.resetStepsIfWorkLost(latestTask);
|
|
70236
|
-
if (worktreePath &&
|
|
70699
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70237
70700
|
try {
|
|
70238
70701
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70239
70702
|
executorLog.log(`Removed old worktree for stuck-killed retry: ${worktreePath}`);
|
|
@@ -70738,7 +71201,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
70738
71201
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70739
71202
|
*/
|
|
70740
71203
|
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
70741
|
-
const promptPath =
|
|
71204
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70742
71205
|
let content;
|
|
70743
71206
|
try {
|
|
70744
71207
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70792,6 +71255,217 @@ ${feedback}
|
|
|
70792
71255
|
*
|
|
70793
71256
|
* @returns true if a retry was scheduled, false if retries are exhausted
|
|
70794
71257
|
*/
|
|
71258
|
+
/**
|
|
71259
|
+
* Run deterministic verification (test + build commands) in the task's worktree.
|
|
71260
|
+
* Returns a structured result indicating whether all commands passed.
|
|
71261
|
+
*/
|
|
71262
|
+
async runExecutorDeterministicVerification(task, worktreePath, settings) {
|
|
71263
|
+
const testCommand = settings.testCommand?.trim();
|
|
71264
|
+
const buildCommand2 = settings.buildCommand?.trim();
|
|
71265
|
+
if (!testCommand && !buildCommand2) {
|
|
71266
|
+
executorLog.log(`${task.id}: no test/build commands configured \u2014 skipping verification`);
|
|
71267
|
+
return { allPassed: true };
|
|
71268
|
+
}
|
|
71269
|
+
const parts = [];
|
|
71270
|
+
if (testCommand) parts.push(`test: ${testCommand}`);
|
|
71271
|
+
if (buildCommand2) parts.push(`build: ${buildCommand2}`);
|
|
71272
|
+
executorLog.log(`${task.id}: [verification] running deterministic verification (${parts.join(", ")})`);
|
|
71273
|
+
await this.store.logEntry(
|
|
71274
|
+
task.id,
|
|
71275
|
+
`[verification] Running deterministic verification (${parts.join(", ")})`,
|
|
71276
|
+
void 0,
|
|
71277
|
+
this.currentRunContext
|
|
71278
|
+
);
|
|
71279
|
+
const result = { allPassed: true };
|
|
71280
|
+
if (testCommand) {
|
|
71281
|
+
const testResult = await runVerificationCommand(
|
|
71282
|
+
this.store,
|
|
71283
|
+
worktreePath,
|
|
71284
|
+
task.id,
|
|
71285
|
+
testCommand,
|
|
71286
|
+
"test",
|
|
71287
|
+
void 0,
|
|
71288
|
+
executorLog,
|
|
71289
|
+
"executor"
|
|
71290
|
+
);
|
|
71291
|
+
result.testResult = testResult;
|
|
71292
|
+
if (!testResult.success) {
|
|
71293
|
+
result.allPassed = false;
|
|
71294
|
+
result.failedCommand = "testCommand";
|
|
71295
|
+
executorLog.log(`${task.id}: [verification] test failed (exit ${testResult.exitCode})`);
|
|
71296
|
+
return result;
|
|
71297
|
+
}
|
|
71298
|
+
}
|
|
71299
|
+
if (buildCommand2) {
|
|
71300
|
+
const buildResult = await runVerificationCommand(
|
|
71301
|
+
this.store,
|
|
71302
|
+
worktreePath,
|
|
71303
|
+
task.id,
|
|
71304
|
+
buildCommand2,
|
|
71305
|
+
"build",
|
|
71306
|
+
void 0,
|
|
71307
|
+
executorLog,
|
|
71308
|
+
"executor"
|
|
71309
|
+
);
|
|
71310
|
+
result.buildResult = buildResult;
|
|
71311
|
+
if (!buildResult.success) {
|
|
71312
|
+
result.allPassed = false;
|
|
71313
|
+
result.failedCommand = "buildCommand";
|
|
71314
|
+
executorLog.log(`${task.id}: [verification] build failed (exit ${buildResult.exitCode})`);
|
|
71315
|
+
return result;
|
|
71316
|
+
}
|
|
71317
|
+
}
|
|
71318
|
+
executorLog.log(`${task.id}: [verification] passed`);
|
|
71319
|
+
await this.store.logEntry(
|
|
71320
|
+
task.id,
|
|
71321
|
+
`[verification] Deterministic verification passed`,
|
|
71322
|
+
void 0,
|
|
71323
|
+
this.currentRunContext
|
|
71324
|
+
);
|
|
71325
|
+
return result;
|
|
71326
|
+
}
|
|
71327
|
+
/**
|
|
71328
|
+
* Attempt to fix verification failures by spawning a dedicated AI fix agent.
|
|
71329
|
+
* Follows the pattern established by the merger's attemptInMergeVerificationFix.
|
|
71330
|
+
* Returns true if verification passes after the fix attempt, false otherwise.
|
|
71331
|
+
*/
|
|
71332
|
+
async attemptExecutorVerificationFix(task, worktreePath, failureContext, settings, retryNumber, maxRetries) {
|
|
71333
|
+
try {
|
|
71334
|
+
executorLog.log(`${task.id}: spawning executor verification fix agent (attempt ${retryNumber}/${maxRetries})`);
|
|
71335
|
+
const logger2 = new AgentLogger({
|
|
71336
|
+
store: this.store,
|
|
71337
|
+
taskId: task.id,
|
|
71338
|
+
agent: "executor",
|
|
71339
|
+
persistAgentToolOutput: settings.persistAgentToolOutput,
|
|
71340
|
+
onAgentText: this.options.onAgentText,
|
|
71341
|
+
onAgentTool: this.options.onAgentTool
|
|
71342
|
+
});
|
|
71343
|
+
let skillContext;
|
|
71344
|
+
if (this.options.agentStore) {
|
|
71345
|
+
try {
|
|
71346
|
+
skillContext = await buildSessionSkillContext({
|
|
71347
|
+
agentStore: this.options.agentStore,
|
|
71348
|
+
task,
|
|
71349
|
+
sessionPurpose: "executor",
|
|
71350
|
+
projectRootDir: worktreePath,
|
|
71351
|
+
pluginRunner: this.options.pluginRunner
|
|
71352
|
+
});
|
|
71353
|
+
} catch {
|
|
71354
|
+
}
|
|
71355
|
+
}
|
|
71356
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair2(
|
|
71357
|
+
task.modelProvider,
|
|
71358
|
+
task.modelId,
|
|
71359
|
+
settings
|
|
71360
|
+
);
|
|
71361
|
+
const { session } = await createResolvedAgentSession({
|
|
71362
|
+
sessionPurpose: "executor",
|
|
71363
|
+
pluginRunner: this.options.pluginRunner,
|
|
71364
|
+
cwd: worktreePath,
|
|
71365
|
+
// Run in the task's worktree
|
|
71366
|
+
systemPrompt: `You are a verification fix agent running during task execution in a worktree.
|
|
71367
|
+
|
|
71368
|
+
All step-session steps completed successfully but the deterministic verification command failed. Your job is to fix the failing code directly in the working directory.
|
|
71369
|
+
|
|
71370
|
+
## Scope
|
|
71371
|
+
Only fix what is required to make the failing verification pass.
|
|
71372
|
+
Do not refactor, rename broadly, or make opportunistic improvements.
|
|
71373
|
+
|
|
71374
|
+
## Rules
|
|
71375
|
+
1. Read the error output carefully to understand what is failing before editing anything
|
|
71376
|
+
2. Before assuming a code fix is needed, check whether the failure is caused by stale/missing build artifacts in a sibling workspace package \u2014 typical signatures: \`Failed to resolve import "./X.js"\` pointing into another package's \`dist/\`, \`Cannot find module\`, or \`ERR_MODULE_NOT_FOUND\` referencing a workspace-internal path. In that case, rebuild the affected package(s) (e.g. \`pnpm --filter <pkg> build\`, or \`pnpm --filter "<scope>/*" build\` for a group) and re-run verification before editing source files.
|
|
71377
|
+
3. Make targeted fixes to the failing code path
|
|
71378
|
+
4. After fixing, run the verification command to confirm the fix works
|
|
71379
|
+
5. Do NOT make any git commits \u2014 just fix the code
|
|
71380
|
+
6. You MAY modify any files needed to make the verification pass, including files unrelated to this task's original change. Pre-existing build/test breakage is in scope: fix it. Prefer the smallest change that makes verification green.
|
|
71381
|
+
7. If you cannot fix the issue within scope, explain why and what evidence indicates a deeper/root problem`,
|
|
71382
|
+
tools: "coding",
|
|
71383
|
+
onText: logger2.onText,
|
|
71384
|
+
onThinking: logger2.onThinking,
|
|
71385
|
+
onToolStart: logger2.onToolStart,
|
|
71386
|
+
onToolEnd: logger2.onToolEnd,
|
|
71387
|
+
defaultProvider: executorProvider,
|
|
71388
|
+
defaultModelId: executorModelId,
|
|
71389
|
+
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
71390
|
+
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
71391
|
+
});
|
|
71392
|
+
await this.store.logEntry(
|
|
71393
|
+
task.id,
|
|
71394
|
+
`Executor verification fix agent started (model: ${describeModel(session)}, attempt ${retryNumber}/${maxRetries})`,
|
|
71395
|
+
void 0,
|
|
71396
|
+
this.currentRunContext
|
|
71397
|
+
);
|
|
71398
|
+
await this.store.appendAgentLog(
|
|
71399
|
+
task.id,
|
|
71400
|
+
`Fix agent started (model: ${describeModel(session)}, attempt ${retryNumber}/${maxRetries})`,
|
|
71401
|
+
"text",
|
|
71402
|
+
void 0,
|
|
71403
|
+
"executor"
|
|
71404
|
+
);
|
|
71405
|
+
try {
|
|
71406
|
+
const fixPrompt = `Fix the failing ${failureContext.type} verification for task ${task.id}.
|
|
71407
|
+
|
|
71408
|
+
## Failed command
|
|
71409
|
+
Command: \`${failureContext.command}\`
|
|
71410
|
+
Exit code: ${failureContext.exitCode}
|
|
71411
|
+
|
|
71412
|
+
## Error output
|
|
71413
|
+
${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
71414
|
+
|
|
71415
|
+
## Instructions
|
|
71416
|
+
1. Read the error output and identify the root cause
|
|
71417
|
+
2. Make targeted fixes to resolve the failure
|
|
71418
|
+
3. Run the verification command \`${failureContext.command}\` to confirm your fix works
|
|
71419
|
+
4. If the fix doesn't work, try a different approach
|
|
71420
|
+
5. Do NOT make any git commits`;
|
|
71421
|
+
await withRateLimitRetry(async () => {
|
|
71422
|
+
await promptWithFallback(session, fixPrompt);
|
|
71423
|
+
}, {
|
|
71424
|
+
onRetry: (attempt, delayMs, error) => {
|
|
71425
|
+
const delaySec = Math.round(delayMs / 1e3);
|
|
71426
|
+
executorLog.warn(`\u23F3 ${task.id} executor fix agent rate limited \u2014 retry ${attempt} in ${delaySec}s: ${error.message}`);
|
|
71427
|
+
}
|
|
71428
|
+
});
|
|
71429
|
+
await accumulateSessionTokenUsage(this.store, task.id, session);
|
|
71430
|
+
executorLog.log(`${task.id}: re-running deterministic verification after fix attempt ${retryNumber}/${maxRetries}`);
|
|
71431
|
+
await this.store.logEntry(
|
|
71432
|
+
task.id,
|
|
71433
|
+
`Re-running deterministic verification (attempt ${retryNumber}/${maxRetries})`,
|
|
71434
|
+
void 0,
|
|
71435
|
+
this.currentRunContext
|
|
71436
|
+
);
|
|
71437
|
+
await this.store.appendAgentLog(
|
|
71438
|
+
task.id,
|
|
71439
|
+
`Re-running verification (attempt ${retryNumber}/${maxRetries})`,
|
|
71440
|
+
"text",
|
|
71441
|
+
void 0,
|
|
71442
|
+
"executor"
|
|
71443
|
+
);
|
|
71444
|
+
const reRunResult = await this.runExecutorDeterministicVerification(task, worktreePath, settings);
|
|
71445
|
+
return reRunResult.allPassed;
|
|
71446
|
+
} finally {
|
|
71447
|
+
await logger2.flush();
|
|
71448
|
+
await session.dispose();
|
|
71449
|
+
}
|
|
71450
|
+
} catch (err) {
|
|
71451
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
71452
|
+
executorLog.warn(`${task.id}: executor verification fix agent error: ${errorMessage}`);
|
|
71453
|
+
await this.store.logEntry(
|
|
71454
|
+
task.id,
|
|
71455
|
+
`Executor verification fix agent encountered an error`,
|
|
71456
|
+
errorMessage,
|
|
71457
|
+
this.currentRunContext
|
|
71458
|
+
);
|
|
71459
|
+
await this.store.appendAgentLog(
|
|
71460
|
+
task.id,
|
|
71461
|
+
"Fix agent encountered an error",
|
|
71462
|
+
"tool_error",
|
|
71463
|
+
errorMessage,
|
|
71464
|
+
"executor"
|
|
71465
|
+
);
|
|
71466
|
+
return false;
|
|
71467
|
+
}
|
|
71468
|
+
}
|
|
70795
71469
|
async handleWorkflowStepFailure(task, worktreePath, failureFeedback, stepName) {
|
|
70796
71470
|
this.clearCompletedTaskWatchdog(task.id);
|
|
70797
71471
|
const currentRetries = task.workflowStepRetries ?? 0;
|
|
@@ -70861,7 +71535,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
70861
71535
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70862
71536
|
*/
|
|
70863
71537
|
async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount) {
|
|
70864
|
-
const promptPath =
|
|
71538
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70865
71539
|
let content;
|
|
70866
71540
|
try {
|
|
70867
71541
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70926,31 +71600,33 @@ ${failureFeedback}
|
|
|
70926
71600
|
* Uses git diff against the stored baseCommitSha to determine what changed.
|
|
70927
71601
|
* Returns an empty array if no changes or if git commands fail.
|
|
70928
71602
|
*/
|
|
71603
|
+
async resolveDiffBaseRef(worktreePath, baseCommitSha) {
|
|
71604
|
+
if (baseCommitSha) return baseCommitSha;
|
|
71605
|
+
try {
|
|
71606
|
+
const { stdout } = await execAsync5(
|
|
71607
|
+
"git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main",
|
|
71608
|
+
{ cwd: worktreePath, encoding: "utf-8" }
|
|
71609
|
+
);
|
|
71610
|
+
const ref = stdout.trim();
|
|
71611
|
+
if (ref) return ref;
|
|
71612
|
+
} catch (mergeBaseErr) {
|
|
71613
|
+
const mergeBaseMsg = mergeBaseErr instanceof Error ? mergeBaseErr.message : String(mergeBaseErr);
|
|
71614
|
+
executorLog.warn(`Failed merge-base lookup for diff base in ${worktreePath}, trying HEAD~1 fallback: ${mergeBaseMsg}`);
|
|
71615
|
+
}
|
|
71616
|
+
try {
|
|
71617
|
+
const { stdout } = await execAsync5("git rev-parse HEAD~1", {
|
|
71618
|
+
cwd: worktreePath,
|
|
71619
|
+
encoding: "utf-8"
|
|
71620
|
+
});
|
|
71621
|
+
return stdout.trim() || void 0;
|
|
71622
|
+
} catch {
|
|
71623
|
+
executorLog.log(`Could not determine base commit for diff in ${worktreePath}`);
|
|
71624
|
+
return void 0;
|
|
71625
|
+
}
|
|
71626
|
+
}
|
|
70929
71627
|
async captureModifiedFiles(worktreePath, baseCommitSha) {
|
|
70930
71628
|
try {
|
|
70931
|
-
|
|
70932
|
-
if (!baseRef) {
|
|
70933
|
-
try {
|
|
70934
|
-
const { stdout: stdout2 } = await execAsync5("git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main", {
|
|
70935
|
-
cwd: worktreePath,
|
|
70936
|
-
encoding: "utf-8"
|
|
70937
|
-
});
|
|
70938
|
-
baseRef = stdout2.trim();
|
|
70939
|
-
} catch (mergeBaseErr) {
|
|
70940
|
-
const mergeBaseMsg = mergeBaseErr instanceof Error ? mergeBaseErr.message : String(mergeBaseErr);
|
|
70941
|
-
executorLog.warn(`Failed merge-base lookup for diff base in ${worktreePath}, trying HEAD~1 fallback: ${mergeBaseMsg}`);
|
|
70942
|
-
try {
|
|
70943
|
-
const { stdout: stdout2 } = await execAsync5("git rev-parse HEAD~1", {
|
|
70944
|
-
cwd: worktreePath,
|
|
70945
|
-
encoding: "utf-8"
|
|
70946
|
-
});
|
|
70947
|
-
baseRef = stdout2.trim();
|
|
70948
|
-
} catch {
|
|
70949
|
-
executorLog.log(`Could not determine base commit for diff in ${worktreePath}`);
|
|
70950
|
-
return [];
|
|
70951
|
-
}
|
|
70952
|
-
}
|
|
70953
|
-
}
|
|
71629
|
+
const baseRef = await this.resolveDiffBaseRef(worktreePath, baseCommitSha);
|
|
70954
71630
|
if (!baseRef) {
|
|
70955
71631
|
return [];
|
|
70956
71632
|
}
|
|
@@ -71186,6 +71862,30 @@ ${failureFeedback}
|
|
|
71186
71862
|
*/
|
|
71187
71863
|
async executeWorkflowStep(task, workflowStep, worktreePath, settings) {
|
|
71188
71864
|
const toolMode = workflowStep.toolMode || "readonly";
|
|
71865
|
+
const scopedFiles = await this.captureModifiedFiles(worktreePath, task.baseCommitSha);
|
|
71866
|
+
let diffShortstat;
|
|
71867
|
+
try {
|
|
71868
|
+
const baseRef = await this.resolveDiffBaseRef(worktreePath, task.baseCommitSha);
|
|
71869
|
+
if (baseRef) {
|
|
71870
|
+
const { stdout } = await execAsync5(`git diff --shortstat ${baseRef}..HEAD`, {
|
|
71871
|
+
cwd: worktreePath,
|
|
71872
|
+
encoding: "utf-8"
|
|
71873
|
+
});
|
|
71874
|
+
diffShortstat = stdout.trim() || void 0;
|
|
71875
|
+
}
|
|
71876
|
+
} catch {
|
|
71877
|
+
}
|
|
71878
|
+
const MAX_SCOPE_FILES = 100;
|
|
71879
|
+
const scopeFileBlock = scopedFiles.length === 0 ? "(no modified files detected for this task \u2014 review the worktree directly, but do NOT browse unrelated files)" : scopedFiles.length > MAX_SCOPE_FILES ? `${scopedFiles.slice(0, MAX_SCOPE_FILES).map((f) => `- ${f}`).join("\n")}
|
|
71880
|
+
- ... (${scopedFiles.length - MAX_SCOPE_FILES} more files truncated)` : scopedFiles.map((f) => `- ${f}`).join("\n");
|
|
71881
|
+
const scopeBlock = `Diff Scope (files changed by THIS task vs base):
|
|
71882
|
+
${scopeFileBlock}${diffShortstat ? `
|
|
71883
|
+
Diff stat: ${diffShortstat}` : ""}
|
|
71884
|
+
|
|
71885
|
+
CRITICAL SCOPING RULES \u2014 read before doing anything else:
|
|
71886
|
+
- Review ONLY the files listed above. Do NOT analyze unmodified files or unrelated parts of the codebase.
|
|
71887
|
+
- If NONE of the files in the diff scope are relevant to your review category (e.g. a UX/design reviewer with no UI/CSS/component files in scope, a security reviewer with no auth/network code in scope, an a11y reviewer with no markup changes), respond IMMEDIATELY with a single short approval line such as "No relevant changes in scope \u2014 approved." and STOP. Do not start exploring the codebase.
|
|
71888
|
+
- Your wall-clock budget is short. Spending it browsing unmodified files will cause this step to time out and block merge.`;
|
|
71189
71889
|
const systemPrompt = `You are a workflow step agent executing: ${workflowStep.name}
|
|
71190
71890
|
|
|
71191
71891
|
Task Context:
|
|
@@ -71193,6 +71893,8 @@ Task Context:
|
|
|
71193
71893
|
- Task Description: ${task.description}
|
|
71194
71894
|
- Worktree: ${worktreePath}
|
|
71195
71895
|
|
|
71896
|
+
${scopeBlock}
|
|
71897
|
+
|
|
71196
71898
|
Your role:
|
|
71197
71899
|
- Execute this workflow step exactly as scoped.
|
|
71198
71900
|
- Prioritize high-impact correctness/risk findings over stylistic nits.
|
|
@@ -71661,7 +72363,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71661
72363
|
* rather than fail the task permanently.
|
|
71662
72364
|
*/
|
|
71663
72365
|
async resolveWorktreeStartPoint(startPoint, taskId) {
|
|
71664
|
-
const command = isAbsolute11(startPoint) &&
|
|
72366
|
+
const command = isAbsolute11(startPoint) && existsSync29(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
|
|
71665
72367
|
try {
|
|
71666
72368
|
const { stdout } = await execAsync5(command, { cwd: this.rootDir });
|
|
71667
72369
|
return stdout.trim() || startPoint;
|
|
@@ -71681,7 +72383,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71681
72383
|
*/
|
|
71682
72384
|
async tryCreateWorktree(branch, path2, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0) {
|
|
71683
72385
|
await this.assertWorktreePathNotNested(path2, taskId);
|
|
71684
|
-
if (
|
|
72386
|
+
if (existsSync29(path2)) {
|
|
71685
72387
|
const isRegistered = await this.isRegisteredWorktree(path2);
|
|
71686
72388
|
if (!isRegistered) {
|
|
71687
72389
|
await this.store.logEntry(
|
|
@@ -71832,7 +72534,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71832
72534
|
);
|
|
71833
72535
|
if (shouldGenerateNewName) {
|
|
71834
72536
|
const conflictStartPoint = branch;
|
|
71835
|
-
const newPath =
|
|
72537
|
+
const newPath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
71836
72538
|
for (let suffix = 2; suffix <= 6; suffix++) {
|
|
71837
72539
|
const suffixedBranch = `${branch}-${suffix}`;
|
|
71838
72540
|
try {
|
|
@@ -72432,7 +73134,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
72432
73134
|
metadata: { type: "spawned", parentTaskId: taskId }
|
|
72433
73135
|
});
|
|
72434
73136
|
const childWorktreeName = generateWorktreeName(this.rootDir);
|
|
72435
|
-
const childWorktreePath =
|
|
73137
|
+
const childWorktreePath = join36(this.rootDir, ".worktrees", childWorktreeName);
|
|
72436
73138
|
const childBranch = `fusion/spawn-${agent.id}`;
|
|
72437
73139
|
await this.createWorktree(childBranch, childWorktreePath, taskId, worktreePath);
|
|
72438
73140
|
await this.options.agentStore.updateAgentState(agent.id, "active");
|
|
@@ -72621,9 +73323,9 @@ var init_node_routing_policy = __esm({
|
|
|
72621
73323
|
});
|
|
72622
73324
|
|
|
72623
73325
|
// ../engine/src/scheduler.ts
|
|
72624
|
-
import { existsSync as
|
|
73326
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
72625
73327
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
72626
|
-
import { basename as basename8, join as
|
|
73328
|
+
import { basename as basename8, join as join37 } from "node:path";
|
|
72627
73329
|
function pathsOverlap2(a, b) {
|
|
72628
73330
|
for (const pa of a) {
|
|
72629
73331
|
const prefixA = pa.endsWith("/*") ? pa.slice(0, -1) : null;
|
|
@@ -72793,12 +73495,12 @@ var init_scheduler = __esm({
|
|
|
72793
73495
|
* @returns Object with `valid: true` if checks pass, or `valid: false` with a `reason` string if they fail
|
|
72794
73496
|
*/
|
|
72795
73497
|
async validateTaskFilesystem(id) {
|
|
72796
|
-
const taskDir =
|
|
72797
|
-
if (!
|
|
73498
|
+
const taskDir = join37(this.store.getTasksDir(), id);
|
|
73499
|
+
if (!existsSync30(taskDir)) {
|
|
72798
73500
|
return { valid: false, reason: "missing directory" };
|
|
72799
73501
|
}
|
|
72800
|
-
const promptPath =
|
|
72801
|
-
if (!
|
|
73502
|
+
const promptPath = join37(taskDir, "PROMPT.md");
|
|
73503
|
+
if (!existsSync30(promptPath)) {
|
|
72802
73504
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
72803
73505
|
}
|
|
72804
73506
|
try {
|
|
@@ -72937,7 +73639,7 @@ var init_scheduler = __esm({
|
|
|
72937
73639
|
break;
|
|
72938
73640
|
}
|
|
72939
73641
|
reservedNames.add(worktreeName);
|
|
72940
|
-
return
|
|
73642
|
+
return join37(this.store.getRootDir(), ".worktrees", worktreeName);
|
|
72941
73643
|
}
|
|
72942
73644
|
/**
|
|
72943
73645
|
* Run one scheduling pass.
|
|
@@ -74214,7 +74916,7 @@ var init_mission_execution_loop = __esm({
|
|
|
74214
74916
|
init_pi();
|
|
74215
74917
|
init_agent_session_helpers();
|
|
74216
74918
|
init_logger2();
|
|
74217
|
-
|
|
74919
|
+
init_fallback_model_observer();
|
|
74218
74920
|
loopLog = createLogger2("mission-loop");
|
|
74219
74921
|
VALIDATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
74220
74922
|
MissionExecutionLoop = class extends EventEmitter17 {
|
|
@@ -74448,7 +75150,13 @@ Assertions: ${assertions.map((a) => a.title).join(", ")}`,
|
|
|
74448
75150
|
},
|
|
74449
75151
|
taskId: task?.id,
|
|
74450
75152
|
taskTitle: task?.title,
|
|
74451
|
-
onFallbackModelUsed:
|
|
75153
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
75154
|
+
agent: "reviewer",
|
|
75155
|
+
label: "mission validator",
|
|
75156
|
+
store: this.taskStore,
|
|
75157
|
+
taskId: task?.id,
|
|
75158
|
+
taskTitle: task?.title
|
|
75159
|
+
})
|
|
74452
75160
|
});
|
|
74453
75161
|
session = { session: sessionResult.session, sessionFile: sessionResult.sessionFile };
|
|
74454
75162
|
loopLog.log(`Validation session created for feature ${feature.id}`);
|
|
@@ -76256,7 +76964,7 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
76256
76964
|
};
|
|
76257
76965
|
}
|
|
76258
76966
|
};
|
|
76259
|
-
const { createResolvedAgentSession: createResolvedAgentSession2, extractRuntimeHint: extractRuntimeHint2 } = await Promise.resolve().then(() => (init_agent_session_helpers(), agent_session_helpers_exports));
|
|
76967
|
+
const { createResolvedAgentSession: createResolvedAgentSession2, extractRuntimeHint: extractRuntimeHint2, extractRuntimeModel: extractRuntimeModel2 } = await Promise.resolve().then(() => (init_agent_session_helpers(), agent_session_helpers_exports));
|
|
76260
76968
|
const { buildSessionSkillContextSync: buildSessionSkillContextSync2 } = await Promise.resolve().then(() => (init_session_skill_context(), session_skill_context_exports));
|
|
76261
76969
|
let heartbeatTools;
|
|
76262
76970
|
if (isNoTaskRun) {
|
|
@@ -76335,8 +77043,10 @@ not loop on the same plan across heartbeats without recording why.`;
|
|
|
76335
77043
|
systemPrompt,
|
|
76336
77044
|
tools: "readonly",
|
|
76337
77045
|
customTools: heartbeatTools,
|
|
76338
|
-
|
|
76339
|
-
|
|
77046
|
+
...(() => {
|
|
77047
|
+
const { provider, modelId } = extractRuntimeModel2(agent.runtimeConfig);
|
|
77048
|
+
return { defaultProvider: provider, defaultModelId: modelId };
|
|
77049
|
+
})(),
|
|
76340
77050
|
onText: (delta) => {
|
|
76341
77051
|
outputLength += delta.length;
|
|
76342
77052
|
appendStdoutExcerpt(delta);
|
|
@@ -77711,7 +78421,7 @@ async function createAiPromptExecutor(cwd) {
|
|
|
77711
78421
|
}
|
|
77712
78422
|
};
|
|
77713
78423
|
}
|
|
77714
|
-
function
|
|
78424
|
+
function truncateOutput2(stdout, stderr) {
|
|
77715
78425
|
const out = stdout ?? "";
|
|
77716
78426
|
const err = stderr ?? "";
|
|
77717
78427
|
let combined = out;
|
|
@@ -77891,7 +78601,7 @@ var init_cron_runner = __esm({
|
|
|
77891
78601
|
maxBuffer: MAX_BUFFER,
|
|
77892
78602
|
shell: defaultShell
|
|
77893
78603
|
});
|
|
77894
|
-
const output =
|
|
78604
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77895
78605
|
log15.log(`\u2713 ${schedule.name} completed (${output.length} bytes output)`);
|
|
77896
78606
|
return {
|
|
77897
78607
|
success: true,
|
|
@@ -77902,7 +78612,7 @@ var init_cron_runner = __esm({
|
|
|
77902
78612
|
} catch (err) {
|
|
77903
78613
|
const stdout = err.stdout ?? "";
|
|
77904
78614
|
const stderr = err.stderr ?? "";
|
|
77905
|
-
const output =
|
|
78615
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77906
78616
|
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6) / 1e3}s` : err.message ?? String(err);
|
|
77907
78617
|
log15.warn(`\u2717 ${schedule.name} failed: ${errorMessage}`);
|
|
77908
78618
|
return {
|
|
@@ -77947,7 +78657,7 @@ var init_cron_runner = __esm({
|
|
|
77947
78657
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
77948
78658
|
return {
|
|
77949
78659
|
success: result.success,
|
|
77950
|
-
output:
|
|
78660
|
+
output: truncateOutput2(result.output ?? "", ""),
|
|
77951
78661
|
error: result.success ? void 0 : result.output
|
|
77952
78662
|
};
|
|
77953
78663
|
} catch (err) {
|
|
@@ -77988,7 +78698,7 @@ var init_cron_runner = __esm({
|
|
|
77988
78698
|
if (sr.output) outputParts.push(sr.output);
|
|
77989
78699
|
if (sr.error) outputParts.push(`Error: ${sr.error}`);
|
|
77990
78700
|
}
|
|
77991
|
-
const output =
|
|
78701
|
+
const output = truncateOutput2(outputParts.join("\n"), "");
|
|
77992
78702
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
77993
78703
|
const error = failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0;
|
|
77994
78704
|
const status = overallSuccess ? "\u2713" : "\u2717";
|
|
@@ -78067,7 +78777,7 @@ var init_cron_runner = __esm({
|
|
|
78067
78777
|
stepName: step.name,
|
|
78068
78778
|
stepIndex,
|
|
78069
78779
|
success: true,
|
|
78070
|
-
output:
|
|
78780
|
+
output: truncateOutput2(stdout, stderr),
|
|
78071
78781
|
startedAt,
|
|
78072
78782
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78073
78783
|
};
|
|
@@ -78080,7 +78790,7 @@ var init_cron_runner = __esm({
|
|
|
78080
78790
|
stepName: step.name,
|
|
78081
78791
|
stepIndex,
|
|
78082
78792
|
success: false,
|
|
78083
|
-
output:
|
|
78793
|
+
output: truncateOutput2(stdout, stderr),
|
|
78084
78794
|
error: errorMessage,
|
|
78085
78795
|
startedAt,
|
|
78086
78796
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78230,7 +78940,7 @@ var init_cron_runner = __esm({
|
|
|
78230
78940
|
// ../engine/src/routine-runner.ts
|
|
78231
78941
|
import { exec as exec8 } from "node:child_process";
|
|
78232
78942
|
import { promisify as promisify8 } from "node:util";
|
|
78233
|
-
function
|
|
78943
|
+
function truncateOutput3(stdout, stderr) {
|
|
78234
78944
|
let output = stdout;
|
|
78235
78945
|
if (stderr) {
|
|
78236
78946
|
output += stdout ? "\n--- stderr ---\n" : "";
|
|
@@ -78413,7 +79123,7 @@ var init_routine_runner = __esm({
|
|
|
78413
79123
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
78414
79124
|
return {
|
|
78415
79125
|
success: result.success,
|
|
78416
|
-
output:
|
|
79126
|
+
output: truncateOutput3(result.output ?? "", ""),
|
|
78417
79127
|
error: result.success ? void 0 : result.output,
|
|
78418
79128
|
startedAt,
|
|
78419
79129
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78437,7 +79147,7 @@ var init_routine_runner = __esm({
|
|
|
78437
79147
|
});
|
|
78438
79148
|
return {
|
|
78439
79149
|
success: true,
|
|
78440
|
-
output:
|
|
79150
|
+
output: truncateOutput3(stdout, stderr),
|
|
78441
79151
|
startedAt,
|
|
78442
79152
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78443
79153
|
};
|
|
@@ -78448,7 +79158,7 @@ var init_routine_runner = __esm({
|
|
|
78448
79158
|
const error = errObj.killed === true ? `Command timed out after ${(timeoutMs ?? DEFAULT_TIMEOUT_MS7) / 1e3}s` : (err instanceof Error ? err.message : null) ?? String(err);
|
|
78449
79159
|
return {
|
|
78450
79160
|
success: false,
|
|
78451
|
-
output:
|
|
79161
|
+
output: truncateOutput3(stdout, stderr),
|
|
78452
79162
|
error,
|
|
78453
79163
|
startedAt,
|
|
78454
79164
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78481,7 +79191,7 @@ var init_routine_runner = __esm({
|
|
|
78481
79191
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
78482
79192
|
return {
|
|
78483
79193
|
success: overallSuccess,
|
|
78484
|
-
output:
|
|
79194
|
+
output: truncateOutput3(outputParts.join("\n"), ""),
|
|
78485
79195
|
error: failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0,
|
|
78486
79196
|
startedAt,
|
|
78487
79197
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -78516,7 +79226,7 @@ var init_routine_runner = __esm({
|
|
|
78516
79226
|
this.options.aiPromptExecutor(step.prompt, step.modelProvider, step.modelId),
|
|
78517
79227
|
new Promise((_resolve, reject) => setTimeout(() => reject(new Error(`AI prompt step timed out after ${timeoutMs / 1e3}s`)), timeoutMs))
|
|
78518
79228
|
]);
|
|
78519
|
-
return { stepId: step.id, stepName: step.name, stepIndex, success: true, output:
|
|
79229
|
+
return { stepId: step.id, stepName: step.name, stepIndex, success: true, output: truncateOutput3(output, ""), startedAt, completedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
78520
79230
|
} catch (err) {
|
|
78521
79231
|
return { stepId: step.id, stepName: step.name, stepIndex, success: false, output: "", error: err instanceof Error ? err.message : String(err), startedAt, completedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
78522
79232
|
}
|
|
@@ -79130,8 +79840,8 @@ var init_stuck_task_detector = __esm({
|
|
|
79130
79840
|
// ../engine/src/self-healing.ts
|
|
79131
79841
|
import { exec as exec9 } from "node:child_process";
|
|
79132
79842
|
import { promisify as promisify9 } from "node:util";
|
|
79133
|
-
import { existsSync as
|
|
79134
|
-
import { isAbsolute as isAbsolute13, join as
|
|
79843
|
+
import { existsSync as existsSync31, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
79844
|
+
import { isAbsolute as isAbsolute13, join as join38, relative as relative8, resolve as resolve17 } from "node:path";
|
|
79135
79845
|
function shellQuote(value) {
|
|
79136
79846
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
79137
79847
|
}
|
|
@@ -79527,7 +80237,7 @@ var init_self_healing = __esm({
|
|
|
79527
80237
|
return commit;
|
|
79528
80238
|
}
|
|
79529
80239
|
async cleanupInterruptedMergeArtifacts(task) {
|
|
79530
|
-
if (task.worktree &&
|
|
80240
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
79531
80241
|
try {
|
|
79532
80242
|
await execAsync7(`git worktree remove ${shellQuote(task.worktree)} --force`, {
|
|
79533
80243
|
cwd: this.options.rootDir,
|
|
@@ -80148,7 +80858,7 @@ var init_self_healing = __esm({
|
|
|
80148
80858
|
return false;
|
|
80149
80859
|
}
|
|
80150
80860
|
const staleness = now - new Date(t.updatedAt).getTime();
|
|
80151
|
-
const hasWorktree = t.worktree &&
|
|
80861
|
+
const hasWorktree = t.worktree && existsSync31(t.worktree);
|
|
80152
80862
|
const graceMs = hasWorktree ? ORPHANED_WITH_WORKTREE_GRACE_MS : ORPHANED_EXECUTION_RECOVERY_GRACE_MS;
|
|
80153
80863
|
return staleness >= graceMs;
|
|
80154
80864
|
});
|
|
@@ -80157,7 +80867,7 @@ var init_self_healing = __esm({
|
|
|
80157
80867
|
let recovered = 0;
|
|
80158
80868
|
for (const task of orphaned) {
|
|
80159
80869
|
try {
|
|
80160
|
-
const hadWorktree = task.worktree &&
|
|
80870
|
+
const hadWorktree = task.worktree && existsSync31(task.worktree);
|
|
80161
80871
|
const reason = hadWorktree ? "worktree exists but no active session" : "missing worktree/session";
|
|
80162
80872
|
await this.resetStepsIfWorkLost(task);
|
|
80163
80873
|
await this.store.updateTask(task.id, {
|
|
@@ -80303,7 +81013,7 @@ var init_self_healing = __esm({
|
|
|
80303
81013
|
}
|
|
80304
81014
|
}
|
|
80305
81015
|
async hasRecoverableGitWork(task) {
|
|
80306
|
-
if (task.worktree &&
|
|
81016
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
80307
81017
|
try {
|
|
80308
81018
|
const { stdout: status } = await execAsync7("git status --porcelain", {
|
|
80309
81019
|
cwd: task.worktree,
|
|
@@ -80488,11 +81198,11 @@ var init_self_healing = __esm({
|
|
|
80488
81198
|
* tracks registered idle worktrees, never these orphans.
|
|
80489
81199
|
*/
|
|
80490
81200
|
async reapUnregisteredOrphans() {
|
|
80491
|
-
const worktreesDir =
|
|
80492
|
-
if (!
|
|
81201
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81202
|
+
if (!existsSync31(worktreesDir)) return 0;
|
|
80493
81203
|
let dirs;
|
|
80494
81204
|
try {
|
|
80495
|
-
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
81205
|
+
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join38(worktreesDir, e.name));
|
|
80496
81206
|
} catch (err) {
|
|
80497
81207
|
log17.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
80498
81208
|
return 0;
|
|
@@ -80597,8 +81307,8 @@ var init_self_healing = __esm({
|
|
|
80597
81307
|
}
|
|
80598
81308
|
/** Remove oldest idle worktrees if total count exceeds 2× maxWorktrees. */
|
|
80599
81309
|
async enforceWorktreeCap() {
|
|
80600
|
-
const worktreesDir =
|
|
80601
|
-
if (!
|
|
81310
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81311
|
+
if (!existsSync31(worktreesDir)) return;
|
|
80602
81312
|
try {
|
|
80603
81313
|
const settings = await this.store.getSettings();
|
|
80604
81314
|
const cap = (settings.maxWorktrees ?? 4) * 2;
|
|
@@ -82436,7 +83146,7 @@ var init_ipc_host = __esm({
|
|
|
82436
83146
|
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
82437
83147
|
import { fork } from "node:child_process";
|
|
82438
83148
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
82439
|
-
import { dirname as dirname11, join as
|
|
83149
|
+
import { dirname as dirname11, join as join39 } from "node:path";
|
|
82440
83150
|
var HealthMonitor, ChildProcessRuntime;
|
|
82441
83151
|
var init_child_process_runtime = __esm({
|
|
82442
83152
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -82598,7 +83308,7 @@ var init_child_process_runtime = __esm({
|
|
|
82598
83308
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
82599
83309
|
const currentDir = dirname11(fileURLToPath3(import.meta.url));
|
|
82600
83310
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
82601
|
-
return
|
|
83311
|
+
return join39(currentDir, workerFile);
|
|
82602
83312
|
}
|
|
82603
83313
|
/**
|
|
82604
83314
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -86851,6 +87561,8 @@ __export(src_exports2, {
|
|
|
86851
87561
|
describeAgentModel: () => describeAgentModel,
|
|
86852
87562
|
describeModel: () => describeModel,
|
|
86853
87563
|
ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
|
|
87564
|
+
extractRuntimeHint: () => extractRuntimeHint,
|
|
87565
|
+
extractRuntimeModel: () => extractRuntimeModel,
|
|
86854
87566
|
formatTaskIdentifier: () => formatTaskIdentifier,
|
|
86855
87567
|
getDefaultPiRuntime: () => getDefaultPiRuntime,
|
|
86856
87568
|
getHostExtensionPaths: () => getHostExtensionPaths,
|
|
@@ -101409,7 +102121,7 @@ var init_auth_middleware = __esm({
|
|
|
101409
102121
|
|
|
101410
102122
|
// ../dashboard/src/server.ts
|
|
101411
102123
|
import express from "express";
|
|
101412
|
-
import { join as
|
|
102124
|
+
import { join as join40, dirname as dirname12 } from "node:path";
|
|
101413
102125
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
101414
102126
|
function clearAiSessionCleanupInterval() {
|
|
101415
102127
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -101701,8 +102413,8 @@ __export(task_exports, {
|
|
|
101701
102413
|
runTaskUpdate: () => runTaskUpdate
|
|
101702
102414
|
});
|
|
101703
102415
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
101704
|
-
import { watchFile, unwatchFile, statSync as statSync6, existsSync as
|
|
101705
|
-
import { basename as basename10, join as
|
|
102416
|
+
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync32, readFileSync as readFileSync11 } from "node:fs";
|
|
102417
|
+
import { basename as basename10, join as join41 } from "node:path";
|
|
101706
102418
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
101707
102419
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
101708
102420
|
const issueUrl = sourceMetadata.issueUrl;
|
|
@@ -102015,8 +102727,8 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102015
102727
|
printEntries(filteredEntries);
|
|
102016
102728
|
if (options.follow) {
|
|
102017
102729
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
102018
|
-
const logPath =
|
|
102019
|
-
if (!
|
|
102730
|
+
const logPath = join41(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
102731
|
+
if (!existsSync32(logPath)) {
|
|
102020
102732
|
console.log(`
|
|
102021
102733
|
Waiting for log file to be created...`);
|
|
102022
102734
|
}
|
|
@@ -102045,7 +102757,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102045
102757
|
lastPosition = 0;
|
|
102046
102758
|
}
|
|
102047
102759
|
if (stats.size > lastPosition) {
|
|
102048
|
-
const content =
|
|
102760
|
+
const content = readFileSync11(logPath, "utf-8");
|
|
102049
102761
|
const lines = content.slice(lastPosition).split("\n");
|
|
102050
102762
|
for (const line of lines) {
|
|
102051
102763
|
if (!line.trim()) continue;
|
|
@@ -103158,9 +103870,9 @@ init_src();
|
|
|
103158
103870
|
init_gh_cli();
|
|
103159
103871
|
import { Type as Type8 } from "typebox";
|
|
103160
103872
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
103161
|
-
import { resolve as resolve19, basename as basename11, extname as extname3, join as
|
|
103873
|
+
import { resolve as resolve19, basename as basename11, extname as extname3, join as join42 } from "node:path";
|
|
103162
103874
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
103163
|
-
import { existsSync as
|
|
103875
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
103164
103876
|
import { spawn as spawn11 } from "node:child_process";
|
|
103165
103877
|
var MIME_TYPES2 = {
|
|
103166
103878
|
".png": "image/png",
|
|
@@ -103180,7 +103892,7 @@ var MIME_TYPES2 = {
|
|
|
103180
103892
|
function resolveProjectRoot2(cwd) {
|
|
103181
103893
|
let current = resolve19(cwd);
|
|
103182
103894
|
while (true) {
|
|
103183
|
-
if (
|
|
103895
|
+
if (existsSync33(join42(current, ".fusion"))) {
|
|
103184
103896
|
return current;
|
|
103185
103897
|
}
|
|
103186
103898
|
const parent = resolve19(current, "..");
|
|
@@ -103201,7 +103913,7 @@ async function getStore2(cwd) {
|
|
|
103201
103913
|
return store;
|
|
103202
103914
|
}
|
|
103203
103915
|
function getFusionDir(cwd) {
|
|
103204
|
-
return
|
|
103916
|
+
return join42(resolveProjectRoot2(cwd), ".fusion");
|
|
103205
103917
|
}
|
|
103206
103918
|
async function validateAssignableAgentId(cwd, agentId) {
|
|
103207
103919
|
const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|