@runfusion/fusion 0.27.0 → 0.27.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 +1865 -576
- package/dist/client/assets/{AgentDetailView-DwLmRXTY.js → AgentDetailView-shgiiUb4.js} +1 -1
- package/dist/client/assets/{AgentsView-CV3vm7Qk.css → AgentsView-B3ADnF0D.css} +1 -1
- package/dist/client/assets/{AgentsView-D-N6aA0P.js → AgentsView-CpwqOVDz.js} +8 -8
- package/dist/client/assets/ChatView-DyRBOIKL.js +1 -0
- package/dist/client/assets/{DevServerView-BiA1nYtt.js → DevServerView-Cdelj9-m.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DvBviDG6.js → DirectoryPicker-C0kmRv0u.js} +1 -1
- package/dist/client/assets/{DocumentsView-BWXOxpuq.js → DocumentsView-B94U9ijs.js} +1 -1
- package/dist/client/assets/{EvalsView-CJFbtL7i.js → EvalsView-O_4YWy--.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-DuGIPd0B.js → ExperimentalAgentOnboardingModal-CkEiF85-.js} +1 -1
- package/dist/client/assets/{InsightsView-BBpRiolN.js → InsightsView-D-Qe0tRr.js} +1 -1
- package/dist/client/assets/{MemoryView-48LuNkKk.js → MemoryView-CoRUmRvb.js} +1 -1
- package/dist/client/assets/{NodesView-CGQWSNZM.js → NodesView-DQzXjcLc.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-i-7UL2oh.js → PiExtensionsManager-Dn1LmFbq.js} +1 -1
- package/dist/client/assets/{PluginManager-DoSAykD6.js → PluginManager-Y0fs-6No.js} +1 -1
- package/dist/client/assets/{ResearchView-XZuRtOxE.js → ResearchView-CjOxKhdS.js} +1 -1
- package/dist/client/assets/{SettingsModal-CmeF8CN4.js → SettingsModal-Bg1-3JO_.js} +1 -1
- package/dist/client/assets/SettingsModal-DL7tjJQa.js +31 -0
- package/dist/client/assets/{SetupWizardModal-CgtvpMX9.js → SetupWizardModal-DuzYPbuJ.js} +1 -1
- package/dist/client/assets/{SkillsView-DErYRumF.js → SkillsView-BIFoVNUf.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-QJrNS4Vg.js → StashRecoveryView-C52KsV7f.js} +1 -1
- package/dist/client/assets/{TodoView-BD9NRwq0.js → TodoView-sS_mT0Y7.js} +1 -1
- package/dist/client/assets/{dashboard-view-Ws9_ZnKu.js → dashboard-view-MB-86hAu.js} +1 -1
- package/dist/client/assets/{folder-open-CHSlllzf.js → folder-open-B9cwJ-OX.js} +1 -1
- package/dist/client/assets/{index-bEwSVl7B.js → index-BOjPRqEk.js} +161 -161
- package/dist/client/assets/index-BmSEq8Rb.css +1 -0
- package/dist/client/assets/{star-BgVwWAPz.js → star-BDn04UYV.js} +1 -1
- package/dist/client/assets/{upload-CAzycxr9.js → upload-zdPPycKQ.js} +1 -1
- package/dist/client/assets/{users-CZnxCCCJ.js → users-CPYZjK2g.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +1721 -532
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/skill/fusion/SKILL.md +1 -1
- package/skill/fusion/references/engine-tools.md +1 -1
- package/skill/fusion/references/extension-tools.md +1 -0
- package/skill/fusion/workflows/task-management.md +3 -1
- package/dist/client/assets/ChatView-DnCdKu8Z.js +0 -1
- package/dist/client/assets/SettingsModal-DBcjf9Bu.js +0 -31
- package/dist/client/assets/index-DCovGm5b.css +0 -1
package/dist/extension.js
CHANGED
|
@@ -64,6 +64,7 @@ var init_settings_schema = __esm({
|
|
|
64
64
|
ntfyEnabled: false,
|
|
65
65
|
ntfyTopic: void 0,
|
|
66
66
|
ntfyBaseUrl: void 0,
|
|
67
|
+
ntfyAccessToken: void 0,
|
|
67
68
|
ntfyEvents: [
|
|
68
69
|
"in-review",
|
|
69
70
|
"merged",
|
|
@@ -247,6 +248,7 @@ var init_settings_schema = __esm({
|
|
|
247
248
|
worktreeRebaseLocalBase: true,
|
|
248
249
|
mergeConflictStrategy: "smart-prefer-main",
|
|
249
250
|
workflowStepTimeoutMs: 36e4,
|
|
251
|
+
workflowRevisionForkOnScopeMismatch: true,
|
|
250
252
|
strictScopeEnforcement: false,
|
|
251
253
|
buildRetryCount: 0,
|
|
252
254
|
verificationFixRetries: 3,
|
|
@@ -3357,9 +3359,9 @@ var init_sqlite_adapter = __esm({
|
|
|
3357
3359
|
|
|
3358
3360
|
// ../core/src/db.ts
|
|
3359
3361
|
import { isAbsolute, join as join2 } from "node:path";
|
|
3360
|
-
import { mkdirSync, existsSync } from "node:fs";
|
|
3362
|
+
import { mkdirSync, existsSync, statSync } from "node:fs";
|
|
3361
3363
|
import { spawnSync } from "node:child_process";
|
|
3362
|
-
import { randomUUID } from "node:crypto";
|
|
3364
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
3363
3365
|
function toJson(value) {
|
|
3364
3366
|
if (value === void 0 || value === null) return "[]";
|
|
3365
3367
|
if (Array.isArray(value) && value.length === 0) return "[]";
|
|
@@ -3379,6 +3381,15 @@ function fromJson(json) {
|
|
|
3379
3381
|
return void 0;
|
|
3380
3382
|
}
|
|
3381
3383
|
}
|
|
3384
|
+
function isSqliteLockError(error) {
|
|
3385
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3386
|
+
return /SQLITE_(?:BUSY|LOCKED)|database is locked|database table is locked/i.test(message);
|
|
3387
|
+
}
|
|
3388
|
+
function sleepSync(ms) {
|
|
3389
|
+
if (ms <= 0) return;
|
|
3390
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
3391
|
+
Atomics.wait(signal, 0, 0, ms);
|
|
3392
|
+
}
|
|
3382
3393
|
function probeFts5(db) {
|
|
3383
3394
|
if (process.env.FUSION_DISABLE_FTS5 === "1" || process.env.FUSION_DISABLE_FTS5 === "true") {
|
|
3384
3395
|
return false;
|
|
@@ -3478,15 +3489,28 @@ function getSchemaCompatibilityTableSchemas() {
|
|
|
3478
3489
|
}
|
|
3479
3490
|
return tables;
|
|
3480
3491
|
}
|
|
3492
|
+
function canonicalizeSchemaTables(tables) {
|
|
3493
|
+
return Object.fromEntries(
|
|
3494
|
+
[...tables.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([tableName, columns]) => [
|
|
3495
|
+
tableName,
|
|
3496
|
+
Object.fromEntries(
|
|
3497
|
+
[...columns.entries()].sort(([left], [right]) => left.localeCompare(right))
|
|
3498
|
+
)
|
|
3499
|
+
])
|
|
3500
|
+
);
|
|
3501
|
+
}
|
|
3481
3502
|
function createDatabase(fusionDir, options) {
|
|
3482
3503
|
return new Database(fusionDir, options);
|
|
3483
3504
|
}
|
|
3484
|
-
var SCHEMA_VERSION, SCHEMA_SQL, TABLE_LEVEL_CONSTRAINT_PREFIXES, SCHEMA_TABLE_SCHEMAS, MIGRATION_ONLY_TABLE_SCHEMAS, Database;
|
|
3505
|
+
var DEFAULT_SQLITE_BUSY_TIMEOUT_MS, DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS, DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS, SCHEMA_VERSION, SCHEMA_SQL, TABLE_LEVEL_CONSTRAINT_PREFIXES, SCHEMA_TABLE_SCHEMAS, MIGRATION_ONLY_TABLE_SCHEMAS, SCHEMA_COMPAT_FINGERPRINT, Database;
|
|
3485
3506
|
var init_db = __esm({
|
|
3486
3507
|
"../core/src/db.ts"() {
|
|
3487
3508
|
"use strict";
|
|
3488
3509
|
init_sqlite_adapter();
|
|
3489
3510
|
init_types();
|
|
3511
|
+
DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
|
|
3512
|
+
DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
|
|
3513
|
+
DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
|
|
3490
3514
|
SCHEMA_VERSION = 72;
|
|
3491
3515
|
SCHEMA_SQL = `
|
|
3492
3516
|
-- Tasks table with JSON columns for nested data
|
|
@@ -3578,6 +3602,9 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
3578
3602
|
);
|
|
3579
3603
|
|
|
3580
3604
|
-- Config table (single row with project settings)
|
|
3605
|
+
-- nextId is a deprecated legacy allocator counter retained read-only for one
|
|
3606
|
+
-- release so older databases/config consumers can still load it during the
|
|
3607
|
+
-- distributed_task_id_state transition.
|
|
3581
3608
|
CREATE TABLE IF NOT EXISTS config (
|
|
3582
3609
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
3583
3610
|
nextId INTEGER DEFAULT 1,
|
|
@@ -4352,6 +4379,20 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
4352
4379
|
createdAt: "TEXT NOT NULL"
|
|
4353
4380
|
}
|
|
4354
4381
|
};
|
|
4382
|
+
SCHEMA_COMPAT_FINGERPRINT = createHash2("sha1").update(
|
|
4383
|
+
JSON.stringify({
|
|
4384
|
+
schemaVersion: SCHEMA_VERSION,
|
|
4385
|
+
schemaSqlTables: canonicalizeSchemaTables(SCHEMA_TABLE_SCHEMAS),
|
|
4386
|
+
migrationOnlyTableSchemas: Object.fromEntries(
|
|
4387
|
+
Object.entries(MIGRATION_ONLY_TABLE_SCHEMAS).sort(([left], [right]) => left.localeCompare(right)).map(([tableName, columns]) => [
|
|
4388
|
+
tableName,
|
|
4389
|
+
Object.fromEntries(
|
|
4390
|
+
Object.entries(columns).sort(([left], [right]) => left.localeCompare(right))
|
|
4391
|
+
)
|
|
4392
|
+
])
|
|
4393
|
+
)
|
|
4394
|
+
})
|
|
4395
|
+
).digest("hex");
|
|
4355
4396
|
Database = class _Database {
|
|
4356
4397
|
static sharedIntegrityChecks = /* @__PURE__ */ new Map();
|
|
4357
4398
|
db;
|
|
@@ -4369,10 +4410,16 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
4369
4410
|
_fts5Available;
|
|
4370
4411
|
integrityCheckScheduled = false;
|
|
4371
4412
|
closed = false;
|
|
4413
|
+
busyTimeoutMs;
|
|
4414
|
+
lockRecoveryWindowMs;
|
|
4415
|
+
lockRecoveryDelayMs;
|
|
4372
4416
|
constructor(fusionDir, options) {
|
|
4373
4417
|
const inMemory = options?.inMemory === true;
|
|
4374
4418
|
this.inMemory = inMemory;
|
|
4375
4419
|
this.dbPath = inMemory ? ":memory:" : join2(fusionDir, "fusion.db");
|
|
4420
|
+
this.busyTimeoutMs = Math.max(0, options?.busyTimeoutMs ?? DEFAULT_SQLITE_BUSY_TIMEOUT_MS);
|
|
4421
|
+
this.lockRecoveryWindowMs = Math.max(0, options?.lockRecoveryWindowMs ?? DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS);
|
|
4422
|
+
this.lockRecoveryDelayMs = Math.max(1, options?.lockRecoveryDelayMs ?? DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS);
|
|
4376
4423
|
if (!inMemory && !isAbsolute(fusionDir)) {
|
|
4377
4424
|
throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
|
|
4378
4425
|
}
|
|
@@ -4392,13 +4439,13 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4392
4439
|
throw new Error(`Failed to open Fusion database at ${this.dbPath}: ${message}`);
|
|
4393
4440
|
}
|
|
4394
4441
|
if (!inMemory) {
|
|
4395
|
-
this.db.exec(
|
|
4442
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
4396
4443
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
4397
4444
|
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
4398
4445
|
this.db.exec("PRAGMA wal_autocheckpoint = 100");
|
|
4399
4446
|
this.db.exec("PRAGMA journal_size_limit = 4194304");
|
|
4400
4447
|
} else {
|
|
4401
|
-
this.db.exec(
|
|
4448
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
4402
4449
|
}
|
|
4403
4450
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
4404
4451
|
this._fts5Available = probeFts5(this.db);
|
|
@@ -4509,6 +4556,44 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4509
4556
|
});
|
|
4510
4557
|
return rebuilt.status === 0;
|
|
4511
4558
|
}
|
|
4559
|
+
/**
|
|
4560
|
+
* Run WAL truncation + VACUUM and report compaction stats.
|
|
4561
|
+
*
|
|
4562
|
+
* In-memory databases no-op and return zeroed stats. Disk-backed databases
|
|
4563
|
+
* sample file size before/after compaction, run `wal_checkpoint(TRUNCATE)`,
|
|
4564
|
+
* and then run `VACUUM` while the connection is in EXCLUSIVE locking mode to
|
|
4565
|
+
* prevent concurrent writes from other connections during maintenance.
|
|
4566
|
+
*/
|
|
4567
|
+
vacuum() {
|
|
4568
|
+
if (this.inMemory) {
|
|
4569
|
+
return { beforeBytes: 0, afterBytes: 0, durationMs: 0 };
|
|
4570
|
+
}
|
|
4571
|
+
const beforeBytes = existsSync(this.dbPath) ? statSync(this.dbPath).size : 0;
|
|
4572
|
+
const startedAt = Date.now();
|
|
4573
|
+
this.db.exec("PRAGMA locking_mode=EXCLUSIVE");
|
|
4574
|
+
try {
|
|
4575
|
+
try {
|
|
4576
|
+
this.walCheckpoint("TRUNCATE");
|
|
4577
|
+
} catch (error) {
|
|
4578
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4579
|
+
throw new Error(`Database vacuum maintenance failed during WAL checkpoint (dbPath=${this.dbPath}): ${message}`);
|
|
4580
|
+
}
|
|
4581
|
+
try {
|
|
4582
|
+
this.db.exec("VACUUM");
|
|
4583
|
+
} catch (error) {
|
|
4584
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4585
|
+
throw new Error(`Database vacuum maintenance failed during VACUUM (dbPath=${this.dbPath}): ${message}`);
|
|
4586
|
+
}
|
|
4587
|
+
const afterBytes = existsSync(this.dbPath) ? statSync(this.dbPath).size : 0;
|
|
4588
|
+
return {
|
|
4589
|
+
beforeBytes,
|
|
4590
|
+
afterBytes,
|
|
4591
|
+
durationMs: Date.now() - startedAt
|
|
4592
|
+
};
|
|
4593
|
+
} finally {
|
|
4594
|
+
this.db.exec("PRAGMA locking_mode=NORMAL");
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4512
4597
|
/**
|
|
4513
4598
|
* Initialize the database: create tables if they don't exist
|
|
4514
4599
|
* and seed meta values.
|
|
@@ -4523,10 +4608,20 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4523
4608
|
`INSERT OR IGNORE INTO __meta (key, value) VALUES ('lastModified', '${Date.now()}')`
|
|
4524
4609
|
);
|
|
4525
4610
|
this.migrate();
|
|
4526
|
-
this.
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4611
|
+
const schemaCompatFingerprint = this.getMetaValue("schemaCompatFingerprint");
|
|
4612
|
+
const skipColumnReconciliation = schemaCompatFingerprint === SCHEMA_COMPAT_FINGERPRINT;
|
|
4613
|
+
const tableColumnsCache = skipColumnReconciliation ? void 0 : /* @__PURE__ */ new Map();
|
|
4614
|
+
const compatibilityOptions = {
|
|
4615
|
+
tableColumnsCache,
|
|
4616
|
+
skipColumnReconciliation
|
|
4617
|
+
};
|
|
4618
|
+
this.ensureSchemaCompatibility(compatibilityOptions);
|
|
4619
|
+
this.ensureRoutinesSchemaCompatibility(compatibilityOptions);
|
|
4620
|
+
this.ensureInsightRunsSchemaCompatibility(compatibilityOptions);
|
|
4621
|
+
this.ensureEvalTaskResultsSchemaCompatibility(compatibilityOptions);
|
|
4622
|
+
if (!skipColumnReconciliation) {
|
|
4623
|
+
this.setMetaValue("schemaCompatFingerprint", SCHEMA_COMPAT_FINGERPRINT);
|
|
4624
|
+
}
|
|
4530
4625
|
const configNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
4531
4626
|
this.db.exec(
|
|
4532
4627
|
`INSERT OR IGNORE INTO config (id, nextId, nextWorkflowStepId, settings, workflowSteps, updatedAt) VALUES (1, 1, 1, '${JSON.stringify(DEFAULT_PROJECT_SETTINGS)}', '[]', '${configNow}')`
|
|
@@ -4544,20 +4639,27 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4544
4639
|
* re-run even if a previous migration partially applied.
|
|
4545
4640
|
*/
|
|
4546
4641
|
/**
|
|
4547
|
-
*
|
|
4642
|
+
* Reconciles additive columns for every known project DB table unless the
|
|
4643
|
+
* persisted `schemaCompatFingerprint` already matches SCHEMA_COMPAT_FINGERPRINT.
|
|
4548
4644
|
*
|
|
4549
|
-
*
|
|
4550
|
-
*
|
|
4551
|
-
*
|
|
4552
|
-
*
|
|
4553
|
-
*
|
|
4645
|
+
* The fingerprint is invalidated automatically by SCHEMA_VERSION changes and by
|
|
4646
|
+
* edits to the canonicalized column declarations from SCHEMA_SQL or
|
|
4647
|
+
* MIGRATION_ONLY_TABLE_SCHEMAS. When it is absent or stale, this method runs the
|
|
4648
|
+
* full FN-3879/FN-3887/FN-3898 safety pass so every declared column exists on
|
|
4649
|
+
* every live table after init() returns.
|
|
4554
4650
|
*/
|
|
4555
|
-
ensureSchemaCompatibility() {
|
|
4651
|
+
ensureSchemaCompatibility(options = {}) {
|
|
4652
|
+
if (options.skipColumnReconciliation) {
|
|
4653
|
+
return;
|
|
4654
|
+
}
|
|
4556
4655
|
const knownTableSchemas = getSchemaCompatibilityTableSchemas();
|
|
4656
|
+
const tableColumnsCache = options.tableColumnsCache;
|
|
4557
4657
|
for (const [tableName, columns] of knownTableSchemas) {
|
|
4558
4658
|
if (!this.hasTable(tableName)) continue;
|
|
4659
|
+
const cachedColumns = this.getTableColumns(tableName, true, tableColumnsCache);
|
|
4559
4660
|
for (const [columnName, columnDefinition] of columns) {
|
|
4560
|
-
|
|
4661
|
+
if (cachedColumns.has(columnName)) continue;
|
|
4662
|
+
this.addColumnIfMissingCached(tableName, columnName, columnDefinition, tableColumnsCache);
|
|
4561
4663
|
}
|
|
4562
4664
|
}
|
|
4563
4665
|
}
|
|
@@ -4568,10 +4670,14 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4568
4670
|
* agent IDs from earlier table definitions. `RoutineStore.rowToRoutine()` and
|
|
4569
4671
|
* backup routine sync expect a safe string value, so normalize to ''.
|
|
4570
4672
|
*/
|
|
4571
|
-
ensureRoutinesSchemaCompatibility() {
|
|
4673
|
+
ensureRoutinesSchemaCompatibility(options = {}) {
|
|
4572
4674
|
if (!this.hasTable("routines")) {
|
|
4573
4675
|
return;
|
|
4574
4676
|
}
|
|
4677
|
+
if (!options.skipColumnReconciliation) {
|
|
4678
|
+
this.addColumnIfMissingCached("routines", "agentId", "TEXT DEFAULT ''", options.tableColumnsCache);
|
|
4679
|
+
this.addColumnIfMissingCached("routines", "scope", "TEXT DEFAULT 'project'", options.tableColumnsCache);
|
|
4680
|
+
}
|
|
4575
4681
|
this.db.exec("UPDATE routines SET agentId = '' WHERE agentId IS NULL");
|
|
4576
4682
|
this.db.exec("UPDATE routines SET scope = 'project' WHERE scope IS NULL OR TRIM(scope) = ''");
|
|
4577
4683
|
this.db.exec("CREATE INDEX IF NOT EXISTS idxRoutinesNextRunAt ON routines(nextRunAt)");
|
|
@@ -4585,13 +4691,17 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4585
4691
|
* remains focused on index creation that should run after the generic column
|
|
4586
4692
|
* backfill pass.
|
|
4587
4693
|
*/
|
|
4588
|
-
ensureInsightRunsSchemaCompatibility() {
|
|
4694
|
+
ensureInsightRunsSchemaCompatibility(options = {}) {
|
|
4589
4695
|
if (!this.hasTable("project_insight_runs")) {
|
|
4590
4696
|
return;
|
|
4591
4697
|
}
|
|
4698
|
+
if (!options.skipColumnReconciliation) {
|
|
4699
|
+
this.addColumnIfMissingCached("project_insight_runs", "lifecycle", "TEXT", options.tableColumnsCache);
|
|
4700
|
+
this.addColumnIfMissingCached("project_insight_runs", "cancelledAt", "TEXT", options.tableColumnsCache);
|
|
4701
|
+
}
|
|
4592
4702
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunsProjectTriggerStatus ON project_insight_runs(projectId, trigger, status)`);
|
|
4593
4703
|
}
|
|
4594
|
-
ensureEvalTaskResultsSchemaCompatibility() {
|
|
4704
|
+
ensureEvalTaskResultsSchemaCompatibility(_options = {}) {
|
|
4595
4705
|
if (!this.hasTable("eval_task_results")) {
|
|
4596
4706
|
return;
|
|
4597
4707
|
}
|
|
@@ -5933,12 +6043,26 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
5933
6043
|
const lower = message.toLowerCase();
|
|
5934
6044
|
return lower.includes("corruption found reading blob") || lower.includes("database disk image is malformed") || lower.includes("fts5") && lower.includes("corrupt");
|
|
5935
6045
|
}
|
|
6046
|
+
/**
|
|
6047
|
+
* Read the declared columns for a table.
|
|
6048
|
+
*/
|
|
6049
|
+
getTableColumns(table, useCache = false, cache) {
|
|
6050
|
+
if (useCache && cache?.has(table)) {
|
|
6051
|
+
return cache.get(table) ?? /* @__PURE__ */ new Set();
|
|
6052
|
+
}
|
|
6053
|
+
const columns = new Set(
|
|
6054
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name)
|
|
6055
|
+
);
|
|
6056
|
+
if (useCache && cache) {
|
|
6057
|
+
cache.set(table, columns);
|
|
6058
|
+
}
|
|
6059
|
+
return columns;
|
|
6060
|
+
}
|
|
5936
6061
|
/**
|
|
5937
6062
|
* Check whether a table has a given column.
|
|
5938
6063
|
*/
|
|
5939
6064
|
hasColumn(table, column) {
|
|
5940
|
-
|
|
5941
|
-
return cols.some((c) => c.name === column);
|
|
6065
|
+
return this.getTableColumns(table).has(column);
|
|
5942
6066
|
}
|
|
5943
6067
|
/**
|
|
5944
6068
|
* Add a column to a table if it does not already exist.
|
|
@@ -5948,6 +6072,20 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
5948
6072
|
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
5949
6073
|
}
|
|
5950
6074
|
}
|
|
6075
|
+
/**
|
|
6076
|
+
* Add a column using a per-init table-info cache when available.
|
|
6077
|
+
*/
|
|
6078
|
+
addColumnIfMissingCached(table, column, definition, cache) {
|
|
6079
|
+
const columns = this.getTableColumns(table, Boolean(cache), cache);
|
|
6080
|
+
if (columns.has(column)) {
|
|
6081
|
+
return;
|
|
6082
|
+
}
|
|
6083
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
6084
|
+
columns.add(column);
|
|
6085
|
+
if (cache) {
|
|
6086
|
+
cache.set(table, columns);
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
5951
6089
|
/**
|
|
5952
6090
|
* Normalize legacy steering comments into the unified comments field exactly once.
|
|
5953
6091
|
*
|
|
@@ -6048,25 +6186,67 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6048
6186
|
this.integrityCheckPending = false;
|
|
6049
6187
|
this.db.close();
|
|
6050
6188
|
}
|
|
6189
|
+
runWithLockRecovery(action, fn) {
|
|
6190
|
+
const deadline = Date.now() + this.lockRecoveryWindowMs;
|
|
6191
|
+
let attempt = 0;
|
|
6192
|
+
while (true) {
|
|
6193
|
+
try {
|
|
6194
|
+
fn();
|
|
6195
|
+
return;
|
|
6196
|
+
} catch (error) {
|
|
6197
|
+
if (!isSqliteLockError(error)) {
|
|
6198
|
+
throw error;
|
|
6199
|
+
}
|
|
6200
|
+
if (Date.now() >= deadline) {
|
|
6201
|
+
throw new Error(
|
|
6202
|
+
`SQLite ${action} failed after ${attempt + 1} attempt${attempt === 0 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`
|
|
6203
|
+
);
|
|
6204
|
+
}
|
|
6205
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
6206
|
+
const delayMs = Math.min(this.lockRecoveryDelayMs * Math.max(1, attempt + 1), remainingMs);
|
|
6207
|
+
sleepSync(delayMs);
|
|
6208
|
+
attempt += 1;
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6051
6212
|
/**
|
|
6052
6213
|
* Execute a function inside a SQLite transaction.
|
|
6053
6214
|
* Supports nested calls via SAVEPOINTs.
|
|
6054
6215
|
* If the function throws, the transaction/savepoint is rolled back.
|
|
6055
6216
|
* If the function returns normally, the transaction/savepoint is committed.
|
|
6217
|
+
*
|
|
6218
|
+
* Outermost transactions default to `BEGIN` (DEFERRED) so read-only callers
|
|
6219
|
+
* avoid taking a writer lock until they actually mutate state.
|
|
6220
|
+
* Use `transactionImmediate()` for write-heavy paths that should acquire the
|
|
6221
|
+
* RESERVED lock before user code runs and fail/retry before the callback executes.
|
|
6056
6222
|
*/
|
|
6057
|
-
transaction(fn) {
|
|
6223
|
+
transaction(fn, options) {
|
|
6058
6224
|
const depth = this.transactionDepth++;
|
|
6059
6225
|
const isOutermost = depth === 0;
|
|
6060
6226
|
const savepointName = `sp_${depth}`;
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6227
|
+
const mode = options?.mode ?? "deferred";
|
|
6228
|
+
try {
|
|
6229
|
+
if (isOutermost) {
|
|
6230
|
+
if (mode === "immediate") {
|
|
6231
|
+
this.runWithLockRecovery("BEGIN IMMEDIATE", () => {
|
|
6232
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
6233
|
+
});
|
|
6234
|
+
} else {
|
|
6235
|
+
this.db.exec("BEGIN");
|
|
6236
|
+
}
|
|
6237
|
+
} else {
|
|
6238
|
+
this.db.exec(`SAVEPOINT ${savepointName}`);
|
|
6239
|
+
}
|
|
6240
|
+
} catch (error) {
|
|
6241
|
+
this.transactionDepth--;
|
|
6242
|
+
throw error;
|
|
6065
6243
|
}
|
|
6066
6244
|
try {
|
|
6067
6245
|
const result = fn();
|
|
6068
6246
|
if (isOutermost) {
|
|
6069
|
-
this.
|
|
6247
|
+
this.runWithLockRecovery("COMMIT", () => {
|
|
6248
|
+
this.db.exec("COMMIT");
|
|
6249
|
+
});
|
|
6070
6250
|
} else {
|
|
6071
6251
|
this.db.exec(`RELEASE ${savepointName}`);
|
|
6072
6252
|
}
|
|
@@ -6083,6 +6263,9 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6083
6263
|
this.transactionDepth--;
|
|
6084
6264
|
}
|
|
6085
6265
|
}
|
|
6266
|
+
transactionImmediate(fn) {
|
|
6267
|
+
return this.transaction(fn, { mode: "immediate" });
|
|
6268
|
+
}
|
|
6086
6269
|
/**
|
|
6087
6270
|
* Execute plugin-provided schema initialization hooks.
|
|
6088
6271
|
*
|
|
@@ -6118,14 +6301,24 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6118
6301
|
exec(sql) {
|
|
6119
6302
|
this.db.exec(sql);
|
|
6120
6303
|
}
|
|
6304
|
+
getMetaValue(key) {
|
|
6305
|
+
const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(key);
|
|
6306
|
+
return row?.value;
|
|
6307
|
+
}
|
|
6308
|
+
/**
|
|
6309
|
+
* Persist a __meta value idempotently.
|
|
6310
|
+
*/
|
|
6311
|
+
setMetaValue(key, value) {
|
|
6312
|
+
this.db.prepare("INSERT OR REPLACE INTO __meta (key, value) VALUES (?, ?)").run(key, value);
|
|
6313
|
+
}
|
|
6121
6314
|
/**
|
|
6122
6315
|
* Get the last modification timestamp (epoch ms).
|
|
6123
6316
|
* Returns 0 if the value is not set.
|
|
6124
6317
|
*/
|
|
6125
6318
|
getLastModified() {
|
|
6126
|
-
const
|
|
6127
|
-
if (!
|
|
6128
|
-
return parseInt(
|
|
6319
|
+
const value = this.getMetaValue("lastModified");
|
|
6320
|
+
if (!value) return 0;
|
|
6321
|
+
return parseInt(value, 10) || 0;
|
|
6129
6322
|
}
|
|
6130
6323
|
/**
|
|
6131
6324
|
* Update the last modification timestamp to the current time.
|
|
@@ -6144,9 +6337,9 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6144
6337
|
* Get the schema version number.
|
|
6145
6338
|
*/
|
|
6146
6339
|
getSchemaVersion() {
|
|
6147
|
-
const
|
|
6148
|
-
if (!
|
|
6149
|
-
return parseInt(
|
|
6340
|
+
const value = this.getMetaValue("schemaVersion");
|
|
6341
|
+
if (!value) return 0;
|
|
6342
|
+
return parseInt(value, 10) || 0;
|
|
6150
6343
|
}
|
|
6151
6344
|
/**
|
|
6152
6345
|
* Get the database file path.
|
|
@@ -6162,7 +6355,7 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6162
6355
|
import { mkdir, readFile, writeFile, readdir, unlink, rename, access, appendFile } from "node:fs/promises";
|
|
6163
6356
|
import { constants as fsConstants } from "node:fs";
|
|
6164
6357
|
import { basename, dirname, join as join3, resolve as resolve2 } from "node:path";
|
|
6165
|
-
import { randomUUID as randomUUID2, randomBytes, createHash as
|
|
6358
|
+
import { randomUUID as randomUUID2, randomBytes, createHash as createHash3 } from "node:crypto";
|
|
6166
6359
|
import { EventEmitter } from "node:events";
|
|
6167
6360
|
function resolveCreationRuntimeConfig(incoming, metadata) {
|
|
6168
6361
|
const isEphemeral = isEphemeralAgent({ metadata });
|
|
@@ -7328,7 +7521,7 @@ var init_agent_store = __esm({
|
|
|
7328
7521
|
throw new Error(`Agent ${agentId} not found`);
|
|
7329
7522
|
}
|
|
7330
7523
|
const token = randomBytes(32).toString("hex");
|
|
7331
|
-
const tokenHash =
|
|
7524
|
+
const tokenHash = createHash3("sha256").update(token).digest("hex");
|
|
7332
7525
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7333
7526
|
const label = options?.label?.trim();
|
|
7334
7527
|
const key = {
|
|
@@ -9484,10 +9677,10 @@ END;
|
|
|
9484
9677
|
mkdirSync3(fusionDir, { recursive: true });
|
|
9485
9678
|
}
|
|
9486
9679
|
this.db = new DatabaseSync(inMemory ? ":memory:" : join6(fusionDir, "archive.db"));
|
|
9680
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
9487
9681
|
if (!inMemory) {
|
|
9488
9682
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
9489
9683
|
}
|
|
9490
|
-
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
9491
9684
|
this._fts5Available = probeFts5(this.db);
|
|
9492
9685
|
}
|
|
9493
9686
|
/** True when this SQLite build has FTS5. See db.ts#probeFts5. */
|
|
@@ -13162,9 +13355,15 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13162
13355
|
globalDir;
|
|
13163
13356
|
/** Tracks transaction nesting depth for savepoint-based nested transactions. */
|
|
13164
13357
|
transactionDepth = 0;
|
|
13165
|
-
|
|
13358
|
+
busyTimeoutMs;
|
|
13359
|
+
lockRecoveryWindowMs;
|
|
13360
|
+
lockRecoveryDelayMs;
|
|
13361
|
+
constructor(globalDir, options) {
|
|
13166
13362
|
this.globalDir = resolveGlobalDir(globalDir);
|
|
13167
13363
|
this.dbPath = join8(this.globalDir, "fusion-central.db");
|
|
13364
|
+
this.busyTimeoutMs = Math.max(0, options?.busyTimeoutMs ?? 5e3);
|
|
13365
|
+
this.lockRecoveryWindowMs = Math.max(0, options?.lockRecoveryWindowMs ?? 1e3);
|
|
13366
|
+
this.lockRecoveryDelayMs = Math.max(1, options?.lockRecoveryDelayMs ?? 50);
|
|
13168
13367
|
if (!existsSync6(this.globalDir)) {
|
|
13169
13368
|
mkdirSync4(this.globalDir, { recursive: true });
|
|
13170
13369
|
}
|
|
@@ -13174,8 +13373,8 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13174
13373
|
const message = error instanceof Error ? error.message : String(error);
|
|
13175
13374
|
throw new Error(`Failed to open Fusion central database at ${this.dbPath}: ${message}`);
|
|
13176
13375
|
}
|
|
13376
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
13177
13377
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
13178
|
-
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
13179
13378
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
13180
13379
|
}
|
|
13181
13380
|
/**
|
|
@@ -13281,6 +13480,29 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13281
13480
|
close() {
|
|
13282
13481
|
this.db.close();
|
|
13283
13482
|
}
|
|
13483
|
+
runWithLockRecovery(action, fn) {
|
|
13484
|
+
const deadline = Date.now() + this.lockRecoveryWindowMs;
|
|
13485
|
+
let attempt = 0;
|
|
13486
|
+
while (true) {
|
|
13487
|
+
try {
|
|
13488
|
+
fn();
|
|
13489
|
+
return;
|
|
13490
|
+
} catch (error) {
|
|
13491
|
+
if (!isSqliteLockError(error)) {
|
|
13492
|
+
throw error;
|
|
13493
|
+
}
|
|
13494
|
+
if (Date.now() >= deadline) {
|
|
13495
|
+
throw new Error(
|
|
13496
|
+
`SQLite ${action} failed after ${attempt + 1} attempt${attempt === 0 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`
|
|
13497
|
+
);
|
|
13498
|
+
}
|
|
13499
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
13500
|
+
const delayMs = Math.min(this.lockRecoveryDelayMs * Math.max(1, attempt + 1), remainingMs);
|
|
13501
|
+
sleepSync(delayMs);
|
|
13502
|
+
attempt += 1;
|
|
13503
|
+
}
|
|
13504
|
+
}
|
|
13505
|
+
}
|
|
13284
13506
|
/**
|
|
13285
13507
|
* Execute a function inside a SQLite transaction.
|
|
13286
13508
|
* Supports nested calls via SAVEPOINTs.
|
|
@@ -13291,15 +13513,24 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13291
13513
|
const depth = this.transactionDepth++;
|
|
13292
13514
|
const isOutermost = depth === 0;
|
|
13293
13515
|
const savepointName = `sp_${depth}`;
|
|
13294
|
-
|
|
13295
|
-
|
|
13296
|
-
|
|
13297
|
-
|
|
13516
|
+
try {
|
|
13517
|
+
if (isOutermost) {
|
|
13518
|
+
this.runWithLockRecovery("BEGIN IMMEDIATE", () => {
|
|
13519
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
13520
|
+
});
|
|
13521
|
+
} else {
|
|
13522
|
+
this.db.exec(`SAVEPOINT ${savepointName}`);
|
|
13523
|
+
}
|
|
13524
|
+
} catch (error) {
|
|
13525
|
+
this.transactionDepth--;
|
|
13526
|
+
throw error;
|
|
13298
13527
|
}
|
|
13299
13528
|
try {
|
|
13300
13529
|
const result = fn();
|
|
13301
13530
|
if (isOutermost) {
|
|
13302
|
-
this.
|
|
13531
|
+
this.runWithLockRecovery("COMMIT", () => {
|
|
13532
|
+
this.db.exec("COMMIT");
|
|
13533
|
+
});
|
|
13303
13534
|
} else {
|
|
13304
13535
|
this.db.exec(`RELEASE ${savepointName}`);
|
|
13305
13536
|
}
|
|
@@ -19920,8 +20151,8 @@ var init_system_metrics = __esm({
|
|
|
19920
20151
|
|
|
19921
20152
|
// ../core/src/central-core.ts
|
|
19922
20153
|
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
19923
|
-
import { createHash as
|
|
19924
|
-
import { existsSync as existsSync7, statSync } from "node:fs";
|
|
20154
|
+
import { createHash as createHash4, randomUUID as randomUUID9 } from "node:crypto";
|
|
20155
|
+
import { existsSync as existsSync7, statSync as statSync2 } from "node:fs";
|
|
19925
20156
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
19926
20157
|
import { isAbsolute as isAbsolute2, join as join11, basename as basename2, resolve as resolve5 } from "node:path";
|
|
19927
20158
|
var CentralCore;
|
|
@@ -20030,7 +20261,7 @@ var init_central_core = __esm({
|
|
|
20030
20261
|
if (!existsSync7(input.path)) {
|
|
20031
20262
|
throw new Error(`Project path does not exist: ${input.path}`);
|
|
20032
20263
|
}
|
|
20033
|
-
if (!
|
|
20264
|
+
if (!statSync2(input.path).isDirectory()) {
|
|
20034
20265
|
throw new Error(`Project path must be a directory: ${input.path}`);
|
|
20035
20266
|
}
|
|
20036
20267
|
const existingByPath = await this.getProjectByPath(input.path);
|
|
@@ -21769,7 +22000,7 @@ var init_central_core = __esm({
|
|
|
21769
22000
|
const dbPath = this.db.getPath();
|
|
21770
22001
|
let dbSizeBytes = 0;
|
|
21771
22002
|
try {
|
|
21772
|
-
dbSizeBytes =
|
|
22003
|
+
dbSizeBytes = statSync2(dbPath).size;
|
|
21773
22004
|
} catch {
|
|
21774
22005
|
}
|
|
21775
22006
|
return { projectCount, totalTasksCompleted, dbSizeBytes };
|
|
@@ -21987,10 +22218,10 @@ var init_central_core = __esm({
|
|
|
21987
22218
|
*/
|
|
21988
22219
|
async generateProjectName(projectPath) {
|
|
21989
22220
|
try {
|
|
21990
|
-
const { execFile:
|
|
21991
|
-
const { promisify:
|
|
21992
|
-
const
|
|
21993
|
-
const { stdout } = await
|
|
22221
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
22222
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
22223
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
22224
|
+
const { stdout } = await execFileAsync7(
|
|
21994
22225
|
"git",
|
|
21995
22226
|
["remote", "get-url", "origin"],
|
|
21996
22227
|
{ cwd: projectPath, timeout: 5e3 }
|
|
@@ -22292,7 +22523,7 @@ var init_central_core = __esm({
|
|
|
22292
22523
|
exportedAt: snapshot.exportedAt,
|
|
22293
22524
|
version: 1
|
|
22294
22525
|
};
|
|
22295
|
-
const checksum =
|
|
22526
|
+
const checksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22296
22527
|
return this.applyRemoteSettings({ ...payloadWithoutChecksum, checksum });
|
|
22297
22528
|
}
|
|
22298
22529
|
getAuthMaterialSnapshot(providerAuth) {
|
|
@@ -22335,7 +22566,7 @@ var init_central_core = __esm({
|
|
|
22335
22566
|
exportedAt,
|
|
22336
22567
|
version: 1
|
|
22337
22568
|
};
|
|
22338
|
-
const checksum =
|
|
22569
|
+
const checksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22339
22570
|
return {
|
|
22340
22571
|
...payloadWithoutChecksum,
|
|
22341
22572
|
checksum
|
|
@@ -22370,7 +22601,7 @@ var init_central_core = __esm({
|
|
|
22370
22601
|
exportedAt: payload.exportedAt,
|
|
22371
22602
|
version: payload.version
|
|
22372
22603
|
};
|
|
22373
|
-
const computedChecksum =
|
|
22604
|
+
const computedChecksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22374
22605
|
if (computedChecksum !== payload.checksum) {
|
|
22375
22606
|
return {
|
|
22376
22607
|
success: false,
|
|
@@ -22492,13 +22723,13 @@ var init_central_core = __esm({
|
|
|
22492
22723
|
});
|
|
22493
22724
|
|
|
22494
22725
|
// ../core/src/sqlite-validation.ts
|
|
22495
|
-
import { existsSync as existsSync8, statSync as
|
|
22726
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "node:fs";
|
|
22496
22727
|
function isValidSqliteDatabaseFile(dbPath) {
|
|
22497
22728
|
if (!existsSync8(dbPath)) {
|
|
22498
22729
|
return false;
|
|
22499
22730
|
}
|
|
22500
22731
|
try {
|
|
22501
|
-
if (!
|
|
22732
|
+
if (!statSync3(dbPath).isFile()) {
|
|
22502
22733
|
return false;
|
|
22503
22734
|
}
|
|
22504
22735
|
} catch {
|
|
@@ -22691,10 +22922,10 @@ var init_migration = __esm({
|
|
|
22691
22922
|
return basename3(projectPath);
|
|
22692
22923
|
}
|
|
22693
22924
|
try {
|
|
22694
|
-
const { execFile:
|
|
22695
|
-
const { promisify:
|
|
22696
|
-
const
|
|
22697
|
-
const { stdout } = await
|
|
22925
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
22926
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
22927
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
22928
|
+
const { stdout } = await execFileAsync7(
|
|
22698
22929
|
"git",
|
|
22699
22930
|
["remote", "get-url", "origin"],
|
|
22700
22931
|
{ cwd: projectPath, timeout: 1e3 }
|
|
@@ -32967,7 +33198,7 @@ var init_memory_dreams = __esm({
|
|
|
32967
33198
|
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6, access as access3, constants, readdir as readdir4, stat as stat2 } from "node:fs/promises";
|
|
32968
33199
|
import { existsSync as existsSync11 } from "node:fs";
|
|
32969
33200
|
import { basename as basename4, dirname as dirname5, isAbsolute as isAbsolute4, join as join15, normalize as normalize2, relative, resolve as resolve7, sep as sep3 } from "node:path";
|
|
32970
|
-
import { createHash as
|
|
33201
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
32971
33202
|
function shouldSkipBackgroundQmdRefresh() {
|
|
32972
33203
|
return (process.env.VITEST === "true" || process.env.NODE_ENV === "test") && process.env.FUSION_ENABLE_QMD_REFRESH_IN_TESTS !== "1";
|
|
32973
33204
|
}
|
|
@@ -32983,7 +33214,7 @@ function memoryDreamsPath(rootDir) {
|
|
|
32983
33214
|
function qmdMemoryCollectionName(rootDir) {
|
|
32984
33215
|
const absoluteRoot = resolve7(rootDir);
|
|
32985
33216
|
const slug = basename4(absoluteRoot).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
32986
|
-
const hash =
|
|
33217
|
+
const hash = createHash5("sha1").update(absoluteRoot).digest("hex").slice(0, 12);
|
|
32987
33218
|
return `${QMD_COLLECTION_PREFIX}-${slug}-${hash}`;
|
|
32988
33219
|
}
|
|
32989
33220
|
function buildQmdSearchArgs(rootDir, options) {
|
|
@@ -33019,7 +33250,7 @@ function buildQmdRefreshCommands(rootDir) {
|
|
|
33019
33250
|
function qmdAgentMemoryCollectionName(rootDir, agentId) {
|
|
33020
33251
|
const absoluteRoot = resolve7(rootDir);
|
|
33021
33252
|
const safeAgentId = agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
33022
|
-
const hash =
|
|
33253
|
+
const hash = createHash5("sha1").update(`${absoluteRoot}:${agentId}`).digest("hex").slice(0, 12);
|
|
33023
33254
|
return `fusion-agent-memory-${safeAgentId.toLowerCase()}-${hash}`;
|
|
33024
33255
|
}
|
|
33025
33256
|
function dailyMemoryPath(rootDir, date = /* @__PURE__ */ new Date()) {
|
|
@@ -33423,13 +33654,13 @@ async function searchWithQmd(rootDir, options) {
|
|
|
33423
33654
|
const command = "qmd";
|
|
33424
33655
|
const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
|
|
33425
33656
|
try {
|
|
33426
|
-
const { execFile:
|
|
33427
|
-
const { promisify:
|
|
33428
|
-
const
|
|
33429
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
33657
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
33658
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
33659
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
33660
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync7);
|
|
33430
33661
|
scheduleQmdProjectMemoryRefresh(rootDir);
|
|
33431
33662
|
const args = buildQmdSearchArgs(rootDir, options);
|
|
33432
|
-
const { stdout } = await
|
|
33663
|
+
const { stdout } = await execFileAsync7(command, args, {
|
|
33433
33664
|
cwd: rootDir,
|
|
33434
33665
|
timeout: 4e3,
|
|
33435
33666
|
maxBuffer: 1024 * 1024
|
|
@@ -33454,12 +33685,12 @@ async function searchWithQmd(rootDir, options) {
|
|
|
33454
33685
|
return [];
|
|
33455
33686
|
}
|
|
33456
33687
|
}
|
|
33457
|
-
async function ensureQmdProjectMemoryCollection(rootDir,
|
|
33688
|
+
async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync7) {
|
|
33458
33689
|
const collectionName = qmdMemoryCollectionName(rootDir);
|
|
33459
33690
|
const memoryDir = memoryWorkspacePath(rootDir);
|
|
33460
33691
|
await mkdir6(memoryDir, { recursive: true });
|
|
33461
33692
|
try {
|
|
33462
|
-
await
|
|
33693
|
+
await execFileAsync7("qmd", buildQmdCollectionAddArgs(rootDir), {
|
|
33463
33694
|
cwd: rootDir,
|
|
33464
33695
|
timeout: 4e3,
|
|
33465
33696
|
maxBuffer: 512 * 1024
|
|
@@ -33475,9 +33706,9 @@ ${stderr}`)) {
|
|
|
33475
33706
|
return collectionName;
|
|
33476
33707
|
}
|
|
33477
33708
|
async function getDefaultExecFileAsync() {
|
|
33478
|
-
const { execFile:
|
|
33479
|
-
const { promisify:
|
|
33480
|
-
return
|
|
33709
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
33710
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
33711
|
+
return promisify15(execFile9);
|
|
33481
33712
|
}
|
|
33482
33713
|
async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
33483
33714
|
const key = resolve7(rootDir);
|
|
@@ -33492,14 +33723,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
|
33492
33723
|
}
|
|
33493
33724
|
}
|
|
33494
33725
|
const promise = (async () => {
|
|
33495
|
-
const
|
|
33496
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
33497
|
-
await
|
|
33726
|
+
const execFileAsync7 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33727
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync7);
|
|
33728
|
+
await execFileAsync7("qmd", ["update"], {
|
|
33498
33729
|
cwd: rootDir,
|
|
33499
33730
|
timeout: 3e4,
|
|
33500
33731
|
maxBuffer: 1024 * 1024
|
|
33501
33732
|
});
|
|
33502
|
-
await
|
|
33733
|
+
await execFileAsync7("qmd", ["embed"], {
|
|
33503
33734
|
cwd: rootDir,
|
|
33504
33735
|
timeout: 12e4,
|
|
33505
33736
|
maxBuffer: 1024 * 1024
|
|
@@ -33535,12 +33766,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
|
|
|
33535
33766
|
}
|
|
33536
33767
|
}
|
|
33537
33768
|
const promise = (async () => {
|
|
33538
|
-
const
|
|
33769
|
+
const execFileAsync7 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33539
33770
|
const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
|
|
33540
33771
|
const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
|
|
33541
33772
|
await mkdir6(workspacePath, { recursive: true });
|
|
33542
33773
|
try {
|
|
33543
|
-
await
|
|
33774
|
+
await execFileAsync7("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
|
|
33544
33775
|
cwd: rootDir,
|
|
33545
33776
|
timeout: 4e3,
|
|
33546
33777
|
maxBuffer: 512 * 1024
|
|
@@ -33553,8 +33784,8 @@ ${stderr}`)) {
|
|
|
33553
33784
|
throw err;
|
|
33554
33785
|
}
|
|
33555
33786
|
}
|
|
33556
|
-
await
|
|
33557
|
-
await
|
|
33787
|
+
await execFileAsync7("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
33788
|
+
await execFileAsync7("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
33558
33789
|
})();
|
|
33559
33790
|
qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
33560
33791
|
try {
|
|
@@ -33575,8 +33806,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
|
|
|
33575
33806
|
}
|
|
33576
33807
|
async function isQmdAvailable() {
|
|
33577
33808
|
try {
|
|
33578
|
-
const
|
|
33579
|
-
await
|
|
33809
|
+
const execFileAsync7 = await getDefaultExecFileAsync();
|
|
33810
|
+
await execFileAsync7("qmd", ["--help"], {
|
|
33580
33811
|
timeout: 3e3,
|
|
33581
33812
|
maxBuffer: 128 * 1024
|
|
33582
33813
|
});
|
|
@@ -33586,12 +33817,12 @@ async function isQmdAvailable() {
|
|
|
33586
33817
|
}
|
|
33587
33818
|
}
|
|
33588
33819
|
async function installQmd(options) {
|
|
33589
|
-
const
|
|
33820
|
+
const execFileAsync7 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33590
33821
|
const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
|
|
33591
33822
|
if (!command || args.length === 0) {
|
|
33592
33823
|
throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
|
|
33593
33824
|
}
|
|
33594
|
-
await
|
|
33825
|
+
await execFileAsync7(command, args, {
|
|
33595
33826
|
timeout: 12e4,
|
|
33596
33827
|
maxBuffer: 1024 * 1024
|
|
33597
33828
|
});
|
|
@@ -34892,6 +35123,135 @@ function resolveLocalNodeId(nodes, fallback = "local") {
|
|
|
34892
35123
|
const localNode = nodes?.find((node) => node.type === "local");
|
|
34893
35124
|
return localNode?.id ?? fallback;
|
|
34894
35125
|
}
|
|
35126
|
+
function parseTaskId(taskId) {
|
|
35127
|
+
const match = taskId.trim().toUpperCase().match(TASK_ID_PATTERN);
|
|
35128
|
+
if (!match) {
|
|
35129
|
+
return null;
|
|
35130
|
+
}
|
|
35131
|
+
const sequence = Number.parseInt(match[2], 10);
|
|
35132
|
+
if (!Number.isFinite(sequence)) {
|
|
35133
|
+
return null;
|
|
35134
|
+
}
|
|
35135
|
+
return { prefix: match[1], sequence };
|
|
35136
|
+
}
|
|
35137
|
+
function getConfiguredPrefixAndLegacyNextId(db) {
|
|
35138
|
+
try {
|
|
35139
|
+
const row = db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get();
|
|
35140
|
+
if (!row) {
|
|
35141
|
+
return { prefix: "KB", nextId: null };
|
|
35142
|
+
}
|
|
35143
|
+
const settings = row.settings ? JSON.parse(row.settings) : null;
|
|
35144
|
+
return {
|
|
35145
|
+
prefix: (settings?.taskPrefix ?? "KB").trim().toUpperCase(),
|
|
35146
|
+
nextId: typeof row.nextId === "number" ? row.nextId : null
|
|
35147
|
+
};
|
|
35148
|
+
} catch {
|
|
35149
|
+
return { prefix: "KB", nextId: null };
|
|
35150
|
+
}
|
|
35151
|
+
}
|
|
35152
|
+
function getKnownPrefixes(db) {
|
|
35153
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
35154
|
+
const configured = getConfiguredPrefixAndLegacyNextId(db).prefix;
|
|
35155
|
+
if (configured) {
|
|
35156
|
+
prefixes.add(configured);
|
|
35157
|
+
}
|
|
35158
|
+
const addFromQuery = (sql, mapper) => {
|
|
35159
|
+
try {
|
|
35160
|
+
const rows = db.prepare(sql).all();
|
|
35161
|
+
for (const row of rows) {
|
|
35162
|
+
const prefix = mapper(row)?.trim().toUpperCase();
|
|
35163
|
+
if (prefix) {
|
|
35164
|
+
prefixes.add(prefix);
|
|
35165
|
+
}
|
|
35166
|
+
}
|
|
35167
|
+
} catch {
|
|
35168
|
+
}
|
|
35169
|
+
};
|
|
35170
|
+
addFromQuery("SELECT prefix FROM distributed_task_id_state", (row) => row.prefix);
|
|
35171
|
+
addFromQuery("SELECT prefix FROM distributed_task_id_reservations", (row) => row.prefix);
|
|
35172
|
+
addFromQuery("SELECT id FROM tasks", (row) => parseTaskId(String(row.id ?? ""))?.prefix);
|
|
35173
|
+
addFromQuery("SELECT id FROM archivedTasks", (row) => parseTaskId(String(row.id ?? ""))?.prefix);
|
|
35174
|
+
return prefixes;
|
|
35175
|
+
}
|
|
35176
|
+
function getMaxTaskSequenceFromTable(db, table, prefix) {
|
|
35177
|
+
try {
|
|
35178
|
+
const rows = db.prepare(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${prefix}-%`);
|
|
35179
|
+
let maxSequence = 0;
|
|
35180
|
+
for (const row of rows) {
|
|
35181
|
+
const parsed = parseTaskId(row.id);
|
|
35182
|
+
if (parsed?.prefix === prefix && parsed.sequence > maxSequence) {
|
|
35183
|
+
maxSequence = parsed.sequence;
|
|
35184
|
+
}
|
|
35185
|
+
}
|
|
35186
|
+
return maxSequence;
|
|
35187
|
+
} catch {
|
|
35188
|
+
return 0;
|
|
35189
|
+
}
|
|
35190
|
+
}
|
|
35191
|
+
function getMaxReservationSequence(db, prefix) {
|
|
35192
|
+
try {
|
|
35193
|
+
const row = db.prepare("SELECT MAX(sequence) AS maxSeq FROM distributed_task_id_reservations WHERE prefix = ?").get(prefix);
|
|
35194
|
+
return typeof row?.maxSeq === "number" ? row.maxSeq : 0;
|
|
35195
|
+
} catch {
|
|
35196
|
+
return 0;
|
|
35197
|
+
}
|
|
35198
|
+
}
|
|
35199
|
+
function getNextSequenceFloor(db, prefix) {
|
|
35200
|
+
const configured = getConfiguredPrefixAndLegacyNextId(db);
|
|
35201
|
+
let nextSequence = 1;
|
|
35202
|
+
if (configured.prefix === prefix && configured.nextId && configured.nextId > nextSequence) {
|
|
35203
|
+
nextSequence = configured.nextId;
|
|
35204
|
+
}
|
|
35205
|
+
const taskHighWaterMark = getMaxTaskSequenceFromTable(db, "tasks", prefix) + 1;
|
|
35206
|
+
const archivedHighWaterMark = getMaxTaskSequenceFromTable(db, "archivedTasks", prefix) + 1;
|
|
35207
|
+
const reservationHighWaterMark = getMaxReservationSequence(db, prefix) + 1;
|
|
35208
|
+
nextSequence = Math.max(nextSequence, taskHighWaterMark, archivedHighWaterMark, reservationHighWaterMark);
|
|
35209
|
+
return nextSequence;
|
|
35210
|
+
}
|
|
35211
|
+
function ensureStateRow(db, prefix) {
|
|
35212
|
+
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
35213
|
+
const nextSequence = getNextSequenceFloor(db, prefix);
|
|
35214
|
+
db.prepare(
|
|
35215
|
+
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
35216
|
+
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
35217
|
+
) VALUES (?, ?, 0, NULL, ?)`
|
|
35218
|
+
).run(prefix, nextSequence, nowIso3);
|
|
35219
|
+
db.prepare(
|
|
35220
|
+
`UPDATE distributed_task_id_state
|
|
35221
|
+
SET nextSequence = MAX(nextSequence, ?),
|
|
35222
|
+
updatedAt = ?
|
|
35223
|
+
WHERE prefix = ?`
|
|
35224
|
+
).run(nextSequence, nowIso3, prefix);
|
|
35225
|
+
}
|
|
35226
|
+
function reconcileTaskIdState(db) {
|
|
35227
|
+
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
35228
|
+
return db.transaction(() => {
|
|
35229
|
+
const reconciled = [];
|
|
35230
|
+
for (const prefix of getKnownPrefixes(db)) {
|
|
35231
|
+
const nextSequence = getNextSequenceFloor(db, prefix);
|
|
35232
|
+
db.prepare(
|
|
35233
|
+
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
35234
|
+
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
35235
|
+
) VALUES (?, ?, 0, NULL, ?)`
|
|
35236
|
+
).run(prefix, nextSequence, nowIso3);
|
|
35237
|
+
const before = db.prepare("SELECT nextSequence FROM distributed_task_id_state WHERE prefix = ?").get(prefix);
|
|
35238
|
+
db.prepare(
|
|
35239
|
+
`UPDATE distributed_task_id_state
|
|
35240
|
+
SET nextSequence = MAX(nextSequence, ?),
|
|
35241
|
+
updatedAt = ?
|
|
35242
|
+
WHERE prefix = ?`
|
|
35243
|
+
).run(nextSequence, nowIso3, prefix);
|
|
35244
|
+
const after = db.prepare("SELECT nextSequence FROM distributed_task_id_state WHERE prefix = ?").get(prefix);
|
|
35245
|
+
if (!before || !after || after.nextSequence !== before.nextSequence) {
|
|
35246
|
+
reconciled.push(prefix);
|
|
35247
|
+
}
|
|
35248
|
+
}
|
|
35249
|
+
if (reconciled.length > 0) {
|
|
35250
|
+
db.bumpLastModified();
|
|
35251
|
+
}
|
|
35252
|
+
return reconciled;
|
|
35253
|
+
});
|
|
35254
|
+
}
|
|
34895
35255
|
function formatDistributedTaskId(prefix, sequence) {
|
|
34896
35256
|
const normalizedPrefix = prefix.trim().toUpperCase();
|
|
34897
35257
|
if (!normalizedPrefix) {
|
|
@@ -34934,48 +35294,6 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
34934
35294
|
};
|
|
34935
35295
|
return existsInTable("tasks") || existsInTable("archivedTasks");
|
|
34936
35296
|
};
|
|
34937
|
-
const ensureStateRow = (prefix) => {
|
|
34938
|
-
let seedSequence = 1;
|
|
34939
|
-
try {
|
|
34940
|
-
const configRow = db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get();
|
|
34941
|
-
if (configRow) {
|
|
34942
|
-
const settings = configRow.settings ? JSON.parse(configRow.settings) : null;
|
|
34943
|
-
const configuredPrefix = (settings?.taskPrefix ?? "KB").trim().toUpperCase();
|
|
34944
|
-
if (configuredPrefix === prefix && typeof configRow.nextId === "number" && configRow.nextId > seedSequence) {
|
|
34945
|
-
seedSequence = configRow.nextId;
|
|
34946
|
-
}
|
|
34947
|
-
}
|
|
34948
|
-
} catch {
|
|
34949
|
-
}
|
|
34950
|
-
const idPattern = `${prefix}-%`;
|
|
34951
|
-
const probeTable = (table) => {
|
|
34952
|
-
try {
|
|
34953
|
-
const row = db.prepare(
|
|
34954
|
-
`SELECT MAX(CAST(substr(id, ${prefix.length + 2}) AS INTEGER)) AS maxSeq
|
|
34955
|
-
FROM ${table}
|
|
34956
|
-
WHERE id LIKE ? AND substr(id, ${prefix.length + 2}) GLOB '[0-9]*'`
|
|
34957
|
-
).get(idPattern);
|
|
34958
|
-
if (row && typeof row.maxSeq === "number" && row.maxSeq + 1 > seedSequence) {
|
|
34959
|
-
seedSequence = row.maxSeq + 1;
|
|
34960
|
-
}
|
|
34961
|
-
} catch {
|
|
34962
|
-
}
|
|
34963
|
-
};
|
|
34964
|
-
probeTable("tasks");
|
|
34965
|
-
probeTable("archivedTasks");
|
|
34966
|
-
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
34967
|
-
db.prepare(
|
|
34968
|
-
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
34969
|
-
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
34970
|
-
) VALUES (?, ?, 0, NULL, ?)`
|
|
34971
|
-
).run(prefix, seedSequence, nowIso3);
|
|
34972
|
-
db.prepare(
|
|
34973
|
-
`UPDATE distributed_task_id_state
|
|
34974
|
-
SET nextSequence = MAX(nextSequence, ?),
|
|
34975
|
-
updatedAt = ?
|
|
34976
|
-
WHERE prefix = ?`
|
|
34977
|
-
).run(seedSequence, nowIso3, prefix);
|
|
34978
|
-
};
|
|
34979
35297
|
return {
|
|
34980
35298
|
formatDistributedTaskId,
|
|
34981
35299
|
reserveDistributedTaskId: async (input) => withLock(async () => {
|
|
@@ -34989,7 +35307,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
34989
35307
|
if (!prefix) {
|
|
34990
35308
|
throw new DistributedTaskIdError("prefix is required", "invalid_prefix");
|
|
34991
35309
|
}
|
|
34992
|
-
ensureStateRow(prefix);
|
|
35310
|
+
ensureStateRow(db, prefix);
|
|
34993
35311
|
const state = db.prepare(
|
|
34994
35312
|
"SELECT nextSequence, committedClusterTaskCount FROM distributed_task_id_state WHERE prefix = ?"
|
|
34995
35313
|
).get(prefix);
|
|
@@ -35043,7 +35361,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35043
35361
|
SET status = 'committed', committedAt = ?, updatedAt = ?
|
|
35044
35362
|
WHERE reservationId = ?`
|
|
35045
35363
|
).run(nowIso3, nowIso3, row.reservationId);
|
|
35046
|
-
ensureStateRow(row.prefix);
|
|
35364
|
+
ensureStateRow(db, row.prefix);
|
|
35047
35365
|
db.prepare(
|
|
35048
35366
|
`UPDATE distributed_task_id_state
|
|
35049
35367
|
SET committedClusterTaskCount = committedClusterTaskCount + 1,
|
|
@@ -35089,7 +35407,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35089
35407
|
WHERE reservationId = ?`
|
|
35090
35408
|
).run(input.reason, nowIso3, nowIso3, row.reservationId);
|
|
35091
35409
|
}
|
|
35092
|
-
ensureStateRow(row.prefix);
|
|
35410
|
+
ensureStateRow(db, row.prefix);
|
|
35093
35411
|
const state = db.prepare(
|
|
35094
35412
|
"SELECT committedClusterTaskCount FROM distributed_task_id_state WHERE prefix = ?"
|
|
35095
35413
|
).get(row.prefix);
|
|
@@ -35111,7 +35429,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35111
35429
|
if (!prefix) {
|
|
35112
35430
|
throw new DistributedTaskIdError("prefix is required", "invalid_prefix");
|
|
35113
35431
|
}
|
|
35114
|
-
ensureStateRow(prefix);
|
|
35432
|
+
ensureStateRow(db, prefix);
|
|
35115
35433
|
const row = db.prepare(
|
|
35116
35434
|
`SELECT nextSequence, committedClusterTaskCount, lastCommittedTaskId
|
|
35117
35435
|
FROM distributed_task_id_state
|
|
@@ -35136,11 +35454,12 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35136
35454
|
})
|
|
35137
35455
|
};
|
|
35138
35456
|
}
|
|
35139
|
-
var DEFAULT_RESERVATION_TTL_MS, DistributedTaskIdError;
|
|
35457
|
+
var DEFAULT_RESERVATION_TTL_MS, TASK_ID_PATTERN, DistributedTaskIdError;
|
|
35140
35458
|
var init_distributed_task_id = __esm({
|
|
35141
35459
|
"../core/src/distributed-task-id.ts"() {
|
|
35142
35460
|
"use strict";
|
|
35143
35461
|
DEFAULT_RESERVATION_TTL_MS = 15 * 60 * 1e3;
|
|
35462
|
+
TASK_ID_PATTERN = /^([A-Z][A-Z0-9]*)-(\d+)$/;
|
|
35144
35463
|
DistributedTaskIdError = class extends Error {
|
|
35145
35464
|
constructor(message, code) {
|
|
35146
35465
|
super(message);
|
|
@@ -35435,6 +35754,8 @@ var init_store = __esm({
|
|
|
35435
35754
|
worktreeAllocationLock = Promise.resolve();
|
|
35436
35755
|
/** Promise chain for serializing config.json read-modify-write cycles */
|
|
35437
35756
|
configLock = Promise.resolve();
|
|
35757
|
+
/** Startup/open guard for distributed_task_id_state reconciliation. */
|
|
35758
|
+
taskIdStateReconciled = false;
|
|
35438
35759
|
/** Cached workflow steps — invalidated on create/update/delete */
|
|
35439
35760
|
workflowStepsCache = null;
|
|
35440
35761
|
/** Plugin-contributed workflow step templates injected by engine runtime. */
|
|
@@ -35501,6 +35822,7 @@ var init_store = __esm({
|
|
|
35501
35822
|
throw error;
|
|
35502
35823
|
}
|
|
35503
35824
|
this._db = db;
|
|
35825
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35504
35826
|
if (detectLegacyData(this.fusionDir)) {
|
|
35505
35827
|
}
|
|
35506
35828
|
}
|
|
@@ -35520,6 +35842,13 @@ var init_store = __esm({
|
|
|
35520
35842
|
}
|
|
35521
35843
|
return this._archiveDb;
|
|
35522
35844
|
}
|
|
35845
|
+
reconcileDistributedTaskIdStateOnOpen() {
|
|
35846
|
+
if (this.taskIdStateReconciled) {
|
|
35847
|
+
return;
|
|
35848
|
+
}
|
|
35849
|
+
reconcileTaskIdState(this.db);
|
|
35850
|
+
this.taskIdStateReconciled = true;
|
|
35851
|
+
}
|
|
35523
35852
|
async init() {
|
|
35524
35853
|
await mkdir7(this.tasksDir, { recursive: true });
|
|
35525
35854
|
if (!this._db) {
|
|
@@ -35532,15 +35861,18 @@ var init_store = __esm({
|
|
|
35532
35861
|
}
|
|
35533
35862
|
this._db = db;
|
|
35534
35863
|
}
|
|
35864
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35535
35865
|
if (detectLegacyData(this.fusionDir)) {
|
|
35536
35866
|
await migrateFromLegacy(this.fusionDir, this._db);
|
|
35537
35867
|
}
|
|
35538
35868
|
await this.migrateActiveArchivedTasksToArchiveDb();
|
|
35539
35869
|
await this.importLegacyAgentLogsOnce();
|
|
35870
|
+
this.taskIdStateReconciled = false;
|
|
35871
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35540
35872
|
if (!existsSync13(this.configPath)) {
|
|
35541
35873
|
const config = await this.readConfig();
|
|
35542
35874
|
try {
|
|
35543
|
-
await writeFile6(this.configPath,
|
|
35875
|
+
await writeFile6(this.configPath, this.serializeConfigForDisk(config));
|
|
35544
35876
|
} catch (err) {
|
|
35545
35877
|
storeLog.warn("Backward-compat config.json sync failed during init", {
|
|
35546
35878
|
phase: "init:config-sync",
|
|
@@ -36129,117 +36461,8 @@ ${outcome}`;
|
|
|
36129
36461
|
`;
|
|
36130
36462
|
return [...columns, limitedLog].join(", ");
|
|
36131
36463
|
}
|
|
36132
|
-
|
|
36133
|
-
|
|
36134
|
-
*/
|
|
36135
|
-
upsertTask(task) {
|
|
36136
|
-
this.db.prepare(`
|
|
36137
|
-
INSERT INTO tasks (
|
|
36138
|
-
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36139
|
-
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36140
|
-
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36141
|
-
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36142
|
-
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36143
|
-
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36144
|
-
executionStartedAt, executionCompletedAt,
|
|
36145
|
-
dependencies, steps, log, attachments, steeringComments,
|
|
36146
|
-
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36147
|
-
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36148
|
-
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
|
36149
|
-
) VALUES (
|
|
36150
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36151
|
-
)
|
|
36152
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
36153
|
-
lineageId = excluded.lineageId,
|
|
36154
|
-
title = excluded.title,
|
|
36155
|
-
description = excluded.description,
|
|
36156
|
-
priority = excluded.priority,
|
|
36157
|
-
"column" = excluded."column",
|
|
36158
|
-
status = excluded.status,
|
|
36159
|
-
size = excluded.size,
|
|
36160
|
-
reviewLevel = excluded.reviewLevel,
|
|
36161
|
-
currentStep = excluded.currentStep,
|
|
36162
|
-
worktree = excluded.worktree,
|
|
36163
|
-
blockedBy = excluded.blockedBy,
|
|
36164
|
-
paused = excluded.paused,
|
|
36165
|
-
baseBranch = excluded.baseBranch,
|
|
36166
|
-
branch = excluded.branch,
|
|
36167
|
-
executionStartBranch = excluded.executionStartBranch,
|
|
36168
|
-
baseCommitSha = excluded.baseCommitSha,
|
|
36169
|
-
modelPresetId = excluded.modelPresetId,
|
|
36170
|
-
modelProvider = excluded.modelProvider,
|
|
36171
|
-
modelId = excluded.modelId,
|
|
36172
|
-
validatorModelProvider = excluded.validatorModelProvider,
|
|
36173
|
-
validatorModelId = excluded.validatorModelId,
|
|
36174
|
-
planningModelProvider = excluded.planningModelProvider,
|
|
36175
|
-
planningModelId = excluded.planningModelId,
|
|
36176
|
-
mergeRetries = excluded.mergeRetries,
|
|
36177
|
-
workflowStepRetries = excluded.workflowStepRetries,
|
|
36178
|
-
stuckKillCount = excluded.stuckKillCount,
|
|
36179
|
-
postReviewFixCount = excluded.postReviewFixCount,
|
|
36180
|
-
recoveryRetryCount = excluded.recoveryRetryCount,
|
|
36181
|
-
taskDoneRetryCount = excluded.taskDoneRetryCount,
|
|
36182
|
-
verificationFailureCount = excluded.verificationFailureCount,
|
|
36183
|
-
mergeConflictBounceCount = excluded.mergeConflictBounceCount,
|
|
36184
|
-
nextRecoveryAt = excluded.nextRecoveryAt,
|
|
36185
|
-
error = excluded.error,
|
|
36186
|
-
summary = excluded.summary,
|
|
36187
|
-
thinkingLevel = excluded.thinkingLevel,
|
|
36188
|
-
executionMode = excluded.executionMode,
|
|
36189
|
-
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
36190
|
-
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
36191
|
-
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
36192
|
-
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
36193
|
-
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
36194
|
-
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
36195
|
-
createdAt = excluded.createdAt,
|
|
36196
|
-
updatedAt = excluded.updatedAt,
|
|
36197
|
-
columnMovedAt = excluded.columnMovedAt,
|
|
36198
|
-
executionStartedAt = excluded.executionStartedAt,
|
|
36199
|
-
executionCompletedAt = excluded.executionCompletedAt,
|
|
36200
|
-
dependencies = excluded.dependencies,
|
|
36201
|
-
steps = excluded.steps,
|
|
36202
|
-
log = excluded.log,
|
|
36203
|
-
attachments = excluded.attachments,
|
|
36204
|
-
steeringComments = excluded.steeringComments,
|
|
36205
|
-
comments = excluded.comments,
|
|
36206
|
-
review = excluded.review,
|
|
36207
|
-
reviewState = excluded.reviewState,
|
|
36208
|
-
workflowStepResults = excluded.workflowStepResults,
|
|
36209
|
-
prInfo = excluded.prInfo,
|
|
36210
|
-
issueInfo = excluded.issueInfo,
|
|
36211
|
-
githubTracking = excluded.githubTracking,
|
|
36212
|
-
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
36213
|
-
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
36214
|
-
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
36215
|
-
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
36216
|
-
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
36217
|
-
mergeDetails = excluded.mergeDetails,
|
|
36218
|
-
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
36219
|
-
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
36220
|
-
modifiedFiles = excluded.modifiedFiles,
|
|
36221
|
-
missionId = excluded.missionId,
|
|
36222
|
-
sliceId = excluded.sliceId,
|
|
36223
|
-
assignedAgentId = excluded.assignedAgentId,
|
|
36224
|
-
pausedByAgentId = excluded.pausedByAgentId,
|
|
36225
|
-
assigneeUserId = excluded.assigneeUserId,
|
|
36226
|
-
nodeId = excluded.nodeId,
|
|
36227
|
-
effectiveNodeId = excluded.effectiveNodeId,
|
|
36228
|
-
effectiveNodeSource = excluded.effectiveNodeSource,
|
|
36229
|
-
sourceType = excluded.sourceType,
|
|
36230
|
-
sourceAgentId = excluded.sourceAgentId,
|
|
36231
|
-
sourceRunId = excluded.sourceRunId,
|
|
36232
|
-
sourceSessionId = excluded.sourceSessionId,
|
|
36233
|
-
sourceMessageId = excluded.sourceMessageId,
|
|
36234
|
-
sourceParentTaskId = excluded.sourceParentTaskId,
|
|
36235
|
-
sourceMetadata = excluded.sourceMetadata,
|
|
36236
|
-
checkedOutBy = excluded.checkedOutBy,
|
|
36237
|
-
checkedOutAt = excluded.checkedOutAt,
|
|
36238
|
-
checkoutNodeId = excluded.checkoutNodeId,
|
|
36239
|
-
checkoutRunId = excluded.checkoutRunId,
|
|
36240
|
-
checkoutLeaseRenewedAt = excluded.checkoutLeaseRenewedAt,
|
|
36241
|
-
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch
|
|
36242
|
-
`).run(
|
|
36464
|
+
getTaskPersistValues(task) {
|
|
36465
|
+
return [
|
|
36243
36466
|
task.id,
|
|
36244
36467
|
task.lineageId ?? generateTaskLineageId(),
|
|
36245
36468
|
task.title ?? null,
|
|
@@ -36330,9 +36553,193 @@ ${outcome}`;
|
|
|
36330
36553
|
task.checkoutRunId ?? null,
|
|
36331
36554
|
task.checkoutLeaseRenewedAt ?? null,
|
|
36332
36555
|
task.checkoutLeaseEpoch ?? 0
|
|
36333
|
-
|
|
36556
|
+
];
|
|
36557
|
+
}
|
|
36558
|
+
/**
|
|
36559
|
+
* Insert a brand-new task row. Create paths must use this so SQLite raises on
|
|
36560
|
+
* duplicate IDs instead of silently rewriting the existing row.
|
|
36561
|
+
*/
|
|
36562
|
+
insertTask(task) {
|
|
36563
|
+
this.db.prepare(`
|
|
36564
|
+
INSERT INTO tasks (
|
|
36565
|
+
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36566
|
+
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36567
|
+
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36568
|
+
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36569
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36570
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36571
|
+
executionStartedAt, executionCompletedAt,
|
|
36572
|
+
dependencies, steps, log, attachments, steeringComments,
|
|
36573
|
+
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36574
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36575
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
|
36576
|
+
) VALUES (
|
|
36577
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36578
|
+
)
|
|
36579
|
+
`).run(...this.getTaskPersistValues(task));
|
|
36334
36580
|
this.db.bumpLastModified();
|
|
36335
36581
|
}
|
|
36582
|
+
/**
|
|
36583
|
+
* Upsert a task to the database. Update paths intentionally retain ON CONFLICT
|
|
36584
|
+
* semantics; create paths must use insertTask() instead.
|
|
36585
|
+
*/
|
|
36586
|
+
upsertTask(task) {
|
|
36587
|
+
this.db.prepare(`
|
|
36588
|
+
INSERT INTO tasks (
|
|
36589
|
+
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36590
|
+
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36591
|
+
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36592
|
+
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36593
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36594
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36595
|
+
executionStartedAt, executionCompletedAt,
|
|
36596
|
+
dependencies, steps, log, attachments, steeringComments,
|
|
36597
|
+
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36598
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36599
|
+
mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, pausedByAgentId, assigneeUserId, nodeId, effectiveNodeId, effectiveNodeSource, sourceType, sourceAgentId, sourceRunId, sourceSessionId, sourceMessageId, sourceParentTaskId, sourceMetadata, checkedOutBy, checkedOutAt, checkoutNodeId, checkoutRunId, checkoutLeaseRenewedAt, checkoutLeaseEpoch
|
|
36600
|
+
) VALUES (
|
|
36601
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36602
|
+
)
|
|
36603
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
36604
|
+
lineageId = excluded.lineageId,
|
|
36605
|
+
title = excluded.title,
|
|
36606
|
+
description = excluded.description,
|
|
36607
|
+
priority = excluded.priority,
|
|
36608
|
+
"column" = excluded."column",
|
|
36609
|
+
status = excluded.status,
|
|
36610
|
+
size = excluded.size,
|
|
36611
|
+
reviewLevel = excluded.reviewLevel,
|
|
36612
|
+
currentStep = excluded.currentStep,
|
|
36613
|
+
worktree = excluded.worktree,
|
|
36614
|
+
blockedBy = excluded.blockedBy,
|
|
36615
|
+
paused = excluded.paused,
|
|
36616
|
+
baseBranch = excluded.baseBranch,
|
|
36617
|
+
branch = excluded.branch,
|
|
36618
|
+
executionStartBranch = excluded.executionStartBranch,
|
|
36619
|
+
baseCommitSha = excluded.baseCommitSha,
|
|
36620
|
+
modelPresetId = excluded.modelPresetId,
|
|
36621
|
+
modelProvider = excluded.modelProvider,
|
|
36622
|
+
modelId = excluded.modelId,
|
|
36623
|
+
validatorModelProvider = excluded.validatorModelProvider,
|
|
36624
|
+
validatorModelId = excluded.validatorModelId,
|
|
36625
|
+
planningModelProvider = excluded.planningModelProvider,
|
|
36626
|
+
planningModelId = excluded.planningModelId,
|
|
36627
|
+
mergeRetries = excluded.mergeRetries,
|
|
36628
|
+
workflowStepRetries = excluded.workflowStepRetries,
|
|
36629
|
+
stuckKillCount = excluded.stuckKillCount,
|
|
36630
|
+
postReviewFixCount = excluded.postReviewFixCount,
|
|
36631
|
+
recoveryRetryCount = excluded.recoveryRetryCount,
|
|
36632
|
+
taskDoneRetryCount = excluded.taskDoneRetryCount,
|
|
36633
|
+
verificationFailureCount = excluded.verificationFailureCount,
|
|
36634
|
+
mergeConflictBounceCount = excluded.mergeConflictBounceCount,
|
|
36635
|
+
nextRecoveryAt = excluded.nextRecoveryAt,
|
|
36636
|
+
error = excluded.error,
|
|
36637
|
+
summary = excluded.summary,
|
|
36638
|
+
thinkingLevel = excluded.thinkingLevel,
|
|
36639
|
+
executionMode = excluded.executionMode,
|
|
36640
|
+
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
36641
|
+
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
36642
|
+
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
36643
|
+
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
36644
|
+
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
36645
|
+
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
36646
|
+
createdAt = excluded.createdAt,
|
|
36647
|
+
updatedAt = excluded.updatedAt,
|
|
36648
|
+
columnMovedAt = excluded.columnMovedAt,
|
|
36649
|
+
executionStartedAt = excluded.executionStartedAt,
|
|
36650
|
+
executionCompletedAt = excluded.executionCompletedAt,
|
|
36651
|
+
dependencies = excluded.dependencies,
|
|
36652
|
+
steps = excluded.steps,
|
|
36653
|
+
log = excluded.log,
|
|
36654
|
+
attachments = excluded.attachments,
|
|
36655
|
+
steeringComments = excluded.steeringComments,
|
|
36656
|
+
comments = excluded.comments,
|
|
36657
|
+
review = excluded.review,
|
|
36658
|
+
reviewState = excluded.reviewState,
|
|
36659
|
+
workflowStepResults = excluded.workflowStepResults,
|
|
36660
|
+
prInfo = excluded.prInfo,
|
|
36661
|
+
issueInfo = excluded.issueInfo,
|
|
36662
|
+
githubTracking = excluded.githubTracking,
|
|
36663
|
+
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
36664
|
+
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
36665
|
+
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
36666
|
+
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
36667
|
+
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
36668
|
+
mergeDetails = excluded.mergeDetails,
|
|
36669
|
+
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
36670
|
+
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
36671
|
+
modifiedFiles = excluded.modifiedFiles,
|
|
36672
|
+
missionId = excluded.missionId,
|
|
36673
|
+
sliceId = excluded.sliceId,
|
|
36674
|
+
assignedAgentId = excluded.assignedAgentId,
|
|
36675
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
36676
|
+
assigneeUserId = excluded.assigneeUserId,
|
|
36677
|
+
nodeId = excluded.nodeId,
|
|
36678
|
+
effectiveNodeId = excluded.effectiveNodeId,
|
|
36679
|
+
effectiveNodeSource = excluded.effectiveNodeSource,
|
|
36680
|
+
sourceType = excluded.sourceType,
|
|
36681
|
+
sourceAgentId = excluded.sourceAgentId,
|
|
36682
|
+
sourceRunId = excluded.sourceRunId,
|
|
36683
|
+
sourceSessionId = excluded.sourceSessionId,
|
|
36684
|
+
sourceMessageId = excluded.sourceMessageId,
|
|
36685
|
+
sourceParentTaskId = excluded.sourceParentTaskId,
|
|
36686
|
+
sourceMetadata = excluded.sourceMetadata,
|
|
36687
|
+
checkedOutBy = excluded.checkedOutBy,
|
|
36688
|
+
checkedOutAt = excluded.checkedOutAt,
|
|
36689
|
+
checkoutNodeId = excluded.checkoutNodeId,
|
|
36690
|
+
checkoutRunId = excluded.checkoutRunId,
|
|
36691
|
+
checkoutLeaseRenewedAt = excluded.checkoutLeaseRenewedAt,
|
|
36692
|
+
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch
|
|
36693
|
+
`).run(...this.getTaskPersistValues(task));
|
|
36694
|
+
this.db.bumpLastModified();
|
|
36695
|
+
}
|
|
36696
|
+
isTaskIdConflictError(error) {
|
|
36697
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36698
|
+
return /SQLITE_CONSTRAINT|UNIQUE constraint failed: tasks\.id|PRIMARY KEY constraint failed: tasks\.id/i.test(message);
|
|
36699
|
+
}
|
|
36700
|
+
logTaskCreateConflict(task, operation, error) {
|
|
36701
|
+
storeLog.error("Refused colliding task create", {
|
|
36702
|
+
phase: "task-create:id-conflict",
|
|
36703
|
+
operation,
|
|
36704
|
+
taskId: task.id,
|
|
36705
|
+
column: task.column,
|
|
36706
|
+
sourceType: task.sourceType,
|
|
36707
|
+
error: error instanceof Error ? error.message : String(error)
|
|
36708
|
+
});
|
|
36709
|
+
}
|
|
36710
|
+
insertTaskWithFtsRecovery(task, operation) {
|
|
36711
|
+
const normalizeConflict = (error) => {
|
|
36712
|
+
this.logTaskCreateConflict(task, operation, error);
|
|
36713
|
+
throw new Error(`Task ID already exists: ${task.id}`);
|
|
36714
|
+
};
|
|
36715
|
+
try {
|
|
36716
|
+
this.insertTask(task);
|
|
36717
|
+
return;
|
|
36718
|
+
} catch (error) {
|
|
36719
|
+
if (this.isTaskIdConflictError(error)) {
|
|
36720
|
+
normalizeConflict(error);
|
|
36721
|
+
}
|
|
36722
|
+
if (!this.db.isFts5CorruptionError(error)) {
|
|
36723
|
+
throw error;
|
|
36724
|
+
}
|
|
36725
|
+
console.warn(`[fusion:store] FTS5 corruption detected during insert for task ${task.id}; rebuilding index and retrying once`);
|
|
36726
|
+
try {
|
|
36727
|
+
this.db.rebuildFts5Index();
|
|
36728
|
+
} catch (rebuildError) {
|
|
36729
|
+
console.warn("[fusion:store] FTS5 rebuild failed; propagating original insert error", rebuildError);
|
|
36730
|
+
throw error;
|
|
36731
|
+
}
|
|
36732
|
+
try {
|
|
36733
|
+
this.insertTask(task);
|
|
36734
|
+
} catch (retryError) {
|
|
36735
|
+
if (this.isTaskIdConflictError(retryError)) {
|
|
36736
|
+
normalizeConflict(retryError);
|
|
36737
|
+
}
|
|
36738
|
+
console.warn("[fusion:store] Insert retry after FTS5 rebuild failed; propagating original insert error", retryError);
|
|
36739
|
+
throw error;
|
|
36740
|
+
}
|
|
36741
|
+
}
|
|
36742
|
+
}
|
|
36336
36743
|
upsertTaskWithFtsRecovery(task) {
|
|
36337
36744
|
try {
|
|
36338
36745
|
this.upsertTask(task);
|
|
@@ -36365,6 +36772,28 @@ ${outcome}`;
|
|
|
36365
36772
|
if (!row) return void 0;
|
|
36366
36773
|
return this.rowToTask(row);
|
|
36367
36774
|
}
|
|
36775
|
+
isTaskIdPresentInArchivedTasksTable(id) {
|
|
36776
|
+
try {
|
|
36777
|
+
const row = this.db.prepare("SELECT 1 as found FROM archivedTasks WHERE id = ? LIMIT 1").get(id);
|
|
36778
|
+
return row?.found === 1;
|
|
36779
|
+
} catch {
|
|
36780
|
+
return false;
|
|
36781
|
+
}
|
|
36782
|
+
}
|
|
36783
|
+
taskIdExistsAnywhere(id) {
|
|
36784
|
+
if (this.readTaskFromDb(id)) {
|
|
36785
|
+
return true;
|
|
36786
|
+
}
|
|
36787
|
+
if (this.isTaskIdPresentInArchivedTasksTable(id)) {
|
|
36788
|
+
return true;
|
|
36789
|
+
}
|
|
36790
|
+
return this.archiveDb.get(id) !== void 0;
|
|
36791
|
+
}
|
|
36792
|
+
assertTaskIdAvailable(id) {
|
|
36793
|
+
if (this.taskIdExistsAnywhere(id)) {
|
|
36794
|
+
throw new Error(`Task ID already exists: ${id}`);
|
|
36795
|
+
}
|
|
36796
|
+
}
|
|
36368
36797
|
isTaskArchived(id) {
|
|
36369
36798
|
const row = this.db.prepare('SELECT "column" FROM tasks WHERE id = ?').get(id);
|
|
36370
36799
|
if (row) {
|
|
@@ -36592,7 +37021,16 @@ ${outcome}`;
|
|
|
36592
37021
|
await rename4(tmpPath, taskJsonPath);
|
|
36593
37022
|
}
|
|
36594
37023
|
/**
|
|
36595
|
-
* Write a task to SQLite (primary store) and also write task.json to disk
|
|
37024
|
+
* Write a brand-new task to SQLite (primary store) and also write task.json to disk
|
|
37025
|
+
* for backward compatibility and debugging. Create paths must call this variant
|
|
37026
|
+
* so duplicate IDs fail safely instead of overwriting existing rows.
|
|
37027
|
+
*/
|
|
37028
|
+
async atomicCreateTaskJson(dir, task, operation) {
|
|
37029
|
+
this.insertTaskWithFtsRecovery(task, operation);
|
|
37030
|
+
await this.writeTaskJsonFile(dir, task);
|
|
37031
|
+
}
|
|
37032
|
+
/**
|
|
37033
|
+
* Write an existing task to SQLite (primary store) and also write task.json to disk
|
|
36596
37034
|
* for backward compatibility and debugging.
|
|
36597
37035
|
*/
|
|
36598
37036
|
async atomicWriteTaskJson(dir, task) {
|
|
@@ -36608,7 +37046,7 @@ ${outcome}`;
|
|
|
36608
37046
|
* @param auditInput - Optional audit event input to record atomically with the task write
|
|
36609
37047
|
*/
|
|
36610
37048
|
async atomicWriteTaskJsonWithAudit(dir, task, auditInput) {
|
|
36611
|
-
this.db.
|
|
37049
|
+
this.db.transactionImmediate(() => {
|
|
36612
37050
|
this.upsertTaskWithFtsRecovery(task);
|
|
36613
37051
|
if (auditInput) {
|
|
36614
37052
|
const eventId = randomUUID13();
|
|
@@ -36886,6 +37324,10 @@ ${outcome}`;
|
|
|
36886
37324
|
settings: fromJson(row.settings)
|
|
36887
37325
|
};
|
|
36888
37326
|
}
|
|
37327
|
+
serializeConfigForDisk(config) {
|
|
37328
|
+
const { nextId: _deprecatedNextId, ...configForDisk } = config;
|
|
37329
|
+
return JSON.stringify(configForDisk, null, 2);
|
|
37330
|
+
}
|
|
36889
37331
|
async writeConfig(config, options) {
|
|
36890
37332
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36891
37333
|
const row = this.db.prepare("SELECT nextWorkflowStepId FROM config WHERE id = 1").get();
|
|
@@ -36893,10 +37335,14 @@ ${outcome}`;
|
|
|
36893
37335
|
const legacyWorkflowSteps = config.workflowSteps;
|
|
36894
37336
|
const workflowStepsJson = Array.isArray(legacyWorkflowSteps) ? JSON.stringify(legacyWorkflowSteps) : "[]";
|
|
36895
37337
|
this.db.prepare(
|
|
36896
|
-
`INSERT
|
|
36897
|
-
VALUES (1, ?, ?, ?,
|
|
37338
|
+
`INSERT INTO config (id, nextWorkflowStepId, settings, workflowSteps, updatedAt)
|
|
37339
|
+
VALUES (1, ?, ?, ?, ?)
|
|
37340
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
37341
|
+
nextWorkflowStepId = excluded.nextWorkflowStepId,
|
|
37342
|
+
settings = excluded.settings,
|
|
37343
|
+
workflowSteps = excluded.workflowSteps,
|
|
37344
|
+
updatedAt = excluded.updatedAt`
|
|
36898
37345
|
).run(
|
|
36899
|
-
config.nextId || 1,
|
|
36900
37346
|
nextWorkflowStepId,
|
|
36901
37347
|
JSON.stringify(config.settings || {}),
|
|
36902
37348
|
workflowStepsJson,
|
|
@@ -36905,7 +37351,7 @@ ${outcome}`;
|
|
|
36905
37351
|
this.db.bumpLastModified();
|
|
36906
37352
|
try {
|
|
36907
37353
|
const tmpPath = this.configPath + ".tmp";
|
|
36908
|
-
await writeFile6(tmpPath,
|
|
37354
|
+
await writeFile6(tmpPath, this.serializeConfigForDisk(config));
|
|
36909
37355
|
await rename4(tmpPath, this.configPath);
|
|
36910
37356
|
} catch (err) {
|
|
36911
37357
|
storeLog.warn("Backward-compat config.json sync failed after config write", {
|
|
@@ -37152,9 +37598,7 @@ ${outcome}`;
|
|
|
37152
37598
|
if (input.dependencies?.includes(id)) {
|
|
37153
37599
|
throw new Error(`Task ${id} cannot depend on itself`);
|
|
37154
37600
|
}
|
|
37155
|
-
|
|
37156
|
-
throw new Error(`Task ID already exists: ${id}`);
|
|
37157
|
-
}
|
|
37601
|
+
this.assertTaskIdAvailable(id);
|
|
37158
37602
|
const title = input.title?.trim() || void 0;
|
|
37159
37603
|
let resolvedWorkflowSteps = input.enabledWorkflowSteps?.length ? await this.resolveEnabledWorkflowSteps(input.enabledWorkflowSteps) : void 0;
|
|
37160
37604
|
if (input.enabledWorkflowSteps === void 0 && options.applyDefaultWorkflowSteps !== false) {
|
|
@@ -37250,9 +37694,9 @@ ${outcome}`;
|
|
|
37250
37694
|
createdAt: now,
|
|
37251
37695
|
updatedAt: options?.updatedAt ?? now
|
|
37252
37696
|
};
|
|
37697
|
+
this.assertTaskIdAvailable(id);
|
|
37253
37698
|
const dir = this.taskDir(id);
|
|
37254
|
-
await
|
|
37255
|
-
await this.atomicWriteTaskJson(dir, task);
|
|
37699
|
+
await this.atomicCreateTaskJson(dir, task, "createTask");
|
|
37256
37700
|
if (this.isWatching) this.taskCache.set(id, { ...task });
|
|
37257
37701
|
const prompt = options?.promptOverride ?? (task.column === "triage" ? buildBootstrapPrompt(id, task.title, task.description) : this.generateSpecifiedPrompt(task));
|
|
37258
37702
|
await mkdir7(dir, { recursive: true });
|
|
@@ -37291,9 +37735,9 @@ ${outcome}`;
|
|
|
37291
37735
|
updatedAt: now,
|
|
37292
37736
|
baseBranch: sourceTask.baseBranch
|
|
37293
37737
|
};
|
|
37738
|
+
this.assertTaskIdAvailable(newId);
|
|
37294
37739
|
const newDir = this.taskDir(newId);
|
|
37295
|
-
await
|
|
37296
|
-
await this.atomicWriteTaskJson(newDir, newTask);
|
|
37740
|
+
await this.atomicCreateTaskJson(newDir, newTask, "duplicateTask");
|
|
37297
37741
|
await mkdir7(newDir, { recursive: true });
|
|
37298
37742
|
await writeFile6(join16(newDir, "PROMPT.md"), sourceTask.prompt);
|
|
37299
37743
|
if (this.isWatching) this.taskCache.set(newId, { ...newTask });
|
|
@@ -37347,9 +37791,9 @@ Refines: ${id}`,
|
|
|
37347
37791
|
updatedAt: now,
|
|
37348
37792
|
attachments: sourceTask.attachments ? [...sourceTask.attachments] : void 0
|
|
37349
37793
|
};
|
|
37794
|
+
this.assertTaskIdAvailable(newId);
|
|
37350
37795
|
const newDir = this.taskDir(newId);
|
|
37351
|
-
await
|
|
37352
|
-
await this.atomicWriteTaskJson(newDir, newTask);
|
|
37796
|
+
await this.atomicCreateTaskJson(newDir, newTask, "refineTask");
|
|
37353
37797
|
const prompt = `# ${newTask.title}
|
|
37354
37798
|
|
|
37355
37799
|
${newTask.description}
|
|
@@ -37731,6 +38175,8 @@ ${newTask.description}
|
|
|
37731
38175
|
task.status = void 0;
|
|
37732
38176
|
task.error = void 0;
|
|
37733
38177
|
task.blockedBy = void 0;
|
|
38178
|
+
task.paused = void 0;
|
|
38179
|
+
task.pausedByAgentId = void 0;
|
|
37734
38180
|
const hasNonPendingStepProgress = task.steps.some((step) => step.status !== "pending");
|
|
37735
38181
|
const preserveStepProgress = options?.preserveResumeState || options?.preserveProgress === true && hasNonPendingStepProgress;
|
|
37736
38182
|
if (!options?.preserveWorktree) {
|
|
@@ -38402,21 +38848,23 @@ ${newTask.description}
|
|
|
38402
38848
|
target: input.target,
|
|
38403
38849
|
metadata: input.metadata
|
|
38404
38850
|
};
|
|
38405
|
-
this.db.
|
|
38406
|
-
|
|
38407
|
-
|
|
38408
|
-
|
|
38409
|
-
|
|
38410
|
-
|
|
38411
|
-
|
|
38412
|
-
|
|
38413
|
-
|
|
38414
|
-
|
|
38415
|
-
|
|
38416
|
-
|
|
38417
|
-
|
|
38418
|
-
|
|
38419
|
-
|
|
38851
|
+
this.db.transactionImmediate(() => {
|
|
38852
|
+
this.db.prepare(`
|
|
38853
|
+
INSERT INTO runAuditEvents (
|
|
38854
|
+
id, timestamp, taskId, agentId, runId, domain, mutationType, target, metadata
|
|
38855
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
38856
|
+
`).run(
|
|
38857
|
+
event.id,
|
|
38858
|
+
event.timestamp,
|
|
38859
|
+
event.taskId ?? null,
|
|
38860
|
+
event.agentId,
|
|
38861
|
+
event.runId,
|
|
38862
|
+
event.domain,
|
|
38863
|
+
event.mutationType,
|
|
38864
|
+
event.target,
|
|
38865
|
+
toJsonNullable(event.metadata)
|
|
38866
|
+
);
|
|
38867
|
+
});
|
|
38420
38868
|
return event;
|
|
38421
38869
|
}
|
|
38422
38870
|
/**
|
|
@@ -40549,6 +40997,7 @@ ${stepsSection}`;
|
|
|
40549
40997
|
if (this._db) {
|
|
40550
40998
|
this._db.close();
|
|
40551
40999
|
this._db = null;
|
|
41000
|
+
this.taskIdStateReconciled = false;
|
|
40552
41001
|
}
|
|
40553
41002
|
if (this._archiveDb) {
|
|
40554
41003
|
this._archiveDb.close();
|
|
@@ -40582,9 +41031,9 @@ ${stepsSection}`;
|
|
|
40582
41031
|
}
|
|
40583
41032
|
getDatabaseHealth() {
|
|
40584
41033
|
return {
|
|
40585
|
-
|
|
40586
|
-
|
|
40587
|
-
|
|
41034
|
+
healthy: !this.db.corruptionDetected,
|
|
41035
|
+
lastCheckedAt: this.db.integrityCheckLastRunAt ? new Date(this.db.integrityCheckLastRunAt) : null,
|
|
41036
|
+
isRunning: this.db.integrityCheckPending
|
|
40588
41037
|
};
|
|
40589
41038
|
}
|
|
40590
41039
|
getDistributedTaskIdAllocator() {
|
|
@@ -41098,7 +41547,7 @@ var init_daemon_token = __esm({
|
|
|
41098
41547
|
});
|
|
41099
41548
|
|
|
41100
41549
|
// ../core/src/pi-extensions.ts
|
|
41101
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync3, statSync as
|
|
41550
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync4, writeFileSync } from "node:fs";
|
|
41102
41551
|
import { homedir as homedir3 } from "node:os";
|
|
41103
41552
|
import { basename as basename5, isAbsolute as isAbsolute5, join as join17, relative as relative2, resolve as resolve8, sep as sep4, win32 } from "node:path";
|
|
41104
41553
|
function getHomeDir3(home) {
|
|
@@ -41188,7 +41637,7 @@ function discoverExtensionsInDir(dir, cwd, home) {
|
|
|
41188
41637
|
let isDirectory = entry.isDirectory();
|
|
41189
41638
|
if (entry.isSymbolicLink()) {
|
|
41190
41639
|
try {
|
|
41191
|
-
isDirectory =
|
|
41640
|
+
isDirectory = statSync4(entryPath).isDirectory();
|
|
41192
41641
|
} catch {
|
|
41193
41642
|
isDirectory = false;
|
|
41194
41643
|
}
|
|
@@ -49064,11 +49513,11 @@ var require_extract_zip = __commonJS({
|
|
|
49064
49513
|
var { createWriteStream, promises: fs2 } = __require("fs");
|
|
49065
49514
|
var getStream = require_get_stream();
|
|
49066
49515
|
var path2 = __require("path");
|
|
49067
|
-
var { promisify:
|
|
49516
|
+
var { promisify: promisify15 } = __require("util");
|
|
49068
49517
|
var stream = __require("stream");
|
|
49069
49518
|
var yauzl = require_yauzl();
|
|
49070
|
-
var openZip =
|
|
49071
|
-
var pipeline =
|
|
49519
|
+
var openZip = promisify15(yauzl.open);
|
|
49520
|
+
var pipeline = promisify15(stream.pipeline);
|
|
49072
49521
|
var Extractor = class {
|
|
49073
49522
|
constructor(zipPath, opts) {
|
|
49074
49523
|
this.zipPath = zipPath;
|
|
@@ -49150,7 +49599,7 @@ var require_extract_zip = __commonJS({
|
|
|
49150
49599
|
await fs2.mkdir(destDir, mkdirOptions);
|
|
49151
49600
|
if (isDir) return;
|
|
49152
49601
|
debug("opening read stream", dest);
|
|
49153
|
-
const readStream = await
|
|
49602
|
+
const readStream = await promisify15(this.zipfile.openReadStream.bind(this.zipfile))(entry);
|
|
49154
49603
|
if (symlink) {
|
|
49155
49604
|
const link = await getStream(readStream);
|
|
49156
49605
|
debug("creating symlink", link, dest);
|
|
@@ -56496,7 +56945,7 @@ var require_dist3 = __commonJS({
|
|
|
56496
56945
|
});
|
|
56497
56946
|
|
|
56498
56947
|
// ../core/src/agent-companies-parser.ts
|
|
56499
|
-
import { existsSync as existsSync19, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync, statSync as
|
|
56948
|
+
import { existsSync as existsSync19, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync, statSync as statSync5 } from "node:fs";
|
|
56500
56949
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
56501
56950
|
import { isAbsolute as isAbsolute7, join as join22, normalize as normalize3, resolve as resolve11 } from "node:path";
|
|
56502
56951
|
function slugifyAgentReference(value) {
|
|
@@ -56772,7 +57221,7 @@ function parseCompanyDirectory(dirPath) {
|
|
|
56772
57221
|
if (!existsSync19(resolvedPath)) {
|
|
56773
57222
|
throw new AgentCompaniesParseError(`Company directory does not exist: ${resolvedPath}`);
|
|
56774
57223
|
}
|
|
56775
|
-
if (!
|
|
57224
|
+
if (!statSync5(resolvedPath).isDirectory()) {
|
|
56776
57225
|
throw new AgentCompaniesParseError(`Company path is not a directory: ${resolvedPath}`);
|
|
56777
57226
|
}
|
|
56778
57227
|
const companyPath = join22(resolvedPath, "COMPANY.md");
|
|
@@ -56811,12 +57260,12 @@ function resolveExtractionRoot(tempDir) {
|
|
|
56811
57260
|
return tempDir;
|
|
56812
57261
|
}
|
|
56813
57262
|
async function extractTarArchive(archivePath, outputDir) {
|
|
56814
|
-
const [{ execFile:
|
|
57263
|
+
const [{ execFile: execFile9 }, { promisify: promisify15 }] = await Promise.all([
|
|
56815
57264
|
import("node:child_process"),
|
|
56816
57265
|
import("node:util")
|
|
56817
57266
|
]);
|
|
56818
|
-
const
|
|
56819
|
-
await
|
|
57267
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
57268
|
+
await execFileAsync7("tar", ["xzf", archivePath, "-C", outputDir]);
|
|
56820
57269
|
}
|
|
56821
57270
|
function sanitizeCompanySubPath(subPath) {
|
|
56822
57271
|
const trimmed = subPath.trim();
|
|
@@ -59369,15 +59818,15 @@ function evaluateAgentActionGate(params) {
|
|
|
59369
59818
|
let resourceId;
|
|
59370
59819
|
if (params.toolName === "bash") {
|
|
59371
59820
|
const command = extractShellCommand(args);
|
|
59372
|
-
const
|
|
59373
|
-
if (
|
|
59821
|
+
const git2 = classifyGitCommand(command);
|
|
59822
|
+
if (git2?.write) {
|
|
59374
59823
|
category = "git_write";
|
|
59375
|
-
operation =
|
|
59824
|
+
operation = git2.operation;
|
|
59376
59825
|
resourceType = "git";
|
|
59377
59826
|
} else {
|
|
59378
59827
|
category = "command_execution";
|
|
59379
|
-
operation =
|
|
59380
|
-
resourceType =
|
|
59828
|
+
operation = git2?.operation ?? "shell command";
|
|
59829
|
+
resourceType = git2 ? "git" : "command";
|
|
59381
59830
|
}
|
|
59382
59831
|
} else if (params.toolName === "write" || params.toolName === "edit") {
|
|
59383
59832
|
category = "file_write_delete";
|
|
@@ -61236,6 +61685,71 @@ _... existing specification middle trimmed ..._
|
|
|
61236
61685
|
|
|
61237
61686
|
${tail}`;
|
|
61238
61687
|
}
|
|
61688
|
+
function extractMarkdownSection(document2, headingName) {
|
|
61689
|
+
const heading = `## ${headingName}`;
|
|
61690
|
+
const start = document2.indexOf(heading);
|
|
61691
|
+
if (start === -1) {
|
|
61692
|
+
return "";
|
|
61693
|
+
}
|
|
61694
|
+
const afterHeading = start + heading.length;
|
|
61695
|
+
const nextH2 = document2.indexOf("\n## ", afterHeading);
|
|
61696
|
+
const nextH1 = document2.indexOf("\n# ", afterHeading);
|
|
61697
|
+
const endCandidates = [nextH2, nextH1].filter((value) => value !== -1);
|
|
61698
|
+
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : document2.length;
|
|
61699
|
+
return document2.slice(start, end).trim();
|
|
61700
|
+
}
|
|
61701
|
+
function compactTaskPromptStepsSection(section) {
|
|
61702
|
+
const stepTitles = Array.from(section.matchAll(/^### Step \d+:.*$/gm), (match) => match[0].trim());
|
|
61703
|
+
if (stepTitles.length === 0) {
|
|
61704
|
+
return section.trim();
|
|
61705
|
+
}
|
|
61706
|
+
return [
|
|
61707
|
+
"## Steps",
|
|
61708
|
+
...stepTitles,
|
|
61709
|
+
"",
|
|
61710
|
+
"_... step checklist details trimmed for context limits ..._"
|
|
61711
|
+
].join("\n").trim();
|
|
61712
|
+
}
|
|
61713
|
+
function truncateCompactedSection(section, maxChars, label) {
|
|
61714
|
+
const trimmed = section.trim();
|
|
61715
|
+
if (!trimmed || trimmed.length <= maxChars) {
|
|
61716
|
+
return trimmed;
|
|
61717
|
+
}
|
|
61718
|
+
const marker = `_... ${label} trimmed for context limits ..._`;
|
|
61719
|
+
const headBudget = Math.max(200, maxChars - marker.length - 2);
|
|
61720
|
+
return [
|
|
61721
|
+
`${trimmed.slice(0, headBudget).trimEnd()}\u2026`,
|
|
61722
|
+
"",
|
|
61723
|
+
marker
|
|
61724
|
+
].join("\n").trim();
|
|
61725
|
+
}
|
|
61726
|
+
function compactTaskPromptSectionBody(body) {
|
|
61727
|
+
const trimmed = body.trim();
|
|
61728
|
+
if (trimmed.length <= MAX_COMPACTED_TASK_PROMPT_CHARS) {
|
|
61729
|
+
return trimmed;
|
|
61730
|
+
}
|
|
61731
|
+
const fencedMatch = /^```markdown\s*\n([\s\S]*?)\n```$/m.exec(trimmed);
|
|
61732
|
+
const promptContent = fencedMatch ? fencedMatch[1].trim() : trimmed;
|
|
61733
|
+
const firstSectionIndex = promptContent.indexOf("\n## ");
|
|
61734
|
+
const preamble = (firstSectionIndex === -1 ? promptContent : promptContent.slice(0, firstSectionIndex)).trim();
|
|
61735
|
+
const missionSection = extractMarkdownSection(promptContent, "Mission");
|
|
61736
|
+
const dependenciesSection = extractMarkdownSection(promptContent, "Dependencies");
|
|
61737
|
+
const fileScopeSection = extractMarkdownSection(promptContent, "File Scope");
|
|
61738
|
+
const stepsSection = compactTaskPromptStepsSection(extractMarkdownSection(promptContent, "Steps"));
|
|
61739
|
+
const compactedContent = [
|
|
61740
|
+
truncateCompactedSection(preamble, 400, "task header"),
|
|
61741
|
+
truncateCompactedSection(missionSection, 900, "mission"),
|
|
61742
|
+
truncateCompactedSection(dependenciesSection, 500, "dependencies"),
|
|
61743
|
+
truncateCompactedSection(fileScopeSection, 1e3, "file scope"),
|
|
61744
|
+
truncateCompactedSection(stepsSection, 1200, "steps outline"),
|
|
61745
|
+
"_... remaining PROMPT.md sections trimmed for context limits ..._"
|
|
61746
|
+
].filter(Boolean).join("\n\n").trim();
|
|
61747
|
+
const narrowedContent = compactedContent.length <= MAX_COMPACTED_TASK_PROMPT_CHARS ? compactedContent : compactExistingSpecificationSectionBody(compactedContent);
|
|
61748
|
+
const finalContent = fencedMatch ? `\`\`\`markdown
|
|
61749
|
+
${narrowedContent}
|
|
61750
|
+
\`\`\`` : narrowedContent;
|
|
61751
|
+
return finalContent.length < trimmed.length ? finalContent : compactExistingSpecificationSectionBody(trimmed);
|
|
61752
|
+
}
|
|
61239
61753
|
function compactUserCommentsSectionBody(body) {
|
|
61240
61754
|
const trimmed = body.trim();
|
|
61241
61755
|
if (trimmed.length <= MAX_COMPACTED_USER_COMMENTS_CHARS) {
|
|
@@ -61268,7 +61782,7 @@ function compactUserCommentsSectionBody(body) {
|
|
|
61268
61782
|
].join("\n").trim();
|
|
61269
61783
|
}
|
|
61270
61784
|
function compactLargePromptSections(prompt) {
|
|
61271
|
-
const sectionPattern = /(^|\n)(## (?:Subtask Consideration|Subtask Breakdown Requested|Attachments|Existing Specification|User Comments)\n)([\s\S]*?)(?=\n## [^#]|\n# [^#]|$)/g;
|
|
61785
|
+
const sectionPattern = /(^|\n)(## (?:Subtask Consideration|Subtask Breakdown Requested|Attachments|Existing Specification|Task PROMPT\.md|User Comments)\n)((?:\n*```markdown[\s\S]*?\n```|[\s\S]*?))(?=\n## [^#]|\n# [^#]|$)/g;
|
|
61272
61786
|
let changed = false;
|
|
61273
61787
|
const compactedPrompt = prompt.replace(sectionPattern, (match, prefix, heading, body) => {
|
|
61274
61788
|
const headingName = heading.trim().replace(/^##\s+/, "");
|
|
@@ -61278,6 +61792,7 @@ function compactLargePromptSections(prompt) {
|
|
|
61278
61792
|
"Subtask Breakdown Requested": MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS,
|
|
61279
61793
|
Attachments: MAX_COMPACTED_ATTACHMENTS_CHARS,
|
|
61280
61794
|
"Existing Specification": MAX_COMPACTED_EXISTING_SPEC_CHARS,
|
|
61795
|
+
"Task PROMPT.md": MAX_COMPACTED_TASK_PROMPT_CHARS,
|
|
61281
61796
|
"User Comments": MAX_COMPACTED_USER_COMMENTS_CHARS
|
|
61282
61797
|
};
|
|
61283
61798
|
const maxChars = maxByHeading[headingName] ?? MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
@@ -61291,6 +61806,8 @@ function compactLargePromptSections(prompt) {
|
|
|
61291
61806
|
compactedBody = compactAttachmentSectionBody(trimmedBody);
|
|
61292
61807
|
} else if (headingName === "Existing Specification") {
|
|
61293
61808
|
compactedBody = compactExistingSpecificationSectionBody(trimmedBody);
|
|
61809
|
+
} else if (headingName === "Task PROMPT.md") {
|
|
61810
|
+
compactedBody = compactTaskPromptSectionBody(trimmedBody);
|
|
61294
61811
|
} else if (headingName === "User Comments") {
|
|
61295
61812
|
compactedBody = compactUserCommentsSectionBody(trimmedBody);
|
|
61296
61813
|
}
|
|
@@ -62315,7 +62832,7 @@ async function createFnAgent2(options) {
|
|
|
62315
62832
|
});
|
|
62316
62833
|
return { session: promptableSession, sessionFile: promptableSession.sessionFile };
|
|
62317
62834
|
}
|
|
62318
|
-
var execAsync, hostExtensionPaths, FN_MEMORY_APPEND_TOOL_NAME, FUSION_SHUTDOWN_WRAP_FLAG, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS, MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS, MAX_COMPACTED_ATTACHMENTS_CHARS, MAX_COMPACTED_EXISTING_SPEC_CHARS, MAX_COMPACTED_USER_COMMENTS_CHARS, GATE_BYPASS_TOOL_NAMES;
|
|
62835
|
+
var execAsync, hostExtensionPaths, FN_MEMORY_APPEND_TOOL_NAME, FUSION_SHUTDOWN_WRAP_FLAG, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS, MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS, MAX_COMPACTED_ATTACHMENTS_CHARS, MAX_COMPACTED_EXISTING_SPEC_CHARS, MAX_COMPACTED_TASK_PROMPT_CHARS, MAX_COMPACTED_USER_COMMENTS_CHARS, GATE_BYPASS_TOOL_NAMES;
|
|
62319
62836
|
var init_pi = __esm({
|
|
62320
62837
|
"../engine/src/pi.ts"() {
|
|
62321
62838
|
"use strict";
|
|
@@ -62341,6 +62858,7 @@ var init_pi = __esm({
|
|
|
62341
62858
|
MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS = 1200;
|
|
62342
62859
|
MAX_COMPACTED_ATTACHMENTS_CHARS = 4e3;
|
|
62343
62860
|
MAX_COMPACTED_EXISTING_SPEC_CHARS = 4e3;
|
|
62861
|
+
MAX_COMPACTED_TASK_PROMPT_CHARS = MAX_COMPACTED_EXISTING_SPEC_CHARS;
|
|
62344
62862
|
MAX_COMPACTED_USER_COMMENTS_CHARS = 2e3;
|
|
62345
62863
|
GATE_BYPASS_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
62346
62864
|
"fn_heartbeat_done",
|
|
@@ -63609,7 +64127,7 @@ var init_research_step_runner = __esm({
|
|
|
63609
64127
|
// ../engine/src/agent-tools.ts
|
|
63610
64128
|
import { appendFile as appendFile3, mkdir as mkdir12, readFile as readFile14, readdir as readdir8, stat as stat5, writeFile as writeFile11 } from "node:fs/promises";
|
|
63611
64129
|
import { existsSync as existsSync24 } from "node:fs";
|
|
63612
|
-
import { createHash as
|
|
64130
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
63613
64131
|
import { join as join30, relative as relative6, resolve as resolve16 } from "node:path";
|
|
63614
64132
|
import { Type } from "@mariozechner/pi-ai";
|
|
63615
64133
|
function sanitizeAgentMemoryId(agentId) {
|
|
@@ -63652,7 +64170,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
|
|
|
63652
64170
|
}
|
|
63653
64171
|
}
|
|
63654
64172
|
function qmdAgentMemoryCollectionName2(rootDir, agentId) {
|
|
63655
|
-
const hash =
|
|
64173
|
+
const hash = createHash6("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
|
|
63656
64174
|
return `fusion-agent-memory-${sanitizeAgentMemoryId(agentId).toLowerCase()}-${hash}`;
|
|
63657
64175
|
}
|
|
63658
64176
|
function buildQmdAgentMemoryCollectionAddArgs(rootDir, agentId) {
|
|
@@ -63781,11 +64299,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
|
|
|
63781
64299
|
return;
|
|
63782
64300
|
}
|
|
63783
64301
|
const promise = (async () => {
|
|
63784
|
-
const { execFile:
|
|
63785
|
-
const { promisify:
|
|
63786
|
-
const
|
|
64302
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
64303
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
64304
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
63787
64305
|
try {
|
|
63788
|
-
await
|
|
64306
|
+
await execFileAsync7("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
|
|
63789
64307
|
cwd: rootDir,
|
|
63790
64308
|
timeout: 4e3,
|
|
63791
64309
|
maxBuffer: 512 * 1024
|
|
@@ -63798,8 +64316,8 @@ ${stderr}`)) {
|
|
|
63798
64316
|
throw error;
|
|
63799
64317
|
}
|
|
63800
64318
|
}
|
|
63801
|
-
await
|
|
63802
|
-
await
|
|
64319
|
+
await execFileAsync7("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
64320
|
+
await execFileAsync7("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
63803
64321
|
})();
|
|
63804
64322
|
agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
63805
64323
|
try {
|
|
@@ -63854,10 +64372,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
|
|
|
63854
64372
|
}
|
|
63855
64373
|
try {
|
|
63856
64374
|
await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
|
|
63857
|
-
const { execFile:
|
|
63858
|
-
const { promisify:
|
|
63859
|
-
const
|
|
63860
|
-
const { stdout } = await
|
|
64375
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
64376
|
+
const { promisify: promisify15 } = await import("node:util");
|
|
64377
|
+
const execFileAsync7 = promisify15(execFile9);
|
|
64378
|
+
const { stdout } = await execFileAsync7("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
|
|
63861
64379
|
cwd: rootDir,
|
|
63862
64380
|
timeout: 4e3,
|
|
63863
64381
|
maxBuffer: 1024 * 1024
|
|
@@ -63936,24 +64454,36 @@ function createTaskCreateTool(store, provenance, options) {
|
|
|
63936
64454
|
description: "Create a new task for out-of-scope work discovered during execution. The task goes into triage where it will be specified by the AI. Optionally set dependencies (e.g., the new task depends on the current one, or the current task should wait for the new one).",
|
|
63937
64455
|
parameters: taskCreateParams,
|
|
63938
64456
|
execute: async (_id, params) => {
|
|
63939
|
-
|
|
63940
|
-
|
|
63941
|
-
|
|
63942
|
-
|
|
63943
|
-
|
|
63944
|
-
|
|
63945
|
-
|
|
63946
|
-
|
|
63947
|
-
|
|
63948
|
-
|
|
63949
|
-
|
|
63950
|
-
|
|
63951
|
-
|
|
63952
|
-
|
|
63953
|
-
|
|
63954
|
-
|
|
63955
|
-
|
|
63956
|
-
|
|
64457
|
+
try {
|
|
64458
|
+
const task = await createAgentTask(store, {
|
|
64459
|
+
description: params.description,
|
|
64460
|
+
dependencies: params.dependencies,
|
|
64461
|
+
column: "triage",
|
|
64462
|
+
priority: params.priority,
|
|
64463
|
+
source: provenance ? {
|
|
64464
|
+
sourceType: provenance.sourceType,
|
|
64465
|
+
sourceAgentId: provenance.sourceAgentId,
|
|
64466
|
+
sourceRunId: provenance.sourceRunId
|
|
64467
|
+
} : void 0
|
|
64468
|
+
}, options);
|
|
64469
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
64470
|
+
return {
|
|
64471
|
+
content: [{
|
|
64472
|
+
type: "text",
|
|
64473
|
+
text: `Created ${task.id}: ${params.description}${deps}`
|
|
64474
|
+
}],
|
|
64475
|
+
details: { taskId: task.id }
|
|
64476
|
+
};
|
|
64477
|
+
} catch (err) {
|
|
64478
|
+
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
|
64479
|
+
return {
|
|
64480
|
+
content: [{ type: "text", text: `ERROR: ${err.message}` }],
|
|
64481
|
+
details: {},
|
|
64482
|
+
isError: true
|
|
64483
|
+
};
|
|
64484
|
+
}
|
|
64485
|
+
throw err;
|
|
64486
|
+
}
|
|
63957
64487
|
}
|
|
63958
64488
|
};
|
|
63959
64489
|
}
|
|
@@ -64864,24 +65394,35 @@ function createDelegateTaskTool(agentStore, taskStore, options) {
|
|
|
64864
65394
|
details: {}
|
|
64865
65395
|
};
|
|
64866
65396
|
}
|
|
64867
|
-
|
|
64868
|
-
|
|
64869
|
-
|
|
64870
|
-
|
|
64871
|
-
|
|
64872
|
-
|
|
64873
|
-
|
|
64874
|
-
|
|
65397
|
+
try {
|
|
65398
|
+
const task = await createAgentTask(taskStore, {
|
|
65399
|
+
description: params.description,
|
|
65400
|
+
dependencies: params.dependencies,
|
|
65401
|
+
column: "todo",
|
|
65402
|
+
assignedAgentId: params.agent_id,
|
|
65403
|
+
source: {
|
|
65404
|
+
sourceType: "api",
|
|
65405
|
+
...override ? { sourceMetadata: { executorRoleOverride: true } } : {}
|
|
65406
|
+
}
|
|
65407
|
+
}, options);
|
|
65408
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
65409
|
+
return {
|
|
65410
|
+
content: [{
|
|
65411
|
+
type: "text",
|
|
65412
|
+
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. The task will be picked up by ${agent.name} on their next heartbeat cycle.`
|
|
65413
|
+
}],
|
|
65414
|
+
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
65415
|
+
};
|
|
65416
|
+
} catch (err) {
|
|
65417
|
+
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
|
65418
|
+
return {
|
|
65419
|
+
content: [{ type: "text", text: `ERROR: ${err.message}` }],
|
|
65420
|
+
details: {},
|
|
65421
|
+
isError: true
|
|
65422
|
+
};
|
|
64875
65423
|
}
|
|
64876
|
-
|
|
64877
|
-
|
|
64878
|
-
return {
|
|
64879
|
-
content: [{
|
|
64880
|
-
type: "text",
|
|
64881
|
-
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. The task will be picked up by ${agent.name} on their next heartbeat cycle.`
|
|
64882
|
-
}],
|
|
64883
|
-
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
64884
|
-
};
|
|
65424
|
+
throw err;
|
|
65425
|
+
}
|
|
64885
65426
|
}
|
|
64886
65427
|
};
|
|
64887
65428
|
}
|
|
@@ -65221,7 +65762,7 @@ ${lines.join("\n")}`
|
|
|
65221
65762
|
}
|
|
65222
65763
|
};
|
|
65223
65764
|
}
|
|
65224
|
-
var taskCreateParams, taskLogParams, taskDocumentWriteParams, taskDocumentReadParams, reflectOnPerformanceParams, readEvaluationsParams, updateIdentityParams, listAgentsParams, delegateTaskParams, getAgentConfigParams, updateAgentConfigParams, createAgentParams, deleteAgentParams, sendMessageParams, readMessagesParams, memorySearchParams, memoryGetParams, webFetchParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, memoryAppendParams, log10, MAX_INSTRUCTIONS_TEXT_LENGTH, MAX_MEMORY_LENGTH, MAX_SOUL_LENGTH, AGENT_MEMORY_ROOT3, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2;
|
|
65765
|
+
var TASK_CREATE_PRIORITY_VALUES, taskCreateParams, taskLogParams, taskDocumentWriteParams, taskDocumentReadParams, reflectOnPerformanceParams, readEvaluationsParams, updateIdentityParams, listAgentsParams, delegateTaskParams, getAgentConfigParams, updateAgentConfigParams, createAgentParams, deleteAgentParams, sendMessageParams, readMessagesParams, memorySearchParams, memoryGetParams, webFetchParams, researchRunParams, researchListParams, researchGetParams, researchCancelParams, memoryAppendParams, log10, MAX_INSTRUCTIONS_TEXT_LENGTH, MAX_MEMORY_LENGTH, MAX_SOUL_LENGTH, AGENT_MEMORY_ROOT3, AGENT_MEMORY_FILENAME2, AGENT_DREAMS_FILENAME2, agentQmdRefreshState, AGENT_QMD_REFRESH_INTERVAL_MS, DAILY_AGENT_MEMORY_RE2;
|
|
65225
65766
|
var init_agent_tools = __esm({
|
|
65226
65767
|
"../engine/src/agent-tools.ts"() {
|
|
65227
65768
|
"use strict";
|
|
@@ -65232,10 +65773,16 @@ var init_agent_tools = __esm({
|
|
|
65232
65773
|
init_logger2();
|
|
65233
65774
|
init_web_fetch();
|
|
65234
65775
|
init_agent_action_gate();
|
|
65776
|
+
TASK_CREATE_PRIORITY_VALUES = ["low", "normal", "high", "urgent"];
|
|
65235
65777
|
taskCreateParams = Type.Object({
|
|
65236
65778
|
description: Type.String({ description: "What needs to be done" }),
|
|
65237
65779
|
dependencies: Type.Optional(
|
|
65238
65780
|
Type.Array(Type.String(), { description: 'Task IDs this new task depends on (e.g. ["KB-001"])' })
|
|
65781
|
+
),
|
|
65782
|
+
priority: Type.Optional(
|
|
65783
|
+
Type.Union(TASK_CREATE_PRIORITY_VALUES.map((priority) => Type.Literal(priority)), {
|
|
65784
|
+
description: "Task priority (low, normal, high, urgent)"
|
|
65785
|
+
})
|
|
65239
65786
|
)
|
|
65240
65787
|
});
|
|
65241
65788
|
taskLogParams = Type.Object({
|
|
@@ -66473,6 +67020,7 @@ var init_ntfy_provider = __esm({
|
|
|
66473
67020
|
);
|
|
66474
67021
|
const response = await sendNtfyNotificationWithResult({
|
|
66475
67022
|
ntfyBaseUrl: this.config.ntfyBaseUrl,
|
|
67023
|
+
ntfyAccessToken: this.config.ntfyAccessToken,
|
|
66476
67024
|
topic: this.config.topic,
|
|
66477
67025
|
title: content.title,
|
|
66478
67026
|
message: content.message,
|
|
@@ -66777,7 +67325,7 @@ var init_notification_service = __esm({
|
|
|
66777
67325
|
handleSettingsUpdated = async (data) => {
|
|
66778
67326
|
const { settings, previous } = data;
|
|
66779
67327
|
this.setNotificationsEnabledFromSettings(settings);
|
|
66780
|
-
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyBaseUrl !== previous.ntfyBaseUrl || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
67328
|
+
if (settings.ntfyEnabled !== previous.ntfyEnabled || settings.ntfyTopic !== previous.ntfyTopic || settings.ntfyBaseUrl !== previous.ntfyBaseUrl || settings.ntfyAccessToken !== previous.ntfyAccessToken || settings.ntfyDashboardHost !== previous.ntfyDashboardHost || JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
66781
67329
|
const wasEnabled = Boolean(previous.ntfyEnabled && previous.ntfyTopic);
|
|
66782
67330
|
const isEnabled = Boolean(settings.ntfyEnabled && settings.ntfyTopic);
|
|
66783
67331
|
await this.syncNtfyProvider(settings);
|
|
@@ -66789,6 +67337,8 @@ var init_notification_service = __esm({
|
|
|
66789
67337
|
schedulerLog.log("NotificationService ntfy topic updated");
|
|
66790
67338
|
} else if (settings.ntfyBaseUrl !== previous.ntfyBaseUrl) {
|
|
66791
67339
|
schedulerLog.log("NotificationService ntfy base URL updated");
|
|
67340
|
+
} else if (settings.ntfyAccessToken !== previous.ntfyAccessToken) {
|
|
67341
|
+
schedulerLog.log("NotificationService ntfy access token updated");
|
|
66792
67342
|
} else if (settings.ntfyDashboardHost !== previous.ntfyDashboardHost) {
|
|
66793
67343
|
schedulerLog.log("NotificationService ntfy dashboard host updated");
|
|
66794
67344
|
} else if (JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
@@ -66817,6 +67367,7 @@ var init_notification_service = __esm({
|
|
|
66817
67367
|
await this.ntfyProvider.initialize?.({
|
|
66818
67368
|
topic: settings.ntfyTopic,
|
|
66819
67369
|
ntfyBaseUrl: settings.ntfyBaseUrl ?? this.options.ntfyBaseUrl,
|
|
67370
|
+
ntfyAccessToken: settings.ntfyAccessToken,
|
|
66820
67371
|
dashboardHost: settings.ntfyDashboardHost,
|
|
66821
67372
|
events: settings.ntfyEvents ?? [...DEFAULT_NTFY_EVENTS],
|
|
66822
67373
|
projectId: this.options.projectId
|
|
@@ -67023,6 +67574,7 @@ function buildNtfyClickUrl(options) {
|
|
|
67023
67574
|
}
|
|
67024
67575
|
async function sendNtfyNotificationWithResult({
|
|
67025
67576
|
ntfyBaseUrl,
|
|
67577
|
+
ntfyAccessToken,
|
|
67026
67578
|
topic,
|
|
67027
67579
|
title,
|
|
67028
67580
|
message,
|
|
@@ -67039,6 +67591,10 @@ async function sendNtfyNotificationWithResult({
|
|
|
67039
67591
|
if (clickUrl) {
|
|
67040
67592
|
headers.Click = clickUrl;
|
|
67041
67593
|
}
|
|
67594
|
+
const trimmedToken = ntfyAccessToken?.trim();
|
|
67595
|
+
if (trimmedToken) {
|
|
67596
|
+
headers.Authorization = `Bearer ${trimmedToken}`;
|
|
67597
|
+
}
|
|
67042
67598
|
const resolvedBaseUrl = resolveNtfyBaseUrl(ntfyBaseUrl);
|
|
67043
67599
|
const response = await fetch(`${resolvedBaseUrl}/${topic}`, {
|
|
67044
67600
|
method: "POST",
|
|
@@ -67354,9 +67910,33 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
67354
67910
|
this.reason = reason;
|
|
67355
67911
|
}
|
|
67356
67912
|
}
|
|
67357
|
-
|
|
67358
|
-
|
|
67359
|
-
|
|
67913
|
+
const activeSessions = /* @__PURE__ */ new Set();
|
|
67914
|
+
let reviewText = "";
|
|
67915
|
+
const endSession = (session2) => {
|
|
67916
|
+
if (!activeSessions.delete(session2)) {
|
|
67917
|
+
return;
|
|
67918
|
+
}
|
|
67919
|
+
session2.dispose();
|
|
67920
|
+
options.onSessionEnded?.(session2);
|
|
67921
|
+
};
|
|
67922
|
+
const buildPauseUnavailableResult = async (reason) => {
|
|
67923
|
+
reviewerLog.log(
|
|
67924
|
+
`${taskId}: ${reviewType} review for Step ${stepNumber} aborted before spawn \u2014 ${reason} active`
|
|
67925
|
+
);
|
|
67926
|
+
if (options.store && options.taskId) {
|
|
67927
|
+
await options.store.logEntry(
|
|
67928
|
+
options.taskId,
|
|
67929
|
+
`${reviewType} review aborted before spawn \u2014 ${reason} active`
|
|
67930
|
+
).catch(() => void 0);
|
|
67931
|
+
}
|
|
67932
|
+
return {
|
|
67933
|
+
verdict: "UNAVAILABLE",
|
|
67934
|
+
review: `${reason} active \u2014 reviewer not spawned. Stop calling fn_review_* and exit cleanly; the parent task will resume after unpause.`,
|
|
67935
|
+
summary: `Skipped: ${reason}`
|
|
67936
|
+
};
|
|
67937
|
+
};
|
|
67938
|
+
const createReviewerSession = async () => {
|
|
67939
|
+
const { session: session2 } = await createResolvedAgentSession({
|
|
67360
67940
|
sessionPurpose: "reviewer",
|
|
67361
67941
|
runtimeHint: extractRuntimeHint(memoryAgent?.runtimeConfig),
|
|
67362
67942
|
pluginRunner: options.pluginRunner,
|
|
@@ -67374,7 +67954,6 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
67374
67954
|
fallbackProvider: validatorFallbackProvider,
|
|
67375
67955
|
fallbackModelId: validatorFallbackModelId,
|
|
67376
67956
|
defaultThinkingLevel: options.defaultThinkingLevel,
|
|
67377
|
-
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
67378
67957
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
67379
67958
|
taskId: options.taskId,
|
|
67380
67959
|
taskTitle: options.taskTitle,
|
|
@@ -67398,52 +67977,138 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
67398
67977
|
throw new ReviewerPauseAbortError(reason);
|
|
67399
67978
|
}
|
|
67400
67979
|
}
|
|
67401
|
-
})
|
|
67980
|
+
});
|
|
67981
|
+
const reviewerModelDesc = describeModel(session2);
|
|
67982
|
+
const reviewerModelMarker = `Reviewer using model: ${reviewerModelDesc}`;
|
|
67983
|
+
reviewerLog.log(`${taskId}: reviewer using model ${reviewerModelDesc}`);
|
|
67984
|
+
if (options.store && options.taskId) {
|
|
67985
|
+
await options.store.logEntry(options.taskId, reviewerModelMarker);
|
|
67986
|
+
await options.store.appendAgentLog(options.taskId, reviewerModelMarker, "text", void 0, "reviewer").catch(() => void 0);
|
|
67987
|
+
}
|
|
67988
|
+
activeSessions.add(session2);
|
|
67989
|
+
options.onSessionCreated?.(session2);
|
|
67990
|
+
session2.subscribe((event) => {
|
|
67991
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
67992
|
+
reviewText += event.assistantMessageEvent.delta;
|
|
67993
|
+
}
|
|
67994
|
+
});
|
|
67995
|
+
return session2;
|
|
67996
|
+
};
|
|
67997
|
+
const runReviewPrompt = async (session2, prompt) => {
|
|
67998
|
+
await promptWithFallback(session2, prompt);
|
|
67999
|
+
checkSessionError(session2);
|
|
68000
|
+
};
|
|
68001
|
+
let session;
|
|
68002
|
+
try {
|
|
68003
|
+
session = await createReviewerSession();
|
|
67402
68004
|
} catch (err) {
|
|
67403
68005
|
if (err instanceof ReviewerPauseAbortError) {
|
|
67404
|
-
|
|
67405
|
-
`${taskId}: ${reviewType} review for Step ${stepNumber} aborted before spawn \u2014 ${err.reason} active`
|
|
67406
|
-
);
|
|
67407
|
-
if (options.store && options.taskId) {
|
|
67408
|
-
await options.store.logEntry(
|
|
67409
|
-
options.taskId,
|
|
67410
|
-
`${reviewType} review aborted before spawn \u2014 ${err.reason} active`
|
|
67411
|
-
).catch(() => void 0);
|
|
67412
|
-
}
|
|
67413
|
-
return {
|
|
67414
|
-
verdict: "UNAVAILABLE",
|
|
67415
|
-
review: `${err.reason} active \u2014 reviewer not spawned. Stop calling fn_review_* and exit cleanly; the parent task will resume after unpause.`,
|
|
67416
|
-
summary: `Skipped: ${err.reason}`
|
|
67417
|
-
};
|
|
68006
|
+
return buildPauseUnavailableResult(err.reason);
|
|
67418
68007
|
}
|
|
67419
68008
|
throw err;
|
|
67420
68009
|
}
|
|
67421
|
-
const reviewerModelDesc = describeModel(session);
|
|
67422
|
-
const reviewerModelMarker = `Reviewer using model: ${reviewerModelDesc}`;
|
|
67423
|
-
reviewerLog.log(`${taskId}: reviewer using model ${reviewerModelDesc}`);
|
|
67424
|
-
if (options.store && options.taskId) {
|
|
67425
|
-
await options.store.logEntry(options.taskId, reviewerModelMarker);
|
|
67426
|
-
await options.store.appendAgentLog(options.taskId, reviewerModelMarker, "text", void 0, "reviewer").catch(() => void 0);
|
|
67427
|
-
}
|
|
67428
|
-
options.onSessionCreated?.(session);
|
|
67429
|
-
let reviewText = "";
|
|
67430
|
-
session.subscribe((event) => {
|
|
67431
|
-
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
67432
|
-
reviewText += event.assistantMessageEvent.delta;
|
|
67433
|
-
}
|
|
67434
|
-
});
|
|
67435
68010
|
try {
|
|
67436
|
-
|
|
67437
|
-
|
|
68011
|
+
try {
|
|
68012
|
+
await runReviewPrompt(session, request2);
|
|
68013
|
+
} catch (err) {
|
|
68014
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
68015
|
+
if (!isContextLimitError(errorMessage)) {
|
|
68016
|
+
throw err;
|
|
68017
|
+
}
|
|
68018
|
+
const retryLogMessage = reviewType === "code" ? "code review hit context limit \u2014 retrying with compacted request" : `${reviewType} review hit context limit \u2014 retrying with compacted request`;
|
|
68019
|
+
reviewerLog.warn(`${taskId}: ${retryLogMessage}`);
|
|
68020
|
+
if (options.store && options.taskId) {
|
|
68021
|
+
await options.store.logEntry(options.taskId, retryLogMessage).catch(() => void 0);
|
|
68022
|
+
}
|
|
68023
|
+
reviewText = "";
|
|
68024
|
+
const reducedRequest = buildReducedReviewRequest(
|
|
68025
|
+
taskId,
|
|
68026
|
+
stepNumber,
|
|
68027
|
+
stepName,
|
|
68028
|
+
reviewType,
|
|
68029
|
+
promptContent,
|
|
68030
|
+
cwd,
|
|
68031
|
+
baseline
|
|
68032
|
+
);
|
|
68033
|
+
try {
|
|
68034
|
+
await runReviewPrompt(session, reducedRequest);
|
|
68035
|
+
} catch (retryErr) {
|
|
68036
|
+
if (!isReviewerSessionReuseError(retryErr)) {
|
|
68037
|
+
throw retryErr;
|
|
68038
|
+
}
|
|
68039
|
+
endSession(session);
|
|
68040
|
+
try {
|
|
68041
|
+
session = await createReviewerSession();
|
|
68042
|
+
} catch (recreateErr) {
|
|
68043
|
+
if (recreateErr instanceof ReviewerPauseAbortError) {
|
|
68044
|
+
return buildPauseUnavailableResult(recreateErr.reason);
|
|
68045
|
+
}
|
|
68046
|
+
throw recreateErr;
|
|
68047
|
+
}
|
|
68048
|
+
await runReviewPrompt(session, reducedRequest);
|
|
68049
|
+
}
|
|
68050
|
+
}
|
|
67438
68051
|
} finally {
|
|
67439
|
-
if (agentLogger)
|
|
67440
|
-
|
|
67441
|
-
|
|
68052
|
+
if (agentLogger) {
|
|
68053
|
+
await agentLogger.flush();
|
|
68054
|
+
}
|
|
68055
|
+
for (const activeSession of [...activeSessions]) {
|
|
68056
|
+
endSession(activeSession);
|
|
68057
|
+
}
|
|
67442
68058
|
}
|
|
67443
68059
|
const verdict = extractVerdict(reviewText);
|
|
67444
68060
|
const summary = extractSummary(reviewText);
|
|
67445
68061
|
return { verdict, review: reviewText, summary };
|
|
67446
68062
|
}
|
|
68063
|
+
function isReviewerSessionReuseError(error) {
|
|
68064
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68065
|
+
return /prompt is in progress|session (?:is )?(?:closed|disposed|ended)|conversation already active/i.test(message);
|
|
68066
|
+
}
|
|
68067
|
+
function extractPromptSection(promptContent, sectionName) {
|
|
68068
|
+
const heading = `## ${sectionName}`;
|
|
68069
|
+
const start = promptContent.indexOf(heading);
|
|
68070
|
+
if (start === -1) {
|
|
68071
|
+
return "";
|
|
68072
|
+
}
|
|
68073
|
+
const afterHeading = start + heading.length;
|
|
68074
|
+
const nextH2 = promptContent.indexOf("\n## ", afterHeading);
|
|
68075
|
+
const nextH1 = promptContent.indexOf("\n# ", afterHeading);
|
|
68076
|
+
const endCandidates = [nextH2, nextH1].filter((value) => value !== -1);
|
|
68077
|
+
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : promptContent.length;
|
|
68078
|
+
return promptContent.slice(start, end).trim();
|
|
68079
|
+
}
|
|
68080
|
+
function summarizePromptSteps(promptContent) {
|
|
68081
|
+
const stepTitles = Array.from(promptContent.matchAll(/^### Step \d+:.*$/gm), (match) => match[0].trim());
|
|
68082
|
+
if (stepTitles.length === 0) {
|
|
68083
|
+
return "";
|
|
68084
|
+
}
|
|
68085
|
+
return ["## Steps", ...stepTitles].join("\n");
|
|
68086
|
+
}
|
|
68087
|
+
function buildReducedTaskPromptSummary(promptContent) {
|
|
68088
|
+
const firstSectionIndex = promptContent.indexOf("\n## ");
|
|
68089
|
+
const header = (firstSectionIndex === -1 ? promptContent : promptContent.slice(0, firstSectionIndex)).trim();
|
|
68090
|
+
const sections = [
|
|
68091
|
+
header,
|
|
68092
|
+
extractPromptSection(promptContent, "Mission"),
|
|
68093
|
+
extractPromptSection(promptContent, "Dependencies"),
|
|
68094
|
+
extractPromptSection(promptContent, "File Scope"),
|
|
68095
|
+
summarizePromptSteps(promptContent),
|
|
68096
|
+
"_... additional PROMPT.md sections omitted after context-limit retry ..._"
|
|
68097
|
+
].filter(Boolean);
|
|
68098
|
+
return sections.join("\n\n").trim();
|
|
68099
|
+
}
|
|
68100
|
+
function buildReducedReviewRequest(taskId, stepNumber, stepName, reviewType, promptContent, cwd, baseline) {
|
|
68101
|
+
return buildReviewRequest(
|
|
68102
|
+
taskId,
|
|
68103
|
+
stepNumber,
|
|
68104
|
+
stepName,
|
|
68105
|
+
reviewType,
|
|
68106
|
+
buildReducedTaskPromptSummary(promptContent),
|
|
68107
|
+
cwd,
|
|
68108
|
+
baseline,
|
|
68109
|
+
void 0
|
|
68110
|
+
);
|
|
68111
|
+
}
|
|
67447
68112
|
function buildReviewRequest(taskId, stepNumber, stepName, reviewType, promptContent, cwd, baseline, userComments) {
|
|
67448
68113
|
const parts = [
|
|
67449
68114
|
`Review request for task ${taskId}, Step ${stepNumber}: ${stepName}`,
|
|
@@ -67568,6 +68233,7 @@ var init_reviewer = __esm({
|
|
|
67568
68233
|
"use strict";
|
|
67569
68234
|
init_src();
|
|
67570
68235
|
init_pi();
|
|
68236
|
+
init_context_limit_detector();
|
|
67571
68237
|
init_agent_session_helpers();
|
|
67572
68238
|
init_session_skill_context();
|
|
67573
68239
|
init_agent_logger();
|
|
@@ -69544,11 +70210,17 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
69544
70210
|
const taskGetParams = Type2.Object({
|
|
69545
70211
|
id: Type2.String({ description: "Task ID (e.g. KB-001)" })
|
|
69546
70212
|
});
|
|
70213
|
+
const taskCreatePriorityValues = ["low", "normal", "high", "urgent"];
|
|
69547
70214
|
const taskCreateParams3 = Type2.Object({
|
|
69548
70215
|
title: Type2.Optional(Type2.String({ description: "Short child task title" })),
|
|
69549
70216
|
description: Type2.String({ description: "Child task description/mission" }),
|
|
69550
70217
|
dependencies: Type2.Optional(
|
|
69551
70218
|
Type2.Array(Type2.String({ description: "Task ID dependency (e.g. KB-001)" }))
|
|
70219
|
+
),
|
|
70220
|
+
priority: Type2.Optional(
|
|
70221
|
+
Type2.Union(taskCreatePriorityValues.map((priority) => Type2.Literal(priority)), {
|
|
70222
|
+
description: "Task priority (low, normal, high, urgent)"
|
|
70223
|
+
})
|
|
69552
70224
|
)
|
|
69553
70225
|
});
|
|
69554
70226
|
const taskList = {
|
|
@@ -69670,6 +70342,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
69670
70342
|
description: params.description,
|
|
69671
70343
|
dependencies: validDeps,
|
|
69672
70344
|
column: "triage",
|
|
70345
|
+
priority: params.priority,
|
|
69673
70346
|
// Inherit parent's model settings if available
|
|
69674
70347
|
modelProvider: parentTask?.modelProvider,
|
|
69675
70348
|
modelId: parentTask?.modelId,
|
|
@@ -70485,11 +71158,146 @@ var init_run_audit = __esm({
|
|
|
70485
71158
|
}
|
|
70486
71159
|
});
|
|
70487
71160
|
|
|
70488
|
-
// ../engine/src/merger.ts
|
|
70489
|
-
import {
|
|
71161
|
+
// ../engine/src/merger-squash-audit.ts
|
|
71162
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
70490
71163
|
import { promisify as promisify4 } from "node:util";
|
|
71164
|
+
async function auditSquashMerge({
|
|
71165
|
+
rootDir,
|
|
71166
|
+
squashSha,
|
|
71167
|
+
lookback = DEFAULT_LOOKBACK
|
|
71168
|
+
}) {
|
|
71169
|
+
const normalizedLookback = normalizeLookback(lookback);
|
|
71170
|
+
const parentSha = await git(rootDir, ["rev-parse", `${squashSha}^`]);
|
|
71171
|
+
const squashSubject = await git(rootDir, ["log", "-1", "--format=%s", squashSha]);
|
|
71172
|
+
const branchSubjects = normalizeLines(await git(rootDir, ["log", "-1", "--format=%b", squashSha])).map((line) => line.replace(/^- /, "").trim()).filter(Boolean);
|
|
71173
|
+
const recentMainCommits = await listRecentMainCommits(rootDir, parentSha, normalizedLookback);
|
|
71174
|
+
const recentMainSubjects = recentMainCommits.map((entry) => entry.subject);
|
|
71175
|
+
const duplicateSubjects = branchSubjects.filter((subject) => recentMainSubjects.includes(subject)).map((subject) => ({ type: "duplicate-subject", subject }));
|
|
71176
|
+
const touchedFiles = normalizeLines(await git(rootDir, ["diff", "--name-only", parentSha, squashSha]));
|
|
71177
|
+
const touchedFileOverlaps = [];
|
|
71178
|
+
for (const file of touchedFiles) {
|
|
71179
|
+
const overlappingCommits = [];
|
|
71180
|
+
for (const commit of recentMainCommits) {
|
|
71181
|
+
const touchedInCommit = await git(rootDir, ["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha, "--", file]);
|
|
71182
|
+
if (normalizeLines(touchedInCommit).includes(file)) {
|
|
71183
|
+
overlappingCommits.push({ sha: commit.shortSha, subject: commit.subject });
|
|
71184
|
+
}
|
|
71185
|
+
}
|
|
71186
|
+
if (overlappingCommits.length > 0) {
|
|
71187
|
+
touchedFileOverlaps.push({
|
|
71188
|
+
type: "touched-file-overlap",
|
|
71189
|
+
file,
|
|
71190
|
+
recentMainCommits: overlappingCommits
|
|
71191
|
+
});
|
|
71192
|
+
}
|
|
71193
|
+
}
|
|
71194
|
+
const findings = [...duplicateSubjects, ...touchedFileOverlaps];
|
|
71195
|
+
return {
|
|
71196
|
+
squashSha,
|
|
71197
|
+
parentSha,
|
|
71198
|
+
squashSubject,
|
|
71199
|
+
lookback: normalizedLookback,
|
|
71200
|
+
branchSubjects,
|
|
71201
|
+
recentMainSubjects,
|
|
71202
|
+
duplicateSubjects,
|
|
71203
|
+
touchedFiles,
|
|
71204
|
+
touchedFileOverlaps,
|
|
71205
|
+
findings,
|
|
71206
|
+
issueCount: findings.length,
|
|
71207
|
+
clean: findings.length === 0
|
|
71208
|
+
};
|
|
71209
|
+
}
|
|
71210
|
+
function formatSquashAuditReport(findings) {
|
|
71211
|
+
const lines = [
|
|
71212
|
+
`Auditing squash: ${findings.squashSha} \u2014 ${findings.squashSubject}`,
|
|
71213
|
+
`Parent (main before squash): ${findings.parentSha}`,
|
|
71214
|
+
`Lookback window on main: ${findings.lookback} commits`,
|
|
71215
|
+
"",
|
|
71216
|
+
"=== Duplicate-cherry-pick risk ==="
|
|
71217
|
+
];
|
|
71218
|
+
if (findings.duplicateSubjects.length === 0) {
|
|
71219
|
+
lines.push("(none \u2014 no branch commit subjects match recent main commits)", "");
|
|
71220
|
+
} else {
|
|
71221
|
+
lines.push(
|
|
71222
|
+
"WARN: branch contains commits whose subjects match recent main commits.",
|
|
71223
|
+
"Auto-resolve may have picked the older side, dropping refinements.",
|
|
71224
|
+
"Action: diff each main commit below against HEAD and confirm its",
|
|
71225
|
+
"net contribution survived. Restore anything dropped as a follow-up.",
|
|
71226
|
+
"",
|
|
71227
|
+
...findings.duplicateSubjects.map((entry) => ` - ${entry.subject}`),
|
|
71228
|
+
""
|
|
71229
|
+
);
|
|
71230
|
+
}
|
|
71231
|
+
lines.push(`=== Touched-file overlap (${findings.touchedFiles.length} files in squash) ===`);
|
|
71232
|
+
if (findings.touchedFileOverlaps.length === 0) {
|
|
71233
|
+
lines.push("(none \u2014 squash touches files no recent main commit touched)", "");
|
|
71234
|
+
} else {
|
|
71235
|
+
lines.push(
|
|
71236
|
+
"Files the squash touched that also have recent main activity.",
|
|
71237
|
+
"Action: for each commit below, verify its changes still appear",
|
|
71238
|
+
"in HEAD. Reapply any silently dropped changes on the same branch.",
|
|
71239
|
+
""
|
|
71240
|
+
);
|
|
71241
|
+
for (const overlap of findings.touchedFileOverlaps) {
|
|
71242
|
+
lines.push(` ${overlap.file}`);
|
|
71243
|
+
for (const commit of overlap.recentMainCommits) {
|
|
71244
|
+
lines.push(` - ${commit.sha} ${commit.subject}`);
|
|
71245
|
+
}
|
|
71246
|
+
}
|
|
71247
|
+
lines.push("");
|
|
71248
|
+
}
|
|
71249
|
+
lines.push(`Audit complete. ${findings.issueCount} item(s) for the calling agent to review.`);
|
|
71250
|
+
return lines.join("\n");
|
|
71251
|
+
}
|
|
71252
|
+
async function git(rootDir, args) {
|
|
71253
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
71254
|
+
cwd: rootDir,
|
|
71255
|
+
encoding: "utf-8",
|
|
71256
|
+
maxBuffer: GIT_OUTPUT_MAX_BUFFER
|
|
71257
|
+
});
|
|
71258
|
+
return stdout.trim();
|
|
71259
|
+
}
|
|
71260
|
+
function normalizeLines(value) {
|
|
71261
|
+
const trimmed = value.trim();
|
|
71262
|
+
if (!trimmed) return [];
|
|
71263
|
+
return trimmed.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
71264
|
+
}
|
|
71265
|
+
async function listRecentMainCommits(rootDir, parentSha, lookback) {
|
|
71266
|
+
const entries = normalizeLines(await git(rootDir, ["log", `--format=%H~%h~%s`, `-n`, String(lookback), parentSha]));
|
|
71267
|
+
return entries.map((entry) => {
|
|
71268
|
+
const [sha, shortSha, ...subjectParts] = entry.split("~");
|
|
71269
|
+
const subject = subjectParts.join("~").trim();
|
|
71270
|
+
if (!sha?.trim() || !shortSha?.trim() || !subject) {
|
|
71271
|
+
return null;
|
|
71272
|
+
}
|
|
71273
|
+
return {
|
|
71274
|
+
sha: sha.trim(),
|
|
71275
|
+
shortSha: shortSha.trim(),
|
|
71276
|
+
subject
|
|
71277
|
+
};
|
|
71278
|
+
}).filter((entry) => entry !== null);
|
|
71279
|
+
}
|
|
71280
|
+
function normalizeLookback(value) {
|
|
71281
|
+
if (!Number.isFinite(value) || !value || value < 1) {
|
|
71282
|
+
return DEFAULT_LOOKBACK;
|
|
71283
|
+
}
|
|
71284
|
+
return Math.trunc(value);
|
|
71285
|
+
}
|
|
71286
|
+
var execFileAsync, DEFAULT_LOOKBACK, GIT_OUTPUT_MAX_BUFFER;
|
|
71287
|
+
var init_merger_squash_audit = __esm({
|
|
71288
|
+
"../engine/src/merger-squash-audit.ts"() {
|
|
71289
|
+
"use strict";
|
|
71290
|
+
execFileAsync = promisify4(execFile3);
|
|
71291
|
+
DEFAULT_LOOKBACK = 30;
|
|
71292
|
+
GIT_OUTPUT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
71293
|
+
}
|
|
71294
|
+
});
|
|
71295
|
+
|
|
71296
|
+
// ../engine/src/merger.ts
|
|
71297
|
+
import { execSync, exec as exec3, execFile as execFile4 } from "node:child_process";
|
|
71298
|
+
import { promisify as promisify5 } from "node:util";
|
|
70491
71299
|
import { existsSync as existsSync25, readFileSync as readFileSync11, writeFileSync as writeFileSync2, unlinkSync, renameSync as renameSync2 } from "node:fs";
|
|
70492
|
-
import { createHash as
|
|
71300
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
70493
71301
|
import { join as join32 } from "node:path";
|
|
70494
71302
|
import { hostname } from "node:os";
|
|
70495
71303
|
import { Type as Type3 } from "typebox";
|
|
@@ -70558,7 +71366,7 @@ function computeLockfileHash(rootDir) {
|
|
|
70558
71366
|
const p = join32(rootDir, name);
|
|
70559
71367
|
if (existsSync25(p)) {
|
|
70560
71368
|
try {
|
|
70561
|
-
return
|
|
71369
|
+
return createHash7("sha256").update(readFileSync11(p)).digest("hex");
|
|
70562
71370
|
} catch {
|
|
70563
71371
|
return null;
|
|
70564
71372
|
}
|
|
@@ -70616,8 +71424,8 @@ function commitOwnedByTask(taskId, subject, body) {
|
|
|
70616
71424
|
async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
70617
71425
|
const tryHydrate = async (sha) => {
|
|
70618
71426
|
try {
|
|
70619
|
-
await
|
|
70620
|
-
const { stdout } = await
|
|
71427
|
+
await execFileAsync2("git", ["merge-base", "--is-ancestor", sha, "HEAD"], { cwd: rootDir });
|
|
71428
|
+
const { stdout } = await execFileAsync2("git", ["log", "-1", "--format=%H%x1f%s%x1f%b", sha], {
|
|
70621
71429
|
cwd: rootDir,
|
|
70622
71430
|
encoding: "utf-8"
|
|
70623
71431
|
});
|
|
@@ -70625,7 +71433,7 @@ async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
|
70625
71433
|
if (!resolvedSha || !commitOwnedByTask(task.id, subject, body)) return null;
|
|
70626
71434
|
const owned = { sha: resolvedSha, subject };
|
|
70627
71435
|
try {
|
|
70628
|
-
const { stdout: statsOut } = await
|
|
71436
|
+
const { stdout: statsOut } = await execFileAsync2("git", ["show", "--shortstat", "--format=", resolvedSha], {
|
|
70629
71437
|
cwd: rootDir,
|
|
70630
71438
|
encoding: "utf-8"
|
|
70631
71439
|
});
|
|
@@ -70648,7 +71456,7 @@ async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
|
70648
71456
|
];
|
|
70649
71457
|
for (const args of searches) {
|
|
70650
71458
|
try {
|
|
70651
|
-
const { stdout } = await
|
|
71459
|
+
const { stdout } = await execFileAsync2("git", args, { cwd: rootDir, encoding: "utf-8" });
|
|
70652
71460
|
const first = stdout.trim().split("\n").find(Boolean);
|
|
70653
71461
|
if (!first) continue;
|
|
70654
71462
|
const [sha] = first.split("");
|
|
@@ -70711,15 +71519,15 @@ async function snapshotDirtyFiles(rootDir) {
|
|
|
70711
71519
|
const paths = /* @__PURE__ */ new Set();
|
|
70712
71520
|
try {
|
|
70713
71521
|
const [unstagedOut, stagedOut, porcelainOut] = await Promise.all([
|
|
70714
|
-
|
|
71522
|
+
execFileAsync2("git", ["diff", "-z", "--name-only"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
70715
71523
|
(r) => r.stdout,
|
|
70716
71524
|
() => ""
|
|
70717
71525
|
),
|
|
70718
|
-
|
|
71526
|
+
execFileAsync2("git", ["diff", "-z", "--cached", "--name-only"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
70719
71527
|
(r) => r.stdout,
|
|
70720
71528
|
() => ""
|
|
70721
71529
|
),
|
|
70722
|
-
|
|
71530
|
+
execFileAsync2("git", ["status", "-z", "--porcelain"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
70723
71531
|
(r) => r.stdout,
|
|
70724
71532
|
() => ""
|
|
70725
71533
|
)
|
|
@@ -70744,18 +71552,18 @@ async function snapshotDirtyFiles(rootDir) {
|
|
|
70744
71552
|
async function gitDirtyFingerprint(rootDir) {
|
|
70745
71553
|
try {
|
|
70746
71554
|
const [diffOut, statusOut] = await Promise.all([
|
|
70747
|
-
|
|
71555
|
+
execFileAsync2("git", ["diff", "HEAD"], {
|
|
70748
71556
|
cwd: rootDir,
|
|
70749
71557
|
encoding: "utf-8",
|
|
70750
71558
|
maxBuffer: 64 * 1024 * 1024
|
|
70751
71559
|
}).then((r) => r.stdout, () => ""),
|
|
70752
|
-
|
|
71560
|
+
execFileAsync2("git", ["status", "-z", "--porcelain"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
70753
71561
|
(r) => r.stdout,
|
|
70754
71562
|
() => ""
|
|
70755
71563
|
)
|
|
70756
71564
|
]);
|
|
70757
71565
|
if (!diffOut && !statusOut) return "";
|
|
70758
|
-
return
|
|
71566
|
+
return createHash7("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
|
|
70759
71567
|
} catch {
|
|
70760
71568
|
return "";
|
|
70761
71569
|
}
|
|
@@ -72434,7 +73242,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
72434
73242
|
encoding: "utf-8"
|
|
72435
73243
|
});
|
|
72436
73244
|
const unstaged = new Set(unstagedOut.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
72437
|
-
const { stdout: porcelainOut } = await
|
|
73245
|
+
const { stdout: porcelainOut } = await execFileAsync2("git", ["status", "-z", "--porcelain"], {
|
|
72438
73246
|
cwd: rootDir,
|
|
72439
73247
|
encoding: "utf-8"
|
|
72440
73248
|
});
|
|
@@ -72455,7 +73263,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
72455
73263
|
}
|
|
72456
73264
|
}
|
|
72457
73265
|
if (unstagedToStage.length > 0) {
|
|
72458
|
-
await
|
|
73266
|
+
await execFileAsync2("git", ["add", "--", ...unstagedToStage], { cwd: rootDir });
|
|
72459
73267
|
}
|
|
72460
73268
|
const untrackedToStage = [];
|
|
72461
73269
|
for (const p of untracked) {
|
|
@@ -72468,7 +73276,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
72468
73276
|
}
|
|
72469
73277
|
}
|
|
72470
73278
|
if (untrackedToStage.length > 0) {
|
|
72471
|
-
await
|
|
73279
|
+
await execFileAsync2("git", ["add", "--", ...untrackedToStage], { cwd: rootDir });
|
|
72472
73280
|
}
|
|
72473
73281
|
const cap = (arr, n = 20) => arr.length <= n ? arr.join(", ") : `${arr.slice(0, n).join(", ")} ... (+${arr.length - n} more)`;
|
|
72474
73282
|
mergerLog.log(
|
|
@@ -72971,8 +73779,8 @@ async function classifyConflict(filePath, cwd) {
|
|
|
72971
73779
|
}
|
|
72972
73780
|
async function resolveWithOurs(filePath, cwd) {
|
|
72973
73781
|
try {
|
|
72974
|
-
await
|
|
72975
|
-
await
|
|
73782
|
+
await execFileAsync2("git", ["checkout", "--ours", "--", filePath], { cwd });
|
|
73783
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
72976
73784
|
mergerLog.log(`Auto-resolved ${filePath} using --ours`);
|
|
72977
73785
|
} catch (error) {
|
|
72978
73786
|
throw new Error(`Failed to auto-resolve ${filePath} with ours: ${error}`);
|
|
@@ -72980,8 +73788,8 @@ async function resolveWithOurs(filePath, cwd) {
|
|
|
72980
73788
|
}
|
|
72981
73789
|
async function resolveWithTheirs(filePath, cwd) {
|
|
72982
73790
|
try {
|
|
72983
|
-
await
|
|
72984
|
-
await
|
|
73791
|
+
await execFileAsync2("git", ["checkout", "--theirs", "--", filePath], { cwd });
|
|
73792
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
72985
73793
|
mergerLog.log(`Auto-resolved ${filePath} using --theirs`);
|
|
72986
73794
|
} catch (error) {
|
|
72987
73795
|
throw new Error(`Failed to auto-resolve ${filePath} with theirs: ${error}`);
|
|
@@ -72989,7 +73797,7 @@ async function resolveWithTheirs(filePath, cwd) {
|
|
|
72989
73797
|
}
|
|
72990
73798
|
async function resolveTrivialWhitespace(filePath, cwd) {
|
|
72991
73799
|
try {
|
|
72992
|
-
await
|
|
73800
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
72993
73801
|
mergerLog.log(`Auto-resolved ${filePath} (trivial whitespace)`);
|
|
72994
73802
|
} catch (error) {
|
|
72995
73803
|
throw new Error(`Failed to auto-resolve ${filePath} trivial conflict: ${error}`);
|
|
@@ -73183,6 +73991,43 @@ async function findWorktreeUser(store, worktreePath, excludeTaskId) {
|
|
|
73183
73991
|
function quoteArg(value) {
|
|
73184
73992
|
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
73185
73993
|
}
|
|
73994
|
+
function shouldRunPostSquashAudit(result, mergeWasEmpty, isEmptyCommit, commitSha) {
|
|
73995
|
+
if (mergeWasEmpty || isEmptyCommit || !commitSha) {
|
|
73996
|
+
return false;
|
|
73997
|
+
}
|
|
73998
|
+
return (result.autoResolvedCount ?? 0) > 0 || result.attemptsMade === 3;
|
|
73999
|
+
}
|
|
74000
|
+
function buildSquashAuditBlockingMessage(taskId, squashSha, findings) {
|
|
74001
|
+
const riskParts = [];
|
|
74002
|
+
if (findings.duplicateSubjects.length > 0) {
|
|
74003
|
+
riskParts.push(`${findings.duplicateSubjects.length} duplicate-subject risk${findings.duplicateSubjects.length === 1 ? "" : "s"}`);
|
|
74004
|
+
}
|
|
74005
|
+
if (findings.touchedFileOverlaps.length > 0) {
|
|
74006
|
+
riskParts.push(`${findings.touchedFileOverlaps.length} touched-file overlap risk${findings.touchedFileOverlaps.length === 1 ? "" : "s"}`);
|
|
74007
|
+
}
|
|
74008
|
+
const summary = riskParts.length > 0 ? riskParts.join(", ") : `${findings.issueCount} audit finding(s)`;
|
|
74009
|
+
return `${taskId}: post-squash audit blocked auto-completion for ${squashSha.slice(0, 8)} (${summary})`;
|
|
74010
|
+
}
|
|
74011
|
+
function formatSquashAuditAgentLog(findings) {
|
|
74012
|
+
const lines = [];
|
|
74013
|
+
if (findings.duplicateSubjects.length > 0) {
|
|
74014
|
+
lines.push("Duplicate-subject risks:");
|
|
74015
|
+
for (const duplicate of findings.duplicateSubjects) {
|
|
74016
|
+
lines.push(`- ${duplicate.subject}`);
|
|
74017
|
+
}
|
|
74018
|
+
}
|
|
74019
|
+
if (findings.touchedFileOverlaps.length > 0) {
|
|
74020
|
+
if (lines.length > 0) lines.push("");
|
|
74021
|
+
lines.push("Touched-file overlap risks:");
|
|
74022
|
+
for (const overlap of findings.touchedFileOverlaps) {
|
|
74023
|
+
lines.push(`- ${overlap.file}`);
|
|
74024
|
+
for (const commit of overlap.recentMainCommits) {
|
|
74025
|
+
lines.push(` - ${commit.sha} ${commit.subject}`);
|
|
74026
|
+
}
|
|
74027
|
+
}
|
|
74028
|
+
}
|
|
74029
|
+
return lines.join("\n");
|
|
74030
|
+
}
|
|
73186
74031
|
async function resolveSafeCommitBody(opts) {
|
|
73187
74032
|
const cleanLog = opts.commitLog.trim();
|
|
73188
74033
|
if (cleanLog.length > 0) return cleanLog;
|
|
@@ -74514,6 +75359,26 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
74514
75359
|
}
|
|
74515
75360
|
const isEmptyCommit = filesChanged === 0;
|
|
74516
75361
|
const recordedSha = isEmptyCommit || mergeWasEmpty ? void 0 : commitSha;
|
|
75362
|
+
const auditSha = recordedSha;
|
|
75363
|
+
if (auditSha && shouldRunPostSquashAudit(result, mergeWasEmpty, isEmptyCommit, auditSha)) {
|
|
75364
|
+
const auditFindings = await auditSquashMerge({
|
|
75365
|
+
rootDir,
|
|
75366
|
+
squashSha: auditSha
|
|
75367
|
+
});
|
|
75368
|
+
if (!auditFindings.clean) {
|
|
75369
|
+
const auditError = new SquashAuditError(taskId, auditSha, auditFindings);
|
|
75370
|
+
await store.appendAgentLog(
|
|
75371
|
+
taskId,
|
|
75372
|
+
auditError.message,
|
|
75373
|
+
"tool_error",
|
|
75374
|
+
formatSquashAuditAgentLog(auditFindings),
|
|
75375
|
+
"merger"
|
|
75376
|
+
);
|
|
75377
|
+
await store.updateTask(taskId, { status: null });
|
|
75378
|
+
throw auditError;
|
|
75379
|
+
}
|
|
75380
|
+
await store.appendAgentLog(taskId, "post-squash audit clean", "text", void 0, "merger");
|
|
75381
|
+
}
|
|
74517
75382
|
if (isEmptyCommit) {
|
|
74518
75383
|
mergerLog.warn(
|
|
74519
75384
|
`${taskId}: local squash produced an empty commit (${commitSha?.slice(0, 8)}) \u2014 branch likely contained dupes of main. Skipping commitSha; recovery will backfill when real commit lands.`
|
|
@@ -74578,6 +75443,9 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
74578
75443
|
"merger"
|
|
74579
75444
|
);
|
|
74580
75445
|
} catch (err) {
|
|
75446
|
+
if (err instanceof SquashAuditError || err?.name === "SquashAuditError") {
|
|
75447
|
+
throw err;
|
|
75448
|
+
}
|
|
74581
75449
|
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
|
|
74582
75450
|
}
|
|
74583
75451
|
try {
|
|
@@ -75813,7 +76681,7 @@ async function completeTask(store, taskId, result) {
|
|
|
75813
76681
|
result.task = task;
|
|
75814
76682
|
store.emit("task:merged", result);
|
|
75815
76683
|
}
|
|
75816
|
-
var execAsync2,
|
|
76684
|
+
var execAsync2, execFileAsync2, LOCKFILE_PATTERNS, GENERATED_PATTERNS, DEPENDENCY_SYNC_TRIGGER_PATTERNS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, PULL_REBASE_TIMEOUT_MS, PUSH_TIMEOUT_MS, MERGE_COMMIT_LOG_MAX_CHARS, MERGE_DIFF_STAT_MAX_CHARS, INSTALL_MARKER_RELPATH, LOCKFILE_CANDIDATES, VerificationError, MergeAbortedError, SquashAuditError, VERIFICATION_EXTRA_ENV, AUTOSTASH_LABEL_PREFIX, AUTOSTASH_TIMESTAMP_RE, ACTIVE_MERGER_STATUS_FILENAME, FUSION_TASK_ID_TRAILER_KEY;
|
|
75817
76685
|
var init_merger = __esm({
|
|
75818
76686
|
"../engine/src/merger.ts"() {
|
|
75819
76687
|
"use strict";
|
|
@@ -75833,8 +76701,9 @@ var init_merger = __esm({
|
|
|
75833
76701
|
init_agent_instructions();
|
|
75834
76702
|
init_run_audit();
|
|
75835
76703
|
init_agent_tools();
|
|
75836
|
-
|
|
75837
|
-
|
|
76704
|
+
init_merger_squash_audit();
|
|
76705
|
+
execAsync2 = promisify5(exec3);
|
|
76706
|
+
execFileAsync2 = promisify5(execFile4);
|
|
75838
76707
|
LOCKFILE_PATTERNS = [
|
|
75839
76708
|
"package-lock.json",
|
|
75840
76709
|
"pnpm-lock.yaml",
|
|
@@ -75891,6 +76760,14 @@ var init_merger = __esm({
|
|
|
75891
76760
|
this.name = "MergeAbortedError";
|
|
75892
76761
|
}
|
|
75893
76762
|
};
|
|
76763
|
+
SquashAuditError = class extends Error {
|
|
76764
|
+
constructor(taskId, squashSha, findings) {
|
|
76765
|
+
super(buildSquashAuditBlockingMessage(taskId, squashSha, findings));
|
|
76766
|
+
this.squashSha = squashSha;
|
|
76767
|
+
this.findings = findings;
|
|
76768
|
+
this.name = "SquashAuditError";
|
|
76769
|
+
}
|
|
76770
|
+
};
|
|
75894
76771
|
VERIFICATION_EXTRA_ENV = Object.fromEntries(
|
|
75895
76772
|
[
|
|
75896
76773
|
["FUSION_TEST_TOTAL_WORKERS", "8"],
|
|
@@ -76092,7 +76969,7 @@ __export(worktree_pool_exports, {
|
|
|
76092
76969
|
scanOrphanedBranches: () => scanOrphanedBranches
|
|
76093
76970
|
});
|
|
76094
76971
|
import { exec as exec4 } from "node:child_process";
|
|
76095
|
-
import { promisify as
|
|
76972
|
+
import { promisify as promisify6 } from "node:util";
|
|
76096
76973
|
import { existsSync as existsSync27, lstatSync, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
76097
76974
|
import { join as join34, relative as relative8, resolve as resolve18, isAbsolute as isAbsolute10 } from "node:path";
|
|
76098
76975
|
function getExecStdout(result) {
|
|
@@ -76303,7 +77180,7 @@ var init_worktree_pool = __esm({
|
|
|
76303
77180
|
"../engine/src/worktree-pool.ts"() {
|
|
76304
77181
|
"use strict";
|
|
76305
77182
|
init_logger2();
|
|
76306
|
-
execAsync3 =
|
|
77183
|
+
execAsync3 = promisify6(exec4);
|
|
76307
77184
|
WorktreePool = class {
|
|
76308
77185
|
idle = /* @__PURE__ */ new Set();
|
|
76309
77186
|
/**
|
|
@@ -76495,7 +77372,7 @@ var init_token_cap_detector = __esm({
|
|
|
76495
77372
|
|
|
76496
77373
|
// ../engine/src/step-session-executor.ts
|
|
76497
77374
|
import { exec as exec5 } from "node:child_process";
|
|
76498
|
-
import { promisify as
|
|
77375
|
+
import { promisify as promisify7 } from "node:util";
|
|
76499
77376
|
import { existsSync as existsSync28 } from "node:fs";
|
|
76500
77377
|
import { join as join35 } from "node:path";
|
|
76501
77378
|
function parseStepFileScopes(prompt) {
|
|
@@ -76758,7 +77635,7 @@ var init_step_session_executor = __esm({
|
|
|
76758
77635
|
init_context_limit_detector();
|
|
76759
77636
|
init_usage_limit_detector();
|
|
76760
77637
|
init_agent_tools();
|
|
76761
|
-
execAsync4 =
|
|
77638
|
+
execAsync4 = promisify7(exec5);
|
|
76762
77639
|
stepExecLog = createLogger2("step-session-executor");
|
|
76763
77640
|
MAX_STEP_RETRIES = 3;
|
|
76764
77641
|
RETRY_DELAYS_MS = [1e3, 5e3, 15e3];
|
|
@@ -77412,7 +78289,9 @@ async function hydrateWorktreeDb({
|
|
|
77412
78289
|
ensureWorktreeSchema(worktreePath);
|
|
77413
78290
|
}
|
|
77414
78291
|
srcDb = new DatabaseSync(srcDbPath);
|
|
78292
|
+
srcDb.exec("PRAGMA busy_timeout = 5000");
|
|
77415
78293
|
dstDb = openWorktreeDbWithRecovery(dstDbPath, worktreePath);
|
|
78294
|
+
dstDb.exec("PRAGMA busy_timeout = 5000");
|
|
77416
78295
|
dstDb.exec("PRAGMA journal_mode = WAL");
|
|
77417
78296
|
const srcTaskCols = getColumns(srcDb, "tasks");
|
|
77418
78297
|
const dstTaskCols = getColumns(dstDb, "tasks");
|
|
@@ -77861,12 +78740,120 @@ var init_run_verification_tool = __esm({
|
|
|
77861
78740
|
|
|
77862
78741
|
// ../engine/src/executor.ts
|
|
77863
78742
|
import { exec as exec6 } from "node:child_process";
|
|
77864
|
-
import { promisify as
|
|
78743
|
+
import { promisify as promisify8 } from "node:util";
|
|
77865
78744
|
import { delimiter, isAbsolute as isAbsolute12, join as join39, relative as relative9, resolve as resolvePath } from "node:path";
|
|
77866
78745
|
import { existsSync as existsSync31 } from "node:fs";
|
|
77867
78746
|
import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
|
|
77868
78747
|
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
77869
78748
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
78749
|
+
function normalizeWorkflowScopePath(pathValue) {
|
|
78750
|
+
return pathValue.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/").replace(/\/$/, "");
|
|
78751
|
+
}
|
|
78752
|
+
function stripTrailingPathPunctuation(pathValue) {
|
|
78753
|
+
return pathValue.replace(/[),.:;!?]+$/g, "");
|
|
78754
|
+
}
|
|
78755
|
+
function extractReferencedPathsFromWorkflowFeedback(feedback) {
|
|
78756
|
+
const extracted = [];
|
|
78757
|
+
const seen = /* @__PURE__ */ new Set();
|
|
78758
|
+
for (const match of feedback.matchAll(WORKFLOW_FEEDBACK_PATH_REGEX)) {
|
|
78759
|
+
const candidate = stripTrailingPathPunctuation(match[1] ?? match[2] ?? "");
|
|
78760
|
+
const normalized = normalizeWorkflowScopePath(candidate);
|
|
78761
|
+
if (!normalized.includes("/") || !normalized) continue;
|
|
78762
|
+
if (seen.has(normalized)) continue;
|
|
78763
|
+
seen.add(normalized);
|
|
78764
|
+
extracted.push(normalized);
|
|
78765
|
+
}
|
|
78766
|
+
return extracted;
|
|
78767
|
+
}
|
|
78768
|
+
function workflowPathMatchesDeclaredScope(filePath, scopePatterns) {
|
|
78769
|
+
const normalizedPath = normalizeWorkflowScopePath(filePath);
|
|
78770
|
+
for (const rawPattern of scopePatterns) {
|
|
78771
|
+
const pattern = normalizeWorkflowScopePath(rawPattern);
|
|
78772
|
+
if (!pattern) continue;
|
|
78773
|
+
if (/\/\*+$/.test(pattern)) {
|
|
78774
|
+
const directory = pattern.replace(/\/\*+$/, "");
|
|
78775
|
+
if (normalizedPath === directory || normalizedPath.startsWith(`${directory}/`)) return true;
|
|
78776
|
+
continue;
|
|
78777
|
+
}
|
|
78778
|
+
if (pattern.endsWith("/")) {
|
|
78779
|
+
if (normalizedPath.startsWith(pattern)) return true;
|
|
78780
|
+
continue;
|
|
78781
|
+
}
|
|
78782
|
+
if (normalizedPath === pattern) return true;
|
|
78783
|
+
}
|
|
78784
|
+
return false;
|
|
78785
|
+
}
|
|
78786
|
+
function partitionWorkflowRevisionFeedback(feedback, declaredFileScope) {
|
|
78787
|
+
const trimmedFeedback = feedback.trim();
|
|
78788
|
+
if (!trimmedFeedback || declaredFileScope.length === 0) {
|
|
78789
|
+
return {
|
|
78790
|
+
inScopeFeedback: trimmedFeedback,
|
|
78791
|
+
outOfScopeFeedback: "",
|
|
78792
|
+
inScopeSegments: trimmedFeedback ? [trimmedFeedback] : [],
|
|
78793
|
+
outOfScopeSegments: [],
|
|
78794
|
+
detectedPaths: extractReferencedPathsFromWorkflowFeedback(trimmedFeedback)
|
|
78795
|
+
};
|
|
78796
|
+
}
|
|
78797
|
+
const segments = trimmedFeedback.split(/\n\s*\n/).map((segment) => segment.trim()).filter(Boolean);
|
|
78798
|
+
const allDetectedPaths = extractReferencedPathsFromWorkflowFeedback(trimmedFeedback);
|
|
78799
|
+
if (allDetectedPaths.length === 0) {
|
|
78800
|
+
return {
|
|
78801
|
+
inScopeFeedback: trimmedFeedback,
|
|
78802
|
+
outOfScopeFeedback: "",
|
|
78803
|
+
inScopeSegments: trimmedFeedback ? [trimmedFeedback] : [],
|
|
78804
|
+
outOfScopeSegments: [],
|
|
78805
|
+
detectedPaths: []
|
|
78806
|
+
};
|
|
78807
|
+
}
|
|
78808
|
+
const inScopeSegments = [];
|
|
78809
|
+
const outOfScopeSegments = [];
|
|
78810
|
+
for (const segment of segments) {
|
|
78811
|
+
const segmentPaths = extractReferencedPathsFromWorkflowFeedback(segment);
|
|
78812
|
+
if (segmentPaths.length === 0) {
|
|
78813
|
+
inScopeSegments.push(segment);
|
|
78814
|
+
continue;
|
|
78815
|
+
}
|
|
78816
|
+
const hasOutOfScopePath = segmentPaths.some((path2) => !workflowPathMatchesDeclaredScope(path2, declaredFileScope));
|
|
78817
|
+
if (hasOutOfScopePath) {
|
|
78818
|
+
outOfScopeSegments.push(segment);
|
|
78819
|
+
} else {
|
|
78820
|
+
inScopeSegments.push(segment);
|
|
78821
|
+
}
|
|
78822
|
+
}
|
|
78823
|
+
return {
|
|
78824
|
+
inScopeFeedback: inScopeSegments.join("\n\n"),
|
|
78825
|
+
outOfScopeFeedback: outOfScopeSegments.join("\n\n"),
|
|
78826
|
+
inScopeSegments,
|
|
78827
|
+
outOfScopeSegments,
|
|
78828
|
+
detectedPaths: allDetectedPaths
|
|
78829
|
+
};
|
|
78830
|
+
}
|
|
78831
|
+
function normalizeWorktreePath(pathValue) {
|
|
78832
|
+
return resolvePath(pathValue).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
78833
|
+
}
|
|
78834
|
+
async function extractPersistedSessionWorktreePath(sessionFile) {
|
|
78835
|
+
try {
|
|
78836
|
+
const content = await readFile17(sessionFile, "utf-8");
|
|
78837
|
+
const matches = content.match(SESSION_WORKTREE_PATH_REGEX) ?? [];
|
|
78838
|
+
if (matches.length === 0) return null;
|
|
78839
|
+
const normalizedCounts = /* @__PURE__ */ new Map();
|
|
78840
|
+
for (const match of matches) {
|
|
78841
|
+
const normalized = normalizeWorktreePath(match);
|
|
78842
|
+
normalizedCounts.set(normalized, (normalizedCounts.get(normalized) ?? 0) + 1);
|
|
78843
|
+
}
|
|
78844
|
+
let best = null;
|
|
78845
|
+
for (const [path2, count] of normalizedCounts.entries()) {
|
|
78846
|
+
if (!best || count > best.count) best = { path: path2, count };
|
|
78847
|
+
}
|
|
78848
|
+
return best?.path ?? null;
|
|
78849
|
+
} catch {
|
|
78850
|
+
return null;
|
|
78851
|
+
}
|
|
78852
|
+
}
|
|
78853
|
+
function isSessionWorktreeCompatible(persistedWorktreePath, currentWorktreePath) {
|
|
78854
|
+
if (!persistedWorktreePath) return true;
|
|
78855
|
+
return persistedWorktreePath === normalizeWorktreePath(currentWorktreePath);
|
|
78856
|
+
}
|
|
77870
78857
|
function truncateWorkflowScriptOutput2(output) {
|
|
77871
78858
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
|
|
77872
78859
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2} characters ...
|
|
@@ -78146,7 +79133,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
78146
79133
|
];
|
|
78147
79134
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
78148
79135
|
}
|
|
78149
|
-
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
79136
|
+
var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, WORKFLOW_FEEDBACK_PATH_REGEX, NonRetryableWorktreeError, SESSION_WORKTREE_PATH_REGEX, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
|
|
78150
79137
|
var init_executor = __esm({
|
|
78151
79138
|
"../engine/src/executor.ts"() {
|
|
78152
79139
|
"use strict";
|
|
@@ -78183,7 +79170,7 @@ var init_executor = __esm({
|
|
|
78183
79170
|
init_fallback_model_observer();
|
|
78184
79171
|
init_agent_logger();
|
|
78185
79172
|
init_agent_tools();
|
|
78186
|
-
execAsync5 =
|
|
79173
|
+
execAsync5 = promisify8(exec6);
|
|
78187
79174
|
STEP_STATUSES = ["pending", "in-progress", "done", "skipped"];
|
|
78188
79175
|
MAX_WORKFLOW_STEP_RETRIES = 3;
|
|
78189
79176
|
MAX_TASK_DONE_SESSION_RETRIES = 3;
|
|
@@ -78191,8 +79178,10 @@ var init_executor = __esm({
|
|
|
78191
79178
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
78192
79179
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
78193
79180
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
79181
|
+
WORKFLOW_FEEDBACK_PATH_REGEX = /`([^`\n]+)`|(?<![A-Za-z0-9_.-])((?:\.\.?\/)?(?:@?[A-Za-z0-9._-]+\/)+[A-Za-z0-9._-]+(?:\.[A-Za-z0-9._-]+)?)/g;
|
|
78194
79182
|
NonRetryableWorktreeError = class extends Error {
|
|
78195
79183
|
};
|
|
79184
|
+
SESSION_WORKTREE_PATH_REGEX = /([A-Za-z]:)?[^"'\s]*\.worktrees[\\/][^"'\s]+/g;
|
|
78196
79185
|
taskUpdateParams = Type5.Object({
|
|
78197
79186
|
step: Type5.Number({ description: "Step number (1-indexed)" }),
|
|
78198
79187
|
status: Type5.Union(
|
|
@@ -80266,15 +81255,18 @@ ${summary}`,
|
|
|
80266
81255
|
}
|
|
80267
81256
|
if (!workflowResult.allPassed) {
|
|
80268
81257
|
if (workflowResult.revisionRequested) {
|
|
80269
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
80270
|
-
|
|
80271
|
-
|
|
80272
|
-
|
|
80273
|
-
|
|
81258
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
81259
|
+
if (rerunScheduled) {
|
|
81260
|
+
return;
|
|
81261
|
+
}
|
|
81262
|
+
} else {
|
|
81263
|
+
const retried = await this.handleWorkflowStepFailure(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown");
|
|
81264
|
+
if (retried) {
|
|
81265
|
+
return;
|
|
81266
|
+
}
|
|
81267
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
80274
81268
|
return;
|
|
80275
81269
|
}
|
|
80276
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
80277
|
-
return;
|
|
80278
81270
|
}
|
|
80279
81271
|
} else {
|
|
80280
81272
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -80530,7 +81522,23 @@ ${summary}`,
|
|
|
80530
81522
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
80531
81523
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
80532
81524
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
80533
|
-
|
|
81525
|
+
let isResuming = !!task.sessionFile && existsSync31(task.sessionFile);
|
|
81526
|
+
if (isResuming) {
|
|
81527
|
+
const persistedWorktreePath = await extractPersistedSessionWorktreePath(task.sessionFile);
|
|
81528
|
+
if (!isSessionWorktreeCompatible(persistedWorktreePath, worktreePath)) {
|
|
81529
|
+
executorLog.warn(
|
|
81530
|
+
`${task.id}: stale sessionFile worktree mismatch (session=${persistedWorktreePath}, task=${worktreePath}); starting fresh session`
|
|
81531
|
+
);
|
|
81532
|
+
await this.store.logEntry(
|
|
81533
|
+
task.id,
|
|
81534
|
+
`Detected stale persisted session metadata (worktree mismatch: ${persistedWorktreePath} vs ${worktreePath}) \u2014 discarded resume state and started fresh session`,
|
|
81535
|
+
void 0,
|
|
81536
|
+
this.currentRunContext
|
|
81537
|
+
);
|
|
81538
|
+
await this.store.updateTask(task.id, { sessionFile: null });
|
|
81539
|
+
isResuming = false;
|
|
81540
|
+
}
|
|
81541
|
+
}
|
|
80534
81542
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
80535
81543
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
80536
81544
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -80760,15 +81768,18 @@ ${summary}`,
|
|
|
80760
81768
|
}
|
|
80761
81769
|
if (!workflowResult.allPassed) {
|
|
80762
81770
|
if (workflowResult.revisionRequested) {
|
|
80763
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
80764
|
-
|
|
80765
|
-
|
|
80766
|
-
|
|
80767
|
-
|
|
81771
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
81772
|
+
if (rerunScheduled) {
|
|
81773
|
+
return;
|
|
81774
|
+
}
|
|
81775
|
+
} else {
|
|
81776
|
+
const retried = await this.handleWorkflowStepFailure(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown");
|
|
81777
|
+
if (retried) {
|
|
81778
|
+
return;
|
|
81779
|
+
}
|
|
81780
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
80768
81781
|
return;
|
|
80769
81782
|
}
|
|
80770
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
80771
|
-
return;
|
|
80772
81783
|
}
|
|
80773
81784
|
} else {
|
|
80774
81785
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -80928,11 +81939,14 @@ ${summary}`,
|
|
|
80928
81939
|
}
|
|
80929
81940
|
if (!workflowResult.allPassed) {
|
|
80930
81941
|
if (workflowResult.revisionRequested) {
|
|
80931
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
81942
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
81943
|
+
if (rerunScheduled) {
|
|
81944
|
+
return;
|
|
81945
|
+
}
|
|
81946
|
+
} else {
|
|
81947
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed on retry");
|
|
80932
81948
|
return;
|
|
80933
81949
|
}
|
|
80934
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed on retry");
|
|
80935
|
-
return;
|
|
80936
81950
|
}
|
|
80937
81951
|
} else {
|
|
80938
81952
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -81732,18 +82746,38 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
81732
82746
|
* the injected feedback from PROMPT.md and applies an in-place fix rather
|
|
81733
82747
|
* than redoing any completed step.
|
|
81734
82748
|
*/
|
|
81735
|
-
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
|
|
82749
|
+
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName, settings) {
|
|
81736
82750
|
executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
|
|
81737
82751
|
this.clearCompletedTaskWatchdog(task.id);
|
|
82752
|
+
const shouldForkOnScopeMismatch = settings.workflowRevisionForkOnScopeMismatch !== false;
|
|
82753
|
+
let inScopeFeedback = feedback.trim();
|
|
82754
|
+
let outOfScopeFeedback = "";
|
|
82755
|
+
let followUpTaskId;
|
|
82756
|
+
if (shouldForkOnScopeMismatch) {
|
|
82757
|
+
const declaredFileScope = await this.store.parseFileScopeFromPrompt(task.id).catch(() => []);
|
|
82758
|
+
const partition = partitionWorkflowRevisionFeedback(feedback, declaredFileScope);
|
|
82759
|
+
inScopeFeedback = partition.inScopeFeedback;
|
|
82760
|
+
outOfScopeFeedback = partition.outOfScopeFeedback;
|
|
82761
|
+
if (outOfScopeFeedback) {
|
|
82762
|
+
const followUpTask = await this.createWorkflowRevisionFollowUpTask(task, stepName, outOfScopeFeedback);
|
|
82763
|
+
followUpTaskId = followUpTask.id;
|
|
82764
|
+
}
|
|
82765
|
+
}
|
|
82766
|
+
if (!inScopeFeedback) {
|
|
82767
|
+
await this.store.logEntry(
|
|
82768
|
+
task.id,
|
|
82769
|
+
followUpTaskId ? `Workflow step "${stepName}" requested revision \u2014 feedback forked to follow-up ${followUpTaskId}; original task left unchanged` : `Workflow step "${stepName}" requested revision \u2014 no in-scope feedback detected`,
|
|
82770
|
+
outOfScopeFeedback || feedback,
|
|
82771
|
+
this.currentRunContext
|
|
82772
|
+
);
|
|
82773
|
+
return false;
|
|
82774
|
+
}
|
|
81738
82775
|
const updatedTask = await this.store.getTask(task.id);
|
|
81739
82776
|
const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
81740
82777
|
const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
|
|
81741
|
-
|
|
81742
|
-
|
|
81743
|
-
|
|
81744
|
-
feedback
|
|
81745
|
-
);
|
|
81746
|
-
await this.injectWorkflowRevisionInstructions(task, feedback);
|
|
82778
|
+
const logMessage = followUpTaskId ? `Workflow step "${stepName}" requested revision \u2014 split feedback: appended in-scope guidance and forked out-of-scope work to ${followUpTaskId}; ${reopenSummary}` : `Workflow step "${stepName}" requested revision \u2014 feedback appended to original task; ${reopenSummary}`;
|
|
82779
|
+
await this.store.logEntry(task.id, logMessage, inScopeFeedback, this.currentRunContext);
|
|
82780
|
+
await this.injectWorkflowRevisionInstructions(task, inScopeFeedback);
|
|
81747
82781
|
await this.store.updateTask(task.id, {
|
|
81748
82782
|
status: null,
|
|
81749
82783
|
sessionFile: null
|
|
@@ -81754,6 +82788,35 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
81754
82788
|
worktreePath,
|
|
81755
82789
|
`${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`
|
|
81756
82790
|
);
|
|
82791
|
+
return true;
|
|
82792
|
+
}
|
|
82793
|
+
async createWorkflowRevisionFollowUpTask(task, stepName, feedback) {
|
|
82794
|
+
const title = `${task.id}: workflow follow-up from ${stepName}`;
|
|
82795
|
+
const description = [
|
|
82796
|
+
`Follow-up work forked from workflow revision feedback on ${task.id}.`,
|
|
82797
|
+
"",
|
|
82798
|
+
`Original task: ${task.id}${task.title ? ` \u2014 ${task.title}` : ""}`,
|
|
82799
|
+
`Workflow step: ${stepName}`,
|
|
82800
|
+
"",
|
|
82801
|
+
"This feedback referenced files outside the original task's declared File Scope, so it was forked into a follow-up task instead of mutating the original PROMPT.md.",
|
|
82802
|
+
"",
|
|
82803
|
+
"## Out-of-Scope Workflow Revision Feedback",
|
|
82804
|
+
"",
|
|
82805
|
+
feedback
|
|
82806
|
+
].join("\n");
|
|
82807
|
+
return this.store.createTask({
|
|
82808
|
+
title,
|
|
82809
|
+
description,
|
|
82810
|
+
dependencies: [task.id],
|
|
82811
|
+
source: {
|
|
82812
|
+
sourceType: "workflow_step",
|
|
82813
|
+
sourceParentTaskId: task.id,
|
|
82814
|
+
sourceMetadata: {
|
|
82815
|
+
workflowStepName: stepName,
|
|
82816
|
+
routing: "scope-mismatch-fork"
|
|
82817
|
+
}
|
|
82818
|
+
}
|
|
82819
|
+
});
|
|
81757
82820
|
}
|
|
81758
82821
|
/**
|
|
81759
82822
|
* Re-open the last non-pending step so a revision/failure handler gives the
|
|
@@ -87050,7 +88113,7 @@ Follow this process:
|
|
|
87050
88113
|
|
|
87051
88114
|
// ../engine/src/agent-heartbeat.ts
|
|
87052
88115
|
import { Type as Type6 } from "@mariozechner/pi-ai";
|
|
87053
|
-
import { createHash as
|
|
88116
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
87054
88117
|
function formatDuration(ms) {
|
|
87055
88118
|
const totalMinutes = Math.floor(ms / 6e4);
|
|
87056
88119
|
if (totalMinutes < 1) return "<1m";
|
|
@@ -87094,7 +88157,7 @@ function truncatePrompt(text, maxChars) {
|
|
|
87094
88157
|
... (truncated, ${text.length} chars)`;
|
|
87095
88158
|
}
|
|
87096
88159
|
function shortContentHash(value) {
|
|
87097
|
-
return
|
|
88160
|
+
return createHash8("sha256").update(value).digest("hex").slice(0, 8);
|
|
87098
88161
|
}
|
|
87099
88162
|
function buildIdentitySnapshot(args) {
|
|
87100
88163
|
const { agent, resolvedInstructions, workspaceMemory } = args;
|
|
@@ -88946,13 +90009,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
88946
90009
|
});
|
|
88947
90010
|
}
|
|
88948
90011
|
async buildReportsHealthSection(agentId, agentStore) {
|
|
88949
|
-
const
|
|
88950
|
-
if (typeof
|
|
90012
|
+
const storeWithReports = agentStore;
|
|
90013
|
+
if (typeof storeWithReports.getAgentsByReportsTo !== "function") {
|
|
88951
90014
|
return null;
|
|
88952
90015
|
}
|
|
88953
90016
|
let reports;
|
|
88954
90017
|
try {
|
|
88955
|
-
reports = await
|
|
90018
|
+
reports = await storeWithReports.getAgentsByReportsTo(agentId);
|
|
88956
90019
|
} catch (err) {
|
|
88957
90020
|
heartbeatLog.warn(`Failed to load reports for ${agentId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
88958
90021
|
return null;
|
|
@@ -91347,7 +92410,7 @@ var init_cron_runner = __esm({
|
|
|
91347
92410
|
|
|
91348
92411
|
// ../engine/src/routine-runner.ts
|
|
91349
92412
|
import { exec as exec8 } from "node:child_process";
|
|
91350
|
-
import { promisify as
|
|
92413
|
+
import { promisify as promisify9 } from "node:util";
|
|
91351
92414
|
function truncateOutput3(stdout, stderr) {
|
|
91352
92415
|
let output = stdout;
|
|
91353
92416
|
if (stderr) {
|
|
@@ -91369,7 +92432,7 @@ var init_routine_runner = __esm({
|
|
|
91369
92432
|
init_logger2();
|
|
91370
92433
|
init_shell_utils();
|
|
91371
92434
|
log16 = createLogger2("routine-runner");
|
|
91372
|
-
execAsync6 =
|
|
92435
|
+
execAsync6 = promisify9(exec8);
|
|
91373
92436
|
DEFAULT_TIMEOUT_MS8 = 5 * 60 * 1e3;
|
|
91374
92437
|
MAX_BUFFER2 = 1024 * 1024;
|
|
91375
92438
|
MAX_OUTPUT_LENGTH2 = 10 * 1024;
|
|
@@ -92306,17 +93369,25 @@ function isMissingWorktreeSessionStartFailure(error) {
|
|
|
92306
93369
|
if (typeof error !== "string") {
|
|
92307
93370
|
return false;
|
|
92308
93371
|
}
|
|
92309
|
-
return error.includes(
|
|
93372
|
+
return error.includes(MISSING_WORKTREE_SESSION_PREFIX);
|
|
93373
|
+
}
|
|
93374
|
+
function extractMissingWorktreePathFromSessionStartFailure(error) {
|
|
93375
|
+
if (typeof error !== "string") return null;
|
|
93376
|
+
const idx = error.indexOf(MISSING_WORKTREE_SESSION_PREFIX);
|
|
93377
|
+
if (idx < 0) return null;
|
|
93378
|
+
const pathPart = error.slice(idx + MISSING_WORKTREE_SESSION_PREFIX.length).trim();
|
|
93379
|
+
return pathPart.length > 0 ? pathPart : null;
|
|
92310
93380
|
}
|
|
92311
93381
|
function isRecoverableMissingWorktreeReviewFailure(task) {
|
|
92312
93382
|
return task.column === "in-review" && !task.paused && task.status === "failed" && isMissingWorktreeSessionStartFailure(task.error) && hasStepProgress(task);
|
|
92313
93383
|
}
|
|
92314
|
-
var log17, RestartRecoveryCoordinator;
|
|
93384
|
+
var log17, MISSING_WORKTREE_SESSION_PREFIX, RestartRecoveryCoordinator;
|
|
92315
93385
|
var init_restart_recovery_coordinator = __esm({
|
|
92316
93386
|
"../engine/src/restart-recovery-coordinator.ts"() {
|
|
92317
93387
|
"use strict";
|
|
92318
93388
|
init_logger2();
|
|
92319
93389
|
log17 = createLogger2("restart-recovery");
|
|
93390
|
+
MISSING_WORKTREE_SESSION_PREFIX = "Refusing to start coding agent in missing worktree:";
|
|
92320
93391
|
RestartRecoveryCoordinator = class {
|
|
92321
93392
|
constructor(store, executor) {
|
|
92322
93393
|
this.store = store;
|
|
@@ -92360,8 +93431,8 @@ var init_restart_recovery_coordinator = __esm({
|
|
|
92360
93431
|
|
|
92361
93432
|
// ../engine/src/self-healing.ts
|
|
92362
93433
|
import { exec as exec9, execSync as execSync2 } from "node:child_process";
|
|
92363
|
-
import { promisify as
|
|
92364
|
-
import { existsSync as existsSync33, readdirSync as readdirSync5, rmSync as rmSync3, statSync as
|
|
93434
|
+
import { promisify as promisify10 } from "node:util";
|
|
93435
|
+
import { existsSync as existsSync33, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync6 } from "node:fs";
|
|
92365
93436
|
import { isAbsolute as isAbsolute14, join as join41, relative as relative10, resolve as resolve20 } from "node:path";
|
|
92366
93437
|
function commitOwnedByTask2(taskId, lineageId, subject, body) {
|
|
92367
93438
|
if (lineageId && body.includes(`Fusion-Task-Lineage: ${lineageId}`)) {
|
|
@@ -92454,7 +93525,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
92454
93525
|
function hasStepProgress2(task) {
|
|
92455
93526
|
return task.steps.some((step) => step.status !== "pending");
|
|
92456
93527
|
}
|
|
92457
|
-
var log18, execAsync7, APPROVED_TRIAGE_RECOVERY_GRACE_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES, NON_TERMINAL_STEP_STATUSES2, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_AUTO_MERGE_RETRIES, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, SelfHealingManager;
|
|
93528
|
+
var log18, execAsync7, APPROVED_TRIAGE_RECOVERY_GRACE_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES, NON_TERMINAL_STEP_STATUSES2, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, MAX_AUTO_MERGE_RETRIES, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS, DURABLE_ERROR_RECOVERY_MAX_RETRIES, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, SelfHealingManager;
|
|
92458
93529
|
var init_self_healing = __esm({
|
|
92459
93530
|
"../engine/src/self-healing.ts"() {
|
|
92460
93531
|
"use strict";
|
|
@@ -92464,7 +93535,7 @@ var init_self_healing = __esm({
|
|
|
92464
93535
|
init_restart_recovery_coordinator();
|
|
92465
93536
|
init_transient_error_detector();
|
|
92466
93537
|
log18 = createLogger2("self-healing");
|
|
92467
|
-
execAsync7 =
|
|
93538
|
+
execAsync7 = promisify10(exec9);
|
|
92468
93539
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
92469
93540
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
92470
93541
|
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
@@ -92480,6 +93551,7 @@ var init_self_healing = __esm({
|
|
|
92480
93551
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
92481
93552
|
MAX_TASK_DONE_RETRIES = 3;
|
|
92482
93553
|
MAX_AUTO_MERGE_RETRIES = 3;
|
|
93554
|
+
MAX_STARVATION_DROPS = 3;
|
|
92483
93555
|
DEADLOCK_RECOVERY_COOLDOWN_MS = 15 * 6e4;
|
|
92484
93556
|
DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS = 5 * 6e4;
|
|
92485
93557
|
DURABLE_ERROR_RECOVERY_MAX_RETRIES = 5;
|
|
@@ -92502,6 +93574,7 @@ var init_self_healing = __esm({
|
|
|
92502
93574
|
settingsListener = null;
|
|
92503
93575
|
// ── Per-task deadlock recovery cooldown ─────────────────────────────
|
|
92504
93576
|
deadlockRecoveryCooldown = /* @__PURE__ */ new Map();
|
|
93577
|
+
mergeStarvationDrops = /* @__PURE__ */ new Map();
|
|
92505
93578
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
92506
93579
|
start() {
|
|
92507
93580
|
this.settingsListener = ({ settings, previous }) => {
|
|
@@ -93225,6 +94298,7 @@ var init_self_healing = __esm({
|
|
|
93225
94298
|
try {
|
|
93226
94299
|
log18.warn(`Clearing stale merge status for ${task.id}: ${previousStatus}`);
|
|
93227
94300
|
await this.store.updateTask(task.id, { status: null });
|
|
94301
|
+
this.options.clearMergeActive?.(task.id);
|
|
93228
94302
|
await this.store.logEntry(
|
|
93229
94303
|
task.id,
|
|
93230
94304
|
`Auto-recovered: cleared stale '${previousStatus}' status (no active merger)`
|
|
@@ -93295,6 +94369,8 @@ var init_self_healing = __esm({
|
|
|
93295
94369
|
reason = `blocker ${blockerId} in-review + paused`;
|
|
93296
94370
|
} else if (blocker.column === "in-review" && blocker.status === "failed" && (blocker.mergeRetries ?? 0) >= MAX_AUTO_MERGE_RETRIES) {
|
|
93297
94371
|
reason = `blocker ${blockerId} in-review + failed (mergeRetries ${blocker.mergeRetries ?? 0}/${MAX_AUTO_MERGE_RETRIES})`;
|
|
94372
|
+
} else if (blocker.column === "in-review" && blocker.status === "failed" && isMissingWorktreeSessionStartFailure(blocker.error)) {
|
|
94373
|
+
reason = `blocker ${blockerId} in-review + failed (missing-worktree session start)`;
|
|
93298
94374
|
} else if (task.dependencies.length > 0 && !unresolvedDeps.includes(blockerId)) {
|
|
93299
94375
|
reason = `blocker ${blockerId} not among unresolved dependencies`;
|
|
93300
94376
|
}
|
|
@@ -93404,7 +94480,7 @@ var init_self_healing = __esm({
|
|
|
93404
94480
|
if (settings.globalPause || settings.enginePaused) return 0;
|
|
93405
94481
|
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
|
93406
94482
|
const mergeable = tasks.filter(
|
|
93407
|
-
(t) => t.column === "in-review" && !t.paused && // Exclude transient merge statuses. Active merges should be left alone;
|
|
94483
|
+
(t) => t.column === "in-review" && !t.paused && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
|
|
93408
94484
|
// stale ones are handled by recoverStaleMergingStatus().
|
|
93409
94485
|
t.status !== "merging" && t.status !== "merging-pr" && Boolean(t.worktree) && t.mergeDetails?.mergeConfirmed !== true && t.mergeDetails?.noOpMerge !== true && !hasTerminalInvalidDoneTransition(t) && // Mirror ProjectEngine.canMergeTask retry gate. If retries are already
|
|
93410
94486
|
// exhausted, re-enqueueing here is a no-op and each recovery log write
|
|
@@ -93413,6 +94489,13 @@ var init_self_healing = __esm({
|
|
|
93413
94489
|
// in case updateTask(moveTask) is briefly out-of-order during recovery.
|
|
93414
94490
|
(t.mergeRetries ?? 0) < MAX_AUTO_MERGE_RETRIES && getTaskMergeBlocker(t) === void 0
|
|
93415
94491
|
);
|
|
94492
|
+
const inReviewIds = new Set(tasks.map((task) => task.id));
|
|
94493
|
+
const mergeableIds = new Set(mergeable.map((task) => task.id));
|
|
94494
|
+
for (const taskId of [...this.mergeStarvationDrops.keys()]) {
|
|
94495
|
+
if (!inReviewIds.has(taskId) || !mergeableIds.has(taskId)) {
|
|
94496
|
+
this.mergeStarvationDrops.delete(taskId);
|
|
94497
|
+
}
|
|
94498
|
+
}
|
|
93416
94499
|
if (mergeable.length === 0) return 0;
|
|
93417
94500
|
log18.warn(`Found ${mergeable.length} mergeable review task(s) stuck in in-review`);
|
|
93418
94501
|
const enqueueMerge = this.options.enqueueMerge;
|
|
@@ -93420,7 +94503,23 @@ var init_self_healing = __esm({
|
|
|
93420
94503
|
for (const task of mergeable) {
|
|
93421
94504
|
try {
|
|
93422
94505
|
if (enqueueMerge) {
|
|
93423
|
-
enqueueMerge(task.id);
|
|
94506
|
+
const queued = enqueueMerge(task.id);
|
|
94507
|
+
if (!queued) {
|
|
94508
|
+
const drops = (this.mergeStarvationDrops.get(task.id) ?? 0) + 1;
|
|
94509
|
+
this.mergeStarvationDrops.set(task.id, drops);
|
|
94510
|
+
log18.warn(
|
|
94511
|
+
`Auto-recovery enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); engine merge queue rejected re-enqueue`
|
|
94512
|
+
);
|
|
94513
|
+
if (drops >= MAX_STARVATION_DROPS) {
|
|
94514
|
+
const error = `Auto-merge starvation: ${MAX_STARVATION_DROPS} consecutive enqueue attempts were dropped by the engine merge queue; task requires manual intervention.`;
|
|
94515
|
+
await this.store.updateTask(task.id, { status: "failed", error });
|
|
94516
|
+
await this.store.logEntry(task.id, error);
|
|
94517
|
+
this.mergeStarvationDrops.delete(task.id);
|
|
94518
|
+
recovered++;
|
|
94519
|
+
}
|
|
94520
|
+
continue;
|
|
94521
|
+
}
|
|
94522
|
+
this.mergeStarvationDrops.delete(task.id);
|
|
93424
94523
|
} else {
|
|
93425
94524
|
await this.store.mergeTask(task.id);
|
|
93426
94525
|
}
|
|
@@ -94470,16 +95569,18 @@ var init_self_healing = __esm({
|
|
|
94470
95569
|
for (const task of candidates) {
|
|
94471
95570
|
try {
|
|
94472
95571
|
const staleWorktree = task.worktree;
|
|
95572
|
+
const missingWorktreePath = extractMissingWorktreePathFromSessionStartFailure(task.error);
|
|
95573
|
+
const hasMismatchedLiveWorktree = typeof staleWorktree === "string" && staleWorktree.length > 0 && typeof missingWorktreePath === "string" && missingWorktreePath.length > 0 && resolve20(staleWorktree) !== resolve20(missingWorktreePath);
|
|
94473
95574
|
await this.store.updateTask(task.id, {
|
|
94474
95575
|
status: null,
|
|
94475
95576
|
error: null,
|
|
94476
|
-
worktree: null,
|
|
94477
|
-
branch: null,
|
|
95577
|
+
worktree: hasMismatchedLiveWorktree ? staleWorktree : null,
|
|
95578
|
+
branch: hasMismatchedLiveWorktree ? task.branch ?? null : null,
|
|
94478
95579
|
sessionFile: null
|
|
94479
95580
|
});
|
|
94480
95581
|
await this.store.logEntry(
|
|
94481
95582
|
task.id,
|
|
94482
|
-
`Auto-recovered: retry/verification session targeted missing worktree${staleWorktree ? ` (${staleWorktree})` : ""} \u2014 cleared stale session metadata and requeued to todo`
|
|
95583
|
+
hasMismatchedLiveWorktree ? `Auto-recovered: stale resume referenced missing worktree (${missingWorktreePath}) while live task worktree is ${staleWorktree} \u2014 cleared stale session metadata and requeued to todo` : `Auto-recovered: retry/verification session targeted missing worktree${staleWorktree ? ` (${staleWorktree})` : ""} \u2014 cleared stale session metadata and requeued to todo`
|
|
94483
95584
|
);
|
|
94484
95585
|
await this.store.moveTask(task.id, "todo", { preserveProgress: true });
|
|
94485
95586
|
recovered++;
|
|
@@ -94873,7 +95974,7 @@ var init_self_healing = __esm({
|
|
|
94873
95974
|
if (idle.length === 0) return;
|
|
94874
95975
|
const withMtime = idle.map((p) => {
|
|
94875
95976
|
try {
|
|
94876
|
-
return { path: p, mtime:
|
|
95977
|
+
return { path: p, mtime: statSync6(p).mtimeMs };
|
|
94877
95978
|
} catch (err) {
|
|
94878
95979
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
94879
95980
|
log18.warn(`Failed to read mtime for worktree ${p}: ${errorMessage} \u2014 defaulting mtime to 0`);
|
|
@@ -96088,6 +97189,7 @@ var init_in_process_runtime = __esm({
|
|
|
96088
97189
|
* before `start()` via `setMergeEnqueuer`.
|
|
96089
97190
|
*/
|
|
96090
97191
|
mergeEnqueuer;
|
|
97192
|
+
clearMergeActive;
|
|
96091
97193
|
activeMergeTaskIdProvider;
|
|
96092
97194
|
/** Tracks whether startup recovery was intentionally deferred due to pause state. */
|
|
96093
97195
|
startupRecoveryDeferred = false;
|
|
@@ -96485,7 +97587,8 @@ var init_in_process_runtime = __esm({
|
|
|
96485
97587
|
recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
|
|
96486
97588
|
getPlanningTaskIds: () => this.triageProcessor?.getProcessingTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
96487
97589
|
evictStaleTriageProcessing: () => this.triageProcessor?.evictStaleProcessing() ?? /* @__PURE__ */ new Set(),
|
|
96488
|
-
enqueueMerge: this.mergeEnqueuer ? (taskId) => this.mergeEnqueuer?.(taskId) : void 0,
|
|
97590
|
+
enqueueMerge: this.mergeEnqueuer ? (taskId) => this.mergeEnqueuer?.(taskId) ?? false : void 0,
|
|
97591
|
+
clearMergeActive: this.clearMergeActive ? (taskId) => this.clearMergeActive?.(taskId) : void 0,
|
|
96489
97592
|
getActiveMergeTaskId: () => this.activeMergeTaskIdProvider?.() ?? null,
|
|
96490
97593
|
leaseManager: this.leaseManager,
|
|
96491
97594
|
hasActiveAgentExecution: (agentId) => this.heartbeatMonitor?.getTrackedAgents().includes(agentId) ?? false,
|
|
@@ -96679,6 +97782,9 @@ var init_in_process_runtime = __esm({
|
|
|
96679
97782
|
setMergeEnqueuer(enqueueMerge) {
|
|
96680
97783
|
this.mergeEnqueuer = enqueueMerge;
|
|
96681
97784
|
}
|
|
97785
|
+
setMergeActiveClearer(clearMergeActive) {
|
|
97786
|
+
this.clearMergeActive = clearMergeActive;
|
|
97787
|
+
}
|
|
96682
97788
|
setActiveMergeTaskIdProvider(getActiveMergeTaskId) {
|
|
96683
97789
|
this.activeMergeTaskIdProvider = getActiveMergeTaskId;
|
|
96684
97790
|
}
|
|
@@ -98783,8 +99889,8 @@ var init_provider_adapters = __esm({
|
|
|
98783
99889
|
|
|
98784
99890
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
98785
99891
|
import { EventEmitter as EventEmitter23 } from "node:events";
|
|
98786
|
-
import { exec as exec10, execFile as
|
|
98787
|
-
import { promisify as
|
|
99892
|
+
import { exec as exec10, execFile as execFile5, spawn as spawn5 } from "node:child_process";
|
|
99893
|
+
import { promisify as promisify11 } from "node:util";
|
|
98788
99894
|
function nowIso2() {
|
|
98789
99895
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
98790
99896
|
}
|
|
@@ -98824,7 +99930,7 @@ function toStateError(code, err) {
|
|
|
98824
99930
|
at: nowIso2()
|
|
98825
99931
|
};
|
|
98826
99932
|
}
|
|
98827
|
-
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2,
|
|
99933
|
+
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, execFileAsync3, execAsync8, LineBuffer, TunnelProcessManager;
|
|
98828
99934
|
var init_tunnel_process_manager = __esm({
|
|
98829
99935
|
"../engine/src/remote-access/tunnel-process-manager.ts"() {
|
|
98830
99936
|
"use strict";
|
|
@@ -98832,8 +99938,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
98832
99938
|
init_provider_adapters();
|
|
98833
99939
|
DEFAULT_MAX_LOG_ENTRIES = 400;
|
|
98834
99940
|
DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
|
|
98835
|
-
|
|
98836
|
-
execAsync8 =
|
|
99941
|
+
execFileAsync3 = promisify11(execFile5);
|
|
99942
|
+
execAsync8 = promisify11(exec10);
|
|
98837
99943
|
LineBuffer = class {
|
|
98838
99944
|
pending = "";
|
|
98839
99945
|
push(chunk) {
|
|
@@ -98910,7 +100016,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
98910
100016
|
return null;
|
|
98911
100017
|
}
|
|
98912
100018
|
try {
|
|
98913
|
-
const { stdout } = await
|
|
100019
|
+
const { stdout } = await execFileAsync3("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
98914
100020
|
const data = JSON.parse(String(stdout));
|
|
98915
100021
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
98916
100022
|
if (!dnsName) {
|
|
@@ -98933,7 +100039,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
98933
100039
|
];
|
|
98934
100040
|
for (const resetCommand of resetCommands) {
|
|
98935
100041
|
try {
|
|
98936
|
-
await
|
|
100042
|
+
await execFileAsync3(resetCommand.command, resetCommand.args, { timeout: 5e3 });
|
|
98937
100043
|
return;
|
|
98938
100044
|
} catch {
|
|
98939
100045
|
}
|
|
@@ -99232,8 +100338,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
99232
100338
|
});
|
|
99233
100339
|
|
|
99234
100340
|
// ../engine/src/project-engine.ts
|
|
99235
|
-
import { execFile as
|
|
99236
|
-
import { promisify as
|
|
100341
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
100342
|
+
import { promisify as promisify12 } from "node:util";
|
|
99237
100343
|
function formatErrorDetails(error) {
|
|
99238
100344
|
if (error instanceof Error) {
|
|
99239
100345
|
return {
|
|
@@ -99248,7 +100354,7 @@ function isInvalidDoneTransitionError(error) {
|
|
|
99248
100354
|
const message = error instanceof Error ? error.message : String(error);
|
|
99249
100355
|
return message.includes("Invalid transition:") && message.includes("\u2192 'done'");
|
|
99250
100356
|
}
|
|
99251
|
-
var
|
|
100357
|
+
var execFileAsync4, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
99252
100358
|
var init_project_engine = __esm({
|
|
99253
100359
|
"../engine/src/project-engine.ts"() {
|
|
99254
100360
|
"use strict";
|
|
@@ -99266,7 +100372,7 @@ var init_project_engine = __esm({
|
|
|
99266
100372
|
init_research_orchestrator();
|
|
99267
100373
|
init_research_step_runner();
|
|
99268
100374
|
init_tunnel_process_manager();
|
|
99269
|
-
|
|
100375
|
+
execFileAsync4 = promisify12(execFile6);
|
|
99270
100376
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
99271
100377
|
isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
|
99272
100378
|
ProjectEngine = class _ProjectEngine {
|
|
@@ -99285,7 +100391,10 @@ var init_project_engine = __esm({
|
|
|
99285
100391
|
this.activeMergeTaskId = null;
|
|
99286
100392
|
}
|
|
99287
100393
|
this.mergeActive.delete(taskId);
|
|
99288
|
-
this.internalEnqueueMerge(taskId);
|
|
100394
|
+
return this.internalEnqueueMerge(taskId);
|
|
100395
|
+
});
|
|
100396
|
+
this.runtime.setMergeActiveClearer?.((taskId) => {
|
|
100397
|
+
this.mergeActive.delete(taskId);
|
|
99289
100398
|
});
|
|
99290
100399
|
}
|
|
99291
100400
|
runtime;
|
|
@@ -99320,6 +100429,7 @@ var init_project_engine = __esm({
|
|
|
99320
100429
|
mergeAbortController = null;
|
|
99321
100430
|
mergeRetryTimer = null;
|
|
99322
100431
|
autostashSweepTimer = null;
|
|
100432
|
+
mergeActiveReconcileTimer = null;
|
|
99323
100433
|
/**
|
|
99324
100434
|
* Pending manual merge resolvers — keyed by taskId.
|
|
99325
100435
|
* When `onMerge` is called, the task is enqueued like auto-merge but a
|
|
@@ -99515,6 +100625,7 @@ ${detail}`
|
|
|
99515
100625
|
this.wireAutostashOrphanRecovery(store);
|
|
99516
100626
|
await this.startupMergeSweep(store);
|
|
99517
100627
|
this.scheduleMergeRetry(store);
|
|
100628
|
+
this.scheduleMergeActiveReconciliation(settings.maintenanceIntervalMs ?? 9e5);
|
|
99518
100629
|
void this.runStaleAutostashSweep(store, "startup");
|
|
99519
100630
|
this.scheduleStaleAutostashSweep(store);
|
|
99520
100631
|
this.started = true;
|
|
@@ -99540,6 +100651,10 @@ ${detail}`
|
|
|
99540
100651
|
clearTimeout(this.autostashSweepTimer);
|
|
99541
100652
|
this.autostashSweepTimer = null;
|
|
99542
100653
|
}
|
|
100654
|
+
if (this.mergeActiveReconcileTimer) {
|
|
100655
|
+
clearInterval(this.mergeActiveReconcileTimer);
|
|
100656
|
+
this.mergeActiveReconcileTimer = null;
|
|
100657
|
+
}
|
|
99543
100658
|
this.mergeAbortController?.abort();
|
|
99544
100659
|
this.mergeAbortController = null;
|
|
99545
100660
|
this.activeMergeTaskId = null;
|
|
@@ -99761,7 +100876,7 @@ ${detail}`
|
|
|
99761
100876
|
* an external `onMerge` callback (e.g. dashboard's createServer call).
|
|
99762
100877
|
*/
|
|
99763
100878
|
enqueueMerge(taskId) {
|
|
99764
|
-
this.internalEnqueueMerge(taskId);
|
|
100879
|
+
return this.internalEnqueueMerge(taskId);
|
|
99765
100880
|
}
|
|
99766
100881
|
/**
|
|
99767
100882
|
* Perform an AI-powered merge for a task, serialized through the merge queue.
|
|
@@ -99778,7 +100893,10 @@ ${detail}`
|
|
|
99778
100893
|
}
|
|
99779
100894
|
return new Promise((resolve24, reject) => {
|
|
99780
100895
|
this.manualMergeResolvers.set(taskId, { resolve: resolve24, reject });
|
|
99781
|
-
this.internalEnqueueMerge(taskId)
|
|
100896
|
+
if (!this.internalEnqueueMerge(taskId)) {
|
|
100897
|
+
this.manualMergeResolvers.delete(taskId);
|
|
100898
|
+
reject(new Error(`Merge enqueue rejected for ${taskId}`));
|
|
100899
|
+
}
|
|
99782
100900
|
});
|
|
99783
100901
|
}
|
|
99784
100902
|
setRestoreDiagnostics(outcome, reason, provider, message) {
|
|
@@ -99956,7 +101074,7 @@ ${detail}`
|
|
|
99956
101074
|
async checkExecutableAvailable(command) {
|
|
99957
101075
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
99958
101076
|
try {
|
|
99959
|
-
await
|
|
101077
|
+
await execFileAsync4(checker, [command]);
|
|
99960
101078
|
return { available: true };
|
|
99961
101079
|
} catch {
|
|
99962
101080
|
return {
|
|
@@ -100045,15 +101163,17 @@ ${detail}`
|
|
|
100045
101163
|
return void 0;
|
|
100046
101164
|
}
|
|
100047
101165
|
internalEnqueueMerge(taskId) {
|
|
100048
|
-
if (this.shuttingDown) return;
|
|
101166
|
+
if (this.shuttingDown) return false;
|
|
100049
101167
|
if (this.mergeActive.has(taskId)) {
|
|
100050
101168
|
const isActuallyLive = this.mergeQueue.includes(taskId) || this.activeMergeTaskId === taskId;
|
|
100051
101169
|
if (!isActuallyLive) {
|
|
100052
101170
|
runtimeLog.warn(
|
|
100053
|
-
`internalEnqueueMerge(${taskId}): skipped \u2014 mergeActive entry is leaked (not queued, not active).
|
|
101171
|
+
`internalEnqueueMerge(${taskId}): skipped \u2014 mergeActive entry is leaked (not queued, not active). Reconciling stale entry and retrying enqueue now.`
|
|
100054
101172
|
);
|
|
101173
|
+
this.mergeActive.delete(taskId);
|
|
101174
|
+
} else {
|
|
101175
|
+
return false;
|
|
100055
101176
|
}
|
|
100056
|
-
return;
|
|
100057
101177
|
}
|
|
100058
101178
|
this.mergeActive.add(taskId);
|
|
100059
101179
|
this.mergeQueue.push(taskId);
|
|
@@ -100062,6 +101182,7 @@ ${detail}`
|
|
|
100062
101182
|
`Merge queue drain failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`
|
|
100063
101183
|
);
|
|
100064
101184
|
});
|
|
101185
|
+
return true;
|
|
100065
101186
|
}
|
|
100066
101187
|
/**
|
|
100067
101188
|
* Filter a sweep's listTasks() result to merge-eligible tasks, sort by
|
|
@@ -100080,6 +101201,27 @@ ${detail}`
|
|
|
100080
101201
|
}
|
|
100081
101202
|
return eligible.length;
|
|
100082
101203
|
}
|
|
101204
|
+
reconcileStaleMergeActive() {
|
|
101205
|
+
let cleared = 0;
|
|
101206
|
+
for (const taskId of [...this.mergeActive]) {
|
|
101207
|
+
if (taskId === this.activeMergeTaskId) continue;
|
|
101208
|
+
if (this.mergeQueue.includes(taskId)) continue;
|
|
101209
|
+
this.mergeActive.delete(taskId);
|
|
101210
|
+
cleared++;
|
|
101211
|
+
}
|
|
101212
|
+
return cleared;
|
|
101213
|
+
}
|
|
101214
|
+
scheduleMergeActiveReconciliation(intervalMs) {
|
|
101215
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
101216
|
+
return;
|
|
101217
|
+
}
|
|
101218
|
+
this.mergeActiveReconcileTimer = setInterval(() => {
|
|
101219
|
+
const cleared = this.reconcileStaleMergeActive();
|
|
101220
|
+
if (cleared > 0) {
|
|
101221
|
+
runtimeLog.warn(`Reconciled ${cleared} stale mergeActive entr${cleared === 1 ? "y" : "ies"}`);
|
|
101222
|
+
}
|
|
101223
|
+
}, intervalMs);
|
|
101224
|
+
}
|
|
100083
101225
|
async findActiveRecoveryFollowUp(store, parentTaskId, branch) {
|
|
100084
101226
|
const tasks = await store.listTasks({ slim: true }).catch(() => []);
|
|
100085
101227
|
const activeRecoveryTasks = tasks.filter(
|
|
@@ -100099,6 +101241,7 @@ ${detail}`
|
|
|
100099
101241
|
if (this.mergeRunning) return;
|
|
100100
101242
|
this.mergeRunning = true;
|
|
100101
101243
|
try {
|
|
101244
|
+
this.reconcileStaleMergeActive();
|
|
100102
101245
|
const store = this.runtime.getTaskStore();
|
|
100103
101246
|
const cwd = this.config.workingDirectory;
|
|
100104
101247
|
while (this.mergeQueue.length > 0 && !this.shuttingDown) {
|
|
@@ -100878,6 +102021,21 @@ ${detail}`
|
|
|
100878
102021
|
};
|
|
100879
102022
|
store.on("settings:updated", onEngineUnpause);
|
|
100880
102023
|
this.settingsHandlers.push(onEngineUnpause);
|
|
102024
|
+
const onMaintenanceIntervalChange = ({
|
|
102025
|
+
settings: s,
|
|
102026
|
+
previous: prev
|
|
102027
|
+
}) => {
|
|
102028
|
+
if (s.maintenanceIntervalMs === prev.maintenanceIntervalMs) {
|
|
102029
|
+
return;
|
|
102030
|
+
}
|
|
102031
|
+
if (this.mergeActiveReconcileTimer) {
|
|
102032
|
+
clearInterval(this.mergeActiveReconcileTimer);
|
|
102033
|
+
this.mergeActiveReconcileTimer = null;
|
|
102034
|
+
}
|
|
102035
|
+
this.scheduleMergeActiveReconciliation(s.maintenanceIntervalMs ?? 9e5);
|
|
102036
|
+
};
|
|
102037
|
+
store.on("settings:updated", onMaintenanceIntervalChange);
|
|
102038
|
+
this.settingsHandlers.push(onMaintenanceIntervalChange);
|
|
100881
102039
|
const onStuckTimeoutChange = async ({
|
|
100882
102040
|
settings: s,
|
|
100883
102041
|
previous: prev
|
|
@@ -101977,6 +103135,7 @@ __export(src_exports2, {
|
|
|
101977
103135
|
applyAutostashBySha: () => applyAutostashBySha,
|
|
101978
103136
|
applyUnavailableNodePolicy: () => applyUnavailableNodePolicy,
|
|
101979
103137
|
assertSafeUrl: () => assertSafeUrl,
|
|
103138
|
+
auditSquashMerge: () => auditSquashMerge,
|
|
101980
103139
|
buildAgentChatPrompt: () => buildAgentChatPrompt,
|
|
101981
103140
|
buildNtfyClickUrl: () => buildNtfyClickUrl,
|
|
101982
103141
|
buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
|
|
@@ -102002,6 +103161,7 @@ __export(src_exports2, {
|
|
|
102002
103161
|
extractRuntimeHint: () => extractRuntimeHint,
|
|
102003
103162
|
extractRuntimeModel: () => extractRuntimeModel,
|
|
102004
103163
|
fetchWebContent: () => fetchWebContent,
|
|
103164
|
+
formatSquashAuditReport: () => formatSquashAuditReport,
|
|
102005
103165
|
formatTaskIdentifier: () => formatTaskIdentifier,
|
|
102006
103166
|
generateReservedWorktreeName: () => generateReservedWorktreeName,
|
|
102007
103167
|
generateWorktreeName: () => generateWorktreeName,
|
|
@@ -102056,6 +103216,7 @@ var init_src2 = __esm({
|
|
|
102056
103216
|
init_mission_autopilot();
|
|
102057
103217
|
init_mission_execution_loop();
|
|
102058
103218
|
init_merger();
|
|
103219
|
+
init_merger_squash_audit();
|
|
102059
103220
|
init_reviewer();
|
|
102060
103221
|
init_pi();
|
|
102061
103222
|
init_pi();
|
|
@@ -107101,13 +108262,13 @@ var init_github_poll = __esm({
|
|
|
107101
108262
|
});
|
|
107102
108263
|
|
|
107103
108264
|
// ../dashboard/src/routes/resolve-diff-base.ts
|
|
107104
|
-
import { execFile as
|
|
107105
|
-
import { promisify as
|
|
107106
|
-
var
|
|
108265
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
108266
|
+
import { promisify as promisify13 } from "node:util";
|
|
108267
|
+
var execFileAsync5;
|
|
107107
108268
|
var init_resolve_diff_base = __esm({
|
|
107108
108269
|
"../dashboard/src/routes/resolve-diff-base.ts"() {
|
|
107109
108270
|
"use strict";
|
|
107110
|
-
|
|
108271
|
+
execFileAsync5 = promisify13(execFile7);
|
|
107111
108272
|
}
|
|
107112
108273
|
});
|
|
107113
108274
|
|
|
@@ -112299,13 +113460,13 @@ var init_register_agents_projects_nodes = __esm({
|
|
|
112299
113460
|
});
|
|
112300
113461
|
|
|
112301
113462
|
// ../dashboard/src/exec-file.ts
|
|
112302
|
-
import { execFile as
|
|
112303
|
-
import { promisify as
|
|
112304
|
-
var
|
|
113463
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
113464
|
+
import { promisify as promisify14 } from "node:util";
|
|
113465
|
+
var execFileAsync6;
|
|
112305
113466
|
var init_exec_file = __esm({
|
|
112306
113467
|
"../dashboard/src/exec-file.ts"() {
|
|
112307
113468
|
"use strict";
|
|
112308
|
-
|
|
113469
|
+
execFileAsync6 = promisify14(execFile8);
|
|
112309
113470
|
}
|
|
112310
113471
|
});
|
|
112311
113472
|
|
|
@@ -117849,7 +119010,7 @@ var require_websocket = __commonJS({
|
|
|
117849
119010
|
var http = __require("http");
|
|
117850
119011
|
var net = __require("net");
|
|
117851
119012
|
var tls = __require("tls");
|
|
117852
|
-
var { randomBytes: randomBytes3, createHash:
|
|
119013
|
+
var { randomBytes: randomBytes3, createHash: createHash9 } = __require("crypto");
|
|
117853
119014
|
var { Duplex, Readable } = __require("stream");
|
|
117854
119015
|
var { URL: URL2 } = __require("url");
|
|
117855
119016
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -118509,7 +119670,7 @@ var require_websocket = __commonJS({
|
|
|
118509
119670
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
118510
119671
|
return;
|
|
118511
119672
|
}
|
|
118512
|
-
const digest =
|
|
119673
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
118513
119674
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
118514
119675
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
118515
119676
|
return;
|
|
@@ -118876,7 +120037,7 @@ var require_websocket_server = __commonJS({
|
|
|
118876
120037
|
var EventEmitter32 = __require("events");
|
|
118877
120038
|
var http = __require("http");
|
|
118878
120039
|
var { Duplex } = __require("stream");
|
|
118879
|
-
var { createHash:
|
|
120040
|
+
var { createHash: createHash9 } = __require("crypto");
|
|
118880
120041
|
var extension2 = require_extension();
|
|
118881
120042
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
118882
120043
|
var subprotocol2 = require_subprotocol();
|
|
@@ -119177,7 +120338,7 @@ var require_websocket_server = __commonJS({
|
|
|
119177
120338
|
);
|
|
119178
120339
|
}
|
|
119179
120340
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
119180
|
-
const digest =
|
|
120341
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
119181
120342
|
const headers = [
|
|
119182
120343
|
"HTTP/1.1 101 Switching Protocols",
|
|
119183
120344
|
"Upgrade: websocket",
|
|
@@ -119607,7 +120768,7 @@ __export(task_exports, {
|
|
|
119607
120768
|
runTaskUpdate: () => runTaskUpdate
|
|
119608
120769
|
});
|
|
119609
120770
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
119610
|
-
import { watchFile, unwatchFile, statSync as
|
|
120771
|
+
import { watchFile, unwatchFile, statSync as statSync7, existsSync as existsSync35, readFileSync as readFileSync13 } from "node:fs";
|
|
119611
120772
|
import { basename as basename10, join as join46 } from "node:path";
|
|
119612
120773
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
119613
120774
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
@@ -119929,7 +121090,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
119929
121090
|
let lastPosition = 0;
|
|
119930
121091
|
let lastSize = 0;
|
|
119931
121092
|
try {
|
|
119932
|
-
const stats =
|
|
121093
|
+
const stats = statSync7(logPath);
|
|
119933
121094
|
lastSize = stats.size;
|
|
119934
121095
|
lastPosition = lastSize;
|
|
119935
121096
|
} catch {
|
|
@@ -119946,7 +121107,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
119946
121107
|
watchFile(logPath, { interval: 1e3 }, () => {
|
|
119947
121108
|
if (isShuttingDown) return;
|
|
119948
121109
|
try {
|
|
119949
|
-
const stats =
|
|
121110
|
+
const stats = statSync7(logPath);
|
|
119950
121111
|
if (stats.size < lastPosition) {
|
|
119951
121112
|
lastPosition = 0;
|
|
119952
121113
|
}
|
|
@@ -121347,6 +122508,9 @@ function kbExtension(pi) {
|
|
|
121347
122508
|
Type8.String({
|
|
121348
122509
|
description: "Agent ID to assign this task to (e.g. 'agent-abc123')"
|
|
121349
122510
|
})
|
|
122511
|
+
),
|
|
122512
|
+
priority: Type8.Optional(
|
|
122513
|
+
StringEnum([...TASK_PRIORITIES], { description: "Task priority (low, normal, high, urgent)" })
|
|
121350
122514
|
)
|
|
121351
122515
|
}),
|
|
121352
122516
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
@@ -121363,31 +122527,45 @@ function kbExtension(pi) {
|
|
|
121363
122527
|
};
|
|
121364
122528
|
}
|
|
121365
122529
|
}
|
|
121366
|
-
|
|
121367
|
-
|
|
121368
|
-
|
|
121369
|
-
|
|
121370
|
-
|
|
121371
|
-
|
|
121372
|
-
|
|
121373
|
-
|
|
121374
|
-
|
|
121375
|
-
|
|
121376
|
-
|
|
121377
|
-
|
|
122530
|
+
try {
|
|
122531
|
+
const task = await store.createTask({
|
|
122532
|
+
description: params.description.trim(),
|
|
122533
|
+
dependencies: params.depends,
|
|
122534
|
+
assignedAgentId: normalizedAgentId === null ? void 0 : normalizedAgentId,
|
|
122535
|
+
priority: params.priority,
|
|
122536
|
+
source: { sourceType: "api" }
|
|
122537
|
+
});
|
|
122538
|
+
const label = task.description.length > 80 ? task.description.slice(0, 80) + "\u2026" : task.description;
|
|
122539
|
+
return {
|
|
122540
|
+
content: [
|
|
122541
|
+
{
|
|
122542
|
+
type: "text",
|
|
122543
|
+
text: `Created ${task.id}: ${label}
|
|
121378
122544
|
Column: triage
|
|
121379
122545
|
` + (task.dependencies.length ? `Dependencies: ${task.dependencies.join(", ")}
|
|
121380
122546
|
` : "") + (task.assignedAgentId ? `Assigned to: ${task.assignedAgentId}
|
|
121381
|
-
` : "") + `
|
|
122547
|
+
` : "") + `Priority: ${task.priority}
|
|
122548
|
+
Path: .fusion/tasks/${task.id}/`
|
|
122549
|
+
}
|
|
122550
|
+
],
|
|
122551
|
+
details: {
|
|
122552
|
+
taskId: task.id,
|
|
122553
|
+
column: task.column,
|
|
122554
|
+
dependencies: task.dependencies,
|
|
122555
|
+
assignedAgentId: task.assignedAgentId,
|
|
122556
|
+
priority: task.priority
|
|
121382
122557
|
}
|
|
121383
|
-
|
|
121384
|
-
|
|
121385
|
-
|
|
121386
|
-
|
|
121387
|
-
|
|
121388
|
-
|
|
122558
|
+
};
|
|
122559
|
+
} catch (error) {
|
|
122560
|
+
if (error instanceof Error && error.message.startsWith("Task ID already exists:")) {
|
|
122561
|
+
return {
|
|
122562
|
+
content: [{ type: "text", text: `ERROR: ${error.message}` }],
|
|
122563
|
+
isError: true,
|
|
122564
|
+
details: { error: error.message }
|
|
122565
|
+
};
|
|
121389
122566
|
}
|
|
121390
|
-
|
|
122567
|
+
throw error;
|
|
122568
|
+
}
|
|
121391
122569
|
}
|
|
121392
122570
|
});
|
|
121393
122571
|
pi.registerTool({
|
|
@@ -123329,25 +124507,36 @@ ${lines.join("\n\n")}` }],
|
|
|
123329
124507
|
const agentStore = new AgentStore2({ rootDir: getFusionDir(ctx.cwd) });
|
|
123330
124508
|
await agentStore.init();
|
|
123331
124509
|
const agent = await agentStore.getAgent(params.agent_id);
|
|
123332
|
-
|
|
123333
|
-
|
|
123334
|
-
|
|
123335
|
-
|
|
123336
|
-
|
|
123337
|
-
|
|
123338
|
-
|
|
123339
|
-
|
|
123340
|
-
|
|
124510
|
+
try {
|
|
124511
|
+
const store = await getStore2(ctx.cwd);
|
|
124512
|
+
const task = await store.createTask({
|
|
124513
|
+
description: params.description,
|
|
124514
|
+
dependencies: params.dependencies,
|
|
124515
|
+
column: "todo",
|
|
124516
|
+
assignedAgentId: params.agent_id,
|
|
124517
|
+
source: {
|
|
124518
|
+
sourceType: "api",
|
|
124519
|
+
...params.override === true ? { sourceMetadata: { executorRoleOverride: true } } : {}
|
|
124520
|
+
}
|
|
124521
|
+
});
|
|
124522
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
124523
|
+
return {
|
|
124524
|
+
content: [{
|
|
124525
|
+
type: "text",
|
|
124526
|
+
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. The task will be picked up by ${agent.name} on their next heartbeat cycle.`
|
|
124527
|
+
}],
|
|
124528
|
+
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
124529
|
+
};
|
|
124530
|
+
} catch (error) {
|
|
124531
|
+
if (error instanceof Error && error.message.startsWith("Task ID already exists:")) {
|
|
124532
|
+
return {
|
|
124533
|
+
content: [{ type: "text", text: `ERROR: ${error.message}` }],
|
|
124534
|
+
isError: true,
|
|
124535
|
+
details: { error: error.message }
|
|
124536
|
+
};
|
|
123341
124537
|
}
|
|
123342
|
-
|
|
123343
|
-
|
|
123344
|
-
return {
|
|
123345
|
-
content: [{
|
|
123346
|
-
type: "text",
|
|
123347
|
-
text: `Delegated to ${agent.name} (${agent.id}): Created ${task.id}${deps}. The task will be picked up by ${agent.name} on their next heartbeat cycle.`
|
|
123348
|
-
}],
|
|
123349
|
-
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
123350
|
-
};
|
|
124538
|
+
throw error;
|
|
124539
|
+
}
|
|
123351
124540
|
}
|
|
123352
124541
|
});
|
|
123353
124542
|
pi.registerTool({
|