@runfusion/fusion 0.35.0 → 0.36.0
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 +2526 -575
- package/dist/client/.vite/manifest.json +59 -59
- package/dist/client/assets/{AgentDetailView-CFX3jxfA.js → AgentDetailView-CGycokdR.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-4tnYHS0-.js → AgentPermissionPolicyEditor-BoD6come.js} +1 -1
- package/dist/client/assets/{AgentsView-DnNWfwiY.js → AgentsView-B_afRRfc.js} +3 -3
- package/dist/client/assets/{ChatView-rwl2O-s0.js → ChatView-IjNrSZSf.js} +1 -1
- package/dist/client/assets/{DevServerView-C9fEzcFQ.js → DevServerView-Dd613aZ5.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-Cwyr0Sl4.js → DirectoryPicker-H-fUl2dF.js} +1 -1
- package/dist/client/assets/{DocumentsView-gDWTMU2w.js → DocumentsView-Dshc9e5K.js} +1 -1
- package/dist/client/assets/{EvalsView-Cw7D1x0W.js → EvalsView-50W0K5h7.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-8yeOcqbX.js → ExperimentalAgentOnboardingModal-BXBljPFj.js} +1 -1
- package/dist/client/assets/{GoalsView-kRIhgAA6.js → GoalsView-Dh508Z2s.js} +1 -1
- package/dist/client/assets/{InsightsView-IHHikd5n.js → InsightsView-sVbGRKTl.js} +1 -1
- package/dist/client/assets/{MemoryView-pii3HOba.js → MemoryView-006a_sB0.js} +1 -1
- package/dist/client/assets/{NodesView-CoDK14B3.js → NodesView-CiRPqgVx.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-vH7EJ5JD.js → PiExtensionsManager-Y1ETuTxf.js} +1 -1
- package/dist/client/assets/{PluginManager-PbkFQ0Nu.js → PluginManager-Df_KHiXn.js} +1 -1
- package/dist/client/assets/{ResearchView-Dp4g_BWq.js → ResearchView-DU_QuS_-.js} +1 -1
- package/dist/client/assets/{SecretsView-Q8wES8_N.js → SecretsView-D61Jvysn.js} +1 -1
- package/dist/client/assets/{SettingsModal-DdUztATB.js → SettingsModal-BarzmqNb.js} +1 -1
- package/dist/client/assets/{SettingsModal-127YAB8c.js → SettingsModal-Cpur3WpT.js} +4 -4
- package/dist/client/assets/{SetupWizardModal-BSAFiQpJ.js → SetupWizardModal-BF3iRNq9.js} +1 -1
- package/dist/client/assets/{SkillsView-Bj8KQQXB.js → SkillsView-tCKOFw3j.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-DvKN_b_k.js → StashRecoveryView-nGtypd10.js} +1 -1
- package/dist/client/assets/{TodoView-C-cabt2D.js → TodoView-IyGPV6AO.js} +1 -1
- package/dist/client/assets/{dashboard-view-DrHb8VOl.js → dashboard-view-DiA5-hnb.js} +1 -1
- package/dist/client/assets/{folder-open-CYVKFKAx.js → folder-open-Byqfi_N-.js} +1 -1
- package/dist/client/assets/index-B9Ro_NwC.js +742 -0
- package/dist/client/assets/index-DVFBXjGT.css +1 -0
- package/dist/client/assets/{upload-eQh-mH3-.js → upload-D6_BjqVi.js} +1 -1
- package/dist/client/assets/{users-B5upejXg.js → users-CERYgInO.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/index.ts +1 -1
- package/dist/droid-cli/package.json +3 -3
- package/dist/droid-cli/src/__tests__/thinking-config.test.ts +1 -1
- package/dist/extension.js +1738 -277
- package/dist/pi-claude-cli/index.ts +3 -3
- package/dist/pi-claude-cli/package.json +3 -3
- package/dist/pi-claude-cli/src/__tests__/event-bridge.test.ts +3 -3
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +3 -3
- package/dist/pi-claude-cli/src/__tests__/thinking-config.test.ts +1 -1
- package/dist/pi-claude-cli/src/event-bridge.ts +2 -2
- package/dist/pi-claude-cli/src/provider.ts +1 -1
- package/dist/pi-claude-cli/src/thinking-config.ts +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +34607 -33421
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +7 -7
- package/skill/fusion/SKILL.md +1 -0
- package/skill/fusion/references/cli-commands.md +3 -0
- package/skill/fusion/references/extension-tools.md +27 -0
- package/skill/fusion/references/fusion-capabilities.md +3 -0
- package/dist/client/assets/index-Cuh_Pqv0.css +0 -1
- package/dist/client/assets/index-Cze5qbME.js +0 -742
package/dist/extension.js
CHANGED
|
@@ -328,6 +328,7 @@ var init_settings_schema = __esm({
|
|
|
328
328
|
],
|
|
329
329
|
prerebaseDivergenceThreshold: 50,
|
|
330
330
|
mergeConflictStrategy: "smart-prefer-main",
|
|
331
|
+
merger: { mode: "ai", maxReviewPasses: 3 },
|
|
331
332
|
mergeDiffVolumeMinLines: void 0,
|
|
332
333
|
mergeDiffVolumeThreshold: void 0,
|
|
333
334
|
mergeDiffVolumeAllowlist: void 0,
|
|
@@ -407,6 +408,7 @@ var init_settings_schema = __esm({
|
|
|
407
408
|
autoUpdatePrStatus: false,
|
|
408
409
|
githubCommentOnDone: false,
|
|
409
410
|
githubCommentTemplate: void 0,
|
|
411
|
+
githubCloseSourceIssueOnDone: false,
|
|
410
412
|
githubTrackingEnabledByDefault: false,
|
|
411
413
|
githubTrackingDefaultRepo: void 0,
|
|
412
414
|
githubTrackingDedupEnabled: true,
|
|
@@ -1271,6 +1273,9 @@ function normalizePostMergeAuditMode(value) {
|
|
|
1271
1273
|
function normalizeMergeAuditAutoRecovery(value) {
|
|
1272
1274
|
return typeof value === "string" && MERGE_AUDIT_AUTO_RECOVERY_MODES.includes(value) ? value : "ai-assisted";
|
|
1273
1275
|
}
|
|
1276
|
+
function normalizeMergerMode(value) {
|
|
1277
|
+
return typeof value === "string" && MERGER_MODES.includes(value) ? value : "ai";
|
|
1278
|
+
}
|
|
1274
1279
|
function normalizeAutoRecovery(value) {
|
|
1275
1280
|
const fallback = { mode: "deterministic-only", maxRetries: 3 };
|
|
1276
1281
|
if (!value || typeof value !== "object") return fallback;
|
|
@@ -1577,7 +1582,7 @@ function validateMessageMetadata(metadata) {
|
|
|
1577
1582
|
throw new Error("metadata.wakeRecipient must be a boolean");
|
|
1578
1583
|
}
|
|
1579
1584
|
}
|
|
1580
|
-
var THINKING_LEVELS, COLUMNS, DEFAULT_COLUMN, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, warnedLegacyCwdMain, MERGE_ADVANCE_AUTO_SYNC_MODES, MERGE_STRATEGY_OVERLAP_BEHAVIORS, POST_MERGE_AUDIT_MODES, MERGE_AUDIT_AUTO_RECOVERY_MODES, AUTO_RECOVERY_MODES, NOTIFICATION_EVENTS, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, REPO_OVERRIDE_RE, CheckoutConflictError, DUPLICATE_OF_METADATA_KEY, SHARED_STATE_SNAPSHOT_VERSION, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES, AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES, AGENT_PROVISIONING_APPROVAL_MODES, SECRET_ACCESS_POLICIES, SANDBOX_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, DEFAULT_HEARTBEAT_PROCEDURE_PATH, DASHBOARD_USER_ID, DASHBOARD_USER_ALIASES;
|
|
1585
|
+
var THINKING_LEVELS, COLUMNS, DEFAULT_COLUMN, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, HIGH_FANOUT_BLOCKER_TODO_THRESHOLD, STALE_HIGH_FANOUT_BLOCKER_AGE_THRESHOLD_MS, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, warnedLegacyCwdMain, MERGE_ADVANCE_AUTO_SYNC_MODES, MERGE_STRATEGY_OVERLAP_BEHAVIORS, POST_MERGE_AUDIT_MODES, MERGE_AUDIT_AUTO_RECOVERY_MODES, MERGER_MODES, AUTO_RECOVERY_MODES, NOTIFICATION_EVENTS, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, REPO_OVERRIDE_RE, CheckoutConflictError, DUPLICATE_OF_METADATA_KEY, SHARED_STATE_SNAPSHOT_VERSION, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS, PERMANENT_AGENT_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_ACTION_CATEGORIES, AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES, AGENT_PERMISSION_POLICY_EXEMPT_TOOL_EXAMPLES, AGENT_PROVISIONING_APPROVAL_MODES, SECRET_ACCESS_POLICIES, SANDBOX_PROVISIONING_APPROVAL_MODES, AGENT_PERMISSION_POLICY_PRESET_IDS, APPROVAL_REQUEST_STATUSES, APPROVAL_REQUEST_AUDIT_EVENT_TYPES, LEGACY_AGENT_PERMISSION_POLICY_ACTION_CATEGORY_ALIASES, DEFAULT_HEARTBEAT_PROCEDURE_PATH, DASHBOARD_USER_ID, DASHBOARD_USER_ALIASES;
|
|
1581
1586
|
var init_types = __esm({
|
|
1582
1587
|
"../core/src/types.ts"() {
|
|
1583
1588
|
"use strict";
|
|
@@ -1662,6 +1667,7 @@ var init_types = __esm({
|
|
|
1662
1667
|
];
|
|
1663
1668
|
POST_MERGE_AUDIT_MODES = ["block", "warn", "off"];
|
|
1664
1669
|
MERGE_AUDIT_AUTO_RECOVERY_MODES = ["deterministic-only", "programmatic", "ai-assisted", "off"];
|
|
1670
|
+
MERGER_MODES = ["ai", "deterministic"];
|
|
1665
1671
|
AUTO_RECOVERY_MODES = ["off", "deterministic-only", "programmatic", "ai-assisted"];
|
|
1666
1672
|
NOTIFICATION_EVENTS = [
|
|
1667
1673
|
"in-review",
|
|
@@ -4666,7 +4672,7 @@ var init_db = __esm({
|
|
|
4666
4672
|
DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
|
|
4667
4673
|
DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
|
|
4668
4674
|
DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
|
|
4669
|
-
SCHEMA_VERSION =
|
|
4675
|
+
SCHEMA_VERSION = 92;
|
|
4670
4676
|
SCHEMA_SQL = `
|
|
4671
4677
|
-- Tasks table with JSON columns for nested data
|
|
4672
4678
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -5219,6 +5225,17 @@ CREATE TABLE IF NOT EXISTS missions (
|
|
|
5219
5225
|
updatedAt TEXT NOT NULL
|
|
5220
5226
|
);
|
|
5221
5227
|
|
|
5228
|
+
-- Goals table (strategic intent across mission timelines)
|
|
5229
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
5230
|
+
id TEXT PRIMARY KEY,
|
|
5231
|
+
title TEXT NOT NULL,
|
|
5232
|
+
description TEXT,
|
|
5233
|
+
status TEXT NOT NULL,
|
|
5234
|
+
createdAt TEXT NOT NULL,
|
|
5235
|
+
updatedAt TEXT NOT NULL
|
|
5236
|
+
);
|
|
5237
|
+
CREATE INDEX IF NOT EXISTS idxGoalsStatus ON goals(status);
|
|
5238
|
+
|
|
5222
5239
|
-- Milestones table (phases within a mission)
|
|
5223
5240
|
CREATE TABLE IF NOT EXISTS milestones (
|
|
5224
5241
|
id TEXT PRIMARY KEY,
|
|
@@ -7531,6 +7548,24 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
7531
7548
|
this.addColumnIfMissing("missions", "baseBranch", "TEXT");
|
|
7532
7549
|
});
|
|
7533
7550
|
}
|
|
7551
|
+
if (version < 92) {
|
|
7552
|
+
this.applyMigration(92, () => {
|
|
7553
|
+
this.db.exec(`
|
|
7554
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
7555
|
+
id TEXT PRIMARY KEY,
|
|
7556
|
+
title TEXT NOT NULL,
|
|
7557
|
+
description TEXT,
|
|
7558
|
+
status TEXT NOT NULL,
|
|
7559
|
+
createdAt TEXT NOT NULL,
|
|
7560
|
+
updatedAt TEXT NOT NULL
|
|
7561
|
+
)
|
|
7562
|
+
`);
|
|
7563
|
+
this.db.exec(`
|
|
7564
|
+
CREATE INDEX IF NOT EXISTS idxGoalsStatus
|
|
7565
|
+
ON goals(status)
|
|
7566
|
+
`);
|
|
7567
|
+
});
|
|
7568
|
+
}
|
|
7534
7569
|
}
|
|
7535
7570
|
/**
|
|
7536
7571
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -18654,6 +18689,158 @@ var init_todo_store = __esm({
|
|
|
18654
18689
|
}
|
|
18655
18690
|
});
|
|
18656
18691
|
|
|
18692
|
+
// ../core/src/goal-types.ts
|
|
18693
|
+
var ACTIVE_GOAL_LIMIT, ActiveGoalLimitExceededError;
|
|
18694
|
+
var init_goal_types = __esm({
|
|
18695
|
+
"../core/src/goal-types.ts"() {
|
|
18696
|
+
"use strict";
|
|
18697
|
+
ACTIVE_GOAL_LIMIT = 5;
|
|
18698
|
+
ActiveGoalLimitExceededError = class extends Error {
|
|
18699
|
+
constructor(limit, currentActive) {
|
|
18700
|
+
super(`Active goal limit exceeded: ${currentActive}/${limit}`);
|
|
18701
|
+
this.limit = limit;
|
|
18702
|
+
this.currentActive = currentActive;
|
|
18703
|
+
this.name = "ActiveGoalLimitExceededError";
|
|
18704
|
+
}
|
|
18705
|
+
code = "ACTIVE_GOAL_LIMIT_EXCEEDED";
|
|
18706
|
+
};
|
|
18707
|
+
}
|
|
18708
|
+
});
|
|
18709
|
+
|
|
18710
|
+
// ../core/src/goal-store.ts
|
|
18711
|
+
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
18712
|
+
var GoalStore;
|
|
18713
|
+
var init_goal_store = __esm({
|
|
18714
|
+
"../core/src/goal-store.ts"() {
|
|
18715
|
+
"use strict";
|
|
18716
|
+
init_goal_types();
|
|
18717
|
+
GoalStore = class extends EventEmitter10 {
|
|
18718
|
+
constructor(_fusionDir, db) {
|
|
18719
|
+
super();
|
|
18720
|
+
this.db = db;
|
|
18721
|
+
}
|
|
18722
|
+
idSequence = 0;
|
|
18723
|
+
createGoal(input) {
|
|
18724
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
18725
|
+
const goal = this.db.transactionImmediate(() => {
|
|
18726
|
+
const activeCountRow = this.db.prepare("SELECT COUNT(*) as count FROM goals WHERE status = 'active'").get();
|
|
18727
|
+
const currentActive = activeCountRow?.count ?? 0;
|
|
18728
|
+
if (currentActive >= ACTIVE_GOAL_LIMIT) {
|
|
18729
|
+
throw new ActiveGoalLimitExceededError(ACTIVE_GOAL_LIMIT, currentActive);
|
|
18730
|
+
}
|
|
18731
|
+
const created = {
|
|
18732
|
+
id: this.generateGoalId(),
|
|
18733
|
+
title: input.title,
|
|
18734
|
+
description: input.description,
|
|
18735
|
+
status: "active",
|
|
18736
|
+
createdAt: now,
|
|
18737
|
+
updatedAt: now
|
|
18738
|
+
};
|
|
18739
|
+
this.db.prepare(
|
|
18740
|
+
"INSERT INTO goals (id, title, description, status, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)"
|
|
18741
|
+
).run(created.id, created.title, created.description ?? null, created.status, created.createdAt, created.updatedAt);
|
|
18742
|
+
return created;
|
|
18743
|
+
});
|
|
18744
|
+
this.db.bumpLastModified();
|
|
18745
|
+
this.emit("goal:created", goal);
|
|
18746
|
+
return goal;
|
|
18747
|
+
}
|
|
18748
|
+
updateGoal(id, input) {
|
|
18749
|
+
const existing = this.getGoal(id);
|
|
18750
|
+
if (!existing) {
|
|
18751
|
+
throw new Error(`Goal ${id} not found`);
|
|
18752
|
+
}
|
|
18753
|
+
const updated = {
|
|
18754
|
+
...existing,
|
|
18755
|
+
title: input.title ?? existing.title,
|
|
18756
|
+
description: input.description ?? existing.description,
|
|
18757
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18758
|
+
};
|
|
18759
|
+
this.db.prepare("UPDATE goals SET title = ?, description = ?, updatedAt = ? WHERE id = ?").run(updated.title, updated.description ?? null, updated.updatedAt, id);
|
|
18760
|
+
this.db.bumpLastModified();
|
|
18761
|
+
this.emit("goal:updated", updated);
|
|
18762
|
+
return updated;
|
|
18763
|
+
}
|
|
18764
|
+
archiveGoal(id) {
|
|
18765
|
+
const existing = this.getGoal(id);
|
|
18766
|
+
if (!existing) {
|
|
18767
|
+
throw new Error(`Goal ${id} not found`);
|
|
18768
|
+
}
|
|
18769
|
+
if (existing.status === "archived") {
|
|
18770
|
+
this.emit("goal:updated", existing);
|
|
18771
|
+
return existing;
|
|
18772
|
+
}
|
|
18773
|
+
const updated = {
|
|
18774
|
+
...existing,
|
|
18775
|
+
status: "archived",
|
|
18776
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18777
|
+
};
|
|
18778
|
+
this.db.prepare("UPDATE goals SET status = 'archived', updatedAt = ? WHERE id = ?").run(updated.updatedAt, id);
|
|
18779
|
+
this.db.bumpLastModified();
|
|
18780
|
+
this.emit("goal:updated", updated);
|
|
18781
|
+
return updated;
|
|
18782
|
+
}
|
|
18783
|
+
unarchiveGoal(id) {
|
|
18784
|
+
const { goal, changed } = this.db.transactionImmediate(() => {
|
|
18785
|
+
const existing = this.getGoal(id);
|
|
18786
|
+
if (!existing) {
|
|
18787
|
+
throw new Error(`Goal ${id} not found`);
|
|
18788
|
+
}
|
|
18789
|
+
if (existing.status === "active") {
|
|
18790
|
+
return { goal: existing, changed: false };
|
|
18791
|
+
}
|
|
18792
|
+
const activeCountRow = this.db.prepare("SELECT COUNT(*) as count FROM goals WHERE status = 'active'").get();
|
|
18793
|
+
const currentActive = activeCountRow?.count ?? 0;
|
|
18794
|
+
if (currentActive >= ACTIVE_GOAL_LIMIT) {
|
|
18795
|
+
throw new ActiveGoalLimitExceededError(ACTIVE_GOAL_LIMIT, currentActive);
|
|
18796
|
+
}
|
|
18797
|
+
const updated = {
|
|
18798
|
+
...existing,
|
|
18799
|
+
status: "active",
|
|
18800
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
18801
|
+
};
|
|
18802
|
+
this.db.prepare("UPDATE goals SET status = 'active', updatedAt = ? WHERE id = ?").run(updated.updatedAt, id);
|
|
18803
|
+
return { goal: updated, changed: true };
|
|
18804
|
+
});
|
|
18805
|
+
if (!changed) {
|
|
18806
|
+
return goal;
|
|
18807
|
+
}
|
|
18808
|
+
this.db.bumpLastModified();
|
|
18809
|
+
this.emit("goal:updated", goal);
|
|
18810
|
+
return goal;
|
|
18811
|
+
}
|
|
18812
|
+
listGoals(filter) {
|
|
18813
|
+
const rows = filter?.status ? this.db.prepare("SELECT id, title, description, status, createdAt, updatedAt FROM goals WHERE status = ? ORDER BY createdAt ASC").all(filter.status) : this.db.prepare("SELECT id, title, description, status, createdAt, updatedAt FROM goals ORDER BY createdAt ASC").all();
|
|
18814
|
+
return rows.map((row) => this.toGoal(row));
|
|
18815
|
+
}
|
|
18816
|
+
getGoal(id) {
|
|
18817
|
+
const row = this.db.prepare("SELECT id, title, description, status, createdAt, updatedAt FROM goals WHERE id = ?").get(id);
|
|
18818
|
+
if (!row) {
|
|
18819
|
+
return null;
|
|
18820
|
+
}
|
|
18821
|
+
return this.toGoal(row);
|
|
18822
|
+
}
|
|
18823
|
+
toGoal(row) {
|
|
18824
|
+
return {
|
|
18825
|
+
id: row.id,
|
|
18826
|
+
title: row.title,
|
|
18827
|
+
description: row.description ?? void 0,
|
|
18828
|
+
status: row.status,
|
|
18829
|
+
createdAt: row.createdAt,
|
|
18830
|
+
updatedAt: row.updatedAt
|
|
18831
|
+
};
|
|
18832
|
+
}
|
|
18833
|
+
generateGoalId() {
|
|
18834
|
+
const timestamp = Date.now().toString(36).toUpperCase();
|
|
18835
|
+
this.idSequence += 1;
|
|
18836
|
+
const sequence = this.idSequence.toString(36).toUpperCase().padStart(4, "0");
|
|
18837
|
+
const random = Math.random().toString(36).substring(2, 6).toUpperCase();
|
|
18838
|
+
return `G-${timestamp}-${sequence}-${random}`;
|
|
18839
|
+
}
|
|
18840
|
+
};
|
|
18841
|
+
}
|
|
18842
|
+
});
|
|
18843
|
+
|
|
18657
18844
|
// ../core/src/eval-types.ts
|
|
18658
18845
|
function normalizeEvalFollowUpText(value) {
|
|
18659
18846
|
return value.toLowerCase().replace(/\s+/g, " ").replace(/[^a-z0-9 ]/g, "").trim();
|
|
@@ -18732,7 +18919,7 @@ var init_eval_types = __esm({
|
|
|
18732
18919
|
});
|
|
18733
18920
|
|
|
18734
18921
|
// ../core/src/eval-store.ts
|
|
18735
|
-
import { EventEmitter as
|
|
18922
|
+
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
18736
18923
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
18737
18924
|
function generateRunId3() {
|
|
18738
18925
|
return `ER-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 7).toUpperCase()}`;
|
|
@@ -18828,7 +19015,7 @@ var init_eval_store = __esm({
|
|
|
18828
19015
|
this.name = "EvalLifecycleError";
|
|
18829
19016
|
}
|
|
18830
19017
|
};
|
|
18831
|
-
EvalStore = class extends
|
|
19018
|
+
EvalStore = class extends EventEmitter11 {
|
|
18832
19019
|
constructor(db) {
|
|
18833
19020
|
super();
|
|
18834
19021
|
this.db = db;
|
|
@@ -19788,7 +19975,7 @@ var require_registry = __commonJS({
|
|
|
19788
19975
|
setTimeout(send, Math.random() * 250);
|
|
19789
19976
|
}
|
|
19790
19977
|
announce(server, service) {
|
|
19791
|
-
var
|
|
19978
|
+
var delay5 = 1e3;
|
|
19792
19979
|
var packet = service.records();
|
|
19793
19980
|
server.register(packet);
|
|
19794
19981
|
const broadcast = () => {
|
|
@@ -19800,9 +19987,9 @@ var require_registry = __commonJS({
|
|
|
19800
19987
|
service.published = true;
|
|
19801
19988
|
service.emit("up");
|
|
19802
19989
|
}
|
|
19803
|
-
|
|
19804
|
-
if (
|
|
19805
|
-
setTimeout(broadcast,
|
|
19990
|
+
delay5 = delay5 * REANNOUNCE_FACTOR;
|
|
19991
|
+
if (delay5 < REANNOUNCE_MAX_MS && !service.destroyed) {
|
|
19992
|
+
setTimeout(broadcast, delay5).unref();
|
|
19806
19993
|
}
|
|
19807
19994
|
});
|
|
19808
19995
|
};
|
|
@@ -22628,7 +22815,7 @@ var require_dist = __commonJS({
|
|
|
22628
22815
|
});
|
|
22629
22816
|
|
|
22630
22817
|
// ../core/src/node-discovery.ts
|
|
22631
|
-
import { EventEmitter as
|
|
22818
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
22632
22819
|
import os from "node:os";
|
|
22633
22820
|
var import_bonjour_service, DEFAULT_DISCOVERY_CONFIG, STALE_CLEANUP_INTERVAL_MS, FUSION_VERSION, NodeDiscovery;
|
|
22634
22821
|
var init_node_discovery = __esm({
|
|
@@ -22644,7 +22831,7 @@ var init_node_discovery = __esm({
|
|
|
22644
22831
|
};
|
|
22645
22832
|
STALE_CLEANUP_INTERVAL_MS = 6e4;
|
|
22646
22833
|
FUSION_VERSION = "0.1.0";
|
|
22647
|
-
NodeDiscovery = class extends
|
|
22834
|
+
NodeDiscovery = class extends EventEmitter12 {
|
|
22648
22835
|
config;
|
|
22649
22836
|
bonjour = null;
|
|
22650
22837
|
broadcastService = null;
|
|
@@ -23180,7 +23367,7 @@ var init_project_identity = __esm({
|
|
|
23180
23367
|
});
|
|
23181
23368
|
|
|
23182
23369
|
// ../core/src/central-core.ts
|
|
23183
|
-
import { EventEmitter as
|
|
23370
|
+
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
23184
23371
|
import { createHash as createHash5, randomUUID as randomUUID10 } from "node:crypto";
|
|
23185
23372
|
import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
|
|
23186
23373
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
@@ -23198,7 +23385,7 @@ var init_central_core = __esm({
|
|
|
23198
23385
|
init_system_metrics();
|
|
23199
23386
|
init_shared_mesh_state();
|
|
23200
23387
|
init_project_identity();
|
|
23201
|
-
CentralCore = class extends
|
|
23388
|
+
CentralCore = class extends EventEmitter13 {
|
|
23202
23389
|
db = null;
|
|
23203
23390
|
globalDir;
|
|
23204
23391
|
initialized = false;
|
|
@@ -25369,10 +25556,10 @@ var init_central_core = __esm({
|
|
|
25369
25556
|
*/
|
|
25370
25557
|
async generateProjectName(projectPath) {
|
|
25371
25558
|
try {
|
|
25372
|
-
const { execFile:
|
|
25373
|
-
const { promisify:
|
|
25374
|
-
const
|
|
25375
|
-
const { stdout } = await
|
|
25559
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
25560
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
25561
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
25562
|
+
const { stdout } = await execFileAsync15(
|
|
25376
25563
|
"git",
|
|
25377
25564
|
["remote", "get-url", "origin"],
|
|
25378
25565
|
{ cwd: projectPath, timeout: 5e3 }
|
|
@@ -26076,10 +26263,10 @@ var init_migration = __esm({
|
|
|
26076
26263
|
return basename5(projectPath);
|
|
26077
26264
|
}
|
|
26078
26265
|
try {
|
|
26079
|
-
const { execFile:
|
|
26080
|
-
const { promisify:
|
|
26081
|
-
const
|
|
26082
|
-
const { stdout } = await
|
|
26266
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
26267
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
26268
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
26269
|
+
const { stdout } = await execFileAsync15(
|
|
26083
26270
|
"git",
|
|
26084
26271
|
["remote", "get-url", "origin"],
|
|
26085
26272
|
{ cwd: projectPath, timeout: 1e3 }
|
|
@@ -36723,7 +36910,7 @@ var automation_store_exports = {};
|
|
|
36723
36910
|
__export(automation_store_exports, {
|
|
36724
36911
|
AutomationStore: () => AutomationStore
|
|
36725
36912
|
});
|
|
36726
|
-
import { EventEmitter as
|
|
36913
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
36727
36914
|
import { join as join16 } from "node:path";
|
|
36728
36915
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
36729
36916
|
var import_cron_parser, CRON_TIMEZONE, AutomationStore;
|
|
@@ -36735,7 +36922,7 @@ var init_automation_store = __esm({
|
|
|
36735
36922
|
init_db();
|
|
36736
36923
|
init_project_root_guard();
|
|
36737
36924
|
CRON_TIMEZONE = "UTC";
|
|
36738
|
-
AutomationStore = class _AutomationStore extends
|
|
36925
|
+
AutomationStore = class _AutomationStore extends EventEmitter14 {
|
|
36739
36926
|
constructor(rootDir, options) {
|
|
36740
36927
|
super();
|
|
36741
36928
|
this.rootDir = rootDir;
|
|
@@ -37827,13 +38014,13 @@ async function searchWithQmd(rootDir, options) {
|
|
|
37827
38014
|
const command = "qmd";
|
|
37828
38015
|
const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
|
|
37829
38016
|
try {
|
|
37830
|
-
const { execFile:
|
|
37831
|
-
const { promisify:
|
|
37832
|
-
const
|
|
37833
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
38017
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
38018
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
38019
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
38020
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync15);
|
|
37834
38021
|
scheduleQmdProjectMemoryRefresh(rootDir);
|
|
37835
38022
|
const args = buildQmdSearchArgs(rootDir, options);
|
|
37836
|
-
const { stdout } = await
|
|
38023
|
+
const { stdout } = await execFileAsync15(command, args, {
|
|
37837
38024
|
cwd: rootDir,
|
|
37838
38025
|
timeout: 4e3,
|
|
37839
38026
|
maxBuffer: 1024 * 1024
|
|
@@ -37858,12 +38045,12 @@ async function searchWithQmd(rootDir, options) {
|
|
|
37858
38045
|
return [];
|
|
37859
38046
|
}
|
|
37860
38047
|
}
|
|
37861
|
-
async function ensureQmdProjectMemoryCollection(rootDir,
|
|
38048
|
+
async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync15) {
|
|
37862
38049
|
const collectionName = qmdMemoryCollectionName(rootDir);
|
|
37863
38050
|
const memoryDir = memoryWorkspacePath(rootDir);
|
|
37864
38051
|
await mkdir6(memoryDir, { recursive: true });
|
|
37865
38052
|
try {
|
|
37866
|
-
await
|
|
38053
|
+
await execFileAsync15("qmd", buildQmdCollectionAddArgs(rootDir), {
|
|
37867
38054
|
cwd: rootDir,
|
|
37868
38055
|
timeout: 4e3,
|
|
37869
38056
|
maxBuffer: 512 * 1024
|
|
@@ -37879,9 +38066,9 @@ ${stderr}`)) {
|
|
|
37879
38066
|
return collectionName;
|
|
37880
38067
|
}
|
|
37881
38068
|
async function getDefaultExecFileAsync() {
|
|
37882
|
-
const { execFile:
|
|
37883
|
-
const { promisify:
|
|
37884
|
-
return
|
|
38069
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
38070
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
38071
|
+
return promisify51(execFile20);
|
|
37885
38072
|
}
|
|
37886
38073
|
async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
37887
38074
|
const key = resolve8(rootDir);
|
|
@@ -37896,14 +38083,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
|
37896
38083
|
}
|
|
37897
38084
|
}
|
|
37898
38085
|
const promise = (async () => {
|
|
37899
|
-
const
|
|
37900
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
37901
|
-
await
|
|
38086
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
38087
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync15);
|
|
38088
|
+
await execFileAsync15("qmd", ["update"], {
|
|
37902
38089
|
cwd: rootDir,
|
|
37903
38090
|
timeout: 3e4,
|
|
37904
38091
|
maxBuffer: 1024 * 1024
|
|
37905
38092
|
});
|
|
37906
|
-
await
|
|
38093
|
+
await execFileAsync15("qmd", ["embed"], {
|
|
37907
38094
|
cwd: rootDir,
|
|
37908
38095
|
timeout: 12e4,
|
|
37909
38096
|
maxBuffer: 1024 * 1024
|
|
@@ -37939,12 +38126,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
|
|
|
37939
38126
|
}
|
|
37940
38127
|
}
|
|
37941
38128
|
const promise = (async () => {
|
|
37942
|
-
const
|
|
38129
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
37943
38130
|
const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
|
|
37944
38131
|
const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
|
|
37945
38132
|
await mkdir6(workspacePath, { recursive: true });
|
|
37946
38133
|
try {
|
|
37947
|
-
await
|
|
38134
|
+
await execFileAsync15("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
|
|
37948
38135
|
cwd: rootDir,
|
|
37949
38136
|
timeout: 4e3,
|
|
37950
38137
|
maxBuffer: 512 * 1024
|
|
@@ -37957,8 +38144,8 @@ ${stderr}`)) {
|
|
|
37957
38144
|
throw err;
|
|
37958
38145
|
}
|
|
37959
38146
|
}
|
|
37960
|
-
await
|
|
37961
|
-
await
|
|
38147
|
+
await execFileAsync15("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
38148
|
+
await execFileAsync15("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
37962
38149
|
})();
|
|
37963
38150
|
qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
37964
38151
|
try {
|
|
@@ -37979,8 +38166,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
|
|
|
37979
38166
|
}
|
|
37980
38167
|
async function isQmdAvailable() {
|
|
37981
38168
|
try {
|
|
37982
|
-
const
|
|
37983
|
-
await
|
|
38169
|
+
const execFileAsync15 = await getDefaultExecFileAsync();
|
|
38170
|
+
await execFileAsync15("qmd", ["--help"], {
|
|
37984
38171
|
timeout: 3e3,
|
|
37985
38172
|
maxBuffer: 128 * 1024
|
|
37986
38173
|
});
|
|
@@ -37990,12 +38177,12 @@ async function isQmdAvailable() {
|
|
|
37990
38177
|
}
|
|
37991
38178
|
}
|
|
37992
38179
|
async function installQmd(options) {
|
|
37993
|
-
const
|
|
38180
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
37994
38181
|
const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
|
|
37995
38182
|
if (!command || args.length === 0) {
|
|
37996
38183
|
throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
|
|
37997
38184
|
}
|
|
37998
|
-
await
|
|
38185
|
+
await execFileAsync15(command, args, {
|
|
37999
38186
|
timeout: 12e4,
|
|
38000
38187
|
maxBuffer: 1024 * 1024
|
|
38001
38188
|
});
|
|
@@ -39646,7 +39833,7 @@ var init_task_id_integrity = __esm({
|
|
|
39646
39833
|
});
|
|
39647
39834
|
|
|
39648
39835
|
// ../core/src/store.ts
|
|
39649
|
-
import { EventEmitter as
|
|
39836
|
+
import { EventEmitter as EventEmitter15 } from "node:events";
|
|
39650
39837
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
39651
39838
|
import { mkdir as mkdir7, readdir as readdir5, readFile as readFile8, writeFile as writeFile6, rename as rename4, unlink as unlink3 } from "node:fs/promises";
|
|
39652
39839
|
import { join as join19 } from "node:path";
|
|
@@ -39978,6 +40165,7 @@ var init_store = __esm({
|
|
|
39978
40165
|
init_research_store();
|
|
39979
40166
|
init_experiment_session_store();
|
|
39980
40167
|
init_todo_store();
|
|
40168
|
+
init_goal_store();
|
|
39981
40169
|
init_eval_store();
|
|
39982
40170
|
init_migration();
|
|
39983
40171
|
init_central_core();
|
|
@@ -40171,7 +40359,7 @@ var init_store = __esm({
|
|
|
40171
40359
|
this.name = "HandoffInvariantViolationError";
|
|
40172
40360
|
}
|
|
40173
40361
|
};
|
|
40174
|
-
TaskStore = class _TaskStore extends
|
|
40362
|
+
TaskStore = class _TaskStore extends EventEmitter15 {
|
|
40175
40363
|
constructor(rootDir, globalSettingsDir, options) {
|
|
40176
40364
|
super();
|
|
40177
40365
|
this.rootDir = rootDir;
|
|
@@ -40303,6 +40491,8 @@ var init_store = __esm({
|
|
|
40303
40491
|
experimentSessionStore = null;
|
|
40304
40492
|
/** Cached TodoStore instance */
|
|
40305
40493
|
todoStore = null;
|
|
40494
|
+
/** Cached GoalStore instance */
|
|
40495
|
+
goalStore = null;
|
|
40306
40496
|
/** Cached EvalStore instance */
|
|
40307
40497
|
evalStore = null;
|
|
40308
40498
|
/** Cached SecretsStore instance */
|
|
@@ -42686,9 +42876,9 @@ ${outcome}`;
|
|
|
42686
42876
|
if (validation.invalid.length > 0) {
|
|
42687
42877
|
if (this.isWatching) this.taskCache.delete(id);
|
|
42688
42878
|
this.deleteTaskById(id);
|
|
42689
|
-
const { rm:
|
|
42879
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
42690
42880
|
if (existsSync15(dir)) {
|
|
42691
|
-
await
|
|
42881
|
+
await rm10(dir, { recursive: true, force: true });
|
|
42692
42882
|
}
|
|
42693
42883
|
throw new InvalidFileScopeError(id, validation.invalid);
|
|
42694
42884
|
}
|
|
@@ -42783,10 +42973,10 @@ ${outcome}`;
|
|
|
42783
42973
|
});
|
|
42784
42974
|
if (this.isWatching) this.taskCache.delete(task.id);
|
|
42785
42975
|
this.deleteTaskById(task.id);
|
|
42786
|
-
const { rm:
|
|
42976
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
42787
42977
|
const taskDir = this.taskDir(task.id);
|
|
42788
42978
|
if (existsSync15(taskDir)) {
|
|
42789
|
-
await
|
|
42979
|
+
await rm10(taskDir, { recursive: true, force: true });
|
|
42790
42980
|
}
|
|
42791
42981
|
throw new TombstonedTaskResurrectionError(
|
|
42792
42982
|
tombstonedMatch.id,
|
|
@@ -45803,8 +45993,8 @@ ${newTask.description}
|
|
|
45803
45993
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
45804
45994
|
this.db.bumpLastModified();
|
|
45805
45995
|
});
|
|
45806
|
-
const { rm:
|
|
45807
|
-
await
|
|
45996
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
45997
|
+
await rm10(dir, { recursive: true, force: true });
|
|
45808
45998
|
if (this.isWatching) {
|
|
45809
45999
|
this.taskCache.delete(id);
|
|
45810
46000
|
}
|
|
@@ -47213,14 +47403,14 @@ ${newTask.description}
|
|
|
47213
47403
|
if (rows.length === 0) {
|
|
47214
47404
|
return;
|
|
47215
47405
|
}
|
|
47216
|
-
const { rm:
|
|
47406
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
47217
47407
|
for (const row of rows) {
|
|
47218
47408
|
const task = this.rowToTask(row);
|
|
47219
47409
|
const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
47220
47410
|
const entry = await this.taskToArchiveEntry(task, archivedAt);
|
|
47221
47411
|
this.archiveDb.upsert(entry);
|
|
47222
47412
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
47223
|
-
await
|
|
47413
|
+
await rm10(this.taskDir(task.id), { recursive: true, force: true });
|
|
47224
47414
|
if (this.isWatching) {
|
|
47225
47415
|
this.taskCache.delete(task.id);
|
|
47226
47416
|
}
|
|
@@ -47247,8 +47437,8 @@ ${newTask.description}
|
|
|
47247
47437
|
this.archiveDb.upsert(entry);
|
|
47248
47438
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
47249
47439
|
this.db.bumpLastModified();
|
|
47250
|
-
const { rm:
|
|
47251
|
-
await
|
|
47440
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
47441
|
+
await rm10(dir, { recursive: true, force: true });
|
|
47252
47442
|
if (this.isWatching) {
|
|
47253
47443
|
this.taskCache.delete(task.id);
|
|
47254
47444
|
}
|
|
@@ -47982,6 +48172,16 @@ ${notificationsSection}`;
|
|
|
47982
48172
|
}
|
|
47983
48173
|
return this.todoStore;
|
|
47984
48174
|
}
|
|
48175
|
+
/**
|
|
48176
|
+
* Get the GoalStore instance for project-scoped goals operations.
|
|
48177
|
+
* Lazily initializes the GoalStore on first access.
|
|
48178
|
+
*/
|
|
48179
|
+
getGoalStore() {
|
|
48180
|
+
if (!this.goalStore) {
|
|
48181
|
+
this.goalStore = new GoalStore(this.fusionDir, this.db);
|
|
48182
|
+
}
|
|
48183
|
+
return this.goalStore;
|
|
48184
|
+
}
|
|
47985
48185
|
/**
|
|
47986
48186
|
* Get the EvalStore instance for eval run and task result operations.
|
|
47987
48187
|
* Lazily initializes the EvalStore on first access.
|
|
@@ -49179,8 +49379,11 @@ function describeAbortReason(reason) {
|
|
|
49179
49379
|
if (typeof reason === "string") return reason;
|
|
49180
49380
|
return "aborted";
|
|
49181
49381
|
}
|
|
49382
|
+
function shouldAutoAppendJsonFlag(args) {
|
|
49383
|
+
return args[0] !== "api" && !args.includes("--json");
|
|
49384
|
+
}
|
|
49182
49385
|
function runGhJson(args, cwd) {
|
|
49183
|
-
const jsonArgs = args
|
|
49386
|
+
const jsonArgs = shouldAutoAppendJsonFlag(args) ? [...args, "--json"] : args;
|
|
49184
49387
|
const output = runGh(jsonArgs, cwd);
|
|
49185
49388
|
try {
|
|
49186
49389
|
return JSON.parse(output);
|
|
@@ -49189,7 +49392,7 @@ function runGhJson(args, cwd) {
|
|
|
49189
49392
|
}
|
|
49190
49393
|
}
|
|
49191
49394
|
async function runGhJsonAsync(args, cwdOrOptions) {
|
|
49192
|
-
const jsonArgs = args
|
|
49395
|
+
const jsonArgs = shouldAutoAppendJsonFlag(args) ? [...args, "--json"] : args;
|
|
49193
49396
|
const output = await runGhAsync(jsonArgs, cwdOrOptions);
|
|
49194
49397
|
try {
|
|
49195
49398
|
return JSON.parse(output);
|
|
@@ -49789,7 +49992,7 @@ var routine_store_exports = {};
|
|
|
49789
49992
|
__export(routine_store_exports, {
|
|
49790
49993
|
RoutineStore: () => RoutineStore
|
|
49791
49994
|
});
|
|
49792
|
-
import { EventEmitter as
|
|
49995
|
+
import { EventEmitter as EventEmitter16 } from "node:events";
|
|
49793
49996
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
49794
49997
|
var import_cron_parser2, CRON_TIMEZONE2, RoutineStore;
|
|
49795
49998
|
var init_routine_store = __esm({
|
|
@@ -49800,7 +50003,7 @@ var init_routine_store = __esm({
|
|
|
49800
50003
|
init_routine();
|
|
49801
50004
|
init_project_root_guard();
|
|
49802
50005
|
CRON_TIMEZONE2 = "UTC";
|
|
49803
|
-
RoutineStore = class _RoutineStore extends
|
|
50006
|
+
RoutineStore = class _RoutineStore extends EventEmitter16 {
|
|
49804
50007
|
constructor(rootDir, options) {
|
|
49805
50008
|
super();
|
|
49806
50009
|
this.rootDir = rootDir;
|
|
@@ -50402,7 +50605,7 @@ import { basename as basename8, dirname as dirname7, extname, isAbsolute as isAb
|
|
|
50402
50605
|
import { stat as stat3 } from "node:fs/promises";
|
|
50403
50606
|
import { copyFile } from "node:fs/promises";
|
|
50404
50607
|
import { pathToFileURL } from "node:url";
|
|
50405
|
-
import { EventEmitter as
|
|
50608
|
+
import { EventEmitter as EventEmitter17 } from "node:events";
|
|
50406
50609
|
var MINIMUM_FUSION_VERSION, moduleImportVersion, PluginLoader;
|
|
50407
50610
|
var init_plugin_loader = __esm({
|
|
50408
50611
|
"../core/src/plugin-loader.ts"() {
|
|
@@ -50413,7 +50616,7 @@ var init_plugin_loader = __esm({
|
|
|
50413
50616
|
init_plugin_security_scan();
|
|
50414
50617
|
MINIMUM_FUSION_VERSION = "0.1.0";
|
|
50415
50618
|
moduleImportVersion = 0;
|
|
50416
|
-
PluginLoader = class extends
|
|
50619
|
+
PluginLoader = class extends EventEmitter17 {
|
|
50417
50620
|
constructor(options) {
|
|
50418
50621
|
super();
|
|
50419
50622
|
this.options = options;
|
|
@@ -55965,15 +56168,15 @@ var require_fd_slicer = __commonJS({
|
|
|
55965
56168
|
var Writable = stream.Writable;
|
|
55966
56169
|
var PassThrough = stream.PassThrough;
|
|
55967
56170
|
var Pend = require_pend();
|
|
55968
|
-
var
|
|
56171
|
+
var EventEmitter35 = __require("events").EventEmitter;
|
|
55969
56172
|
exports.createFromBuffer = createFromBuffer;
|
|
55970
56173
|
exports.createFromFd = createFromFd;
|
|
55971
56174
|
exports.BufferSlicer = BufferSlicer;
|
|
55972
56175
|
exports.FdSlicer = FdSlicer;
|
|
55973
|
-
util.inherits(FdSlicer,
|
|
56176
|
+
util.inherits(FdSlicer, EventEmitter35);
|
|
55974
56177
|
function FdSlicer(fd, options) {
|
|
55975
56178
|
options = options || {};
|
|
55976
|
-
|
|
56179
|
+
EventEmitter35.call(this);
|
|
55977
56180
|
this.fd = fd;
|
|
55978
56181
|
this.pend = new Pend();
|
|
55979
56182
|
this.pend.max = 1;
|
|
@@ -56117,9 +56320,9 @@ var require_fd_slicer = __commonJS({
|
|
|
56117
56320
|
this.destroyed = true;
|
|
56118
56321
|
this.context.unref();
|
|
56119
56322
|
};
|
|
56120
|
-
util.inherits(BufferSlicer,
|
|
56323
|
+
util.inherits(BufferSlicer, EventEmitter35);
|
|
56121
56324
|
function BufferSlicer(buffer, options) {
|
|
56122
|
-
|
|
56325
|
+
EventEmitter35.call(this);
|
|
56123
56326
|
options = options || {};
|
|
56124
56327
|
this.refCount = 0;
|
|
56125
56328
|
this.buffer = buffer;
|
|
@@ -56531,7 +56734,7 @@ var require_yauzl = __commonJS({
|
|
|
56531
56734
|
var fd_slicer = require_fd_slicer();
|
|
56532
56735
|
var crc32 = require_buffer_crc32();
|
|
56533
56736
|
var util = __require("util");
|
|
56534
|
-
var
|
|
56737
|
+
var EventEmitter35 = __require("events").EventEmitter;
|
|
56535
56738
|
var Transform = __require("stream").Transform;
|
|
56536
56739
|
var PassThrough = __require("stream").PassThrough;
|
|
56537
56740
|
var Writable = __require("stream").Writable;
|
|
@@ -56663,10 +56866,10 @@ var require_yauzl = __commonJS({
|
|
|
56663
56866
|
callback(new Error("end of central directory record signature not found"));
|
|
56664
56867
|
});
|
|
56665
56868
|
}
|
|
56666
|
-
util.inherits(ZipFile,
|
|
56869
|
+
util.inherits(ZipFile, EventEmitter35);
|
|
56667
56870
|
function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {
|
|
56668
56871
|
var self = this;
|
|
56669
|
-
|
|
56872
|
+
EventEmitter35.call(self);
|
|
56670
56873
|
self.reader = reader;
|
|
56671
56874
|
self.reader.on("error", function(err) {
|
|
56672
56875
|
emitError(self, err);
|
|
@@ -57027,9 +57230,9 @@ var require_yauzl = __commonJS({
|
|
|
57027
57230
|
}
|
|
57028
57231
|
cb();
|
|
57029
57232
|
};
|
|
57030
|
-
util.inherits(RandomAccessReader,
|
|
57233
|
+
util.inherits(RandomAccessReader, EventEmitter35);
|
|
57031
57234
|
function RandomAccessReader() {
|
|
57032
|
-
|
|
57235
|
+
EventEmitter35.call(this);
|
|
57033
57236
|
this.refCount = 0;
|
|
57034
57237
|
}
|
|
57035
57238
|
RandomAccessReader.prototype.ref = function() {
|
|
@@ -57160,11 +57363,11 @@ var require_extract_zip = __commonJS({
|
|
|
57160
57363
|
var { createWriteStream, promises: fs6 } = __require("fs");
|
|
57161
57364
|
var getStream = require_get_stream();
|
|
57162
57365
|
var path6 = __require("path");
|
|
57163
|
-
var { promisify:
|
|
57366
|
+
var { promisify: promisify51 } = __require("util");
|
|
57164
57367
|
var stream = __require("stream");
|
|
57165
57368
|
var yauzl = require_yauzl();
|
|
57166
|
-
var openZip =
|
|
57167
|
-
var pipeline =
|
|
57369
|
+
var openZip = promisify51(yauzl.open);
|
|
57370
|
+
var pipeline = promisify51(stream.pipeline);
|
|
57168
57371
|
var Extractor = class {
|
|
57169
57372
|
constructor(zipPath, opts) {
|
|
57170
57373
|
this.zipPath = zipPath;
|
|
@@ -57246,7 +57449,7 @@ var require_extract_zip = __commonJS({
|
|
|
57246
57449
|
await fs6.mkdir(destDir, mkdirOptions);
|
|
57247
57450
|
if (isDir) return;
|
|
57248
57451
|
debug("opening read stream", dest);
|
|
57249
|
-
const readStream = await
|
|
57452
|
+
const readStream = await promisify51(this.zipfile.openReadStream.bind(this.zipfile))(entry);
|
|
57250
57453
|
if (symlink) {
|
|
57251
57454
|
const link = await getStream(readStream);
|
|
57252
57455
|
debug("creating symlink", link, dest);
|
|
@@ -68267,14 +68470,14 @@ var init_agent_companies_exporter = __esm({
|
|
|
68267
68470
|
});
|
|
68268
68471
|
|
|
68269
68472
|
// ../core/src/chat-store.ts
|
|
68270
|
-
import { EventEmitter as
|
|
68473
|
+
import { EventEmitter as EventEmitter18 } from "node:events";
|
|
68271
68474
|
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
68272
68475
|
var ChatStore;
|
|
68273
68476
|
var init_chat_store = __esm({
|
|
68274
68477
|
"../core/src/chat-store.ts"() {
|
|
68275
68478
|
"use strict";
|
|
68276
68479
|
init_db();
|
|
68277
|
-
ChatStore = class extends
|
|
68480
|
+
ChatStore = class extends EventEmitter18 {
|
|
68278
68481
|
constructor(fusionDir, db) {
|
|
68279
68482
|
super();
|
|
68280
68483
|
this.fusionDir = fusionDir;
|
|
@@ -69366,6 +69569,7 @@ var init_secrets_sync = __esm({
|
|
|
69366
69569
|
// ../core/src/index.ts
|
|
69367
69570
|
var src_exports = {};
|
|
69368
69571
|
__export(src_exports, {
|
|
69572
|
+
ACTIVE_GOAL_LIMIT: () => ACTIVE_GOAL_LIMIT,
|
|
69369
69573
|
AGENT_PERMISSIONS: () => AGENT_PERMISSIONS,
|
|
69370
69574
|
AGENT_PERMISSION_POLICY_ACTION_CATEGORIES: () => AGENT_PERMISSION_POLICY_ACTION_CATEGORIES,
|
|
69371
69575
|
AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES: () => AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES,
|
|
@@ -69379,6 +69583,7 @@ __export(src_exports, {
|
|
|
69379
69583
|
AUTOPILOT_STATES: () => AUTOPILOT_STATES,
|
|
69380
69584
|
AUTO_RECOVERY_MODES: () => AUTO_RECOVERY_MODES,
|
|
69381
69585
|
AUTO_SUMMARIZE_SCHEDULE_NAME: () => AUTO_SUMMARIZE_SCHEDULE_NAME,
|
|
69586
|
+
ActiveGoalLimitExceededError: () => ActiveGoalLimitExceededError,
|
|
69382
69587
|
AgentCompaniesParseError: () => AgentCompaniesParseError,
|
|
69383
69588
|
AgentStore: () => AgentStore,
|
|
69384
69589
|
AiServiceError: () => AiServiceError,
|
|
@@ -69472,6 +69677,7 @@ __export(src_exports, {
|
|
|
69472
69677
|
FirstRunDetector: () => FirstRunDetector,
|
|
69473
69678
|
GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
|
|
69474
69679
|
GlobalSettingsStore: () => GlobalSettingsStore,
|
|
69680
|
+
GoalStore: () => GoalStore,
|
|
69475
69681
|
HIGH_FANOUT_BLOCKER_TODO_THRESHOLD: () => HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
|
|
69476
69682
|
HandoffInvariantViolationError: () => HandoffInvariantViolationError,
|
|
69477
69683
|
INSIGHT_EXTRACTION_SCHEDULE_NAME: () => INSIGHT_EXTRACTION_SCHEDULE_NAME,
|
|
@@ -69506,6 +69712,7 @@ __export(src_exports, {
|
|
|
69506
69712
|
MEMORY_LONG_TERM_FILENAME: () => MEMORY_LONG_TERM_FILENAME,
|
|
69507
69713
|
MEMORY_WORKING_PATH: () => MEMORY_WORKING_PATH,
|
|
69508
69714
|
MEMORY_WORKSPACE_PATH: () => MEMORY_WORKSPACE_PATH,
|
|
69715
|
+
MERGER_MODES: () => MERGER_MODES,
|
|
69509
69716
|
MERGE_ADVANCE_AUTO_SYNC_MODES: () => MERGE_ADVANCE_AUTO_SYNC_MODES,
|
|
69510
69717
|
MERGE_AUDIT_AUTO_RECOVERY_MODES: () => MERGE_AUDIT_AUTO_RECOVERY_MODES,
|
|
69511
69718
|
MERGE_COMMIT_SUMMARIZE_SYSTEM_PROMPT: () => MERGE_COMMIT_SUMMARIZE_SYSTEM_PROMPT,
|
|
@@ -69853,6 +70060,7 @@ __export(src_exports, {
|
|
|
69853
70060
|
normalizeMergeConflictStrategy: () => normalizeMergeConflictStrategy,
|
|
69854
70061
|
normalizeMergeIntegrationWorktreeMode: () => normalizeMergeIntegrationWorktreeMode,
|
|
69855
70062
|
normalizeMergeStrategyOverlapBehavior: () => normalizeMergeStrategyOverlapBehavior,
|
|
70063
|
+
normalizeMergerMode: () => normalizeMergerMode,
|
|
69856
70064
|
normalizeMessageParticipant: () => normalizeMessageParticipant,
|
|
69857
70065
|
normalizePermissions: () => normalizePermissions,
|
|
69858
70066
|
normalizePluginUiContributionDefinition: () => normalizePluginUiContributionDefinition,
|
|
@@ -70110,6 +70318,8 @@ var init_src = __esm({
|
|
|
70110
70318
|
init_task_priority();
|
|
70111
70319
|
init_mission_types();
|
|
70112
70320
|
init_mission_store();
|
|
70321
|
+
init_goal_types();
|
|
70322
|
+
init_goal_store();
|
|
70113
70323
|
init_central_core();
|
|
70114
70324
|
init_central_db();
|
|
70115
70325
|
init_node_connection();
|
|
@@ -70740,15 +70950,15 @@ function evaluateAgentActionGate(params) {
|
|
|
70740
70950
|
let resourceId;
|
|
70741
70951
|
if (params.toolName === "bash") {
|
|
70742
70952
|
const command = extractShellCommand(args);
|
|
70743
|
-
const
|
|
70744
|
-
if (
|
|
70953
|
+
const git3 = classifyGitCommand(command);
|
|
70954
|
+
if (git3?.write) {
|
|
70745
70955
|
category = "git_write";
|
|
70746
|
-
operation =
|
|
70956
|
+
operation = git3.operation;
|
|
70747
70957
|
resourceType = "git";
|
|
70748
70958
|
} else {
|
|
70749
70959
|
category = "command_execution";
|
|
70750
|
-
operation =
|
|
70751
|
-
resourceType =
|
|
70960
|
+
operation = git3?.operation ?? "shell command";
|
|
70961
|
+
resourceType = git3 ? "git" : "command";
|
|
70752
70962
|
}
|
|
70753
70963
|
} else if (params.toolName === "write" || params.toolName === "edit") {
|
|
70754
70964
|
category = "file_write_delete";
|
|
@@ -71988,8 +72198,8 @@ var init_context_limit_detector = __esm({
|
|
|
71988
72198
|
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
71989
72199
|
import { homedir as homedir5 } from "node:os";
|
|
71990
72200
|
import { join as join29 } from "node:path";
|
|
71991
|
-
import { AuthStorage } from "@
|
|
71992
|
-
import { getOAuthProvider } from "@
|
|
72201
|
+
import { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
72202
|
+
import { getOAuthProvider } from "@earendil-works/pi-ai/oauth";
|
|
71993
72203
|
function getHomeDir5() {
|
|
71994
72204
|
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
71995
72205
|
}
|
|
@@ -72398,7 +72608,7 @@ import {
|
|
|
72398
72608
|
ModelRegistry,
|
|
72399
72609
|
SessionManager,
|
|
72400
72610
|
SettingsManager
|
|
72401
|
-
} from "@
|
|
72611
|
+
} from "@earendil-works/pi-coding-agent";
|
|
72402
72612
|
function normalizeRtkRewriteOptions(options) {
|
|
72403
72613
|
const modeEnv = process.env.FUSION_RTK_REWRITE?.toLowerCase();
|
|
72404
72614
|
const envMode = modeEnv === "1" || modeEnv === "true" || modeEnv === "rewrite" ? "rewrite" : "off";
|
|
@@ -74928,9 +75138,9 @@ var init_web_search_provider = __esm({
|
|
|
74928
75138
|
if (!isRetryableHttp || attempt >= RETRY_MAX_ATTEMPTS) {
|
|
74929
75139
|
break;
|
|
74930
75140
|
}
|
|
74931
|
-
const
|
|
74932
|
-
const jitter =
|
|
74933
|
-
const totalDelay = Math.max(0,
|
|
75141
|
+
const delay5 = Math.min(RETRY_BASE_DELAY_MS * 2 ** (attempt - 1), RETRY_MAX_DELAY_MS);
|
|
75142
|
+
const jitter = delay5 * (Math.random() * 0.2 - 0.1);
|
|
75143
|
+
const totalDelay = Math.max(0, delay5 + jitter);
|
|
74934
75144
|
log9.warn(`retrying ${backend} search`, { attempt, totalDelay });
|
|
74935
75145
|
await sleep2(totalDelay, signal);
|
|
74936
75146
|
}
|
|
@@ -75384,7 +75594,7 @@ import { appendFile as appendFile3, mkdir as mkdir12, readFile as readFile14, re
|
|
|
75384
75594
|
import { existsSync as existsSync26 } from "node:fs";
|
|
75385
75595
|
import { createHash as createHash8 } from "node:crypto";
|
|
75386
75596
|
import { join as join33, relative as relative6, resolve as resolve17 } from "node:path";
|
|
75387
|
-
import { Type } from "@
|
|
75597
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
75388
75598
|
function sanitizeAgentMemoryId(agentId) {
|
|
75389
75599
|
return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
75390
75600
|
}
|
|
@@ -75554,11 +75764,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
|
|
|
75554
75764
|
return;
|
|
75555
75765
|
}
|
|
75556
75766
|
const promise = (async () => {
|
|
75557
|
-
const { execFile:
|
|
75558
|
-
const { promisify:
|
|
75559
|
-
const
|
|
75767
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
75768
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
75769
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
75560
75770
|
try {
|
|
75561
|
-
await
|
|
75771
|
+
await execFileAsync15("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
|
|
75562
75772
|
cwd: rootDir,
|
|
75563
75773
|
timeout: 4e3,
|
|
75564
75774
|
maxBuffer: 512 * 1024
|
|
@@ -75571,8 +75781,8 @@ ${stderr}`)) {
|
|
|
75571
75781
|
throw error;
|
|
75572
75782
|
}
|
|
75573
75783
|
}
|
|
75574
|
-
await
|
|
75575
|
-
await
|
|
75784
|
+
await execFileAsync15("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
75785
|
+
await execFileAsync15("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
75576
75786
|
})();
|
|
75577
75787
|
agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
75578
75788
|
try {
|
|
@@ -75627,10 +75837,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
|
|
|
75627
75837
|
}
|
|
75628
75838
|
try {
|
|
75629
75839
|
await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
|
|
75630
|
-
const { execFile:
|
|
75631
|
-
const { promisify:
|
|
75632
|
-
const
|
|
75633
|
-
const { stdout } = await
|
|
75840
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
75841
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
75842
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
75843
|
+
const { stdout } = await execFileAsync15("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
|
|
75634
75844
|
cwd: rootDir,
|
|
75635
75845
|
timeout: 4e3,
|
|
75636
75846
|
maxBuffer: 1024 * 1024
|
|
@@ -79134,6 +79344,24 @@ var init_webhook_provider = __esm({
|
|
|
79134
79344
|
}
|
|
79135
79345
|
});
|
|
79136
79346
|
|
|
79347
|
+
// ../engine/src/transient-merge-error-classifier.ts
|
|
79348
|
+
function classifyTransientMergeError(error) {
|
|
79349
|
+
if (!error) return null;
|
|
79350
|
+
if (/lease-handoff-failed[^a-z]+target-not-queued/i.test(error)) {
|
|
79351
|
+
return "lease-handoff-target-not-queued";
|
|
79352
|
+
}
|
|
79353
|
+
const sameSha = error.match(/advanced concurrently \(expected ([0-9a-f]{7,40}),\s+observed ([0-9a-f]{7,40})\)/i);
|
|
79354
|
+
if (sameSha && sameSha[1].toLowerCase() === sameSha[2].toLowerCase()) {
|
|
79355
|
+
return "spurious-concurrent-advance-same-sha";
|
|
79356
|
+
}
|
|
79357
|
+
return null;
|
|
79358
|
+
}
|
|
79359
|
+
var init_transient_merge_error_classifier = __esm({
|
|
79360
|
+
"../engine/src/transient-merge-error-classifier.ts"() {
|
|
79361
|
+
"use strict";
|
|
79362
|
+
}
|
|
79363
|
+
});
|
|
79364
|
+
|
|
79137
79365
|
// ../engine/src/notification/notification-service.ts
|
|
79138
79366
|
var NotificationService;
|
|
79139
79367
|
var init_notification_service = __esm({
|
|
@@ -79142,6 +79370,7 @@ var init_notification_service = __esm({
|
|
|
79142
79370
|
init_src();
|
|
79143
79371
|
init_notifier();
|
|
79144
79372
|
init_logger2();
|
|
79373
|
+
init_transient_merge_error_classifier();
|
|
79145
79374
|
init_ntfy_provider();
|
|
79146
79375
|
init_webhook_provider();
|
|
79147
79376
|
NotificationService = class {
|
|
@@ -79243,6 +79472,14 @@ var init_notification_service = __esm({
|
|
|
79243
79472
|
return;
|
|
79244
79473
|
}
|
|
79245
79474
|
if (task.status === "failed") {
|
|
79475
|
+
const transientClass = classifyTransientMergeError(task.error);
|
|
79476
|
+
if (transientClass) {
|
|
79477
|
+
this.failureNotificationSuppressedCount += 1;
|
|
79478
|
+
schedulerLog.log(
|
|
79479
|
+
`[notify] ${task.id} transient merge failure (${transientClass}) \u2014 suppressed notification (self-heal in flight)`
|
|
79480
|
+
);
|
|
79481
|
+
return;
|
|
79482
|
+
}
|
|
79246
79483
|
if (this.failureNotificationMode === "all") {
|
|
79247
79484
|
this.maybeNotify(task.id, "failed", this.createTaskPayload(task, "failed"));
|
|
79248
79485
|
} else {
|
|
@@ -79559,6 +79796,14 @@ var init_notification_service = __esm({
|
|
|
79559
79796
|
schedulerLog.log(`[notify] ${taskId} no longer failed at dispatch time \u2014 suppressed notification`);
|
|
79560
79797
|
return;
|
|
79561
79798
|
}
|
|
79799
|
+
const transientClassAtDispatch = classifyTransientMergeError(task.error);
|
|
79800
|
+
if (transientClassAtDispatch) {
|
|
79801
|
+
this.failureNotificationSuppressedCount += 1;
|
|
79802
|
+
schedulerLog.log(
|
|
79803
|
+
`[notify] ${taskId} transient merge failure (${transientClassAtDispatch}) at dispatch time \u2014 suppressed notification (self-heal in flight)`
|
|
79804
|
+
);
|
|
79805
|
+
return;
|
|
79806
|
+
}
|
|
79562
79807
|
const isTerminal = task.paused === true || task.column === "in-review";
|
|
79563
79808
|
if (this.failureNotificationMode === "terminal-only" && !isTerminal) {
|
|
79564
79809
|
this.failureNotificationSuppressedCount += 1;
|
|
@@ -79811,6 +80056,19 @@ function truncateNtfyMessage(message) {
|
|
|
79811
80056
|
}
|
|
79812
80057
|
return best;
|
|
79813
80058
|
}
|
|
80059
|
+
function ntfyPriorityToInt(priority) {
|
|
80060
|
+
switch (priority) {
|
|
80061
|
+
case "low":
|
|
80062
|
+
return 2;
|
|
80063
|
+
case "high":
|
|
80064
|
+
return 4;
|
|
80065
|
+
case "urgent":
|
|
80066
|
+
return 5;
|
|
80067
|
+
case "default":
|
|
80068
|
+
default:
|
|
80069
|
+
return 3;
|
|
80070
|
+
}
|
|
80071
|
+
}
|
|
79814
80072
|
function resolveNtfyEvents(events) {
|
|
79815
80073
|
return events ? [...events] : [...DEFAULT_NTFY_EVENTS];
|
|
79816
80074
|
}
|
|
@@ -79860,10 +80118,10 @@ async function sendNtfyNotificationWithResult({
|
|
|
79860
80118
|
const truncatedMessage = truncateNtfyMessage(message);
|
|
79861
80119
|
const latin1Safe = isLatin1Safe(truncatedTitle) && isLatin1Safe(truncatedMessage);
|
|
79862
80120
|
const headers = {
|
|
79863
|
-
Priority: priority,
|
|
79864
80121
|
"Content-Type": latin1Safe ? "text/plain" : "application/json"
|
|
79865
80122
|
};
|
|
79866
80123
|
if (latin1Safe) {
|
|
80124
|
+
headers.Priority = priority;
|
|
79867
80125
|
headers.Title = truncatedTitle;
|
|
79868
80126
|
if (clickUrl) {
|
|
79869
80127
|
headers.Click = clickUrl;
|
|
@@ -79879,7 +80137,7 @@ async function sendNtfyNotificationWithResult({
|
|
|
79879
80137
|
topic,
|
|
79880
80138
|
title: truncatedTitle,
|
|
79881
80139
|
message: truncatedMessage,
|
|
79882
|
-
priority,
|
|
80140
|
+
priority: ntfyPriorityToInt(priority),
|
|
79883
80141
|
...clickUrl ? { click: clickUrl } : {}
|
|
79884
80142
|
}),
|
|
79885
80143
|
signal
|
|
@@ -85151,11 +85409,11 @@ var init_step_session_executor = __esm({
|
|
|
85151
85409
|
}
|
|
85152
85410
|
if (attempt > 0) {
|
|
85153
85411
|
retries++;
|
|
85154
|
-
const
|
|
85412
|
+
const delay5 = RETRY_DELAYS_MS[attempt - 1] ?? 15e3;
|
|
85155
85413
|
stepExecLog.log(
|
|
85156
|
-
`Step ${stepIndex} retry ${attempt}/${MAX_STEP_RETRIES} (delay=${
|
|
85414
|
+
`Step ${stepIndex} retry ${attempt}/${MAX_STEP_RETRIES} (delay=${delay5}ms) for task ${taskDetail.id}`
|
|
85157
85415
|
);
|
|
85158
|
-
await sleep3(
|
|
85416
|
+
await sleep3(delay5);
|
|
85159
85417
|
}
|
|
85160
85418
|
const agentLogger = new AgentLogger({
|
|
85161
85419
|
store: this.store,
|
|
@@ -85921,9 +86179,9 @@ async function withRateLimitRetry(fn2, options = {}) {
|
|
|
85921
86179
|
}
|
|
85922
86180
|
const rawDelay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
85923
86181
|
const jitter = rawDelay * 0.1 * (2 * Math.random() - 1);
|
|
85924
|
-
const
|
|
85925
|
-
onRetry?.(attempt + 1,
|
|
85926
|
-
await sleep4(
|
|
86182
|
+
const delay5 = Math.max(0, Math.round(rawDelay + jitter));
|
|
86183
|
+
onRetry?.(attempt + 1, delay5, error);
|
|
86184
|
+
await sleep4(delay5, signal);
|
|
85927
86185
|
}
|
|
85928
86186
|
}
|
|
85929
86187
|
throw lastError ?? new Error("withRateLimitRetry: unexpected state");
|
|
@@ -88993,10 +89251,13 @@ function decideAutoPrerebase(input) {
|
|
|
88993
89251
|
if (hotMatches.length > 0) {
|
|
88994
89252
|
return { fire: true, reason: "hot-file", commitsBehind, hotMatches };
|
|
88995
89253
|
}
|
|
88996
|
-
const threshold = input.settings.prerebaseDivergenceThreshold ??
|
|
88997
|
-
if (threshold > 0 && commitsBehind
|
|
89254
|
+
const threshold = input.settings.prerebaseDivergenceThreshold ?? 1;
|
|
89255
|
+
if (threshold > 0 && commitsBehind >= threshold) {
|
|
88998
89256
|
return { fire: true, reason: "divergence-threshold", commitsBehind, hotMatches: [] };
|
|
88999
89257
|
}
|
|
89258
|
+
if (commitsBehind > 0) {
|
|
89259
|
+
return { fire: true, reason: "safety-fallback-any-divergence", commitsBehind, hotMatches: [] };
|
|
89260
|
+
}
|
|
89000
89261
|
return { fire: false, reason: "no-divergence", commitsBehind, hotMatches: [] };
|
|
89001
89262
|
}
|
|
89002
89263
|
async function runAutoPrerebase(deps) {
|
|
@@ -90914,12 +91175,22 @@ async function advanceIntegrationBranchRef(args) {
|
|
|
90914
91175
|
return { advanced: true, previousSha: expectedCurrentSha, newSha };
|
|
90915
91176
|
} catch (error) {
|
|
90916
91177
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
90917
|
-
|
|
90918
|
-
|
|
90919
|
-
|
|
91178
|
+
let postFailureCurrentSha;
|
|
91179
|
+
try {
|
|
91180
|
+
const { stdout: postStdout } = await testHooks.runGit(
|
|
91181
|
+
["rev-parse", "--verify", ref],
|
|
91182
|
+
rootDir
|
|
91183
|
+
);
|
|
91184
|
+
postFailureCurrentSha = postStdout.trim() || void 0;
|
|
91185
|
+
} catch {
|
|
91186
|
+
postFailureCurrentSha = observedCurrentSha;
|
|
91187
|
+
}
|
|
91188
|
+
const effectiveObserved = postFailureCurrentSha || observedCurrentSha;
|
|
91189
|
+
const refMoved = !!effectiveObserved && effectiveObserved !== expectedCurrentSha;
|
|
91190
|
+
const reason = refMoved ? "concurrent-advance" : "ref-update-refused";
|
|
90920
91191
|
await emitRefAdvance({
|
|
90921
91192
|
succeeded: false,
|
|
90922
|
-
fromSha:
|
|
91193
|
+
fromSha: effectiveObserved || expectedCurrentSha,
|
|
90923
91194
|
toSha: newSha,
|
|
90924
91195
|
error: `${reason}: ${diagnostic}`
|
|
90925
91196
|
});
|
|
@@ -90927,7 +91198,7 @@ async function advanceIntegrationBranchRef(args) {
|
|
|
90927
91198
|
advanced: false,
|
|
90928
91199
|
reason,
|
|
90929
91200
|
diagnostic,
|
|
90930
|
-
observedCurrentSha
|
|
91201
|
+
observedCurrentSha: effectiveObserved
|
|
90931
91202
|
};
|
|
90932
91203
|
}
|
|
90933
91204
|
}
|
|
@@ -91417,6 +91688,7 @@ __export(merger_exports, {
|
|
|
91417
91688
|
classifyConflict: () => classifyConflict,
|
|
91418
91689
|
classifyOwnedLandedEvidence: () => classifyOwnedLandedEvidence,
|
|
91419
91690
|
commitOrAmendMergeWithFixes: () => commitOrAmendMergeWithFixes,
|
|
91691
|
+
composeMergeCommitBody: () => composeMergeCommitBody,
|
|
91420
91692
|
deriveDeterministicSubjectSummary: () => deriveDeterministicSubjectSummary,
|
|
91421
91693
|
deriveScopedPnpmTestCommand: () => deriveScopedPnpmTestCommand,
|
|
91422
91694
|
detectResolvableConflicts: () => detectResolvableConflicts,
|
|
@@ -93906,6 +94178,23 @@ async function generateAiMergeSummary(commitLog, diffStat, settings, rootDir) {
|
|
|
93906
94178
|
return null;
|
|
93907
94179
|
}
|
|
93908
94180
|
}
|
|
94181
|
+
async function generateAiMergeBody(commitLog, diffStat, settings, rootDir, branch, taskId, signal) {
|
|
94182
|
+
const cleanStat = diffStat.trim();
|
|
94183
|
+
if (!cleanStat) return null;
|
|
94184
|
+
try {
|
|
94185
|
+
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
|
94186
|
+
return await summarizeCommitBody(cleanStat, rootDir, resolved.provider, resolved.modelId, {
|
|
94187
|
+
branch,
|
|
94188
|
+
taskId,
|
|
94189
|
+
commitLog,
|
|
94190
|
+
signal
|
|
94191
|
+
});
|
|
94192
|
+
} catch (err) {
|
|
94193
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94194
|
+
mergerLog.warn(`AI merge body failed; using deterministic fallback (${message})`);
|
|
94195
|
+
return null;
|
|
94196
|
+
}
|
|
94197
|
+
}
|
|
93909
94198
|
async function generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, signal) {
|
|
93910
94199
|
try {
|
|
93911
94200
|
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
|
@@ -93973,22 +94262,43 @@ function deriveDeterministicSubjectSummary(commitLog) {
|
|
|
93973
94262
|
const summary = extras > 0 ? `${headline} (+${extras} more)` : headline;
|
|
93974
94263
|
return summary;
|
|
93975
94264
|
}
|
|
93976
|
-
|
|
93977
|
-
const {
|
|
93978
|
-
const
|
|
93979
|
-
const
|
|
93980
|
-
const derived = trimmedAiSubject.length === 0 ? deriveDeterministicSubjectSummary(commitLog ?? "") : null;
|
|
93981
|
-
const subjectSummary = trimmedAiSubject.length > 0 ? trimmedAiSubject : derived ?? `merge ${branch}`;
|
|
93982
|
-
const subject = `${prefix}: ${subjectSummary}`;
|
|
94265
|
+
function composeMergeCommitBody(params) {
|
|
94266
|
+
const { branch, commitLog, diffStat, aiSummary, aiBody } = params;
|
|
94267
|
+
const trimmedSummary = aiSummary?.trim() ?? "";
|
|
94268
|
+
const trimmedAiBody = aiBody?.trim() ?? "";
|
|
93983
94269
|
const trimmedCommitLog = commitLog?.trim() ?? "";
|
|
93984
94270
|
const trimmedDiffStat = diffStat?.trim() ?? "";
|
|
93985
94271
|
const commitsSection = trimmedCommitLog.length > 0 ? trimmedCommitLog : `- merge ${branch}`;
|
|
93986
|
-
const
|
|
94272
|
+
const parts = [];
|
|
94273
|
+
if (trimmedSummary.length > 0) parts.push(trimmedSummary);
|
|
94274
|
+
if (trimmedAiBody.length > 0) parts.push(trimmedAiBody);
|
|
94275
|
+
const deterministicFallback = [
|
|
93987
94276
|
`Commits merged:
|
|
93988
94277
|
${commitsSection}`,
|
|
93989
94278
|
trimmedDiffStat.length > 0 ? `Files changed:
|
|
93990
94279
|
${trimmedDiffStat}` : ""
|
|
93991
94280
|
].filter(Boolean).join("\n\n");
|
|
94281
|
+
if (parts.length === 0) return deterministicFallback;
|
|
94282
|
+
if (trimmedDiffStat.length > 0) {
|
|
94283
|
+
parts.push(`Files changed:
|
|
94284
|
+
${trimmedDiffStat}`);
|
|
94285
|
+
}
|
|
94286
|
+
return parts.join("\n\n");
|
|
94287
|
+
}
|
|
94288
|
+
async function buildDeterministicMergeMessage(params) {
|
|
94289
|
+
const { taskId, branch, commitLog, diffStat, includeTaskId, aiSummary, aiBody, aiSubject } = params;
|
|
94290
|
+
const prefix = includeTaskId ? `feat(${taskId})` : "feat";
|
|
94291
|
+
const trimmedAiSubject = aiSubject?.trim() ?? "";
|
|
94292
|
+
const derived = trimmedAiSubject.length === 0 ? deriveDeterministicSubjectSummary(commitLog ?? "") : null;
|
|
94293
|
+
const subjectSummary = trimmedAiSubject.length > 0 ? trimmedAiSubject : derived ?? `merge ${branch}`;
|
|
94294
|
+
const subject = `${prefix}: ${subjectSummary}`;
|
|
94295
|
+
const body = composeMergeCommitBody({
|
|
94296
|
+
branch,
|
|
94297
|
+
commitLog,
|
|
94298
|
+
diffStat,
|
|
94299
|
+
aiSummary,
|
|
94300
|
+
aiBody
|
|
94301
|
+
});
|
|
93992
94302
|
const escape = (s3) => s3.replace(/(["\\$`])/g, "\\$1");
|
|
93993
94303
|
return {
|
|
93994
94304
|
subjectArg: `-m "${escape(subject)}"`,
|
|
@@ -94400,6 +94710,7 @@ ${msg}`;
|
|
|
94400
94710
|
diffStat: messageDiffStat,
|
|
94401
94711
|
includeTaskId,
|
|
94402
94712
|
aiSummary,
|
|
94713
|
+
aiBody: void 0,
|
|
94403
94714
|
aiSubject
|
|
94404
94715
|
});
|
|
94405
94716
|
let lineageId;
|
|
@@ -97593,8 +97904,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
97593
97904
|
mergerLog.log(`${taskId}: ${routeMessage}`);
|
|
97594
97905
|
await store.appendAgentLog(taskId, routeMessage, "text", void 0, "merger");
|
|
97595
97906
|
}
|
|
97596
|
-
const aiMergeSummary = settings.useAiMergeCommitSummary ? await
|
|
97597
|
-
|
|
97907
|
+
const [aiMergeSummary, aiMergeBody, aiMergeSubject] = settings.useAiMergeCommitSummary ? await Promise.all([
|
|
97908
|
+
generateAiMergeSummary(commitLog, diffStat, settings, rootDir),
|
|
97909
|
+
generateAiMergeBody(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal),
|
|
97910
|
+
generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, options.signal)
|
|
97911
|
+
]) : [null, null, null];
|
|
97598
97912
|
try {
|
|
97599
97913
|
const scopeResult = await validateDiffScope(store, taskId, diffStat, settings.strictScopeEnforcement);
|
|
97600
97914
|
for (const warning of scopeResult.warnings) {
|
|
@@ -97670,6 +97984,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
97670
97984
|
commitLog,
|
|
97671
97985
|
diffStat,
|
|
97672
97986
|
aiSummary: aiMergeSummary,
|
|
97987
|
+
aiBody: aiMergeBody,
|
|
97673
97988
|
aiSubject: aiMergeSubject,
|
|
97674
97989
|
includeTaskId,
|
|
97675
97990
|
sourceIssueRef,
|
|
@@ -98268,6 +98583,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98268
98583
|
const recordedFilesChanged = mergeWasEmpty ? 0 : filesChanged;
|
|
98269
98584
|
const recordedInsertions = mergeWasEmpty ? 0 : insertions;
|
|
98270
98585
|
const recordedDeletions = mergeWasEmpty ? 0 : deletions;
|
|
98586
|
+
const mergeConfirmedAtThisPoint = !reuseTaskWorktreeMerge;
|
|
98271
98587
|
const mergeDetails = {
|
|
98272
98588
|
commitSha: recordedSha,
|
|
98273
98589
|
rebaseBaseSha: !mergeWasEmpty && rebaseMergeBaseSha ? rebaseMergeBaseSha : void 0,
|
|
@@ -98278,9 +98594,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98278
98594
|
noOpVerifiedShortCircuit,
|
|
98279
98595
|
landedFilesAttributionRestricted,
|
|
98280
98596
|
landedFilesCaptureFallback,
|
|
98597
|
+
// Keep mergeDetails headline-only for dashboard cards; rich bullet body
|
|
98598
|
+
// is used in the actual git commit message composition path.
|
|
98281
98599
|
mergeCommitMessage: aiMergeSummary || commitLog,
|
|
98282
98600
|
mergedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98283
|
-
mergeConfirmed:
|
|
98601
|
+
mergeConfirmed: mergeConfirmedAtThisPoint,
|
|
98284
98602
|
mergeTargetBranch: mergeTarget.branch,
|
|
98285
98603
|
mergeTargetSource: mergeTarget.source,
|
|
98286
98604
|
resolutionStrategy: result.resolutionStrategy,
|
|
@@ -98374,6 +98692,19 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98374
98692
|
mergerLog.log(
|
|
98375
98693
|
`${taskId}: ${integrationBranch2} advanced to ${worktreeHeadSha.slice(0, 8)} via update-ref; your checked-out worktree at ${projectRootDir} is now behind`
|
|
98376
98694
|
);
|
|
98695
|
+
try {
|
|
98696
|
+
const currentTask = await store.getTask(taskId).catch(() => null);
|
|
98697
|
+
const currentMergeDetails = currentTask?.mergeDetails;
|
|
98698
|
+
if (currentMergeDetails && !currentMergeDetails.mergeConfirmed) {
|
|
98699
|
+
await store.updateTask(taskId, {
|
|
98700
|
+
mergeDetails: { ...currentMergeDetails, mergeConfirmed: true }
|
|
98701
|
+
});
|
|
98702
|
+
}
|
|
98703
|
+
} catch (promoteErr) {
|
|
98704
|
+
mergerLog.warn(
|
|
98705
|
+
`${taskId}: failed to promote mergeConfirmed post-ref-advance: ${promoteErr instanceof Error ? promoteErr.message : String(promoteErr)}`
|
|
98706
|
+
);
|
|
98707
|
+
}
|
|
98377
98708
|
const autoSyncMode = normalizeMergeAdvanceAutoSyncMode(settings.mergeAdvanceAutoSync);
|
|
98378
98709
|
if (autoSyncMode !== "off") {
|
|
98379
98710
|
try {
|
|
@@ -98738,6 +99069,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
98738
99069
|
commitLog,
|
|
98739
99070
|
diffStat,
|
|
98740
99071
|
aiSummary,
|
|
99072
|
+
aiBody,
|
|
98741
99073
|
aiSubject,
|
|
98742
99074
|
includeTaskId,
|
|
98743
99075
|
sourceIssueRef,
|
|
@@ -98996,6 +99328,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
98996
99328
|
commitLog,
|
|
98997
99329
|
diffStat,
|
|
98998
99330
|
aiSummary,
|
|
99331
|
+
aiBody,
|
|
98999
99332
|
aiSubject,
|
|
99000
99333
|
includeTaskId,
|
|
99001
99334
|
hasConflicts,
|
|
@@ -99142,7 +99475,7 @@ async function attemptWithMixedSideStrategy(params, strategy, aiTracker) {
|
|
|
99142
99475
|
}
|
|
99143
99476
|
}
|
|
99144
99477
|
async function finalizeSideStrategyAttempt(params, side, aiTracker) {
|
|
99145
|
-
const { rootDir, branch, commitLog, diffStat, aiSummary, aiSubject, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
|
|
99478
|
+
const { rootDir, branch, commitLog, diffStat, aiSummary, aiBody, aiSubject, includeTaskId, sourceIssueRef, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
|
|
99146
99479
|
const staged = execSyncText("git diff --cached --quiet 2>&1; echo $?", {
|
|
99147
99480
|
cwd: rootDir,
|
|
99148
99481
|
encoding: "utf-8"
|
|
@@ -99183,7 +99516,8 @@ async function finalizeSideStrategyAttempt(params, side, aiTracker) {
|
|
|
99183
99516
|
commitLog,
|
|
99184
99517
|
diffStat,
|
|
99185
99518
|
includeTaskId,
|
|
99186
|
-
aiSummary
|
|
99519
|
+
aiSummary,
|
|
99520
|
+
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
|
99187
99521
|
aiSubject
|
|
99188
99522
|
});
|
|
99189
99523
|
await enforceSquashFileScopeInvariant({
|
|
@@ -99241,6 +99575,7 @@ async function runAiAgentForCommit(params) {
|
|
|
99241
99575
|
commitLog,
|
|
99242
99576
|
diffStat,
|
|
99243
99577
|
aiSummary,
|
|
99578
|
+
aiBody,
|
|
99244
99579
|
aiSubject,
|
|
99245
99580
|
includeTaskId,
|
|
99246
99581
|
hasConflicts,
|
|
@@ -99462,7 +99797,8 @@ async function runAiAgentForCommit(params) {
|
|
|
99462
99797
|
commitLog,
|
|
99463
99798
|
diffStat,
|
|
99464
99799
|
includeTaskId,
|
|
99465
|
-
aiSummary
|
|
99800
|
+
aiSummary,
|
|
99801
|
+
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
|
99466
99802
|
aiSubject
|
|
99467
99803
|
});
|
|
99468
99804
|
await runDiffVolumeGate({
|
|
@@ -100287,7 +100623,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
100287
100623
|
function hasStepProgress2(task) {
|
|
100288
100624
|
return task.steps.some((step) => step.status !== "pending");
|
|
100289
100625
|
}
|
|
100290
|
-
var log20, worktreeMetadataReconcileLog, execAsync27, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS, COMPLETION_HANDOFF_LIMBO_GRACE_MS, MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES2, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_WORKTREE_SESSION_RETRIES, MAX_AUTO_MERGE_RETRIES, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
100626
|
+
var log20, worktreeMetadataReconcileLog, execAsync27, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, STALE_ACTIVE_BRANCH_EXECUTION_GRACE_MS, COMPLETION_HANDOFF_LIMBO_GRACE_MS, MAX_COMPLETION_HANDOFF_LIMBO_RECOVERIES, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES2, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_WORKTREE_SESSION_RETRIES, MAX_AUTO_MERGE_RETRIES, MAX_TRANSIENT_MERGE_RECOVERIES, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
|
|
100291
100627
|
var init_self_healing = __esm({
|
|
100292
100628
|
"../engine/src/self-healing.ts"() {
|
|
100293
100629
|
"use strict";
|
|
@@ -100308,6 +100644,8 @@ var init_self_healing = __esm({
|
|
|
100308
100644
|
init_foreign_only_contamination();
|
|
100309
100645
|
init_notifier();
|
|
100310
100646
|
init_dependency_blocked_todo_reporter();
|
|
100647
|
+
init_transient_merge_error_classifier();
|
|
100648
|
+
init_transient_merge_error_classifier();
|
|
100311
100649
|
log20 = createLogger2("self-healing");
|
|
100312
100650
|
worktreeMetadataReconcileLog = createLogger2("worktree-metadata-reconcile");
|
|
100313
100651
|
execAsync27 = promisify35(exec29);
|
|
@@ -100346,6 +100684,7 @@ var init_self_healing = __esm({
|
|
|
100346
100684
|
MAX_TASK_DONE_RETRIES = 3;
|
|
100347
100685
|
MAX_WORKTREE_SESSION_RETRIES = 3;
|
|
100348
100686
|
MAX_AUTO_MERGE_RETRIES = 3;
|
|
100687
|
+
MAX_TRANSIENT_MERGE_RECOVERIES = 2;
|
|
100349
100688
|
MAX_STARVATION_DROPS = 3;
|
|
100350
100689
|
DEADLOCK_RECOVERY_COOLDOWN_MS = 15 * 6e4;
|
|
100351
100690
|
DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS = 5 * 6e4;
|
|
@@ -100543,6 +100882,7 @@ var init_self_healing = __esm({
|
|
|
100543
100882
|
{ name: "stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks().then(() => void 0) },
|
|
100544
100883
|
{ name: "failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps().then(() => void 0) },
|
|
100545
100884
|
{ name: "interrupted-merging", fn: () => this.recoverInterruptedMergingTasks().then(() => void 0) },
|
|
100885
|
+
{ name: "transient-merge-failures", fn: () => this.recoverTransientMergeFailures().then(() => void 0) },
|
|
100546
100886
|
{ name: "done-merge-metadata", fn: () => this.recoverDoneTaskMergeMetadata().then(() => void 0) },
|
|
100547
100887
|
{ name: "reconcile-done-task-integrity", fn: () => this.reconcileDoneTaskIntegrity().then(() => void 0) },
|
|
100548
100888
|
// FN-5092: must run BEFORE any merger pickup path so the merger queue is
|
|
@@ -100637,8 +100977,8 @@ var init_self_healing = __esm({
|
|
|
100637
100977
|
this.lastPauseTriggeredAt = Date.now();
|
|
100638
100978
|
const baseDelay = settings.autoUnpauseBaseDelayMs ?? 3e5;
|
|
100639
100979
|
const maxDelay = settings.autoUnpauseMaxDelayMs ?? 36e5;
|
|
100640
|
-
const
|
|
100641
|
-
this.scheduleUnpause(
|
|
100980
|
+
const delay5 = Math.min(baseDelay * Math.pow(2, this.unpauseAttempt), maxDelay);
|
|
100981
|
+
this.scheduleUnpause(delay5);
|
|
100642
100982
|
}
|
|
100643
100983
|
if (previous.globalPause && !settings.globalPause) {
|
|
100644
100984
|
this.cancelUnpauseTimer();
|
|
@@ -101072,6 +101412,7 @@ var init_self_healing = __esm({
|
|
|
101072
101412
|
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
|
101073
101413
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
101074
101414
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
101415
|
+
{ name: "recover-transient-merge-failures", fn: () => this.recoverTransientMergeFailures() },
|
|
101075
101416
|
{ name: "recover-done-merge-metadata", fn: () => this.recoverDoneTaskMergeMetadata() },
|
|
101076
101417
|
{ name: "recover-stale-merging-status", fn: () => this.recoverStaleMergingStatus() },
|
|
101077
101418
|
{ name: "finalize-noop-review", fn: () => this.finalizeNoOpReviewTasks() },
|
|
@@ -104244,6 +104585,142 @@ var init_self_healing = __esm({
|
|
|
104244
104585
|
* No-op when `settings.autoMerge === false` — PR-based review flow owns lifecycle until human merge.
|
|
104245
104586
|
* @returns Number of tasks finalized or unblocked
|
|
104246
104587
|
*/
|
|
104588
|
+
/**
|
|
104589
|
+
* FN-5627 follow-up: recover in-review tasks that are stuck with
|
|
104590
|
+
* `mergeRetries >= MAX_AUTO_MERGE_RETRIES` and `status='failed'` due to a
|
|
104591
|
+
* TRANSIENT merge failure class (e.g., `target-not-queued` lease handoff
|
|
104592
|
+
* race, legacy same-SHA spurious concurrent-advance). These tasks are
|
|
104593
|
+
* NOT really stuck — they just hit a race or a misclassified ref-update
|
|
104594
|
+
* failure that would have cleared on a fresh attempt. Without this sweep,
|
|
104595
|
+
* the only path forward is manual intervention (the
|
|
104596
|
+
* `AUTO_MERGE_COOLDOWN_MS`-based reset takes hours).
|
|
104597
|
+
*
|
|
104598
|
+
* For each matching task:
|
|
104599
|
+
* - Reset `mergeRetries=0`, clear `status` and `error`.
|
|
104600
|
+
* - Increment `mergeDetails.transientRecoveryCount`.
|
|
104601
|
+
* - Re-enqueue via `requeueForAutoMerge`.
|
|
104602
|
+
* - Bounded by `MAX_TRANSIENT_MERGE_RECOVERIES`; exhausted tasks stay
|
|
104603
|
+
* parked as failed and emit `merger:transient-failure-budget-exhausted`
|
|
104604
|
+
* once for diagnostic visibility.
|
|
104605
|
+
*
|
|
104606
|
+
* No-op when `settings.autoMerge === false`, no `requeueForAutoMerge`
|
|
104607
|
+
* callback is wired, or global/engine pause is active.
|
|
104608
|
+
*
|
|
104609
|
+
* @returns Number of tasks recovered
|
|
104610
|
+
*/
|
|
104611
|
+
async recoverTransientMergeFailures() {
|
|
104612
|
+
const requeue = this.options.requeueForAutoMerge;
|
|
104613
|
+
if (!requeue) return 0;
|
|
104614
|
+
try {
|
|
104615
|
+
const settings = await this.store.getSettings();
|
|
104616
|
+
if (settings.autoMerge === false) return 0;
|
|
104617
|
+
if (settings.globalPause || settings.enginePaused) return 0;
|
|
104618
|
+
const slim = await this.store.listTasks({ column: "in-review", slim: true });
|
|
104619
|
+
const candidates = slim.filter(
|
|
104620
|
+
(t) => t.column === "in-review" && t.status === "failed" && (t.mergeRetries ?? 0) >= MAX_AUTO_MERGE_RETRIES && typeof t.error === "string" && t.error.length > 0 && classifyTransientMergeError(t.error) !== null
|
|
104621
|
+
);
|
|
104622
|
+
if (candidates.length === 0) return 0;
|
|
104623
|
+
log20.warn(
|
|
104624
|
+
`Found ${candidates.length} in-review task(s) with transient merge failures stuck at mergeRetries=${MAX_AUTO_MERGE_RETRIES}; attempting auto-recovery`
|
|
104625
|
+
);
|
|
104626
|
+
let recovered = 0;
|
|
104627
|
+
for (const slimTask of candidates) {
|
|
104628
|
+
const task = await this.store.getTask(slimTask.id).catch(() => null);
|
|
104629
|
+
if (!task) continue;
|
|
104630
|
+
if (task.column !== "in-review" || task.status !== "failed" || (task.mergeRetries ?? 0) < MAX_AUTO_MERGE_RETRIES) {
|
|
104631
|
+
continue;
|
|
104632
|
+
}
|
|
104633
|
+
const errorText = task.error ?? "";
|
|
104634
|
+
const transientClass = classifyTransientMergeError(errorText);
|
|
104635
|
+
if (!transientClass) continue;
|
|
104636
|
+
const currentCount = task.mergeDetails?.transientRecoveryCount ?? 0;
|
|
104637
|
+
const errorSnippet = errorText.slice(0, 200);
|
|
104638
|
+
const audit = createRunAuditor(this.store, {
|
|
104639
|
+
runId: generateSyntheticRunId("self-heal-transient-merge-recovery", task.id),
|
|
104640
|
+
agentId: "self-healing",
|
|
104641
|
+
taskId: task.id,
|
|
104642
|
+
phase: "recover-transient-merge-failures"
|
|
104643
|
+
});
|
|
104644
|
+
if (currentCount >= MAX_TRANSIENT_MERGE_RECOVERIES) {
|
|
104645
|
+
if (!errorText.includes("[transient-recovery-budget-exhausted]")) {
|
|
104646
|
+
await this.store.logEntry(
|
|
104647
|
+
task.id,
|
|
104648
|
+
`[FN-5627] Transient merge failure auto-recovery budget exhausted (${transientClass}, ${currentCount}/${MAX_TRANSIENT_MERGE_RECOVERIES}). Task remains parked in in-review for manual review.`
|
|
104649
|
+
);
|
|
104650
|
+
await this.store.updateTask(task.id, {
|
|
104651
|
+
error: `${errorText} [transient-recovery-budget-exhausted]`
|
|
104652
|
+
});
|
|
104653
|
+
try {
|
|
104654
|
+
await audit.database({
|
|
104655
|
+
type: "merger:transient-failure-budget-exhausted",
|
|
104656
|
+
target: task.id,
|
|
104657
|
+
metadata: {
|
|
104658
|
+
taskId: task.id,
|
|
104659
|
+
transientClass,
|
|
104660
|
+
recoveryCount: currentCount,
|
|
104661
|
+
maxRecoveries: MAX_TRANSIENT_MERGE_RECOVERIES,
|
|
104662
|
+
errorSnippet
|
|
104663
|
+
}
|
|
104664
|
+
});
|
|
104665
|
+
} catch (auditErr) {
|
|
104666
|
+
log20.warn(
|
|
104667
|
+
`recoverTransientMergeFailures: audit emit failed for ${task.id}: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`
|
|
104668
|
+
);
|
|
104669
|
+
}
|
|
104670
|
+
}
|
|
104671
|
+
continue;
|
|
104672
|
+
}
|
|
104673
|
+
const nextCount = currentCount + 1;
|
|
104674
|
+
await this.store.logEntry(
|
|
104675
|
+
task.id,
|
|
104676
|
+
`Auto-recovered: transient merge failure (${transientClass}); resetting mergeRetries=0 and re-enqueueing (recovery ${nextCount}/${MAX_TRANSIENT_MERGE_RECOVERIES}) [FN-5627]`
|
|
104677
|
+
);
|
|
104678
|
+
await this.store.updateTask(task.id, {
|
|
104679
|
+
mergeRetries: 0,
|
|
104680
|
+
status: null,
|
|
104681
|
+
error: null,
|
|
104682
|
+
mergeDetails: {
|
|
104683
|
+
...task.mergeDetails,
|
|
104684
|
+
transientRecoveryCount: nextCount
|
|
104685
|
+
}
|
|
104686
|
+
});
|
|
104687
|
+
try {
|
|
104688
|
+
await audit.database({
|
|
104689
|
+
type: "merger:transient-failure-auto-recovered",
|
|
104690
|
+
target: task.id,
|
|
104691
|
+
metadata: {
|
|
104692
|
+
taskId: task.id,
|
|
104693
|
+
transientClass,
|
|
104694
|
+
mergeRetries: task.mergeRetries ?? 0,
|
|
104695
|
+
recoveryCount: nextCount,
|
|
104696
|
+
errorSnippet
|
|
104697
|
+
}
|
|
104698
|
+
});
|
|
104699
|
+
} catch (auditErr) {
|
|
104700
|
+
log20.warn(
|
|
104701
|
+
`recoverTransientMergeFailures: audit emit failed for ${task.id}: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`
|
|
104702
|
+
);
|
|
104703
|
+
}
|
|
104704
|
+
try {
|
|
104705
|
+
await requeue(task.id);
|
|
104706
|
+
recovered++;
|
|
104707
|
+
} catch (requeueErr) {
|
|
104708
|
+
log20.warn(
|
|
104709
|
+
`recoverTransientMergeFailures: requeue failed for ${task.id}: ${requeueErr instanceof Error ? requeueErr.message : String(requeueErr)}`
|
|
104710
|
+
);
|
|
104711
|
+
}
|
|
104712
|
+
}
|
|
104713
|
+
if (recovered > 0) {
|
|
104714
|
+
log20.log(`recoverTransientMergeFailures: re-enqueued ${recovered} stuck in-review task(s)`);
|
|
104715
|
+
}
|
|
104716
|
+
return recovered;
|
|
104717
|
+
} catch (err) {
|
|
104718
|
+
log20.warn(
|
|
104719
|
+
`recoverTransientMergeFailures sweep failed: ${err instanceof Error ? err.message : String(err)}`
|
|
104720
|
+
);
|
|
104721
|
+
return 0;
|
|
104722
|
+
}
|
|
104723
|
+
}
|
|
104247
104724
|
async recoverInterruptedMergingTasks() {
|
|
104248
104725
|
try {
|
|
104249
104726
|
const settings = await this.store.getSettings();
|
|
@@ -106545,7 +107022,7 @@ var init_self_healing = __esm({
|
|
|
106545
107022
|
});
|
|
106546
107023
|
|
|
106547
107024
|
// ../engine/src/triage.ts
|
|
106548
|
-
import { Type as Type3 } from "@
|
|
107025
|
+
import { Type as Type3 } from "@earendil-works/pi-ai";
|
|
106549
107026
|
import { exec as exec30 } from "node:child_process";
|
|
106550
107027
|
import { readFile as readFile21 } from "node:fs/promises";
|
|
106551
107028
|
import { join as join44 } from "node:path";
|
|
@@ -106604,9 +107081,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
106604
107081
|
return { attachmentContents, imageContents };
|
|
106605
107082
|
}
|
|
106606
107083
|
const { readFile: readFile29 } = await import("node:fs/promises");
|
|
106607
|
-
const { join:
|
|
107084
|
+
const { join: join59 } = await import("node:path");
|
|
106608
107085
|
for (const att of attachments) {
|
|
106609
|
-
const filePath =
|
|
107086
|
+
const filePath = join59(
|
|
106610
107087
|
rootDir,
|
|
106611
107088
|
".fusion",
|
|
106612
107089
|
"tasks",
|
|
@@ -108079,8 +108556,8 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
108079
108556
|
});
|
|
108080
108557
|
if (decision.shouldRetry) {
|
|
108081
108558
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
108082
|
-
const
|
|
108083
|
-
const retryMessage = `Spec review not approved (${verdictDesc}) \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
108559
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
108560
|
+
const retryMessage = `Spec review not approved (${verdictDesc}) \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}.`;
|
|
108084
108561
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
108085
108562
|
await this.store.logEntry(task.id, retryMessage);
|
|
108086
108563
|
const restoreStatus = task.status === "needs-replan" ? "needs-replan" : null;
|
|
@@ -108186,10 +108663,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
108186
108663
|
});
|
|
108187
108664
|
if (decision.shouldRetry) {
|
|
108188
108665
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
108189
|
-
const
|
|
108666
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
108190
108667
|
if (!isSilentTransientError(errorMessage)) {
|
|
108191
|
-
planLog.warn(`\u26A1 ${task.id} transient error during triage \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
108192
|
-
await this.store.logEntry(task.id, `Transient error during specification (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
108668
|
+
planLog.warn(`\u26A1 ${task.id} transient error during triage \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}: ${errorMessage}`);
|
|
108669
|
+
await this.store.logEntry(task.id, `Transient error during specification (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}): ${errorMessage}`).catch((err2) => {
|
|
108193
108670
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
108194
108671
|
planLog.warn(`${task.id}: failed to log transient-error retry entry: ${msg}`);
|
|
108195
108672
|
});
|
|
@@ -108488,9 +108965,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
108488
108965
|
}
|
|
108489
108966
|
try {
|
|
108490
108967
|
const { readFile: readFile29 } = await import("node:fs/promises");
|
|
108491
|
-
const { join:
|
|
108968
|
+
const { join: join59 } = await import("node:path");
|
|
108492
108969
|
const promptContent = await readFile29(
|
|
108493
|
-
|
|
108970
|
+
join59(rootDir, promptPath),
|
|
108494
108971
|
"utf-8"
|
|
108495
108972
|
).catch((err) => {
|
|
108496
108973
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -109636,7 +110113,7 @@ var init_task_completion = __esm({
|
|
|
109636
110113
|
import { spawn as spawn6 } from "node:child_process";
|
|
109637
110114
|
import { existsSync as existsSync40 } from "node:fs";
|
|
109638
110115
|
import { isAbsolute as isAbsolute13, join as join46 } from "node:path";
|
|
109639
|
-
import { Type as Type4 } from "@
|
|
110116
|
+
import { Type as Type4 } from "@earendil-works/pi-ai";
|
|
109640
110117
|
function createBuffer() {
|
|
109641
110118
|
return { headChunks: [], headBytes: 0, tailChunks: [], tailBytes: 0, totalBytes: 0 };
|
|
109642
110119
|
}
|
|
@@ -109945,8 +110422,8 @@ import { setImmediate as setImmediateCb3 } from "node:timers";
|
|
|
109945
110422
|
import { delimiter, isAbsolute as isAbsolute14, join as join47, relative as relative11, resolve as resolvePath } from "node:path";
|
|
109946
110423
|
import { existsSync as existsSync41, realpathSync as realpathSync6 } from "node:fs";
|
|
109947
110424
|
import { readFile as readFile22, rm as rm5, writeFile as writeFile16 } from "node:fs/promises";
|
|
109948
|
-
import { Type as Type5 } from "@
|
|
109949
|
-
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@
|
|
110425
|
+
import { Type as Type5 } from "@earendil-works/pi-ai";
|
|
110426
|
+
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
|
|
109950
110427
|
function getResumeOrphanDelayMs() {
|
|
109951
110428
|
const raw = process.env.FUSION_RESUME_ORPHAN_DELAY_MS;
|
|
109952
110429
|
if (raw !== void 0) {
|
|
@@ -109963,6 +110440,26 @@ function canonicalizePath2(path6) {
|
|
|
109963
110440
|
return resolvePath(path6);
|
|
109964
110441
|
}
|
|
109965
110442
|
}
|
|
110443
|
+
function isTransientMissingTaskJsonError(error, task) {
|
|
110444
|
+
if (error instanceof TaskDeletedError) {
|
|
110445
|
+
return false;
|
|
110446
|
+
}
|
|
110447
|
+
const message = typeof error === "string" ? error : error instanceof Error ? error.message : "";
|
|
110448
|
+
const match = TRANSIENT_WORKTREE_TASK_JSON_ENOENT_PATTERN.exec(message);
|
|
110449
|
+
if (!match) {
|
|
110450
|
+
return false;
|
|
110451
|
+
}
|
|
110452
|
+
const [, filePath, taskIdFromPath] = match;
|
|
110453
|
+
if (taskIdFromPath !== task.id) {
|
|
110454
|
+
return false;
|
|
110455
|
+
}
|
|
110456
|
+
if (typeof task.worktree !== "string" || task.worktree.length === 0) {
|
|
110457
|
+
return false;
|
|
110458
|
+
}
|
|
110459
|
+
const normalizedWorktree = resolvePath(task.worktree);
|
|
110460
|
+
const normalizedTaskJsonPath = resolvePath(filePath);
|
|
110461
|
+
return normalizedTaskJsonPath.startsWith(`${normalizedWorktree}/`);
|
|
110462
|
+
}
|
|
109966
110463
|
function detectPendingReviewBlock(task, codeReviewVerdicts) {
|
|
109967
110464
|
const inProgressStepIndices = [];
|
|
109968
110465
|
for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
|
|
@@ -110498,7 +110995,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
110498
110995
|
];
|
|
110499
110996
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
110500
110997
|
}
|
|
110501
|
-
var execAsync30, yieldEventLoop3, tokenCacheMetricsLog, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, TASK_DONE_REFUSAL_SUFFIX, DISSENT_PATTERNS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, WORKFLOW_FEEDBACK_PATH_REGEX, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, WORKFLOW_STEP_VERDICTS, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
110998
|
+
var execAsync30, yieldEventLoop3, tokenCacheMetricsLog, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, TASK_DONE_REFUSAL_SUFFIX, TRANSIENT_WORKTREE_TASK_JSON_ENOENT_PATTERN, DISSENT_PATTERNS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, WORKFLOW_FEEDBACK_PATH_REGEX, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, WORKFLOW_STEP_VERDICTS, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
110502
110999
|
var init_executor = __esm({
|
|
110503
111000
|
"../engine/src/executor.ts"() {
|
|
110504
111001
|
"use strict";
|
|
@@ -110566,6 +111063,7 @@ var init_executor = __esm({
|
|
|
110566
111063
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
110567
111064
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
110568
111065
|
TASK_DONE_REFUSAL_SUFFIX = "Either finish the work and resubmit, or do not call fn_task_done \u2014 exit the session and the engine will requeue.";
|
|
111066
|
+
TRANSIENT_WORKTREE_TASK_JSON_ENOENT_PATTERN = /ENOENT:\s+no such file or directory,\s+open\s+'([^']+\/\.fusion\/tasks\/([^/]+)\/task\.json)'/;
|
|
110569
111067
|
DISSENT_PATTERNS = [
|
|
110570
111068
|
/\btask (is|was)(?: not|n['’]?t) complete\b/i,
|
|
110571
111069
|
/\b(?:i (?:could|can)(?:not|n['’]?t)|unable to|failed to) (?:complete|finish|implement)\b/i,
|
|
@@ -113057,10 +113555,10 @@ ${summary}`,
|
|
|
113057
113555
|
});
|
|
113058
113556
|
if (decision.shouldRetry) {
|
|
113059
113557
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
113060
|
-
const
|
|
113558
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
113061
113559
|
if (!isSilentTransientError(errorMessage)) {
|
|
113062
|
-
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
113063
|
-
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
113560
|
+
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}: ${errorMessage}`);
|
|
113561
|
+
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}): ${errorMessage}`, void 0, this.getRunContextFor(task.id));
|
|
113064
113562
|
}
|
|
113065
113563
|
if (worktreePath && existsSync41(worktreePath)) {
|
|
113066
113564
|
try {
|
|
@@ -114022,9 +114520,9 @@ ${summary}`,
|
|
|
114022
114520
|
});
|
|
114023
114521
|
if (decision.shouldRetry) {
|
|
114024
114522
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
114025
|
-
const
|
|
114026
|
-
executorLog.warn(`\u26A1 ${task.id} context-overflow fresh-session requeue ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
114027
|
-
await this.store.logEntry(task.id, `Context-overflow fresh-session requeue (${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
114523
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
114524
|
+
executorLog.warn(`\u26A1 ${task.id} context-overflow fresh-session requeue ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}`);
|
|
114525
|
+
await this.store.logEntry(task.id, `Context-overflow fresh-session requeue (${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}): ${errorMessage}`, void 0, this.getRunContextFor(task.id));
|
|
114028
114526
|
await this.store.updateTask(task.id, {
|
|
114029
114527
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
114030
114528
|
nextRecoveryAt: decision.nextState.nextRecoveryAt,
|
|
@@ -114312,10 +114810,10 @@ ${summary}`,
|
|
|
114312
114810
|
});
|
|
114313
114811
|
if (decision.shouldRetry) {
|
|
114314
114812
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
114315
|
-
const
|
|
114813
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
114316
114814
|
if (!isSilentTransientError(errorMessage)) {
|
|
114317
|
-
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
114318
|
-
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
114815
|
+
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}: ${errorMessage}`);
|
|
114816
|
+
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay5}): ${errorMessage}`, void 0, this.getRunContextFor(task.id));
|
|
114319
114817
|
}
|
|
114320
114818
|
if (worktreePath && existsSync41(worktreePath)) {
|
|
114321
114819
|
try {
|
|
@@ -117017,8 +117515,8 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
117017
117515
|
`Failed to create worktree after ${this.MAX_WORKTREE_RETRIES} attempts: ${errorMessage}`
|
|
117018
117516
|
);
|
|
117019
117517
|
}
|
|
117020
|
-
const
|
|
117021
|
-
await new Promise((resolve32) => setTimeout(resolve32,
|
|
117518
|
+
const delay5 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
|
|
117519
|
+
await new Promise((resolve32) => setTimeout(resolve32, delay5));
|
|
117022
117520
|
}
|
|
117023
117521
|
}
|
|
117024
117522
|
throw new Error("Unexpected exit from worktree creation retry loop");
|
|
@@ -117256,9 +117754,15 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
117256
117754
|
}
|
|
117257
117755
|
async recoverMissingWorktreeSessionStartFailure(task, worktreePath, error, audit) {
|
|
117258
117756
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
117259
|
-
|
|
117757
|
+
const missingWorktreeFailure = isMissingWorktreeSessionStartFailure(errorText);
|
|
117758
|
+
const missingTaskJsonFailure = isTransientMissingTaskJsonError(error, task);
|
|
117759
|
+
if (!missingWorktreeFailure && !missingTaskJsonFailure) return false;
|
|
117260
117760
|
const classification = classifyMissingWorktreeSessionStartFailure(errorText);
|
|
117261
|
-
const
|
|
117761
|
+
const missingTaskJsonPath = errorText.match(TRANSIENT_WORKTREE_TASK_JSON_ENOENT_PATTERN)?.[1] ?? null;
|
|
117762
|
+
const staleWorktreePath = extractMissingWorktreePathFromSessionStartFailure(errorText) ?? (missingTaskJsonPath ? resolvePath(missingTaskJsonPath, "..", "..", "..") : null) ?? worktreePath;
|
|
117763
|
+
if (missingTaskJsonFailure) {
|
|
117764
|
+
executorLog.log(`[transient-task-json-suppressed] taskId=${task.id} elapsedMs=0 reason=missing-task-json-under-worktree path=${missingTaskJsonPath ?? "unknown"}`);
|
|
117765
|
+
}
|
|
117262
117766
|
await audit.git({
|
|
117263
117767
|
type: "worktree:incomplete-detected",
|
|
117264
117768
|
target: staleWorktreePath,
|
|
@@ -120414,6 +120918,12 @@ var init_scheduler = __esm({
|
|
|
120414
120918
|
}
|
|
120415
120919
|
const sliceIdBeforeUpdate = feature.sliceId;
|
|
120416
120920
|
if (this.options.missionExecutionLoop) {
|
|
120921
|
+
if (!this.options.missionExecutionLoop.isRunning()) {
|
|
120922
|
+
schedulerLog.warn(
|
|
120923
|
+
`MissionExecutionLoop was not running during task completion for ${taskId}; starting loop before processing outcome`
|
|
120924
|
+
);
|
|
120925
|
+
this.options.missionExecutionLoop.start();
|
|
120926
|
+
}
|
|
120417
120927
|
void this.options.missionExecutionLoop.processTaskOutcome(taskId).catch((err) => {
|
|
120418
120928
|
schedulerLog.error(`Error in missionExecutionLoop.processTaskOutcome for ${taskId}:`, err);
|
|
120419
120929
|
});
|
|
@@ -120876,19 +121386,19 @@ var init_mission_autopilot = __esm({
|
|
|
120876
121386
|
autopilotLog.error(`Error advancing slice for mission ${missionId}:`, err);
|
|
120877
121387
|
state.retryCount++;
|
|
120878
121388
|
if (state.retryCount <= MAX_RETRY_ATTEMPTS) {
|
|
120879
|
-
const
|
|
121389
|
+
const delay5 = RETRY_BASE_DELAY_MS2 * Math.pow(3, state.retryCount - 1);
|
|
120880
121390
|
this.logMissionEventSafe(
|
|
120881
121391
|
missionId,
|
|
120882
121392
|
"autopilot_retry",
|
|
120883
121393
|
`Retrying slice activation after error (attempt ${state.retryCount}/${MAX_RETRY_ATTEMPTS})`,
|
|
120884
|
-
{ retryCount: state.retryCount, maxRetries: MAX_RETRY_ATTEMPTS, delayMs:
|
|
121394
|
+
{ retryCount: state.retryCount, maxRetries: MAX_RETRY_ATTEMPTS, delayMs: delay5 }
|
|
120885
121395
|
);
|
|
120886
|
-
autopilotLog.log(`Retrying slice activation for mission ${missionId} (attempt ${state.retryCount}/${MAX_RETRY_ATTEMPTS}, delay ${
|
|
121396
|
+
autopilotLog.log(`Retrying slice activation for mission ${missionId} (attempt ${state.retryCount}/${MAX_RETRY_ATTEMPTS}, delay ${delay5}ms)`);
|
|
120887
121397
|
setTimeout(() => {
|
|
120888
121398
|
if (this.isWatching(missionId)) {
|
|
120889
121399
|
void this.advanceToNextSlice(missionId);
|
|
120890
121400
|
}
|
|
120891
|
-
},
|
|
121401
|
+
}, delay5);
|
|
120892
121402
|
} else {
|
|
120893
121403
|
this.logMissionEventSafe(
|
|
120894
121404
|
missionId,
|
|
@@ -121335,7 +121845,7 @@ var init_mission_autopilot = __esm({
|
|
|
121335
121845
|
});
|
|
121336
121846
|
|
|
121337
121847
|
// ../engine/src/mission-execution-loop.ts
|
|
121338
|
-
import { EventEmitter as
|
|
121848
|
+
import { EventEmitter as EventEmitter19 } from "node:events";
|
|
121339
121849
|
var loopLog, VALIDATION_TIMEOUT_MS, MissionExecutionLoop;
|
|
121340
121850
|
var init_mission_execution_loop = __esm({
|
|
121341
121851
|
"../engine/src/mission-execution-loop.ts"() {
|
|
@@ -121347,7 +121857,7 @@ var init_mission_execution_loop = __esm({
|
|
|
121347
121857
|
init_run_audit();
|
|
121348
121858
|
loopLog = createLogger2("mission-loop");
|
|
121349
121859
|
VALIDATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
121350
|
-
MissionExecutionLoop = class extends
|
|
121860
|
+
MissionExecutionLoop = class extends EventEmitter19 {
|
|
121351
121861
|
running = false;
|
|
121352
121862
|
taskStore;
|
|
121353
121863
|
missionStore;
|
|
@@ -121487,6 +121997,10 @@ var init_mission_execution_loop = __esm({
|
|
|
121487
121997
|
loopLog.log(`Feature ${feature.id} loopState is "${feature.loopState}"; skipping validation`);
|
|
121488
121998
|
return;
|
|
121489
121999
|
}
|
|
122000
|
+
if (this.activeValidations.has(feature.id)) {
|
|
122001
|
+
loopLog.log(`Feature ${feature.id} already has an active validation; skipping duplicate trigger`);
|
|
122002
|
+
return;
|
|
122003
|
+
}
|
|
121490
122004
|
const assertions = this.missionStore.listAssertionsForFeature(feature.id);
|
|
121491
122005
|
if (assertions.length === 0) {
|
|
121492
122006
|
loopLog.log(`Feature ${feature.id} has no linked assertions; marking as passed`);
|
|
@@ -121875,6 +122389,10 @@ ${taskContext}` : ""}`;
|
|
|
121875
122389
|
if (runId) {
|
|
121876
122390
|
this.missionStore.completeValidatorRun(runId, "passed", summary);
|
|
121877
122391
|
}
|
|
122392
|
+
const feature = this.missionStore.getFeature(featureId);
|
|
122393
|
+
if (feature && feature.status !== "done") {
|
|
122394
|
+
this.missionStore.updateFeatureStatus(featureId, "done");
|
|
122395
|
+
}
|
|
121878
122396
|
loopLog.log(`Feature ${featureId} passed validation`);
|
|
121879
122397
|
if (this.missionAutopilot?.notifyValidationComplete) {
|
|
121880
122398
|
await this.missionAutopilot.notifyValidationComplete(featureId, "passed");
|
|
@@ -123085,7 +123603,7 @@ var init_room_coordination = __esm({
|
|
|
123085
123603
|
});
|
|
123086
123604
|
|
|
123087
123605
|
// ../engine/src/agent-heartbeat.ts
|
|
123088
|
-
import { Type as Type6 } from "@
|
|
123606
|
+
import { Type as Type6 } from "@earendil-works/pi-ai";
|
|
123089
123607
|
import { createHash as createHash12 } from "node:crypto";
|
|
123090
123608
|
function adjustHeartbeatMemoryPrimer(basePrompt, mode) {
|
|
123091
123609
|
if (mode === "full") return basePrompt;
|
|
@@ -130216,7 +130734,7 @@ var init_stuck_task_detector = __esm({
|
|
|
130216
130734
|
});
|
|
130217
130735
|
|
|
130218
130736
|
// ../engine/src/plugin-runner.ts
|
|
130219
|
-
import { Type as Type7 } from "@
|
|
130737
|
+
import { Type as Type7 } from "@earendil-works/pi-ai";
|
|
130220
130738
|
import { isAbsolute as isAbsolute16 } from "node:path";
|
|
130221
130739
|
var DEFAULT_HOOK_TIMEOUT_MS, PluginRunner;
|
|
130222
130740
|
var init_plugin_runner = __esm({
|
|
@@ -131367,7 +131885,7 @@ var init_task_agent_sync = __esm({
|
|
|
131367
131885
|
});
|
|
131368
131886
|
|
|
131369
131887
|
// ../engine/src/runtimes/in-process-runtime.ts
|
|
131370
|
-
import { EventEmitter as
|
|
131888
|
+
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
131371
131889
|
import { setImmediate as setImmediateCb4 } from "node:timers";
|
|
131372
131890
|
var yieldEventLoop4, InProcessRuntime;
|
|
131373
131891
|
var init_in_process_runtime = __esm({
|
|
@@ -131397,7 +131915,7 @@ var init_in_process_runtime = __esm({
|
|
|
131397
131915
|
init_task_agent_sync();
|
|
131398
131916
|
init_run_audit();
|
|
131399
131917
|
yieldEventLoop4 = () => new Promise((resolve32) => setImmediateCb4(resolve32));
|
|
131400
|
-
InProcessRuntime = class extends
|
|
131918
|
+
InProcessRuntime = class extends EventEmitter20 {
|
|
131401
131919
|
/**
|
|
131402
131920
|
* @param config - Runtime configuration
|
|
131403
131921
|
* @param centralCore - CentralCore reference for global coordination
|
|
@@ -132427,14 +132945,14 @@ var init_ipc_protocol = __esm({
|
|
|
132427
132945
|
});
|
|
132428
132946
|
|
|
132429
132947
|
// ../engine/src/ipc/ipc-host.ts
|
|
132430
|
-
import { EventEmitter as
|
|
132948
|
+
import { EventEmitter as EventEmitter21 } from "node:events";
|
|
132431
132949
|
var IpcHost;
|
|
132432
132950
|
var init_ipc_host = __esm({
|
|
132433
132951
|
"../engine/src/ipc/ipc-host.ts"() {
|
|
132434
132952
|
"use strict";
|
|
132435
132953
|
init_ipc_protocol();
|
|
132436
132954
|
init_logger2();
|
|
132437
|
-
IpcHost = class extends
|
|
132955
|
+
IpcHost = class extends EventEmitter21 {
|
|
132438
132956
|
/**
|
|
132439
132957
|
* @param childProcess - The forked child process to communicate with
|
|
132440
132958
|
* @param options - Optional configuration
|
|
@@ -132624,7 +133142,7 @@ var child_process_runtime_exports = {};
|
|
|
132624
133142
|
__export(child_process_runtime_exports, {
|
|
132625
133143
|
ChildProcessRuntime: () => ChildProcessRuntime
|
|
132626
133144
|
});
|
|
132627
|
-
import { EventEmitter as
|
|
133145
|
+
import { EventEmitter as EventEmitter22 } from "node:events";
|
|
132628
133146
|
import { fork } from "node:child_process";
|
|
132629
133147
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
132630
133148
|
import { dirname as dirname18, join as join49 } from "node:path";
|
|
@@ -132681,8 +133199,8 @@ var init_child_process_runtime = __esm({
|
|
|
132681
133199
|
runtimeLog.log("Health monitor stopped");
|
|
132682
133200
|
}
|
|
132683
133201
|
getRestartDelay() {
|
|
132684
|
-
const
|
|
132685
|
-
return
|
|
133202
|
+
const delay5 = this.restartDelays[this.restartAttempts] ?? this.restartDelays[this.restartDelays.length - 1];
|
|
133203
|
+
return delay5;
|
|
132686
133204
|
}
|
|
132687
133205
|
incrementRestartAttempts() {
|
|
132688
133206
|
this.restartAttempts++;
|
|
@@ -132694,7 +133212,7 @@ var init_child_process_runtime = __esm({
|
|
|
132694
133212
|
return this.missedHeartbeats;
|
|
132695
133213
|
}
|
|
132696
133214
|
};
|
|
132697
|
-
ChildProcessRuntime = class extends
|
|
133215
|
+
ChildProcessRuntime = class extends EventEmitter22 {
|
|
132698
133216
|
/**
|
|
132699
133217
|
* @param config - Runtime configuration
|
|
132700
133218
|
* @param centralCore - CentralCore reference for global coordination
|
|
@@ -132963,9 +133481,9 @@ var init_child_process_runtime = __esm({
|
|
|
132963
133481
|
this.emit("error", new Error("Child process failed after max restart attempts"));
|
|
132964
133482
|
return;
|
|
132965
133483
|
}
|
|
132966
|
-
const
|
|
133484
|
+
const delay5 = this.healthMonitor.getRestartDelay();
|
|
132967
133485
|
this.healthMonitor.incrementRestartAttempts();
|
|
132968
|
-
runtimeLog.log(`Attempting restart ${this.healthMonitor.getRestartAttempts()}/${maxRestarts} after ${
|
|
133486
|
+
runtimeLog.log(`Attempting restart ${this.healthMonitor.getRestartAttempts()}/${maxRestarts} after ${delay5}ms`);
|
|
132969
133487
|
const gen = this.generation;
|
|
132970
133488
|
this.restartTimer = setTimeout(async () => {
|
|
132971
133489
|
this.restartTimer = null;
|
|
@@ -132984,7 +133502,7 @@ var init_child_process_runtime = __esm({
|
|
|
132984
133502
|
this.setStatus("errored");
|
|
132985
133503
|
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
132986
133504
|
}
|
|
132987
|
-
},
|
|
133505
|
+
}, delay5);
|
|
132988
133506
|
}
|
|
132989
133507
|
/**
|
|
132990
133508
|
* Handle child process exit.
|
|
@@ -133379,14 +133897,14 @@ var init_remote_node_client = __esm({
|
|
|
133379
133897
|
});
|
|
133380
133898
|
|
|
133381
133899
|
// ../engine/src/runtimes/remote-node-runtime.ts
|
|
133382
|
-
import { EventEmitter as
|
|
133900
|
+
import { EventEmitter as EventEmitter23 } from "node:events";
|
|
133383
133901
|
var RemoteNodeRuntime;
|
|
133384
133902
|
var init_remote_node_runtime = __esm({
|
|
133385
133903
|
"../engine/src/runtimes/remote-node-runtime.ts"() {
|
|
133386
133904
|
"use strict";
|
|
133387
133905
|
init_logger2();
|
|
133388
133906
|
init_remote_node_client();
|
|
133389
|
-
RemoteNodeRuntime = class extends
|
|
133907
|
+
RemoteNodeRuntime = class extends EventEmitter23 {
|
|
133390
133908
|
constructor(config) {
|
|
133391
133909
|
super();
|
|
133392
133910
|
this.config = config;
|
|
@@ -133696,7 +134214,7 @@ var init_remote_node_runtime = __esm({
|
|
|
133696
134214
|
});
|
|
133697
134215
|
|
|
133698
134216
|
// ../engine/src/project-manager.ts
|
|
133699
|
-
import { EventEmitter as
|
|
134217
|
+
import { EventEmitter as EventEmitter24 } from "node:events";
|
|
133700
134218
|
var ProjectManager;
|
|
133701
134219
|
var init_project_manager = __esm({
|
|
133702
134220
|
"../engine/src/project-manager.ts"() {
|
|
@@ -133706,7 +134224,7 @@ var init_project_manager = __esm({
|
|
|
133706
134224
|
init_remote_node_runtime();
|
|
133707
134225
|
init_concurrency();
|
|
133708
134226
|
init_logger2();
|
|
133709
|
-
ProjectManager = class extends
|
|
134227
|
+
ProjectManager = class extends EventEmitter24 {
|
|
133710
134228
|
/**
|
|
133711
134229
|
* @param centralCore - CentralCore reference for global coordination
|
|
133712
134230
|
*/
|
|
@@ -134173,6 +134691,653 @@ var init_gridlock_detector = __esm({
|
|
|
134173
134691
|
}
|
|
134174
134692
|
});
|
|
134175
134693
|
|
|
134694
|
+
// ../engine/src/merger-ai.ts
|
|
134695
|
+
import { execFile as execFile15 } from "node:child_process";
|
|
134696
|
+
import { promisify as promisify43 } from "node:util";
|
|
134697
|
+
import { mkdtemp as mkdtemp2, rm as rm6 } from "node:fs/promises";
|
|
134698
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
134699
|
+
import { join as join50 } from "node:path";
|
|
134700
|
+
async function git2(args, cwd, opts = {}) {
|
|
134701
|
+
const { stdout } = await execFileAsync10("git", args, {
|
|
134702
|
+
cwd,
|
|
134703
|
+
encoding: "utf-8",
|
|
134704
|
+
timeout: opts.timeout ?? 12e4,
|
|
134705
|
+
maxBuffer: 16 * 1024 * 1024
|
|
134706
|
+
});
|
|
134707
|
+
return stdout.trim();
|
|
134708
|
+
}
|
|
134709
|
+
async function gitOk(args, cwd) {
|
|
134710
|
+
try {
|
|
134711
|
+
await git2(args, cwd);
|
|
134712
|
+
return true;
|
|
134713
|
+
} catch {
|
|
134714
|
+
return false;
|
|
134715
|
+
}
|
|
134716
|
+
}
|
|
134717
|
+
function taskTrailers(taskId, lineageId) {
|
|
134718
|
+
const trailers = [`${FUSION_TASK_ID_TRAILER_KEY3}: ${taskId}`];
|
|
134719
|
+
if (lineageId) trailers.push(buildTaskLineageTrailer(lineageId));
|
|
134720
|
+
return trailers;
|
|
134721
|
+
}
|
|
134722
|
+
async function ensureCommitTaskMetadata(mergeRoot, taskId, includeTaskId, trailers) {
|
|
134723
|
+
const fullMessage = await git2(["log", "-1", "--pretty=%B"], mergeRoot).catch(() => "");
|
|
134724
|
+
if (!fullMessage) return;
|
|
134725
|
+
const subject = (fullMessage.split("\n")[0] ?? "").trim();
|
|
134726
|
+
const body = await git2(["log", "-1", "--pretty=%b"], mergeRoot).catch(() => "");
|
|
134727
|
+
const needsPrefix = includeTaskId && !subject.toLowerCase().startsWith(taskId.toLowerCase());
|
|
134728
|
+
const missingTrailers = trailers.filter((t) => !fullMessage.includes(t));
|
|
134729
|
+
if (!needsPrefix && missingTrailers.length === 0) return;
|
|
134730
|
+
const args = ["-c", "trailer.ifExists=addIfDifferent", "commit", "--amend"];
|
|
134731
|
+
if (needsPrefix) {
|
|
134732
|
+
args.push("-m", `${taskId}: ${subject}`);
|
|
134733
|
+
if (body.trim()) args.push("-m", body);
|
|
134734
|
+
} else {
|
|
134735
|
+
args.push("--no-edit");
|
|
134736
|
+
}
|
|
134737
|
+
for (const t of missingTrailers) args.push("--trailer", t);
|
|
134738
|
+
await git2(args, mergeRoot).catch((err) => {
|
|
134739
|
+
aiMergeLog.warn(`failed to amend task metadata onto squash (${err instanceof Error ? err.message : String(err)})`);
|
|
134740
|
+
});
|
|
134741
|
+
}
|
|
134742
|
+
function parseReviewVerdict(agentText) {
|
|
134743
|
+
const text = (agentText ?? "").trim();
|
|
134744
|
+
if (!text) return { verdict: "reject", reasons: ["reviewer produced no output"], severity: "blocking" };
|
|
134745
|
+
const lines = text.split(/\r?\n/);
|
|
134746
|
+
let verdictLineIndex = -1;
|
|
134747
|
+
let decision = null;
|
|
134748
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
134749
|
+
const m2 = lines[i].match(VERDICT_LINE_RE);
|
|
134750
|
+
if (m2) {
|
|
134751
|
+
decision = m2[1].toLowerCase();
|
|
134752
|
+
verdictLineIndex = i;
|
|
134753
|
+
break;
|
|
134754
|
+
}
|
|
134755
|
+
}
|
|
134756
|
+
if (!decision) {
|
|
134757
|
+
return {
|
|
134758
|
+
verdict: "reject",
|
|
134759
|
+
reasons: [`reviewer did not emit a "${REVIEW_VERDICT_MARKER} approve|reject" line`],
|
|
134760
|
+
severity: "blocking"
|
|
134761
|
+
};
|
|
134762
|
+
}
|
|
134763
|
+
if (decision === "approve") return { verdict: "approve", reasons: [] };
|
|
134764
|
+
const severity = SEVERITY_LINE_RE.test(text) ? text.match(SEVERITY_LINE_RE)[1].toLowerCase() : "blocking";
|
|
134765
|
+
return { verdict: "reject", reasons: extractRejectReasons(lines, verdictLineIndex), severity };
|
|
134766
|
+
}
|
|
134767
|
+
function extractRejectReasons(lines, verdictLineIndex) {
|
|
134768
|
+
const reasons = [];
|
|
134769
|
+
const inline = lines[verdictLineIndex].replace(VERDICT_LINE_RE, "").replace(/^[\s:–—-]+/, "").trim();
|
|
134770
|
+
if (inline) reasons.push(inline);
|
|
134771
|
+
for (let i = verdictLineIndex + 1; i < lines.length; i++) {
|
|
134772
|
+
if (SEVERITY_LINE_RE.test(lines[i])) continue;
|
|
134773
|
+
const cleaned = lines[i].replace(/^\s*(?:[-*•]|\d+[.)])\s+/, "").trim();
|
|
134774
|
+
if (cleaned) reasons.push(cleaned);
|
|
134775
|
+
}
|
|
134776
|
+
if (reasons.length === 0) reasons.push("reviewer rejected the merge without a stated reason");
|
|
134777
|
+
return reasons;
|
|
134778
|
+
}
|
|
134779
|
+
function buildMergeSystemPrompt2(agentPrompts) {
|
|
134780
|
+
const base = resolveAgentPrompt("merger", agentPrompts).trim();
|
|
134781
|
+
return [
|
|
134782
|
+
base,
|
|
134783
|
+
base ? "" : void 0,
|
|
134784
|
+
"## AI merge \u2014 clean room",
|
|
134785
|
+
"You are on a CLEAN, detached checkout at the integration branch's current",
|
|
134786
|
+
"tip. Land the task branch's work as a single commit.",
|
|
134787
|
+
"",
|
|
134788
|
+
"Constraints:",
|
|
134789
|
+
" - Resolve every conflict in favor of the task branch's intent; never drop",
|
|
134790
|
+
" the task's changes to make a conflict go away.",
|
|
134791
|
+
" - Do not make edits unrelated to reconciling the two branches.",
|
|
134792
|
+
" - Do NOT push, force-push, or run `git update-ref` / `git reset --hard`",
|
|
134793
|
+
" on any other branch. Only commit on this detached HEAD.",
|
|
134794
|
+
" - Finish with exactly ONE new commit on HEAD containing the task's work.",
|
|
134795
|
+
"",
|
|
134796
|
+
"Verify before committing:",
|
|
134797
|
+
" - After resolving the merge, run the project's checks \u2014 tests, type-check,",
|
|
134798
|
+
" and lint (discover them from the project config / package.json scripts,",
|
|
134799
|
+
" e.g. test / typecheck / lint / build).",
|
|
134800
|
+
" - FIX any NEW failure the merge or conflict resolution introduced (a check",
|
|
134801
|
+
" that passed on the task branch or the integration tip but fails on the",
|
|
134802
|
+
" merged tree). You do not need to fix failures that were already broken on",
|
|
134803
|
+
" the integration branch beforehand, but never commit a merge that adds new",
|
|
134804
|
+
" test, type-check, or lint failures.",
|
|
134805
|
+
"",
|
|
134806
|
+
"Commit message:",
|
|
134807
|
+
" - The subject line must CONCISELY SUMMARIZE the squashed changes in",
|
|
134808
|
+
' imperative mood (e.g. "add X", "fix Y") based on the actual diff \u2014 do',
|
|
134809
|
+
" not just restate the task title.",
|
|
134810
|
+
" - Include the task-id prefix and the trailer lines EXACTLY as given in the",
|
|
134811
|
+
" task instructions (they associate the commit with the board task)."
|
|
134812
|
+
].filter((l) => l !== void 0).join("\n");
|
|
134813
|
+
}
|
|
134814
|
+
function buildMergePrompt2(input) {
|
|
134815
|
+
const subjectShape = input.includeTaskId ? `"${input.taskId}: <concise imperative summary of the squashed changes>"` : `"<concise imperative summary of the squashed changes>"`;
|
|
134816
|
+
const trailerArgs = input.trailers.map((t) => ` -m ${JSON.stringify(t)}`).join("");
|
|
134817
|
+
const lines = [
|
|
134818
|
+
`Merge branch "${input.branch}" into "${input.integrationBranch}" (HEAD is detached at ${short(input.tipSha)}).`,
|
|
134819
|
+
"",
|
|
134820
|
+
"Steps:",
|
|
134821
|
+
` 1. Run: git merge --squash ${input.branch}`,
|
|
134822
|
+
" 2. If there are conflicts, resolve them (favor the task's intent), then `git add` the resolved files.",
|
|
134823
|
+
" 3. Commit the staged result as a SINGLE commit whose subject summarizes the",
|
|
134824
|
+
` actual changes${input.taskTitle ? ` (task title hint: ${JSON.stringify(input.taskTitle)})` : ""}, including the required trailers:`,
|
|
134825
|
+
` git commit -m ${subjectShape}${trailerArgs}`,
|
|
134826
|
+
" Keep the trailer line(s) verbatim \u2014 they link the commit to the board task.",
|
|
134827
|
+
" 4. Verify `git log --oneline ${tip}..HEAD` shows exactly one new commit and `git status` is clean.".replace("${tip}", short(input.tipSha)),
|
|
134828
|
+
"",
|
|
134829
|
+
"If `git merge --squash` reports the branch is already up to date (nothing to",
|
|
134830
|
+
"merge), do nothing and leave HEAD unchanged."
|
|
134831
|
+
];
|
|
134832
|
+
if (input.correctiveReasons && input.correctiveReasons.length > 0) {
|
|
134833
|
+
lines.push(
|
|
134834
|
+
"",
|
|
134835
|
+
"A prior attempt was REJECTED by review. Redo the merge from the clean tip",
|
|
134836
|
+
"and address each of these problems:",
|
|
134837
|
+
...input.correctiveReasons.map((r) => ` - ${r}`)
|
|
134838
|
+
);
|
|
134839
|
+
}
|
|
134840
|
+
return lines.join("\n");
|
|
134841
|
+
}
|
|
134842
|
+
function buildReviewSystemPrompt() {
|
|
134843
|
+
return [
|
|
134844
|
+
"You are an adversarial, read-only merge reviewer. Do NOT edit, stage, commit,",
|
|
134845
|
+
"or run any mutating git command. Audit the squash commit that is about to be",
|
|
134846
|
+
"merged into the integration branch and decide whether it is safe to land.",
|
|
134847
|
+
"",
|
|
134848
|
+
"Investigate with read-only commands (git show, git diff, git log, cat, grep).",
|
|
134849
|
+
"Judge on four axes:",
|
|
134850
|
+
" 1. Completeness \u2014 does the squash contain ALL of the task branch's intended",
|
|
134851
|
+
" changes? Flag any hunk silently dropped during conflict resolution.",
|
|
134852
|
+
" 2. No collateral \u2014 does it touch only files within the task's footprint?",
|
|
134853
|
+
" 3. Conflict soundness \u2014 were conflicts resolved coherently (both sides'",
|
|
134854
|
+
" intent preserved), not by blindly discarding one side?",
|
|
134855
|
+
" 4. Commit message \u2014 read `git show`'s message: the subject must concisely",
|
|
134856
|
+
" and ACCURATELY summarize the actual changes (not vague, not a mere",
|
|
134857
|
+
" restatement of the task title, not misleading). A poor/inaccurate",
|
|
134858
|
+
" message is an ADVISORY concern (it should be rewritten on retry, but",
|
|
134859
|
+
" must not block the merge).",
|
|
134860
|
+
"",
|
|
134861
|
+
"Bias toward rejection when uncertain.",
|
|
134862
|
+
"",
|
|
134863
|
+
`End with a single decision line: "${REVIEW_VERDICT_MARKER} approve" or`,
|
|
134864
|
+
`"${REVIEW_VERDICT_MARKER} reject". When rejecting, add a "SEVERITY:" line:`,
|
|
134865
|
+
" - SEVERITY: blocking \u2014 a correctness problem (dropped/lost task changes,",
|
|
134866
|
+
" incomplete squash, or a conflict resolution that discards intent). The",
|
|
134867
|
+
" merge must NOT land if this is unfixable.",
|
|
134868
|
+
" - SEVERITY: advisory \u2014 a quality/style concern that does not risk",
|
|
134869
|
+
" correctness; acceptable to land if unresolved.",
|
|
134870
|
+
"Then list each concrete reason as a bullet."
|
|
134871
|
+
].join("\n");
|
|
134872
|
+
}
|
|
134873
|
+
function buildReviewPrompt(input) {
|
|
134874
|
+
const lines = [
|
|
134875
|
+
`Review the squash merge for task ${input.taskId} (branch ${input.branch} \u2192 ${input.integrationBranch}).`,
|
|
134876
|
+
"",
|
|
134877
|
+
`Integration tip: ${short(input.tipSha)}`,
|
|
134878
|
+
`Squash commit: ${short(input.squashSha)}`,
|
|
134879
|
+
"",
|
|
134880
|
+
"Inspect with:",
|
|
134881
|
+
` git show ${input.squashSha}`,
|
|
134882
|
+
` git diff ${input.tipSha}..${input.squashSha}`,
|
|
134883
|
+
"",
|
|
134884
|
+
"Files changed (git diff --stat):",
|
|
134885
|
+
input.diffStat.trim() || "(none reported)"
|
|
134886
|
+
];
|
|
134887
|
+
if (input.priorReasons && input.priorReasons.length > 0) {
|
|
134888
|
+
lines.push(
|
|
134889
|
+
"",
|
|
134890
|
+
"A prior pass rejected an earlier attempt for these reasons \u2014 confirm they",
|
|
134891
|
+
"are now resolved:",
|
|
134892
|
+
...input.priorReasons.map((r) => ` - ${r}`)
|
|
134893
|
+
);
|
|
134894
|
+
}
|
|
134895
|
+
return lines.join("\n");
|
|
134896
|
+
}
|
|
134897
|
+
function buildStashResolveSystemPrompt() {
|
|
134898
|
+
return [
|
|
134899
|
+
"You are resolving a conflict between the user's restored local working-tree",
|
|
134900
|
+
"edits and the freshly-merged integration branch. The user's uncommitted work",
|
|
134901
|
+
"was stashed, the checkout fast-forwarded to the new tip, and re-applying the",
|
|
134902
|
+
"stash produced conflicts.",
|
|
134903
|
+
"",
|
|
134904
|
+
"Resolve every conflict marker so BOTH sides are preserved: keep the user's",
|
|
134905
|
+
"local intent AND the upstream changes that just landed. Stage each resolved",
|
|
134906
|
+
"file with `git add`.",
|
|
134907
|
+
"",
|
|
134908
|
+
"Do NOT commit, stash, reset, checkout a different branch, or run update-ref.",
|
|
134909
|
+
"Leave the resolved changes in the working tree as the user's uncommitted edits."
|
|
134910
|
+
].join("\n");
|
|
134911
|
+
}
|
|
134912
|
+
function buildStashResolvePrompt(conflictedFiles) {
|
|
134913
|
+
return [
|
|
134914
|
+
"Re-applying your stashed local changes onto the updated branch conflicted.",
|
|
134915
|
+
"",
|
|
134916
|
+
"Conflicted files:",
|
|
134917
|
+
...conflictedFiles.map((f2) => ` - ${f2}`),
|
|
134918
|
+
"",
|
|
134919
|
+
"Resolve each file's conflict markers (preserve both the local edits and the",
|
|
134920
|
+
"upstream changes), then `git add` it. Do not commit."
|
|
134921
|
+
].join("\n");
|
|
134922
|
+
}
|
|
134923
|
+
function short(sha) {
|
|
134924
|
+
return /^[0-9a-f]{7,40}$/i.test(sha) ? sha.slice(0, 8) : sha;
|
|
134925
|
+
}
|
|
134926
|
+
function makeMutatingAgent(store, settings, taskId, options, audit, systemPrompt) {
|
|
134927
|
+
return async (cwd, prompt) => {
|
|
134928
|
+
const model = resolveMergerSessionModel(settings);
|
|
134929
|
+
const { session } = await createResolvedAgentSession({
|
|
134930
|
+
sessionPurpose: "merger",
|
|
134931
|
+
pluginRunner: options.pluginRunner,
|
|
134932
|
+
cwd,
|
|
134933
|
+
systemPrompt,
|
|
134934
|
+
tools: "coding",
|
|
134935
|
+
onText: options.onAgentText ? (delta) => options.onAgentText?.(delta) : void 0,
|
|
134936
|
+
defaultProvider: model.provider,
|
|
134937
|
+
defaultModelId: model.modelId,
|
|
134938
|
+
fallbackProvider: settings.fallbackProvider,
|
|
134939
|
+
fallbackModelId: settings.fallbackModelId,
|
|
134940
|
+
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
134941
|
+
runAuditor: audit,
|
|
134942
|
+
settings,
|
|
134943
|
+
taskId
|
|
134944
|
+
});
|
|
134945
|
+
options.onSession?.(session);
|
|
134946
|
+
try {
|
|
134947
|
+
await withRateLimitRetry(async () => {
|
|
134948
|
+
await promptWithFallback(session, prompt);
|
|
134949
|
+
checkSessionError(session);
|
|
134950
|
+
}, { signal: options.signal });
|
|
134951
|
+
await accumulateSessionTokenUsage(store, taskId, session);
|
|
134952
|
+
} finally {
|
|
134953
|
+
session.dispose();
|
|
134954
|
+
}
|
|
134955
|
+
};
|
|
134956
|
+
}
|
|
134957
|
+
function makeReviewAgent(store, settings, taskId, options, audit) {
|
|
134958
|
+
return async (cwd, prompt) => {
|
|
134959
|
+
const validator = resolveValidatorSettingsModel(settings);
|
|
134960
|
+
const model = validator.provider && validator.modelId ? validator : resolveMergerSessionModel(settings);
|
|
134961
|
+
let captured = "";
|
|
134962
|
+
const { session } = await createResolvedAgentSession({
|
|
134963
|
+
sessionPurpose: "merger",
|
|
134964
|
+
pluginRunner: options.pluginRunner,
|
|
134965
|
+
cwd,
|
|
134966
|
+
systemPrompt: buildReviewSystemPrompt(),
|
|
134967
|
+
tools: "coding",
|
|
134968
|
+
onText: (delta) => {
|
|
134969
|
+
captured += delta;
|
|
134970
|
+
options.onAgentText?.(delta);
|
|
134971
|
+
},
|
|
134972
|
+
defaultProvider: model.provider,
|
|
134973
|
+
defaultModelId: model.modelId,
|
|
134974
|
+
fallbackProvider: settings.fallbackProvider,
|
|
134975
|
+
fallbackModelId: settings.fallbackModelId,
|
|
134976
|
+
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
134977
|
+
runAuditor: audit,
|
|
134978
|
+
settings,
|
|
134979
|
+
taskId
|
|
134980
|
+
});
|
|
134981
|
+
options.onSession?.(session);
|
|
134982
|
+
try {
|
|
134983
|
+
await withRateLimitRetry(async () => {
|
|
134984
|
+
await promptWithFallback(session, prompt);
|
|
134985
|
+
checkSessionError(session);
|
|
134986
|
+
}, { signal: options.signal });
|
|
134987
|
+
await accumulateSessionTokenUsage(store, taskId, session);
|
|
134988
|
+
} finally {
|
|
134989
|
+
session.dispose();
|
|
134990
|
+
}
|
|
134991
|
+
return captured;
|
|
134992
|
+
};
|
|
134993
|
+
}
|
|
134994
|
+
async function hasUnresolvedConflicts(cwd) {
|
|
134995
|
+
return (await git2(["ls-files", "-u"], cwd)).length > 0;
|
|
134996
|
+
}
|
|
134997
|
+
async function landSquash(input) {
|
|
134998
|
+
const { projectRootDir, mergeRoot, integrationBranch, tipSha, squashSha, taskId, audit, resolveConflicts: resolveConflicts2 } = input;
|
|
134999
|
+
const emit2 = (outcome, extra = {}) => audit.git({ type: "merge:ai-local-sync", target: integrationBranch, metadata: { taskId, outcome, squashSha, ...extra } }).catch(() => void 0);
|
|
135000
|
+
const currentBranch2 = await git2(["rev-parse", "--abbrev-ref", "HEAD"], projectRootDir).catch(() => "");
|
|
135001
|
+
if (currentBranch2 !== integrationBranch) {
|
|
135002
|
+
const adv = await advanceIntegrationBranchRef({
|
|
135003
|
+
rootDir: mergeRoot,
|
|
135004
|
+
projectRootDir,
|
|
135005
|
+
integrationBranch,
|
|
135006
|
+
newSha: squashSha,
|
|
135007
|
+
expectedCurrentSha: tipSha,
|
|
135008
|
+
taskId,
|
|
135009
|
+
audit
|
|
135010
|
+
});
|
|
135011
|
+
if (!adv.advanced) {
|
|
135012
|
+
if (adv.reason === "concurrent-advance" || adv.reason === "non-fast-forward-advance") {
|
|
135013
|
+
return { outcome: "concurrent", localSync: "skipped-other-branch" };
|
|
135014
|
+
}
|
|
135015
|
+
throw new Error(`AI merge could not advance ${integrationBranch} for ${taskId}: ${adv.reason} (${adv.diagnostic})`);
|
|
135016
|
+
}
|
|
135017
|
+
await emit2("skipped-other-branch", { currentBranch: currentBranch2 });
|
|
135018
|
+
return { outcome: "advanced", localSync: "skipped-other-branch" };
|
|
135019
|
+
}
|
|
135020
|
+
const head = await git2(["rev-parse", "HEAD"], projectRootDir).catch(() => "");
|
|
135021
|
+
if (head !== tipSha) {
|
|
135022
|
+
return { outcome: "concurrent", localSync: "skipped-other-branch" };
|
|
135023
|
+
}
|
|
135024
|
+
const dirty = (await git2(["status", "--porcelain"], projectRootDir)).length > 0;
|
|
135025
|
+
const stashed = dirty ? await gitOk(["stash", "push", "--include-untracked", "-m", `fusion-ai-merge-sync-${taskId}`], projectRootDir) : false;
|
|
135026
|
+
if (dirty && !stashed) {
|
|
135027
|
+
const adv = await advanceIntegrationBranchRef({
|
|
135028
|
+
rootDir: mergeRoot,
|
|
135029
|
+
projectRootDir,
|
|
135030
|
+
integrationBranch,
|
|
135031
|
+
newSha: squashSha,
|
|
135032
|
+
expectedCurrentSha: tipSha,
|
|
135033
|
+
taskId,
|
|
135034
|
+
audit
|
|
135035
|
+
});
|
|
135036
|
+
if (!adv.advanced) {
|
|
135037
|
+
if (adv.reason === "concurrent-advance" || adv.reason === "non-fast-forward-advance") {
|
|
135038
|
+
return { outcome: "concurrent", localSync: "skipped-dirty-unstashable" };
|
|
135039
|
+
}
|
|
135040
|
+
throw new Error(`AI merge could not advance ${integrationBranch} for ${taskId}: ${adv.reason} (${adv.diagnostic})`);
|
|
135041
|
+
}
|
|
135042
|
+
aiMergeLog.warn(`${taskId}: local checkout has un-stashable dirty state \u2014 advanced ${integrationBranch} without syncing your working tree; pull manually.`);
|
|
135043
|
+
await emit2("skipped-dirty-unstashable");
|
|
135044
|
+
return { outcome: "advanced", localSync: "skipped-dirty-unstashable" };
|
|
135045
|
+
}
|
|
135046
|
+
if (!await gitOk(["merge", "--ff-only", squashSha], projectRootDir)) {
|
|
135047
|
+
if (stashed) await gitOk(["stash", "pop"], projectRootDir);
|
|
135048
|
+
return { outcome: "concurrent", localSync: "skipped-other-branch" };
|
|
135049
|
+
}
|
|
135050
|
+
if (!stashed) {
|
|
135051
|
+
await emit2("ff");
|
|
135052
|
+
return { outcome: "advanced", localSync: "ff" };
|
|
135053
|
+
}
|
|
135054
|
+
if (await gitOk(["stash", "pop"], projectRootDir)) {
|
|
135055
|
+
await emit2("stash-ff-restore");
|
|
135056
|
+
return { outcome: "advanced", localSync: "stash-ff-restore" };
|
|
135057
|
+
}
|
|
135058
|
+
if (resolveConflicts2) {
|
|
135059
|
+
const conflicted = (await git2(["diff", "--name-only", "--diff-filter=U"], projectRootDir)).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
135060
|
+
try {
|
|
135061
|
+
await resolveConflicts2(projectRootDir, buildStashResolvePrompt(conflicted));
|
|
135062
|
+
} catch (err) {
|
|
135063
|
+
aiMergeLog.warn(`${taskId}: AI stash-conflict resolver threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
135064
|
+
}
|
|
135065
|
+
if (!await hasUnresolvedConflicts(projectRootDir)) {
|
|
135066
|
+
await gitOk(["reset"], projectRootDir);
|
|
135067
|
+
aiMergeLog.log(`${taskId}: reconciled your local edits with the new tip; original pre-merge edits also kept in a stash as a backup (\`git stash list\`).`);
|
|
135068
|
+
await emit2("stash-ff-airesolved", { conflicted, stashRetained: true });
|
|
135069
|
+
return { outcome: "advanced", localSync: "stash-ff-airesolved" };
|
|
135070
|
+
}
|
|
135071
|
+
}
|
|
135072
|
+
aiMergeLog.warn(`${taskId}: restoring your local changes onto the new tip conflicted and could not be auto-resolved. Your work is preserved in the stash (\`git stash list\`); re-apply with \`git stash pop\` and resolve manually.`);
|
|
135073
|
+
await emit2("stash-ff-conflict");
|
|
135074
|
+
return { outcome: "advanced", localSync: "stash-ff-conflict" };
|
|
135075
|
+
}
|
|
135076
|
+
function noOpResult(task, branch, reason) {
|
|
135077
|
+
return {
|
|
135078
|
+
task,
|
|
135079
|
+
branch,
|
|
135080
|
+
merged: false,
|
|
135081
|
+
noOp: true,
|
|
135082
|
+
ok: true,
|
|
135083
|
+
reason,
|
|
135084
|
+
worktreeRemoved: false,
|
|
135085
|
+
branchDeleted: false
|
|
135086
|
+
};
|
|
135087
|
+
}
|
|
135088
|
+
async function runAiMerge(store, projectRootDir, taskId, options = {}, deps = {}) {
|
|
135089
|
+
const task = await store.getTask(taskId);
|
|
135090
|
+
const branch = task.branch || canonicalFusionBranchName(taskId);
|
|
135091
|
+
if (task.column === "done" || task.column === "archived") {
|
|
135092
|
+
return noOpResult(task, branch, "already-finalized");
|
|
135093
|
+
}
|
|
135094
|
+
const blocker = getTaskMergeBlocker(task, { manual: options.manual === true });
|
|
135095
|
+
if (blocker) throw new Error(`Cannot merge ${taskId}: ${blocker}`);
|
|
135096
|
+
const settings = await store.getSettings();
|
|
135097
|
+
const projectDefaultBranch = await resolveIntegrationBranch(projectRootDir, settings);
|
|
135098
|
+
const mergeTarget = resolveTaskMergeTarget(task, { projectDefaultBranch });
|
|
135099
|
+
const integrationBranch = mergeTarget.branch;
|
|
135100
|
+
const audit = createRunAuditor(store, {
|
|
135101
|
+
runId: generateSyntheticRunId("ai-merge", taskId),
|
|
135102
|
+
agentId: "merger",
|
|
135103
|
+
taskId,
|
|
135104
|
+
phase: "merge"
|
|
135105
|
+
});
|
|
135106
|
+
const log25 = async (message) => {
|
|
135107
|
+
await store.logEntry(taskId, message, "AiMerge").catch(() => void 0);
|
|
135108
|
+
await store.appendAgentLog(taskId, message, "text", void 0, "merger").catch(() => void 0);
|
|
135109
|
+
};
|
|
135110
|
+
const setStatus = (status) => store.updateTask(taskId, { status }).catch(() => void 0);
|
|
135111
|
+
if (!await gitOk(["rev-parse", "--verify", `refs/heads/${branch}`], projectRootDir)) {
|
|
135112
|
+
const wasExecuted = !!task.baseCommitSha;
|
|
135113
|
+
const alreadyMerged = task.mergeDetails?.mergeConfirmed === true || !!task.mergeDetails?.commitSha;
|
|
135114
|
+
if (wasExecuted && !alreadyMerged) {
|
|
135115
|
+
await audit.git({
|
|
135116
|
+
type: "merge:ai-no-branch",
|
|
135117
|
+
target: branch,
|
|
135118
|
+
metadata: { taskId, kind: "executed-branch-missing", baseCommitSha: task.baseCommitSha }
|
|
135119
|
+
});
|
|
135120
|
+
throw new Error(
|
|
135121
|
+
`AI merge for ${taskId}: branch "${branch}" is missing, but the task was executed (baseCommitSha ${String(task.baseCommitSha).slice(0, 8)}) and has no recorded merge \u2014 its work appears lost. Not finalizing; investigate.`
|
|
135122
|
+
);
|
|
135123
|
+
}
|
|
135124
|
+
await audit.git({
|
|
135125
|
+
type: "merge:ai-no-branch",
|
|
135126
|
+
target: branch,
|
|
135127
|
+
metadata: { taskId, kind: alreadyMerged ? "already-merged" : "never-executed" }
|
|
135128
|
+
});
|
|
135129
|
+
return await finalizeTask(store, taskId, noOpResult(task, branch, alreadyMerged ? "already-merged" : "no-branch"));
|
|
135130
|
+
}
|
|
135131
|
+
if (!await gitOk(["rev-parse", "--verify", `refs/heads/${integrationBranch}`], projectRootDir)) {
|
|
135132
|
+
await audit.git({ type: "merge:ai-no-branch", target: integrationBranch, metadata: { taskId, kind: "integration-branch-missing" } });
|
|
135133
|
+
throw new Error(`AI merge for ${taskId}: target branch "${integrationBranch}" has no local ref (refs/heads/${integrationBranch}). Create or check out the branch locally before merging.`);
|
|
135134
|
+
}
|
|
135135
|
+
const maxPasses = Math.max(0, Math.trunc(settings.merger?.maxReviewPasses ?? 3));
|
|
135136
|
+
const mergeAgent = deps.mergeAgent ?? makeMutatingAgent(store, settings, taskId, options, audit, buildMergeSystemPrompt2(settings.agentPrompts));
|
|
135137
|
+
const reviewAgent = deps.reviewAgent ?? makeReviewAgent(store, settings, taskId, options, audit);
|
|
135138
|
+
const stashResolveAgent = deps.stashResolveAgent ?? makeMutatingAgent(store, settings, taskId, options, audit, buildStashResolveSystemPrompt());
|
|
135139
|
+
const includeTaskId = settings.includeTaskIdInCommit !== false;
|
|
135140
|
+
const trailers = taskTrailers(taskId, task.lineageId);
|
|
135141
|
+
const taskTitle = task.title?.trim() ? task.title.split("\n")[0] : void 0;
|
|
135142
|
+
await setStatus("merging");
|
|
135143
|
+
let advanceRetries = 0;
|
|
135144
|
+
while (true) {
|
|
135145
|
+
throwIfAborted2(options.signal, taskId);
|
|
135146
|
+
const tipSha = await git2(["rev-parse", "--verify", `refs/heads/${integrationBranch}`], projectRootDir);
|
|
135147
|
+
const mergeRoot = await mkdtemp2(join50(tmpdir5(), `fusion-ai-merge-${taskId.toLowerCase()}-`));
|
|
135148
|
+
let worktreeAdded = false;
|
|
135149
|
+
try {
|
|
135150
|
+
await git2(["worktree", "add", "--detach", mergeRoot, tipSha], projectRootDir);
|
|
135151
|
+
worktreeAdded = true;
|
|
135152
|
+
await audit.git({ type: "merge:ai-clean-room", target: integrationBranch, metadata: { taskId, tipSha, mergeRoot } });
|
|
135153
|
+
await log25(`AI merge: merging ${branch} into ${integrationBranch} (clean room at ${short(tipSha)})${advanceRetries ? ` \u2014 retry ${advanceRetries} after concurrent advance` : ""}`);
|
|
135154
|
+
const squashSha = await mergeAndReview({
|
|
135155
|
+
mergeRoot,
|
|
135156
|
+
branch,
|
|
135157
|
+
integrationBranch,
|
|
135158
|
+
tipSha,
|
|
135159
|
+
taskTitle,
|
|
135160
|
+
includeTaskId,
|
|
135161
|
+
trailers,
|
|
135162
|
+
taskId,
|
|
135163
|
+
maxPasses,
|
|
135164
|
+
mergeAgent,
|
|
135165
|
+
reviewAgent,
|
|
135166
|
+
audit,
|
|
135167
|
+
log: log25,
|
|
135168
|
+
setStatus,
|
|
135169
|
+
signal: options.signal
|
|
135170
|
+
});
|
|
135171
|
+
if (!squashSha) {
|
|
135172
|
+
await audit.git({ type: "merge:ai-empty", target: integrationBranch, metadata: { taskId, tipSha } });
|
|
135173
|
+
await log25(`AI merge: ${branch} had no net changes vs ${integrationBranch} \u2014 finalizing as no-op`);
|
|
135174
|
+
return await finalizeMerged(store, projectRootDir, taskId, task, branch, integrationBranch, tipSha, audit, log25, { empty: true });
|
|
135175
|
+
}
|
|
135176
|
+
await setStatus("landing");
|
|
135177
|
+
const landed = await landSquash({
|
|
135178
|
+
projectRootDir,
|
|
135179
|
+
mergeRoot,
|
|
135180
|
+
integrationBranch,
|
|
135181
|
+
tipSha,
|
|
135182
|
+
squashSha,
|
|
135183
|
+
taskId,
|
|
135184
|
+
audit,
|
|
135185
|
+
resolveConflicts: stashResolveAgent
|
|
135186
|
+
});
|
|
135187
|
+
if (landed.outcome === "concurrent") {
|
|
135188
|
+
if (advanceRetries < MAX_CONCURRENT_ADVANCE_RETRIES) {
|
|
135189
|
+
advanceRetries++;
|
|
135190
|
+
await log25(`AI merge: ${integrationBranch} moved during merge \u2014 rebuilding on new tip (retry ${advanceRetries})`);
|
|
135191
|
+
continue;
|
|
135192
|
+
}
|
|
135193
|
+
throw new Error(`AI merge could not advance ${integrationBranch} for ${taskId} after ${advanceRetries} retries (concurrent advances)`);
|
|
135194
|
+
}
|
|
135195
|
+
await log25(`AI merge: advanced ${integrationBranch} \u2192 ${short(squashSha)} (local checkout: ${landed.localSync})`);
|
|
135196
|
+
return await finalizeMerged(store, projectRootDir, taskId, task, branch, integrationBranch, squashSha, audit, log25, { empty: false });
|
|
135197
|
+
} finally {
|
|
135198
|
+
if (worktreeAdded) {
|
|
135199
|
+
await gitOk(["worktree", "remove", "--force", mergeRoot], projectRootDir);
|
|
135200
|
+
}
|
|
135201
|
+
await rm6(mergeRoot, { recursive: true, force: true }).catch(() => void 0);
|
|
135202
|
+
}
|
|
135203
|
+
}
|
|
135204
|
+
}
|
|
135205
|
+
async function mergeAndReview(input) {
|
|
135206
|
+
const { mergeRoot, branch, integrationBranch, tipSha, taskTitle, includeTaskId, trailers, taskId, maxPasses, mergeAgent, reviewAgent, audit, log: log25, setStatus, signal } = input;
|
|
135207
|
+
let priorReasons = [];
|
|
135208
|
+
for (let attempt = 0; ; attempt++) {
|
|
135209
|
+
throwIfAborted2(signal, taskId);
|
|
135210
|
+
await git2(["reset", "--hard", tipSha], mergeRoot);
|
|
135211
|
+
await git2(["clean", "-fd"], mergeRoot);
|
|
135212
|
+
if (attempt > 0) {
|
|
135213
|
+
await setStatus("merging");
|
|
135214
|
+
await log25(`AI merge: corrective re-merge (pass ${attempt}/${maxPasses}) addressing: ${priorReasons.join("; ")}`);
|
|
135215
|
+
}
|
|
135216
|
+
await mergeAgent(mergeRoot, buildMergePrompt2({
|
|
135217
|
+
taskId,
|
|
135218
|
+
branch,
|
|
135219
|
+
integrationBranch,
|
|
135220
|
+
tipSha,
|
|
135221
|
+
taskTitle,
|
|
135222
|
+
includeTaskId,
|
|
135223
|
+
trailers,
|
|
135224
|
+
correctiveReasons: priorReasons.length ? priorReasons : void 0
|
|
135225
|
+
}));
|
|
135226
|
+
let head = await git2(["rev-parse", "HEAD"], mergeRoot);
|
|
135227
|
+
if (head === tipSha) return null;
|
|
135228
|
+
await ensureCommitTaskMetadata(mergeRoot, taskId, includeTaskId, trailers);
|
|
135229
|
+
head = await git2(["rev-parse", "HEAD"], mergeRoot);
|
|
135230
|
+
await setStatus("reviewing");
|
|
135231
|
+
const diffStat = await git2(["diff", "--stat", `${tipSha}..${head}`], mergeRoot);
|
|
135232
|
+
const verdict = parseReviewVerdict(await reviewAgent(mergeRoot, buildReviewPrompt({
|
|
135233
|
+
taskId,
|
|
135234
|
+
branch,
|
|
135235
|
+
integrationBranch,
|
|
135236
|
+
tipSha,
|
|
135237
|
+
squashSha: head,
|
|
135238
|
+
diffStat,
|
|
135239
|
+
priorReasons
|
|
135240
|
+
})));
|
|
135241
|
+
await audit.git({
|
|
135242
|
+
type: "merge:ai-review-verdict",
|
|
135243
|
+
target: integrationBranch,
|
|
135244
|
+
metadata: { taskId, attempt, verdict: verdict.verdict, severity: verdict.severity, reasons: verdict.reasons, squashSha: head }
|
|
135245
|
+
});
|
|
135246
|
+
if (verdict.verdict === "approve") {
|
|
135247
|
+
await log25(`AI merge review (pass ${attempt + 1}): approved`);
|
|
135248
|
+
return head;
|
|
135249
|
+
}
|
|
135250
|
+
const budgetExhausted = attempt >= maxPasses;
|
|
135251
|
+
if (budgetExhausted) {
|
|
135252
|
+
if (verdict.severity === "blocking") {
|
|
135253
|
+
await audit.git({ type: "merge:ai-review-blocked", target: integrationBranch, metadata: { taskId, attempt, reasons: verdict.reasons } });
|
|
135254
|
+
await log25(`AI merge BLOCKED after ${attempt} corrective pass(es) \u2014 unresolved correctness concern: ${verdict.reasons.join("; ")}`);
|
|
135255
|
+
throw new AiMergeBlockedError(taskId, verdict.reasons);
|
|
135256
|
+
}
|
|
135257
|
+
await audit.git({ type: "merge:ai-review-landed-with-concerns", target: integrationBranch, metadata: { taskId, attempt, reasons: verdict.reasons, squashSha: head } });
|
|
135258
|
+
await log25(`AI merge: landing with unresolved advisory concern(s): ${verdict.reasons.join("; ")}`);
|
|
135259
|
+
return head;
|
|
135260
|
+
}
|
|
135261
|
+
priorReasons = verdict.reasons;
|
|
135262
|
+
await log25(`AI merge review (pass ${attempt + 1}): rejected (${verdict.severity}) \u2014 ${verdict.reasons.join("; ")}`);
|
|
135263
|
+
}
|
|
135264
|
+
}
|
|
135265
|
+
async function finalizeMerged(store, projectRootDir, taskId, task, branch, integrationBranch, landedSha, audit, log25, opts) {
|
|
135266
|
+
let branchDeleted = false;
|
|
135267
|
+
if (branch !== integrationBranch && await gitOk(["branch", "-D", branch], projectRootDir)) {
|
|
135268
|
+
branchDeleted = true;
|
|
135269
|
+
await audit.git({ type: "branch:delete", target: branch, metadata: { taskId, force: true } }).catch(() => void 0);
|
|
135270
|
+
}
|
|
135271
|
+
let worktreeRemoved = false;
|
|
135272
|
+
if (task.worktree) {
|
|
135273
|
+
worktreeRemoved = await gitOk(["worktree", "remove", "--force", task.worktree], projectRootDir);
|
|
135274
|
+
await store.updateTask(taskId, { worktree: null }).catch(() => void 0);
|
|
135275
|
+
}
|
|
135276
|
+
const result = {
|
|
135277
|
+
task,
|
|
135278
|
+
branch,
|
|
135279
|
+
merged: !opts.empty,
|
|
135280
|
+
noOp: opts.empty,
|
|
135281
|
+
ok: true,
|
|
135282
|
+
reason: opts.empty ? "no-net-changes" : void 0,
|
|
135283
|
+
commitSha: opts.empty ? void 0 : landedSha,
|
|
135284
|
+
mergeConfirmed: !opts.empty,
|
|
135285
|
+
worktreeRemoved,
|
|
135286
|
+
branchDeleted
|
|
135287
|
+
};
|
|
135288
|
+
await audit.git({ type: "merge:ai-landed", target: integrationBranch, metadata: { taskId, landedSha, empty: opts.empty } }).catch(() => void 0);
|
|
135289
|
+
await log25(opts.empty ? `AI merge: finalized ${taskId} (no-op) \u2192 done` : `AI merge: landed ${short(landedSha)}, task \u2192 done`);
|
|
135290
|
+
return await finalizeTask(store, taskId, result);
|
|
135291
|
+
}
|
|
135292
|
+
async function finalizeTask(store, taskId, result) {
|
|
135293
|
+
await store.updateTask(taskId, { status: null }).catch(() => void 0);
|
|
135294
|
+
const task = await store.moveTask(taskId, "done");
|
|
135295
|
+
result.task = task;
|
|
135296
|
+
store.emit("task:merged", result);
|
|
135297
|
+
return result;
|
|
135298
|
+
}
|
|
135299
|
+
function throwIfAborted2(signal, taskId) {
|
|
135300
|
+
if (signal?.aborted) {
|
|
135301
|
+
const err = new Error(`AI merge aborted for ${taskId}`);
|
|
135302
|
+
err.name = "MergeAbortedError";
|
|
135303
|
+
throw err;
|
|
135304
|
+
}
|
|
135305
|
+
}
|
|
135306
|
+
var execFileAsync10, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, FUSION_TASK_ID_TRAILER_KEY3, REVIEW_VERDICT_MARKER, VERDICT_LINE_RE, SEVERITY_LINE_RE, AiMergeBlockedError;
|
|
135307
|
+
var init_merger_ai = __esm({
|
|
135308
|
+
"../engine/src/merger-ai.ts"() {
|
|
135309
|
+
"use strict";
|
|
135310
|
+
init_src();
|
|
135311
|
+
init_worktree_names();
|
|
135312
|
+
init_integration_branch();
|
|
135313
|
+
init_merger_ref_update_advance();
|
|
135314
|
+
init_agent_session_helpers();
|
|
135315
|
+
init_pi();
|
|
135316
|
+
init_rate_limit_retry();
|
|
135317
|
+
init_usage_limit_detector();
|
|
135318
|
+
init_session_token_usage();
|
|
135319
|
+
init_run_audit();
|
|
135320
|
+
init_logger2();
|
|
135321
|
+
execFileAsync10 = promisify43(execFile15);
|
|
135322
|
+
aiMergeLog = createLogger2("merger-ai");
|
|
135323
|
+
MAX_CONCURRENT_ADVANCE_RETRIES = 3;
|
|
135324
|
+
FUSION_TASK_ID_TRAILER_KEY3 = "Fusion-Task-Id";
|
|
135325
|
+
REVIEW_VERDICT_MARKER = "REVIEW_VERDICT:";
|
|
135326
|
+
VERDICT_LINE_RE = /REVIEW_VERDICT:\s*(approve|reject)\b/i;
|
|
135327
|
+
SEVERITY_LINE_RE = /SEVERITY:\s*(blocking|advisory)\b/i;
|
|
135328
|
+
AiMergeBlockedError = class extends Error {
|
|
135329
|
+
taskId;
|
|
135330
|
+
reasons;
|
|
135331
|
+
constructor(taskId, reasons) {
|
|
135332
|
+
super(`AI merge blocked ${taskId} (unresolved correctness concern): ${reasons.join("; ") || "no reason given"}`);
|
|
135333
|
+
this.name = "AiMergeBlockedError";
|
|
135334
|
+
this.taskId = taskId;
|
|
135335
|
+
this.reasons = reasons;
|
|
135336
|
+
}
|
|
135337
|
+
};
|
|
135338
|
+
}
|
|
135339
|
+
});
|
|
135340
|
+
|
|
134176
135341
|
// ../engine/src/research-dispatcher.ts
|
|
134177
135342
|
var log24, ResearchRunDispatcher;
|
|
134178
135343
|
var init_research_dispatcher = __esm({
|
|
@@ -134415,9 +135580,9 @@ var init_provider_adapters = __esm({
|
|
|
134415
135580
|
});
|
|
134416
135581
|
|
|
134417
135582
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
134418
|
-
import { EventEmitter as
|
|
134419
|
-
import { exec as exec36, execFile as
|
|
134420
|
-
import { promisify as
|
|
135583
|
+
import { EventEmitter as EventEmitter25 } from "node:events";
|
|
135584
|
+
import { exec as exec36, execFile as execFile16, spawn as spawn8 } from "node:child_process";
|
|
135585
|
+
import { promisify as promisify44 } from "node:util";
|
|
134421
135586
|
function nowIso2() {
|
|
134422
135587
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
134423
135588
|
}
|
|
@@ -134457,7 +135622,7 @@ function toStateError(code, err) {
|
|
|
134457
135622
|
at: nowIso2()
|
|
134458
135623
|
};
|
|
134459
135624
|
}
|
|
134460
|
-
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2,
|
|
135625
|
+
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, execFileAsync11, execAsync32, LineBuffer, TunnelProcessManager;
|
|
134461
135626
|
var init_tunnel_process_manager = __esm({
|
|
134462
135627
|
"../engine/src/remote-access/tunnel-process-manager.ts"() {
|
|
134463
135628
|
"use strict";
|
|
@@ -134466,8 +135631,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
134466
135631
|
init_provider_adapters();
|
|
134467
135632
|
DEFAULT_MAX_LOG_ENTRIES = 400;
|
|
134468
135633
|
DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
|
|
134469
|
-
|
|
134470
|
-
execAsync32 =
|
|
135634
|
+
execFileAsync11 = promisify44(execFile16);
|
|
135635
|
+
execAsync32 = promisify44(exec36);
|
|
134471
135636
|
LineBuffer = class {
|
|
134472
135637
|
pending = "";
|
|
134473
135638
|
push(chunk) {
|
|
@@ -134482,7 +135647,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134482
135647
|
return tail ? [tail] : [];
|
|
134483
135648
|
}
|
|
134484
135649
|
};
|
|
134485
|
-
TunnelProcessManager = class extends
|
|
135650
|
+
TunnelProcessManager = class extends EventEmitter25 {
|
|
134486
135651
|
maxLogEntries;
|
|
134487
135652
|
defaultStopTimeoutMs;
|
|
134488
135653
|
spawnImpl;
|
|
@@ -134544,7 +135709,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134544
135709
|
return null;
|
|
134545
135710
|
}
|
|
134546
135711
|
try {
|
|
134547
|
-
const { stdout } = await
|
|
135712
|
+
const { stdout } = await execFileAsync11("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
134548
135713
|
const data = JSON.parse(String(stdout));
|
|
134549
135714
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
134550
135715
|
if (!dnsName) {
|
|
@@ -134567,7 +135732,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134567
135732
|
];
|
|
134568
135733
|
for (const resetCommand of resetCommands) {
|
|
134569
135734
|
try {
|
|
134570
|
-
await
|
|
135735
|
+
await execFileAsync11(resetCommand.command, resetCommand.args, { timeout: 5e3 });
|
|
134571
135736
|
return;
|
|
134572
135737
|
} catch {
|
|
134573
135738
|
}
|
|
@@ -134868,8 +136033,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
134868
136033
|
});
|
|
134869
136034
|
|
|
134870
136035
|
// ../engine/src/project-engine.ts
|
|
134871
|
-
import { execFile as
|
|
134872
|
-
import { promisify as
|
|
136036
|
+
import { execFile as execFile17 } from "node:child_process";
|
|
136037
|
+
import { promisify as promisify45 } from "node:util";
|
|
134873
136038
|
function formatErrorDetails(error) {
|
|
134874
136039
|
if (error instanceof Error) {
|
|
134875
136040
|
return {
|
|
@@ -134884,6 +136049,40 @@ function isInvalidDoneTransitionError(error) {
|
|
|
134884
136049
|
const message = error instanceof Error ? error.message : String(error);
|
|
134885
136050
|
return message.includes("Invalid transition:") && message.includes("\u2192 'done'");
|
|
134886
136051
|
}
|
|
136052
|
+
async function verifyMergeConfirmedReachability(args) {
|
|
136053
|
+
const { commitSha, integrationBranch, cwd } = args;
|
|
136054
|
+
if (!commitSha || !commitSha.trim()) {
|
|
136055
|
+
return { reachable: true, skipped: "no-commit-sha" };
|
|
136056
|
+
}
|
|
136057
|
+
if (!integrationBranch || !integrationBranch.trim()) {
|
|
136058
|
+
return { reachable: true, skipped: "no-integration-branch" };
|
|
136059
|
+
}
|
|
136060
|
+
try {
|
|
136061
|
+
await execFileAsync12("git", ["cat-file", "-e", `${commitSha}^{commit}`], {
|
|
136062
|
+
cwd,
|
|
136063
|
+
timeout: 1e4
|
|
136064
|
+
});
|
|
136065
|
+
} catch (error) {
|
|
136066
|
+
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
136067
|
+
return { reachable: false, reason: "commit-missing", diagnostic };
|
|
136068
|
+
}
|
|
136069
|
+
try {
|
|
136070
|
+
await execFileAsync12(
|
|
136071
|
+
"git",
|
|
136072
|
+
["merge-base", "--is-ancestor", commitSha, `refs/heads/${integrationBranch}`],
|
|
136073
|
+
{ cwd, timeout: 1e4 }
|
|
136074
|
+
);
|
|
136075
|
+
return { reachable: true };
|
|
136076
|
+
} catch (error) {
|
|
136077
|
+
const err = error;
|
|
136078
|
+
const code = typeof err.code === "number" ? err.code : void 0;
|
|
136079
|
+
const diagnostic = err.message ?? String(error);
|
|
136080
|
+
if (code === 1) {
|
|
136081
|
+
return { reachable: false, reason: "not-ancestor", diagnostic };
|
|
136082
|
+
}
|
|
136083
|
+
return { reachable: false, reason: "git-error", diagnostic };
|
|
136084
|
+
}
|
|
136085
|
+
}
|
|
134887
136086
|
function buildVerificationFailureSignature(error) {
|
|
134888
136087
|
const commandResult = error.verificationResult.testResult ?? error.verificationResult.buildResult;
|
|
134889
136088
|
const lane = commandResult?.command?.trim() || error.verificationResult.failedCommand?.trim() || "verification-failure";
|
|
@@ -134894,7 +136093,7 @@ function buildVerificationFailureSignature(error) {
|
|
|
134894
136093
|
failedCommand: commandResult?.command ?? error.verificationResult.failedCommand ?? null
|
|
134895
136094
|
}).signature;
|
|
134896
136095
|
}
|
|
134897
|
-
var
|
|
136096
|
+
var execFileAsync12, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
134898
136097
|
var init_project_engine = __esm({
|
|
134899
136098
|
"../engine/src/project-engine.ts"() {
|
|
134900
136099
|
"use strict";
|
|
@@ -134908,6 +136107,7 @@ var init_project_engine = __esm({
|
|
|
134908
136107
|
init_auth_storage();
|
|
134909
136108
|
init_cron_runner();
|
|
134910
136109
|
init_merger();
|
|
136110
|
+
init_merger_ai();
|
|
134911
136111
|
init_concurrency();
|
|
134912
136112
|
init_logger2();
|
|
134913
136113
|
init_research_orchestrator();
|
|
@@ -134916,7 +136116,7 @@ var init_project_engine = __esm({
|
|
|
134916
136116
|
init_run_audit();
|
|
134917
136117
|
init_verification_followup_dedup();
|
|
134918
136118
|
init_tunnel_process_manager();
|
|
134919
|
-
|
|
136119
|
+
execFileAsync12 = promisify45(execFile17);
|
|
134920
136120
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
134921
136121
|
isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
|
134922
136122
|
ProjectEngine = class _ProjectEngine {
|
|
@@ -135661,7 +136861,7 @@ ${detail}`
|
|
|
135661
136861
|
async checkExecutableAvailable(command) {
|
|
135662
136862
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
135663
136863
|
try {
|
|
135664
|
-
await
|
|
136864
|
+
await execFileAsync12(checker, [command]);
|
|
135665
136865
|
return { available: true };
|
|
135666
136866
|
} catch {
|
|
135667
136867
|
return {
|
|
@@ -135861,6 +137061,112 @@ ${detail}`
|
|
|
135861
137061
|
continue;
|
|
135862
137062
|
}
|
|
135863
137063
|
if (task.mergeDetails?.mergeConfirmed) {
|
|
137064
|
+
const integrationBranchForGate = task.mergeDetails.mergeTargetBranch || task.baseBranch || "main";
|
|
137065
|
+
const reachability = await verifyMergeConfirmedReachability({
|
|
137066
|
+
commitSha: task.mergeDetails.commitSha,
|
|
137067
|
+
integrationBranch: integrationBranchForGate,
|
|
137068
|
+
cwd
|
|
137069
|
+
});
|
|
137070
|
+
if (!reachability.reachable) {
|
|
137071
|
+
const sha = task.mergeDetails.commitSha || "";
|
|
137072
|
+
const shortSha = sha ? sha.slice(0, 8) : "<no-sha>";
|
|
137073
|
+
const currentRetries = task.mergeRetries ?? 0;
|
|
137074
|
+
const budgetExhausted = currentRetries >= _ProjectEngine.MAX_AUTO_MERGE_RETRIES;
|
|
137075
|
+
const cleanedMergeDetails = {
|
|
137076
|
+
...task.mergeDetails,
|
|
137077
|
+
mergeConfirmed: false,
|
|
137078
|
+
commitSha: void 0,
|
|
137079
|
+
mergedAt: void 0,
|
|
137080
|
+
landedFiles: void 0,
|
|
137081
|
+
filesChanged: void 0,
|
|
137082
|
+
insertions: void 0,
|
|
137083
|
+
deletions: void 0,
|
|
137084
|
+
noOpVerifiedShortCircuit: void 0,
|
|
137085
|
+
landedFilesAttributionRestricted: void 0
|
|
137086
|
+
};
|
|
137087
|
+
if (budgetExhausted) {
|
|
137088
|
+
const errorMsg = `Auto-merge fast-path refused after ${currentRetries} attempts: commit ${shortSha} is not reachable from ${integrationBranchForGate} (${reachability.reason}). Manual review required.`;
|
|
137089
|
+
runtimeLog.warn(
|
|
137090
|
+
`Auto-merge: ${taskId} fast-path REFUSED + budget exhausted \u2014 ${reachability.reason}: ${reachability.diagnostic}`
|
|
137091
|
+
);
|
|
137092
|
+
await store.logEntry(
|
|
137093
|
+
taskId,
|
|
137094
|
+
`[FN-5627] Auto-merge fast-path refused (retry budget exhausted) \u2014 ${errorMsg}`
|
|
137095
|
+
);
|
|
137096
|
+
await store.updateTask(taskId, {
|
|
137097
|
+
mergeDetails: cleanedMergeDetails,
|
|
137098
|
+
status: "failed",
|
|
137099
|
+
error: errorMsg
|
|
137100
|
+
});
|
|
137101
|
+
try {
|
|
137102
|
+
const auditor = createRunAuditor(store, {
|
|
137103
|
+
runId: generateSyntheticRunId("merger-fast-path-refused", taskId),
|
|
137104
|
+
agentId: "merger",
|
|
137105
|
+
taskId,
|
|
137106
|
+
phase: "auto-merge-fast-path-gate"
|
|
137107
|
+
});
|
|
137108
|
+
await auditor.database({
|
|
137109
|
+
type: "merger:fast-path-blocked-foreign-commit",
|
|
137110
|
+
target: taskId,
|
|
137111
|
+
metadata: {
|
|
137112
|
+
taskId,
|
|
137113
|
+
commitSha: sha,
|
|
137114
|
+
integrationBranch: integrationBranchForGate,
|
|
137115
|
+
reason: reachability.reason,
|
|
137116
|
+
diagnostic: reachability.diagnostic,
|
|
137117
|
+
mergeRetries: currentRetries,
|
|
137118
|
+
budgetExhausted: true
|
|
137119
|
+
}
|
|
137120
|
+
});
|
|
137121
|
+
} catch (auditErr) {
|
|
137122
|
+
runtimeLog.warn(
|
|
137123
|
+
`Auto-merge: ${taskId} fast-path audit emit failed: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`
|
|
137124
|
+
);
|
|
137125
|
+
}
|
|
137126
|
+
continue;
|
|
137127
|
+
}
|
|
137128
|
+
const nextRetries = currentRetries + 1;
|
|
137129
|
+
runtimeLog.warn(
|
|
137130
|
+
`Auto-merge: ${taskId} fast-path REFUSED \u2014 auto-recovering (attempt ${nextRetries}/${_ProjectEngine.MAX_AUTO_MERGE_RETRIES}): ${reachability.reason}: ${reachability.diagnostic}`
|
|
137131
|
+
);
|
|
137132
|
+
await store.logEntry(
|
|
137133
|
+
taskId,
|
|
137134
|
+
`Auto-recovered: fast-path refused \u2014 cleared poisoned mergeDetails (commit ${shortSha} not reachable from ${integrationBranchForGate}, ${reachability.reason}). Re-enqueueing for fresh merge attempt ${nextRetries}/${_ProjectEngine.MAX_AUTO_MERGE_RETRIES} [FN-5627].`
|
|
137135
|
+
);
|
|
137136
|
+
await store.updateTask(taskId, {
|
|
137137
|
+
mergeDetails: cleanedMergeDetails,
|
|
137138
|
+
mergeRetries: nextRetries,
|
|
137139
|
+
status: null,
|
|
137140
|
+
error: null
|
|
137141
|
+
});
|
|
137142
|
+
try {
|
|
137143
|
+
const auditor = createRunAuditor(store, {
|
|
137144
|
+
runId: generateSyntheticRunId("merger-fast-path-auto-recovered", taskId),
|
|
137145
|
+
agentId: "merger",
|
|
137146
|
+
taskId,
|
|
137147
|
+
phase: "auto-merge-fast-path-gate"
|
|
137148
|
+
});
|
|
137149
|
+
await auditor.database({
|
|
137150
|
+
type: "merger:fast-path-auto-recovered",
|
|
137151
|
+
target: taskId,
|
|
137152
|
+
metadata: {
|
|
137153
|
+
taskId,
|
|
137154
|
+
commitSha: sha,
|
|
137155
|
+
integrationBranch: integrationBranchForGate,
|
|
137156
|
+
reason: reachability.reason,
|
|
137157
|
+
diagnostic: reachability.diagnostic,
|
|
137158
|
+
mergeRetries: nextRetries,
|
|
137159
|
+
maxRetries: _ProjectEngine.MAX_AUTO_MERGE_RETRIES
|
|
137160
|
+
}
|
|
137161
|
+
});
|
|
137162
|
+
} catch (auditErr) {
|
|
137163
|
+
runtimeLog.warn(
|
|
137164
|
+
`Auto-merge: ${taskId} fast-path audit emit failed: ${auditErr instanceof Error ? auditErr.message : String(auditErr)}`
|
|
137165
|
+
);
|
|
137166
|
+
}
|
|
137167
|
+
this.internalEnqueueMerge(taskId);
|
|
137168
|
+
continue;
|
|
137169
|
+
}
|
|
135864
137170
|
const blockerReason = getTaskHardMergeBlocker(task);
|
|
135865
137171
|
if (blockerReason) {
|
|
135866
137172
|
await store.updateTask(taskId, {
|
|
@@ -135996,10 +137302,10 @@ ${detail}`
|
|
|
135996
137302
|
const pool = this.runtime.worktreePool;
|
|
135997
137303
|
const agentStore = this.runtime.agentStore;
|
|
135998
137304
|
const usageLimitPauser = this.runtime.usageLimitPauser;
|
|
135999
|
-
const rawMerge = () => {
|
|
137305
|
+
const rawMerge = async () => {
|
|
136000
137306
|
this.activeMergeTaskId = taskId;
|
|
136001
137307
|
this.mergeAbortController = new AbortController();
|
|
136002
|
-
|
|
137308
|
+
const mergerOptions = {
|
|
136003
137309
|
manual: !!manualResolver,
|
|
136004
137310
|
pool,
|
|
136005
137311
|
usageLimitPauser,
|
|
@@ -136008,7 +137314,9 @@ ${detail}`
|
|
|
136008
137314
|
onSession: (session) => {
|
|
136009
137315
|
this.activeMergeSession = session;
|
|
136010
137316
|
}
|
|
136011
|
-
}
|
|
137317
|
+
};
|
|
137318
|
+
const mergerMode = normalizeMergerMode((await store.getSettings().catch(() => ({}))).merger?.mode);
|
|
137319
|
+
return mergerMode === "ai" ? runAiMerge(store, cwd, taskId, mergerOptions) : aiMergeTask(store, cwd, taskId, mergerOptions);
|
|
136012
137320
|
};
|
|
136013
137321
|
let result;
|
|
136014
137322
|
if (semaphore) {
|
|
@@ -138564,8 +139872,8 @@ var require_proper_lockfile = __commonJS({
|
|
|
138564
139872
|
import { mkdirSync as mkdirSync10 } from "node:fs";
|
|
138565
139873
|
import { access as access7, unlink as unlink7, writeFile as writeFile18 } from "node:fs/promises";
|
|
138566
139874
|
import net from "node:net";
|
|
138567
|
-
import { platform as platform3, tmpdir as
|
|
138568
|
-
import { join as
|
|
139875
|
+
import { platform as platform3, tmpdir as tmpdir6 } from "node:os";
|
|
139876
|
+
import { join as join51 } from "node:path";
|
|
138569
139877
|
import { createHash as createHash14 } from "node:crypto";
|
|
138570
139878
|
function projectHash(projectId) {
|
|
138571
139879
|
return createHash14("sha1").update(projectId).digest("hex").slice(0, 16);
|
|
@@ -138575,10 +139883,10 @@ function computeEngineSocketPath(projectId) {
|
|
|
138575
139883
|
if (platform3() === "win32") {
|
|
138576
139884
|
return `\\\\.\\pipe\\fusion-engine-${hash}`;
|
|
138577
139885
|
}
|
|
138578
|
-
return
|
|
139886
|
+
return join51(tmpdir6(), `fusion-engine-${hash}.sock`);
|
|
138579
139887
|
}
|
|
138580
139888
|
function computeEngineLockFilePath(workingDir) {
|
|
138581
|
-
return
|
|
139889
|
+
return join51(workingDir, ".fusion", "engine.lock");
|
|
138582
139890
|
}
|
|
138583
139891
|
async function ensureLockTargetExists(lockPath) {
|
|
138584
139892
|
try {
|
|
@@ -138588,7 +139896,7 @@ async function ensureLockTargetExists(lockPath) {
|
|
|
138588
139896
|
}
|
|
138589
139897
|
}
|
|
138590
139898
|
async function acquireLockfile(workingDir, onCompromised) {
|
|
138591
|
-
const dir =
|
|
139899
|
+
const dir = join51(workingDir, ".fusion");
|
|
138592
139900
|
mkdirSync10(dir, { recursive: true });
|
|
138593
139901
|
const path6 = computeEngineLockFilePath(workingDir);
|
|
138594
139902
|
await ensureLockTargetExists(path6);
|
|
@@ -139204,7 +140512,7 @@ var init_node_health_monitor = __esm({
|
|
|
139204
140512
|
});
|
|
139205
140513
|
|
|
139206
140514
|
// ../engine/src/hybrid-executor.ts
|
|
139207
|
-
import { EventEmitter as
|
|
140515
|
+
import { EventEmitter as EventEmitter26 } from "node:events";
|
|
139208
140516
|
var HybridExecutor;
|
|
139209
140517
|
var init_hybrid_executor = __esm({
|
|
139210
140518
|
"../engine/src/hybrid-executor.ts"() {
|
|
@@ -139212,7 +140520,7 @@ var init_hybrid_executor = __esm({
|
|
|
139212
140520
|
init_project_manager();
|
|
139213
140521
|
init_node_health_monitor();
|
|
139214
140522
|
init_logger2();
|
|
139215
|
-
HybridExecutor = class extends
|
|
140523
|
+
HybridExecutor = class extends EventEmitter26 {
|
|
139216
140524
|
/**
|
|
139217
140525
|
* @param centralCore - CentralCore reference for global coordination
|
|
139218
140526
|
* @param options - Optional configuration callbacks
|
|
@@ -140522,7 +141830,7 @@ var init_planning_board_tools = __esm({
|
|
|
140522
141830
|
|
|
140523
141831
|
// ../dashboard/src/planning.ts
|
|
140524
141832
|
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
140525
|
-
import { EventEmitter as
|
|
141833
|
+
import { EventEmitter as EventEmitter27 } from "node:events";
|
|
140526
141834
|
function ensureEngineReady() {
|
|
140527
141835
|
return Promise.resolve();
|
|
140528
141836
|
}
|
|
@@ -141546,7 +142854,7 @@ For completion:
|
|
|
141546
142854
|
process.on("beforeExit", () => {
|
|
141547
142855
|
clearInterval(cleanupInterval2);
|
|
141548
142856
|
});
|
|
141549
|
-
PlanningStreamManager = class extends
|
|
142857
|
+
PlanningStreamManager = class extends EventEmitter27 {
|
|
141550
142858
|
constructor(bufferSize = 100) {
|
|
141551
142859
|
super();
|
|
141552
142860
|
this.bufferSize = bufferSize;
|
|
@@ -141761,7 +143069,7 @@ var init_ai_session_timeout = __esm({
|
|
|
141761
143069
|
});
|
|
141762
143070
|
|
|
141763
143071
|
// ../dashboard/src/subtask-breakdown.ts
|
|
141764
|
-
import { EventEmitter as
|
|
143072
|
+
import { EventEmitter as EventEmitter28 } from "node:events";
|
|
141765
143073
|
function cleanupInMemorySubtaskSession(sessionId) {
|
|
141766
143074
|
const session = sessions2.get(sessionId);
|
|
141767
143075
|
if (!session) {
|
|
@@ -141803,7 +143111,7 @@ var init_subtask_breakdown = __esm({
|
|
|
141803
143111
|
process.on("beforeExit", () => {
|
|
141804
143112
|
clearInterval(cleanupInterval3);
|
|
141805
143113
|
});
|
|
141806
|
-
SubtaskStreamManager = class extends
|
|
143114
|
+
SubtaskStreamManager = class extends EventEmitter28 {
|
|
141807
143115
|
constructor(bufferSize = 100) {
|
|
141808
143116
|
super();
|
|
141809
143117
|
this.bufferSize = bufferSize;
|
|
@@ -141865,7 +143173,7 @@ var init_subtask_breakdown = __esm({
|
|
|
141865
143173
|
});
|
|
141866
143174
|
|
|
141867
143175
|
// ../dashboard/src/mission-interview.ts
|
|
141868
|
-
import { EventEmitter as
|
|
143176
|
+
import { EventEmitter as EventEmitter29 } from "node:events";
|
|
141869
143177
|
function cleanupInMemoryMissionSession(sessionId) {
|
|
141870
143178
|
const session = sessions3.get(sessionId);
|
|
141871
143179
|
if (!session) {
|
|
@@ -141916,7 +143224,7 @@ var init_mission_interview = __esm({
|
|
|
141916
143224
|
cleanupInterval4 = setInterval(cleanupExpiredSessions3, CLEANUP_INTERVAL_MS4);
|
|
141917
143225
|
cleanupInterval4.unref?.();
|
|
141918
143226
|
process.on("beforeExit", () => clearInterval(cleanupInterval4));
|
|
141919
|
-
MissionInterviewStreamManager = class extends
|
|
143227
|
+
MissionInterviewStreamManager = class extends EventEmitter29 {
|
|
141920
143228
|
constructor(bufferSize = 100) {
|
|
141921
143229
|
super();
|
|
141922
143230
|
this.bufferSize = bufferSize;
|
|
@@ -141984,7 +143292,7 @@ var init_mission_interview = __esm({
|
|
|
141984
143292
|
});
|
|
141985
143293
|
|
|
141986
143294
|
// ../dashboard/src/milestone-slice-interview.ts
|
|
141987
|
-
import { EventEmitter as
|
|
143295
|
+
import { EventEmitter as EventEmitter30 } from "node:events";
|
|
141988
143296
|
function cleanupInMemorySession2(sessionId) {
|
|
141989
143297
|
const session = sessions4.get(sessionId);
|
|
141990
143298
|
if (!session) {
|
|
@@ -142036,7 +143344,7 @@ var init_milestone_slice_interview = __esm({
|
|
|
142036
143344
|
cleanupInterval5 = setInterval(cleanupExpiredSessions4, CLEANUP_INTERVAL_MS5);
|
|
142037
143345
|
cleanupInterval5.unref?.();
|
|
142038
143346
|
process.on("beforeExit", () => clearInterval(cleanupInterval5));
|
|
142039
|
-
MilestoneSliceInterviewStreamManager = class extends
|
|
143347
|
+
MilestoneSliceInterviewStreamManager = class extends EventEmitter30 {
|
|
142040
143348
|
constructor(bufferSize = 100) {
|
|
142041
143349
|
super();
|
|
142042
143350
|
this.bufferSize = bufferSize;
|
|
@@ -142351,13 +143659,13 @@ var init_cli_spawn = __esm({
|
|
|
142351
143659
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/fusion-skill-install.js
|
|
142352
143660
|
import { cpSync, existsSync as existsSync43, lstatSync as lstatSync2, mkdirSync as mkdirSync11, readFileSync as readFileSync14, readlinkSync, rmSync as rmSync5, symlinkSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
142353
143661
|
import { homedir as homedir8 } from "node:os";
|
|
142354
|
-
import { basename as basename14, dirname as dirname19, join as
|
|
143662
|
+
import { basename as basename14, dirname as dirname19, join as join52, resolve as resolve29 } from "node:path";
|
|
142355
143663
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
142356
143664
|
function resolveHermesHome(profile) {
|
|
142357
|
-
const base = process.env.HERMES_HOME ??
|
|
143665
|
+
const base = process.env.HERMES_HOME ?? join52(homedir8(), ".hermes");
|
|
142358
143666
|
if (!profile || profile === "default")
|
|
142359
143667
|
return base;
|
|
142360
|
-
return
|
|
143668
|
+
return join52(base, "profiles", profile);
|
|
142361
143669
|
}
|
|
142362
143670
|
function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
142363
143671
|
const here = fileURLToPath4(moduleUrl);
|
|
@@ -142372,14 +143680,14 @@ function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
|
142372
143680
|
function resolveBundledFusionSkillSource() {
|
|
142373
143681
|
const candidates = getFusionSkillSourceCandidates();
|
|
142374
143682
|
for (const candidate of candidates) {
|
|
142375
|
-
if (existsSync43(
|
|
143683
|
+
if (existsSync43(join52(candidate, "SKILL.md")))
|
|
142376
143684
|
return candidate;
|
|
142377
143685
|
}
|
|
142378
143686
|
return null;
|
|
142379
143687
|
}
|
|
142380
143688
|
function installFusionSkillIntoHermesHome(options = {}) {
|
|
142381
143689
|
const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
|
|
142382
|
-
const targetDir =
|
|
143690
|
+
const targetDir = join52(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
|
|
142383
143691
|
if (!sourceDir) {
|
|
142384
143692
|
return {
|
|
142385
143693
|
outcome: "warning",
|
|
@@ -142468,7 +143776,7 @@ function isBrokenSymlink(path6) {
|
|
|
142468
143776
|
}
|
|
142469
143777
|
}
|
|
142470
143778
|
function looksLikePriorFusionInstall(path6) {
|
|
142471
|
-
const skillMd =
|
|
143779
|
+
const skillMd = join52(path6, "SKILL.md");
|
|
142472
143780
|
if (!existsSync43(skillMd))
|
|
142473
143781
|
return false;
|
|
142474
143782
|
try {
|
|
@@ -142483,7 +143791,7 @@ function looksLikeFusionSkillTarget(path6) {
|
|
|
142483
143791
|
return false;
|
|
142484
143792
|
if (basename14(path6).toLowerCase() === FUSION_SKILL_NAME)
|
|
142485
143793
|
return true;
|
|
142486
|
-
return existsSync43(
|
|
143794
|
+
return existsSync43(join52(path6, "SKILL.md"));
|
|
142487
143795
|
}
|
|
142488
143796
|
var FUSION_SKILL_NAME;
|
|
142489
143797
|
var init_fusion_skill_install = __esm({
|
|
@@ -142861,8 +144169,8 @@ var init_pi_module = __esm({
|
|
|
142861
144169
|
|
|
142862
144170
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/mcp-config.js
|
|
142863
144171
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
142864
|
-
import { tmpdir as
|
|
142865
|
-
import { join as
|
|
144172
|
+
import { tmpdir as tmpdir7 } from "node:os";
|
|
144173
|
+
import { join as join53 } from "node:path";
|
|
142866
144174
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
142867
144175
|
import { dirname as dirname20 } from "node:path";
|
|
142868
144176
|
function toolsToMcpToolDefs(tools) {
|
|
@@ -142876,16 +144184,16 @@ function toolsToMcpToolDefs(tools) {
|
|
|
142876
144184
|
}
|
|
142877
144185
|
function writeOpenClawMcpBridgeFiles(toolDefs, cacheKey) {
|
|
142878
144186
|
const suffix = cacheKey ? `${process.pid}-${cacheKey}` : `${process.pid}`;
|
|
142879
|
-
const schemaPath =
|
|
144187
|
+
const schemaPath = join53(tmpdir7(), `openclaw-runtime-mcp-schemas-${suffix}.json`);
|
|
142880
144188
|
writeFileSync5(schemaPath, JSON.stringify(toolDefs));
|
|
142881
144189
|
const __filename = fileURLToPath5(import.meta.url);
|
|
142882
144190
|
const __dirname2 = dirname20(__filename);
|
|
142883
|
-
const serverPath =
|
|
144191
|
+
const serverPath = join53(__dirname2, "mcp-schema-server.cjs");
|
|
142884
144192
|
const serverConfig = {
|
|
142885
144193
|
command: "node",
|
|
142886
144194
|
args: [serverPath, schemaPath]
|
|
142887
144195
|
};
|
|
142888
|
-
const serverConfigPath =
|
|
144196
|
+
const serverConfigPath = join53(tmpdir7(), `openclaw-runtime-mcp-server-${suffix}.json`);
|
|
142889
144197
|
writeFileSync5(serverConfigPath, JSON.stringify(serverConfig));
|
|
142890
144198
|
return {
|
|
142891
144199
|
schemaPath,
|
|
@@ -143130,10 +144438,10 @@ var init_context = __esm({
|
|
|
143130
144438
|
|
|
143131
144439
|
// ../dashboard/src/github.ts
|
|
143132
144440
|
import { exec as exec37 } from "node:child_process";
|
|
143133
|
-
import { mkdtemp as
|
|
143134
|
-
import { tmpdir as
|
|
143135
|
-
import { join as
|
|
143136
|
-
import { promisify as
|
|
144441
|
+
import { mkdtemp as mkdtemp3, rm as rm7 } from "node:fs/promises";
|
|
144442
|
+
import { tmpdir as tmpdir8 } from "node:os";
|
|
144443
|
+
import { join as join54 } from "node:path";
|
|
144444
|
+
import { promisify as promisify46 } from "node:util";
|
|
143137
144445
|
function quoteGitArg(value) {
|
|
143138
144446
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
143139
144447
|
}
|
|
@@ -143391,7 +144699,7 @@ var init_github = __esm({
|
|
|
143391
144699
|
"../dashboard/src/github.ts"() {
|
|
143392
144700
|
"use strict";
|
|
143393
144701
|
init_src();
|
|
143394
|
-
execAsync33 =
|
|
144702
|
+
execAsync33 = promisify46(exec37);
|
|
143395
144703
|
PR_REVIEW_PAGE_SIZE = 100;
|
|
143396
144704
|
MAX_PR_REVIEW_PAGES = 10;
|
|
143397
144705
|
MAX_BADGE_BATCH_SIZE = 100;
|
|
@@ -144059,8 +145367,8 @@ var init_github = __esm({
|
|
|
144059
145367
|
{ timeout: 3e4, maxBuffer: 10 * 1024 * 1024 }
|
|
144060
145368
|
);
|
|
144061
145369
|
const mergeBase = mergeBaseStdout.trim();
|
|
144062
|
-
const indexDir = await
|
|
144063
|
-
const indexPath =
|
|
145370
|
+
const indexDir = await mkdtemp3(join54(tmpdir8(), "fn-pr-conflict-"));
|
|
145371
|
+
const indexPath = join54(indexDir, "index");
|
|
144064
145372
|
const gitEnv = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
144065
145373
|
try {
|
|
144066
145374
|
await execAsync33(`git -C ${quoteGitArg(opts.repoRoot)} read-tree -m ${quoteGitArg(mergeBase)} ${quoteGitArg(baseRef)} ${quoteGitArg(headRef)}`, {
|
|
@@ -144079,7 +145387,7 @@ var init_github = __esm({
|
|
|
144079
145387
|
)
|
|
144080
145388
|
];
|
|
144081
145389
|
} finally {
|
|
144082
|
-
await
|
|
145390
|
+
await rm7(indexDir, { recursive: true, force: true });
|
|
144083
145391
|
}
|
|
144084
145392
|
} catch {
|
|
144085
145393
|
conflictingFiles = [];
|
|
@@ -146111,8 +147419,18 @@ var init_github_tracking_reconciler = __esm({
|
|
|
146111
147419
|
}
|
|
146112
147420
|
});
|
|
146113
147421
|
|
|
147422
|
+
// ../dashboard/src/github-source-issue-close.ts
|
|
147423
|
+
var init_github_source_issue_close = __esm({
|
|
147424
|
+
"../dashboard/src/github-source-issue-close.ts"() {
|
|
147425
|
+
"use strict";
|
|
147426
|
+
init_github_auth();
|
|
147427
|
+
init_github();
|
|
147428
|
+
init_github_tracking_state();
|
|
147429
|
+
}
|
|
147430
|
+
});
|
|
147431
|
+
|
|
146114
147432
|
// ../dashboard/src/github-poll.ts
|
|
146115
|
-
import { EventEmitter as
|
|
147433
|
+
import { EventEmitter as EventEmitter31 } from "node:events";
|
|
146116
147434
|
function toAlias(type, number) {
|
|
146117
147435
|
return `${type}_${number}`;
|
|
146118
147436
|
}
|
|
@@ -146158,7 +147476,7 @@ var init_github_poll = __esm({
|
|
|
146158
147476
|
}
|
|
146159
147477
|
};
|
|
146160
147478
|
githubRateLimiter = new GitHubRateLimiter();
|
|
146161
|
-
GitHubPollingService = class extends
|
|
147479
|
+
GitHubPollingService = class extends EventEmitter31 {
|
|
146162
147480
|
watches = /* @__PURE__ */ new Map();
|
|
146163
147481
|
rateLimiter;
|
|
146164
147482
|
pollingIntervalMs;
|
|
@@ -146403,10 +147721,10 @@ var init_github_poll = __esm({
|
|
|
146403
147721
|
});
|
|
146404
147722
|
|
|
146405
147723
|
// ../dashboard/src/pr-metadata-generator.ts
|
|
146406
|
-
import { promisify as
|
|
147724
|
+
import { promisify as promisify47 } from "node:util";
|
|
146407
147725
|
import { exec as execCb } from "node:child_process";
|
|
146408
147726
|
import { access as access8, readFile as readFile26 } from "node:fs/promises";
|
|
146409
|
-
import { join as
|
|
147727
|
+
import { join as join55 } from "node:path";
|
|
146410
147728
|
function buildFallback(task) {
|
|
146411
147729
|
return {
|
|
146412
147730
|
title: task.title ?? task.id,
|
|
@@ -146539,12 +147857,12 @@ async function generatePrMetadata(input) {
|
|
|
146539
147857
|
]);
|
|
146540
147858
|
let promptContent = "";
|
|
146541
147859
|
try {
|
|
146542
|
-
const promptPath =
|
|
147860
|
+
const promptPath = join55(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
146543
147861
|
promptContent = (await readFile26(promptPath, "utf8")).trim();
|
|
146544
147862
|
} catch {
|
|
146545
147863
|
promptContent = "";
|
|
146546
147864
|
}
|
|
146547
|
-
const templatePath =
|
|
147865
|
+
const templatePath = join55(repoRoot, ".github", "pull_request_template.md");
|
|
146548
147866
|
const templateExists = await access8(templatePath).then(() => true).catch(() => false);
|
|
146549
147867
|
const template = templateExists ? await readFile26(templatePath, "utf8") : "";
|
|
146550
147868
|
const model = resolveTaskPlanningModel(task, settings);
|
|
@@ -146603,24 +147921,24 @@ var init_pr_metadata_generator = __esm({
|
|
|
146603
147921
|
"use strict";
|
|
146604
147922
|
init_src();
|
|
146605
147923
|
init_src2();
|
|
146606
|
-
execAsync34 =
|
|
147924
|
+
execAsync34 = promisify47(execCb);
|
|
146607
147925
|
}
|
|
146608
147926
|
});
|
|
146609
147927
|
|
|
146610
147928
|
// ../dashboard/src/routes/resolve-diff-base.ts
|
|
146611
|
-
import { execFile as
|
|
146612
|
-
import { promisify as
|
|
146613
|
-
var
|
|
147929
|
+
import { execFile as execFile18 } from "node:child_process";
|
|
147930
|
+
import { promisify as promisify48 } from "node:util";
|
|
147931
|
+
var execFileAsync13;
|
|
146614
147932
|
var init_resolve_diff_base = __esm({
|
|
146615
147933
|
"../dashboard/src/routes/resolve-diff-base.ts"() {
|
|
146616
147934
|
"use strict";
|
|
146617
|
-
|
|
147935
|
+
execFileAsync13 = promisify48(execFile18);
|
|
146618
147936
|
}
|
|
146619
147937
|
});
|
|
146620
147938
|
|
|
146621
147939
|
// ../dashboard/src/routes/register-git-github.ts
|
|
146622
147940
|
import { exec as execCb2, spawn as spawn12 } from "node:child_process";
|
|
146623
|
-
import { promisify as
|
|
147941
|
+
import { promisify as promisify49 } from "node:util";
|
|
146624
147942
|
var execAsync35, PR_ROUTE_MAX_BUFFER_BYTES;
|
|
146625
147943
|
var init_register_git_github = __esm({
|
|
146626
147944
|
"../dashboard/src/routes/register-git-github.ts"() {
|
|
@@ -146633,12 +147951,13 @@ var init_register_git_github = __esm({
|
|
|
146633
147951
|
init_github_tracking_comments();
|
|
146634
147952
|
init_github_tracking_state();
|
|
146635
147953
|
init_github_tracking_reconciler();
|
|
147954
|
+
init_github_source_issue_close();
|
|
146636
147955
|
init_github_poll();
|
|
146637
147956
|
init_project_store_resolver();
|
|
146638
147957
|
init_pr_metadata_generator();
|
|
146639
147958
|
init_github_webhooks();
|
|
146640
147959
|
init_resolve_diff_base();
|
|
146641
|
-
execAsync35 =
|
|
147960
|
+
execAsync35 = promisify49(execCb2);
|
|
146642
147961
|
PR_ROUTE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
146643
147962
|
}
|
|
146644
147963
|
});
|
|
@@ -146677,8 +147996,8 @@ var init_register_planning_subtask_routes = __esm({
|
|
|
146677
147996
|
});
|
|
146678
147997
|
|
|
146679
147998
|
// ../dashboard/src/chat.ts
|
|
146680
|
-
import { EventEmitter as
|
|
146681
|
-
import { SessionManager as SessionManager3 } from "@
|
|
147999
|
+
import { EventEmitter as EventEmitter32 } from "node:events";
|
|
148000
|
+
import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
|
|
146682
148001
|
var defaultDiagnostics, _diagnostics, diagnostics6, RATE_LIMIT_WINDOW_MS5, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
|
|
146683
148002
|
var init_chat = __esm({
|
|
146684
148003
|
"../dashboard/src/chat.ts"() {
|
|
@@ -146712,7 +148031,7 @@ var init_chat = __esm({
|
|
|
146712
148031
|
};
|
|
146713
148032
|
RATE_LIMIT_WINDOW_MS5 = 60 * 1e3;
|
|
146714
148033
|
MAX_REFERENCED_FILE_SIZE = 50 * 1024;
|
|
146715
|
-
ChatStreamManager = class extends
|
|
148034
|
+
ChatStreamManager = class extends EventEmitter32 {
|
|
146716
148035
|
constructor(bufferSize = 100) {
|
|
146717
148036
|
super();
|
|
146718
148037
|
this.bufferSize = bufferSize;
|
|
@@ -151496,7 +152815,7 @@ var init_register_messaging_scripts = __esm({
|
|
|
151496
152815
|
// ../dashboard/src/terminal.ts
|
|
151497
152816
|
import { spawn as spawn13 } from "node:child_process";
|
|
151498
152817
|
import { randomUUID as randomUUID25 } from "node:crypto";
|
|
151499
|
-
import { EventEmitter as
|
|
152818
|
+
import { EventEmitter as EventEmitter33 } from "node:events";
|
|
151500
152819
|
function extractBaseCommand(command) {
|
|
151501
152820
|
let trimmed = command.trim();
|
|
151502
152821
|
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
|
|
@@ -151642,7 +152961,7 @@ var init_terminal = __esm({
|
|
|
151642
152961
|
// >(...) — process substitution (bash)
|
|
151643
152962
|
];
|
|
151644
152963
|
CHAIN_OPERATORS = /&&|\|\||;|(?<!\|)\|(?!\|)/;
|
|
151645
|
-
TerminalSessionManager = class extends
|
|
152964
|
+
TerminalSessionManager = class extends EventEmitter33 {
|
|
151646
152965
|
sessions = /* @__PURE__ */ new Map();
|
|
151647
152966
|
defaultTimeout = 3e4;
|
|
151648
152967
|
// 30 seconds
|
|
@@ -151850,19 +153169,19 @@ var init_register_agents_projects_nodes = __esm({
|
|
|
151850
153169
|
});
|
|
151851
153170
|
|
|
151852
153171
|
// ../dashboard/src/exec-file.ts
|
|
151853
|
-
import { execFile as
|
|
151854
|
-
import { promisify as
|
|
151855
|
-
var
|
|
153172
|
+
import { execFile as execFile19 } from "node:child_process";
|
|
153173
|
+
import { promisify as promisify50 } from "node:util";
|
|
153174
|
+
var execFileAsync14;
|
|
151856
153175
|
var init_exec_file = __esm({
|
|
151857
153176
|
"../dashboard/src/exec-file.ts"() {
|
|
151858
153177
|
"use strict";
|
|
151859
|
-
|
|
153178
|
+
execFileAsync14 = promisify50(execFile19);
|
|
151860
153179
|
}
|
|
151861
153180
|
});
|
|
151862
153181
|
|
|
151863
153182
|
// ../dashboard/src/routes/register-project-routes.ts
|
|
151864
153183
|
import * as fsPromises from "node:fs/promises";
|
|
151865
|
-
var access9, stat9, mkdir16, readdir9,
|
|
153184
|
+
var access9, stat9, mkdir16, readdir9, rm8;
|
|
151866
153185
|
var init_register_project_routes = __esm({
|
|
151867
153186
|
"../dashboard/src/routes/register-project-routes.ts"() {
|
|
151868
153187
|
"use strict";
|
|
@@ -151875,7 +153194,7 @@ var init_register_project_routes = __esm({
|
|
|
151875
153194
|
stat: stat9,
|
|
151876
153195
|
mkdir: mkdir16,
|
|
151877
153196
|
readdir: readdir9,
|
|
151878
|
-
rm:
|
|
153197
|
+
rm: rm8
|
|
151879
153198
|
} = fsPromises);
|
|
151880
153199
|
}
|
|
151881
153200
|
});
|
|
@@ -152071,7 +153390,7 @@ var init_agent_generation = __esm({
|
|
|
152071
153390
|
|
|
152072
153391
|
// ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
|
|
152073
153392
|
import * as fsPromises2 from "node:fs/promises";
|
|
152074
|
-
var
|
|
153393
|
+
var mkdtemp4, access10, stat10, mkdir17, rm9, fsWriteFile;
|
|
152075
153394
|
var init_register_agent_import_export_generation_routes = __esm({
|
|
152076
153395
|
"../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
|
|
152077
153396
|
"use strict";
|
|
@@ -152079,7 +153398,7 @@ var init_register_agent_import_export_generation_routes = __esm({
|
|
|
152079
153398
|
init_ai_session_diagnostics();
|
|
152080
153399
|
init_sse_buffer();
|
|
152081
153400
|
init_agent_generation();
|
|
152082
|
-
({ mkdtemp:
|
|
153401
|
+
({ mkdtemp: mkdtemp4, access: access10, stat: stat10, mkdir: mkdir17, rm: rm9, writeFile: fsWriteFile } = fsPromises2);
|
|
152083
153402
|
}
|
|
152084
153403
|
});
|
|
152085
153404
|
|
|
@@ -153349,6 +154668,16 @@ var init_todo_routes = __esm({
|
|
|
153349
154668
|
}
|
|
153350
154669
|
});
|
|
153351
154670
|
|
|
154671
|
+
// ../dashboard/src/goals-routes.ts
|
|
154672
|
+
import { Router as Router9 } from "express";
|
|
154673
|
+
var init_goals_routes = __esm({
|
|
154674
|
+
"../dashboard/src/goals-routes.ts"() {
|
|
154675
|
+
"use strict";
|
|
154676
|
+
init_api_error();
|
|
154677
|
+
init_project_store_resolver();
|
|
154678
|
+
}
|
|
154679
|
+
});
|
|
154680
|
+
|
|
153352
154681
|
// ../../plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js
|
|
153353
154682
|
function compareOrderedEntities(a, b2) {
|
|
153354
154683
|
if (a.orderIndex !== b2.orderIndex) {
|
|
@@ -153514,13 +154843,13 @@ var init_roadmap_ordering = __esm({
|
|
|
153514
154843
|
});
|
|
153515
154844
|
|
|
153516
154845
|
// ../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js
|
|
153517
|
-
import { EventEmitter as
|
|
154846
|
+
import { EventEmitter as EventEmitter34 } from "node:events";
|
|
153518
154847
|
var RoadmapStore;
|
|
153519
154848
|
var init_roadmap_store = __esm({
|
|
153520
154849
|
"../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js"() {
|
|
153521
154850
|
"use strict";
|
|
153522
154851
|
init_roadmap_ordering();
|
|
153523
|
-
RoadmapStore = class extends
|
|
154852
|
+
RoadmapStore = class extends EventEmitter34 {
|
|
153524
154853
|
db;
|
|
153525
154854
|
/**
|
|
153526
154855
|
* Creates a new RoadmapStore instance.
|
|
@@ -155024,7 +156353,7 @@ var init_src5 = __esm({
|
|
|
155024
156353
|
});
|
|
155025
156354
|
|
|
155026
156355
|
// ../dashboard/src/roadmap-routes.ts
|
|
155027
|
-
import { Router as
|
|
156356
|
+
import { Router as Router10 } from "express";
|
|
155028
156357
|
var init_roadmap_routes3 = __esm({
|
|
155029
156358
|
"../dashboard/src/roadmap-routes.ts"() {
|
|
155030
156359
|
"use strict";
|
|
@@ -155068,7 +156397,7 @@ var init_dev_server_process = __esm({
|
|
|
155068
156397
|
});
|
|
155069
156398
|
|
|
155070
156399
|
// ../dashboard/src/dev-server-routes.ts
|
|
155071
|
-
import { Router as
|
|
156400
|
+
import { Router as Router11 } from "express";
|
|
155072
156401
|
var init_dev_server_routes = __esm({
|
|
155073
156402
|
"../dashboard/src/dev-server-routes.ts"() {
|
|
155074
156403
|
"use strict";
|
|
@@ -155080,7 +156409,7 @@ var init_dev_server_routes = __esm({
|
|
|
155080
156409
|
});
|
|
155081
156410
|
|
|
155082
156411
|
// ../dashboard/src/routes/register-stash-recovery-routes.ts
|
|
155083
|
-
import { Router as
|
|
156412
|
+
import { Router as Router12 } from "express";
|
|
155084
156413
|
var init_register_stash_recovery_routes = __esm({
|
|
155085
156414
|
"../dashboard/src/routes/register-stash-recovery-routes.ts"() {
|
|
155086
156415
|
"use strict";
|
|
@@ -155099,6 +156428,7 @@ var init_register_integrated_routers = __esm({
|
|
|
155099
156428
|
init_research_routes();
|
|
155100
156429
|
init_experiment_routes();
|
|
155101
156430
|
init_todo_routes();
|
|
156431
|
+
init_goals_routes();
|
|
155102
156432
|
init_roadmap_routes3();
|
|
155103
156433
|
init_dev_server_routes();
|
|
155104
156434
|
init_register_stash_recovery_routes();
|
|
@@ -157463,7 +158793,7 @@ var require_extension = __commonJS({
|
|
|
157463
158793
|
var require_websocket = __commonJS({
|
|
157464
158794
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports, module) {
|
|
157465
158795
|
"use strict";
|
|
157466
|
-
var
|
|
158796
|
+
var EventEmitter35 = __require("events");
|
|
157467
158797
|
var https = __require("https");
|
|
157468
158798
|
var http = __require("http");
|
|
157469
158799
|
var net2 = __require("net");
|
|
@@ -157495,7 +158825,7 @@ var require_websocket = __commonJS({
|
|
|
157495
158825
|
var protocolVersions = [8, 13];
|
|
157496
158826
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
157497
158827
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
157498
|
-
var WebSocket2 = class _WebSocket extends
|
|
158828
|
+
var WebSocket2 = class _WebSocket extends EventEmitter35 {
|
|
157499
158829
|
/**
|
|
157500
158830
|
* Create a new `WebSocket`.
|
|
157501
158831
|
*
|
|
@@ -158492,7 +159822,7 @@ var require_subprotocol = __commonJS({
|
|
|
158492
159822
|
var require_websocket_server = __commonJS({
|
|
158493
159823
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
158494
159824
|
"use strict";
|
|
158495
|
-
var
|
|
159825
|
+
var EventEmitter35 = __require("events");
|
|
158496
159826
|
var http = __require("http");
|
|
158497
159827
|
var { Duplex } = __require("stream");
|
|
158498
159828
|
var { createHash: createHash15 } = __require("crypto");
|
|
@@ -158505,7 +159835,7 @@ var require_websocket_server = __commonJS({
|
|
|
158505
159835
|
var RUNNING = 0;
|
|
158506
159836
|
var CLOSING = 1;
|
|
158507
159837
|
var CLOSED = 2;
|
|
158508
|
-
var WebSocketServer2 = class extends
|
|
159838
|
+
var WebSocketServer2 = class extends EventEmitter35 {
|
|
158509
159839
|
/**
|
|
158510
159840
|
* Create a `WebSocketServer` instance.
|
|
158511
159841
|
*
|
|
@@ -158943,7 +160273,7 @@ var init_view_chunk_manifest = __esm({
|
|
|
158943
160273
|
|
|
158944
160274
|
// ../dashboard/src/server.ts
|
|
158945
160275
|
import express from "express";
|
|
158946
|
-
import { join as
|
|
160276
|
+
import { join as join56, dirname as dirname21 } from "node:path";
|
|
158947
160277
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
158948
160278
|
function clearAiSessionCleanupInterval() {
|
|
158949
160279
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -159052,6 +160382,7 @@ var init_src6 = __esm({
|
|
|
159052
160382
|
init_rate_limit();
|
|
159053
160383
|
init_github_poll();
|
|
159054
160384
|
init_github_issue_comment();
|
|
160385
|
+
init_github_source_issue_close();
|
|
159055
160386
|
init_github_tracking_comments();
|
|
159056
160387
|
init_github_tracking_state();
|
|
159057
160388
|
init_github_tracking_reconciler();
|
|
@@ -159248,7 +160579,7 @@ __export(task_exports, {
|
|
|
159248
160579
|
});
|
|
159249
160580
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
159250
160581
|
import { watchFile, unwatchFile, statSync as statSync7, existsSync as existsSync44, readFileSync as readFileSync15 } from "node:fs";
|
|
159251
|
-
import { basename as basename16, join as
|
|
160582
|
+
import { basename as basename16, join as join57 } from "node:path";
|
|
159252
160583
|
function formatGhErrorForCli(err) {
|
|
159253
160584
|
const structured = classifyGhError(err);
|
|
159254
160585
|
const lines = [`GitHub error: ${structured.message}`];
|
|
@@ -159709,7 +161040,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
159709
161040
|
printEntries(filteredEntries);
|
|
159710
161041
|
if (options.follow) {
|
|
159711
161042
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
159712
|
-
const logPath =
|
|
161043
|
+
const logPath = join57(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
159713
161044
|
if (!existsSync44(logPath)) {
|
|
159714
161045
|
console.log(`
|
|
159715
161046
|
Waiting for log file to be created...`);
|
|
@@ -160887,8 +162218,8 @@ init_gh_cli();
|
|
|
160887
162218
|
init_src2();
|
|
160888
162219
|
init_src6();
|
|
160889
162220
|
import { Type as Type8 } from "typebox";
|
|
160890
|
-
import { StringEnum } from "@
|
|
160891
|
-
import { resolve as resolve31, basename as basename17, extname as extname3, join as
|
|
162221
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
162222
|
+
import { resolve as resolve31, basename as basename17, extname as extname3, join as join58 } from "node:path";
|
|
160892
162223
|
import { readFile as readFile28 } from "node:fs/promises";
|
|
160893
162224
|
import { existsSync as existsSync45 } from "node:fs";
|
|
160894
162225
|
import { spawn as spawn16 } from "node:child_process";
|
|
@@ -160914,12 +162245,12 @@ function resolveProjectRoot2(cwd) {
|
|
|
160914
162245
|
warnedMissingProjectRootResolver = true;
|
|
160915
162246
|
console.warn("[fusion-extension] @fusion/core.getProjectRootFromWorktree is unavailable; using filesystem fallback resolution.");
|
|
160916
162247
|
}
|
|
160917
|
-
if (worktreeProjectRoot && existsSync45(
|
|
162248
|
+
if (worktreeProjectRoot && existsSync45(join58(worktreeProjectRoot, ".fusion"))) {
|
|
160918
162249
|
return worktreeProjectRoot;
|
|
160919
162250
|
}
|
|
160920
162251
|
let current = resolve31(cwd);
|
|
160921
162252
|
while (true) {
|
|
160922
|
-
if (existsSync45(
|
|
162253
|
+
if (existsSync45(join58(current, ".fusion"))) {
|
|
160923
162254
|
return current;
|
|
160924
162255
|
}
|
|
160925
162256
|
const parent = resolve31(current, "..");
|
|
@@ -160940,7 +162271,7 @@ async function getStore2(cwd) {
|
|
|
160940
162271
|
return store;
|
|
160941
162272
|
}
|
|
160942
162273
|
function getFusionDir(cwd) {
|
|
160943
|
-
return
|
|
162274
|
+
return join58(resolveProjectRoot2(cwd), ".fusion");
|
|
160944
162275
|
}
|
|
160945
162276
|
function emitSecretAudit(store, ctx, mutationType, target, metadata) {
|
|
160946
162277
|
if (!ctx.runId || !ctx.agentId) return;
|
|
@@ -162804,6 +164135,136 @@ Status: ${createdMission.status}${createdMission.autoAdvance ? "\nAuto-advance:
|
|
|
162804
164135
|
};
|
|
162805
164136
|
}
|
|
162806
164137
|
});
|
|
164138
|
+
pi2.registerTool({
|
|
164139
|
+
name: "fn_goal_list",
|
|
164140
|
+
label: "fn: List Goals",
|
|
164141
|
+
description: "List goals by status with active-goal warning details.",
|
|
164142
|
+
promptSnippet: "List project goals",
|
|
164143
|
+
promptGuidelines: [
|
|
164144
|
+
"Use to inspect current goals before creating or archiving",
|
|
164145
|
+
"Default status is active; pass archived/all when needed",
|
|
164146
|
+
"Soft warning begins at 3 active goals and hard cap is 5"
|
|
164147
|
+
],
|
|
164148
|
+
parameters: Type8.Object({
|
|
164149
|
+
status: Type8.Optional(
|
|
164150
|
+
Type8.Union([
|
|
164151
|
+
Type8.Literal("active"),
|
|
164152
|
+
Type8.Literal("archived"),
|
|
164153
|
+
Type8.Literal("all")
|
|
164154
|
+
], { description: "Filter by goal status (default: active)" })
|
|
164155
|
+
)
|
|
164156
|
+
}),
|
|
164157
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
164158
|
+
const store = await getStore2(ctx.cwd);
|
|
164159
|
+
const goalStore = store.getGoalStore();
|
|
164160
|
+
const status = params.status ?? "active";
|
|
164161
|
+
const goals = status === "all" ? goalStore.listGoals() : goalStore.listGoals({ status });
|
|
164162
|
+
const activeCount = goalStore.listGoals({ status: "active" }).length;
|
|
164163
|
+
const softWarning = activeCount >= 3;
|
|
164164
|
+
const lines = [];
|
|
164165
|
+
lines.push(`Goals (${goals.length}) [filter: ${status}]`);
|
|
164166
|
+
lines.push(`Active: ${activeCount}/5`);
|
|
164167
|
+
if (softWarning) {
|
|
164168
|
+
lines.push("\u26A0 3/5 active goals \u2014 soft warning at 3, hard cap at 5");
|
|
164169
|
+
}
|
|
164170
|
+
lines.push("");
|
|
164171
|
+
if (goals.length === 0) {
|
|
164172
|
+
lines.push("No goals found.");
|
|
164173
|
+
} else {
|
|
164174
|
+
for (const goal of goals) {
|
|
164175
|
+
const description = goal.description ? ` \u2014 ${goal.description}` : "";
|
|
164176
|
+
lines.push(`- ${goal.id} [${goal.status}] ${goal.title}${description}`);
|
|
164177
|
+
}
|
|
164178
|
+
}
|
|
164179
|
+
return {
|
|
164180
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
164181
|
+
details: { goals, activeCount, softWarning, hardLimit: 5 }
|
|
164182
|
+
};
|
|
164183
|
+
}
|
|
164184
|
+
});
|
|
164185
|
+
pi2.registerTool({
|
|
164186
|
+
name: "fn_goal_create",
|
|
164187
|
+
label: "fn: Create Goal",
|
|
164188
|
+
description: "Create a new project goal.",
|
|
164189
|
+
promptSnippet: "Create a new goal",
|
|
164190
|
+
promptGuidelines: [
|
|
164191
|
+
"Use clear titles and optional context-rich descriptions",
|
|
164192
|
+
"Goal creation counts toward the 5 active-goal hard cap",
|
|
164193
|
+
"Archive older goals when the active cap is reached"
|
|
164194
|
+
],
|
|
164195
|
+
parameters: Type8.Object({
|
|
164196
|
+
title: Type8.String({ description: "Goal title \u2014 brief but descriptive" }),
|
|
164197
|
+
description: Type8.Optional(
|
|
164198
|
+
Type8.String({ description: "Long-form goal description (free-text markdown)" })
|
|
164199
|
+
)
|
|
164200
|
+
}),
|
|
164201
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
164202
|
+
const store = await getStore2(ctx.cwd);
|
|
164203
|
+
const goalStore = store.getGoalStore();
|
|
164204
|
+
try {
|
|
164205
|
+
const goal = goalStore.createGoal({
|
|
164206
|
+
title: params.title.trim(),
|
|
164207
|
+
description: params.description?.trim() || void 0
|
|
164208
|
+
});
|
|
164209
|
+
const activeCount = goalStore.listGoals({ status: "active" }).length;
|
|
164210
|
+
const softWarning = activeCount >= 3;
|
|
164211
|
+
return {
|
|
164212
|
+
content: [{ type: "text", text: `Created ${goal.id}: ${goal.title}
|
|
164213
|
+
Status: ${goal.status}${softWarning ? `
|
|
164214
|
+
\u26A0 ${activeCount}/5 active goals \u2014 approaching hard cap` : ""}` }],
|
|
164215
|
+
details: { goalId: goal.id, title: goal.title, status: goal.status, softWarning }
|
|
164216
|
+
};
|
|
164217
|
+
} catch (err) {
|
|
164218
|
+
if (typeof err === "object" && err !== null && "code" in err && err.code === "ACTIVE_GOAL_LIMIT_EXCEEDED") {
|
|
164219
|
+
const limit = err.limit ?? 5;
|
|
164220
|
+
const currentActive = err.currentActive ?? 5;
|
|
164221
|
+
return {
|
|
164222
|
+
isError: true,
|
|
164223
|
+
content: [{ type: "text", text: `Cannot create goal \u2014 already at the hard cap of 5 active goals (currently ${currentActive}). Archive one first.` }],
|
|
164224
|
+
details: { code: "ACTIVE_GOAL_LIMIT_EXCEEDED", limit, currentActive }
|
|
164225
|
+
};
|
|
164226
|
+
}
|
|
164227
|
+
throw err;
|
|
164228
|
+
}
|
|
164229
|
+
}
|
|
164230
|
+
});
|
|
164231
|
+
pi2.registerTool({
|
|
164232
|
+
name: "fn_goal_archive",
|
|
164233
|
+
label: "fn: Archive Goal",
|
|
164234
|
+
description: "Archive a goal by ID.",
|
|
164235
|
+
promptSnippet: "Archive a goal by ID",
|
|
164236
|
+
promptGuidelines: [
|
|
164237
|
+
"Use when a goal is complete or no longer active",
|
|
164238
|
+
"Archiving frees active-goal capacity",
|
|
164239
|
+
"Returns success for already archived goals"
|
|
164240
|
+
],
|
|
164241
|
+
parameters: Type8.Object({
|
|
164242
|
+
id: Type8.String({ description: "Goal ID (G-\u2026) to archive" })
|
|
164243
|
+
}),
|
|
164244
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
164245
|
+
const store = await getStore2(ctx.cwd);
|
|
164246
|
+
const goalStore = store.getGoalStore();
|
|
164247
|
+
const goal = goalStore.getGoal(params.id);
|
|
164248
|
+
if (!goal) {
|
|
164249
|
+
return {
|
|
164250
|
+
isError: true,
|
|
164251
|
+
content: [{ type: "text", text: `Goal ${params.id} not found` }],
|
|
164252
|
+
details: { code: "GOAL_NOT_FOUND", goalId: params.id }
|
|
164253
|
+
};
|
|
164254
|
+
}
|
|
164255
|
+
if (goal.status === "archived") {
|
|
164256
|
+
return {
|
|
164257
|
+
content: [{ type: "text", text: `Goal ${params.id} is already archived` }],
|
|
164258
|
+
details: { goalId: params.id, status: "archived" }
|
|
164259
|
+
};
|
|
164260
|
+
}
|
|
164261
|
+
const archived = goalStore.archiveGoal(params.id);
|
|
164262
|
+
return {
|
|
164263
|
+
content: [{ type: "text", text: `Archived ${archived.id}: ${archived.title}` }],
|
|
164264
|
+
details: { goalId: archived.id, status: "archived" }
|
|
164265
|
+
};
|
|
164266
|
+
}
|
|
164267
|
+
});
|
|
162807
164268
|
pi2.registerTool({
|
|
162808
164269
|
name: "fn_mission_show",
|
|
162809
164270
|
label: "fn: Show Mission",
|