@runfusion/fusion 0.17.1 → 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 +1706 -787
- package/dist/client/assets/{AgentDetailView-BmxnuM0D.js → AgentDetailView-17J-F0Rl.js} +1 -1
- package/dist/client/assets/{AgentsView-1xSqjJxs.js → AgentsView-sbBkb7Wd.js} +3 -3
- package/dist/client/assets/ChatView-BR5cvK_B.js +1 -0
- package/dist/client/assets/{DevServerView-DIrmWI5T.js → DevServerView-GFFVXHVP.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-Sqwdifcb.js → DirectoryPicker-WPDSBdT6.js} +1 -1
- package/dist/client/assets/{DocumentsView-Cx_02o_z.js → DocumentsView-BHpDsIIt.js} +1 -1
- package/dist/client/assets/{InsightsView-DAJSq4gV.js → InsightsView-Bxu0TJkt.js} +1 -1
- package/dist/client/assets/{MemoryView-CCIBAre3.js → MemoryView-CmnzZorw.js} +1 -1
- package/dist/client/assets/{NodesView-D02HxGCl.js → NodesView-CO9_4hCr.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-DD0fTQNf.js → PiExtensionsManager-4e3MlD62.js} +2 -2
- package/dist/client/assets/{PluginManager-Cfl0VBX9.js → PluginManager-DGN2rvOY.js} +1 -1
- package/dist/client/assets/{ResearchView-B9RqOVbr.js → ResearchView-Dsa6Gykl.js} +1 -1
- package/dist/client/assets/{RoadmapsView-DsH7Hicx.js → RoadmapsView-jHTOK0RQ.js} +1 -1
- package/dist/client/assets/{SettingsModal-Cn_CIPXu.js → SettingsModal-4Z8ZJMzD.js} +1 -1
- package/dist/client/assets/SettingsModal-D0kuJpBA.js +31 -0
- package/dist/client/assets/{SetupWizardModal-k5vqrHZU.js → SetupWizardModal-Bhumd4Rf.js} +1 -1
- package/dist/client/assets/{SkillsView-BIdt5cfB.js → SkillsView-MHweJTz4.js} +1 -1
- package/dist/client/assets/{folder-open-B3TO7t7Z.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-DW-M-BD_.js → star-7L86NZrT.js} +1 -1
- package/dist/client/assets/{upload-BzG6fknr.js → upload-DsAS6tno.js} +1 -1
- package/dist/client/assets/{users-DEicv0kj.js → users-D3u6f2Rz.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +1215 -524
- 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-CkWkEwXL.js +0 -1
- package/dist/client/assets/SettingsModal-YH_rM1ZT.js +0 -31
- package/dist/client/assets/index-BlkXZ4C5.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();
|
|
54742
|
+
}
|
|
54743
|
+
function getFusionAuthPath(home = getHomeDir5()) {
|
|
54744
|
+
return join24(home, ".fusion", "agent", "auth.json");
|
|
54541
54745
|
}
|
|
54542
|
-
function
|
|
54543
|
-
return
|
|
54746
|
+
function getFusionModelsPath(home = getHomeDir5()) {
|
|
54747
|
+
return join24(home, ".fusion", "agent", "models.json");
|
|
54544
54748
|
}
|
|
54545
|
-
function
|
|
54546
|
-
return
|
|
54749
|
+
function getLegacyAuthPaths(home = getHomeDir5()) {
|
|
54750
|
+
return [
|
|
54751
|
+
join24(home, ".pi", "agent", "auth.json"),
|
|
54752
|
+
join24(home, ".pi", "auth.json")
|
|
54753
|
+
];
|
|
54547
54754
|
}
|
|
54548
|
-
function
|
|
54755
|
+
function getSupplementalAuthPaths(home = getHomeDir5()) {
|
|
54549
54756
|
return [
|
|
54550
|
-
|
|
54551
|
-
|
|
54757
|
+
...getLegacyAuthPaths(home),
|
|
54758
|
+
getCodexCliAuthPath(home)
|
|
54552
54759
|
];
|
|
54553
54760
|
}
|
|
54554
|
-
function getLegacyModelsPaths(home =
|
|
54761
|
+
function getLegacyModelsPaths(home = getHomeDir5()) {
|
|
54555
54762
|
return [
|
|
54556
|
-
|
|
54557
|
-
|
|
54763
|
+
join24(home, ".pi", "agent", "models.json"),
|
|
54764
|
+
join24(home, ".pi", "models.json")
|
|
54558
54765
|
];
|
|
54559
54766
|
}
|
|
54560
|
-
function getModelRegistryModelsPath(home =
|
|
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
|
}
|
|
@@ -59148,6 +59382,38 @@ var init_notifier = __esm({
|
|
|
59148
59382
|
}
|
|
59149
59383
|
});
|
|
59150
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
|
+
|
|
59151
59417
|
// ../engine/src/reviewer.ts
|
|
59152
59418
|
async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptContent, baseline, options = {}) {
|
|
59153
59419
|
let liveSettings = options.settings;
|
|
@@ -59278,7 +59544,13 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
59278
59544
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
59279
59545
|
taskId: options.taskId,
|
|
59280
59546
|
taskTitle: options.taskTitle,
|
|
59281
|
-
onFallbackModelUsed:
|
|
59547
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
59548
|
+
agent: "reviewer",
|
|
59549
|
+
label: "reviewer",
|
|
59550
|
+
store: options.store,
|
|
59551
|
+
taskId: options.taskId,
|
|
59552
|
+
taskTitle: options.taskTitle
|
|
59553
|
+
}),
|
|
59282
59554
|
beforeSpawnSession: async () => {
|
|
59283
59555
|
if (!options.store) return;
|
|
59284
59556
|
let finalSettings;
|
|
@@ -59465,7 +59737,7 @@ var init_reviewer = __esm({
|
|
|
59465
59737
|
init_logger2();
|
|
59466
59738
|
init_usage_limit_detector();
|
|
59467
59739
|
init_agent_instructions();
|
|
59468
|
-
|
|
59740
|
+
init_fallback_model_observer();
|
|
59469
59741
|
init_agent_tools();
|
|
59470
59742
|
REVIEWER_SYSTEM_PROMPT = `You are an independent code and plan reviewer.
|
|
59471
59743
|
|
|
@@ -59826,7 +60098,7 @@ var init_recovery_policy = __esm({
|
|
|
59826
60098
|
// ../engine/src/triage.ts
|
|
59827
60099
|
import { Type as Type2 } from "@mariozechner/pi-ai";
|
|
59828
60100
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
59829
|
-
import { join as
|
|
60101
|
+
import { join as join29 } from "node:path";
|
|
59830
60102
|
function extractPromptDeclaredTitle(prompt, taskId) {
|
|
59831
60103
|
const headingMatch = prompt.match(/^#\s+Task:\s+([A-Z]+-\d+)\s+-\s+(.+)$/m);
|
|
59832
60104
|
if (!headingMatch) return null;
|
|
@@ -59855,9 +60127,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
59855
60127
|
return { attachmentContents, imageContents };
|
|
59856
60128
|
}
|
|
59857
60129
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
59858
|
-
const { join:
|
|
60130
|
+
const { join: join43 } = await import("node:path");
|
|
59859
60131
|
for (const att of attachments) {
|
|
59860
|
-
const filePath =
|
|
60132
|
+
const filePath = join43(
|
|
59861
60133
|
rootDir,
|
|
59862
60134
|
".fusion",
|
|
59863
60135
|
"tasks",
|
|
@@ -60082,7 +60354,7 @@ var init_triage = __esm({
|
|
|
60082
60354
|
init_concurrency();
|
|
60083
60355
|
init_agent_logger();
|
|
60084
60356
|
init_agent_instructions();
|
|
60085
|
-
|
|
60357
|
+
init_fallback_model_observer();
|
|
60086
60358
|
init_logger2();
|
|
60087
60359
|
init_usage_limit_detector();
|
|
60088
60360
|
init_transient_error_detector();
|
|
@@ -60692,7 +60964,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60692
60964
|
return false;
|
|
60693
60965
|
}
|
|
60694
60966
|
const settings = await this.store.getSettings();
|
|
60695
|
-
const promptPath =
|
|
60967
|
+
const promptPath = join29(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
60696
60968
|
const written = await readFile14(promptPath, "utf-8").catch((err) => {
|
|
60697
60969
|
const msg = err instanceof Error ? err.message : String(err);
|
|
60698
60970
|
planLog.warn(`${task.id}: failed to read PROMPT.md during approved-spec recovery (${promptPath}): ${msg}`);
|
|
@@ -60922,7 +61194,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
60922
61194
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
60923
61195
|
taskId: task.id,
|
|
60924
61196
|
taskTitle: task.title,
|
|
60925
|
-
onFallbackModelUsed:
|
|
61197
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61198
|
+
agent: "triage",
|
|
61199
|
+
label: "triage",
|
|
61200
|
+
store: this.store,
|
|
61201
|
+
taskId: task.id,
|
|
61202
|
+
taskTitle: task.title
|
|
61203
|
+
})
|
|
60926
61204
|
});
|
|
60927
61205
|
const modelDesc = describeModel(session);
|
|
60928
61206
|
planLog.log(`${task.id}: using model ${modelDesc}`);
|
|
@@ -61065,7 +61343,13 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61065
61343
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
61066
61344
|
taskId: task.id,
|
|
61067
61345
|
taskTitle: task.title,
|
|
61068
|
-
onFallbackModelUsed:
|
|
61346
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
61347
|
+
agent: "triage",
|
|
61348
|
+
label: "triage",
|
|
61349
|
+
store: this.store,
|
|
61350
|
+
taskId: task.id,
|
|
61351
|
+
taskTitle: task.title
|
|
61352
|
+
})
|
|
61069
61353
|
});
|
|
61070
61354
|
session = fallbackResult.session;
|
|
61071
61355
|
const fallbackModelDesc = describeModel(session);
|
|
@@ -61150,7 +61434,7 @@ Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\
|
|
|
61150
61434
|
return;
|
|
61151
61435
|
}
|
|
61152
61436
|
const written = await readFile14(
|
|
61153
|
-
|
|
61437
|
+
join29(this.rootDir, promptPath),
|
|
61154
61438
|
"utf-8"
|
|
61155
61439
|
).catch((err) => {
|
|
61156
61440
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61469,9 +61753,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
61469
61753
|
}
|
|
61470
61754
|
try {
|
|
61471
61755
|
const { readFile: readFile20 } = await import("node:fs/promises");
|
|
61472
|
-
const { join:
|
|
61756
|
+
const { join: join43 } = await import("node:path");
|
|
61473
61757
|
const promptContent = await readFile20(
|
|
61474
|
-
|
|
61758
|
+
join43(rootDir, promptPath),
|
|
61475
61759
|
"utf-8"
|
|
61476
61760
|
).catch((err) => {
|
|
61477
61761
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -61699,160 +61983,8 @@ Take a completely different approach to writing this specification. Do NOT repea
|
|
|
61699
61983
|
}
|
|
61700
61984
|
});
|
|
61701
61985
|
|
|
61702
|
-
// ../engine/src/
|
|
61703
|
-
|
|
61704
|
-
__export(session_token_usage_exports, {
|
|
61705
|
-
accumulateSessionTokenUsage: () => accumulateSessionTokenUsage
|
|
61706
|
-
});
|
|
61707
|
-
function readSessionStats(session) {
|
|
61708
|
-
const accessor = session.getSessionStats;
|
|
61709
|
-
if (typeof accessor !== "function") return void 0;
|
|
61710
|
-
try {
|
|
61711
|
-
return accessor.call(session);
|
|
61712
|
-
} catch {
|
|
61713
|
-
return void 0;
|
|
61714
|
-
}
|
|
61715
|
-
}
|
|
61716
|
-
async function accumulateSessionTokenUsage(store, taskId, session) {
|
|
61717
|
-
try {
|
|
61718
|
-
const stats = readSessionStats(session);
|
|
61719
|
-
const tokens = stats?.tokens;
|
|
61720
|
-
if (!tokens) return;
|
|
61721
|
-
const currentInput = (tokens.input ?? 0) + (tokens.cacheWrite ?? 0);
|
|
61722
|
-
const currentOutput = tokens.output ?? 0;
|
|
61723
|
-
const currentCached = tokens.cacheRead ?? 0;
|
|
61724
|
-
const baseline = sessionBaselines.get(session) ?? { input: 0, output: 0, cached: 0 };
|
|
61725
|
-
const inputDelta = Math.max(0, currentInput - baseline.input);
|
|
61726
|
-
const outputDelta = Math.max(0, currentOutput - baseline.output);
|
|
61727
|
-
const cachedDelta = Math.max(0, currentCached - baseline.cached);
|
|
61728
|
-
sessionBaselines.set(session, {
|
|
61729
|
-
input: currentInput,
|
|
61730
|
-
output: currentOutput,
|
|
61731
|
-
cached: currentCached
|
|
61732
|
-
});
|
|
61733
|
-
if (inputDelta === 0 && outputDelta === 0 && cachedDelta === 0) return;
|
|
61734
|
-
const task = await store.getTask(taskId);
|
|
61735
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
61736
|
-
const newInput = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
|
|
61737
|
-
const newOutput = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
|
|
61738
|
-
const newCached = (task.tokenUsage?.cachedTokens ?? 0) + cachedDelta;
|
|
61739
|
-
await store.updateTask(taskId, {
|
|
61740
|
-
tokenUsage: {
|
|
61741
|
-
inputTokens: newInput,
|
|
61742
|
-
outputTokens: newOutput,
|
|
61743
|
-
cachedTokens: newCached,
|
|
61744
|
-
totalTokens: newInput + newOutput + newCached,
|
|
61745
|
-
firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
|
|
61746
|
-
lastUsedAt: now
|
|
61747
|
-
}
|
|
61748
|
-
});
|
|
61749
|
-
} catch (err) {
|
|
61750
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
61751
|
-
log14.warn(`${taskId}: session token usage accumulate failed: ${message}`);
|
|
61752
|
-
}
|
|
61753
|
-
}
|
|
61754
|
-
var log14, sessionBaselines;
|
|
61755
|
-
var init_session_token_usage = __esm({
|
|
61756
|
-
"../engine/src/session-token-usage.ts"() {
|
|
61757
|
-
"use strict";
|
|
61758
|
-
init_logger2();
|
|
61759
|
-
log14 = createLogger2("session-token-usage");
|
|
61760
|
-
sessionBaselines = /* @__PURE__ */ new WeakMap();
|
|
61761
|
-
}
|
|
61762
|
-
});
|
|
61763
|
-
|
|
61764
|
-
// ../engine/src/run-audit.ts
|
|
61765
|
-
function createRunAuditor(store, context) {
|
|
61766
|
-
if (!context) {
|
|
61767
|
-
return {
|
|
61768
|
-
git: async () => {
|
|
61769
|
-
},
|
|
61770
|
-
database: async () => {
|
|
61771
|
-
},
|
|
61772
|
-
filesystem: async () => {
|
|
61773
|
-
}
|
|
61774
|
-
};
|
|
61775
|
-
}
|
|
61776
|
-
const hasRecordAuditEvent = typeof store.recordRunAuditEvent === "function";
|
|
61777
|
-
if (!hasRecordAuditEvent) {
|
|
61778
|
-
return {
|
|
61779
|
-
git: async () => {
|
|
61780
|
-
},
|
|
61781
|
-
database: async () => {
|
|
61782
|
-
},
|
|
61783
|
-
filesystem: async () => {
|
|
61784
|
-
}
|
|
61785
|
-
};
|
|
61786
|
-
}
|
|
61787
|
-
return {
|
|
61788
|
-
git: async (input) => {
|
|
61789
|
-
const eventInput = {
|
|
61790
|
-
taskId: context.taskId,
|
|
61791
|
-
agentId: context.agentId,
|
|
61792
|
-
runId: context.runId,
|
|
61793
|
-
domain: "git",
|
|
61794
|
-
mutationType: input.type,
|
|
61795
|
-
target: input.target,
|
|
61796
|
-
metadata: {
|
|
61797
|
-
phase: context.phase,
|
|
61798
|
-
...context.source ? { source: context.source } : {},
|
|
61799
|
-
...input.metadata
|
|
61800
|
-
}
|
|
61801
|
-
};
|
|
61802
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61803
|
-
},
|
|
61804
|
-
database: async (input) => {
|
|
61805
|
-
const inferredTaskId = input.target.startsWith("FN-") || input.target.startsWith("KB-") ? input.target : context.taskId;
|
|
61806
|
-
const eventInput = {
|
|
61807
|
-
taskId: inferredTaskId,
|
|
61808
|
-
agentId: context.agentId,
|
|
61809
|
-
runId: context.runId,
|
|
61810
|
-
domain: "database",
|
|
61811
|
-
mutationType: input.type,
|
|
61812
|
-
target: input.target,
|
|
61813
|
-
metadata: {
|
|
61814
|
-
phase: context.phase,
|
|
61815
|
-
...context.source ? { source: context.source } : {},
|
|
61816
|
-
...input.metadata
|
|
61817
|
-
}
|
|
61818
|
-
};
|
|
61819
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61820
|
-
},
|
|
61821
|
-
filesystem: async (input) => {
|
|
61822
|
-
const eventInput = {
|
|
61823
|
-
taskId: context.taskId,
|
|
61824
|
-
agentId: context.agentId,
|
|
61825
|
-
runId: context.runId,
|
|
61826
|
-
domain: "filesystem",
|
|
61827
|
-
mutationType: input.type,
|
|
61828
|
-
target: input.target,
|
|
61829
|
-
metadata: {
|
|
61830
|
-
phase: context.phase,
|
|
61831
|
-
...context.source ? { source: context.source } : {},
|
|
61832
|
-
...input.metadata
|
|
61833
|
-
}
|
|
61834
|
-
};
|
|
61835
|
-
await store.recordRunAuditEvent(eventInput);
|
|
61836
|
-
}
|
|
61837
|
-
};
|
|
61838
|
-
}
|
|
61839
|
-
function generateSyntheticRunId(prefix, taskId) {
|
|
61840
|
-
const timestamp = Date.now();
|
|
61841
|
-
const random = Math.random().toString(36).slice(2, 6);
|
|
61842
|
-
return `${prefix}-${taskId}-${timestamp}-${random}`;
|
|
61843
|
-
}
|
|
61844
|
-
var init_run_audit = __esm({
|
|
61845
|
-
"../engine/src/run-audit.ts"() {
|
|
61846
|
-
"use strict";
|
|
61847
|
-
}
|
|
61848
|
-
});
|
|
61849
|
-
|
|
61850
|
-
// ../engine/src/merger.ts
|
|
61851
|
-
import { execSync, exec as exec3, spawn as spawn3 } from "node:child_process";
|
|
61852
|
-
import { promisify as promisify4 } from "node:util";
|
|
61853
|
-
import { existsSync as existsSync23 } from "node:fs";
|
|
61854
|
-
import { join as join29 } from "node:path";
|
|
61855
|
-
import { Type as Type3 } from "typebox";
|
|
61986
|
+
// ../engine/src/verification-utils.ts
|
|
61987
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
61856
61988
|
async function execWithProcessGroup(command, options) {
|
|
61857
61989
|
return new Promise((resolve20, reject) => {
|
|
61858
61990
|
if (options.signal?.aborted) {
|
|
@@ -61964,6 +62096,11 @@ function truncateWithEllipsis(text, maxChars) {
|
|
|
61964
62096
|
return `${text.slice(0, maxChars)}
|
|
61965
62097
|
... (truncated)`;
|
|
61966
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
|
+
}
|
|
61967
62104
|
function summarizeVerificationOutput(output, type) {
|
|
61968
62105
|
const lines = output.split("\n");
|
|
61969
62106
|
let summaryLine = null;
|
|
@@ -62029,6 +62166,13 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62029
62166
|
failureNames.add(truncated);
|
|
62030
62167
|
}
|
|
62031
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
|
+
}
|
|
62032
62176
|
const parts = [];
|
|
62033
62177
|
if (summaryLine) {
|
|
62034
62178
|
parts.push(summaryLine);
|
|
@@ -62046,29 +62190,292 @@ function summarizeVerificationOutput(output, type) {
|
|
|
62046
62190
|
parts.push(` \u2022 ... and ${names.length - 5} more failures`);
|
|
62047
62191
|
}
|
|
62048
62192
|
}
|
|
62049
|
-
if (parts.length
|
|
62050
|
-
|
|
62051
|
-
|
|
62052
|
-
|
|
62053
|
-
|
|
62054
|
-
|
|
62055
|
-
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)}
|
|
62056
62199
|
${footer}`;
|
|
62057
62200
|
}
|
|
62058
|
-
|
|
62059
|
-
return `${trimmed}
|
|
62201
|
+
return parts.join("\n") + `
|
|
62060
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
|
+
);
|
|
62061
62212
|
}
|
|
62062
|
-
|
|
62063
|
-
|
|
62064
|
-
|
|
62065
|
-
|
|
62066
|
-
|
|
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
|
+
);
|
|
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;
|
|
62067
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
|
+
);
|
|
62068
62312
|
}
|
|
62069
|
-
return
|
|
62070
|
-
${footer}`;
|
|
62313
|
+
return result;
|
|
62071
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}`);
|
|
62375
|
+
}
|
|
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";
|
|
62072
62479
|
function truncateWorkflowScriptOutput(output) {
|
|
62073
62480
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS) return output;
|
|
62074
62481
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS} characters ...
|
|
@@ -62112,7 +62519,7 @@ async function getStagedFiles(cwd) {
|
|
|
62112
62519
|
}
|
|
62113
62520
|
}
|
|
62114
62521
|
function hasInstallState(rootDir) {
|
|
62115
|
-
return
|
|
62522
|
+
return existsSync24(join30(rootDir, "node_modules")) || existsSync24(join30(rootDir, ".pnp.cjs"));
|
|
62116
62523
|
}
|
|
62117
62524
|
function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
62118
62525
|
if (!installStatePresent) return true;
|
|
@@ -62121,10 +62528,10 @@ function shouldSyncDependenciesForMerge(stagedFiles, installStatePresent) {
|
|
|
62121
62528
|
);
|
|
62122
62529
|
}
|
|
62123
62530
|
function getDependencySyncCommand(rootDir) {
|
|
62124
|
-
if (
|
|
62125
|
-
if (
|
|
62126
|
-
if (
|
|
62127
|
-
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"))) {
|
|
62128
62535
|
return "bun install --frozen-lockfile";
|
|
62129
62536
|
}
|
|
62130
62537
|
return null;
|
|
@@ -62157,8 +62564,8 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62157
62564
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62158
62565
|
};
|
|
62159
62566
|
}
|
|
62160
|
-
if (
|
|
62161
|
-
if (
|
|
62567
|
+
if (existsSync24(join30(rootDir, "pnpm-lock.yaml"))) {
|
|
62568
|
+
if (existsSync24(join30(rootDir, "pnpm-workspace.yaml"))) {
|
|
62162
62569
|
mergerLog.warn(
|
|
62163
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\`.`
|
|
62164
62571
|
);
|
|
@@ -62169,21 +62576,21 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
62169
62576
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62170
62577
|
};
|
|
62171
62578
|
}
|
|
62172
|
-
if (
|
|
62579
|
+
if (existsSync24(join30(rootDir, "yarn.lock"))) {
|
|
62173
62580
|
return {
|
|
62174
62581
|
command: "yarn test",
|
|
62175
62582
|
testSource: "inferred",
|
|
62176
62583
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62177
62584
|
};
|
|
62178
62585
|
}
|
|
62179
|
-
if (
|
|
62586
|
+
if (existsSync24(join30(rootDir, "bun.lock")) || existsSync24(join30(rootDir, "bun.lockb"))) {
|
|
62180
62587
|
return {
|
|
62181
62588
|
command: "bun test",
|
|
62182
62589
|
testSource: "inferred",
|
|
62183
62590
|
buildSource: explicitBuildCommand?.trim() ? "explicit" : void 0
|
|
62184
62591
|
};
|
|
62185
62592
|
}
|
|
62186
|
-
if (
|
|
62593
|
+
if (existsSync24(join30(rootDir, "package-lock.json"))) {
|
|
62187
62594
|
return {
|
|
62188
62595
|
command: "npm test",
|
|
62189
62596
|
testSource: "inferred",
|
|
@@ -62220,7 +62627,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62220
62627
|
await store.logEntry(taskId, deterministicVerificationMessage);
|
|
62221
62628
|
await store.appendAgentLog(taskId, deterministicVerificationMessage, "text", void 0, "merger");
|
|
62222
62629
|
if (hasTestCommand) {
|
|
62223
|
-
const testResult = await
|
|
62630
|
+
const testResult = await runVerificationCommand2(
|
|
62224
62631
|
store,
|
|
62225
62632
|
rootDir,
|
|
62226
62633
|
taskId,
|
|
@@ -62251,7 +62658,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62251
62658
|
}
|
|
62252
62659
|
}
|
|
62253
62660
|
if (hasBuildCommand) {
|
|
62254
|
-
const buildResult = await
|
|
62661
|
+
const buildResult = await runVerificationCommand2(
|
|
62255
62662
|
store,
|
|
62256
62663
|
rootDir,
|
|
62257
62664
|
taskId,
|
|
@@ -62286,98 +62693,9 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
|
|
|
62286
62693
|
await store.appendAgentLog(taskId, "Deterministic merge verification passed", "text", void 0, "merger");
|
|
62287
62694
|
return result;
|
|
62288
62695
|
}
|
|
62289
|
-
async function
|
|
62696
|
+
async function runVerificationCommand2(store, rootDir, taskId, command, type, signal) {
|
|
62290
62697
|
throwIfAborted(signal, taskId);
|
|
62291
|
-
|
|
62292
|
-
await store.logEntry(taskId, `[verification] Running ${type} command: ${command}`);
|
|
62293
|
-
await store.appendAgentLog(taskId, `Running ${type} command`, "tool", command, "merger");
|
|
62294
|
-
const result = {
|
|
62295
|
-
command,
|
|
62296
|
-
exitCode: null,
|
|
62297
|
-
stdout: "",
|
|
62298
|
-
stderr: "",
|
|
62299
|
-
success: false
|
|
62300
|
-
};
|
|
62301
|
-
const verificationStartedAt = Date.now();
|
|
62302
|
-
try {
|
|
62303
|
-
const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
|
|
62304
|
-
cwd: rootDir,
|
|
62305
|
-
timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
|
|
62306
|
-
maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
|
|
62307
|
-
signal
|
|
62308
|
-
});
|
|
62309
|
-
throwIfAborted(signal, taskId);
|
|
62310
|
-
result.stdout = stdout?.toString?.() || "";
|
|
62311
|
-
result.stderr = stderr?.toString?.() || "";
|
|
62312
|
-
result.exitCode = 0;
|
|
62313
|
-
result.success = true;
|
|
62314
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62315
|
-
const timingDetail = `${verificationDurationMs}ms`;
|
|
62316
|
-
if (bufferOverflow) {
|
|
62317
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62318
|
-
await store.logEntry(
|
|
62319
|
-
taskId,
|
|
62320
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62321
|
-
);
|
|
62322
|
-
await store.appendAgentLog(
|
|
62323
|
-
taskId,
|
|
62324
|
-
`${type} command succeeded (exit 0)`,
|
|
62325
|
-
"tool_result",
|
|
62326
|
-
timingDetail,
|
|
62327
|
-
"merger"
|
|
62328
|
-
);
|
|
62329
|
-
} else {
|
|
62330
|
-
mergerLog.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
62331
|
-
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
62332
|
-
await store.appendAgentLog(
|
|
62333
|
-
taskId,
|
|
62334
|
-
`${type} command succeeded (exit 0)`,
|
|
62335
|
-
"tool_result",
|
|
62336
|
-
timingDetail,
|
|
62337
|
-
"merger"
|
|
62338
|
-
);
|
|
62339
|
-
}
|
|
62340
|
-
return result;
|
|
62341
|
-
} catch (error) {
|
|
62342
|
-
throwIfAborted(signal, taskId);
|
|
62343
|
-
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
62344
|
-
result.stdout = error?.stdout?.toString?.() || "";
|
|
62345
|
-
result.stderr = error?.stderr?.toString?.() || "";
|
|
62346
|
-
result.exitCode = typeof error?.status === "number" ? error.status : typeof error?.code === "number" ? error.code : null;
|
|
62347
|
-
const maxBufferExceeded = error?.code === "ENOBUFS" || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(error?.message ?? "").includes("maxBuffer");
|
|
62348
|
-
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
62349
|
-
if (result.success) {
|
|
62350
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
62351
|
-
await store.logEntry(
|
|
62352
|
-
taskId,
|
|
62353
|
-
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
62354
|
-
);
|
|
62355
|
-
await store.appendAgentLog(
|
|
62356
|
-
taskId,
|
|
62357
|
-
`${type} command succeeded (exit 0)`,
|
|
62358
|
-
"tool_result",
|
|
62359
|
-
`${verificationDurationMs}ms`,
|
|
62360
|
-
"merger"
|
|
62361
|
-
);
|
|
62362
|
-
return result;
|
|
62363
|
-
}
|
|
62364
|
-
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
|
62365
|
-
const summary = summarizeVerificationOutput(output, type);
|
|
62366
|
-
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
62367
|
-
await store.logEntry(
|
|
62368
|
-
taskId,
|
|
62369
|
-
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
62370
|
-
${summary}`
|
|
62371
|
-
);
|
|
62372
|
-
await store.appendAgentLog(
|
|
62373
|
-
taskId,
|
|
62374
|
-
`${type} command failed (exit ${result.exitCode})`,
|
|
62375
|
-
"tool_error",
|
|
62376
|
-
summary,
|
|
62377
|
-
"merger"
|
|
62378
|
-
);
|
|
62379
|
-
}
|
|
62380
|
-
return result;
|
|
62698
|
+
return runVerificationCommand(store, rootDir, taskId, command, type, signal, mergerLog, "merger");
|
|
62381
62699
|
}
|
|
62382
62700
|
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, mergeRunContext, fixAttemptNumber, _testCommand, _buildCommand) {
|
|
62383
62701
|
try {
|
|
@@ -62440,9 +62758,20 @@ Do not refactor, rename broadly, or make opportunistic improvements.
|
|
|
62440
62758
|
onToolEnd: logger2.onToolEnd,
|
|
62441
62759
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
62442
62760
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
62761
|
+
fallbackProvider: settings.fallbackProvider,
|
|
62762
|
+
fallbackModelId: settings.fallbackModelId,
|
|
62443
62763
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
62444
62764
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
62445
|
-
...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
|
+
})
|
|
62446
62775
|
});
|
|
62447
62776
|
const runId = mergeRunContext?.runId;
|
|
62448
62777
|
const agentId = mergeRunContext?.agentId ?? "merger";
|
|
@@ -62495,7 +62824,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
62495
62824
|
void 0,
|
|
62496
62825
|
"merger"
|
|
62497
62826
|
);
|
|
62498
|
-
const reRunResult = await
|
|
62827
|
+
const reRunResult = await runVerificationCommand2(
|
|
62499
62828
|
store,
|
|
62500
62829
|
rootDir,
|
|
62501
62830
|
taskId,
|
|
@@ -63237,9 +63566,16 @@ You are assisting with a paused \`git pull --rebase\`.
|
|
|
63237
63566
|
onToolEnd: agentLogger.onToolEnd,
|
|
63238
63567
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
63239
63568
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
63569
|
+
fallbackProvider: settings.fallbackProvider,
|
|
63570
|
+
fallbackModelId: settings.fallbackModelId,
|
|
63240
63571
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
63241
63572
|
taskId,
|
|
63242
|
-
onFallbackModelUsed:
|
|
63573
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
63574
|
+
agent: "merger",
|
|
63575
|
+
label: "rebase conflict resolver",
|
|
63576
|
+
store,
|
|
63577
|
+
taskId
|
|
63578
|
+
})
|
|
63243
63579
|
});
|
|
63244
63580
|
const prompt = [
|
|
63245
63581
|
`Resolve rebase conflicts for task ${taskId}.`,
|
|
@@ -63431,7 +63767,7 @@ async function pushToRemoteAfterMerge(store, rootDir, taskId, settings, options)
|
|
|
63431
63767
|
}
|
|
63432
63768
|
async function createPostMergeWorktree(rootDir, taskId) {
|
|
63433
63769
|
const randomSuffix = Math.random().toString(36).slice(2, 10);
|
|
63434
|
-
const postMergeWorktree =
|
|
63770
|
+
const postMergeWorktree = join30(rootDir, ".worktrees", `post-merge-${taskId}-${randomSuffix}`);
|
|
63435
63771
|
try {
|
|
63436
63772
|
await execAsync2(`git worktree add ${quoteArg(postMergeWorktree)} HEAD`, { cwd: rootDir });
|
|
63437
63773
|
return postMergeWorktree;
|
|
@@ -64372,7 +64708,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
64372
64708
|
}
|
|
64373
64709
|
}
|
|
64374
64710
|
throwIfAborted(options.signal, taskId);
|
|
64375
|
-
if (worktreePath &&
|
|
64711
|
+
if (worktreePath && existsSync24(worktreePath)) {
|
|
64376
64712
|
const otherUser = await findWorktreeUser(store, worktreePath, taskId);
|
|
64377
64713
|
if (otherUser) {
|
|
64378
64714
|
mergerLog.log(`Worktree retained \u2014 still needed by ${otherUser}`);
|
|
@@ -65026,9 +65362,20 @@ async function runAiAgentForCommit(params) {
|
|
|
65026
65362
|
onToolEnd: agentLogger.onToolEnd,
|
|
65027
65363
|
defaultProvider: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultProviderOverride : settings.defaultProvider,
|
|
65028
65364
|
defaultModelId: settings.defaultProviderOverride && settings.defaultModelIdOverride ? settings.defaultModelIdOverride : settings.defaultModelId,
|
|
65365
|
+
fallbackProvider: settings.fallbackProvider,
|
|
65366
|
+
fallbackModelId: settings.fallbackModelId,
|
|
65029
65367
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65030
65368
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65031
|
-
...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
|
+
})
|
|
65032
65379
|
});
|
|
65033
65380
|
options.onSession?.(session);
|
|
65034
65381
|
try {
|
|
@@ -65459,7 +65806,14 @@ If issues are found that need attention, describe them clearly and include concr
|
|
|
65459
65806
|
fallbackModelId: settings.fallbackModelId,
|
|
65460
65807
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
65461
65808
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
65462
|
-
...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
|
+
})
|
|
65463
65817
|
});
|
|
65464
65818
|
mergerLog.log(`${taskId}: [post-merge] workflow step '${workflowStep.name}' using model ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
65465
65819
|
await store.logEntry(taskId, `[post-merge] Workflow step '${workflowStep.name}' using model: ${describeModel(session)}${useOverride ? " (workflow step override)" : ""}`);
|
|
@@ -65495,15 +65849,17 @@ async function completeTask(store, taskId, result) {
|
|
|
65495
65849
|
result.task = task;
|
|
65496
65850
|
store.emit("task:merged", result);
|
|
65497
65851
|
}
|
|
65498
|
-
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;
|
|
65499
65853
|
var init_merger = __esm({
|
|
65500
65854
|
"../engine/src/merger.ts"() {
|
|
65501
65855
|
"use strict";
|
|
65856
|
+
init_verification_utils();
|
|
65857
|
+
init_verification_utils();
|
|
65502
65858
|
init_src();
|
|
65503
65859
|
init_pi();
|
|
65504
65860
|
init_session_token_usage();
|
|
65505
65861
|
init_agent_session_helpers();
|
|
65506
|
-
|
|
65862
|
+
init_fallback_model_observer();
|
|
65507
65863
|
init_session_skill_context();
|
|
65508
65864
|
init_agent_logger();
|
|
65509
65865
|
init_logger2();
|
|
@@ -65549,9 +65905,6 @@ var init_merger = __esm({
|
|
|
65549
65905
|
"bun.lock",
|
|
65550
65906
|
"packages/*/package.json"
|
|
65551
65907
|
];
|
|
65552
|
-
VERIFICATION_COMMAND_MAX_BUFFER = 50 * 1024 * 1024;
|
|
65553
|
-
VERIFICATION_COMMAND_TIMEOUT_MS = 6e5;
|
|
65554
|
-
VERIFICATION_LOG_MAX_CHARS = 2e4;
|
|
65555
65908
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS = 4e3;
|
|
65556
65909
|
PULL_REBASE_TIMEOUT_MS = 12e4;
|
|
65557
65910
|
PUSH_TIMEOUT_MS = 6e4;
|
|
@@ -65576,8 +65929,8 @@ var init_merger = __esm({
|
|
|
65576
65929
|
|
|
65577
65930
|
// ../engine/src/worktree-names.ts
|
|
65578
65931
|
import { readdirSync as readdirSync3 } from "node:fs";
|
|
65579
|
-
import { join as
|
|
65580
|
-
import { existsSync as
|
|
65932
|
+
import { join as join31 } from "node:path";
|
|
65933
|
+
import { existsSync as existsSync25 } from "node:fs";
|
|
65581
65934
|
function slugify2(str) {
|
|
65582
65935
|
return str.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
65583
65936
|
}
|
|
@@ -65588,7 +65941,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65588
65941
|
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
65589
65942
|
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
65590
65943
|
const baseName = `${adjective}-${noun}`;
|
|
65591
|
-
const worktreesDir =
|
|
65944
|
+
const worktreesDir = join31(rootDir, ".worktrees");
|
|
65592
65945
|
const existing = getExistingWorktreeNames(worktreesDir);
|
|
65593
65946
|
for (const reserved of reservedNames) {
|
|
65594
65947
|
existing.add(reserved);
|
|
@@ -65603,7 +65956,7 @@ function generateReservedWorktreeName(rootDir, reservedNames = /* @__PURE__ */ n
|
|
|
65603
65956
|
return `${baseName}-${suffix}`;
|
|
65604
65957
|
}
|
|
65605
65958
|
function getExistingWorktreeNames(worktreesDir) {
|
|
65606
|
-
if (!
|
|
65959
|
+
if (!existsSync25(worktreesDir)) {
|
|
65607
65960
|
return /* @__PURE__ */ new Set();
|
|
65608
65961
|
}
|
|
65609
65962
|
try {
|
|
@@ -65740,8 +66093,8 @@ __export(worktree_pool_exports, {
|
|
|
65740
66093
|
});
|
|
65741
66094
|
import { exec as exec4 } from "node:child_process";
|
|
65742
66095
|
import { promisify as promisify5 } from "node:util";
|
|
65743
|
-
import { existsSync as
|
|
65744
|
-
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";
|
|
65745
66098
|
function getExecStdout(result) {
|
|
65746
66099
|
if (typeof result === "string") return result;
|
|
65747
66100
|
if (result && typeof result === "object" && "stdout" in result) {
|
|
@@ -65787,10 +66140,10 @@ async function isRegisteredGitWorktree2(rootDir, worktreePath) {
|
|
|
65787
66140
|
return (await getRegisteredWorktreePaths(rootDir)).has(resolve15(worktreePath));
|
|
65788
66141
|
}
|
|
65789
66142
|
function hasRequiredWorktreeFiles(worktreePath) {
|
|
65790
|
-
return
|
|
66143
|
+
return existsSync26(join32(worktreePath, ".git")) && existsSync26(join32(worktreePath, "package.json"));
|
|
65791
66144
|
}
|
|
65792
66145
|
async function isUsableTaskWorktree(rootDir, worktreePath) {
|
|
65793
|
-
return
|
|
66146
|
+
return existsSync26(worktreePath) && await isRegisteredGitWorktree2(rootDir, worktreePath) && hasRequiredWorktreeFiles(worktreePath);
|
|
65794
66147
|
}
|
|
65795
66148
|
function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
65796
66149
|
const worktreesDir = resolve15(rootDir, ".worktrees");
|
|
@@ -65799,14 +66152,14 @@ function isInsideWorktreesDir(rootDir, worktreePath) {
|
|
|
65799
66152
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute9(rel);
|
|
65800
66153
|
}
|
|
65801
66154
|
async function scanIdleWorktrees(rootDir, store) {
|
|
65802
|
-
const worktreesDir =
|
|
65803
|
-
if (!
|
|
66155
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66156
|
+
if (!existsSync26(worktreesDir)) {
|
|
65804
66157
|
return [];
|
|
65805
66158
|
}
|
|
65806
66159
|
let dirs;
|
|
65807
66160
|
try {
|
|
65808
66161
|
const entries = readdirSync4(worktreesDir, { withFileTypes: true });
|
|
65809
|
-
dirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
66162
|
+
dirs = entries.filter((e) => e.isDirectory()).map((e) => join32(worktreesDir, e.name));
|
|
65810
66163
|
} catch (err) {
|
|
65811
66164
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65812
66165
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory: ${errorMessage}`);
|
|
@@ -65829,16 +66182,16 @@ async function scanIdleWorktrees(rootDir, store) {
|
|
|
65829
66182
|
return registeredDirs.filter((dir) => !activeWorktrees.has(resolve15(dir)));
|
|
65830
66183
|
}
|
|
65831
66184
|
async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
65832
|
-
const worktreesDir =
|
|
65833
|
-
if (!
|
|
66185
|
+
const worktreesDir = join32(rootDir, ".worktrees");
|
|
66186
|
+
if (!existsSync26(worktreesDir)) {
|
|
65834
66187
|
return 0;
|
|
65835
66188
|
}
|
|
65836
66189
|
const orphaned = await scanIdleWorktrees(rootDir, store);
|
|
65837
66190
|
const registeredWorktrees = await getRegisteredWorktreePaths(rootDir);
|
|
65838
66191
|
let dirs = [];
|
|
65839
|
-
if (
|
|
66192
|
+
if (existsSync26(worktreesDir)) {
|
|
65840
66193
|
try {
|
|
65841
|
-
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));
|
|
65842
66195
|
} catch (err) {
|
|
65843
66196
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65844
66197
|
worktreePoolLog.warn(`Failed to read .worktrees/ directory for cleanup: ${errorMessage}`);
|
|
@@ -65870,8 +66223,8 @@ async function cleanupOrphanedWorktrees(rootDir, store) {
|
|
|
65870
66223
|
return cleaned;
|
|
65871
66224
|
}
|
|
65872
66225
|
async function reapOrphanWorktrees(projectRoot) {
|
|
65873
|
-
const worktreesDir =
|
|
65874
|
-
if (!
|
|
66226
|
+
const worktreesDir = join32(projectRoot, ".worktrees");
|
|
66227
|
+
if (!existsSync26(worktreesDir)) {
|
|
65875
66228
|
return 0;
|
|
65876
66229
|
}
|
|
65877
66230
|
let entries;
|
|
@@ -65879,11 +66232,11 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65879
66232
|
entries = readdirSync4(worktreesDir, { withFileTypes: true }).filter((e) => {
|
|
65880
66233
|
if (!e.isDirectory()) return false;
|
|
65881
66234
|
try {
|
|
65882
|
-
return lstatSync(
|
|
66235
|
+
return lstatSync(join32(worktreesDir, e.name)).isDirectory() && !lstatSync(join32(worktreesDir, e.name)).isSymbolicLink();
|
|
65883
66236
|
} catch {
|
|
65884
66237
|
return false;
|
|
65885
66238
|
}
|
|
65886
|
-
}).map((e) => ({ name: e.name, fullPath:
|
|
66239
|
+
}).map((e) => ({ name: e.name, fullPath: join32(worktreesDir, e.name) }));
|
|
65887
66240
|
} catch (err) {
|
|
65888
66241
|
const msg = err instanceof Error ? err.message : String(err);
|
|
65889
66242
|
worktreePoolLog.warn(`reapOrphanWorktrees: failed to read .worktrees/ \u2014 ${msg}`);
|
|
@@ -65902,8 +66255,8 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
65902
66255
|
if (registered.has(resolvedFull)) {
|
|
65903
66256
|
continue;
|
|
65904
66257
|
}
|
|
65905
|
-
const dotGit =
|
|
65906
|
-
if (
|
|
66258
|
+
const dotGit = join32(resolvedFull, ".git");
|
|
66259
|
+
if (existsSync26(dotGit)) {
|
|
65907
66260
|
worktreePoolLog.log(`reapOrphanWorktrees: skipping ${name} (has .git entry but not in registered list \u2014 may be partially registered)`);
|
|
65908
66261
|
continue;
|
|
65909
66262
|
}
|
|
@@ -65963,7 +66316,7 @@ var init_worktree_pool = __esm({
|
|
|
65963
66316
|
acquire() {
|
|
65964
66317
|
for (const path2 of this.idle) {
|
|
65965
66318
|
this.idle.delete(path2);
|
|
65966
|
-
if (
|
|
66319
|
+
if (existsSync26(path2)) {
|
|
65967
66320
|
return path2;
|
|
65968
66321
|
}
|
|
65969
66322
|
worktreePoolLog.log(`Pruned stale entry: ${path2}`);
|
|
@@ -66010,7 +66363,7 @@ var init_worktree_pool = __esm({
|
|
|
66010
66363
|
*/
|
|
66011
66364
|
rehydrate(idlePaths) {
|
|
66012
66365
|
for (const path2 of idlePaths) {
|
|
66013
|
-
if (
|
|
66366
|
+
if (existsSync26(path2)) {
|
|
66014
66367
|
this.idle.add(path2);
|
|
66015
66368
|
} else {
|
|
66016
66369
|
worktreePoolLog.log(`Rehydrate skipped (not on disk): ${path2}`);
|
|
@@ -66066,7 +66419,7 @@ var init_worktree_pool = __esm({
|
|
|
66066
66419
|
throw err;
|
|
66067
66420
|
}
|
|
66068
66421
|
const conflictingPath = match[1];
|
|
66069
|
-
if (!
|
|
66422
|
+
if (!existsSync26(conflictingPath)) {
|
|
66070
66423
|
await execAsync3("git worktree prune", { cwd: worktreePath });
|
|
66071
66424
|
await execAsync3(checkoutCmd, { cwd: worktreePath });
|
|
66072
66425
|
return branchName;
|
|
@@ -66143,8 +66496,8 @@ var init_token_cap_detector = __esm({
|
|
|
66143
66496
|
// ../engine/src/step-session-executor.ts
|
|
66144
66497
|
import { exec as exec5 } from "node:child_process";
|
|
66145
66498
|
import { promisify as promisify6 } from "node:util";
|
|
66146
|
-
import { existsSync as
|
|
66147
|
-
import { join as
|
|
66499
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
66500
|
+
import { join as join33 } from "node:path";
|
|
66148
66501
|
function parseStepFileScopes(prompt) {
|
|
66149
66502
|
const result = /* @__PURE__ */ new Map();
|
|
66150
66503
|
if (!prompt) return result;
|
|
@@ -66431,7 +66784,7 @@ var init_step_session_executor = __esm({
|
|
|
66431
66784
|
init_worktree_names();
|
|
66432
66785
|
init_agent_logger();
|
|
66433
66786
|
init_logger2();
|
|
66434
|
-
|
|
66787
|
+
init_fallback_model_observer();
|
|
66435
66788
|
init_context_limit_detector();
|
|
66436
66789
|
init_usage_limit_detector();
|
|
66437
66790
|
init_agent_tools();
|
|
@@ -66548,7 +66901,7 @@ var init_step_session_executor = __esm({
|
|
|
66548
66901
|
}
|
|
66549
66902
|
for (const [stepIdx, worktreePath] of this.parallelWorktrees) {
|
|
66550
66903
|
try {
|
|
66551
|
-
if (
|
|
66904
|
+
if (existsSync27(worktreePath)) {
|
|
66552
66905
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66553
66906
|
cwd: this.options.rootDir
|
|
66554
66907
|
});
|
|
@@ -66713,7 +67066,13 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66713
67066
|
...this.options.skillSelection ? { skillSelection: this.options.skillSelection } : {},
|
|
66714
67067
|
taskId: taskDetail.id,
|
|
66715
67068
|
taskTitle: taskDetail.title,
|
|
66716
|
-
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
|
+
})
|
|
66717
67076
|
});
|
|
66718
67077
|
session = createResult.session;
|
|
66719
67078
|
const handle = {
|
|
@@ -66893,7 +67252,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66893
67252
|
for (const [stepIdx, worktreePath] of worktreePaths) {
|
|
66894
67253
|
if (worktreePath !== this.options.worktreePath) {
|
|
66895
67254
|
try {
|
|
66896
|
-
if (
|
|
67255
|
+
if (existsSync27(worktreePath)) {
|
|
66897
67256
|
await execAsync4(`git worktree remove "${worktreePath}" --force`, {
|
|
66898
67257
|
cwd: this.options.rootDir
|
|
66899
67258
|
});
|
|
@@ -66923,7 +67282,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66923
67282
|
async createStepWorktree(stepIndex) {
|
|
66924
67283
|
const { rootDir } = this.options;
|
|
66925
67284
|
const name = generateWorktreeName(rootDir);
|
|
66926
|
-
const worktreePath =
|
|
67285
|
+
const worktreePath = join33(rootDir, ".worktrees", name);
|
|
66927
67286
|
const branchName = `fusion/step-${stepIndex}-${name}`;
|
|
66928
67287
|
stepExecLog.log(`Creating worktree for step ${stepIndex}: ${worktreePath} (branch: ${branchName})`);
|
|
66929
67288
|
try {
|
|
@@ -66998,7 +67357,7 @@ Follow instructions precisely and avoid unrelated changes.`,
|
|
|
66998
67357
|
|
|
66999
67358
|
// ../engine/src/spec-staleness.ts
|
|
67000
67359
|
import { stat as stat5 } from "node:fs/promises";
|
|
67001
|
-
import { join as
|
|
67360
|
+
import { join as join34 } from "node:path";
|
|
67002
67361
|
async function evaluateSpecStaleness(options) {
|
|
67003
67362
|
const { settings, promptPath, nowMs } = options;
|
|
67004
67363
|
if (settings.specStalenessEnabled !== true) {
|
|
@@ -67038,7 +67397,7 @@ async function evaluateSpecStaleness(options) {
|
|
|
67038
67397
|
};
|
|
67039
67398
|
}
|
|
67040
67399
|
function getPromptPath(tasksDir, taskId) {
|
|
67041
|
-
return
|
|
67400
|
+
return join34(tasksDir, taskId, "PROMPT.md");
|
|
67042
67401
|
}
|
|
67043
67402
|
var DEFAULT_SPEC_STALENESS_MAX_AGE_MS;
|
|
67044
67403
|
var init_spec_staleness = __esm({
|
|
@@ -67069,8 +67428,8 @@ var init_task_completion = __esm({
|
|
|
67069
67428
|
|
|
67070
67429
|
// ../engine/src/run-verification-tool.ts
|
|
67071
67430
|
import { spawn as spawn4 } from "node:child_process";
|
|
67072
|
-
import { existsSync as
|
|
67073
|
-
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";
|
|
67074
67433
|
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
67075
67434
|
function createBuffer() {
|
|
67076
67435
|
return { headChunks: [], headBytes: 0, tailChunks: [], tailBytes: 0, totalBytes: 0 };
|
|
@@ -67103,7 +67462,7 @@ function flattenBuffer(buf) {
|
|
|
67103
67462
|
|
|
67104
67463
|
` + tail;
|
|
67105
67464
|
}
|
|
67106
|
-
async function
|
|
67465
|
+
async function runVerificationCommand3(opts) {
|
|
67107
67466
|
const { command, cwd, timeoutMs, expectFailure = false, onHeartbeat, onLine } = opts;
|
|
67108
67467
|
const startMs = Date.now();
|
|
67109
67468
|
const warnings = [];
|
|
@@ -67243,7 +67602,7 @@ function createRunVerificationTool(opts) {
|
|
|
67243
67602
|
if (params.cwd && isAbsolute10(params.cwd)) {
|
|
67244
67603
|
resolvedCwd = params.cwd;
|
|
67245
67604
|
} else if (params.cwd) {
|
|
67246
|
-
resolvedCwd =
|
|
67605
|
+
resolvedCwd = join35(worktreePath, params.cwd);
|
|
67247
67606
|
} else {
|
|
67248
67607
|
resolvedCwd = worktreePath;
|
|
67249
67608
|
}
|
|
@@ -67258,8 +67617,8 @@ function createRunVerificationTool(opts) {
|
|
|
67258
67617
|
}
|
|
67259
67618
|
let effectiveCommand = command;
|
|
67260
67619
|
if (command.trimStart().startsWith("pnpm --filter")) {
|
|
67261
|
-
const modulesYaml =
|
|
67262
|
-
if (!
|
|
67620
|
+
const modulesYaml = join35(rootDir, "node_modules", ".modules.yaml");
|
|
67621
|
+
if (!existsSync28(modulesYaml)) {
|
|
67263
67622
|
const installCmd = "pnpm install --prefer-offline";
|
|
67264
67623
|
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
67265
67624
|
warnings.push(msg);
|
|
@@ -67270,7 +67629,7 @@ function createRunVerificationTool(opts) {
|
|
|
67270
67629
|
log18.info(
|
|
67271
67630
|
`[fn_run_verification] ${taskId}: scope=${scope} timeout=${timeoutSec}s cwd=${resolvedCwd} cmd=${effectiveCommand}`
|
|
67272
67631
|
);
|
|
67273
|
-
const result = await
|
|
67632
|
+
const result = await runVerificationCommand3({
|
|
67274
67633
|
command: effectiveCommand,
|
|
67275
67634
|
cwd: resolvedCwd,
|
|
67276
67635
|
timeoutMs,
|
|
@@ -67370,8 +67729,8 @@ var init_run_verification_tool = __esm({
|
|
|
67370
67729
|
// ../engine/src/executor.ts
|
|
67371
67730
|
import { exec as exec6 } from "node:child_process";
|
|
67372
67731
|
import { promisify as promisify7 } from "node:util";
|
|
67373
|
-
import { isAbsolute as isAbsolute11, join as
|
|
67374
|
-
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";
|
|
67375
67734
|
import { readFile as readFile15, writeFile as writeFile12 } from "node:fs/promises";
|
|
67376
67735
|
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
67377
67736
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
@@ -67665,6 +68024,7 @@ var init_executor = __esm({
|
|
|
67665
68024
|
"use strict";
|
|
67666
68025
|
init_src();
|
|
67667
68026
|
init_merger();
|
|
68027
|
+
init_verification_utils();
|
|
67668
68028
|
init_worktree_names();
|
|
67669
68029
|
init_pi();
|
|
67670
68030
|
init_session_token_usage();
|
|
@@ -67689,7 +68049,7 @@ var init_executor = __esm({
|
|
|
67689
68049
|
init_task_completion();
|
|
67690
68050
|
init_auth_storage();
|
|
67691
68051
|
init_run_verification_tool();
|
|
67692
|
-
|
|
68052
|
+
init_fallback_model_observer();
|
|
67693
68053
|
init_agent_logger();
|
|
67694
68054
|
init_agent_tools();
|
|
67695
68055
|
execAsync5 = promisify7(exec6);
|
|
@@ -68836,7 +69196,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
68836
69196
|
);
|
|
68837
69197
|
return false;
|
|
68838
69198
|
}
|
|
68839
|
-
if (task.worktree &&
|
|
69199
|
+
if (task.worktree && existsSync29(task.worktree)) {
|
|
68840
69200
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
68841
69201
|
if (modifiedFiles.length > 0) {
|
|
68842
69202
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
@@ -69016,7 +69376,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69016
69376
|
if (task.dependencies.length === 0) return null;
|
|
69017
69377
|
for (const depId of task.dependencies) {
|
|
69018
69378
|
const dep = allTasks.find((t) => t.id === depId);
|
|
69019
|
-
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)) {
|
|
69020
69380
|
return dep.worktree;
|
|
69021
69381
|
}
|
|
69022
69382
|
}
|
|
@@ -69067,7 +69427,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69067
69427
|
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
69068
69428
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
69069
69429
|
if (!isActiveTask) {
|
|
69070
|
-
const tasksDir =
|
|
69430
|
+
const tasksDir = join36(this.store.getFusionDir(), "tasks");
|
|
69071
69431
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
69072
69432
|
const staleness = await evaluateSpecStaleness({ settings, promptPath });
|
|
69073
69433
|
if (staleness.isStale) {
|
|
@@ -69114,7 +69474,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69114
69474
|
worktreeName = generateWorktreeName(this.rootDir);
|
|
69115
69475
|
break;
|
|
69116
69476
|
}
|
|
69117
|
-
worktreePath =
|
|
69477
|
+
worktreePath = join36(this.rootDir, ".worktrees", worktreeName);
|
|
69118
69478
|
}
|
|
69119
69479
|
let stuckRequeue = null;
|
|
69120
69480
|
let taskDone = false;
|
|
@@ -69138,7 +69498,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69138
69498
|
);
|
|
69139
69499
|
}
|
|
69140
69500
|
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
69141
|
-
let isResume =
|
|
69501
|
+
let isResume = existsSync29(worktreePath);
|
|
69142
69502
|
let acquiredFromPool = false;
|
|
69143
69503
|
const baseBranch = task.baseBranch || null;
|
|
69144
69504
|
if (task.worktree && isResume && !await isUsableTaskWorktree(this.rootDir, worktreePath)) {
|
|
@@ -69151,8 +69511,8 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69151
69511
|
this.currentRunContext
|
|
69152
69512
|
);
|
|
69153
69513
|
await this.store.updateTask(task.id, { worktree: null, branch: null });
|
|
69154
|
-
worktreePath =
|
|
69155
|
-
isResume =
|
|
69514
|
+
worktreePath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
69515
|
+
isResume = existsSync29(worktreePath);
|
|
69156
69516
|
}
|
|
69157
69517
|
if (!isResume) {
|
|
69158
69518
|
if (this.options.pool && settings.recycleWorktrees) {
|
|
@@ -69378,6 +69738,84 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69378
69738
|
if (await this.shouldDeferCompletionForGlobalPause(task.id, "before workflow steps after step-session completion")) {
|
|
69379
69739
|
return;
|
|
69380
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
|
+
}
|
|
69381
69819
|
if (executionMode !== "fast") {
|
|
69382
69820
|
const workflowResult = await this.runWorkflowSteps(task, worktreePath, settings);
|
|
69383
69821
|
if (workflowResult === "deferred-paused") {
|
|
@@ -69466,7 +69904,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69466
69904
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
69467
69905
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
69468
69906
|
}
|
|
69469
|
-
if (worktreePath &&
|
|
69907
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69470
69908
|
try {
|
|
69471
69909
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69472
69910
|
await audit.git({ type: "worktree:remove", target: worktreePath });
|
|
@@ -69525,7 +69963,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69525
69963
|
try {
|
|
69526
69964
|
const latestTask = await this.store.getTask(task.id);
|
|
69527
69965
|
await this.resetStepsIfWorkLost(latestTask);
|
|
69528
|
-
if (worktreePath &&
|
|
69966
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
69529
69967
|
try {
|
|
69530
69968
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
69531
69969
|
} catch (wtErr) {
|
|
@@ -69637,7 +70075,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69637
70075
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
69638
70076
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
69639
70077
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
69640
|
-
const isResuming = !!task.sessionFile &&
|
|
70078
|
+
const isResuming = !!task.sessionFile && existsSync29(task.sessionFile);
|
|
69641
70079
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
69642
70080
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
69643
70081
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -69667,7 +70105,13 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
69667
70105
|
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
69668
70106
|
taskId: task.id,
|
|
69669
70107
|
taskTitle: detail.title,
|
|
69670
|
-
onFallbackModelUsed:
|
|
70108
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
70109
|
+
agent: "executor",
|
|
70110
|
+
label: "executor",
|
|
70111
|
+
store: this.store,
|
|
70112
|
+
taskId: task.id,
|
|
70113
|
+
taskTitle: detail.title
|
|
70114
|
+
})
|
|
69671
70115
|
});
|
|
69672
70116
|
if (isResuming) {
|
|
69673
70117
|
executorLog.log(`${task.id}: resumed session from ${task.sessionFile}`);
|
|
@@ -70101,7 +70545,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70101
70545
|
this.options.onComplete?.(task);
|
|
70102
70546
|
} else {
|
|
70103
70547
|
executorLog.log(`${task.id} paused \u2014 moving to todo`);
|
|
70104
|
-
if (worktreePath &&
|
|
70548
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70105
70549
|
try {
|
|
70106
70550
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70107
70551
|
executorLog.log(`Removed old worktree for paused task: ${worktreePath}`);
|
|
@@ -70197,7 +70641,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70197
70641
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}: ${errorMessage}`);
|
|
70198
70642
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay3}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
70199
70643
|
}
|
|
70200
|
-
if (worktreePath &&
|
|
70644
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70201
70645
|
try {
|
|
70202
70646
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70203
70647
|
executorLog.log(`Removed old worktree for transient retry: ${worktreePath}`);
|
|
@@ -70252,7 +70696,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
|
|
|
70252
70696
|
try {
|
|
70253
70697
|
const latestTask = await this.store.getTask(task.id);
|
|
70254
70698
|
await this.resetStepsIfWorkLost(latestTask);
|
|
70255
|
-
if (worktreePath &&
|
|
70699
|
+
if (worktreePath && existsSync29(worktreePath)) {
|
|
70256
70700
|
try {
|
|
70257
70701
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
70258
70702
|
executorLog.log(`Removed old worktree for stuck-killed retry: ${worktreePath}`);
|
|
@@ -70757,7 +71201,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
70757
71201
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70758
71202
|
*/
|
|
70759
71203
|
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
70760
|
-
const promptPath =
|
|
71204
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70761
71205
|
let content;
|
|
70762
71206
|
try {
|
|
70763
71207
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70811,6 +71255,217 @@ ${feedback}
|
|
|
70811
71255
|
*
|
|
70812
71256
|
* @returns true if a retry was scheduled, false if retries are exhausted
|
|
70813
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
|
+
}
|
|
70814
71469
|
async handleWorkflowStepFailure(task, worktreePath, failureFeedback, stepName) {
|
|
70815
71470
|
this.clearCompletedTaskWatchdog(task.id);
|
|
70816
71471
|
const currentRetries = task.workflowStepRetries ?? 0;
|
|
@@ -70880,7 +71535,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
70880
71535
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
70881
71536
|
*/
|
|
70882
71537
|
async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount) {
|
|
70883
|
-
const promptPath =
|
|
71538
|
+
const promptPath = join36(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
70884
71539
|
let content;
|
|
70885
71540
|
try {
|
|
70886
71541
|
content = await readFile15(promptPath, "utf-8");
|
|
@@ -70945,31 +71600,33 @@ ${failureFeedback}
|
|
|
70945
71600
|
* Uses git diff against the stored baseCommitSha to determine what changed.
|
|
70946
71601
|
* Returns an empty array if no changes or if git commands fail.
|
|
70947
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
|
+
}
|
|
70948
71627
|
async captureModifiedFiles(worktreePath, baseCommitSha) {
|
|
70949
71628
|
try {
|
|
70950
|
-
|
|
70951
|
-
if (!baseRef) {
|
|
70952
|
-
try {
|
|
70953
|
-
const { stdout: stdout2 } = await execAsync5("git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main", {
|
|
70954
|
-
cwd: worktreePath,
|
|
70955
|
-
encoding: "utf-8"
|
|
70956
|
-
});
|
|
70957
|
-
baseRef = stdout2.trim();
|
|
70958
|
-
} catch (mergeBaseErr) {
|
|
70959
|
-
const mergeBaseMsg = mergeBaseErr instanceof Error ? mergeBaseErr.message : String(mergeBaseErr);
|
|
70960
|
-
executorLog.warn(`Failed merge-base lookup for diff base in ${worktreePath}, trying HEAD~1 fallback: ${mergeBaseMsg}`);
|
|
70961
|
-
try {
|
|
70962
|
-
const { stdout: stdout2 } = await execAsync5("git rev-parse HEAD~1", {
|
|
70963
|
-
cwd: worktreePath,
|
|
70964
|
-
encoding: "utf-8"
|
|
70965
|
-
});
|
|
70966
|
-
baseRef = stdout2.trim();
|
|
70967
|
-
} catch {
|
|
70968
|
-
executorLog.log(`Could not determine base commit for diff in ${worktreePath}`);
|
|
70969
|
-
return [];
|
|
70970
|
-
}
|
|
70971
|
-
}
|
|
70972
|
-
}
|
|
71629
|
+
const baseRef = await this.resolveDiffBaseRef(worktreePath, baseCommitSha);
|
|
70973
71630
|
if (!baseRef) {
|
|
70974
71631
|
return [];
|
|
70975
71632
|
}
|
|
@@ -71205,6 +71862,30 @@ ${failureFeedback}
|
|
|
71205
71862
|
*/
|
|
71206
71863
|
async executeWorkflowStep(task, workflowStep, worktreePath, settings) {
|
|
71207
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.`;
|
|
71208
71889
|
const systemPrompt = `You are a workflow step agent executing: ${workflowStep.name}
|
|
71209
71890
|
|
|
71210
71891
|
Task Context:
|
|
@@ -71212,6 +71893,8 @@ Task Context:
|
|
|
71212
71893
|
- Task Description: ${task.description}
|
|
71213
71894
|
- Worktree: ${worktreePath}
|
|
71214
71895
|
|
|
71896
|
+
${scopeBlock}
|
|
71897
|
+
|
|
71215
71898
|
Your role:
|
|
71216
71899
|
- Execute this workflow step exactly as scoped.
|
|
71217
71900
|
- Prioritize high-impact correctness/risk findings over stylistic nits.
|
|
@@ -71680,7 +72363,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71680
72363
|
* rather than fail the task permanently.
|
|
71681
72364
|
*/
|
|
71682
72365
|
async resolveWorktreeStartPoint(startPoint, taskId) {
|
|
71683
|
-
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}"`;
|
|
71684
72367
|
try {
|
|
71685
72368
|
const { stdout } = await execAsync5(command, { cwd: this.rootDir });
|
|
71686
72369
|
return stdout.trim() || startPoint;
|
|
@@ -71700,7 +72383,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71700
72383
|
*/
|
|
71701
72384
|
async tryCreateWorktree(branch, path2, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0) {
|
|
71702
72385
|
await this.assertWorktreePathNotNested(path2, taskId);
|
|
71703
|
-
if (
|
|
72386
|
+
if (existsSync29(path2)) {
|
|
71704
72387
|
const isRegistered = await this.isRegisteredWorktree(path2);
|
|
71705
72388
|
if (!isRegistered) {
|
|
71706
72389
|
await this.store.logEntry(
|
|
@@ -71851,7 +72534,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
71851
72534
|
);
|
|
71852
72535
|
if (shouldGenerateNewName) {
|
|
71853
72536
|
const conflictStartPoint = branch;
|
|
71854
|
-
const newPath =
|
|
72537
|
+
const newPath = join36(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
71855
72538
|
for (let suffix = 2; suffix <= 6; suffix++) {
|
|
71856
72539
|
const suffixedBranch = `${branch}-${suffix}`;
|
|
71857
72540
|
try {
|
|
@@ -72451,7 +73134,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
72451
73134
|
metadata: { type: "spawned", parentTaskId: taskId }
|
|
72452
73135
|
});
|
|
72453
73136
|
const childWorktreeName = generateWorktreeName(this.rootDir);
|
|
72454
|
-
const childWorktreePath =
|
|
73137
|
+
const childWorktreePath = join36(this.rootDir, ".worktrees", childWorktreeName);
|
|
72455
73138
|
const childBranch = `fusion/spawn-${agent.id}`;
|
|
72456
73139
|
await this.createWorktree(childBranch, childWorktreePath, taskId, worktreePath);
|
|
72457
73140
|
await this.options.agentStore.updateAgentState(agent.id, "active");
|
|
@@ -72640,9 +73323,9 @@ var init_node_routing_policy = __esm({
|
|
|
72640
73323
|
});
|
|
72641
73324
|
|
|
72642
73325
|
// ../engine/src/scheduler.ts
|
|
72643
|
-
import { existsSync as
|
|
73326
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
72644
73327
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
72645
|
-
import { basename as basename8, join as
|
|
73328
|
+
import { basename as basename8, join as join37 } from "node:path";
|
|
72646
73329
|
function pathsOverlap2(a, b) {
|
|
72647
73330
|
for (const pa of a) {
|
|
72648
73331
|
const prefixA = pa.endsWith("/*") ? pa.slice(0, -1) : null;
|
|
@@ -72812,12 +73495,12 @@ var init_scheduler = __esm({
|
|
|
72812
73495
|
* @returns Object with `valid: true` if checks pass, or `valid: false` with a `reason` string if they fail
|
|
72813
73496
|
*/
|
|
72814
73497
|
async validateTaskFilesystem(id) {
|
|
72815
|
-
const taskDir =
|
|
72816
|
-
if (!
|
|
73498
|
+
const taskDir = join37(this.store.getTasksDir(), id);
|
|
73499
|
+
if (!existsSync30(taskDir)) {
|
|
72817
73500
|
return { valid: false, reason: "missing directory" };
|
|
72818
73501
|
}
|
|
72819
|
-
const promptPath =
|
|
72820
|
-
if (!
|
|
73502
|
+
const promptPath = join37(taskDir, "PROMPT.md");
|
|
73503
|
+
if (!existsSync30(promptPath)) {
|
|
72821
73504
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
72822
73505
|
}
|
|
72823
73506
|
try {
|
|
@@ -72956,7 +73639,7 @@ var init_scheduler = __esm({
|
|
|
72956
73639
|
break;
|
|
72957
73640
|
}
|
|
72958
73641
|
reservedNames.add(worktreeName);
|
|
72959
|
-
return
|
|
73642
|
+
return join37(this.store.getRootDir(), ".worktrees", worktreeName);
|
|
72960
73643
|
}
|
|
72961
73644
|
/**
|
|
72962
73645
|
* Run one scheduling pass.
|
|
@@ -74233,7 +74916,7 @@ var init_mission_execution_loop = __esm({
|
|
|
74233
74916
|
init_pi();
|
|
74234
74917
|
init_agent_session_helpers();
|
|
74235
74918
|
init_logger2();
|
|
74236
|
-
|
|
74919
|
+
init_fallback_model_observer();
|
|
74237
74920
|
loopLog = createLogger2("mission-loop");
|
|
74238
74921
|
VALIDATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
74239
74922
|
MissionExecutionLoop = class extends EventEmitter17 {
|
|
@@ -74467,7 +75150,13 @@ Assertions: ${assertions.map((a) => a.title).join(", ")}`,
|
|
|
74467
75150
|
},
|
|
74468
75151
|
taskId: task?.id,
|
|
74469
75152
|
taskTitle: task?.title,
|
|
74470
|
-
onFallbackModelUsed:
|
|
75153
|
+
onFallbackModelUsed: createFallbackModelObserver({
|
|
75154
|
+
agent: "reviewer",
|
|
75155
|
+
label: "mission validator",
|
|
75156
|
+
store: this.taskStore,
|
|
75157
|
+
taskId: task?.id,
|
|
75158
|
+
taskTitle: task?.title
|
|
75159
|
+
})
|
|
74471
75160
|
});
|
|
74472
75161
|
session = { session: sessionResult.session, sessionFile: sessionResult.sessionFile };
|
|
74473
75162
|
loopLog.log(`Validation session created for feature ${feature.id}`);
|
|
@@ -77732,7 +78421,7 @@ async function createAiPromptExecutor(cwd) {
|
|
|
77732
78421
|
}
|
|
77733
78422
|
};
|
|
77734
78423
|
}
|
|
77735
|
-
function
|
|
78424
|
+
function truncateOutput2(stdout, stderr) {
|
|
77736
78425
|
const out = stdout ?? "";
|
|
77737
78426
|
const err = stderr ?? "";
|
|
77738
78427
|
let combined = out;
|
|
@@ -77912,7 +78601,7 @@ var init_cron_runner = __esm({
|
|
|
77912
78601
|
maxBuffer: MAX_BUFFER,
|
|
77913
78602
|
shell: defaultShell
|
|
77914
78603
|
});
|
|
77915
|
-
const output =
|
|
78604
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77916
78605
|
log15.log(`\u2713 ${schedule.name} completed (${output.length} bytes output)`);
|
|
77917
78606
|
return {
|
|
77918
78607
|
success: true,
|
|
@@ -77923,7 +78612,7 @@ var init_cron_runner = __esm({
|
|
|
77923
78612
|
} catch (err) {
|
|
77924
78613
|
const stdout = err.stdout ?? "";
|
|
77925
78614
|
const stderr = err.stderr ?? "";
|
|
77926
|
-
const output =
|
|
78615
|
+
const output = truncateOutput2(stdout, stderr);
|
|
77927
78616
|
const errorMessage = err.killed ? `Command timed out after ${(schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6) / 1e3}s` : err.message ?? String(err);
|
|
77928
78617
|
log15.warn(`\u2717 ${schedule.name} failed: ${errorMessage}`);
|
|
77929
78618
|
return {
|
|
@@ -77968,7 +78657,7 @@ var init_cron_runner = __esm({
|
|
|
77968
78657
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
77969
78658
|
return {
|
|
77970
78659
|
success: result.success,
|
|
77971
|
-
output:
|
|
78660
|
+
output: truncateOutput2(result.output ?? "", ""),
|
|
77972
78661
|
error: result.success ? void 0 : result.output
|
|
77973
78662
|
};
|
|
77974
78663
|
} catch (err) {
|
|
@@ -78009,7 +78698,7 @@ var init_cron_runner = __esm({
|
|
|
78009
78698
|
if (sr.output) outputParts.push(sr.output);
|
|
78010
78699
|
if (sr.error) outputParts.push(`Error: ${sr.error}`);
|
|
78011
78700
|
}
|
|
78012
|
-
const output =
|
|
78701
|
+
const output = truncateOutput2(outputParts.join("\n"), "");
|
|
78013
78702
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
78014
78703
|
const error = failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0;
|
|
78015
78704
|
const status = overallSuccess ? "\u2713" : "\u2717";
|
|
@@ -78088,7 +78777,7 @@ var init_cron_runner = __esm({
|
|
|
78088
78777
|
stepName: step.name,
|
|
78089
78778
|
stepIndex,
|
|
78090
78779
|
success: true,
|
|
78091
|
-
output:
|
|
78780
|
+
output: truncateOutput2(stdout, stderr),
|
|
78092
78781
|
startedAt,
|
|
78093
78782
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78094
78783
|
};
|
|
@@ -78101,7 +78790,7 @@ var init_cron_runner = __esm({
|
|
|
78101
78790
|
stepName: step.name,
|
|
78102
78791
|
stepIndex,
|
|
78103
78792
|
success: false,
|
|
78104
|
-
output:
|
|
78793
|
+
output: truncateOutput2(stdout, stderr),
|
|
78105
78794
|
error: errorMessage,
|
|
78106
78795
|
startedAt,
|
|
78107
78796
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78251,7 +78940,7 @@ var init_cron_runner = __esm({
|
|
|
78251
78940
|
// ../engine/src/routine-runner.ts
|
|
78252
78941
|
import { exec as exec8 } from "node:child_process";
|
|
78253
78942
|
import { promisify as promisify8 } from "node:util";
|
|
78254
|
-
function
|
|
78943
|
+
function truncateOutput3(stdout, stderr) {
|
|
78255
78944
|
let output = stdout;
|
|
78256
78945
|
if (stderr) {
|
|
78257
78946
|
output += stdout ? "\n--- stderr ---\n" : "";
|
|
@@ -78434,7 +79123,7 @@ var init_routine_runner = __esm({
|
|
|
78434
79123
|
const result = await runBackupCommand2(fusionDir, settings);
|
|
78435
79124
|
return {
|
|
78436
79125
|
success: result.success,
|
|
78437
|
-
output:
|
|
79126
|
+
output: truncateOutput3(result.output ?? "", ""),
|
|
78438
79127
|
error: result.success ? void 0 : result.output,
|
|
78439
79128
|
startedAt,
|
|
78440
79129
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78458,7 +79147,7 @@ var init_routine_runner = __esm({
|
|
|
78458
79147
|
});
|
|
78459
79148
|
return {
|
|
78460
79149
|
success: true,
|
|
78461
|
-
output:
|
|
79150
|
+
output: truncateOutput3(stdout, stderr),
|
|
78462
79151
|
startedAt,
|
|
78463
79152
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
78464
79153
|
};
|
|
@@ -78469,7 +79158,7 @@ var init_routine_runner = __esm({
|
|
|
78469
79158
|
const error = errObj.killed === true ? `Command timed out after ${(timeoutMs ?? DEFAULT_TIMEOUT_MS7) / 1e3}s` : (err instanceof Error ? err.message : null) ?? String(err);
|
|
78470
79159
|
return {
|
|
78471
79160
|
success: false,
|
|
78472
|
-
output:
|
|
79161
|
+
output: truncateOutput3(stdout, stderr),
|
|
78473
79162
|
error,
|
|
78474
79163
|
startedAt,
|
|
78475
79164
|
completedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -78502,7 +79191,7 @@ var init_routine_runner = __esm({
|
|
|
78502
79191
|
const failedSteps = stepResults.filter((sr) => !sr.success);
|
|
78503
79192
|
return {
|
|
78504
79193
|
success: overallSuccess,
|
|
78505
|
-
output:
|
|
79194
|
+
output: truncateOutput3(outputParts.join("\n"), ""),
|
|
78506
79195
|
error: failedSteps.length > 0 ? `${failedSteps.length} step(s) failed: ${failedSteps.map((s) => s.stepName).join(", ")}${stoppedEarly ? " (execution stopped)" : ""}` : void 0,
|
|
78507
79196
|
startedAt,
|
|
78508
79197
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -78537,7 +79226,7 @@ var init_routine_runner = __esm({
|
|
|
78537
79226
|
this.options.aiPromptExecutor(step.prompt, step.modelProvider, step.modelId),
|
|
78538
79227
|
new Promise((_resolve, reject) => setTimeout(() => reject(new Error(`AI prompt step timed out after ${timeoutMs / 1e3}s`)), timeoutMs))
|
|
78539
79228
|
]);
|
|
78540
|
-
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() };
|
|
78541
79230
|
} catch (err) {
|
|
78542
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() };
|
|
78543
79232
|
}
|
|
@@ -79151,8 +79840,8 @@ var init_stuck_task_detector = __esm({
|
|
|
79151
79840
|
// ../engine/src/self-healing.ts
|
|
79152
79841
|
import { exec as exec9 } from "node:child_process";
|
|
79153
79842
|
import { promisify as promisify9 } from "node:util";
|
|
79154
|
-
import { existsSync as
|
|
79155
|
-
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";
|
|
79156
79845
|
function shellQuote(value) {
|
|
79157
79846
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
79158
79847
|
}
|
|
@@ -79548,7 +80237,7 @@ var init_self_healing = __esm({
|
|
|
79548
80237
|
return commit;
|
|
79549
80238
|
}
|
|
79550
80239
|
async cleanupInterruptedMergeArtifacts(task) {
|
|
79551
|
-
if (task.worktree &&
|
|
80240
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
79552
80241
|
try {
|
|
79553
80242
|
await execAsync7(`git worktree remove ${shellQuote(task.worktree)} --force`, {
|
|
79554
80243
|
cwd: this.options.rootDir,
|
|
@@ -80169,7 +80858,7 @@ var init_self_healing = __esm({
|
|
|
80169
80858
|
return false;
|
|
80170
80859
|
}
|
|
80171
80860
|
const staleness = now - new Date(t.updatedAt).getTime();
|
|
80172
|
-
const hasWorktree = t.worktree &&
|
|
80861
|
+
const hasWorktree = t.worktree && existsSync31(t.worktree);
|
|
80173
80862
|
const graceMs = hasWorktree ? ORPHANED_WITH_WORKTREE_GRACE_MS : ORPHANED_EXECUTION_RECOVERY_GRACE_MS;
|
|
80174
80863
|
return staleness >= graceMs;
|
|
80175
80864
|
});
|
|
@@ -80178,7 +80867,7 @@ var init_self_healing = __esm({
|
|
|
80178
80867
|
let recovered = 0;
|
|
80179
80868
|
for (const task of orphaned) {
|
|
80180
80869
|
try {
|
|
80181
|
-
const hadWorktree = task.worktree &&
|
|
80870
|
+
const hadWorktree = task.worktree && existsSync31(task.worktree);
|
|
80182
80871
|
const reason = hadWorktree ? "worktree exists but no active session" : "missing worktree/session";
|
|
80183
80872
|
await this.resetStepsIfWorkLost(task);
|
|
80184
80873
|
await this.store.updateTask(task.id, {
|
|
@@ -80324,7 +81013,7 @@ var init_self_healing = __esm({
|
|
|
80324
81013
|
}
|
|
80325
81014
|
}
|
|
80326
81015
|
async hasRecoverableGitWork(task) {
|
|
80327
|
-
if (task.worktree &&
|
|
81016
|
+
if (task.worktree && existsSync31(task.worktree)) {
|
|
80328
81017
|
try {
|
|
80329
81018
|
const { stdout: status } = await execAsync7("git status --porcelain", {
|
|
80330
81019
|
cwd: task.worktree,
|
|
@@ -80509,11 +81198,11 @@ var init_self_healing = __esm({
|
|
|
80509
81198
|
* tracks registered idle worktrees, never these orphans.
|
|
80510
81199
|
*/
|
|
80511
81200
|
async reapUnregisteredOrphans() {
|
|
80512
|
-
const worktreesDir =
|
|
80513
|
-
if (!
|
|
81201
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81202
|
+
if (!existsSync31(worktreesDir)) return 0;
|
|
80514
81203
|
let dirs;
|
|
80515
81204
|
try {
|
|
80516
|
-
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));
|
|
80517
81206
|
} catch (err) {
|
|
80518
81207
|
log17.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
80519
81208
|
return 0;
|
|
@@ -80618,8 +81307,8 @@ var init_self_healing = __esm({
|
|
|
80618
81307
|
}
|
|
80619
81308
|
/** Remove oldest idle worktrees if total count exceeds 2× maxWorktrees. */
|
|
80620
81309
|
async enforceWorktreeCap() {
|
|
80621
|
-
const worktreesDir =
|
|
80622
|
-
if (!
|
|
81310
|
+
const worktreesDir = join38(this.options.rootDir, ".worktrees");
|
|
81311
|
+
if (!existsSync31(worktreesDir)) return;
|
|
80623
81312
|
try {
|
|
80624
81313
|
const settings = await this.store.getSettings();
|
|
80625
81314
|
const cap = (settings.maxWorktrees ?? 4) * 2;
|
|
@@ -82457,7 +83146,7 @@ var init_ipc_host = __esm({
|
|
|
82457
83146
|
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
82458
83147
|
import { fork } from "node:child_process";
|
|
82459
83148
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
82460
|
-
import { dirname as dirname11, join as
|
|
83149
|
+
import { dirname as dirname11, join as join39 } from "node:path";
|
|
82461
83150
|
var HealthMonitor, ChildProcessRuntime;
|
|
82462
83151
|
var init_child_process_runtime = __esm({
|
|
82463
83152
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -82619,7 +83308,7 @@ var init_child_process_runtime = __esm({
|
|
|
82619
83308
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
82620
83309
|
const currentDir = dirname11(fileURLToPath3(import.meta.url));
|
|
82621
83310
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
82622
|
-
return
|
|
83311
|
+
return join39(currentDir, workerFile);
|
|
82623
83312
|
}
|
|
82624
83313
|
/**
|
|
82625
83314
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -86872,6 +87561,8 @@ __export(src_exports2, {
|
|
|
86872
87561
|
describeAgentModel: () => describeAgentModel,
|
|
86873
87562
|
describeModel: () => describeModel,
|
|
86874
87563
|
ensureDefaultHeartbeatProcedureFile: () => ensureDefaultHeartbeatProcedureFile,
|
|
87564
|
+
extractRuntimeHint: () => extractRuntimeHint,
|
|
87565
|
+
extractRuntimeModel: () => extractRuntimeModel,
|
|
86875
87566
|
formatTaskIdentifier: () => formatTaskIdentifier,
|
|
86876
87567
|
getDefaultPiRuntime: () => getDefaultPiRuntime,
|
|
86877
87568
|
getHostExtensionPaths: () => getHostExtensionPaths,
|
|
@@ -101430,7 +102121,7 @@ var init_auth_middleware = __esm({
|
|
|
101430
102121
|
|
|
101431
102122
|
// ../dashboard/src/server.ts
|
|
101432
102123
|
import express from "express";
|
|
101433
|
-
import { join as
|
|
102124
|
+
import { join as join40, dirname as dirname12 } from "node:path";
|
|
101434
102125
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
101435
102126
|
function clearAiSessionCleanupInterval() {
|
|
101436
102127
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -101722,8 +102413,8 @@ __export(task_exports, {
|
|
|
101722
102413
|
runTaskUpdate: () => runTaskUpdate
|
|
101723
102414
|
});
|
|
101724
102415
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
101725
|
-
import { watchFile, unwatchFile, statSync as statSync6, existsSync as
|
|
101726
|
-
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";
|
|
101727
102418
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
101728
102419
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
101729
102420
|
const issueUrl = sourceMetadata.issueUrl;
|
|
@@ -102036,8 +102727,8 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102036
102727
|
printEntries(filteredEntries);
|
|
102037
102728
|
if (options.follow) {
|
|
102038
102729
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
102039
|
-
const logPath =
|
|
102040
|
-
if (!
|
|
102730
|
+
const logPath = join41(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
102731
|
+
if (!existsSync32(logPath)) {
|
|
102041
102732
|
console.log(`
|
|
102042
102733
|
Waiting for log file to be created...`);
|
|
102043
102734
|
}
|
|
@@ -102066,7 +102757,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
102066
102757
|
lastPosition = 0;
|
|
102067
102758
|
}
|
|
102068
102759
|
if (stats.size > lastPosition) {
|
|
102069
|
-
const content =
|
|
102760
|
+
const content = readFileSync11(logPath, "utf-8");
|
|
102070
102761
|
const lines = content.slice(lastPosition).split("\n");
|
|
102071
102762
|
for (const line of lines) {
|
|
102072
102763
|
if (!line.trim()) continue;
|
|
@@ -103179,9 +103870,9 @@ init_src();
|
|
|
103179
103870
|
init_gh_cli();
|
|
103180
103871
|
import { Type as Type8 } from "typebox";
|
|
103181
103872
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
103182
|
-
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";
|
|
103183
103874
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
103184
|
-
import { existsSync as
|
|
103875
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
103185
103876
|
import { spawn as spawn11 } from "node:child_process";
|
|
103186
103877
|
var MIME_TYPES2 = {
|
|
103187
103878
|
".png": "image/png",
|
|
@@ -103201,7 +103892,7 @@ var MIME_TYPES2 = {
|
|
|
103201
103892
|
function resolveProjectRoot2(cwd) {
|
|
103202
103893
|
let current = resolve19(cwd);
|
|
103203
103894
|
while (true) {
|
|
103204
|
-
if (
|
|
103895
|
+
if (existsSync33(join42(current, ".fusion"))) {
|
|
103205
103896
|
return current;
|
|
103206
103897
|
}
|
|
103207
103898
|
const parent = resolve19(current, "..");
|
|
@@ -103222,7 +103913,7 @@ async function getStore2(cwd) {
|
|
|
103222
103913
|
return store;
|
|
103223
103914
|
}
|
|
103224
103915
|
function getFusionDir(cwd) {
|
|
103225
|
-
return
|
|
103916
|
+
return join42(resolveProjectRoot2(cwd), ".fusion");
|
|
103226
103917
|
}
|
|
103227
103918
|
async function validateAssignableAgentId(cwd, agentId) {
|
|
103228
103919
|
const { AgentStore: AgentStore2, isEphemeralAgent: isEphemeralAgent2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|