@runfusion/fusion 0.1.2 → 0.1.3
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 +2069 -865
- package/dist/client/assets/index-BuenKJX0.css +1 -0
- package/dist/client/assets/index-CjGu8HRV.js +1250 -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 +797 -345
- package/dist/pi-claude-cli/index.ts +131 -0
- package/dist/pi-claude-cli/package.json +39 -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 +6 -5
- 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
|
@@ -767,7 +767,7 @@ function validateMessageMetadata(metadata) {
|
|
|
767
767
|
throw new Error("metadata.replyTo.messageId must be a non-empty string");
|
|
768
768
|
}
|
|
769
769
|
}
|
|
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;
|
|
770
|
+
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
771
|
var init_types = __esm({
|
|
772
772
|
"../core/src/types.ts"() {
|
|
773
773
|
"use strict";
|
|
@@ -776,6 +776,8 @@ var init_types = __esm({
|
|
|
776
776
|
init_error_message();
|
|
777
777
|
THINKING_LEVELS = ["off", "minimal", "low", "medium", "high"];
|
|
778
778
|
COLUMNS = ["triage", "todo", "in-progress", "in-review", "done", "archived"];
|
|
779
|
+
TASK_PRIORITIES = ["low", "normal", "high", "urgent"];
|
|
780
|
+
DEFAULT_TASK_PRIORITY = "normal";
|
|
779
781
|
EXECUTION_MODES = ["standard", "fast"];
|
|
780
782
|
DEFAULT_EXECUTION_MODE = "standard";
|
|
781
783
|
THEME_MODES = ["dark", "light", "system"];
|
|
@@ -2069,13 +2071,14 @@ var init_db = __esm({
|
|
|
2069
2071
|
"../core/src/db.ts"() {
|
|
2070
2072
|
"use strict";
|
|
2071
2073
|
init_types();
|
|
2072
|
-
SCHEMA_VERSION =
|
|
2074
|
+
SCHEMA_VERSION = 45;
|
|
2073
2075
|
SCHEMA_SQL = `
|
|
2074
2076
|
-- Tasks table with JSON columns for nested data
|
|
2075
2077
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
2076
2078
|
id TEXT PRIMARY KEY,
|
|
2077
2079
|
title TEXT,
|
|
2078
2080
|
description TEXT NOT NULL,
|
|
2081
|
+
priority TEXT DEFAULT 'normal',
|
|
2079
2082
|
"column" TEXT NOT NULL,
|
|
2080
2083
|
status TEXT,
|
|
2081
2084
|
size TEXT,
|
|
@@ -2103,6 +2106,12 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2103
2106
|
summary TEXT,
|
|
2104
2107
|
thinkingLevel TEXT,
|
|
2105
2108
|
executionMode TEXT DEFAULT 'standard',
|
|
2109
|
+
tokenUsageInputTokens INTEGER,
|
|
2110
|
+
tokenUsageOutputTokens INTEGER,
|
|
2111
|
+
tokenUsageCachedTokens INTEGER,
|
|
2112
|
+
tokenUsageTotalTokens INTEGER,
|
|
2113
|
+
tokenUsageFirstUsedAt TEXT,
|
|
2114
|
+
tokenUsageLastUsedAt TEXT,
|
|
2106
2115
|
createdAt TEXT NOT NULL,
|
|
2107
2116
|
updatedAt TEXT NOT NULL,
|
|
2108
2117
|
columnMovedAt TEXT,
|
|
@@ -2116,6 +2125,11 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
2116
2125
|
workflowStepResults TEXT DEFAULT '[]',
|
|
2117
2126
|
prInfo TEXT,
|
|
2118
2127
|
issueInfo TEXT,
|
|
2128
|
+
sourceIssueProvider TEXT,
|
|
2129
|
+
sourceIssueRepository TEXT,
|
|
2130
|
+
sourceIssueExternalIssueId TEXT,
|
|
2131
|
+
sourceIssueNumber INTEGER,
|
|
2132
|
+
sourceIssueUrl TEXT,
|
|
2119
2133
|
mergeDetails TEXT,
|
|
2120
2134
|
breakIntoSubtasks INTEGER DEFAULT 0,
|
|
2121
2135
|
enabledWorkflowSteps TEXT DEFAULT '[]',
|
|
@@ -3410,6 +3424,35 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
3410
3424
|
`);
|
|
3411
3425
|
});
|
|
3412
3426
|
}
|
|
3427
|
+
if (version < 43) {
|
|
3428
|
+
this.applyMigration(43, () => {
|
|
3429
|
+
this.addColumnIfMissing("tasks", "priority", "TEXT DEFAULT 'normal'");
|
|
3430
|
+
this.db.exec(`
|
|
3431
|
+
UPDATE tasks
|
|
3432
|
+
SET priority = 'normal'
|
|
3433
|
+
WHERE priority IS NULL OR priority = '' OR priority NOT IN ('low', 'normal', 'high', 'urgent')
|
|
3434
|
+
`);
|
|
3435
|
+
});
|
|
3436
|
+
}
|
|
3437
|
+
if (version < 44) {
|
|
3438
|
+
this.applyMigration(44, () => {
|
|
3439
|
+
this.addColumnIfMissing("tasks", "tokenUsageInputTokens", "INTEGER");
|
|
3440
|
+
this.addColumnIfMissing("tasks", "tokenUsageOutputTokens", "INTEGER");
|
|
3441
|
+
this.addColumnIfMissing("tasks", "tokenUsageCachedTokens", "INTEGER");
|
|
3442
|
+
this.addColumnIfMissing("tasks", "tokenUsageTotalTokens", "INTEGER");
|
|
3443
|
+
this.addColumnIfMissing("tasks", "tokenUsageFirstUsedAt", "TEXT");
|
|
3444
|
+
this.addColumnIfMissing("tasks", "tokenUsageLastUsedAt", "TEXT");
|
|
3445
|
+
});
|
|
3446
|
+
}
|
|
3447
|
+
if (version < 45) {
|
|
3448
|
+
this.applyMigration(45, () => {
|
|
3449
|
+
this.addColumnIfMissing("tasks", "sourceIssueProvider", "TEXT");
|
|
3450
|
+
this.addColumnIfMissing("tasks", "sourceIssueRepository", "TEXT");
|
|
3451
|
+
this.addColumnIfMissing("tasks", "sourceIssueExternalIssueId", "TEXT");
|
|
3452
|
+
this.addColumnIfMissing("tasks", "sourceIssueNumber", "INTEGER");
|
|
3453
|
+
this.addColumnIfMissing("tasks", "sourceIssueUrl", "TEXT");
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3413
3456
|
}
|
|
3414
3457
|
/**
|
|
3415
3458
|
* Run a single migration step inside a transaction and bump the version.
|
|
@@ -5680,6 +5723,54 @@ var init_message_store = __esm({
|
|
|
5680
5723
|
}
|
|
5681
5724
|
});
|
|
5682
5725
|
|
|
5726
|
+
// ../core/src/task-priority.ts
|
|
5727
|
+
function isTaskPriority(value) {
|
|
5728
|
+
return typeof value === "string" && TASK_PRIORITIES.includes(value);
|
|
5729
|
+
}
|
|
5730
|
+
function normalizeTaskPriority(priority) {
|
|
5731
|
+
return isTaskPriority(priority) ? priority : DEFAULT_TASK_PRIORITY;
|
|
5732
|
+
}
|
|
5733
|
+
function getTaskPriorityRank(priority) {
|
|
5734
|
+
return PRIORITY_RANK[normalizeTaskPriority(priority)];
|
|
5735
|
+
}
|
|
5736
|
+
function compareTaskPriority(a, b) {
|
|
5737
|
+
return getTaskPriorityRank(b) - getTaskPriorityRank(a);
|
|
5738
|
+
}
|
|
5739
|
+
function compareTaskId(a, b) {
|
|
5740
|
+
const aNum = Number.parseInt(a.slice(a.lastIndexOf("-") + 1), 10);
|
|
5741
|
+
const bNum = Number.parseInt(b.slice(b.lastIndexOf("-") + 1), 10);
|
|
5742
|
+
if (Number.isFinite(aNum) && Number.isFinite(bNum) && aNum !== bNum) {
|
|
5743
|
+
return aNum - bNum;
|
|
5744
|
+
}
|
|
5745
|
+
return a.localeCompare(b);
|
|
5746
|
+
}
|
|
5747
|
+
function compareTasksByPriorityThenAgeAndId(a, b) {
|
|
5748
|
+
const priorityCmp = compareTaskPriority(a.priority, b.priority);
|
|
5749
|
+
if (priorityCmp !== 0) {
|
|
5750
|
+
return priorityCmp;
|
|
5751
|
+
}
|
|
5752
|
+
if (a.createdAt !== b.createdAt) {
|
|
5753
|
+
return a.createdAt.localeCompare(b.createdAt);
|
|
5754
|
+
}
|
|
5755
|
+
return compareTaskId(a.id, b.id);
|
|
5756
|
+
}
|
|
5757
|
+
function sortTasksByPriorityThenAgeAndId(tasks) {
|
|
5758
|
+
return [...tasks].sort(compareTasksByPriorityThenAgeAndId);
|
|
5759
|
+
}
|
|
5760
|
+
var PRIORITY_RANK;
|
|
5761
|
+
var init_task_priority = __esm({
|
|
5762
|
+
"../core/src/task-priority.ts"() {
|
|
5763
|
+
"use strict";
|
|
5764
|
+
init_types();
|
|
5765
|
+
PRIORITY_RANK = {
|
|
5766
|
+
low: 0,
|
|
5767
|
+
normal: 1,
|
|
5768
|
+
high: 2,
|
|
5769
|
+
urgent: 3
|
|
5770
|
+
};
|
|
5771
|
+
}
|
|
5772
|
+
});
|
|
5773
|
+
|
|
5683
5774
|
// ../core/src/global-settings.ts
|
|
5684
5775
|
import { homedir } from "node:os";
|
|
5685
5776
|
import { dirname, join as join4 } from "node:path";
|
|
@@ -6177,17 +6268,18 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6177
6268
|
let skipped = 0;
|
|
6178
6269
|
const insertStmt = db.prepare(`
|
|
6179
6270
|
INSERT OR REPLACE INTO tasks (
|
|
6180
|
-
id, title, description, "column", status, size, reviewLevel, currentStep,
|
|
6271
|
+
id, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
6181
6272
|
worktree, blockedBy, paused, baseBranch, baseCommitSha, modelPresetId,
|
|
6182
6273
|
modelProvider, modelId, validatorModelProvider, validatorModelId,
|
|
6183
6274
|
mergeRetries, recoveryRetryCount, nextRecoveryAt,
|
|
6184
6275
|
error, summary, thinkingLevel, createdAt, updatedAt,
|
|
6185
6276
|
columnMovedAt, dependencies, steps, log, attachments, steeringComments,
|
|
6186
|
-
comments, workflowStepResults, prInfo, issueInfo,
|
|
6187
|
-
|
|
6277
|
+
comments, workflowStepResults, prInfo, issueInfo,
|
|
6278
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
6279
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, sliceId
|
|
6188
6280
|
) VALUES (
|
|
6189
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
6190
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
6281
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
6282
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
6191
6283
|
)
|
|
6192
6284
|
`);
|
|
6193
6285
|
for (const entry of entries) {
|
|
@@ -6205,6 +6297,7 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6205
6297
|
task.id,
|
|
6206
6298
|
task.title ?? null,
|
|
6207
6299
|
task.description,
|
|
6300
|
+
normalizeTaskPriority(task.priority),
|
|
6208
6301
|
task.column,
|
|
6209
6302
|
task.status ?? null,
|
|
6210
6303
|
task.size ?? null,
|
|
@@ -6238,6 +6331,11 @@ async function migrateTasks(fusionDir, db) {
|
|
|
6238
6331
|
toJson(task.workflowStepResults || []),
|
|
6239
6332
|
toJsonNullable(task.prInfo),
|
|
6240
6333
|
toJsonNullable(task.issueInfo),
|
|
6334
|
+
task.sourceIssue?.provider ?? null,
|
|
6335
|
+
task.sourceIssue?.repository ?? null,
|
|
6336
|
+
task.sourceIssue?.externalIssueId ?? null,
|
|
6337
|
+
task.sourceIssue?.issueNumber ?? null,
|
|
6338
|
+
task.sourceIssue?.url ?? null,
|
|
6241
6339
|
toJsonNullable(task.mergeDetails),
|
|
6242
6340
|
task.breakIntoSubtasks ? 1 : 0,
|
|
6243
6341
|
toJson(task.enabledWorkflowSteps || []),
|
|
@@ -6488,6 +6586,7 @@ var init_db_migrate = __esm({
|
|
|
6488
6586
|
"../core/src/db-migrate.ts"() {
|
|
6489
6587
|
"use strict";
|
|
6490
6588
|
init_db();
|
|
6589
|
+
init_task_priority();
|
|
6491
6590
|
}
|
|
6492
6591
|
});
|
|
6493
6592
|
|
|
@@ -20322,7 +20421,7 @@ var require_luxon = __commonJS({
|
|
|
20322
20421
|
}, zone || mergedZone, next];
|
|
20323
20422
|
}, [{}, null, 1]).slice(0, 2);
|
|
20324
20423
|
}
|
|
20325
|
-
function
|
|
20424
|
+
function parse(s2, ...patterns) {
|
|
20326
20425
|
if (s2 == null) {
|
|
20327
20426
|
return [null, null];
|
|
20328
20427
|
}
|
|
@@ -20465,26 +20564,26 @@ var require_luxon = __commonJS({
|
|
|
20465
20564
|
var extractISOOrdinalDateAndTime = combineExtractors(extractISOOrdinalData, extractISOTime, extractISOOffset, extractIANAZone);
|
|
20466
20565
|
var extractISOTimeAndOffset = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);
|
|
20467
20566
|
function parseISODate(s2) {
|
|
20468
|
-
return
|
|
20567
|
+
return parse(s2, [isoYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [isoWeekWithTimeExtensionRegex, extractISOWeekTimeAndOffset], [isoOrdinalWithTimeExtensionRegex, extractISOOrdinalDateAndTime], [isoTimeCombinedRegex, extractISOTimeAndOffset]);
|
|
20469
20568
|
}
|
|
20470
20569
|
function parseRFC2822Date(s2) {
|
|
20471
|
-
return
|
|
20570
|
+
return parse(preprocessRFC2822(s2), [rfc2822, extractRFC2822]);
|
|
20472
20571
|
}
|
|
20473
20572
|
function parseHTTPDate(s2) {
|
|
20474
|
-
return
|
|
20573
|
+
return parse(s2, [rfc1123, extractRFC1123Or850], [rfc850, extractRFC1123Or850], [ascii, extractASCII]);
|
|
20475
20574
|
}
|
|
20476
20575
|
function parseISODuration(s2) {
|
|
20477
|
-
return
|
|
20576
|
+
return parse(s2, [isoDuration, extractISODuration]);
|
|
20478
20577
|
}
|
|
20479
20578
|
var extractISOTimeOnly = combineExtractors(extractISOTime);
|
|
20480
20579
|
function parseISOTimeOnly(s2) {
|
|
20481
|
-
return
|
|
20580
|
+
return parse(s2, [isoTimeOnly, extractISOTimeOnly]);
|
|
20482
20581
|
}
|
|
20483
20582
|
var sqlYmdWithTimeExtensionRegex = combineRegexes(sqlYmdRegex, sqlTimeExtensionRegex);
|
|
20484
20583
|
var sqlTimeCombinedRegex = combineRegexes(sqlTimeRegex);
|
|
20485
20584
|
var extractISOTimeOffsetAndIANAZone = combineExtractors(extractISOTime, extractISOOffset, extractIANAZone);
|
|
20486
20585
|
function parseSQL(s2) {
|
|
20487
|
-
return
|
|
20586
|
+
return parse(s2, [sqlYmdWithTimeExtensionRegex, extractISOYmdTimeAndOffset], [sqlTimeCombinedRegex, extractISOTimeOffsetAndIANAZone]);
|
|
20488
20587
|
}
|
|
20489
20588
|
var INVALID$2 = "Invalid Duration";
|
|
20490
20589
|
var lowOrderMatrix = {
|
|
@@ -26807,13 +26906,14 @@ __export(automation_store_exports, {
|
|
|
26807
26906
|
import { EventEmitter as EventEmitter10 } from "node:events";
|
|
26808
26907
|
import { join as join12 } from "node:path";
|
|
26809
26908
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
26810
|
-
var import_cron_parser, AutomationStore;
|
|
26909
|
+
var import_cron_parser, CRON_TIMEZONE, AutomationStore;
|
|
26811
26910
|
var init_automation_store = __esm({
|
|
26812
26911
|
"../core/src/automation-store.ts"() {
|
|
26813
26912
|
"use strict";
|
|
26814
26913
|
import_cron_parser = __toESM(require_dist2(), 1);
|
|
26815
26914
|
init_automation();
|
|
26816
26915
|
init_db();
|
|
26916
|
+
CRON_TIMEZONE = "UTC";
|
|
26817
26917
|
AutomationStore = class _AutomationStore extends EventEmitter10 {
|
|
26818
26918
|
constructor(rootDir) {
|
|
26819
26919
|
super();
|
|
@@ -26931,7 +27031,8 @@ var init_automation_store = __esm({
|
|
|
26931
27031
|
*/
|
|
26932
27032
|
computeNextRun(cronExpression, fromDate) {
|
|
26933
27033
|
const interval = import_cron_parser.CronExpressionParser.parse(cronExpression, {
|
|
26934
|
-
currentDate: fromDate ?? /* @__PURE__ */ new Date()
|
|
27034
|
+
currentDate: fromDate ?? /* @__PURE__ */ new Date(),
|
|
27035
|
+
tz: CRON_TIMEZONE
|
|
26935
27036
|
});
|
|
26936
27037
|
const next = interval.next();
|
|
26937
27038
|
return next.toISOString() ?? new Date(next.getTime()).toISOString();
|
|
@@ -28551,8 +28652,8 @@ This project has OpenClaw-style memory files:
|
|
|
28551
28652
|
- \`.fusion/memory/YYYY-MM-DD.md\` \u2014 append-only daily notes for running context
|
|
28552
28653
|
|
|
28553
28654
|
**Before writing the specification:**
|
|
28554
|
-
1. Use \`
|
|
28555
|
-
2. Use \`
|
|
28655
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28656
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28556
28657
|
3. Incorporate relevant learnings into your specification \u2014 reference actual patterns, constraints, and conventions documented there
|
|
28557
28658
|
|
|
28558
28659
|
Do not read all memory directly by default. If memory is irrelevant, skip it.
|
|
@@ -28564,8 +28665,8 @@ Do not read all memory directly by default. If memory is irrelevant, skip it.
|
|
|
28564
28665
|
This project has a memory system that stores durable project learnings.
|
|
28565
28666
|
|
|
28566
28667
|
**Before writing the specification:**
|
|
28567
|
-
1. Use \`
|
|
28568
|
-
2. Use \`
|
|
28668
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28669
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28569
28670
|
3. Incorporate useful learnings into your specification
|
|
28570
28671
|
|
|
28571
28672
|
**If the memory contains useful context for this task, reference it in the specification.**
|
|
@@ -28597,12 +28698,12 @@ This project has OpenClaw-style memory files:
|
|
|
28597
28698
|
- \`.fusion/memory/YYYY-MM-DD.md\` \u2014 append-only daily notes for running observations and open loops
|
|
28598
28699
|
|
|
28599
28700
|
**At the start of execution:**
|
|
28600
|
-
1. Use \`
|
|
28601
|
-
2. Use \`
|
|
28701
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28702
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28602
28703
|
3. Apply relevant learnings to your implementation \u2014 follow documented patterns and avoid known pitfalls
|
|
28603
28704
|
4. Do not load all memory directly by default. Skip memory reads when memory is irrelevant or context is tight.
|
|
28604
28705
|
|
|
28605
|
-
**At the end of execution (before calling \`
|
|
28706
|
+
**At the end of execution (before calling \`fn_task_done()\`):**
|
|
28606
28707
|
1. Review what you learned during this task that would genuinely benefit future runs
|
|
28607
28708
|
2. Write durable decisions, conventions, and pitfalls to \`.fusion/memory/MEMORY.md\`
|
|
28608
28709
|
3. Write running observations, unresolved context, and open loops to today's \`.fusion/memory/YYYY-MM-DD.md\`
|
|
@@ -28631,11 +28732,11 @@ This project has OpenClaw-style memory files:
|
|
|
28631
28732
|
This project has a memory system that stores durable project learnings accumulated from past task runs.
|
|
28632
28733
|
|
|
28633
28734
|
**At the start of execution:**
|
|
28634
|
-
1. Use \`
|
|
28635
|
-
2. Use \`
|
|
28735
|
+
1. Use \`fn_memory_search\` first for task-relevant context
|
|
28736
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28636
28737
|
3. Apply useful learnings to your implementation
|
|
28637
28738
|
|
|
28638
|
-
**At the end of execution (before calling \`
|
|
28739
|
+
**At the end of execution (before calling \`fn_task_done()\`):**
|
|
28639
28740
|
1. Review what you learned during this task that would genuinely benefit future runs
|
|
28640
28741
|
2. **If nothing durable was learned, skip the memory update entirely** \u2014 do not append trivial or task-specific notes
|
|
28641
28742
|
3. Only write when you have genuinely durable, reusable insights such as:
|
|
@@ -28663,8 +28764,8 @@ function buildReviewerMemoryInstructions(rootDir, settings) {
|
|
|
28663
28764
|
This project has a memory system that stores durable project learnings.
|
|
28664
28765
|
|
|
28665
28766
|
**During review:**
|
|
28666
|
-
1. Use \`
|
|
28667
|
-
2. Use \`
|
|
28767
|
+
1. Use \`fn_memory_search\` for task-relevant project conventions, pitfalls, and prior decisions when they could affect your verdict
|
|
28768
|
+
2. Use \`fn_memory_get\` only for specific memory files/line ranges returned by search
|
|
28668
28769
|
3. Treat documented durable conventions and pitfalls as review evidence when deciding APPROVE, REVISE, or RETHINK
|
|
28669
28770
|
4. Do not update memory during review; reviewer memory access is read-only
|
|
28670
28771
|
5. Skip memory reads when they are not relevant to the reviewed plan or code
|
|
@@ -28786,23 +28887,29 @@ var init_run_command = __esm({
|
|
|
28786
28887
|
});
|
|
28787
28888
|
|
|
28788
28889
|
// ../core/src/logger.ts
|
|
28890
|
+
function withSeverityMarker(level, payload) {
|
|
28891
|
+
return `${LOG_LEVEL_MARKER_PREFIX}${level}${LOG_LEVEL_MARKER_SUFFIX}${payload}`;
|
|
28892
|
+
}
|
|
28789
28893
|
function createLogger(prefix) {
|
|
28790
28894
|
const tag = `[${prefix}]`;
|
|
28791
28895
|
return {
|
|
28792
28896
|
log(message, ...args) {
|
|
28793
|
-
console.error(`${tag} ${message}
|
|
28897
|
+
console.error(withSeverityMarker("info", `${tag} ${message}`), ...args);
|
|
28794
28898
|
},
|
|
28795
28899
|
warn(message, ...args) {
|
|
28796
|
-
console.warn(`${tag} ${message}
|
|
28900
|
+
console.warn(withSeverityMarker("warn", `${tag} ${message}`), ...args);
|
|
28797
28901
|
},
|
|
28798
28902
|
error(message, ...args) {
|
|
28799
|
-
console.error(`${tag} ${message}
|
|
28903
|
+
console.error(withSeverityMarker("error", `${tag} ${message}`), ...args);
|
|
28800
28904
|
}
|
|
28801
28905
|
};
|
|
28802
28906
|
}
|
|
28907
|
+
var LOG_LEVEL_MARKER_PREFIX, LOG_LEVEL_MARKER_SUFFIX;
|
|
28803
28908
|
var init_logger = __esm({
|
|
28804
28909
|
"../core/src/logger.ts"() {
|
|
28805
28910
|
"use strict";
|
|
28911
|
+
LOG_LEVEL_MARKER_PREFIX = "\0fnlvl=";
|
|
28912
|
+
LOG_LEVEL_MARKER_SUFFIX = "\0";
|
|
28806
28913
|
}
|
|
28807
28914
|
});
|
|
28808
28915
|
|
|
@@ -28854,6 +28961,7 @@ var init_store = __esm({
|
|
|
28854
28961
|
"../core/src/store.ts"() {
|
|
28855
28962
|
"use strict";
|
|
28856
28963
|
init_types();
|
|
28964
|
+
init_task_priority();
|
|
28857
28965
|
init_global_settings();
|
|
28858
28966
|
init_db();
|
|
28859
28967
|
init_archive_db();
|
|
@@ -29043,6 +29151,7 @@ var init_store = __esm({
|
|
|
29043
29151
|
id: row.id,
|
|
29044
29152
|
title: row.title || void 0,
|
|
29045
29153
|
description: row.description,
|
|
29154
|
+
priority: normalizeTaskPriority(row.priority),
|
|
29046
29155
|
column: row.column,
|
|
29047
29156
|
status: row.status || void 0,
|
|
29048
29157
|
size: row.size || void 0,
|
|
@@ -29078,6 +29187,19 @@ var init_store = __esm({
|
|
|
29078
29187
|
dependencies: fromJson(row.dependencies) || [],
|
|
29079
29188
|
steps: fromJson(row.steps) || [],
|
|
29080
29189
|
log: fromJson(row.log) || [],
|
|
29190
|
+
tokenUsage: (() => {
|
|
29191
|
+
if (row.tokenUsageInputTokens === null || row.tokenUsageOutputTokens === null || row.tokenUsageCachedTokens === null || row.tokenUsageTotalTokens === null || row.tokenUsageFirstUsedAt === null || row.tokenUsageLastUsedAt === null) {
|
|
29192
|
+
return void 0;
|
|
29193
|
+
}
|
|
29194
|
+
return {
|
|
29195
|
+
inputTokens: row.tokenUsageInputTokens,
|
|
29196
|
+
outputTokens: row.tokenUsageOutputTokens,
|
|
29197
|
+
cachedTokens: row.tokenUsageCachedTokens,
|
|
29198
|
+
totalTokens: row.tokenUsageTotalTokens,
|
|
29199
|
+
firstUsedAt: row.tokenUsageFirstUsedAt,
|
|
29200
|
+
lastUsedAt: row.tokenUsageLastUsedAt
|
|
29201
|
+
};
|
|
29202
|
+
})(),
|
|
29081
29203
|
attachments: (() => {
|
|
29082
29204
|
const a = fromJson(row.attachments);
|
|
29083
29205
|
return a && a.length > 0 ? a : void 0;
|
|
@@ -29102,6 +29224,18 @@ var init_store = __esm({
|
|
|
29102
29224
|
})(),
|
|
29103
29225
|
prInfo: fromJson(row.prInfo),
|
|
29104
29226
|
issueInfo: fromJson(row.issueInfo),
|
|
29227
|
+
sourceIssue: (() => {
|
|
29228
|
+
if (row.sourceIssueProvider === null || row.sourceIssueRepository === null || row.sourceIssueExternalIssueId === null || row.sourceIssueNumber === null) {
|
|
29229
|
+
return void 0;
|
|
29230
|
+
}
|
|
29231
|
+
return {
|
|
29232
|
+
provider: row.sourceIssueProvider,
|
|
29233
|
+
repository: row.sourceIssueRepository,
|
|
29234
|
+
externalIssueId: row.sourceIssueExternalIssueId,
|
|
29235
|
+
issueNumber: row.sourceIssueNumber,
|
|
29236
|
+
url: row.sourceIssueUrl ?? void 0
|
|
29237
|
+
};
|
|
29238
|
+
})(),
|
|
29105
29239
|
mergeDetails: fromJson(row.mergeDetails),
|
|
29106
29240
|
breakIntoSubtasks: row.breakIntoSubtasks ? true : void 0,
|
|
29107
29241
|
enabledWorkflowSteps: (() => {
|
|
@@ -29125,6 +29259,7 @@ var init_store = __esm({
|
|
|
29125
29259
|
id: entry.id,
|
|
29126
29260
|
title: entry.title,
|
|
29127
29261
|
description: entry.description,
|
|
29262
|
+
priority: normalizeTaskPriority(entry.priority),
|
|
29128
29263
|
column: "archived",
|
|
29129
29264
|
dependencies: entry.dependencies ?? [],
|
|
29130
29265
|
steps: entry.steps ?? [],
|
|
@@ -29133,6 +29268,7 @@ var init_store = __esm({
|
|
|
29133
29268
|
reviewLevel: entry.reviewLevel,
|
|
29134
29269
|
prInfo: slim ? void 0 : entry.prInfo,
|
|
29135
29270
|
issueInfo: slim ? void 0 : entry.issueInfo,
|
|
29271
|
+
sourceIssue: slim ? void 0 : entry.sourceIssue,
|
|
29136
29272
|
attachments: slim ? void 0 : entry.attachments,
|
|
29137
29273
|
comments: entry.comments,
|
|
29138
29274
|
log: slim ? [] : entry.log ?? [],
|
|
@@ -29221,6 +29357,7 @@ ${recentText}` : void 0
|
|
|
29221
29357
|
id: task.id,
|
|
29222
29358
|
title: task.title,
|
|
29223
29359
|
description: task.description,
|
|
29360
|
+
priority: normalizeTaskPriority(task.priority),
|
|
29224
29361
|
column: "archived",
|
|
29225
29362
|
dependencies: task.dependencies,
|
|
29226
29363
|
steps: task.steps,
|
|
@@ -29229,6 +29366,7 @@ ${recentText}` : void 0
|
|
|
29229
29366
|
reviewLevel: task.reviewLevel,
|
|
29230
29367
|
prInfo: task.prInfo,
|
|
29231
29368
|
issueInfo: task.issueInfo,
|
|
29369
|
+
sourceIssue: task.sourceIssue,
|
|
29232
29370
|
attachments: task.attachments,
|
|
29233
29371
|
comments: task.comments,
|
|
29234
29372
|
prompt,
|
|
@@ -29297,6 +29435,7 @@ ${recentText}` : void 0
|
|
|
29297
29435
|
"id",
|
|
29298
29436
|
"title",
|
|
29299
29437
|
"description",
|
|
29438
|
+
"priority",
|
|
29300
29439
|
'"column"',
|
|
29301
29440
|
"status",
|
|
29302
29441
|
"size",
|
|
@@ -29326,6 +29465,12 @@ ${recentText}` : void 0
|
|
|
29326
29465
|
"summary",
|
|
29327
29466
|
"thinkingLevel",
|
|
29328
29467
|
"executionMode",
|
|
29468
|
+
"tokenUsageInputTokens",
|
|
29469
|
+
"tokenUsageOutputTokens",
|
|
29470
|
+
"tokenUsageCachedTokens",
|
|
29471
|
+
"tokenUsageTotalTokens",
|
|
29472
|
+
"tokenUsageFirstUsedAt",
|
|
29473
|
+
"tokenUsageLastUsedAt",
|
|
29329
29474
|
"createdAt",
|
|
29330
29475
|
"updatedAt",
|
|
29331
29476
|
"columnMovedAt",
|
|
@@ -29337,6 +29482,11 @@ ${recentText}` : void 0
|
|
|
29337
29482
|
"attachments",
|
|
29338
29483
|
"prInfo",
|
|
29339
29484
|
"issueInfo",
|
|
29485
|
+
"sourceIssueProvider",
|
|
29486
|
+
"sourceIssueRepository",
|
|
29487
|
+
"sourceIssueExternalIssueId",
|
|
29488
|
+
"sourceIssueNumber",
|
|
29489
|
+
"sourceIssueUrl",
|
|
29340
29490
|
"mergeDetails",
|
|
29341
29491
|
"breakIntoSubtasks",
|
|
29342
29492
|
"enabledWorkflowSteps",
|
|
@@ -29354,6 +29504,7 @@ ${recentText}` : void 0
|
|
|
29354
29504
|
"id",
|
|
29355
29505
|
"title",
|
|
29356
29506
|
"description",
|
|
29507
|
+
"priority",
|
|
29357
29508
|
'"column"',
|
|
29358
29509
|
"status",
|
|
29359
29510
|
"size",
|
|
@@ -29383,6 +29534,12 @@ ${recentText}` : void 0
|
|
|
29383
29534
|
"summary",
|
|
29384
29535
|
"thinkingLevel",
|
|
29385
29536
|
"executionMode",
|
|
29537
|
+
"tokenUsageInputTokens",
|
|
29538
|
+
"tokenUsageOutputTokens",
|
|
29539
|
+
"tokenUsageCachedTokens",
|
|
29540
|
+
"tokenUsageTotalTokens",
|
|
29541
|
+
"tokenUsageFirstUsedAt",
|
|
29542
|
+
"tokenUsageLastUsedAt",
|
|
29386
29543
|
"createdAt",
|
|
29387
29544
|
"updatedAt",
|
|
29388
29545
|
"columnMovedAt",
|
|
@@ -29394,6 +29551,11 @@ ${recentText}` : void 0
|
|
|
29394
29551
|
"workflowStepResults",
|
|
29395
29552
|
"prInfo",
|
|
29396
29553
|
"issueInfo",
|
|
29554
|
+
"sourceIssueProvider",
|
|
29555
|
+
"sourceIssueRepository",
|
|
29556
|
+
"sourceIssueExternalIssueId",
|
|
29557
|
+
"sourceIssueNumber",
|
|
29558
|
+
"sourceIssueUrl",
|
|
29397
29559
|
"mergeDetails",
|
|
29398
29560
|
"breakIntoSubtasks",
|
|
29399
29561
|
"enabledWorkflowSteps",
|
|
@@ -29431,21 +29593,23 @@ ${recentText}` : void 0
|
|
|
29431
29593
|
upsertTask(task) {
|
|
29432
29594
|
this.db.prepare(`
|
|
29433
29595
|
INSERT INTO tasks (
|
|
29434
|
-
id, title, description, "column", status, size, reviewLevel, currentStep,
|
|
29596
|
+
id, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
29435
29597
|
worktree, blockedBy, paused, baseBranch, branch, baseCommitSha, modelPresetId, modelProvider,
|
|
29436
29598
|
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
29437
29599
|
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, nextRecoveryAt, error,
|
|
29438
|
-
summary, thinkingLevel, executionMode,
|
|
29600
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
29601
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
29439
29602
|
dependencies, steps, log, attachments, steeringComments,
|
|
29440
|
-
comments, workflowStepResults, prInfo, issueInfo,
|
|
29441
|
-
|
|
29603
|
+
comments, workflowStepResults, prInfo, issueInfo,
|
|
29604
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
29605
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, checkedOutBy, checkedOutAt
|
|
29442
29606
|
) VALUES (
|
|
29443
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
29444
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
29607
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
29445
29608
|
)
|
|
29446
29609
|
ON CONFLICT(id) DO UPDATE SET
|
|
29447
29610
|
title = excluded.title,
|
|
29448
29611
|
description = excluded.description,
|
|
29612
|
+
priority = excluded.priority,
|
|
29449
29613
|
"column" = excluded."column",
|
|
29450
29614
|
status = excluded.status,
|
|
29451
29615
|
size = excluded.size,
|
|
@@ -29475,6 +29639,12 @@ ${recentText}` : void 0
|
|
|
29475
29639
|
summary = excluded.summary,
|
|
29476
29640
|
thinkingLevel = excluded.thinkingLevel,
|
|
29477
29641
|
executionMode = excluded.executionMode,
|
|
29642
|
+
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
29643
|
+
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
29644
|
+
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
29645
|
+
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
29646
|
+
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
29647
|
+
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
29478
29648
|
createdAt = excluded.createdAt,
|
|
29479
29649
|
updatedAt = excluded.updatedAt,
|
|
29480
29650
|
columnMovedAt = excluded.columnMovedAt,
|
|
@@ -29487,6 +29657,11 @@ ${recentText}` : void 0
|
|
|
29487
29657
|
workflowStepResults = excluded.workflowStepResults,
|
|
29488
29658
|
prInfo = excluded.prInfo,
|
|
29489
29659
|
issueInfo = excluded.issueInfo,
|
|
29660
|
+
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
29661
|
+
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
29662
|
+
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
29663
|
+
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
29664
|
+
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
29490
29665
|
mergeDetails = excluded.mergeDetails,
|
|
29491
29666
|
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
29492
29667
|
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
@@ -29501,6 +29676,7 @@ ${recentText}` : void 0
|
|
|
29501
29676
|
task.id,
|
|
29502
29677
|
task.title ?? null,
|
|
29503
29678
|
task.description,
|
|
29679
|
+
normalizeTaskPriority(task.priority),
|
|
29504
29680
|
task.column,
|
|
29505
29681
|
task.status ?? null,
|
|
29506
29682
|
task.size ?? null,
|
|
@@ -29530,6 +29706,12 @@ ${recentText}` : void 0
|
|
|
29530
29706
|
task.summary ?? null,
|
|
29531
29707
|
task.thinkingLevel ?? null,
|
|
29532
29708
|
task.executionMode ?? null,
|
|
29709
|
+
task.tokenUsage?.inputTokens ?? null,
|
|
29710
|
+
task.tokenUsage?.outputTokens ?? null,
|
|
29711
|
+
task.tokenUsage?.cachedTokens ?? null,
|
|
29712
|
+
task.tokenUsage?.totalTokens ?? null,
|
|
29713
|
+
task.tokenUsage?.firstUsedAt ?? null,
|
|
29714
|
+
task.tokenUsage?.lastUsedAt ?? null,
|
|
29533
29715
|
task.createdAt,
|
|
29534
29716
|
task.updatedAt,
|
|
29535
29717
|
task.columnMovedAt ?? null,
|
|
@@ -29542,6 +29724,11 @@ ${recentText}` : void 0
|
|
|
29542
29724
|
toJson(task.workflowStepResults || []),
|
|
29543
29725
|
toJsonNullable(task.prInfo),
|
|
29544
29726
|
toJsonNullable(task.issueInfo),
|
|
29727
|
+
task.sourceIssue?.provider ?? null,
|
|
29728
|
+
task.sourceIssue?.repository ?? null,
|
|
29729
|
+
task.sourceIssue?.externalIssueId ?? null,
|
|
29730
|
+
task.sourceIssue?.issueNumber ?? null,
|
|
29731
|
+
task.sourceIssue?.url ?? null,
|
|
29545
29732
|
toJsonNullable(task.mergeDetails),
|
|
29546
29733
|
task.breakIntoSubtasks ? 1 : 0,
|
|
29547
29734
|
toJson(task.enabledWorkflowSteps || []),
|
|
@@ -29746,6 +29933,7 @@ ${recentText}` : void 0
|
|
|
29746
29933
|
if (!Array.isArray(fileTask.log)) fileTask.log = [];
|
|
29747
29934
|
if (!Array.isArray(fileTask.dependencies)) fileTask.dependencies = [];
|
|
29748
29935
|
if (!Array.isArray(fileTask.steps)) fileTask.steps = [];
|
|
29936
|
+
fileTask.priority = normalizeTaskPriority(fileTask.priority);
|
|
29749
29937
|
return fileTask;
|
|
29750
29938
|
} catch (err) {
|
|
29751
29939
|
throw new Error(
|
|
@@ -30280,6 +30468,9 @@ ${recentText}` : void 0
|
|
|
30280
30468
|
id,
|
|
30281
30469
|
title,
|
|
30282
30470
|
description: input.description,
|
|
30471
|
+
priority: normalizeTaskPriority(input.priority),
|
|
30472
|
+
tokenUsage: input.tokenUsage,
|
|
30473
|
+
sourceIssue: input.sourceIssue,
|
|
30283
30474
|
column: input.column || "triage",
|
|
30284
30475
|
dependencies: input.dependencies || [],
|
|
30285
30476
|
breakIntoSubtasks: input.breakIntoSubtasks === true ? true : void 0,
|
|
@@ -30334,6 +30525,7 @@ ${task.description}
|
|
|
30334
30525
|
description: `${sourceTask.description}
|
|
30335
30526
|
|
|
30336
30527
|
(Duplicated from ${id})`,
|
|
30528
|
+
priority: normalizeTaskPriority(sourceTask.priority),
|
|
30337
30529
|
column: "triage",
|
|
30338
30530
|
modelPresetId: sourceTask.modelPresetId,
|
|
30339
30531
|
dependencies: [],
|
|
@@ -30392,6 +30584,7 @@ ${task.description}
|
|
|
30392
30584
|
description: `${feedback.trim()}
|
|
30393
30585
|
|
|
30394
30586
|
Refines: ${id}`,
|
|
30587
|
+
priority: normalizeTaskPriority(sourceTask.priority),
|
|
30395
30588
|
column: "triage",
|
|
30396
30589
|
dependencies: [id],
|
|
30397
30590
|
// Refinement depends on the original being complete
|
|
@@ -30716,6 +30909,11 @@ ${newTask.description}
|
|
|
30716
30909
|
}
|
|
30717
30910
|
if (updates.title !== void 0) task.title = updates.title;
|
|
30718
30911
|
if (updates.description !== void 0) task.description = updates.description;
|
|
30912
|
+
if (updates.priority === null) {
|
|
30913
|
+
task.priority = normalizeTaskPriority(void 0);
|
|
30914
|
+
} else if (updates.priority !== void 0) {
|
|
30915
|
+
task.priority = normalizeTaskPriority(updates.priority);
|
|
30916
|
+
}
|
|
30719
30917
|
if (updates.worktree === null) {
|
|
30720
30918
|
task.worktree = void 0;
|
|
30721
30919
|
} else if (updates.worktree !== void 0) {
|
|
@@ -30883,6 +31081,16 @@ ${newTask.description}
|
|
|
30883
31081
|
} else if (updates.mergeDetails !== void 0) {
|
|
30884
31082
|
task.mergeDetails = updates.mergeDetails;
|
|
30885
31083
|
}
|
|
31084
|
+
if (updates.sourceIssue === null) {
|
|
31085
|
+
task.sourceIssue = void 0;
|
|
31086
|
+
} else if (updates.sourceIssue !== void 0) {
|
|
31087
|
+
task.sourceIssue = updates.sourceIssue;
|
|
31088
|
+
}
|
|
31089
|
+
if (updates.tokenUsage === null) {
|
|
31090
|
+
task.tokenUsage = void 0;
|
|
31091
|
+
} else if (updates.tokenUsage !== void 0) {
|
|
31092
|
+
task.tokenUsage = updates.tokenUsage;
|
|
31093
|
+
}
|
|
30886
31094
|
if (updates.modifiedFiles === null) {
|
|
30887
31095
|
task.modifiedFiles = void 0;
|
|
30888
31096
|
} else if (updates.modifiedFiles !== void 0) {
|
|
@@ -31283,14 +31491,14 @@ ${task.description}
|
|
|
31283
31491
|
}
|
|
31284
31492
|
return paths;
|
|
31285
31493
|
}
|
|
31286
|
-
async deleteTask(id) {
|
|
31494
|
+
async deleteTask(id, options) {
|
|
31287
31495
|
return this.withTaskLock(id, async () => {
|
|
31288
31496
|
const task = this.readTaskFromDb(id);
|
|
31289
31497
|
if (!task) {
|
|
31290
31498
|
throw new Error(`Task ${id} not found`);
|
|
31291
31499
|
}
|
|
31292
31500
|
const dependentIds = this.findLiveDependents(id);
|
|
31293
|
-
if (dependentIds.length > 0) {
|
|
31501
|
+
if (dependentIds.length > 0 && !options?.removeDependencyReferences) {
|
|
31294
31502
|
throw new TaskHasDependentsError(id, dependentIds);
|
|
31295
31503
|
}
|
|
31296
31504
|
const cleanedBranches = await this.cleanupBranchForTask(task);
|
|
@@ -31301,18 +31509,50 @@ ${task.description}
|
|
|
31301
31509
|
action: `Cleaned up branch: ${cleanedBranches.join(", ")}`
|
|
31302
31510
|
});
|
|
31303
31511
|
}
|
|
31304
|
-
|
|
31305
|
-
this.db.bumpLastModified();
|
|
31512
|
+
const rewrittenDependents = this.rewriteDependentsAndDeleteTask(id, dependentIds);
|
|
31306
31513
|
if (this.isWatching) this.taskCache.delete(id);
|
|
31307
31514
|
const dir = this.taskDir(id);
|
|
31308
31515
|
if (existsSync12(dir)) {
|
|
31309
|
-
const { rm:
|
|
31310
|
-
await
|
|
31516
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
31517
|
+
await rm3(dir, { recursive: true });
|
|
31518
|
+
}
|
|
31519
|
+
for (const dependentTask of rewrittenDependents) {
|
|
31520
|
+
this.emit("task:updated", dependentTask);
|
|
31311
31521
|
}
|
|
31312
31522
|
this.emit("task:deleted", task);
|
|
31313
31523
|
return task;
|
|
31314
31524
|
});
|
|
31315
31525
|
}
|
|
31526
|
+
rewriteDependentsAndDeleteTask(taskId, dependentIds) {
|
|
31527
|
+
const rewrittenDependents = [];
|
|
31528
|
+
this.db.transaction(() => {
|
|
31529
|
+
for (const dependentId of dependentIds) {
|
|
31530
|
+
const dependentTask = this.readTaskFromDb(dependentId);
|
|
31531
|
+
if (!dependentTask) continue;
|
|
31532
|
+
const nextDependencies = dependentTask.dependencies.filter((dependencyId) => dependencyId !== taskId);
|
|
31533
|
+
if (nextDependencies.length === dependentTask.dependencies.length) {
|
|
31534
|
+
continue;
|
|
31535
|
+
}
|
|
31536
|
+
const updatedDependent = {
|
|
31537
|
+
...dependentTask,
|
|
31538
|
+
dependencies: nextDependencies,
|
|
31539
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31540
|
+
};
|
|
31541
|
+
this.db.prepare("UPDATE tasks SET dependencies = ?, updatedAt = ? WHERE id = ?").run(
|
|
31542
|
+
toJson(updatedDependent.dependencies),
|
|
31543
|
+
updatedDependent.updatedAt,
|
|
31544
|
+
updatedDependent.id
|
|
31545
|
+
);
|
|
31546
|
+
if (this.isWatching) {
|
|
31547
|
+
this.taskCache.set(updatedDependent.id, updatedDependent);
|
|
31548
|
+
}
|
|
31549
|
+
rewrittenDependents.push(updatedDependent);
|
|
31550
|
+
}
|
|
31551
|
+
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(taskId);
|
|
31552
|
+
this.db.bumpLastModified();
|
|
31553
|
+
});
|
|
31554
|
+
return rewrittenDependents;
|
|
31555
|
+
}
|
|
31316
31556
|
/**
|
|
31317
31557
|
* Clean up the git branch associated with a task.
|
|
31318
31558
|
*
|
|
@@ -31609,8 +31849,8 @@ ${task.description}
|
|
|
31609
31849
|
this.archiveDb.upsert(entry);
|
|
31610
31850
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
|
|
31611
31851
|
this.db.bumpLastModified();
|
|
31612
|
-
const { rm:
|
|
31613
|
-
await
|
|
31852
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
31853
|
+
await rm3(dir, { recursive: true, force: true });
|
|
31614
31854
|
if (this.isWatching) {
|
|
31615
31855
|
this.taskCache.delete(id);
|
|
31616
31856
|
}
|
|
@@ -32088,24 +32328,64 @@ ${task.description}
|
|
|
32088
32328
|
this.emit("task:updated", task2);
|
|
32089
32329
|
return task2;
|
|
32090
32330
|
});
|
|
32331
|
+
const commentContextBase = {
|
|
32332
|
+
taskId: id,
|
|
32333
|
+
author,
|
|
32334
|
+
commentLength: text.length,
|
|
32335
|
+
column: task.column,
|
|
32336
|
+
priorStatus: task.status ?? null
|
|
32337
|
+
};
|
|
32338
|
+
if (runContext) {
|
|
32339
|
+
commentContextBase.runId = runContext.runId;
|
|
32340
|
+
commentContextBase.agentId = runContext.agentId;
|
|
32341
|
+
if (runContext.source) {
|
|
32342
|
+
commentContextBase.runSource = runContext.source;
|
|
32343
|
+
}
|
|
32344
|
+
}
|
|
32091
32345
|
if (task.column === "done" && author === "user" && !options?.skipRefinement) {
|
|
32092
32346
|
try {
|
|
32093
32347
|
await this.refineTask(id, text);
|
|
32094
|
-
} catch {
|
|
32348
|
+
} catch (err) {
|
|
32349
|
+
storeLog.warn("Best-effort post-comment auto-refinement failed", {
|
|
32350
|
+
...commentContextBase,
|
|
32351
|
+
phase: "addComment:auto-refinement",
|
|
32352
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32353
|
+
});
|
|
32095
32354
|
}
|
|
32096
32355
|
}
|
|
32097
32356
|
if (task.column === "triage" && task.status === "awaiting-approval" && author === "user") {
|
|
32357
|
+
let invalidatedStatus = false;
|
|
32098
32358
|
try {
|
|
32099
32359
|
await this.updateTask(id, {
|
|
32100
32360
|
status: "needs-respecify"
|
|
32101
32361
|
});
|
|
32102
|
-
|
|
32103
|
-
|
|
32104
|
-
|
|
32105
|
-
|
|
32106
|
-
|
|
32107
|
-
|
|
32108
|
-
|
|
32362
|
+
invalidatedStatus = true;
|
|
32363
|
+
} catch (err) {
|
|
32364
|
+
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
|
|
32365
|
+
...commentContextBase,
|
|
32366
|
+
phase: "addComment:awaiting-approval-invalidation",
|
|
32367
|
+
stage: "status-update",
|
|
32368
|
+
nextStatus: "needs-respecify",
|
|
32369
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32370
|
+
});
|
|
32371
|
+
}
|
|
32372
|
+
if (invalidatedStatus) {
|
|
32373
|
+
try {
|
|
32374
|
+
await this.logEntry(
|
|
32375
|
+
id,
|
|
32376
|
+
`User comment invalidated spec approval \u2014 task needs re-specification`,
|
|
32377
|
+
void 0,
|
|
32378
|
+
runContext
|
|
32379
|
+
);
|
|
32380
|
+
} catch (err) {
|
|
32381
|
+
storeLog.warn("Best-effort post-comment awaiting-approval invalidation failed", {
|
|
32382
|
+
...commentContextBase,
|
|
32383
|
+
phase: "addComment:awaiting-approval-invalidation",
|
|
32384
|
+
stage: "post-invalidation-log-entry",
|
|
32385
|
+
nextStatus: "needs-respecify",
|
|
32386
|
+
error: err instanceof Error ? err.message : String(err)
|
|
32387
|
+
});
|
|
32388
|
+
}
|
|
32109
32389
|
}
|
|
32110
32390
|
}
|
|
32111
32391
|
return task;
|
|
@@ -32381,7 +32661,7 @@ ${task.description}
|
|
|
32381
32661
|
const rows2 = this.db.prepare(`
|
|
32382
32662
|
SELECT * FROM agentLogEntries
|
|
32383
32663
|
WHERE taskId = ?
|
|
32384
|
-
ORDER BY timestamp DESC
|
|
32664
|
+
ORDER BY timestamp DESC, id DESC
|
|
32385
32665
|
LIMIT ?
|
|
32386
32666
|
`).all(taskId, readCount);
|
|
32387
32667
|
const entries2 = rows2.map((row) => this.mapAgentLogRow(row)).reverse();
|
|
@@ -32393,7 +32673,7 @@ ${task.description}
|
|
|
32393
32673
|
const rows = this.db.prepare(`
|
|
32394
32674
|
SELECT * FROM agentLogEntries
|
|
32395
32675
|
WHERE taskId = ?
|
|
32396
|
-
ORDER BY timestamp ASC
|
|
32676
|
+
ORDER BY timestamp ASC, id ASC
|
|
32397
32677
|
`).all(taskId);
|
|
32398
32678
|
const entries = rows.map((row) => this.mapAgentLogRow(row));
|
|
32399
32679
|
if (offset > 0) {
|
|
@@ -32426,7 +32706,7 @@ ${task.description}
|
|
|
32426
32706
|
const rows = this.db.prepare(`
|
|
32427
32707
|
SELECT * FROM agentLogEntries
|
|
32428
32708
|
WHERE taskId = ? AND timestamp >= ? AND timestamp <= ?
|
|
32429
|
-
ORDER BY timestamp ASC
|
|
32709
|
+
ORDER BY timestamp ASC, id ASC
|
|
32430
32710
|
`).all(taskId, startIso, end);
|
|
32431
32711
|
return rows.map((row) => this.mapAgentLogRow(row));
|
|
32432
32712
|
}
|
|
@@ -32522,14 +32802,14 @@ ${task.description}
|
|
|
32522
32802
|
if (rows.length === 0) {
|
|
32523
32803
|
return;
|
|
32524
32804
|
}
|
|
32525
|
-
const { rm:
|
|
32805
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
32526
32806
|
for (const row of rows) {
|
|
32527
32807
|
const task = this.rowToTask(row);
|
|
32528
32808
|
const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
32529
32809
|
const entry = await this.taskToArchiveEntry(task, archivedAt);
|
|
32530
32810
|
this.archiveDb.upsert(entry);
|
|
32531
32811
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
32532
|
-
await
|
|
32812
|
+
await rm3(this.taskDir(task.id), { recursive: true, force: true });
|
|
32533
32813
|
if (this.isWatching) {
|
|
32534
32814
|
this.taskCache.delete(task.id);
|
|
32535
32815
|
}
|
|
@@ -32552,8 +32832,8 @@ ${task.description}
|
|
|
32552
32832
|
this.archiveDb.upsert(entry);
|
|
32553
32833
|
this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
|
|
32554
32834
|
this.db.bumpLastModified();
|
|
32555
|
-
const { rm:
|
|
32556
|
-
await
|
|
32835
|
+
const { rm: rm3 } = await import("node:fs/promises");
|
|
32836
|
+
await rm3(dir, { recursive: true, force: true });
|
|
32557
32837
|
if (this.isWatching) {
|
|
32558
32838
|
this.taskCache.delete(task.id);
|
|
32559
32839
|
}
|
|
@@ -32576,6 +32856,7 @@ ${task.description}
|
|
|
32576
32856
|
id: entry.id,
|
|
32577
32857
|
title: entry.title,
|
|
32578
32858
|
description: entry.description,
|
|
32859
|
+
priority: normalizeTaskPriority(entry.priority),
|
|
32579
32860
|
column: "archived",
|
|
32580
32861
|
// Will be changed to "done" by unarchiveTask
|
|
32581
32862
|
dependencies: entry.dependencies,
|
|
@@ -32585,6 +32866,7 @@ ${task.description}
|
|
|
32585
32866
|
reviewLevel: entry.reviewLevel,
|
|
32586
32867
|
prInfo: entry.prInfo,
|
|
32587
32868
|
issueInfo: entry.issueInfo,
|
|
32869
|
+
sourceIssue: entry.sourceIssue,
|
|
32588
32870
|
attachments: entry.attachments,
|
|
32589
32871
|
log: [...entry.log, { timestamp: (/* @__PURE__ */ new Date()).toISOString(), action: "Task restored from archive" }],
|
|
32590
32872
|
comments: entry.comments,
|
|
@@ -33194,6 +33476,28 @@ var init_daemon_token = __esm({
|
|
|
33194
33476
|
const settings = await this.settingsStore.getSettings();
|
|
33195
33477
|
return settings.daemonToken;
|
|
33196
33478
|
}
|
|
33479
|
+
/**
|
|
33480
|
+
* Retrieve the existing daemon token or create/persist one if missing.
|
|
33481
|
+
*
|
|
33482
|
+
* Safe for concurrent callers: if another process writes the token between
|
|
33483
|
+
* the initial read and generateToken(), this method re-reads and returns the
|
|
33484
|
+
* persisted token instead of failing.
|
|
33485
|
+
*/
|
|
33486
|
+
async getOrCreateToken() {
|
|
33487
|
+
const existing = await this.getToken();
|
|
33488
|
+
if (existing) {
|
|
33489
|
+
return existing;
|
|
33490
|
+
}
|
|
33491
|
+
try {
|
|
33492
|
+
return await this.generateToken();
|
|
33493
|
+
} catch (error) {
|
|
33494
|
+
const afterRace = await this.getToken();
|
|
33495
|
+
if (afterRace) {
|
|
33496
|
+
return afterRace;
|
|
33497
|
+
}
|
|
33498
|
+
throw error;
|
|
33499
|
+
}
|
|
33500
|
+
}
|
|
33197
33501
|
/**
|
|
33198
33502
|
* Validate that a provided token matches the stored token.
|
|
33199
33503
|
*
|
|
@@ -33637,13 +33941,14 @@ __export(routine_store_exports, {
|
|
|
33637
33941
|
});
|
|
33638
33942
|
import { EventEmitter as EventEmitter12 } from "node:events";
|
|
33639
33943
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
33640
|
-
var import_cron_parser2, RoutineStore;
|
|
33944
|
+
var import_cron_parser2, CRON_TIMEZONE2, RoutineStore;
|
|
33641
33945
|
var init_routine_store = __esm({
|
|
33642
33946
|
"../core/src/routine-store.ts"() {
|
|
33643
33947
|
"use strict";
|
|
33644
33948
|
import_cron_parser2 = __toESM(require_dist2(), 1);
|
|
33645
33949
|
init_db();
|
|
33646
33950
|
init_routine();
|
|
33951
|
+
CRON_TIMEZONE2 = "UTC";
|
|
33647
33952
|
RoutineStore = class _RoutineStore extends EventEmitter12 {
|
|
33648
33953
|
constructor(rootDir) {
|
|
33649
33954
|
super();
|
|
@@ -33806,7 +34111,8 @@ var init_routine_store = __esm({
|
|
|
33806
34111
|
*/
|
|
33807
34112
|
computeNextRun(cronExpression, fromDate) {
|
|
33808
34113
|
const interval = import_cron_parser2.CronExpressionParser.parse(cronExpression, {
|
|
33809
|
-
currentDate: fromDate ?? /* @__PURE__ */ new Date()
|
|
34114
|
+
currentDate: fromDate ?? /* @__PURE__ */ new Date(),
|
|
34115
|
+
tz: CRON_TIMEZONE2
|
|
33810
34116
|
});
|
|
33811
34117
|
const next = interval.next();
|
|
33812
34118
|
return new Date(next.getTime()).toISOString();
|
|
@@ -34048,12 +34354,11 @@ var init_routine_store = __esm({
|
|
|
34048
34354
|
});
|
|
34049
34355
|
|
|
34050
34356
|
// ../core/src/plugin-loader.ts
|
|
34051
|
-
import {
|
|
34052
|
-
import { copyFile,
|
|
34053
|
-
import { isAbsolute as isAbsolute5, parse, resolve as resolve7 } from "node:path";
|
|
34357
|
+
import { basename as basename6, dirname as dirname5, extname, isAbsolute as isAbsolute5, resolve as resolve7 } from "node:path";
|
|
34358
|
+
import { copyFile, rm } from "node:fs/promises";
|
|
34054
34359
|
import { pathToFileURL } from "node:url";
|
|
34055
34360
|
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
34056
|
-
var MINIMUM_FUSION_VERSION, log, PluginLoader;
|
|
34361
|
+
var MINIMUM_FUSION_VERSION, log, moduleImportVersion, PluginLoader;
|
|
34057
34362
|
var init_plugin_loader = __esm({
|
|
34058
34363
|
"../core/src/plugin-loader.ts"() {
|
|
34059
34364
|
"use strict";
|
|
@@ -34061,6 +34366,7 @@ var init_plugin_loader = __esm({
|
|
|
34061
34366
|
init_logger();
|
|
34062
34367
|
MINIMUM_FUSION_VERSION = "0.1.0";
|
|
34063
34368
|
log = createLogger("plugin-loader");
|
|
34369
|
+
moduleImportVersion = 0;
|
|
34064
34370
|
PluginLoader = class extends EventEmitter13 {
|
|
34065
34371
|
constructor(options) {
|
|
34066
34372
|
super();
|
|
@@ -34070,8 +34376,6 @@ var init_plugin_loader = __esm({
|
|
|
34070
34376
|
plugins = /* @__PURE__ */ new Map();
|
|
34071
34377
|
/** Cache of dynamically imported modules */
|
|
34072
34378
|
loadedModules = /* @__PURE__ */ new Map();
|
|
34073
|
-
/** Monotonic nonce to guarantee unique cache-busting import URLs. */
|
|
34074
|
-
importNonce = 0;
|
|
34075
34379
|
getProjectRoot() {
|
|
34076
34380
|
return this.options.taskStore.getRootDir();
|
|
34077
34381
|
}
|
|
@@ -34201,31 +34505,24 @@ var init_plugin_loader = __esm({
|
|
|
34201
34505
|
if (!bypassCache && this.loadedModules.has(path)) {
|
|
34202
34506
|
return this.loadedModules.get(path);
|
|
34203
34507
|
}
|
|
34204
|
-
|
|
34205
|
-
let
|
|
34508
|
+
const moduleUrl = pathToFileURL(path).href;
|
|
34509
|
+
let mod;
|
|
34206
34510
|
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
|
-
});
|
|
34511
|
+
moduleImportVersion += 1;
|
|
34512
|
+
const ext = extname(path);
|
|
34513
|
+
const baseName = basename6(path, ext);
|
|
34514
|
+
const reloadedPath = resolve7(dirname5(path), `.${baseName}.reload-${moduleImportVersion}${ext}`);
|
|
34515
|
+
await copyFile(path, reloadedPath);
|
|
34516
|
+
try {
|
|
34517
|
+
mod = await import(pathToFileURL(reloadedPath).href);
|
|
34518
|
+
} finally {
|
|
34519
|
+
await rm(reloadedPath, { force: true }).catch(() => void 0);
|
|
34227
34520
|
}
|
|
34521
|
+
} else {
|
|
34522
|
+
mod = await import(moduleUrl);
|
|
34228
34523
|
}
|
|
34524
|
+
this.loadedModules.set(path, mod);
|
|
34525
|
+
return mod;
|
|
34229
34526
|
}
|
|
34230
34527
|
/**
|
|
34231
34528
|
* Invalidate the module cache for a plugin path.
|
|
@@ -34614,7 +34911,7 @@ var init_plugin_loader = __esm({
|
|
|
34614
34911
|
});
|
|
34615
34912
|
|
|
34616
34913
|
// ../core/src/backup.ts
|
|
34617
|
-
import { cp, mkdir as mkdir8, readdir as readdir6, stat as stat3, unlink as
|
|
34914
|
+
import { cp, mkdir as mkdir8, readdir as readdir6, stat as stat3, unlink as unlink4 } from "node:fs/promises";
|
|
34618
34915
|
import { existsSync as existsSync14 } from "node:fs";
|
|
34619
34916
|
import { join as join17 } from "node:path";
|
|
34620
34917
|
function generateBackupFilename() {
|
|
@@ -34867,7 +35164,7 @@ var init_backup = __esm({
|
|
|
34867
35164
|
let deletedCount = 0;
|
|
34868
35165
|
for (const backup of toDelete) {
|
|
34869
35166
|
try {
|
|
34870
|
-
await
|
|
35167
|
+
await unlink4(backup.path);
|
|
34871
35168
|
deletedCount++;
|
|
34872
35169
|
} catch {
|
|
34873
35170
|
}
|
|
@@ -35577,7 +35874,7 @@ var init_mission_types = __esm({
|
|
|
35577
35874
|
// ../core/src/memory-insights.ts
|
|
35578
35875
|
import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir9 } from "node:fs/promises";
|
|
35579
35876
|
import { existsSync as existsSync15 } from "node:fs";
|
|
35580
|
-
import { dirname as
|
|
35877
|
+
import { dirname as dirname6, join as join18 } from "node:path";
|
|
35581
35878
|
async function readWorkingMemory(rootDir) {
|
|
35582
35879
|
const filePath = join18(rootDir, MEMORY_WORKING_PATH);
|
|
35583
35880
|
if (!existsSync15(filePath)) {
|
|
@@ -35602,7 +35899,7 @@ async function writeInsightsMemory(rootDir, content) {
|
|
|
35602
35899
|
}
|
|
35603
35900
|
async function writeWorkingMemory(rootDir, content) {
|
|
35604
35901
|
const filePath = join18(rootDir, MEMORY_WORKING_PATH);
|
|
35605
|
-
const dir =
|
|
35902
|
+
const dir = dirname6(filePath);
|
|
35606
35903
|
if (!existsSync15(dir)) {
|
|
35607
35904
|
await mkdir9(dir, { recursive: true });
|
|
35608
35905
|
}
|
|
@@ -36447,7 +36744,7 @@ var require_ms = __commonJS({
|
|
|
36447
36744
|
options = options || {};
|
|
36448
36745
|
var type = typeof val;
|
|
36449
36746
|
if (type === "string" && val.length > 0) {
|
|
36450
|
-
return
|
|
36747
|
+
return parse(val);
|
|
36451
36748
|
} else if (type === "number" && isFinite(val)) {
|
|
36452
36749
|
return options.long ? fmtLong(val) : fmtShort(val);
|
|
36453
36750
|
}
|
|
@@ -36455,7 +36752,7 @@ var require_ms = __commonJS({
|
|
|
36455
36752
|
"val is not a non-empty string or a valid number. val=" + JSON.stringify(val)
|
|
36456
36753
|
);
|
|
36457
36754
|
};
|
|
36458
|
-
function
|
|
36755
|
+
function parse(str) {
|
|
36459
36756
|
str = String(str);
|
|
36460
36757
|
if (str.length > 100) {
|
|
36461
36758
|
return;
|
|
@@ -46128,7 +46425,7 @@ var require_public_api = __commonJS({
|
|
|
46128
46425
|
}
|
|
46129
46426
|
return doc;
|
|
46130
46427
|
}
|
|
46131
|
-
function
|
|
46428
|
+
function parse(src, reviver, options) {
|
|
46132
46429
|
let _reviver = void 0;
|
|
46133
46430
|
if (typeof reviver === "function") {
|
|
46134
46431
|
_reviver = reviver;
|
|
@@ -46169,7 +46466,7 @@ var require_public_api = __commonJS({
|
|
|
46169
46466
|
return value.toString(options);
|
|
46170
46467
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
46171
46468
|
}
|
|
46172
|
-
exports.parse =
|
|
46469
|
+
exports.parse = parse;
|
|
46173
46470
|
exports.parseAllDocuments = parseAllDocuments;
|
|
46174
46471
|
exports.parseDocument = parseDocument;
|
|
46175
46472
|
exports.stringify = stringify;
|
|
@@ -46270,10 +46567,10 @@ function pushAlias(aliases, value) {
|
|
|
46270
46567
|
if (pathRef !== normalized && pathRef.length > 0) {
|
|
46271
46568
|
aliases.add(pathRef);
|
|
46272
46569
|
}
|
|
46273
|
-
const
|
|
46274
|
-
if (
|
|
46275
|
-
aliases.add(
|
|
46276
|
-
const basenameSlug = slugifyAgentReference(
|
|
46570
|
+
const basename9 = extractPathBasename(value);
|
|
46571
|
+
if (basename9) {
|
|
46572
|
+
aliases.add(basename9);
|
|
46573
|
+
const basenameSlug = slugifyAgentReference(basename9);
|
|
46277
46574
|
if (basenameSlug.length > 0) {
|
|
46278
46575
|
aliases.add(basenameSlug);
|
|
46279
46576
|
}
|
|
@@ -46959,7 +47256,7 @@ var init_agent_companies_exporter = __esm({
|
|
|
46959
47256
|
|
|
46960
47257
|
// ../core/src/chat-store.ts
|
|
46961
47258
|
import { EventEmitter as EventEmitter14 } from "node:events";
|
|
46962
|
-
import { randomUUID as
|
|
47259
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
46963
47260
|
var ChatStore;
|
|
46964
47261
|
var init_chat_store = __esm({
|
|
46965
47262
|
"../core/src/chat-store.ts"() {
|
|
@@ -47012,7 +47309,7 @@ var init_chat_store = __esm({
|
|
|
47012
47309
|
*/
|
|
47013
47310
|
createSession(input) {
|
|
47014
47311
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
47015
|
-
const id = `chat-${
|
|
47312
|
+
const id = `chat-${randomUUID8().slice(0, 8)}`;
|
|
47016
47313
|
const session = {
|
|
47017
47314
|
id,
|
|
47018
47315
|
agentId: input.agentId,
|
|
@@ -47080,6 +47377,58 @@ var init_chat_store = __esm({
|
|
|
47080
47377
|
`).all(...params);
|
|
47081
47378
|
return rows.map((row) => this.rowToSession(row));
|
|
47082
47379
|
}
|
|
47380
|
+
/**
|
|
47381
|
+
* Find the newest active session for a specific quick-chat target.
|
|
47382
|
+
*
|
|
47383
|
+
* Matching semantics:
|
|
47384
|
+
* - model target (`modelProvider` + `modelId`): exact agent+model match
|
|
47385
|
+
* - agent target (no model): prefer model-less sessions, then newest agent session fallback
|
|
47386
|
+
*/
|
|
47387
|
+
findLatestActiveSessionForTarget(options) {
|
|
47388
|
+
const normalizedAgentId = options.agentId.trim();
|
|
47389
|
+
if (!normalizedAgentId) {
|
|
47390
|
+
return void 0;
|
|
47391
|
+
}
|
|
47392
|
+
const normalizedProvider = options.modelProvider?.trim();
|
|
47393
|
+
const normalizedModelId = options.modelId?.trim();
|
|
47394
|
+
if (normalizedProvider && !normalizedModelId || !normalizedProvider && normalizedModelId) {
|
|
47395
|
+
throw new Error("modelProvider and modelId must both be provided together, or neither");
|
|
47396
|
+
}
|
|
47397
|
+
const whereClauses = ["status = ?", "agentId = ?"];
|
|
47398
|
+
const baseParams = ["active", normalizedAgentId];
|
|
47399
|
+
if (options.projectId && options.projectId.trim()) {
|
|
47400
|
+
whereClauses.push("projectId = ?");
|
|
47401
|
+
baseParams.push(options.projectId.trim());
|
|
47402
|
+
}
|
|
47403
|
+
const baseWhereSql = whereClauses.join(" AND ");
|
|
47404
|
+
if (normalizedProvider && normalizedModelId) {
|
|
47405
|
+
const row = this.db.prepare(`
|
|
47406
|
+
SELECT * FROM chat_sessions
|
|
47407
|
+
WHERE ${baseWhereSql} AND modelProvider = ? AND modelId = ?
|
|
47408
|
+
ORDER BY updatedAt DESC
|
|
47409
|
+
LIMIT 1
|
|
47410
|
+
`).get(...baseParams, normalizedProvider, normalizedModelId);
|
|
47411
|
+
return row ? this.rowToSession(row) : void 0;
|
|
47412
|
+
}
|
|
47413
|
+
const modelLessRow = this.db.prepare(`
|
|
47414
|
+
SELECT * FROM chat_sessions
|
|
47415
|
+
WHERE ${baseWhereSql}
|
|
47416
|
+
AND COALESCE(TRIM(modelProvider), '') = ''
|
|
47417
|
+
AND COALESCE(TRIM(modelId), '') = ''
|
|
47418
|
+
ORDER BY updatedAt DESC
|
|
47419
|
+
LIMIT 1
|
|
47420
|
+
`).get(...baseParams);
|
|
47421
|
+
if (modelLessRow) {
|
|
47422
|
+
return this.rowToSession(modelLessRow);
|
|
47423
|
+
}
|
|
47424
|
+
const fallbackRow = this.db.prepare(`
|
|
47425
|
+
SELECT * FROM chat_sessions
|
|
47426
|
+
WHERE ${baseWhereSql}
|
|
47427
|
+
ORDER BY updatedAt DESC
|
|
47428
|
+
LIMIT 1
|
|
47429
|
+
`).get(...baseParams);
|
|
47430
|
+
return fallbackRow ? this.rowToSession(fallbackRow) : void 0;
|
|
47431
|
+
}
|
|
47083
47432
|
/**
|
|
47084
47433
|
* Update a chat session.
|
|
47085
47434
|
*
|
|
@@ -47158,7 +47507,7 @@ var init_chat_store = __esm({
|
|
|
47158
47507
|
throw new Error(`Chat session ${sessionId} not found`);
|
|
47159
47508
|
}
|
|
47160
47509
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
47161
|
-
const id = `msg-${
|
|
47510
|
+
const id = `msg-${randomUUID8().slice(0, 8)}`;
|
|
47162
47511
|
const message = {
|
|
47163
47512
|
id,
|
|
47164
47513
|
sessionId,
|
|
@@ -47312,6 +47661,7 @@ __export(src_exports, {
|
|
|
47312
47661
|
DEFAULT_MIN_INTERVAL_MS: () => DEFAULT_MIN_INTERVAL_MS,
|
|
47313
47662
|
DEFAULT_PROJECT_SETTINGS: () => DEFAULT_PROJECT_SETTINGS,
|
|
47314
47663
|
DEFAULT_SETTINGS: () => DEFAULT_SETTINGS,
|
|
47664
|
+
DEFAULT_TASK_PRIORITY: () => DEFAULT_TASK_PRIORITY,
|
|
47315
47665
|
DaemonTokenManager: () => DaemonTokenManager,
|
|
47316
47666
|
Database: () => Database,
|
|
47317
47667
|
EXECUTION_MODES: () => EXECUTION_MODES,
|
|
@@ -47367,6 +47717,7 @@ __export(src_exports, {
|
|
|
47367
47717
|
SLICE_PLAN_STATES: () => SLICE_PLAN_STATES,
|
|
47368
47718
|
SLICE_STATUSES: () => SLICE_STATUSES,
|
|
47369
47719
|
SUMMARIZE_SYSTEM_PROMPT: () => SUMMARIZE_SYSTEM_PROMPT,
|
|
47720
|
+
TASK_PRIORITIES: () => TASK_PRIORITIES,
|
|
47370
47721
|
THEME_MODES: () => THEME_MODES,
|
|
47371
47722
|
THINKING_LEVELS: () => THINKING_LEVELS,
|
|
47372
47723
|
TaskStore: () => TaskStore,
|
|
@@ -47399,6 +47750,8 @@ __export(src_exports, {
|
|
|
47399
47750
|
clearOverrides: () => clearOverrides,
|
|
47400
47751
|
collectSystemMetrics: () => collectSystemMetrics,
|
|
47401
47752
|
compactMemoryWithAi: () => compactMemoryWithAi,
|
|
47753
|
+
compareTaskPriority: () => compareTaskPriority,
|
|
47754
|
+
compareTasksByPriorityThenAgeAndId: () => compareTasksByPriorityThenAgeAndId,
|
|
47402
47755
|
computeAccessState: () => computeAccessState,
|
|
47403
47756
|
computeInsightFingerprint: () => computeInsightFingerprint,
|
|
47404
47757
|
convertAgentCompanies: () => convertAgentCompanies,
|
|
@@ -47454,6 +47807,7 @@ __export(src_exports, {
|
|
|
47454
47807
|
getRateLimitResetTime: () => getRateLimitResetTime,
|
|
47455
47808
|
getTaskCompletionBlocker: () => getTaskCompletionBlocker,
|
|
47456
47809
|
getTaskMergeBlocker: () => getTaskMergeBlocker,
|
|
47810
|
+
getTaskPriorityRank: () => getTaskPriorityRank,
|
|
47457
47811
|
getTemplatesForRole: () => getTemplatesForRole,
|
|
47458
47812
|
getValidTransitions: () => getValidTransitions,
|
|
47459
47813
|
hasAgentIdentity: () => hasAgentIdentity,
|
|
@@ -47470,6 +47824,7 @@ __export(src_exports, {
|
|
|
47470
47824
|
isManualTrigger: () => isManualTrigger,
|
|
47471
47825
|
isProjectSettingsKey: () => isProjectSettingsKey,
|
|
47472
47826
|
isQmdAvailable: () => isQmdAvailable,
|
|
47827
|
+
isTaskPriority: () => isTaskPriority,
|
|
47473
47828
|
isTaskReadyForMerge: () => isTaskReadyForMerge,
|
|
47474
47829
|
isValidPermission: () => isValidPermission,
|
|
47475
47830
|
isValidPromptKey: () => isValidPromptKey,
|
|
@@ -47493,6 +47848,7 @@ __export(src_exports, {
|
|
|
47493
47848
|
normalizePermissions: () => normalizePermissions,
|
|
47494
47849
|
normalizeRoadmapFeatureOrder: () => normalizeRoadmapFeatureOrder,
|
|
47495
47850
|
normalizeRoadmapMilestoneOrder: () => normalizeRoadmapMilestoneOrder,
|
|
47851
|
+
normalizeTaskPriority: () => normalizeTaskPriority,
|
|
47496
47852
|
parseAgentManifest: () => parseAgentManifest,
|
|
47497
47853
|
parseCompanyArchive: () => parseCompanyArchive,
|
|
47498
47854
|
parseCompanyDirectory: () => parseCompanyDirectory,
|
|
@@ -47545,6 +47901,7 @@ __export(src_exports, {
|
|
|
47545
47901
|
shouldSkipBackgroundQmdRefresh: () => shouldSkipBackgroundQmdRefresh,
|
|
47546
47902
|
shouldTriggerExtraction: () => shouldTriggerExtraction,
|
|
47547
47903
|
slugify: () => slugify,
|
|
47904
|
+
sortTasksByPriorityThenAgeAndId: () => sortTasksByPriorityThenAgeAndId,
|
|
47548
47905
|
summarizeTitle: () => summarizeTitle,
|
|
47549
47906
|
syncAutoSummarizeAutomation: () => syncAutoSummarizeAutomation,
|
|
47550
47907
|
syncBackupAutomation: () => syncBackupAutomation,
|
|
@@ -47602,6 +47959,7 @@ var init_src = __esm({
|
|
|
47602
47959
|
init_ai_summarize();
|
|
47603
47960
|
init_memory_compaction();
|
|
47604
47961
|
init_roadmap_ordering();
|
|
47962
|
+
init_task_priority();
|
|
47605
47963
|
init_roadmap_handoff();
|
|
47606
47964
|
init_mission_types();
|
|
47607
47965
|
init_mission_store();
|
|
@@ -47628,24 +47986,49 @@ var init_src = __esm({
|
|
|
47628
47986
|
});
|
|
47629
47987
|
|
|
47630
47988
|
// ../engine/src/logger.js
|
|
47989
|
+
function withSeverityMarker2(level, payload) {
|
|
47990
|
+
return `${LOG_LEVEL_MARKER_PREFIX2}${level}${LOG_LEVEL_MARKER_SUFFIX2}${payload}`;
|
|
47991
|
+
}
|
|
47631
47992
|
function createLogger2(prefix) {
|
|
47632
47993
|
const tag = `[${prefix}]`;
|
|
47633
47994
|
return {
|
|
47634
47995
|
log(message, ...args) {
|
|
47635
|
-
globalThis.console.error(`${tag} ${message}
|
|
47996
|
+
globalThis.console.error(withSeverityMarker2("info", `${tag} ${message}`), ...args);
|
|
47636
47997
|
},
|
|
47637
47998
|
warn(message, ...args) {
|
|
47638
|
-
globalThis.console.warn(`${tag} ${message}
|
|
47999
|
+
globalThis.console.warn(withSeverityMarker2("warn", `${tag} ${message}`), ...args);
|
|
47639
48000
|
},
|
|
47640
48001
|
error(message, ...args) {
|
|
47641
|
-
globalThis.console.error(`${tag} ${message}
|
|
48002
|
+
globalThis.console.error(withSeverityMarker2("error", `${tag} ${message}`), ...args);
|
|
47642
48003
|
}
|
|
47643
48004
|
};
|
|
47644
48005
|
}
|
|
47645
|
-
|
|
48006
|
+
function formatError(err) {
|
|
48007
|
+
if (err instanceof Error) {
|
|
48008
|
+
const message2 = err.message || err.name || "Error";
|
|
48009
|
+
const stack = err.stack;
|
|
48010
|
+
const detail = stack && stack.includes(message2) ? stack : stack ? `${message2}
|
|
48011
|
+
${stack}` : message2;
|
|
48012
|
+
return { message: message2, stack, detail };
|
|
48013
|
+
}
|
|
48014
|
+
let message;
|
|
48015
|
+
if (typeof err === "string") {
|
|
48016
|
+
message = err;
|
|
48017
|
+
} else {
|
|
48018
|
+
try {
|
|
48019
|
+
message = JSON.stringify(err);
|
|
48020
|
+
} catch {
|
|
48021
|
+
message = String(err);
|
|
48022
|
+
}
|
|
48023
|
+
}
|
|
48024
|
+
return { message, detail: message };
|
|
48025
|
+
}
|
|
48026
|
+
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
48027
|
var init_logger2 = __esm({
|
|
47647
48028
|
"../engine/src/logger.js"() {
|
|
47648
48029
|
"use strict";
|
|
48030
|
+
LOG_LEVEL_MARKER_PREFIX2 = "\0fnlvl=";
|
|
48031
|
+
LOG_LEVEL_MARKER_SUFFIX2 = "\0";
|
|
47649
48032
|
schedulerLog = createLogger2("scheduler");
|
|
47650
48033
|
executorLog = createLogger2("executor");
|
|
47651
48034
|
triageLog = createLogger2("triage");
|
|
@@ -48100,7 +48483,7 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path, startLine = 1, l
|
|
|
48100
48483
|
}
|
|
48101
48484
|
function createTaskCreateTool(store) {
|
|
48102
48485
|
return {
|
|
48103
|
-
name: "
|
|
48486
|
+
name: "fn_task_create",
|
|
48104
48487
|
label: "Create Task",
|
|
48105
48488
|
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
48489
|
parameters: taskCreateParams,
|
|
@@ -48123,7 +48506,7 @@ function createTaskCreateTool(store) {
|
|
|
48123
48506
|
}
|
|
48124
48507
|
function createTaskLogTool(store, taskId) {
|
|
48125
48508
|
return {
|
|
48126
|
-
name: "
|
|
48509
|
+
name: "fn_task_log",
|
|
48127
48510
|
label: "Log Entry",
|
|
48128
48511
|
description: "Log an important action, decision, or issue for this task. Use for significant events \u2014 not every small step.",
|
|
48129
48512
|
parameters: taskLogParams,
|
|
@@ -48138,7 +48521,7 @@ function createTaskLogTool(store, taskId) {
|
|
|
48138
48521
|
}
|
|
48139
48522
|
function createTaskLogToolWithContext(store, taskId, runContext) {
|
|
48140
48523
|
return {
|
|
48141
|
-
name: "
|
|
48524
|
+
name: "fn_task_log",
|
|
48142
48525
|
label: "Log Entry",
|
|
48143
48526
|
description: "Log an important action, decision, or issue for this task. Use for significant events \u2014 not every small step.",
|
|
48144
48527
|
parameters: taskLogParams,
|
|
@@ -48153,7 +48536,7 @@ function createTaskLogToolWithContext(store, taskId, runContext) {
|
|
|
48153
48536
|
}
|
|
48154
48537
|
function createTaskDocumentWriteTool(store, taskId) {
|
|
48155
48538
|
return {
|
|
48156
|
-
name: "
|
|
48539
|
+
name: "fn_task_document_write",
|
|
48157
48540
|
label: "Write Document",
|
|
48158
48541
|
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
48542
|
parameters: taskDocumentWriteParams,
|
|
@@ -48186,7 +48569,7 @@ function createTaskDocumentWriteTool(store, taskId) {
|
|
|
48186
48569
|
}
|
|
48187
48570
|
function createTaskDocumentReadTool(store, taskId) {
|
|
48188
48571
|
return {
|
|
48189
|
-
name: "
|
|
48572
|
+
name: "fn_task_document_read",
|
|
48190
48573
|
label: "Read Document",
|
|
48191
48574
|
description: "Read a named document for this task, or list all documents when no key is provided.",
|
|
48192
48575
|
parameters: taskDocumentReadParams,
|
|
@@ -48242,9 +48625,9 @@ ${lines.join("\n")}`
|
|
|
48242
48625
|
}
|
|
48243
48626
|
function createMemorySearchTool(rootDir, settings, options) {
|
|
48244
48627
|
return {
|
|
48245
|
-
name: "
|
|
48628
|
+
name: "fn_memory_search",
|
|
48246
48629
|
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
|
|
48630
|
+
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
48631
|
parameters: memorySearchParams,
|
|
48249
48632
|
execute: async (_id, params) => {
|
|
48250
48633
|
const limit = params.limit ?? 5;
|
|
@@ -48270,9 +48653,9 @@ function createMemorySearchTool(rootDir, settings, options) {
|
|
|
48270
48653
|
}
|
|
48271
48654
|
function createMemoryGetTool(rootDir, settings, options) {
|
|
48272
48655
|
return {
|
|
48273
|
-
name: "
|
|
48656
|
+
name: "fn_memory_get",
|
|
48274
48657
|
label: "Get Memory",
|
|
48275
|
-
description: "Read a bounded line window from a memory file returned by
|
|
48658
|
+
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
48659
|
parameters: memoryGetParams,
|
|
48277
48660
|
execute: async (_id, params) => {
|
|
48278
48661
|
const agentResult = options?.agentMemory ? await getAgentMemoryWindow(rootDir, options.agentMemory, params.path, params.startLine, params.lineCount) : null;
|
|
@@ -48306,7 +48689,7 @@ ${result.content}`
|
|
|
48306
48689
|
}
|
|
48307
48690
|
function createMemoryAppendTool(rootDir, settings, options) {
|
|
48308
48691
|
return {
|
|
48309
|
-
name: "
|
|
48692
|
+
name: "fn_memory_append",
|
|
48310
48693
|
label: "Append Memory",
|
|
48311
48694
|
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
48695
|
parameters: memoryAppendParams,
|
|
@@ -48368,7 +48751,7 @@ function createMemoryTools(rootDir, settings, options) {
|
|
|
48368
48751
|
}
|
|
48369
48752
|
function createReflectOnPerformanceTool(reflectionService, agentId) {
|
|
48370
48753
|
return {
|
|
48371
|
-
name: "
|
|
48754
|
+
name: "fn_reflect_on_performance",
|
|
48372
48755
|
label: "Reflect on Performance",
|
|
48373
48756
|
description: 'Review your past task performance and generate insights for improvement. Optionally focus on a specific area like "code quality", "speed", or "testing".',
|
|
48374
48757
|
parameters: reflectOnPerformanceParams,
|
|
@@ -48401,7 +48784,7 @@ function createReflectOnPerformanceTool(reflectionService, agentId) {
|
|
|
48401
48784
|
}
|
|
48402
48785
|
function createListAgentsTool(agentStore) {
|
|
48403
48786
|
return {
|
|
48404
|
-
name: "
|
|
48787
|
+
name: "fn_list_agents",
|
|
48405
48788
|
label: "List Agents",
|
|
48406
48789
|
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
48790
|
parameters: listAgentsParams,
|
|
@@ -48444,9 +48827,9 @@ ${lines.join("\n\n")}` }],
|
|
|
48444
48827
|
}
|
|
48445
48828
|
function createDelegateTaskTool(agentStore, taskStore) {
|
|
48446
48829
|
return {
|
|
48447
|
-
name: "
|
|
48830
|
+
name: "fn_delegate_task",
|
|
48448
48831
|
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
|
|
48832
|
+
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
48833
|
parameters: delegateTaskParams,
|
|
48451
48834
|
execute: async (_id, params) => {
|
|
48452
48835
|
const agent = await agentStore.getAgent(params.agent_id);
|
|
@@ -48481,7 +48864,7 @@ function createDelegateTaskTool(agentStore, taskStore) {
|
|
|
48481
48864
|
}
|
|
48482
48865
|
function createSendMessageTool(messageStore, fromAgentId) {
|
|
48483
48866
|
return {
|
|
48484
|
-
name: "
|
|
48867
|
+
name: "fn_send_message",
|
|
48485
48868
|
label: "Send Message",
|
|
48486
48869
|
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
48870
|
parameters: sendMessageParams,
|
|
@@ -48538,7 +48921,7 @@ function createSendMessageTool(messageStore, fromAgentId) {
|
|
|
48538
48921
|
}
|
|
48539
48922
|
function createReadMessagesTool(messageStore, agentId) {
|
|
48540
48923
|
return {
|
|
48541
|
-
name: "
|
|
48924
|
+
name: "fn_read_messages",
|
|
48542
48925
|
label: "Read Messages",
|
|
48543
48926
|
description: "Read your inbox messages. Returns unread messages by default.",
|
|
48544
48927
|
parameters: readMessagesParams,
|
|
@@ -48640,7 +49023,7 @@ var init_agent_tools = __esm({
|
|
|
48640
49023
|
Type.Literal("agent-to-user")
|
|
48641
49024
|
], { description: "Message type (defaults to 'agent-to-agent')" })),
|
|
48642
49025
|
reply_to_message_id: Type.Optional(
|
|
48643
|
-
Type.String({ description: "Optional ID of the message you are replying to (use IDs from
|
|
49026
|
+
Type.String({ description: "Optional ID of the message you are replying to (use IDs from fn_read_messages output)" })
|
|
48644
49027
|
)
|
|
48645
49028
|
});
|
|
48646
49029
|
readMessagesParams = Type.Object({
|
|
@@ -48652,7 +49035,7 @@ var init_agent_tools = __esm({
|
|
|
48652
49035
|
limit: Type.Optional(Type.Number({ description: "Maximum snippets to return (default: 5, max: 20)" }))
|
|
48653
49036
|
});
|
|
48654
49037
|
memoryGetParams = Type.Object({
|
|
48655
|
-
path: Type.String({ description: "Memory path from
|
|
49038
|
+
path: Type.String({ description: "Memory path from fn_memory_search, e.g. .fusion/memory/MEMORY.md or .fusion/memory/YYYY-MM-DD.md" }),
|
|
48656
49039
|
startLine: Type.Optional(Type.Number({ description: "1-based start line (default: 1)" })),
|
|
48657
49040
|
lineCount: Type.Optional(Type.Number({ description: "Number of lines to read (default: 120, max: 400)" }))
|
|
48658
49041
|
});
|
|
@@ -49156,7 +49539,7 @@ __export(pi_exports, {
|
|
|
49156
49539
|
import { existsSync as existsSync20, readFileSync as readFileSync6 } from "node:fs";
|
|
49157
49540
|
import { exec } from "node:child_process";
|
|
49158
49541
|
import { promisify as promisify2 } from "node:util";
|
|
49159
|
-
import { basename as
|
|
49542
|
+
import { basename as basename7, dirname as dirname7, join as join24, relative as relative3, isAbsolute as isAbsolute6, resolve as resolve10 } from "node:path";
|
|
49160
49543
|
import { createAgentSession, createCodingTools, createExtensionRuntime, createReadOnlyTools, DefaultResourceLoader, DefaultPackageManager, discoverAndLoadExtensions, ModelRegistry, SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent";
|
|
49161
49544
|
function getSessionStateError(session) {
|
|
49162
49545
|
const error = session.state?.error;
|
|
@@ -49480,10 +49863,10 @@ function hasPackageManagerSettings(settings) {
|
|
|
49480
49863
|
return Array.isArray(settings.packages) || Array.isArray(settings.npmCommand);
|
|
49481
49864
|
}
|
|
49482
49865
|
function siblingAgentDir(agentDir, siblingRoot) {
|
|
49483
|
-
if (
|
|
49866
|
+
if (basename7(agentDir) !== "agent") {
|
|
49484
49867
|
return void 0;
|
|
49485
49868
|
}
|
|
49486
|
-
return join24(
|
|
49869
|
+
return join24(dirname7(dirname7(agentDir)), siblingRoot, "agent");
|
|
49487
49870
|
}
|
|
49488
49871
|
function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
49489
49872
|
const projectRoot = resolvePiExtensionProjectRoot(cwd);
|
|
@@ -50802,12 +51185,12 @@ When reviewing specs, actively assess whether the task should have been broken i
|
|
|
50802
51185
|
Say explicitly: "This task should be broken into subtasks because [specific reason]."
|
|
50803
51186
|
Recommend the number of child tasks (2-5) and what each should cover.
|
|
50804
51187
|
**Critically**, instruct the planner to take these actions in your REVISE feedback:
|
|
50805
|
-
1. Use the \`
|
|
51188
|
+
1. Use the \`fn_task_create\` tool to create 2\u20135 child tasks from the oversized spec
|
|
50806
51189
|
2. Do NOT write a parent PROMPT.md \u2014 the parent will be closed automatically after children are created
|
|
50807
51190
|
3. Each child task should cover one coherent deliverable with clear scope boundaries
|
|
50808
51191
|
|
|
50809
51192
|
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
|
|
51193
|
+
"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
51194
|
|
|
50812
51195
|
**Do NOT flag if:**
|
|
50813
51196
|
- Steps are sequential and tightly coupled (e.g., a pipeline where each step depends on the previous)
|
|
@@ -51180,12 +51563,12 @@ The user has requested that this task be broken into smaller subtasks if it is c
|
|
|
51180
51563
|
|
|
51181
51564
|
**How to split:**
|
|
51182
51565
|
1. First, analyze the task to determine if it should be split
|
|
51183
|
-
2. If splitting: use the \\\`
|
|
51566
|
+
2. If splitting: use the \\\`fn_task_create\\\` tool to create child tasks in order, setting up dependencies as needed
|
|
51184
51567
|
3. Include clear descriptions and acceptance criteria for each child task
|
|
51185
51568
|
4. After creating all subtasks, stop \u2014 do NOT write a PROMPT.md for the parent task
|
|
51186
51569
|
5. If NOT splitting: proceed with a normal PROMPT.md specification for this task
|
|
51187
51570
|
|
|
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 \`
|
|
51571
|
+
**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
51572
|
|
|
51190
51573
|
**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
51574
|
} else {
|
|
@@ -51211,7 +51594,7 @@ The user did not explicitly request subtask breakdown, so you should first asses
|
|
|
51211
51594
|
- Adding a small feature to one module with 5 steps
|
|
51212
51595
|
|
|
51213
51596
|
**How to decide:**
|
|
51214
|
-
- If you choose to split: use the \\\`
|
|
51597
|
+
- 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
51598
|
- **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
51599
|
- 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
51600
|
- If size is uncertain at first, make a quick assessment from the available context before deciding.`;
|
|
@@ -51325,8 +51708,8 @@ Follow this structure exactly:
|
|
|
51325
51708
|
### Step {N}: Documentation & Delivery
|
|
51326
51709
|
|
|
51327
51710
|
- [ ] Update relevant documentation
|
|
51328
|
-
- [ ] Save documentation deliverables as task documents via \`
|
|
51329
|
-
- [ ] Out-of-scope findings created as new tasks via \`
|
|
51711
|
+
- [ ] Save documentation deliverables as task documents via \`fn_task_document_write\` (key="docs", content=...)
|
|
51712
|
+
- [ ] Out-of-scope findings created as new tasks via \`fn_task_create\` tool
|
|
51330
51713
|
|
|
51331
51714
|
## Documentation Requirements
|
|
51332
51715
|
|
|
@@ -51359,7 +51742,7 @@ Commits at step boundaries. All commits include the task ID:
|
|
|
51359
51742
|
- Refuse necessary fixes just because they touch files outside the initial File Scope
|
|
51360
51743
|
- Commit without the task ID prefix
|
|
51361
51744
|
- 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 \`
|
|
51745
|
+
- Remove features as "cleanup" \u2014 if something seems unused, create a task via \`fn_task_create\`
|
|
51363
51746
|
|
|
51364
51747
|
## Changeset Requirements
|
|
51365
51748
|
|
|
@@ -51381,13 +51764,13 @@ tests. Manual verification is NOT a test.
|
|
|
51381
51764
|
as part of this task (not just skipping tests)
|
|
51382
51765
|
|
|
51383
51766
|
## Duplicate check
|
|
51384
|
-
Before writing a spec, call \`
|
|
51767
|
+
Before writing a spec, call \`fn_task_list\` to see existing tasks.
|
|
51385
51768
|
If a task already covers the same work (even if worded differently), do NOT
|
|
51386
51769
|
write a PROMPT.md. Instead, write a single line to the output file:
|
|
51387
51770
|
\`DUPLICATE: {existing-task-id}\`
|
|
51388
51771
|
|
|
51389
51772
|
## Dependency awareness
|
|
51390
|
-
When you plan to list a task in the \`## Dependencies\` section, first call \`
|
|
51773
|
+
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
51774
|
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
51775
|
If the dependency task has no PROMPT.md yet (not yet specified), note that in the Dependencies section.
|
|
51393
51776
|
|
|
@@ -51395,8 +51778,8 @@ If the dependency task has no PROMPT.md yet (not yet specified), note that in th
|
|
|
51395
51778
|
When the task includes \`breakIntoSubtasks: true\`, first decide whether it should be split.
|
|
51396
51779
|
|
|
51397
51780
|
- 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 \`
|
|
51781
|
+
- 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.
|
|
51782
|
+
- **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
51783
|
- If not splitting: proceed with a normal PROMPT.md specification.
|
|
51401
51784
|
|
|
51402
51785
|
## Proactive Subtask Breakdown for M/L Tasks
|
|
@@ -51419,20 +51802,20 @@ For tasks you assess as Size M or L, proactively evaluate whether splitting into
|
|
|
51419
51802
|
|
|
51420
51803
|
## Triage tools
|
|
51421
51804
|
You have these extra tools during triage:
|
|
51422
|
-
- \`
|
|
51423
|
-
- \`
|
|
51424
|
-
- \`
|
|
51425
|
-
- \`
|
|
51426
|
-
- \`
|
|
51805
|
+
- \`fn_task_list\` \u2014 list existing active tasks
|
|
51806
|
+
- \`fn_task_get\` \u2014 inspect a task and its PROMPT.md
|
|
51807
|
+
- \`fn_task_create\` \u2014 create a child/follow-up task while triaging
|
|
51808
|
+
- \`fn_task_document_write\` \u2014 save a planning document (e.g., key="plan")
|
|
51809
|
+
- \`fn_task_document_read\` \u2014 read back a previously saved document
|
|
51427
51810
|
|
|
51428
|
-
When the planning conversation produces a structured plan, save it as a document with \`
|
|
51811
|
+
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
51812
|
|
|
51430
51813
|
## Guidelines
|
|
51431
51814
|
- Read the project structure and relevant source files to understand context BEFORE writing
|
|
51432
51815
|
- Be specific \u2014 name actual files, functions, and patterns from the codebase
|
|
51433
51816
|
- Steps should express OUTCOMES, not micro-instructions (2-5 checkboxes per step)
|
|
51434
51817
|
- 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 \`
|
|
51818
|
+
- 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
51819
|
- Include a "Do NOT" section with project-appropriate guardrails
|
|
51437
51820
|
- Size assessment: S (<2h), M (2-4h), L (4-8h). Split if XL (8h+)
|
|
51438
51821
|
- Review level scoring: Blast radius (0-2), Pattern novelty (0-2), Security (0-2), Reversibility (0-2)
|
|
@@ -51446,16 +51829,16 @@ package.json when explicit commands are provided.
|
|
|
51446
51829
|
|
|
51447
51830
|
## Spec Review
|
|
51448
51831
|
|
|
51449
|
-
After writing the PROMPT.md, call \`
|
|
51832
|
+
After writing the PROMPT.md, call \`fn_review_spec()\` to get an independent quality review.
|
|
51450
51833
|
|
|
51451
51834
|
- **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 \`
|
|
51835
|
+
- **REVISE** \u2192 fix the issues described in the review feedback, rewrite the PROMPT.md, and call \`fn_review_spec()\` again. Repeat until approved.
|
|
51453
51836
|
- **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
51837
|
|
|
51455
|
-
You MUST call \`
|
|
51838
|
+
You MUST call \`fn_review_spec()\` after writing the PROMPT.md. Do not finish without getting an APPROVE verdict.
|
|
51456
51839
|
|
|
51457
51840
|
## Output
|
|
51458
|
-
Write the PROMPT.md directly using the write tool, then call \`
|
|
51841
|
+
Write the PROMPT.md directly using the write tool, then call \`fn_review_spec()\` for review.
|
|
51459
51842
|
|
|
51460
51843
|
## Frontend UX Criteria Injection
|
|
51461
51844
|
|
|
@@ -51702,9 +52085,10 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51702
52085
|
this.wasEnginePaused = false;
|
|
51703
52086
|
const allTasks = await this.store.listTasks({ slim: true, includeArchived: false });
|
|
51704
52087
|
const now = Date.now();
|
|
51705
|
-
const
|
|
52088
|
+
const eligibleTriageTasks = allTasks.filter(
|
|
51706
52089
|
(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
52090
|
);
|
|
52091
|
+
const triageTasks = sortTasksByPriorityThenAgeAndId(eligibleTriageTasks);
|
|
51708
52092
|
const maxTriageConcurrent = settings.maxTriageConcurrent ?? settings.maxConcurrent ?? 2;
|
|
51709
52093
|
const specifying = allTasks.filter(
|
|
51710
52094
|
(t) => t.column === "triage" && t.status === "specifying" && !t.paused
|
|
@@ -51730,11 +52114,11 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51730
52114
|
/**
|
|
51731
52115
|
* Specify a triage task by spawning an AI agent to generate a PROMPT.md.
|
|
51732
52116
|
*
|
|
51733
|
-
* After the agent writes the PROMPT.md, it calls `
|
|
52117
|
+
* After the agent writes the PROMPT.md, it calls `fn_review_spec()` to spawn
|
|
51734
52118
|
* an independent reviewer agent that evaluates the specification quality.
|
|
51735
52119
|
* The review loop works as follows:
|
|
51736
52120
|
* - **APPROVE**: the spec is accepted and the task moves to `todo`
|
|
51737
|
-
* - **REVISE**: the agent revises the spec and calls `
|
|
52121
|
+
* - **REVISE**: the agent revises the spec and calls `fn_review_spec()` again.
|
|
51738
52122
|
* If the agent finishes without getting APPROVE, the task is NOT moved to
|
|
51739
52123
|
* `todo` — a post-session gate requires an explicit APPROVE verdict.
|
|
51740
52124
|
* - **RETHINK**: the conversation rewinds to a pre-specification checkpoint
|
|
@@ -51938,7 +52322,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
51938
52322
|
const planningFallbackModelId = settings.planningFallbackModelId;
|
|
51939
52323
|
const canRetryWithPlanningFallback = specReviewVerdictRef.current !== "APPROVE" && planningFallbackProvider && planningFallbackModelId && modelDesc !== `${planningFallbackProvider}/${planningFallbackModelId}`;
|
|
51940
52324
|
if (canRetryWithPlanningFallback) {
|
|
51941
|
-
const verdictDesc = specReviewVerdictRef.current === null ? "
|
|
52325
|
+
const verdictDesc = specReviewVerdictRef.current === null ? "fn_review_spec was never called" : `verdict was ${specReviewVerdictRef.current}`;
|
|
51942
52326
|
const fallbackDesc = `${planningFallbackProvider}/${planningFallbackModelId}`;
|
|
51943
52327
|
triageLog.warn(
|
|
51944
52328
|
`${task.id} primary planning model produced no approved spec (${verdictDesc}) \u2014 retrying with fallback ${fallbackDesc}`
|
|
@@ -52001,7 +52385,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52001
52385
|
}
|
|
52002
52386
|
}
|
|
52003
52387
|
if (specReviewVerdictRef.current !== "APPROVE") {
|
|
52004
|
-
const verdictDesc = specReviewVerdictRef.current === null ? "
|
|
52388
|
+
const verdictDesc = specReviewVerdictRef.current === null ? "fn_review_spec was never called" : `verdict was ${specReviewVerdictRef.current}`;
|
|
52005
52389
|
const decision = computeRecoveryDecision({
|
|
52006
52390
|
recoveryRetryCount: task.recoveryRetryCount,
|
|
52007
52391
|
nextRecoveryAt: task.nextRecoveryAt
|
|
@@ -52086,7 +52470,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52086
52470
|
await retryableWork();
|
|
52087
52471
|
}
|
|
52088
52472
|
} catch (err) {
|
|
52089
|
-
const errorMessage
|
|
52473
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
52090
52474
|
if (err.code === "ENOENT") {
|
|
52091
52475
|
triageLog.log(`${task.id} no longer exists \u2014 skipping`);
|
|
52092
52476
|
} else if (this.pauseAborted.has(task.id)) {
|
|
@@ -52159,7 +52543,13 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52159
52543
|
const msg = restoreErr instanceof Error ? restoreErr.message : String(restoreErr);
|
|
52160
52544
|
triageLog.warn(`${task.id}: failed to restore status to '${restoreStatus}' after specification error: ${msg}`);
|
|
52161
52545
|
});
|
|
52162
|
-
triageLog.error(`\u2717 ${task.id} specification failed:`,
|
|
52546
|
+
triageLog.error(`\u2717 ${task.id} specification failed:`, errorDetail);
|
|
52547
|
+
if (errorStack) {
|
|
52548
|
+
await this.store.logEntry(task.id, `Specification failed: ${errorMessage}`, errorStack).catch((logErr) => {
|
|
52549
|
+
const msg = logErr instanceof Error ? logErr.message : String(logErr);
|
|
52550
|
+
triageLog.warn(`${task.id}: failed to persist specification-failure stack trace: ${msg}`);
|
|
52551
|
+
});
|
|
52552
|
+
}
|
|
52163
52553
|
this.options.onSpecifyError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
52164
52554
|
}
|
|
52165
52555
|
} finally {
|
|
@@ -52180,7 +52570,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52180
52570
|
)
|
|
52181
52571
|
});
|
|
52182
52572
|
const taskList = {
|
|
52183
|
-
name: "
|
|
52573
|
+
name: "fn_task_list",
|
|
52184
52574
|
label: "List Tasks",
|
|
52185
52575
|
description: "List all tasks that aren't done. Returns ID, description, column, and dependencies for each. Use to check for duplicates before specifying.",
|
|
52186
52576
|
parameters: Type2.Object({}),
|
|
@@ -52205,7 +52595,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52205
52595
|
}
|
|
52206
52596
|
};
|
|
52207
52597
|
const taskGet = {
|
|
52208
|
-
name: "
|
|
52598
|
+
name: "fn_task_get",
|
|
52209
52599
|
label: "Get Task",
|
|
52210
52600
|
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
52601
|
parameters: taskGetParams,
|
|
@@ -52227,7 +52617,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52227
52617
|
};
|
|
52228
52618
|
} catch (err) {
|
|
52229
52619
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52230
|
-
triageLog.warn(`${options.parentTaskId}:
|
|
52620
|
+
triageLog.warn(`${options.parentTaskId}: fn_task_get lookup failed for ${params.id}: ${msg}`);
|
|
52231
52621
|
return {
|
|
52232
52622
|
content: [
|
|
52233
52623
|
{ type: "text", text: `Task ${params.id} not found.` }
|
|
@@ -52238,7 +52628,7 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52238
52628
|
}
|
|
52239
52629
|
};
|
|
52240
52630
|
const taskCreate = {
|
|
52241
|
-
name: "
|
|
52631
|
+
name: "fn_task_create",
|
|
52242
52632
|
label: "Create Child Task",
|
|
52243
52633
|
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
52634
|
parameters: taskCreateParams3,
|
|
@@ -52276,10 +52666,10 @@ Only inject this section when the task genuinely touches frontend UI. Omit it fo
|
|
|
52276
52666
|
content: [
|
|
52277
52667
|
{
|
|
52278
52668
|
type: "text",
|
|
52279
|
-
text: `ERROR:
|
|
52669
|
+
text: `ERROR: fn_task_create rejected. Invalid dependencies:
|
|
52280
52670
|
${summary}
|
|
52281
52671
|
|
|
52282
|
-
Remove or replace these ids and call
|
|
52672
|
+
Remove or replace these ids and call fn_task_create again.`
|
|
52283
52673
|
}
|
|
52284
52674
|
],
|
|
52285
52675
|
details: { rejectedDependencies: rejected }
|
|
@@ -52290,7 +52680,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52290
52680
|
parentTask = await store.getTask(options.parentTaskId);
|
|
52291
52681
|
} catch (err) {
|
|
52292
52682
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52293
|
-
triageLog.warn(`${options.parentTaskId}: failed to load parent task for
|
|
52683
|
+
triageLog.warn(`${options.parentTaskId}: failed to load parent task for fn_task_create inheritance: ${msg}`);
|
|
52294
52684
|
parentTask = void 0;
|
|
52295
52685
|
}
|
|
52296
52686
|
const newTask = await store.createTask({
|
|
@@ -52331,13 +52721,13 @@ Remove or replace these ids and call task_create again.`
|
|
|
52331
52721
|
return [taskList, taskGet, taskCreate];
|
|
52332
52722
|
}
|
|
52333
52723
|
/**
|
|
52334
|
-
* Create the `
|
|
52724
|
+
* Create the `fn_review_spec` tool for the triage agent.
|
|
52335
52725
|
*
|
|
52336
52726
|
* Spawns an independent reviewer agent to evaluate the generated PROMPT.md.
|
|
52337
52727
|
* Verdict handling:
|
|
52338
52728
|
* - **APPROVE**: returns "APPROVE" — the triage agent's work is done.
|
|
52339
52729
|
* - **REVISE**: returns the review feedback. The triage agent must fix the
|
|
52340
|
-
* PROMPT.md and call `
|
|
52730
|
+
* PROMPT.md and call `fn_review_spec` again. A post-session gate in
|
|
52341
52731
|
* `specifyTask()` prevents moving to `todo` if the last verdict is REVISE.
|
|
52342
52732
|
* - **RETHINK**: rewinds the conversation to a pre-specification checkpoint
|
|
52343
52733
|
* using `session.navigateTree()`. Returns a re-prompt instructing the agent
|
|
@@ -52348,7 +52738,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52348
52738
|
const rootDir = this.rootDir;
|
|
52349
52739
|
const options = this.options;
|
|
52350
52740
|
return {
|
|
52351
|
-
name: "
|
|
52741
|
+
name: "fn_review_spec",
|
|
52352
52742
|
label: "Review Specification",
|
|
52353
52743
|
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
52744
|
parameters: Type2.Object({}),
|
|
@@ -52366,7 +52756,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52366
52756
|
"utf-8"
|
|
52367
52757
|
).catch((err) => {
|
|
52368
52758
|
const msg = err instanceof Error ? err.message : String(err);
|
|
52369
|
-
triageLog.warn(`${taskId}: failed to read PROMPT.md for
|
|
52759
|
+
triageLog.warn(`${taskId}: failed to read PROMPT.md for fn_review_spec (${promptPath}): ${msg}`);
|
|
52370
52760
|
return "";
|
|
52371
52761
|
});
|
|
52372
52762
|
if (!promptContent) {
|
|
@@ -52374,7 +52764,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52374
52764
|
content: [
|
|
52375
52765
|
{
|
|
52376
52766
|
type: "text",
|
|
52377
|
-
text: "UNAVAILABLE \u2014 PROMPT.md file not found or empty. Write the specification first, then call
|
|
52767
|
+
text: "UNAVAILABLE \u2014 PROMPT.md file not found or empty. Write the specification first, then call fn_review_spec."
|
|
52378
52768
|
}
|
|
52379
52769
|
],
|
|
52380
52770
|
details: {}
|
|
@@ -52430,7 +52820,7 @@ Remove or replace these ids and call task_create again.`
|
|
|
52430
52820
|
text = "APPROVE";
|
|
52431
52821
|
break;
|
|
52432
52822
|
case "REVISE":
|
|
52433
|
-
text = `REVISE \u2014 fix the issues below, rewrite the PROMPT.md, and call
|
|
52823
|
+
text = `REVISE \u2014 fix the issues below, rewrite the PROMPT.md, and call fn_review_spec() again.
|
|
52434
52824
|
|
|
52435
52825
|
${result.review}`;
|
|
52436
52826
|
break;
|
|
@@ -52854,6 +53244,11 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
|
|
|
52854
53244
|
};
|
|
52855
53245
|
}
|
|
52856
53246
|
if (existsSync21(join26(rootDir, "pnpm-lock.yaml"))) {
|
|
53247
|
+
if (existsSync21(join26(rootDir, "pnpm-workspace.yaml"))) {
|
|
53248
|
+
mergerLog.warn(
|
|
53249
|
+
`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\`.`
|
|
53250
|
+
);
|
|
53251
|
+
}
|
|
52857
53252
|
return {
|
|
52858
53253
|
command: "pnpm test",
|
|
52859
53254
|
testSource: "inferred",
|
|
@@ -52962,6 +53357,7 @@ async function runVerificationCommand(store, rootDir, taskId, command, type) {
|
|
|
52962
53357
|
stderr: "",
|
|
52963
53358
|
success: false
|
|
52964
53359
|
};
|
|
53360
|
+
const verificationStartedAt = Date.now();
|
|
52965
53361
|
try {
|
|
52966
53362
|
const { stdout, stderr } = await execAsync2(command, {
|
|
52967
53363
|
cwd: rootDir,
|
|
@@ -52973,37 +53369,46 @@ async function runVerificationCommand(store, rootDir, taskId, command, type) {
|
|
|
52973
53369
|
result.stderr = stderr?.toString?.() || "";
|
|
52974
53370
|
result.exitCode = 0;
|
|
52975
53371
|
result.success = true;
|
|
52976
|
-
|
|
52977
|
-
|
|
53372
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
53373
|
+
mergerLog.log(`${taskId}: ${type} command succeeded in ${verificationDurationMs}ms`);
|
|
53374
|
+
await store.logEntry(taskId, `[timing] [verification] ${type} command succeeded (exit 0) in ${verificationDurationMs}ms`);
|
|
52978
53375
|
return result;
|
|
52979
53376
|
} catch (error) {
|
|
53377
|
+
const verificationDurationMs = Date.now() - verificationStartedAt;
|
|
52980
53378
|
result.stdout = error?.stdout?.toString?.() || "";
|
|
52981
53379
|
result.stderr = error?.stderr?.toString?.() || "";
|
|
52982
53380
|
result.exitCode = typeof error?.status === "number" ? error.status : typeof error?.code === "number" ? error.code : null;
|
|
52983
53381
|
const maxBufferExceeded = error?.code === "ENOBUFS" || error?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER" || String(error?.message ?? "").includes("maxBuffer");
|
|
52984
53382
|
result.success = maxBufferExceeded && result.exitCode === 0;
|
|
52985
53383
|
if (result.success) {
|
|
52986
|
-
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer)`);
|
|
53384
|
+
mergerLog.log(`${taskId}: ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`);
|
|
52987
53385
|
await store.logEntry(
|
|
52988
53386
|
taskId,
|
|
52989
|
-
`[verification] ${type} command succeeded (exit 0, output exceeded buffer)`
|
|
53387
|
+
`[timing] [verification] ${type} command succeeded (exit 0, output exceeded buffer) in ${verificationDurationMs}ms`
|
|
52990
53388
|
);
|
|
52991
53389
|
return result;
|
|
52992
53390
|
}
|
|
52993
53391
|
const output = result.stderr || result.stdout || error?.message || "Unknown error";
|
|
52994
53392
|
const summary = summarizeVerificationOutput(output, type);
|
|
52995
|
-
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}); output captured in task log`);
|
|
53393
|
+
mergerLog.error(`${taskId}: ${type} command failed (exit ${result.exitCode}) in ${verificationDurationMs}ms; output captured in task log`);
|
|
52996
53394
|
await store.logEntry(
|
|
52997
53395
|
taskId,
|
|
52998
|
-
`[verification] ${type} command failed (exit ${result.exitCode}):
|
|
53396
|
+
`[timing] [verification] ${type} command failed (exit ${result.exitCode}) after ${verificationDurationMs}ms:
|
|
52999
53397
|
${summary}`
|
|
53000
53398
|
);
|
|
53001
53399
|
}
|
|
53002
53400
|
return result;
|
|
53003
53401
|
}
|
|
53004
|
-
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, _testCommand, _buildCommand) {
|
|
53402
|
+
async function attemptInMergeVerificationFix(store, rootDir, taskId, failureContext, settings, options, mergeRunContext, fixAttemptNumber, _testCommand, _buildCommand) {
|
|
53005
53403
|
try {
|
|
53006
53404
|
mergerLog.log(`${taskId}: spawning in-merge verification fix agent`);
|
|
53405
|
+
const logger2 = new AgentLogger({
|
|
53406
|
+
store,
|
|
53407
|
+
taskId,
|
|
53408
|
+
agent: "merger",
|
|
53409
|
+
onAgentText: options.onAgentText,
|
|
53410
|
+
onAgentTool: options.onAgentTool
|
|
53411
|
+
});
|
|
53007
53412
|
let skillContext = void 0;
|
|
53008
53413
|
if (options.agentStore) {
|
|
53009
53414
|
try {
|
|
@@ -53035,12 +53440,22 @@ A merge has been applied and the verification command failed. Your job is to fix
|
|
|
53035
53440
|
6. If you cannot fix the issue, explain why`,
|
|
53036
53441
|
tools: "coding",
|
|
53037
53442
|
// Agent needs read/write file access
|
|
53443
|
+
onText: logger2.onText,
|
|
53444
|
+
onThinking: logger2.onThinking,
|
|
53445
|
+
onToolStart: logger2.onToolStart,
|
|
53446
|
+
onToolEnd: logger2.onToolEnd,
|
|
53038
53447
|
defaultProvider: settings.defaultProvider,
|
|
53039
53448
|
defaultModelId: settings.defaultModelId,
|
|
53040
53449
|
defaultThinkingLevel: settings.defaultThinkingLevel,
|
|
53041
53450
|
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
53042
53451
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
53043
53452
|
});
|
|
53453
|
+
const runId = mergeRunContext?.runId;
|
|
53454
|
+
const agentId = mergeRunContext?.agentId ?? "merger";
|
|
53455
|
+
await store.logEntry(
|
|
53456
|
+
taskId,
|
|
53457
|
+
`In-merge verification fix agent started (model: ${describeModel(session)}, runId: ${runId ?? "unknown"}, agentId: ${agentId})`
|
|
53458
|
+
);
|
|
53044
53459
|
try {
|
|
53045
53460
|
const fixPrompt = `Fix the failing ${failureContext.type} verification for task ${taskId}.
|
|
53046
53461
|
|
|
@@ -53065,6 +53480,10 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
53065
53480
|
mergerLog.warn(`\u23F3 ${taskId} in-merge fix rate limited \u2014 retry ${attempt} in ${delaySec}s: ${error.message}`);
|
|
53066
53481
|
}
|
|
53067
53482
|
});
|
|
53483
|
+
await store.logEntry(
|
|
53484
|
+
taskId,
|
|
53485
|
+
`Re-running deterministic merge verification (attempt ${fixAttemptNumber ?? "unknown"})`
|
|
53486
|
+
);
|
|
53068
53487
|
const reRunResult = await runVerificationCommand(
|
|
53069
53488
|
store,
|
|
53070
53489
|
rootDir,
|
|
@@ -53074,6 +53493,7 @@ ${failureContext.output.slice(0, VERIFICATION_LOG_MAX_CHARS)}
|
|
|
53074
53493
|
);
|
|
53075
53494
|
return reRunResult.success;
|
|
53076
53495
|
} finally {
|
|
53496
|
+
await logger2.flush();
|
|
53077
53497
|
await session.dispose();
|
|
53078
53498
|
}
|
|
53079
53499
|
} catch (err) {
|
|
@@ -53365,7 +53785,7 @@ and the bash tool returned exit code 0.
|
|
|
53365
53785
|
1. Run the build command (shown in the prompt context below)
|
|
53366
53786
|
2. If the build succeeds (exit code 0), proceed with the commit
|
|
53367
53787
|
3. If the build fails (non-zero exit code), DO NOT commit. Instead:
|
|
53368
|
-
- Call the \`
|
|
53788
|
+
- Call the \`fn_report_build_failure\` tool with the real error details
|
|
53369
53789
|
- Stop immediately and do not run \`git commit\`
|
|
53370
53790
|
- Do not claim success in plain text
|
|
53371
53791
|
|
|
@@ -53404,7 +53824,7 @@ and the bash tool returned exit code 0.
|
|
|
53404
53824
|
1. Run the build command (shown in the prompt context below)
|
|
53405
53825
|
2. If the build succeeds (exit code 0), proceed with the commit
|
|
53406
53826
|
3. If the build fails (non-zero exit code), DO NOT commit. Instead:
|
|
53407
|
-
- Call the \`
|
|
53827
|
+
- Call the \`fn_report_build_failure\` tool with the real error details
|
|
53408
53828
|
- Stop immediately and do not run \`git commit\`
|
|
53409
53829
|
- Do not claim success in plain text
|
|
53410
53830
|
|
|
@@ -53947,6 +54367,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53947
54367
|
if (failedResult) {
|
|
53948
54368
|
let fixSuccess = false;
|
|
53949
54369
|
for (let fixAttempt = 1; fixAttempt <= maxFixRetries; fixAttempt++) {
|
|
54370
|
+
const fixAttemptStartedAt = Date.now();
|
|
53950
54371
|
mergerLog.log(`${taskId}: in-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53951
54372
|
await store.logEntry(taskId, `In-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53952
54373
|
fixSuccess = await attemptInMergeVerificationFix(
|
|
@@ -53961,16 +54382,19 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53961
54382
|
},
|
|
53962
54383
|
settings,
|
|
53963
54384
|
options,
|
|
54385
|
+
{ runId: mergeRunId, agentId: engineRunContext.agentId },
|
|
54386
|
+
fixAttempt,
|
|
53964
54387
|
effectiveTestCommand,
|
|
53965
54388
|
effectiveBuildCommand
|
|
53966
54389
|
);
|
|
54390
|
+
const fixAttemptDurationMs = Date.now() - fixAttemptStartedAt;
|
|
53967
54391
|
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`);
|
|
54392
|
+
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54393
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms \u2014 verification now passes`);
|
|
53970
54394
|
break;
|
|
53971
54395
|
}
|
|
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`);
|
|
54396
|
+
mergerLog.warn(`${taskId}: in-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
54397
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
53974
54398
|
}
|
|
53975
54399
|
if (fixSuccess) {
|
|
53976
54400
|
const authorArg = getCommitAuthorArg(settings);
|
|
@@ -53991,6 +54415,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53991
54415
|
const fixType = effectiveBuildCommand ? "build" : "test";
|
|
53992
54416
|
let fixSuccess = false;
|
|
53993
54417
|
for (let fixAttempt = 1; fixAttempt <= maxFixRetries; fixAttempt++) {
|
|
54418
|
+
const fixAttemptStartedAt = Date.now();
|
|
53994
54419
|
mergerLog.log(`${taskId}: in-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53995
54420
|
await store.logEntry(taskId, `In-merge verification fix attempt ${fixAttempt}/${maxFixRetries}`);
|
|
53996
54421
|
fixSuccess = await attemptInMergeVerificationFix(
|
|
@@ -54005,14 +54430,18 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54005
54430
|
},
|
|
54006
54431
|
settings,
|
|
54007
54432
|
options,
|
|
54433
|
+
{ runId: mergeRunId, agentId: engineRunContext.agentId },
|
|
54434
|
+
fixAttempt,
|
|
54008
54435
|
effectiveTestCommand,
|
|
54009
54436
|
effectiveBuildCommand
|
|
54010
54437
|
);
|
|
54438
|
+
const fixAttemptDurationMs = Date.now() - fixAttemptStartedAt;
|
|
54011
54439
|
if (fixSuccess) {
|
|
54012
|
-
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt}`);
|
|
54013
|
-
await store.logEntry(taskId, `In-merge verification fix succeeded`);
|
|
54440
|
+
mergerLog.log(`${taskId}: in-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54441
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix succeeded on attempt ${fixAttempt} in ${fixAttemptDurationMs}ms`);
|
|
54014
54442
|
break;
|
|
54015
54443
|
}
|
|
54444
|
+
await store.logEntry(taskId, `[timing] In-merge verification fix attempt ${fixAttempt} \u2014 verification still fails (${fixAttemptDurationMs}ms)`);
|
|
54016
54445
|
}
|
|
54017
54446
|
if (fixSuccess) {
|
|
54018
54447
|
const authorArg = getCommitAuthorArg(settings);
|
|
@@ -54091,8 +54520,15 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54091
54520
|
deletions = deletionsMatch ? Number.parseInt(deletionsMatch[1], 10) : 0;
|
|
54092
54521
|
} catch {
|
|
54093
54522
|
}
|
|
54523
|
+
const isEmptyCommit = filesChanged === 0;
|
|
54524
|
+
const recordedSha = isEmptyCommit ? void 0 : commitSha;
|
|
54525
|
+
if (isEmptyCommit) {
|
|
54526
|
+
mergerLog.warn(
|
|
54527
|
+
`${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.`
|
|
54528
|
+
);
|
|
54529
|
+
}
|
|
54094
54530
|
const mergeDetails = {
|
|
54095
|
-
commitSha,
|
|
54531
|
+
commitSha: recordedSha,
|
|
54096
54532
|
filesChanged,
|
|
54097
54533
|
insertions,
|
|
54098
54534
|
deletions,
|
|
@@ -54105,7 +54541,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
54105
54541
|
autoResolvedCount: result.autoResolvedCount
|
|
54106
54542
|
};
|
|
54107
54543
|
await store.updateTask(taskId, { mergeDetails });
|
|
54108
|
-
mergerLog.log(`${taskId}: merge details stored (commitSha: ${
|
|
54544
|
+
mergerLog.log(`${taskId}: merge details stored (commitSha: ${recordedSha?.slice(0, 8) ?? "<deferred>"})`);
|
|
54109
54545
|
} catch (err) {
|
|
54110
54546
|
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
|
|
54111
54547
|
}
|
|
@@ -54461,7 +54897,7 @@ async function runAiAgentForCommit(params) {
|
|
|
54461
54897
|
let buildFailed = false;
|
|
54462
54898
|
let buildErrorMessage = "";
|
|
54463
54899
|
const reportBuildFailureTool = {
|
|
54464
|
-
name: "
|
|
54900
|
+
name: "fn_report_build_failure",
|
|
54465
54901
|
label: "Report Build Failure",
|
|
54466
54902
|
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
54903
|
parameters: Type3.Object({
|
|
@@ -54678,7 +55114,7 @@ function buildMergePrompt(params) {
|
|
|
54678
55114
|
"This command is mandatory before commit.",
|
|
54679
55115
|
"Run it with the bash tool in the current worktree and inspect the actual exit code.",
|
|
54680
55116
|
"Only proceed if it exits 0.",
|
|
54681
|
-
"If it exits non-zero, call `
|
|
55117
|
+
"If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
|
|
54682
55118
|
);
|
|
54683
55119
|
}
|
|
54684
55120
|
if (buildCommand) {
|
|
@@ -54690,7 +55126,7 @@ function buildMergePrompt(params) {
|
|
|
54690
55126
|
"This command is mandatory before commit.",
|
|
54691
55127
|
"Run it with the bash tool in the current worktree and inspect the actual exit code.",
|
|
54692
55128
|
"Only commit if it exits 0.",
|
|
54693
|
-
"If it exits non-zero, call `
|
|
55129
|
+
"If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
|
|
54694
55130
|
);
|
|
54695
55131
|
}
|
|
54696
55132
|
return parts.join("\n");
|
|
@@ -55719,11 +56155,11 @@ function buildStepPrompt(taskDetail, stepIndex, rootDir, settings, worktreePath)
|
|
|
55719
56155
|
if (isLastStep) {
|
|
55720
56156
|
parts.push(
|
|
55721
56157
|
"",
|
|
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 \`
|
|
56158
|
+
`**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
56159
|
""
|
|
55724
56160
|
);
|
|
55725
56161
|
}
|
|
55726
|
-
parts.push("After completing this step, commit your changes and call
|
|
56162
|
+
parts.push("After completing this step, commit your changes and call fn_task_done(). Do NOT proceed to subsequent steps.");
|
|
55727
56163
|
return parts.join("\n");
|
|
55728
56164
|
}
|
|
55729
56165
|
function scopePromptToWorktree(prompt, rootDir, worktreePath) {
|
|
@@ -55778,7 +56214,7 @@ function buildReducedStepPrompt(taskDetail, stepIndex) {
|
|
|
55778
56214
|
"IMPORTANT: Your previous attempt hit the context window limit.",
|
|
55779
56215
|
"Do NOT repeat work that's already been done.",
|
|
55780
56216
|
"Check git status and git log to see what's been committed.",
|
|
55781
|
-
"Complete the remaining work and call
|
|
56217
|
+
"Complete the remaining work and call fn_task_done()."
|
|
55782
56218
|
];
|
|
55783
56219
|
return parts.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
55784
56220
|
}
|
|
@@ -56578,10 +57014,10 @@ ${attachmentsSection}${commandsSection}${memorySection}${progressSection}${steer
|
|
|
56578
57014
|
|
|
56579
57015
|
${reviewLevel === 0 ? "No reviews required. Implement directly." : ""}
|
|
56580
57016
|
${reviewLevel >= 1 ? `Before implementing each step (except Step 0 and the final step), call:
|
|
56581
|
-
\`
|
|
57017
|
+
\`fn_review_step(step=N, type="plan", step_name="...")\`` : ""}
|
|
56582
57018
|
${reviewLevel >= 2 ? `After implementing + committing each step, call:
|
|
56583
|
-
\`
|
|
56584
|
-
${reviewLevel >= 3 ? `After tests, also call
|
|
57019
|
+
\`fn_review_step(step=N, type="code", step_name="...", baseline="<SHA from before step>")\`` : ""}
|
|
57020
|
+
${reviewLevel >= 3 ? `After tests, also call fn_review_step with type="code" for test review.` : ""}
|
|
56585
57021
|
|
|
56586
57022
|
## Worktree Boundaries
|
|
56587
57023
|
|
|
@@ -56590,23 +57026,24 @@ You are running in an **isolated git worktree**. This means:
|
|
|
56590
57026
|
- **All code changes must be made inside the current worktree directory.** Do not modify files outside the worktree.
|
|
56591
57027
|
- **Exception \u2014 Project memory:** You MAY read and write to files under \`.fusion/memory/\` at the project root to save durable project learnings.
|
|
56592
57028
|
- **Exception \u2014 Task attachments:** You MAY read files under \`.fusion/tasks/{taskId}/attachments/\` at the project root for context.
|
|
57029
|
+
- **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
57030
|
- **Shell commands** run inside the worktree by default. Avoid using \`cd\` to navigate outside the worktree.
|
|
56594
57031
|
|
|
56595
57032
|
## Begin
|
|
56596
57033
|
|
|
56597
57034
|
${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 \`
|
|
57035
|
+
Use \`fn_task_update\` to report progress on every step transition.
|
|
57036
|
+
Use \`fn_task_log\` for important actions and decisions.
|
|
57037
|
+
Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
|
|
56601
57038
|
Commit at step boundaries: \`git commit -m "feat(${task.id}): complete Step N \u2014 description"${authorArg}\`
|
|
56602
|
-
When all steps are complete: call \`
|
|
57039
|
+
When all steps are complete: call \`fn_task_done()\`
|
|
56603
57040
|
|
|
56604
|
-
If a build command is configured, run that exact command in this worktree before calling \`
|
|
57041
|
+
If a build command is configured, run that exact command in this worktree before calling \`fn_task_done()\`.
|
|
56605
57042
|
Treat a non-zero exit code as a blocking failure. Do not claim success without a real passing run.
|
|
56606
57043
|
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 \`
|
|
57044
|
+
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.
|
|
57045
|
+
If the repo has a typecheck command, run it before \`fn_task_done()\` and fix any failures it reports.
|
|
57046
|
+
Use \`fn_task_create\` for truly separate follow-up work, not for fixes required to get tests, build, or typecheck back to green.
|
|
56610
57047
|
If lint is configured and failing, fix that too before completion.
|
|
56611
57048
|
**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
57049
|
}
|
|
@@ -56721,22 +57158,22 @@ You are working in a git worktree isolated from the main branch. Your job is to
|
|
|
56721
57158
|
You have tools to report progress. The board updates in real-time.
|
|
56722
57159
|
|
|
56723
57160
|
**Step lifecycle:**
|
|
56724
|
-
- Before starting a step: \`
|
|
56725
|
-
- After completing a step: \`
|
|
56726
|
-
- If skipping a step: \`
|
|
57161
|
+
- Before starting a step: \`fn_task_update(step=N, status="in-progress")\`
|
|
57162
|
+
- After completing a step: \`fn_task_update(step=N, status="done")\`
|
|
57163
|
+
- If skipping a step: \`fn_task_update(step=N, status="skipped")\`
|
|
56727
57164
|
|
|
56728
|
-
**Logging important actions:** \`
|
|
57165
|
+
**Logging important actions:** \`fn_task_log(message="what happened")\`
|
|
56729
57166
|
|
|
56730
|
-
**Out-of-scope work found during execution:** \`
|
|
57167
|
+
**Out-of-scope work found during execution:** \`fn_task_create(description="what needs doing")\`
|
|
56731
57168
|
When creating multiple related tasks, declare dependencies between them:
|
|
56732
|
-
\`
|
|
56733
|
-
\`
|
|
57169
|
+
\`fn_task_create(description="load door sounds", dependencies=[])\` \u2192 returns KB-050
|
|
57170
|
+
\`fn_task_create(description="play sound on door open/close", dependencies=["KB-050"])\`
|
|
56734
57171
|
|
|
56735
|
-
**Discovered a dependency:** \`
|
|
57172
|
+
**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
57173
|
|
|
56737
|
-
## Cross-model review via
|
|
57174
|
+
## Cross-model review via fn_review_step tool
|
|
56738
57175
|
|
|
56739
|
-
You have a \`
|
|
57176
|
+
You have a \`fn_review_step\` tool. It spawns a SEPARATE reviewer agent (different
|
|
56740
57177
|
model, read-only access) to independently assess your work.
|
|
56741
57178
|
|
|
56742
57179
|
**When to call it** \u2014 based on the Review Level in the PROMPT.md:
|
|
@@ -56744,8 +57181,8 @@ model, read-only access) to independently assess your work.
|
|
|
56744
57181
|
| Review Level | Before implementing | After implementing + committing |
|
|
56745
57182
|
|-------------|--------------------|---------------------------------|
|
|
56746
57183
|
| 0 (None) | \u2014 | \u2014 |
|
|
56747
|
-
| 1 (Plan) | \`
|
|
56748
|
-
| 2 (Plan+Code) | \`
|
|
57184
|
+
| 1 (Plan) | \`fn_review_step(step, "plan", step_name)\` | \u2014 |
|
|
57185
|
+
| 2 (Plan+Code) | \`fn_review_step(step, "plan", step_name)\` | \`fn_review_step(step, "code", step_name, baseline)\` |
|
|
56749
57186
|
| 3 (Full) | plan review | code review + test review |
|
|
56750
57187
|
|
|
56751
57188
|
**Skip reviews for** Step 0 (Preflight) and the final documentation/delivery step.
|
|
@@ -56754,13 +57191,13 @@ model, read-only access) to independently assess your work.
|
|
|
56754
57191
|
1. Before starting a step, capture baseline: \`git rev-parse HEAD\`
|
|
56755
57192
|
2. Implement the step
|
|
56756
57193
|
3. Commit
|
|
56757
|
-
4. Call \`
|
|
57194
|
+
4. Call \`fn_review_step\` with the baseline SHA so the reviewer sees only your changes
|
|
56758
57195
|
|
|
56759
57196
|
**Handling verdicts:**
|
|
56760
57197
|
- **APPROVE** \u2192 proceed to next step
|
|
56761
57198
|
- **REVISE (code review)** \u2192 **enforced**. You MUST fix the issues, commit again,
|
|
56762
|
-
and re-run \`
|
|
56763
|
-
\`
|
|
57199
|
+
and re-run \`fn_review_step(type="code")\` before the step can be marked done.
|
|
57200
|
+
\`fn_task_update(status="done")\` will be rejected until the code review passes.
|
|
56764
57201
|
- **REVISE (plan review)** \u2192 advisory. Incorporate the feedback at your discretion
|
|
56765
57202
|
and proceed with implementation. No re-review is required.
|
|
56766
57203
|
- **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 +57207,13 @@ model, read-only access) to independently assess your work.
|
|
|
56770
57207
|
|
|
56771
57208
|
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
57209
|
|
|
56773
|
-
- **Save a document:** \`
|
|
56774
|
-
- **Read a document:** \`
|
|
56775
|
-
- **List all documents:** \`
|
|
57210
|
+
- **Save a document:** \`fn_task_document_write(key="plan", content="...")\`
|
|
57211
|
+
- **Read a document:** \`fn_task_document_read(key="plan")\`
|
|
57212
|
+
- **List all documents:** \`fn_task_document_read()\` (no key)
|
|
56776
57213
|
|
|
56777
57214
|
Documents are versioned \u2014 each write creates a new revision. Use meaningful keys like "plan", "notes", "research", "architecture".
|
|
56778
57215
|
|
|
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 \`
|
|
57216
|
+
**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
57217
|
|
|
56781
57218
|
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
57219
|
|
|
@@ -56792,6 +57229,7 @@ You are running in an **isolated git worktree**. This means:
|
|
|
56792
57229
|
- **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
57230
|
- **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
57231
|
- **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.
|
|
57232
|
+
- **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
57233
|
- **Shell commands** run inside the worktree by default. Avoid using cd to navigate outside the worktree.
|
|
56796
57234
|
|
|
56797
57235
|
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 +57240,7 @@ If you attempt to write to a path outside the worktree, the file tools will reje
|
|
|
56802
57240
|
- Read "Context to Read First" files before starting
|
|
56803
57241
|
- Follow the "Do NOT" section strictly
|
|
56804
57242
|
- 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 \`
|
|
57243
|
+
- Use \`fn_task_create\` for genuinely separate follow-up work, not for mandatory fixes required to make this task land cleanly
|
|
56806
57244
|
- Update documentation listed in "Must Update" and check "Check If Affected"
|
|
56807
57245
|
- NEVER delete, remove, or gut modules, interfaces, settings, exports, or test files outside your File Scope
|
|
56808
57246
|
- NEVER remove features as "cleanup" \u2014 if something seems unused, create a task for investigation instead
|
|
@@ -56813,14 +57251,14 @@ If you attempt to write to a path outside the worktree, the file tools will reje
|
|
|
56813
57251
|
|
|
56814
57252
|
You can spawn child agents to handle parallel work or specialized sub-tasks:
|
|
56815
57253
|
|
|
56816
|
-
**When to use \`
|
|
57254
|
+
**When to use \`fn_spawn_agent\`:**
|
|
56817
57255
|
- Parallel work that can be divided into independent chunks
|
|
56818
57256
|
- Specialized tasks requiring different expertise or tools
|
|
56819
57257
|
- Delegation of sub-tasks to specialized agents
|
|
56820
57258
|
|
|
56821
57259
|
**How to spawn:**
|
|
56822
57260
|
\`\`\`javascript
|
|
56823
|
-
|
|
57261
|
+
fn_spawn_agent({
|
|
56824
57262
|
name: "researcher",
|
|
56825
57263
|
role: "engineer",
|
|
56826
57264
|
task: "Research best practices for authentication in React applications"
|
|
@@ -56830,7 +57268,7 @@ spawn_agent({
|
|
|
56830
57268
|
**Child agent behavior:**
|
|
56831
57269
|
- Each child runs in its own git worktree (branched from your worktree)
|
|
56832
57270
|
- Children execute autonomously and report completion
|
|
56833
|
-
- When you end (
|
|
57271
|
+
- When you end (fn_task_done), all spawned children are terminated
|
|
56834
57272
|
- Check AgentStore for spawned agent status
|
|
56835
57273
|
|
|
56836
57274
|
**Limits:**
|
|
@@ -56840,13 +57278,13 @@ spawn_agent({
|
|
|
56840
57278
|
## Completion
|
|
56841
57279
|
After all steps are done, lint passes, tests pass, typecheck passes, and docs are updated:
|
|
56842
57280
|
\`\`\`bash
|
|
56843
|
-
Call \`
|
|
57281
|
+
Call \`fn_task_done()\` to signal completion.
|
|
56844
57282
|
\`\`\`
|
|
56845
57283
|
|
|
56846
57284
|
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 \`
|
|
57285
|
+
- Run the exact build command in the current worktree before \`fn_task_done()\`
|
|
56848
57286
|
- Do not claim the build passes unless you actually ran it and got exit code 0
|
|
56849
|
-
- If the build fails, do NOT call \`
|
|
57287
|
+
- If the build fails, do NOT call \`fn_task_done()\`; keep working until it passes
|
|
56850
57288
|
|
|
56851
57289
|
Lint, tests, and typecheck are also hard quality gates:
|
|
56852
57290
|
- Keep fixing failures until lint, the configured/full test suite, and typecheck all pass
|
|
@@ -57104,15 +57542,15 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57104
57542
|
}
|
|
57105
57543
|
/**
|
|
57106
57544
|
* 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
|
|
57545
|
+
* Used to detect tasks that called fn_task_done() but never transitioned to in-review
|
|
57546
|
+
* (e.g., killed by stuck detector after fn_task_done but before moveTask).
|
|
57109
57547
|
*/
|
|
57110
57548
|
isTaskWorkComplete(task) {
|
|
57111
57549
|
if (task.steps.length === 0) return false;
|
|
57112
57550
|
return task.steps.every((s) => s.status === "done" || s.status === "skipped");
|
|
57113
57551
|
}
|
|
57114
57552
|
isNoProgressNoTaskDoneFailure(task) {
|
|
57115
|
-
return task.status === "failed" && task.error?.includes("without calling
|
|
57553
|
+
return task.status === "failed" && task.error?.includes("without calling fn_task_done") === true && task.steps.every((step) => step.status === "pending");
|
|
57116
57554
|
}
|
|
57117
57555
|
async clearResumeFailureState(task) {
|
|
57118
57556
|
const updates = {};
|
|
@@ -57288,7 +57726,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57288
57726
|
continue;
|
|
57289
57727
|
}
|
|
57290
57728
|
if (this.isNoProgressNoTaskDoneFailure(task)) {
|
|
57291
|
-
executorLog.log(`${task.id} failed without
|
|
57729
|
+
executorLog.log(`${task.id} failed without fn_task_done and has no step progress \u2014 leaving for self-healing requeue`);
|
|
57292
57730
|
continue;
|
|
57293
57731
|
}
|
|
57294
57732
|
executorLog.log(`Resuming ${task.id}: ${task.title || task.description.slice(0, 60)}`);
|
|
@@ -57513,13 +57951,15 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57513
57951
|
await this.store.logEntry(task.id, `Worktree created at ${worktreePath}`, void 0, this.currentRunContext);
|
|
57514
57952
|
}
|
|
57515
57953
|
if (settings.worktreeInitCommand) {
|
|
57954
|
+
const initStartedAt = Date.now();
|
|
57516
57955
|
try {
|
|
57517
57956
|
const initResult = await runConfiguredCommand(settings.worktreeInitCommand, worktreePath, 3e5);
|
|
57518
57957
|
if (initResult.spawnError || initResult.timedOut || initResult.exitCode !== 0) {
|
|
57519
57958
|
throw new Error(configuredCommandErrorMessage(initResult));
|
|
57520
57959
|
}
|
|
57521
|
-
await this.store.logEntry(task.id,
|
|
57960
|
+
await this.store.logEntry(task.id, `[timing] Worktree init command completed in ${Date.now() - initStartedAt}ms`, settings.worktreeInitCommand, this.currentRunContext);
|
|
57522
57961
|
} catch (err) {
|
|
57962
|
+
await this.store.logEntry(task.id, `[timing] Worktree init command failed after ${Date.now() - initStartedAt}ms`, void 0, this.currentRunContext);
|
|
57523
57963
|
const execError = err instanceof Error ? err : new Error(String(err));
|
|
57524
57964
|
const message = "stderr" in execError && typeof execError.stderr === "string" ? String(execError.stderr) : execError.message;
|
|
57525
57965
|
executorLog.error(`${task.id}: worktree init command failed \u2014 first test run will likely fail: ${message}`);
|
|
@@ -57534,12 +57974,13 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57534
57974
|
if (settings.setupScript) {
|
|
57535
57975
|
const scriptCommand = settings.scripts?.[settings.setupScript];
|
|
57536
57976
|
if (scriptCommand) {
|
|
57977
|
+
const setupStartedAt = Date.now();
|
|
57537
57978
|
try {
|
|
57538
57979
|
const setupResult = await runConfiguredCommand(scriptCommand, worktreePath, 12e4);
|
|
57539
57980
|
if (setupResult.spawnError || setupResult.timedOut || setupResult.exitCode !== 0) {
|
|
57540
57981
|
throw new Error(configuredCommandErrorMessage(setupResult));
|
|
57541
57982
|
}
|
|
57542
|
-
await this.store.logEntry(task.id, `Setup script '${settings.setupScript}' completed`, scriptCommand, this.currentRunContext);
|
|
57983
|
+
await this.store.logEntry(task.id, `[timing] Setup script '${settings.setupScript}' completed in ${Date.now() - setupStartedAt}ms`, scriptCommand, this.currentRunContext);
|
|
57543
57984
|
} catch (err) {
|
|
57544
57985
|
const execError = err instanceof Error ? err : new Error(String(err));
|
|
57545
57986
|
const message = "stderr" in execError && typeof execError.stderr === "string" ? String(execError.stderr) : execError.message;
|
|
@@ -57705,7 +58146,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57705
58146
|
await retryableStepWork();
|
|
57706
58147
|
}
|
|
57707
58148
|
} catch (err) {
|
|
57708
|
-
const errorMessage
|
|
58149
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
57709
58150
|
if (this.depAborted.has(task.id)) {
|
|
57710
58151
|
this.depAborted.delete(task.id);
|
|
57711
58152
|
await this.handleDepAbortCleanup(task.id, worktreePath);
|
|
@@ -57749,7 +58190,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57749
58190
|
stuckRequeue = null;
|
|
57750
58191
|
return;
|
|
57751
58192
|
}
|
|
57752
|
-
executorLog.error(`\u2717 ${task.id} transient error retries exhausted: ${
|
|
58193
|
+
executorLog.error(`\u2717 ${task.id} transient error retries exhausted: ${errorDetail}`);
|
|
58194
|
+
if (errorStack) {
|
|
58195
|
+
await this.store.logEntry(task.id, `Transient error retries exhausted: ${errorMessage}`, errorStack, this.currentRunContext);
|
|
58196
|
+
}
|
|
57753
58197
|
await this.store.updateTask(task.id, {
|
|
57754
58198
|
status: "failed",
|
|
57755
58199
|
error: errorMessage,
|
|
@@ -57760,8 +58204,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57760
58204
|
executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
|
|
57761
58205
|
this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
57762
58206
|
} else {
|
|
57763
|
-
executorLog.error(`\u2717 ${task.id} step-session execution failed:`,
|
|
57764
|
-
await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`,
|
|
58207
|
+
executorLog.error(`\u2717 ${task.id} step-session execution failed:`, errorDetail);
|
|
58208
|
+
await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
57765
58209
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
57766
58210
|
await this.store.moveTask(task.id, "in-review");
|
|
57767
58211
|
executorLog.log(`\u2717 ${task.id} step-session execution failed \u2192 in-review`);
|
|
@@ -57811,7 +58255,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57811
58255
|
const reflectionTools = this.options.reflectionService && settings.reflectionEnabled && assignedAgentId ? [createReflectOnPerformanceTool(this.options.reflectionService, assignedAgentId)] : [];
|
|
57812
58256
|
const assignedAgent = assignedAgentId && this.options.agentStore ? await this.options.agentStore.getAgent(assignedAgentId).catch(() => null) : null;
|
|
57813
58257
|
if (executionMode === "fast") {
|
|
57814
|
-
executorLog.log(`${task.id}: fast mode \u2014
|
|
58258
|
+
executorLog.log(`${task.id}: fast mode \u2014 fn_review_step tool not injected`);
|
|
57815
58259
|
}
|
|
57816
58260
|
const customTools = [
|
|
57817
58261
|
this.createTaskUpdateTool(task.id, codeReviewVerdicts, sessionRef, stepCheckpoints, stuckDetector),
|
|
@@ -57821,7 +58265,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57821
58265
|
this.createTaskDoneTool(task.id, () => {
|
|
57822
58266
|
taskDone = true;
|
|
57823
58267
|
}),
|
|
57824
|
-
// Skip
|
|
58268
|
+
// Skip fn_review_step tool in fast mode — fast mode bypasses automated review gates
|
|
57825
58269
|
...executionMode !== "fast" ? [
|
|
57826
58270
|
this.createReviewStepTool(task.id, worktreePath, detail.prompt, codeReviewVerdicts, sessionRef, stepCheckpoints, detail, stuckDetector)
|
|
57827
58271
|
] : [],
|
|
@@ -57978,7 +58422,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57978
58422
|
"3. Review the PROMPT.md steps to see which are still pending",
|
|
57979
58423
|
"",
|
|
57980
58424
|
"Take a DIFFERENT approach from what you were doing before.",
|
|
57981
|
-
"If the current step is complete, call
|
|
58425
|
+
"If the current step is complete, call fn_task_update to mark it done and move to the next step.",
|
|
57982
58426
|
"If you're stuck on a problem, try a simpler or alternative solution.",
|
|
57983
58427
|
"",
|
|
57984
58428
|
"Continue the task from where you left off."
|
|
@@ -58016,8 +58460,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58016
58460
|
const implicitCheck = await this.store.getTask(task.id);
|
|
58017
58461
|
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
58018
58462
|
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
|
|
58463
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
58464
|
+
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
58465
|
}
|
|
58022
58466
|
}
|
|
58023
58467
|
if (taskDone) {
|
|
@@ -58054,11 +58498,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58054
58498
|
while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
|
|
58055
58499
|
taskDoneSessionRetries++;
|
|
58056
58500
|
executorLog.log(
|
|
58057
|
-
`\u26A0 ${task.id} finished without
|
|
58501
|
+
`\u26A0 ${task.id} finished without fn_task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
|
|
58058
58502
|
);
|
|
58059
58503
|
await this.store.logEntry(
|
|
58060
58504
|
task.id,
|
|
58061
|
-
`Agent finished without calling
|
|
58505
|
+
`Agent finished without calling fn_task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`,
|
|
58062
58506
|
void 0,
|
|
58063
58507
|
this.currentRunContext
|
|
58064
58508
|
);
|
|
@@ -58100,10 +58544,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58100
58544
|
});
|
|
58101
58545
|
stuckDetector?.trackTask(task.id, retrySession);
|
|
58102
58546
|
const retryPrompt = [
|
|
58103
|
-
"Your previous session ended without calling the
|
|
58547
|
+
"Your previous session ended without calling the fn_task_done tool.",
|
|
58104
58548
|
"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
|
|
58549
|
+
"1. If the work is done, call fn_task_done with a summary of what was accomplished.",
|
|
58550
|
+
"2. If there is remaining work, finish it and then call fn_task_done.",
|
|
58107
58551
|
"",
|
|
58108
58552
|
"Original task:",
|
|
58109
58553
|
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
@@ -58115,8 +58559,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58115
58559
|
const implicitCheck = await this.store.getTask(task.id);
|
|
58116
58560
|
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
58117
58561
|
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
|
|
58562
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit fn_task_done`);
|
|
58563
|
+
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
58564
|
}
|
|
58121
58565
|
}
|
|
58122
58566
|
}
|
|
@@ -58148,7 +58592,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58148
58592
|
} else {
|
|
58149
58593
|
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
58150
58594
|
const nextRequeueCount = priorRequeues + 1;
|
|
58151
|
-
const errorMessage = `Agent finished without calling
|
|
58595
|
+
const errorMessage = `Agent finished without calling fn_task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
58152
58596
|
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
58153
58597
|
await this.store.updateTask(task.id, {
|
|
58154
58598
|
status: "failed",
|
|
@@ -58167,7 +58611,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58167
58611
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
58168
58612
|
await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
|
|
58169
58613
|
await this.store.moveTask(task.id, "in-review");
|
|
58170
|
-
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no
|
|
58614
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no fn_task_done \u2192 in-review`);
|
|
58171
58615
|
}
|
|
58172
58616
|
this.options.onError?.(task, new Error(errorMessage));
|
|
58173
58617
|
}
|
|
@@ -58203,7 +58647,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58203
58647
|
await retryableWork();
|
|
58204
58648
|
}
|
|
58205
58649
|
} catch (err) {
|
|
58206
|
-
const errorMessage
|
|
58650
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
58207
58651
|
if (this.depAborted.has(task.id)) {
|
|
58208
58652
|
this.depAborted.delete(task.id);
|
|
58209
58653
|
await this.handleDepAbortCleanup(task.id, worktreePath);
|
|
@@ -58271,7 +58715,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58271
58715
|
"2. Identify the most critical remaining work",
|
|
58272
58716
|
"3. Complete it with a simpler, more focused approach",
|
|
58273
58717
|
"",
|
|
58274
|
-
"Do not repeat what's already been done. Just complete the task and call
|
|
58718
|
+
"Do not repeat what's already been done. Just complete the task and call fn_task_done."
|
|
58275
58719
|
].join("\n");
|
|
58276
58720
|
await promptWithFallback(activeEntry.session, reducedPrompt);
|
|
58277
58721
|
checkSessionError(activeEntry.session);
|
|
@@ -58346,8 +58790,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58346
58790
|
await this.store.moveTask(task.id, "todo");
|
|
58347
58791
|
return;
|
|
58348
58792
|
}
|
|
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}`,
|
|
58793
|
+
executorLog.error(`\u2717 ${task.id} transient error retries exhausted (${MAX_RECOVERY_RETRIES} attempts): ${errorDetail}`);
|
|
58794
|
+
await this.store.logEntry(task.id, `Transient error retries exhausted after ${MAX_RECOVERY_RETRIES} attempts: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
58351
58795
|
await this.store.updateTask(task.id, {
|
|
58352
58796
|
status: "failed",
|
|
58353
58797
|
error: errorMessage,
|
|
@@ -58359,8 +58803,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58359
58803
|
this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
|
|
58360
58804
|
return;
|
|
58361
58805
|
}
|
|
58362
|
-
executorLog.error(`\u2717 ${task.id} execution failed:`,
|
|
58363
|
-
await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`,
|
|
58806
|
+
executorLog.error(`\u2717 ${task.id} execution failed:`, errorDetail);
|
|
58807
|
+
await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
|
|
58364
58808
|
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
58365
58809
|
await this.store.moveTask(task.id, "in-review");
|
|
58366
58810
|
executorLog.log(`\u2717 ${task.id} execution failed \u2192 in-review`);
|
|
@@ -58408,7 +58852,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58408
58852
|
createTaskUpdateTool(taskId, codeReviewVerdicts, sessionRef, stepCheckpoints, stuckDetector) {
|
|
58409
58853
|
const store = this.store;
|
|
58410
58854
|
return {
|
|
58411
|
-
name: "
|
|
58855
|
+
name: "fn_task_update",
|
|
58412
58856
|
label: "Update Step",
|
|
58413
58857
|
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
58858
|
parameters: taskUpdateParams,
|
|
@@ -58421,7 +58865,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58421
58865
|
return {
|
|
58422
58866
|
content: [{
|
|
58423
58867
|
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
|
|
58868
|
+
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
58869
|
}],
|
|
58426
58870
|
details: {}
|
|
58427
58871
|
};
|
|
@@ -58460,7 +58904,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58460
58904
|
createTaskAddDepTool(taskId) {
|
|
58461
58905
|
const store = this.store;
|
|
58462
58906
|
return {
|
|
58463
|
-
name: "
|
|
58907
|
+
name: "fn_task_add_dep",
|
|
58464
58908
|
label: "Add Dependency",
|
|
58465
58909
|
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
58910
|
parameters: taskAddDepParams,
|
|
@@ -58530,7 +58974,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58530
58974
|
createTaskDoneTool(taskId, onDone) {
|
|
58531
58975
|
const store = this.store;
|
|
58532
58976
|
return {
|
|
58533
|
-
name: "
|
|
58977
|
+
name: "fn_task_done",
|
|
58534
58978
|
label: "Mark Task Done",
|
|
58535
58979
|
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
58980
|
parameters: Type4.Object({
|
|
@@ -58545,7 +58989,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58545
58989
|
return {
|
|
58546
58990
|
content: [{
|
|
58547
58991
|
type: "text",
|
|
58548
|
-
text: `Cannot mark task done yet \u2014 ${completionBlocker}. Resolve the blocker before calling
|
|
58992
|
+
text: `Cannot mark task done yet \u2014 ${completionBlocker}. Resolve the blocker before calling fn_task_done().`
|
|
58549
58993
|
}],
|
|
58550
58994
|
details: {}
|
|
58551
58995
|
};
|
|
@@ -58569,7 +59013,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58569
59013
|
};
|
|
58570
59014
|
}
|
|
58571
59015
|
/**
|
|
58572
|
-
* Create the
|
|
59016
|
+
* Create the fn_review_step tool for the executor agent.
|
|
58573
59017
|
*
|
|
58574
59018
|
* When the reviewer returns a RETHINK verdict, this tool:
|
|
58575
59019
|
* 1. Runs `git reset --hard <baseline>` to revert file changes
|
|
@@ -58581,7 +59025,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58581
59025
|
const store = this.store;
|
|
58582
59026
|
const options = this.options;
|
|
58583
59027
|
return {
|
|
58584
|
-
name: "
|
|
59028
|
+
name: "fn_review_step",
|
|
58585
59029
|
label: "Review Step",
|
|
58586
59030
|
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
59031
|
parameters: reviewStepParams,
|
|
@@ -58651,7 +59095,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
58651
59095
|
if (reviewType === "code") {
|
|
58652
59096
|
text = `REVISE \u2014 this step cannot be marked done until the code review passes.
|
|
58653
59097
|
|
|
58654
|
-
Fix the issues below, commit your changes, and call
|
|
59098
|
+
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
59099
|
|
|
58656
59100
|
${result.review}`;
|
|
58657
59101
|
} else {
|
|
@@ -58851,7 +59295,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
58851
59295
|
executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping revision injection`);
|
|
58852
59296
|
return;
|
|
58853
59297
|
}
|
|
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 `
|
|
59298
|
+
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
59299
|
const revisionSectionHeader = "## Workflow Revision Instructions";
|
|
58856
59300
|
const revisionSectionContent = `${revisionSectionHeader}
|
|
58857
59301
|
|
|
@@ -59150,6 +59594,7 @@ ${failureFeedback}
|
|
|
59150
59594
|
await this.store.logEntry(task.id, `[pre-merge] Starting workflow step: ${ws.name} (${stepMode} mode)`);
|
|
59151
59595
|
executorLog.log(`${task.id} \u2014 [pre-merge] running workflow step: ${ws.name} (${stepMode} mode)`);
|
|
59152
59596
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59597
|
+
const stepStartedAtMs = Date.now();
|
|
59153
59598
|
results.push({
|
|
59154
59599
|
workflowStepId: ws.id,
|
|
59155
59600
|
workflowStepName: ws.name,
|
|
@@ -59162,6 +59607,7 @@ ${failureFeedback}
|
|
|
59162
59607
|
const result = stepMode === "script" ? await this.executeScriptWorkflowStep(task, ws, worktreePath, settings) : await this.executeWorkflowStep(task, ws, worktreePath, settings);
|
|
59163
59608
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59164
59609
|
if (result.success) {
|
|
59610
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' completed in ${Date.now() - stepStartedAtMs}ms`);
|
|
59165
59611
|
await this.store.logEntry(task.id, `[pre-merge] Workflow step completed: ${ws.name}`);
|
|
59166
59612
|
executorLog.log(`${task.id} \u2014 [pre-merge] workflow step passed: ${ws.name}`);
|
|
59167
59613
|
const existingIdx = results.findIndex((r) => r.workflowStepId === ws.id);
|
|
@@ -59175,6 +59621,7 @@ ${failureFeedback}
|
|
|
59175
59621
|
}
|
|
59176
59622
|
await this.store.updateTask(task.id, { workflowStepResults: results });
|
|
59177
59623
|
} else if (result.revisionRequested) {
|
|
59624
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' requested revision after ${Date.now() - stepStartedAtMs}ms`);
|
|
59178
59625
|
await this.store.logEntry(
|
|
59179
59626
|
task.id,
|
|
59180
59627
|
`[pre-merge] Workflow step requested revision: ${ws.name}`,
|
|
@@ -59198,6 +59645,7 @@ ${failureFeedback}
|
|
|
59198
59645
|
stepName: ws.name
|
|
59199
59646
|
};
|
|
59200
59647
|
} else {
|
|
59648
|
+
await this.store.logEntry(task.id, `[timing] Workflow step '${ws.name}' failed after ${Date.now() - stepStartedAtMs}ms`);
|
|
59201
59649
|
await this.store.logEntry(
|
|
59202
59650
|
task.id,
|
|
59203
59651
|
`[pre-merge] Workflow step failed: ${ws.name}`,
|
|
@@ -59222,14 +59670,14 @@ ${failureFeedback}
|
|
|
59222
59670
|
};
|
|
59223
59671
|
}
|
|
59224
59672
|
} catch (err) {
|
|
59225
|
-
const errorMessage
|
|
59673
|
+
const { message: errorMessage, detail: errorDetail, stack: errorStack } = formatError(err);
|
|
59226
59674
|
const completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59227
59675
|
await this.store.logEntry(
|
|
59228
59676
|
task.id,
|
|
59229
59677
|
`[pre-merge] Workflow step failed: ${ws.name}`,
|
|
59230
|
-
|
|
59678
|
+
errorStack ?? errorDetail
|
|
59231
59679
|
);
|
|
59232
|
-
executorLog.error(`${task.id} \u2014 [pre-merge] workflow step error: ${ws.name} \u2014 ${
|
|
59680
|
+
executorLog.error(`${task.id} \u2014 [pre-merge] workflow step error: ${ws.name} \u2014 ${errorDetail}`);
|
|
59233
59681
|
const existingIdx = results.findIndex((r) => r.workflowStepId === ws.id);
|
|
59234
59682
|
if (existingIdx >= 0) {
|
|
59235
59683
|
results[existingIdx] = {
|
|
@@ -59855,7 +60303,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
59855
60303
|
/**
|
|
59856
60304
|
* When the engine restarts mid-step, an `in-progress` step may have already
|
|
59857
60305
|
* passed its code review (log: `code review Step N: APPROVE`) but not yet
|
|
59858
|
-
* been flipped to `done` by the agent's next `
|
|
60306
|
+
* been flipped to `done` by the agent's next `fn_task_update` call. Without
|
|
59859
60307
|
* intervention, the next executor pass re-enters the step and replays plan
|
|
59860
60308
|
* + code review, which we've measured at 5–20 min of pure waste per restart.
|
|
59861
60309
|
*
|
|
@@ -60118,14 +60566,14 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
60118
60566
|
}
|
|
60119
60567
|
}
|
|
60120
60568
|
/**
|
|
60121
|
-
* Create the
|
|
60569
|
+
* Create the fn_spawn_agent tool definition.
|
|
60122
60570
|
* Allows the parent agent to spawn child agents with delegated tasks.
|
|
60123
60571
|
*/
|
|
60124
60572
|
createSpawnAgentTool(taskId, worktreePath, settings) {
|
|
60125
60573
|
return {
|
|
60126
|
-
name: "
|
|
60574
|
+
name: "fn_spawn_agent",
|
|
60127
60575
|
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 (
|
|
60576
|
+
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
60577
|
parameters: spawnAgentParams,
|
|
60130
60578
|
execute: async (_id, params) => {
|
|
60131
60579
|
const { name, role, task: taskPrompt } = params;
|
|
@@ -60645,6 +61093,7 @@ var init_scheduler = __esm({
|
|
|
60645
61093
|
}
|
|
60646
61094
|
}
|
|
60647
61095
|
if (todo.length === 0) return;
|
|
61096
|
+
todo = sortTasksByPriorityThenAgeAndId(todo);
|
|
60648
61097
|
const activeScopes = /* @__PURE__ */ new Map();
|
|
60649
61098
|
if (settings.groupOverlappingFiles) {
|
|
60650
61099
|
for (const t of inProgress) {
|
|
@@ -64822,14 +65271,14 @@ var init_agent_heartbeat = __esm({
|
|
|
64822
65271
|
Your job:
|
|
64823
65272
|
1. Check your assigned task \u2014 read the description and PROMPT.md if present.
|
|
64824
65273
|
2. Do ONE useful action: analyze, review, create follow-up tasks, or log findings.
|
|
64825
|
-
3. Use
|
|
64826
|
-
4. Use
|
|
64827
|
-
5. Call
|
|
65274
|
+
3. Use fn_task_create to spawn follow-up work, fn_task_log to record observations.
|
|
65275
|
+
4. Use fn_task_document_write to save durable findings, plans, or research notes.
|
|
65276
|
+
5. Call fn_heartbeat_done when finished with an optional summary of what was accomplished.
|
|
64828
65277
|
|
|
64829
65278
|
Keep work lightweight \u2014 this is a single-pass check, not a full implementation run.
|
|
64830
|
-
You have readonly file access plus
|
|
65279
|
+
You have readonly file access plus fn_task_create, fn_task_log, and fn_task_document tools.
|
|
64831
65280
|
|
|
64832
|
-
**Task Documents:** Save important findings with
|
|
65281
|
+
**Task Documents:** Save important findings with fn_task_document_write(key="...", content="...").
|
|
64833
65282
|
Documents persist across sessions and are visible in the dashboard's Documents tab.
|
|
64834
65283
|
|
|
64835
65284
|
## Memory Boundaries
|
|
@@ -64842,12 +65291,12 @@ You may receive an Agent Memory section and a Project Memory section.
|
|
|
64842
65291
|
## Processing Messages
|
|
64843
65292
|
|
|
64844
65293
|
When you are woken by an incoming message (source includes "wake-on-message"), you should:
|
|
64845
|
-
1. Use
|
|
65294
|
+
1. Use fn_read_messages to check your inbox for unread messages.
|
|
64846
65295
|
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
|
|
65296
|
+
- If the message requires a response, use fn_send_message to reply.
|
|
65297
|
+
- When replying, include 'reply_to_message_id' with the original message ID from fn_read_messages output.
|
|
65298
|
+
- If the message is informational, acknowledge it by logging with fn_task_log.
|
|
65299
|
+
- If the message requests work, create a follow-up task with fn_task_create or handle it directly.
|
|
64851
65300
|
3. After processing messages, continue with your normal heartbeat duties.
|
|
64852
65301
|
|
|
64853
65302
|
When sending messages:
|
|
@@ -64860,17 +65309,17 @@ When sending messages:
|
|
|
64860
65309
|
Your job:
|
|
64861
65310
|
1. Review your context \u2014 check messages, memory, and project state.
|
|
64862
65311
|
2. Do ONE useful action: analyze, create follow-up tasks, delegate work, or update memory.
|
|
64863
|
-
3. Use
|
|
64864
|
-
4. Use
|
|
64865
|
-
5. Call
|
|
65312
|
+
3. Use fn_task_create to spawn follow-up work.
|
|
65313
|
+
4. Use fn_list_agents and fn_delegate_task to coordinate with other agents.
|
|
65314
|
+
5. Call fn_heartbeat_done when finished with an optional summary of what was accomplished.
|
|
64866
65315
|
|
|
64867
65316
|
Keep work lightweight \u2014 this is a single-pass ambient check, not a full implementation run.
|
|
64868
65317
|
You have readonly file access plus:
|
|
64869
|
-
-
|
|
64870
|
-
-
|
|
64871
|
-
-
|
|
64872
|
-
-
|
|
64873
|
-
-
|
|
65318
|
+
- fn_task_create
|
|
65319
|
+
- fn_list_agents and fn_delegate_task
|
|
65320
|
+
- fn_memory_search, fn_memory_get, and fn_memory_append
|
|
65321
|
+
- fn_heartbeat_done
|
|
65322
|
+
- fn_send_message and fn_read_messages when messaging is enabled for this run (they may not always be available)
|
|
64874
65323
|
|
|
64875
65324
|
## Memory Boundaries
|
|
64876
65325
|
|
|
@@ -64882,12 +65331,12 @@ You may receive an Agent Memory section and a Project Memory section.
|
|
|
64882
65331
|
## Processing Messages
|
|
64883
65332
|
|
|
64884
65333
|
When you are woken by an incoming message (source includes "wake-on-message"), you should:
|
|
64885
|
-
1. If
|
|
65334
|
+
1. If fn_read_messages is available, use it to check your inbox for unread messages.
|
|
64886
65335
|
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
|
|
65336
|
+
- If the message requires a response and fn_send_message is available, use fn_send_message to reply.
|
|
65337
|
+
- When replying, include 'reply_to_message_id' with the original message ID from fn_read_messages output.
|
|
65338
|
+
- If the message is informational, acknowledge it and respond via fn_send_message when appropriate.
|
|
65339
|
+
- If the message requests work, create a follow-up task with fn_task_create.
|
|
64891
65340
|
3. After processing messages, continue with your ambient work.
|
|
64892
65341
|
|
|
64893
65342
|
When sending messages:
|
|
@@ -65294,7 +65743,7 @@ When sending messages:
|
|
|
65294
65743
|
* Implements the Paperclip-style execution model:
|
|
65295
65744
|
* 1. Wake — start a heartbeat run record
|
|
65296
65745
|
* 2. Check inbox — resolve the agent's assigned task
|
|
65297
|
-
* 3. Work — run a lightweight agent session with readonly tools +
|
|
65746
|
+
* 3. Work — run a lightweight agent session with readonly tools + fn_task_create/fn_task_log
|
|
65298
65747
|
* 4. Exit — record results and complete the run
|
|
65299
65748
|
*
|
|
65300
65749
|
* Budget governance:
|
|
@@ -65544,7 +65993,7 @@ When sending messages:
|
|
|
65544
65993
|
stdoutExcerpt += delta.slice(0, remaining);
|
|
65545
65994
|
};
|
|
65546
65995
|
const heartbeatDoneTool = {
|
|
65547
|
-
name: "
|
|
65996
|
+
name: "fn_heartbeat_done",
|
|
65548
65997
|
label: "Heartbeat Done",
|
|
65549
65998
|
description: "Signal that the heartbeat execution is complete. Call when finished.",
|
|
65550
65999
|
parameters: heartbeatDoneParams,
|
|
@@ -65673,16 +66122,16 @@ When sending messages:
|
|
|
65673
66122
|
"You have identity (soul, instructions, and/or memory) loaded, which means you can perform",
|
|
65674
66123
|
"useful ambient work. Here are some things you can do:",
|
|
65675
66124
|
"",
|
|
65676
|
-
"1. **Check your messages** \u2014 Use
|
|
65677
|
-
" and use
|
|
66125
|
+
"1. **Check your messages** \u2014 Use fn_read_messages to review any pending messages",
|
|
66126
|
+
" and use fn_send_message with reply_to_message_id when responding.",
|
|
65678
66127
|
"",
|
|
65679
|
-
"2. **Create new tasks** \u2014 Use
|
|
66128
|
+
"2. **Create new tasks** \u2014 Use fn_task_create to spawn follow-up work that needs",
|
|
65680
66129
|
" to be done. This is useful for surfacing issues or ideas you discover.",
|
|
65681
66130
|
"",
|
|
65682
|
-
"3. **Delegate work** \u2014 Use
|
|
65683
|
-
"
|
|
66131
|
+
"3. **Delegate work** \u2014 Use fn_list_agents to discover available agents and",
|
|
66132
|
+
" fn_delegate_task to assign work to them.",
|
|
65684
66133
|
"",
|
|
65685
|
-
"4. **Update your memory** \u2014 Use
|
|
66134
|
+
"4. **Update your memory** \u2014 Use fn_memory_append to persist important learnings",
|
|
65686
66135
|
" or context that will help you in future sessions.",
|
|
65687
66136
|
"",
|
|
65688
66137
|
"5. **Monitor the project** \u2014 Review the task board and identify any issues",
|
|
@@ -65691,7 +66140,7 @@ When sending messages:
|
|
|
65691
66140
|
"",
|
|
65692
66141
|
"Your soul, instructions, and memory are already loaded in the system prompt.",
|
|
65693
66142
|
"Focus on work that benefits the project without requiring a specific task context.",
|
|
65694
|
-
"Call
|
|
66143
|
+
"Call fn_heartbeat_done when finished."
|
|
65695
66144
|
].join("\n");
|
|
65696
66145
|
} else {
|
|
65697
66146
|
const taskTitle = taskDetail.title ?? taskDetail.description.slice(0, 100);
|
|
@@ -65751,7 +66200,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65751
66200
|
...triggeringCommentLines,
|
|
65752
66201
|
...pendingMessagesLines,
|
|
65753
66202
|
"",
|
|
65754
|
-
"Review the task status and take appropriate action. Call
|
|
66203
|
+
"Review the task status and take appropriate action. Call fn_heartbeat_done when finished."
|
|
65755
66204
|
].join("\n");
|
|
65756
66205
|
}
|
|
65757
66206
|
await promptWithFallback2(session, executionPrompt);
|
|
@@ -65783,12 +66232,12 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65783
66232
|
});
|
|
65784
66233
|
heartbeatLog.log(`Heartbeat completed for ${agentId} (${toolCallCount} tool calls, ~${estimatedOutputTokens} output tokens)`);
|
|
65785
66234
|
} catch (err) {
|
|
65786
|
-
const
|
|
65787
|
-
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${
|
|
66235
|
+
const errorDetail = formatError(err).detail;
|
|
66236
|
+
heartbeatLog.error(`Heartbeat execution failed for ${agentId}: ${errorDetail}`);
|
|
65788
66237
|
await flushAgentLogger();
|
|
65789
66238
|
await this.completeRun(agentId, run.id, {
|
|
65790
66239
|
status: "failed",
|
|
65791
|
-
stderrExcerpt:
|
|
66240
|
+
stderrExcerpt: errorDetail,
|
|
65792
66241
|
stdoutExcerpt: stdoutExcerpt || void 0
|
|
65793
66242
|
});
|
|
65794
66243
|
} finally {
|
|
@@ -65807,13 +66256,14 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65807
66256
|
}
|
|
65808
66257
|
return await this.store.getRunDetail(agentId, run.id);
|
|
65809
66258
|
} catch (err) {
|
|
66259
|
+
const errorDetail = formatError(err).detail;
|
|
65810
66260
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
65811
|
-
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${
|
|
66261
|
+
heartbeatLog.error(`Heartbeat execution error for ${agentId}: ${errorDetail}`);
|
|
65812
66262
|
await flushAgentLogger();
|
|
65813
66263
|
try {
|
|
65814
66264
|
await this.completeRun(agentId, run.id, {
|
|
65815
66265
|
status: "failed",
|
|
65816
|
-
stderrExcerpt:
|
|
66266
|
+
stderrExcerpt: errorDetail
|
|
65817
66267
|
});
|
|
65818
66268
|
} catch (completeRunErr) {
|
|
65819
66269
|
const completeRunErrMsg = completeRunErr instanceof Error ? completeRunErr.message : String(completeRunErr);
|
|
@@ -65851,7 +66301,7 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65851
66301
|
*
|
|
65852
66302
|
* @param agentId - The agent ID (used for tracking and logging)
|
|
65853
66303
|
* @param taskStore - TaskStore for task creation and logging
|
|
65854
|
-
* @param taskId - The assigned task ID (for
|
|
66304
|
+
* @param taskId - The assigned task ID (for fn_task_log context)
|
|
65855
66305
|
* @param runContext - Optional run context for mutation correlation
|
|
65856
66306
|
* @param audit - Optional run auditor for audit trail (FN-1404)
|
|
65857
66307
|
* @param messageStore - Optional MessageStore for messaging tools
|
|
@@ -69927,7 +70377,7 @@ var init_sse_buffer = __esm({
|
|
|
69927
70377
|
});
|
|
69928
70378
|
|
|
69929
70379
|
// ../dashboard/src/ai-session-diagnostics.ts
|
|
69930
|
-
import { randomUUID as
|
|
70380
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
69931
70381
|
function defaultSink(level, scope, message, context) {
|
|
69932
70382
|
const prefix = `[${scope}]`;
|
|
69933
70383
|
const logArgs = [prefix, message, context];
|
|
@@ -69971,7 +70421,7 @@ function emit(level, scope, message, context) {
|
|
|
69971
70421
|
const fullContext = {
|
|
69972
70422
|
...context,
|
|
69973
70423
|
_emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
69974
|
-
_diagnosticsId:
|
|
70424
|
+
_diagnosticsId: randomUUID9()
|
|
69975
70425
|
};
|
|
69976
70426
|
try {
|
|
69977
70427
|
_sink(level, scope, message, fullContext);
|
|
@@ -70028,7 +70478,7 @@ var init_ai_session_diagnostics = __esm({
|
|
|
70028
70478
|
});
|
|
70029
70479
|
|
|
70030
70480
|
// ../dashboard/src/planning.ts
|
|
70031
|
-
import { randomUUID as
|
|
70481
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
70032
70482
|
import { EventEmitter as EventEmitter17 } from "node:events";
|
|
70033
70483
|
async function initEngine2() {
|
|
70034
70484
|
try {
|
|
@@ -70222,7 +70672,7 @@ async function createSession(ip, initialPlan, _store, rootDir, promptOverrides)
|
|
|
70222
70672
|
if (!rootDir) {
|
|
70223
70673
|
throw new Error("rootDir is required for AI-powered planning sessions");
|
|
70224
70674
|
}
|
|
70225
|
-
const sessionId =
|
|
70675
|
+
const sessionId = randomUUID10();
|
|
70226
70676
|
const session = {
|
|
70227
70677
|
id: sessionId,
|
|
70228
70678
|
ip,
|
|
@@ -72749,7 +73199,7 @@ var init_github_poll = __esm({
|
|
|
72749
73199
|
|
|
72750
73200
|
// ../dashboard/src/terminal.ts
|
|
72751
73201
|
import { spawn as spawn2 } from "node:child_process";
|
|
72752
|
-
import { randomUUID as
|
|
73202
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
72753
73203
|
import { EventEmitter as EventEmitter19 } from "node:events";
|
|
72754
73204
|
function extractBaseCommand(command) {
|
|
72755
73205
|
let trimmed = command.trim();
|
|
@@ -72909,7 +73359,7 @@ var init_terminal = __esm({
|
|
|
72909
73359
|
if (!validation.valid) {
|
|
72910
73360
|
return { sessionId: "", error: validation.error };
|
|
72911
73361
|
}
|
|
72912
|
-
const sessionId =
|
|
73362
|
+
const sessionId = randomUUID11();
|
|
72913
73363
|
const childProcess = spawn2(command, [], {
|
|
72914
73364
|
cwd,
|
|
72915
73365
|
shell: true,
|
|
@@ -73281,6 +73731,8 @@ function cleanupExpiredSessions3() {
|
|
|
73281
73731
|
diagnostics4.info("Cleanup completed", {
|
|
73282
73732
|
cleanedSessions,
|
|
73283
73733
|
cleanedRateLimits,
|
|
73734
|
+
ttlMs: SESSION_TTL_MS3,
|
|
73735
|
+
rateLimitWindowMs: RATE_LIMIT_WINDOW_MS3,
|
|
73284
73736
|
operation: "cleanup-expired"
|
|
73285
73737
|
});
|
|
73286
73738
|
}
|
|
@@ -73615,7 +74067,7 @@ async function initPromptOverrides() {
|
|
|
73615
74067
|
promptOverridesReady = true;
|
|
73616
74068
|
}
|
|
73617
74069
|
}
|
|
73618
|
-
var mkdtemp, access3, stat6, mkdir12, readdir8,
|
|
74070
|
+
var mkdtemp, access3, stat6, mkdir12, readdir8, rm2, fsReadFile, fsWriteFile, upload, execFileAsync, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
|
|
73619
74071
|
var init_routes = __esm({
|
|
73620
74072
|
"../dashboard/src/routes.ts"() {
|
|
73621
74073
|
"use strict";
|
|
@@ -73652,7 +74104,7 @@ var init_routes = __esm({
|
|
|
73652
74104
|
stat: stat6,
|
|
73653
74105
|
mkdir: mkdir12,
|
|
73654
74106
|
readdir: readdir8,
|
|
73655
|
-
rm,
|
|
74107
|
+
rm: rm2,
|
|
73656
74108
|
readFile: fsReadFile,
|
|
73657
74109
|
writeFile: fsWriteFile
|
|
73658
74110
|
} = fsPromises);
|
|
@@ -75739,7 +76191,7 @@ var require_extension = __commonJS({
|
|
|
75739
76191
|
if (dest[name] === void 0) dest[name] = [elem];
|
|
75740
76192
|
else dest[name].push(elem);
|
|
75741
76193
|
}
|
|
75742
|
-
function
|
|
76194
|
+
function parse(header) {
|
|
75743
76195
|
const offers = /* @__PURE__ */ Object.create(null);
|
|
75744
76196
|
let params = /* @__PURE__ */ Object.create(null);
|
|
75745
76197
|
let mustUnescape = false;
|
|
@@ -75879,7 +76331,7 @@ var require_extension = __commonJS({
|
|
|
75879
76331
|
}).join(", ");
|
|
75880
76332
|
}).join(", ");
|
|
75881
76333
|
}
|
|
75882
|
-
module.exports = { format, parse
|
|
76334
|
+
module.exports = { format, parse };
|
|
75883
76335
|
}
|
|
75884
76336
|
});
|
|
75885
76337
|
|
|
@@ -75913,7 +76365,7 @@ var require_websocket = __commonJS({
|
|
|
75913
76365
|
var {
|
|
75914
76366
|
EventTarget: { addEventListener, removeEventListener }
|
|
75915
76367
|
} = require_event_target();
|
|
75916
|
-
var { format, parse
|
|
76368
|
+
var { format, parse } = require_extension();
|
|
75917
76369
|
var { toBuffer } = require_buffer_util();
|
|
75918
76370
|
var kAborted = /* @__PURE__ */ Symbol("kAborted");
|
|
75919
76371
|
var protocolVersions = [8, 13];
|
|
@@ -76582,7 +77034,7 @@ var require_websocket = __commonJS({
|
|
|
76582
77034
|
}
|
|
76583
77035
|
let extensions;
|
|
76584
77036
|
try {
|
|
76585
|
-
extensions =
|
|
77037
|
+
extensions = parse(secWebSocketExtensions);
|
|
76586
77038
|
} catch (err) {
|
|
76587
77039
|
const message = "Invalid Sec-WebSocket-Extensions header";
|
|
76588
77040
|
abortHandshake(websocket, socket, message);
|
|
@@ -76872,7 +77324,7 @@ var require_subprotocol = __commonJS({
|
|
|
76872
77324
|
"../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/subprotocol.js"(exports, module) {
|
|
76873
77325
|
"use strict";
|
|
76874
77326
|
var { tokenChars } = require_validation();
|
|
76875
|
-
function
|
|
77327
|
+
function parse(header) {
|
|
76876
77328
|
const protocols = /* @__PURE__ */ new Set();
|
|
76877
77329
|
let start = -1;
|
|
76878
77330
|
let end = -1;
|
|
@@ -76908,7 +77360,7 @@ var require_subprotocol = __commonJS({
|
|
|
76908
77360
|
protocols.add(protocol);
|
|
76909
77361
|
return protocols;
|
|
76910
77362
|
}
|
|
76911
|
-
module.exports = { parse
|
|
77363
|
+
module.exports = { parse };
|
|
76912
77364
|
}
|
|
76913
77365
|
});
|
|
76914
77366
|
|
|
@@ -77478,7 +77930,7 @@ var init_auth_middleware = __esm({
|
|
|
77478
77930
|
|
|
77479
77931
|
// ../dashboard/src/server.ts
|
|
77480
77932
|
import express from "express";
|
|
77481
|
-
import { join as join34, dirname as
|
|
77933
|
+
import { join as join34, dirname as dirname8 } from "node:path";
|
|
77482
77934
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
77483
77935
|
function clearAiSessionCleanupInterval() {
|
|
77484
77936
|
if (!aiSessionCleanupIntervalHandle) {
|
|
@@ -77512,7 +77964,7 @@ var init_server = __esm({
|
|
|
77512
77964
|
init_chat();
|
|
77513
77965
|
init_dev_server_routes();
|
|
77514
77966
|
init_auth_middleware();
|
|
77515
|
-
__dirname =
|
|
77967
|
+
__dirname = dirname8(fileURLToPath2(import.meta.url));
|
|
77516
77968
|
MIN_AI_SESSION_TTL_MS = 10 * 60 * 1e3;
|
|
77517
77969
|
MAX_AI_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
77518
77970
|
MIN_AI_SESSION_CLEANUP_INTERVAL_MS = 60 * 1e3;
|
|
@@ -77581,7 +78033,7 @@ var init_src3 = __esm({
|
|
|
77581
78033
|
});
|
|
77582
78034
|
|
|
77583
78035
|
// src/project-context.ts
|
|
77584
|
-
import { resolve as resolve14, dirname as
|
|
78036
|
+
import { resolve as resolve14, dirname as dirname9 } from "node:path";
|
|
77585
78037
|
import { existsSync as existsSync28 } from "node:fs";
|
|
77586
78038
|
async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
|
|
77587
78039
|
const central = new CentralCore(globalDir);
|
|
@@ -77672,7 +78124,7 @@ async function detectProjectFromCwd(cwd, central) {
|
|
|
77672
78124
|
path: currentDir
|
|
77673
78125
|
};
|
|
77674
78126
|
}
|
|
77675
|
-
const parentDir =
|
|
78127
|
+
const parentDir = dirname9(currentDir);
|
|
77676
78128
|
if (parentDir === currentDir) {
|
|
77677
78129
|
break;
|
|
77678
78130
|
}
|
|
@@ -77834,11 +78286,11 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName)
|
|
|
77834
78286
|
console.log(` Path: .fusion/tasks/${task.id}/`);
|
|
77835
78287
|
if (attachFiles && attachFiles.length > 0) {
|
|
77836
78288
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
77837
|
-
const { basename:
|
|
78289
|
+
const { basename: basename9, extname: extname3, resolve: resolve16 } = await import("node:path");
|
|
77838
78290
|
for (const filePath of attachFiles) {
|
|
77839
78291
|
const resolvedPath = resolve16(filePath);
|
|
77840
|
-
const filename =
|
|
77841
|
-
const ext =
|
|
78292
|
+
const filename = basename9(resolvedPath);
|
|
78293
|
+
const ext = extname3(filename).toLowerCase();
|
|
77842
78294
|
const mimeType = MIME_TYPES[ext];
|
|
77843
78295
|
if (!mimeType) {
|
|
77844
78296
|
console.error(` \u2717 Unsupported file type: ${ext} (${filename})`);
|
|
@@ -78082,11 +78534,11 @@ async function runTaskMerge(id, projectName) {
|
|
|
78082
78534
|
}
|
|
78083
78535
|
async function runTaskAttach(id, filePath, projectName) {
|
|
78084
78536
|
const { readFile: readFile19 } = await import("node:fs/promises");
|
|
78085
|
-
const { basename:
|
|
78537
|
+
const { basename: basename9, extname: extname3 } = await import("node:path");
|
|
78086
78538
|
const { resolve: resolve16 } = await import("node:path");
|
|
78087
78539
|
const resolvedPath = resolve16(filePath);
|
|
78088
|
-
const filename =
|
|
78089
|
-
const ext =
|
|
78540
|
+
const filename = basename9(resolvedPath);
|
|
78541
|
+
const ext = extname3(filename).toLowerCase();
|
|
78090
78542
|
const mimeType = MIME_TYPES[ext];
|
|
78091
78543
|
if (!mimeType) {
|
|
78092
78544
|
console.error(`Unsupported file type: ${ext}`);
|
|
@@ -79037,7 +79489,7 @@ init_src();
|
|
|
79037
79489
|
init_gh_cli();
|
|
79038
79490
|
import { Type as Type7 } from "typebox";
|
|
79039
79491
|
import { StringEnum } from "@mariozechner/pi-ai";
|
|
79040
|
-
import { resolve as resolve15, basename as
|
|
79492
|
+
import { resolve as resolve15, basename as basename8, extname as extname2, join as join36 } from "node:path";
|
|
79041
79493
|
import { readFile as readFile18 } from "node:fs/promises";
|
|
79042
79494
|
import { existsSync as existsSync30 } from "node:fs";
|
|
79043
79495
|
import { spawn as spawn4 } from "node:child_process";
|
|
@@ -79358,8 +79810,8 @@ Column: triage
|
|
|
79358
79810
|
}),
|
|
79359
79811
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
79360
79812
|
const filePath = resolve15(ctx.cwd, params.path.replace(/^@/, ""));
|
|
79361
|
-
const filename =
|
|
79362
|
-
const ext =
|
|
79813
|
+
const filename = basename8(filePath);
|
|
79814
|
+
const ext = extname2(filename).toLowerCase();
|
|
79363
79815
|
const mimeType = MIME_TYPES2[ext];
|
|
79364
79816
|
if (!mimeType) {
|
|
79365
79817
|
throw new Error(
|