@runfusion/fusion 0.34.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 +2790 -633
- package/dist/client/.vite/manifest.json +56 -56
- package/dist/client/assets/{AgentDetailView-CzqwIIpv.js → AgentDetailView-CGycokdR.js} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor-2eoM2XQH.js → AgentPermissionPolicyEditor-BoD6come.js} +1 -1
- package/dist/client/assets/{AgentsView-BrtAvhJl.js → AgentsView-B_afRRfc.js} +3 -3
- package/dist/client/assets/ChatView-IjNrSZSf.js +6 -0
- package/dist/client/assets/{DevServerView-CF6FPLRb.js → DevServerView-Dd613aZ5.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BpvHQxhu.js → DirectoryPicker-H-fUl2dF.js} +1 -1
- package/dist/client/assets/{DocumentsView-RMWZBJ-y.js → DocumentsView-Dshc9e5K.js} +1 -1
- package/dist/client/assets/{EvalsView-DtVxpc4T.js → EvalsView-50W0K5h7.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-fzJmb-yG.js → ExperimentalAgentOnboardingModal-BXBljPFj.js} +1 -1
- package/dist/client/assets/{GoalsView-BAchggxo.js → GoalsView-Dh508Z2s.js} +1 -1
- package/dist/client/assets/{InsightsView-B8892IbG.js → InsightsView-sVbGRKTl.js} +1 -1
- package/dist/client/assets/{MemoryView-Cfp1WmAm.js → MemoryView-006a_sB0.js} +1 -1
- package/dist/client/assets/{NodesView-Dt26DSEQ.js → NodesView-CiRPqgVx.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-xHU56SMh.js → PiExtensionsManager-Y1ETuTxf.js} +1 -1
- package/dist/client/assets/{PluginManager-37WUEOmp.js → PluginManager-Df_KHiXn.js} +1 -1
- package/dist/client/assets/{ResearchView-pu88M5xL.js → ResearchView-DU_QuS_-.js} +1 -1
- package/dist/client/assets/{SecretsView-BT7ofJLN.js → SecretsView-D61Jvysn.js} +1 -1
- package/dist/client/assets/{SettingsModal-DxYXsn08.js → SettingsModal-BarzmqNb.js} +1 -1
- package/dist/client/assets/SettingsModal-Cpur3WpT.js +31 -0
- package/dist/client/assets/{SetupWizardModal-yWTk3Cac.js → SetupWizardModal-BF3iRNq9.js} +1 -1
- package/dist/client/assets/{SkillsView-CWNfKNTs.js → SkillsView-tCKOFw3j.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-D0t4IIX-.js → StashRecoveryView-nGtypd10.js} +1 -1
- package/dist/client/assets/{TodoView-B4W1Lhy6.js → TodoView-IyGPV6AO.js} +1 -1
- package/dist/client/assets/{dashboard-view-4OCJIthP.js → dashboard-view-DiA5-hnb.js} +1 -1
- package/dist/client/assets/{folder-open-BTdhwFQ5.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-kESyjow_.js → upload-D6_BjqVi.js} +1 -1
- package/dist/client/assets/{users-DNXMrdcp.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 +1959 -310
- 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/ChatView-BuIe4MhL.js +0 -6
- package/dist/client/assets/SettingsModal-Cz9MHNGg.js +0 -31
- package/dist/client/assets/index-D4nQgFjo.js +0 -742
- package/dist/client/assets/index-k4s0JWir.css +0 -1
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",
|
|
@@ -2070,7 +2076,31 @@ Do NOT spend time on nits when no real issues exist.`
|
|
|
2070
2076
|
];
|
|
2071
2077
|
DEFAULT_HEARTBEAT_PROCEDURE_PATH = ".fusion/HEARTBEAT.md";
|
|
2072
2078
|
DASHBOARD_USER_ID = "dashboard";
|
|
2073
|
-
DASHBOARD_USER_ALIASES = /* @__PURE__ */ new Set([DASHBOARD_USER_ID, "user:dashboard", "User: user:dashboard"]);
|
|
2079
|
+
DASHBOARD_USER_ALIASES = /* @__PURE__ */ new Set([DASHBOARD_USER_ID, "user", "user:dashboard", "User: user:dashboard"]);
|
|
2080
|
+
}
|
|
2081
|
+
});
|
|
2082
|
+
|
|
2083
|
+
// ../core/src/custom-provider-key.ts
|
|
2084
|
+
function slugifyProviderName(name) {
|
|
2085
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
2086
|
+
}
|
|
2087
|
+
function customProviderRegistryKey(provider, allProviders) {
|
|
2088
|
+
const base = slugifyProviderName(provider.name) || provider.id;
|
|
2089
|
+
let count = 0;
|
|
2090
|
+
for (const current of allProviders) {
|
|
2091
|
+
const currentBase = slugifyProviderName(current.name) || current.id;
|
|
2092
|
+
if (currentBase === base) {
|
|
2093
|
+
count += 1;
|
|
2094
|
+
}
|
|
2095
|
+
if (current.id === provider.id) {
|
|
2096
|
+
break;
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
return count <= 1 ? base : `${base}-${count}`;
|
|
2100
|
+
}
|
|
2101
|
+
var init_custom_provider_key = __esm({
|
|
2102
|
+
"../core/src/custom-provider-key.ts"() {
|
|
2103
|
+
"use strict";
|
|
2074
2104
|
}
|
|
2075
2105
|
});
|
|
2076
2106
|
|
|
@@ -4642,7 +4672,7 @@ var init_db = __esm({
|
|
|
4642
4672
|
DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
|
|
4643
4673
|
DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
|
|
4644
4674
|
DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
|
|
4645
|
-
SCHEMA_VERSION =
|
|
4675
|
+
SCHEMA_VERSION = 92;
|
|
4646
4676
|
SCHEMA_SQL = `
|
|
4647
4677
|
-- Tasks table with JSON columns for nested data
|
|
4648
4678
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
@@ -5195,6 +5225,17 @@ CREATE TABLE IF NOT EXISTS missions (
|
|
|
5195
5225
|
updatedAt TEXT NOT NULL
|
|
5196
5226
|
);
|
|
5197
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
|
+
|
|
5198
5239
|
-- Milestones table (phases within a mission)
|
|
5199
5240
|
CREATE TABLE IF NOT EXISTS milestones (
|
|
5200
5241
|
id TEXT PRIMARY KEY,
|
|
@@ -7507,6 +7548,24 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
7507
7548
|
this.addColumnIfMissing("missions", "baseBranch", "TEXT");
|
|
7508
7549
|
});
|
|
7509
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
|
+
}
|
|
7510
7569
|
}
|
|
7511
7570
|
/**
|
|
7512
7571
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -10497,7 +10556,7 @@ var init_message_store = __esm({
|
|
|
10497
10556
|
}
|
|
10498
10557
|
getParticipantIdsForLookup(ownerId, ownerType) {
|
|
10499
10558
|
if (ownerType === "user" && ownerId === DASHBOARD_USER_ID) {
|
|
10500
|
-
return [DASHBOARD_USER_ID, "user:dashboard", "User: user:dashboard"];
|
|
10559
|
+
return [DASHBOARD_USER_ID, "user", "user:dashboard", "User: user:dashboard"];
|
|
10501
10560
|
}
|
|
10502
10561
|
return [ownerId];
|
|
10503
10562
|
}
|
|
@@ -18630,6 +18689,158 @@ var init_todo_store = __esm({
|
|
|
18630
18689
|
}
|
|
18631
18690
|
});
|
|
18632
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
|
+
|
|
18633
18844
|
// ../core/src/eval-types.ts
|
|
18634
18845
|
function normalizeEvalFollowUpText(value) {
|
|
18635
18846
|
return value.toLowerCase().replace(/\s+/g, " ").replace(/[^a-z0-9 ]/g, "").trim();
|
|
@@ -18708,7 +18919,7 @@ var init_eval_types = __esm({
|
|
|
18708
18919
|
});
|
|
18709
18920
|
|
|
18710
18921
|
// ../core/src/eval-store.ts
|
|
18711
|
-
import { EventEmitter as
|
|
18922
|
+
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
18712
18923
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
18713
18924
|
function generateRunId3() {
|
|
18714
18925
|
return `ER-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).slice(2, 7).toUpperCase()}`;
|
|
@@ -18804,7 +19015,7 @@ var init_eval_store = __esm({
|
|
|
18804
19015
|
this.name = "EvalLifecycleError";
|
|
18805
19016
|
}
|
|
18806
19017
|
};
|
|
18807
|
-
EvalStore = class extends
|
|
19018
|
+
EvalStore = class extends EventEmitter11 {
|
|
18808
19019
|
constructor(db) {
|
|
18809
19020
|
super();
|
|
18810
19021
|
this.db = db;
|
|
@@ -19764,7 +19975,7 @@ var require_registry = __commonJS({
|
|
|
19764
19975
|
setTimeout(send, Math.random() * 250);
|
|
19765
19976
|
}
|
|
19766
19977
|
announce(server, service) {
|
|
19767
|
-
var
|
|
19978
|
+
var delay5 = 1e3;
|
|
19768
19979
|
var packet = service.records();
|
|
19769
19980
|
server.register(packet);
|
|
19770
19981
|
const broadcast = () => {
|
|
@@ -19776,9 +19987,9 @@ var require_registry = __commonJS({
|
|
|
19776
19987
|
service.published = true;
|
|
19777
19988
|
service.emit("up");
|
|
19778
19989
|
}
|
|
19779
|
-
|
|
19780
|
-
if (
|
|
19781
|
-
setTimeout(broadcast,
|
|
19990
|
+
delay5 = delay5 * REANNOUNCE_FACTOR;
|
|
19991
|
+
if (delay5 < REANNOUNCE_MAX_MS && !service.destroyed) {
|
|
19992
|
+
setTimeout(broadcast, delay5).unref();
|
|
19782
19993
|
}
|
|
19783
19994
|
});
|
|
19784
19995
|
};
|
|
@@ -22604,7 +22815,7 @@ var require_dist = __commonJS({
|
|
|
22604
22815
|
});
|
|
22605
22816
|
|
|
22606
22817
|
// ../core/src/node-discovery.ts
|
|
22607
|
-
import { EventEmitter as
|
|
22818
|
+
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
22608
22819
|
import os from "node:os";
|
|
22609
22820
|
var import_bonjour_service, DEFAULT_DISCOVERY_CONFIG, STALE_CLEANUP_INTERVAL_MS, FUSION_VERSION, NodeDiscovery;
|
|
22610
22821
|
var init_node_discovery = __esm({
|
|
@@ -22620,7 +22831,7 @@ var init_node_discovery = __esm({
|
|
|
22620
22831
|
};
|
|
22621
22832
|
STALE_CLEANUP_INTERVAL_MS = 6e4;
|
|
22622
22833
|
FUSION_VERSION = "0.1.0";
|
|
22623
|
-
NodeDiscovery = class extends
|
|
22834
|
+
NodeDiscovery = class extends EventEmitter12 {
|
|
22624
22835
|
config;
|
|
22625
22836
|
bonjour = null;
|
|
22626
22837
|
broadcastService = null;
|
|
@@ -23156,7 +23367,7 @@ var init_project_identity = __esm({
|
|
|
23156
23367
|
});
|
|
23157
23368
|
|
|
23158
23369
|
// ../core/src/central-core.ts
|
|
23159
|
-
import { EventEmitter as
|
|
23370
|
+
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
23160
23371
|
import { createHash as createHash5, randomUUID as randomUUID10 } from "node:crypto";
|
|
23161
23372
|
import { existsSync as existsSync9, statSync as statSync2 } from "node:fs";
|
|
23162
23373
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
@@ -23174,7 +23385,7 @@ var init_central_core = __esm({
|
|
|
23174
23385
|
init_system_metrics();
|
|
23175
23386
|
init_shared_mesh_state();
|
|
23176
23387
|
init_project_identity();
|
|
23177
|
-
CentralCore = class extends
|
|
23388
|
+
CentralCore = class extends EventEmitter13 {
|
|
23178
23389
|
db = null;
|
|
23179
23390
|
globalDir;
|
|
23180
23391
|
initialized = false;
|
|
@@ -25345,10 +25556,10 @@ var init_central_core = __esm({
|
|
|
25345
25556
|
*/
|
|
25346
25557
|
async generateProjectName(projectPath) {
|
|
25347
25558
|
try {
|
|
25348
|
-
const { execFile:
|
|
25349
|
-
const { promisify:
|
|
25350
|
-
const
|
|
25351
|
-
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(
|
|
25352
25563
|
"git",
|
|
25353
25564
|
["remote", "get-url", "origin"],
|
|
25354
25565
|
{ cwd: projectPath, timeout: 5e3 }
|
|
@@ -26052,10 +26263,10 @@ var init_migration = __esm({
|
|
|
26052
26263
|
return basename5(projectPath);
|
|
26053
26264
|
}
|
|
26054
26265
|
try {
|
|
26055
|
-
const { execFile:
|
|
26056
|
-
const { promisify:
|
|
26057
|
-
const
|
|
26058
|
-
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(
|
|
26059
26270
|
"git",
|
|
26060
26271
|
["remote", "get-url", "origin"],
|
|
26061
26272
|
{ cwd: projectPath, timeout: 1e3 }
|
|
@@ -36699,7 +36910,7 @@ var automation_store_exports = {};
|
|
|
36699
36910
|
__export(automation_store_exports, {
|
|
36700
36911
|
AutomationStore: () => AutomationStore
|
|
36701
36912
|
});
|
|
36702
|
-
import { EventEmitter as
|
|
36913
|
+
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
36703
36914
|
import { join as join16 } from "node:path";
|
|
36704
36915
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
36705
36916
|
var import_cron_parser, CRON_TIMEZONE, AutomationStore;
|
|
@@ -36711,7 +36922,7 @@ var init_automation_store = __esm({
|
|
|
36711
36922
|
init_db();
|
|
36712
36923
|
init_project_root_guard();
|
|
36713
36924
|
CRON_TIMEZONE = "UTC";
|
|
36714
|
-
AutomationStore = class _AutomationStore extends
|
|
36925
|
+
AutomationStore = class _AutomationStore extends EventEmitter14 {
|
|
36715
36926
|
constructor(rootDir, options) {
|
|
36716
36927
|
super();
|
|
36717
36928
|
this.rootDir = rootDir;
|
|
@@ -37803,13 +38014,13 @@ async function searchWithQmd(rootDir, options) {
|
|
|
37803
38014
|
const command = "qmd";
|
|
37804
38015
|
const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
|
|
37805
38016
|
try {
|
|
37806
|
-
const { execFile:
|
|
37807
|
-
const { promisify:
|
|
37808
|
-
const
|
|
37809
|
-
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);
|
|
37810
38021
|
scheduleQmdProjectMemoryRefresh(rootDir);
|
|
37811
38022
|
const args = buildQmdSearchArgs(rootDir, options);
|
|
37812
|
-
const { stdout } = await
|
|
38023
|
+
const { stdout } = await execFileAsync15(command, args, {
|
|
37813
38024
|
cwd: rootDir,
|
|
37814
38025
|
timeout: 4e3,
|
|
37815
38026
|
maxBuffer: 1024 * 1024
|
|
@@ -37834,12 +38045,12 @@ async function searchWithQmd(rootDir, options) {
|
|
|
37834
38045
|
return [];
|
|
37835
38046
|
}
|
|
37836
38047
|
}
|
|
37837
|
-
async function ensureQmdProjectMemoryCollection(rootDir,
|
|
38048
|
+
async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync15) {
|
|
37838
38049
|
const collectionName = qmdMemoryCollectionName(rootDir);
|
|
37839
38050
|
const memoryDir = memoryWorkspacePath(rootDir);
|
|
37840
38051
|
await mkdir6(memoryDir, { recursive: true });
|
|
37841
38052
|
try {
|
|
37842
|
-
await
|
|
38053
|
+
await execFileAsync15("qmd", buildQmdCollectionAddArgs(rootDir), {
|
|
37843
38054
|
cwd: rootDir,
|
|
37844
38055
|
timeout: 4e3,
|
|
37845
38056
|
maxBuffer: 512 * 1024
|
|
@@ -37855,9 +38066,9 @@ ${stderr}`)) {
|
|
|
37855
38066
|
return collectionName;
|
|
37856
38067
|
}
|
|
37857
38068
|
async function getDefaultExecFileAsync() {
|
|
37858
|
-
const { execFile:
|
|
37859
|
-
const { promisify:
|
|
37860
|
-
return
|
|
38069
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
38070
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
38071
|
+
return promisify51(execFile20);
|
|
37861
38072
|
}
|
|
37862
38073
|
async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
37863
38074
|
const key = resolve8(rootDir);
|
|
@@ -37872,14 +38083,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
|
37872
38083
|
}
|
|
37873
38084
|
}
|
|
37874
38085
|
const promise = (async () => {
|
|
37875
|
-
const
|
|
37876
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
37877
|
-
await
|
|
38086
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
38087
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync15);
|
|
38088
|
+
await execFileAsync15("qmd", ["update"], {
|
|
37878
38089
|
cwd: rootDir,
|
|
37879
38090
|
timeout: 3e4,
|
|
37880
38091
|
maxBuffer: 1024 * 1024
|
|
37881
38092
|
});
|
|
37882
|
-
await
|
|
38093
|
+
await execFileAsync15("qmd", ["embed"], {
|
|
37883
38094
|
cwd: rootDir,
|
|
37884
38095
|
timeout: 12e4,
|
|
37885
38096
|
maxBuffer: 1024 * 1024
|
|
@@ -37915,12 +38126,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
|
|
|
37915
38126
|
}
|
|
37916
38127
|
}
|
|
37917
38128
|
const promise = (async () => {
|
|
37918
|
-
const
|
|
38129
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
37919
38130
|
const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
|
|
37920
38131
|
const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
|
|
37921
38132
|
await mkdir6(workspacePath, { recursive: true });
|
|
37922
38133
|
try {
|
|
37923
|
-
await
|
|
38134
|
+
await execFileAsync15("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
|
|
37924
38135
|
cwd: rootDir,
|
|
37925
38136
|
timeout: 4e3,
|
|
37926
38137
|
maxBuffer: 512 * 1024
|
|
@@ -37933,8 +38144,8 @@ ${stderr}`)) {
|
|
|
37933
38144
|
throw err;
|
|
37934
38145
|
}
|
|
37935
38146
|
}
|
|
37936
|
-
await
|
|
37937
|
-
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 });
|
|
37938
38149
|
})();
|
|
37939
38150
|
qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
37940
38151
|
try {
|
|
@@ -37955,8 +38166,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
|
|
|
37955
38166
|
}
|
|
37956
38167
|
async function isQmdAvailable() {
|
|
37957
38168
|
try {
|
|
37958
|
-
const
|
|
37959
|
-
await
|
|
38169
|
+
const execFileAsync15 = await getDefaultExecFileAsync();
|
|
38170
|
+
await execFileAsync15("qmd", ["--help"], {
|
|
37960
38171
|
timeout: 3e3,
|
|
37961
38172
|
maxBuffer: 128 * 1024
|
|
37962
38173
|
});
|
|
@@ -37966,12 +38177,12 @@ async function isQmdAvailable() {
|
|
|
37966
38177
|
}
|
|
37967
38178
|
}
|
|
37968
38179
|
async function installQmd(options) {
|
|
37969
|
-
const
|
|
38180
|
+
const execFileAsync15 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
37970
38181
|
const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
|
|
37971
38182
|
if (!command || args.length === 0) {
|
|
37972
38183
|
throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
|
|
37973
38184
|
}
|
|
37974
|
-
await
|
|
38185
|
+
await execFileAsync15(command, args, {
|
|
37975
38186
|
timeout: 12e4,
|
|
37976
38187
|
maxBuffer: 1024 * 1024
|
|
37977
38188
|
});
|
|
@@ -39622,7 +39833,7 @@ var init_task_id_integrity = __esm({
|
|
|
39622
39833
|
});
|
|
39623
39834
|
|
|
39624
39835
|
// ../core/src/store.ts
|
|
39625
|
-
import { EventEmitter as
|
|
39836
|
+
import { EventEmitter as EventEmitter15 } from "node:events";
|
|
39626
39837
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
39627
39838
|
import { mkdir as mkdir7, readdir as readdir5, readFile as readFile8, writeFile as writeFile6, rename as rename4, unlink as unlink3 } from "node:fs/promises";
|
|
39628
39839
|
import { join as join19 } from "node:path";
|
|
@@ -39954,6 +40165,7 @@ var init_store = __esm({
|
|
|
39954
40165
|
init_research_store();
|
|
39955
40166
|
init_experiment_session_store();
|
|
39956
40167
|
init_todo_store();
|
|
40168
|
+
init_goal_store();
|
|
39957
40169
|
init_eval_store();
|
|
39958
40170
|
init_migration();
|
|
39959
40171
|
init_central_core();
|
|
@@ -40147,7 +40359,7 @@ var init_store = __esm({
|
|
|
40147
40359
|
this.name = "HandoffInvariantViolationError";
|
|
40148
40360
|
}
|
|
40149
40361
|
};
|
|
40150
|
-
TaskStore = class _TaskStore extends
|
|
40362
|
+
TaskStore = class _TaskStore extends EventEmitter15 {
|
|
40151
40363
|
constructor(rootDir, globalSettingsDir, options) {
|
|
40152
40364
|
super();
|
|
40153
40365
|
this.rootDir = rootDir;
|
|
@@ -40279,6 +40491,8 @@ var init_store = __esm({
|
|
|
40279
40491
|
experimentSessionStore = null;
|
|
40280
40492
|
/** Cached TodoStore instance */
|
|
40281
40493
|
todoStore = null;
|
|
40494
|
+
/** Cached GoalStore instance */
|
|
40495
|
+
goalStore = null;
|
|
40282
40496
|
/** Cached EvalStore instance */
|
|
40283
40497
|
evalStore = null;
|
|
40284
40498
|
/** Cached SecretsStore instance */
|
|
@@ -42662,9 +42876,9 @@ ${outcome}`;
|
|
|
42662
42876
|
if (validation.invalid.length > 0) {
|
|
42663
42877
|
if (this.isWatching) this.taskCache.delete(id);
|
|
42664
42878
|
this.deleteTaskById(id);
|
|
42665
|
-
const { rm:
|
|
42879
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
42666
42880
|
if (existsSync15(dir)) {
|
|
42667
|
-
await
|
|
42881
|
+
await rm10(dir, { recursive: true, force: true });
|
|
42668
42882
|
}
|
|
42669
42883
|
throw new InvalidFileScopeError(id, validation.invalid);
|
|
42670
42884
|
}
|
|
@@ -42759,10 +42973,10 @@ ${outcome}`;
|
|
|
42759
42973
|
});
|
|
42760
42974
|
if (this.isWatching) this.taskCache.delete(task.id);
|
|
42761
42975
|
this.deleteTaskById(task.id);
|
|
42762
|
-
const { rm:
|
|
42976
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
42763
42977
|
const taskDir = this.taskDir(task.id);
|
|
42764
42978
|
if (existsSync15(taskDir)) {
|
|
42765
|
-
await
|
|
42979
|
+
await rm10(taskDir, { recursive: true, force: true });
|
|
42766
42980
|
}
|
|
42767
42981
|
throw new TombstonedTaskResurrectionError(
|
|
42768
42982
|
tombstonedMatch.id,
|
|
@@ -45779,8 +45993,8 @@ ${newTask.description}
|
|
|
45779
45993
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
45780
45994
|
this.db.bumpLastModified();
|
|
45781
45995
|
});
|
|
45782
|
-
const { rm:
|
|
45783
|
-
await
|
|
45996
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
45997
|
+
await rm10(dir, { recursive: true, force: true });
|
|
45784
45998
|
if (this.isWatching) {
|
|
45785
45999
|
this.taskCache.delete(id);
|
|
45786
46000
|
}
|
|
@@ -47189,14 +47403,14 @@ ${newTask.description}
|
|
|
47189
47403
|
if (rows.length === 0) {
|
|
47190
47404
|
return;
|
|
47191
47405
|
}
|
|
47192
|
-
const { rm:
|
|
47406
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
47193
47407
|
for (const row of rows) {
|
|
47194
47408
|
const task = this.rowToTask(row);
|
|
47195
47409
|
const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
47196
47410
|
const entry = await this.taskToArchiveEntry(task, archivedAt);
|
|
47197
47411
|
this.archiveDb.upsert(entry);
|
|
47198
47412
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
47199
|
-
await
|
|
47413
|
+
await rm10(this.taskDir(task.id), { recursive: true, force: true });
|
|
47200
47414
|
if (this.isWatching) {
|
|
47201
47415
|
this.taskCache.delete(task.id);
|
|
47202
47416
|
}
|
|
@@ -47223,8 +47437,8 @@ ${newTask.description}
|
|
|
47223
47437
|
this.archiveDb.upsert(entry);
|
|
47224
47438
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
47225
47439
|
this.db.bumpLastModified();
|
|
47226
|
-
const { rm:
|
|
47227
|
-
await
|
|
47440
|
+
const { rm: rm10 } = await import("node:fs/promises");
|
|
47441
|
+
await rm10(dir, { recursive: true, force: true });
|
|
47228
47442
|
if (this.isWatching) {
|
|
47229
47443
|
this.taskCache.delete(task.id);
|
|
47230
47444
|
}
|
|
@@ -47958,6 +48172,16 @@ ${notificationsSection}`;
|
|
|
47958
48172
|
}
|
|
47959
48173
|
return this.todoStore;
|
|
47960
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
|
+
}
|
|
47961
48185
|
/**
|
|
47962
48186
|
* Get the EvalStore instance for eval run and task result operations.
|
|
47963
48187
|
* Lazily initializes the EvalStore on first access.
|
|
@@ -49155,8 +49379,11 @@ function describeAbortReason(reason) {
|
|
|
49155
49379
|
if (typeof reason === "string") return reason;
|
|
49156
49380
|
return "aborted";
|
|
49157
49381
|
}
|
|
49382
|
+
function shouldAutoAppendJsonFlag(args) {
|
|
49383
|
+
return args[0] !== "api" && !args.includes("--json");
|
|
49384
|
+
}
|
|
49158
49385
|
function runGhJson(args, cwd) {
|
|
49159
|
-
const jsonArgs = args
|
|
49386
|
+
const jsonArgs = shouldAutoAppendJsonFlag(args) ? [...args, "--json"] : args;
|
|
49160
49387
|
const output = runGh(jsonArgs, cwd);
|
|
49161
49388
|
try {
|
|
49162
49389
|
return JSON.parse(output);
|
|
@@ -49165,7 +49392,7 @@ function runGhJson(args, cwd) {
|
|
|
49165
49392
|
}
|
|
49166
49393
|
}
|
|
49167
49394
|
async function runGhJsonAsync(args, cwdOrOptions) {
|
|
49168
|
-
const jsonArgs = args
|
|
49395
|
+
const jsonArgs = shouldAutoAppendJsonFlag(args) ? [...args, "--json"] : args;
|
|
49169
49396
|
const output = await runGhAsync(jsonArgs, cwdOrOptions);
|
|
49170
49397
|
try {
|
|
49171
49398
|
return JSON.parse(output);
|
|
@@ -49765,7 +49992,7 @@ var routine_store_exports = {};
|
|
|
49765
49992
|
__export(routine_store_exports, {
|
|
49766
49993
|
RoutineStore: () => RoutineStore
|
|
49767
49994
|
});
|
|
49768
|
-
import { EventEmitter as
|
|
49995
|
+
import { EventEmitter as EventEmitter16 } from "node:events";
|
|
49769
49996
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
49770
49997
|
var import_cron_parser2, CRON_TIMEZONE2, RoutineStore;
|
|
49771
49998
|
var init_routine_store = __esm({
|
|
@@ -49776,7 +50003,7 @@ var init_routine_store = __esm({
|
|
|
49776
50003
|
init_routine();
|
|
49777
50004
|
init_project_root_guard();
|
|
49778
50005
|
CRON_TIMEZONE2 = "UTC";
|
|
49779
|
-
RoutineStore = class _RoutineStore extends
|
|
50006
|
+
RoutineStore = class _RoutineStore extends EventEmitter16 {
|
|
49780
50007
|
constructor(rootDir, options) {
|
|
49781
50008
|
super();
|
|
49782
50009
|
this.rootDir = rootDir;
|
|
@@ -50378,7 +50605,7 @@ import { basename as basename8, dirname as dirname7, extname, isAbsolute as isAb
|
|
|
50378
50605
|
import { stat as stat3 } from "node:fs/promises";
|
|
50379
50606
|
import { copyFile } from "node:fs/promises";
|
|
50380
50607
|
import { pathToFileURL } from "node:url";
|
|
50381
|
-
import { EventEmitter as
|
|
50608
|
+
import { EventEmitter as EventEmitter17 } from "node:events";
|
|
50382
50609
|
var MINIMUM_FUSION_VERSION, moduleImportVersion, PluginLoader;
|
|
50383
50610
|
var init_plugin_loader = __esm({
|
|
50384
50611
|
"../core/src/plugin-loader.ts"() {
|
|
@@ -50389,7 +50616,7 @@ var init_plugin_loader = __esm({
|
|
|
50389
50616
|
init_plugin_security_scan();
|
|
50390
50617
|
MINIMUM_FUSION_VERSION = "0.1.0";
|
|
50391
50618
|
moduleImportVersion = 0;
|
|
50392
|
-
PluginLoader = class extends
|
|
50619
|
+
PluginLoader = class extends EventEmitter17 {
|
|
50393
50620
|
constructor(options) {
|
|
50394
50621
|
super();
|
|
50395
50622
|
this.options = options;
|
|
@@ -55941,15 +56168,15 @@ var require_fd_slicer = __commonJS({
|
|
|
55941
56168
|
var Writable = stream.Writable;
|
|
55942
56169
|
var PassThrough = stream.PassThrough;
|
|
55943
56170
|
var Pend = require_pend();
|
|
55944
|
-
var
|
|
56171
|
+
var EventEmitter35 = __require("events").EventEmitter;
|
|
55945
56172
|
exports.createFromBuffer = createFromBuffer;
|
|
55946
56173
|
exports.createFromFd = createFromFd;
|
|
55947
56174
|
exports.BufferSlicer = BufferSlicer;
|
|
55948
56175
|
exports.FdSlicer = FdSlicer;
|
|
55949
|
-
util.inherits(FdSlicer,
|
|
56176
|
+
util.inherits(FdSlicer, EventEmitter35);
|
|
55950
56177
|
function FdSlicer(fd, options) {
|
|
55951
56178
|
options = options || {};
|
|
55952
|
-
|
|
56179
|
+
EventEmitter35.call(this);
|
|
55953
56180
|
this.fd = fd;
|
|
55954
56181
|
this.pend = new Pend();
|
|
55955
56182
|
this.pend.max = 1;
|
|
@@ -56093,9 +56320,9 @@ var require_fd_slicer = __commonJS({
|
|
|
56093
56320
|
this.destroyed = true;
|
|
56094
56321
|
this.context.unref();
|
|
56095
56322
|
};
|
|
56096
|
-
util.inherits(BufferSlicer,
|
|
56323
|
+
util.inherits(BufferSlicer, EventEmitter35);
|
|
56097
56324
|
function BufferSlicer(buffer, options) {
|
|
56098
|
-
|
|
56325
|
+
EventEmitter35.call(this);
|
|
56099
56326
|
options = options || {};
|
|
56100
56327
|
this.refCount = 0;
|
|
56101
56328
|
this.buffer = buffer;
|
|
@@ -56507,7 +56734,7 @@ var require_yauzl = __commonJS({
|
|
|
56507
56734
|
var fd_slicer = require_fd_slicer();
|
|
56508
56735
|
var crc32 = require_buffer_crc32();
|
|
56509
56736
|
var util = __require("util");
|
|
56510
|
-
var
|
|
56737
|
+
var EventEmitter35 = __require("events").EventEmitter;
|
|
56511
56738
|
var Transform = __require("stream").Transform;
|
|
56512
56739
|
var PassThrough = __require("stream").PassThrough;
|
|
56513
56740
|
var Writable = __require("stream").Writable;
|
|
@@ -56639,10 +56866,10 @@ var require_yauzl = __commonJS({
|
|
|
56639
56866
|
callback(new Error("end of central directory record signature not found"));
|
|
56640
56867
|
});
|
|
56641
56868
|
}
|
|
56642
|
-
util.inherits(ZipFile,
|
|
56869
|
+
util.inherits(ZipFile, EventEmitter35);
|
|
56643
56870
|
function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {
|
|
56644
56871
|
var self = this;
|
|
56645
|
-
|
|
56872
|
+
EventEmitter35.call(self);
|
|
56646
56873
|
self.reader = reader;
|
|
56647
56874
|
self.reader.on("error", function(err) {
|
|
56648
56875
|
emitError(self, err);
|
|
@@ -57003,9 +57230,9 @@ var require_yauzl = __commonJS({
|
|
|
57003
57230
|
}
|
|
57004
57231
|
cb();
|
|
57005
57232
|
};
|
|
57006
|
-
util.inherits(RandomAccessReader,
|
|
57233
|
+
util.inherits(RandomAccessReader, EventEmitter35);
|
|
57007
57234
|
function RandomAccessReader() {
|
|
57008
|
-
|
|
57235
|
+
EventEmitter35.call(this);
|
|
57009
57236
|
this.refCount = 0;
|
|
57010
57237
|
}
|
|
57011
57238
|
RandomAccessReader.prototype.ref = function() {
|
|
@@ -57136,11 +57363,11 @@ var require_extract_zip = __commonJS({
|
|
|
57136
57363
|
var { createWriteStream, promises: fs6 } = __require("fs");
|
|
57137
57364
|
var getStream = require_get_stream();
|
|
57138
57365
|
var path6 = __require("path");
|
|
57139
|
-
var { promisify:
|
|
57366
|
+
var { promisify: promisify51 } = __require("util");
|
|
57140
57367
|
var stream = __require("stream");
|
|
57141
57368
|
var yauzl = require_yauzl();
|
|
57142
|
-
var openZip =
|
|
57143
|
-
var pipeline =
|
|
57369
|
+
var openZip = promisify51(yauzl.open);
|
|
57370
|
+
var pipeline = promisify51(stream.pipeline);
|
|
57144
57371
|
var Extractor = class {
|
|
57145
57372
|
constructor(zipPath, opts) {
|
|
57146
57373
|
this.zipPath = zipPath;
|
|
@@ -57222,7 +57449,7 @@ var require_extract_zip = __commonJS({
|
|
|
57222
57449
|
await fs6.mkdir(destDir, mkdirOptions);
|
|
57223
57450
|
if (isDir) return;
|
|
57224
57451
|
debug("opening read stream", dest);
|
|
57225
|
-
const readStream = await
|
|
57452
|
+
const readStream = await promisify51(this.zipfile.openReadStream.bind(this.zipfile))(entry);
|
|
57226
57453
|
if (symlink) {
|
|
57227
57454
|
const link = await getStream(readStream);
|
|
57228
57455
|
debug("creating symlink", link, dest);
|
|
@@ -68243,14 +68470,14 @@ var init_agent_companies_exporter = __esm({
|
|
|
68243
68470
|
});
|
|
68244
68471
|
|
|
68245
68472
|
// ../core/src/chat-store.ts
|
|
68246
|
-
import { EventEmitter as
|
|
68473
|
+
import { EventEmitter as EventEmitter18 } from "node:events";
|
|
68247
68474
|
import { randomUUID as randomUUID19 } from "node:crypto";
|
|
68248
68475
|
var ChatStore;
|
|
68249
68476
|
var init_chat_store = __esm({
|
|
68250
68477
|
"../core/src/chat-store.ts"() {
|
|
68251
68478
|
"use strict";
|
|
68252
68479
|
init_db();
|
|
68253
|
-
ChatStore = class extends
|
|
68480
|
+
ChatStore = class extends EventEmitter18 {
|
|
68254
68481
|
constructor(fusionDir, db) {
|
|
68255
68482
|
super();
|
|
68256
68483
|
this.fusionDir = fusionDir;
|
|
@@ -68622,7 +68849,7 @@ var init_chat_store = __esm({
|
|
|
68622
68849
|
*
|
|
68623
68850
|
* @param sessionId - Session ID
|
|
68624
68851
|
* @param filter - Optional filter (limit, offset, before cursor)
|
|
68625
|
-
* @returns Array of messages ordered by createdAt ASC
|
|
68852
|
+
* @returns Array of messages ordered by createdAt ASC (default) or DESC
|
|
68626
68853
|
*/
|
|
68627
68854
|
getMessages(sessionId, filter) {
|
|
68628
68855
|
const whereClauses = ["sessionId = ?"];
|
|
@@ -68634,10 +68861,11 @@ var init_chat_store = __esm({
|
|
|
68634
68861
|
const whereSql = whereClauses.join(" AND ");
|
|
68635
68862
|
const limit = filter?.limit ?? 100;
|
|
68636
68863
|
const offset = filter?.offset ?? 0;
|
|
68864
|
+
const order = filter?.order === "desc" ? "DESC" : "ASC";
|
|
68637
68865
|
const rows = this.db.prepare(`
|
|
68638
68866
|
SELECT * FROM chat_messages
|
|
68639
68867
|
WHERE ${whereSql}
|
|
68640
|
-
ORDER BY createdAt
|
|
68868
|
+
ORDER BY createdAt ${order}
|
|
68641
68869
|
LIMIT ? OFFSET ?
|
|
68642
68870
|
`).all(...params, limit, offset);
|
|
68643
68871
|
return rows.map((row) => this.rowToMessage(row));
|
|
@@ -69341,6 +69569,7 @@ var init_secrets_sync = __esm({
|
|
|
69341
69569
|
// ../core/src/index.ts
|
|
69342
69570
|
var src_exports = {};
|
|
69343
69571
|
__export(src_exports, {
|
|
69572
|
+
ACTIVE_GOAL_LIMIT: () => ACTIVE_GOAL_LIMIT,
|
|
69344
69573
|
AGENT_PERMISSIONS: () => AGENT_PERMISSIONS,
|
|
69345
69574
|
AGENT_PERMISSION_POLICY_ACTION_CATEGORIES: () => AGENT_PERMISSION_POLICY_ACTION_CATEGORIES,
|
|
69346
69575
|
AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES: () => AGENT_PERMISSION_POLICY_CATEGORY_TOOL_EXAMPLES,
|
|
@@ -69354,6 +69583,7 @@ __export(src_exports, {
|
|
|
69354
69583
|
AUTOPILOT_STATES: () => AUTOPILOT_STATES,
|
|
69355
69584
|
AUTO_RECOVERY_MODES: () => AUTO_RECOVERY_MODES,
|
|
69356
69585
|
AUTO_SUMMARIZE_SCHEDULE_NAME: () => AUTO_SUMMARIZE_SCHEDULE_NAME,
|
|
69586
|
+
ActiveGoalLimitExceededError: () => ActiveGoalLimitExceededError,
|
|
69357
69587
|
AgentCompaniesParseError: () => AgentCompaniesParseError,
|
|
69358
69588
|
AgentStore: () => AgentStore,
|
|
69359
69589
|
AiServiceError: () => AiServiceError,
|
|
@@ -69447,6 +69677,7 @@ __export(src_exports, {
|
|
|
69447
69677
|
FirstRunDetector: () => FirstRunDetector,
|
|
69448
69678
|
GLOBAL_SETTINGS_KEYS: () => GLOBAL_SETTINGS_KEYS,
|
|
69449
69679
|
GlobalSettingsStore: () => GlobalSettingsStore,
|
|
69680
|
+
GoalStore: () => GoalStore,
|
|
69450
69681
|
HIGH_FANOUT_BLOCKER_TODO_THRESHOLD: () => HIGH_FANOUT_BLOCKER_TODO_THRESHOLD,
|
|
69451
69682
|
HandoffInvariantViolationError: () => HandoffInvariantViolationError,
|
|
69452
69683
|
INSIGHT_EXTRACTION_SCHEDULE_NAME: () => INSIGHT_EXTRACTION_SCHEDULE_NAME,
|
|
@@ -69481,6 +69712,7 @@ __export(src_exports, {
|
|
|
69481
69712
|
MEMORY_LONG_TERM_FILENAME: () => MEMORY_LONG_TERM_FILENAME,
|
|
69482
69713
|
MEMORY_WORKING_PATH: () => MEMORY_WORKING_PATH,
|
|
69483
69714
|
MEMORY_WORKSPACE_PATH: () => MEMORY_WORKSPACE_PATH,
|
|
69715
|
+
MERGER_MODES: () => MERGER_MODES,
|
|
69484
69716
|
MERGE_ADVANCE_AUTO_SYNC_MODES: () => MERGE_ADVANCE_AUTO_SYNC_MODES,
|
|
69485
69717
|
MERGE_AUDIT_AUTO_RECOVERY_MODES: () => MERGE_AUDIT_AUTO_RECOVERY_MODES,
|
|
69486
69718
|
MERGE_COMMIT_SUMMARIZE_SYSTEM_PROMPT: () => MERGE_COMMIT_SUMMARIZE_SYSTEM_PROMPT,
|
|
@@ -69670,6 +69902,7 @@ __export(src_exports, {
|
|
|
69670
69902
|
createSecretCipher: () => createSecretCipher,
|
|
69671
69903
|
createTaskMetadataSnapshot: () => createTaskMetadataSnapshot,
|
|
69672
69904
|
currentBackupTimestamp: () => currentBackupTimestamp,
|
|
69905
|
+
customProviderRegistryKey: () => customProviderRegistryKey,
|
|
69673
69906
|
dailyMemoryPath: () => dailyMemoryPath,
|
|
69674
69907
|
detectDependencyCycle: () => detectDependencyCycle,
|
|
69675
69908
|
detectFnBinary: () => detectFnBinary,
|
|
@@ -69827,6 +70060,7 @@ __export(src_exports, {
|
|
|
69827
70060
|
normalizeMergeConflictStrategy: () => normalizeMergeConflictStrategy,
|
|
69828
70061
|
normalizeMergeIntegrationWorktreeMode: () => normalizeMergeIntegrationWorktreeMode,
|
|
69829
70062
|
normalizeMergeStrategyOverlapBehavior: () => normalizeMergeStrategyOverlapBehavior,
|
|
70063
|
+
normalizeMergerMode: () => normalizeMergerMode,
|
|
69830
70064
|
normalizeMessageParticipant: () => normalizeMessageParticipant,
|
|
69831
70065
|
normalizePermissions: () => normalizePermissions,
|
|
69832
70066
|
normalizePluginUiContributionDefinition: () => normalizePluginUiContributionDefinition,
|
|
@@ -69995,6 +70229,7 @@ var init_src = __esm({
|
|
|
69995
70229
|
"use strict";
|
|
69996
70230
|
init_types();
|
|
69997
70231
|
init_types();
|
|
70232
|
+
init_custom_provider_key();
|
|
69998
70233
|
init_mock_provider_constants();
|
|
69999
70234
|
init_worktrunk_settings();
|
|
70000
70235
|
init_agent_memory_mode();
|
|
@@ -70083,6 +70318,8 @@ var init_src = __esm({
|
|
|
70083
70318
|
init_task_priority();
|
|
70084
70319
|
init_mission_types();
|
|
70085
70320
|
init_mission_store();
|
|
70321
|
+
init_goal_types();
|
|
70322
|
+
init_goal_store();
|
|
70086
70323
|
init_central_core();
|
|
70087
70324
|
init_central_db();
|
|
70088
70325
|
init_node_connection();
|
|
@@ -70713,15 +70950,15 @@ function evaluateAgentActionGate(params) {
|
|
|
70713
70950
|
let resourceId;
|
|
70714
70951
|
if (params.toolName === "bash") {
|
|
70715
70952
|
const command = extractShellCommand(args);
|
|
70716
|
-
const
|
|
70717
|
-
if (
|
|
70953
|
+
const git3 = classifyGitCommand(command);
|
|
70954
|
+
if (git3?.write) {
|
|
70718
70955
|
category = "git_write";
|
|
70719
|
-
operation =
|
|
70956
|
+
operation = git3.operation;
|
|
70720
70957
|
resourceType = "git";
|
|
70721
70958
|
} else {
|
|
70722
70959
|
category = "command_execution";
|
|
70723
|
-
operation =
|
|
70724
|
-
resourceType =
|
|
70960
|
+
operation = git3?.operation ?? "shell command";
|
|
70961
|
+
resourceType = git3 ? "git" : "command";
|
|
70725
70962
|
}
|
|
70726
70963
|
} else if (params.toolName === "write" || params.toolName === "edit") {
|
|
70727
70964
|
category = "file_write_delete";
|
|
@@ -71961,8 +72198,8 @@ var init_context_limit_detector = __esm({
|
|
|
71961
72198
|
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
71962
72199
|
import { homedir as homedir5 } from "node:os";
|
|
71963
72200
|
import { join as join29 } from "node:path";
|
|
71964
|
-
import { AuthStorage } from "@
|
|
71965
|
-
import { getOAuthProvider } from "@
|
|
72201
|
+
import { AuthStorage } from "@earendil-works/pi-coding-agent";
|
|
72202
|
+
import { getOAuthProvider } from "@earendil-works/pi-ai/oauth";
|
|
71966
72203
|
function getHomeDir5() {
|
|
71967
72204
|
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
71968
72205
|
}
|
|
@@ -72349,7 +72586,7 @@ var init_workflow_step_tool_policy = __esm({
|
|
|
72349
72586
|
|
|
72350
72587
|
// ../engine/src/pi.ts
|
|
72351
72588
|
import { existsSync as existsSync25, readFileSync as readFileSync10 } from "node:fs";
|
|
72352
|
-
import { exec as exec2 } from "node:child_process";
|
|
72589
|
+
import { exec as exec2, execFile as execFile3 } from "node:child_process";
|
|
72353
72590
|
import { promisify as promisify3 } from "node:util";
|
|
72354
72591
|
import { createRequire as createRequire3 } from "node:module";
|
|
72355
72592
|
import { basename as basename9, dirname as dirname11, join as join31, relative as relative4, isAbsolute as isAbsolute8, resolve as resolve15 } from "node:path";
|
|
@@ -72371,7 +72608,61 @@ import {
|
|
|
72371
72608
|
ModelRegistry,
|
|
72372
72609
|
SessionManager,
|
|
72373
72610
|
SettingsManager
|
|
72374
|
-
} from "@
|
|
72611
|
+
} from "@earendil-works/pi-coding-agent";
|
|
72612
|
+
function normalizeRtkRewriteOptions(options) {
|
|
72613
|
+
const modeEnv = process.env.FUSION_RTK_REWRITE?.toLowerCase();
|
|
72614
|
+
const envMode = modeEnv === "1" || modeEnv === "true" || modeEnv === "rewrite" ? "rewrite" : "off";
|
|
72615
|
+
const envTimeoutMs = Number.parseInt(process.env.FUSION_RTK_REWRITE_TIMEOUT_MS ?? "2000", 10);
|
|
72616
|
+
const requestedTimeoutMs = options?.timeoutMs ?? envTimeoutMs;
|
|
72617
|
+
return {
|
|
72618
|
+
mode: options?.mode ?? envMode,
|
|
72619
|
+
timeoutMs: Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0 ? requestedTimeoutMs : 2e3
|
|
72620
|
+
};
|
|
72621
|
+
}
|
|
72622
|
+
function resolveRtkRewriteOptions() {
|
|
72623
|
+
return normalizeRtkRewriteOptions();
|
|
72624
|
+
}
|
|
72625
|
+
function getRtkErrorCode(error) {
|
|
72626
|
+
if (!error) return 0;
|
|
72627
|
+
const rawCode = error.code;
|
|
72628
|
+
if (typeof rawCode === "number" || typeof rawCode === "string") return rawCode;
|
|
72629
|
+
return null;
|
|
72630
|
+
}
|
|
72631
|
+
function shouldWarnForRtkFailure(code) {
|
|
72632
|
+
if (typeof code === "number") return !RTK_EXPECTED_PASSTHROUGH_EXIT_CODES.has(code);
|
|
72633
|
+
if (typeof code === "string") return !RTK_EXPECTED_FAIL_OPEN_ERROR_CODES.has(code);
|
|
72634
|
+
return true;
|
|
72635
|
+
}
|
|
72636
|
+
function rewriteCommandWithRtk(command, options, signal) {
|
|
72637
|
+
if (signal?.aborted) {
|
|
72638
|
+
return Promise.resolve(null);
|
|
72639
|
+
}
|
|
72640
|
+
return new Promise((resolve32) => {
|
|
72641
|
+
execFile3(
|
|
72642
|
+
"rtk",
|
|
72643
|
+
["rewrite", command],
|
|
72644
|
+
{
|
|
72645
|
+
timeout: options.timeoutMs,
|
|
72646
|
+
maxBuffer: RTK_REWRITE_MAX_BUFFER_BYTES,
|
|
72647
|
+
signal,
|
|
72648
|
+
windowsHide: true
|
|
72649
|
+
},
|
|
72650
|
+
(error, stdout, stderr) => {
|
|
72651
|
+
const code = getRtkErrorCode(error);
|
|
72652
|
+
if (typeof code !== "number" || !RTK_ACCEPTED_REWRITE_EXIT_CODES.has(code)) {
|
|
72653
|
+
if (shouldWarnForRtkFailure(code)) {
|
|
72654
|
+
const reason = stderr?.toString().trim() || (error instanceof Error ? error.message : `exit ${String(code)}`);
|
|
72655
|
+
piLog.warn(`[pi] rtk rewrite failed open: ${reason}`);
|
|
72656
|
+
}
|
|
72657
|
+
resolve32(null);
|
|
72658
|
+
return;
|
|
72659
|
+
}
|
|
72660
|
+
const rewritten = stdout.toString().trim();
|
|
72661
|
+
resolve32(rewritten && rewritten !== command ? rewritten : null);
|
|
72662
|
+
}
|
|
72663
|
+
);
|
|
72664
|
+
});
|
|
72665
|
+
}
|
|
72375
72666
|
function setHostExtensionPaths(paths) {
|
|
72376
72667
|
hostExtensionPaths = [...paths];
|
|
72377
72668
|
}
|
|
@@ -73272,6 +73563,36 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
73272
73563
|
};
|
|
73273
73564
|
});
|
|
73274
73565
|
}
|
|
73566
|
+
function wrapToolsWithRtkRewrite(tools, options = resolveRtkRewriteOptions()) {
|
|
73567
|
+
const resolvedOptions = normalizeRtkRewriteOptions(options);
|
|
73568
|
+
if (resolvedOptions.mode !== "rewrite") {
|
|
73569
|
+
return tools;
|
|
73570
|
+
}
|
|
73571
|
+
return tools.map((tool) => {
|
|
73572
|
+
if (tool.name !== "bash") {
|
|
73573
|
+
return tool;
|
|
73574
|
+
}
|
|
73575
|
+
const originalExecute = tool.execute;
|
|
73576
|
+
return {
|
|
73577
|
+
...tool,
|
|
73578
|
+
execute: async (...args) => {
|
|
73579
|
+
const params = args[1];
|
|
73580
|
+
const command = params?.command;
|
|
73581
|
+
if (typeof command !== "string" || !command.trim()) {
|
|
73582
|
+
return originalExecute(...args);
|
|
73583
|
+
}
|
|
73584
|
+
const signal = args[2];
|
|
73585
|
+
const rewrittenCommand = await rewriteCommandWithRtk(command, resolvedOptions, signal);
|
|
73586
|
+
if (!rewrittenCommand) {
|
|
73587
|
+
return originalExecute(...args);
|
|
73588
|
+
}
|
|
73589
|
+
const rewrittenArgs = [...args];
|
|
73590
|
+
rewrittenArgs[1] = { ...params ?? {}, command: rewrittenCommand };
|
|
73591
|
+
return originalExecute(...rewrittenArgs);
|
|
73592
|
+
}
|
|
73593
|
+
};
|
|
73594
|
+
});
|
|
73595
|
+
}
|
|
73275
73596
|
function wrapToolsWithPermanentAgentGating(tools, gating) {
|
|
73276
73597
|
if (!gating) {
|
|
73277
73598
|
return tools;
|
|
@@ -73407,11 +73728,13 @@ async function createFnAgent2(options) {
|
|
|
73407
73728
|
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
|
73408
73729
|
const resolvedProjectRoot = getProjectRootFromWorktree(options.cwd) ?? resolvePiExtensionProjectRoot(options.cwd);
|
|
73409
73730
|
await registerExtensionProviders(resolvedProjectRoot, modelRegistry);
|
|
73410
|
-
|
|
73731
|
+
const customProviders = readCustomProviders();
|
|
73732
|
+
for (const provider of customProviders) {
|
|
73411
73733
|
try {
|
|
73412
|
-
|
|
73734
|
+
const registryKey2 = customProviderRegistryKey(provider, customProviders);
|
|
73735
|
+
modelRegistry.registerProvider(registryKey2, {
|
|
73413
73736
|
baseUrl: provider.baseUrl,
|
|
73414
|
-
api: provider.apiType === "anthropic-compatible" ? "anthropic" : "openai-completions",
|
|
73737
|
+
api: provider.apiType === "anthropic-compatible" ? "anthropic" : provider.apiType === "openai-responses" ? "openai-responses" : "openai-completions",
|
|
73415
73738
|
apiKey: provider.apiKey,
|
|
73416
73739
|
models: (provider.models ?? []).map((model) => ({
|
|
73417
73740
|
id: model.id,
|
|
@@ -73428,10 +73751,11 @@ async function createFnAgent2(options) {
|
|
|
73428
73751
|
maxTokens: 16384
|
|
73429
73752
|
}))
|
|
73430
73753
|
});
|
|
73431
|
-
piLog.log(`Registered custom provider ${provider.id}`);
|
|
73754
|
+
piLog.log(`Registered custom provider "${provider.name}" (key=${registryKey2}, id=${provider.id})`);
|
|
73432
73755
|
} catch (error) {
|
|
73433
73756
|
const message = error instanceof Error ? error.message : String(error);
|
|
73434
|
-
|
|
73757
|
+
const registryKey2 = customProviderRegistryKey(provider, customProviders);
|
|
73758
|
+
piLog.warn(`Failed to register custom provider "${provider.name}" (key=${registryKey2}, id=${provider.id}): ${message}`);
|
|
73435
73759
|
}
|
|
73436
73760
|
}
|
|
73437
73761
|
modelRegistry.refresh();
|
|
@@ -73550,8 +73874,9 @@ async function createFnAgent2(options) {
|
|
|
73550
73874
|
...tools,
|
|
73551
73875
|
...readonlyFilteredCustomTools.allowed
|
|
73552
73876
|
];
|
|
73877
|
+
const toolsWithRtkRewrite = wrapToolsWithRtkRewrite(toolChainStart);
|
|
73553
73878
|
const toolsWithPermanentGating = wrapToolsWithPermanentAgentGating(
|
|
73554
|
-
|
|
73879
|
+
toolsWithRtkRewrite,
|
|
73555
73880
|
options.permanentAgentGating
|
|
73556
73881
|
);
|
|
73557
73882
|
const toolsWithActionGate = wrapToolsWithActionGate(
|
|
@@ -73818,7 +74143,7 @@ async function createFnAgent2(options) {
|
|
|
73818
74143
|
});
|
|
73819
74144
|
return { session: promptableSession, sessionFile: promptableSession.sessionFile };
|
|
73820
74145
|
}
|
|
73821
|
-
var execAsync, hostExtensionPaths, FN_MEMORY_APPEND_TOOL_NAME, FUSION_SHUTDOWN_WRAP_FLAG, promptWithFallbackInFlight, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS, MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS, MAX_COMPACTED_ATTACHMENTS_CHARS, MAX_COMPACTED_EXISTING_SPEC_CHARS, MAX_COMPACTED_TASK_PROMPT_CHARS, MAX_COMPACTED_USER_COMMENTS_CHARS, GATE_BYPASS_TOOL_NAMES;
|
|
74146
|
+
var execAsync, RTK_ACCEPTED_REWRITE_EXIT_CODES, RTK_EXPECTED_PASSTHROUGH_EXIT_CODES, RTK_EXPECTED_FAIL_OPEN_ERROR_CODES, RTK_REWRITE_MAX_BUFFER_BYTES, hostExtensionPaths, FN_MEMORY_APPEND_TOOL_NAME, FUSION_SHUTDOWN_WRAP_FLAG, promptWithFallbackInFlight, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS, MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS, MAX_COMPACTED_ATTACHMENTS_CHARS, MAX_COMPACTED_EXISTING_SPEC_CHARS, MAX_COMPACTED_TASK_PROMPT_CHARS, MAX_COMPACTED_USER_COMMENTS_CHARS, GATE_BYPASS_TOOL_NAMES;
|
|
73822
74147
|
var init_pi = __esm({
|
|
73823
74148
|
"../engine/src/pi.ts"() {
|
|
73824
74149
|
"use strict";
|
|
@@ -73832,6 +74157,10 @@ var init_pi = __esm({
|
|
|
73832
74157
|
init_permanent_agent_gating();
|
|
73833
74158
|
init_workflow_step_tool_policy();
|
|
73834
74159
|
execAsync = promisify3(exec2);
|
|
74160
|
+
RTK_ACCEPTED_REWRITE_EXIT_CODES = /* @__PURE__ */ new Set([0, 3]);
|
|
74161
|
+
RTK_EXPECTED_PASSTHROUGH_EXIT_CODES = /* @__PURE__ */ new Set([1, 2]);
|
|
74162
|
+
RTK_EXPECTED_FAIL_OPEN_ERROR_CODES = /* @__PURE__ */ new Set(["ABORT_ERR", "ENOENT", "ETIMEDOUT"]);
|
|
74163
|
+
RTK_REWRITE_MAX_BUFFER_BYTES = 64 * 1024;
|
|
73835
74164
|
hostExtensionPaths = [];
|
|
73836
74165
|
FN_MEMORY_APPEND_TOOL_NAME = "fn_memory_append";
|
|
73837
74166
|
FUSION_SHUTDOWN_WRAP_FLAG = "__fusionSessionShutdownDisposeWrapped";
|
|
@@ -74809,9 +75138,9 @@ var init_web_search_provider = __esm({
|
|
|
74809
75138
|
if (!isRetryableHttp || attempt >= RETRY_MAX_ATTEMPTS) {
|
|
74810
75139
|
break;
|
|
74811
75140
|
}
|
|
74812
|
-
const
|
|
74813
|
-
const jitter =
|
|
74814
|
-
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);
|
|
74815
75144
|
log9.warn(`retrying ${backend} search`, { attempt, totalDelay });
|
|
74816
75145
|
await sleep2(totalDelay, signal);
|
|
74817
75146
|
}
|
|
@@ -75265,7 +75594,7 @@ import { appendFile as appendFile3, mkdir as mkdir12, readFile as readFile14, re
|
|
|
75265
75594
|
import { existsSync as existsSync26 } from "node:fs";
|
|
75266
75595
|
import { createHash as createHash8 } from "node:crypto";
|
|
75267
75596
|
import { join as join33, relative as relative6, resolve as resolve17 } from "node:path";
|
|
75268
|
-
import { Type } from "@
|
|
75597
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
75269
75598
|
function sanitizeAgentMemoryId(agentId) {
|
|
75270
75599
|
return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
75271
75600
|
}
|
|
@@ -75435,11 +75764,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
|
|
|
75435
75764
|
return;
|
|
75436
75765
|
}
|
|
75437
75766
|
const promise = (async () => {
|
|
75438
|
-
const { execFile:
|
|
75439
|
-
const { promisify:
|
|
75440
|
-
const
|
|
75767
|
+
const { execFile: execFile20 } = await import("node:child_process");
|
|
75768
|
+
const { promisify: promisify51 } = await import("node:util");
|
|
75769
|
+
const execFileAsync15 = promisify51(execFile20);
|
|
75441
75770
|
try {
|
|
75442
|
-
await
|
|
75771
|
+
await execFileAsync15("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
|
|
75443
75772
|
cwd: rootDir,
|
|
75444
75773
|
timeout: 4e3,
|
|
75445
75774
|
maxBuffer: 512 * 1024
|
|
@@ -75452,8 +75781,8 @@ ${stderr}`)) {
|
|
|
75452
75781
|
throw error;
|
|
75453
75782
|
}
|
|
75454
75783
|
}
|
|
75455
|
-
await
|
|
75456
|
-
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 });
|
|
75457
75786
|
})();
|
|
75458
75787
|
agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
75459
75788
|
try {
|
|
@@ -75508,10 +75837,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
|
|
|
75508
75837
|
}
|
|
75509
75838
|
try {
|
|
75510
75839
|
await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
|
|
75511
|
-
const { execFile:
|
|
75512
|
-
const { promisify:
|
|
75513
|
-
const
|
|
75514
|
-
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), {
|
|
75515
75844
|
cwd: rootDir,
|
|
75516
75845
|
timeout: 4e3,
|
|
75517
75846
|
maxBuffer: 1024 * 1024
|
|
@@ -79015,6 +79344,24 @@ var init_webhook_provider = __esm({
|
|
|
79015
79344
|
}
|
|
79016
79345
|
});
|
|
79017
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
|
+
|
|
79018
79365
|
// ../engine/src/notification/notification-service.ts
|
|
79019
79366
|
var NotificationService;
|
|
79020
79367
|
var init_notification_service = __esm({
|
|
@@ -79023,6 +79370,7 @@ var init_notification_service = __esm({
|
|
|
79023
79370
|
init_src();
|
|
79024
79371
|
init_notifier();
|
|
79025
79372
|
init_logger2();
|
|
79373
|
+
init_transient_merge_error_classifier();
|
|
79026
79374
|
init_ntfy_provider();
|
|
79027
79375
|
init_webhook_provider();
|
|
79028
79376
|
NotificationService = class {
|
|
@@ -79124,6 +79472,14 @@ var init_notification_service = __esm({
|
|
|
79124
79472
|
return;
|
|
79125
79473
|
}
|
|
79126
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
|
+
}
|
|
79127
79483
|
if (this.failureNotificationMode === "all") {
|
|
79128
79484
|
this.maybeNotify(task.id, "failed", this.createTaskPayload(task, "failed"));
|
|
79129
79485
|
} else {
|
|
@@ -79440,6 +79796,14 @@ var init_notification_service = __esm({
|
|
|
79440
79796
|
schedulerLog.log(`[notify] ${taskId} no longer failed at dispatch time \u2014 suppressed notification`);
|
|
79441
79797
|
return;
|
|
79442
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
|
+
}
|
|
79443
79807
|
const isTerminal = task.paused === true || task.column === "in-review";
|
|
79444
79808
|
if (this.failureNotificationMode === "terminal-only" && !isTerminal) {
|
|
79445
79809
|
this.failureNotificationSuppressedCount += 1;
|
|
@@ -79566,6 +79930,69 @@ var init_oauth_expiry_monitor = __esm({
|
|
|
79566
79930
|
}
|
|
79567
79931
|
});
|
|
79568
79932
|
|
|
79933
|
+
// ../engine/src/notification/oauth-validity-logger.ts
|
|
79934
|
+
var DEFAULT_INTERVAL_MS2, OAuthValidityLogger;
|
|
79935
|
+
var init_oauth_validity_logger = __esm({
|
|
79936
|
+
"../engine/src/notification/oauth-validity-logger.ts"() {
|
|
79937
|
+
"use strict";
|
|
79938
|
+
init_logger2();
|
|
79939
|
+
DEFAULT_INTERVAL_MS2 = 24 * 60 * 60 * 1e3;
|
|
79940
|
+
OAuthValidityLogger = class {
|
|
79941
|
+
constructor(opts) {
|
|
79942
|
+
this.opts = opts;
|
|
79943
|
+
this.intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS2;
|
|
79944
|
+
this.clock = opts.clock ?? Date.now;
|
|
79945
|
+
this.logger = opts.logger ?? ((message, meta) => schedulerLog.warn(message, meta));
|
|
79946
|
+
}
|
|
79947
|
+
intervalMs;
|
|
79948
|
+
clock;
|
|
79949
|
+
logger;
|
|
79950
|
+
timer = null;
|
|
79951
|
+
async start() {
|
|
79952
|
+
if (this.timer) {
|
|
79953
|
+
return;
|
|
79954
|
+
}
|
|
79955
|
+
await this.check();
|
|
79956
|
+
this.timer = setInterval(() => {
|
|
79957
|
+
void this.check();
|
|
79958
|
+
}, this.intervalMs);
|
|
79959
|
+
this.timer.unref?.();
|
|
79960
|
+
}
|
|
79961
|
+
stop() {
|
|
79962
|
+
if (!this.timer) {
|
|
79963
|
+
return;
|
|
79964
|
+
}
|
|
79965
|
+
clearInterval(this.timer);
|
|
79966
|
+
this.timer = null;
|
|
79967
|
+
}
|
|
79968
|
+
async check() {
|
|
79969
|
+
this.opts.authStorage.reload?.();
|
|
79970
|
+
const providers = this.opts.authStorage.getOAuthProviders?.() ?? [];
|
|
79971
|
+
const now = this.clock();
|
|
79972
|
+
for (const provider of providers) {
|
|
79973
|
+
try {
|
|
79974
|
+
const credential = this.opts.authStorage.get?.(provider.id);
|
|
79975
|
+
if (credential?.type !== "oauth" || typeof credential.expires !== "number") {
|
|
79976
|
+
continue;
|
|
79977
|
+
}
|
|
79978
|
+
if (credential.expires > now) {
|
|
79979
|
+
continue;
|
|
79980
|
+
}
|
|
79981
|
+
this.logger("oauth credential expired \u2014 provider re-login required", {
|
|
79982
|
+
providerId: provider.id,
|
|
79983
|
+
providerName: provider.name,
|
|
79984
|
+
expiresAt: new Date(credential.expires).toISOString()
|
|
79985
|
+
});
|
|
79986
|
+
} catch (error) {
|
|
79987
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
79988
|
+
schedulerLog.warn(`OAuth validity logger failed for provider=${provider.id}: ${message}`);
|
|
79989
|
+
}
|
|
79990
|
+
}
|
|
79991
|
+
}
|
|
79992
|
+
};
|
|
79993
|
+
}
|
|
79994
|
+
});
|
|
79995
|
+
|
|
79569
79996
|
// ../engine/src/notification/index.ts
|
|
79570
79997
|
var init_notification = __esm({
|
|
79571
79998
|
"../engine/src/notification/index.ts"() {
|
|
@@ -79574,6 +80001,7 @@ var init_notification = __esm({
|
|
|
79574
80001
|
init_webhook_provider();
|
|
79575
80002
|
init_notification_service();
|
|
79576
80003
|
init_oauth_expiry_monitor();
|
|
80004
|
+
init_oauth_validity_logger();
|
|
79577
80005
|
}
|
|
79578
80006
|
});
|
|
79579
80007
|
|
|
@@ -79628,6 +80056,19 @@ function truncateNtfyMessage(message) {
|
|
|
79628
80056
|
}
|
|
79629
80057
|
return best;
|
|
79630
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
|
+
}
|
|
79631
80072
|
function resolveNtfyEvents(events) {
|
|
79632
80073
|
return events ? [...events] : [...DEFAULT_NTFY_EVENTS];
|
|
79633
80074
|
}
|
|
@@ -79677,10 +80118,10 @@ async function sendNtfyNotificationWithResult({
|
|
|
79677
80118
|
const truncatedMessage = truncateNtfyMessage(message);
|
|
79678
80119
|
const latin1Safe = isLatin1Safe(truncatedTitle) && isLatin1Safe(truncatedMessage);
|
|
79679
80120
|
const headers = {
|
|
79680
|
-
Priority: priority,
|
|
79681
80121
|
"Content-Type": latin1Safe ? "text/plain" : "application/json"
|
|
79682
80122
|
};
|
|
79683
80123
|
if (latin1Safe) {
|
|
80124
|
+
headers.Priority = priority;
|
|
79684
80125
|
headers.Title = truncatedTitle;
|
|
79685
80126
|
if (clickUrl) {
|
|
79686
80127
|
headers.Click = clickUrl;
|
|
@@ -79696,7 +80137,7 @@ async function sendNtfyNotificationWithResult({
|
|
|
79696
80137
|
topic,
|
|
79697
80138
|
title: truncatedTitle,
|
|
79698
80139
|
message: truncatedMessage,
|
|
79699
|
-
priority,
|
|
80140
|
+
priority: ntfyPriorityToInt(priority),
|
|
79700
80141
|
...clickUrl ? { click: clickUrl } : {}
|
|
79701
80142
|
}),
|
|
79702
80143
|
signal
|
|
@@ -83389,7 +83830,7 @@ ${getErrorStderr(error) ?? ""}`;
|
|
|
83389
83830
|
import { createHash as createHash9 } from "node:crypto";
|
|
83390
83831
|
import { promises as fs5 } from "node:fs";
|
|
83391
83832
|
import path2 from "node:path";
|
|
83392
|
-
import { execFile as
|
|
83833
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
83393
83834
|
function isValidFilename(filename) {
|
|
83394
83835
|
return !!filename && !filename.includes("/") && !filename.includes("\\") && !filename.includes("..") && !filename.includes("\0") && filename !== FINGERPRINT_FILE;
|
|
83395
83836
|
}
|
|
@@ -83450,7 +83891,7 @@ async function writeSecretsEnvFile(opts) {
|
|
|
83450
83891
|
} catch {
|
|
83451
83892
|
}
|
|
83452
83893
|
if (cfg?.requireGitignored !== false) {
|
|
83453
|
-
const check = await checkIgnored(opts.execFileImpl ??
|
|
83894
|
+
const check = await checkIgnored(opts.execFileImpl ?? execFile4, opts.worktreePath, filename);
|
|
83454
83895
|
if (!check.ignored) {
|
|
83455
83896
|
await opts.audit?.filesystem({ type: "secret:env-write-skipped", target: opts.taskId, metadata: { filename, reason: "not-gitignored", overwritePolicy, checkIgnoreError: check.error } });
|
|
83456
83897
|
return { outcome: "skipped", filename, reason: "not-gitignored" };
|
|
@@ -84968,11 +85409,11 @@ var init_step_session_executor = __esm({
|
|
|
84968
85409
|
}
|
|
84969
85410
|
if (attempt > 0) {
|
|
84970
85411
|
retries++;
|
|
84971
|
-
const
|
|
85412
|
+
const delay5 = RETRY_DELAYS_MS[attempt - 1] ?? 15e3;
|
|
84972
85413
|
stepExecLog.log(
|
|
84973
|
-
`Step ${stepIndex} retry ${attempt}/${MAX_STEP_RETRIES} (delay=${
|
|
85414
|
+
`Step ${stepIndex} retry ${attempt}/${MAX_STEP_RETRIES} (delay=${delay5}ms) for task ${taskDetail.id}`
|
|
84974
85415
|
);
|
|
84975
|
-
await sleep3(
|
|
85416
|
+
await sleep3(delay5);
|
|
84976
85417
|
}
|
|
84977
85418
|
const agentLogger = new AgentLogger({
|
|
84978
85419
|
store: this.store,
|
|
@@ -85738,9 +86179,9 @@ async function withRateLimitRetry(fn2, options = {}) {
|
|
|
85738
86179
|
}
|
|
85739
86180
|
const rawDelay = Math.min(baseDelayMs * 2 ** attempt, maxDelayMs);
|
|
85740
86181
|
const jitter = rawDelay * 0.1 * (2 * Math.random() - 1);
|
|
85741
|
-
const
|
|
85742
|
-
onRetry?.(attempt + 1,
|
|
85743
|
-
await sleep4(
|
|
86182
|
+
const delay5 = Math.max(0, Math.round(rawDelay + jitter));
|
|
86183
|
+
onRetry?.(attempt + 1, delay5, error);
|
|
86184
|
+
await sleep4(delay5, signal);
|
|
85744
86185
|
}
|
|
85745
86186
|
}
|
|
85746
86187
|
throw lastError ?? new Error("withRateLimitRetry: unexpected state");
|
|
@@ -88398,7 +88839,7 @@ var init_session_token_usage = __esm({
|
|
|
88398
88839
|
});
|
|
88399
88840
|
|
|
88400
88841
|
// ../engine/src/merger-squash-audit.ts
|
|
88401
|
-
import { execFile as
|
|
88842
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
88402
88843
|
import { promisify as promisify24 } from "node:util";
|
|
88403
88844
|
async function auditSquashMerge(input) {
|
|
88404
88845
|
const normalizedLookback = normalizeMergeOverlapLookback(input.lookback);
|
|
@@ -88554,14 +88995,14 @@ var execFileAsync, MERGER_MAIN_OVERLAP_LOOKBACK_COMMITS, GIT_OUTPUT_MAX_BUFFER;
|
|
|
88554
88995
|
var init_merger_squash_audit = __esm({
|
|
88555
88996
|
"../engine/src/merger-squash-audit.ts"() {
|
|
88556
88997
|
"use strict";
|
|
88557
|
-
execFileAsync = promisify24(
|
|
88998
|
+
execFileAsync = promisify24(execFile6);
|
|
88558
88999
|
MERGER_MAIN_OVERLAP_LOOKBACK_COMMITS = 30;
|
|
88559
89000
|
GIT_OUTPUT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
88560
89001
|
}
|
|
88561
89002
|
});
|
|
88562
89003
|
|
|
88563
89004
|
// ../engine/src/merger-overlap-guard.ts
|
|
88564
|
-
import { execFile as
|
|
89005
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
88565
89006
|
import { promisify as promisify25 } from "node:util";
|
|
88566
89007
|
async function getBranchTouchedFiles({
|
|
88567
89008
|
rootDir,
|
|
@@ -88687,13 +89128,13 @@ var init_merger_overlap_guard = __esm({
|
|
|
88687
89128
|
"use strict";
|
|
88688
89129
|
init_logger2();
|
|
88689
89130
|
init_merger_squash_audit();
|
|
88690
|
-
execFileAsync2 = promisify25(
|
|
89131
|
+
execFileAsync2 = promisify25(execFile7);
|
|
88691
89132
|
GIT_OUTPUT_MAX_BUFFER2 = 10 * 1024 * 1024;
|
|
88692
89133
|
}
|
|
88693
89134
|
});
|
|
88694
89135
|
|
|
88695
89136
|
// ../engine/src/merger-diff-volume-gate.ts
|
|
88696
|
-
import { execFile as
|
|
89137
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
88697
89138
|
import { promisify as promisify26 } from "node:util";
|
|
88698
89139
|
function buildMessage(findings) {
|
|
88699
89140
|
const details = findings.map((finding) => `${finding.file} (branch_net=${finding.branchNet}, staged=${finding.staged}, ratio=${finding.ratio.toFixed(3)})`).join(", ");
|
|
@@ -88755,7 +89196,7 @@ var init_merger_diff_volume_gate = __esm({
|
|
|
88755
89196
|
"../engine/src/merger-diff-volume-gate.ts"() {
|
|
88756
89197
|
"use strict";
|
|
88757
89198
|
init_merger();
|
|
88758
|
-
execFileAsync3 = promisify26(
|
|
89199
|
+
execFileAsync3 = promisify26(execFile8);
|
|
88759
89200
|
DiffVolumeRegressionError = class extends Error {
|
|
88760
89201
|
constructor(findings) {
|
|
88761
89202
|
super(buildMessage(findings));
|
|
@@ -88810,10 +89251,13 @@ function decideAutoPrerebase(input) {
|
|
|
88810
89251
|
if (hotMatches.length > 0) {
|
|
88811
89252
|
return { fire: true, reason: "hot-file", commitsBehind, hotMatches };
|
|
88812
89253
|
}
|
|
88813
|
-
const threshold = input.settings.prerebaseDivergenceThreshold ??
|
|
88814
|
-
if (threshold > 0 && commitsBehind
|
|
89254
|
+
const threshold = input.settings.prerebaseDivergenceThreshold ?? 1;
|
|
89255
|
+
if (threshold > 0 && commitsBehind >= threshold) {
|
|
88815
89256
|
return { fire: true, reason: "divergence-threshold", commitsBehind, hotMatches: [] };
|
|
88816
89257
|
}
|
|
89258
|
+
if (commitsBehind > 0) {
|
|
89259
|
+
return { fire: true, reason: "safety-fallback-any-divergence", commitsBehind, hotMatches: [] };
|
|
89260
|
+
}
|
|
88817
89261
|
return { fire: false, reason: "no-divergence", commitsBehind, hotMatches: [] };
|
|
88818
89262
|
}
|
|
88819
89263
|
async function runAutoPrerebase(deps) {
|
|
@@ -89416,7 +89860,7 @@ var init_mesh_lease_manager = __esm({
|
|
|
89416
89860
|
|
|
89417
89861
|
// ../engine/src/merger-integration-worktree.ts
|
|
89418
89862
|
import { createHash as createHash10 } from "node:crypto";
|
|
89419
|
-
import { exec as exec25, execFile as
|
|
89863
|
+
import { exec as exec25, execFile as execFile9 } from "node:child_process";
|
|
89420
89864
|
import { promisify as promisify29 } from "node:util";
|
|
89421
89865
|
function quoteAutostashArg(value) {
|
|
89422
89866
|
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
@@ -89895,7 +90339,7 @@ var init_merger_integration_worktree = __esm({
|
|
|
89895
90339
|
init_worktree_pool();
|
|
89896
90340
|
init_worktree_names();
|
|
89897
90341
|
execAsync23 = promisify29(exec25);
|
|
89898
|
-
execFileAsync4 = promisify29(
|
|
90342
|
+
execFileAsync4 = promisify29(execFile9);
|
|
89899
90343
|
MERGE_HANDOFF_WORKER_ID = "merger-reuse-handoff";
|
|
89900
90344
|
MergeHandoffRefusedError = class extends Error {
|
|
89901
90345
|
reason;
|
|
@@ -90608,7 +91052,7 @@ var init_worktree_acquisition = __esm({
|
|
|
90608
91052
|
});
|
|
90609
91053
|
|
|
90610
91054
|
// ../engine/src/merger-ref-update-advance.ts
|
|
90611
|
-
import { execFile as
|
|
91055
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
90612
91056
|
import { promisify as promisify31 } from "node:util";
|
|
90613
91057
|
async function runGit3(args, cwd) {
|
|
90614
91058
|
return await execFileAsync5("git", args, {
|
|
@@ -90731,12 +91175,22 @@ async function advanceIntegrationBranchRef(args) {
|
|
|
90731
91175
|
return { advanced: true, previousSha: expectedCurrentSha, newSha };
|
|
90732
91176
|
} catch (error) {
|
|
90733
91177
|
const diagnostic = error instanceof Error ? error.message : String(error);
|
|
90734
|
-
|
|
90735
|
-
|
|
90736
|
-
|
|
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";
|
|
90737
91191
|
await emitRefAdvance({
|
|
90738
91192
|
succeeded: false,
|
|
90739
|
-
fromSha:
|
|
91193
|
+
fromSha: effectiveObserved || expectedCurrentSha,
|
|
90740
91194
|
toSha: newSha,
|
|
90741
91195
|
error: `${reason}: ${diagnostic}`
|
|
90742
91196
|
});
|
|
@@ -90744,7 +91198,7 @@ async function advanceIntegrationBranchRef(args) {
|
|
|
90744
91198
|
advanced: false,
|
|
90745
91199
|
reason,
|
|
90746
91200
|
diagnostic,
|
|
90747
|
-
observedCurrentSha
|
|
91201
|
+
observedCurrentSha: effectiveObserved
|
|
90748
91202
|
};
|
|
90749
91203
|
}
|
|
90750
91204
|
}
|
|
@@ -90752,7 +91206,7 @@ var execFileAsync5, testHooks, IntegrationBranchConcurrentAdvanceError;
|
|
|
90752
91206
|
var init_merger_ref_update_advance = __esm({
|
|
90753
91207
|
"../engine/src/merger-ref-update-advance.ts"() {
|
|
90754
91208
|
"use strict";
|
|
90755
|
-
execFileAsync5 = promisify31(
|
|
91209
|
+
execFileAsync5 = promisify31(execFile10);
|
|
90756
91210
|
testHooks = {
|
|
90757
91211
|
runGit: runGit3
|
|
90758
91212
|
};
|
|
@@ -90779,7 +91233,7 @@ var init_merger_ref_update_advance = __esm({
|
|
|
90779
91233
|
});
|
|
90780
91234
|
|
|
90781
91235
|
// ../engine/src/worktree-ref-sync.ts
|
|
90782
|
-
import { execFile as
|
|
91236
|
+
import { execFile as execFile11, spawn as spawn5 } from "node:child_process";
|
|
90783
91237
|
import { promisify as promisify32 } from "node:util";
|
|
90784
91238
|
import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, readFileSync as readFileSync11, rmSync as rmSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
90785
91239
|
import { dirname as dirname17, join as join40 } from "node:path";
|
|
@@ -91023,7 +91477,7 @@ var execFileAsync6;
|
|
|
91023
91477
|
var init_worktree_ref_sync = __esm({
|
|
91024
91478
|
"../engine/src/worktree-ref-sync.ts"() {
|
|
91025
91479
|
"use strict";
|
|
91026
|
-
execFileAsync6 = promisify32(
|
|
91480
|
+
execFileAsync6 = promisify32(execFile11);
|
|
91027
91481
|
}
|
|
91028
91482
|
});
|
|
91029
91483
|
|
|
@@ -91234,6 +91688,7 @@ __export(merger_exports, {
|
|
|
91234
91688
|
classifyConflict: () => classifyConflict,
|
|
91235
91689
|
classifyOwnedLandedEvidence: () => classifyOwnedLandedEvidence,
|
|
91236
91690
|
commitOrAmendMergeWithFixes: () => commitOrAmendMergeWithFixes,
|
|
91691
|
+
composeMergeCommitBody: () => composeMergeCommitBody,
|
|
91237
91692
|
deriveDeterministicSubjectSummary: () => deriveDeterministicSubjectSummary,
|
|
91238
91693
|
deriveScopedPnpmTestCommand: () => deriveScopedPnpmTestCommand,
|
|
91239
91694
|
detectResolvableConflicts: () => detectResolvableConflicts,
|
|
@@ -91294,7 +91749,7 @@ __export(merger_exports, {
|
|
|
91294
91749
|
tryFastForwardFromOrigin: () => tryFastForwardFromOrigin,
|
|
91295
91750
|
validateDiffScope: () => validateDiffScope
|
|
91296
91751
|
});
|
|
91297
|
-
import { execSync as execSync4, exec as exec28, execFile as
|
|
91752
|
+
import { execSync as execSync4, exec as exec28, execFile as execFile12 } from "node:child_process";
|
|
91298
91753
|
import { promisify as promisify34 } from "node:util";
|
|
91299
91754
|
import { existsSync as existsSync37, readFileSync as readFileSync12, readdirSync as readdirSync5, writeFileSync as writeFileSync3, unlinkSync, renameSync as renameSync2 } from "node:fs";
|
|
91300
91755
|
import { createHash as createHash11 } from "node:crypto";
|
|
@@ -93723,6 +94178,23 @@ async function generateAiMergeSummary(commitLog, diffStat, settings, rootDir) {
|
|
|
93723
94178
|
return null;
|
|
93724
94179
|
}
|
|
93725
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
|
+
}
|
|
93726
94198
|
async function generateAiMergeSubject(commitLog, diffStat, settings, rootDir, branch, taskId, signal) {
|
|
93727
94199
|
try {
|
|
93728
94200
|
const resolved = resolveTitleSummarizerSettingsModel(settings);
|
|
@@ -93790,22 +94262,43 @@ function deriveDeterministicSubjectSummary(commitLog) {
|
|
|
93790
94262
|
const summary = extras > 0 ? `${headline} (+${extras} more)` : headline;
|
|
93791
94263
|
return summary;
|
|
93792
94264
|
}
|
|
93793
|
-
|
|
93794
|
-
const {
|
|
93795
|
-
const
|
|
93796
|
-
const
|
|
93797
|
-
const derived = trimmedAiSubject.length === 0 ? deriveDeterministicSubjectSummary(commitLog ?? "") : null;
|
|
93798
|
-
const subjectSummary = trimmedAiSubject.length > 0 ? trimmedAiSubject : derived ?? `merge ${branch}`;
|
|
93799
|
-
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() ?? "";
|
|
93800
94269
|
const trimmedCommitLog = commitLog?.trim() ?? "";
|
|
93801
94270
|
const trimmedDiffStat = diffStat?.trim() ?? "";
|
|
93802
94271
|
const commitsSection = trimmedCommitLog.length > 0 ? trimmedCommitLog : `- merge ${branch}`;
|
|
93803
|
-
const
|
|
94272
|
+
const parts = [];
|
|
94273
|
+
if (trimmedSummary.length > 0) parts.push(trimmedSummary);
|
|
94274
|
+
if (trimmedAiBody.length > 0) parts.push(trimmedAiBody);
|
|
94275
|
+
const deterministicFallback = [
|
|
93804
94276
|
`Commits merged:
|
|
93805
94277
|
${commitsSection}`,
|
|
93806
94278
|
trimmedDiffStat.length > 0 ? `Files changed:
|
|
93807
94279
|
${trimmedDiffStat}` : ""
|
|
93808
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
|
+
});
|
|
93809
94302
|
const escape = (s3) => s3.replace(/(["\\$`])/g, "\\$1");
|
|
93810
94303
|
return {
|
|
93811
94304
|
subjectArg: `-m "${escape(subject)}"`,
|
|
@@ -94217,6 +94710,7 @@ ${msg}`;
|
|
|
94217
94710
|
diffStat: messageDiffStat,
|
|
94218
94711
|
includeTaskId,
|
|
94219
94712
|
aiSummary,
|
|
94713
|
+
aiBody: void 0,
|
|
94220
94714
|
aiSubject
|
|
94221
94715
|
});
|
|
94222
94716
|
let lineageId;
|
|
@@ -97410,8 +97904,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
97410
97904
|
mergerLog.log(`${taskId}: ${routeMessage}`);
|
|
97411
97905
|
await store.appendAgentLog(taskId, routeMessage, "text", void 0, "merger");
|
|
97412
97906
|
}
|
|
97413
|
-
const aiMergeSummary = settings.useAiMergeCommitSummary ? await
|
|
97414
|
-
|
|
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];
|
|
97415
97912
|
try {
|
|
97416
97913
|
const scopeResult = await validateDiffScope(store, taskId, diffStat, settings.strictScopeEnforcement);
|
|
97417
97914
|
for (const warning of scopeResult.warnings) {
|
|
@@ -97487,6 +97984,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
97487
97984
|
commitLog,
|
|
97488
97985
|
diffStat,
|
|
97489
97986
|
aiSummary: aiMergeSummary,
|
|
97987
|
+
aiBody: aiMergeBody,
|
|
97490
97988
|
aiSubject: aiMergeSubject,
|
|
97491
97989
|
includeTaskId,
|
|
97492
97990
|
sourceIssueRef,
|
|
@@ -98085,6 +98583,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98085
98583
|
const recordedFilesChanged = mergeWasEmpty ? 0 : filesChanged;
|
|
98086
98584
|
const recordedInsertions = mergeWasEmpty ? 0 : insertions;
|
|
98087
98585
|
const recordedDeletions = mergeWasEmpty ? 0 : deletions;
|
|
98586
|
+
const mergeConfirmedAtThisPoint = !reuseTaskWorktreeMerge;
|
|
98088
98587
|
const mergeDetails = {
|
|
98089
98588
|
commitSha: recordedSha,
|
|
98090
98589
|
rebaseBaseSha: !mergeWasEmpty && rebaseMergeBaseSha ? rebaseMergeBaseSha : void 0,
|
|
@@ -98095,9 +98594,11 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98095
98594
|
noOpVerifiedShortCircuit,
|
|
98096
98595
|
landedFilesAttributionRestricted,
|
|
98097
98596
|
landedFilesCaptureFallback,
|
|
98597
|
+
// Keep mergeDetails headline-only for dashboard cards; rich bullet body
|
|
98598
|
+
// is used in the actual git commit message composition path.
|
|
98098
98599
|
mergeCommitMessage: aiMergeSummary || commitLog,
|
|
98099
98600
|
mergedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
98100
|
-
mergeConfirmed:
|
|
98601
|
+
mergeConfirmed: mergeConfirmedAtThisPoint,
|
|
98101
98602
|
mergeTargetBranch: mergeTarget.branch,
|
|
98102
98603
|
mergeTargetSource: mergeTarget.source,
|
|
98103
98604
|
resolutionStrategy: result.resolutionStrategy,
|
|
@@ -98191,6 +98692,19 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
98191
98692
|
mergerLog.log(
|
|
98192
98693
|
`${taskId}: ${integrationBranch2} advanced to ${worktreeHeadSha.slice(0, 8)} via update-ref; your checked-out worktree at ${projectRootDir} is now behind`
|
|
98193
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
|
+
}
|
|
98194
98708
|
const autoSyncMode = normalizeMergeAdvanceAutoSyncMode(settings.mergeAdvanceAutoSync);
|
|
98195
98709
|
if (autoSyncMode !== "off") {
|
|
98196
98710
|
try {
|
|
@@ -98555,6 +99069,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
98555
99069
|
commitLog,
|
|
98556
99070
|
diffStat,
|
|
98557
99071
|
aiSummary,
|
|
99072
|
+
aiBody,
|
|
98558
99073
|
aiSubject,
|
|
98559
99074
|
includeTaskId,
|
|
98560
99075
|
sourceIssueRef,
|
|
@@ -98813,6 +99328,7 @@ async function executeMergeAttempt(params, aiTracker) {
|
|
|
98813
99328
|
commitLog,
|
|
98814
99329
|
diffStat,
|
|
98815
99330
|
aiSummary,
|
|
99331
|
+
aiBody,
|
|
98816
99332
|
aiSubject,
|
|
98817
99333
|
includeTaskId,
|
|
98818
99334
|
hasConflicts,
|
|
@@ -98959,7 +99475,7 @@ async function attemptWithMixedSideStrategy(params, strategy, aiTracker) {
|
|
|
98959
99475
|
}
|
|
98960
99476
|
}
|
|
98961
99477
|
async function finalizeSideStrategyAttempt(params, side, aiTracker) {
|
|
98962
|
-
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;
|
|
98963
99479
|
const staged = execSyncText("git diff --cached --quiet 2>&1; echo $?", {
|
|
98964
99480
|
cwd: rootDir,
|
|
98965
99481
|
encoding: "utf-8"
|
|
@@ -99000,7 +99516,8 @@ async function finalizeSideStrategyAttempt(params, side, aiTracker) {
|
|
|
99000
99516
|
commitLog,
|
|
99001
99517
|
diffStat,
|
|
99002
99518
|
includeTaskId,
|
|
99003
|
-
aiSummary
|
|
99519
|
+
aiSummary,
|
|
99520
|
+
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
|
99004
99521
|
aiSubject
|
|
99005
99522
|
});
|
|
99006
99523
|
await enforceSquashFileScopeInvariant({
|
|
@@ -99058,6 +99575,7 @@ async function runAiAgentForCommit(params) {
|
|
|
99058
99575
|
commitLog,
|
|
99059
99576
|
diffStat,
|
|
99060
99577
|
aiSummary,
|
|
99578
|
+
aiBody,
|
|
99061
99579
|
aiSubject,
|
|
99062
99580
|
includeTaskId,
|
|
99063
99581
|
hasConflicts,
|
|
@@ -99279,7 +99797,8 @@ async function runAiAgentForCommit(params) {
|
|
|
99279
99797
|
commitLog,
|
|
99280
99798
|
diffStat,
|
|
99281
99799
|
includeTaskId,
|
|
99282
|
-
aiSummary
|
|
99800
|
+
aiSummary,
|
|
99801
|
+
aiBody: aiBody?.trim().length ? aiBody : safeBody,
|
|
99283
99802
|
aiSubject
|
|
99284
99803
|
});
|
|
99285
99804
|
await runDiffVolumeGate({
|
|
@@ -99734,7 +100253,7 @@ var init_merger = __esm({
|
|
|
99734
100253
|
init_merger_diff_volume_gate();
|
|
99735
100254
|
init_merger_ref_update_advance();
|
|
99736
100255
|
execAsync26 = promisify34(exec28);
|
|
99737
|
-
execFileAsync7 = promisify34(
|
|
100256
|
+
execFileAsync7 = promisify34(execFile12);
|
|
99738
100257
|
LOCKFILE_PATTERNS = [
|
|
99739
100258
|
"package-lock.json",
|
|
99740
100259
|
"pnpm-lock.yaml",
|
|
@@ -100104,7 +100623,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
100104
100623
|
function hasStepProgress2(task) {
|
|
100105
100624
|
return task.steps.some((step) => step.status !== "pending");
|
|
100106
100625
|
}
|
|
100107
|
-
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;
|
|
100108
100627
|
var init_self_healing = __esm({
|
|
100109
100628
|
"../engine/src/self-healing.ts"() {
|
|
100110
100629
|
"use strict";
|
|
@@ -100125,6 +100644,8 @@ var init_self_healing = __esm({
|
|
|
100125
100644
|
init_foreign_only_contamination();
|
|
100126
100645
|
init_notifier();
|
|
100127
100646
|
init_dependency_blocked_todo_reporter();
|
|
100647
|
+
init_transient_merge_error_classifier();
|
|
100648
|
+
init_transient_merge_error_classifier();
|
|
100128
100649
|
log20 = createLogger2("self-healing");
|
|
100129
100650
|
worktreeMetadataReconcileLog = createLogger2("worktree-metadata-reconcile");
|
|
100130
100651
|
execAsync27 = promisify35(exec29);
|
|
@@ -100163,6 +100684,7 @@ var init_self_healing = __esm({
|
|
|
100163
100684
|
MAX_TASK_DONE_RETRIES = 3;
|
|
100164
100685
|
MAX_WORKTREE_SESSION_RETRIES = 3;
|
|
100165
100686
|
MAX_AUTO_MERGE_RETRIES = 3;
|
|
100687
|
+
MAX_TRANSIENT_MERGE_RECOVERIES = 2;
|
|
100166
100688
|
MAX_STARVATION_DROPS = 3;
|
|
100167
100689
|
DEADLOCK_RECOVERY_COOLDOWN_MS = 15 * 6e4;
|
|
100168
100690
|
DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS = 5 * 6e4;
|
|
@@ -100360,6 +100882,7 @@ var init_self_healing = __esm({
|
|
|
100360
100882
|
{ name: "stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks().then(() => void 0) },
|
|
100361
100883
|
{ name: "failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps().then(() => void 0) },
|
|
100362
100884
|
{ name: "interrupted-merging", fn: () => this.recoverInterruptedMergingTasks().then(() => void 0) },
|
|
100885
|
+
{ name: "transient-merge-failures", fn: () => this.recoverTransientMergeFailures().then(() => void 0) },
|
|
100363
100886
|
{ name: "done-merge-metadata", fn: () => this.recoverDoneTaskMergeMetadata().then(() => void 0) },
|
|
100364
100887
|
{ name: "reconcile-done-task-integrity", fn: () => this.reconcileDoneTaskIntegrity().then(() => void 0) },
|
|
100365
100888
|
// FN-5092: must run BEFORE any merger pickup path so the merger queue is
|
|
@@ -100454,8 +100977,8 @@ var init_self_healing = __esm({
|
|
|
100454
100977
|
this.lastPauseTriggeredAt = Date.now();
|
|
100455
100978
|
const baseDelay = settings.autoUnpauseBaseDelayMs ?? 3e5;
|
|
100456
100979
|
const maxDelay = settings.autoUnpauseMaxDelayMs ?? 36e5;
|
|
100457
|
-
const
|
|
100458
|
-
this.scheduleUnpause(
|
|
100980
|
+
const delay5 = Math.min(baseDelay * Math.pow(2, this.unpauseAttempt), maxDelay);
|
|
100981
|
+
this.scheduleUnpause(delay5);
|
|
100459
100982
|
}
|
|
100460
100983
|
if (previous.globalPause && !settings.globalPause) {
|
|
100461
100984
|
this.cancelUnpauseTimer();
|
|
@@ -100889,6 +101412,7 @@ var init_self_healing = __esm({
|
|
|
100889
101412
|
{ name: "recover-stale-incomplete-review", fn: () => this.recoverStaleIncompleteReviewTasks() },
|
|
100890
101413
|
{ name: "recover-failed-pre-merge-steps", fn: () => this.recoverReviewTasksWithFailedPreMergeSteps() },
|
|
100891
101414
|
{ name: "recover-interrupted-merging", fn: () => this.recoverInterruptedMergingTasks() },
|
|
101415
|
+
{ name: "recover-transient-merge-failures", fn: () => this.recoverTransientMergeFailures() },
|
|
100892
101416
|
{ name: "recover-done-merge-metadata", fn: () => this.recoverDoneTaskMergeMetadata() },
|
|
100893
101417
|
{ name: "recover-stale-merging-status", fn: () => this.recoverStaleMergingStatus() },
|
|
100894
101418
|
{ name: "finalize-noop-review", fn: () => this.finalizeNoOpReviewTasks() },
|
|
@@ -104061,6 +104585,142 @@ var init_self_healing = __esm({
|
|
|
104061
104585
|
* No-op when `settings.autoMerge === false` — PR-based review flow owns lifecycle until human merge.
|
|
104062
104586
|
* @returns Number of tasks finalized or unblocked
|
|
104063
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
|
+
}
|
|
104064
104724
|
async recoverInterruptedMergingTasks() {
|
|
104065
104725
|
try {
|
|
104066
104726
|
const settings = await this.store.getSettings();
|
|
@@ -106362,7 +107022,7 @@ var init_self_healing = __esm({
|
|
|
106362
107022
|
});
|
|
106363
107023
|
|
|
106364
107024
|
// ../engine/src/triage.ts
|
|
106365
|
-
import { Type as Type3 } from "@
|
|
107025
|
+
import { Type as Type3 } from "@earendil-works/pi-ai";
|
|
106366
107026
|
import { exec as exec30 } from "node:child_process";
|
|
106367
107027
|
import { readFile as readFile21 } from "node:fs/promises";
|
|
106368
107028
|
import { join as join44 } from "node:path";
|
|
@@ -106421,9 +107081,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
106421
107081
|
return { attachmentContents, imageContents };
|
|
106422
107082
|
}
|
|
106423
107083
|
const { readFile: readFile29 } = await import("node:fs/promises");
|
|
106424
|
-
const { join:
|
|
107084
|
+
const { join: join59 } = await import("node:path");
|
|
106425
107085
|
for (const att of attachments) {
|
|
106426
|
-
const filePath =
|
|
107086
|
+
const filePath = join59(
|
|
106427
107087
|
rootDir,
|
|
106428
107088
|
".fusion",
|
|
106429
107089
|
"tasks",
|
|
@@ -107896,8 +108556,8 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
107896
108556
|
});
|
|
107897
108557
|
if (decision.shouldRetry) {
|
|
107898
108558
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
107899
|
-
const
|
|
107900
|
-
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}.`;
|
|
107901
108561
|
planLog.warn(`${task.id} ${retryMessage}`);
|
|
107902
108562
|
await this.store.logEntry(task.id, retryMessage);
|
|
107903
108563
|
const restoreStatus = task.status === "needs-replan" ? "needs-replan" : null;
|
|
@@ -108003,10 +108663,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
108003
108663
|
});
|
|
108004
108664
|
if (decision.shouldRetry) {
|
|
108005
108665
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
108006
|
-
const
|
|
108666
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
108007
108667
|
if (!isSilentTransientError(errorMessage)) {
|
|
108008
|
-
planLog.warn(`\u26A1 ${task.id} transient error during triage \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
108009
|
-
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) => {
|
|
108010
108670
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
108011
108671
|
planLog.warn(`${task.id}: failed to log transient-error retry entry: ${msg}`);
|
|
108012
108672
|
});
|
|
@@ -108305,9 +108965,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
108305
108965
|
}
|
|
108306
108966
|
try {
|
|
108307
108967
|
const { readFile: readFile29 } = await import("node:fs/promises");
|
|
108308
|
-
const { join:
|
|
108968
|
+
const { join: join59 } = await import("node:path");
|
|
108309
108969
|
const promptContent = await readFile29(
|
|
108310
|
-
|
|
108970
|
+
join59(rootDir, promptPath),
|
|
108311
108971
|
"utf-8"
|
|
108312
108972
|
).catch((err) => {
|
|
108313
108973
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -108782,7 +109442,7 @@ Take a completely different approach to writing this specification. Do NOT repea
|
|
|
108782
109442
|
});
|
|
108783
109443
|
|
|
108784
109444
|
// ../engine/src/merger-orphan-rehome.ts
|
|
108785
|
-
import { execFile as
|
|
109445
|
+
import { execFile as execFile13 } from "node:child_process";
|
|
108786
109446
|
import { promisify as promisify37 } from "node:util";
|
|
108787
109447
|
async function runGit5(args, cwd) {
|
|
108788
109448
|
const { stdout } = await execFileAsync8("git", args, {
|
|
@@ -108888,7 +109548,7 @@ var init_merger_orphan_rehome = __esm({
|
|
|
108888
109548
|
"../engine/src/merger-orphan-rehome.ts"() {
|
|
108889
109549
|
"use strict";
|
|
108890
109550
|
init_merger_ref_update_advance();
|
|
108891
|
-
execFileAsync8 = promisify37(
|
|
109551
|
+
execFileAsync8 = promisify37(execFile13);
|
|
108892
109552
|
}
|
|
108893
109553
|
});
|
|
108894
109554
|
|
|
@@ -109453,7 +110113,7 @@ var init_task_completion = __esm({
|
|
|
109453
110113
|
import { spawn as spawn6 } from "node:child_process";
|
|
109454
110114
|
import { existsSync as existsSync40 } from "node:fs";
|
|
109455
110115
|
import { isAbsolute as isAbsolute13, join as join46 } from "node:path";
|
|
109456
|
-
import { Type as Type4 } from "@
|
|
110116
|
+
import { Type as Type4 } from "@earendil-works/pi-ai";
|
|
109457
110117
|
function createBuffer() {
|
|
109458
110118
|
return { headChunks: [], headBytes: 0, tailChunks: [], tailBytes: 0, totalBytes: 0 };
|
|
109459
110119
|
}
|
|
@@ -109762,8 +110422,8 @@ import { setImmediate as setImmediateCb3 } from "node:timers";
|
|
|
109762
110422
|
import { delimiter, isAbsolute as isAbsolute14, join as join47, relative as relative11, resolve as resolvePath } from "node:path";
|
|
109763
110423
|
import { existsSync as existsSync41, realpathSync as realpathSync6 } from "node:fs";
|
|
109764
110424
|
import { readFile as readFile22, rm as rm5, writeFile as writeFile16 } from "node:fs/promises";
|
|
109765
|
-
import { Type as Type5 } from "@
|
|
109766
|
-
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";
|
|
109767
110427
|
function getResumeOrphanDelayMs() {
|
|
109768
110428
|
const raw = process.env.FUSION_RESUME_ORPHAN_DELAY_MS;
|
|
109769
110429
|
if (raw !== void 0) {
|
|
@@ -109780,6 +110440,26 @@ function canonicalizePath2(path6) {
|
|
|
109780
110440
|
return resolvePath(path6);
|
|
109781
110441
|
}
|
|
109782
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
|
+
}
|
|
109783
110463
|
function detectPendingReviewBlock(task, codeReviewVerdicts) {
|
|
109784
110464
|
const inProgressStepIndices = [];
|
|
109785
110465
|
for (let stepIndex = 0; stepIndex < task.steps.length; stepIndex++) {
|
|
@@ -110315,7 +110995,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
110315
110995
|
];
|
|
110316
110996
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
110317
110997
|
}
|
|
110318
|
-
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;
|
|
110319
110999
|
var init_executor = __esm({
|
|
110320
111000
|
"../engine/src/executor.ts"() {
|
|
110321
111001
|
"use strict";
|
|
@@ -110383,6 +111063,7 @@ var init_executor = __esm({
|
|
|
110383
111063
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
110384
111064
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
110385
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)'/;
|
|
110386
111067
|
DISSENT_PATTERNS = [
|
|
110387
111068
|
/\btask (is|was)(?: not|n['’]?t) complete\b/i,
|
|
110388
111069
|
/\b(?:i (?:could|can)(?:not|n['’]?t)|unable to|failed to) (?:complete|finish|implement)\b/i,
|
|
@@ -112874,10 +113555,10 @@ ${summary}`,
|
|
|
112874
113555
|
});
|
|
112875
113556
|
if (decision.shouldRetry) {
|
|
112876
113557
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
112877
|
-
const
|
|
113558
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
112878
113559
|
if (!isSilentTransientError(errorMessage)) {
|
|
112879
|
-
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
112880
|
-
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));
|
|
112881
113562
|
}
|
|
112882
113563
|
if (worktreePath && existsSync41(worktreePath)) {
|
|
112883
113564
|
try {
|
|
@@ -113839,9 +114520,9 @@ ${summary}`,
|
|
|
113839
114520
|
});
|
|
113840
114521
|
if (decision.shouldRetry) {
|
|
113841
114522
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
113842
|
-
const
|
|
113843
|
-
executorLog.warn(`\u26A1 ${task.id} context-overflow fresh-session requeue ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
113844
|
-
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));
|
|
113845
114526
|
await this.store.updateTask(task.id, {
|
|
113846
114527
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
113847
114528
|
nextRecoveryAt: decision.nextState.nextRecoveryAt,
|
|
@@ -114129,10 +114810,10 @@ ${summary}`,
|
|
|
114129
114810
|
});
|
|
114130
114811
|
if (decision.shouldRetry) {
|
|
114131
114812
|
const attempt = decision.nextState.recoveryRetryCount;
|
|
114132
|
-
const
|
|
114813
|
+
const delay5 = formatDelay(decision.delayMs);
|
|
114133
114814
|
if (!isSilentTransientError(errorMessage)) {
|
|
114134
|
-
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${
|
|
114135
|
-
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));
|
|
114136
114817
|
}
|
|
114137
114818
|
if (worktreePath && existsSync41(worktreePath)) {
|
|
114138
114819
|
try {
|
|
@@ -116834,8 +117515,8 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
116834
117515
|
`Failed to create worktree after ${this.MAX_WORKTREE_RETRIES} attempts: ${errorMessage}`
|
|
116835
117516
|
);
|
|
116836
117517
|
}
|
|
116837
|
-
const
|
|
116838
|
-
await new Promise((resolve32) => setTimeout(resolve32,
|
|
117518
|
+
const delay5 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
|
|
117519
|
+
await new Promise((resolve32) => setTimeout(resolve32, delay5));
|
|
116839
117520
|
}
|
|
116840
117521
|
}
|
|
116841
117522
|
throw new Error("Unexpected exit from worktree creation retry loop");
|
|
@@ -117073,9 +117754,15 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
117073
117754
|
}
|
|
117074
117755
|
async recoverMissingWorktreeSessionStartFailure(task, worktreePath, error, audit) {
|
|
117075
117756
|
const errorText = error instanceof Error ? error.message : String(error);
|
|
117076
|
-
|
|
117757
|
+
const missingWorktreeFailure = isMissingWorktreeSessionStartFailure(errorText);
|
|
117758
|
+
const missingTaskJsonFailure = isTransientMissingTaskJsonError(error, task);
|
|
117759
|
+
if (!missingWorktreeFailure && !missingTaskJsonFailure) return false;
|
|
117077
117760
|
const classification = classifyMissingWorktreeSessionStartFailure(errorText);
|
|
117078
|
-
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
|
+
}
|
|
117079
117766
|
await audit.git({
|
|
117080
117767
|
type: "worktree:incomplete-detected",
|
|
117081
117768
|
target: staleWorktreePath,
|
|
@@ -120231,6 +120918,12 @@ var init_scheduler = __esm({
|
|
|
120231
120918
|
}
|
|
120232
120919
|
const sliceIdBeforeUpdate = feature.sliceId;
|
|
120233
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
|
+
}
|
|
120234
120927
|
void this.options.missionExecutionLoop.processTaskOutcome(taskId).catch((err) => {
|
|
120235
120928
|
schedulerLog.error(`Error in missionExecutionLoop.processTaskOutcome for ${taskId}:`, err);
|
|
120236
120929
|
});
|
|
@@ -120693,19 +121386,19 @@ var init_mission_autopilot = __esm({
|
|
|
120693
121386
|
autopilotLog.error(`Error advancing slice for mission ${missionId}:`, err);
|
|
120694
121387
|
state.retryCount++;
|
|
120695
121388
|
if (state.retryCount <= MAX_RETRY_ATTEMPTS) {
|
|
120696
|
-
const
|
|
121389
|
+
const delay5 = RETRY_BASE_DELAY_MS2 * Math.pow(3, state.retryCount - 1);
|
|
120697
121390
|
this.logMissionEventSafe(
|
|
120698
121391
|
missionId,
|
|
120699
121392
|
"autopilot_retry",
|
|
120700
121393
|
`Retrying slice activation after error (attempt ${state.retryCount}/${MAX_RETRY_ATTEMPTS})`,
|
|
120701
|
-
{ retryCount: state.retryCount, maxRetries: MAX_RETRY_ATTEMPTS, delayMs:
|
|
121394
|
+
{ retryCount: state.retryCount, maxRetries: MAX_RETRY_ATTEMPTS, delayMs: delay5 }
|
|
120702
121395
|
);
|
|
120703
|
-
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)`);
|
|
120704
121397
|
setTimeout(() => {
|
|
120705
121398
|
if (this.isWatching(missionId)) {
|
|
120706
121399
|
void this.advanceToNextSlice(missionId);
|
|
120707
121400
|
}
|
|
120708
|
-
},
|
|
121401
|
+
}, delay5);
|
|
120709
121402
|
} else {
|
|
120710
121403
|
this.logMissionEventSafe(
|
|
120711
121404
|
missionId,
|
|
@@ -121152,7 +121845,7 @@ var init_mission_autopilot = __esm({
|
|
|
121152
121845
|
});
|
|
121153
121846
|
|
|
121154
121847
|
// ../engine/src/mission-execution-loop.ts
|
|
121155
|
-
import { EventEmitter as
|
|
121848
|
+
import { EventEmitter as EventEmitter19 } from "node:events";
|
|
121156
121849
|
var loopLog, VALIDATION_TIMEOUT_MS, MissionExecutionLoop;
|
|
121157
121850
|
var init_mission_execution_loop = __esm({
|
|
121158
121851
|
"../engine/src/mission-execution-loop.ts"() {
|
|
@@ -121164,7 +121857,7 @@ var init_mission_execution_loop = __esm({
|
|
|
121164
121857
|
init_run_audit();
|
|
121165
121858
|
loopLog = createLogger2("mission-loop");
|
|
121166
121859
|
VALIDATION_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
121167
|
-
MissionExecutionLoop = class extends
|
|
121860
|
+
MissionExecutionLoop = class extends EventEmitter19 {
|
|
121168
121861
|
running = false;
|
|
121169
121862
|
taskStore;
|
|
121170
121863
|
missionStore;
|
|
@@ -121304,6 +121997,10 @@ var init_mission_execution_loop = __esm({
|
|
|
121304
121997
|
loopLog.log(`Feature ${feature.id} loopState is "${feature.loopState}"; skipping validation`);
|
|
121305
121998
|
return;
|
|
121306
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
|
+
}
|
|
121307
122004
|
const assertions = this.missionStore.listAssertionsForFeature(feature.id);
|
|
121308
122005
|
if (assertions.length === 0) {
|
|
121309
122006
|
loopLog.log(`Feature ${feature.id} has no linked assertions; marking as passed`);
|
|
@@ -121692,6 +122389,10 @@ ${taskContext}` : ""}`;
|
|
|
121692
122389
|
if (runId) {
|
|
121693
122390
|
this.missionStore.completeValidatorRun(runId, "passed", summary);
|
|
121694
122391
|
}
|
|
122392
|
+
const feature = this.missionStore.getFeature(featureId);
|
|
122393
|
+
if (feature && feature.status !== "done") {
|
|
122394
|
+
this.missionStore.updateFeatureStatus(featureId, "done");
|
|
122395
|
+
}
|
|
121695
122396
|
loopLog.log(`Feature ${featureId} passed validation`);
|
|
121696
122397
|
if (this.missionAutopilot?.notifyValidationComplete) {
|
|
121697
122398
|
await this.missionAutopilot.notifyValidationComplete(featureId, "passed");
|
|
@@ -121795,7 +122496,7 @@ ${taskContext}` : ""}`;
|
|
|
121795
122496
|
});
|
|
121796
122497
|
|
|
121797
122498
|
// ../engine/src/smart-pull.ts
|
|
121798
|
-
import { execFile as
|
|
122499
|
+
import { execFile as execFile14 } from "node:child_process";
|
|
121799
122500
|
import { promisify as promisify41 } from "node:util";
|
|
121800
122501
|
async function runGit6(args, cwd, timeoutMs) {
|
|
121801
122502
|
const result = await execFileAsync9("git", args, {
|
|
@@ -121953,7 +122654,7 @@ var execFileAsync9;
|
|
|
121953
122654
|
var init_smart_pull = __esm({
|
|
121954
122655
|
"../engine/src/smart-pull.ts"() {
|
|
121955
122656
|
"use strict";
|
|
121956
|
-
execFileAsync9 = promisify41(
|
|
122657
|
+
execFileAsync9 = promisify41(execFile14);
|
|
121957
122658
|
}
|
|
121958
122659
|
});
|
|
121959
122660
|
|
|
@@ -122902,7 +123603,7 @@ var init_room_coordination = __esm({
|
|
|
122902
123603
|
});
|
|
122903
123604
|
|
|
122904
123605
|
// ../engine/src/agent-heartbeat.ts
|
|
122905
|
-
import { Type as Type6 } from "@
|
|
123606
|
+
import { Type as Type6 } from "@earendil-works/pi-ai";
|
|
122906
123607
|
import { createHash as createHash12 } from "node:crypto";
|
|
122907
123608
|
function adjustHeartbeatMemoryPrimer(basePrompt, mode) {
|
|
122908
123609
|
if (mode === "full") return basePrompt;
|
|
@@ -130033,7 +130734,7 @@ var init_stuck_task_detector = __esm({
|
|
|
130033
130734
|
});
|
|
130034
130735
|
|
|
130035
130736
|
// ../engine/src/plugin-runner.ts
|
|
130036
|
-
import { Type as Type7 } from "@
|
|
130737
|
+
import { Type as Type7 } from "@earendil-works/pi-ai";
|
|
130037
130738
|
import { isAbsolute as isAbsolute16 } from "node:path";
|
|
130038
130739
|
var DEFAULT_HOOK_TIMEOUT_MS, PluginRunner;
|
|
130039
130740
|
var init_plugin_runner = __esm({
|
|
@@ -131184,7 +131885,7 @@ var init_task_agent_sync = __esm({
|
|
|
131184
131885
|
});
|
|
131185
131886
|
|
|
131186
131887
|
// ../engine/src/runtimes/in-process-runtime.ts
|
|
131187
|
-
import { EventEmitter as
|
|
131888
|
+
import { EventEmitter as EventEmitter20 } from "node:events";
|
|
131188
131889
|
import { setImmediate as setImmediateCb4 } from "node:timers";
|
|
131189
131890
|
var yieldEventLoop4, InProcessRuntime;
|
|
131190
131891
|
var init_in_process_runtime = __esm({
|
|
@@ -131214,7 +131915,7 @@ var init_in_process_runtime = __esm({
|
|
|
131214
131915
|
init_task_agent_sync();
|
|
131215
131916
|
init_run_audit();
|
|
131216
131917
|
yieldEventLoop4 = () => new Promise((resolve32) => setImmediateCb4(resolve32));
|
|
131217
|
-
InProcessRuntime = class extends
|
|
131918
|
+
InProcessRuntime = class extends EventEmitter20 {
|
|
131218
131919
|
/**
|
|
131219
131920
|
* @param config - Runtime configuration
|
|
131220
131921
|
* @param centralCore - CentralCore reference for global coordination
|
|
@@ -132244,14 +132945,14 @@ var init_ipc_protocol = __esm({
|
|
|
132244
132945
|
});
|
|
132245
132946
|
|
|
132246
132947
|
// ../engine/src/ipc/ipc-host.ts
|
|
132247
|
-
import { EventEmitter as
|
|
132948
|
+
import { EventEmitter as EventEmitter21 } from "node:events";
|
|
132248
132949
|
var IpcHost;
|
|
132249
132950
|
var init_ipc_host = __esm({
|
|
132250
132951
|
"../engine/src/ipc/ipc-host.ts"() {
|
|
132251
132952
|
"use strict";
|
|
132252
132953
|
init_ipc_protocol();
|
|
132253
132954
|
init_logger2();
|
|
132254
|
-
IpcHost = class extends
|
|
132955
|
+
IpcHost = class extends EventEmitter21 {
|
|
132255
132956
|
/**
|
|
132256
132957
|
* @param childProcess - The forked child process to communicate with
|
|
132257
132958
|
* @param options - Optional configuration
|
|
@@ -132441,7 +133142,7 @@ var child_process_runtime_exports = {};
|
|
|
132441
133142
|
__export(child_process_runtime_exports, {
|
|
132442
133143
|
ChildProcessRuntime: () => ChildProcessRuntime
|
|
132443
133144
|
});
|
|
132444
|
-
import { EventEmitter as
|
|
133145
|
+
import { EventEmitter as EventEmitter22 } from "node:events";
|
|
132445
133146
|
import { fork } from "node:child_process";
|
|
132446
133147
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
132447
133148
|
import { dirname as dirname18, join as join49 } from "node:path";
|
|
@@ -132498,8 +133199,8 @@ var init_child_process_runtime = __esm({
|
|
|
132498
133199
|
runtimeLog.log("Health monitor stopped");
|
|
132499
133200
|
}
|
|
132500
133201
|
getRestartDelay() {
|
|
132501
|
-
const
|
|
132502
|
-
return
|
|
133202
|
+
const delay5 = this.restartDelays[this.restartAttempts] ?? this.restartDelays[this.restartDelays.length - 1];
|
|
133203
|
+
return delay5;
|
|
132503
133204
|
}
|
|
132504
133205
|
incrementRestartAttempts() {
|
|
132505
133206
|
this.restartAttempts++;
|
|
@@ -132511,7 +133212,7 @@ var init_child_process_runtime = __esm({
|
|
|
132511
133212
|
return this.missedHeartbeats;
|
|
132512
133213
|
}
|
|
132513
133214
|
};
|
|
132514
|
-
ChildProcessRuntime = class extends
|
|
133215
|
+
ChildProcessRuntime = class extends EventEmitter22 {
|
|
132515
133216
|
/**
|
|
132516
133217
|
* @param config - Runtime configuration
|
|
132517
133218
|
* @param centralCore - CentralCore reference for global coordination
|
|
@@ -132780,9 +133481,9 @@ var init_child_process_runtime = __esm({
|
|
|
132780
133481
|
this.emit("error", new Error("Child process failed after max restart attempts"));
|
|
132781
133482
|
return;
|
|
132782
133483
|
}
|
|
132783
|
-
const
|
|
133484
|
+
const delay5 = this.healthMonitor.getRestartDelay();
|
|
132784
133485
|
this.healthMonitor.incrementRestartAttempts();
|
|
132785
|
-
runtimeLog.log(`Attempting restart ${this.healthMonitor.getRestartAttempts()}/${maxRestarts} after ${
|
|
133486
|
+
runtimeLog.log(`Attempting restart ${this.healthMonitor.getRestartAttempts()}/${maxRestarts} after ${delay5}ms`);
|
|
132786
133487
|
const gen = this.generation;
|
|
132787
133488
|
this.restartTimer = setTimeout(async () => {
|
|
132788
133489
|
this.restartTimer = null;
|
|
@@ -132801,7 +133502,7 @@ var init_child_process_runtime = __esm({
|
|
|
132801
133502
|
this.setStatus("errored");
|
|
132802
133503
|
this.emit("error", error instanceof Error ? error : new Error(String(error)));
|
|
132803
133504
|
}
|
|
132804
|
-
},
|
|
133505
|
+
}, delay5);
|
|
132805
133506
|
}
|
|
132806
133507
|
/**
|
|
132807
133508
|
* Handle child process exit.
|
|
@@ -133196,14 +133897,14 @@ var init_remote_node_client = __esm({
|
|
|
133196
133897
|
});
|
|
133197
133898
|
|
|
133198
133899
|
// ../engine/src/runtimes/remote-node-runtime.ts
|
|
133199
|
-
import { EventEmitter as
|
|
133900
|
+
import { EventEmitter as EventEmitter23 } from "node:events";
|
|
133200
133901
|
var RemoteNodeRuntime;
|
|
133201
133902
|
var init_remote_node_runtime = __esm({
|
|
133202
133903
|
"../engine/src/runtimes/remote-node-runtime.ts"() {
|
|
133203
133904
|
"use strict";
|
|
133204
133905
|
init_logger2();
|
|
133205
133906
|
init_remote_node_client();
|
|
133206
|
-
RemoteNodeRuntime = class extends
|
|
133907
|
+
RemoteNodeRuntime = class extends EventEmitter23 {
|
|
133207
133908
|
constructor(config) {
|
|
133208
133909
|
super();
|
|
133209
133910
|
this.config = config;
|
|
@@ -133513,7 +134214,7 @@ var init_remote_node_runtime = __esm({
|
|
|
133513
134214
|
});
|
|
133514
134215
|
|
|
133515
134216
|
// ../engine/src/project-manager.ts
|
|
133516
|
-
import { EventEmitter as
|
|
134217
|
+
import { EventEmitter as EventEmitter24 } from "node:events";
|
|
133517
134218
|
var ProjectManager;
|
|
133518
134219
|
var init_project_manager = __esm({
|
|
133519
134220
|
"../engine/src/project-manager.ts"() {
|
|
@@ -133523,7 +134224,7 @@ var init_project_manager = __esm({
|
|
|
133523
134224
|
init_remote_node_runtime();
|
|
133524
134225
|
init_concurrency();
|
|
133525
134226
|
init_logger2();
|
|
133526
|
-
ProjectManager = class extends
|
|
134227
|
+
ProjectManager = class extends EventEmitter24 {
|
|
133527
134228
|
/**
|
|
133528
134229
|
* @param centralCore - CentralCore reference for global coordination
|
|
133529
134230
|
*/
|
|
@@ -133990,6 +134691,653 @@ var init_gridlock_detector = __esm({
|
|
|
133990
134691
|
}
|
|
133991
134692
|
});
|
|
133992
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
|
+
|
|
133993
135341
|
// ../engine/src/research-dispatcher.ts
|
|
133994
135342
|
var log24, ResearchRunDispatcher;
|
|
133995
135343
|
var init_research_dispatcher = __esm({
|
|
@@ -134232,9 +135580,9 @@ var init_provider_adapters = __esm({
|
|
|
134232
135580
|
});
|
|
134233
135581
|
|
|
134234
135582
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
134235
|
-
import { EventEmitter as
|
|
134236
|
-
import { exec as exec36, execFile as
|
|
134237
|
-
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";
|
|
134238
135586
|
function nowIso2() {
|
|
134239
135587
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
134240
135588
|
}
|
|
@@ -134274,7 +135622,7 @@ function toStateError(code, err) {
|
|
|
134274
135622
|
at: nowIso2()
|
|
134275
135623
|
};
|
|
134276
135624
|
}
|
|
134277
|
-
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2,
|
|
135625
|
+
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, execFileAsync11, execAsync32, LineBuffer, TunnelProcessManager;
|
|
134278
135626
|
var init_tunnel_process_manager = __esm({
|
|
134279
135627
|
"../engine/src/remote-access/tunnel-process-manager.ts"() {
|
|
134280
135628
|
"use strict";
|
|
@@ -134283,8 +135631,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
134283
135631
|
init_provider_adapters();
|
|
134284
135632
|
DEFAULT_MAX_LOG_ENTRIES = 400;
|
|
134285
135633
|
DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
|
|
134286
|
-
|
|
134287
|
-
execAsync32 =
|
|
135634
|
+
execFileAsync11 = promisify44(execFile16);
|
|
135635
|
+
execAsync32 = promisify44(exec36);
|
|
134288
135636
|
LineBuffer = class {
|
|
134289
135637
|
pending = "";
|
|
134290
135638
|
push(chunk) {
|
|
@@ -134299,7 +135647,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134299
135647
|
return tail ? [tail] : [];
|
|
134300
135648
|
}
|
|
134301
135649
|
};
|
|
134302
|
-
TunnelProcessManager = class extends
|
|
135650
|
+
TunnelProcessManager = class extends EventEmitter25 {
|
|
134303
135651
|
maxLogEntries;
|
|
134304
135652
|
defaultStopTimeoutMs;
|
|
134305
135653
|
spawnImpl;
|
|
@@ -134361,7 +135709,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134361
135709
|
return null;
|
|
134362
135710
|
}
|
|
134363
135711
|
try {
|
|
134364
|
-
const { stdout } = await
|
|
135712
|
+
const { stdout } = await execFileAsync11("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
134365
135713
|
const data = JSON.parse(String(stdout));
|
|
134366
135714
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
134367
135715
|
if (!dnsName) {
|
|
@@ -134384,7 +135732,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
134384
135732
|
];
|
|
134385
135733
|
for (const resetCommand of resetCommands) {
|
|
134386
135734
|
try {
|
|
134387
|
-
await
|
|
135735
|
+
await execFileAsync11(resetCommand.command, resetCommand.args, { timeout: 5e3 });
|
|
134388
135736
|
return;
|
|
134389
135737
|
} catch {
|
|
134390
135738
|
}
|
|
@@ -134685,8 +136033,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
134685
136033
|
});
|
|
134686
136034
|
|
|
134687
136035
|
// ../engine/src/project-engine.ts
|
|
134688
|
-
import { execFile as
|
|
134689
|
-
import { promisify as
|
|
136036
|
+
import { execFile as execFile17 } from "node:child_process";
|
|
136037
|
+
import { promisify as promisify45 } from "node:util";
|
|
134690
136038
|
function formatErrorDetails(error) {
|
|
134691
136039
|
if (error instanceof Error) {
|
|
134692
136040
|
return {
|
|
@@ -134701,6 +136049,40 @@ function isInvalidDoneTransitionError(error) {
|
|
|
134701
136049
|
const message = error instanceof Error ? error.message : String(error);
|
|
134702
136050
|
return message.includes("Invalid transition:") && message.includes("\u2192 'done'");
|
|
134703
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
|
+
}
|
|
134704
136086
|
function buildVerificationFailureSignature(error) {
|
|
134705
136087
|
const commandResult = error.verificationResult.testResult ?? error.verificationResult.buildResult;
|
|
134706
136088
|
const lane = commandResult?.command?.trim() || error.verificationResult.failedCommand?.trim() || "verification-failure";
|
|
@@ -134711,7 +136093,7 @@ function buildVerificationFailureSignature(error) {
|
|
|
134711
136093
|
failedCommand: commandResult?.command ?? error.verificationResult.failedCommand ?? null
|
|
134712
136094
|
}).signature;
|
|
134713
136095
|
}
|
|
134714
|
-
var
|
|
136096
|
+
var execFileAsync12, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
134715
136097
|
var init_project_engine = __esm({
|
|
134716
136098
|
"../engine/src/project-engine.ts"() {
|
|
134717
136099
|
"use strict";
|
|
@@ -134725,6 +136107,7 @@ var init_project_engine = __esm({
|
|
|
134725
136107
|
init_auth_storage();
|
|
134726
136108
|
init_cron_runner();
|
|
134727
136109
|
init_merger();
|
|
136110
|
+
init_merger_ai();
|
|
134728
136111
|
init_concurrency();
|
|
134729
136112
|
init_logger2();
|
|
134730
136113
|
init_research_orchestrator();
|
|
@@ -134733,7 +136116,7 @@ var init_project_engine = __esm({
|
|
|
134733
136116
|
init_run_audit();
|
|
134734
136117
|
init_verification_followup_dedup();
|
|
134735
136118
|
init_tunnel_process_manager();
|
|
134736
|
-
|
|
136119
|
+
execFileAsync12 = promisify45(execFile17);
|
|
134737
136120
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
134738
136121
|
isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
|
134739
136122
|
ProjectEngine = class _ProjectEngine {
|
|
@@ -134765,6 +136148,7 @@ var init_project_engine = __esm({
|
|
|
134765
136148
|
notifier;
|
|
134766
136149
|
notificationService;
|
|
134767
136150
|
oauthExpiryMonitor;
|
|
136151
|
+
oauthValidityLogger;
|
|
134768
136152
|
gridlockDetector;
|
|
134769
136153
|
cronRunner;
|
|
134770
136154
|
automationStore;
|
|
@@ -134878,11 +136262,14 @@ var init_project_engine = __esm({
|
|
|
134878
136262
|
agentNameResolver
|
|
134879
136263
|
});
|
|
134880
136264
|
await this.notificationService.start();
|
|
136265
|
+
const authStorage = createFusionAuthStorage();
|
|
134881
136266
|
this.oauthExpiryMonitor = new OAuthExpiryMonitor({
|
|
134882
|
-
authStorage
|
|
136267
|
+
authStorage,
|
|
134883
136268
|
notificationService: this.notificationService
|
|
134884
136269
|
});
|
|
134885
136270
|
await this.oauthExpiryMonitor.start();
|
|
136271
|
+
this.oauthValidityLogger = new OAuthValidityLogger({ authStorage });
|
|
136272
|
+
await this.oauthValidityLogger.start();
|
|
134886
136273
|
this.notifier = new NtfyNotifier(
|
|
134887
136274
|
store,
|
|
134888
136275
|
{
|
|
@@ -135066,6 +136453,7 @@ ${detail}`
|
|
|
135066
136453
|
} catch {
|
|
135067
136454
|
}
|
|
135068
136455
|
this.oauthExpiryMonitor?.stop();
|
|
136456
|
+
this.oauthValidityLogger?.stop();
|
|
135069
136457
|
this.notificationService?.stop();
|
|
135070
136458
|
this.notifier?.stop();
|
|
135071
136459
|
this.gridlockDetector?.stop();
|
|
@@ -135473,7 +136861,7 @@ ${detail}`
|
|
|
135473
136861
|
async checkExecutableAvailable(command) {
|
|
135474
136862
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
135475
136863
|
try {
|
|
135476
|
-
await
|
|
136864
|
+
await execFileAsync12(checker, [command]);
|
|
135477
136865
|
return { available: true };
|
|
135478
136866
|
} catch {
|
|
135479
136867
|
return {
|
|
@@ -135673,6 +137061,112 @@ ${detail}`
|
|
|
135673
137061
|
continue;
|
|
135674
137062
|
}
|
|
135675
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
|
+
}
|
|
135676
137170
|
const blockerReason = getTaskHardMergeBlocker(task);
|
|
135677
137171
|
if (blockerReason) {
|
|
135678
137172
|
await store.updateTask(taskId, {
|
|
@@ -135808,10 +137302,10 @@ ${detail}`
|
|
|
135808
137302
|
const pool = this.runtime.worktreePool;
|
|
135809
137303
|
const agentStore = this.runtime.agentStore;
|
|
135810
137304
|
const usageLimitPauser = this.runtime.usageLimitPauser;
|
|
135811
|
-
const rawMerge = () => {
|
|
137305
|
+
const rawMerge = async () => {
|
|
135812
137306
|
this.activeMergeTaskId = taskId;
|
|
135813
137307
|
this.mergeAbortController = new AbortController();
|
|
135814
|
-
|
|
137308
|
+
const mergerOptions = {
|
|
135815
137309
|
manual: !!manualResolver,
|
|
135816
137310
|
pool,
|
|
135817
137311
|
usageLimitPauser,
|
|
@@ -135820,7 +137314,9 @@ ${detail}`
|
|
|
135820
137314
|
onSession: (session) => {
|
|
135821
137315
|
this.activeMergeSession = session;
|
|
135822
137316
|
}
|
|
135823
|
-
}
|
|
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);
|
|
135824
137320
|
};
|
|
135825
137321
|
let result;
|
|
135826
137322
|
if (semaphore) {
|
|
@@ -138376,8 +139872,8 @@ var require_proper_lockfile = __commonJS({
|
|
|
138376
139872
|
import { mkdirSync as mkdirSync10 } from "node:fs";
|
|
138377
139873
|
import { access as access7, unlink as unlink7, writeFile as writeFile18 } from "node:fs/promises";
|
|
138378
139874
|
import net from "node:net";
|
|
138379
|
-
import { platform as platform3, tmpdir as
|
|
138380
|
-
import { join as
|
|
139875
|
+
import { platform as platform3, tmpdir as tmpdir6 } from "node:os";
|
|
139876
|
+
import { join as join51 } from "node:path";
|
|
138381
139877
|
import { createHash as createHash14 } from "node:crypto";
|
|
138382
139878
|
function projectHash(projectId) {
|
|
138383
139879
|
return createHash14("sha1").update(projectId).digest("hex").slice(0, 16);
|
|
@@ -138387,10 +139883,10 @@ function computeEngineSocketPath(projectId) {
|
|
|
138387
139883
|
if (platform3() === "win32") {
|
|
138388
139884
|
return `\\\\.\\pipe\\fusion-engine-${hash}`;
|
|
138389
139885
|
}
|
|
138390
|
-
return
|
|
139886
|
+
return join51(tmpdir6(), `fusion-engine-${hash}.sock`);
|
|
138391
139887
|
}
|
|
138392
139888
|
function computeEngineLockFilePath(workingDir) {
|
|
138393
|
-
return
|
|
139889
|
+
return join51(workingDir, ".fusion", "engine.lock");
|
|
138394
139890
|
}
|
|
138395
139891
|
async function ensureLockTargetExists(lockPath) {
|
|
138396
139892
|
try {
|
|
@@ -138400,7 +139896,7 @@ async function ensureLockTargetExists(lockPath) {
|
|
|
138400
139896
|
}
|
|
138401
139897
|
}
|
|
138402
139898
|
async function acquireLockfile(workingDir, onCompromised) {
|
|
138403
|
-
const dir =
|
|
139899
|
+
const dir = join51(workingDir, ".fusion");
|
|
138404
139900
|
mkdirSync10(dir, { recursive: true });
|
|
138405
139901
|
const path6 = computeEngineLockFilePath(workingDir);
|
|
138406
139902
|
await ensureLockTargetExists(path6);
|
|
@@ -139016,7 +140512,7 @@ var init_node_health_monitor = __esm({
|
|
|
139016
140512
|
});
|
|
139017
140513
|
|
|
139018
140514
|
// ../engine/src/hybrid-executor.ts
|
|
139019
|
-
import { EventEmitter as
|
|
140515
|
+
import { EventEmitter as EventEmitter26 } from "node:events";
|
|
139020
140516
|
var HybridExecutor;
|
|
139021
140517
|
var init_hybrid_executor = __esm({
|
|
139022
140518
|
"../engine/src/hybrid-executor.ts"() {
|
|
@@ -139024,7 +140520,7 @@ var init_hybrid_executor = __esm({
|
|
|
139024
140520
|
init_project_manager();
|
|
139025
140521
|
init_node_health_monitor();
|
|
139026
140522
|
init_logger2();
|
|
139027
|
-
HybridExecutor = class extends
|
|
140523
|
+
HybridExecutor = class extends EventEmitter26 {
|
|
139028
140524
|
/**
|
|
139029
140525
|
* @param centralCore - CentralCore reference for global coordination
|
|
139030
140526
|
* @param options - Optional configuration callbacks
|
|
@@ -140334,7 +141830,7 @@ var init_planning_board_tools = __esm({
|
|
|
140334
141830
|
|
|
140335
141831
|
// ../dashboard/src/planning.ts
|
|
140336
141832
|
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
140337
|
-
import { EventEmitter as
|
|
141833
|
+
import { EventEmitter as EventEmitter27 } from "node:events";
|
|
140338
141834
|
function ensureEngineReady() {
|
|
140339
141835
|
return Promise.resolve();
|
|
140340
141836
|
}
|
|
@@ -141358,7 +142854,7 @@ For completion:
|
|
|
141358
142854
|
process.on("beforeExit", () => {
|
|
141359
142855
|
clearInterval(cleanupInterval2);
|
|
141360
142856
|
});
|
|
141361
|
-
PlanningStreamManager = class extends
|
|
142857
|
+
PlanningStreamManager = class extends EventEmitter27 {
|
|
141362
142858
|
constructor(bufferSize = 100) {
|
|
141363
142859
|
super();
|
|
141364
142860
|
this.bufferSize = bufferSize;
|
|
@@ -141573,7 +143069,7 @@ var init_ai_session_timeout = __esm({
|
|
|
141573
143069
|
});
|
|
141574
143070
|
|
|
141575
143071
|
// ../dashboard/src/subtask-breakdown.ts
|
|
141576
|
-
import { EventEmitter as
|
|
143072
|
+
import { EventEmitter as EventEmitter28 } from "node:events";
|
|
141577
143073
|
function cleanupInMemorySubtaskSession(sessionId) {
|
|
141578
143074
|
const session = sessions2.get(sessionId);
|
|
141579
143075
|
if (!session) {
|
|
@@ -141615,7 +143111,7 @@ var init_subtask_breakdown = __esm({
|
|
|
141615
143111
|
process.on("beforeExit", () => {
|
|
141616
143112
|
clearInterval(cleanupInterval3);
|
|
141617
143113
|
});
|
|
141618
|
-
SubtaskStreamManager = class extends
|
|
143114
|
+
SubtaskStreamManager = class extends EventEmitter28 {
|
|
141619
143115
|
constructor(bufferSize = 100) {
|
|
141620
143116
|
super();
|
|
141621
143117
|
this.bufferSize = bufferSize;
|
|
@@ -141677,7 +143173,7 @@ var init_subtask_breakdown = __esm({
|
|
|
141677
143173
|
});
|
|
141678
143174
|
|
|
141679
143175
|
// ../dashboard/src/mission-interview.ts
|
|
141680
|
-
import { EventEmitter as
|
|
143176
|
+
import { EventEmitter as EventEmitter29 } from "node:events";
|
|
141681
143177
|
function cleanupInMemoryMissionSession(sessionId) {
|
|
141682
143178
|
const session = sessions3.get(sessionId);
|
|
141683
143179
|
if (!session) {
|
|
@@ -141728,7 +143224,7 @@ var init_mission_interview = __esm({
|
|
|
141728
143224
|
cleanupInterval4 = setInterval(cleanupExpiredSessions3, CLEANUP_INTERVAL_MS4);
|
|
141729
143225
|
cleanupInterval4.unref?.();
|
|
141730
143226
|
process.on("beforeExit", () => clearInterval(cleanupInterval4));
|
|
141731
|
-
MissionInterviewStreamManager = class extends
|
|
143227
|
+
MissionInterviewStreamManager = class extends EventEmitter29 {
|
|
141732
143228
|
constructor(bufferSize = 100) {
|
|
141733
143229
|
super();
|
|
141734
143230
|
this.bufferSize = bufferSize;
|
|
@@ -141796,7 +143292,7 @@ var init_mission_interview = __esm({
|
|
|
141796
143292
|
});
|
|
141797
143293
|
|
|
141798
143294
|
// ../dashboard/src/milestone-slice-interview.ts
|
|
141799
|
-
import { EventEmitter as
|
|
143295
|
+
import { EventEmitter as EventEmitter30 } from "node:events";
|
|
141800
143296
|
function cleanupInMemorySession2(sessionId) {
|
|
141801
143297
|
const session = sessions4.get(sessionId);
|
|
141802
143298
|
if (!session) {
|
|
@@ -141848,7 +143344,7 @@ var init_milestone_slice_interview = __esm({
|
|
|
141848
143344
|
cleanupInterval5 = setInterval(cleanupExpiredSessions4, CLEANUP_INTERVAL_MS5);
|
|
141849
143345
|
cleanupInterval5.unref?.();
|
|
141850
143346
|
process.on("beforeExit", () => clearInterval(cleanupInterval5));
|
|
141851
|
-
MilestoneSliceInterviewStreamManager = class extends
|
|
143347
|
+
MilestoneSliceInterviewStreamManager = class extends EventEmitter30 {
|
|
141852
143348
|
constructor(bufferSize = 100) {
|
|
141853
143349
|
super();
|
|
141854
143350
|
this.bufferSize = bufferSize;
|
|
@@ -142163,13 +143659,13 @@ var init_cli_spawn = __esm({
|
|
|
142163
143659
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/fusion-skill-install.js
|
|
142164
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";
|
|
142165
143661
|
import { homedir as homedir8 } from "node:os";
|
|
142166
|
-
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";
|
|
142167
143663
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
142168
143664
|
function resolveHermesHome(profile) {
|
|
142169
|
-
const base = process.env.HERMES_HOME ??
|
|
143665
|
+
const base = process.env.HERMES_HOME ?? join52(homedir8(), ".hermes");
|
|
142170
143666
|
if (!profile || profile === "default")
|
|
142171
143667
|
return base;
|
|
142172
|
-
return
|
|
143668
|
+
return join52(base, "profiles", profile);
|
|
142173
143669
|
}
|
|
142174
143670
|
function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
142175
143671
|
const here = fileURLToPath4(moduleUrl);
|
|
@@ -142184,14 +143680,14 @@ function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
|
|
|
142184
143680
|
function resolveBundledFusionSkillSource() {
|
|
142185
143681
|
const candidates = getFusionSkillSourceCandidates();
|
|
142186
143682
|
for (const candidate of candidates) {
|
|
142187
|
-
if (existsSync43(
|
|
143683
|
+
if (existsSync43(join52(candidate, "SKILL.md")))
|
|
142188
143684
|
return candidate;
|
|
142189
143685
|
}
|
|
142190
143686
|
return null;
|
|
142191
143687
|
}
|
|
142192
143688
|
function installFusionSkillIntoHermesHome(options = {}) {
|
|
142193
143689
|
const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
|
|
142194
|
-
const targetDir =
|
|
143690
|
+
const targetDir = join52(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
|
|
142195
143691
|
if (!sourceDir) {
|
|
142196
143692
|
return {
|
|
142197
143693
|
outcome: "warning",
|
|
@@ -142280,7 +143776,7 @@ function isBrokenSymlink(path6) {
|
|
|
142280
143776
|
}
|
|
142281
143777
|
}
|
|
142282
143778
|
function looksLikePriorFusionInstall(path6) {
|
|
142283
|
-
const skillMd =
|
|
143779
|
+
const skillMd = join52(path6, "SKILL.md");
|
|
142284
143780
|
if (!existsSync43(skillMd))
|
|
142285
143781
|
return false;
|
|
142286
143782
|
try {
|
|
@@ -142295,7 +143791,7 @@ function looksLikeFusionSkillTarget(path6) {
|
|
|
142295
143791
|
return false;
|
|
142296
143792
|
if (basename14(path6).toLowerCase() === FUSION_SKILL_NAME)
|
|
142297
143793
|
return true;
|
|
142298
|
-
return existsSync43(
|
|
143794
|
+
return existsSync43(join52(path6, "SKILL.md"));
|
|
142299
143795
|
}
|
|
142300
143796
|
var FUSION_SKILL_NAME;
|
|
142301
143797
|
var init_fusion_skill_install = __esm({
|
|
@@ -142673,8 +144169,8 @@ var init_pi_module = __esm({
|
|
|
142673
144169
|
|
|
142674
144170
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/mcp-config.js
|
|
142675
144171
|
import { writeFileSync as writeFileSync5 } from "node:fs";
|
|
142676
|
-
import { tmpdir as
|
|
142677
|
-
import { join as
|
|
144172
|
+
import { tmpdir as tmpdir7 } from "node:os";
|
|
144173
|
+
import { join as join53 } from "node:path";
|
|
142678
144174
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
142679
144175
|
import { dirname as dirname20 } from "node:path";
|
|
142680
144176
|
function toolsToMcpToolDefs(tools) {
|
|
@@ -142688,16 +144184,16 @@ function toolsToMcpToolDefs(tools) {
|
|
|
142688
144184
|
}
|
|
142689
144185
|
function writeOpenClawMcpBridgeFiles(toolDefs, cacheKey) {
|
|
142690
144186
|
const suffix = cacheKey ? `${process.pid}-${cacheKey}` : `${process.pid}`;
|
|
142691
|
-
const schemaPath =
|
|
144187
|
+
const schemaPath = join53(tmpdir7(), `openclaw-runtime-mcp-schemas-${suffix}.json`);
|
|
142692
144188
|
writeFileSync5(schemaPath, JSON.stringify(toolDefs));
|
|
142693
144189
|
const __filename = fileURLToPath5(import.meta.url);
|
|
142694
144190
|
const __dirname2 = dirname20(__filename);
|
|
142695
|
-
const serverPath =
|
|
144191
|
+
const serverPath = join53(__dirname2, "mcp-schema-server.cjs");
|
|
142696
144192
|
const serverConfig = {
|
|
142697
144193
|
command: "node",
|
|
142698
144194
|
args: [serverPath, schemaPath]
|
|
142699
144195
|
};
|
|
142700
|
-
const serverConfigPath =
|
|
144196
|
+
const serverConfigPath = join53(tmpdir7(), `openclaw-runtime-mcp-server-${suffix}.json`);
|
|
142701
144197
|
writeFileSync5(serverConfigPath, JSON.stringify(serverConfig));
|
|
142702
144198
|
return {
|
|
142703
144199
|
schemaPath,
|
|
@@ -142942,10 +144438,10 @@ var init_context = __esm({
|
|
|
142942
144438
|
|
|
142943
144439
|
// ../dashboard/src/github.ts
|
|
142944
144440
|
import { exec as exec37 } from "node:child_process";
|
|
142945
|
-
import { mkdtemp as
|
|
142946
|
-
import { tmpdir as
|
|
142947
|
-
import { join as
|
|
142948
|
-
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";
|
|
142949
144445
|
function quoteGitArg(value) {
|
|
142950
144446
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
142951
144447
|
}
|
|
@@ -143203,7 +144699,7 @@ var init_github = __esm({
|
|
|
143203
144699
|
"../dashboard/src/github.ts"() {
|
|
143204
144700
|
"use strict";
|
|
143205
144701
|
init_src();
|
|
143206
|
-
execAsync33 =
|
|
144702
|
+
execAsync33 = promisify46(exec37);
|
|
143207
144703
|
PR_REVIEW_PAGE_SIZE = 100;
|
|
143208
144704
|
MAX_PR_REVIEW_PAGES = 10;
|
|
143209
144705
|
MAX_BADGE_BATCH_SIZE = 100;
|
|
@@ -143871,8 +145367,8 @@ var init_github = __esm({
|
|
|
143871
145367
|
{ timeout: 3e4, maxBuffer: 10 * 1024 * 1024 }
|
|
143872
145368
|
);
|
|
143873
145369
|
const mergeBase = mergeBaseStdout.trim();
|
|
143874
|
-
const indexDir = await
|
|
143875
|
-
const indexPath =
|
|
145370
|
+
const indexDir = await mkdtemp3(join54(tmpdir8(), "fn-pr-conflict-"));
|
|
145371
|
+
const indexPath = join54(indexDir, "index");
|
|
143876
145372
|
const gitEnv = { ...process.env, GIT_INDEX_FILE: indexPath };
|
|
143877
145373
|
try {
|
|
143878
145374
|
await execAsync33(`git -C ${quoteGitArg(opts.repoRoot)} read-tree -m ${quoteGitArg(mergeBase)} ${quoteGitArg(baseRef)} ${quoteGitArg(headRef)}`, {
|
|
@@ -143891,7 +145387,7 @@ var init_github = __esm({
|
|
|
143891
145387
|
)
|
|
143892
145388
|
];
|
|
143893
145389
|
} finally {
|
|
143894
|
-
await
|
|
145390
|
+
await rm7(indexDir, { recursive: true, force: true });
|
|
143895
145391
|
}
|
|
143896
145392
|
} catch {
|
|
143897
145393
|
conflictingFiles = [];
|
|
@@ -145923,8 +147419,18 @@ var init_github_tracking_reconciler = __esm({
|
|
|
145923
147419
|
}
|
|
145924
147420
|
});
|
|
145925
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
|
+
|
|
145926
147432
|
// ../dashboard/src/github-poll.ts
|
|
145927
|
-
import { EventEmitter as
|
|
147433
|
+
import { EventEmitter as EventEmitter31 } from "node:events";
|
|
145928
147434
|
function toAlias(type, number) {
|
|
145929
147435
|
return `${type}_${number}`;
|
|
145930
147436
|
}
|
|
@@ -145970,7 +147476,7 @@ var init_github_poll = __esm({
|
|
|
145970
147476
|
}
|
|
145971
147477
|
};
|
|
145972
147478
|
githubRateLimiter = new GitHubRateLimiter();
|
|
145973
|
-
GitHubPollingService = class extends
|
|
147479
|
+
GitHubPollingService = class extends EventEmitter31 {
|
|
145974
147480
|
watches = /* @__PURE__ */ new Map();
|
|
145975
147481
|
rateLimiter;
|
|
145976
147482
|
pollingIntervalMs;
|
|
@@ -146215,10 +147721,10 @@ var init_github_poll = __esm({
|
|
|
146215
147721
|
});
|
|
146216
147722
|
|
|
146217
147723
|
// ../dashboard/src/pr-metadata-generator.ts
|
|
146218
|
-
import { promisify as
|
|
147724
|
+
import { promisify as promisify47 } from "node:util";
|
|
146219
147725
|
import { exec as execCb } from "node:child_process";
|
|
146220
147726
|
import { access as access8, readFile as readFile26 } from "node:fs/promises";
|
|
146221
|
-
import { join as
|
|
147727
|
+
import { join as join55 } from "node:path";
|
|
146222
147728
|
function buildFallback(task) {
|
|
146223
147729
|
return {
|
|
146224
147730
|
title: task.title ?? task.id,
|
|
@@ -146351,12 +147857,12 @@ async function generatePrMetadata(input) {
|
|
|
146351
147857
|
]);
|
|
146352
147858
|
let promptContent = "";
|
|
146353
147859
|
try {
|
|
146354
|
-
const promptPath =
|
|
147860
|
+
const promptPath = join55(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
|
|
146355
147861
|
promptContent = (await readFile26(promptPath, "utf8")).trim();
|
|
146356
147862
|
} catch {
|
|
146357
147863
|
promptContent = "";
|
|
146358
147864
|
}
|
|
146359
|
-
const templatePath =
|
|
147865
|
+
const templatePath = join55(repoRoot, ".github", "pull_request_template.md");
|
|
146360
147866
|
const templateExists = await access8(templatePath).then(() => true).catch(() => false);
|
|
146361
147867
|
const template = templateExists ? await readFile26(templatePath, "utf8") : "";
|
|
146362
147868
|
const model = resolveTaskPlanningModel(task, settings);
|
|
@@ -146415,24 +147921,24 @@ var init_pr_metadata_generator = __esm({
|
|
|
146415
147921
|
"use strict";
|
|
146416
147922
|
init_src();
|
|
146417
147923
|
init_src2();
|
|
146418
|
-
execAsync34 =
|
|
147924
|
+
execAsync34 = promisify47(execCb);
|
|
146419
147925
|
}
|
|
146420
147926
|
});
|
|
146421
147927
|
|
|
146422
147928
|
// ../dashboard/src/routes/resolve-diff-base.ts
|
|
146423
|
-
import { execFile as
|
|
146424
|
-
import { promisify as
|
|
146425
|
-
var
|
|
147929
|
+
import { execFile as execFile18 } from "node:child_process";
|
|
147930
|
+
import { promisify as promisify48 } from "node:util";
|
|
147931
|
+
var execFileAsync13;
|
|
146426
147932
|
var init_resolve_diff_base = __esm({
|
|
146427
147933
|
"../dashboard/src/routes/resolve-diff-base.ts"() {
|
|
146428
147934
|
"use strict";
|
|
146429
|
-
|
|
147935
|
+
execFileAsync13 = promisify48(execFile18);
|
|
146430
147936
|
}
|
|
146431
147937
|
});
|
|
146432
147938
|
|
|
146433
147939
|
// ../dashboard/src/routes/register-git-github.ts
|
|
146434
147940
|
import { exec as execCb2, spawn as spawn12 } from "node:child_process";
|
|
146435
|
-
import { promisify as
|
|
147941
|
+
import { promisify as promisify49 } from "node:util";
|
|
146436
147942
|
var execAsync35, PR_ROUTE_MAX_BUFFER_BYTES;
|
|
146437
147943
|
var init_register_git_github = __esm({
|
|
146438
147944
|
"../dashboard/src/routes/register-git-github.ts"() {
|
|
@@ -146445,12 +147951,13 @@ var init_register_git_github = __esm({
|
|
|
146445
147951
|
init_github_tracking_comments();
|
|
146446
147952
|
init_github_tracking_state();
|
|
146447
147953
|
init_github_tracking_reconciler();
|
|
147954
|
+
init_github_source_issue_close();
|
|
146448
147955
|
init_github_poll();
|
|
146449
147956
|
init_project_store_resolver();
|
|
146450
147957
|
init_pr_metadata_generator();
|
|
146451
147958
|
init_github_webhooks();
|
|
146452
147959
|
init_resolve_diff_base();
|
|
146453
|
-
execAsync35 =
|
|
147960
|
+
execAsync35 = promisify49(execCb2);
|
|
146454
147961
|
PR_ROUTE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
146455
147962
|
}
|
|
146456
147963
|
});
|
|
@@ -146489,8 +147996,8 @@ var init_register_planning_subtask_routes = __esm({
|
|
|
146489
147996
|
});
|
|
146490
147997
|
|
|
146491
147998
|
// ../dashboard/src/chat.ts
|
|
146492
|
-
import { EventEmitter as
|
|
146493
|
-
import { SessionManager as SessionManager3 } from "@
|
|
147999
|
+
import { EventEmitter as EventEmitter32 } from "node:events";
|
|
148000
|
+
import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
|
|
146494
148001
|
var defaultDiagnostics, _diagnostics, diagnostics6, RATE_LIMIT_WINDOW_MS5, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
|
|
146495
148002
|
var init_chat = __esm({
|
|
146496
148003
|
"../dashboard/src/chat.ts"() {
|
|
@@ -146524,7 +148031,7 @@ var init_chat = __esm({
|
|
|
146524
148031
|
};
|
|
146525
148032
|
RATE_LIMIT_WINDOW_MS5 = 60 * 1e3;
|
|
146526
148033
|
MAX_REFERENCED_FILE_SIZE = 50 * 1024;
|
|
146527
|
-
ChatStreamManager = class extends
|
|
148034
|
+
ChatStreamManager = class extends EventEmitter32 {
|
|
146528
148035
|
constructor(bufferSize = 100) {
|
|
146529
148036
|
super();
|
|
146530
148037
|
this.bufferSize = bufferSize;
|
|
@@ -151308,7 +152815,7 @@ var init_register_messaging_scripts = __esm({
|
|
|
151308
152815
|
// ../dashboard/src/terminal.ts
|
|
151309
152816
|
import { spawn as spawn13 } from "node:child_process";
|
|
151310
152817
|
import { randomUUID as randomUUID25 } from "node:crypto";
|
|
151311
|
-
import { EventEmitter as
|
|
152818
|
+
import { EventEmitter as EventEmitter33 } from "node:events";
|
|
151312
152819
|
function extractBaseCommand(command) {
|
|
151313
152820
|
let trimmed = command.trim();
|
|
151314
152821
|
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
|
|
@@ -151454,7 +152961,7 @@ var init_terminal = __esm({
|
|
|
151454
152961
|
// >(...) — process substitution (bash)
|
|
151455
152962
|
];
|
|
151456
152963
|
CHAIN_OPERATORS = /&&|\|\||;|(?<!\|)\|(?!\|)/;
|
|
151457
|
-
TerminalSessionManager = class extends
|
|
152964
|
+
TerminalSessionManager = class extends EventEmitter33 {
|
|
151458
152965
|
sessions = /* @__PURE__ */ new Map();
|
|
151459
152966
|
defaultTimeout = 3e4;
|
|
151460
152967
|
// 30 seconds
|
|
@@ -151662,19 +153169,19 @@ var init_register_agents_projects_nodes = __esm({
|
|
|
151662
153169
|
});
|
|
151663
153170
|
|
|
151664
153171
|
// ../dashboard/src/exec-file.ts
|
|
151665
|
-
import { execFile as
|
|
151666
|
-
import { promisify as
|
|
151667
|
-
var
|
|
153172
|
+
import { execFile as execFile19 } from "node:child_process";
|
|
153173
|
+
import { promisify as promisify50 } from "node:util";
|
|
153174
|
+
var execFileAsync14;
|
|
151668
153175
|
var init_exec_file = __esm({
|
|
151669
153176
|
"../dashboard/src/exec-file.ts"() {
|
|
151670
153177
|
"use strict";
|
|
151671
|
-
|
|
153178
|
+
execFileAsync14 = promisify50(execFile19);
|
|
151672
153179
|
}
|
|
151673
153180
|
});
|
|
151674
153181
|
|
|
151675
153182
|
// ../dashboard/src/routes/register-project-routes.ts
|
|
151676
153183
|
import * as fsPromises from "node:fs/promises";
|
|
151677
|
-
var access9, stat9, mkdir16, readdir9,
|
|
153184
|
+
var access9, stat9, mkdir16, readdir9, rm8;
|
|
151678
153185
|
var init_register_project_routes = __esm({
|
|
151679
153186
|
"../dashboard/src/routes/register-project-routes.ts"() {
|
|
151680
153187
|
"use strict";
|
|
@@ -151687,7 +153194,7 @@ var init_register_project_routes = __esm({
|
|
|
151687
153194
|
stat: stat9,
|
|
151688
153195
|
mkdir: mkdir16,
|
|
151689
153196
|
readdir: readdir9,
|
|
151690
|
-
rm:
|
|
153197
|
+
rm: rm8
|
|
151691
153198
|
} = fsPromises);
|
|
151692
153199
|
}
|
|
151693
153200
|
});
|
|
@@ -151883,7 +153390,7 @@ var init_agent_generation = __esm({
|
|
|
151883
153390
|
|
|
151884
153391
|
// ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
|
|
151885
153392
|
import * as fsPromises2 from "node:fs/promises";
|
|
151886
|
-
var
|
|
153393
|
+
var mkdtemp4, access10, stat10, mkdir17, rm9, fsWriteFile;
|
|
151887
153394
|
var init_register_agent_import_export_generation_routes = __esm({
|
|
151888
153395
|
"../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
|
|
151889
153396
|
"use strict";
|
|
@@ -151891,7 +153398,7 @@ var init_register_agent_import_export_generation_routes = __esm({
|
|
|
151891
153398
|
init_ai_session_diagnostics();
|
|
151892
153399
|
init_sse_buffer();
|
|
151893
153400
|
init_agent_generation();
|
|
151894
|
-
({ mkdtemp:
|
|
153401
|
+
({ mkdtemp: mkdtemp4, access: access10, stat: stat10, mkdir: mkdir17, rm: rm9, writeFile: fsWriteFile } = fsPromises2);
|
|
151895
153402
|
}
|
|
151896
153403
|
});
|
|
151897
153404
|
|
|
@@ -153161,6 +154668,16 @@ var init_todo_routes = __esm({
|
|
|
153161
154668
|
}
|
|
153162
154669
|
});
|
|
153163
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
|
+
|
|
153164
154681
|
// ../../plugins/fusion-plugin-roadmap/src/store/roadmap-ordering.js
|
|
153165
154682
|
function compareOrderedEntities(a, b2) {
|
|
153166
154683
|
if (a.orderIndex !== b2.orderIndex) {
|
|
@@ -153326,13 +154843,13 @@ var init_roadmap_ordering = __esm({
|
|
|
153326
154843
|
});
|
|
153327
154844
|
|
|
153328
154845
|
// ../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js
|
|
153329
|
-
import { EventEmitter as
|
|
154846
|
+
import { EventEmitter as EventEmitter34 } from "node:events";
|
|
153330
154847
|
var RoadmapStore;
|
|
153331
154848
|
var init_roadmap_store = __esm({
|
|
153332
154849
|
"../../plugins/fusion-plugin-roadmap/src/store/roadmap-store.js"() {
|
|
153333
154850
|
"use strict";
|
|
153334
154851
|
init_roadmap_ordering();
|
|
153335
|
-
RoadmapStore = class extends
|
|
154852
|
+
RoadmapStore = class extends EventEmitter34 {
|
|
153336
154853
|
db;
|
|
153337
154854
|
/**
|
|
153338
154855
|
* Creates a new RoadmapStore instance.
|
|
@@ -154836,7 +156353,7 @@ var init_src5 = __esm({
|
|
|
154836
156353
|
});
|
|
154837
156354
|
|
|
154838
156355
|
// ../dashboard/src/roadmap-routes.ts
|
|
154839
|
-
import { Router as
|
|
156356
|
+
import { Router as Router10 } from "express";
|
|
154840
156357
|
var init_roadmap_routes3 = __esm({
|
|
154841
156358
|
"../dashboard/src/roadmap-routes.ts"() {
|
|
154842
156359
|
"use strict";
|
|
@@ -154880,7 +156397,7 @@ var init_dev_server_process = __esm({
|
|
|
154880
156397
|
});
|
|
154881
156398
|
|
|
154882
156399
|
// ../dashboard/src/dev-server-routes.ts
|
|
154883
|
-
import { Router as
|
|
156400
|
+
import { Router as Router11 } from "express";
|
|
154884
156401
|
var init_dev_server_routes = __esm({
|
|
154885
156402
|
"../dashboard/src/dev-server-routes.ts"() {
|
|
154886
156403
|
"use strict";
|
|
@@ -154892,7 +156409,7 @@ var init_dev_server_routes = __esm({
|
|
|
154892
156409
|
});
|
|
154893
156410
|
|
|
154894
156411
|
// ../dashboard/src/routes/register-stash-recovery-routes.ts
|
|
154895
|
-
import { Router as
|
|
156412
|
+
import { Router as Router12 } from "express";
|
|
154896
156413
|
var init_register_stash_recovery_routes = __esm({
|
|
154897
156414
|
"../dashboard/src/routes/register-stash-recovery-routes.ts"() {
|
|
154898
156415
|
"use strict";
|
|
@@ -154911,6 +156428,7 @@ var init_register_integrated_routers = __esm({
|
|
|
154911
156428
|
init_research_routes();
|
|
154912
156429
|
init_experiment_routes();
|
|
154913
156430
|
init_todo_routes();
|
|
156431
|
+
init_goals_routes();
|
|
154914
156432
|
init_roadmap_routes3();
|
|
154915
156433
|
init_dev_server_routes();
|
|
154916
156434
|
init_register_stash_recovery_routes();
|
|
@@ -157275,7 +158793,7 @@ var require_extension = __commonJS({
|
|
|
157275
158793
|
var require_websocket = __commonJS({
|
|
157276
158794
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports, module) {
|
|
157277
158795
|
"use strict";
|
|
157278
|
-
var
|
|
158796
|
+
var EventEmitter35 = __require("events");
|
|
157279
158797
|
var https = __require("https");
|
|
157280
158798
|
var http = __require("http");
|
|
157281
158799
|
var net2 = __require("net");
|
|
@@ -157307,7 +158825,7 @@ var require_websocket = __commonJS({
|
|
|
157307
158825
|
var protocolVersions = [8, 13];
|
|
157308
158826
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
157309
158827
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
157310
|
-
var WebSocket2 = class _WebSocket extends
|
|
158828
|
+
var WebSocket2 = class _WebSocket extends EventEmitter35 {
|
|
157311
158829
|
/**
|
|
157312
158830
|
* Create a new `WebSocket`.
|
|
157313
158831
|
*
|
|
@@ -158304,7 +159822,7 @@ var require_subprotocol = __commonJS({
|
|
|
158304
159822
|
var require_websocket_server = __commonJS({
|
|
158305
159823
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
|
|
158306
159824
|
"use strict";
|
|
158307
|
-
var
|
|
159825
|
+
var EventEmitter35 = __require("events");
|
|
158308
159826
|
var http = __require("http");
|
|
158309
159827
|
var { Duplex } = __require("stream");
|
|
158310
159828
|
var { createHash: createHash15 } = __require("crypto");
|
|
@@ -158317,7 +159835,7 @@ var require_websocket_server = __commonJS({
|
|
|
158317
159835
|
var RUNNING = 0;
|
|
158318
159836
|
var CLOSING = 1;
|
|
158319
159837
|
var CLOSED = 2;
|
|
158320
|
-
var WebSocketServer2 = class extends
|
|
159838
|
+
var WebSocketServer2 = class extends EventEmitter35 {
|
|
158321
159839
|
/**
|
|
158322
159840
|
* Create a `WebSocketServer` instance.
|
|
158323
159841
|
*
|
|
@@ -158755,7 +160273,7 @@ var init_view_chunk_manifest = __esm({
|
|
|
158755
160273
|
|
|
158756
160274
|
// ../dashboard/src/server.ts
|
|
158757
160275
|
import express from "express";
|
|
158758
|
-
import { join as
|
|
160276
|
+
import { join as join56, dirname as dirname21 } from "node:path";
|
|
158759
160277
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
158760
160278
|
function clearAiSessionCleanupInterval() {
|
|
158761
160279
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -158864,6 +160382,7 @@ var init_src6 = __esm({
|
|
|
158864
160382
|
init_rate_limit();
|
|
158865
160383
|
init_github_poll();
|
|
158866
160384
|
init_github_issue_comment();
|
|
160385
|
+
init_github_source_issue_close();
|
|
158867
160386
|
init_github_tracking_comments();
|
|
158868
160387
|
init_github_tracking_state();
|
|
158869
160388
|
init_github_tracking_reconciler();
|
|
@@ -159060,7 +160579,7 @@ __export(task_exports, {
|
|
|
159060
160579
|
});
|
|
159061
160580
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
159062
160581
|
import { watchFile, unwatchFile, statSync as statSync7, existsSync as existsSync44, readFileSync as readFileSync15 } from "node:fs";
|
|
159063
|
-
import { basename as basename16, join as
|
|
160582
|
+
import { basename as basename16, join as join57 } from "node:path";
|
|
159064
160583
|
function formatGhErrorForCli(err) {
|
|
159065
160584
|
const structured = classifyGhError(err);
|
|
159066
160585
|
const lines = [`GitHub error: ${structured.message}`];
|
|
@@ -159521,7 +161040,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
159521
161040
|
printEntries(filteredEntries);
|
|
159522
161041
|
if (options.follow) {
|
|
159523
161042
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
159524
|
-
const logPath =
|
|
161043
|
+
const logPath = join57(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
159525
161044
|
if (!existsSync44(logPath)) {
|
|
159526
161045
|
console.log(`
|
|
159527
161046
|
Waiting for log file to be created...`);
|
|
@@ -160699,8 +162218,8 @@ init_gh_cli();
|
|
|
160699
162218
|
init_src2();
|
|
160700
162219
|
init_src6();
|
|
160701
162220
|
import { Type as Type8 } from "typebox";
|
|
160702
|
-
import { StringEnum } from "@
|
|
160703
|
-
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";
|
|
160704
162223
|
import { readFile as readFile28 } from "node:fs/promises";
|
|
160705
162224
|
import { existsSync as existsSync45 } from "node:fs";
|
|
160706
162225
|
import { spawn as spawn16 } from "node:child_process";
|
|
@@ -160726,12 +162245,12 @@ function resolveProjectRoot2(cwd) {
|
|
|
160726
162245
|
warnedMissingProjectRootResolver = true;
|
|
160727
162246
|
console.warn("[fusion-extension] @fusion/core.getProjectRootFromWorktree is unavailable; using filesystem fallback resolution.");
|
|
160728
162247
|
}
|
|
160729
|
-
if (worktreeProjectRoot && existsSync45(
|
|
162248
|
+
if (worktreeProjectRoot && existsSync45(join58(worktreeProjectRoot, ".fusion"))) {
|
|
160730
162249
|
return worktreeProjectRoot;
|
|
160731
162250
|
}
|
|
160732
162251
|
let current = resolve31(cwd);
|
|
160733
162252
|
while (true) {
|
|
160734
|
-
if (existsSync45(
|
|
162253
|
+
if (existsSync45(join58(current, ".fusion"))) {
|
|
160735
162254
|
return current;
|
|
160736
162255
|
}
|
|
160737
162256
|
const parent = resolve31(current, "..");
|
|
@@ -160752,7 +162271,7 @@ async function getStore2(cwd) {
|
|
|
160752
162271
|
return store;
|
|
160753
162272
|
}
|
|
160754
162273
|
function getFusionDir(cwd) {
|
|
160755
|
-
return
|
|
162274
|
+
return join58(resolveProjectRoot2(cwd), ".fusion");
|
|
160756
162275
|
}
|
|
160757
162276
|
function emitSecretAudit(store, ctx, mutationType, target, metadata) {
|
|
160758
162277
|
if (!ctx.runId || !ctx.agentId) return;
|
|
@@ -162616,6 +164135,136 @@ Status: ${createdMission.status}${createdMission.autoAdvance ? "\nAuto-advance:
|
|
|
162616
164135
|
};
|
|
162617
164136
|
}
|
|
162618
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
|
+
});
|
|
162619
164268
|
pi2.registerTool({
|
|
162620
164269
|
name: "fn_mission_show",
|
|
162621
164270
|
label: "fn: Show Mission",
|