@runfusion/fusion 0.1.0 → 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 +951 -261
- 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 +490 -184
- package/package.json +1 -1
- package/dist/client/assets/index-CYkQfLYV.css +0 -1
- package/dist/client/assets/index-ep-146OC.js +0 -1241
package/dist/bin.js
CHANGED
|
@@ -1975,6 +1975,18 @@ function fromJson(json) {
|
|
|
1975
1975
|
return void 0;
|
|
1976
1976
|
}
|
|
1977
1977
|
}
|
|
1978
|
+
function probeFts5(db) {
|
|
1979
|
+
if (process.env.FUSION_DISABLE_FTS5 === "1" || process.env.FUSION_DISABLE_FTS5 === "true") {
|
|
1980
|
+
return false;
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS __fusion_fts5_probe USING fts5(x)");
|
|
1984
|
+
db.exec("DROP TABLE IF EXISTS __fusion_fts5_probe");
|
|
1985
|
+
return true;
|
|
1986
|
+
} catch {
|
|
1987
|
+
return false;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1978
1990
|
function normalizeTaskComments(steeringComments, comments) {
|
|
1979
1991
|
const normalizedComments = [];
|
|
1980
1992
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -2495,6 +2507,7 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2495
2507
|
dbPath;
|
|
2496
2508
|
/** Tracks transaction nesting depth for savepoint-based nested transactions. */
|
|
2497
2509
|
transactionDepth = 0;
|
|
2510
|
+
_fts5Available;
|
|
2498
2511
|
constructor(kbDir) {
|
|
2499
2512
|
this.dbPath = join(kbDir, "fusion.db");
|
|
2500
2513
|
if (!isAbsolute(kbDir)) {
|
|
@@ -2507,6 +2520,16 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2507
2520
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
2508
2521
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
2509
2522
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
2523
|
+
this._fts5Available = probeFts5(this.db);
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* True when the underlying SQLite build has FTS5 (`CREATE VIRTUAL TABLE … USING fts5`).
|
|
2527
|
+
* Node's bundled SQLite only exposes FTS5 when built with `SQLITE_ENABLE_FTS5`;
|
|
2528
|
+
* older Node 22.x LTS builds do not. Consumers must fall back to LIKE-based scans
|
|
2529
|
+
* when this is false. Override with `FUSION_DISABLE_FTS5=1` to force the fallback path.
|
|
2530
|
+
*/
|
|
2531
|
+
get fts5Available() {
|
|
2532
|
+
return this._fts5Available;
|
|
2510
2533
|
}
|
|
2511
2534
|
/**
|
|
2512
2535
|
* Initialize the database: create tables if they don't exist
|
|
@@ -2816,6 +2839,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
2816
2839
|
}
|
|
2817
2840
|
if (version < 21) {
|
|
2818
2841
|
this.applyMigration(21, () => {
|
|
2842
|
+
if (!this._fts5Available) {
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2819
2845
|
this.db.exec(`
|
|
2820
2846
|
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
2821
2847
|
id,
|
|
@@ -3225,6 +3251,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
|
|
|
3225
3251
|
}
|
|
3226
3252
|
if (version < 35) {
|
|
3227
3253
|
this.applyMigration(35, () => {
|
|
3254
|
+
if (!this._fts5Available) {
|
|
3255
|
+
return;
|
|
3256
|
+
}
|
|
3228
3257
|
const hasTaskTitle = this.hasColumn("tasks", "title");
|
|
3229
3258
|
const updateColumns = hasTaskTitle ? "id, title, description, comments" : "id, description, comments";
|
|
3230
3259
|
const oldTitle = hasTaskTitle ? "COALESCE(old.title, '')" : "''";
|
|
@@ -3518,6 +3547,9 @@ function resolveCreationRuntimeConfig(incoming, metadata) {
|
|
|
3518
3547
|
return incoming;
|
|
3519
3548
|
}
|
|
3520
3549
|
const rc = { ...incoming ?? {} };
|
|
3550
|
+
if (typeof rc.enabled !== "boolean") {
|
|
3551
|
+
rc.enabled = true;
|
|
3552
|
+
}
|
|
3521
3553
|
if (typeof rc.heartbeatIntervalMs !== "number" || !Number.isFinite(rc.heartbeatIntervalMs)) {
|
|
3522
3554
|
rc.heartbeatIntervalMs = DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS;
|
|
3523
3555
|
}
|
|
@@ -3563,6 +3595,7 @@ var init_agent_store = __esm({
|
|
|
3563
3595
|
const _2 = this.db;
|
|
3564
3596
|
await mkdir(this.agentsDir, { recursive: true });
|
|
3565
3597
|
await this.importLegacyFileDataOnce();
|
|
3598
|
+
await this.normalizeHeartbeatDefaultsOnce();
|
|
3566
3599
|
}
|
|
3567
3600
|
/**
|
|
3568
3601
|
* One-way migration helper for projects that still have legacy agent JSON
|
|
@@ -3667,6 +3700,51 @@ var init_agent_store = __esm({
|
|
|
3667
3700
|
`).run(migrationKey, migrationVersion);
|
|
3668
3701
|
this.db.bumpLastModified();
|
|
3669
3702
|
}
|
|
3703
|
+
/**
|
|
3704
|
+
* One-time normalization for durable agents created before the heartbeat
|
|
3705
|
+
* toggle was exposed in the UI. Those agents could persist
|
|
3706
|
+
* `runtimeConfig.enabled = false` even though users had no supported way to
|
|
3707
|
+
* manage that flag, which caused timers to stay disabled after restart.
|
|
3708
|
+
*
|
|
3709
|
+
* We normalize only once per project. After this migration lands, explicit
|
|
3710
|
+
* user choices are preserved because the version gate prevents reruns.
|
|
3711
|
+
*/
|
|
3712
|
+
async normalizeHeartbeatDefaultsOnce() {
|
|
3713
|
+
const migrationKey = "agentHeartbeatDefaultVersion";
|
|
3714
|
+
const migrationVersion = "1";
|
|
3715
|
+
const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(migrationKey);
|
|
3716
|
+
if (row?.value === migrationVersion) {
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
const agents = await this.listAgents({ includeEphemeral: true });
|
|
3720
|
+
let changed = 0;
|
|
3721
|
+
for (const agent of agents) {
|
|
3722
|
+
if (isEphemeralAgent(agent)) {
|
|
3723
|
+
continue;
|
|
3724
|
+
}
|
|
3725
|
+
const nextRuntimeConfig = {
|
|
3726
|
+
...resolveCreationRuntimeConfig(agent.runtimeConfig, agent.metadata) ?? {},
|
|
3727
|
+
enabled: true
|
|
3728
|
+
};
|
|
3729
|
+
const currentRuntimeConfig = agent.runtimeConfig ?? void 0;
|
|
3730
|
+
if (JSON.stringify(nextRuntimeConfig) === JSON.stringify(currentRuntimeConfig)) {
|
|
3731
|
+
continue;
|
|
3732
|
+
}
|
|
3733
|
+
await this.writeAgent({
|
|
3734
|
+
...agent,
|
|
3735
|
+
runtimeConfig: nextRuntimeConfig
|
|
3736
|
+
});
|
|
3737
|
+
changed++;
|
|
3738
|
+
}
|
|
3739
|
+
this.db.prepare(`
|
|
3740
|
+
INSERT INTO __meta (key, value)
|
|
3741
|
+
VALUES (?, ?)
|
|
3742
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value
|
|
3743
|
+
`).run(migrationKey, migrationVersion);
|
|
3744
|
+
if (changed > 0) {
|
|
3745
|
+
this.db.bumpLastModified();
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3670
3748
|
/**
|
|
3671
3749
|
* Create a new agent with "idle" state.
|
|
3672
3750
|
*
|
|
@@ -5776,11 +5854,12 @@ var init_global_settings = __esm({
|
|
|
5776
5854
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
5777
5855
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
|
|
5778
5856
|
import { join as join5 } from "node:path";
|
|
5779
|
-
var
|
|
5857
|
+
var BASE_SCHEMA_SQL, FTS5_SCHEMA_SQL, ArchiveDatabase;
|
|
5780
5858
|
var init_archive_db = __esm({
|
|
5781
5859
|
"../core/src/archive-db.ts"() {
|
|
5782
5860
|
"use strict";
|
|
5783
|
-
|
|
5861
|
+
init_db();
|
|
5862
|
+
BASE_SCHEMA_SQL = `
|
|
5784
5863
|
CREATE TABLE IF NOT EXISTS archived_tasks (
|
|
5785
5864
|
id TEXT PRIMARY KEY,
|
|
5786
5865
|
taskJson TEXT NOT NULL,
|
|
@@ -5796,7 +5875,8 @@ CREATE TABLE IF NOT EXISTS archived_tasks (
|
|
|
5796
5875
|
|
|
5797
5876
|
CREATE INDEX IF NOT EXISTS idxArchivedTasksArchivedAt ON archived_tasks(archivedAt);
|
|
5798
5877
|
CREATE INDEX IF NOT EXISTS idxArchivedTasksCreatedAt ON archived_tasks(createdAt);
|
|
5799
|
-
|
|
5878
|
+
`;
|
|
5879
|
+
FTS5_SCHEMA_SQL = `
|
|
5800
5880
|
CREATE VIRTUAL TABLE IF NOT EXISTS archived_tasks_fts USING fts5(
|
|
5801
5881
|
id,
|
|
5802
5882
|
title,
|
|
@@ -5824,18 +5904,26 @@ CREATE TRIGGER IF NOT EXISTS archived_tasks_fts_ad AFTER DELETE ON archived_task
|
|
|
5824
5904
|
END;
|
|
5825
5905
|
`;
|
|
5826
5906
|
ArchiveDatabase = class {
|
|
5907
|
+
db;
|
|
5908
|
+
_fts5Available;
|
|
5827
5909
|
constructor(kbDir) {
|
|
5828
|
-
this.kbDir = kbDir;
|
|
5829
5910
|
if (!existsSync4(kbDir)) {
|
|
5830
5911
|
mkdirSync3(kbDir, { recursive: true });
|
|
5831
5912
|
}
|
|
5832
5913
|
this.db = new DatabaseSync2(join5(kbDir, "archive.db"));
|
|
5833
5914
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
5834
5915
|
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
5916
|
+
this._fts5Available = probeFts5(this.db);
|
|
5917
|
+
}
|
|
5918
|
+
/** True when this SQLite build has FTS5. See db.ts#probeFts5. */
|
|
5919
|
+
get fts5Available() {
|
|
5920
|
+
return this._fts5Available;
|
|
5835
5921
|
}
|
|
5836
|
-
db;
|
|
5837
5922
|
init() {
|
|
5838
|
-
this.db.exec(
|
|
5923
|
+
this.db.exec(BASE_SCHEMA_SQL);
|
|
5924
|
+
if (this._fts5Available) {
|
|
5925
|
+
this.db.exec(FTS5_SCHEMA_SQL);
|
|
5926
|
+
}
|
|
5839
5927
|
this.addColumnIfMissing("archived_tasks", "prompt", "TEXT");
|
|
5840
5928
|
}
|
|
5841
5929
|
upsert(entry) {
|
|
@@ -5876,15 +5964,48 @@ END;
|
|
|
5876
5964
|
delete(id) {
|
|
5877
5965
|
this.db.prepare("DELETE FROM archived_tasks WHERE id = ?").run(id);
|
|
5878
5966
|
}
|
|
5967
|
+
/**
|
|
5968
|
+
* Full-text search over archived tasks. Accepts a raw user query and routes
|
|
5969
|
+
* through FTS5 when available, or a LIKE-based scan when not.
|
|
5970
|
+
*/
|
|
5879
5971
|
search(query, limit) {
|
|
5972
|
+
const trimmed = query?.trim();
|
|
5973
|
+
if (!trimmed) return [];
|
|
5974
|
+
const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0).map((t) => t.replace(/["{}:*^+()]/g, "")).filter((t) => t.length > 0);
|
|
5975
|
+
if (tokens.length === 0) return [];
|
|
5976
|
+
if (this._fts5Available) {
|
|
5977
|
+
const ftsQuery = tokens.map((token) => {
|
|
5978
|
+
if (/[":(){}*^+-]/.test(token)) {
|
|
5979
|
+
return `"${token.replace(/"/g, '\\"')}"`;
|
|
5980
|
+
}
|
|
5981
|
+
return token;
|
|
5982
|
+
}).join(" OR ");
|
|
5983
|
+
const rows2 = this.db.prepare(`
|
|
5984
|
+
SELECT a.taskJson
|
|
5985
|
+
FROM archived_tasks a
|
|
5986
|
+
JOIN archived_tasks_fts fts ON a.rowid = fts.rowid
|
|
5987
|
+
WHERE archived_tasks_fts MATCH ?
|
|
5988
|
+
ORDER BY rank
|
|
5989
|
+
LIMIT ?
|
|
5990
|
+
`).all(ftsQuery, limit);
|
|
5991
|
+
return rows2.map((row) => JSON.parse(row.taskJson));
|
|
5992
|
+
}
|
|
5993
|
+
const searchColumns = ["id", "title", "description", "comments"];
|
|
5994
|
+
const perTokenClause = `(${searchColumns.map((c) => `"${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
5995
|
+
const whereTokens = tokens.map(() => perTokenClause).join(" OR ");
|
|
5996
|
+
const params = [];
|
|
5997
|
+
for (const token of tokens) {
|
|
5998
|
+
const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
5999
|
+
for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
|
|
6000
|
+
}
|
|
6001
|
+
params.push(limit);
|
|
5880
6002
|
const rows = this.db.prepare(`
|
|
5881
|
-
SELECT
|
|
5882
|
-
FROM archived_tasks
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
ORDER BY rank
|
|
6003
|
+
SELECT taskJson
|
|
6004
|
+
FROM archived_tasks
|
|
6005
|
+
WHERE ${whereTokens}
|
|
6006
|
+
ORDER BY archivedAt DESC
|
|
5886
6007
|
LIMIT ?
|
|
5887
|
-
`).all(
|
|
6008
|
+
`).all(...params);
|
|
5888
6009
|
return rows.map((row) => JSON.parse(row.taskJson));
|
|
5889
6010
|
}
|
|
5890
6011
|
close() {
|
|
@@ -12197,6 +12318,9 @@ var init_migration = __esm({
|
|
|
12197
12318
|
* @returns Generated name
|
|
12198
12319
|
*/
|
|
12199
12320
|
async generateProjectName(projectPath) {
|
|
12321
|
+
if (!existsSync7(join9(projectPath, ".git"))) {
|
|
12322
|
+
return basename3(projectPath);
|
|
12323
|
+
}
|
|
12200
12324
|
try {
|
|
12201
12325
|
const { execFile: execFile5 } = await import("node:child_process");
|
|
12202
12326
|
const { promisify: promisify14 } = await import("node:util");
|
|
@@ -12204,7 +12328,7 @@ var init_migration = __esm({
|
|
|
12204
12328
|
const { stdout } = await execFileAsync3(
|
|
12205
12329
|
"git",
|
|
12206
12330
|
["remote", "get-url", "origin"],
|
|
12207
|
-
{ cwd: projectPath, timeout:
|
|
12331
|
+
{ cwd: projectPath, timeout: 1e3 }
|
|
12208
12332
|
);
|
|
12209
12333
|
const remoteUrl = stdout.trim();
|
|
12210
12334
|
if (remoteUrl) {
|
|
@@ -28698,6 +28822,27 @@ var init_run_command = __esm({
|
|
|
28698
28822
|
}
|
|
28699
28823
|
});
|
|
28700
28824
|
|
|
28825
|
+
// ../core/src/logger.ts
|
|
28826
|
+
function createLogger(prefix) {
|
|
28827
|
+
const tag = `[${prefix}]`;
|
|
28828
|
+
return {
|
|
28829
|
+
log(message, ...args) {
|
|
28830
|
+
console.error(`${tag} ${message}`, ...args);
|
|
28831
|
+
},
|
|
28832
|
+
warn(message, ...args) {
|
|
28833
|
+
console.warn(`${tag} ${message}`, ...args);
|
|
28834
|
+
},
|
|
28835
|
+
error(message, ...args) {
|
|
28836
|
+
console.error(`${tag} ${message}`, ...args);
|
|
28837
|
+
}
|
|
28838
|
+
};
|
|
28839
|
+
}
|
|
28840
|
+
var init_logger = __esm({
|
|
28841
|
+
"../core/src/logger.ts"() {
|
|
28842
|
+
"use strict";
|
|
28843
|
+
}
|
|
28844
|
+
});
|
|
28845
|
+
|
|
28701
28846
|
// ../core/src/store.ts
|
|
28702
28847
|
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
28703
28848
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
@@ -28746,7 +28891,7 @@ function canonicalizeSettings(settings) {
|
|
|
28746
28891
|
}
|
|
28747
28892
|
return settings;
|
|
28748
28893
|
}
|
|
28749
|
-
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;
|
|
28894
|
+
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;
|
|
28750
28895
|
var init_store = __esm({
|
|
28751
28896
|
"../core/src/store.ts"() {
|
|
28752
28897
|
"use strict";
|
|
@@ -28764,11 +28909,13 @@ var init_store = __esm({
|
|
|
28764
28909
|
init_task_merge();
|
|
28765
28910
|
init_project_memory();
|
|
28766
28911
|
init_run_command();
|
|
28912
|
+
init_logger();
|
|
28767
28913
|
LEGACY_BACKUP_DIR = ".kb/backups";
|
|
28768
28914
|
TASK_ACTIVITY_LOG_ENTRY_LIMIT = 1e3;
|
|
28769
28915
|
TASK_ACTIVITY_LOG_OUTCOME_LIMIT = 4e3;
|
|
28770
28916
|
ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT = 25;
|
|
28771
28917
|
ARCHIVE_AGENT_LOG_SNIPPET_LIMIT = 160;
|
|
28918
|
+
storeLog = createLogger("task-store");
|
|
28772
28919
|
TaskHasDependentsError = class extends Error {
|
|
28773
28920
|
taskId;
|
|
28774
28921
|
dependentIds;
|
|
@@ -29478,45 +29625,53 @@ ${recentText}` : void 0
|
|
|
29478
29625
|
*/
|
|
29479
29626
|
setupActivityLogListeners() {
|
|
29480
29627
|
this.on("task:created", (task) => {
|
|
29481
|
-
this.
|
|
29482
|
-
|
|
29483
|
-
|
|
29484
|
-
|
|
29485
|
-
|
|
29486
|
-
|
|
29487
|
-
|
|
29628
|
+
this.recordActivityFromListener(
|
|
29629
|
+
{
|
|
29630
|
+
type: "task:created",
|
|
29631
|
+
taskId: task.id,
|
|
29632
|
+
taskTitle: task.title,
|
|
29633
|
+
details: `Task ${task.id} created${task.title ? `: ${task.title}` : ""}`
|
|
29634
|
+
},
|
|
29635
|
+
"task:created"
|
|
29636
|
+
);
|
|
29488
29637
|
});
|
|
29489
29638
|
this.on("task:moved", (data) => {
|
|
29490
|
-
this.
|
|
29491
|
-
|
|
29492
|
-
|
|
29493
|
-
|
|
29494
|
-
|
|
29495
|
-
|
|
29496
|
-
|
|
29497
|
-
|
|
29639
|
+
this.recordActivityFromListener(
|
|
29640
|
+
{
|
|
29641
|
+
type: "task:moved",
|
|
29642
|
+
taskId: data.task.id,
|
|
29643
|
+
taskTitle: data.task.title,
|
|
29644
|
+
details: `Task ${data.task.id} moved: ${data.from} \u2192 ${data.to}`,
|
|
29645
|
+
metadata: { from: data.from, to: data.to }
|
|
29646
|
+
},
|
|
29647
|
+
"task:moved"
|
|
29648
|
+
);
|
|
29498
29649
|
});
|
|
29499
29650
|
this.on("task:merged", (result) => {
|
|
29500
29651
|
const status = result.merged ? "successfully merged" : "merge attempted";
|
|
29501
|
-
this.
|
|
29502
|
-
|
|
29503
|
-
|
|
29504
|
-
|
|
29505
|
-
|
|
29506
|
-
|
|
29507
|
-
|
|
29508
|
-
|
|
29652
|
+
this.recordActivityFromListener(
|
|
29653
|
+
{
|
|
29654
|
+
type: "task:merged",
|
|
29655
|
+
taskId: result.task.id,
|
|
29656
|
+
taskTitle: result.task.title,
|
|
29657
|
+
details: `Task ${result.task.id} ${status} to main`,
|
|
29658
|
+
metadata: { merged: result.merged, branch: result.branch }
|
|
29659
|
+
},
|
|
29660
|
+
"task:merged"
|
|
29661
|
+
);
|
|
29509
29662
|
});
|
|
29510
29663
|
this.on("task:updated", (task) => {
|
|
29511
29664
|
if (task.status === "failed") {
|
|
29512
|
-
this.
|
|
29513
|
-
|
|
29514
|
-
|
|
29515
|
-
|
|
29516
|
-
|
|
29517
|
-
|
|
29518
|
-
|
|
29519
|
-
|
|
29665
|
+
this.recordActivityFromListener(
|
|
29666
|
+
{
|
|
29667
|
+
type: "task:failed",
|
|
29668
|
+
taskId: task.id,
|
|
29669
|
+
taskTitle: task.title,
|
|
29670
|
+
details: `Task ${task.id} failed${task.error ? `: ${task.error}` : ""}`,
|
|
29671
|
+
metadata: task.error ? { error: task.error } : void 0
|
|
29672
|
+
},
|
|
29673
|
+
"task:updated"
|
|
29674
|
+
);
|
|
29520
29675
|
}
|
|
29521
29676
|
});
|
|
29522
29677
|
this.on("settings:updated", (data) => {
|
|
@@ -29534,21 +29689,35 @@ ${recentText}` : void 0
|
|
|
29534
29689
|
importantChanges.push(`engine pause ${data.settings.enginePaused ? "enabled" : "disabled"}`);
|
|
29535
29690
|
}
|
|
29536
29691
|
if (importantChanges.length > 0) {
|
|
29537
|
-
this.
|
|
29538
|
-
|
|
29539
|
-
|
|
29540
|
-
|
|
29541
|
-
|
|
29542
|
-
|
|
29692
|
+
this.recordActivityFromListener(
|
|
29693
|
+
{
|
|
29694
|
+
type: "settings:updated",
|
|
29695
|
+
details: `Settings updated: ${importantChanges.join(", ")}`,
|
|
29696
|
+
metadata: { changes: importantChanges }
|
|
29697
|
+
},
|
|
29698
|
+
"settings:updated"
|
|
29699
|
+
);
|
|
29543
29700
|
}
|
|
29544
29701
|
});
|
|
29545
29702
|
this.on("task:deleted", (task) => {
|
|
29546
|
-
this.
|
|
29547
|
-
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29703
|
+
this.recordActivityFromListener(
|
|
29704
|
+
{
|
|
29705
|
+
type: "task:deleted",
|
|
29706
|
+
taskId: task.id,
|
|
29707
|
+
taskTitle: task.title,
|
|
29708
|
+
details: `Task ${task.id} deleted${task.title ? `: ${task.title}` : ""}`
|
|
29709
|
+
},
|
|
29710
|
+
"task:deleted"
|
|
29711
|
+
);
|
|
29712
|
+
});
|
|
29713
|
+
}
|
|
29714
|
+
recordActivityFromListener(entry, sourceEvent) {
|
|
29715
|
+
this.recordActivity(entry).catch((err) => {
|
|
29716
|
+
storeLog.warn("Activity logging listener failed", {
|
|
29717
|
+
sourceEvent,
|
|
29718
|
+
type: entry.type,
|
|
29719
|
+
taskId: entry.taskId,
|
|
29720
|
+
error: err instanceof Error ? err.message : String(err)
|
|
29552
29721
|
});
|
|
29553
29722
|
});
|
|
29554
29723
|
}
|
|
@@ -30070,7 +30239,11 @@ ${recentText}` : void 0
|
|
|
30070
30239
|
if (defaultOnSteps.length > 0) {
|
|
30071
30240
|
resolvedWorkflowSteps = defaultOnSteps;
|
|
30072
30241
|
}
|
|
30073
|
-
} catch {
|
|
30242
|
+
} catch (err) {
|
|
30243
|
+
storeLog.warn("Failed to auto-apply default workflow steps during task creation", {
|
|
30244
|
+
error: err instanceof Error ? err.message : String(err),
|
|
30245
|
+
descriptionLength: input.description.length
|
|
30246
|
+
});
|
|
30074
30247
|
}
|
|
30075
30248
|
} else if (input.enabledWorkflowSteps.length === 0) {
|
|
30076
30249
|
resolvedWorkflowSteps = void 0;
|
|
@@ -30088,13 +30261,26 @@ ${recentText}` : void 0
|
|
|
30088
30261
|
}
|
|
30089
30262
|
}
|
|
30090
30263
|
} catch (err) {
|
|
30091
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
30092
30264
|
const autoEnabled = options?.settings?.autoSummarizeTitles === true;
|
|
30093
|
-
|
|
30094
|
-
|
|
30265
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
30266
|
+
storeLog.warn(
|
|
30267
|
+
`Title summarization failed for task ${id}: ${errorMessage} (desc length: ${input.description.length}, auto-summarize: ${autoEnabled})`,
|
|
30268
|
+
{
|
|
30269
|
+
taskId: id,
|
|
30270
|
+
descriptionLength: input.description.length,
|
|
30271
|
+
autoSummarizeEnabled: autoEnabled,
|
|
30272
|
+
error: errorMessage
|
|
30273
|
+
}
|
|
30095
30274
|
);
|
|
30096
30275
|
}
|
|
30097
|
-
}).catch(() => {
|
|
30276
|
+
}).catch((err) => {
|
|
30277
|
+
const autoEnabled = options?.settings?.autoSummarizeTitles === true;
|
|
30278
|
+
storeLog.error("Unexpected title summarization promise-chain failure", {
|
|
30279
|
+
taskId: id,
|
|
30280
|
+
descriptionLength: input.description.length,
|
|
30281
|
+
autoSummarizeEnabled: autoEnabled,
|
|
30282
|
+
error: err instanceof Error ? err.message : String(err)
|
|
30283
|
+
});
|
|
30098
30284
|
});
|
|
30099
30285
|
}
|
|
30100
30286
|
return task;
|
|
@@ -30361,26 +30547,46 @@ ${newTask.description}
|
|
|
30361
30547
|
if (sanitizedTokens.length === 0) {
|
|
30362
30548
|
return this.listTasks(options);
|
|
30363
30549
|
}
|
|
30364
|
-
const ftsQuery = sanitizedTokens.map((token) => {
|
|
30365
|
-
if (/[":(){}*^+-]/.test(token)) {
|
|
30366
|
-
return `"${token.replace(/"/g, '\\"')}"`;
|
|
30367
|
-
}
|
|
30368
|
-
return token;
|
|
30369
|
-
}).join(" OR ");
|
|
30370
30550
|
const limit = options?.limit ?? -1;
|
|
30371
30551
|
const offset = options?.offset ?? 0;
|
|
30372
30552
|
const offsetClause = offset > 0 ? ` OFFSET ${offset}` : "";
|
|
30373
30553
|
const includeArchived = options?.includeArchived ?? true;
|
|
30374
|
-
const
|
|
30375
|
-
const selectClause = this.getTaskSelectClause(
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30379
|
-
|
|
30380
|
-
|
|
30381
|
-
|
|
30382
|
-
|
|
30383
|
-
|
|
30554
|
+
const slim = options?.slim ?? false;
|
|
30555
|
+
const selectClause = this.getTaskSelectClause(slim, "t");
|
|
30556
|
+
let rows;
|
|
30557
|
+
if (this.db.fts5Available) {
|
|
30558
|
+
const ftsQuery = sanitizedTokens.map((token) => {
|
|
30559
|
+
if (/[":(){}*^+-]/.test(token)) {
|
|
30560
|
+
return `"${token.replace(/"/g, '\\"')}"`;
|
|
30561
|
+
}
|
|
30562
|
+
return token;
|
|
30563
|
+
}).join(" OR ");
|
|
30564
|
+
const whereClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
|
|
30565
|
+
rows = this.db.prepare(`
|
|
30566
|
+
SELECT ${selectClause} FROM tasks t
|
|
30567
|
+
JOIN tasks_fts fts ON t.rowid = fts.rowid
|
|
30568
|
+
WHERE tasks_fts MATCH ?
|
|
30569
|
+
${whereClause}
|
|
30570
|
+
ORDER BY rank
|
|
30571
|
+
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
|
|
30572
|
+
`).all(ftsQuery);
|
|
30573
|
+
} else {
|
|
30574
|
+
const searchColumns = ["id", "title", "description", "comments"];
|
|
30575
|
+
const perTokenClause = `(${searchColumns.map((c) => `t."${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
|
|
30576
|
+
const whereTokens = sanitizedTokens.map(() => perTokenClause).join(" OR ");
|
|
30577
|
+
const params = [];
|
|
30578
|
+
for (const token of sanitizedTokens) {
|
|
30579
|
+
const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
30580
|
+
for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
|
|
30581
|
+
}
|
|
30582
|
+
const archivedClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
|
|
30583
|
+
rows = this.db.prepare(`
|
|
30584
|
+
SELECT ${selectClause} FROM tasks t
|
|
30585
|
+
WHERE (${whereTokens})${archivedClause}
|
|
30586
|
+
ORDER BY t.createdAt ASC
|
|
30587
|
+
LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
|
|
30588
|
+
`).all(...params);
|
|
30589
|
+
}
|
|
30384
30590
|
const activeMatches = await Promise.all(rows.map(async (row) => {
|
|
30385
30591
|
const task = this.rowToTask(row);
|
|
30386
30592
|
if (task.steps.length > 0) {
|
|
@@ -30389,7 +30595,7 @@ ${newTask.description}
|
|
|
30389
30595
|
const steps = await this.parseStepsFromPrompt(task.id);
|
|
30390
30596
|
return steps.length > 0 ? { ...task, steps } : task;
|
|
30391
30597
|
}));
|
|
30392
|
-
const archiveMatches = includeArchived ? this.archiveDb.search(
|
|
30598
|
+
const archiveMatches = includeArchived ? this.archiveDb.search(trimmedQuery, limit >= 0 ? limit : 100).map((entry) => this.archiveEntryToTask(entry, slim)) : [];
|
|
30393
30599
|
const matches = [...activeMatches, ...archiveMatches];
|
|
30394
30600
|
return limit >= 0 ? matches.slice(0, limit) : matches;
|
|
30395
30601
|
}
|
|
@@ -31525,9 +31731,17 @@ ${task.description}
|
|
|
31525
31731
|
try {
|
|
31526
31732
|
this.watcher = watch(this.tasksDir, { recursive: true }, (_event, _filename) => {
|
|
31527
31733
|
});
|
|
31528
|
-
this.watcher.on("error", () => {
|
|
31734
|
+
this.watcher.on("error", (err) => {
|
|
31735
|
+
storeLog.warn("fs.watch emitted an error; polling will continue", {
|
|
31736
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31737
|
+
tasksDir: this.tasksDir
|
|
31738
|
+
});
|
|
31739
|
+
});
|
|
31740
|
+
} catch (err) {
|
|
31741
|
+
storeLog.warn("fs.watch unavailable; falling back to polling-only updates", {
|
|
31742
|
+
error: err instanceof Error ? err.message : String(err),
|
|
31743
|
+
tasksDir: this.tasksDir
|
|
31529
31744
|
});
|
|
31530
|
-
} catch {
|
|
31531
31745
|
}
|
|
31532
31746
|
this.pollInterval = setInterval(() => {
|
|
31533
31747
|
void this.checkForChanges();
|
|
@@ -31583,9 +31797,17 @@ ${task.description}
|
|
|
31583
31797
|
}
|
|
31584
31798
|
const elapsed = Date.now() - startTime;
|
|
31585
31799
|
if (elapsed > 100) {
|
|
31586
|
-
|
|
31800
|
+
storeLog.warn("checkForChanges took longer than expected", {
|
|
31801
|
+
elapsedMs: elapsed,
|
|
31802
|
+
thresholdMs: 100
|
|
31803
|
+
});
|
|
31587
31804
|
}
|
|
31588
|
-
} catch {
|
|
31805
|
+
} catch (err) {
|
|
31806
|
+
storeLog.warn("checkForChanges poll cycle failed", {
|
|
31807
|
+
lastKnownModified: this.lastKnownModified,
|
|
31808
|
+
lastPollTime: this.lastPollTime,
|
|
31809
|
+
error: err instanceof Error ? err.message : String(err)
|
|
31810
|
+
});
|
|
31589
31811
|
} finally {
|
|
31590
31812
|
this.pollingInProgress = false;
|
|
31591
31813
|
}
|
|
@@ -32840,7 +33062,15 @@ ${notificationsSection}
|
|
|
32840
33062
|
);
|
|
32841
33063
|
this.db.bumpLastModified();
|
|
32842
33064
|
} catch (err) {
|
|
32843
|
-
|
|
33065
|
+
storeLog.error("Failed to record activity", {
|
|
33066
|
+
id: fullEntry.id,
|
|
33067
|
+
type: fullEntry.type,
|
|
33068
|
+
taskId: fullEntry.taskId,
|
|
33069
|
+
taskTitle: fullEntry.taskTitle,
|
|
33070
|
+
detailsLength: fullEntry.details.length,
|
|
33071
|
+
hasMetadata: fullEntry.metadata !== void 0,
|
|
33072
|
+
error: err instanceof Error ? err.message : String(err)
|
|
33073
|
+
});
|
|
32844
33074
|
}
|
|
32845
33075
|
return fullEntry;
|
|
32846
33076
|
}
|
|
@@ -33822,27 +34052,6 @@ var init_routine_store = __esm({
|
|
|
33822
34052
|
}
|
|
33823
34053
|
});
|
|
33824
34054
|
|
|
33825
|
-
// ../core/src/logger.ts
|
|
33826
|
-
function createLogger(prefix) {
|
|
33827
|
-
const tag = `[${prefix}]`;
|
|
33828
|
-
return {
|
|
33829
|
-
log(message, ...args) {
|
|
33830
|
-
console.error(`${tag} ${message}`, ...args);
|
|
33831
|
-
},
|
|
33832
|
-
warn(message, ...args) {
|
|
33833
|
-
console.warn(`${tag} ${message}`, ...args);
|
|
33834
|
-
},
|
|
33835
|
-
error(message, ...args) {
|
|
33836
|
-
console.error(`${tag} ${message}`, ...args);
|
|
33837
|
-
}
|
|
33838
|
-
};
|
|
33839
|
-
}
|
|
33840
|
-
var init_logger = __esm({
|
|
33841
|
-
"../core/src/logger.ts"() {
|
|
33842
|
-
"use strict";
|
|
33843
|
-
}
|
|
33844
|
-
});
|
|
33845
|
-
|
|
33846
34055
|
// ../core/src/plugin-loader.ts
|
|
33847
34056
|
import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
|
|
33848
34057
|
import { EventEmitter as EventEmitter13 } from "node:events";
|
|
@@ -59048,13 +59257,13 @@ function createLogger2(prefix) {
|
|
|
59048
59257
|
const tag = `[${prefix}]`;
|
|
59049
59258
|
return {
|
|
59050
59259
|
log(message, ...args) {
|
|
59051
|
-
console.error(`${tag} ${message}`, ...args);
|
|
59260
|
+
globalThis.console.error(`${tag} ${message}`, ...args);
|
|
59052
59261
|
},
|
|
59053
59262
|
warn(message, ...args) {
|
|
59054
|
-
console.warn(`${tag} ${message}`, ...args);
|
|
59263
|
+
globalThis.console.warn(`${tag} ${message}`, ...args);
|
|
59055
59264
|
},
|
|
59056
59265
|
error(message, ...args) {
|
|
59057
|
-
console.error(`${tag} ${message}`, ...args);
|
|
59266
|
+
globalThis.console.error(`${tag} ${message}`, ...args);
|
|
59058
59267
|
}
|
|
59059
59268
|
};
|
|
59060
59269
|
}
|
|
@@ -60372,7 +60581,6 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
|
|
|
60372
60581
|
}
|
|
60373
60582
|
}
|
|
60374
60583
|
if (newDiagnostics.length > 0) {
|
|
60375
|
-
const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills";
|
|
60376
60584
|
for (const diag of newDiagnostics) {
|
|
60377
60585
|
piLog.warn(`[skills] ${diag.type}: ${diag.message}`);
|
|
60378
60586
|
}
|
|
@@ -60442,7 +60650,7 @@ import { join as join27 } from "node:path";
|
|
|
60442
60650
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
60443
60651
|
import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
|
|
60444
60652
|
function getHomeDir2() {
|
|
60445
|
-
return process.env.HOME || process.env.USERPROFILE || homedir6();
|
|
60653
|
+
return globalThis.process.env.HOME || globalThis.process.env.USERPROFILE || homedir6();
|
|
60446
60654
|
}
|
|
60447
60655
|
function getFusionAuthPath2(home = getHomeDir2()) {
|
|
60448
60656
|
return join27(home, ".fusion", "agent", "auth.json");
|
|
@@ -60488,7 +60696,7 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
|
|
|
60488
60696
|
function resolveStoredApiKey(key) {
|
|
60489
60697
|
if (!key)
|
|
60490
60698
|
return void 0;
|
|
60491
|
-
return process.env[key] ?? key;
|
|
60699
|
+
return globalThis.process.env[key] ?? key;
|
|
60492
60700
|
}
|
|
60493
60701
|
function resolveOAuthApiKey(providerId, credential) {
|
|
60494
60702
|
if (credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires) {
|
|
@@ -60791,8 +60999,8 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
|
|
|
60791
60999
|
const fusionProjectSettings = readJsonObject2(join28(projectRoot, ".fusion", "settings.json"));
|
|
60792
61000
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
60793
61001
|
return {
|
|
60794
|
-
getGlobalSettings: () => structuredClone(globalSettings),
|
|
60795
|
-
getProjectSettings: () => structuredClone(fusionProjectSettings),
|
|
61002
|
+
getGlobalSettings: () => globalThis.structuredClone(globalSettings),
|
|
61003
|
+
getProjectSettings: () => globalThis.structuredClone(fusionProjectSettings),
|
|
60796
61004
|
getNpmCommand: () => Array.isArray(mergedSettings.npmCommand) ? [...mergedSettings.npmCommand] : void 0
|
|
60797
61005
|
};
|
|
60798
61006
|
}
|
|
@@ -60911,9 +61119,7 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
|
|
|
60911
61119
|
return {
|
|
60912
61120
|
...tool,
|
|
60913
61121
|
execute: async (...args) => {
|
|
60914
|
-
const _toolCallId = args[0];
|
|
60915
61122
|
const params = args[1];
|
|
60916
|
-
const _signal = args[2];
|
|
60917
61123
|
const pathArg = params.path;
|
|
60918
61124
|
if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
|
|
60919
61125
|
const relToProject = relative4(projectRoot, pathArg);
|
|
@@ -67131,6 +67337,36 @@ function buildReducedStepPrompt(taskDetail, stepIndex) {
|
|
|
67131
67337
|
];
|
|
67132
67338
|
return parts.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
67133
67339
|
}
|
|
67340
|
+
function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
|
|
67341
|
+
if (taskModelProvider && taskModelId) {
|
|
67342
|
+
return { provider: taskModelProvider, modelId: taskModelId };
|
|
67343
|
+
}
|
|
67344
|
+
if (settings?.executionProvider && settings?.executionModelId) {
|
|
67345
|
+
return {
|
|
67346
|
+
provider: settings.executionProvider,
|
|
67347
|
+
modelId: settings.executionModelId
|
|
67348
|
+
};
|
|
67349
|
+
}
|
|
67350
|
+
if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
|
|
67351
|
+
return {
|
|
67352
|
+
provider: settings.executionGlobalProvider,
|
|
67353
|
+
modelId: settings.executionGlobalModelId
|
|
67354
|
+
};
|
|
67355
|
+
}
|
|
67356
|
+
if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
|
|
67357
|
+
return {
|
|
67358
|
+
provider: settings.defaultProviderOverride,
|
|
67359
|
+
modelId: settings.defaultModelIdOverride
|
|
67360
|
+
};
|
|
67361
|
+
}
|
|
67362
|
+
if (settings?.defaultProvider && settings?.defaultModelId) {
|
|
67363
|
+
return {
|
|
67364
|
+
provider: settings.defaultProvider,
|
|
67365
|
+
modelId: settings.defaultModelId
|
|
67366
|
+
};
|
|
67367
|
+
}
|
|
67368
|
+
return { provider: void 0, modelId: void 0 };
|
|
67369
|
+
}
|
|
67134
67370
|
function sleep2(ms) {
|
|
67135
67371
|
return new Promise((resolve29) => setTimeout(resolve29, ms));
|
|
67136
67372
|
}
|
|
@@ -67323,8 +67559,11 @@ var init_step_session_executor = __esm({
|
|
|
67323
67559
|
createSendMessageTool(this.options.messageStore, taskDetail.assignedAgentId),
|
|
67324
67560
|
createReadMessagesTool(this.options.messageStore, taskDetail.assignedAgentId)
|
|
67325
67561
|
] : [];
|
|
67326
|
-
const
|
|
67327
|
-
|
|
67562
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair(
|
|
67563
|
+
taskDetail.modelProvider,
|
|
67564
|
+
taskDetail.modelId,
|
|
67565
|
+
settings
|
|
67566
|
+
);
|
|
67328
67567
|
const createResult = await createFnAgent5({
|
|
67329
67568
|
cwd: worktreePath,
|
|
67330
67569
|
systemPrompt: `You are an AI agent executing step ${stepIndex} of task ${taskDetail.id}. Follow instructions precisely.`,
|
|
@@ -67758,6 +67997,36 @@ function getExecutorSystemPrompt(settings) {
|
|
|
67758
67997
|
const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
|
|
67759
67998
|
return customPrompt || EXECUTOR_SYSTEM_PROMPT;
|
|
67760
67999
|
}
|
|
68000
|
+
function resolveExecutorModelPair2(taskModelProvider, taskModelId, settings) {
|
|
68001
|
+
if (taskModelProvider && taskModelId) {
|
|
68002
|
+
return { provider: taskModelProvider, modelId: taskModelId };
|
|
68003
|
+
}
|
|
68004
|
+
if (settings?.executionProvider && settings?.executionModelId) {
|
|
68005
|
+
return {
|
|
68006
|
+
provider: settings.executionProvider,
|
|
68007
|
+
modelId: settings.executionModelId
|
|
68008
|
+
};
|
|
68009
|
+
}
|
|
68010
|
+
if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
|
|
68011
|
+
return {
|
|
68012
|
+
provider: settings.executionGlobalProvider,
|
|
68013
|
+
modelId: settings.executionGlobalModelId
|
|
68014
|
+
};
|
|
68015
|
+
}
|
|
68016
|
+
if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
|
|
68017
|
+
return {
|
|
68018
|
+
provider: settings.defaultProviderOverride,
|
|
68019
|
+
modelId: settings.defaultModelIdOverride
|
|
68020
|
+
};
|
|
68021
|
+
}
|
|
68022
|
+
if (settings?.defaultProvider && settings?.defaultModelId) {
|
|
68023
|
+
return {
|
|
68024
|
+
provider: settings.defaultProvider,
|
|
68025
|
+
modelId: settings.defaultModelId
|
|
68026
|
+
};
|
|
68027
|
+
}
|
|
68028
|
+
return { provider: void 0, modelId: void 0 };
|
|
68029
|
+
}
|
|
67761
68030
|
function formatTimestamp2(iso) {
|
|
67762
68031
|
const date = new Date(iso);
|
|
67763
68032
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -67914,7 +68183,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
67914
68183
|
];
|
|
67915
68184
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
67916
68185
|
}
|
|
67917
|
-
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
68186
|
+
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;
|
|
67918
68187
|
var init_executor = __esm({
|
|
67919
68188
|
"../engine/src/executor.ts"() {
|
|
67920
68189
|
"use strict";
|
|
@@ -67947,6 +68216,8 @@ var init_executor = __esm({
|
|
|
67947
68216
|
execAsync5 = promisify6(exec5);
|
|
67948
68217
|
STEP_STATUSES = ["pending", "in-progress", "done", "skipped"];
|
|
67949
68218
|
MAX_WORKFLOW_STEP_RETRIES = 3;
|
|
68219
|
+
MAX_TASK_DONE_SESSION_RETRIES = 3;
|
|
68220
|
+
MAX_TASK_DONE_REQUEUE_RETRIES = 3;
|
|
67950
68221
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
67951
68222
|
NonRetryableWorktreeError = class extends Error {
|
|
67952
68223
|
};
|
|
@@ -68234,8 +68505,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
68234
68505
|
activeEntry.lastModelProvider = task.modelProvider;
|
|
68235
68506
|
activeEntry.lastModelId = task.modelId;
|
|
68236
68507
|
const settings = await this.store.getSettings();
|
|
68237
|
-
const
|
|
68238
|
-
|
|
68508
|
+
const { provider: newProvider, modelId: newModelId } = resolveExecutorModelPair2(
|
|
68509
|
+
task.modelProvider,
|
|
68510
|
+
task.modelId,
|
|
68511
|
+
settings
|
|
68512
|
+
);
|
|
68239
68513
|
if (newProvider && newModelId) {
|
|
68240
68514
|
try {
|
|
68241
68515
|
const model = this.modelRegistry.find(newProvider, newModelId);
|
|
@@ -69142,8 +69416,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
69142
69416
|
}
|
|
69143
69417
|
});
|
|
69144
69418
|
const agentWork = async () => {
|
|
69145
|
-
const
|
|
69146
|
-
|
|
69419
|
+
const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair2(
|
|
69420
|
+
detail.modelProvider,
|
|
69421
|
+
detail.modelId,
|
|
69422
|
+
settings
|
|
69423
|
+
);
|
|
69147
69424
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
69148
69425
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
69149
69426
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
@@ -69325,63 +69602,74 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
69325
69602
|
executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
|
|
69326
69603
|
this.options.onComplete?.(task);
|
|
69327
69604
|
} else {
|
|
69328
|
-
|
|
69329
|
-
|
|
69330
|
-
|
|
69331
|
-
|
|
69332
|
-
|
|
69333
|
-
|
|
69334
|
-
|
|
69335
|
-
|
|
69336
|
-
|
|
69337
|
-
|
|
69338
|
-
|
|
69339
|
-
|
|
69340
|
-
|
|
69341
|
-
|
|
69342
|
-
|
|
69343
|
-
|
|
69344
|
-
|
|
69345
|
-
|
|
69346
|
-
|
|
69347
|
-
|
|
69348
|
-
|
|
69349
|
-
|
|
69350
|
-
|
|
69351
|
-
|
|
69352
|
-
|
|
69353
|
-
|
|
69354
|
-
|
|
69355
|
-
|
|
69605
|
+
let taskDoneSessionRetries = 0;
|
|
69606
|
+
while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
|
|
69607
|
+
taskDoneSessionRetries++;
|
|
69608
|
+
executorLog.log(
|
|
69609
|
+
`\u26A0 ${task.id} finished without task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
|
|
69610
|
+
);
|
|
69611
|
+
await this.store.logEntry(
|
|
69612
|
+
task.id,
|
|
69613
|
+
`Agent finished without calling task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`,
|
|
69614
|
+
void 0,
|
|
69615
|
+
this.currentRunContext
|
|
69616
|
+
);
|
|
69617
|
+
this.activeSessions.delete(task.id);
|
|
69618
|
+
session.dispose();
|
|
69619
|
+
const { session: retrySession2, sessionFile: retrySessionFile } = await createResolvedAgentSession({
|
|
69620
|
+
sessionPurpose: "executor",
|
|
69621
|
+
pluginRunner: this.options.pluginRunner,
|
|
69622
|
+
cwd: worktreePath,
|
|
69623
|
+
systemPrompt: executorSystemPrompt,
|
|
69624
|
+
tools: "coding",
|
|
69625
|
+
customTools,
|
|
69626
|
+
onText: agentLogger.onText,
|
|
69627
|
+
onThinking: agentLogger.onThinking,
|
|
69628
|
+
onToolStart: agentLogger.onToolStart,
|
|
69629
|
+
onToolEnd: agentLogger.onToolEnd,
|
|
69630
|
+
defaultProvider: executorProvider,
|
|
69631
|
+
defaultModelId: executorModelId,
|
|
69632
|
+
fallbackProvider: executorFallbackProvider,
|
|
69633
|
+
fallbackModelId: executorFallbackModelId,
|
|
69634
|
+
defaultThinkingLevel: executorThinkingLevel,
|
|
69635
|
+
sessionManager: SessionManager2.create(worktreePath),
|
|
69636
|
+
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
69637
|
+
...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
|
|
69356
69638
|
});
|
|
69357
|
-
|
|
69358
|
-
|
|
69359
|
-
|
|
69360
|
-
|
|
69361
|
-
|
|
69362
|
-
|
|
69363
|
-
|
|
69364
|
-
|
|
69365
|
-
|
|
69366
|
-
|
|
69367
|
-
|
|
69368
|
-
|
|
69369
|
-
|
|
69370
|
-
|
|
69371
|
-
|
|
69372
|
-
|
|
69373
|
-
|
|
69374
|
-
|
|
69375
|
-
|
|
69376
|
-
|
|
69377
|
-
|
|
69378
|
-
|
|
69379
|
-
|
|
69380
|
-
|
|
69381
|
-
|
|
69382
|
-
|
|
69383
|
-
|
|
69384
|
-
|
|
69639
|
+
if (retrySessionFile) {
|
|
69640
|
+
this.store.updateTask(task.id, { sessionFile: retrySessionFile }).catch((err) => {
|
|
69641
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
69642
|
+
executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
|
|
69643
|
+
});
|
|
69644
|
+
}
|
|
69645
|
+
session = retrySession2;
|
|
69646
|
+
sessionRef.current = retrySession2;
|
|
69647
|
+
this.activeSessions.set(task.id, {
|
|
69648
|
+
session: retrySession2,
|
|
69649
|
+
seenSteeringIds,
|
|
69650
|
+
lastModelProvider: detail.modelProvider,
|
|
69651
|
+
lastModelId: detail.modelId
|
|
69652
|
+
});
|
|
69653
|
+
stuckDetector?.trackTask(task.id, retrySession2);
|
|
69654
|
+
const retryPrompt = [
|
|
69655
|
+
"Your previous session ended without calling the task_done tool.",
|
|
69656
|
+
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
69657
|
+
"1. If the work is done, call task_done with a summary of what was accomplished.",
|
|
69658
|
+
"2. If there is remaining work, finish it and then call task_done.",
|
|
69659
|
+
"",
|
|
69660
|
+
"Original task:",
|
|
69661
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
69662
|
+
].join("\n");
|
|
69663
|
+
stuckDetector?.recordActivity(task.id);
|
|
69664
|
+
await promptWithFallback(retrySession2, retryPrompt);
|
|
69665
|
+
checkSessionError(retrySession2);
|
|
69666
|
+
if (!taskDone) {
|
|
69667
|
+
const implicitCheck = await this.store.getTask(task.id);
|
|
69668
|
+
if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
69669
|
+
taskDone = true;
|
|
69670
|
+
executorLog.log(`${task.id} all steps done \u2014 treating as implicit task_done`);
|
|
69671
|
+
await this.store.logEntry(task.id, "All steps complete \u2014 implicit task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
|
|
69672
|
+
}
|
|
69385
69673
|
}
|
|
69386
69674
|
}
|
|
69387
69675
|
if (taskDone) {
|
|
@@ -69410,11 +69698,29 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
69410
69698
|
executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
|
|
69411
69699
|
this.options.onComplete?.(task);
|
|
69412
69700
|
} else {
|
|
69413
|
-
const
|
|
69414
|
-
|
|
69415
|
-
|
|
69416
|
-
|
|
69417
|
-
|
|
69701
|
+
const priorRequeues = task.taskDoneRetryCount ?? 0;
|
|
69702
|
+
const nextRequeueCount = priorRequeues + 1;
|
|
69703
|
+
const errorMessage = `Agent finished without calling task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
|
|
69704
|
+
if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
|
|
69705
|
+
await this.store.updateTask(task.id, {
|
|
69706
|
+
status: "failed",
|
|
69707
|
+
error: errorMessage,
|
|
69708
|
+
taskDoneRetryCount: nextRequeueCount
|
|
69709
|
+
});
|
|
69710
|
+
await this.store.logEntry(
|
|
69711
|
+
task.id,
|
|
69712
|
+
`${errorMessage} \u2014 requeued to todo immediately (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`,
|
|
69713
|
+
void 0,
|
|
69714
|
+
this.currentRunContext
|
|
69715
|
+
);
|
|
69716
|
+
await this.store.moveTask(task.id, "todo");
|
|
69717
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`);
|
|
69718
|
+
} else {
|
|
69719
|
+
await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
|
|
69720
|
+
await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
|
|
69721
|
+
await this.store.moveTask(task.id, "in-review");
|
|
69722
|
+
executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no task_done \u2192 in-review`);
|
|
69723
|
+
}
|
|
69418
69724
|
this.options.onError?.(task, new Error(errorMessage));
|
|
69419
69725
|
}
|
|
69420
69726
|
}
|
|
@@ -76000,7 +76306,7 @@ async function getHeartbeatMemorySettings(taskStore) {
|
|
|
76000
76306
|
return maybeGetSettings.call(taskStore);
|
|
76001
76307
|
}
|
|
76002
76308
|
function isTickableState(state) {
|
|
76003
|
-
return state === "active" || state === "running";
|
|
76309
|
+
return state === "active" || state === "running" || state === "idle";
|
|
76004
76310
|
}
|
|
76005
76311
|
function isHeartbeatManaged(agent) {
|
|
76006
76312
|
return !isEphemeralAgent(agent);
|
|
@@ -115145,9 +115451,10 @@ function createApiRoutes(store, options) {
|
|
|
115145
115451
|
try {
|
|
115146
115452
|
const { store: scopedStore } = await getProjectContext2(req);
|
|
115147
115453
|
const settings = await scopedStore.getSettingsFast();
|
|
115454
|
+
const prAuthAvailable = isGhAvailable() && isGhAuthenticated() || Boolean(githubToken);
|
|
115148
115455
|
res.json({
|
|
115149
115456
|
...settings,
|
|
115150
|
-
|
|
115457
|
+
prAuthAvailable
|
|
115151
115458
|
});
|
|
115152
115459
|
} catch (err) {
|
|
115153
115460
|
if (err instanceof ApiError) {
|
|
@@ -115159,7 +115466,7 @@ function createApiRoutes(store, options) {
|
|
|
115159
115466
|
router.put("/settings", async (req, res) => {
|
|
115160
115467
|
try {
|
|
115161
115468
|
const { store: scopedStore, engine } = await getProjectContext2(req);
|
|
115162
|
-
const { githubTokenConfigured, ...clientSettings } = req.body;
|
|
115469
|
+
const { githubTokenConfigured, prAuthAvailable, ...clientSettings } = req.body;
|
|
115163
115470
|
const globalKeySet = new Set(GLOBAL_SETTINGS_KEYS);
|
|
115164
115471
|
const globalFieldsFound = Object.keys(clientSettings).filter((k) => globalKeySet.has(k));
|
|
115165
115472
|
if (globalFieldsFound.length > 0) {
|
|
@@ -133847,6 +134154,26 @@ function visibleTruncate(text, maxWidth) {
|
|
|
133847
134154
|
}
|
|
133848
134155
|
return result;
|
|
133849
134156
|
}
|
|
134157
|
+
function formatConsoleArgs(args) {
|
|
134158
|
+
const stringified = args.map((arg) => {
|
|
134159
|
+
if (typeof arg === "string") return arg;
|
|
134160
|
+
if (arg instanceof Error) return arg.stack ?? arg.message;
|
|
134161
|
+
if (arg === null || arg === void 0) return String(arg);
|
|
134162
|
+
if (typeof arg === "object") {
|
|
134163
|
+
try {
|
|
134164
|
+
return JSON.stringify(arg);
|
|
134165
|
+
} catch {
|
|
134166
|
+
return String(arg);
|
|
134167
|
+
}
|
|
134168
|
+
}
|
|
134169
|
+
return String(arg);
|
|
134170
|
+
}).join(" ");
|
|
134171
|
+
const match = stringified.match(/^\[([^\]]+)\]\s*(.*)$/s);
|
|
134172
|
+
if (match) {
|
|
134173
|
+
return { prefix: match[1], message: match[2] };
|
|
134174
|
+
}
|
|
134175
|
+
return { message: stringified };
|
|
134176
|
+
}
|
|
133850
134177
|
function centerText(text, width, padChar = " ") {
|
|
133851
134178
|
const visibleLen = visibleLength(text);
|
|
133852
134179
|
const padding = Math.max(0, width - visibleLen);
|
|
@@ -133854,11 +134181,6 @@ function centerText(text, width, padChar = " ") {
|
|
|
133854
134181
|
const rightPad = padding - leftPad;
|
|
133855
134182
|
return padChar.repeat(leftPad) + text + padChar.repeat(rightPad);
|
|
133856
134183
|
}
|
|
133857
|
-
function padRight(text, width) {
|
|
133858
|
-
if (width <= 0) return "";
|
|
133859
|
-
const visibleLen = visibleLength(text);
|
|
133860
|
-
return text + " ".repeat(Math.max(0, width - visibleLen));
|
|
133861
|
-
}
|
|
133862
134184
|
function isTTYAvailable() {
|
|
133863
134185
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
133864
134186
|
}
|
|
@@ -133896,9 +134218,9 @@ var init_dashboard_tui = __esm({
|
|
|
133896
134218
|
return this.count;
|
|
133897
134219
|
}
|
|
133898
134220
|
};
|
|
133899
|
-
SECTION_ORDER = ["
|
|
134221
|
+
SECTION_ORDER = ["system", "logs", "utilities", "stats", "settings"];
|
|
133900
134222
|
DashboardTUI = class {
|
|
133901
|
-
activeSection = "
|
|
134223
|
+
activeSection = "system";
|
|
133902
134224
|
logBuffer;
|
|
133903
134225
|
systemInfo = null;
|
|
133904
134226
|
taskStats = null;
|
|
@@ -133911,6 +134233,11 @@ var init_dashboard_tui = __esm({
|
|
|
133911
134233
|
showHelp = false;
|
|
133912
134234
|
uptimeTimer = null;
|
|
133913
134235
|
resizeHandler = null;
|
|
134236
|
+
// Logs interaction state
|
|
134237
|
+
selectedLogIndex = 0;
|
|
134238
|
+
logsViewportStart = 0;
|
|
134239
|
+
logsWrapEnabled = false;
|
|
134240
|
+
logsExpandedMode = false;
|
|
133914
134241
|
constructor() {
|
|
133915
134242
|
this.logBuffer = new LogRingBuffer();
|
|
133916
134243
|
}
|
|
@@ -133939,8 +134266,21 @@ var init_dashboard_tui = __esm({
|
|
|
133939
134266
|
...entry,
|
|
133940
134267
|
timestamp: /* @__PURE__ */ new Date()
|
|
133941
134268
|
});
|
|
134269
|
+
const newLength = this.logBuffer.getAll().length;
|
|
134270
|
+
if (this.selectedLogIndex >= newLength) {
|
|
134271
|
+
this.selectedLogIndex = Math.max(0, newLength - 1);
|
|
134272
|
+
}
|
|
133942
134273
|
this.render();
|
|
133943
134274
|
}
|
|
134275
|
+
/**
|
|
134276
|
+
* Clear logs and reset selection state.
|
|
134277
|
+
*/
|
|
134278
|
+
clearLogs() {
|
|
134279
|
+
this.logBuffer.clear();
|
|
134280
|
+
this.selectedLogIndex = 0;
|
|
134281
|
+
this.logsViewportStart = 0;
|
|
134282
|
+
this.logsExpandedMode = false;
|
|
134283
|
+
}
|
|
133944
134284
|
log(message, prefix) {
|
|
133945
134285
|
this.addLog({ level: "info", message, prefix });
|
|
133946
134286
|
}
|
|
@@ -133970,6 +134310,8 @@ var init_dashboard_tui = __esm({
|
|
|
133970
134310
|
this.handleKeypress(str);
|
|
133971
134311
|
} else if (key.ctrl && key.name === "c") {
|
|
133972
134312
|
this.handleKeypress("");
|
|
134313
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
134314
|
+
this.handleKeypress("\r");
|
|
133973
134315
|
} else if (key.name === "right") {
|
|
133974
134316
|
this.handleKeypress("\x1B[C");
|
|
133975
134317
|
} else if (key.name === "left") {
|
|
@@ -133980,6 +134322,12 @@ var init_dashboard_tui = __esm({
|
|
|
133980
134322
|
this.handleKeypress("\x1B[B");
|
|
133981
134323
|
} else if (key.name === "escape") {
|
|
133982
134324
|
this.handleKeypress("\x1B");
|
|
134325
|
+
} else if (key.name === "home") {
|
|
134326
|
+
this.handleKeypress("Home");
|
|
134327
|
+
} else if (key.name === "end") {
|
|
134328
|
+
this.handleKeypress("End");
|
|
134329
|
+
} else if (key.name === "space") {
|
|
134330
|
+
this.handleKeypress(" ");
|
|
133983
134331
|
}
|
|
133984
134332
|
});
|
|
133985
134333
|
this.uptimeTimer = setInterval(() => {
|
|
@@ -134079,6 +134427,79 @@ var init_dashboard_tui = __esm({
|
|
|
134079
134427
|
}
|
|
134080
134428
|
if (this.activeSection === "utilities") {
|
|
134081
134429
|
this.handleUtilityKeypress(key);
|
|
134430
|
+
return;
|
|
134431
|
+
}
|
|
134432
|
+
if (this.activeSection === "logs") {
|
|
134433
|
+
this.handleLogsKeypress(key);
|
|
134434
|
+
return;
|
|
134435
|
+
}
|
|
134436
|
+
}
|
|
134437
|
+
handleLogsKeypress(key) {
|
|
134438
|
+
const entries = this.logBuffer.getAll();
|
|
134439
|
+
const maxIndex = Math.max(0, entries.length - 1);
|
|
134440
|
+
if (key === "\x1B") {
|
|
134441
|
+
if (this.logsExpandedMode) {
|
|
134442
|
+
this.logsExpandedMode = false;
|
|
134443
|
+
this.showHelp = false;
|
|
134444
|
+
this.render();
|
|
134445
|
+
} else if (this.showHelp) {
|
|
134446
|
+
this.showHelp = false;
|
|
134447
|
+
this.render();
|
|
134448
|
+
}
|
|
134449
|
+
return;
|
|
134450
|
+
}
|
|
134451
|
+
if (key === "\r") {
|
|
134452
|
+
if (entries.length === 0) {
|
|
134453
|
+
return;
|
|
134454
|
+
}
|
|
134455
|
+
this.logsExpandedMode = !this.logsExpandedMode;
|
|
134456
|
+
this.render();
|
|
134457
|
+
return;
|
|
134458
|
+
}
|
|
134459
|
+
if (key === "w" || key === "W") {
|
|
134460
|
+
this.logsWrapEnabled = !this.logsWrapEnabled;
|
|
134461
|
+
this.render();
|
|
134462
|
+
return;
|
|
134463
|
+
}
|
|
134464
|
+
if (key === "\x1B[A" || key === "k" || key === "K") {
|
|
134465
|
+
if (entries.length === 0) return;
|
|
134466
|
+
if (this.selectedLogIndex > 0) {
|
|
134467
|
+
this.selectedLogIndex--;
|
|
134468
|
+
this.render();
|
|
134469
|
+
}
|
|
134470
|
+
return;
|
|
134471
|
+
}
|
|
134472
|
+
if (key === "\x1B[B" || key === "j" || key === "J") {
|
|
134473
|
+
if (entries.length === 0) return;
|
|
134474
|
+
if (this.selectedLogIndex < maxIndex) {
|
|
134475
|
+
this.selectedLogIndex++;
|
|
134476
|
+
this.render();
|
|
134477
|
+
}
|
|
134478
|
+
return;
|
|
134479
|
+
}
|
|
134480
|
+
if (key === "Home") {
|
|
134481
|
+
if (entries.length === 0) return;
|
|
134482
|
+
if (this.selectedLogIndex !== 0) {
|
|
134483
|
+
this.selectedLogIndex = 0;
|
|
134484
|
+
this.render();
|
|
134485
|
+
}
|
|
134486
|
+
return;
|
|
134487
|
+
}
|
|
134488
|
+
if (key === "End") {
|
|
134489
|
+
if (entries.length === 0) return;
|
|
134490
|
+
if (this.selectedLogIndex !== maxIndex) {
|
|
134491
|
+
this.selectedLogIndex = maxIndex;
|
|
134492
|
+
this.render();
|
|
134493
|
+
}
|
|
134494
|
+
return;
|
|
134495
|
+
}
|
|
134496
|
+
if (key === " " || key === "e" || key === "E") {
|
|
134497
|
+
if (entries.length === 0) {
|
|
134498
|
+
return;
|
|
134499
|
+
}
|
|
134500
|
+
this.logsExpandedMode = !this.logsExpandedMode;
|
|
134501
|
+
this.render();
|
|
134502
|
+
return;
|
|
134082
134503
|
}
|
|
134083
134504
|
}
|
|
134084
134505
|
async handleUtilityKeypress(key) {
|
|
@@ -134089,8 +134510,7 @@ var init_dashboard_tui = __esm({
|
|
|
134089
134510
|
break;
|
|
134090
134511
|
case "c":
|
|
134091
134512
|
this.callbacks.onClearLogs();
|
|
134092
|
-
this.
|
|
134093
|
-
this.render();
|
|
134513
|
+
this.clearLogs();
|
|
134094
134514
|
break;
|
|
134095
134515
|
case "t":
|
|
134096
134516
|
if (this.systemInfo) {
|
|
@@ -134117,7 +134537,7 @@ var init_dashboard_tui = __esm({
|
|
|
134117
134537
|
}
|
|
134118
134538
|
renderHeader() {
|
|
134119
134539
|
const cols = process.stdout.columns || 80;
|
|
134120
|
-
const title = colorize("
|
|
134540
|
+
const title = colorize(" fusion ", "cyan");
|
|
134121
134541
|
const titleLen = visibleLength(title);
|
|
134122
134542
|
process.stdout.write(title);
|
|
134123
134543
|
if (cols >= 70) {
|
|
@@ -134199,28 +134619,159 @@ var init_dashboard_tui = __esm({
|
|
|
134199
134619
|
break;
|
|
134200
134620
|
}
|
|
134201
134621
|
}
|
|
134622
|
+
getLogViewportStart(totalEntries, maxRows) {
|
|
134623
|
+
if (totalEntries <= 0) {
|
|
134624
|
+
this.logsViewportStart = 0;
|
|
134625
|
+
return 0;
|
|
134626
|
+
}
|
|
134627
|
+
const maxStart = Math.max(0, totalEntries - maxRows);
|
|
134628
|
+
let start = Math.min(this.logsViewportStart, maxStart);
|
|
134629
|
+
if (this.selectedLogIndex < start) {
|
|
134630
|
+
start = this.selectedLogIndex;
|
|
134631
|
+
} else if (this.selectedLogIndex >= start + maxRows) {
|
|
134632
|
+
start = this.selectedLogIndex - maxRows + 1;
|
|
134633
|
+
}
|
|
134634
|
+
this.logsViewportStart = Math.max(0, Math.min(start, maxStart));
|
|
134635
|
+
return this.logsViewportStart;
|
|
134636
|
+
}
|
|
134202
134637
|
renderLogsSection() {
|
|
134203
134638
|
const cols = process.stdout.columns || 80;
|
|
134204
134639
|
const entries = this.logBuffer.getAll();
|
|
134205
|
-
const maxRows = Math.max(1, (process.stdout.rows ?? 38) -
|
|
134640
|
+
const maxRows = Math.max(1, (process.stdout.rows ?? 38) - 9);
|
|
134206
134641
|
process.stdout.write(colorize("\n LOGS\n", "bold"));
|
|
134207
134642
|
process.stdout.write(colorize(` Ring buffer: ${this.logBuffer.total}/${MAX_LOG_ENTRIES} entries
|
|
134208
|
-
|
|
134209
134643
|
`, "dim"));
|
|
134210
134644
|
if (entries.length === 0) {
|
|
134211
134645
|
process.stdout.write(colorize(" No log entries yet.\n", "dim"));
|
|
134212
134646
|
return;
|
|
134213
134647
|
}
|
|
134214
|
-
const
|
|
134215
|
-
|
|
134648
|
+
const safeSelectedIndex = Math.min(this.selectedLogIndex, Math.max(0, entries.length - 1));
|
|
134649
|
+
if (safeSelectedIndex !== this.selectedLogIndex) {
|
|
134650
|
+
this.selectedLogIndex = safeSelectedIndex;
|
|
134651
|
+
}
|
|
134652
|
+
if (this.logsExpandedMode) {
|
|
134653
|
+
this.renderLogsExpandedPane(entries[safeSelectedIndex], safeSelectedIndex, entries.length);
|
|
134654
|
+
return;
|
|
134655
|
+
}
|
|
134656
|
+
const modeIndicator = this.logsWrapEnabled ? colorize(" [w] wrap on", "dim") : colorize(" [w] wrap off", "dim");
|
|
134657
|
+
process.stdout.write(modeIndicator + "\n\n");
|
|
134658
|
+
const startIndex = this.getLogViewportStart(entries.length, maxRows);
|
|
134659
|
+
const visibleEntries = entries.slice(startIndex, startIndex + maxRows);
|
|
134660
|
+
const visibleReversed = [...visibleEntries].reverse();
|
|
134661
|
+
const selectedDisplayIndex = safeSelectedIndex >= startIndex && safeSelectedIndex < startIndex + visibleEntries.length ? visibleEntries.length - 1 - (safeSelectedIndex - startIndex) : -1;
|
|
134662
|
+
const prefixLen = 30;
|
|
134663
|
+
const availableWidth = Math.max(8, cols - prefixLen);
|
|
134664
|
+
for (let displayIdx = 0; displayIdx < visibleReversed.length; displayIdx++) {
|
|
134665
|
+
const entry = visibleReversed[displayIdx];
|
|
134666
|
+
const isSelected = displayIdx === selectedDisplayIndex;
|
|
134667
|
+
const selector = isSelected ? colorize("\u25B8 ", "brightGreen") : " ";
|
|
134216
134668
|
const ts = colorize(formatTimestamp3(entry.timestamp), "dim");
|
|
134217
134669
|
const prefix = entry.prefix ? colorize(`[${entry.prefix}]`, "gray") : "";
|
|
134218
134670
|
const levelChar = entry.level === "error" ? colorize("\u2717", "brightRed") : entry.level === "warn" ? colorize("\u26A0", "brightYellow") : colorize("\u2713", "brightGreen");
|
|
134219
|
-
|
|
134220
|
-
|
|
134221
|
-
|
|
134222
|
-
|
|
134671
|
+
if (this.logsWrapEnabled) {
|
|
134672
|
+
const wrappedLines = this.wrapText(entry.message, availableWidth);
|
|
134673
|
+
const firstLine = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${wrappedLines[0]}`;
|
|
134674
|
+
process.stdout.write(visibleTruncate(firstLine, cols - 1) + "\n");
|
|
134675
|
+
for (let i = 1; i < wrappedLines.length; i++) {
|
|
134676
|
+
const continuation = ` ${wrappedLines[i]}`;
|
|
134677
|
+
process.stdout.write(visibleTruncate(continuation, cols - 1) + "\n");
|
|
134678
|
+
}
|
|
134679
|
+
} else {
|
|
134680
|
+
const messageWidth = Math.max(8, cols - prefixLen);
|
|
134681
|
+
const message = visibleTruncate(entry.message, messageWidth);
|
|
134682
|
+
const line = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${message}`;
|
|
134683
|
+
process.stdout.write(visibleTruncate(line, cols - 1) + "\n");
|
|
134684
|
+
}
|
|
134685
|
+
}
|
|
134686
|
+
}
|
|
134687
|
+
/**
|
|
134688
|
+
* Render the expanded log entry detail pane.
|
|
134689
|
+
* Replaces the normal list view with a focused view of a single entry.
|
|
134690
|
+
*/
|
|
134691
|
+
renderLogsExpandedPane(entry, index2, total) {
|
|
134692
|
+
const cols = process.stdout.columns || 80;
|
|
134693
|
+
const rows = process.stdout.rows ?? 24;
|
|
134694
|
+
const maxContentRows = Math.max(1, rows - 12);
|
|
134695
|
+
process.stdout.write(colorize(" EXPANDED LOG ENTRY\n", "bold"));
|
|
134696
|
+
const navHint = colorize(` Entry ${index2 + 1} of ${total} | [\u2191/k] older [\u2193/j] newer [Enter/Esc] close
|
|
134697
|
+
`, "dim");
|
|
134698
|
+
process.stdout.write(navHint);
|
|
134699
|
+
process.stdout.write(colorize(" " + "\u2500".repeat(Math.max(20, cols - 4)) + "\n", "dim"));
|
|
134700
|
+
const ts = formatTimestamp3(entry.timestamp);
|
|
134701
|
+
const levelLabel = entry.level === "error" ? colorize("ERROR", "brightRed") : entry.level === "warn" ? colorize("WARN", "brightYellow") : colorize("INFO", "brightGreen");
|
|
134702
|
+
process.stdout.write(colorize(` Timestamp: `, "gray") + colorize(ts, "white") + "\n");
|
|
134703
|
+
process.stdout.write(colorize(` Level: `, "gray") + levelLabel + "\n");
|
|
134704
|
+
if (entry.prefix) {
|
|
134705
|
+
process.stdout.write(colorize(` Prefix: `, "gray") + colorize(entry.prefix, "dim") + "\n");
|
|
134223
134706
|
}
|
|
134707
|
+
process.stdout.write("\n");
|
|
134708
|
+
process.stdout.write(colorize(" MESSAGE\n", "bold"));
|
|
134709
|
+
const messageIndent = " ";
|
|
134710
|
+
const availableWidth = Math.max(8, cols - messageIndent.length);
|
|
134711
|
+
const wrappedMessage = this.wrapText(entry.message, availableWidth);
|
|
134712
|
+
let linesPrinted = 5;
|
|
134713
|
+
for (const line of wrappedMessage) {
|
|
134714
|
+
if (linesPrinted >= maxContentRows) {
|
|
134715
|
+
process.stdout.write(colorize(`
|
|
134716
|
+
... (truncated)
|
|
134717
|
+
`, "dim"));
|
|
134718
|
+
break;
|
|
134719
|
+
}
|
|
134720
|
+
process.stdout.write(messageIndent + line + "\n");
|
|
134721
|
+
linesPrinted++;
|
|
134722
|
+
}
|
|
134723
|
+
const footerHint = colorize(`
|
|
134724
|
+
[Esc] or [Enter] to close expanded view
|
|
134725
|
+
`, "dim");
|
|
134726
|
+
process.stdout.write(footerHint);
|
|
134727
|
+
}
|
|
134728
|
+
/**
|
|
134729
|
+
* Wrap text to fit within available width, returning an array of lines.
|
|
134730
|
+
* Respects ANSI escape sequences via visibleLength.
|
|
134731
|
+
*/
|
|
134732
|
+
wrapText(text, maxWidth) {
|
|
134733
|
+
if (maxWidth <= 0) return [""];
|
|
134734
|
+
if (visibleLength(text) <= maxWidth) return [text];
|
|
134735
|
+
const lines = [];
|
|
134736
|
+
let remaining = text;
|
|
134737
|
+
while (visibleLength(remaining) > maxWidth) {
|
|
134738
|
+
let breakIdx = 0;
|
|
134739
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
134740
|
+
const char = remaining[i];
|
|
134741
|
+
if (char === " " || char === " ") {
|
|
134742
|
+
if (visibleLength(remaining.substring(0, i)) <= maxWidth) {
|
|
134743
|
+
breakIdx = i;
|
|
134744
|
+
}
|
|
134745
|
+
}
|
|
134746
|
+
if (visibleLength(remaining.substring(0, i + 1)) > maxWidth) {
|
|
134747
|
+
break;
|
|
134748
|
+
}
|
|
134749
|
+
}
|
|
134750
|
+
if (breakIdx === 0) {
|
|
134751
|
+
const firstTokenMatch = remaining.match(/^(\S+)/);
|
|
134752
|
+
if (firstTokenMatch) {
|
|
134753
|
+
const firstToken = firstTokenMatch[1];
|
|
134754
|
+
if (visibleLength(firstToken) > maxWidth) {
|
|
134755
|
+
const chunkSize = Math.max(1, maxWidth - 1);
|
|
134756
|
+
let chunkStart = 0;
|
|
134757
|
+
while (chunkStart < firstToken.length) {
|
|
134758
|
+
const chunk = firstToken.substring(chunkStart, chunkStart + chunkSize);
|
|
134759
|
+
lines.push(chunk);
|
|
134760
|
+
chunkStart += chunkSize;
|
|
134761
|
+
}
|
|
134762
|
+
remaining = remaining.substring(firstToken.length).trimStart();
|
|
134763
|
+
continue;
|
|
134764
|
+
}
|
|
134765
|
+
}
|
|
134766
|
+
breakIdx = Math.min(maxWidth, remaining.length);
|
|
134767
|
+
}
|
|
134768
|
+
lines.push(remaining.substring(0, breakIdx).trimEnd());
|
|
134769
|
+
remaining = remaining.substring(breakIdx).trimStart();
|
|
134770
|
+
}
|
|
134771
|
+
if (remaining.length > 0) {
|
|
134772
|
+
lines.push(remaining);
|
|
134773
|
+
}
|
|
134774
|
+
return lines.length > 0 ? lines : [""];
|
|
134224
134775
|
}
|
|
134225
134776
|
renderSystemSection() {
|
|
134226
134777
|
if (!this.systemInfo) {
|
|
@@ -134355,39 +134906,59 @@ var init_dashboard_tui = __esm({
|
|
|
134355
134906
|
process.stdout.write(right);
|
|
134356
134907
|
process.stdout.write("\n");
|
|
134357
134908
|
}
|
|
134358
|
-
|
|
134359
|
-
|
|
134360
|
-
|
|
134361
|
-
|
|
134362
|
-
|
|
134363
|
-
|
|
134909
|
+
/**
|
|
134910
|
+
* Build help overlay lines as an array of strings.
|
|
134911
|
+
* Uses dynamic visibleLength calculation to ensure consistent box width.
|
|
134912
|
+
* All box-drawing rows (including borders) have the same total visible width.
|
|
134913
|
+
*
|
|
134914
|
+
* @param boxWidth - The interior content width (excluding the two box characters)
|
|
134915
|
+
* @param useBoxDrawing - Whether to use box-drawing characters (true) or compact text (false)
|
|
134916
|
+
* @returns Array of help lines
|
|
134917
|
+
*/
|
|
134918
|
+
buildHelpLines(boxWidth, useBoxDrawing) {
|
|
134364
134919
|
if (useBoxDrawing) {
|
|
134365
|
-
|
|
134366
|
-
|
|
134367
|
-
|
|
134368
|
-
|
|
134369
|
-
|
|
134370
|
-
|
|
134371
|
-
|
|
134372
|
-
|
|
134373
|
-
|
|
134374
|
-
|
|
134375
|
-
|
|
134376
|
-
|
|
134377
|
-
|
|
134378
|
-
|
|
134920
|
+
const boxRow = (content) => {
|
|
134921
|
+
const padding = Math.max(0, boxWidth - visibleLength(content));
|
|
134922
|
+
return "\u2502" + content + " ".repeat(padding) + "\u2502";
|
|
134923
|
+
};
|
|
134924
|
+
return [
|
|
134925
|
+
"\u250C" + "\u2500".repeat(boxWidth) + "\u2510",
|
|
134926
|
+
boxRow(centerText("KEYBOARD SHORTCUTS", boxWidth, " ")),
|
|
134927
|
+
"\u251C" + "\u2500".repeat(boxWidth) + "\u2524",
|
|
134928
|
+
boxRow(" [1-5] Switch to tab by number"),
|
|
134929
|
+
boxRow(" [n] / \u2192 Next tab"),
|
|
134930
|
+
boxRow(" [p] / \u2190 Previous tab"),
|
|
134931
|
+
boxRow(" [r] Refresh stats (Utilities)"),
|
|
134932
|
+
boxRow(" [c] Clear logs (Utilities)"),
|
|
134933
|
+
boxRow(" [t] Toggle engine pause (Utilities)"),
|
|
134934
|
+
boxRow(" [\u2191/\u2193/k/j] Navigate log entries (Logs)"),
|
|
134935
|
+
boxRow(" [Home/End] First/last log entry (Logs)"),
|
|
134936
|
+
boxRow(" [Enter/Space/e] Expand log (Logs)"),
|
|
134937
|
+
boxRow(" [w] Toggle word wrap (Logs)"),
|
|
134938
|
+
boxRow(" [?] / [h] Toggle help"),
|
|
134939
|
+
boxRow(" [q] Quit"),
|
|
134940
|
+
boxRow(" [Ctrl+C] Force quit"),
|
|
134941
|
+
"\u2514" + "\u2500".repeat(boxWidth) + "\u2518"
|
|
134379
134942
|
];
|
|
134380
134943
|
} else {
|
|
134381
|
-
|
|
134382
|
-
|
|
134383
|
-
|
|
134384
|
-
|
|
134385
|
-
|
|
134386
|
-
|
|
134944
|
+
return [
|
|
134945
|
+
"KEYBOARD SHORTCUTS",
|
|
134946
|
+
" [1-5] Switch tab | [n/p] Next/Prev | [q] Quit",
|
|
134947
|
+
" [\u2191\u2193/k/j] Navigate logs | [Home/End] First/Last (Logs)",
|
|
134948
|
+
" [Enter/Space/e] Expand log | [w] Toggle wrap (Logs)",
|
|
134949
|
+
" [r] Refresh | [c] Clear logs | [t] Toggle engine",
|
|
134950
|
+
" [?/h] Help | [Ctrl+C] Force quit"
|
|
134387
134951
|
];
|
|
134388
134952
|
}
|
|
134389
|
-
|
|
134390
|
-
|
|
134953
|
+
}
|
|
134954
|
+
renderHelpOverlay() {
|
|
134955
|
+
const cols = process.stdout.columns || 80;
|
|
134956
|
+
const rows = process.stdout.rows || 24;
|
|
134957
|
+
const boxWidth = Math.min(62, Math.max(cols - 4, 20));
|
|
134958
|
+
const useBoxDrawing = cols >= boxWidth + 4;
|
|
134959
|
+
const rawHelpLines = this.buildHelpLines(boxWidth, useBoxDrawing);
|
|
134960
|
+
const compactBoxWidth = useBoxDrawing ? boxWidth : Math.max(...rawHelpLines.map(visibleLength));
|
|
134961
|
+
const boxHeight = rawHelpLines.length;
|
|
134391
134962
|
const safeStartX = Math.max(1, Math.floor((cols - compactBoxWidth) / 2));
|
|
134392
134963
|
const safeStartY = Math.max(1, Math.floor((rows - boxHeight) / 2));
|
|
134393
134964
|
const clearTop = Math.max(1, safeStartY - 1);
|
|
@@ -134396,15 +134967,17 @@ var init_dashboard_tui = __esm({
|
|
|
134396
134967
|
moveCursorTo(1, y);
|
|
134397
134968
|
clearLine();
|
|
134398
134969
|
}
|
|
134399
|
-
for (let i = 0; i <
|
|
134970
|
+
for (let i = 0; i < rawHelpLines.length; i++) {
|
|
134971
|
+
const color = i === 0 || i === 2 || i === rawHelpLines.length - 1 ? "brightBlue" : "white";
|
|
134400
134972
|
moveCursorTo(safeStartX, safeStartY + i);
|
|
134401
|
-
process.stdout.write(
|
|
134973
|
+
process.stdout.write(colorize(rawHelpLines[i], color));
|
|
134402
134974
|
}
|
|
134403
134975
|
}
|
|
134404
134976
|
};
|
|
134405
134977
|
DashboardLogSink = class {
|
|
134406
134978
|
tui = null;
|
|
134407
134979
|
isTTY;
|
|
134980
|
+
originalConsole = null;
|
|
134408
134981
|
constructor(tui) {
|
|
134409
134982
|
this.tui = tui ?? null;
|
|
134410
134983
|
this.isTTY = tui?.running ?? false;
|
|
@@ -134414,26 +134987,74 @@ var init_dashboard_tui = __esm({
|
|
|
134414
134987
|
this.isTTY = true;
|
|
134415
134988
|
}
|
|
134416
134989
|
log(message, prefix) {
|
|
134990
|
+
const line = prefix ? `[${prefix}] ${message}` : message;
|
|
134417
134991
|
if (this.tui && this.isTTY) {
|
|
134418
134992
|
this.tui.log(message, prefix);
|
|
134993
|
+
} else if (this.originalConsole) {
|
|
134994
|
+
this.originalConsole.log.call(console, line);
|
|
134419
134995
|
} else {
|
|
134420
|
-
console.log(
|
|
134996
|
+
console.log(line);
|
|
134421
134997
|
}
|
|
134422
134998
|
}
|
|
134423
134999
|
warn(message, prefix) {
|
|
135000
|
+
const line = prefix ? `[${prefix}] ${message}` : message;
|
|
134424
135001
|
if (this.tui && this.isTTY) {
|
|
134425
135002
|
this.tui.warn(message, prefix);
|
|
135003
|
+
} else if (this.originalConsole) {
|
|
135004
|
+
this.originalConsole.warn.call(console, line);
|
|
134426
135005
|
} else {
|
|
134427
|
-
console.warn(
|
|
135006
|
+
console.warn(line);
|
|
134428
135007
|
}
|
|
134429
135008
|
}
|
|
134430
135009
|
error(message, prefix) {
|
|
135010
|
+
const line = prefix ? `[${prefix}] ${message}` : message;
|
|
134431
135011
|
if (this.tui && this.isTTY) {
|
|
134432
135012
|
this.tui.error(message, prefix);
|
|
135013
|
+
} else if (this.originalConsole) {
|
|
135014
|
+
this.originalConsole.error.call(console, line);
|
|
134433
135015
|
} else {
|
|
134434
|
-
console.error(
|
|
135016
|
+
console.error(line);
|
|
134435
135017
|
}
|
|
134436
135018
|
}
|
|
135019
|
+
/**
|
|
135020
|
+
* Monkey-patch `console.log/warn/error` so everything (including the engine's
|
|
135021
|
+
* createLogger() output, which writes directly to console.error) surfaces in
|
|
135022
|
+
* the TUI's log ring buffer. Without this, most runtime logs render beneath
|
|
135023
|
+
* the alt-screen TUI and are immediately overwritten on the next render,
|
|
135024
|
+
* leaving the Logs tab nearly empty.
|
|
135025
|
+
*
|
|
135026
|
+
* Messages that start with `[prefix] rest` are unpacked so the TUI stores
|
|
135027
|
+
* `prefix="prefix"` and `message="rest"`. Idempotent; call `releaseConsole()`
|
|
135028
|
+
* on TUI shutdown to restore the originals.
|
|
135029
|
+
*/
|
|
135030
|
+
captureConsole() {
|
|
135031
|
+
if (this.originalConsole) return;
|
|
135032
|
+
this.originalConsole = {
|
|
135033
|
+
log: console.log,
|
|
135034
|
+
warn: console.warn,
|
|
135035
|
+
error: console.error
|
|
135036
|
+
};
|
|
135037
|
+
console.log = (...args) => {
|
|
135038
|
+
const { message, prefix } = formatConsoleArgs(args);
|
|
135039
|
+
this.log(message, prefix);
|
|
135040
|
+
};
|
|
135041
|
+
console.warn = (...args) => {
|
|
135042
|
+
const { message, prefix } = formatConsoleArgs(args);
|
|
135043
|
+
this.warn(message, prefix);
|
|
135044
|
+
};
|
|
135045
|
+
console.error = (...args) => {
|
|
135046
|
+
const { message, prefix } = formatConsoleArgs(args);
|
|
135047
|
+
this.error(message, prefix);
|
|
135048
|
+
};
|
|
135049
|
+
}
|
|
135050
|
+
/** Restore console.log/warn/error to their pre-capture implementations. */
|
|
135051
|
+
releaseConsole() {
|
|
135052
|
+
if (!this.originalConsole) return;
|
|
135053
|
+
console.log = this.originalConsole.log;
|
|
135054
|
+
console.warn = this.originalConsole.warn;
|
|
135055
|
+
console.error = this.originalConsole.error;
|
|
135056
|
+
this.originalConsole = null;
|
|
135057
|
+
}
|
|
134437
135058
|
};
|
|
134438
135059
|
}
|
|
134439
135060
|
});
|
|
@@ -134441,6 +135062,7 @@ var init_dashboard_tui = __esm({
|
|
|
134441
135062
|
// src/commands/dashboard.ts
|
|
134442
135063
|
var dashboard_exports = {};
|
|
134443
135064
|
__export(dashboard_exports, {
|
|
135065
|
+
StreamedLogBuffer: () => StreamedLogBuffer,
|
|
134444
135066
|
promptForPort: () => promptForPort,
|
|
134445
135067
|
runDashboard: () => runDashboard
|
|
134446
135068
|
});
|
|
@@ -134628,6 +135250,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134628
135250
|
});
|
|
134629
135251
|
await tui.start();
|
|
134630
135252
|
logSink.setTUI(tui);
|
|
135253
|
+
logSink.captureConsole();
|
|
134631
135254
|
}
|
|
134632
135255
|
store = new TaskStore(cwd);
|
|
134633
135256
|
await store.init();
|
|
@@ -134710,7 +135333,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134710
135333
|
try {
|
|
134711
135334
|
if (!store) {
|
|
134712
135335
|
taskSummary = "tasks=unavailable (store not initialized)";
|
|
134713
|
-
|
|
135336
|
+
logSink.log(`shutdown requested reason=${reason} pid=${process.pid} ppid=${process.ppid} uptime=${uptimeSeconds}s ${taskSummary}`, "dashboard");
|
|
134714
135337
|
return;
|
|
134715
135338
|
}
|
|
134716
135339
|
const tasks = await store.listTasks({ slim: true, includeArchived: false });
|
|
@@ -134726,8 +135349,9 @@ async function runDashboard(port, opts = {}) {
|
|
|
134726
135349
|
const message = error instanceof Error ? error.message : String(error);
|
|
134727
135350
|
taskSummary = `tasks=unavailable (${message})`;
|
|
134728
135351
|
}
|
|
134729
|
-
|
|
134730
|
-
`
|
|
135352
|
+
logSink.log(
|
|
135353
|
+
`shutdown requested reason=${reason} pid=${process.pid} ppid=${process.ppid} uptime=${uptimeSeconds}s ${taskSummary}`,
|
|
135354
|
+
"dashboard"
|
|
134731
135355
|
);
|
|
134732
135356
|
}
|
|
134733
135357
|
function registerHandler(target, event, handler) {
|
|
@@ -134762,10 +135386,21 @@ async function runDashboard(port, opts = {}) {
|
|
|
134762
135386
|
await store.updateSettings({ enginePaused: true });
|
|
134763
135387
|
logSink.log("Starting in paused mode \u2014 automation disabled", "engine");
|
|
134764
135388
|
}
|
|
134765
|
-
const onMergeImpl = (taskId) =>
|
|
134766
|
-
|
|
134767
|
-
|
|
134768
|
-
|
|
135389
|
+
const onMergeImpl = async (taskId) => {
|
|
135390
|
+
const streamedMergeLog = new StreamedLogBuffer(
|
|
135391
|
+
(line) => logSink.log(line, "merge"),
|
|
135392
|
+
STREAM_LOG_FLUSH_IDLE_MS
|
|
135393
|
+
);
|
|
135394
|
+
try {
|
|
135395
|
+
return await aiMergeTask(store, cwd, taskId, {
|
|
135396
|
+
agentStore,
|
|
135397
|
+
onAgentText: (delta) => streamedMergeLog.push(delta)
|
|
135398
|
+
});
|
|
135399
|
+
} finally {
|
|
135400
|
+
streamedMergeLog.flush();
|
|
135401
|
+
streamedMergeLog.dispose();
|
|
135402
|
+
}
|
|
135403
|
+
};
|
|
134769
135404
|
const onMerge = (taskId) => onMergeImpl(taskId);
|
|
134770
135405
|
const missionAutopilotImpl = new MissionAutopilot(store, store.getMissionStore());
|
|
134771
135406
|
const missionExecutionLoopImpl = new MissionExecutionLoop({
|
|
@@ -134877,6 +135512,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134877
135512
|
tuiRefreshDebounceTimer = null;
|
|
134878
135513
|
}
|
|
134879
135514
|
if (tui) {
|
|
135515
|
+
logSink.releaseConsole();
|
|
134880
135516
|
void tui.stop();
|
|
134881
135517
|
}
|
|
134882
135518
|
for (const { target, event, handler } of handlers) {
|
|
@@ -134910,7 +135546,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134910
135546
|
peerExchangeService.start();
|
|
134911
135547
|
} catch (err) {
|
|
134912
135548
|
const message = err instanceof Error ? err.message : String(err);
|
|
134913
|
-
|
|
135549
|
+
logSink.warn(`Failed to start peer exchange service: ${message}`, "dashboard");
|
|
134914
135550
|
}
|
|
134915
135551
|
centralCoreForMesh = centralCoreForEngine;
|
|
134916
135552
|
let cwdEngine;
|
|
@@ -134960,7 +135596,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134960
135596
|
handleTypes[type] = (handleTypes[type] ?? 0) + 1;
|
|
134961
135597
|
}
|
|
134962
135598
|
const handleSummary = Object.entries(handleTypes).sort((a, b) => b[1] - a[1]).map(([type, count]) => `${type}:${count}`).join(", ");
|
|
134963
|
-
|
|
135599
|
+
logSink.log(`active handles at shutdown: ${handleSummary}`, "dashboard");
|
|
134964
135600
|
} catch {
|
|
134965
135601
|
}
|
|
134966
135602
|
await logShutdownDiagnostics(signal);
|
|
@@ -134972,7 +135608,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134972
135608
|
await peerExchangeService.stop();
|
|
134973
135609
|
} catch (err) {
|
|
134974
135610
|
const message = err instanceof Error ? err.message : String(err);
|
|
134975
|
-
|
|
135611
|
+
logSink.warn(`Failed to stop peer exchange service: ${message}`, "dashboard");
|
|
134976
135612
|
}
|
|
134977
135613
|
}
|
|
134978
135614
|
if (centralCoreForMesh && localNodeIdForMesh) {
|
|
@@ -134980,13 +135616,13 @@ async function runDashboard(port, opts = {}) {
|
|
|
134980
135616
|
centralCoreForMesh.stopDiscovery();
|
|
134981
135617
|
} catch (err) {
|
|
134982
135618
|
const message = err instanceof Error ? err.message : String(err);
|
|
134983
|
-
|
|
135619
|
+
logSink.warn(`Failed to stop mDNS discovery: ${message}`, "dashboard");
|
|
134984
135620
|
}
|
|
134985
135621
|
try {
|
|
134986
135622
|
await centralCoreForMesh.updateNode(localNodeIdForMesh, { status: "offline" });
|
|
134987
135623
|
} catch (err) {
|
|
134988
135624
|
const message = err instanceof Error ? err.message : String(err);
|
|
134989
|
-
|
|
135625
|
+
logSink.warn(`Failed to set local node offline: ${message}`, "dashboard");
|
|
134990
135626
|
}
|
|
134991
135627
|
}
|
|
134992
135628
|
await centralCoreForEngine.close().catch(() => {
|
|
@@ -134997,7 +135633,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
134997
135633
|
registerHandler(process, "SIGINT", () => void shutdown("SIGINT"));
|
|
134998
135634
|
registerHandler(process, "SIGTERM", () => void shutdown("SIGTERM"));
|
|
134999
135635
|
registerHandler(process, "SIGHUP", () => {
|
|
135000
|
-
|
|
135636
|
+
logSink.log("Received SIGHUP (terminal disconnected) \u2014 ignoring", "dashboard");
|
|
135001
135637
|
});
|
|
135002
135638
|
} else {
|
|
135003
135639
|
try {
|
|
@@ -135007,7 +135643,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135007
135643
|
peerExchangeService.start();
|
|
135008
135644
|
} catch (err) {
|
|
135009
135645
|
const message = err instanceof Error ? err.message : String(err);
|
|
135010
|
-
|
|
135646
|
+
logSink.warn(`Failed to initialize mesh networking: ${message}`, "dashboard");
|
|
135011
135647
|
}
|
|
135012
135648
|
try {
|
|
135013
135649
|
heartbeatMonitorImpl = new HeartbeatMonitor({
|
|
@@ -135104,7 +135740,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135104
135740
|
handleTypes[type] = (handleTypes[type] ?? 0) + 1;
|
|
135105
135741
|
}
|
|
135106
135742
|
const handleSummary = Object.entries(handleTypes).sort((a, b) => b[1] - a[1]).map(([type, count]) => `${type}:${count}`).join(", ");
|
|
135107
|
-
|
|
135743
|
+
logSink.log(`active handles at shutdown: ${handleSummary}`, "dashboard");
|
|
135108
135744
|
} catch {
|
|
135109
135745
|
}
|
|
135110
135746
|
await logShutdownDiagnostics(signal);
|
|
@@ -135117,7 +135753,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135117
135753
|
await peerExchangeService.stop();
|
|
135118
135754
|
} catch (err) {
|
|
135119
135755
|
const message = err instanceof Error ? err.message : String(err);
|
|
135120
|
-
|
|
135756
|
+
logSink.warn(`Failed to stop peer exchange service: ${message}`, "dashboard");
|
|
135121
135757
|
}
|
|
135122
135758
|
}
|
|
135123
135759
|
if (centralCoreForMesh && localNodeIdForMesh) {
|
|
@@ -135125,13 +135761,13 @@ async function runDashboard(port, opts = {}) {
|
|
|
135125
135761
|
centralCoreForMesh.stopDiscovery();
|
|
135126
135762
|
} catch (err) {
|
|
135127
135763
|
const message = err instanceof Error ? err.message : String(err);
|
|
135128
|
-
|
|
135764
|
+
logSink.warn(`Failed to stop mDNS discovery: ${message}`, "dashboard");
|
|
135129
135765
|
}
|
|
135130
135766
|
try {
|
|
135131
135767
|
await centralCoreForMesh.updateNode(localNodeIdForMesh, { status: "offline" });
|
|
135132
135768
|
} catch (err) {
|
|
135133
135769
|
const message = err instanceof Error ? err.message : String(err);
|
|
135134
|
-
|
|
135770
|
+
logSink.warn(`Failed to set local node offline: ${message}`, "dashboard");
|
|
135135
135771
|
}
|
|
135136
135772
|
}
|
|
135137
135773
|
if (centralCoreForMesh) {
|
|
@@ -135144,7 +135780,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135144
135780
|
registerHandler(process, "SIGINT", () => void devShutdown("SIGINT"));
|
|
135145
135781
|
registerHandler(process, "SIGTERM", () => void devShutdown("SIGTERM"));
|
|
135146
135782
|
registerHandler(process, "SIGHUP", () => {
|
|
135147
|
-
|
|
135783
|
+
logSink.log("Received SIGHUP (terminal disconnected) \u2014 ignoring", "dashboard");
|
|
135148
135784
|
});
|
|
135149
135785
|
}
|
|
135150
135786
|
const server = app.listen(selectedPort, selectedHost);
|
|
@@ -135152,14 +135788,14 @@ async function runDashboard(port, opts = {}) {
|
|
|
135152
135788
|
if (err.code === "EADDRINUSE") {
|
|
135153
135789
|
server.listen(0, selectedHost);
|
|
135154
135790
|
} else {
|
|
135155
|
-
|
|
135791
|
+
logSink.error(`Failed to start server: ${err.message}`, "dashboard");
|
|
135156
135792
|
process.exit(1);
|
|
135157
135793
|
}
|
|
135158
135794
|
});
|
|
135159
135795
|
server.on("listening", async () => {
|
|
135160
135796
|
const actualPort = server.address().port;
|
|
135161
135797
|
if (actualPort !== selectedPort) {
|
|
135162
|
-
|
|
135798
|
+
logSink.warn(`Port ${selectedPort} in use, using ${actualPort} instead`, "dashboard");
|
|
135163
135799
|
}
|
|
135164
135800
|
if (centralCoreForMesh) {
|
|
135165
135801
|
try {
|
|
@@ -135172,7 +135808,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135172
135808
|
});
|
|
135173
135809
|
} catch (err) {
|
|
135174
135810
|
const message = err instanceof Error ? err.message : String(err);
|
|
135175
|
-
|
|
135811
|
+
logSink.warn(`Failed to start mDNS discovery: ${message}`, "dashboard");
|
|
135176
135812
|
}
|
|
135177
135813
|
}
|
|
135178
135814
|
if (centralCoreForMesh) {
|
|
@@ -135185,7 +135821,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135185
135821
|
}
|
|
135186
135822
|
} catch (err) {
|
|
135187
135823
|
const message = err instanceof Error ? err.message : String(err);
|
|
135188
|
-
|
|
135824
|
+
logSink.warn(`Failed to set local node online: ${message}`, "dashboard");
|
|
135189
135825
|
}
|
|
135190
135826
|
}
|
|
135191
135827
|
const displayHost = selectedHost === "0.0.0.0" || selectedHost === "::" ? selectedHost : "localhost";
|
|
@@ -135277,7 +135913,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
135277
135913
|
});
|
|
135278
135914
|
return { dispose };
|
|
135279
135915
|
}
|
|
135280
|
-
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck;
|
|
135916
|
+
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer;
|
|
135281
135917
|
var init_dashboard = __esm({
|
|
135282
135918
|
"src/commands/dashboard.ts"() {
|
|
135283
135919
|
"use strict";
|
|
@@ -135297,6 +135933,60 @@ var init_dashboard = __esm({
|
|
|
135297
135933
|
diagnosticStartTime = 0;
|
|
135298
135934
|
diagnosticDbHealthCheck = null;
|
|
135299
135935
|
diagnosticStoreListenerCheck = null;
|
|
135936
|
+
STREAM_LOG_FLUSH_IDLE_MS = 100;
|
|
135937
|
+
StreamedLogBuffer = class {
|
|
135938
|
+
constructor(emitLine, flushIdleMs = STREAM_LOG_FLUSH_IDLE_MS) {
|
|
135939
|
+
this.emitLine = emitLine;
|
|
135940
|
+
this.flushIdleMs = flushIdleMs;
|
|
135941
|
+
}
|
|
135942
|
+
pending = "";
|
|
135943
|
+
flushTimer = null;
|
|
135944
|
+
push(delta) {
|
|
135945
|
+
if (!delta) return;
|
|
135946
|
+
this.pending += delta;
|
|
135947
|
+
this.flushCompletedLines();
|
|
135948
|
+
this.scheduleFlush();
|
|
135949
|
+
}
|
|
135950
|
+
flush() {
|
|
135951
|
+
this.clearFlushTimer();
|
|
135952
|
+
const trailing = this.pending.trim();
|
|
135953
|
+
if (trailing.length > 0) {
|
|
135954
|
+
this.emitLine(trailing);
|
|
135955
|
+
}
|
|
135956
|
+
this.pending = "";
|
|
135957
|
+
}
|
|
135958
|
+
dispose() {
|
|
135959
|
+
this.clearFlushTimer();
|
|
135960
|
+
this.pending = "";
|
|
135961
|
+
}
|
|
135962
|
+
flushCompletedLines() {
|
|
135963
|
+
if (!this.pending.includes("\n")) {
|
|
135964
|
+
return;
|
|
135965
|
+
}
|
|
135966
|
+
const splitLines = this.pending.split(/\r?\n/);
|
|
135967
|
+
const completeLines = splitLines.slice(0, -1);
|
|
135968
|
+
this.pending = splitLines[splitLines.length - 1] ?? "";
|
|
135969
|
+
for (const line of completeLines) {
|
|
135970
|
+
const normalized = line.trim();
|
|
135971
|
+
if (normalized.length > 0) {
|
|
135972
|
+
this.emitLine(normalized);
|
|
135973
|
+
}
|
|
135974
|
+
}
|
|
135975
|
+
}
|
|
135976
|
+
scheduleFlush() {
|
|
135977
|
+
this.clearFlushTimer();
|
|
135978
|
+
this.flushTimer = setTimeout(() => {
|
|
135979
|
+
this.flush();
|
|
135980
|
+
}, this.flushIdleMs);
|
|
135981
|
+
this.flushTimer.unref?.();
|
|
135982
|
+
}
|
|
135983
|
+
clearFlushTimer() {
|
|
135984
|
+
if (this.flushTimer) {
|
|
135985
|
+
clearTimeout(this.flushTimer);
|
|
135986
|
+
this.flushTimer = null;
|
|
135987
|
+
}
|
|
135988
|
+
}
|
|
135989
|
+
};
|
|
135300
135990
|
}
|
|
135301
135991
|
});
|
|
135302
135992
|
|