@runfusion/fusion 0.1.2 → 0.2.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/README.md +2 -0
- package/dist/bin.js +4055 -1755
- package/dist/client/assets/AgentDetailView-CDZED6Dy.css +1 -0
- package/dist/client/assets/AgentDetailView-zycSdnO8.js +28 -0
- package/dist/client/assets/AgentsView-DoQkkDLf.css +1 -0
- package/dist/client/assets/AgentsView-pO7WiBS5.js +522 -0
- package/dist/client/assets/ChatView-BOd-sxbT.js +1 -0
- package/dist/client/assets/DevServerView-09GQf34f.js +11 -0
- package/dist/client/assets/DevServerView-ZeBGQkLI.css +1 -0
- package/dist/client/assets/DirectoryPicker-CcdN1Zs7.js +1 -0
- package/dist/client/assets/DocumentsView-CS8aiwtz.js +1 -0
- package/dist/client/assets/DocumentsView-Co9to4Zp.css +1 -0
- package/dist/client/assets/InsightsView-Bu9Cv8Ol.js +11 -0
- package/dist/client/assets/InsightsView-Egu71gmh.css +1 -0
- package/dist/client/assets/MemoryView-CtqgDtV9.js +2 -0
- package/dist/client/assets/MemoryView-DhinauGs.css +1 -0
- package/dist/client/assets/NodesView-BInPcedy.js +14 -0
- package/dist/client/assets/NodesView-DlQZHGXA.css +1 -0
- package/dist/client/assets/PiExtensionsManager-COxkYM2m.js +11 -0
- package/dist/client/assets/PiExtensionsManager-CPgmJgDk.css +1 -0
- package/dist/client/assets/PluginManager-CXUWZBOc.js +1 -0
- package/dist/client/assets/PluginManager-D64RIzmL.css +1 -0
- package/dist/client/assets/RoadmapsView-BOYnyMCh.css +1 -0
- package/dist/client/assets/RoadmapsView-BbCexaoi.js +6 -0
- package/dist/client/assets/SetupWizardModal-Cakxqkad.js +1 -0
- package/dist/client/assets/SkillsView-Cytf009Z.css +1 -0
- package/dist/client/assets/SkillsView-D3iqYCVf.js +1 -0
- package/dist/client/assets/folder-open-kO5Hsk66.js +6 -0
- package/dist/client/assets/index-BiSuUXCa.css +1 -0
- package/dist/client/assets/index-y194HxzU.js +644 -0
- package/dist/client/assets/upload-DHBQat92.js +6 -0
- package/dist/client/index.html +2 -2
- package/dist/client/sw.js +45 -1
- package/dist/client/theme-data.css +109 -0
- package/dist/extension.js +969 -408
- package/dist/pi-claude-cli/index.ts +131 -0
- package/dist/pi-claude-cli/package.json +39 -0
- package/dist/pi-claude-cli/src/__tests__/control-handler.test.ts +191 -0
- package/dist/pi-claude-cli/src/__tests__/event-bridge.test.ts +1244 -0
- package/dist/pi-claude-cli/src/__tests__/mcp-config.test.ts +272 -0
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +619 -0
- package/dist/pi-claude-cli/src/__tests__/prompt-builder.test.ts +1067 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +1902 -0
- package/dist/pi-claude-cli/src/__tests__/stream-parser.test.ts +188 -0
- package/dist/pi-claude-cli/src/__tests__/thinking-config.test.ts +141 -0
- package/dist/pi-claude-cli/src/__tests__/tool-mapping.test.ts +252 -0
- package/dist/pi-claude-cli/src/control-handler.ts +68 -0
- package/dist/pi-claude-cli/src/event-bridge.ts +386 -0
- package/dist/pi-claude-cli/src/mcp-config.ts +111 -0
- package/dist/pi-claude-cli/src/mcp-schema-server.cjs +49 -0
- package/dist/pi-claude-cli/src/process-manager.ts +218 -0
- package/dist/pi-claude-cli/src/prompt-builder.ts +536 -0
- package/dist/pi-claude-cli/src/provider.ts +354 -0
- package/dist/pi-claude-cli/src/stream-parser.ts +37 -0
- package/dist/pi-claude-cli/src/thinking-config.ts +83 -0
- package/dist/pi-claude-cli/src/tool-mapping.ts +147 -0
- package/dist/pi-claude-cli/src/types.ts +87 -0
- package/package.json +11 -4
- package/skill/fusion/SKILL.md +5 -3
- package/skill/fusion/references/cli-commands.md +22 -22
- package/skill/fusion/references/extension-tools.md +3 -1
- package/skill/fusion/references/fusion-capabilities.md +28 -35
- package/skill/fusion/references/task-structure.md +4 -4
- package/skill/fusion/workflows/dashboard-cli.md +6 -6
- package/skill/fusion/workflows/specifications.md +5 -3
- package/skill/fusion/workflows/task-lifecycle.md +1 -1
- package/skill/fusion/workflows/task-management.md +3 -1
- package/dist/client/assets/index-Djv5vKo0.css +0 -1
- package/dist/client/assets/index-zfXYuUXG.js +0 -1241
package/dist/extension.js
CHANGED
|
@@ -60,6 +60,7 @@ var init_settings_schema = __esm({
|
|
|
60
60
|
defaultThinkingLevel: void 0,
|
|
61
61
|
ntfyEnabled: false,
|
|
62
62
|
ntfyTopic: void 0,
|
|
63
|
+
ntfyBaseUrl: void 0,
|
|
63
64
|
ntfyEvents: ["in-review", "merged", "failed", "awaiting-approval", "awaiting-user-review", "planning-awaiting-input"],
|
|
64
65
|
ntfyDashboardHost: void 0,
|
|
65
66
|
defaultProjectId: void 0,
|
|
@@ -767,7 +768,7 @@ function validateMessageMetadata(metadata) {
|
|
|
767
768
|
throw new Error("metadata.replyTo.messageId must be a non-empty string");
|
|
768
769
|
}
|
|
769
770
|
}
|
|
770
|
-
var THINKING_LEVELS, COLUMNS, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, CheckoutConflictError, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS;
|
|
771
|
+
var THINKING_LEVELS, COLUMNS, TASK_PRIORITIES, DEFAULT_TASK_PRIORITY, EXECUTION_MODES, DEFAULT_EXECUTION_MODE, THEME_MODES, COLOR_THEMES, WORKFLOW_STEP_TEMPLATES, DOCUMENT_KEY_RE, CheckoutConflictError, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS, AGENT_VALID_TRANSITIONS, AGENT_PERMISSIONS;
|
|
771
772
|
var init_types = __esm({
|
|
772
773
|
"../core/src/types.ts"() {
|
|
773
774
|
"use strict";
|
|
@@ -776,6 +777,8 @@ var init_types = __esm({
|
|
|
776
777
|
init_error_message();
|
|
777
778
|
THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
778
779
|
COLUMNS = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
|
780
|
+
TASK_PRIORITIES = ["low", "normal", "high", "urgent"];
|
|
781
|
+
DEFAULT_TASK_PRIORITY = "normal";
|
|
779
782
|
EXECUTION_MODES = ["standard", "fast"];
|
|
780
783
|
DEFAULT_EXECUTION_MODE = "standard";
|
|
781
784
|
THEME_MODES = ["dark", "light", "system"];
|
|
@@ -2069,13 +2072,14 @@ var init_db = __esm({
|
|
|
2069
2072
|
"../core/src/db.ts"() {
|
|
2070
2073
|
"use strict";
|
|
2071
2074
|
init_types();
|
|
2072
|
-
SCHEMA_VERSION =
|
|
2075
|
+
SCHEMA_VERSION = 45;
|
|
2073
2076
|
SCHEMA_SQL = `
|
|
2074
2077
|
-- Tasks table with JSON columns for nested data
|
|
2075
2078
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
2076
2079
|
id TEXT PRIMARY KEY,
|
|
2077
2080
|
title TEXT,
|
|
2078
2081
|
description TEXT NOT NULL,
|
|
2082
|
+
priority TEXT DEFAULT 'normal',
|
|
2079
2083
|
"column" TEXT NOT NULL,
|
|
2080
2084
|
status TEXT,
|
|
2081
2085
|
size TEXT,
|
|
@@ -2103,6 +2107,12 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2103
2107
|
summary TEXT,
|
|
2104
2108
|
thinkingLevel TEXT,
|
|
2105
2109
|
executionMode TEXT DEFAULT 'standard',
|
|
2110
|
+
tokenUsageInputTokens INTEGER,
|
|
2111
|
+
tokenUsageOutputTokens INTEGER,
|
|
2112
|
+
tokenUsageCachedTokens INTEGER,
|
|
2113
|
+
tokenUsageTotalTokens INTEGER,
|
|
2114
|
+
tokenUsageFirstUsedAt TEXT,
|
|
2115
|
+
tokenUsageLastUsedAt TEXT,
|
|
2106
2116
|
createdAt TEXT NOT NULL,
|
|
2107
2117
|
updatedAt TEXT NOT NULL,
|
|
2108
2118
|
columnMovedAt TEXT,
|
|
@@ -2116,6 +2126,11 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2116
2126
|
workflowStepResults TEXT DEFAULT '[]',
|
|
2117
2127
|
prInfo TEXT,
|
|
2118
2128
|
issueInfo TEXT,
|
|
2129
|
+
sourceIssueProvider TEXT,
|
|
2130
|
+
sourceIssueRepository TEXT,
|
|
2131
|
+
sourceIssueExternalIssueId TEXT,
|
|
2132
|
+
sourceIssueNumber INTEGER,
|
|
2133
|
+
sourceIssueUrl TEXT,
|
|
2119
2134
|
mergeDetails TEXT,
|
|
2120
2135
|
breakIntoSubtasks INTEGER DEFAULT 0,
|
|
2121
2136
|
enabledWorkflowSteps TEXT DEFAULT '[]',
|
|
@@ -3410,6 +3425,35 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
3410
3425
|
`);
|
|
3411
3426
|
});
|
|
3412
3427
|
}
|
|
3428
|
+
if (version < 43) {
|
|
3429
|
+
this.applyMigration(43, () => {
|
|
3430
|
+
this.addColumnIfMissing("tasks", "priority", "TEXT DEFAULT 'normal'");
|
|
3431
|
+
this.db.exec(`
|
|
3432
|
+
UPDATE tasks
|
|
3433
|
+
SET priority = 'normal'
|
|
3434
|
+
WHERE priority IS NULL OR priority = '' OR priority NOT IN ('low', 'normal', 'high', 'urgent')
|
|
3435
|
+
`);
|
|
3436
|
+
});
|
|
3437
|
+
}
|
|
3438
|
+
if (version < 44) {
|
|
3439
|
+
this.applyMigration(44, () => {
|
|
3440
|
+
this.addColumnIfMissing("tasks", "tokenUsageInputTokens", "INTEGER");
|
|
3441
|
+
this.addColumnIfMissing("tasks", "tokenUsageOutputTokens", "INTEGER");
|
|
3442
|
+
this.addColumnIfMissing("tasks", "tokenUsageCachedTokens", "INTEGER");
|
|
3443
|
+
this.addColumnIfMissing("tasks", "tokenUsageTotalTokens", "INTEGER");
|
|
3444
|
+
this.addColumnIfMissing("tasks", "tokenUsageFirstUsedAt", "TEXT");
|
|
3445
|
+
this.addColumnIfMissing("tasks", "tokenUsageLastUsedAt", "TEXT");
|
|
3446
|
+
});
|
|
3447
|
+
}
|
|
3448
|
+
if (version < 45) {
|
|
3449
|
+
this.applyMigration(45, () => {
|
|
3450
|
+
this.addColumnIfMissing("tasks", "sourceIssueProvider", "TEXT");
|
|
3451
|
+
this.addColumnIfMissing("tasks", "sourceIssueRepository", "TEXT");
|
|
3452
|
+
this.addColumnIfMissing("tasks", "sourceIssueExternalIssueId", "TEXT");
|
|
3453
|
+
this.addColumnIfMissing("tasks", "sourceIssueNumber", "INTEGER");
|
|
3454
|
+
this.addColumnIfMissing("tasks", "sourceIssueUrl", "TEXT");
|
|
3455
|
+
});
|
|
3456
|
+
}
|
|
3413
3457
|
}
|
|
3414
3458
|
/**
|
|
3415
3459
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -5680,6 +5724,54 @@ var init_message_store = __esm({
|
|
|
5680
5724
|
}
|
|
5681
5725
|
});
|
|
5682
5726
|
|
|
5727
|
+
// ../core/src/task-priority.ts
|
|
5728
|
+
function isTaskPriority(value) {
|
|
5729
|
+
return typeof value === "string" && TASK_PRIORITIES.includes(value);
|
|
5730
|
+
}
|
|
5731
|
+
function normalizeTaskPriority(priority) {
|
|
5732
|
+
return isTaskPriority(priority) ? priority : DEFAULT_TASK_PRIORITY;
|
|
5733
|
+
}
|
|
5734
|
+
function getTaskPriorityRank(priority) {
|
|
5735
|
+
return PRIORITY_RANK[normalizeTaskPriority(priority)];
|
|
5736
|
+
}
|
|
5737
|
+
function compareTaskPriority(a, b) {
|
|
5738
|
+
return getTaskPriorityRank(b) - getTaskPriorityRank(a);
|
|
5739
|
+
}
|
|
5740
|
+
function compareTaskId(a, b) {
|
|
5741
|
+
const aNum = Number.parseInt(a.slice(a.lastIndexOf("-") + 1), 10);
|
|
5742
|
+
const bNum = Number.parseInt(b.slice(b.lastIndexOf("-") + 1), 10);
|
|
5743
|
+
if (Number.isFinite(aNum) && Number.isFinite(bNum) && aNum !== bNum) {
|
|
5744
|
+
return aNum - bNum;
|
|
5745
|
+
}
|
|
5746
|
+
return a.localeCompare(b);
|
|
5747
|
+
}
|
|
5748
|
+
function compareTasksByPriorityThenAgeAndId(a, b) {
|
|
5749
|
+
const priorityCmp = compareTaskPriority(a.priority, b.priority);
|
|
5750
|
+
if (priorityCmp !== 0) {
|
|
5751
|
+
return priorityCmp;
|
|
5752
|
+
}
|
|
5753
|
+
if (a.createdAt !== b.createdAt) {
|
|
5754
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
5755
|
+
}
|
|
5756
|
+
return compareTaskId(a.id, b.id);
|
|
5757
|
+
}
|
|
5758
|
+
function sortTasksByPriorityThenAgeAndId(tasks) {
|
|
5759
|
+
return [...tasks].sort(compareTasksByPriorityThenAgeAndId);
|
|
5760
|
+
}
|
|
5761
|
+
var PRIORITY_RANK;
|
|
5762
|
+
var init_task_priority = __esm({
|
|
5763
|
+
"../core/src/task-priority.ts"() {
|
|
5764
|
+
"use strict";
|
|
5765
|
+
init_types();
|
|
5766
|
+
PRIORITY_RANK = {
|
|
5767
|
+
low: 0,
|
|
5768
|
+
normal: 1,
|
|
5769
|
+
high: 2,
|
|
5770
|
+
urgent: 3
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
});
|
|
5774
|
+
|
|
5683
5775
|
// ../core/src/global-settings.ts
|
|
5684
5776
|
import { homedir } from "node:os";
|
|
5685
5777
|
import { dirname, join as join4 } from "node:path";
|
|
@@ -6177,17 +6269,18 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6177
6269
|
let skipped = 0;
|
|
6178
6270
|
const insertStmt = db.prepare(`
|
|
6179
6271
|
INSERT OR REPLACE INTO tasks (
|
|
6180
|
-
id, title, description, "column", status, size, reviewLevel, currentStep,
|
|
6272
|
+
id, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
6181
6273
|
worktree, blockedBy, paused, baseBranch, baseCommitSha, modelPresetId,
|
|
6182
6274
|
modelProvider, modelId, validatorModelProvider, validatorModelId,
|
|
6183
6275
|
mergeRetries, recoveryRetryCount, nextRecoveryAt,
|
|
6184
6276
|
error, summary, thinkingLevel, createdAt, updatedAt,
|
|
6185
6277
|
columnMovedAt, dependencies, steps, log, attachments, steeringComments,
|
|
6186
|
-
comments, workflowStepResults, prInfo, issueInfo,
|
|
6187
|
-
|
|
6278
|
+
comments, workflowStepResults, prInfo, issueInfo,
|
|
6279
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
6280
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, sliceId
|
|
6188
6281
|
) VALUES (
|
|
6189
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
6190
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
6282
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
6283
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
6191
6284
|
)
|
|
6192
6285
|
`);
|
|
6193
6286
|
for (const entry of entries) {
|
|
@@ -6205,6 +6298,7 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6205
6298
|
task.id,
|
|
6206
6299
|
task.title ?? null,
|
|
6207
6300
|
task.description,
|
|
6301
|
+
normalizeTaskPriority(task.priority),
|
|
6208
6302
|
task.column,
|
|
6209
6303
|
task.status ?? null,
|
|
6210
6304
|
task.size ?? null,
|
|
@@ -6238,6 +6332,11 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6238
6332
|
toJson(task.workflowStepResults || []),
|
|
6239
6333
|
toJsonNullable(task.prInfo),
|
|
6240
6334
|
toJsonNullable(task.issueInfo),
|
|
6335
|
+
task.sourceIssue?.provider ?? null,
|
|
6336
|
+
task.sourceIssue?.repository ?? null,
|
|
6337
|
+
task.sourceIssue?.externalIssueId ?? null,
|
|
6338
|
+
task.sourceIssue?.issueNumber ?? null,
|
|
6339
|
+
task.sourceIssue?.url ?? null,
|
|
6241
6340
|
toJsonNullable(task.mergeDetails),
|
|
6242
6341
|
task.breakIntoSubtasks ? 1 : 0,
|
|
6243
6342
|
toJson(task.enabledWorkflowSteps || []),
|
|
@@ -6488,6 +6587,7 @@ var init_db_migrate = __esm({
|
|
|
6488
6587
|
"../core/src/db-migrate.ts"() {
|
|
6489
6588
|
"use strict";
|
|
6490
6589
|
init_db();
|
|
6590
|
+
init_task_priority();
|
|
6491
6591
|
}
|
|
6492
6592
|
});
|
|
6493
6593
|
|
|
@@ -20322,7 +20422,7 @@ var require_luxon = __commonJS({
|
|
|
20322
20422
|
}, zone || mergedZone, next];
|
|
20323
20423
|
}, [{}, null, 1]).slice(0, 2);
|
|
20324
20424
|
}
|
|
20325
|
-
function
|
|
20425
|
+
function parse(s2, ...patterns) {
|
|
20326
20426
|
if (s2 == null) {
|
|
20327
20427
|
return [null, null];
|
|
20328
20428
|
}
|
|
@@ -20465,26 +20565,26 @@ var require_luxon = __commonJS({
|
|
|
20465
20565
|
var extractISOOrdinalDateAndTime = combineExtractors(extractISOOrdinalData, extractISOTime, extractISOOffset, extractIANAZone);
|
|
20466
20566
|
var extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);
|
|
20467
20567
|
function parseISODate(s2) {
|
|
20468
|
-
return
|
|
20568
|
+
return parse(s2, [isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset], [isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDateAndTime], [isoTimeCombinedRegex, extractISOTimeAndOffset]);
|
|
20469
20569
|
}
|
|
20470
20570
|
function parseRFC2822Date(s2) {
|
|
20471
|
-
return
|
|
20571
|
+
return parse(preprocessRFC2822(s2), [rfc2822, extractRFC2822]);
|
|
20472
20572
|
}
|
|
20473
20573
|
function parseHTTPDate(s2) {
|
|
20474
|
-
return
|
|
20574
|
+
return parse(s2, [rfc1123, extractRFC1123Or850], [rfc850, extractRFC1123Or850], [ascii, extractASCII]);
|
|
20475
20575
|
}
|
|
20476
20576
|
function parseISODuration(s2) {
|
|
20477
|
-
return
|
|
20577
|
+
return parse(s2, [isoDuration, extractISODuration]);
|
|
20478
20578
|
}
|
|
20479
20579
|
var extractISOTimeOnly = combineExtractors(extractISOTime);
|
|
20480
20580
|
function parseISOTimeOnly(s2) {
|
|
20481
|
-
return
|
|
20581
|
+
return parse(s2, [isoTimeOnly, extractISOTimeOnly]);
|
|
20482
20582
|
}
|
|
20483
20583
|
var sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);
|
|
20484
20584
|
var sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);
|
|
20485
20585
|
var extractISOTimeOffsetAndIANAZone = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);
|
|
20486
20586
|
function parseSQL(s2) {
|
|
20487
|
-
return
|
|
20587
|
+
return parse(s2, [sqlYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]);
|
|
20488
20588
|
}
|
|
20489
20589
|
var INVALID$2 = "Invalid Duration";
|
|
20490
20590
|
var lowOrderMatrix = {
|
|
@@ -26807,13 +26907,14 @@ __export(automation_store_exports, {
|
|
|
26807
26907
|
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
26808
26908
|
import { join as join12 } from "node:path";
|
|
26809
26909
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
26810
|
-
var import_cron_parser, AutomationStore;
|
|
26910
|
+
var import_cron_parser, CRON_TIMEZONE, AutomationStore;
|
|
26811
26911
|
var init_automation_store = __esm({
|
|
26812
26912
|
"../core/src/automation-store.ts"() {
|
|
26813
26913
|
"use strict";
|
|
26814
26914
|
import_cron_parser = __toESM(require_dist2(), 1);
|
|
26815
26915
|
init_automation();
|
|
26816
26916
|
init_db();
|
|
26917
|
+
CRON_TIMEZONE = "UTC";
|
|
26817
26918
|
AutomationStore = class _AutomationStore extends EventEmitter10 {
|
|
26818
26919
|
constructor(rootDir) {
|
|
26819
26920
|
super();
|
|
@@ -26931,7 +27032,8 @@ var init_automation_store = __esm({
|
|
|
26931
27032
|
*/
|
|
26932
27033
|
computeNextRun(cronExpression, fromDate) {
|
|
26933
27034
|
const interval = import_cron_parser.CronExpressionParser.parse(cronExpression, {
|
|
26934
|
-
currentDate: fromDate ?? /* @__PURE__ */ new Date()
|
|
27035
|
+
currentDate: fromDate ?? /* @__PURE__ */ new Date(),
|
|
27036
|
+
tz: CRON_TIMEZONE
|
|
26935
27037
|
});
|
|
26936
27038
|
const next = interval.next();
|
|
26937
27039
|
return next.toISOString() ?? new Date(next.getTime()).toISOString();
|
|
@@ -28551,8 +28653,8 @@ This project has OpenClaw-style memory files:
|
|
|
28551
28653
|
- \`.fusion/memory/YYYY-MM-DD.md\` \u2014 append-only daily notes for running context
|
|
28552
28654
|
|
|
28553
28655
|
**Before writing the specification:**
|
|
28554
|
-
1. Use \`
|
|
28555
|
-
2. Use \`
|
|
28656
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28657
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28556
28658
|
3. Incorporate relevant learnings into your specification \u2014 reference actual patterns, constraints, and conventions documented there
|
|
28557
28659
|
|
|
28558
28660
|
Do not read all memory directly by default. If memory is irrelevant, skip it.
|
|
@@ -28564,8 +28666,8 @@ Do not read all memory directly by default. If memory is irrelevant, skip it.
|
|
|
28564
28666
|
This project has a memory system that stores durable project learnings.
|
|
28565
28667
|
|
|
28566
28668
|
**Before writing the specification:**
|
|
28567
|
-
1. Use \`
|
|
28568
|
-
2. Use \`
|
|
28669
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28670
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28569
28671
|
3. Incorporate useful learnings into your specification
|
|
28570
28672
|
|
|
28571
28673
|
**If the memory contains useful context for this task, reference it in the specification.**
|
|
@@ -28597,12 +28699,12 @@ This project has OpenClaw-style memory files:
|
|
|
28597
28699
|
- \`.fusion/memory/YYYY-MM-DD.md\` \u2014 append-only daily notes for running observations and open loops
|
|
28598
28700
|
|
|
28599
28701
|
**At the start of execution:**
|
|
28600
|
-
1. Use \`
|
|
28601
|
-
2. Use \`
|
|
28702
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28703
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28602
28704
|
3. Apply relevant learnings to your implementation \u2014 follow documented patterns and avoid known pitfalls
|
|
28603
28705
|
4. Do not load all memory directly by default. Skip memory reads when memory is irrelevant or context is tight.
|
|
28604
28706
|
|
|
28605
|
-
**At the end of execution (before calling \`
|
|
28707
|
+
**At the end of execution (before calling \`fn_task_done()\`):**
|
|
28606
28708
|
1. Review what you learned during this task that would genuinely benefit future runs
|
|
28607
28709
|
2. Write durable decisions, conventions, and pitfalls to \`.fusion/memory/MEMORY.md\`
|
|
28608
28710
|
3. Write running observations, unresolved context, and open loops to today's \`.fusion/memory/YYYY-MM-DD.md\`
|
|
@@ -28631,11 +28733,11 @@ This project has OpenClaw-style memory files:
|
|
|
28631
28733
|
This project has a memory system that stores durable project learnings accumulated from past task runs.
|
|
28632
28734
|
|
|
28633
28735
|
**At the start of execution:**
|
|
28634
|
-
1. Use \`
|
|
28635
|
-
2. Use \`
|
|
28736
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28737
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28636
28738
|
3. Apply useful learnings to your implementation
|
|
28637
28739
|
|
|
28638
|
-
**At the end of execution (before calling \`
|
|
28740
|
+
**At the end of execution (before calling \`fn_task_done()\`):**
|
|
28639
28741
|
1. Review what you learned during this task that would genuinely benefit future runs
|
|
28640
28742
|
2. **If nothing durable was learned, skip the memory update entirely** \u2014 do not append trivial or task-specific notes
|
|
28641
28743
|
3. Only write when you have genuinely durable, reusable insights such as:
|
|
@@ -28663,8 +28765,8 @@ function buildReviewerMemoryInstructions(rootDir, settings) {
|
|
|
28663
28765
|
This project has a memory system that stores durable project learnings.
|
|
28664
28766
|
|
|
28665
28767
|
**During review:**
|
|
28666
|
-
1. Use \`
|
|
28667
|
-
2. Use \`
|
|
28768
|
+
1. Use \`fn_memory_search\` for task-relevant project conventions, pitfalls, and prior decisions when they could affect your verdict
|
|
28769
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28668
28770
|
3. Treat documented durable conventions and pitfalls as review evidence when deciding APPROVE, REVISE, or RETHINK
|
|
28669
28771
|
4. Do not update memory during review; reviewer memory access is read-only
|
|
28670
28772
|
5. Skip memory reads when they are not relevant to the reviewed plan or code
|
|
@@ -28786,23 +28888,29 @@ var init_run_command = __esm({
|
|
|
28786
28888
|
});
|
|
28787
28889
|
|
|
28788
28890
|
// ../core/src/logger.ts
|
|
28891
|
+
function withSeverityMarker(level, payload) {
|
|
28892
|
+
return `${LOG_LEVEL_MARKER_PREFIX}${level}${LOG_LEVEL_MARKER_SUFFIX}${payload}`;
|
|
28893
|
+
}
|
|
28789
28894
|
function createLogger(prefix) {
|
|
28790
28895
|
const tag = `[${prefix}]`;
|
|
28791
28896
|
return {
|
|
28792
28897
|
log(message, ...args) {
|
|
28793
|
-
console.error(`${tag} ${message}
|
|
28898
|
+
console.error(withSeverityMarker("info", `${tag} ${message}`), ...args);
|
|
28794
28899
|
},
|
|
28795
28900
|
warn(message, ...args) {
|
|
28796
|
-
console.warn(`${tag} ${message}
|
|
28901
|
+
console.warn(withSeverityMarker("warn", `${tag} ${message}`), ...args);
|
|
28797
28902
|
},
|
|
28798
28903
|
error(message, ...args) {
|
|
28799
|
-
console.error(`${tag} ${message}
|
|
28904
|
+
console.error(withSeverityMarker("error", `${tag} ${message}`), ...args);
|
|
28800
28905
|
}
|
|
28801
28906
|
};
|
|
28802
28907
|
}
|
|
28908
|
+
var LOG_LEVEL_MARKER_PREFIX, LOG_LEVEL_MARKER_SUFFIX;
|
|
28803
28909
|
var init_logger = __esm({
|
|
28804
28910
|
"../core/src/logger.ts"() {
|
|
28805
28911
|
"use strict";
|
|
28912
|
+
LOG_LEVEL_MARKER_PREFIX = "\0fnlvl=";
|
|
28913
|
+
LOG_LEVEL_MARKER_SUFFIX = "\0";
|
|
28806
28914
|
}
|
|
28807
28915
|
});
|
|
28808
28916
|
|
|
@@ -28854,6 +28962,7 @@ var init_store = __esm({
|
|
|
28854
28962
|
"../core/src/store.ts"() {
|
|
28855
28963
|
"use strict";
|
|
28856
28964
|
init_types();
|
|
28965
|
+
init_task_priority();
|
|
28857
28966
|
init_global_settings();
|
|
28858
28967
|
init_db();
|
|
28859
28968
|
init_archive_db();
|
|
@@ -29043,6 +29152,7 @@ var init_store = __esm({
|
|
|
29043
29152
|
id: row.id,
|
|
29044
29153
|
title: row.title || void 0,
|
|
29045
29154
|
description: row.description,
|
|
29155
|
+
priority: normalizeTaskPriority(row.priority),
|
|
29046
29156
|
column: row.column,
|
|
29047
29157
|
status: row.status || void 0,
|
|
29048
29158
|
size: row.size || void 0,
|
|
@@ -29078,6 +29188,19 @@ var init_store = __esm({
|
|
|
29078
29188
|
dependencies: fromJson(row.dependencies) || [],
|
|
29079
29189
|
steps: fromJson(row.steps) || [],
|
|
29080
29190
|
log: fromJson(row.log) || [],
|
|
29191
|
+
tokenUsage: (() => {
|
|
29192
|
+
if (row.tokenUsageInputTokens === null || row.tokenUsageOutputTokens === null || row.tokenUsageCachedTokens === null || row.tokenUsageTotalTokens === null || row.tokenUsageFirstUsedAt === null || row.tokenUsageLastUsedAt === null) {
|
|
29193
|
+
return void 0;
|
|
29194
|
+
}
|
|
29195
|
+
return {
|
|
29196
|
+
inputTokens: row.tokenUsageInputTokens,
|
|
29197
|
+
outputTokens: row.tokenUsageOutputTokens,
|
|
29198
|
+
cachedTokens: row.tokenUsageCachedTokens,
|
|
29199
|
+
totalTokens: row.tokenUsageTotalTokens,
|
|
29200
|
+
firstUsedAt: row.tokenUsageFirstUsedAt,
|
|
29201
|
+
lastUsedAt: row.tokenUsageLastUsedAt
|
|
29202
|
+
};
|
|
29203
|
+
})(),
|
|
29081
29204
|
attachments: (() => {
|
|
29082
29205
|
const a = fromJson(row.attachments);
|
|
29083
29206
|
return a && a.length > 0 ? a : void 0;
|
|
@@ -29102,6 +29225,18 @@ var init_store = __esm({
|
|
|
29102
29225
|
})(),
|
|
29103
29226
|
prInfo: fromJson(row.prInfo),
|
|
29104
29227
|
issueInfo: fromJson(row.issueInfo),
|
|
29228
|
+
sourceIssue: (() => {
|
|
29229
|
+
if (row.sourceIssueProvider === null || row.sourceIssueRepository === null || row.sourceIssueExternalIssueId === null || row.sourceIssueNumber === null) {
|
|
29230
|
+
return void 0;
|
|
29231
|
+
}
|
|
29232
|
+
return {
|
|
29233
|
+
provider: row.sourceIssueProvider,
|
|
29234
|
+
repository: row.sourceIssueRepository,
|
|
29235
|
+
externalIssueId: row.sourceIssueExternalIssueId,
|
|
29236
|
+
issueNumber: row.sourceIssueNumber,
|
|
29237
|
+
url: row.sourceIssueUrl ?? void 0
|
|
29238
|
+
};
|
|
29239
|
+
})(),
|
|
29105
29240
|
mergeDetails: fromJson(row.mergeDetails),
|
|
29106
29241
|
breakIntoSubtasks: row.breakIntoSubtasks ? true : void 0,
|
|
29107
29242
|
enabledWorkflowSteps: (() => {
|
|
@@ -29125,6 +29260,7 @@ var init_store = __esm({
|
|
|
29125
29260
|
id: entry.id,
|
|
29126
29261
|
title: entry.title,
|
|
29127
29262
|
description: entry.description,
|
|
29263
|
+
priority: normalizeTaskPriority(entry.priority),
|
|
29128
29264
|
column: "archived",
|
|
29129
29265
|
dependencies: entry.dependencies ?? [],
|
|
29130
29266
|
steps: entry.steps ?? [],
|
|
@@ -29133,6 +29269,7 @@ var init_store = __esm({
|
|
|
29133
29269
|
reviewLevel: entry.reviewLevel,
|
|
29134
29270
|
prInfo: slim ? void 0 : entry.prInfo,
|
|
29135
29271
|
issueInfo: slim ? void 0 : entry.issueInfo,
|
|
29272
|
+
sourceIssue: slim ? void 0 : entry.sourceIssue,
|
|
29136
29273
|
attachments: slim ? void 0 : entry.attachments,
|
|
29137
29274
|
comments: entry.comments,
|
|
29138
29275
|
log: slim ? [] : entry.log ?? [],
|
|
@@ -29221,6 +29358,7 @@ ${recentText}` : void 0
|
|
|
29221
29358
|
id: task.id,
|
|
29222
29359
|
title: task.title,
|
|
29223
29360
|
description: task.description,
|
|
29361
|
+
priority: normalizeTaskPriority(task.priority),
|
|
29224
29362
|
column: "archived",
|
|
29225
29363
|
dependencies: task.dependencies,
|
|
29226
29364
|
steps: task.steps,
|
|
@@ -29229,6 +29367,7 @@ ${recentText}` : void 0
|
|
|
29229
29367
|
reviewLevel: task.reviewLevel,
|
|
29230
29368
|
prInfo: task.prInfo,
|
|
29231
29369
|
issueInfo: task.issueInfo,
|
|
29370
|
+
sourceIssue: task.sourceIssue,
|
|
29232
29371
|
attachments: task.attachments,
|
|
29233
29372
|
comments: task.comments,
|
|
29234
29373
|
prompt,
|
|
@@ -29297,6 +29436,7 @@ ${recentText}` : void 0
|
|
|
29297
29436
|
"id",
|
|
29298
29437
|
"title",
|
|
29299
29438
|
"description",
|
|
29439
|
+
"priority",
|
|
29300
29440
|
'"column"',
|
|
29301
29441
|
"status",
|
|
29302
29442
|
"size",
|
|
@@ -29326,6 +29466,12 @@ ${recentText}` : void 0
|
|
|
29326
29466
|
"summary",
|
|
29327
29467
|
"thinkingLevel",
|
|
29328
29468
|
"executionMode",
|
|
29469
|
+
"tokenUsageInputTokens",
|
|
29470
|
+
"tokenUsageOutputTokens",
|
|
29471
|
+
"tokenUsageCachedTokens",
|
|
29472
|
+
"tokenUsageTotalTokens",
|
|
29473
|
+
"tokenUsageFirstUsedAt",
|
|
29474
|
+
"tokenUsageLastUsedAt",
|
|
29329
29475
|
"createdAt",
|
|
29330
29476
|
"updatedAt",
|
|
29331
29477
|
"columnMovedAt",
|
|
@@ -29337,6 +29483,11 @@ ${recentText}` : void 0
|
|
|
29337
29483
|
"attachments",
|
|
29338
29484
|
"prInfo",
|
|
29339
29485
|
"issueInfo",
|
|
29486
|
+
"sourceIssueProvider",
|
|
29487
|
+
"sourceIssueRepository",
|
|
29488
|
+
"sourceIssueExternalIssueId",
|
|
29489
|
+
"sourceIssueNumber",
|
|
29490
|
+
"sourceIssueUrl",
|
|
29340
29491
|
"mergeDetails",
|
|
29341
29492
|
"breakIntoSubtasks",
|
|
29342
29493
|
"enabledWorkflowSteps",
|
|
@@ -29354,6 +29505,7 @@ ${recentText}` : void 0
|
|
|
29354
29505
|
"id",
|
|
29355
29506
|
"title",
|
|
29356
29507
|
"description",
|
|
29508
|
+
"priority",
|
|
29357
29509
|
'"column"',
|
|
29358
29510
|
"status",
|
|
29359
29511
|
"size",
|
|
@@ -29383,6 +29535,12 @@ ${recentText}` : void 0
|
|
|
29383
29535
|
"summary",
|
|
29384
29536
|
"thinkingLevel",
|
|
29385
29537
|
"executionMode",
|
|
29538
|
+
"tokenUsageInputTokens",
|
|
29539
|
+
"tokenUsageOutputTokens",
|
|
29540
|
+
"tokenUsageCachedTokens",
|
|
29541
|
+
"tokenUsageTotalTokens",
|
|
29542
|
+
"tokenUsageFirstUsedAt",
|
|
29543
|
+
"tokenUsageLastUsedAt",
|
|
29386
29544
|
"createdAt",
|
|
29387
29545
|
"updatedAt",
|
|
29388
29546
|
"columnMovedAt",
|
|
@@ -29394,6 +29552,11 @@ ${recentText}` : void 0
|
|
|
29394
29552
|
"workflowStepResults",
|
|
29395
29553
|
"prInfo",
|
|
29396
29554
|
"issueInfo",
|
|
29555
|
+
"sourceIssueProvider",
|
|
29556
|
+
"sourceIssueRepository",
|
|
29557
|
+
"sourceIssueExternalIssueId",
|
|
29558
|
+
"sourceIssueNumber",
|
|
29559
|
+
"sourceIssueUrl",
|
|
29397
29560
|
"mergeDetails",
|
|
29398
29561
|
"breakIntoSubtasks",
|
|
29399
29562
|
"enabledWorkflowSteps",
|
|
@@ -29431,21 +29594,23 @@ ${recentText}` : void 0
|
|
|
29431
29594
|
upsertTask(task) {
|
|
29432
29595
|
this.db.prepare(`
|
|
29433
29596
|
INSERT INTO tasks (
|
|
29434
|
-
id, title, description, "column", status, size, reviewLevel, currentStep,
|
|
29597
|
+
id, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
29435
29598
|
worktree, blockedBy, paused, baseBranch, branch, baseCommitSha, modelPresetId, modelProvider,
|
|
29436
29599
|
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
29437
29600
|
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, nextRecoveryAt, error,
|
|
29438
|
-
summary, thinkingLevel, executionMode,
|
|
29601
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
29602
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
29439
29603
|
dependencies, steps, log, attachments, steeringComments,
|
|
29440
|
-
comments, workflowStepResults, prInfo, issueInfo,
|
|
29441
|
-
|
|
29604
|
+
comments, workflowStepResults, prInfo, issueInfo,
|
|
29605
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
29606
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, checkedOutBy, checkedOutAt
|
|
29442
29607
|
) VALUES (
|
|
29443
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
29444
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
29608
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
29445
29609
|
)
|
|
29446
29610
|
ON CONFLICT(id) DO UPDATE SET
|
|
29447
29611
|
title = excluded.title,
|
|
29448
29612
|
description = excluded.description,
|
|
29613
|
+
priority = excluded.priority,
|
|
29449
29614
|
"column" = excluded."column",
|
|
29450
29615
|
status = excluded.status,
|
|
29451
29616
|
size = excluded.size,
|
|
@@ -29475,6 +29640,12 @@ ${recentText}` : void 0
|
|
|
29475
29640
|
summary = excluded.summary,
|
|
29476
29641
|
thinkingLevel = excluded.thinkingLevel,
|
|
29477
29642
|
executionMode = excluded.executionMode,
|
|
29643
|
+
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
29644
|
+
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
29645
|
+
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
29646
|
+
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
29647
|
+
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
29648
|
+
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
29478
29649
|
createdAt = excluded.createdAt,
|
|
29479
29650
|
updatedAt = excluded.updatedAt,
|
|
29480
29651
|
columnMovedAt = excluded.columnMovedAt,
|
|
@@ -29487,6 +29658,11 @@ ${recentText}` : void 0
|
|
|
29487
29658
|
workflowStepResults = excluded.workflowStepResults,
|
|
29488
29659
|
prInfo = excluded.prInfo,
|
|
29489
29660
|
issueInfo = excluded.issueInfo,
|
|
29661
|
+
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
29662
|
+
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
29663
|
+
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
29664
|
+
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
29665
|
+
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
29490
29666
|
mergeDetails = excluded.mergeDetails,
|
|
29491
29667
|
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
29492
29668
|
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
@@ -29501,6 +29677,7 @@ ${recentText}` : void 0
|
|
|
29501
29677
|
task.id,
|
|
29502
29678
|
task.title ?? null,
|
|
29503
29679
|
task.description,
|
|
29680
|
+
normalizeTaskPriority(task.priority),
|
|
29504
29681
|
task.column,
|
|
29505
29682
|
task.status ?? null,
|
|
29506
29683
|
task.size ?? null,
|
|
@@ -29530,6 +29707,12 @@ ${recentText}` : void 0
|
|
|
29530
29707
|
task.summary ?? null,
|
|
29531
29708
|
task.thinkingLevel ?? null,
|
|
29532
29709
|
task.executionMode ?? null,
|
|
29710
|
+
task.tokenUsage?.inputTokens ?? null,
|
|
29711
|
+
task.tokenUsage?.outputTokens ?? null,
|
|
29712
|
+
task.tokenUsage?.cachedTokens ?? null,
|
|
29713
|
+
task.tokenUsage?.totalTokens ?? null,
|
|
29714
|
+
task.tokenUsage?.firstUsedAt ?? null,
|
|
29715
|
+
task.tokenUsage?.lastUsedAt ?? null,
|
|
29533
29716
|
task.createdAt,
|
|
29534
29717
|
task.updatedAt,
|
|
29535
29718
|
task.columnMovedAt ?? null,
|
|
@@ -29542,6 +29725,11 @@ ${recentText}` : void 0
|
|
|
29542
29725
|
toJson(task.workflowStepResults || []),
|
|
29543
29726
|
toJsonNullable(task.prInfo),
|
|
29544
29727
|
toJsonNullable(task.issueInfo),
|
|
29728
|
+
task.sourceIssue?.provider ?? null,
|
|
29729
|
+
task.sourceIssue?.repository ?? null,
|
|
29730
|
+
task.sourceIssue?.externalIssueId ?? null,
|
|
29731
|
+
task.sourceIssue?.issueNumber ?? null,
|
|
29732
|
+
task.sourceIssue?.url ?? null,
|
|
29545
29733
|
toJsonNullable(task.mergeDetails),
|
|
29546
29734
|
task.breakIntoSubtasks ? 1 : 0,
|
|
29547
29735
|
toJson(task.enabledWorkflowSteps || []),
|
|
@@ -29746,6 +29934,7 @@ ${recentText}` : void 0
|
|
|
29746
29934
|
if (!Array.isArray(fileTask.log)) fileTask.log = [];
|
|
29747
29935
|
if (!Array.isArray(fileTask.dependencies)) fileTask.dependencies = [];
|
|
29748
29936
|
if (!Array.isArray(fileTask.steps)) fileTask.steps = [];
|
|
29937
|
+
fileTask.priority = normalizeTaskPriority(fileTask.priority);
|
|
29749
29938
|
return fileTask;
|
|
29750
29939
|
} catch (err) {
|
|
29751
29940
|
throw new Error(
|
|
@@ -30280,6 +30469,9 @@ ${recentText}` : void 0
|
|
|
30280
30469
|
id,
|
|
30281
30470
|
title,
|
|
30282
30471
|
description: input.description,
|
|
30472
|
+
priority: normalizeTaskPriority(input.priority),
|
|
30473
|
+
tokenUsage: input.tokenUsage,
|
|
30474
|
+
sourceIssue: input.sourceIssue,
|
|
30283
30475
|
column: input.column || "triage",
|
|
30284
30476
|
dependencies: input.dependencies || [],
|
|
30285
30477
|
breakIntoSubtasks: input.breakIntoSubtasks === true ? true : void 0,
|
|
@@ -30334,6 +30526,7 @@ ${task.description}
|
|
|
30334
30526
|
description: `${sourceTask.description}
|
|
30335
30527
|
|
|
30336
30528
|
(Duplicated from ${id})`,
|
|
30529
|
+
priority: normalizeTaskPriority(sourceTask.priority),
|
|
30337
30530
|
column: "triage",
|
|
30338
30531
|
modelPresetId: sourceTask.modelPresetId,
|
|
30339
30532
|
dependencies: [],
|
|
@@ -30392,6 +30585,7 @@ ${task.description}
|
|
|
30392
30585
|
description: `${feedback.trim()}
|
|
30393
30586
|
|
|
30394
30587
|
Refines: ${id}`,
|
|
30588
|
+
priority: normalizeTaskPriority(sourceTask.priority),
|
|
30395
30589
|
column: "triage",
|
|
30396
30590
|
dependencies: [id],
|
|
30397
30591
|
// Refinement depends on the original being complete
|
|
@@ -30716,6 +30910,11 @@ ${newTask.description}
|
|
|
30716
30910
|
}
|
|
30717
30911
|
if (updates.title !== void 0) task.title = updates.title;
|
|
30718
30912
|
if (updates.description !== void 0) task.description = updates.description;
|
|
30913
|
+
if (updates.priority === null) {
|
|
30914
|
+
task.priority = normalizeTaskPriority(void 0);
|
|
30915
|
+
} else if (updates.priority !== void 0) {
|
|
30916
|
+
task.priority = normalizeTaskPriority(updates.priority);
|
|
30917
|
+
}
|
|
30719
30918
|
if (updates.worktree === null) {
|
|
30720
30919
|
task.worktree = void 0;
|
|
30721
30920
|
} else if (updates.worktree !== void 0) {
|
|
@@ -30883,6 +31082,16 @@ ${newTask.description}
|
|
|
30883
31082
|
} else if (updates.mergeDetails !== void 0) {
|
|
30884
31083
|
task.mergeDetails = updates.mergeDetails;
|
|
30885
31084
|
}
|
|
31085
|
+
if (updates.sourceIssue === null) {
|
|
31086
|
+
task.sourceIssue = void 0;
|
|
31087
|
+
} else if (updates.sourceIssue !== void 0) {
|
|
31088
|
+
task.sourceIssue = updates.sourceIssue;
|
|
31089
|
+
}
|
|
31090
|
+
if (updates.tokenUsage === null) {
|
|
31091
|
+
task.tokenUsage = void 0;
|
|
31092
|
+
} else if (updates.tokenUsage !== void 0) {
|
|
31093
|
+
task.tokenUsage = updates.tokenUsage;
|
|
31094
|
+
}
|
|
30886
31095
|
if (updates.modifiedFiles === null) {
|
|
30887
31096
|
task.modifiedFiles = void 0;
|
|
30888
31097
|
} else if (updates.modifiedFiles !== void 0) {
|
|
@@ -31283,14 +31492,14 @@ ${task.description}
|
|
|
31283
31492
|
}
|
|
31284
31493
|
return paths;
|
|
31285
31494
|
}
|
|
31286
|
-
async deleteTask(id) {
|
|
31495
|
+
async deleteTask(id, options) {
|
|
31287
31496
|
return this.withTaskLock(id, async () => {
|
|
31288
31497
|
const task = this.readTaskFromDb(id);
|
|
31289
31498
|
if (!task) {
|
|
31290
31499
|
throw new Error(`Task ${id} not found`);
|
|
31291
31500
|
}
|
|
31292
31501
|
const dependentIds = this.findLiveDependents(id);
|
|
31293
|
-
if (dependentIds.length > 0) {
|
|
31502
|
+
if (dependentIds.length > 0 && !options?.removeDependencyReferences) {
|
|
31294
31503
|
throw new TaskHasDependentsError(id, dependentIds);
|
|
31295
31504
|
}
|
|
31296
31505
|
const cleanedBranches = await this.cleanupBranchForTask(task);
|
|
@@ -31301,18 +31510,50 @@ ${task.description}
|
|
|
31301
31510
|
action: `Cleaned up branch: ${cleanedBranches.join(", ")}`
|
|
31302
31511
|
});
|
|
31303
31512
|
}
|
|
31304
|
-
|
|
31305
|
-
this.db.bumpLastModified();
|
|
31513
|
+
const rewrittenDependents = this.rewriteDependentsAndDeleteTask(id, dependentIds);
|
|
31306
31514
|
if (this.isWatching) this.taskCache.delete(id);
|
|
31307
31515
|
const dir = this.taskDir(id);
|
|
31308
31516
|
if (existsSync12(dir)) {
|
|
31309
|
-
const { rm:
|
|
31310
|
-
await
|
|
31517
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
31518
|
+
await rm3(dir, { recursive: true });
|
|
31519
|
+
}
|
|
31520
|
+
for (const dependentTask of rewrittenDependents) {
|
|
31521
|
+
this.emit("task:updated", dependentTask);
|
|
31311
31522
|
}
|
|
31312
31523
|
this.emit("task:deleted", task);
|
|
31313
31524
|
return task;
|
|
31314
31525
|
});
|
|
31315
31526
|
}
|
|
31527
|
+
rewriteDependentsAndDeleteTask(taskId, dependentIds) {
|
|
31528
|
+
const rewrittenDependents = [];
|
|
31529
|
+
this.db.transaction(() => {
|
|
31530
|
+
for (const dependentId of dependentIds) {
|
|
31531
|
+
const dependentTask = this.readTaskFromDb(dependentId);
|
|
31532
|
+
if (!dependentTask) continue;
|
|
31533
|
+
const nextDependencies = dependentTask.dependencies.filter((dependencyId) => dependencyId !== taskId);
|
|
31534
|
+
if (nextDependencies.length === dependentTask.dependencies.length) {
|
|
31535
|
+
continue;
|
|
31536
|
+
}
|
|
31537
|
+
const updatedDependent = {
|
|
31538
|
+
...dependentTask,
|
|
31539
|
+
dependencies: nextDependencies,
|
|
31540
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31541
|
+
};
|
|
31542
|
+
this.db.prepare("UPDATE tasks SET dependencies = ?, updatedAt = ? WHERE id = ?").run(
|
|
31543
|
+
toJson(updatedDependent.dependencies),
|
|
31544
|
+
updatedDependent.updatedAt,
|
|
31545
|
+
updatedDependent.id
|
|
31546
|
+
);
|
|
31547
|
+
if (this.isWatching) {
|
|
31548
|
+
this.taskCache.set(updatedDependent.id, updatedDependent);
|
|
31549
|
+
}
|
|
31550
|
+
rewrittenDependents.push(updatedDependent);
|
|
31551
|
+
}
|
|
31552
|
+
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
|
|
31553
|
+
this.db.bumpLastModified();
|
|
31554
|
+
});
|
|
31555
|
+
return rewrittenDependents;
|
|
31556
|
+
}
|
|
31316
31557
|
/**
|
|
31317
31558
|
* Clean up the git branch associated with a task.
|
|
31318
31559
|
*
|
|
@@ -31609,8 +31850,8 @@ ${task.description}
|
|
|
31609
31850
|
this.archiveDb.upsert(entry);
|
|
31610
31851
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
31611
31852
|
this.db.bumpLastModified();
|
|
31612
|
-
const { rm:
|
|
31613
|
-
await
|
|
31853
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
31854
|
+
await rm3(dir, { recursive: true, force: true });
|
|
31614
31855
|
if (this.isWatching) {
|
|
31615
31856
|
this.taskCache.delete(id);
|
|
31616
31857
|
}
|
|
@@ -32088,24 +32329,64 @@ ${task.description}
|
|
|
32088
32329
|
this.emit("task:updated", task2);
|
|
32089
32330
|
return task2;
|
|
32090
32331
|
});
|
|
32332
|
+
const commentContextBase = {
|
|
32333
|
+
taskId: id,
|
|
32334
|
+
author,
|
|
32335
|
+
commentLength: text.length,
|
|
32336
|
+
column: task.column,
|
|
32337
|
+
priorStatus: task.status ?? null
|
|
32338
|
+
};
|
|
32339
|
+
if (runContext) {
|
|
32340
|
+
commentContextBase.runId = runContext.runId;
|
|
32341
|
+
commentContextBase.agentId = runContext.agentId;
|
|
32342
|
+
if (runContext.source) {
|
|
32343
|
+
commentContextBase.runSource = runContext.source;
|
|
32344
|
+
}
|
|
32345
|
+
}
|
|
32091
32346
|
if (task.column === "done" && author === "user" && !options?.skipRefinement) {
|
|
32092
32347
|
try {
|
|
32093
32348
|
await this.refineTask(id, text);
|
|
32094
|
-
} catch {
|
|
32349
|
+
} catch (err) {
|
|
32350
|
+
storeLog.warn("Best-effort post-comment auto-refinement failed", {
|
|
32351
|
+
...commentContextBase,
|
|
32352
|
+
phase: "addComment:auto-refinement",
|
|
32353
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32354
|
+
});
|
|
32095
32355
|
}
|
|
32096
32356
|
}
|
|
32097
32357
|
if (task.column === "triage" && task.status === "awaiting-approval" && author === "user") {
|
|
32358
|
+
let invalidatedStatus = false;
|
|
32098
32359
|
try {
|
|
32099
32360
|
await this.updateTask(id, {
|
|
32100
32361
|
status: "needs-respecify"
|
|
32101
32362
|
});
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32363
|
+
invalidatedStatus = true;
|
|
32364
|
+
} catch (err) {
|
|
32365
|
+
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
|
|
32366
|
+
...commentContextBase,
|
|
32367
|
+
phase: "addComment:awaiting-approval-invalidation",
|
|
32368
|
+
stage: "status-update",
|
|
32369
|
+
nextStatus: "needs-respecify",
|
|
32370
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32371
|
+
});
|
|
32372
|
+
}
|
|
32373
|
+
if (invalidatedStatus) {
|
|
32374
|
+
try {
|
|
32375
|
+
await this.logEntry(
|
|
32376
|
+
id,
|
|
32377
|
+
`User comment invalidated spec approval \u2014 task needs re-specification`,
|
|
32378
|
+
void 0,
|
|
32379
|
+
runContext
|
|
32380
|
+
);
|
|
32381
|
+
} catch (err) {
|
|
32382
|
+
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
|
|
32383
|
+
...commentContextBase,
|
|
32384
|
+
phase: "addComment:awaiting-approval-invalidation",
|
|
32385
|
+
stage: "post-invalidation-log-entry",
|
|
32386
|
+
nextStatus: "needs-respecify",
|
|
32387
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32388
|
+
});
|
|
32389
|
+
}
|
|
32109
32390
|
}
|
|
32110
32391
|
}
|
|
32111
32392
|
return task;
|
|
@@ -32381,7 +32662,7 @@ ${task.description}
|
|
|
32381
32662
|
const rows2 = this.db.prepare(`
|
|
32382
32663
|
SELECT * FROM agentLogEntries
|
|
32383
32664
|
WHERE taskId = ?
|
|
32384
|
-
ORDER BY timestamp DESC
|
|
32665
|
+
ORDER BY timestamp DESC, id DESC
|
|
32385
32666
|
LIMIT ?
|
|
32386
32667
|
`).all(taskId, readCount);
|
|
32387
32668
|
const entries2 = rows2.map((row) => this.mapAgentLogRow(row)).reverse();
|
|
@@ -32393,7 +32674,7 @@ ${task.description}
|
|
|
32393
32674
|
const rows = this.db.prepare(`
|
|
32394
32675
|
SELECT * FROM agentLogEntries
|
|
32395
32676
|
WHERE taskId = ?
|
|
32396
|
-
ORDER BY timestamp ASC
|
|
32677
|
+
ORDER BY timestamp ASC, id ASC
|
|
32397
32678
|
`).all(taskId);
|
|
32398
32679
|
const entries = rows.map((row) => this.mapAgentLogRow(row));
|
|
32399
32680
|
if (offset > 0) {
|
|
@@ -32426,7 +32707,7 @@ ${task.description}
|
|
|
32426
32707
|
const rows = this.db.prepare(`
|
|
32427
32708
|
SELECT * FROM agentLogEntries
|
|
32428
32709
|
WHERE taskId = ? AND timestamp >= ? AND timestamp <= ?
|
|
32429
|
-
ORDER BY timestamp ASC
|
|
32710
|
+
ORDER BY timestamp ASC, id ASC
|
|
32430
32711
|
`).all(taskId, startIso, end);
|
|
32431
32712
|
return rows.map((row) => this.mapAgentLogRow(row));
|
|
32432
32713
|
}
|
|
@@ -32522,14 +32803,14 @@ ${task.description}
|
|
|
32522
32803
|
if (rows.length === 0) {
|
|
32523
32804
|
return;
|
|
32524
32805
|
}
|
|
32525
|
-
const { rm:
|
|
32806
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
32526
32807
|
for (const row of rows) {
|
|
32527
32808
|
const task = this.rowToTask(row);
|
|
32528
32809
|
const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
32529
32810
|
const entry = await this.taskToArchiveEntry(task, archivedAt);
|
|
32530
32811
|
this.archiveDb.upsert(entry);
|
|
32531
32812
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
32532
|
-
await
|
|
32813
|
+
await rm3(this.taskDir(task.id), { recursive: true, force: true });
|
|
32533
32814
|
if (this.isWatching) {
|
|
32534
32815
|
this.taskCache.delete(task.id);
|
|
32535
32816
|
}
|
|
@@ -32552,8 +32833,8 @@ ${task.description}
|
|
|
32552
32833
|
this.archiveDb.upsert(entry);
|
|
32553
32834
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
32554
32835
|
this.db.bumpLastModified();
|
|
32555
|
-
const { rm:
|
|
32556
|
-
await
|
|
32836
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
32837
|
+
await rm3(dir, { recursive: true, force: true });
|
|
32557
32838
|
if (this.isWatching) {
|
|
32558
32839
|
this.taskCache.delete(task.id);
|
|
32559
32840
|
}
|
|
@@ -32576,6 +32857,7 @@ ${task.description}
|
|
|
32576
32857
|
id: entry.id,
|
|
32577
32858
|
title: entry.title,
|
|
32578
32859
|
description: entry.description,
|
|
32860
|
+
priority: normalizeTaskPriority(entry.priority),
|
|
32579
32861
|
column: "archived",
|
|
32580
32862
|
// Will be changed to "done" by unarchiveTask
|
|
32581
32863
|
dependencies: entry.dependencies,
|
|
@@ -32585,6 +32867,7 @@ ${task.description}
|
|
|
32585
32867
|
reviewLevel: entry.reviewLevel,
|
|
32586
32868
|
prInfo: entry.prInfo,
|
|
32587
32869
|
issueInfo: entry.issueInfo,
|
|
32870
|
+
sourceIssue: entry.sourceIssue,
|
|
32588
32871
|
attachments: entry.attachments,
|
|
32589
32872
|
log: [...entry.log, { timestamp: (/* @__PURE__ */ new Date()).toISOString(), action: "Task restored from archive" }],
|
|
32590
32873
|
comments: entry.comments,
|
|
@@ -33194,6 +33477,28 @@ var init_daemon_token = __esm({
|
|
|
33194
33477
|
const settings = await this.settingsStore.getSettings();
|
|
33195
33478
|
return settings.daemonToken;
|
|
33196
33479
|
}
|
|
33480
|
+
/**
|
|
33481
|
+
* Retrieve the existing daemon token or create/persist one if missing.
|
|
33482
|
+
*
|
|
33483
|
+
* Safe for concurrent callers: if another process writes the token between
|
|
33484
|
+
* the initial read and generateToken(), this method re-reads and returns the
|
|
33485
|
+
* persisted token instead of failing.
|
|
33486
|
+
*/
|
|
33487
|
+
async getOrCreateToken() {
|
|
33488
|
+
const existing = await this.getToken();
|
|
33489
|
+
if (existing) {
|
|
33490
|
+
return existing;
|
|
33491
|
+
}
|
|
33492
|
+
try {
|
|
33493
|
+
return await this.generateToken();
|
|
33494
|
+
} catch (error) {
|
|
33495
|
+
const afterRace = await this.getToken();
|
|
33496
|
+
if (afterRace) {
|
|
33497
|
+
return afterRace;
|
|
33498
|
+
}
|
|
33499
|
+
throw error;
|
|
33500
|
+
}
|
|
33501
|
+
}
|
|
33197
33502
|
/**
|
|
33198
33503
|
* Validate that a provided token matches the stored token.
|
|
33199
33504
|
*
|
|
@@ -33637,13 +33942,14 @@ __export(routine_store_exports, {
|
|
|
33637
33942
|
});
|
|
33638
33943
|
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
33639
33944
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
33640
|
-
var import_cron_parser2, RoutineStore;
|
|
33945
|
+
var import_cron_parser2, CRON_TIMEZONE2, RoutineStore;
|
|
33641
33946
|
var init_routine_store = __esm({
|
|
33642
33947
|
"../core/src/routine-store.ts"() {
|
|
33643
33948
|
"use strict";
|
|
33644
33949
|
import_cron_parser2 = __toESM(require_dist2(), 1);
|
|
33645
33950
|
init_db();
|
|
33646
33951
|
init_routine();
|
|
33952
|
+
CRON_TIMEZONE2 = "UTC";
|
|
33647
33953
|
RoutineStore = class _RoutineStore extends EventEmitter12 {
|
|
33648
33954
|
constructor(rootDir) {
|
|
33649
33955
|
super();
|
|
@@ -33806,7 +34112,8 @@ var init_routine_store = __esm({
|
|
|
33806
34112
|
*/
|
|
33807
34113
|
computeNextRun(cronExpression, fromDate) {
|
|
33808
34114
|
const interval = import_cron_parser2.CronExpressionParser.parse(cronExpression, {
|
|
33809
|
-
currentDate: fromDate ?? /* @__PURE__ */ new Date()
|
|
34115
|
+
currentDate: fromDate ?? /* @__PURE__ */ new Date(),
|
|
34116
|
+
tz: CRON_TIMEZONE2
|
|
33810
34117
|
});
|
|
33811
34118
|
const next = interval.next();
|
|
33812
34119
|
return new Date(next.getTime()).toISOString();
|
|
@@ -34048,12 +34355,11 @@ var init_routine_store = __esm({
|
|
|
34048
34355
|
});
|
|
34049
34356
|
|
|
34050
34357
|
// ../core/src/plugin-loader.ts
|
|
34051
|
-
import {
|
|
34052
|
-
import { copyFile,
|
|
34053
|
-
import { isAbsolute as isAbsolute5, parse, resolve as resolve7 } from "node:path";
|
|
34358
|
+
import { basename as basename6, dirname as dirname5, extname, isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
|
|
34359
|
+
import { copyFile, rm } from "node:fs/promises";
|
|
34054
34360
|
import { pathToFileURL } from "node:url";
|
|
34055
34361
|
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
34056
|
-
var MINIMUM_FUSION_VERSION, log, PluginLoader;
|
|
34362
|
+
var MINIMUM_FUSION_VERSION, log, moduleImportVersion, PluginLoader;
|
|
34057
34363
|
var init_plugin_loader = __esm({
|
|
34058
34364
|
"../core/src/plugin-loader.ts"() {
|
|
34059
34365
|
"use strict";
|
|
@@ -34061,6 +34367,7 @@ var init_plugin_loader = __esm({
|
|
|
34061
34367
|
init_logger();
|
|
34062
34368
|
MINIMUM_FUSION_VERSION = "0.1.0";
|
|
34063
34369
|
log = createLogger("plugin-loader");
|
|
34370
|
+
moduleImportVersion = 0;
|
|
34064
34371
|
PluginLoader = class extends EventEmitter13 {
|
|
34065
34372
|
constructor(options) {
|
|
34066
34373
|
super();
|
|
@@ -34070,8 +34377,6 @@ var init_plugin_loader = __esm({
|
|
|
34070
34377
|
plugins = /* @__PURE__ */ new Map();
|
|
34071
34378
|
/** Cache of dynamically imported modules */
|
|
34072
34379
|
loadedModules = /* @__PURE__ */ new Map();
|
|
34073
|
-
/** Monotonic nonce to guarantee unique cache-busting import URLs. */
|
|
34074
|
-
importNonce = 0;
|
|
34075
34380
|
getProjectRoot() {
|
|
34076
34381
|
return this.options.taskStore.getRootDir();
|
|
34077
34382
|
}
|
|
@@ -34201,31 +34506,24 @@ var init_plugin_loader = __esm({
|
|
|
34201
34506
|
if (!bypassCache && this.loadedModules.has(path)) {
|
|
34202
34507
|
return this.loadedModules.get(path);
|
|
34203
34508
|
}
|
|
34204
|
-
|
|
34205
|
-
let
|
|
34509
|
+
const moduleUrl = pathToFileURL(path).href;
|
|
34510
|
+
let mod;
|
|
34206
34511
|
if (bypassCache) {
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
|
|
34210
|
-
|
|
34211
|
-
);
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34215
|
-
|
|
34216
|
-
if (bypassCache) {
|
|
34217
|
-
fileUrl.searchParams.set("t", `${Date.now()}-${this.importNonce}`);
|
|
34218
|
-
}
|
|
34219
|
-
try {
|
|
34220
|
-
const mod = await import(fileUrl.href);
|
|
34221
|
-
this.loadedModules.set(path, mod);
|
|
34222
|
-
return mod;
|
|
34223
|
-
} finally {
|
|
34224
|
-
if (tempPath) {
|
|
34225
|
-
void unlink4(tempPath).catch(() => {
|
|
34226
|
-
});
|
|
34512
|
+
moduleImportVersion += 1;
|
|
34513
|
+
const ext = extname(path);
|
|
34514
|
+
const baseName = basename6(path, ext);
|
|
34515
|
+
const reloadedPath = resolve7(dirname5(path), `.${baseName}.reload-${moduleImportVersion}${ext}`);
|
|
34516
|
+
await copyFile(path, reloadedPath);
|
|
34517
|
+
try {
|
|
34518
|
+
mod = await import(pathToFileURL(reloadedPath).href);
|
|
34519
|
+
} finally {
|
|
34520
|
+
await rm(reloadedPath, { force: true }).catch(() => void 0);
|
|
34227
34521
|
}
|
|
34522
|
+
} else {
|
|
34523
|
+
mod = await import(moduleUrl);
|
|
34228
34524
|
}
|
|
34525
|
+
this.loadedModules.set(path, mod);
|
|
34526
|
+
return mod;
|
|
34229
34527
|
}
|
|
34230
34528
|
/**
|
|
34231
34529
|
* Invalidate the module cache for a plugin path.
|
|
@@ -34614,7 +34912,7 @@ var init_plugin_loader = __esm({
|
|
|
34614
34912
|
});
|
|
34615
34913
|
|
|
34616
34914
|
// ../core/src/backup.ts
|
|
34617
|
-
import { cp, mkdir as mkdir8, readdir as readdir6, stat as stat3, unlink as
|
|
34915
|
+
import { cp, mkdir as mkdir8, readdir as readdir6, stat as stat3, unlink as unlink4 } from "node:fs/promises";
|
|
34618
34916
|
import { existsSync as existsSync14 } from "node:fs";
|
|
34619
34917
|
import { join as join17 } from "node:path";
|
|
34620
34918
|
function generateBackupFilename() {
|
|
@@ -34867,7 +35165,7 @@ var init_backup = __esm({
|
|
|
34867
35165
|
let deletedCount = 0;
|
|
34868
35166
|
for (const backup of toDelete) {
|
|
34869
35167
|
try {
|
|
34870
|
-
await
|
|
35168
|
+
await unlink4(backup.path);
|
|
34871
35169
|
deletedCount++;
|
|
34872
35170
|
} catch {
|
|
34873
35171
|
}
|
|
@@ -35577,7 +35875,7 @@ var init_mission_types = __esm({
|
|
|
35577
35875
|
// ../core/src/memory-insights.ts
|
|
35578
35876
|
import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
|
|
35579
35877
|
import { existsSync as existsSync15 } from "node:fs";
|
|
35580
|
-
import { dirname as
|
|
35878
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
35581
35879
|
async function readWorkingMemory(rootDir) {
|
|
35582
35880
|
const filePath = join18(rootDir, MEMORY_WORKING_PATH);
|
|
35583
35881
|
if (!existsSync15(filePath)) {
|
|
@@ -35602,7 +35900,7 @@ async function writeInsightsMemory(rootDir, content) {
|
|
|
35602
35900
|
}
|
|
35603
35901
|
async function writeWorkingMemory(rootDir, content) {
|
|
35604
35902
|
const filePath = join18(rootDir, MEMORY_WORKING_PATH);
|
|
35605
|
-
const dir =
|
|
35903
|
+
const dir = dirname6(filePath);
|
|
35606
35904
|
if (!existsSync15(dir)) {
|
|
35607
35905
|
await mkdir9(dir, { recursive: true });
|
|
35608
35906
|
}
|
|
@@ -36447,7 +36745,7 @@ var require_ms = __commonJS({
|
|
|
36447
36745
|
options = options || {};
|
|
36448
36746
|
var type = typeof val;
|
|
36449
36747
|
if (type === "string" && val.length > 0) {
|
|
36450
|
-
return
|
|
36748
|
+
return parse(val);
|
|
36451
36749
|
} else if (type === "number" && isFinite(val)) {
|
|
36452
36750
|
return options.long ? fmtLong(val) : fmtShort(val);
|
|
36453
36751
|
}
|
|
@@ -36455,7 +36753,7 @@ var require_ms = __commonJS({
|
|
|
36455
36753
|
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
|
36456
36754
|
);
|
|
36457
36755
|
};
|
|
36458
|
-
function
|
|
36756
|
+
function parse(str) {
|
|
36459
36757
|
str = String(str);
|
|
36460
36758
|
if (str.length > 100) {
|
|
36461
36759
|
return;
|
|
@@ -46128,7 +46426,7 @@ var require_public_api = __commonJS({
|
|
|
46128
46426
|
}
|
|
46129
46427
|
return doc;
|
|
46130
46428
|
}
|
|
46131
|
-
function
|
|
46429
|
+
function parse(src, reviver, options) {
|
|
46132
46430
|
let _reviver = void 0;
|
|
46133
46431
|
if (typeof reviver === "function") {
|
|
46134
46432
|
_reviver = reviver;
|
|
@@ -46169,7 +46467,7 @@ var require_public_api = __commonJS({
|
|
|
46169
46467
|
return value.toString(options);
|
|
46170
46468
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
46171
46469
|
}
|
|
46172
|
-
exports.parse =
|
|
46470
|
+
exports.parse = parse;
|
|
46173
46471
|
exports.parseAllDocuments = parseAllDocuments;
|
|
46174
46472
|
exports.parseDocument = parseDocument;
|
|
46175
46473
|
exports.stringify = stringify;
|
|
@@ -46270,10 +46568,10 @@ function pushAlias(aliases, value) {
|
|
|
46270
46568
|
if (pathRef !== normalized && pathRef.length > 0) {
|
|
46271
46569
|
aliases.add(pathRef);
|
|
46272
46570
|
}
|
|
46273
|
-
const
|
|
46274
|
-
if (
|
|
46275
|
-
aliases.add(
|
|
46276
|
-
const basenameSlug = slugifyAgentReference(
|
|
46571
|
+
const basename9 = extractPathBasename(value);
|
|
46572
|
+
if (basename9) {
|
|
46573
|
+
aliases.add(basename9);
|
|
46574
|
+
const basenameSlug = slugifyAgentReference(basename9);
|
|
46277
46575
|
if (basenameSlug.length > 0) {
|
|
46278
46576
|
aliases.add(basenameSlug);
|
|
46279
46577
|
}
|
|
@@ -46959,7 +47257,7 @@ var init_agent_companies_exporter = __esm({
|
|
|
46959
47257
|
|
|
46960
47258
|
// ../core/src/chat-store.ts
|
|
46961
47259
|
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
46962
|
-
import { randomUUID as
|
|
47260
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
46963
47261
|
var ChatStore;
|
|
46964
47262
|
var init_chat_store = __esm({
|
|
46965
47263
|
"../core/src/chat-store.ts"() {
|
|
@@ -47012,7 +47310,7 @@ var init_chat_store = __esm({
|
|
|
47012
47310
|
*/
|
|
47013
47311
|
createSession(input) {
|
|
47014
47312
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
47015
|
-
const id = `chat-${
|
|
47313
|
+
const id = `chat-${randomUUID8().slice(0, 8)}`;
|
|
47016
47314
|
const session = {
|
|
47017
47315
|
id,
|
|
47018
47316
|
agentId: input.agentId,
|
|
@@ -47080,6 +47378,58 @@ var init_chat_store = __esm({
|
|
|
47080
47378
|
`).all(...params);
|
|
47081
47379
|
return rows.map((row) => this.rowToSession(row));
|
|
47082
47380
|
}
|
|
47381
|
+
/**
|
|
47382
|
+
* Find the newest active session for a specific quick-chat target.
|
|
47383
|
+
*
|
|
47384
|
+
* Matching semantics:
|
|
47385
|
+
* - model target (`modelProvider` + `modelId`): exact agent+model match
|
|
47386
|
+
* - agent target (no model): prefer model-less sessions, then newest agent session fallback
|
|
47387
|
+
*/
|
|
47388
|
+
findLatestActiveSessionForTarget(options) {
|
|
47389
|
+
const normalizedAgentId = options.agentId.trim();
|
|
47390
|
+
if (!normalizedAgentId) {
|
|
47391
|
+
return void 0;
|
|
47392
|
+
}
|
|
47393
|
+
const normalizedProvider = options.modelProvider?.trim();
|
|
47394
|
+
const normalizedModelId = options.modelId?.trim();
|
|
47395
|
+
if (normalizedProvider && !normalizedModelId || !normalizedProvider && normalizedModelId) {
|
|
47396
|
+
throw new Error("modelProvider and modelId must both be provided together, or neither");
|
|
47397
|
+
}
|
|
47398
|
+
const whereClauses = ["status = ?", "agentId = ?"];
|
|
47399
|
+
const baseParams = ["active", normalizedAgentId];
|
|
47400
|
+
if (options.projectId && options.projectId.trim()) {
|
|
47401
|
+
whereClauses.push("projectId = ?");
|
|
47402
|
+
baseParams.push(options.projectId.trim());
|
|
47403
|
+
}
|
|
47404
|
+
const baseWhereSql = whereClauses.join(" AND ");
|
|
47405
|
+
if (normalizedProvider && normalizedModelId) {
|
|
47406
|
+
const row = this.db.prepare(`
|
|
47407
|
+
SELECT * FROM chat_sessions
|
|
47408
|
+
WHERE ${baseWhereSql} AND modelProvider = ? AND modelId = ?
|
|
47409
|
+
ORDER BY updatedAt DESC
|
|
47410
|
+
LIMIT 1
|
|
47411
|
+
`).get(...baseParams, normalizedProvider, normalizedModelId);
|
|
47412
|
+
return row ? this.rowToSession(row) : void 0;
|
|
47413
|
+
}
|
|
47414
|
+
const modelLessRow = this.db.prepare(`
|
|
47415
|
+
SELECT * FROM chat_sessions
|
|
47416
|
+
WHERE ${baseWhereSql}
|
|
47417
|
+
AND COALESCE(TRIM(modelProvider), '') = ''
|
|
47418
|
+
AND COALESCE(TRIM(modelId), '') = ''
|
|
47419
|
+
ORDER BY updatedAt DESC
|
|
47420
|
+
LIMIT 1
|
|
47421
|
+
`).get(...baseParams);
|
|
47422
|
+
if (modelLessRow) {
|
|
47423
|
+
return this.rowToSession(modelLessRow);
|
|
47424
|
+
}
|
|
47425
|
+
const fallbackRow = this.db.prepare(`
|
|
47426
|
+
SELECT * FROM chat_sessions
|
|
47427
|
+
WHERE ${baseWhereSql}
|
|
47428
|
+
ORDER BY updatedAt DESC
|
|
47429
|
+
LIMIT 1
|
|
47430
|
+
`).get(...baseParams);
|
|
47431
|
+
return fallbackRow ? this.rowToSession(fallbackRow) : void 0;
|
|
47432
|
+
}
|
|
47083
47433
|
/**
|
|
47084
47434
|
* Update a chat session.
|
|
47085
47435
|
*
|
|
@@ -47158,7 +47508,7 @@ var init_chat_store = __esm({
|
|
|
47158
47508
|
throw new Error(`Chat session ${sessionId} not found`);
|
|
47159
47509
|
}
|
|
47160
47510
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
47161
|
-
const id = `msg-${
|
|
47511
|
+
const id = `msg-${randomUUID8().slice(0, 8)}`;
|
|
47162
47512
|
const message = {
|
|
47163
47513
|
id,
|
|
47164
47514
|
sessionId,
|
|
@@ -47312,6 +47662,7 @@ __export(src_exports, {
|
|
|
47312
47662
|
DEFAULT_MIN_INTERVAL_MS: () => DEFAULT_MIN_INTERVAL_MS,
|
|
47313
47663
|
DEFAULT_PROJECT_SETTINGS: () => DEFAULT_PROJECT_SETTINGS,
|
|
47314
47664
|
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
|
|
47665
|
+
DEFAULT_TASK_PRIORITY: () => DEFAULT_TASK_PRIORITY,
|
|
47315
47666
|
DaemonTokenManager: () => DaemonTokenManager,
|
|
47316
47667
|
Database: () => Database,
|
|
47317
47668
|
EXECUTION_MODES: () => EXECUTION_MODES,
|
|
@@ -47367,6 +47718,7 @@ __export(src_exports, {
|
|
|
47367
47718
|
SLICE_PLAN_STATES: () => SLICE_PLAN_STATES,
|
|
47368
47719
|
SLICE_STATUSES: () => SLICE_STATUSES,
|
|
47369
47720
|
SUMMARIZE_SYSTEM_PROMPT: () => SUMMARIZE_SYSTEM_PROMPT,
|
|
47721
|
+
TASK_PRIORITIES: () => TASK_PRIORITIES,
|
|
47370
47722
|
THEME_MODES: () => THEME_MODES,
|
|
47371
47723
|
THINKING_LEVELS: () => THINKING_LEVELS,
|
|
47372
47724
|
TaskStore: () => TaskStore,
|
|
@@ -47399,6 +47751,8 @@ __export(src_exports, {
|
|
|
47399
47751
|
clearOverrides: () => clearOverrides,
|
|
47400
47752
|
collectSystemMetrics: () => collectSystemMetrics,
|
|
47401
47753
|
compactMemoryWithAi: () => compactMemoryWithAi,
|
|
47754
|
+
compareTaskPriority: () => compareTaskPriority,
|
|
47755
|
+
compareTasksByPriorityThenAgeAndId: () => compareTasksByPriorityThenAgeAndId,
|
|
47402
47756
|
computeAccessState: () => computeAccessState,
|
|
47403
47757
|
computeInsightFingerprint: () => computeInsightFingerprint,
|
|
47404
47758
|
convertAgentCompanies: () => convertAgentCompanies,
|
|
@@ -47454,6 +47808,7 @@ __export(src_exports, {
|
|
|
47454
47808
|
getRateLimitResetTime: () => getRateLimitResetTime,
|
|
47455
47809
|
getTaskCompletionBlocker: () => getTaskCompletionBlocker,
|
|
47456
47810
|
getTaskMergeBlocker: () => getTaskMergeBlocker,
|
|
47811
|
+
getTaskPriorityRank: () => getTaskPriorityRank,
|
|
47457
47812
|
getTemplatesForRole: () => getTemplatesForRole,
|
|
47458
47813
|
getValidTransitions: () => getValidTransitions,
|
|
47459
47814
|
hasAgentIdentity: () => hasAgentIdentity,
|
|
@@ -47470,6 +47825,7 @@ __export(src_exports, {
|
|
|
47470
47825
|
isManualTrigger: () => isManualTrigger,
|
|
47471
47826
|
isProjectSettingsKey: () => isProjectSettingsKey,
|
|
47472
47827
|
isQmdAvailable: () => isQmdAvailable,
|
|
47828
|
+
isTaskPriority: () => isTaskPriority,
|
|
47473
47829
|
isTaskReadyForMerge: () => isTaskReadyForMerge,
|
|
47474
47830
|
isValidPermission: () => isValidPermission,
|
|
47475
47831
|
isValidPromptKey: () => isValidPromptKey,
|
|
@@ -47493,6 +47849,7 @@ __export(src_exports, {
|
|
|
47493
47849
|
normalizePermissions: () => normalizePermissions,
|
|
47494
47850
|
normalizeRoadmapFeatureOrder: () => normalizeRoadmapFeatureOrder,
|
|
47495
47851
|
normalizeRoadmapMilestoneOrder: () => normalizeRoadmapMilestoneOrder,
|
|
47852
|
+
normalizeTaskPriority: () => normalizeTaskPriority,
|
|
47496
47853
|
parseAgentManifest: () => parseAgentManifest,
|
|
47497
47854
|
parseCompanyArchive: () => parseCompanyArchive,
|
|
47498
47855
|
parseCompanyDirectory: () => parseCompanyDirectory,
|
|
@@ -47545,6 +47902,7 @@ __export(src_exports, {
|
|
|
47545
47902
|
shouldSkipBackgroundQmdRefresh: () => shouldSkipBackgroundQmdRefresh,
|
|
47546
47903
|
shouldTriggerExtraction: () => shouldTriggerExtraction,
|
|
47547
47904
|
slugify: () => slugify,
|
|
47905
|
+
sortTasksByPriorityThenAgeAndId: () => sortTasksByPriorityThenAgeAndId,
|
|
47548
47906
|
summarizeTitle: () => summarizeTitle,
|
|
47549
47907
|
syncAutoSummarizeAutomation: () => syncAutoSummarizeAutomation,
|
|
47550
47908
|
syncBackupAutomation: () => syncBackupAutomation,
|
|
@@ -47602,6 +47960,7 @@ var init_src = __esm({
|
|
|
47602
47960
|
init_ai_summarize();
|
|
47603
47961
|
init_memory_compaction();
|
|
47604
47962
|
init_roadmap_ordering();
|
|
47963
|
+
init_task_priority();
|
|
47605
47964
|
init_roadmap_handoff();
|
|
47606
47965
|
init_mission_types();
|
|
47607
47966
|
init_mission_store();
|
|
@@ -47627,25 +47986,50 @@ var init_src = __esm({
|
|
|
47627
47986
|
}
|
|
47628
47987
|
});
|
|
47629
47988
|
|
|
47630
|
-
// ../engine/src/logger.
|
|
47989
|
+
// ../engine/src/logger.ts
|
|
47990
|
+
function withSeverityMarker2(level, payload) {
|
|
47991
|
+
return `${LOG_LEVEL_MARKER_PREFIX2}${level}${LOG_LEVEL_MARKER_SUFFIX2}${payload}`;
|
|
47992
|
+
}
|
|
47631
47993
|
function createLogger2(prefix) {
|
|
47632
47994
|
const tag = `[${prefix}]`;
|
|
47633
47995
|
return {
|
|
47634
47996
|
log(message, ...args) {
|
|
47635
|
-
|
|
47997
|
+
console.error(withSeverityMarker2("info", `${tag} ${message}`), ...args);
|
|
47636
47998
|
},
|
|
47637
47999
|
warn(message, ...args) {
|
|
47638
|
-
|
|
48000
|
+
console.warn(withSeverityMarker2("warn", `${tag} ${message}`), ...args);
|
|
47639
48001
|
},
|
|
47640
48002
|
error(message, ...args) {
|
|
47641
|
-
|
|
48003
|
+
console.error(withSeverityMarker2("error", `${tag} ${message}`), ...args);
|
|
47642
48004
|
}
|
|
47643
48005
|
};
|
|
47644
48006
|
}
|
|
47645
|
-
|
|
48007
|
+
function formatError(err) {
|
|
48008
|
+
if (err instanceof Error) {
|
|
48009
|
+
const message2 = err.message || err.name || "Error";
|
|
48010
|
+
const stack = err.stack;
|
|
48011
|
+
const detail = stack && stack.includes(message2) ? stack : stack ? `${message2}
|
|
48012
|
+
${stack}` : message2;
|
|
48013
|
+
return { message: message2, stack, detail };
|
|
48014
|
+
}
|
|
48015
|
+
let message;
|
|
48016
|
+
if (typeof err === "string") {
|
|
48017
|
+
message = err;
|
|
48018
|
+
} else {
|
|
48019
|
+
try {
|
|
48020
|
+
message = JSON.stringify(err);
|
|
48021
|
+
} catch {
|
|
48022
|
+
message = String(err);
|
|
48023
|
+
}
|
|
48024
|
+
}
|
|
48025
|
+
return { message, detail: message };
|
|
48026
|
+
}
|
|
48027
|
+
var LOG_LEVEL_MARKER_PREFIX2, LOG_LEVEL_MARKER_SUFFIX2, schedulerLog, executorLog, triageLog, piLog, extensionsLog, mergerLog, worktreePoolLog, reviewerLog, prMonitorLog, runtimeLog, ipcLog, projectManagerLog, hybridExecutorLog, autopilotLog, heartbeatLog, remoteNodeLog, nodeHealthMonitorLog, peerExchangeLog;
|
|
47646
48028
|
var init_logger2 = __esm({
|
|
47647
|
-
"../engine/src/logger.
|
|
48029
|
+
"../engine/src/logger.ts"() {
|
|
47648
48030
|
"use strict";
|
|
48031
|
+
LOG_LEVEL_MARKER_PREFIX2 = "\0fnlvl=";
|
|
48032
|
+
LOG_LEVEL_MARKER_SUFFIX2 = "\0";
|
|
47649
48033
|
schedulerLog = createLogger2("scheduler");
|
|
47650
48034
|
executorLog = createLogger2("executor");
|
|
47651
48035
|
triageLog = createLogger2("triage");
|
|
@@ -48100,7 +48484,7 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path, startLine = 1, l
|
|
|
48100
48484
|
}
|
|
48101
48485
|
function createTaskCreateTool(store) {
|
|
48102
48486
|
return {
|
|
48103
|
-
name: "
|
|
48487
|
+
name: "fn_task_create",
|
|
48104
48488
|
label: "Create Task",
|
|
48105
48489
|
description: "Create a new task for out-of-scope work discovered during execution. The task goes into triage where it will be specified by the AI. Optionally set dependencies (e.g., the new task depends on the current one, or the current task should wait for the new one).",
|
|
48106
48490
|
parameters: taskCreateParams,
|
|
@@ -48123,7 +48507,7 @@ function createTaskCreateTool(store) {
|
|
|
48123
48507
|
}
|
|
48124
48508
|
function createTaskLogTool(store, taskId) {
|
|
48125
48509
|
return {
|
|
48126
|
-
name: "
|
|
48510
|
+
name: "fn_task_log",
|
|
48127
48511
|
label: "Log Entry",
|
|
48128
48512
|
description: "Log an important action, decision, or issue for this task. Use for significant events \u2014 not every small step.",
|
|
48129
48513
|
parameters: taskLogParams,
|
|
@@ -48138,7 +48522,7 @@ function createTaskLogTool(store, taskId) {
|
|
|
48138
48522
|
}
|
|
48139
48523
|
function createTaskLogToolWithContext(store, taskId, runContext) {
|
|
48140
48524
|
return {
|
|
48141
|
-
name: "
|
|
48525
|
+
name: "fn_task_log",
|
|
48142
48526
|
label: "Log Entry",
|
|
48143
48527
|
description: "Log an important action, decision, or issue for this task. Use for significant events \u2014 not every small step.",
|
|
48144
48528
|
parameters: taskLogParams,
|
|
@@ -48153,7 +48537,7 @@ function createTaskLogToolWithContext(store, taskId, runContext) {
|
|
|
48153
48537
|
}
|
|
48154
48538
|
function createTaskDocumentWriteTool(store, taskId) {
|
|
48155
48539
|
return {
|
|
48156
|
-
name: "
|
|
48540
|
+
name: "fn_task_document_write",
|
|
48157
48541
|
label: "Write Document",
|
|
48158
48542
|
description: "Save a named document for this task (for example plan, notes, or research). Each write creates a new revision so you can update documents over time.",
|
|
48159
48543
|
parameters: taskDocumentWriteParams,
|
|
@@ -48186,7 +48570,7 @@ function createTaskDocumentWriteTool(store, taskId) {
|
|
|
48186
48570
|
}
|
|
48187
48571
|
function createTaskDocumentReadTool(store, taskId) {
|
|
48188
48572
|
return {
|
|
48189
|
-
name: "
|
|
48573
|
+
name: "fn_task_document_read",
|
|
48190
48574
|
label: "Read Document",
|
|
48191
48575
|
description: "Read a named document for this task, or list all documents when no key is provided.",
|
|
48192
48576
|
parameters: taskDocumentReadParams,
|
|
@@ -48242,9 +48626,9 @@ ${lines.join("\n")}`
|
|
|
48242
48626
|
}
|
|
48243
48627
|
function createMemorySearchTool(rootDir, settings, options) {
|
|
48244
48628
|
return {
|
|
48245
|
-
name: "
|
|
48629
|
+
name: "fn_memory_search",
|
|
48246
48630
|
label: "Search Memory",
|
|
48247
|
-
description: "Search durable project memory and this agent's own memory, returning small snippets with file paths and line ranges. Use this before
|
|
48631
|
+
description: "Search durable project memory and this agent's own memory, returning small snippets with file paths and line ranges. Use this before fn_memory_get; do not read all memory by default.",
|
|
48248
48632
|
parameters: memorySearchParams,
|
|
48249
48633
|
execute: async (_id, params) => {
|
|
48250
48634
|
const limit = params.limit ?? 5;
|
|
@@ -48270,9 +48654,9 @@ function createMemorySearchTool(rootDir, settings, options) {
|
|
|
48270
48654
|
}
|
|
48271
48655
|
function createMemoryGetTool(rootDir, settings, options) {
|
|
48272
48656
|
return {
|
|
48273
|
-
name: "
|
|
48657
|
+
name: "fn_memory_get",
|
|
48274
48658
|
label: "Get Memory",
|
|
48275
|
-
description: "Read a bounded line window from a memory file returned by
|
|
48659
|
+
description: "Read a bounded line window from a memory file returned by fn_memory_search. Allowed files include project memory under .fusion/memory/ and this agent's own .fusion/agent-memory/{agentId}/MEMORY.md file.",
|
|
48276
48660
|
parameters: memoryGetParams,
|
|
48277
48661
|
execute: async (_id, params) => {
|
|
48278
48662
|
const agentResult = options?.agentMemory ? await getAgentMemoryWindow(rootDir, options.agentMemory, params.path, params.startLine, params.lineCount) : null;
|
|
@@ -48306,7 +48690,7 @@ ${result.content}`
|
|
|
48306
48690
|
}
|
|
48307
48691
|
function createMemoryAppendTool(rootDir, settings, options) {
|
|
48308
48692
|
return {
|
|
48309
|
-
name: "
|
|
48693
|
+
name: "fn_memory_append",
|
|
48310
48694
|
label: "Append Memory",
|
|
48311
48695
|
description: "Append concise Markdown to project memory. Use long-term only for durable conventions/decisions/pitfalls; use daily for running observations and open loops. Skip this tool when there is no reusable memory.",
|
|
48312
48696
|
parameters: memoryAppendParams,
|
|
@@ -48368,7 +48752,7 @@ function createMemoryTools(rootDir, settings, options) {
|
|
|
48368
48752
|
}
|
|
48369
48753
|
function createReflectOnPerformanceTool(reflectionService, agentId) {
|
|
48370
48754
|
return {
|
|
48371
|
-
name: "
|
|
48755
|
+
name: "fn_reflect_on_performance",
|
|
48372
48756
|
label: "Reflect on Performance",
|
|
48373
48757
|
description: 'Review your past task performance and generate insights for improvement. Optionally focus on a specific area like "code quality", "speed", or "testing".',
|
|
48374
48758
|
parameters: reflectOnPerformanceParams,
|
|
@@ -48401,7 +48785,7 @@ function createReflectOnPerformanceTool(reflectionService, agentId) {
|
|
|
48401
48785
|
}
|
|
48402
48786
|
function createListAgentsTool(agentStore) {
|
|
48403
48787
|
return {
|
|
48404
|
-
name: "
|
|
48788
|
+
name: "fn_list_agents",
|
|
48405
48789
|
label: "List Agents",
|
|
48406
48790
|
description: "List all available agents in the system. Shows each agent's name, role, state, personality (soul), and current assignment. Use this to discover which agents exist and what they specialize in before delegating work.",
|
|
48407
48791
|
parameters: listAgentsParams,
|
|
@@ -48444,9 +48828,9 @@ ${lines.join("\n\n")}` }],
|
|
|
48444
48828
|
}
|
|
48445
48829
|
function createDelegateTaskTool(agentStore, taskStore) {
|
|
48446
48830
|
return {
|
|
48447
|
-
name: "
|
|
48831
|
+
name: "fn_delegate_task",
|
|
48448
48832
|
label: "Delegate Task",
|
|
48449
|
-
description: "Create a new task and assign it to a specific agent for execution. The task goes to 'todo' and will be picked up by the target agent on their next heartbeat cycle. Use
|
|
48833
|
+
description: "Create a new task and assign it to a specific agent for execution. The task goes to 'todo' and will be picked up by the target agent on their next heartbeat cycle. Use fn_list_agents first to find available agents and their capabilities.",
|
|
48450
48834
|
parameters: delegateTaskParams,
|
|
48451
48835
|
execute: async (_id, params) => {
|
|
48452
48836
|
const agent = await agentStore.getAgent(params.agent_id);
|
|
@@ -48481,7 +48865,7 @@ function createDelegateTaskTool(agentStore, taskStore) {
|
|
|
48481
48865
|
}
|
|
48482
48866
|
function createSendMessageTool(messageStore, fromAgentId) {
|
|
48483
48867
|
return {
|
|
48484
|
-
name: "
|
|
48868
|
+
name: "fn_send_message",
|
|
48485
48869
|
label: "Send Message",
|
|
48486
48870
|
description: "Send a message to another agent or user. The recipient will be woken if they have `messageResponseMode: 'immediate'` configured. When replying to an existing message, include `reply_to_message_id` to preserve threading.",
|
|
48487
48871
|
parameters: sendMessageParams,
|
|
@@ -48538,7 +48922,7 @@ function createSendMessageTool(messageStore, fromAgentId) {
|
|
|
48538
48922
|
}
|
|
48539
48923
|
function createReadMessagesTool(messageStore, agentId) {
|
|
48540
48924
|
return {
|
|
48541
|
-
name: "
|
|
48925
|
+
name: "fn_read_messages",
|
|
48542
48926
|
label: "Read Messages",
|
|
48543
48927
|
description: "Read your inbox messages. Returns unread messages by default.",
|
|
48544
48928
|
parameters: readMessagesParams,
|
|
@@ -48640,7 +49024,7 @@ var init_agent_tools = __esm({
|
|
|
48640
49024
|
Type.Literal("agent-to-user")
|
|
48641
49025
|
], { description: "Message type (defaults to 'agent-to-agent')" })),
|
|
48642
49026
|
reply_to_message_id: Type.Optional(
|
|
48643
|
-
Type.String({ description: "Optional ID of the message you are replying to (use IDs from
|
|
49027
|
+
Type.String({ description: "Optional ID of the message you are replying to (use IDs from fn_read_messages output)" })
|
|
48644
49028
|
)
|
|
48645
49029
|
});
|
|
48646
49030
|
readMessagesParams = Type.Object({
|
|
@@ -48652,7 +49036,7 @@ var init_agent_tools = __esm({
|
|
|
48652
49036
|
limit: Type.Optional(Type.Number({ description: "Maximum snippets to return (default: 5, max: 20)" }))
|
|
48653
49037
|
});
|
|
48654
49038
|
memoryGetParams = Type.Object({
|
|
48655
|
-
path: Type.String({ description: "Memory path from
|
|
49039
|
+
path: Type.String({ description: "Memory path from fn_memory_search, e.g. .fusion/memory/MEMORY.md or .fusion/memory/YYYY-MM-DD.md" }),
|
|
48656
49040
|
startLine: Type.Optional(Type.Number({ description: "1-based start line (default: 1)" })),
|
|
48657
49041
|
lineCount: Type.Optional(Type.Number({ description: "Number of lines to read (default: 120, max: 400)" }))
|
|
48658
49042
|
});
|
|
@@ -48801,7 +49185,7 @@ var init_concurrency = __esm({
|
|
|
48801
49185
|
}
|
|
48802
49186
|
});
|
|
48803
49187
|
|
|
48804
|
-
// ../engine/src/skill-resolver.
|
|
49188
|
+
// ../engine/src/skill-resolver.ts
|
|
48805
49189
|
import { existsSync as existsSync18, readFileSync as readFileSync4 } from "node:fs";
|
|
48806
49190
|
import { join as join22 } from "node:path";
|
|
48807
49191
|
function readJsonObject(path) {
|
|
@@ -48925,9 +49309,13 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
48925
49309
|
let filteredSkills;
|
|
48926
49310
|
if (hasRequestedNames) {
|
|
48927
49311
|
const requestedNamesLower = new Set(requestedSkillNames.map((n) => n.toLowerCase()));
|
|
48928
|
-
filteredSkills = base.skills.filter(
|
|
49312
|
+
filteredSkills = base.skills.filter(
|
|
49313
|
+
(skill) => requestedNamesLower.has(skill.name.toLowerCase()) && !excludedSkillPaths.has(skill.filePath)
|
|
49314
|
+
);
|
|
48929
49315
|
} else if (hasPatterns) {
|
|
48930
|
-
filteredSkills = base.skills.filter(
|
|
49316
|
+
filteredSkills = base.skills.filter(
|
|
49317
|
+
(skill) => allowedSkillPaths.has(skill.filePath) && !excludedSkillPaths.has(skill.filePath)
|
|
49318
|
+
);
|
|
48931
49319
|
} else if (hasExcluded) {
|
|
48932
49320
|
filteredSkills = base.skills.filter((skill) => !excludedSkillPaths.has(skill.filePath));
|
|
48933
49321
|
} else {
|
|
@@ -48967,6 +49355,7 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
48967
49355
|
}
|
|
48968
49356
|
}
|
|
48969
49357
|
if (newDiagnostics.length > 0) {
|
|
49358
|
+
const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills";
|
|
48970
49359
|
for (const diag of newDiagnostics) {
|
|
48971
49360
|
piLog.warn(`[skills] ${diag.type}: ${diag.message}`);
|
|
48972
49361
|
}
|
|
@@ -48978,21 +49367,20 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
48978
49367
|
};
|
|
48979
49368
|
}
|
|
48980
49369
|
var init_skill_resolver = __esm({
|
|
48981
|
-
"../engine/src/skill-resolver.
|
|
49370
|
+
"../engine/src/skill-resolver.ts"() {
|
|
48982
49371
|
"use strict";
|
|
48983
49372
|
init_logger2();
|
|
48984
49373
|
}
|
|
48985
49374
|
});
|
|
48986
49375
|
|
|
48987
|
-
// ../engine/src/context-limit-detector.
|
|
49376
|
+
// ../engine/src/context-limit-detector.ts
|
|
48988
49377
|
function isContextLimitError(message) {
|
|
48989
|
-
if (!message)
|
|
48990
|
-
return false;
|
|
49378
|
+
if (!message) return false;
|
|
48991
49379
|
return CONTEXT_OVERFLOW_PATTERNS.some((pattern) => pattern.test(message));
|
|
48992
49380
|
}
|
|
48993
49381
|
var CONTEXT_OVERFLOW_PATTERNS;
|
|
48994
49382
|
var init_context_limit_detector = __esm({
|
|
48995
|
-
"../engine/src/context-limit-detector.
|
|
49383
|
+
"../engine/src/context-limit-detector.ts"() {
|
|
48996
49384
|
"use strict";
|
|
48997
49385
|
CONTEXT_OVERFLOW_PATTERNS = [
|
|
48998
49386
|
// Anthropic: "prompt is too long: X tokens > Y maximum"
|
|
@@ -49029,14 +49417,14 @@ var init_context_limit_detector = __esm({
|
|
|
49029
49417
|
}
|
|
49030
49418
|
});
|
|
49031
49419
|
|
|
49032
|
-
// ../engine/src/auth-storage.
|
|
49420
|
+
// ../engine/src/auth-storage.ts
|
|
49033
49421
|
import { existsSync as existsSync19, readFileSync as readFileSync5 } from "node:fs";
|
|
49034
49422
|
import { homedir as homedir4 } from "node:os";
|
|
49035
49423
|
import { join as join23 } from "node:path";
|
|
49036
49424
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
49037
49425
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
49038
49426
|
function getHomeDir2() {
|
|
49039
|
-
return
|
|
49427
|
+
return process.env.HOME || process.env.USERPROFILE || homedir4();
|
|
49040
49428
|
}
|
|
49041
49429
|
function getFusionAuthPath(home = getHomeDir2()) {
|
|
49042
49430
|
return join23(home, ".fusion", "agent", "auth.json");
|
|
@@ -49080,9 +49468,8 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
|
|
|
49080
49468
|
return credentials;
|
|
49081
49469
|
}
|
|
49082
49470
|
function resolveStoredApiKey(key) {
|
|
49083
|
-
if (!key)
|
|
49084
|
-
|
|
49085
|
-
return globalThis.process.env[key] ?? key;
|
|
49471
|
+
if (!key) return void 0;
|
|
49472
|
+
return process.env[key] ?? key;
|
|
49086
49473
|
}
|
|
49087
49474
|
function resolveOAuthApiKey(providerId, credential) {
|
|
49088
49475
|
if (credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires) {
|
|
@@ -49128,8 +49515,7 @@ function createFusionAuthStorage() {
|
|
|
49128
49515
|
if (prop === "getApiKey") {
|
|
49129
49516
|
return async (provider) => {
|
|
49130
49517
|
const primaryKey = await target.getApiKey(provider);
|
|
49131
|
-
if (primaryKey)
|
|
49132
|
-
return primaryKey;
|
|
49518
|
+
if (primaryKey) return primaryKey;
|
|
49133
49519
|
return resolveStoredCredentialApiKey(provider, legacyCredentials[provider]);
|
|
49134
49520
|
};
|
|
49135
49521
|
}
|
|
@@ -49138,12 +49524,12 @@ function createFusionAuthStorage() {
|
|
|
49138
49524
|
});
|
|
49139
49525
|
}
|
|
49140
49526
|
var init_auth_storage = __esm({
|
|
49141
|
-
"../engine/src/auth-storage.
|
|
49527
|
+
"../engine/src/auth-storage.ts"() {
|
|
49142
49528
|
"use strict";
|
|
49143
49529
|
}
|
|
49144
49530
|
});
|
|
49145
49531
|
|
|
49146
|
-
// ../engine/src/pi.
|
|
49532
|
+
// ../engine/src/pi.ts
|
|
49147
49533
|
var pi_exports = {};
|
|
49148
49534
|
__export(pi_exports, {
|
|
49149
49535
|
COMPACTION_FALLBACK_INSTRUCTIONS: () => COMPACTION_FALLBACK_INSTRUCTIONS,
|
|
@@ -49156,20 +49542,36 @@ __export(pi_exports, {
|
|
|
49156
49542
|
import { existsSync as existsSync20, readFileSync as readFileSync6 } from "node:fs";
|
|
49157
49543
|
import { exec } from "node:child_process";
|
|
49158
49544
|
import { promisify as promisify2 } from "node:util";
|
|
49159
|
-
import { basename as
|
|
49160
|
-
import {
|
|
49545
|
+
import { basename as basename7, dirname as dirname7, join as join24, relative as relative3, isAbsolute as isAbsolute6, resolve as resolve10 } from "node:path";
|
|
49546
|
+
import {
|
|
49547
|
+
createAgentSession,
|
|
49548
|
+
createCodingTools,
|
|
49549
|
+
createExtensionRuntime,
|
|
49550
|
+
createReadOnlyTools,
|
|
49551
|
+
DefaultResourceLoader,
|
|
49552
|
+
DefaultPackageManager,
|
|
49553
|
+
discoverAndLoadExtensions,
|
|
49554
|
+
ModelRegistry,
|
|
49555
|
+
SessionManager,
|
|
49556
|
+
SettingsManager
|
|
49557
|
+
} from "@mariozechner/pi-coding-agent";
|
|
49161
49558
|
function getSessionStateError(session) {
|
|
49162
|
-
const
|
|
49559
|
+
const state = session.state;
|
|
49560
|
+
const error = state?.errorMessage ?? state?.error;
|
|
49163
49561
|
return typeof error === "string" ? error : "";
|
|
49164
49562
|
}
|
|
49165
49563
|
function clearSessionStateError(session) {
|
|
49166
49564
|
const state = session.state;
|
|
49167
|
-
if (!state || typeof state !== "object"
|
|
49565
|
+
if (!state || typeof state !== "object") {
|
|
49168
49566
|
return;
|
|
49169
49567
|
}
|
|
49170
|
-
|
|
49171
|
-
|
|
49172
|
-
|
|
49568
|
+
for (const key of ["errorMessage", "error"]) {
|
|
49569
|
+
if (key in state) {
|
|
49570
|
+
try {
|
|
49571
|
+
state[key] = void 0;
|
|
49572
|
+
} catch {
|
|
49573
|
+
}
|
|
49574
|
+
}
|
|
49173
49575
|
}
|
|
49174
49576
|
}
|
|
49175
49577
|
async function promptSessionAndCheck(session, prompt, options) {
|
|
@@ -49181,6 +49583,29 @@ async function promptSessionAndCheck(session, prompt, options) {
|
|
|
49181
49583
|
}
|
|
49182
49584
|
const stateError = getSessionStateError(session);
|
|
49183
49585
|
if (stateError) {
|
|
49586
|
+
if (/Cannot read propert(y|ies) of (undefined|null)/i.test(stateError)) {
|
|
49587
|
+
try {
|
|
49588
|
+
const messages = session.agent?.state?.messages ?? session.state?.messages;
|
|
49589
|
+
if (Array.isArray(messages)) {
|
|
49590
|
+
const recent = messages.slice(-6).map((m, idx) => {
|
|
49591
|
+
const i = messages.length - 6 + idx;
|
|
49592
|
+
const content = m?.content;
|
|
49593
|
+
return {
|
|
49594
|
+
index: i < 0 ? idx : i,
|
|
49595
|
+
role: m?.role,
|
|
49596
|
+
contentType: Array.isArray(content) ? `array(len=${content.length})` : typeof content,
|
|
49597
|
+
toolName: m.toolName,
|
|
49598
|
+
stopReason: m.stopReason
|
|
49599
|
+
};
|
|
49600
|
+
});
|
|
49601
|
+
piLog.error(`pi state error \u2014 transcript tail (${messages.length} msgs total): ${JSON.stringify(recent)}`);
|
|
49602
|
+
} else {
|
|
49603
|
+
piLog.error(`pi state error \u2014 state.messages is not an array: ${typeof messages}`);
|
|
49604
|
+
}
|
|
49605
|
+
} catch (inspectErr) {
|
|
49606
|
+
piLog.warn(`pi state error \u2014 failed to inspect transcript: ${inspectErr instanceof Error ? inspectErr.message : String(inspectErr)}`);
|
|
49607
|
+
}
|
|
49608
|
+
}
|
|
49184
49609
|
throw new Error(stateError);
|
|
49185
49610
|
}
|
|
49186
49611
|
}
|
|
@@ -49232,8 +49657,7 @@ async function promptWithFallback(session, prompt, options) {
|
|
|
49232
49657
|
}
|
|
49233
49658
|
function describeModel(session) {
|
|
49234
49659
|
const model = session.model;
|
|
49235
|
-
if (!model)
|
|
49236
|
-
return "unknown model";
|
|
49660
|
+
if (!model) return "unknown model";
|
|
49237
49661
|
return `${model.provider}/${model.id}`;
|
|
49238
49662
|
}
|
|
49239
49663
|
function compactMarkdownMemorySection(sectionBody) {
|
|
@@ -49286,7 +49710,9 @@ async function retryWithCompactedPromptMemory(session, prompt, options) {
|
|
|
49286
49710
|
if (!compactedPrompt) {
|
|
49287
49711
|
return { recovered: false };
|
|
49288
49712
|
}
|
|
49289
|
-
piLog.log(
|
|
49713
|
+
piLog.log(
|
|
49714
|
+
`promptWithFallback: retrying with compacted prompt memory (${prompt.length} \u2192 ${compactedPrompt.length} chars)`
|
|
49715
|
+
);
|
|
49290
49716
|
try {
|
|
49291
49717
|
await promptSessionAndCheck(session, compactedPrompt, options);
|
|
49292
49718
|
piLog.log("promptWithFallback: prompt completed after prompt-memory compaction");
|
|
@@ -49303,7 +49729,7 @@ async function flushMemoryBeforeSessionCompaction(session) {
|
|
|
49303
49729
|
}
|
|
49304
49730
|
const flushPrompt = [
|
|
49305
49731
|
"Before context compaction, preserve only unresolved durable memory if needed.",
|
|
49306
|
-
"If
|
|
49732
|
+
"If fn_memory_append is available and you learned reusable project decisions, conventions, pitfalls, or open loops that are not already saved, append them now.",
|
|
49307
49733
|
'Use layer="long-term" for durable facts and layer="daily" for running notes/open loops.',
|
|
49308
49734
|
"If there is nothing durable to save, reply exactly: NONE."
|
|
49309
49735
|
].join("\n");
|
|
@@ -49348,7 +49774,9 @@ function resolveConfiguredModel(modelRegistry, kind, provider, modelId) {
|
|
|
49348
49774
|
piLog.warn(`${kind} model ${provider}/${modelId} not in registry; using provider base model as template`);
|
|
49349
49775
|
return { ...baseModel, id: modelId, name: modelId };
|
|
49350
49776
|
}
|
|
49351
|
-
throw new Error(
|
|
49777
|
+
throw new Error(
|
|
49778
|
+
`Configured ${kind} model ${provider}/${modelId} was not found in the pi model registry. Open Settings and choose a model from /api/models, or update your pi model configuration.`
|
|
49779
|
+
);
|
|
49352
49780
|
}
|
|
49353
49781
|
function isRetryableModelSelectionError(message) {
|
|
49354
49782
|
const normalized = message.toLowerCase();
|
|
@@ -49393,11 +49821,18 @@ function normalizeAssistantOrToolResultMessage(message) {
|
|
|
49393
49821
|
return false;
|
|
49394
49822
|
}
|
|
49395
49823
|
const role = message.role;
|
|
49396
|
-
if (role !== "assistant" && role !== "toolResult") {
|
|
49824
|
+
if (role !== "assistant" && role !== "toolResult" && role !== "user") {
|
|
49397
49825
|
return false;
|
|
49398
49826
|
}
|
|
49399
|
-
|
|
49400
|
-
|
|
49827
|
+
const obj = message;
|
|
49828
|
+
if (role === "user") {
|
|
49829
|
+
if (typeof obj.content !== "string" && !Array.isArray(obj.content)) {
|
|
49830
|
+
obj.content = [];
|
|
49831
|
+
}
|
|
49832
|
+
return true;
|
|
49833
|
+
}
|
|
49834
|
+
if (!Array.isArray(obj.content)) {
|
|
49835
|
+
obj.content = [];
|
|
49401
49836
|
}
|
|
49402
49837
|
return true;
|
|
49403
49838
|
}
|
|
@@ -49454,6 +49889,12 @@ function installMessageContentGuard(session, sessionManager) {
|
|
|
49454
49889
|
if (session.__fusionMessageContentGuardInstalled) {
|
|
49455
49890
|
return;
|
|
49456
49891
|
}
|
|
49892
|
+
const existingMessages = session.agent?.state?.messages;
|
|
49893
|
+
if (Array.isArray(existingMessages)) {
|
|
49894
|
+
for (const candidate of existingMessages) {
|
|
49895
|
+
normalizeAssistantOrToolResultMessage(candidate);
|
|
49896
|
+
}
|
|
49897
|
+
}
|
|
49457
49898
|
if (typeof session.subscribe === "function") {
|
|
49458
49899
|
session.subscribe((event) => {
|
|
49459
49900
|
if (!event || typeof event !== "object" || event.type !== "message_end") {
|
|
@@ -49480,10 +49921,10 @@ function hasPackageManagerSettings(settings) {
|
|
|
49480
49921
|
return Array.isArray(settings.packages) || Array.isArray(settings.npmCommand);
|
|
49481
49922
|
}
|
|
49482
49923
|
function siblingAgentDir(agentDir, siblingRoot) {
|
|
49483
|
-
if (
|
|
49924
|
+
if (basename7(agentDir) !== "agent") {
|
|
49484
49925
|
return void 0;
|
|
49485
49926
|
}
|
|
49486
|
-
return join24(
|
|
49927
|
+
return join24(dirname7(dirname7(agentDir)), siblingRoot, "agent");
|
|
49487
49928
|
}
|
|
49488
49929
|
function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
49489
49930
|
const projectRoot = resolvePiExtensionProjectRoot(cwd);
|
|
@@ -49496,8 +49937,8 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
49496
49937
|
const fusionProjectSettings = readJsonObject2(join24(projectRoot, ".fusion", "settings.json"));
|
|
49497
49938
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
49498
49939
|
return {
|
|
49499
|
-
getGlobalSettings: () =>
|
|
49500
|
-
getProjectSettings: () =>
|
|
49940
|
+
getGlobalSettings: () => structuredClone(globalSettings),
|
|
49941
|
+
getProjectSettings: () => structuredClone(fusionProjectSettings),
|
|
49501
49942
|
getNpmCommand: () => Array.isArray(mergedSettings.npmCommand) ? [...mergedSettings.npmCommand] : void 0
|
|
49502
49943
|
};
|
|
49503
49944
|
}
|
|
@@ -49524,7 +49965,11 @@ async function registerExtensionProviders(cwd, modelRegistry) {
|
|
|
49524
49965
|
});
|
|
49525
49966
|
const resolvedPaths = await packageManager.resolve();
|
|
49526
49967
|
const packageExtensionPaths = resolvedPaths.extensions.filter((resource) => resource.enabled).map((resource) => resource.path);
|
|
49527
|
-
const extensionsResult = await discoverAndLoadExtensions(
|
|
49968
|
+
const extensionsResult = await discoverAndLoadExtensions(
|
|
49969
|
+
[...getEnabledPiExtensionPaths(cwd), ...packageExtensionPaths],
|
|
49970
|
+
cwd,
|
|
49971
|
+
join24(resolvePiExtensionProjectRoot(cwd), ".fusion", "disabled-auto-extension-discovery")
|
|
49972
|
+
);
|
|
49528
49973
|
for (const { path, error } of extensionsResult.errors) {
|
|
49529
49974
|
extensionsLog.warn(`Failed to load ${path}: ${error}`);
|
|
49530
49975
|
}
|
|
@@ -49559,7 +50004,9 @@ async function isRegisteredGitWorktree(projectRoot, worktreePath) {
|
|
|
49559
50004
|
encoding: "utf-8"
|
|
49560
50005
|
});
|
|
49561
50006
|
const resolvedWorktree = resolve10(worktreePath);
|
|
49562
|
-
return stdout.split("\n").some(
|
|
50007
|
+
return stdout.split("\n").some(
|
|
50008
|
+
(line) => line.startsWith("worktree ") && resolve10(line.slice("worktree ".length)) === resolvedWorktree
|
|
50009
|
+
);
|
|
49563
50010
|
} catch {
|
|
49564
50011
|
return false;
|
|
49565
50012
|
}
|
|
@@ -49586,7 +50033,7 @@ async function assertValidWorktreeSession(cwd, projectRoot) {
|
|
|
49586
50033
|
throw new Error(`Refusing to start coding agent in unregistered git worktree: ${cwd}`);
|
|
49587
50034
|
}
|
|
49588
50035
|
}
|
|
49589
|
-
function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath) {
|
|
50036
|
+
function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath, toolName) {
|
|
49590
50037
|
const worktreeResolved = resolve10(worktreePath);
|
|
49591
50038
|
const projectRootResolved = resolve10(projectRoot);
|
|
49592
50039
|
const requestedResolved = isAbsolute6(requestedPath) ? resolve10(requestedPath) : resolve10(worktreeResolved, requestedPath);
|
|
@@ -49601,8 +50048,20 @@ function isWorktreeAllowedPath(worktreePath, projectRoot, requestedPath) {
|
|
|
49601
50048
|
if (relToProjectRoot.match(/^\.fusion\/tasks\/[^/]+\/attachments\//)) {
|
|
49602
50049
|
return true;
|
|
49603
50050
|
}
|
|
50051
|
+
const readOnlyTools = /* @__PURE__ */ new Set(["read", "glob", "grep"]);
|
|
50052
|
+
if (toolName && readOnlyTools.has(toolName) && /^\.fusion\/tasks\/[^/]+\/(PROMPT\.md|task\.json)$/.test(relToProjectRoot)) {
|
|
50053
|
+
return true;
|
|
50054
|
+
}
|
|
49604
50055
|
return false;
|
|
49605
50056
|
}
|
|
50057
|
+
function boundaryRejection(message) {
|
|
50058
|
+
return {
|
|
50059
|
+
content: [{ type: "text", text: message }],
|
|
50060
|
+
isError: true,
|
|
50061
|
+
ok: false,
|
|
50062
|
+
error: message
|
|
50063
|
+
};
|
|
50064
|
+
}
|
|
49606
50065
|
function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
49607
50066
|
if (!worktreePath || !projectRoot) {
|
|
49608
50067
|
return tools;
|
|
@@ -49616,21 +50075,21 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
49616
50075
|
return {
|
|
49617
50076
|
...tool,
|
|
49618
50077
|
execute: async (...args) => {
|
|
50078
|
+
const _toolCallId = args[0];
|
|
49619
50079
|
const params = args[1];
|
|
50080
|
+
const _signal = args[2];
|
|
49620
50081
|
const pathArg = params.path;
|
|
49621
|
-
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
|
|
50082
|
+
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg, tool.name)) {
|
|
49622
50083
|
const relToProject = relative3(projectRoot, pathArg);
|
|
49623
|
-
return
|
|
49624
|
-
|
|
49625
|
-
|
|
49626
|
-
};
|
|
50084
|
+
return boundaryRejection(
|
|
50085
|
+
`Path "${relToProject}" is outside the worktree boundary. Coding agents can only modify files inside the current worktree. Exceptions (read-only): .fusion/memory/, .fusion/tasks/*/attachments/, and .fusion/tasks/*/{PROMPT.md,task.json} for dependency context.`
|
|
50086
|
+
);
|
|
49627
50087
|
}
|
|
49628
50088
|
const cwdArg = params.cwd;
|
|
49629
|
-
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg)) {
|
|
49630
|
-
return
|
|
49631
|
-
|
|
49632
|
-
|
|
49633
|
-
};
|
|
50089
|
+
if (tool.name === "bash" && cwdArg && !isWorktreeAllowedPath(worktreePath, projectRoot, cwdArg, tool.name)) {
|
|
50090
|
+
return boundaryRejection(
|
|
50091
|
+
`Working directory is outside the worktree boundary. Commands must run inside the worktree.`
|
|
50092
|
+
);
|
|
49634
50093
|
}
|
|
49635
50094
|
return originalExecute(...args);
|
|
49636
50095
|
}
|
|
@@ -49640,7 +50099,7 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
49640
50099
|
async function createFnAgent2(options) {
|
|
49641
50100
|
piLog.log(`createFnAgent called (cwd=${options.cwd}, tools=${options.tools}, provider=${options.defaultProvider}, model=${options.defaultModelId})`);
|
|
49642
50101
|
const authStorage = createFusionAuthStorage();
|
|
49643
|
-
const modelRegistry =
|
|
50102
|
+
const modelRegistry = ModelRegistry.create(authStorage, getModelRegistryModelsPath());
|
|
49644
50103
|
await registerExtensionProviders(options.cwd, modelRegistry);
|
|
49645
50104
|
const tools = options.tools === "readonly" ? createReadOnlyTools(options.cwd) : createCodingTools(options.cwd);
|
|
49646
50105
|
const worktreePath = options.cwd;
|
|
@@ -49653,8 +50112,18 @@ async function createFnAgent2(options) {
|
|
|
49653
50112
|
compaction: { enabled: true },
|
|
49654
50113
|
retry: { enabled: true, maxRetries: 3 }
|
|
49655
50114
|
});
|
|
49656
|
-
const selectedModel = resolveConfiguredModel(
|
|
49657
|
-
|
|
50115
|
+
const selectedModel = resolveConfiguredModel(
|
|
50116
|
+
modelRegistry,
|
|
50117
|
+
"primary",
|
|
50118
|
+
options.defaultProvider,
|
|
50119
|
+
options.defaultModelId
|
|
50120
|
+
);
|
|
50121
|
+
const fallbackModel = resolveConfiguredModel(
|
|
50122
|
+
modelRegistry,
|
|
50123
|
+
"fallback",
|
|
50124
|
+
options.fallbackProvider,
|
|
50125
|
+
options.fallbackModelId
|
|
50126
|
+
);
|
|
49658
50127
|
let effectiveSkillSelection = options.skillSelection;
|
|
49659
50128
|
if (!effectiveSkillSelection && options.skills && options.skills.length > 0) {
|
|
49660
50129
|
piLog.log(`Using skills from convenience parameter: [${options.skills.join(", ")}]`);
|
|
@@ -49680,6 +50149,7 @@ async function createFnAgent2(options) {
|
|
|
49680
50149
|
}
|
|
49681
50150
|
const resourceLoader = new DefaultResourceLoader({
|
|
49682
50151
|
cwd: options.cwd,
|
|
50152
|
+
agentDir: getFusionAgentDir(),
|
|
49683
50153
|
settingsManager,
|
|
49684
50154
|
systemPromptOverride: () => options.systemPrompt,
|
|
49685
50155
|
appendSystemPromptOverride: () => [],
|
|
@@ -49689,13 +50159,17 @@ async function createFnAgent2(options) {
|
|
|
49689
50159
|
const sessionManager = options.sessionManager ?? SessionManager.inMemory();
|
|
49690
50160
|
normalizeSessionHistoryEntries(sessionManager);
|
|
49691
50161
|
const createSessionWithModel = async (modelOverride) => {
|
|
50162
|
+
const customToolList = [
|
|
50163
|
+
...wrappedTools,
|
|
50164
|
+
...options.customTools ?? []
|
|
50165
|
+
];
|
|
49692
50166
|
return createAgentSession({
|
|
49693
50167
|
cwd: options.cwd,
|
|
49694
50168
|
authStorage,
|
|
49695
50169
|
modelRegistry,
|
|
49696
50170
|
resourceLoader,
|
|
49697
|
-
|
|
49698
|
-
customTools:
|
|
50171
|
+
noTools: "builtin",
|
|
50172
|
+
customTools: customToolList,
|
|
49699
50173
|
sessionManager,
|
|
49700
50174
|
settingsManager,
|
|
49701
50175
|
...modelOverride ? { model: modelOverride } : {}
|
|
@@ -49719,7 +50193,7 @@ async function createFnAgent2(options) {
|
|
|
49719
50193
|
const { session } = sessionResult;
|
|
49720
50194
|
installToolResultContentGuard(session);
|
|
49721
50195
|
installMessageContentGuard(session, sessionManager);
|
|
49722
|
-
session.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name ===
|
|
50196
|
+
session.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
|
49723
50197
|
const promptableSession = session;
|
|
49724
50198
|
promptableSession.promptWithFallback = async (prompt, promptOptions) => {
|
|
49725
50199
|
try {
|
|
@@ -49769,8 +50243,11 @@ async function createFnAgent2(options) {
|
|
|
49769
50243
|
const fallbackSessionResult = await createSessionWithModel(fallbackModel);
|
|
49770
50244
|
const fallbackSession = fallbackSessionResult.session;
|
|
49771
50245
|
installToolResultContentGuard(fallbackSession);
|
|
49772
|
-
installMessageContentGuard(
|
|
49773
|
-
|
|
50246
|
+
installMessageContentGuard(
|
|
50247
|
+
fallbackSession,
|
|
50248
|
+
sessionManager
|
|
50249
|
+
);
|
|
50250
|
+
fallbackSession.__fusionMemoryAppendAvailable = options.customTools?.some((tool) => tool.name === FN_MEMORY_APPEND_TOOL_NAME) === true;
|
|
49774
50251
|
if (options.defaultThinkingLevel) {
|
|
49775
50252
|
fallbackSession.setThinkingLevel(options.defaultThinkingLevel);
|
|
49776
50253
|
}
|
|
@@ -49852,9 +50329,9 @@ async function createFnAgent2(options) {
|
|
|
49852
50329
|
});
|
|
49853
50330
|
return { session: promptableSession, sessionFile: promptableSession.sessionFile };
|
|
49854
50331
|
}
|
|
49855
|
-
var execAsync, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
50332
|
+
var execAsync, FN_MEMORY_APPEND_TOOL_NAME, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
49856
50333
|
var init_pi = __esm({
|
|
49857
|
-
"../engine/src/pi.
|
|
50334
|
+
"../engine/src/pi.ts"() {
|
|
49858
50335
|
"use strict";
|
|
49859
50336
|
init_src();
|
|
49860
50337
|
init_skill_resolver();
|
|
@@ -49862,6 +50339,7 @@ var init_pi = __esm({
|
|
|
49862
50339
|
init_auth_storage();
|
|
49863
50340
|
init_logger2();
|
|
49864
50341
|
execAsync = promisify2(exec);
|
|
50342
|
+
FN_MEMORY_APPEND_TOOL_NAME = "fn_memory_append";
|
|
49865
50343
|
COMPACTION_FALLBACK_INSTRUCTIONS = [
|
|
49866
50344
|
"Summarize all completed steps concisely.",
|
|
49867
50345
|
"Preserve the current step number and any in-progress work details.",
|
|
@@ -50802,12 +51280,12 @@ When reviewing specs, actively assess whether the task should have been broken i
|
|
|
50802
51280
|
Say explicitly: "This task should be broken into subtasks because [specific reason]."
|
|
50803
51281
|
Recommend the number of child tasks (2-5) and what each should cover.
|
|
50804
51282
|
**Critically**, instruct the planner to take these actions in your REVISE feedback:
|
|
50805
|
-
1. Use the \`
|
|
51283
|
+
1. Use the \`fn_task_create\` tool to create 2\u20135 child tasks from the oversized spec
|
|
50806
51284
|
2. Do NOT write a parent PROMPT.md \u2014 the parent will be closed automatically after children are created
|
|
50807
51285
|
3. Each child task should cover one coherent deliverable with clear scope boundaries
|
|
50808
51286
|
|
|
50809
51287
|
Example REVISE feedback for an undersplit task:
|
|
50810
|
-
"This task should be broken into 3 subtasks because it spans the engine, dashboard, and CLI packages with independent deliverables. Use
|
|
51288
|
+
"This task should be broken into 3 subtasks because it spans the engine, dashboard, and CLI packages with independent deliverables. Use fn_task_create to create: (1) engine logic, (2) dashboard UI, (3) CLI integration. Do not write a parent PROMPT."
|
|
50811
51289
|
|
|
50812
51290
|
**Do NOT flag if:**
|
|
50813
51291
|
- Steps are sequential and tightly coupled (e.g., a pipeline where each step depends on the previous)
|
|
@@ -51180,12 +51658,12 @@ The user has requested that this task be broken into smaller subtasks if it is c
|
|
|
51180
51658
|
|
|
51181
51659
|
**How to split:**
|
|
51182
51660
|
1. First, analyze the task to determine if it should be split
|
|
51183
|
-
2. If splitting: use the \\\`
|
|
51661
|
+
2. If splitting: use the \\\`fn_task_create\\\` tool to create child tasks in order, setting up dependencies as needed
|
|
51184
51662
|
3. Include clear descriptions and acceptance criteria for each child task
|
|
51185
51663
|
4. After creating all subtasks, stop \u2014 do NOT write a PROMPT.md for the parent task
|
|
51186
51664
|
5. If NOT splitting: proceed with a normal PROMPT.md specification for this task
|
|
51187
51665
|
|
|
51188
|
-
**Subtask dependencies rule:** \`dependencies\` on a child may only reference **sibling subtasks created earlier in this same split** or **pre-existing tasks in the store**. They must NEVER reference the parent task being split \u2014 the parent is deleted after the split completes, and a dependency on a deleted task permanently blocks the dependent. If a child "needs the rest of the parent's work to finish first", create another sibling subtask for that remaining work and depend on the sibling. The \`
|
|
51666
|
+
**Subtask dependencies rule:** \`dependencies\` on a child may only reference **sibling subtasks created earlier in this same split** or **pre-existing tasks in the store**. They must NEVER reference the parent task being split \u2014 the parent is deleted after the split completes, and a dependency on a deleted task permanently blocks the dependent. If a child "needs the rest of the parent's work to finish first", create another sibling subtask for that remaining work and depend on the sibling. The \`fn_task_create\` tool rejects parent-id dependencies.
|
|
51189
51667
|
|
|
51190
51668
|
**Important:** If you create subtasks, this parent task will be closed and replaced by the children. Make sure each child is a complete, executable task.`;
|
|
51191
51669
|
} else {
|
|
@@ -51211,7 +51689,7 @@ The user did not explicitly request subtask breakdown, so you should first asses
|
|
|
51211
51689
|
- Adding a small feature to one module with 5 steps
|
|
51212
51690
|
|
|
51213
51691
|
**How to decide:**
|
|
51214
|
-
- If you choose to split: use the \\\`
|
|
51692
|
+
- If you choose to split: use the \\\`fn_task_create\\\` tool to create the child tasks, set dependencies where needed, and then stop without writing a PROMPT.md for the parent task.
|
|
51215
51693
|
- **Subtask dependencies must only reference sibling subtasks created earlier in this same split, or pre-existing tasks. NEVER depend on the parent task being split \u2014 the parent is deleted after splitting, and the tool will reject parent-id dependencies.**
|
|
51216
51694
|
- If the work appears to be Size S, or if an M/L task genuinely has 5 or fewer focused steps with a clear scope, proceed with a normal PROMPT.md specification.
|
|
51217
51695
|
- If size is uncertain at first, make a quick assessment from the available context before deciding.`;
|
|
@@ -51325,8 +51803,8 @@ Follow this structure exactly:
|
|
|
51325
51803
|
### Step {N}: Documentation & Delivery
|
|
51326
51804
|
|
|
51327
51805
|
- [ ] Update relevant documentation
|
|
51328
|
-
- [ ] Save documentation deliverables as task documents via \`
|
|
51329
|
-
- [ ] Out-of-scope findings created as new tasks via \`
|
|
51806
|
+
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
|
51807
|
+
- [ ] Out-of-scope findings created as new tasks via \`fn_task_create\` tool
|
|
51330
51808
|
|
|
51331
51809
|
## Documentation Requirements
|
|
51332
51810
|
|
|
@@ -51359,7 +51837,7 @@ Commits at step boundaries. All commits include the task ID:
|
|
|
51359
51837
|
- Refuse necessary fixes just because they touch files outside the initial File Scope
|
|
51360
51838
|
- Commit without the task ID prefix
|
|
51361
51839
|
- Remove, delete, or gut modules, settings, interfaces, exports, or test files outside the File Scope
|
|
51362
|
-
- Remove features as "cleanup" \u2014 if something seems unused, create a task via \`
|
|
51840
|
+
- Remove features as "cleanup" \u2014 if something seems unused, create a task via \`fn_task_create\`
|
|
51363
51841
|
|
|
51364
51842
|
## Changeset Requirements
|
|
51365
51843
|
|
|
@@ -51381,13 +51859,13 @@ tests. Manual verification is NOT a test.
|
|
|
51381
51859
|
as part of this task (not just skipping tests)
|
|
51382
51860
|
|
|
51383
51861
|
## Duplicate check
|
|
51384
|
-
Before writing a spec, call \`
|
|
51862
|
+
Before writing a spec, call \`fn_task_list\` to see existing tasks.
|
|
51385
51863
|
If a task already covers the same work (even if worded differently), do NOT
|
|
51386
51864
|
write a PROMPT.md. Instead, write a single line to the output file:
|
|
51387
51865
|
\`DUPLICATE: {existing-task-id}\`
|
|
51388
51866
|
|
|
51389
51867
|
## Dependency awareness
|
|
51390
|
-
When you plan to list a task in the \`## Dependencies\` section, first call \`
|
|
51868
|
+
When you plan to list a task in the \`## Dependencies\` section, first call \`fn_task_get\` on that task ID to read its PROMPT.md.
|
|
51391
51869
|
Use what you learn \u2014 file scope, APIs, patterns, completion criteria \u2014 to make the new spec accurate: reference the right paths, avoid conflicting assumptions, and describe what the dependency must deliver before this task starts.
|
|
51392
51870
|
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
|
|
51393
51871
|
|
|
@@ -51395,8 +51873,8 @@ If the dependency task has no PROMPT.md yet (not yet specified), note that in th
|
|
|
51395
51873
|
When the task includes \`breakIntoSubtasks: true\`, first decide whether it should be split.
|
|
51396
51874
|
|
|
51397
51875
|
- Split only when the work is meaningfully decomposable into 2-5 independently executable child tasks.
|
|
51398
|
-
- If splitting: use the \`
|
|
51399
|
-
- **CRITICAL \u2014 subtask dependencies:** the parent task is deleted once all subtasks are created. \`dependencies\` on a new subtask may ONLY reference sibling subtasks you have created earlier in this same split (or unrelated existing tasks). **Never depend on the parent task's id.** If a child conceptually "waits for the parent's remaining work", create a sibling subtask that does that work and depend on the sibling instead. The \`
|
|
51876
|
+
- If splitting: use the \`fn_task_create\` tool to create child tasks in triage, include clear descriptions and dependencies between them, then stop. Do NOT write a PROMPT.md for the parent task.
|
|
51877
|
+
- **CRITICAL \u2014 subtask dependencies:** the parent task is deleted once all subtasks are created. \`dependencies\` on a new subtask may ONLY reference sibling subtasks you have created earlier in this same split (or unrelated existing tasks). **Never depend on the parent task's id.** If a child conceptually "waits for the parent's remaining work", create a sibling subtask that does that work and depend on the sibling instead. The \`fn_task_create\` tool will reject parent-id dependencies with an error.
|
|
51400
51878
|
- If not splitting: proceed with a normal PROMPT.md specification.
|
|
51401
51879
|
|
|
51402
51880
|
## Proactive Subtask Breakdown for M/L Tasks
|
|
@@ -51419,20 +51897,20 @@ For tasks you assess as Size M or L, proactively evaluate whether splitting into
|
|
|
51419
51897
|
|
|
51420
51898
|
## Triage tools
|
|
51421
51899
|
You have these extra tools during triage:
|
|
51422
|
-
- \`
|
|
51423
|
-
- \`
|
|
51424
|
-
- \`
|
|
51425
|
-
- \`
|
|
51426
|
-
- \`
|
|
51900
|
+
- \`fn_task_list\` \u2014 list existing active tasks
|
|
51901
|
+
- \`fn_task_get\` \u2014 inspect a task and its PROMPT.md
|
|
51902
|
+
- \`fn_task_create\` \u2014 create a child/follow-up task while triaging
|
|
51903
|
+
- \`fn_task_document_write\` \u2014 save a planning document (e.g., key="plan")
|
|
51904
|
+
- \`fn_task_document_read\` \u2014 read back a previously saved document
|
|
51427
51905
|
|
|
51428
|
-
When the planning conversation produces a structured plan, save it as a document with \`
|
|
51906
|
+
When the planning conversation produces a structured plan, save it as a document with \`fn_task_document_write(key='plan', content='...')\` so the executor can reference it during implementation.
|
|
51429
51907
|
|
|
51430
51908
|
## Guidelines
|
|
51431
51909
|
- Read the project structure and relevant source files to understand context BEFORE writing
|
|
51432
51910
|
- Be specific \u2014 name actual files, functions, and patterns from the codebase
|
|
51433
51911
|
- Steps should express OUTCOMES, not micro-instructions (2-5 checkboxes per step)
|
|
51434
51912
|
- Always include a testing step and a documentation step
|
|
51435
|
-
- For tasks whose primary deliverable is documentation (updating docs, writing README, API references), include an explicit step or checkbox instructing the executor to save the final documentation content via \`
|
|
51913
|
+
- For tasks whose primary deliverable is documentation (updating docs, writing README, API references), include an explicit step or checkbox instructing the executor to save the final documentation content via \`fn_task_document_write\`
|
|
51436
51914
|
- Include a "Do NOT" section with project-appropriate guardrails
|
|
51437
51915
|
- Size assessment: S (<2h), M (2-4h), L (4-8h). Split if XL (8h+)
|
|
51438
51916
|
- Review level scoring: Blast radius (0-2), Pattern novelty (0-2), Security (0-2), Reversibility (0-2)
|
|
@@ -51446,16 +51924,16 @@ package.json when explicit commands are provided.
|
|
|
51446
51924
|
|
|
51447
51925
|
## Spec Review
|
|
51448
51926
|
|
|
51449
|
-
After writing the PROMPT.md, call \`
|
|
51927
|
+
After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent quality review.
|
|
51450
51928
|
|
|
51451
51929
|
- **APPROVE** \u2192 your spec is accepted, you're done
|
|
51452
|
-
- **REVISE** \u2192 fix the issues described in the review feedback, rewrite the PROMPT.md, and call \`
|
|
51930
|
+
- **REVISE** \u2192 fix the issues described in the review feedback, rewrite the PROMPT.md, and call \`fn_review_spec()\` again. Repeat until approved.
|
|
51453
51931
|
- **RETHINK** \u2192 your approach was fundamentally rejected. The conversation will rewind. Read the feedback carefully and take a completely different approach. Do NOT repeat the rejected strategy.
|
|
51454
51932
|
|
|
51455
|
-
You MUST call \`
|
|
51933
|
+
You MUST call \`fn_review_spec()\` after writing the PROMPT.md. Do not finish without getting an APPROVE verdict.
|
|
51456
51934
|
|
|
51457
51935
|
## Output
|
|
51458
|
-
Write the PROMPT.md directly using the write tool, then call \`
|
|
51936
|
+
Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\` for review.
|
|
51459
51937
|
|
|
51460
51938
|
## Frontend UX Criteria Injection
|
|
51461
51939
|
|
|
@@ -51702,9 +52180,10 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51702
52180
|
this.wasEnginePaused = false;
|
|
51703
52181
|
const allTasks = await this.store.listTasks({ slim: true, includeArchived: false });
|
|
51704
52182
|
const now = Date.now();
|
|
51705
|
-
const
|
|
52183
|
+
const eligibleTriageTasks = allTasks.filter(
|
|
51706
52184
|
(t) => t.column === "triage" && !this.processing.has(t.id) && !t.paused && t.status !== "awaiting-approval" && t.status !== "failed" && t.status !== "stuck-killed" && !(t.nextRecoveryAt && new Date(t.nextRecoveryAt).getTime() > now)
|
|
51707
52185
|
);
|
|
52186
|
+
const triageTasks = sortTasksByPriorityThenAgeAndId(eligibleTriageTasks);
|
|
51708
52187
|
const maxTriageConcurrent = settings.maxTriageConcurrent ?? settings.maxConcurrent ?? 2;
|
|
51709
52188
|
const specifying = allTasks.filter(
|
|
51710
52189
|
(t) => t.column === "triage" && t.status === "specifying" && !t.paused
|
|
@@ -51730,11 +52209,11 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51730
52209
|
/**
|
|
51731
52210
|
* Specify a triage task by spawning an AI agent to generate a PROMPT.md.
|
|
51732
52211
|
*
|
|
51733
|
-
* After the agent writes the PROMPT.md, it calls `
|
|
52212
|
+
* After the agent writes the PROMPT.md, it calls `fn_review_spec()` to spawn
|
|
51734
52213
|
* an independent reviewer agent that evaluates the specification quality.
|
|
51735
52214
|
* The review loop works as follows:
|
|
51736
52215
|
* - **APPROVE**: the spec is accepted and the task moves to `todo`
|
|
51737
|
-
* - **REVISE**: the agent revises the spec and calls `
|
|
52216
|
+
* - **REVISE**: the agent revises the spec and calls `fn_review_spec()` again.
|
|
51738
52217
|
* If the agent finishes without getting APPROVE, the task is NOT moved to
|
|
51739
52218
|
* `todo` — a post-session gate requires an explicit APPROVE verdict.
|
|
51740
52219
|
* - **RETHINK**: the conversation rewinds to a pre-specification checkpoint
|
|
@@ -51938,7 +52417,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51938
52417
|
const planningFallbackModelId = settings.planningFallbackModelId;
|
|
51939
52418
|
const canRetryWithPlanningFallback = specReviewVerdictRef.current !== "APPROVE" && planningFallbackProvider && planningFallbackModelId && modelDesc !== `${planningFallbackProvider}/${planningFallbackModelId}`;
|
|
51940
52419
|
if (canRetryWithPlanningFallback) {
|
|
51941
|
-
const verdictDesc = specReviewVerdictRef.current === null ? "
|
|
52420
|
+
const verdictDesc = specReviewVerdictRef.current === null ? "fn_review_spec was never called" : `verdict was ${specReviewVerdictRef.current}`;
|
|
51942
52421
|
const fallbackDesc = `${planningFallbackProvider}/${planningFallbackModelId}`;
|
|
51943
52422
|
triageLog.warn(
|
|
51944
52423
|
`${task.id} primary planning model produced no approved spec (${verdictDesc}) \u2014 retrying with fallback ${fallbackDesc}`
|
|
@@ -52001,7 +52480,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52001
52480
|
}
|
|
52002
52481
|
}
|
|
52003
52482
|
if (specReviewVerdictRef.current !== "APPROVE") {
|
|
52004
|
-
const verdictDesc = specReviewVerdictRef.current === null ? "
|
|
52483
|
+
const verdictDesc = specReviewVerdictRef.current === null ? "fn_review_spec was never called" : `verdict was ${specReviewVerdictRef.current}`;
|
|
52005
52484
|
const decision = computeRecoveryDecision({
|
|
52006
52485
|
recoveryRetryCount: task.recoveryRetryCount,
|
|
52007
52486
|
nextRecoveryAt: task.nextRecoveryAt
|
|
@@ -52086,7 +52565,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52086
52565
|
await retryableWork();
|
|
52087
52566
|
}
|
|
52088
52567
|
} catch (err) {
|
|
52089
|
-
const errorMessage
|
|
52568
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
52090
52569
|
if (err.code === "ENOENT") {
|
|
52091
52570
|
triageLog.log(`${task.id} no longer exists \u2014 skipping`);
|
|
52092
52571
|
} else if (this.pauseAborted.has(task.id)) {
|
|
@@ -52159,7 +52638,13 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52159
52638
|
const msg = restoreErr instanceof Error ? restoreErr.message : String(restoreErr);
|
|
52160
52639
|
triageLog.warn(`${task.id}: failed to restore status to '${restoreStatus}' after specification error: ${msg}`);
|
|
52161
52640
|
});
|
|
52162
|
-
triageLog.error(`\u2717 ${task.id} specification failed:`,
|
|
52641
|
+
triageLog.error(`\u2717 ${task.id} specification failed:`, errorDetail);
|
|
52642
|
+
if (errorStack) {
|
|
52643
|
+
await this.store.logEntry(task.id, `Specification failed: ${errorMessage}`, errorStack).catch((logErr) => {
|
|
52644
|
+
const msg = logErr instanceof Error ? logErr.message : String(logErr);
|
|
52645
|
+
triageLog.warn(`${task.id}: failed to persist specification-failure stack trace: ${msg}`);
|
|
52646
|
+
});
|
|
52647
|
+
}
|
|
52163
52648
|
this.options.onSpecifyError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
52164
52649
|
}
|
|
52165
52650
|
} finally {
|
|
@@ -52180,7 +52665,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52180
52665
|
)
|
|
52181
52666
|
});
|
|
52182
52667
|
const taskList = {
|
|
52183
|
-
name: "
|
|
52668
|
+
name: "fn_task_list",
|
|
52184
52669
|
label: "List Tasks",
|
|
52185
52670
|
description: "List all tasks that aren't done. Returns ID, description, column, and dependencies for each. Use to check for duplicates before specifying.",
|
|
52186
52671
|
parameters: Type2.Object({}),
|
|
@@ -52205,7 +52690,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52205
52690
|
}
|
|
52206
52691
|
};
|
|
52207
52692
|
const taskGet = {
|
|
52208
|
-
name: "
|
|
52693
|
+
name: "fn_task_get",
|
|
52209
52694
|
label: "Get Task",
|
|
52210
52695
|
description: "Get full details of a specific task including its PROMPT.md content. Use to verify duplicates and to read dependency task specs before writing a new PROMPT.md.",
|
|
52211
52696
|
parameters: taskGetParams,
|
|
@@ -52227,7 +52712,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52227
52712
|
};
|
|
52228
52713
|
} catch (err) {
|
|
52229
52714
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52230
|
-
triageLog.warn(`${options.parentTaskId}:
|
|
52715
|
+
triageLog.warn(`${options.parentTaskId}: fn_task_get lookup failed for ${params.id}: ${msg}`);
|
|
52231
52716
|
return {
|
|
52232
52717
|
content: [
|
|
52233
52718
|
{ type: "text", text: `Task ${params.id} not found.` }
|
|
@@ -52238,7 +52723,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52238
52723
|
}
|
|
52239
52724
|
};
|
|
52240
52725
|
const taskCreate = {
|
|
52241
|
-
name: "
|
|
52726
|
+
name: "fn_task_create",
|
|
52242
52727
|
label: "Create Child Task",
|
|
52243
52728
|
description: "Create a child task (subtask) while breaking a larger task into smaller pieces. Use this when the work can be split into 2-5 independently executable tasks, either because the user requested subtask breakdown or because the task is oversized (8+ steps, 3+ packages, multiple independent deliverables). The created task will be a child of the current task being triaged. IMPORTANT: `dependencies` may ONLY reference other subtasks you have created in this same triage session. Never depend on the parent task \u2014 the parent is deleted after splitting, and stale dependency ids permanently block the dependent.",
|
|
52244
52729
|
parameters: taskCreateParams3,
|
|
@@ -52276,10 +52761,10 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52276
52761
|
content: [
|
|
52277
52762
|
{
|
|
52278
52763
|
type: "text",
|
|
52279
|
-
text: `ERROR:
|
|
52764
|
+
text: `ERROR: fn_task_create rejected. Invalid dependencies:
|
|
52280
52765
|
${summary}
|
|
52281
52766
|
|
|
52282
|
-
Remove or replace these ids and call
|
|
52767
|
+
Remove or replace these ids and call fn_task_create again.`
|
|
52283
52768
|
}
|
|
52284
52769
|
],
|
|
52285
52770
|
details: { rejectedDependencies: rejected }
|
|
@@ -52290,7 +52775,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52290
52775
|
parentTask = await store.getTask(options.parentTaskId);
|
|
52291
52776
|
} catch (err) {
|
|
52292
52777
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52293
|
-
triageLog.warn(`${options.parentTaskId}: failed to load parent task for
|
|
52778
|
+
triageLog.warn(`${options.parentTaskId}: failed to load parent task for fn_task_create inheritance: ${msg}`);
|
|
52294
52779
|
parentTask = void 0;
|
|
52295
52780
|
}
|
|
52296
52781
|
const newTask = await store.createTask({
|
|
@@ -52331,13 +52816,13 @@ Remove or replace these ids and call task_create again.`
|
|
|
52331
52816
|
return [taskList, taskGet, taskCreate];
|
|
52332
52817
|
}
|
|
52333
52818
|
/**
|
|
52334
|
-
* Create the `
|
|
52819
|
+
* Create the `fn_review_spec` tool for the triage agent.
|
|
52335
52820
|
*
|
|
52336
52821
|
* Spawns an independent reviewer agent to evaluate the generated PROMPT.md.
|
|
52337
52822
|
* Verdict handling:
|
|
52338
52823
|
* - **APPROVE**: returns "APPROVE" — the triage agent's work is done.
|
|
52339
52824
|
* - **REVISE**: returns the review feedback. The triage agent must fix the
|
|
52340
|
-
* PROMPT.md and call `
|
|
52825
|
+
* PROMPT.md and call `fn_review_spec` again. A post-session gate in
|
|
52341
52826
|
* `specifyTask()` prevents moving to `todo` if the last verdict is REVISE.
|
|
52342
52827
|
* - **RETHINK**: rewinds the conversation to a pre-specification checkpoint
|
|
52343
52828
|
* using `session.navigateTree()`. Returns a re-prompt instructing the agent
|
|
@@ -52348,7 +52833,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52348
52833
|
const rootDir = this.rootDir;
|
|
52349
52834
|
const options = this.options;
|
|
52350
52835
|
return {
|
|
52351
|
-
name: "
|
|
52836
|
+
name: "fn_review_spec",
|
|
52352
52837
|
label: "Review Specification",
|
|
52353
52838
|
description: "Spawn a reviewer agent to evaluate the generated PROMPT.md specification. Returns APPROVE, REVISE, RETHINK, or UNAVAILABLE. Call after writing the PROMPT.md.",
|
|
52354
52839
|
parameters: Type2.Object({}),
|
|
@@ -52366,7 +52851,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52366
52851
|
"utf-8"
|
|
52367
52852
|
).catch((err) => {
|
|
52368
52853
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52369
|
-
triageLog.warn(`${taskId}: failed to read PROMPT.md for
|
|
52854
|
+
triageLog.warn(`${taskId}: failed to read PROMPT.md for fn_review_spec (${promptPath}): ${msg}`);
|
|
52370
52855
|
return "";
|
|
52371
52856
|
});
|
|
52372
52857
|
if (!promptContent) {
|
|
@@ -52374,7 +52859,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52374
52859
|
content: [
|
|
52375
52860
|
{
|
|
52376
52861
|
type: "text",
|
|
52377
|
-
text: "UNAVAILABLE \u2014 PROMPT.md file not found or empty. Write the specification first, then call
|
|
52862
|
+
text: "UNAVAILABLE \u2014 PROMPT.md file not found or empty. Write the specification first, then call fn_review_spec."
|
|
52378
52863
|
}
|
|
52379
52864
|
],
|
|
52380
52865
|
details: {}
|
|
@@ -52430,7 +52915,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52430
52915
|
text = "APPROVE";
|
|
52431
52916
|
break;
|
|
52432
52917
|
case "REVISE":
|
|
52433
|
-
text = `REVISE \u2014 fix the issues below, rewrite the PROMPT.md, and call
|
|
52918
|
+
text = `REVISE \u2014 fix the issues below, rewrite the PROMPT.md, and call fn_review_spec() again.
|
|
52434
52919
|
|
|
52435
52920
|
${result.review}`;
|
|
52436
52921
|
break;
|
|
@@ -52854,6 +53339,11 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
52854
53339
|
};
|
|
52855
53340
|
}
|
|
52856
53341
|
if (existsSync21(join26(rootDir, "pnpm-lock.yaml"))) {
|
|
53342
|
+
if (existsSync21(join26(rootDir, "pnpm-workspace.yaml"))) {
|
|
53343
|
+
mergerLog.warn(
|
|
53344
|
+
`Inferred test command "pnpm test" in a pnpm workspace (${rootDir}). This runs the full monorepo suite on every merge. Consider setting an explicit scoped testCommand in project settings, e.g. \`pnpm -r --filter "...[main]" test\`.`
|
|
53345
|
+
);
|
|
53346
|
+
}
|
|
52857
53347
|
return {
|
|
52858
53348
|
command: "pnpm test",
|
|
52859
53349
|
testSource: "inferred",
|
|
@@ -52962,6 +53452,7 @@ async function runVerificationCommand(store, rootDir, taskId, command, type) {
|
|
|
52962
53452
|
stderr: "",
|
|
52963
53453
|
success: false
|
|
52964
53454
|
};
|
|
53455
|
+
const verificationStartedAt = Date.now();
|
|
52965
53456
|
try {
|
|
52966
53457
|
const { stdout, stderr } = await execAsync2(command, {
|
|
52967
53458
|
cwd: rootDir,
|
|
@@ -52973,37 +53464,46 @@ async function runVerificationCommand(store, rootDir, taskId, command, type) {
|
|
|
52973
53464
|
result.stderr = stderr?.toString?.() || "";
|
|
52974
53465
|
result.exitCode = 0;
|
|
52975
53466
|
result.success = true;
|
|
52976
|
-
|
|
52977
|
-
|
|
53467
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
53468
|
+
mergerLog.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
53469
|
+
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
52978
53470
|
return result;
|
|
52979
53471
|
} catch (error) {
|
|
53472
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
52980
53473
|
result.stdout = error?.stdout?.toString?.() || "";
|
|
52981
53474
|
result.stderr = error?.stderr?.toString?.() || "";
|
|
52982
53475
|
result.exitCode = typeof error?.status === "number" ? error.status : typeof error?.code === "number" ? error.code : null;
|
|
52983
53476
|
const maxBufferExceeded = error?.code === "ENOBUFS" || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(error?.message ?? "").includes("maxBuffer");
|
|
52984
53477
|
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
52985
53478
|
if (result.success) {
|
|
52986
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer)`);
|
|
53479
|
+
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
52987
53480
|
await store.logEntry(
|
|
52988
53481
|
taskId,
|
|
52989
|
-
`[verification] ${type} command succeeded (exit 0, output exceeded buffer)`
|
|
53482
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
52990
53483
|
);
|
|
52991
53484
|
return result;
|
|
52992
53485
|
}
|
|
52993
53486
|
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
|
52994
53487
|
const summary = summarizeVerificationOutput(output, type);
|
|
52995
|
-
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}); output captured in task log`);
|
|
53488
|
+
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
52996
53489
|
await store.logEntry(
|
|
52997
53490
|
taskId,
|
|
52998
|
-
`[verification] ${type} command failed (exit ${result.exitCode}):
|
|
53491
|
+
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
52999
53492
|
${summary}`
|
|
53000
53493
|
);
|
|
53001
53494
|
}
|
|
53002
53495
|
return result;
|
|
53003
53496
|
}
|
|
53004
|
-
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, _testCommand, _buildCommand) {
|
|
53497
|
+
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, mergeRunContext, fixAttemptNumber, _testCommand, _buildCommand) {
|
|
53005
53498
|
try {
|
|
53006
53499
|
mergerLog.log(`${taskId}: spawning in-merge verification fix agent`);
|
|
53500
|
+
const logger2 = new AgentLogger({
|
|
53501
|
+
store,
|
|
53502
|
+
taskId,
|
|
53503
|
+
agent: "merger",
|
|
53504
|
+
onAgentText: options.onAgentText,
|
|
53505
|
+
onAgentTool: options.onAgentTool
|
|
53506
|
+
});
|
|
53007
53507
|
let skillContext = void 0;
|
|
53008
53508
|
if (options.agentStore) {
|
|
53009
53509
|
try {
|
|
@@ -53035,12 +53535,22 @@ A merge has been applied and the verification command failed. Your job is to fix
|
|
|
53035
53535
|
6. If you cannot fix the issue, explain why`,
|
|
53036
53536
|
tools: "coding",
|
|
53037
53537
|
// Agent needs read/write file access
|
|
53538
|
+
onText: logger2.onText,
|
|
53539
|
+
onThinking: logger2.onThinking,
|
|
53540
|
+
onToolStart: logger2.onToolStart,
|
|
53541
|
+
onToolEnd: logger2.onToolEnd,
|
|
53038
53542
|
defaultProvider: settings.defaultProvider,
|
|
53039
53543
|
defaultModelId: settings.defaultModelId,
|
|
53040
53544
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
53041
53545
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
53042
53546
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
53043
53547
|
});
|
|
53548
|
+
const runId = mergeRunContext?.runId;
|
|
53549
|
+
const agentId = mergeRunContext?.agentId ?? "merger";
|
|
53550
|
+
await store.logEntry(
|
|
53551
|
+
taskId,
|
|
53552
|
+
`In-merge verification fix agent started (model: ${describeModel(session)}, runId: ${runId ?? "unknown"}, agentId: ${agentId})`
|
|
53553
|
+
);
|
|
53044
53554
|
try {
|
|
53045
53555
|
const fixPrompt = `Fix the failing ${failureContext.type} verification for task ${taskId}.
|
|
53046
53556
|
|
|
@@ -53065,6 +53575,10 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
53065
53575
|
mergerLog.warn(`\u23F3 ${taskId} in-merge fix rate limited \u2014 retry ${attempt} in ${delaySec}s: ${error.message}`);
|
|
53066
53576
|
}
|
|
53067
53577
|
});
|
|
53578
|
+
await store.logEntry(
|
|
53579
|
+
taskId,
|
|
53580
|
+
`Re-running deterministic merge verification (attempt ${fixAttemptNumber ?? "unknown"})`
|
|
53581
|
+
);
|
|
53068
53582
|
const reRunResult = await runVerificationCommand(
|
|
53069
53583
|
store,
|
|
53070
53584
|
rootDir,
|
|
@@ -53074,6 +53588,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
53074
53588
|
);
|
|
53075
53589
|
return reRunResult.success;
|
|
53076
53590
|
} finally {
|
|
53591
|
+
await logger2.flush();
|
|
53077
53592
|
await session.dispose();
|
|
53078
53593
|
}
|
|
53079
53594
|
} catch (err) {
|
|
@@ -53365,7 +53880,7 @@ and the bash tool returned exit code 0.
|
|
|
53365
53880
|
1. Run the build command (shown in the prompt context below)
|
|
53366
53881
|
2. If the build succeeds (exit code 0), proceed with the commit
|
|
53367
53882
|
3. If the build fails (non-zero exit code), DO NOT commit. Instead:
|
|
53368
|
-
- Call the \`
|
|
53883
|
+
- Call the \`fn_report_build_failure\` tool with the real error details
|
|
53369
53884
|
- Stop immediately and do not run \`git commit\`
|
|
53370
53885
|
- Do not claim success in plain text
|
|
53371
53886
|
|
|
@@ -53404,7 +53919,7 @@ and the bash tool returned exit code 0.
|
|
|
53404
53919
|
1. Run the build command (shown in the prompt context below)
|
|
53405
53920
|
2. If the build succeeds (exit code 0), proceed with the commit
|
|
53406
53921
|
3. If the build fails (non-zero exit code), DO NOT commit. Instead:
|
|
53407
|
-
- Call the \`
|
|
53922
|
+
- Call the \`fn_report_build_failure\` tool with the real error details
|
|
53408
53923
|
- Stop immediately and do not run \`git commit\`
|
|
53409
53924
|
- Do not claim success in plain text
|
|
53410
53925
|
|
|
@@ -53947,6 +54462,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53947
54462
|
if (failedResult) {
|
|
53948
54463
|
let fixSuccess = false;
|
|
53949
54464
|
for (let fixAttempt = 1; fixAttempt <= maxFixRetries; fixAttempt++) {
|
|
54465
|
+
const fixAttemptStartedAt = Date.now();
|
|
53950
54466
|
mergerLog.log(`${taskId}: in-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53951
54467
|
await store.logEntry(taskId, `In-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53952
54468
|
fixSuccess = await attemptInMergeVerificationFix(
|
|
@@ -53961,16 +54477,19 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53961
54477
|
},
|
|
53962
54478
|
settings,
|
|
53963
54479
|
options,
|
|
54480
|
+
{ runId: mergeRunId, agentId: engineRunContext.agentId },
|
|
54481
|
+
fixAttempt,
|
|
53964
54482
|
effectiveTestCommand,
|
|
53965
54483
|
effectiveBuildCommand
|
|
53966
54484
|
);
|
|
54485
|
+
const fixAttemptDurationMs = Date.now() - fixAttemptStartedAt;
|
|
53967
54486
|
if (fixSuccess) {
|
|
53968
|
-
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt}`);
|
|
53969
|
-
await store.logEntry(taskId, `In-merge verification fix succeeded \u2014 verification now passes`);
|
|
54487
|
+
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54488
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms \u2014 verification now passes`);
|
|
53970
54489
|
break;
|
|
53971
54490
|
}
|
|
53972
|
-
mergerLog.warn(`${taskId}: in-merge verification fix attempt ${fixAttempt} \u2014 verification still fails`);
|
|
53973
|
-
await store.logEntry(taskId, `In-merge verification fix attempt ${fixAttempt} \u2014 verification still fails`);
|
|
54491
|
+
mergerLog.warn(`${taskId}: in-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
54492
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
53974
54493
|
}
|
|
53975
54494
|
if (fixSuccess) {
|
|
53976
54495
|
const authorArg = getCommitAuthorArg(settings);
|
|
@@ -53991,6 +54510,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53991
54510
|
const fixType = effectiveBuildCommand ? "build" : "test";
|
|
53992
54511
|
let fixSuccess = false;
|
|
53993
54512
|
for (let fixAttempt = 1; fixAttempt <= maxFixRetries; fixAttempt++) {
|
|
54513
|
+
const fixAttemptStartedAt = Date.now();
|
|
53994
54514
|
mergerLog.log(`${taskId}: in-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53995
54515
|
await store.logEntry(taskId, `In-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53996
54516
|
fixSuccess = await attemptInMergeVerificationFix(
|
|
@@ -54005,14 +54525,18 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54005
54525
|
},
|
|
54006
54526
|
settings,
|
|
54007
54527
|
options,
|
|
54528
|
+
{ runId: mergeRunId, agentId: engineRunContext.agentId },
|
|
54529
|
+
fixAttempt,
|
|
54008
54530
|
effectiveTestCommand,
|
|
54009
54531
|
effectiveBuildCommand
|
|
54010
54532
|
);
|
|
54533
|
+
const fixAttemptDurationMs = Date.now() - fixAttemptStartedAt;
|
|
54011
54534
|
if (fixSuccess) {
|
|
54012
|
-
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt}`);
|
|
54013
|
-
await store.logEntry(taskId, `In-merge verification fix succeeded`);
|
|
54535
|
+
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54536
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54014
54537
|
break;
|
|
54015
54538
|
}
|
|
54539
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
54016
54540
|
}
|
|
54017
54541
|
if (fixSuccess) {
|
|
54018
54542
|
const authorArg = getCommitAuthorArg(settings);
|
|
@@ -54091,8 +54615,15 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54091
54615
|
deletions = deletionsMatch ? Number.parseInt(deletionsMatch[1], 10) : 0;
|
|
54092
54616
|
} catch {
|
|
54093
54617
|
}
|
|
54618
|
+
const isEmptyCommit = filesChanged === 0;
|
|
54619
|
+
const recordedSha = isEmptyCommit ? void 0 : commitSha;
|
|
54620
|
+
if (isEmptyCommit) {
|
|
54621
|
+
mergerLog.warn(
|
|
54622
|
+
`${taskId}: local squash produced an empty commit (${commitSha?.slice(0, 8)}) \u2014 branch likely contained dupes of main. Skipping commitSha; recovery will backfill when real commit lands.`
|
|
54623
|
+
);
|
|
54624
|
+
}
|
|
54094
54625
|
const mergeDetails = {
|
|
54095
|
-
commitSha,
|
|
54626
|
+
commitSha: recordedSha,
|
|
54096
54627
|
filesChanged,
|
|
54097
54628
|
insertions,
|
|
54098
54629
|
deletions,
|
|
@@ -54105,7 +54636,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54105
54636
|
autoResolvedCount: result.autoResolvedCount
|
|
54106
54637
|
};
|
|
54107
54638
|
await store.updateTask(taskId, { mergeDetails });
|
|
54108
|
-
mergerLog.log(`${taskId}: merge details stored (commitSha: ${
|
|
54639
|
+
mergerLog.log(`${taskId}: merge details stored (commitSha: ${recordedSha?.slice(0, 8) ?? "<deferred>"})`);
|
|
54109
54640
|
} catch (err) {
|
|
54110
54641
|
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
|
|
54111
54642
|
}
|
|
@@ -54461,7 +54992,7 @@ async function runAiAgentForCommit(params) {
|
|
|
54461
54992
|
let buildFailed = false;
|
|
54462
54993
|
let buildErrorMessage = "";
|
|
54463
54994
|
const reportBuildFailureTool = {
|
|
54464
|
-
name: "
|
|
54995
|
+
name: "fn_report_build_failure",
|
|
54465
54996
|
label: "Report Build Failure",
|
|
54466
54997
|
description: "Report that the build verification failed. Use this when the build command returns a non-zero exit code. Provide the error details in the message parameter.",
|
|
54467
54998
|
parameters: Type3.Object({
|
|
@@ -54678,7 +55209,7 @@ function buildMergePrompt(params) {
|
|
|
54678
55209
|
"This command is mandatory before commit.",
|
|
54679
55210
|
"Run it with the bash tool in the current worktree and inspect the actual exit code.",
|
|
54680
55211
|
"Only proceed if it exits 0.",
|
|
54681
|
-
"If it exits non-zero, call `
|
|
55212
|
+
"If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
|
|
54682
55213
|
);
|
|
54683
55214
|
}
|
|
54684
55215
|
if (buildCommand) {
|
|
@@ -54690,7 +55221,7 @@ function buildMergePrompt(params) {
|
|
|
54690
55221
|
"This command is mandatory before commit.",
|
|
54691
55222
|
"Run it with the bash tool in the current worktree and inspect the actual exit code.",
|
|
54692
55223
|
"Only commit if it exits 0.",
|
|
54693
|
-
"If it exits non-zero, call `
|
|
55224
|
+
"If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
|
|
54694
55225
|
);
|
|
54695
55226
|
}
|
|
54696
55227
|
return parts.join("\n");
|
|
@@ -55719,11 +56250,11 @@ function buildStepPrompt(taskDetail, stepIndex, rootDir, settings, worktreePath)
|
|
|
55719
56250
|
if (isLastStep) {
|
|
55720
56251
|
parts.push(
|
|
55721
56252
|
"",
|
|
55722
|
-
`**Document your deliverables:** When this task produces written output (documentation, specifications, reports, API references, README updates, guides, or any other content), save that content as a task document using \`
|
|
56253
|
+
`**Document your deliverables:** When this task produces written output (documentation, specifications, reports, API references, README updates, guides, or any other content), save that content as a task document using \`fn_task_document_write(key='...', content='...')\`. Use a key that describes the deliverable (e.g., key="readme", key="api-docs"). The document persists in the task for review even after the worktree is cleaned up.`,
|
|
55723
56254
|
""
|
|
55724
56255
|
);
|
|
55725
56256
|
}
|
|
55726
|
-
parts.push("After completing this step, commit your changes and call
|
|
56257
|
+
parts.push("After completing this step, commit your changes and call fn_task_done(). Do NOT proceed to subsequent steps.");
|
|
55727
56258
|
return parts.join("\n");
|
|
55728
56259
|
}
|
|
55729
56260
|
function scopePromptToWorktree(prompt, rootDir, worktreePath) {
|
|
@@ -55778,7 +56309,7 @@ function buildReducedStepPrompt(taskDetail, stepIndex) {
|
|
|
55778
56309
|
"IMPORTANT: Your previous attempt hit the context window limit.",
|
|
55779
56310
|
"Do NOT repeat work that's already been done.",
|
|
55780
56311
|
"Check git status and git log to see what's been committed.",
|
|
55781
|
-
"Complete the remaining work and call
|
|
56312
|
+
"Complete the remaining work and call fn_task_done()."
|
|
55782
56313
|
];
|
|
55783
56314
|
return parts.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
55784
56315
|
}
|
|
@@ -56578,10 +57109,10 @@ ${attachmentsSection}${commandsSection}${memorySection}${progressSection}${steer
|
|
|
56578
57109
|
|
|
56579
57110
|
${reviewLevel === 0 ? "No reviews required. Implement directly." : ""}
|
|
56580
57111
|
${reviewLevel >= 1 ? `Before implementing each step (except Step 0 and the final step), call:
|
|
56581
|
-
\`
|
|
57112
|
+
\`fn_review_step(step=N, type="plan", step_name="...")\`` : ""}
|
|
56582
57113
|
${reviewLevel >= 2 ? `After implementing + committing each step, call:
|
|
56583
|
-
\`
|
|
56584
|
-
${reviewLevel >= 3 ? `After tests, also call
|
|
57114
|
+
\`fn_review_step(step=N, type="code", step_name="...", baseline="<SHA from before step>")\`` : ""}
|
|
57115
|
+
${reviewLevel >= 3 ? `After tests, also call fn_review_step with type="code" for test review.` : ""}
|
|
56585
57116
|
|
|
56586
57117
|
## Worktree Boundaries
|
|
56587
57118
|
|
|
@@ -56590,23 +57121,24 @@ You are running in an **isolated git worktree**. This means:
|
|
|
56590
57121
|
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree.
|
|
56591
57122
|
- **Exception \u2014 Project memory:** You MAY read and write to files under \`.fusion/memory/\` at the project root to save durable project learnings.
|
|
56592
57123
|
- **Exception \u2014 Task attachments:** You MAY read files under \`.fusion/tasks/{taskId}/attachments/\` at the project root for context.
|
|
57124
|
+
- **Exception \u2014 Sibling task specs:** You MAY read \`.fusion/tasks/{taskId}/PROMPT.md\` and \`.fusion/tasks/{taskId}/task.json\` at the project root (read-only) to consult dependency tasks' specifications.
|
|
56593
57125
|
- **Shell commands** run inside the worktree by default. Avoid using \`cd\` to navigate outside the worktree.
|
|
56594
57126
|
|
|
56595
57127
|
## Begin
|
|
56596
57128
|
|
|
56597
57129
|
${hasProgress ? `Resume from Step ${task.currentStep}. Do NOT redo completed steps.` : "Start with Step 0 (Preflight). Work through each step in order."}
|
|
56598
|
-
Use \`
|
|
56599
|
-
Use \`
|
|
56600
|
-
Use \`
|
|
57130
|
+
Use \`fn_task_update\` to report progress on every step transition.
|
|
57131
|
+
Use \`fn_task_log\` for important actions and decisions.
|
|
57132
|
+
Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
|
|
56601
57133
|
Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${authorArg}\`
|
|
56602
|
-
When all steps are complete: call \`
|
|
57134
|
+
When all steps are complete: call \`fn_task_done()\`
|
|
56603
57135
|
|
|
56604
|
-
If a build command is configured, run that exact command in this worktree before calling \`
|
|
57136
|
+
If a build command is configured, run that exact command in this worktree before calling \`fn_task_done()\`.
|
|
56605
57137
|
Treat a non-zero exit code as a blocking failure. Do not claim success without a real passing run.
|
|
56606
57138
|
Run the configured/full test suite and fix failures even when that requires edits outside the original File Scope.
|
|
56607
|
-
If the repo has a lint command (e.g. \`pnpm lint\`, \`npm run lint\`), run it before \`
|
|
56608
|
-
If the repo has a typecheck command, run it before \`
|
|
56609
|
-
Use \`
|
|
57139
|
+
If the repo has a lint command (e.g. \`pnpm lint\`, \`npm run lint\`), run it before \`fn_task_done()\` and fix any failures it reports.
|
|
57140
|
+
If the repo has a typecheck command, run it before \`fn_task_done()\` and fix any failures it reports.
|
|
57141
|
+
Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
|
|
56610
57142
|
If lint is configured and failing, fix that too before completion.
|
|
56611
57143
|
**CRITICAL: Resolve ALL test failures (and any lint/typecheck failures) before completing the task, even if they appear unrelated or pre-existing.** Unrelated failures left unfixed accumulate technical debt and block future integrations. Investigate and fix or suppress them \u2014 do not defer them to a separate task.`;
|
|
56612
57144
|
}
|
|
@@ -56721,22 +57253,22 @@ You are working in a git worktree isolated from the main branch. Your job is to
|
|
|
56721
57253
|
You have tools to report progress. The board updates in real-time.
|
|
56722
57254
|
|
|
56723
57255
|
**Step lifecycle:**
|
|
56724
|
-
- Before starting a step: \`
|
|
56725
|
-
- After completing a step: \`
|
|
56726
|
-
- If skipping a step: \`
|
|
57256
|
+
- Before starting a step: \`fn_task_update(step=N, status="in-progress")\`
|
|
57257
|
+
- After completing a step: \`fn_task_update(step=N, status="done")\`
|
|
57258
|
+
- If skipping a step: \`fn_task_update(step=N, status="skipped")\`
|
|
56727
57259
|
|
|
56728
|
-
**Logging important actions:** \`
|
|
57260
|
+
**Logging important actions:** \`fn_task_log(message="what happened")\`
|
|
56729
57261
|
|
|
56730
|
-
**Out-of-scope work found during execution:** \`
|
|
57262
|
+
**Out-of-scope work found during execution:** \`fn_task_create(description="what needs doing")\`
|
|
56731
57263
|
When creating multiple related tasks, declare dependencies between them:
|
|
56732
|
-
\`
|
|
56733
|
-
\`
|
|
57264
|
+
\`fn_task_create(description="load door sounds", dependencies=[])\` \u2192 returns KB-050
|
|
57265
|
+
\`fn_task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
|
|
56734
57266
|
|
|
56735
|
-
**Discovered a dependency:** \`
|
|
57267
|
+
**Discovered a dependency:** \`fn_task_add_dep(task_id="KB-XXX")\` \u2014 use when you discover mid-execution that another task must be completed first. This will return a warning first \u2014 you must call again with \`confirm=true\` to proceed. Adding a dependency stops execution, discards current work, and moves the task to triage for re-specification.
|
|
56736
57268
|
|
|
56737
|
-
## Cross-model review via
|
|
57269
|
+
## Cross-model review via fn_review_step tool
|
|
56738
57270
|
|
|
56739
|
-
You have a \`
|
|
57271
|
+
You have a \`fn_review_step\` tool. It spawns a SEPARATE reviewer agent (different
|
|
56740
57272
|
model, read-only access) to independently assess your work.
|
|
56741
57273
|
|
|
56742
57274
|
**When to call it** \u2014 based on the Review Level in the PROMPT.md:
|
|
@@ -56744,8 +57276,8 @@ model, read-only access) to independently assess your work.
|
|
|
56744
57276
|
| Review Level | Before implementing | After implementing + committing |
|
|
56745
57277
|
|-------------|--------------------|---------------------------------|
|
|
56746
57278
|
| 0 (None) | \u2014 | \u2014 |
|
|
56747
|
-
| 1 (Plan) | \`
|
|
56748
|
-
| 2 (Plan+Code) | \`
|
|
57279
|
+
| 1 (Plan) | \`fn_review_step(step, "plan", step_name)\` | \u2014 |
|
|
57280
|
+
| 2 (Plan+Code) | \`fn_review_step(step, "plan", step_name)\` | \`fn_review_step(step, "code", step_name, baseline)\` |
|
|
56749
57281
|
| 3 (Full) | plan review | code review + test review |
|
|
56750
57282
|
|
|
56751
57283
|
**Skip reviews for** Step 0 (Preflight) and the final documentation/delivery step.
|
|
@@ -56754,13 +57286,13 @@ model, read-only access) to independently assess your work.
|
|
|
56754
57286
|
1. Before starting a step, capture baseline: \`git rev-parse HEAD\`
|
|
56755
57287
|
2. Implement the step
|
|
56756
57288
|
3. Commit
|
|
56757
|
-
4. Call \`
|
|
57289
|
+
4. Call \`fn_review_step\` with the baseline SHA so the reviewer sees only your changes
|
|
56758
57290
|
|
|
56759
57291
|
**Handling verdicts:**
|
|
56760
57292
|
- **APPROVE** \u2192 proceed to next step
|
|
56761
57293
|
- **REVISE (code review)** \u2192 **enforced**. You MUST fix the issues, commit again,
|
|
56762
|
-
and re-run \`
|
|
56763
|
-
\`
|
|
57294
|
+
and re-run \`fn_review_step(type="code")\` before the step can be marked done.
|
|
57295
|
+
\`fn_task_update(status="done")\` will be rejected until the code review passes.
|
|
56764
57296
|
- **REVISE (plan review)** \u2192 advisory. Incorporate the feedback at your discretion
|
|
56765
57297
|
and proceed with implementation. No re-review is required.
|
|
56766
57298
|
- **RETHINK (code review)** \u2192 your code changes have been reverted and conversation rewound. Read the feedback carefully and take a fundamentally different approach. Do NOT repeat the rejected strategy.
|
|
@@ -56770,13 +57302,13 @@ model, read-only access) to independently assess your work.
|
|
|
56770
57302
|
|
|
56771
57303
|
You can save and retrieve named documents for this task. Use these to store planning notes, research findings, or any persistent data that should survive across sessions.
|
|
56772
57304
|
|
|
56773
|
-
- **Save a document:** \`
|
|
56774
|
-
- **Read a document:** \`
|
|
56775
|
-
- **List all documents:** \`
|
|
57305
|
+
- **Save a document:** \`fn_task_document_write(key="plan", content="...")\`
|
|
57306
|
+
- **Read a document:** \`fn_task_document_read(key="plan")\`
|
|
57307
|
+
- **List all documents:** \`fn_task_document_read()\` (no key)
|
|
56776
57308
|
|
|
56777
57309
|
Documents are versioned \u2014 each write creates a new revision. Use meaningful keys like "plan", "notes", "research", "architecture".
|
|
56778
57310
|
|
|
56779
|
-
**IMPORTANT \u2014 Save your deliverables as documents:** When your task produces written output (documentation, specifications, reports, API references, README updates, guides, or any other content), you MUST save that content as a task document using \`
|
|
57311
|
+
**IMPORTANT \u2014 Save your deliverables as documents:** When your task produces written output (documentation, specifications, reports, API references, README updates, guides, or any other content), you MUST save that content as a task document using \`fn_task_document_write\`. Use a key that describes the deliverable (e.g., key="readme", key="api-docs", key="changelog"). Do this in addition to writing the file to disk \u2014 the document persists in the task for review even after the worktree is cleaned up.
|
|
56780
57312
|
|
|
56781
57313
|
If the task's PROMPT.md includes a "Documentation Requirements" section listing files to update, save each updated file's final content as a task document with a matching key.
|
|
56782
57314
|
|
|
@@ -56792,6 +57324,7 @@ You are running in an **isolated git worktree**. This means:
|
|
|
56792
57324
|
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree \u2014 the worktree is your isolated execution environment.
|
|
56793
57325
|
- **Exception \u2014 Project memory:** You MAY read and write to files under .fusion/memory/ at the project root to save durable project learnings (architecture patterns, conventions, pitfalls).
|
|
56794
57326
|
- **Exception \u2014 Task attachments:** You MAY read files under .fusion/tasks/{taskId}/attachments/ at the project root for context screenshots and documents attached to this task.
|
|
57327
|
+
- **Exception \u2014 Sibling task specs:** You MAY read .fusion/tasks/{taskId}/PROMPT.md and .fusion/tasks/{taskId}/task.json at the project root (read-only) to consult dependency tasks' specifications.
|
|
56795
57328
|
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
|
56796
57329
|
|
|
56797
57330
|
If you attempt to write to a path outside the worktree, the file tools will reject the operation with an error explaining the boundary.
|
|
@@ -56802,7 +57335,7 @@ If you attempt to write to a path outside the worktree, the file tools will reje
|
|
|
56802
57335
|
- Read "Context to Read First" files before starting
|
|
56803
57336
|
- Follow the "Do NOT" section strictly
|
|
56804
57337
|
- If tests, lint, build, or typecheck fail and the fix requires touching code outside the declared File Scope, fix those failures directly and keep the repo green
|
|
56805
|
-
- Use \`
|
|
57338
|
+
- Use \`fn_task_create\` for genuinely separate follow-up work, not for mandatory fixes required to make this task land cleanly
|
|
56806
57339
|
- Update documentation listed in "Must Update" and check "Check If Affected"
|
|
56807
57340
|
- NEVER delete, remove, or gut modules, interfaces, settings, exports, or test files outside your File Scope
|
|
56808
57341
|
- NEVER remove features as "cleanup" \u2014 if something seems unused, create a task for investigation instead
|
|
@@ -56813,14 +57346,14 @@ If you attempt to write to a path outside the worktree, the file tools will reje
|
|
|
56813
57346
|
|
|
56814
57347
|
You can spawn child agents to handle parallel work or specialized sub-tasks:
|
|
56815
57348
|
|
|
56816
|
-
**When to use \`
|
|
57349
|
+
**When to use \`fn_spawn_agent\`:**
|
|
56817
57350
|
- Parallel work that can be divided into independent chunks
|
|
56818
57351
|
- Specialized tasks requiring different expertise or tools
|
|
56819
57352
|
- Delegation of sub-tasks to specialized agents
|
|
56820
57353
|
|
|
56821
57354
|
**How to spawn:**
|
|
56822
57355
|
\`\`\`javascript
|
|
56823
|
-
|
|
57356
|
+
fn_spawn_agent({
|
|
56824
57357
|
name: "researcher",
|
|
56825
57358
|
role: "engineer",
|
|
56826
57359
|
task: "Research best practices for authentication in React applications"
|
|
@@ -56830,7 +57363,7 @@ spawn_agent({
|
|
|
56830
57363
|
**Child agent behavior:**
|
|
56831
57364
|
- Each child runs in its own git worktree (branched from your worktree)
|
|
56832
57365
|
- Children execute autonomously and report completion
|
|
56833
|
-
- When you end (
|
|
57366
|
+
- When you end (fn_task_done), all spawned children are terminated
|
|
56834
57367
|
- Check AgentStore for spawned agent status
|
|
56835
57368
|
|
|
56836
57369
|
**Limits:**
|
|
@@ -56840,13 +57373,13 @@ spawn_agent({
|
|
|
56840
57373
|
## Completion
|
|
56841
57374
|
After all steps are done, lint passes, tests pass, typecheck passes, and docs are updated:
|
|
56842
57375
|
\`\`\`bash
|
|
56843
|
-
Call \`
|
|
57376
|
+
Call \`fn_task_done()\` to signal completion.
|
|
56844
57377
|
\`\`\`
|
|
56845
57378
|
|
|
56846
57379
|
If a project build command is listed in the prompt, it is a hard completion gate:
|
|
56847
|
-
- Run the exact build command in the current worktree before \`
|
|
57380
|
+
- Run the exact build command in the current worktree before \`fn_task_done()\`
|
|
56848
57381
|
- Do not claim the build passes unless you actually ran it and got exit code 0
|
|
56849
|
-
- If the build fails, do NOT call \`
|
|
57382
|
+
- If the build fails, do NOT call \`fn_task_done()\`; keep working until it passes
|
|
56850
57383
|
|
|
56851
57384
|
Lint, tests, and typecheck are also hard quality gates:
|
|
56852
57385
|
- Keep fixing failures until lint, the configured/full test suite, and typecheck all pass
|
|
@@ -57104,15 +57637,15 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57104
57637
|
}
|
|
57105
57638
|
/**
|
|
57106
57639
|
* Check whether a task's work is complete — all steps are done or skipped.
|
|
57107
|
-
* Used to detect tasks that called
|
|
57108
|
-
* (e.g., killed by stuck detector after
|
|
57640
|
+
* Used to detect tasks that called fn_task_done() but never transitioned to in-review
|
|
57641
|
+
* (e.g., killed by stuck detector after fn_task_done but before moveTask).
|
|
57109
57642
|
*/
|
|
57110
57643
|
isTaskWorkComplete(task) {
|
|
57111
57644
|
if (task.steps.length === 0) return false;
|
|
57112
57645
|
return task.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
57113
57646
|
}
|
|
57114
57647
|
isNoProgressNoTaskDoneFailure(task) {
|
|
57115
|
-
return task.status === "failed" && task.error?.includes("without calling
|
|
57648
|
+
return task.status === "failed" && task.error?.includes("without calling fn_task_done") === true && task.steps.every((step) => step.status === "pending");
|
|
57116
57649
|
}
|
|
57117
57650
|
async clearResumeFailureState(task) {
|
|
57118
57651
|
const updates = {};
|
|
@@ -57288,7 +57821,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57288
57821
|
continue;
|
|
57289
57822
|
}
|
|
57290
57823
|
if (this.isNoProgressNoTaskDoneFailure(task)) {
|
|
57291
|
-
executorLog.log(`${task.id} failed without
|
|
57824
|
+
executorLog.log(`${task.id} failed without fn_task_done and has no step progress \u2014 leaving for self-healing requeue`);
|
|
57292
57825
|
continue;
|
|
57293
57826
|
}
|
|
57294
57827
|
executorLog.log(`Resuming ${task.id}: ${task.title || task.description.slice(0, 60)}`);
|
|
@@ -57513,13 +58046,15 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57513
58046
|
await this.store.logEntry(task.id, `Worktree created at ${worktreePath}`, void 0, this.currentRunContext);
|
|
57514
58047
|
}
|
|
57515
58048
|
if (settings.worktreeInitCommand) {
|
|
58049
|
+
const initStartedAt = Date.now();
|
|
57516
58050
|
try {
|
|
57517
58051
|
const initResult = await runConfiguredCommand(settings.worktreeInitCommand, worktreePath, 3e5);
|
|
57518
58052
|
if (initResult.spawnError || initResult.timedOut || initResult.exitCode !== 0) {
|
|
57519
58053
|
throw new Error(configuredCommandErrorMessage(initResult));
|
|
57520
58054
|
}
|
|
57521
|
-
await this.store.logEntry(task.id,
|
|
58055
|
+
await this.store.logEntry(task.id, `[timing] Worktree init command completed in ${Date.now() - initStartedAt}ms`, settings.worktreeInitCommand, this.currentRunContext);
|
|
57522
58056
|
} catch (err) {
|
|
58057
|
+
await this.store.logEntry(task.id, `[timing] Worktree init command failed after ${Date.now() - initStartedAt}ms`, void 0, this.currentRunContext);
|
|
57523
58058
|
const execError = err instanceof Error ? err : new Error(String(err));
|
|
57524
58059
|
const message = "stderr" in execError && typeof execError.stderr === "string" ? String(execError.stderr) : execError.message;
|
|
57525
58060
|
executorLog.error(`${task.id}: worktree init command failed \u2014 first test run will likely fail: ${message}`);
|
|
@@ -57534,12 +58069,13 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57534
58069
|
if (settings.setupScript) {
|
|
57535
58070
|
const scriptCommand = settings.scripts?.[settings.setupScript];
|
|
57536
58071
|
if (scriptCommand) {
|
|
58072
|
+
const setupStartedAt = Date.now();
|
|
57537
58073
|
try {
|
|
57538
58074
|
const setupResult = await runConfiguredCommand(scriptCommand, worktreePath, 12e4);
|
|
57539
58075
|
if (setupResult.spawnError || setupResult.timedOut || setupResult.exitCode !== 0) {
|
|
57540
58076
|
throw new Error(configuredCommandErrorMessage(setupResult));
|
|
57541
58077
|
}
|
|
57542
|
-
await this.store.logEntry(task.id, `Setup script '${settings.setupScript}' completed`, scriptCommand, this.currentRunContext);
|
|
58078
|
+
await this.store.logEntry(task.id, `[timing] Setup script '${settings.setupScript}' completed in ${Date.now() - setupStartedAt}ms`, scriptCommand, this.currentRunContext);
|
|
57543
58079
|
} catch (err) {
|
|
57544
58080
|
const execError = err instanceof Error ? err : new Error(String(err));
|
|
57545
58081
|
const message = "stderr" in execError && typeof execError.stderr === "string" ? String(execError.stderr) : execError.message;
|
|
@@ -57705,7 +58241,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57705
58241
|
await retryableStepWork();
|
|
57706
58242
|
}
|
|
57707
58243
|
} catch (err) {
|
|
57708
|
-
const errorMessage
|
|
58244
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
57709
58245
|
if (this.depAborted.has(task.id)) {
|
|
57710
58246
|
this.depAborted.delete(task.id);
|
|
57711
58247
|
await this.handleDepAbortCleanup(task.id, worktreePath);
|
|
@@ -57749,7 +58285,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57749
58285
|
stuckRequeue = null;
|
|
57750
58286
|
return;
|
|
57751
58287
|
}
|
|
57752
|
-
executorLog.error(`\u2717 ${task.id} transient error retries exhausted: ${
|
|
58288
|
+
executorLog.error(`\u2717 ${task.id} transient error retries exhausted: ${errorDetail}`);
|
|
58289
|
+
if (errorStack) {
|
|
58290
|
+
await this.store.logEntry(task.id, `Transient error retries exhausted: ${errorMessage}`, errorStack, this.currentRunContext);
|
|
58291
|
+
}
|
|
57753
58292
|
await this.store.updateTask(task.id, {
|
|
57754
58293
|
status: "failed",
|
|
57755
58294
|
error: errorMessage,
|
|
@@ -57760,8 +58299,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57760
58299
|
executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
|
|
57761
58300
|
this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
57762
58301
|
} else {
|
|
57763
|
-
executorLog.error(`\u2717 ${task.id} step-session execution failed:`,
|
|
57764
|
-
await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`,
|
|
58302
|
+
executorLog.error(`\u2717 ${task.id} step-session execution failed:`, errorDetail);
|
|
58303
|
+
await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
57765
58304
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
57766
58305
|
await this.store.moveTask(task.id, "in-review");
|
|
57767
58306
|
executorLog.log(`\u2717 ${task.id} step-session execution failed \u2192 in-review`);
|
|
@@ -57811,7 +58350,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57811
58350
|
const reflectionTools = this.options.reflectionService && settings.reflectionEnabled && assignedAgentId ? [createReflectOnPerformanceTool(this.options.reflectionService, assignedAgentId)] : [];
|
|
57812
58351
|
const assignedAgent = assignedAgentId && this.options.agentStore ? await this.options.agentStore.getAgent(assignedAgentId).catch(() => null) : null;
|
|
57813
58352
|
if (executionMode === "fast") {
|
|
57814
|
-
executorLog.log(`${task.id}: fast mode \u2014
|
|
58353
|
+
executorLog.log(`${task.id}: fast mode \u2014 fn_review_step tool not injected`);
|
|
57815
58354
|
}
|
|
57816
58355
|
const customTools = [
|
|
57817
58356
|
this.createTaskUpdateTool(task.id, codeReviewVerdicts, sessionRef, stepCheckpoints, stuckDetector),
|
|
@@ -57821,7 +58360,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57821
58360
|
this.createTaskDoneTool(task.id, () => {
|
|
57822
58361
|
taskDone = true;
|
|
57823
58362
|
}),
|
|
57824
|
-
// Skip
|
|
58363
|
+
// Skip fn_review_step tool in fast mode — fast mode bypasses automated review gates
|
|
57825
58364
|
...executionMode !== "fast" ? [
|
|
57826
58365
|
this.createReviewStepTool(task.id, worktreePath, detail.prompt, codeReviewVerdicts, sessionRef, stepCheckpoints, detail, stuckDetector)
|
|
57827
58366
|
] : [],
|
|
@@ -57978,7 +58517,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57978
58517
|
"3. Review the PROMPT.md steps to see which are still pending",
|
|
57979
58518
|
"",
|
|
57980
58519
|
"Take a DIFFERENT approach from what you were doing before.",
|
|
57981
|
-
"If the current step is complete, call
|
|
58520
|
+
"If the current step is complete, call fn_task_update to mark it done and move to the next step.",
|
|
57982
58521
|
"If you're stuck on a problem, try a simpler or alternative solution.",
|
|
57983
58522
|
"",
|
|
57984
58523
|
"Continue the task from where you left off."
|
|
@@ -58016,8 +58555,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58016
58555
|
const implicitCheck = await this.store.getTask(task.id);
|
|
58017
58556
|
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
58018
58557
|
taskDone = true;
|
|
58019
|
-
executorLog.log(`${task.id} all steps done \u2014 treating as implicit
|
|
58020
|
-
await this.store.logEntry(task.id, "All steps complete \u2014 implicit
|
|
58558
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
58559
|
+
await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
58021
58560
|
}
|
|
58022
58561
|
}
|
|
58023
58562
|
if (taskDone) {
|
|
@@ -58054,11 +58593,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58054
58593
|
while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
|
|
58055
58594
|
taskDoneSessionRetries++;
|
|
58056
58595
|
executorLog.log(
|
|
58057
|
-
`\u26A0 ${task.id} finished without
|
|
58596
|
+
`\u26A0 ${task.id} finished without fn_task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
|
|
58058
58597
|
);
|
|
58059
58598
|
await this.store.logEntry(
|
|
58060
58599
|
task.id,
|
|
58061
|
-
`Agent finished without calling
|
|
58600
|
+
`Agent finished without calling fn_task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`,
|
|
58062
58601
|
void 0,
|
|
58063
58602
|
this.currentRunContext
|
|
58064
58603
|
);
|
|
@@ -58100,10 +58639,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58100
58639
|
});
|
|
58101
58640
|
stuckDetector?.trackTask(task.id, retrySession);
|
|
58102
58641
|
const retryPrompt = [
|
|
58103
|
-
"Your previous session ended without calling the
|
|
58642
|
+
"Your previous session ended without calling the fn_task_done tool.",
|
|
58104
58643
|
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
58105
|
-
"1. If the work is done, call
|
|
58106
|
-
"2. If there is remaining work, finish it and then call
|
|
58644
|
+
"1. If the work is done, call fn_task_done with a summary of what was accomplished.",
|
|
58645
|
+
"2. If there is remaining work, finish it and then call fn_task_done.",
|
|
58107
58646
|
"",
|
|
58108
58647
|
"Original task:",
|
|
58109
58648
|
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
@@ -58115,8 +58654,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58115
58654
|
const implicitCheck = await this.store.getTask(task.id);
|
|
58116
58655
|
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
58117
58656
|
taskDone = true;
|
|
58118
|
-
executorLog.log(`${task.id} all steps done \u2014 treating as implicit
|
|
58119
|
-
await this.store.logEntry(task.id, "All steps complete \u2014 implicit
|
|
58657
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
58658
|
+
await this.store.logEntry(task.id, "All steps complete \u2014 implicit fn_task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
58120
58659
|
}
|
|
58121
58660
|
}
|
|
58122
58661
|
}
|
|
@@ -58148,7 +58687,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58148
58687
|
} else {
|
|
58149
58688
|
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
58150
58689
|
const nextRequeueCount = priorRequeues + 1;
|
|
58151
|
-
const errorMessage = `Agent finished without calling
|
|
58690
|
+
const errorMessage = `Agent finished without calling fn_task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
58152
58691
|
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
58153
58692
|
await this.store.updateTask(task.id, {
|
|
58154
58693
|
status: "failed",
|
|
@@ -58167,7 +58706,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58167
58706
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
58168
58707
|
await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
|
|
58169
58708
|
await this.store.moveTask(task.id, "in-review");
|
|
58170
|
-
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no
|
|
58709
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no fn_task_done \u2192 in-review`);
|
|
58171
58710
|
}
|
|
58172
58711
|
this.options.onError?.(task, new Error(errorMessage));
|
|
58173
58712
|
}
|
|
@@ -58203,7 +58742,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58203
58742
|
await retryableWork();
|
|
58204
58743
|
}
|
|
58205
58744
|
} catch (err) {
|
|
58206
|
-
const errorMessage
|
|
58745
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
58207
58746
|
if (this.depAborted.has(task.id)) {
|
|
58208
58747
|
this.depAborted.delete(task.id);
|
|
58209
58748
|
await this.handleDepAbortCleanup(task.id, worktreePath);
|
|
@@ -58271,7 +58810,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58271
58810
|
"2. Identify the most critical remaining work",
|
|
58272
58811
|
"3. Complete it with a simpler, more focused approach",
|
|
58273
58812
|
"",
|
|
58274
|
-
"Do not repeat what's already been done. Just complete the task and call
|
|
58813
|
+
"Do not repeat what's already been done. Just complete the task and call fn_task_done."
|
|
58275
58814
|
].join("\n");
|
|
58276
58815
|
await promptWithFallback(activeEntry.session, reducedPrompt);
|
|
58277
58816
|
checkSessionError(activeEntry.session);
|
|
@@ -58346,8 +58885,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58346
58885
|
await this.store.moveTask(task.id, "todo");
|
|
58347
58886
|
return;
|
|
58348
58887
|
}
|
|
58349
|
-
executorLog.error(`\u2717 ${task.id} transient error retries exhausted (${MAX_RECOVERY_RETRIES} attempts): ${
|
|
58350
|
-
await this.store.logEntry(task.id, `Transient error retries exhausted after ${MAX_RECOVERY_RETRIES} attempts: ${errorMessage}`,
|
|
58888
|
+
executorLog.error(`\u2717 ${task.id} transient error retries exhausted (${MAX_RECOVERY_RETRIES} attempts): ${errorDetail}`);
|
|
58889
|
+
await this.store.logEntry(task.id, `Transient error retries exhausted after ${MAX_RECOVERY_RETRIES} attempts: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
58351
58890
|
await this.store.updateTask(task.id, {
|
|
58352
58891
|
status: "failed",
|
|
58353
58892
|
error: errorMessage,
|
|
@@ -58359,8 +58898,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58359
58898
|
this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
58360
58899
|
return;
|
|
58361
58900
|
}
|
|
58362
|
-
executorLog.error(`\u2717 ${task.id} execution failed:`,
|
|
58363
|
-
await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`,
|
|
58901
|
+
executorLog.error(`\u2717 ${task.id} execution failed:`, errorDetail);
|
|
58902
|
+
await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
58364
58903
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
58365
58904
|
await this.store.moveTask(task.id, "in-review");
|
|
58366
58905
|
executorLog.log(`\u2717 ${task.id} execution failed \u2192 in-review`);
|
|
@@ -58408,7 +58947,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58408
58947
|
createTaskUpdateTool(taskId, codeReviewVerdicts, sessionRef, stepCheckpoints, stuckDetector) {
|
|
58409
58948
|
const store = this.store;
|
|
58410
58949
|
return {
|
|
58411
|
-
name: "
|
|
58950
|
+
name: "fn_task_update",
|
|
58412
58951
|
label: "Update Step",
|
|
58413
58952
|
description: "Update a step's status. Call before starting a step (in-progress), after completing it (done), or to skip it (skipped). The board updates in real-time.",
|
|
58414
58953
|
parameters: taskUpdateParams,
|
|
@@ -58421,7 +58960,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58421
58960
|
return {
|
|
58422
58961
|
content: [{
|
|
58423
58962
|
type: "text",
|
|
58424
|
-
text: `Cannot mark Step ${step} as done \u2014 the last code review returned REVISE. Fix the issues from the code review, commit your changes, and call
|
|
58963
|
+
text: `Cannot mark Step ${step} as done \u2014 the last code review returned REVISE. Fix the issues from the code review, commit your changes, and call fn_review_step(step=${step}, type="code") again. The step can only advance after the code review passes.`
|
|
58425
58964
|
}],
|
|
58426
58965
|
details: {}
|
|
58427
58966
|
};
|
|
@@ -58460,7 +58999,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58460
58999
|
createTaskAddDepTool(taskId) {
|
|
58461
59000
|
const store = this.store;
|
|
58462
59001
|
return {
|
|
58463
|
-
name: "
|
|
59002
|
+
name: "fn_task_add_dep",
|
|
58464
59003
|
label: "Add Dependency",
|
|
58465
59004
|
description: "Declare a dependency on an existing task. Use when you discover mid-execution that another task must be completed first. Adding a dependency to an in-progress task will stop execution and discard current work, so confirm=true is required. Without confirm=true, a warning is returned first.",
|
|
58466
59005
|
parameters: taskAddDepParams,
|
|
@@ -58530,7 +59069,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58530
59069
|
createTaskDoneTool(taskId, onDone) {
|
|
58531
59070
|
const store = this.store;
|
|
58532
59071
|
return {
|
|
58533
|
-
name: "
|
|
59072
|
+
name: "fn_task_done",
|
|
58534
59073
|
label: "Mark Task Done",
|
|
58535
59074
|
description: "Signal that all steps are complete, tests pass, and documentation is updated. Call this as the final action after finishing all work. Automatically marks all remaining steps as done. Optionally provide a summary of what was changed/fixed.",
|
|
58536
59075
|
parameters: Type4.Object({
|
|
@@ -58545,7 +59084,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58545
59084
|
return {
|
|
58546
59085
|
content: [{
|
|
58547
59086
|
type: "text",
|
|
58548
|
-
text: `Cannot mark task done yet \u2014 ${completionBlocker}. Resolve the blocker before calling
|
|
59087
|
+
text: `Cannot mark task done yet \u2014 ${completionBlocker}. Resolve the blocker before calling fn_task_done().`
|
|
58549
59088
|
}],
|
|
58550
59089
|
details: {}
|
|
58551
59090
|
};
|
|
@@ -58569,7 +59108,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58569
59108
|
};
|
|
58570
59109
|
}
|
|
58571
59110
|
/**
|
|
58572
|
-
* Create the
|
|
59111
|
+
* Create the fn_review_step tool for the executor agent.
|
|
58573
59112
|
*
|
|
58574
59113
|
* When the reviewer returns a RETHINK verdict, this tool:
|
|
58575
59114
|
* 1. Runs `git reset --hard <baseline>` to revert file changes
|
|
@@ -58581,7 +59120,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58581
59120
|
const store = this.store;
|
|
58582
59121
|
const options = this.options;
|
|
58583
59122
|
return {
|
|
58584
|
-
name: "
|
|
59123
|
+
name: "fn_review_step",
|
|
58585
59124
|
label: "Review Step",
|
|
58586
59125
|
description: "Spawn a reviewer agent to evaluate your plan or code for a step. Returns APPROVE, REVISE, RETHINK, or UNAVAILABLE. Call at step boundaries based on the task's review level. Skip reviews for Step 0 (Preflight) and the final documentation step.",
|
|
58587
59126
|
parameters: reviewStepParams,
|
|
@@ -58651,7 +59190,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58651
59190
|
if (reviewType === "code") {
|
|
58652
59191
|
text = `REVISE \u2014 this step cannot be marked done until the code review passes.
|
|
58653
59192
|
|
|
58654
|
-
Fix the issues below, commit your changes, and call
|
|
59193
|
+
Fix the issues below, commit your changes, and call fn_review_step(step=${step}, type="code", step_name="${step_name}", baseline="<new SHA>") again.
|
|
58655
59194
|
|
|
58656
59195
|
${result.review}`;
|
|
58657
59196
|
} else {
|
|
@@ -58851,7 +59390,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
58851
59390
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping revision injection`);
|
|
58852
59391
|
return;
|
|
58853
59392
|
}
|
|
58854
|
-
const scopeLine = "All prior steps remain **done**. Apply the feedback above as an in-place fix (make the necessary code changes, commit, and call `
|
|
59393
|
+
const scopeLine = "All prior steps remain **done**. Apply the feedback above as an in-place fix (make the necessary code changes, commit, and call `fn_task_done()` when complete). Do **not** re-run or re-plan any earlier step unless the feedback explicitly calls it out.";
|
|
58855
59394
|
const revisionSectionHeader = "## Workflow Revision Instructions";
|
|
58856
59395
|
const revisionSectionContent = `${revisionSectionHeader}
|
|
58857
59396
|
|
|
@@ -59150,6 +59689,7 @@ ${failureFeedback}
|
|
|
59150
59689
|
await this.store.logEntry(task.id, `[pre-merge] Starting workflow step: ${ws.name} (${stepMode} mode)`);
|
|
59151
59690
|
executorLog.log(`${task.id} \u2014 [pre-merge] running workflow step: ${ws.name} (${stepMode} mode)`);
|
|
59152
59691
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59692
|
+
const stepStartedAtMs = Date.now();
|
|
59153
59693
|
results.push({
|
|
59154
59694
|
workflowStepId: ws.id,
|
|
59155
59695
|
workflowStepName: ws.name,
|
|
@@ -59162,6 +59702,7 @@ ${failureFeedback}
|
|
|
59162
59702
|
const result = stepMode === "script" ? await this.executeScriptWorkflowStep(task, ws, worktreePath, settings) : await this.executeWorkflowStep(task, ws, worktreePath, settings);
|
|
59163
59703
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59164
59704
|
if (result.success) {
|
|
59705
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' completed in ${Date.now() - stepStartedAtMs}ms`);
|
|
59165
59706
|
await this.store.logEntry(task.id, `[pre-merge] Workflow step completed: ${ws.name}`);
|
|
59166
59707
|
executorLog.log(`${task.id} \u2014 [pre-merge] workflow step passed: ${ws.name}`);
|
|
59167
59708
|
const existingIdx = results.findIndex((r) => r.workflowStepId === ws.id);
|
|
@@ -59175,6 +59716,7 @@ ${failureFeedback}
|
|
|
59175
59716
|
}
|
|
59176
59717
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
59177
59718
|
} else if (result.revisionRequested) {
|
|
59719
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' requested revision after ${Date.now() - stepStartedAtMs}ms`);
|
|
59178
59720
|
await this.store.logEntry(
|
|
59179
59721
|
task.id,
|
|
59180
59722
|
`[pre-merge] Workflow step requested revision: ${ws.name}`,
|
|
@@ -59198,6 +59740,7 @@ ${failureFeedback}
|
|
|
59198
59740
|
stepName: ws.name
|
|
59199
59741
|
};
|
|
59200
59742
|
} else {
|
|
59743
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' failed after ${Date.now() - stepStartedAtMs}ms`);
|
|
59201
59744
|
await this.store.logEntry(
|
|
59202
59745
|
task.id,
|
|
59203
59746
|
`[pre-merge] Workflow step failed: ${ws.name}`,
|
|
@@ -59222,14 +59765,14 @@ ${failureFeedback}
|
|
|
59222
59765
|
};
|
|
59223
59766
|
}
|
|
59224
59767
|
} catch (err) {
|
|
59225
|
-
const errorMessage
|
|
59768
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
59226
59769
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59227
59770
|
await this.store.logEntry(
|
|
59228
59771
|
task.id,
|
|
59229
59772
|
`[pre-merge] Workflow step failed: ${ws.name}`,
|
|
59230
|
-
|
|
59773
|
+
errorStack ?? errorDetail
|
|
59231
59774
|
);
|
|
59232
|
-
executorLog.error(`${task.id} \u2014 [pre-merge] workflow step error: ${ws.name} \u2014 ${
|
|
59775
|
+
executorLog.error(`${task.id} \u2014 [pre-merge] workflow step error: ${ws.name} \u2014 ${errorDetail}`);
|
|
59233
59776
|
const existingIdx = results.findIndex((r) => r.workflowStepId === ws.id);
|
|
59234
59777
|
if (existingIdx >= 0) {
|
|
59235
59778
|
results[existingIdx] = {
|
|
@@ -59855,7 +60398,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
59855
60398
|
/**
|
|
59856
60399
|
* When the engine restarts mid-step, an `in-progress` step may have already
|
|
59857
60400
|
* passed its code review (log: `code review Step N: APPROVE`) but not yet
|
|
59858
|
-
* been flipped to `done` by the agent's next `
|
|
60401
|
+
* been flipped to `done` by the agent's next `fn_task_update` call. Without
|
|
59859
60402
|
* intervention, the next executor pass re-enters the step and replays plan
|
|
59860
60403
|
* + code review, which we've measured at 5–20 min of pure waste per restart.
|
|
59861
60404
|
*
|
|
@@ -60118,14 +60661,14 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
60118
60661
|
}
|
|
60119
60662
|
}
|
|
60120
60663
|
/**
|
|
60121
|
-
* Create the
|
|
60664
|
+
* Create the fn_spawn_agent tool definition.
|
|
60122
60665
|
* Allows the parent agent to spawn child agents with delegated tasks.
|
|
60123
60666
|
*/
|
|
60124
60667
|
createSpawnAgentTool(taskId, worktreePath, settings) {
|
|
60125
60668
|
return {
|
|
60126
|
-
name: "
|
|
60669
|
+
name: "fn_spawn_agent",
|
|
60127
60670
|
label: "Spawn Agent",
|
|
60128
|
-
description: "Spawn a child agent to handle parallel work or specialized sub-tasks. Each child runs in its own git worktree (branched from your worktree) and executes autonomously. When you end (
|
|
60671
|
+
description: "Spawn a child agent to handle parallel work or specialized sub-tasks. Each child runs in its own git worktree (branched from your worktree) and executes autonomously. When you end (fn_task_done), all spawned children are terminated.",
|
|
60129
60672
|
parameters: spawnAgentParams,
|
|
60130
60673
|
execute: async (_id, params) => {
|
|
60131
60674
|
const { name, role, task: taskPrompt } = params;
|
|
@@ -60645,6 +61188,7 @@ var init_scheduler = __esm({
|
|
|
60645
61188
|
}
|
|
60646
61189
|
}
|
|
60647
61190
|
if (todo.length === 0) return;
|
|
61191
|
+
todo = sortTasksByPriorityThenAgeAndId(todo);
|
|
60648
61192
|
const activeScopes = /* @__PURE__ */ new Map();
|
|
60649
61193
|
if (settings.groupOverlappingFiles) {
|
|
60650
61194
|
for (const t of inProgress) {
|
|
@@ -63190,6 +63734,13 @@ function formatTaskIdentifier(task) {
|
|
|
63190
63734
|
const snippet = task.description.length > maxLen ? task.description.slice(0, maxLen) + "..." : task.description;
|
|
63191
63735
|
return `${task.id}: ${snippet}`;
|
|
63192
63736
|
}
|
|
63737
|
+
function resolveNtfyBaseUrl(baseUrl, fallback = DEFAULT_NTFY_BASE_URL) {
|
|
63738
|
+
const trimmed = baseUrl?.trim();
|
|
63739
|
+
if (!trimmed) {
|
|
63740
|
+
return fallback;
|
|
63741
|
+
}
|
|
63742
|
+
return trimmed.replace(/\/+$/, "");
|
|
63743
|
+
}
|
|
63193
63744
|
function resolveNtfyEvents(events) {
|
|
63194
63745
|
return events ? [...events] : [...DEFAULT_NTFY_EVENTS];
|
|
63195
63746
|
}
|
|
@@ -63213,7 +63764,7 @@ function buildNtfyClickUrl(options) {
|
|
|
63213
63764
|
return query ? `${normalizedHost}/?${query}` : `${normalizedHost}/`;
|
|
63214
63765
|
}
|
|
63215
63766
|
async function sendNtfyNotification({
|
|
63216
|
-
ntfyBaseUrl
|
|
63767
|
+
ntfyBaseUrl,
|
|
63217
63768
|
topic,
|
|
63218
63769
|
title,
|
|
63219
63770
|
message,
|
|
@@ -63230,7 +63781,8 @@ async function sendNtfyNotification({
|
|
|
63230
63781
|
if (clickUrl) {
|
|
63231
63782
|
headers.Click = clickUrl;
|
|
63232
63783
|
}
|
|
63233
|
-
const
|
|
63784
|
+
const resolvedBaseUrl = resolveNtfyBaseUrl(ntfyBaseUrl);
|
|
63785
|
+
const response = await fetch(`${resolvedBaseUrl}/${topic}`, {
|
|
63234
63786
|
method: "POST",
|
|
63235
63787
|
headers,
|
|
63236
63788
|
body: message,
|
|
@@ -63246,11 +63798,12 @@ async function sendNtfyNotification({
|
|
|
63246
63798
|
schedulerLog.log(`Failed to send ntfy notification: ${err}`);
|
|
63247
63799
|
}
|
|
63248
63800
|
}
|
|
63249
|
-
var DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
63801
|
+
var DEFAULT_NTFY_BASE_URL, DEFAULT_NTFY_EVENTS, NtfyNotifier;
|
|
63250
63802
|
var init_notifier = __esm({
|
|
63251
63803
|
"../engine/src/notifier.ts"() {
|
|
63252
63804
|
"use strict";
|
|
63253
63805
|
init_logger2();
|
|
63806
|
+
DEFAULT_NTFY_BASE_URL = "https://ntfy.sh";
|
|
63254
63807
|
DEFAULT_NTFY_EVENTS = [
|
|
63255
63808
|
"in-review",
|
|
63256
63809
|
"merged",
|
|
@@ -63262,7 +63815,8 @@ var init_notifier = __esm({
|
|
|
63262
63815
|
NtfyNotifier = class {
|
|
63263
63816
|
constructor(store, options = {}) {
|
|
63264
63817
|
this.store = store;
|
|
63265
|
-
this.
|
|
63818
|
+
this.defaultNtfyBaseUrl = resolveNtfyBaseUrl(options.ntfyBaseUrl);
|
|
63819
|
+
this.ntfyBaseUrl = this.defaultNtfyBaseUrl;
|
|
63266
63820
|
this.projectId = options.projectId;
|
|
63267
63821
|
}
|
|
63268
63822
|
config = {
|
|
@@ -63272,6 +63826,7 @@ var init_notifier = __esm({
|
|
|
63272
63826
|
events: [...DEFAULT_NTFY_EVENTS]
|
|
63273
63827
|
};
|
|
63274
63828
|
ntfyBaseUrl;
|
|
63829
|
+
defaultNtfyBaseUrl;
|
|
63275
63830
|
projectId;
|
|
63276
63831
|
notifiedEvents = /* @__PURE__ */ new Set();
|
|
63277
63832
|
abortController = null;
|
|
@@ -63410,7 +63965,7 @@ var init_notifier = __esm({
|
|
|
63410
63965
|
};
|
|
63411
63966
|
handleSettingsUpdated = (data) => {
|
|
63412
63967
|
const { settings, previous } = data;
|
|
63413
|
-
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
63968
|
+
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyBaseUrl !== previous.ntfyBaseUrl || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
63414
63969
|
const wasEnabled = this.config.enabled;
|
|
63415
63970
|
this.loadConfig(settings);
|
|
63416
63971
|
if (this.config.enabled && !wasEnabled) {
|
|
@@ -63419,6 +63974,8 @@ var init_notifier = __esm({
|
|
|
63419
63974
|
schedulerLog.log("NtfyNotifier disabled");
|
|
63420
63975
|
} else if (this.config.topic !== previous.ntfyTopic) {
|
|
63421
63976
|
schedulerLog.log("NtfyNotifier topic updated");
|
|
63977
|
+
} else if (this.ntfyBaseUrl !== resolveNtfyBaseUrl(previous.ntfyBaseUrl)) {
|
|
63978
|
+
schedulerLog.log("NtfyNotifier base URL updated");
|
|
63422
63979
|
} else if (this.config.dashboardHost !== previous.ntfyDashboardHost) {
|
|
63423
63980
|
schedulerLog.log("NtfyNotifier dashboard host updated");
|
|
63424
63981
|
} else if (JSON.stringify(this.config.events) !== JSON.stringify(previous.ntfyEvents)) {
|
|
@@ -63433,6 +63990,7 @@ var init_notifier = __esm({
|
|
|
63433
63990
|
dashboardHost: settings.ntfyDashboardHost,
|
|
63434
63991
|
events: resolveNtfyEvents(settings.ntfyEvents)
|
|
63435
63992
|
};
|
|
63993
|
+
this.ntfyBaseUrl = resolveNtfyBaseUrl(settings.ntfyBaseUrl, this.defaultNtfyBaseUrl);
|
|
63436
63994
|
}
|
|
63437
63995
|
isEventEnabled(event) {
|
|
63438
63996
|
return isNtfyEventEnabled(this.config.events, event);
|
|
@@ -64822,14 +65380,14 @@ var init_agent_heartbeat = __esm({
|
|
|
64822
65380
|
Your job:
|
|
64823
65381
|
1. Check your assigned task \u2014 read the description and PROMPT.md if present.
|
|
64824
65382
|
2. Do ONE useful action: analyze, review, create follow-up tasks, or log findings.
|
|
64825
|
-
3. Use
|
|
64826
|
-
4. Use
|
|
64827
|
-
5. Call
|
|
65383
|
+
3. Use fn_task_create to spawn follow-up work, fn_task_log to record observations.
|
|
65384
|
+
4. Use fn_task_document_write to save durable findings, plans, or research notes.
|
|
65385
|
+
5. Call fn_heartbeat_done when finished with an optional summary of what was accomplished.
|
|
64828
65386
|
|
|
64829
65387
|
Keep work lightweight \u2014 this is a single-pass check, not a full implementation run.
|
|
64830
|
-
You have readonly file access plus
|
|
65388
|
+
You have readonly file access plus fn_task_create, fn_task_log, and fn_task_document tools.
|
|
64831
65389
|
|
|
64832
|
-
**Task Documents:** Save important findings with
|
|
65390
|
+
**Task Documents:** Save important findings with fn_task_document_write(key="...", content="...").
|
|
64833
65391
|
Documents persist across sessions and are visible in the dashboard's Documents tab.
|
|
64834
65392
|
|
|
64835
65393
|
## Memory Boundaries
|
|
@@ -64842,12 +65400,12 @@ You may receive an Agent Memory section and a Project Memory section.
|
|
|
64842
65400
|
## Processing Messages
|
|
64843
65401
|
|
|
64844
65402
|
When you are woken by an incoming message (source includes "wake-on-message"), you should:
|
|
64845
|
-
1. Use
|
|
65403
|
+
1. Use fn_read_messages to check your inbox for unread messages.
|
|
64846
65404
|
2. Review each message and determine the appropriate action:
|
|
64847
|
-
- If the message requires a response, use
|
|
64848
|
-
- When replying, include 'reply_to_message_id' with the original message ID from
|
|
64849
|
-
- If the message is informational, acknowledge it by logging with
|
|
64850
|
-
- If the message requests work, create a follow-up task with
|
|
65405
|
+
- If the message requires a response, use fn_send_message to reply.
|
|
65406
|
+
- When replying, include 'reply_to_message_id' with the original message ID from fn_read_messages output.
|
|
65407
|
+
- If the message is informational, acknowledge it by logging with fn_task_log.
|
|
65408
|
+
- If the message requests work, create a follow-up task with fn_task_create or handle it directly.
|
|
64851
65409
|
3. After processing messages, continue with your normal heartbeat duties.
|
|
64852
65410
|
|
|
64853
65411
|
When sending messages:
|
|
@@ -64860,17 +65418,17 @@ When sending messages:
|
|
|
64860
65418
|
Your job:
|
|
64861
65419
|
1. Review your context \u2014 check messages, memory, and project state.
|
|
64862
65420
|
2. Do ONE useful action: analyze, create follow-up tasks, delegate work, or update memory.
|
|
64863
|
-
3. Use
|
|
64864
|
-
4. Use
|
|
64865
|
-
5. Call
|
|
65421
|
+
3. Use fn_task_create to spawn follow-up work.
|
|
65422
|
+
4. Use fn_list_agents and fn_delegate_task to coordinate with other agents.
|
|
65423
|
+
5. Call fn_heartbeat_done when finished with an optional summary of what was accomplished.
|
|
64866
65424
|
|
|
64867
65425
|
Keep work lightweight \u2014 this is a single-pass ambient check, not a full implementation run.
|
|
64868
65426
|
You have readonly file access plus:
|
|
64869
|
-
-
|
|
64870
|
-
-
|
|
64871
|
-
-
|
|
64872
|
-
-
|
|
64873
|
-
-
|
|
65427
|
+
- fn_task_create
|
|
65428
|
+
- fn_list_agents and fn_delegate_task
|
|
65429
|
+
- fn_memory_search, fn_memory_get, and fn_memory_append
|
|
65430
|
+
- fn_heartbeat_done
|
|
65431
|
+
- fn_send_message and fn_read_messages when messaging is enabled for this run (they may not always be available)
|
|
64874
65432
|
|
|
64875
65433
|
## Memory Boundaries
|
|
64876
65434
|
|
|
@@ -64882,12 +65440,12 @@ You may receive an Agent Memory section and a Project Memory section.
|
|
|
64882
65440
|
## Processing Messages
|
|
64883
65441
|
|
|
64884
65442
|
When you are woken by an incoming message (source includes "wake-on-message"), you should:
|
|
64885
|
-
1. If
|
|
65443
|
+
1. If fn_read_messages is available, use it to check your inbox for unread messages.
|
|
64886
65444
|
2. Review each message and determine the appropriate action:
|
|
64887
|
-
- If the message requires a response and
|
|
64888
|
-
- When replying, include 'reply_to_message_id' with the original message ID from
|
|
64889
|
-
- If the message is informational, acknowledge it and respond via
|
|
64890
|
-
- If the message requests work, create a follow-up task with
|
|
65445
|
+
- If the message requires a response and fn_send_message is available, use fn_send_message to reply.
|
|
65446
|
+
- When replying, include 'reply_to_message_id' with the original message ID from fn_read_messages output.
|
|
65447
|
+
- If the message is informational, acknowledge it and respond via fn_send_message when appropriate.
|
|
65448
|
+
- If the message requests work, create a follow-up task with fn_task_create.
|
|
64891
65449
|
3. After processing messages, continue with your ambient work.
|
|
64892
65450
|
|
|
64893
65451
|
When sending messages:
|
|
@@ -65294,7 +65852,7 @@ When sending messages:
|
|
|
65294
65852
|
* Implements the Paperclip-style execution model:
|
|
65295
65853
|
* 1. Wake — start a heartbeat run record
|
|
65296
65854
|
* 2. Check inbox — resolve the agent's assigned task
|
|
65297
|
-
* 3. Work — run a lightweight agent session with readonly tools +
|
|
65855
|
+
* 3. Work — run a lightweight agent session with readonly tools + fn_task_create/fn_task_log
|
|
65298
65856
|
* 4. Exit — record results and complete the run
|
|
65299
65857
|
*
|
|
65300
65858
|
* Budget governance:
|
|
@@ -65544,7 +66102,7 @@ When sending messages:
|
|
|
65544
66102
|
stdoutExcerpt += delta.slice(0, remaining);
|
|
65545
66103
|
};
|
|
65546
66104
|
const heartbeatDoneTool = {
|
|
65547
|
-
name: "
|
|
66105
|
+
name: "fn_heartbeat_done",
|
|
65548
66106
|
label: "Heartbeat Done",
|
|
65549
66107
|
description: "Signal that the heartbeat execution is complete. Call when finished.",
|
|
65550
66108
|
parameters: heartbeatDoneParams,
|
|
@@ -65673,16 +66231,16 @@ When sending messages:
|
|
|
65673
66231
|
"You have identity (soul, instructions, and/or memory) loaded, which means you can perform",
|
|
65674
66232
|
"useful ambient work. Here are some things you can do:",
|
|
65675
66233
|
"",
|
|
65676
|
-
"1. **Check your messages** \u2014 Use
|
|
65677
|
-
" and use
|
|
66234
|
+
"1. **Check your messages** \u2014 Use fn_read_messages to review any pending messages",
|
|
66235
|
+
" and use fn_send_message with reply_to_message_id when responding.",
|
|
65678
66236
|
"",
|
|
65679
|
-
"2. **Create new tasks** \u2014 Use
|
|
66237
|
+
"2. **Create new tasks** \u2014 Use fn_task_create to spawn follow-up work that needs",
|
|
65680
66238
|
" to be done. This is useful for surfacing issues or ideas you discover.",
|
|
65681
66239
|
"",
|
|
65682
|
-
"3. **Delegate work** \u2014 Use
|
|
65683
|
-
"
|
|
66240
|
+
"3. **Delegate work** \u2014 Use fn_list_agents to discover available agents and",
|
|
66241
|
+
" fn_delegate_task to assign work to them.",
|
|
65684
66242
|
"",
|
|
65685
|
-
"4. **Update your memory** \u2014 Use
|
|
66243
|
+
"4. **Update your memory** \u2014 Use fn_memory_append to persist important learnings",
|
|
65686
66244
|
" or context that will help you in future sessions.",
|
|
65687
66245
|
"",
|
|
65688
66246
|
"5. **Monitor the project** \u2014 Review the task board and identify any issues",
|
|
@@ -65691,7 +66249,7 @@ When sending messages:
|
|
|
65691
66249
|
"",
|
|
65692
66250
|
"Your soul, instructions, and memory are already loaded in the system prompt.",
|
|
65693
66251
|
"Focus on work that benefits the project without requiring a specific task context.",
|
|
65694
|
-
"Call
|
|
66252
|
+
"Call fn_heartbeat_done when finished."
|
|
65695
66253
|
].join("\n");
|
|
65696
66254
|
} else {
|
|
65697
66255
|
const taskTitle = taskDetail.title ?? taskDetail.description.slice(0, 100);
|
|
@@ -65751,7 +66309,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65751
66309
|
...triggeringCommentLines,
|
|
65752
66310
|
...pendingMessagesLines,
|
|
65753
66311
|
"",
|
|
65754
|
-
"Review the task status and take appropriate action. Call
|
|
66312
|
+
"Review the task status and take appropriate action. Call fn_heartbeat_done when finished."
|
|
65755
66313
|
].join("\n");
|
|
65756
66314
|
}
|
|
65757
66315
|
await promptWithFallback2(session, executionPrompt);
|
|
@@ -65783,12 +66341,12 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65783
66341
|
});
|
|
65784
66342
|
heartbeatLog.log(`Heartbeat completed for ${agentId} (${toolCallCount} tool calls, ~${estimatedOutputTokens} output tokens)`);
|
|
65785
66343
|
} catch (err) {
|
|
65786
|
-
const
|
|
65787
|
-
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${
|
|
66344
|
+
const errorDetail = formatError(err).detail;
|
|
66345
|
+
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${errorDetail}`);
|
|
65788
66346
|
await flushAgentLogger();
|
|
65789
66347
|
await this.completeRun(agentId, run.id, {
|
|
65790
66348
|
status: "failed",
|
|
65791
|
-
stderrExcerpt:
|
|
66349
|
+
stderrExcerpt: errorDetail,
|
|
65792
66350
|
stdoutExcerpt: stdoutExcerpt || void 0
|
|
65793
66351
|
});
|
|
65794
66352
|
} finally {
|
|
@@ -65807,13 +66365,14 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65807
66365
|
}
|
|
65808
66366
|
return await this.store.getRunDetail(agentId, run.id);
|
|
65809
66367
|
} catch (err) {
|
|
66368
|
+
const errorDetail = formatError(err).detail;
|
|
65810
66369
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65811
|
-
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${
|
|
66370
|
+
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${errorDetail}`);
|
|
65812
66371
|
await flushAgentLogger();
|
|
65813
66372
|
try {
|
|
65814
66373
|
await this.completeRun(agentId, run.id, {
|
|
65815
66374
|
status: "failed",
|
|
65816
|
-
stderrExcerpt:
|
|
66375
|
+
stderrExcerpt: errorDetail
|
|
65817
66376
|
});
|
|
65818
66377
|
} catch (completeRunErr) {
|
|
65819
66378
|
const completeRunErrMsg = completeRunErr instanceof Error ? completeRunErr.message : String(completeRunErr);
|
|
@@ -65851,7 +66410,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65851
66410
|
*
|
|
65852
66411
|
* @param agentId - The agent ID (used for tracking and logging)
|
|
65853
66412
|
* @param taskStore - TaskStore for task creation and logging
|
|
65854
|
-
* @param taskId - The assigned task ID (for
|
|
66413
|
+
* @param taskId - The assigned task ID (for fn_task_log context)
|
|
65855
66414
|
* @param runContext - Optional run context for mutation correlation
|
|
65856
66415
|
* @param audit - Optional run auditor for audit trail (FN-1404)
|
|
65857
66416
|
* @param messageStore - Optional MessageStore for messaging tools
|
|
@@ -69927,7 +70486,7 @@ var init_sse_buffer = __esm({
|
|
|
69927
70486
|
});
|
|
69928
70487
|
|
|
69929
70488
|
// ../dashboard/src/ai-session-diagnostics.ts
|
|
69930
|
-
import { randomUUID as
|
|
70489
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
69931
70490
|
function defaultSink(level, scope, message, context) {
|
|
69932
70491
|
const prefix = `[${scope}]`;
|
|
69933
70492
|
const logArgs = [prefix, message, context];
|
|
@@ -69971,7 +70530,7 @@ function emit(level, scope, message, context) {
|
|
|
69971
70530
|
const fullContext = {
|
|
69972
70531
|
...context,
|
|
69973
70532
|
_emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69974
|
-
_diagnosticsId:
|
|
70533
|
+
_diagnosticsId: randomUUID9()
|
|
69975
70534
|
};
|
|
69976
70535
|
try {
|
|
69977
70536
|
_sink(level, scope, message, fullContext);
|
|
@@ -70028,7 +70587,7 @@ var init_ai_session_diagnostics = __esm({
|
|
|
70028
70587
|
});
|
|
70029
70588
|
|
|
70030
70589
|
// ../dashboard/src/planning.ts
|
|
70031
|
-
import { randomUUID as
|
|
70590
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
70032
70591
|
import { EventEmitter as EventEmitter17 } from "node:events";
|
|
70033
70592
|
async function initEngine2() {
|
|
70034
70593
|
try {
|
|
@@ -70222,7 +70781,7 @@ async function createSession(ip, initialPlan, _store, rootDir, promptOverrides)
|
|
|
70222
70781
|
if (!rootDir) {
|
|
70223
70782
|
throw new Error("rootDir is required for AI-powered planning sessions");
|
|
70224
70783
|
}
|
|
70225
|
-
const sessionId =
|
|
70784
|
+
const sessionId = randomUUID10();
|
|
70226
70785
|
const session = {
|
|
70227
70786
|
id: sessionId,
|
|
70228
70787
|
ip,
|
|
@@ -72749,7 +73308,7 @@ var init_github_poll = __esm({
|
|
|
72749
73308
|
|
|
72750
73309
|
// ../dashboard/src/terminal.ts
|
|
72751
73310
|
import { spawn as spawn2 } from "node:child_process";
|
|
72752
|
-
import { randomUUID as
|
|
73311
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
72753
73312
|
import { EventEmitter as EventEmitter19 } from "node:events";
|
|
72754
73313
|
function extractBaseCommand(command) {
|
|
72755
73314
|
let trimmed = command.trim();
|
|
@@ -72909,7 +73468,7 @@ var init_terminal = __esm({
|
|
|
72909
73468
|
if (!validation.valid) {
|
|
72910
73469
|
return { sessionId: "", error: validation.error };
|
|
72911
73470
|
}
|
|
72912
|
-
const sessionId =
|
|
73471
|
+
const sessionId = randomUUID11();
|
|
72913
73472
|
const childProcess = spawn2(command, [], {
|
|
72914
73473
|
cwd,
|
|
72915
73474
|
shell: true,
|
|
@@ -73281,6 +73840,8 @@ function cleanupExpiredSessions3() {
|
|
|
73281
73840
|
diagnostics4.info("Cleanup completed", {
|
|
73282
73841
|
cleanedSessions,
|
|
73283
73842
|
cleanedRateLimits,
|
|
73843
|
+
ttlMs: SESSION_TTL_MS3,
|
|
73844
|
+
rateLimitWindowMs: RATE_LIMIT_WINDOW_MS3,
|
|
73284
73845
|
operation: "cleanup-expired"
|
|
73285
73846
|
});
|
|
73286
73847
|
}
|
|
@@ -73615,7 +74176,7 @@ async function initPromptOverrides() {
|
|
|
73615
74176
|
promptOverridesReady = true;
|
|
73616
74177
|
}
|
|
73617
74178
|
}
|
|
73618
|
-
var mkdtemp, access3, stat6, mkdir12, readdir8,
|
|
74179
|
+
var mkdtemp, access3, stat6, mkdir12, readdir8, rm2, fsReadFile, fsWriteFile, upload, execFileAsync, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
|
|
73619
74180
|
var init_routes = __esm({
|
|
73620
74181
|
"../dashboard/src/routes.ts"() {
|
|
73621
74182
|
"use strict";
|
|
@@ -73652,7 +74213,7 @@ var init_routes = __esm({
|
|
|
73652
74213
|
stat: stat6,
|
|
73653
74214
|
mkdir: mkdir12,
|
|
73654
74215
|
readdir: readdir8,
|
|
73655
|
-
rm,
|
|
74216
|
+
rm: rm2,
|
|
73656
74217
|
readFile: fsReadFile,
|
|
73657
74218
|
writeFile: fsWriteFile
|
|
73658
74219
|
} = fsPromises);
|
|
@@ -75739,7 +76300,7 @@ var require_extension = __commonJS({
|
|
|
75739
76300
|
if (dest[name] === void 0) dest[name] = [elem];
|
|
75740
76301
|
else dest[name].push(elem);
|
|
75741
76302
|
}
|
|
75742
|
-
function
|
|
76303
|
+
function parse(header) {
|
|
75743
76304
|
const offers = /* @__PURE__ */ Object.create(null);
|
|
75744
76305
|
let params = /* @__PURE__ */ Object.create(null);
|
|
75745
76306
|
let mustUnescape = false;
|
|
@@ -75879,7 +76440,7 @@ var require_extension = __commonJS({
|
|
|
75879
76440
|
}).join(", ");
|
|
75880
76441
|
}).join(", ");
|
|
75881
76442
|
}
|
|
75882
|
-
module.exports = { format, parse
|
|
76443
|
+
module.exports = { format, parse };
|
|
75883
76444
|
}
|
|
75884
76445
|
});
|
|
75885
76446
|
|
|
@@ -75913,7 +76474,7 @@ var require_websocket = __commonJS({
|
|
|
75913
76474
|
var {
|
|
75914
76475
|
EventTarget: { addEventListener, removeEventListener }
|
|
75915
76476
|
} = require_event_target();
|
|
75916
|
-
var { format, parse
|
|
76477
|
+
var { format, parse } = require_extension();
|
|
75917
76478
|
var { toBuffer } = require_buffer_util();
|
|
75918
76479
|
var kAborted = /* @__PURE__ */ Symbol("kAborted");
|
|
75919
76480
|
var protocolVersions = [8, 13];
|
|
@@ -76582,7 +77143,7 @@ var require_websocket = __commonJS({
|
|
|
76582
77143
|
}
|
|
76583
77144
|
let extensions;
|
|
76584
77145
|
try {
|
|
76585
|
-
extensions =
|
|
77146
|
+
extensions = parse(secWebSocketExtensions);
|
|
76586
77147
|
} catch (err) {
|
|
76587
77148
|
const message = "Invalid Sec-WebSocket-Extensions header";
|
|
76588
77149
|
abortHandshake(websocket, socket, message);
|
|
@@ -76872,7 +77433,7 @@ var require_subprotocol = __commonJS({
|
|
|
76872
77433
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/subprotocol.js"(exports, module) {
|
|
76873
77434
|
"use strict";
|
|
76874
77435
|
var { tokenChars } = require_validation();
|
|
76875
|
-
function
|
|
77436
|
+
function parse(header) {
|
|
76876
77437
|
const protocols = /* @__PURE__ */ new Set();
|
|
76877
77438
|
let start = -1;
|
|
76878
77439
|
let end = -1;
|
|
@@ -76908,7 +77469,7 @@ var require_subprotocol = __commonJS({
|
|
|
76908
77469
|
protocols.add(protocol);
|
|
76909
77470
|
return protocols;
|
|
76910
77471
|
}
|
|
76911
|
-
module.exports = { parse
|
|
77472
|
+
module.exports = { parse };
|
|
76912
77473
|
}
|
|
76913
77474
|
});
|
|
76914
77475
|
|
|
@@ -77478,7 +78039,7 @@ var init_auth_middleware = __esm({
|
|
|
77478
78039
|
|
|
77479
78040
|
// ../dashboard/src/server.ts
|
|
77480
78041
|
import express from "express";
|
|
77481
|
-
import { join as join34, dirname as
|
|
78042
|
+
import { join as join34, dirname as dirname8 } from "node:path";
|
|
77482
78043
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
77483
78044
|
function clearAiSessionCleanupInterval() {
|
|
77484
78045
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -77512,7 +78073,7 @@ var init_server = __esm({
|
|
|
77512
78073
|
init_chat();
|
|
77513
78074
|
init_dev_server_routes();
|
|
77514
78075
|
init_auth_middleware();
|
|
77515
|
-
__dirname =
|
|
78076
|
+
__dirname = dirname8(fileURLToPath2(import.meta.url));
|
|
77516
78077
|
MIN_AI_SESSION_TTL_MS = 10 * 60 * 1e3;
|
|
77517
78078
|
MAX_AI_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
77518
78079
|
MIN_AI_SESSION_CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
@@ -77581,7 +78142,7 @@ var init_src3 = __esm({
|
|
|
77581
78142
|
});
|
|
77582
78143
|
|
|
77583
78144
|
// src/project-context.ts
|
|
77584
|
-
import { resolve as resolve14, dirname as
|
|
78145
|
+
import { resolve as resolve14, dirname as dirname9 } from "node:path";
|
|
77585
78146
|
import { existsSync as existsSync28 } from "node:fs";
|
|
77586
78147
|
async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
|
|
77587
78148
|
const central = new CentralCore(globalDir);
|
|
@@ -77672,7 +78233,7 @@ async function detectProjectFromCwd(cwd, central) {
|
|
|
77672
78233
|
path: currentDir
|
|
77673
78234
|
};
|
|
77674
78235
|
}
|
|
77675
|
-
const parentDir =
|
|
78236
|
+
const parentDir = dirname9(currentDir);
|
|
77676
78237
|
if (parentDir === currentDir) {
|
|
77677
78238
|
break;
|
|
77678
78239
|
}
|
|
@@ -77834,11 +78395,11 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName)
|
|
|
77834
78395
|
console.log(` Path: .fusion/tasks/${task.id}/`);
|
|
77835
78396
|
if (attachFiles && attachFiles.length > 0) {
|
|
77836
78397
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
77837
|
-
const { basename:
|
|
78398
|
+
const { basename: basename9, extname: extname3, resolve: resolve16 } = await import("node:path");
|
|
77838
78399
|
for (const filePath of attachFiles) {
|
|
77839
78400
|
const resolvedPath = resolve16(filePath);
|
|
77840
|
-
const filename =
|
|
77841
|
-
const ext =
|
|
78401
|
+
const filename = basename9(resolvedPath);
|
|
78402
|
+
const ext = extname3(filename).toLowerCase();
|
|
77842
78403
|
const mimeType = MIME_TYPES[ext];
|
|
77843
78404
|
if (!mimeType) {
|
|
77844
78405
|
console.error(` \u2717 Unsupported file type: ${ext} (${filename})`);
|
|
@@ -78082,11 +78643,11 @@ async function runTaskMerge(id, projectName) {
|
|
|
78082
78643
|
}
|
|
78083
78644
|
async function runTaskAttach(id, filePath, projectName) {
|
|
78084
78645
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
78085
|
-
const { basename:
|
|
78646
|
+
const { basename: basename9, extname: extname3 } = await import("node:path");
|
|
78086
78647
|
const { resolve: resolve16 } = await import("node:path");
|
|
78087
78648
|
const resolvedPath = resolve16(filePath);
|
|
78088
|
-
const filename =
|
|
78089
|
-
const ext =
|
|
78649
|
+
const filename = basename9(resolvedPath);
|
|
78650
|
+
const ext = extname3(filename).toLowerCase();
|
|
78090
78651
|
const mimeType = MIME_TYPES[ext];
|
|
78091
78652
|
if (!mimeType) {
|
|
78092
78653
|
console.error(`Unsupported file type: ${ext}`);
|
|
@@ -79037,7 +79598,7 @@ init_src();
|
|
|
79037
79598
|
init_gh_cli();
|
|
79038
79599
|
import { Type as Type7 } from "typebox";
|
|
79039
79600
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
79040
|
-
import { resolve as resolve15, basename as
|
|
79601
|
+
import { resolve as resolve15, basename as basename8, extname as extname2, join as join36 } from "node:path";
|
|
79041
79602
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
79042
79603
|
import { existsSync as existsSync30 } from "node:fs";
|
|
79043
79604
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -79358,8 +79919,8 @@ Column: triage
|
|
|
79358
79919
|
}),
|
|
79359
79920
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
79360
79921
|
const filePath = resolve15(ctx.cwd, params.path.replace(/^@/, ""));
|
|
79361
|
-
const filename =
|
|
79362
|
-
const ext =
|
|
79922
|
+
const filename = basename8(filePath);
|
|
79923
|
+
const ext = extname2(filename).toLowerCase();
|
|
79363
79924
|
const mimeType = MIME_TYPES2[ext];
|
|
79364
79925
|
if (!mimeType) {
|
|
79365
79926
|
throw new Error(
|