@runfusion/fusion 0.0.6 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +2107 -510
- package/dist/client/assets/index-B3ZN3sln.css +1 -0
- package/dist/client/assets/index-cgKoCmZP.js +1241 -0
- package/dist/client/index.html +2 -2
- package/dist/extension.js +513 -196
- package/package.json +1 -1
- package/dist/client/assets/index-CYkQfLYV.css +0 -1
- package/dist/client/assets/index-zTogbMzz.js +0 -1241
package/dist/extension.js
CHANGED
|
@@ -137,7 +137,7 @@ var init_settings_schema = __esm({
|
|
|
137
137
|
worktreeRebaseRemote: "",
|
|
138
138
|
strictScopeEnforcement: false,
|
|
139
139
|
buildRetryCount: 0,
|
|
140
|
-
verificationFixRetries:
|
|
140
|
+
verificationFixRetries: 3,
|
|
141
141
|
buildTimeoutMs: 3e5,
|
|
142
142
|
requirePlanApproval: false,
|
|
143
143
|
specStalenessEnabled: false,
|
|
@@ -1974,6 +1974,18 @@ function fromJson(json) {
|
|
|
1974
1974
|
return void 0;
|
|
1975
1975
|
}
|
|
1976
1976
|
}
|
|
1977
|
+
function probeFts5(db) {
|
|
1978
|
+
if (process.env.FUSION_DISABLE_FTS5 === "1" || process.env.FUSION_DISABLE_FTS5 === "true") {
|
|
1979
|
+
return false;
|
|
1980
|
+
}
|
|
1981
|
+
try {
|
|
1982
|
+
db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS __fusion_fts5_probe USING fts5(x)");
|
|
1983
|
+
db.exec("DROP TABLE IF EXISTS __fusion_fts5_probe");
|
|
1984
|
+
return true;
|
|
1985
|
+
} catch {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1977
1989
|
function normalizeTaskComments(steeringComments, comments) {
|
|
1978
1990
|
const normalizedComments = [];
|
|
1979
1991
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -2494,6 +2506,7 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2494
2506
|
dbPath;
|
|
2495
2507
|
/** Tracks transaction nesting depth for savepoint-based nested transactions. */
|
|
2496
2508
|
transactionDepth = 0;
|
|
2509
|
+
_fts5Available;
|
|
2497
2510
|
constructor(kbDir) {
|
|
2498
2511
|
this.dbPath = join(kbDir, "fusion.db");
|
|
2499
2512
|
if (!isAbsolute(kbDir)) {
|
|
@@ -2506,6 +2519,16 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2506
2519
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
2507
2520
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
2508
2521
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
2522
|
+
this._fts5Available = probeFts5(this.db);
|
|
2523
|
+
}
|
|
2524
|
+
/**
|
|
2525
|
+
* True when the underlying SQLite build has FTS5 (`CREATE VIRTUAL TABLE … USING fts5`).
|
|
2526
|
+
* Node's bundled SQLite only exposes FTS5 when built with `SQLITE_ENABLE_FTS5`;
|
|
2527
|
+
* older Node 22.x LTS builds do not. Consumers must fall back to LIKE-based scans
|
|
2528
|
+
* when this is false. Override with `FUSION_DISABLE_FTS5=1` to force the fallback path.
|
|
2529
|
+
*/
|
|
2530
|
+
get fts5Available() {
|
|
2531
|
+
return this._fts5Available;
|
|
2509
2532
|
}
|
|
2510
2533
|
/**
|
|
2511
2534
|
* Initialize the database: create tables if they don't exist
|
|
@@ -2815,6 +2838,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2815
2838
|
}
|
|
2816
2839
|
if (version < 21) {
|
|
2817
2840
|
this.applyMigration(21, () => {
|
|
2841
|
+
if (!this._fts5Available) {
|
|
2842
|
+
return;
|
|
2843
|
+
}
|
|
2818
2844
|
this.db.exec(`
|
|
2819
2845
|
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
2820
2846
|
id,
|
|
@@ -3224,6 +3250,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
3224
3250
|
}
|
|
3225
3251
|
if (version < 35) {
|
|
3226
3252
|
this.applyMigration(35, () => {
|
|
3253
|
+
if (!this._fts5Available) {
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3227
3256
|
const hasTaskTitle = this.hasColumn("tasks", "title");
|
|
3228
3257
|
const updateColumns = hasTaskTitle ? "id, title, description, comments" : "id, description, comments";
|
|
3229
3258
|
const oldTitle = hasTaskTitle ? "COALESCE(old.title, '')" : "''";
|
|
@@ -3517,6 +3546,9 @@ function resolveCreationRuntimeConfig(incoming, metadata) {
|
|
|
3517
3546
|
return incoming;
|
|
3518
3547
|
}
|
|
3519
3548
|
const rc = { ...incoming ?? {} };
|
|
3549
|
+
if (typeof rc.enabled !== "boolean") {
|
|
3550
|
+
rc.enabled = true;
|
|
3551
|
+
}
|
|
3520
3552
|
if (typeof rc.heartbeatIntervalMs !== "number" || !Number.isFinite(rc.heartbeatIntervalMs)) {
|
|
3521
3553
|
rc.heartbeatIntervalMs = DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS;
|
|
3522
3554
|
}
|
|
@@ -3562,6 +3594,7 @@ var init_agent_store = __esm({
|
|
|
3562
3594
|
const _ = this.db;
|
|
3563
3595
|
await mkdir(this.agentsDir, { recursive: true });
|
|
3564
3596
|
await this.importLegacyFileDataOnce();
|
|
3597
|
+
await this.normalizeHeartbeatDefaultsOnce();
|
|
3565
3598
|
}
|
|
3566
3599
|
/**
|
|
3567
3600
|
* One-way migration helper for projects that still have legacy agent JSON
|
|
@@ -3666,6 +3699,51 @@ var init_agent_store = __esm({
|
|
|
3666
3699
|
`).run(migrationKey, migrationVersion);
|
|
3667
3700
|
this.db.bumpLastModified();
|
|
3668
3701
|
}
|
|
3702
|
+
/**
|
|
3703
|
+
* One-time normalization for durable agents created before the heartbeat
|
|
3704
|
+
* toggle was exposed in the UI. Those agents could persist
|
|
3705
|
+
* `runtimeConfig.enabled = false` even though users had no supported way to
|
|
3706
|
+
* manage that flag, which caused timers to stay disabled after restart.
|
|
3707
|
+
*
|
|
3708
|
+
* We normalize only once per project. After this migration lands, explicit
|
|
3709
|
+
* user choices are preserved because the version gate prevents reruns.
|
|
3710
|
+
*/
|
|
3711
|
+
async normalizeHeartbeatDefaultsOnce() {
|
|
3712
|
+
const migrationKey = "agentHeartbeatDefaultVersion";
|
|
3713
|
+
const migrationVersion = "1";
|
|
3714
|
+
const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(migrationKey);
|
|
3715
|
+
if (row?.value === migrationVersion) {
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
const agents = await this.listAgents({ includeEphemeral: true });
|
|
3719
|
+
let changed = 0;
|
|
3720
|
+
for (const agent of agents) {
|
|
3721
|
+
if (isEphemeralAgent(agent)) {
|
|
3722
|
+
continue;
|
|
3723
|
+
}
|
|
3724
|
+
const nextRuntimeConfig = {
|
|
3725
|
+
...resolveCreationRuntimeConfig(agent.runtimeConfig, agent.metadata) ?? {},
|
|
3726
|
+
enabled: true
|
|
3727
|
+
};
|
|
3728
|
+
const currentRuntimeConfig = agent.runtimeConfig ?? void 0;
|
|
3729
|
+
if (JSON.stringify(nextRuntimeConfig) === JSON.stringify(currentRuntimeConfig)) {
|
|
3730
|
+
continue;
|
|
3731
|
+
}
|
|
3732
|
+
await this.writeAgent({
|
|
3733
|
+
...agent,
|
|
3734
|
+
runtimeConfig: nextRuntimeConfig
|
|
3735
|
+
});
|
|
3736
|
+
changed++;
|
|
3737
|
+
}
|
|
3738
|
+
this.db.prepare(`
|
|
3739
|
+
INSERT INTO __meta (key, value)
|
|
3740
|
+
VALUES (?, ?)
|
|
3741
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
3742
|
+
`).run(migrationKey, migrationVersion);
|
|
3743
|
+
if (changed > 0) {
|
|
3744
|
+
this.db.bumpLastModified();
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3669
3747
|
/**
|
|
3670
3748
|
* Create a new agent with "idle" state.
|
|
3671
3749
|
*
|
|
@@ -5775,11 +5853,12 @@ var init_global_settings = __esm({
|
|
|
5775
5853
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
5776
5854
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5777
5855
|
import { join as join5 } from "node:path";
|
|
5778
|
-
var
|
|
5856
|
+
var BASE_SCHEMA_SQL, FTS5_SCHEMA_SQL, ArchiveDatabase;
|
|
5779
5857
|
var init_archive_db = __esm({
|
|
5780
5858
|
"../core/src/archive-db.ts"() {
|
|
5781
5859
|
"use strict";
|
|
5782
|
-
|
|
5860
|
+
init_db();
|
|
5861
|
+
BASE_SCHEMA_SQL = `
|
|
5783
5862
|
CREATE TABLE IF NOT EXISTS archived_tasks (
|
|
5784
5863
|
id TEXT PRIMARY KEY,
|
|
5785
5864
|
taskJson TEXT NOT NULL,
|
|
@@ -5795,7 +5874,8 @@ CREATE TABLE IF NOT EXISTS archived_tasks (
|
|
|
5795
5874
|
|
|
5796
5875
|
CREATE INDEX IF NOT EXISTS idxArchivedTasksArchivedAt ON archived_tasks(archivedAt);
|
|
5797
5876
|
CREATE INDEX IF NOT EXISTS idxArchivedTasksCreatedAt ON archived_tasks(createdAt);
|
|
5798
|
-
|
|
5877
|
+
`;
|
|
5878
|
+
FTS5_SCHEMA_SQL = `
|
|
5799
5879
|
CREATE VIRTUAL TABLE IF NOT EXISTS archived_tasks_fts USING fts5(
|
|
5800
5880
|
id,
|
|
5801
5881
|
title,
|
|
@@ -5823,18 +5903,26 @@ CREATE TRIGGER IF NOT EXISTS archived_tasks_fts_ad AFTER DELETE ON archived_task
|
|
|
5823
5903
|
END;
|
|
5824
5904
|
`;
|
|
5825
5905
|
ArchiveDatabase = class {
|
|
5906
|
+
db;
|
|
5907
|
+
_fts5Available;
|
|
5826
5908
|
constructor(kbDir) {
|
|
5827
|
-
this.kbDir = kbDir;
|
|
5828
5909
|
if (!existsSync4(kbDir)) {
|
|
5829
5910
|
mkdirSync3(kbDir, { recursive: true });
|
|
5830
5911
|
}
|
|
5831
5912
|
this.db = new DatabaseSync2(join5(kbDir, "archive.db"));
|
|
5832
5913
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
5833
5914
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
5915
|
+
this._fts5Available = probeFts5(this.db);
|
|
5916
|
+
}
|
|
5917
|
+
/** True when this SQLite build has FTS5. See db.ts#probeFts5. */
|
|
5918
|
+
get fts5Available() {
|
|
5919
|
+
return this._fts5Available;
|
|
5834
5920
|
}
|
|
5835
|
-
db;
|
|
5836
5921
|
init() {
|
|
5837
|
-
this.db.exec(
|
|
5922
|
+
this.db.exec(BASE_SCHEMA_SQL);
|
|
5923
|
+
if (this._fts5Available) {
|
|
5924
|
+
this.db.exec(FTS5_SCHEMA_SQL);
|
|
5925
|
+
}
|
|
5838
5926
|
this.addColumnIfMissing("archived_tasks", "prompt", "TEXT");
|
|
5839
5927
|
}
|
|
5840
5928
|
upsert(entry) {
|
|
@@ -5875,15 +5963,48 @@ END;
|
|
|
5875
5963
|
delete(id) {
|
|
5876
5964
|
this.db.prepare("DELETE FROM archived_tasks WHERE id = ?").run(id);
|
|
5877
5965
|
}
|
|
5966
|
+
/**
|
|
5967
|
+
* Full-text search over archived tasks. Accepts a raw user query and routes
|
|
5968
|
+
* through FTS5 when available, or a LIKE-based scan when not.
|
|
5969
|
+
*/
|
|
5878
5970
|
search(query, limit) {
|
|
5971
|
+
const trimmed = query?.trim();
|
|
5972
|
+
if (!trimmed) return [];
|
|
5973
|
+
const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0).map((t) => t.replace(/["{}:*^+()]/g, "")).filter((t) => t.length > 0);
|
|
5974
|
+
if (tokens.length === 0) return [];
|
|
5975
|
+
if (this._fts5Available) {
|
|
5976
|
+
const ftsQuery = tokens.map((token) => {
|
|
5977
|
+
if (/[":(){}*^+-]/.test(token)) {
|
|
5978
|
+
return `"${token.replace(/"/g, '\\"')}"`;
|
|
5979
|
+
}
|
|
5980
|
+
return token;
|
|
5981
|
+
}).join(" OR ");
|
|
5982
|
+
const rows2 = this.db.prepare(`
|
|
5983
|
+
SELECT a.taskJson
|
|
5984
|
+
FROM archived_tasks a
|
|
5985
|
+
JOIN archived_tasks_fts fts ON a.rowid = fts.rowid
|
|
5986
|
+
WHERE archived_tasks_fts MATCH ?
|
|
5987
|
+
ORDER BY rank
|
|
5988
|
+
LIMIT ?
|
|
5989
|
+
`).all(ftsQuery, limit);
|
|
5990
|
+
return rows2.map((row) => JSON.parse(row.taskJson));
|
|
5991
|
+
}
|
|
5992
|
+
const searchColumns = ["id", "title", "description", "comments"];
|
|
5993
|
+
const perTokenClause = `(${searchColumns.map((c) => `"${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
5994
|
+
const whereTokens = tokens.map(() => perTokenClause).join(" OR ");
|
|
5995
|
+
const params = [];
|
|
5996
|
+
for (const token of tokens) {
|
|
5997
|
+
const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
5998
|
+
for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
|
|
5999
|
+
}
|
|
6000
|
+
params.push(limit);
|
|
5879
6001
|
const rows = this.db.prepare(`
|
|
5880
|
-
SELECT
|
|
5881
|
-
FROM archived_tasks
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
ORDER BY rank
|
|
6002
|
+
SELECT taskJson
|
|
6003
|
+
FROM archived_tasks
|
|
6004
|
+
WHERE ${whereTokens}
|
|
6005
|
+
ORDER BY archivedAt DESC
|
|
5885
6006
|
LIMIT ?
|
|
5886
|
-
`).all(
|
|
6007
|
+
`).all(...params);
|
|
5887
6008
|
return rows.map((row) => JSON.parse(row.taskJson));
|
|
5888
6009
|
}
|
|
5889
6010
|
close() {
|
|
@@ -12196,6 +12317,9 @@ var init_migration = __esm({
|
|
|
12196
12317
|
* @returns Generated name
|
|
12197
12318
|
*/
|
|
12198
12319
|
async generateProjectName(projectPath) {
|
|
12320
|
+
if (!existsSync7(join9(projectPath, ".git"))) {
|
|
12321
|
+
return basename3(projectPath);
|
|
12322
|
+
}
|
|
12199
12323
|
try {
|
|
12200
12324
|
const { execFile: execFile4 } = await import("node:child_process");
|
|
12201
12325
|
const { promisify: promisify11 } = await import("node:util");
|
|
@@ -12203,7 +12327,7 @@ var init_migration = __esm({
|
|
|
12203
12327
|
const { stdout } = await execFileAsync2(
|
|
12204
12328
|
"git",
|
|
12205
12329
|
["remote", "get-url", "origin"],
|
|
12206
|
-
{ cwd: projectPath, timeout:
|
|
12330
|
+
{ cwd: projectPath, timeout: 1e3 }
|
|
12207
12331
|
);
|
|
12208
12332
|
const remoteUrl = stdout.trim();
|
|
12209
12333
|
if (remoteUrl) {
|
|
@@ -28697,6 +28821,27 @@ var init_run_command = __esm({
|
|
|
28697
28821
|
}
|
|
28698
28822
|
});
|
|
28699
28823
|
|
|
28824
|
+
// ../core/src/logger.ts
|
|
28825
|
+
function createLogger(prefix) {
|
|
28826
|
+
const tag = `[${prefix}]`;
|
|
28827
|
+
return {
|
|
28828
|
+
log(message, ...args) {
|
|
28829
|
+
console.error(`${tag} ${message}`, ...args);
|
|
28830
|
+
},
|
|
28831
|
+
warn(message, ...args) {
|
|
28832
|
+
console.warn(`${tag} ${message}`, ...args);
|
|
28833
|
+
},
|
|
28834
|
+
error(message, ...args) {
|
|
28835
|
+
console.error(`${tag} ${message}`, ...args);
|
|
28836
|
+
}
|
|
28837
|
+
};
|
|
28838
|
+
}
|
|
28839
|
+
var init_logger = __esm({
|
|
28840
|
+
"../core/src/logger.ts"() {
|
|
28841
|
+
"use strict";
|
|
28842
|
+
}
|
|
28843
|
+
});
|
|
28844
|
+
|
|
28700
28845
|
// ../core/src/store.ts
|
|
28701
28846
|
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
28702
28847
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
@@ -28745,7 +28890,7 @@ function canonicalizeSettings(settings) {
|
|
|
28745
28890
|
}
|
|
28746
28891
|
return settings;
|
|
28747
28892
|
}
|
|
28748
|
-
var LEGACY_BACKUP_DIR, TASK_ACTIVITY_LOG_ENTRY_LIMIT, TASK_ACTIVITY_LOG_OUTCOME_LIMIT, ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT, ARCHIVE_AGENT_LOG_SNIPPET_LIMIT, TaskHasDependentsError, TaskStore;
|
|
28893
|
+
var LEGACY_BACKUP_DIR, TASK_ACTIVITY_LOG_ENTRY_LIMIT, TASK_ACTIVITY_LOG_OUTCOME_LIMIT, ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT, ARCHIVE_AGENT_LOG_SNIPPET_LIMIT, storeLog, TaskHasDependentsError, TaskStore;
|
|
28749
28894
|
var init_store = __esm({
|
|
28750
28895
|
"../core/src/store.ts"() {
|
|
28751
28896
|
"use strict";
|
|
@@ -28763,11 +28908,13 @@ var init_store = __esm({
|
|
|
28763
28908
|
init_task_merge();
|
|
28764
28909
|
init_project_memory();
|
|
28765
28910
|
init_run_command();
|
|
28911
|
+
init_logger();
|
|
28766
28912
|
LEGACY_BACKUP_DIR = ".kb/backups";
|
|
28767
28913
|
TASK_ACTIVITY_LOG_ENTRY_LIMIT = 1e3;
|
|
28768
28914
|
TASK_ACTIVITY_LOG_OUTCOME_LIMIT = 4e3;
|
|
28769
28915
|
ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT = 25;
|
|
28770
28916
|
ARCHIVE_AGENT_LOG_SNIPPET_LIMIT = 160;
|
|
28917
|
+
storeLog = createLogger("task-store");
|
|
28771
28918
|
TaskHasDependentsError = class extends Error {
|
|
28772
28919
|
taskId;
|
|
28773
28920
|
dependentIds;
|
|
@@ -29477,45 +29624,53 @@ ${recentText}` : void 0
|
|
|
29477
29624
|
*/
|
|
29478
29625
|
setupActivityLogListeners() {
|
|
29479
29626
|
this.on("task:created", (task) => {
|
|
29480
|
-
this.
|
|
29481
|
-
|
|
29482
|
-
|
|
29483
|
-
|
|
29484
|
-
|
|
29485
|
-
|
|
29486
|
-
|
|
29627
|
+
this.recordActivityFromListener(
|
|
29628
|
+
{
|
|
29629
|
+
type: "task:created",
|
|
29630
|
+
taskId: task.id,
|
|
29631
|
+
taskTitle: task.title,
|
|
29632
|
+
details: `Task ${task.id} created${task.title ? `: ${task.title}` : ""}`
|
|
29633
|
+
},
|
|
29634
|
+
"task:created"
|
|
29635
|
+
);
|
|
29487
29636
|
});
|
|
29488
29637
|
this.on("task:moved", (data) => {
|
|
29489
|
-
this.
|
|
29490
|
-
|
|
29491
|
-
|
|
29492
|
-
|
|
29493
|
-
|
|
29494
|
-
|
|
29495
|
-
|
|
29496
|
-
|
|
29638
|
+
this.recordActivityFromListener(
|
|
29639
|
+
{
|
|
29640
|
+
type: "task:moved",
|
|
29641
|
+
taskId: data.task.id,
|
|
29642
|
+
taskTitle: data.task.title,
|
|
29643
|
+
details: `Task ${data.task.id} moved: ${data.from} \u2192 ${data.to}`,
|
|
29644
|
+
metadata: { from: data.from, to: data.to }
|
|
29645
|
+
},
|
|
29646
|
+
"task:moved"
|
|
29647
|
+
);
|
|
29497
29648
|
});
|
|
29498
29649
|
this.on("task:merged", (result) => {
|
|
29499
29650
|
const status = result.merged ? "successfully merged" : "merge attempted";
|
|
29500
|
-
this.
|
|
29501
|
-
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
29507
|
-
|
|
29651
|
+
this.recordActivityFromListener(
|
|
29652
|
+
{
|
|
29653
|
+
type: "task:merged",
|
|
29654
|
+
taskId: result.task.id,
|
|
29655
|
+
taskTitle: result.task.title,
|
|
29656
|
+
details: `Task ${result.task.id} ${status} to main`,
|
|
29657
|
+
metadata: { merged: result.merged, branch: result.branch }
|
|
29658
|
+
},
|
|
29659
|
+
"task:merged"
|
|
29660
|
+
);
|
|
29508
29661
|
});
|
|
29509
29662
|
this.on("task:updated", (task) => {
|
|
29510
29663
|
if (task.status === "failed") {
|
|
29511
|
-
this.
|
|
29512
|
-
|
|
29513
|
-
|
|
29514
|
-
|
|
29515
|
-
|
|
29516
|
-
|
|
29517
|
-
|
|
29518
|
-
|
|
29664
|
+
this.recordActivityFromListener(
|
|
29665
|
+
{
|
|
29666
|
+
type: "task:failed",
|
|
29667
|
+
taskId: task.id,
|
|
29668
|
+
taskTitle: task.title,
|
|
29669
|
+
details: `Task ${task.id} failed${task.error ? `: ${task.error}` : ""}`,
|
|
29670
|
+
metadata: task.error ? { error: task.error } : void 0
|
|
29671
|
+
},
|
|
29672
|
+
"task:updated"
|
|
29673
|
+
);
|
|
29519
29674
|
}
|
|
29520
29675
|
});
|
|
29521
29676
|
this.on("settings:updated", (data) => {
|
|
@@ -29533,21 +29688,35 @@ ${recentText}` : void 0
|
|
|
29533
29688
|
importantChanges.push(`engine pause ${data.settings.enginePaused ? "enabled" : "disabled"}`);
|
|
29534
29689
|
}
|
|
29535
29690
|
if (importantChanges.length > 0) {
|
|
29536
|
-
this.
|
|
29537
|
-
|
|
29538
|
-
|
|
29539
|
-
|
|
29540
|
-
|
|
29541
|
-
|
|
29691
|
+
this.recordActivityFromListener(
|
|
29692
|
+
{
|
|
29693
|
+
type: "settings:updated",
|
|
29694
|
+
details: `Settings updated: ${importantChanges.join(", ")}`,
|
|
29695
|
+
metadata: { changes: importantChanges }
|
|
29696
|
+
},
|
|
29697
|
+
"settings:updated"
|
|
29698
|
+
);
|
|
29542
29699
|
}
|
|
29543
29700
|
});
|
|
29544
29701
|
this.on("task:deleted", (task) => {
|
|
29545
|
-
this.
|
|
29546
|
-
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29702
|
+
this.recordActivityFromListener(
|
|
29703
|
+
{
|
|
29704
|
+
type: "task:deleted",
|
|
29705
|
+
taskId: task.id,
|
|
29706
|
+
taskTitle: task.title,
|
|
29707
|
+
details: `Task ${task.id} deleted${task.title ? `: ${task.title}` : ""}`
|
|
29708
|
+
},
|
|
29709
|
+
"task:deleted"
|
|
29710
|
+
);
|
|
29711
|
+
});
|
|
29712
|
+
}
|
|
29713
|
+
recordActivityFromListener(entry, sourceEvent) {
|
|
29714
|
+
this.recordActivity(entry).catch((err) => {
|
|
29715
|
+
storeLog.warn("Activity logging listener failed", {
|
|
29716
|
+
sourceEvent,
|
|
29717
|
+
type: entry.type,
|
|
29718
|
+
taskId: entry.taskId,
|
|
29719
|
+
error: err instanceof Error ? err.message : String(err)
|
|
29551
29720
|
});
|
|
29552
29721
|
});
|
|
29553
29722
|
}
|
|
@@ -30069,7 +30238,11 @@ ${recentText}` : void 0
|
|
|
30069
30238
|
if (defaultOnSteps.length > 0) {
|
|
30070
30239
|
resolvedWorkflowSteps = defaultOnSteps;
|
|
30071
30240
|
}
|
|
30072
|
-
} catch {
|
|
30241
|
+
} catch (err) {
|
|
30242
|
+
storeLog.warn("Failed to auto-apply default workflow steps during task creation", {
|
|
30243
|
+
error: err instanceof Error ? err.message : String(err),
|
|
30244
|
+
descriptionLength: input.description.length
|
|
30245
|
+
});
|
|
30073
30246
|
}
|
|
30074
30247
|
} else if (input.enabledWorkflowSteps.length === 0) {
|
|
30075
30248
|
resolvedWorkflowSteps = void 0;
|
|
@@ -30087,13 +30260,26 @@ ${recentText}` : void 0
|
|
|
30087
30260
|
}
|
|
30088
30261
|
}
|
|
30089
30262
|
} catch (err) {
|
|
30090
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
30091
30263
|
const autoEnabled = options?.settings?.autoSummarizeTitles === true;
|
|
30092
|
-
|
|
30093
|
-
|
|
30264
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
30265
|
+
storeLog.warn(
|
|
30266
|
+
`Title summarization failed for task ${id}: ${errorMessage} (desc length: ${input.description.length}, auto-summarize: ${autoEnabled})`,
|
|
30267
|
+
{
|
|
30268
|
+
taskId: id,
|
|
30269
|
+
descriptionLength: input.description.length,
|
|
30270
|
+
autoSummarizeEnabled: autoEnabled,
|
|
30271
|
+
error: errorMessage
|
|
30272
|
+
}
|
|
30094
30273
|
);
|
|
30095
30274
|
}
|
|
30096
|
-
}).catch(() => {
|
|
30275
|
+
}).catch((err) => {
|
|
30276
|
+
const autoEnabled = options?.settings?.autoSummarizeTitles === true;
|
|
30277
|
+
storeLog.error("Unexpected title summarization promise-chain failure", {
|
|
30278
|
+
taskId: id,
|
|
30279
|
+
descriptionLength: input.description.length,
|
|
30280
|
+
autoSummarizeEnabled: autoEnabled,
|
|
30281
|
+
error: err instanceof Error ? err.message : String(err)
|
|
30282
|
+
});
|
|
30097
30283
|
});
|
|
30098
30284
|
}
|
|
30099
30285
|
return task;
|
|
@@ -30360,26 +30546,46 @@ ${newTask.description}
|
|
|
30360
30546
|
if (sanitizedTokens.length === 0) {
|
|
30361
30547
|
return this.listTasks(options);
|
|
30362
30548
|
}
|
|
30363
|
-
const ftsQuery = sanitizedTokens.map((token) => {
|
|
30364
|
-
if (/[":(){}*^+-]/.test(token)) {
|
|
30365
|
-
return `"${token.replace(/"/g, '\\"')}"`;
|
|
30366
|
-
}
|
|
30367
|
-
return token;
|
|
30368
|
-
}).join(" OR ");
|
|
30369
30549
|
const limit = options?.limit ?? -1;
|
|
30370
30550
|
const offset = options?.offset ?? 0;
|
|
30371
30551
|
const offsetClause = offset > 0 ? ` OFFSET ${offset}` : "";
|
|
30372
30552
|
const includeArchived = options?.includeArchived ?? true;
|
|
30373
|
-
const
|
|
30374
|
-
const selectClause = this.getTaskSelectClause(
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
|
|
30380
|
-
|
|
30381
|
-
|
|
30382
|
-
|
|
30553
|
+
const slim = options?.slim ?? false;
|
|
30554
|
+
const selectClause = this.getTaskSelectClause(slim, "t");
|
|
30555
|
+
let rows;
|
|
30556
|
+
if (this.db.fts5Available) {
|
|
30557
|
+
const ftsQuery = sanitizedTokens.map((token) => {
|
|
30558
|
+
if (/[":(){}*^+-]/.test(token)) {
|
|
30559
|
+
return `"${token.replace(/"/g, '\\"')}"`;
|
|
30560
|
+
}
|
|
30561
|
+
return token;
|
|
30562
|
+
}).join(" OR ");
|
|
30563
|
+
const whereClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
|
|
30564
|
+
rows = this.db.prepare(`
|
|
30565
|
+
SELECT ${selectClause} FROM tasks t
|
|
30566
|
+
JOIN tasks_fts fts ON t.rowid = fts.rowid
|
|
30567
|
+
WHERE tasks_fts MATCH ?
|
|
30568
|
+
${whereClause}
|
|
30569
|
+
ORDER BY rank
|
|
30570
|
+
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
|
|
30571
|
+
`).all(ftsQuery);
|
|
30572
|
+
} else {
|
|
30573
|
+
const searchColumns = ["id", "title", "description", "comments"];
|
|
30574
|
+
const perTokenClause = `(${searchColumns.map((c) => `t."${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
30575
|
+
const whereTokens = sanitizedTokens.map(() => perTokenClause).join(" OR ");
|
|
30576
|
+
const params = [];
|
|
30577
|
+
for (const token of sanitizedTokens) {
|
|
30578
|
+
const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
30579
|
+
for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
|
|
30580
|
+
}
|
|
30581
|
+
const archivedClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
|
|
30582
|
+
rows = this.db.prepare(`
|
|
30583
|
+
SELECT ${selectClause} FROM tasks t
|
|
30584
|
+
WHERE (${whereTokens})${archivedClause}
|
|
30585
|
+
ORDER BY t.createdAt ASC
|
|
30586
|
+
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
|
|
30587
|
+
`).all(...params);
|
|
30588
|
+
}
|
|
30383
30589
|
const activeMatches = await Promise.all(rows.map(async (row) => {
|
|
30384
30590
|
const task = this.rowToTask(row);
|
|
30385
30591
|
if (task.steps.length > 0) {
|
|
@@ -30388,7 +30594,7 @@ ${newTask.description}
|
|
|
30388
30594
|
const steps = await this.parseStepsFromPrompt(task.id);
|
|
30389
30595
|
return steps.length > 0 ? { ...task, steps } : task;
|
|
30390
30596
|
}));
|
|
30391
|
-
const archiveMatches = includeArchived ? this.archiveDb.search(
|
|
30597
|
+
const archiveMatches = includeArchived ? this.archiveDb.search(trimmedQuery, limit >= 0 ? limit : 100).map((entry) => this.archiveEntryToTask(entry, slim)) : [];
|
|
30392
30598
|
const matches = [...activeMatches, ...archiveMatches];
|
|
30393
30599
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
30394
30600
|
}
|
|
@@ -31524,9 +31730,17 @@ ${task.description}
|
|
|
31524
31730
|
try {
|
|
31525
31731
|
this.watcher = watch(this.tasksDir, { recursive: true }, (_event, _filename) => {
|
|
31526
31732
|
});
|
|
31527
|
-
this.watcher.on("error", () => {
|
|
31733
|
+
this.watcher.on("error", (err) => {
|
|
31734
|
+
storeLog.warn("fs.watch emitted an error; polling will continue", {
|
|
31735
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31736
|
+
tasksDir: this.tasksDir
|
|
31737
|
+
});
|
|
31738
|
+
});
|
|
31739
|
+
} catch (err) {
|
|
31740
|
+
storeLog.warn("fs.watch unavailable; falling back to polling-only updates", {
|
|
31741
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31742
|
+
tasksDir: this.tasksDir
|
|
31528
31743
|
});
|
|
31529
|
-
} catch {
|
|
31530
31744
|
}
|
|
31531
31745
|
this.pollInterval = setInterval(() => {
|
|
31532
31746
|
void this.checkForChanges();
|
|
@@ -31582,9 +31796,17 @@ ${task.description}
|
|
|
31582
31796
|
}
|
|
31583
31797
|
const elapsed = Date.now() - startTime;
|
|
31584
31798
|
if (elapsed > 100) {
|
|
31585
|
-
|
|
31799
|
+
storeLog.warn("checkForChanges took longer than expected", {
|
|
31800
|
+
elapsedMs: elapsed,
|
|
31801
|
+
thresholdMs: 100
|
|
31802
|
+
});
|
|
31586
31803
|
}
|
|
31587
|
-
} catch {
|
|
31804
|
+
} catch (err) {
|
|
31805
|
+
storeLog.warn("checkForChanges poll cycle failed", {
|
|
31806
|
+
lastKnownModified: this.lastKnownModified,
|
|
31807
|
+
lastPollTime: this.lastPollTime,
|
|
31808
|
+
error: err instanceof Error ? err.message : String(err)
|
|
31809
|
+
});
|
|
31588
31810
|
} finally {
|
|
31589
31811
|
this.pollingInProgress = false;
|
|
31590
31812
|
}
|
|
@@ -32839,7 +33061,15 @@ ${notificationsSection}
|
|
|
32839
33061
|
);
|
|
32840
33062
|
this.db.bumpLastModified();
|
|
32841
33063
|
} catch (err) {
|
|
32842
|
-
|
|
33064
|
+
storeLog.error("Failed to record activity", {
|
|
33065
|
+
id: fullEntry.id,
|
|
33066
|
+
type: fullEntry.type,
|
|
33067
|
+
taskId: fullEntry.taskId,
|
|
33068
|
+
taskTitle: fullEntry.taskTitle,
|
|
33069
|
+
detailsLength: fullEntry.details.length,
|
|
33070
|
+
hasMetadata: fullEntry.metadata !== void 0,
|
|
33071
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33072
|
+
});
|
|
32843
33073
|
}
|
|
32844
33074
|
return fullEntry;
|
|
32845
33075
|
}
|
|
@@ -33821,27 +34051,6 @@ var init_routine_store = __esm({
|
|
|
33821
34051
|
}
|
|
33822
34052
|
});
|
|
33823
34053
|
|
|
33824
|
-
// ../core/src/logger.ts
|
|
33825
|
-
function createLogger(prefix) {
|
|
33826
|
-
const tag = `[${prefix}]`;
|
|
33827
|
-
return {
|
|
33828
|
-
log(message, ...args) {
|
|
33829
|
-
console.error(`${tag} ${message}`, ...args);
|
|
33830
|
-
},
|
|
33831
|
-
warn(message, ...args) {
|
|
33832
|
-
console.warn(`${tag} ${message}`, ...args);
|
|
33833
|
-
},
|
|
33834
|
-
error(message, ...args) {
|
|
33835
|
-
console.error(`${tag} ${message}`, ...args);
|
|
33836
|
-
}
|
|
33837
|
-
};
|
|
33838
|
-
}
|
|
33839
|
-
var init_logger = __esm({
|
|
33840
|
-
"../core/src/logger.ts"() {
|
|
33841
|
-
"use strict";
|
|
33842
|
-
}
|
|
33843
|
-
});
|
|
33844
|
-
|
|
33845
34054
|
// ../core/src/plugin-loader.ts
|
|
33846
34055
|
import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
|
|
33847
34056
|
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
@@ -47406,13 +47615,13 @@ function createLogger2(prefix) {
|
|
|
47406
47615
|
const tag = `[${prefix}]`;
|
|
47407
47616
|
return {
|
|
47408
47617
|
log(message, ...args) {
|
|
47409
|
-
console.error(`${tag} ${message}`, ...args);
|
|
47618
|
+
globalThis.console.error(`${tag} ${message}`, ...args);
|
|
47410
47619
|
},
|
|
47411
47620
|
warn(message, ...args) {
|
|
47412
|
-
console.warn(`${tag} ${message}`, ...args);
|
|
47621
|
+
globalThis.console.warn(`${tag} ${message}`, ...args);
|
|
47413
47622
|
},
|
|
47414
47623
|
error(message, ...args) {
|
|
47415
|
-
console.error(`${tag} ${message}`, ...args);
|
|
47624
|
+
globalThis.console.error(`${tag} ${message}`, ...args);
|
|
47416
47625
|
}
|
|
47417
47626
|
};
|
|
47418
47627
|
}
|
|
@@ -48730,7 +48939,6 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
48730
48939
|
}
|
|
48731
48940
|
}
|
|
48732
48941
|
if (newDiagnostics.length > 0) {
|
|
48733
|
-
const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills";
|
|
48734
48942
|
for (const diag of newDiagnostics) {
|
|
48735
48943
|
piLog.warn(`[skills] ${diag.type}: ${diag.message}`);
|
|
48736
48944
|
}
|
|
@@ -48800,7 +49008,7 @@ import { join as join24 } from "node:path";
|
|
|
48800
49008
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
48801
49009
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
48802
49010
|
function getHomeDir2() {
|
|
48803
|
-
return process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
49011
|
+
return globalThis.process.env.HOME || globalThis.process.env.USERPROFILE || homedir5();
|
|
48804
49012
|
}
|
|
48805
49013
|
function getFusionAuthPath(home = getHomeDir2()) {
|
|
48806
49014
|
return join24(home, ".fusion", "agent", "auth.json");
|
|
@@ -48846,7 +49054,7 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
|
|
|
48846
49054
|
function resolveStoredApiKey(key) {
|
|
48847
49055
|
if (!key)
|
|
48848
49056
|
return void 0;
|
|
48849
|
-
return process.env[key] ?? key;
|
|
49057
|
+
return globalThis.process.env[key] ?? key;
|
|
48850
49058
|
}
|
|
48851
49059
|
function resolveOAuthApiKey(providerId, credential) {
|
|
48852
49060
|
if (credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires) {
|
|
@@ -49149,8 +49357,8 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
49149
49357
|
const fusionProjectSettings = readJsonObject2(join25(projectRoot, ".fusion", "settings.json"));
|
|
49150
49358
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
49151
49359
|
return {
|
|
49152
|
-
getGlobalSettings: () => structuredClone(globalSettings),
|
|
49153
|
-
getProjectSettings: () => structuredClone(fusionProjectSettings),
|
|
49360
|
+
getGlobalSettings: () => globalThis.structuredClone(globalSettings),
|
|
49361
|
+
getProjectSettings: () => globalThis.structuredClone(fusionProjectSettings),
|
|
49154
49362
|
getNpmCommand: () => Array.isArray(mergedSettings.npmCommand) ? [...mergedSettings.npmCommand] : void 0
|
|
49155
49363
|
};
|
|
49156
49364
|
}
|
|
@@ -49269,9 +49477,7 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
49269
49477
|
return {
|
|
49270
49478
|
...tool,
|
|
49271
49479
|
execute: async (...args) => {
|
|
49272
|
-
const _toolCallId = args[0];
|
|
49273
49480
|
const params = args[1];
|
|
49274
|
-
const _signal = args[2];
|
|
49275
49481
|
const pathArg = params.path;
|
|
49276
49482
|
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
|
|
49277
49483
|
const relToProject = relative3(projectRoot, pathArg);
|
|
@@ -53586,7 +53792,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53586
53792
|
} catch (error) {
|
|
53587
53793
|
if (error.name === "VerificationError") {
|
|
53588
53794
|
const verificationErr = error;
|
|
53589
|
-
const maxFixRetries = Math.min(settings.verificationFixRetries ??
|
|
53795
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
53590
53796
|
if (maxFixRetries > 0 && (verificationErr.verificationResult.testResult || verificationErr.verificationResult.buildResult)) {
|
|
53591
53797
|
mergerLog.log(`${taskId}: deterministic verification failed \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
|
|
53592
53798
|
await store.logEntry(taskId, `Verification failed during merge \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
|
|
@@ -53631,7 +53837,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
53631
53837
|
throw error;
|
|
53632
53838
|
}
|
|
53633
53839
|
if (error.message?.includes("Build verification failed")) {
|
|
53634
|
-
const maxFixRetries = Math.min(settings.verificationFixRetries ??
|
|
53840
|
+
const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
|
|
53635
53841
|
if (maxFixRetries > 0 && (effectiveTestCommand || effectiveBuildCommand)) {
|
|
53636
53842
|
mergerLog.log(`${taskId}: build verification failed \u2014 attempting in-merge fix`);
|
|
53637
53843
|
await store.logEntry(taskId, `Build verification failed during merge \u2014 attempting in-merge fix`);
|
|
@@ -55430,6 +55636,36 @@ function buildReducedStepPrompt(taskDetail, stepIndex) {
|
|
|
55430
55636
|
];
|
|
55431
55637
|
return parts.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
55432
55638
|
}
|
|
55639
|
+
function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
|
|
55640
|
+
if (taskModelProvider && taskModelId) {
|
|
55641
|
+
return { provider: taskModelProvider, modelId: taskModelId };
|
|
55642
|
+
}
|
|
55643
|
+
if (settings?.executionProvider && settings?.executionModelId) {
|
|
55644
|
+
return {
|
|
55645
|
+
provider: settings.executionProvider,
|
|
55646
|
+
modelId: settings.executionModelId
|
|
55647
|
+
};
|
|
55648
|
+
}
|
|
55649
|
+
if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
|
|
55650
|
+
return {
|
|
55651
|
+
provider: settings.executionGlobalProvider,
|
|
55652
|
+
modelId: settings.executionGlobalModelId
|
|
55653
|
+
};
|
|
55654
|
+
}
|
|
55655
|
+
if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
|
|
55656
|
+
return {
|
|
55657
|
+
provider: settings.defaultProviderOverride,
|
|
55658
|
+
modelId: settings.defaultModelIdOverride
|
|
55659
|
+
};
|
|
55660
|
+
}
|
|
55661
|
+
if (settings?.defaultProvider && settings?.defaultModelId) {
|
|
55662
|
+
return {
|
|
55663
|
+
provider: settings.defaultProvider,
|
|
55664
|
+
modelId: settings.defaultModelId
|
|
55665
|
+
};
|
|
55666
|
+
}
|
|
55667
|
+
return { provider: void 0, modelId: void 0 };
|
|
55668
|
+
}
|
|
55433
55669
|
function sleep2(ms) {
|
|
55434
55670
|
return new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
55435
55671
|
}
|
|
@@ -55622,8 +55858,11 @@ var init_step_session_executor = __esm({
|
|
|
55622
55858
|
createSendMessageTool(this.options.messageStore, taskDetail.assignedAgentId),
|
|
55623
55859
|
createReadMessagesTool(this.options.messageStore, taskDetail.assignedAgentId)
|
|
55624
55860
|
] : [];
|
|
55625
|
-
const
|
|
55626
|
-
|
|
55861
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair(
|
|
55862
|
+
taskDetail.modelProvider,
|
|
55863
|
+
taskDetail.modelId,
|
|
55864
|
+
settings
|
|
55865
|
+
);
|
|
55627
55866
|
const createResult = await createFnAgent2({
|
|
55628
55867
|
cwd: worktreePath,
|
|
55629
55868
|
systemPrompt: `You are an AI agent executing step ${stepIndex} of task ${taskDetail.id}. Follow instructions precisely.`,
|
|
@@ -56057,6 +56296,36 @@ function getExecutorSystemPrompt(settings) {
|
|
|
56057
56296
|
const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
|
|
56058
56297
|
return customPrompt || EXECUTOR_SYSTEM_PROMPT;
|
|
56059
56298
|
}
|
|
56299
|
+
function resolveExecutorModelPair2(taskModelProvider, taskModelId, settings) {
|
|
56300
|
+
if (taskModelProvider && taskModelId) {
|
|
56301
|
+
return { provider: taskModelProvider, modelId: taskModelId };
|
|
56302
|
+
}
|
|
56303
|
+
if (settings?.executionProvider && settings?.executionModelId) {
|
|
56304
|
+
return {
|
|
56305
|
+
provider: settings.executionProvider,
|
|
56306
|
+
modelId: settings.executionModelId
|
|
56307
|
+
};
|
|
56308
|
+
}
|
|
56309
|
+
if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
|
|
56310
|
+
return {
|
|
56311
|
+
provider: settings.executionGlobalProvider,
|
|
56312
|
+
modelId: settings.executionGlobalModelId
|
|
56313
|
+
};
|
|
56314
|
+
}
|
|
56315
|
+
if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
|
|
56316
|
+
return {
|
|
56317
|
+
provider: settings.defaultProviderOverride,
|
|
56318
|
+
modelId: settings.defaultModelIdOverride
|
|
56319
|
+
};
|
|
56320
|
+
}
|
|
56321
|
+
if (settings?.defaultProvider && settings?.defaultModelId) {
|
|
56322
|
+
return {
|
|
56323
|
+
provider: settings.defaultProvider,
|
|
56324
|
+
modelId: settings.defaultModelId
|
|
56325
|
+
};
|
|
56326
|
+
}
|
|
56327
|
+
return { provider: void 0, modelId: void 0 };
|
|
56328
|
+
}
|
|
56060
56329
|
function formatTimestamp2(iso) {
|
|
56061
56330
|
const date = new Date(iso);
|
|
56062
56331
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -56213,7 +56482,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
56213
56482
|
];
|
|
56214
56483
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
56215
56484
|
}
|
|
56216
|
-
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
56485
|
+
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
56217
56486
|
var init_executor = __esm({
|
|
56218
56487
|
"../engine/src/executor.ts"() {
|
|
56219
56488
|
"use strict";
|
|
@@ -56246,6 +56515,8 @@ var init_executor = __esm({
|
|
|
56246
56515
|
execAsync5 = promisify6(exec5);
|
|
56247
56516
|
STEP_STATUSES = ["pending", "in-progress", "done", "skipped"];
|
|
56248
56517
|
MAX_WORKFLOW_STEP_RETRIES = 3;
|
|
56518
|
+
MAX_TASK_DONE_SESSION_RETRIES = 3;
|
|
56519
|
+
MAX_TASK_DONE_REQUEUE_RETRIES = 3;
|
|
56249
56520
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
56250
56521
|
NonRetryableWorktreeError = class extends Error {
|
|
56251
56522
|
};
|
|
@@ -56533,8 +56804,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
56533
56804
|
activeEntry.lastModelProvider = task.modelProvider;
|
|
56534
56805
|
activeEntry.lastModelId = task.modelId;
|
|
56535
56806
|
const settings = await this.store.getSettings();
|
|
56536
|
-
const
|
|
56537
|
-
|
|
56807
|
+
const { provider: newProvider, modelId: newModelId } = resolveExecutorModelPair2(
|
|
56808
|
+
task.modelProvider,
|
|
56809
|
+
task.modelId,
|
|
56810
|
+
settings
|
|
56811
|
+
);
|
|
56538
56812
|
if (newProvider && newModelId) {
|
|
56539
56813
|
try {
|
|
56540
56814
|
const model = this.modelRegistry.find(newProvider, newModelId);
|
|
@@ -57441,8 +57715,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57441
57715
|
}
|
|
57442
57716
|
});
|
|
57443
57717
|
const agentWork = async () => {
|
|
57444
|
-
const
|
|
57445
|
-
|
|
57718
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair2(
|
|
57719
|
+
detail.modelProvider,
|
|
57720
|
+
detail.modelId,
|
|
57721
|
+
settings
|
|
57722
|
+
);
|
|
57446
57723
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
57447
57724
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
57448
57725
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
@@ -57624,63 +57901,74 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57624
57901
|
executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
|
|
57625
57902
|
this.options.onComplete?.(task);
|
|
57626
57903
|
} else {
|
|
57627
|
-
|
|
57628
|
-
|
|
57629
|
-
|
|
57630
|
-
|
|
57631
|
-
|
|
57632
|
-
|
|
57633
|
-
|
|
57634
|
-
|
|
57635
|
-
|
|
57636
|
-
|
|
57637
|
-
|
|
57638
|
-
|
|
57639
|
-
|
|
57640
|
-
|
|
57641
|
-
|
|
57642
|
-
|
|
57643
|
-
|
|
57644
|
-
|
|
57645
|
-
|
|
57646
|
-
|
|
57647
|
-
|
|
57648
|
-
|
|
57649
|
-
|
|
57650
|
-
|
|
57651
|
-
|
|
57652
|
-
|
|
57653
|
-
|
|
57654
|
-
|
|
57904
|
+
let taskDoneSessionRetries = 0;
|
|
57905
|
+
while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
|
|
57906
|
+
taskDoneSessionRetries++;
|
|
57907
|
+
executorLog.log(
|
|
57908
|
+
`\u26A0 ${task.id} finished without task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
|
|
57909
|
+
);
|
|
57910
|
+
await this.store.logEntry(
|
|
57911
|
+
task.id,
|
|
57912
|
+
`Agent finished without calling task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`,
|
|
57913
|
+
void 0,
|
|
57914
|
+
this.currentRunContext
|
|
57915
|
+
);
|
|
57916
|
+
this.activeSessions.delete(task.id);
|
|
57917
|
+
session.dispose();
|
|
57918
|
+
const { session: retrySession, sessionFile: retrySessionFile } = await createResolvedAgentSession({
|
|
57919
|
+
sessionPurpose: "executor",
|
|
57920
|
+
pluginRunner: this.options.pluginRunner,
|
|
57921
|
+
cwd: worktreePath,
|
|
57922
|
+
systemPrompt: executorSystemPrompt,
|
|
57923
|
+
tools: "coding",
|
|
57924
|
+
customTools,
|
|
57925
|
+
onText: agentLogger.onText,
|
|
57926
|
+
onThinking: agentLogger.onThinking,
|
|
57927
|
+
onToolStart: agentLogger.onToolStart,
|
|
57928
|
+
onToolEnd: agentLogger.onToolEnd,
|
|
57929
|
+
defaultProvider: executorProvider,
|
|
57930
|
+
defaultModelId: executorModelId,
|
|
57931
|
+
fallbackProvider: executorFallbackProvider,
|
|
57932
|
+
fallbackModelId: executorFallbackModelId,
|
|
57933
|
+
defaultThinkingLevel: executorThinkingLevel,
|
|
57934
|
+
sessionManager: SessionManager2.create(worktreePath),
|
|
57935
|
+
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
57936
|
+
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
57655
57937
|
});
|
|
57656
|
-
|
|
57657
|
-
|
|
57658
|
-
|
|
57659
|
-
|
|
57660
|
-
|
|
57661
|
-
|
|
57662
|
-
|
|
57663
|
-
|
|
57664
|
-
|
|
57665
|
-
|
|
57666
|
-
|
|
57667
|
-
|
|
57668
|
-
|
|
57669
|
-
|
|
57670
|
-
|
|
57671
|
-
|
|
57672
|
-
|
|
57673
|
-
|
|
57674
|
-
|
|
57675
|
-
|
|
57676
|
-
|
|
57677
|
-
|
|
57678
|
-
|
|
57679
|
-
|
|
57680
|
-
|
|
57681
|
-
|
|
57682
|
-
|
|
57683
|
-
|
|
57938
|
+
if (retrySessionFile) {
|
|
57939
|
+
this.store.updateTask(task.id, { sessionFile: retrySessionFile }).catch((err) => {
|
|
57940
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
57941
|
+
executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
|
|
57942
|
+
});
|
|
57943
|
+
}
|
|
57944
|
+
session = retrySession;
|
|
57945
|
+
sessionRef.current = retrySession;
|
|
57946
|
+
this.activeSessions.set(task.id, {
|
|
57947
|
+
session: retrySession,
|
|
57948
|
+
seenSteeringIds,
|
|
57949
|
+
lastModelProvider: detail.modelProvider,
|
|
57950
|
+
lastModelId: detail.modelId
|
|
57951
|
+
});
|
|
57952
|
+
stuckDetector?.trackTask(task.id, retrySession);
|
|
57953
|
+
const retryPrompt = [
|
|
57954
|
+
"Your previous session ended without calling the task_done tool.",
|
|
57955
|
+
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
57956
|
+
"1. If the work is done, call task_done with a summary of what was accomplished.",
|
|
57957
|
+
"2. If there is remaining work, finish it and then call task_done.",
|
|
57958
|
+
"",
|
|
57959
|
+
"Original task:",
|
|
57960
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
57961
|
+
].join("\n");
|
|
57962
|
+
stuckDetector?.recordActivity(task.id);
|
|
57963
|
+
await promptWithFallback(retrySession, retryPrompt);
|
|
57964
|
+
checkSessionError(retrySession);
|
|
57965
|
+
if (!taskDone) {
|
|
57966
|
+
const implicitCheck = await this.store.getTask(task.id);
|
|
57967
|
+
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
57968
|
+
taskDone = true;
|
|
57969
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit task_done`);
|
|
57970
|
+
await this.store.logEntry(task.id, "All steps complete \u2014 implicit task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
57971
|
+
}
|
|
57684
57972
|
}
|
|
57685
57973
|
}
|
|
57686
57974
|
if (taskDone) {
|
|
@@ -57709,11 +57997,29 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
57709
57997
|
executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
|
|
57710
57998
|
this.options.onComplete?.(task);
|
|
57711
57999
|
} else {
|
|
57712
|
-
const
|
|
57713
|
-
|
|
57714
|
-
|
|
57715
|
-
|
|
57716
|
-
|
|
58000
|
+
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
58001
|
+
const nextRequeueCount = priorRequeues + 1;
|
|
58002
|
+
const errorMessage = `Agent finished without calling task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
58003
|
+
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
58004
|
+
await this.store.updateTask(task.id, {
|
|
58005
|
+
status: "failed",
|
|
58006
|
+
error: errorMessage,
|
|
58007
|
+
taskDoneRetryCount: nextRequeueCount
|
|
58008
|
+
});
|
|
58009
|
+
await this.store.logEntry(
|
|
58010
|
+
task.id,
|
|
58011
|
+
`${errorMessage} \u2014 requeued to todo immediately (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`,
|
|
58012
|
+
void 0,
|
|
58013
|
+
this.currentRunContext
|
|
58014
|
+
);
|
|
58015
|
+
await this.store.moveTask(task.id, "todo");
|
|
58016
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`);
|
|
58017
|
+
} else {
|
|
58018
|
+
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
58019
|
+
await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
|
|
58020
|
+
await this.store.moveTask(task.id, "in-review");
|
|
58021
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no task_done \u2192 in-review`);
|
|
58022
|
+
}
|
|
57717
58023
|
this.options.onError?.(task, new Error(errorMessage));
|
|
57718
58024
|
}
|
|
57719
58025
|
}
|
|
@@ -64299,7 +64605,7 @@ async function getHeartbeatMemorySettings(taskStore) {
|
|
|
64299
64605
|
return maybeGetSettings.call(taskStore);
|
|
64300
64606
|
}
|
|
64301
64607
|
function isTickableState(state) {
|
|
64302
|
-
return state === "active" || state === "running";
|
|
64608
|
+
return state === "active" || state === "running" || state === "idle";
|
|
64303
64609
|
}
|
|
64304
64610
|
function isHeartbeatManaged(agent) {
|
|
64305
64611
|
return !isEphemeralAgent(agent);
|
|
@@ -65629,8 +65935,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
65629
65935
|
this.assignedListener = async (agent, taskId) => {
|
|
65630
65936
|
if (!this.running) return;
|
|
65631
65937
|
try {
|
|
65632
|
-
if (!isHeartbeatManaged(agent)
|
|
65633
|
-
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (
|
|
65938
|
+
if (!isHeartbeatManaged(agent)) {
|
|
65939
|
+
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (ephemeral/internal)`);
|
|
65940
|
+
return;
|
|
65941
|
+
}
|
|
65942
|
+
const runtimeConfig = agent.runtimeConfig ?? {};
|
|
65943
|
+
if (runtimeConfig.enabled === false) {
|
|
65944
|
+
heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (disabled)`);
|
|
65634
65945
|
return;
|
|
65635
65946
|
}
|
|
65636
65947
|
const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
|
|
@@ -66089,6 +66400,10 @@ var init_self_healing = __esm({
|
|
|
66089
66400
|
const result = await readLog("HEAD");
|
|
66090
66401
|
stdout = result.stdout;
|
|
66091
66402
|
}
|
|
66403
|
+
if (!stdout.trim() && task.baseCommitSha) {
|
|
66404
|
+
const result = await readLog("HEAD");
|
|
66405
|
+
stdout = result.stdout;
|
|
66406
|
+
}
|
|
66092
66407
|
const firstLine = stdout.trim().split("\n").find(Boolean);
|
|
66093
66408
|
if (!firstLine) return null;
|
|
66094
66409
|
const [sha, subject] = firstLine.split("");
|
|
@@ -67950,10 +68265,10 @@ var init_in_process_runtime = __esm({
|
|
|
67950
68265
|
this.taskStore
|
|
67951
68266
|
);
|
|
67952
68267
|
this.triggerScheduler.start();
|
|
67953
|
-
const
|
|
68268
|
+
const isHeartbeatEnabledAgent = (agent) => !isEphemeralAgent(agent) && agent.runtimeConfig?.enabled !== false;
|
|
67954
68269
|
this.agentCreatedListener = (agent) => {
|
|
67955
68270
|
if (!this.triggerScheduler) return;
|
|
67956
|
-
if (!
|
|
68271
|
+
if (!isHeartbeatEnabledAgent(agent)) return;
|
|
67957
68272
|
const rc = agent.runtimeConfig;
|
|
67958
68273
|
this.triggerScheduler.registerAgent(agent.id, {
|
|
67959
68274
|
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
@@ -67964,9 +68279,9 @@ var init_in_process_runtime = __esm({
|
|
|
67964
68279
|
this.agentStore.on("agent:created", this.agentCreatedListener);
|
|
67965
68280
|
this.agentUpdatedListener = (agent) => {
|
|
67966
68281
|
if (!this.triggerScheduler) return;
|
|
67967
|
-
if (!
|
|
68282
|
+
if (!isHeartbeatEnabledAgent(agent)) {
|
|
67968
68283
|
this.triggerScheduler.unregisterAgent(agent.id);
|
|
67969
|
-
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers
|
|
68284
|
+
runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers`);
|
|
67970
68285
|
return;
|
|
67971
68286
|
}
|
|
67972
68287
|
const rc = agent.runtimeConfig;
|
|
@@ -67974,7 +68289,7 @@ var init_in_process_runtime = __esm({
|
|
|
67974
68289
|
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
67975
68290
|
maxConcurrentRuns: rc?.maxConcurrentRuns
|
|
67976
68291
|
});
|
|
67977
|
-
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers
|
|
68292
|
+
runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
|
|
67978
68293
|
};
|
|
67979
68294
|
this.agentStore.on("agent:updated", this.agentUpdatedListener);
|
|
67980
68295
|
this.ephemeralTerminationListener = (agentId, from, to) => {
|
|
@@ -68009,7 +68324,7 @@ var init_in_process_runtime = __esm({
|
|
|
68009
68324
|
const agents = await this.agentStore.listAgents();
|
|
68010
68325
|
let registeredCount = 0;
|
|
68011
68326
|
for (const agent of agents) {
|
|
68012
|
-
if (!
|
|
68327
|
+
if (!isHeartbeatEnabledAgent(agent)) continue;
|
|
68013
68328
|
const rc = agent.runtimeConfig;
|
|
68014
68329
|
this.triggerScheduler.registerAgent(agent.id, {
|
|
68015
68330
|
heartbeatIntervalMs: rc?.heartbeatIntervalMs,
|
|
@@ -72458,11 +72773,13 @@ var init_terminal = __esm({
|
|
|
72458
72773
|
});
|
|
72459
72774
|
|
|
72460
72775
|
// ../dashboard/src/terminal-service.ts
|
|
72461
|
-
|
|
72776
|
+
import { createRequire } from "node:module";
|
|
72777
|
+
var isBunBinary, require2;
|
|
72462
72778
|
var init_terminal_service = __esm({
|
|
72463
72779
|
"../dashboard/src/terminal-service.ts"() {
|
|
72464
72780
|
"use strict";
|
|
72465
72781
|
isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
72782
|
+
require2 = createRequire(import.meta.url);
|
|
72466
72783
|
}
|
|
72467
72784
|
});
|
|
72468
72785
|
|