@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/bin.js
CHANGED
|
@@ -66,6 +66,7 @@ var init_settings_schema = __esm({
|
|
|
66
66
|
ntfyEnabled: false,
|
|
67
67
|
ntfyTopic: void 0,
|
|
68
68
|
ntfyBaseUrl: void 0,
|
|
69
|
+
ntfyAccessToken: void 0,
|
|
69
70
|
ntfyEvents: [
|
|
70
71
|
"in-review",
|
|
71
72
|
"merged",
|
|
@@ -249,6 +250,7 @@ var init_settings_schema = __esm({
|
|
|
249
250
|
worktreeRebaseLocalBase: true,
|
|
250
251
|
mergeConflictStrategy: "smart-prefer-main",
|
|
251
252
|
workflowStepTimeoutMs: 36e4,
|
|
253
|
+
workflowRevisionForkOnScopeMismatch: true,
|
|
252
254
|
strictScopeEnforcement: false,
|
|
253
255
|
buildRetryCount: 0,
|
|
254
256
|
verificationFixRetries: 3,
|
|
@@ -3359,9 +3361,9 @@ var init_sqlite_adapter = __esm({
|
|
|
3359
3361
|
|
|
3360
3362
|
// ../core/src/db.ts
|
|
3361
3363
|
import { isAbsolute, join as join2 } from "node:path";
|
|
3362
|
-
import { mkdirSync, existsSync } from "node:fs";
|
|
3364
|
+
import { mkdirSync, existsSync, statSync } from "node:fs";
|
|
3363
3365
|
import { spawnSync } from "node:child_process";
|
|
3364
|
-
import { randomUUID } from "node:crypto";
|
|
3366
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
3365
3367
|
function toJson(value) {
|
|
3366
3368
|
if (value === void 0 || value === null) return "[]";
|
|
3367
3369
|
if (Array.isArray(value) && value.length === 0) return "[]";
|
|
@@ -3381,6 +3383,15 @@ function fromJson(json) {
|
|
|
3381
3383
|
return void 0;
|
|
3382
3384
|
}
|
|
3383
3385
|
}
|
|
3386
|
+
function isSqliteLockError(error) {
|
|
3387
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3388
|
+
return /SQLITE_(?:BUSY|LOCKED)|database is locked|database table is locked/i.test(message);
|
|
3389
|
+
}
|
|
3390
|
+
function sleepSync(ms) {
|
|
3391
|
+
if (ms <= 0) return;
|
|
3392
|
+
const signal = new Int32Array(new SharedArrayBuffer(4));
|
|
3393
|
+
Atomics.wait(signal, 0, 0, ms);
|
|
3394
|
+
}
|
|
3384
3395
|
function probeFts5(db) {
|
|
3385
3396
|
if (process.env.FUSION_DISABLE_FTS5 === "1" || process.env.FUSION_DISABLE_FTS5 === "true") {
|
|
3386
3397
|
return false;
|
|
@@ -3480,15 +3491,28 @@ function getSchemaCompatibilityTableSchemas() {
|
|
|
3480
3491
|
}
|
|
3481
3492
|
return tables;
|
|
3482
3493
|
}
|
|
3494
|
+
function canonicalizeSchemaTables(tables) {
|
|
3495
|
+
return Object.fromEntries(
|
|
3496
|
+
[...tables.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([tableName, columns]) => [
|
|
3497
|
+
tableName,
|
|
3498
|
+
Object.fromEntries(
|
|
3499
|
+
[...columns.entries()].sort(([left], [right]) => left.localeCompare(right))
|
|
3500
|
+
)
|
|
3501
|
+
])
|
|
3502
|
+
);
|
|
3503
|
+
}
|
|
3483
3504
|
function createDatabase(fusionDir, options) {
|
|
3484
3505
|
return new Database(fusionDir, options);
|
|
3485
3506
|
}
|
|
3486
|
-
var SCHEMA_VERSION, SCHEMA_SQL, TABLE_LEVEL_CONSTRAINT_PREFIXES, SCHEMA_TABLE_SCHEMAS, MIGRATION_ONLY_TABLE_SCHEMAS, Database;
|
|
3507
|
+
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;
|
|
3487
3508
|
var init_db = __esm({
|
|
3488
3509
|
"../core/src/db.ts"() {
|
|
3489
3510
|
"use strict";
|
|
3490
3511
|
init_sqlite_adapter();
|
|
3491
3512
|
init_types();
|
|
3513
|
+
DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5e3;
|
|
3514
|
+
DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS = 1e3;
|
|
3515
|
+
DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS = 50;
|
|
3492
3516
|
SCHEMA_VERSION = 72;
|
|
3493
3517
|
SCHEMA_SQL = `
|
|
3494
3518
|
-- Tasks table with JSON columns for nested data
|
|
@@ -3580,6 +3604,9 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
3580
3604
|
);
|
|
3581
3605
|
|
|
3582
3606
|
-- Config table (single row with project settings)
|
|
3607
|
+
-- nextId is a deprecated legacy allocator counter retained read-only for one
|
|
3608
|
+
-- release so older databases/config consumers can still load it during the
|
|
3609
|
+
-- distributed_task_id_state transition.
|
|
3583
3610
|
CREATE TABLE IF NOT EXISTS config (
|
|
3584
3611
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
3585
3612
|
nextId INTEGER DEFAULT 1,
|
|
@@ -4354,6 +4381,20 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
4354
4381
|
createdAt: "TEXT NOT NULL"
|
|
4355
4382
|
}
|
|
4356
4383
|
};
|
|
4384
|
+
SCHEMA_COMPAT_FINGERPRINT = createHash2("sha1").update(
|
|
4385
|
+
JSON.stringify({
|
|
4386
|
+
schemaVersion: SCHEMA_VERSION,
|
|
4387
|
+
schemaSqlTables: canonicalizeSchemaTables(SCHEMA_TABLE_SCHEMAS),
|
|
4388
|
+
migrationOnlyTableSchemas: Object.fromEntries(
|
|
4389
|
+
Object.entries(MIGRATION_ONLY_TABLE_SCHEMAS).sort(([left], [right]) => left.localeCompare(right)).map(([tableName, columns]) => [
|
|
4390
|
+
tableName,
|
|
4391
|
+
Object.fromEntries(
|
|
4392
|
+
Object.entries(columns).sort(([left], [right]) => left.localeCompare(right))
|
|
4393
|
+
)
|
|
4394
|
+
])
|
|
4395
|
+
)
|
|
4396
|
+
})
|
|
4397
|
+
).digest("hex");
|
|
4357
4398
|
Database = class _Database {
|
|
4358
4399
|
static sharedIntegrityChecks = /* @__PURE__ */ new Map();
|
|
4359
4400
|
db;
|
|
@@ -4371,10 +4412,16 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
|
|
|
4371
4412
|
_fts5Available;
|
|
4372
4413
|
integrityCheckScheduled = false;
|
|
4373
4414
|
closed = false;
|
|
4415
|
+
busyTimeoutMs;
|
|
4416
|
+
lockRecoveryWindowMs;
|
|
4417
|
+
lockRecoveryDelayMs;
|
|
4374
4418
|
constructor(fusionDir, options) {
|
|
4375
4419
|
const inMemory = options?.inMemory === true;
|
|
4376
4420
|
this.inMemory = inMemory;
|
|
4377
4421
|
this.dbPath = inMemory ? ":memory:" : join2(fusionDir, "fusion.db");
|
|
4422
|
+
this.busyTimeoutMs = Math.max(0, options?.busyTimeoutMs ?? DEFAULT_SQLITE_BUSY_TIMEOUT_MS);
|
|
4423
|
+
this.lockRecoveryWindowMs = Math.max(0, options?.lockRecoveryWindowMs ?? DEFAULT_SQLITE_LOCK_RECOVERY_WINDOW_MS);
|
|
4424
|
+
this.lockRecoveryDelayMs = Math.max(1, options?.lockRecoveryDelayMs ?? DEFAULT_SQLITE_LOCK_RECOVERY_DELAY_MS);
|
|
4378
4425
|
if (!inMemory && !isAbsolute(fusionDir)) {
|
|
4379
4426
|
throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
|
|
4380
4427
|
}
|
|
@@ -4394,13 +4441,13 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4394
4441
|
throw new Error(`Failed to open Fusion database at ${this.dbPath}: ${message}`);
|
|
4395
4442
|
}
|
|
4396
4443
|
if (!inMemory) {
|
|
4397
|
-
this.db.exec(
|
|
4444
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
4398
4445
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
4399
4446
|
this.db.exec("PRAGMA synchronous = NORMAL");
|
|
4400
4447
|
this.db.exec("PRAGMA wal_autocheckpoint = 100");
|
|
4401
4448
|
this.db.exec("PRAGMA journal_size_limit = 4194304");
|
|
4402
4449
|
} else {
|
|
4403
|
-
this.db.exec(
|
|
4450
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
4404
4451
|
}
|
|
4405
4452
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
4406
4453
|
this._fts5Available = probeFts5(this.db);
|
|
@@ -4511,6 +4558,44 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4511
4558
|
});
|
|
4512
4559
|
return rebuilt.status === 0;
|
|
4513
4560
|
}
|
|
4561
|
+
/**
|
|
4562
|
+
* Run WAL truncation + VACUUM and report compaction stats.
|
|
4563
|
+
*
|
|
4564
|
+
* In-memory databases no-op and return zeroed stats. Disk-backed databases
|
|
4565
|
+
* sample file size before/after compaction, run `wal_checkpoint(TRUNCATE)`,
|
|
4566
|
+
* and then run `VACUUM` while the connection is in EXCLUSIVE locking mode to
|
|
4567
|
+
* prevent concurrent writes from other connections during maintenance.
|
|
4568
|
+
*/
|
|
4569
|
+
vacuum() {
|
|
4570
|
+
if (this.inMemory) {
|
|
4571
|
+
return { beforeBytes: 0, afterBytes: 0, durationMs: 0 };
|
|
4572
|
+
}
|
|
4573
|
+
const beforeBytes = existsSync(this.dbPath) ? statSync(this.dbPath).size : 0;
|
|
4574
|
+
const startedAt = Date.now();
|
|
4575
|
+
this.db.exec("PRAGMA locking_mode=EXCLUSIVE");
|
|
4576
|
+
try {
|
|
4577
|
+
try {
|
|
4578
|
+
this.walCheckpoint("TRUNCATE");
|
|
4579
|
+
} catch (error) {
|
|
4580
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4581
|
+
throw new Error(`Database vacuum maintenance failed during WAL checkpoint (dbPath=${this.dbPath}): ${message}`);
|
|
4582
|
+
}
|
|
4583
|
+
try {
|
|
4584
|
+
this.db.exec("VACUUM");
|
|
4585
|
+
} catch (error) {
|
|
4586
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4587
|
+
throw new Error(`Database vacuum maintenance failed during VACUUM (dbPath=${this.dbPath}): ${message}`);
|
|
4588
|
+
}
|
|
4589
|
+
const afterBytes = existsSync(this.dbPath) ? statSync(this.dbPath).size : 0;
|
|
4590
|
+
return {
|
|
4591
|
+
beforeBytes,
|
|
4592
|
+
afterBytes,
|
|
4593
|
+
durationMs: Date.now() - startedAt
|
|
4594
|
+
};
|
|
4595
|
+
} finally {
|
|
4596
|
+
this.db.exec("PRAGMA locking_mode=NORMAL");
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4514
4599
|
/**
|
|
4515
4600
|
* Initialize the database: create tables if they don't exist
|
|
4516
4601
|
* and seed meta values.
|
|
@@ -4525,10 +4610,20 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4525
4610
|
`INSERT OR IGNORE INTO __meta (key, value) VALUES ('lastModified', '${Date.now()}')`
|
|
4526
4611
|
);
|
|
4527
4612
|
this.migrate();
|
|
4528
|
-
this.
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4613
|
+
const schemaCompatFingerprint = this.getMetaValue("schemaCompatFingerprint");
|
|
4614
|
+
const skipColumnReconciliation = schemaCompatFingerprint === SCHEMA_COMPAT_FINGERPRINT;
|
|
4615
|
+
const tableColumnsCache = skipColumnReconciliation ? void 0 : /* @__PURE__ */ new Map();
|
|
4616
|
+
const compatibilityOptions = {
|
|
4617
|
+
tableColumnsCache,
|
|
4618
|
+
skipColumnReconciliation
|
|
4619
|
+
};
|
|
4620
|
+
this.ensureSchemaCompatibility(compatibilityOptions);
|
|
4621
|
+
this.ensureRoutinesSchemaCompatibility(compatibilityOptions);
|
|
4622
|
+
this.ensureInsightRunsSchemaCompatibility(compatibilityOptions);
|
|
4623
|
+
this.ensureEvalTaskResultsSchemaCompatibility(compatibilityOptions);
|
|
4624
|
+
if (!skipColumnReconciliation) {
|
|
4625
|
+
this.setMetaValue("schemaCompatFingerprint", SCHEMA_COMPAT_FINGERPRINT);
|
|
4626
|
+
}
|
|
4532
4627
|
const configNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
4533
4628
|
this.db.exec(
|
|
4534
4629
|
`INSERT OR IGNORE INTO config (id, nextId, nextWorkflowStepId, settings, workflowSteps, updatedAt) VALUES (1, 1, 1, '${JSON.stringify(DEFAULT_PROJECT_SETTINGS)}', '[]', '${configNow}')`
|
|
@@ -4546,20 +4641,27 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4546
4641
|
* re-run even if a previous migration partially applied.
|
|
4547
4642
|
*/
|
|
4548
4643
|
/**
|
|
4549
|
-
*
|
|
4644
|
+
* Reconciles additive columns for every known project DB table unless the
|
|
4645
|
+
* persisted `schemaCompatFingerprint` already matches SCHEMA_COMPAT_FINGERPRINT.
|
|
4550
4646
|
*
|
|
4551
|
-
*
|
|
4552
|
-
*
|
|
4553
|
-
*
|
|
4554
|
-
*
|
|
4555
|
-
*
|
|
4647
|
+
* The fingerprint is invalidated automatically by SCHEMA_VERSION changes and by
|
|
4648
|
+
* edits to the canonicalized column declarations from SCHEMA_SQL or
|
|
4649
|
+
* MIGRATION_ONLY_TABLE_SCHEMAS. When it is absent or stale, this method runs the
|
|
4650
|
+
* full FN-3879/FN-3887/FN-3898 safety pass so every declared column exists on
|
|
4651
|
+
* every live table after init() returns.
|
|
4556
4652
|
*/
|
|
4557
|
-
ensureSchemaCompatibility() {
|
|
4653
|
+
ensureSchemaCompatibility(options = {}) {
|
|
4654
|
+
if (options.skipColumnReconciliation) {
|
|
4655
|
+
return;
|
|
4656
|
+
}
|
|
4558
4657
|
const knownTableSchemas = getSchemaCompatibilityTableSchemas();
|
|
4658
|
+
const tableColumnsCache = options.tableColumnsCache;
|
|
4559
4659
|
for (const [tableName, columns] of knownTableSchemas) {
|
|
4560
4660
|
if (!this.hasTable(tableName)) continue;
|
|
4661
|
+
const cachedColumns = this.getTableColumns(tableName, true, tableColumnsCache);
|
|
4561
4662
|
for (const [columnName, columnDefinition] of columns) {
|
|
4562
|
-
|
|
4663
|
+
if (cachedColumns.has(columnName)) continue;
|
|
4664
|
+
this.addColumnIfMissingCached(tableName, columnName, columnDefinition, tableColumnsCache);
|
|
4563
4665
|
}
|
|
4564
4666
|
}
|
|
4565
4667
|
}
|
|
@@ -4570,10 +4672,14 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4570
4672
|
* agent IDs from earlier table definitions. `RoutineStore.rowToRoutine()` and
|
|
4571
4673
|
* backup routine sync expect a safe string value, so normalize to ''.
|
|
4572
4674
|
*/
|
|
4573
|
-
ensureRoutinesSchemaCompatibility() {
|
|
4675
|
+
ensureRoutinesSchemaCompatibility(options = {}) {
|
|
4574
4676
|
if (!this.hasTable("routines")) {
|
|
4575
4677
|
return;
|
|
4576
4678
|
}
|
|
4679
|
+
if (!options.skipColumnReconciliation) {
|
|
4680
|
+
this.addColumnIfMissingCached("routines", "agentId", "TEXT DEFAULT ''", options.tableColumnsCache);
|
|
4681
|
+
this.addColumnIfMissingCached("routines", "scope", "TEXT DEFAULT 'project'", options.tableColumnsCache);
|
|
4682
|
+
}
|
|
4577
4683
|
this.db.exec("UPDATE routines SET agentId = '' WHERE agentId IS NULL");
|
|
4578
4684
|
this.db.exec("UPDATE routines SET scope = 'project' WHERE scope IS NULL OR TRIM(scope) = ''");
|
|
4579
4685
|
this.db.exec("CREATE INDEX IF NOT EXISTS idxRoutinesNextRunAt ON routines(nextRunAt)");
|
|
@@ -4587,13 +4693,17 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
4587
4693
|
* remains focused on index creation that should run after the generic column
|
|
4588
4694
|
* backfill pass.
|
|
4589
4695
|
*/
|
|
4590
|
-
ensureInsightRunsSchemaCompatibility() {
|
|
4696
|
+
ensureInsightRunsSchemaCompatibility(options = {}) {
|
|
4591
4697
|
if (!this.hasTable("project_insight_runs")) {
|
|
4592
4698
|
return;
|
|
4593
4699
|
}
|
|
4700
|
+
if (!options.skipColumnReconciliation) {
|
|
4701
|
+
this.addColumnIfMissingCached("project_insight_runs", "lifecycle", "TEXT", options.tableColumnsCache);
|
|
4702
|
+
this.addColumnIfMissingCached("project_insight_runs", "cancelledAt", "TEXT", options.tableColumnsCache);
|
|
4703
|
+
}
|
|
4594
4704
|
this.db.exec(`CREATE INDEX IF NOT EXISTS idxInsightRunsProjectTriggerStatus ON project_insight_runs(projectId, trigger, status)`);
|
|
4595
4705
|
}
|
|
4596
|
-
ensureEvalTaskResultsSchemaCompatibility() {
|
|
4706
|
+
ensureEvalTaskResultsSchemaCompatibility(_options = {}) {
|
|
4597
4707
|
if (!this.hasTable("eval_task_results")) {
|
|
4598
4708
|
return;
|
|
4599
4709
|
}
|
|
@@ -5935,12 +6045,26 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
5935
6045
|
const lower = message.toLowerCase();
|
|
5936
6046
|
return lower.includes("corruption found reading blob") || lower.includes("database disk image is malformed") || lower.includes("fts5") && lower.includes("corrupt");
|
|
5937
6047
|
}
|
|
6048
|
+
/**
|
|
6049
|
+
* Read the declared columns for a table.
|
|
6050
|
+
*/
|
|
6051
|
+
getTableColumns(table, useCache = false, cache) {
|
|
6052
|
+
if (useCache && cache?.has(table)) {
|
|
6053
|
+
return cache.get(table) ?? /* @__PURE__ */ new Set();
|
|
6054
|
+
}
|
|
6055
|
+
const columns = new Set(
|
|
6056
|
+
this.db.prepare(`PRAGMA table_info(${table})`).all().map((column) => column.name)
|
|
6057
|
+
);
|
|
6058
|
+
if (useCache && cache) {
|
|
6059
|
+
cache.set(table, columns);
|
|
6060
|
+
}
|
|
6061
|
+
return columns;
|
|
6062
|
+
}
|
|
5938
6063
|
/**
|
|
5939
6064
|
* Check whether a table has a given column.
|
|
5940
6065
|
*/
|
|
5941
6066
|
hasColumn(table, column) {
|
|
5942
|
-
|
|
5943
|
-
return cols.some((c) => c.name === column);
|
|
6067
|
+
return this.getTableColumns(table).has(column);
|
|
5944
6068
|
}
|
|
5945
6069
|
/**
|
|
5946
6070
|
* Add a column to a table if it does not already exist.
|
|
@@ -5950,6 +6074,20 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
5950
6074
|
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
5951
6075
|
}
|
|
5952
6076
|
}
|
|
6077
|
+
/**
|
|
6078
|
+
* Add a column using a per-init table-info cache when available.
|
|
6079
|
+
*/
|
|
6080
|
+
addColumnIfMissingCached(table, column, definition, cache) {
|
|
6081
|
+
const columns = this.getTableColumns(table, Boolean(cache), cache);
|
|
6082
|
+
if (columns.has(column)) {
|
|
6083
|
+
return;
|
|
6084
|
+
}
|
|
6085
|
+
this.db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
6086
|
+
columns.add(column);
|
|
6087
|
+
if (cache) {
|
|
6088
|
+
cache.set(table, columns);
|
|
6089
|
+
}
|
|
6090
|
+
}
|
|
5953
6091
|
/**
|
|
5954
6092
|
* Normalize legacy steering comments into the unified comments field exactly once.
|
|
5955
6093
|
*
|
|
@@ -6050,25 +6188,67 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6050
6188
|
this.integrityCheckPending = false;
|
|
6051
6189
|
this.db.close();
|
|
6052
6190
|
}
|
|
6191
|
+
runWithLockRecovery(action, fn) {
|
|
6192
|
+
const deadline = Date.now() + this.lockRecoveryWindowMs;
|
|
6193
|
+
let attempt = 0;
|
|
6194
|
+
while (true) {
|
|
6195
|
+
try {
|
|
6196
|
+
fn();
|
|
6197
|
+
return;
|
|
6198
|
+
} catch (error) {
|
|
6199
|
+
if (!isSqliteLockError(error)) {
|
|
6200
|
+
throw error;
|
|
6201
|
+
}
|
|
6202
|
+
if (Date.now() >= deadline) {
|
|
6203
|
+
throw new Error(
|
|
6204
|
+
`SQLite ${action} failed after ${attempt + 1} attempt${attempt === 0 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`
|
|
6205
|
+
);
|
|
6206
|
+
}
|
|
6207
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
6208
|
+
const delayMs = Math.min(this.lockRecoveryDelayMs * Math.max(1, attempt + 1), remainingMs);
|
|
6209
|
+
sleepSync(delayMs);
|
|
6210
|
+
attempt += 1;
|
|
6211
|
+
}
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6053
6214
|
/**
|
|
6054
6215
|
* Execute a function inside a SQLite transaction.
|
|
6055
6216
|
* Supports nested calls via SAVEPOINTs.
|
|
6056
6217
|
* If the function throws, the transaction/savepoint is rolled back.
|
|
6057
6218
|
* If the function returns normally, the transaction/savepoint is committed.
|
|
6219
|
+
*
|
|
6220
|
+
* Outermost transactions default to `BEGIN` (DEFERRED) so read-only callers
|
|
6221
|
+
* avoid taking a writer lock until they actually mutate state.
|
|
6222
|
+
* Use `transactionImmediate()` for write-heavy paths that should acquire the
|
|
6223
|
+
* RESERVED lock before user code runs and fail/retry before the callback executes.
|
|
6058
6224
|
*/
|
|
6059
|
-
transaction(fn) {
|
|
6225
|
+
transaction(fn, options) {
|
|
6060
6226
|
const depth = this.transactionDepth++;
|
|
6061
6227
|
const isOutermost = depth === 0;
|
|
6062
6228
|
const savepointName = `sp_${depth}`;
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6229
|
+
const mode = options?.mode ?? "deferred";
|
|
6230
|
+
try {
|
|
6231
|
+
if (isOutermost) {
|
|
6232
|
+
if (mode === "immediate") {
|
|
6233
|
+
this.runWithLockRecovery("BEGIN IMMEDIATE", () => {
|
|
6234
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
6235
|
+
});
|
|
6236
|
+
} else {
|
|
6237
|
+
this.db.exec("BEGIN");
|
|
6238
|
+
}
|
|
6239
|
+
} else {
|
|
6240
|
+
this.db.exec(`SAVEPOINT ${savepointName}`);
|
|
6241
|
+
}
|
|
6242
|
+
} catch (error) {
|
|
6243
|
+
this.transactionDepth--;
|
|
6244
|
+
throw error;
|
|
6067
6245
|
}
|
|
6068
6246
|
try {
|
|
6069
6247
|
const result = fn();
|
|
6070
6248
|
if (isOutermost) {
|
|
6071
|
-
this.
|
|
6249
|
+
this.runWithLockRecovery("COMMIT", () => {
|
|
6250
|
+
this.db.exec("COMMIT");
|
|
6251
|
+
});
|
|
6072
6252
|
} else {
|
|
6073
6253
|
this.db.exec(`RELEASE ${savepointName}`);
|
|
6074
6254
|
}
|
|
@@ -6085,6 +6265,9 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6085
6265
|
this.transactionDepth--;
|
|
6086
6266
|
}
|
|
6087
6267
|
}
|
|
6268
|
+
transactionImmediate(fn) {
|
|
6269
|
+
return this.transaction(fn, { mode: "immediate" });
|
|
6270
|
+
}
|
|
6088
6271
|
/**
|
|
6089
6272
|
* Execute plugin-provided schema initialization hooks.
|
|
6090
6273
|
*
|
|
@@ -6120,14 +6303,24 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6120
6303
|
exec(sql) {
|
|
6121
6304
|
this.db.exec(sql);
|
|
6122
6305
|
}
|
|
6306
|
+
getMetaValue(key) {
|
|
6307
|
+
const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(key);
|
|
6308
|
+
return row?.value;
|
|
6309
|
+
}
|
|
6310
|
+
/**
|
|
6311
|
+
* Persist a __meta value idempotently.
|
|
6312
|
+
*/
|
|
6313
|
+
setMetaValue(key, value) {
|
|
6314
|
+
this.db.prepare("INSERT OR REPLACE INTO __meta (key, value) VALUES (?, ?)").run(key, value);
|
|
6315
|
+
}
|
|
6123
6316
|
/**
|
|
6124
6317
|
* Get the last modification timestamp (epoch ms).
|
|
6125
6318
|
* Returns 0 if the value is not set.
|
|
6126
6319
|
*/
|
|
6127
6320
|
getLastModified() {
|
|
6128
|
-
const
|
|
6129
|
-
if (!
|
|
6130
|
-
return parseInt(
|
|
6321
|
+
const value = this.getMetaValue("lastModified");
|
|
6322
|
+
if (!value) return 0;
|
|
6323
|
+
return parseInt(value, 10) || 0;
|
|
6131
6324
|
}
|
|
6132
6325
|
/**
|
|
6133
6326
|
* Update the last modification timestamp to the current time.
|
|
@@ -6146,9 +6339,9 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6146
6339
|
* Get the schema version number.
|
|
6147
6340
|
*/
|
|
6148
6341
|
getSchemaVersion() {
|
|
6149
|
-
const
|
|
6150
|
-
if (!
|
|
6151
|
-
return parseInt(
|
|
6342
|
+
const value = this.getMetaValue("schemaVersion");
|
|
6343
|
+
if (!value) return 0;
|
|
6344
|
+
return parseInt(value, 10) || 0;
|
|
6152
6345
|
}
|
|
6153
6346
|
/**
|
|
6154
6347
|
* Get the database file path.
|
|
@@ -6164,7 +6357,7 @@ This means a caller passed a .fusion directory where a project root was expected
|
|
|
6164
6357
|
import { mkdir, readFile, writeFile, readdir, unlink, rename, access, appendFile } from "node:fs/promises";
|
|
6165
6358
|
import { constants as fsConstants } from "node:fs";
|
|
6166
6359
|
import { basename, dirname, join as join3, resolve as resolve2 } from "node:path";
|
|
6167
|
-
import { randomUUID as randomUUID2, randomBytes, createHash as
|
|
6360
|
+
import { randomUUID as randomUUID2, randomBytes, createHash as createHash3 } from "node:crypto";
|
|
6168
6361
|
import { EventEmitter } from "node:events";
|
|
6169
6362
|
function resolveCreationRuntimeConfig(incoming, metadata) {
|
|
6170
6363
|
const isEphemeral = isEphemeralAgent({ metadata });
|
|
@@ -7330,7 +7523,7 @@ var init_agent_store = __esm({
|
|
|
7330
7523
|
throw new Error(`Agent ${agentId} not found`);
|
|
7331
7524
|
}
|
|
7332
7525
|
const token = randomBytes(32).toString("hex");
|
|
7333
|
-
const tokenHash =
|
|
7526
|
+
const tokenHash = createHash3("sha256").update(token).digest("hex");
|
|
7334
7527
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7335
7528
|
const label = options?.label?.trim();
|
|
7336
7529
|
const key = {
|
|
@@ -9486,10 +9679,10 @@ END;
|
|
|
9486
9679
|
mkdirSync3(fusionDir, { recursive: true });
|
|
9487
9680
|
}
|
|
9488
9681
|
this.db = new DatabaseSync(inMemory ? ":memory:" : join6(fusionDir, "archive.db"));
|
|
9682
|
+
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
9489
9683
|
if (!inMemory) {
|
|
9490
9684
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
9491
9685
|
}
|
|
9492
|
-
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
9493
9686
|
this._fts5Available = probeFts5(this.db);
|
|
9494
9687
|
}
|
|
9495
9688
|
/** True when this SQLite build has FTS5. See db.ts#probeFts5. */
|
|
@@ -13164,9 +13357,15 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13164
13357
|
globalDir;
|
|
13165
13358
|
/** Tracks transaction nesting depth for savepoint-based nested transactions. */
|
|
13166
13359
|
transactionDepth = 0;
|
|
13167
|
-
|
|
13360
|
+
busyTimeoutMs;
|
|
13361
|
+
lockRecoveryWindowMs;
|
|
13362
|
+
lockRecoveryDelayMs;
|
|
13363
|
+
constructor(globalDir, options) {
|
|
13168
13364
|
this.globalDir = resolveGlobalDir(globalDir);
|
|
13169
13365
|
this.dbPath = join8(this.globalDir, "fusion-central.db");
|
|
13366
|
+
this.busyTimeoutMs = Math.max(0, options?.busyTimeoutMs ?? 5e3);
|
|
13367
|
+
this.lockRecoveryWindowMs = Math.max(0, options?.lockRecoveryWindowMs ?? 1e3);
|
|
13368
|
+
this.lockRecoveryDelayMs = Math.max(1, options?.lockRecoveryDelayMs ?? 50);
|
|
13170
13369
|
if (!existsSync6(this.globalDir)) {
|
|
13171
13370
|
mkdirSync4(this.globalDir, { recursive: true });
|
|
13172
13371
|
}
|
|
@@ -13176,8 +13375,8 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13176
13375
|
const message = error instanceof Error ? error.message : String(error);
|
|
13177
13376
|
throw new Error(`Failed to open Fusion central database at ${this.dbPath}: ${message}`);
|
|
13178
13377
|
}
|
|
13378
|
+
this.db.exec(`PRAGMA busy_timeout = ${this.busyTimeoutMs}`);
|
|
13179
13379
|
this.db.exec("PRAGMA journal_mode = WAL");
|
|
13180
|
-
this.db.exec("PRAGMA busy_timeout = 5000");
|
|
13181
13380
|
this.db.exec("PRAGMA foreign_keys = ON");
|
|
13182
13381
|
}
|
|
13183
13382
|
/**
|
|
@@ -13283,6 +13482,29 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13283
13482
|
close() {
|
|
13284
13483
|
this.db.close();
|
|
13285
13484
|
}
|
|
13485
|
+
runWithLockRecovery(action, fn) {
|
|
13486
|
+
const deadline = Date.now() + this.lockRecoveryWindowMs;
|
|
13487
|
+
let attempt = 0;
|
|
13488
|
+
while (true) {
|
|
13489
|
+
try {
|
|
13490
|
+
fn();
|
|
13491
|
+
return;
|
|
13492
|
+
} catch (error) {
|
|
13493
|
+
if (!isSqliteLockError(error)) {
|
|
13494
|
+
throw error;
|
|
13495
|
+
}
|
|
13496
|
+
if (Date.now() >= deadline) {
|
|
13497
|
+
throw new Error(
|
|
13498
|
+
`SQLite ${action} failed after ${attempt + 1} attempt${attempt === 0 ? "" : "s"}: ${error instanceof Error ? error.message : String(error)}`
|
|
13499
|
+
);
|
|
13500
|
+
}
|
|
13501
|
+
const remainingMs = Math.max(0, deadline - Date.now());
|
|
13502
|
+
const delayMs = Math.min(this.lockRecoveryDelayMs * Math.max(1, attempt + 1), remainingMs);
|
|
13503
|
+
sleepSync(delayMs);
|
|
13504
|
+
attempt += 1;
|
|
13505
|
+
}
|
|
13506
|
+
}
|
|
13507
|
+
}
|
|
13286
13508
|
/**
|
|
13287
13509
|
* Execute a function inside a SQLite transaction.
|
|
13288
13510
|
* Supports nested calls via SAVEPOINTs.
|
|
@@ -13293,15 +13515,24 @@ CREATE INDEX IF NOT EXISTS idxMeshWriteQueueReplay ON meshWriteQueue(targetNodeI
|
|
|
13293
13515
|
const depth = this.transactionDepth++;
|
|
13294
13516
|
const isOutermost = depth === 0;
|
|
13295
13517
|
const savepointName = `sp_${depth}`;
|
|
13296
|
-
|
|
13297
|
-
|
|
13298
|
-
|
|
13299
|
-
|
|
13518
|
+
try {
|
|
13519
|
+
if (isOutermost) {
|
|
13520
|
+
this.runWithLockRecovery("BEGIN IMMEDIATE", () => {
|
|
13521
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
13522
|
+
});
|
|
13523
|
+
} else {
|
|
13524
|
+
this.db.exec(`SAVEPOINT ${savepointName}`);
|
|
13525
|
+
}
|
|
13526
|
+
} catch (error) {
|
|
13527
|
+
this.transactionDepth--;
|
|
13528
|
+
throw error;
|
|
13300
13529
|
}
|
|
13301
13530
|
try {
|
|
13302
13531
|
const result = fn();
|
|
13303
13532
|
if (isOutermost) {
|
|
13304
|
-
this.
|
|
13533
|
+
this.runWithLockRecovery("COMMIT", () => {
|
|
13534
|
+
this.db.exec("COMMIT");
|
|
13535
|
+
});
|
|
13305
13536
|
} else {
|
|
13306
13537
|
this.db.exec(`RELEASE ${savepointName}`);
|
|
13307
13538
|
}
|
|
@@ -19922,8 +20153,8 @@ var init_system_metrics = __esm({
|
|
|
19922
20153
|
|
|
19923
20154
|
// ../core/src/central-core.ts
|
|
19924
20155
|
import { EventEmitter as EventEmitter11 } from "node:events";
|
|
19925
|
-
import { createHash as
|
|
19926
|
-
import { existsSync as existsSync7, statSync } from "node:fs";
|
|
20156
|
+
import { createHash as createHash4, randomUUID as randomUUID9 } from "node:crypto";
|
|
20157
|
+
import { existsSync as existsSync7, statSync as statSync2 } from "node:fs";
|
|
19927
20158
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
19928
20159
|
import { isAbsolute as isAbsolute2, join as join11, basename as basename2, resolve as resolve5 } from "node:path";
|
|
19929
20160
|
var CentralCore;
|
|
@@ -20032,7 +20263,7 @@ var init_central_core = __esm({
|
|
|
20032
20263
|
if (!existsSync7(input.path)) {
|
|
20033
20264
|
throw new Error(`Project path does not exist: ${input.path}`);
|
|
20034
20265
|
}
|
|
20035
|
-
if (!
|
|
20266
|
+
if (!statSync2(input.path).isDirectory()) {
|
|
20036
20267
|
throw new Error(`Project path must be a directory: ${input.path}`);
|
|
20037
20268
|
}
|
|
20038
20269
|
const existingByPath = await this.getProjectByPath(input.path);
|
|
@@ -21771,7 +22002,7 @@ var init_central_core = __esm({
|
|
|
21771
22002
|
const dbPath = this.db.getPath();
|
|
21772
22003
|
let dbSizeBytes = 0;
|
|
21773
22004
|
try {
|
|
21774
|
-
dbSizeBytes =
|
|
22005
|
+
dbSizeBytes = statSync2(dbPath).size;
|
|
21775
22006
|
} catch {
|
|
21776
22007
|
}
|
|
21777
22008
|
return { projectCount, totalTasksCompleted, dbSizeBytes };
|
|
@@ -21989,10 +22220,10 @@ var init_central_core = __esm({
|
|
|
21989
22220
|
*/
|
|
21990
22221
|
async generateProjectName(projectPath) {
|
|
21991
22222
|
try {
|
|
21992
|
-
const { execFile:
|
|
21993
|
-
const { promisify:
|
|
21994
|
-
const
|
|
21995
|
-
const { stdout } = await
|
|
22223
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
22224
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
22225
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
22226
|
+
const { stdout } = await execFileAsync9(
|
|
21996
22227
|
"git",
|
|
21997
22228
|
["remote", "get-url", "origin"],
|
|
21998
22229
|
{ cwd: projectPath, timeout: 5e3 }
|
|
@@ -22294,7 +22525,7 @@ var init_central_core = __esm({
|
|
|
22294
22525
|
exportedAt: snapshot.exportedAt,
|
|
22295
22526
|
version: 1
|
|
22296
22527
|
};
|
|
22297
|
-
const checksum =
|
|
22528
|
+
const checksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22298
22529
|
return this.applyRemoteSettings({ ...payloadWithoutChecksum, checksum });
|
|
22299
22530
|
}
|
|
22300
22531
|
getAuthMaterialSnapshot(providerAuth) {
|
|
@@ -22337,7 +22568,7 @@ var init_central_core = __esm({
|
|
|
22337
22568
|
exportedAt,
|
|
22338
22569
|
version: 1
|
|
22339
22570
|
};
|
|
22340
|
-
const checksum =
|
|
22571
|
+
const checksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22341
22572
|
return {
|
|
22342
22573
|
...payloadWithoutChecksum,
|
|
22343
22574
|
checksum
|
|
@@ -22372,7 +22603,7 @@ var init_central_core = __esm({
|
|
|
22372
22603
|
exportedAt: payload.exportedAt,
|
|
22373
22604
|
version: payload.version
|
|
22374
22605
|
};
|
|
22375
|
-
const computedChecksum =
|
|
22606
|
+
const computedChecksum = createHash4("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
22376
22607
|
if (computedChecksum !== payload.checksum) {
|
|
22377
22608
|
return {
|
|
22378
22609
|
success: false,
|
|
@@ -22494,13 +22725,13 @@ var init_central_core = __esm({
|
|
|
22494
22725
|
});
|
|
22495
22726
|
|
|
22496
22727
|
// ../core/src/sqlite-validation.ts
|
|
22497
|
-
import { existsSync as existsSync8, statSync as
|
|
22728
|
+
import { existsSync as existsSync8, statSync as statSync3 } from "node:fs";
|
|
22498
22729
|
function isValidSqliteDatabaseFile(dbPath) {
|
|
22499
22730
|
if (!existsSync8(dbPath)) {
|
|
22500
22731
|
return false;
|
|
22501
22732
|
}
|
|
22502
22733
|
try {
|
|
22503
|
-
if (!
|
|
22734
|
+
if (!statSync3(dbPath).isFile()) {
|
|
22504
22735
|
return false;
|
|
22505
22736
|
}
|
|
22506
22737
|
} catch {
|
|
@@ -22693,10 +22924,10 @@ var init_migration = __esm({
|
|
|
22693
22924
|
return basename3(projectPath);
|
|
22694
22925
|
}
|
|
22695
22926
|
try {
|
|
22696
|
-
const { execFile:
|
|
22697
|
-
const { promisify:
|
|
22698
|
-
const
|
|
22699
|
-
const { stdout } = await
|
|
22927
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
22928
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
22929
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
22930
|
+
const { stdout } = await execFileAsync9(
|
|
22700
22931
|
"git",
|
|
22701
22932
|
["remote", "get-url", "origin"],
|
|
22702
22933
|
{ cwd: projectPath, timeout: 1e3 }
|
|
@@ -32969,7 +33200,7 @@ var init_memory_dreams = __esm({
|
|
|
32969
33200
|
import { readFile as readFile6, writeFile as writeFile5, mkdir as mkdir6, access as access3, constants, readdir as readdir4, stat as stat2 } from "node:fs/promises";
|
|
32970
33201
|
import { existsSync as existsSync11 } from "node:fs";
|
|
32971
33202
|
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";
|
|
32972
|
-
import { createHash as
|
|
33203
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
32973
33204
|
function shouldSkipBackgroundQmdRefresh() {
|
|
32974
33205
|
return (process.env.VITEST === "true" || process.env.NODE_ENV === "test") && process.env.FUSION_ENABLE_QMD_REFRESH_IN_TESTS !== "1";
|
|
32975
33206
|
}
|
|
@@ -32985,7 +33216,7 @@ function memoryDreamsPath(rootDir) {
|
|
|
32985
33216
|
function qmdMemoryCollectionName(rootDir) {
|
|
32986
33217
|
const absoluteRoot = resolve7(rootDir);
|
|
32987
33218
|
const slug = basename4(absoluteRoot).toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
32988
|
-
const hash =
|
|
33219
|
+
const hash = createHash5("sha1").update(absoluteRoot).digest("hex").slice(0, 12);
|
|
32989
33220
|
return `${QMD_COLLECTION_PREFIX}-${slug}-${hash}`;
|
|
32990
33221
|
}
|
|
32991
33222
|
function buildQmdSearchArgs(rootDir, options) {
|
|
@@ -33021,7 +33252,7 @@ function buildQmdRefreshCommands(rootDir) {
|
|
|
33021
33252
|
function qmdAgentMemoryCollectionName(rootDir, agentId) {
|
|
33022
33253
|
const absoluteRoot = resolve7(rootDir);
|
|
33023
33254
|
const safeAgentId = agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
|
|
33024
|
-
const hash =
|
|
33255
|
+
const hash = createHash5("sha1").update(`${absoluteRoot}:${agentId}`).digest("hex").slice(0, 12);
|
|
33025
33256
|
return `fusion-agent-memory-${safeAgentId.toLowerCase()}-${hash}`;
|
|
33026
33257
|
}
|
|
33027
33258
|
function dailyMemoryPath(rootDir, date = /* @__PURE__ */ new Date()) {
|
|
@@ -33425,13 +33656,13 @@ async function searchWithQmd(rootDir, options) {
|
|
|
33425
33656
|
const command = "qmd";
|
|
33426
33657
|
const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
|
|
33427
33658
|
try {
|
|
33428
|
-
const { execFile:
|
|
33429
|
-
const { promisify:
|
|
33430
|
-
const
|
|
33431
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
33659
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
33660
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
33661
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
33662
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync9);
|
|
33432
33663
|
scheduleQmdProjectMemoryRefresh(rootDir);
|
|
33433
33664
|
const args = buildQmdSearchArgs(rootDir, options);
|
|
33434
|
-
const { stdout } = await
|
|
33665
|
+
const { stdout } = await execFileAsync9(command, args, {
|
|
33435
33666
|
cwd: rootDir,
|
|
33436
33667
|
timeout: 4e3,
|
|
33437
33668
|
maxBuffer: 1024 * 1024
|
|
@@ -33456,12 +33687,12 @@ async function searchWithQmd(rootDir, options) {
|
|
|
33456
33687
|
return [];
|
|
33457
33688
|
}
|
|
33458
33689
|
}
|
|
33459
|
-
async function ensureQmdProjectMemoryCollection(rootDir,
|
|
33690
|
+
async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync9) {
|
|
33460
33691
|
const collectionName = qmdMemoryCollectionName(rootDir);
|
|
33461
33692
|
const memoryDir = memoryWorkspacePath(rootDir);
|
|
33462
33693
|
await mkdir6(memoryDir, { recursive: true });
|
|
33463
33694
|
try {
|
|
33464
|
-
await
|
|
33695
|
+
await execFileAsync9("qmd", buildQmdCollectionAddArgs(rootDir), {
|
|
33465
33696
|
cwd: rootDir,
|
|
33466
33697
|
timeout: 4e3,
|
|
33467
33698
|
maxBuffer: 512 * 1024
|
|
@@ -33477,9 +33708,9 @@ ${stderr}`)) {
|
|
|
33477
33708
|
return collectionName;
|
|
33478
33709
|
}
|
|
33479
33710
|
async function getDefaultExecFileAsync() {
|
|
33480
|
-
const { execFile:
|
|
33481
|
-
const { promisify:
|
|
33482
|
-
return
|
|
33711
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
33712
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
33713
|
+
return promisify21(execFile12);
|
|
33483
33714
|
}
|
|
33484
33715
|
async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
33485
33716
|
const key = resolve7(rootDir);
|
|
@@ -33494,14 +33725,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
|
33494
33725
|
}
|
|
33495
33726
|
}
|
|
33496
33727
|
const promise = (async () => {
|
|
33497
|
-
const
|
|
33498
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
33499
|
-
await
|
|
33728
|
+
const execFileAsync9 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33729
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync9);
|
|
33730
|
+
await execFileAsync9("qmd", ["update"], {
|
|
33500
33731
|
cwd: rootDir,
|
|
33501
33732
|
timeout: 3e4,
|
|
33502
33733
|
maxBuffer: 1024 * 1024
|
|
33503
33734
|
});
|
|
33504
|
-
await
|
|
33735
|
+
await execFileAsync9("qmd", ["embed"], {
|
|
33505
33736
|
cwd: rootDir,
|
|
33506
33737
|
timeout: 12e4,
|
|
33507
33738
|
maxBuffer: 1024 * 1024
|
|
@@ -33537,12 +33768,12 @@ async function refreshQmdAgentMemoryIndex(rootDir, agentId, options) {
|
|
|
33537
33768
|
}
|
|
33538
33769
|
}
|
|
33539
33770
|
const promise = (async () => {
|
|
33540
|
-
const
|
|
33771
|
+
const execFileAsync9 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33541
33772
|
const { agentMemoryWorkspacePath: agentMemoryWorkspacePath2 } = await Promise.resolve().then(() => (init_memory_dreams(), memory_dreams_exports));
|
|
33542
33773
|
const workspacePath = agentMemoryWorkspacePath2(rootDir, agentId);
|
|
33543
33774
|
await mkdir6(workspacePath, { recursive: true });
|
|
33544
33775
|
try {
|
|
33545
|
-
await
|
|
33776
|
+
await execFileAsync9("qmd", ["collection", "add", workspacePath, "--name", qmdAgentMemoryCollectionName(rootDir, agentId), "--mask", "**/*.md"], {
|
|
33546
33777
|
cwd: rootDir,
|
|
33547
33778
|
timeout: 4e3,
|
|
33548
33779
|
maxBuffer: 512 * 1024
|
|
@@ -33555,8 +33786,8 @@ ${stderr}`)) {
|
|
|
33555
33786
|
throw err;
|
|
33556
33787
|
}
|
|
33557
33788
|
}
|
|
33558
|
-
await
|
|
33559
|
-
await
|
|
33789
|
+
await execFileAsync9("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
33790
|
+
await execFileAsync9("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
33560
33791
|
})();
|
|
33561
33792
|
qmdAgentRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
33562
33793
|
try {
|
|
@@ -33577,8 +33808,8 @@ function scheduleQmdAgentMemoryRefresh(rootDir, agentId) {
|
|
|
33577
33808
|
}
|
|
33578
33809
|
async function isQmdAvailable() {
|
|
33579
33810
|
try {
|
|
33580
|
-
const
|
|
33581
|
-
await
|
|
33811
|
+
const execFileAsync9 = await getDefaultExecFileAsync();
|
|
33812
|
+
await execFileAsync9("qmd", ["--help"], {
|
|
33582
33813
|
timeout: 3e3,
|
|
33583
33814
|
maxBuffer: 128 * 1024
|
|
33584
33815
|
});
|
|
@@ -33588,12 +33819,12 @@ async function isQmdAvailable() {
|
|
|
33588
33819
|
}
|
|
33589
33820
|
}
|
|
33590
33821
|
async function installQmd(options) {
|
|
33591
|
-
const
|
|
33822
|
+
const execFileAsync9 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
33592
33823
|
const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
|
|
33593
33824
|
if (!command || args.length === 0) {
|
|
33594
33825
|
throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
|
|
33595
33826
|
}
|
|
33596
|
-
await
|
|
33827
|
+
await execFileAsync9(command, args, {
|
|
33597
33828
|
timeout: 12e4,
|
|
33598
33829
|
maxBuffer: 1024 * 1024
|
|
33599
33830
|
});
|
|
@@ -34894,6 +35125,135 @@ function resolveLocalNodeId(nodes, fallback2 = "local") {
|
|
|
34894
35125
|
const localNode = nodes?.find((node) => node.type === "local");
|
|
34895
35126
|
return localNode?.id ?? fallback2;
|
|
34896
35127
|
}
|
|
35128
|
+
function parseTaskId(taskId) {
|
|
35129
|
+
const match = taskId.trim().toUpperCase().match(TASK_ID_PATTERN);
|
|
35130
|
+
if (!match) {
|
|
35131
|
+
return null;
|
|
35132
|
+
}
|
|
35133
|
+
const sequence = Number.parseInt(match[2], 10);
|
|
35134
|
+
if (!Number.isFinite(sequence)) {
|
|
35135
|
+
return null;
|
|
35136
|
+
}
|
|
35137
|
+
return { prefix: match[1], sequence };
|
|
35138
|
+
}
|
|
35139
|
+
function getConfiguredPrefixAndLegacyNextId(db) {
|
|
35140
|
+
try {
|
|
35141
|
+
const row = db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get();
|
|
35142
|
+
if (!row) {
|
|
35143
|
+
return { prefix: "KB", nextId: null };
|
|
35144
|
+
}
|
|
35145
|
+
const settings = row.settings ? JSON.parse(row.settings) : null;
|
|
35146
|
+
return {
|
|
35147
|
+
prefix: (settings?.taskPrefix ?? "KB").trim().toUpperCase(),
|
|
35148
|
+
nextId: typeof row.nextId === "number" ? row.nextId : null
|
|
35149
|
+
};
|
|
35150
|
+
} catch {
|
|
35151
|
+
return { prefix: "KB", nextId: null };
|
|
35152
|
+
}
|
|
35153
|
+
}
|
|
35154
|
+
function getKnownPrefixes(db) {
|
|
35155
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
35156
|
+
const configured = getConfiguredPrefixAndLegacyNextId(db).prefix;
|
|
35157
|
+
if (configured) {
|
|
35158
|
+
prefixes.add(configured);
|
|
35159
|
+
}
|
|
35160
|
+
const addFromQuery = (sql, mapper) => {
|
|
35161
|
+
try {
|
|
35162
|
+
const rows = db.prepare(sql).all();
|
|
35163
|
+
for (const row of rows) {
|
|
35164
|
+
const prefix = mapper(row)?.trim().toUpperCase();
|
|
35165
|
+
if (prefix) {
|
|
35166
|
+
prefixes.add(prefix);
|
|
35167
|
+
}
|
|
35168
|
+
}
|
|
35169
|
+
} catch {
|
|
35170
|
+
}
|
|
35171
|
+
};
|
|
35172
|
+
addFromQuery("SELECT prefix FROM distributed_task_id_state", (row) => row.prefix);
|
|
35173
|
+
addFromQuery("SELECT prefix FROM distributed_task_id_reservations", (row) => row.prefix);
|
|
35174
|
+
addFromQuery("SELECT id FROM tasks", (row) => parseTaskId(String(row.id ?? ""))?.prefix);
|
|
35175
|
+
addFromQuery("SELECT id FROM archivedTasks", (row) => parseTaskId(String(row.id ?? ""))?.prefix);
|
|
35176
|
+
return prefixes;
|
|
35177
|
+
}
|
|
35178
|
+
function getMaxTaskSequenceFromTable(db, table, prefix) {
|
|
35179
|
+
try {
|
|
35180
|
+
const rows = db.prepare(`SELECT id FROM ${table} WHERE id LIKE ?`).all(`${prefix}-%`);
|
|
35181
|
+
let maxSequence = 0;
|
|
35182
|
+
for (const row of rows) {
|
|
35183
|
+
const parsed = parseTaskId(row.id);
|
|
35184
|
+
if (parsed?.prefix === prefix && parsed.sequence > maxSequence) {
|
|
35185
|
+
maxSequence = parsed.sequence;
|
|
35186
|
+
}
|
|
35187
|
+
}
|
|
35188
|
+
return maxSequence;
|
|
35189
|
+
} catch {
|
|
35190
|
+
return 0;
|
|
35191
|
+
}
|
|
35192
|
+
}
|
|
35193
|
+
function getMaxReservationSequence(db, prefix) {
|
|
35194
|
+
try {
|
|
35195
|
+
const row = db.prepare("SELECT MAX(sequence) AS maxSeq FROM distributed_task_id_reservations WHERE prefix = ?").get(prefix);
|
|
35196
|
+
return typeof row?.maxSeq === "number" ? row.maxSeq : 0;
|
|
35197
|
+
} catch {
|
|
35198
|
+
return 0;
|
|
35199
|
+
}
|
|
35200
|
+
}
|
|
35201
|
+
function getNextSequenceFloor(db, prefix) {
|
|
35202
|
+
const configured = getConfiguredPrefixAndLegacyNextId(db);
|
|
35203
|
+
let nextSequence = 1;
|
|
35204
|
+
if (configured.prefix === prefix && configured.nextId && configured.nextId > nextSequence) {
|
|
35205
|
+
nextSequence = configured.nextId;
|
|
35206
|
+
}
|
|
35207
|
+
const taskHighWaterMark = getMaxTaskSequenceFromTable(db, "tasks", prefix) + 1;
|
|
35208
|
+
const archivedHighWaterMark = getMaxTaskSequenceFromTable(db, "archivedTasks", prefix) + 1;
|
|
35209
|
+
const reservationHighWaterMark = getMaxReservationSequence(db, prefix) + 1;
|
|
35210
|
+
nextSequence = Math.max(nextSequence, taskHighWaterMark, archivedHighWaterMark, reservationHighWaterMark);
|
|
35211
|
+
return nextSequence;
|
|
35212
|
+
}
|
|
35213
|
+
function ensureStateRow(db, prefix) {
|
|
35214
|
+
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
35215
|
+
const nextSequence = getNextSequenceFloor(db, prefix);
|
|
35216
|
+
db.prepare(
|
|
35217
|
+
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
35218
|
+
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
35219
|
+
) VALUES (?, ?, 0, NULL, ?)`
|
|
35220
|
+
).run(prefix, nextSequence, nowIso3);
|
|
35221
|
+
db.prepare(
|
|
35222
|
+
`UPDATE distributed_task_id_state
|
|
35223
|
+
SET nextSequence = MAX(nextSequence, ?),
|
|
35224
|
+
updatedAt = ?
|
|
35225
|
+
WHERE prefix = ?`
|
|
35226
|
+
).run(nextSequence, nowIso3, prefix);
|
|
35227
|
+
}
|
|
35228
|
+
function reconcileTaskIdState(db) {
|
|
35229
|
+
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
35230
|
+
return db.transaction(() => {
|
|
35231
|
+
const reconciled = [];
|
|
35232
|
+
for (const prefix of getKnownPrefixes(db)) {
|
|
35233
|
+
const nextSequence = getNextSequenceFloor(db, prefix);
|
|
35234
|
+
db.prepare(
|
|
35235
|
+
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
35236
|
+
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
35237
|
+
) VALUES (?, ?, 0, NULL, ?)`
|
|
35238
|
+
).run(prefix, nextSequence, nowIso3);
|
|
35239
|
+
const before = db.prepare("SELECT nextSequence FROM distributed_task_id_state WHERE prefix = ?").get(prefix);
|
|
35240
|
+
db.prepare(
|
|
35241
|
+
`UPDATE distributed_task_id_state
|
|
35242
|
+
SET nextSequence = MAX(nextSequence, ?),
|
|
35243
|
+
updatedAt = ?
|
|
35244
|
+
WHERE prefix = ?`
|
|
35245
|
+
).run(nextSequence, nowIso3, prefix);
|
|
35246
|
+
const after = db.prepare("SELECT nextSequence FROM distributed_task_id_state WHERE prefix = ?").get(prefix);
|
|
35247
|
+
if (!before || !after || after.nextSequence !== before.nextSequence) {
|
|
35248
|
+
reconciled.push(prefix);
|
|
35249
|
+
}
|
|
35250
|
+
}
|
|
35251
|
+
if (reconciled.length > 0) {
|
|
35252
|
+
db.bumpLastModified();
|
|
35253
|
+
}
|
|
35254
|
+
return reconciled;
|
|
35255
|
+
});
|
|
35256
|
+
}
|
|
34897
35257
|
function formatDistributedTaskId(prefix, sequence) {
|
|
34898
35258
|
const normalizedPrefix = prefix.trim().toUpperCase();
|
|
34899
35259
|
if (!normalizedPrefix) {
|
|
@@ -34936,48 +35296,6 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
34936
35296
|
};
|
|
34937
35297
|
return existsInTable("tasks") || existsInTable("archivedTasks");
|
|
34938
35298
|
};
|
|
34939
|
-
const ensureStateRow = (prefix) => {
|
|
34940
|
-
let seedSequence = 1;
|
|
34941
|
-
try {
|
|
34942
|
-
const configRow = db.prepare("SELECT nextId, settings FROM config WHERE id = 1").get();
|
|
34943
|
-
if (configRow) {
|
|
34944
|
-
const settings = configRow.settings ? JSON.parse(configRow.settings) : null;
|
|
34945
|
-
const configuredPrefix = (settings?.taskPrefix ?? "KB").trim().toUpperCase();
|
|
34946
|
-
if (configuredPrefix === prefix && typeof configRow.nextId === "number" && configRow.nextId > seedSequence) {
|
|
34947
|
-
seedSequence = configRow.nextId;
|
|
34948
|
-
}
|
|
34949
|
-
}
|
|
34950
|
-
} catch {
|
|
34951
|
-
}
|
|
34952
|
-
const idPattern = `${prefix}-%`;
|
|
34953
|
-
const probeTable = (table) => {
|
|
34954
|
-
try {
|
|
34955
|
-
const row = db.prepare(
|
|
34956
|
-
`SELECT MAX(CAST(substr(id, ${prefix.length + 2}) AS INTEGER)) AS maxSeq
|
|
34957
|
-
FROM ${table}
|
|
34958
|
-
WHERE id LIKE ? AND substr(id, ${prefix.length + 2}) GLOB '[0-9]*'`
|
|
34959
|
-
).get(idPattern);
|
|
34960
|
-
if (row && typeof row.maxSeq === "number" && row.maxSeq + 1 > seedSequence) {
|
|
34961
|
-
seedSequence = row.maxSeq + 1;
|
|
34962
|
-
}
|
|
34963
|
-
} catch {
|
|
34964
|
-
}
|
|
34965
|
-
};
|
|
34966
|
-
probeTable("tasks");
|
|
34967
|
-
probeTable("archivedTasks");
|
|
34968
|
-
const nowIso3 = (/* @__PURE__ */ new Date()).toISOString();
|
|
34969
|
-
db.prepare(
|
|
34970
|
-
`INSERT OR IGNORE INTO distributed_task_id_state (
|
|
34971
|
-
prefix, nextSequence, committedClusterTaskCount, lastCommittedTaskId, updatedAt
|
|
34972
|
-
) VALUES (?, ?, 0, NULL, ?)`
|
|
34973
|
-
).run(prefix, seedSequence, nowIso3);
|
|
34974
|
-
db.prepare(
|
|
34975
|
-
`UPDATE distributed_task_id_state
|
|
34976
|
-
SET nextSequence = MAX(nextSequence, ?),
|
|
34977
|
-
updatedAt = ?
|
|
34978
|
-
WHERE prefix = ?`
|
|
34979
|
-
).run(seedSequence, nowIso3, prefix);
|
|
34980
|
-
};
|
|
34981
35299
|
return {
|
|
34982
35300
|
formatDistributedTaskId,
|
|
34983
35301
|
reserveDistributedTaskId: async (input) => withLock(async () => {
|
|
@@ -34991,7 +35309,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
34991
35309
|
if (!prefix) {
|
|
34992
35310
|
throw new DistributedTaskIdError("prefix is required", "invalid_prefix");
|
|
34993
35311
|
}
|
|
34994
|
-
ensureStateRow(prefix);
|
|
35312
|
+
ensureStateRow(db, prefix);
|
|
34995
35313
|
const state = db.prepare(
|
|
34996
35314
|
"SELECT nextSequence, committedClusterTaskCount FROM distributed_task_id_state WHERE prefix = ?"
|
|
34997
35315
|
).get(prefix);
|
|
@@ -35045,7 +35363,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35045
35363
|
SET status = 'committed', committedAt = ?, updatedAt = ?
|
|
35046
35364
|
WHERE reservationId = ?`
|
|
35047
35365
|
).run(nowIso3, nowIso3, row.reservationId);
|
|
35048
|
-
ensureStateRow(row.prefix);
|
|
35366
|
+
ensureStateRow(db, row.prefix);
|
|
35049
35367
|
db.prepare(
|
|
35050
35368
|
`UPDATE distributed_task_id_state
|
|
35051
35369
|
SET committedClusterTaskCount = committedClusterTaskCount + 1,
|
|
@@ -35091,7 +35409,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35091
35409
|
WHERE reservationId = ?`
|
|
35092
35410
|
).run(input.reason, nowIso3, nowIso3, row.reservationId);
|
|
35093
35411
|
}
|
|
35094
|
-
ensureStateRow(row.prefix);
|
|
35412
|
+
ensureStateRow(db, row.prefix);
|
|
35095
35413
|
const state = db.prepare(
|
|
35096
35414
|
"SELECT committedClusterTaskCount FROM distributed_task_id_state WHERE prefix = ?"
|
|
35097
35415
|
).get(row.prefix);
|
|
@@ -35113,7 +35431,7 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35113
35431
|
if (!prefix) {
|
|
35114
35432
|
throw new DistributedTaskIdError("prefix is required", "invalid_prefix");
|
|
35115
35433
|
}
|
|
35116
|
-
ensureStateRow(prefix);
|
|
35434
|
+
ensureStateRow(db, prefix);
|
|
35117
35435
|
const row = db.prepare(
|
|
35118
35436
|
`SELECT nextSequence, committedClusterTaskCount, lastCommittedTaskId
|
|
35119
35437
|
FROM distributed_task_id_state
|
|
@@ -35138,11 +35456,12 @@ function createDistributedTaskIdAllocator(db) {
|
|
|
35138
35456
|
})
|
|
35139
35457
|
};
|
|
35140
35458
|
}
|
|
35141
|
-
var DEFAULT_RESERVATION_TTL_MS, DistributedTaskIdError;
|
|
35459
|
+
var DEFAULT_RESERVATION_TTL_MS, TASK_ID_PATTERN, DistributedTaskIdError;
|
|
35142
35460
|
var init_distributed_task_id = __esm({
|
|
35143
35461
|
"../core/src/distributed-task-id.ts"() {
|
|
35144
35462
|
"use strict";
|
|
35145
35463
|
DEFAULT_RESERVATION_TTL_MS = 15 * 60 * 1e3;
|
|
35464
|
+
TASK_ID_PATTERN = /^([A-Z][A-Z0-9]*)-(\d+)$/;
|
|
35146
35465
|
DistributedTaskIdError = class extends Error {
|
|
35147
35466
|
constructor(message, code) {
|
|
35148
35467
|
super(message);
|
|
@@ -35437,6 +35756,8 @@ var init_store = __esm({
|
|
|
35437
35756
|
worktreeAllocationLock = Promise.resolve();
|
|
35438
35757
|
/** Promise chain for serializing config.json read-modify-write cycles */
|
|
35439
35758
|
configLock = Promise.resolve();
|
|
35759
|
+
/** Startup/open guard for distributed_task_id_state reconciliation. */
|
|
35760
|
+
taskIdStateReconciled = false;
|
|
35440
35761
|
/** Cached workflow steps — invalidated on create/update/delete */
|
|
35441
35762
|
workflowStepsCache = null;
|
|
35442
35763
|
/** Plugin-contributed workflow step templates injected by engine runtime. */
|
|
@@ -35503,6 +35824,7 @@ var init_store = __esm({
|
|
|
35503
35824
|
throw error;
|
|
35504
35825
|
}
|
|
35505
35826
|
this._db = db;
|
|
35827
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35506
35828
|
if (detectLegacyData(this.fusionDir)) {
|
|
35507
35829
|
}
|
|
35508
35830
|
}
|
|
@@ -35522,6 +35844,13 @@ var init_store = __esm({
|
|
|
35522
35844
|
}
|
|
35523
35845
|
return this._archiveDb;
|
|
35524
35846
|
}
|
|
35847
|
+
reconcileDistributedTaskIdStateOnOpen() {
|
|
35848
|
+
if (this.taskIdStateReconciled) {
|
|
35849
|
+
return;
|
|
35850
|
+
}
|
|
35851
|
+
reconcileTaskIdState(this.db);
|
|
35852
|
+
this.taskIdStateReconciled = true;
|
|
35853
|
+
}
|
|
35525
35854
|
async init() {
|
|
35526
35855
|
await mkdir7(this.tasksDir, { recursive: true });
|
|
35527
35856
|
if (!this._db) {
|
|
@@ -35534,15 +35863,18 @@ var init_store = __esm({
|
|
|
35534
35863
|
}
|
|
35535
35864
|
this._db = db;
|
|
35536
35865
|
}
|
|
35866
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35537
35867
|
if (detectLegacyData(this.fusionDir)) {
|
|
35538
35868
|
await migrateFromLegacy(this.fusionDir, this._db);
|
|
35539
35869
|
}
|
|
35540
35870
|
await this.migrateActiveArchivedTasksToArchiveDb();
|
|
35541
35871
|
await this.importLegacyAgentLogsOnce();
|
|
35872
|
+
this.taskIdStateReconciled = false;
|
|
35873
|
+
this.reconcileDistributedTaskIdStateOnOpen();
|
|
35542
35874
|
if (!existsSync13(this.configPath)) {
|
|
35543
35875
|
const config = await this.readConfig();
|
|
35544
35876
|
try {
|
|
35545
|
-
await writeFile6(this.configPath,
|
|
35877
|
+
await writeFile6(this.configPath, this.serializeConfigForDisk(config));
|
|
35546
35878
|
} catch (err) {
|
|
35547
35879
|
storeLog.warn("Backward-compat config.json sync failed during init", {
|
|
35548
35880
|
phase: "init:config-sync",
|
|
@@ -36131,117 +36463,8 @@ ${outcome}`;
|
|
|
36131
36463
|
`;
|
|
36132
36464
|
return [...columns, limitedLog].join(", ");
|
|
36133
36465
|
}
|
|
36134
|
-
|
|
36135
|
-
|
|
36136
|
-
*/
|
|
36137
|
-
upsertTask(task) {
|
|
36138
|
-
this.db.prepare(`
|
|
36139
|
-
INSERT INTO tasks (
|
|
36140
|
-
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36141
|
-
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36142
|
-
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36143
|
-
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36144
|
-
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36145
|
-
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36146
|
-
executionStartedAt, executionCompletedAt,
|
|
36147
|
-
dependencies, steps, log, attachments, steeringComments,
|
|
36148
|
-
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36149
|
-
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36150
|
-
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
|
|
36151
|
-
) VALUES (
|
|
36152
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36153
|
-
)
|
|
36154
|
-
ON CONFLICT(id) DO UPDATE SET
|
|
36155
|
-
lineageId = excluded.lineageId,
|
|
36156
|
-
title = excluded.title,
|
|
36157
|
-
description = excluded.description,
|
|
36158
|
-
priority = excluded.priority,
|
|
36159
|
-
"column" = excluded."column",
|
|
36160
|
-
status = excluded.status,
|
|
36161
|
-
size = excluded.size,
|
|
36162
|
-
reviewLevel = excluded.reviewLevel,
|
|
36163
|
-
currentStep = excluded.currentStep,
|
|
36164
|
-
worktree = excluded.worktree,
|
|
36165
|
-
blockedBy = excluded.blockedBy,
|
|
36166
|
-
paused = excluded.paused,
|
|
36167
|
-
baseBranch = excluded.baseBranch,
|
|
36168
|
-
branch = excluded.branch,
|
|
36169
|
-
executionStartBranch = excluded.executionStartBranch,
|
|
36170
|
-
baseCommitSha = excluded.baseCommitSha,
|
|
36171
|
-
modelPresetId = excluded.modelPresetId,
|
|
36172
|
-
modelProvider = excluded.modelProvider,
|
|
36173
|
-
modelId = excluded.modelId,
|
|
36174
|
-
validatorModelProvider = excluded.validatorModelProvider,
|
|
36175
|
-
validatorModelId = excluded.validatorModelId,
|
|
36176
|
-
planningModelProvider = excluded.planningModelProvider,
|
|
36177
|
-
planningModelId = excluded.planningModelId,
|
|
36178
|
-
mergeRetries = excluded.mergeRetries,
|
|
36179
|
-
workflowStepRetries = excluded.workflowStepRetries,
|
|
36180
|
-
stuckKillCount = excluded.stuckKillCount,
|
|
36181
|
-
postReviewFixCount = excluded.postReviewFixCount,
|
|
36182
|
-
recoveryRetryCount = excluded.recoveryRetryCount,
|
|
36183
|
-
taskDoneRetryCount = excluded.taskDoneRetryCount,
|
|
36184
|
-
verificationFailureCount = excluded.verificationFailureCount,
|
|
36185
|
-
mergeConflictBounceCount = excluded.mergeConflictBounceCount,
|
|
36186
|
-
nextRecoveryAt = excluded.nextRecoveryAt,
|
|
36187
|
-
error = excluded.error,
|
|
36188
|
-
summary = excluded.summary,
|
|
36189
|
-
thinkingLevel = excluded.thinkingLevel,
|
|
36190
|
-
executionMode = excluded.executionMode,
|
|
36191
|
-
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
36192
|
-
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
36193
|
-
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
36194
|
-
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
36195
|
-
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
36196
|
-
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
36197
|
-
createdAt = excluded.createdAt,
|
|
36198
|
-
updatedAt = excluded.updatedAt,
|
|
36199
|
-
columnMovedAt = excluded.columnMovedAt,
|
|
36200
|
-
executionStartedAt = excluded.executionStartedAt,
|
|
36201
|
-
executionCompletedAt = excluded.executionCompletedAt,
|
|
36202
|
-
dependencies = excluded.dependencies,
|
|
36203
|
-
steps = excluded.steps,
|
|
36204
|
-
log = excluded.log,
|
|
36205
|
-
attachments = excluded.attachments,
|
|
36206
|
-
steeringComments = excluded.steeringComments,
|
|
36207
|
-
comments = excluded.comments,
|
|
36208
|
-
review = excluded.review,
|
|
36209
|
-
reviewState = excluded.reviewState,
|
|
36210
|
-
workflowStepResults = excluded.workflowStepResults,
|
|
36211
|
-
prInfo = excluded.prInfo,
|
|
36212
|
-
issueInfo = excluded.issueInfo,
|
|
36213
|
-
githubTracking = excluded.githubTracking,
|
|
36214
|
-
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
36215
|
-
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
36216
|
-
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
36217
|
-
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
36218
|
-
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
36219
|
-
mergeDetails = excluded.mergeDetails,
|
|
36220
|
-
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
36221
|
-
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
36222
|
-
modifiedFiles = excluded.modifiedFiles,
|
|
36223
|
-
missionId = excluded.missionId,
|
|
36224
|
-
sliceId = excluded.sliceId,
|
|
36225
|
-
assignedAgentId = excluded.assignedAgentId,
|
|
36226
|
-
pausedByAgentId = excluded.pausedByAgentId,
|
|
36227
|
-
assigneeUserId = excluded.assigneeUserId,
|
|
36228
|
-
nodeId = excluded.nodeId,
|
|
36229
|
-
effectiveNodeId = excluded.effectiveNodeId,
|
|
36230
|
-
effectiveNodeSource = excluded.effectiveNodeSource,
|
|
36231
|
-
sourceType = excluded.sourceType,
|
|
36232
|
-
sourceAgentId = excluded.sourceAgentId,
|
|
36233
|
-
sourceRunId = excluded.sourceRunId,
|
|
36234
|
-
sourceSessionId = excluded.sourceSessionId,
|
|
36235
|
-
sourceMessageId = excluded.sourceMessageId,
|
|
36236
|
-
sourceParentTaskId = excluded.sourceParentTaskId,
|
|
36237
|
-
sourceMetadata = excluded.sourceMetadata,
|
|
36238
|
-
checkedOutBy = excluded.checkedOutBy,
|
|
36239
|
-
checkedOutAt = excluded.checkedOutAt,
|
|
36240
|
-
checkoutNodeId = excluded.checkoutNodeId,
|
|
36241
|
-
checkoutRunId = excluded.checkoutRunId,
|
|
36242
|
-
checkoutLeaseRenewedAt = excluded.checkoutLeaseRenewedAt,
|
|
36243
|
-
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch
|
|
36244
|
-
`).run(
|
|
36466
|
+
getTaskPersistValues(task) {
|
|
36467
|
+
return [
|
|
36245
36468
|
task.id,
|
|
36246
36469
|
task.lineageId ?? generateTaskLineageId(),
|
|
36247
36470
|
task.title ?? null,
|
|
@@ -36332,9 +36555,193 @@ ${outcome}`;
|
|
|
36332
36555
|
task.checkoutRunId ?? null,
|
|
36333
36556
|
task.checkoutLeaseRenewedAt ?? null,
|
|
36334
36557
|
task.checkoutLeaseEpoch ?? 0
|
|
36335
|
-
|
|
36558
|
+
];
|
|
36559
|
+
}
|
|
36560
|
+
/**
|
|
36561
|
+
* Insert a brand-new task row. Create paths must use this so SQLite raises on
|
|
36562
|
+
* duplicate IDs instead of silently rewriting the existing row.
|
|
36563
|
+
*/
|
|
36564
|
+
insertTask(task) {
|
|
36565
|
+
this.db.prepare(`
|
|
36566
|
+
INSERT INTO tasks (
|
|
36567
|
+
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36568
|
+
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36569
|
+
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36570
|
+
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36571
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36572
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36573
|
+
executionStartedAt, executionCompletedAt,
|
|
36574
|
+
dependencies, steps, log, attachments, steeringComments,
|
|
36575
|
+
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36576
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36577
|
+
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
|
|
36578
|
+
) VALUES (
|
|
36579
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36580
|
+
)
|
|
36581
|
+
`).run(...this.getTaskPersistValues(task));
|
|
36582
|
+
this.db.bumpLastModified();
|
|
36583
|
+
}
|
|
36584
|
+
/**
|
|
36585
|
+
* Upsert a task to the database. Update paths intentionally retain ON CONFLICT
|
|
36586
|
+
* semantics; create paths must use insertTask() instead.
|
|
36587
|
+
*/
|
|
36588
|
+
upsertTask(task) {
|
|
36589
|
+
this.db.prepare(`
|
|
36590
|
+
INSERT INTO tasks (
|
|
36591
|
+
id, lineageId, title, description, priority, "column", status, size, reviewLevel, currentStep,
|
|
36592
|
+
worktree, blockedBy, paused, baseBranch, branch, executionStartBranch, baseCommitSha, modelPresetId, modelProvider,
|
|
36593
|
+
modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
|
|
36594
|
+
workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, mergeConflictBounceCount, nextRecoveryAt, error,
|
|
36595
|
+
summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
|
|
36596
|
+
tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
|
|
36597
|
+
executionStartedAt, executionCompletedAt,
|
|
36598
|
+
dependencies, steps, log, attachments, steeringComments,
|
|
36599
|
+
comments, review, reviewState, workflowStepResults, prInfo, issueInfo, githubTracking,
|
|
36600
|
+
sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
|
|
36601
|
+
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
|
|
36602
|
+
) VALUES (
|
|
36603
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
|
36604
|
+
)
|
|
36605
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
36606
|
+
lineageId = excluded.lineageId,
|
|
36607
|
+
title = excluded.title,
|
|
36608
|
+
description = excluded.description,
|
|
36609
|
+
priority = excluded.priority,
|
|
36610
|
+
"column" = excluded."column",
|
|
36611
|
+
status = excluded.status,
|
|
36612
|
+
size = excluded.size,
|
|
36613
|
+
reviewLevel = excluded.reviewLevel,
|
|
36614
|
+
currentStep = excluded.currentStep,
|
|
36615
|
+
worktree = excluded.worktree,
|
|
36616
|
+
blockedBy = excluded.blockedBy,
|
|
36617
|
+
paused = excluded.paused,
|
|
36618
|
+
baseBranch = excluded.baseBranch,
|
|
36619
|
+
branch = excluded.branch,
|
|
36620
|
+
executionStartBranch = excluded.executionStartBranch,
|
|
36621
|
+
baseCommitSha = excluded.baseCommitSha,
|
|
36622
|
+
modelPresetId = excluded.modelPresetId,
|
|
36623
|
+
modelProvider = excluded.modelProvider,
|
|
36624
|
+
modelId = excluded.modelId,
|
|
36625
|
+
validatorModelProvider = excluded.validatorModelProvider,
|
|
36626
|
+
validatorModelId = excluded.validatorModelId,
|
|
36627
|
+
planningModelProvider = excluded.planningModelProvider,
|
|
36628
|
+
planningModelId = excluded.planningModelId,
|
|
36629
|
+
mergeRetries = excluded.mergeRetries,
|
|
36630
|
+
workflowStepRetries = excluded.workflowStepRetries,
|
|
36631
|
+
stuckKillCount = excluded.stuckKillCount,
|
|
36632
|
+
postReviewFixCount = excluded.postReviewFixCount,
|
|
36633
|
+
recoveryRetryCount = excluded.recoveryRetryCount,
|
|
36634
|
+
taskDoneRetryCount = excluded.taskDoneRetryCount,
|
|
36635
|
+
verificationFailureCount = excluded.verificationFailureCount,
|
|
36636
|
+
mergeConflictBounceCount = excluded.mergeConflictBounceCount,
|
|
36637
|
+
nextRecoveryAt = excluded.nextRecoveryAt,
|
|
36638
|
+
error = excluded.error,
|
|
36639
|
+
summary = excluded.summary,
|
|
36640
|
+
thinkingLevel = excluded.thinkingLevel,
|
|
36641
|
+
executionMode = excluded.executionMode,
|
|
36642
|
+
tokenUsageInputTokens = excluded.tokenUsageInputTokens,
|
|
36643
|
+
tokenUsageOutputTokens = excluded.tokenUsageOutputTokens,
|
|
36644
|
+
tokenUsageCachedTokens = excluded.tokenUsageCachedTokens,
|
|
36645
|
+
tokenUsageTotalTokens = excluded.tokenUsageTotalTokens,
|
|
36646
|
+
tokenUsageFirstUsedAt = excluded.tokenUsageFirstUsedAt,
|
|
36647
|
+
tokenUsageLastUsedAt = excluded.tokenUsageLastUsedAt,
|
|
36648
|
+
createdAt = excluded.createdAt,
|
|
36649
|
+
updatedAt = excluded.updatedAt,
|
|
36650
|
+
columnMovedAt = excluded.columnMovedAt,
|
|
36651
|
+
executionStartedAt = excluded.executionStartedAt,
|
|
36652
|
+
executionCompletedAt = excluded.executionCompletedAt,
|
|
36653
|
+
dependencies = excluded.dependencies,
|
|
36654
|
+
steps = excluded.steps,
|
|
36655
|
+
log = excluded.log,
|
|
36656
|
+
attachments = excluded.attachments,
|
|
36657
|
+
steeringComments = excluded.steeringComments,
|
|
36658
|
+
comments = excluded.comments,
|
|
36659
|
+
review = excluded.review,
|
|
36660
|
+
reviewState = excluded.reviewState,
|
|
36661
|
+
workflowStepResults = excluded.workflowStepResults,
|
|
36662
|
+
prInfo = excluded.prInfo,
|
|
36663
|
+
issueInfo = excluded.issueInfo,
|
|
36664
|
+
githubTracking = excluded.githubTracking,
|
|
36665
|
+
sourceIssueProvider = excluded.sourceIssueProvider,
|
|
36666
|
+
sourceIssueRepository = excluded.sourceIssueRepository,
|
|
36667
|
+
sourceIssueExternalIssueId = excluded.sourceIssueExternalIssueId,
|
|
36668
|
+
sourceIssueNumber = excluded.sourceIssueNumber,
|
|
36669
|
+
sourceIssueUrl = excluded.sourceIssueUrl,
|
|
36670
|
+
mergeDetails = excluded.mergeDetails,
|
|
36671
|
+
breakIntoSubtasks = excluded.breakIntoSubtasks,
|
|
36672
|
+
enabledWorkflowSteps = excluded.enabledWorkflowSteps,
|
|
36673
|
+
modifiedFiles = excluded.modifiedFiles,
|
|
36674
|
+
missionId = excluded.missionId,
|
|
36675
|
+
sliceId = excluded.sliceId,
|
|
36676
|
+
assignedAgentId = excluded.assignedAgentId,
|
|
36677
|
+
pausedByAgentId = excluded.pausedByAgentId,
|
|
36678
|
+
assigneeUserId = excluded.assigneeUserId,
|
|
36679
|
+
nodeId = excluded.nodeId,
|
|
36680
|
+
effectiveNodeId = excluded.effectiveNodeId,
|
|
36681
|
+
effectiveNodeSource = excluded.effectiveNodeSource,
|
|
36682
|
+
sourceType = excluded.sourceType,
|
|
36683
|
+
sourceAgentId = excluded.sourceAgentId,
|
|
36684
|
+
sourceRunId = excluded.sourceRunId,
|
|
36685
|
+
sourceSessionId = excluded.sourceSessionId,
|
|
36686
|
+
sourceMessageId = excluded.sourceMessageId,
|
|
36687
|
+
sourceParentTaskId = excluded.sourceParentTaskId,
|
|
36688
|
+
sourceMetadata = excluded.sourceMetadata,
|
|
36689
|
+
checkedOutBy = excluded.checkedOutBy,
|
|
36690
|
+
checkedOutAt = excluded.checkedOutAt,
|
|
36691
|
+
checkoutNodeId = excluded.checkoutNodeId,
|
|
36692
|
+
checkoutRunId = excluded.checkoutRunId,
|
|
36693
|
+
checkoutLeaseRenewedAt = excluded.checkoutLeaseRenewedAt,
|
|
36694
|
+
checkoutLeaseEpoch = excluded.checkoutLeaseEpoch
|
|
36695
|
+
`).run(...this.getTaskPersistValues(task));
|
|
36336
36696
|
this.db.bumpLastModified();
|
|
36337
36697
|
}
|
|
36698
|
+
isTaskIdConflictError(error) {
|
|
36699
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36700
|
+
return /SQLITE_CONSTRAINT|UNIQUE constraint failed: tasks\.id|PRIMARY KEY constraint failed: tasks\.id/i.test(message);
|
|
36701
|
+
}
|
|
36702
|
+
logTaskCreateConflict(task, operation, error) {
|
|
36703
|
+
storeLog.error("Refused colliding task create", {
|
|
36704
|
+
phase: "task-create:id-conflict",
|
|
36705
|
+
operation,
|
|
36706
|
+
taskId: task.id,
|
|
36707
|
+
column: task.column,
|
|
36708
|
+
sourceType: task.sourceType,
|
|
36709
|
+
error: error instanceof Error ? error.message : String(error)
|
|
36710
|
+
});
|
|
36711
|
+
}
|
|
36712
|
+
insertTaskWithFtsRecovery(task, operation) {
|
|
36713
|
+
const normalizeConflict = (error) => {
|
|
36714
|
+
this.logTaskCreateConflict(task, operation, error);
|
|
36715
|
+
throw new Error(`Task ID already exists: ${task.id}`);
|
|
36716
|
+
};
|
|
36717
|
+
try {
|
|
36718
|
+
this.insertTask(task);
|
|
36719
|
+
return;
|
|
36720
|
+
} catch (error) {
|
|
36721
|
+
if (this.isTaskIdConflictError(error)) {
|
|
36722
|
+
normalizeConflict(error);
|
|
36723
|
+
}
|
|
36724
|
+
if (!this.db.isFts5CorruptionError(error)) {
|
|
36725
|
+
throw error;
|
|
36726
|
+
}
|
|
36727
|
+
console.warn(`[fusion:store] FTS5 corruption detected during insert for task ${task.id}; rebuilding index and retrying once`);
|
|
36728
|
+
try {
|
|
36729
|
+
this.db.rebuildFts5Index();
|
|
36730
|
+
} catch (rebuildError) {
|
|
36731
|
+
console.warn("[fusion:store] FTS5 rebuild failed; propagating original insert error", rebuildError);
|
|
36732
|
+
throw error;
|
|
36733
|
+
}
|
|
36734
|
+
try {
|
|
36735
|
+
this.insertTask(task);
|
|
36736
|
+
} catch (retryError) {
|
|
36737
|
+
if (this.isTaskIdConflictError(retryError)) {
|
|
36738
|
+
normalizeConflict(retryError);
|
|
36739
|
+
}
|
|
36740
|
+
console.warn("[fusion:store] Insert retry after FTS5 rebuild failed; propagating original insert error", retryError);
|
|
36741
|
+
throw error;
|
|
36742
|
+
}
|
|
36743
|
+
}
|
|
36744
|
+
}
|
|
36338
36745
|
upsertTaskWithFtsRecovery(task) {
|
|
36339
36746
|
try {
|
|
36340
36747
|
this.upsertTask(task);
|
|
@@ -36367,6 +36774,28 @@ ${outcome}`;
|
|
|
36367
36774
|
if (!row) return void 0;
|
|
36368
36775
|
return this.rowToTask(row);
|
|
36369
36776
|
}
|
|
36777
|
+
isTaskIdPresentInArchivedTasksTable(id) {
|
|
36778
|
+
try {
|
|
36779
|
+
const row = this.db.prepare("SELECT 1 as found FROM archivedTasks WHERE id = ? LIMIT 1").get(id);
|
|
36780
|
+
return row?.found === 1;
|
|
36781
|
+
} catch {
|
|
36782
|
+
return false;
|
|
36783
|
+
}
|
|
36784
|
+
}
|
|
36785
|
+
taskIdExistsAnywhere(id) {
|
|
36786
|
+
if (this.readTaskFromDb(id)) {
|
|
36787
|
+
return true;
|
|
36788
|
+
}
|
|
36789
|
+
if (this.isTaskIdPresentInArchivedTasksTable(id)) {
|
|
36790
|
+
return true;
|
|
36791
|
+
}
|
|
36792
|
+
return this.archiveDb.get(id) !== void 0;
|
|
36793
|
+
}
|
|
36794
|
+
assertTaskIdAvailable(id) {
|
|
36795
|
+
if (this.taskIdExistsAnywhere(id)) {
|
|
36796
|
+
throw new Error(`Task ID already exists: ${id}`);
|
|
36797
|
+
}
|
|
36798
|
+
}
|
|
36370
36799
|
isTaskArchived(id) {
|
|
36371
36800
|
const row = this.db.prepare('SELECT "column" FROM tasks WHERE id = ?').get(id);
|
|
36372
36801
|
if (row) {
|
|
@@ -36594,7 +37023,16 @@ ${outcome}`;
|
|
|
36594
37023
|
await rename4(tmpPath, taskJsonPath);
|
|
36595
37024
|
}
|
|
36596
37025
|
/**
|
|
36597
|
-
* Write a task to SQLite (primary store) and also write task.json to disk
|
|
37026
|
+
* Write a brand-new task to SQLite (primary store) and also write task.json to disk
|
|
37027
|
+
* for backward compatibility and debugging. Create paths must call this variant
|
|
37028
|
+
* so duplicate IDs fail safely instead of overwriting existing rows.
|
|
37029
|
+
*/
|
|
37030
|
+
async atomicCreateTaskJson(dir2, task, operation) {
|
|
37031
|
+
this.insertTaskWithFtsRecovery(task, operation);
|
|
37032
|
+
await this.writeTaskJsonFile(dir2, task);
|
|
37033
|
+
}
|
|
37034
|
+
/**
|
|
37035
|
+
* Write an existing task to SQLite (primary store) and also write task.json to disk
|
|
36598
37036
|
* for backward compatibility and debugging.
|
|
36599
37037
|
*/
|
|
36600
37038
|
async atomicWriteTaskJson(dir2, task) {
|
|
@@ -36610,7 +37048,7 @@ ${outcome}`;
|
|
|
36610
37048
|
* @param auditInput - Optional audit event input to record atomically with the task write
|
|
36611
37049
|
*/
|
|
36612
37050
|
async atomicWriteTaskJsonWithAudit(dir2, task, auditInput) {
|
|
36613
|
-
this.db.
|
|
37051
|
+
this.db.transactionImmediate(() => {
|
|
36614
37052
|
this.upsertTaskWithFtsRecovery(task);
|
|
36615
37053
|
if (auditInput) {
|
|
36616
37054
|
const eventId = randomUUID13();
|
|
@@ -36888,6 +37326,10 @@ ${outcome}`;
|
|
|
36888
37326
|
settings: fromJson(row.settings)
|
|
36889
37327
|
};
|
|
36890
37328
|
}
|
|
37329
|
+
serializeConfigForDisk(config) {
|
|
37330
|
+
const { nextId: _deprecatedNextId, ...configForDisk } = config;
|
|
37331
|
+
return JSON.stringify(configForDisk, null, 2);
|
|
37332
|
+
}
|
|
36891
37333
|
async writeConfig(config, options) {
|
|
36892
37334
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
36893
37335
|
const row = this.db.prepare("SELECT nextWorkflowStepId FROM config WHERE id = 1").get();
|
|
@@ -36895,10 +37337,14 @@ ${outcome}`;
|
|
|
36895
37337
|
const legacyWorkflowSteps = config.workflowSteps;
|
|
36896
37338
|
const workflowStepsJson = Array.isArray(legacyWorkflowSteps) ? JSON.stringify(legacyWorkflowSteps) : "[]";
|
|
36897
37339
|
this.db.prepare(
|
|
36898
|
-
`INSERT
|
|
36899
|
-
VALUES (1, ?, ?, ?,
|
|
37340
|
+
`INSERT INTO config (id, nextWorkflowStepId, settings, workflowSteps, updatedAt)
|
|
37341
|
+
VALUES (1, ?, ?, ?, ?)
|
|
37342
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
37343
|
+
nextWorkflowStepId = excluded.nextWorkflowStepId,
|
|
37344
|
+
settings = excluded.settings,
|
|
37345
|
+
workflowSteps = excluded.workflowSteps,
|
|
37346
|
+
updatedAt = excluded.updatedAt`
|
|
36900
37347
|
).run(
|
|
36901
|
-
config.nextId || 1,
|
|
36902
37348
|
nextWorkflowStepId,
|
|
36903
37349
|
JSON.stringify(config.settings || {}),
|
|
36904
37350
|
workflowStepsJson,
|
|
@@ -36907,7 +37353,7 @@ ${outcome}`;
|
|
|
36907
37353
|
this.db.bumpLastModified();
|
|
36908
37354
|
try {
|
|
36909
37355
|
const tmpPath = this.configPath + ".tmp";
|
|
36910
|
-
await writeFile6(tmpPath,
|
|
37356
|
+
await writeFile6(tmpPath, this.serializeConfigForDisk(config));
|
|
36911
37357
|
await rename4(tmpPath, this.configPath);
|
|
36912
37358
|
} catch (err) {
|
|
36913
37359
|
storeLog.warn("Backward-compat config.json sync failed after config write", {
|
|
@@ -37154,9 +37600,7 @@ ${outcome}`;
|
|
|
37154
37600
|
if (input.dependencies?.includes(id)) {
|
|
37155
37601
|
throw new Error(`Task ${id} cannot depend on itself`);
|
|
37156
37602
|
}
|
|
37157
|
-
|
|
37158
|
-
throw new Error(`Task ID already exists: ${id}`);
|
|
37159
|
-
}
|
|
37603
|
+
this.assertTaskIdAvailable(id);
|
|
37160
37604
|
const title = input.title?.trim() || void 0;
|
|
37161
37605
|
let resolvedWorkflowSteps = input.enabledWorkflowSteps?.length ? await this.resolveEnabledWorkflowSteps(input.enabledWorkflowSteps) : void 0;
|
|
37162
37606
|
if (input.enabledWorkflowSteps === void 0 && options.applyDefaultWorkflowSteps !== false) {
|
|
@@ -37252,9 +37696,9 @@ ${outcome}`;
|
|
|
37252
37696
|
createdAt: now,
|
|
37253
37697
|
updatedAt: options?.updatedAt ?? now
|
|
37254
37698
|
};
|
|
37699
|
+
this.assertTaskIdAvailable(id);
|
|
37255
37700
|
const dir2 = this.taskDir(id);
|
|
37256
|
-
await
|
|
37257
|
-
await this.atomicWriteTaskJson(dir2, task);
|
|
37701
|
+
await this.atomicCreateTaskJson(dir2, task, "createTask");
|
|
37258
37702
|
if (this.isWatching) this.taskCache.set(id, { ...task });
|
|
37259
37703
|
const prompt = options?.promptOverride ?? (task.column === "triage" ? buildBootstrapPrompt(id, task.title, task.description) : this.generateSpecifiedPrompt(task));
|
|
37260
37704
|
await mkdir7(dir2, { recursive: true });
|
|
@@ -37293,9 +37737,9 @@ ${outcome}`;
|
|
|
37293
37737
|
updatedAt: now,
|
|
37294
37738
|
baseBranch: sourceTask.baseBranch
|
|
37295
37739
|
};
|
|
37740
|
+
this.assertTaskIdAvailable(newId);
|
|
37296
37741
|
const newDir = this.taskDir(newId);
|
|
37297
|
-
await
|
|
37298
|
-
await this.atomicWriteTaskJson(newDir, newTask);
|
|
37742
|
+
await this.atomicCreateTaskJson(newDir, newTask, "duplicateTask");
|
|
37299
37743
|
await mkdir7(newDir, { recursive: true });
|
|
37300
37744
|
await writeFile6(join16(newDir, "PROMPT.md"), sourceTask.prompt);
|
|
37301
37745
|
if (this.isWatching) this.taskCache.set(newId, { ...newTask });
|
|
@@ -37349,9 +37793,9 @@ Refines: ${id}`,
|
|
|
37349
37793
|
updatedAt: now,
|
|
37350
37794
|
attachments: sourceTask.attachments ? [...sourceTask.attachments] : void 0
|
|
37351
37795
|
};
|
|
37796
|
+
this.assertTaskIdAvailable(newId);
|
|
37352
37797
|
const newDir = this.taskDir(newId);
|
|
37353
|
-
await
|
|
37354
|
-
await this.atomicWriteTaskJson(newDir, newTask);
|
|
37798
|
+
await this.atomicCreateTaskJson(newDir, newTask, "refineTask");
|
|
37355
37799
|
const prompt = `# ${newTask.title}
|
|
37356
37800
|
|
|
37357
37801
|
${newTask.description}
|
|
@@ -37733,6 +38177,8 @@ ${newTask.description}
|
|
|
37733
38177
|
task.status = void 0;
|
|
37734
38178
|
task.error = void 0;
|
|
37735
38179
|
task.blockedBy = void 0;
|
|
38180
|
+
task.paused = void 0;
|
|
38181
|
+
task.pausedByAgentId = void 0;
|
|
37736
38182
|
const hasNonPendingStepProgress = task.steps.some((step) => step.status !== "pending");
|
|
37737
38183
|
const preserveStepProgress = options?.preserveResumeState || options?.preserveProgress === true && hasNonPendingStepProgress;
|
|
37738
38184
|
if (!options?.preserveWorktree) {
|
|
@@ -38404,21 +38850,23 @@ ${newTask.description}
|
|
|
38404
38850
|
target: input.target,
|
|
38405
38851
|
metadata: input.metadata
|
|
38406
38852
|
};
|
|
38407
|
-
this.db.
|
|
38408
|
-
|
|
38409
|
-
|
|
38410
|
-
|
|
38411
|
-
|
|
38412
|
-
|
|
38413
|
-
|
|
38414
|
-
|
|
38415
|
-
|
|
38416
|
-
|
|
38417
|
-
|
|
38418
|
-
|
|
38419
|
-
|
|
38420
|
-
|
|
38421
|
-
|
|
38853
|
+
this.db.transactionImmediate(() => {
|
|
38854
|
+
this.db.prepare(`
|
|
38855
|
+
INSERT INTO runAuditEvents (
|
|
38856
|
+
id, timestamp, taskId, agentId, runId, domain, mutationType, target, metadata
|
|
38857
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
38858
|
+
`).run(
|
|
38859
|
+
event.id,
|
|
38860
|
+
event.timestamp,
|
|
38861
|
+
event.taskId ?? null,
|
|
38862
|
+
event.agentId,
|
|
38863
|
+
event.runId,
|
|
38864
|
+
event.domain,
|
|
38865
|
+
event.mutationType,
|
|
38866
|
+
event.target,
|
|
38867
|
+
toJsonNullable(event.metadata)
|
|
38868
|
+
);
|
|
38869
|
+
});
|
|
38422
38870
|
return event;
|
|
38423
38871
|
}
|
|
38424
38872
|
/**
|
|
@@ -40551,6 +40999,7 @@ ${stepsSection}`;
|
|
|
40551
40999
|
if (this._db) {
|
|
40552
41000
|
this._db.close();
|
|
40553
41001
|
this._db = null;
|
|
41002
|
+
this.taskIdStateReconciled = false;
|
|
40554
41003
|
}
|
|
40555
41004
|
if (this._archiveDb) {
|
|
40556
41005
|
this._archiveDb.close();
|
|
@@ -40584,9 +41033,9 @@ ${stepsSection}`;
|
|
|
40584
41033
|
}
|
|
40585
41034
|
getDatabaseHealth() {
|
|
40586
41035
|
return {
|
|
40587
|
-
|
|
40588
|
-
|
|
40589
|
-
|
|
41036
|
+
healthy: !this.db.corruptionDetected,
|
|
41037
|
+
lastCheckedAt: this.db.integrityCheckLastRunAt ? new Date(this.db.integrityCheckLastRunAt) : null,
|
|
41038
|
+
isRunning: this.db.integrityCheckPending
|
|
40590
41039
|
};
|
|
40591
41040
|
}
|
|
40592
41041
|
getDistributedTaskIdAllocator() {
|
|
@@ -41100,7 +41549,7 @@ var init_daemon_token = __esm({
|
|
|
41100
41549
|
});
|
|
41101
41550
|
|
|
41102
41551
|
// ../core/src/pi-extensions.ts
|
|
41103
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync3, statSync as
|
|
41552
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync4, writeFileSync } from "node:fs";
|
|
41104
41553
|
import { homedir as homedir3 } from "node:os";
|
|
41105
41554
|
import { basename as basename5, isAbsolute as isAbsolute5, join as join17, relative as relative2, resolve as resolve8, sep as sep4, win32 } from "node:path";
|
|
41106
41555
|
function getHomeDir3(home) {
|
|
@@ -41190,7 +41639,7 @@ function discoverExtensionsInDir(dir2, cwd, home) {
|
|
|
41190
41639
|
let isDirectory = entry.isDirectory();
|
|
41191
41640
|
if (entry.isSymbolicLink()) {
|
|
41192
41641
|
try {
|
|
41193
|
-
isDirectory =
|
|
41642
|
+
isDirectory = statSync4(entryPath).isDirectory();
|
|
41194
41643
|
} catch {
|
|
41195
41644
|
isDirectory = false;
|
|
41196
41645
|
}
|
|
@@ -49066,11 +49515,11 @@ var require_extract_zip = __commonJS({
|
|
|
49066
49515
|
var { createWriteStream: createWriteStream2, promises: fs3 } = __require("fs");
|
|
49067
49516
|
var getStream = require_get_stream();
|
|
49068
49517
|
var path6 = __require("path");
|
|
49069
|
-
var { promisify:
|
|
49518
|
+
var { promisify: promisify21 } = __require("util");
|
|
49070
49519
|
var stream = __require("stream");
|
|
49071
49520
|
var yauzl = require_yauzl();
|
|
49072
|
-
var openZip =
|
|
49073
|
-
var pipeline =
|
|
49521
|
+
var openZip = promisify21(yauzl.open);
|
|
49522
|
+
var pipeline = promisify21(stream.pipeline);
|
|
49074
49523
|
var Extractor = class {
|
|
49075
49524
|
constructor(zipPath, opts) {
|
|
49076
49525
|
this.zipPath = zipPath;
|
|
@@ -49152,7 +49601,7 @@ var require_extract_zip = __commonJS({
|
|
|
49152
49601
|
await fs3.mkdir(destDir, mkdirOptions);
|
|
49153
49602
|
if (isDir) return;
|
|
49154
49603
|
debug("opening read stream", dest);
|
|
49155
|
-
const readStream = await
|
|
49604
|
+
const readStream = await promisify21(this.zipfile.openReadStream.bind(this.zipfile))(entry);
|
|
49156
49605
|
if (symlink) {
|
|
49157
49606
|
const link = await getStream(readStream);
|
|
49158
49607
|
debug("creating symlink", link, dest);
|
|
@@ -56498,7 +56947,7 @@ var require_dist3 = __commonJS({
|
|
|
56498
56947
|
});
|
|
56499
56948
|
|
|
56500
56949
|
// ../core/src/agent-companies-parser.ts
|
|
56501
|
-
import { existsSync as existsSync19, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync, statSync as
|
|
56950
|
+
import { existsSync as existsSync19, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync5, rmSync, statSync as statSync5 } from "node:fs";
|
|
56502
56951
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
56503
56952
|
import { isAbsolute as isAbsolute7, join as join22, normalize as normalize3, resolve as resolve11 } from "node:path";
|
|
56504
56953
|
function slugifyAgentReference(value) {
|
|
@@ -56774,7 +57223,7 @@ function parseCompanyDirectory(dirPath) {
|
|
|
56774
57223
|
if (!existsSync19(resolvedPath)) {
|
|
56775
57224
|
throw new AgentCompaniesParseError(`Company directory does not exist: ${resolvedPath}`);
|
|
56776
57225
|
}
|
|
56777
|
-
if (!
|
|
57226
|
+
if (!statSync5(resolvedPath).isDirectory()) {
|
|
56778
57227
|
throw new AgentCompaniesParseError(`Company path is not a directory: ${resolvedPath}`);
|
|
56779
57228
|
}
|
|
56780
57229
|
const companyPath = join22(resolvedPath, "COMPANY.md");
|
|
@@ -56813,12 +57262,12 @@ function resolveExtractionRoot(tempDir) {
|
|
|
56813
57262
|
return tempDir;
|
|
56814
57263
|
}
|
|
56815
57264
|
async function extractTarArchive(archivePath, outputDir) {
|
|
56816
|
-
const [{ execFile:
|
|
57265
|
+
const [{ execFile: execFile12 }, { promisify: promisify21 }] = await Promise.all([
|
|
56817
57266
|
import("node:child_process"),
|
|
56818
57267
|
import("node:util")
|
|
56819
57268
|
]);
|
|
56820
|
-
const
|
|
56821
|
-
await
|
|
57269
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
57270
|
+
await execFileAsync9("tar", ["xzf", archivePath, "-C", outputDir]);
|
|
56822
57271
|
}
|
|
56823
57272
|
function sanitizeCompanySubPath(subPath) {
|
|
56824
57273
|
const trimmed = subPath.trim();
|
|
@@ -62373,15 +62822,15 @@ function evaluateAgentActionGate(params) {
|
|
|
62373
62822
|
let resourceId;
|
|
62374
62823
|
if (params.toolName === "bash") {
|
|
62375
62824
|
const command = extractShellCommand(args);
|
|
62376
|
-
const
|
|
62377
|
-
if (
|
|
62825
|
+
const git2 = classifyGitCommand(command);
|
|
62826
|
+
if (git2?.write) {
|
|
62378
62827
|
category = "git_write";
|
|
62379
|
-
operation =
|
|
62828
|
+
operation = git2.operation;
|
|
62380
62829
|
resourceType = "git";
|
|
62381
62830
|
} else {
|
|
62382
62831
|
category = "command_execution";
|
|
62383
|
-
operation =
|
|
62384
|
-
resourceType =
|
|
62832
|
+
operation = git2?.operation ?? "shell command";
|
|
62833
|
+
resourceType = git2 ? "git" : "command";
|
|
62385
62834
|
}
|
|
62386
62835
|
} else if (params.toolName === "write" || params.toolName === "edit") {
|
|
62387
62836
|
category = "file_write_delete";
|
|
@@ -64240,6 +64689,71 @@ _... existing specification middle trimmed ..._
|
|
|
64240
64689
|
|
|
64241
64690
|
${tail}`;
|
|
64242
64691
|
}
|
|
64692
|
+
function extractMarkdownSection(document2, headingName) {
|
|
64693
|
+
const heading = `## ${headingName}`;
|
|
64694
|
+
const start = document2.indexOf(heading);
|
|
64695
|
+
if (start === -1) {
|
|
64696
|
+
return "";
|
|
64697
|
+
}
|
|
64698
|
+
const afterHeading = start + heading.length;
|
|
64699
|
+
const nextH2 = document2.indexOf("\n## ", afterHeading);
|
|
64700
|
+
const nextH1 = document2.indexOf("\n# ", afterHeading);
|
|
64701
|
+
const endCandidates = [nextH2, nextH1].filter((value) => value !== -1);
|
|
64702
|
+
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : document2.length;
|
|
64703
|
+
return document2.slice(start, end).trim();
|
|
64704
|
+
}
|
|
64705
|
+
function compactTaskPromptStepsSection(section) {
|
|
64706
|
+
const stepTitles = Array.from(section.matchAll(/^### Step \d+:.*$/gm), (match) => match[0].trim());
|
|
64707
|
+
if (stepTitles.length === 0) {
|
|
64708
|
+
return section.trim();
|
|
64709
|
+
}
|
|
64710
|
+
return [
|
|
64711
|
+
"## Steps",
|
|
64712
|
+
...stepTitles,
|
|
64713
|
+
"",
|
|
64714
|
+
"_... step checklist details trimmed for context limits ..._"
|
|
64715
|
+
].join("\n").trim();
|
|
64716
|
+
}
|
|
64717
|
+
function truncateCompactedSection(section, maxChars, label) {
|
|
64718
|
+
const trimmed = section.trim();
|
|
64719
|
+
if (!trimmed || trimmed.length <= maxChars) {
|
|
64720
|
+
return trimmed;
|
|
64721
|
+
}
|
|
64722
|
+
const marker = `_... ${label} trimmed for context limits ..._`;
|
|
64723
|
+
const headBudget = Math.max(200, maxChars - marker.length - 2);
|
|
64724
|
+
return [
|
|
64725
|
+
`${trimmed.slice(0, headBudget).trimEnd()}\u2026`,
|
|
64726
|
+
"",
|
|
64727
|
+
marker
|
|
64728
|
+
].join("\n").trim();
|
|
64729
|
+
}
|
|
64730
|
+
function compactTaskPromptSectionBody(body) {
|
|
64731
|
+
const trimmed = body.trim();
|
|
64732
|
+
if (trimmed.length <= MAX_COMPACTED_TASK_PROMPT_CHARS) {
|
|
64733
|
+
return trimmed;
|
|
64734
|
+
}
|
|
64735
|
+
const fencedMatch = /^```markdown\s*\n([\s\S]*?)\n```$/m.exec(trimmed);
|
|
64736
|
+
const promptContent = fencedMatch ? fencedMatch[1].trim() : trimmed;
|
|
64737
|
+
const firstSectionIndex = promptContent.indexOf("\n## ");
|
|
64738
|
+
const preamble = (firstSectionIndex === -1 ? promptContent : promptContent.slice(0, firstSectionIndex)).trim();
|
|
64739
|
+
const missionSection = extractMarkdownSection(promptContent, "Mission");
|
|
64740
|
+
const dependenciesSection = extractMarkdownSection(promptContent, "Dependencies");
|
|
64741
|
+
const fileScopeSection = extractMarkdownSection(promptContent, "File Scope");
|
|
64742
|
+
const stepsSection = compactTaskPromptStepsSection(extractMarkdownSection(promptContent, "Steps"));
|
|
64743
|
+
const compactedContent = [
|
|
64744
|
+
truncateCompactedSection(preamble, 400, "task header"),
|
|
64745
|
+
truncateCompactedSection(missionSection, 900, "mission"),
|
|
64746
|
+
truncateCompactedSection(dependenciesSection, 500, "dependencies"),
|
|
64747
|
+
truncateCompactedSection(fileScopeSection, 1e3, "file scope"),
|
|
64748
|
+
truncateCompactedSection(stepsSection, 1200, "steps outline"),
|
|
64749
|
+
"_... remaining PROMPT.md sections trimmed for context limits ..._"
|
|
64750
|
+
].filter(Boolean).join("\n\n").trim();
|
|
64751
|
+
const narrowedContent = compactedContent.length <= MAX_COMPACTED_TASK_PROMPT_CHARS ? compactedContent : compactExistingSpecificationSectionBody(compactedContent);
|
|
64752
|
+
const finalContent = fencedMatch ? `\`\`\`markdown
|
|
64753
|
+
${narrowedContent}
|
|
64754
|
+
\`\`\`` : narrowedContent;
|
|
64755
|
+
return finalContent.length < trimmed.length ? finalContent : compactExistingSpecificationSectionBody(trimmed);
|
|
64756
|
+
}
|
|
64243
64757
|
function compactUserCommentsSectionBody(body) {
|
|
64244
64758
|
const trimmed = body.trim();
|
|
64245
64759
|
if (trimmed.length <= MAX_COMPACTED_USER_COMMENTS_CHARS) {
|
|
@@ -64272,7 +64786,7 @@ function compactUserCommentsSectionBody(body) {
|
|
|
64272
64786
|
].join("\n").trim();
|
|
64273
64787
|
}
|
|
64274
64788
|
function compactLargePromptSections(prompt) {
|
|
64275
|
-
const sectionPattern = /(^|\n)(## (?:Subtask Consideration|Subtask Breakdown Requested|Attachments|Existing Specification|User Comments)\n)([\s\S]*?)(?=\n## [^#]|\n# [^#]|$)/g;
|
|
64789
|
+
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;
|
|
64276
64790
|
let changed = false;
|
|
64277
64791
|
const compactedPrompt = prompt.replace(sectionPattern, (match, prefix, heading, body) => {
|
|
64278
64792
|
const headingName = heading.trim().replace(/^##\s+/, "");
|
|
@@ -64282,6 +64796,7 @@ function compactLargePromptSections(prompt) {
|
|
|
64282
64796
|
"Subtask Breakdown Requested": MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS,
|
|
64283
64797
|
Attachments: MAX_COMPACTED_ATTACHMENTS_CHARS,
|
|
64284
64798
|
"Existing Specification": MAX_COMPACTED_EXISTING_SPEC_CHARS,
|
|
64799
|
+
"Task PROMPT.md": MAX_COMPACTED_TASK_PROMPT_CHARS,
|
|
64285
64800
|
"User Comments": MAX_COMPACTED_USER_COMMENTS_CHARS
|
|
64286
64801
|
};
|
|
64287
64802
|
const maxChars = maxByHeading[headingName] ?? MAX_COMPACTED_PROMPT_MEMORY_CHARS;
|
|
@@ -64295,6 +64810,8 @@ function compactLargePromptSections(prompt) {
|
|
|
64295
64810
|
compactedBody = compactAttachmentSectionBody(trimmedBody);
|
|
64296
64811
|
} else if (headingName === "Existing Specification") {
|
|
64297
64812
|
compactedBody = compactExistingSpecificationSectionBody(trimmedBody);
|
|
64813
|
+
} else if (headingName === "Task PROMPT.md") {
|
|
64814
|
+
compactedBody = compactTaskPromptSectionBody(trimmedBody);
|
|
64298
64815
|
} else if (headingName === "User Comments") {
|
|
64299
64816
|
compactedBody = compactUserCommentsSectionBody(trimmedBody);
|
|
64300
64817
|
}
|
|
@@ -65319,7 +65836,7 @@ async function createFnAgent2(options) {
|
|
|
65319
65836
|
});
|
|
65320
65837
|
return { session: promptableSession, sessionFile: promptableSession.sessionFile };
|
|
65321
65838
|
}
|
|
65322
|
-
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;
|
|
65839
|
+
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;
|
|
65323
65840
|
var init_pi = __esm({
|
|
65324
65841
|
"../engine/src/pi.ts"() {
|
|
65325
65842
|
"use strict";
|
|
@@ -65345,6 +65862,7 @@ var init_pi = __esm({
|
|
|
65345
65862
|
MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS = 1200;
|
|
65346
65863
|
MAX_COMPACTED_ATTACHMENTS_CHARS = 4e3;
|
|
65347
65864
|
MAX_COMPACTED_EXISTING_SPEC_CHARS = 4e3;
|
|
65865
|
+
MAX_COMPACTED_TASK_PROMPT_CHARS = MAX_COMPACTED_EXISTING_SPEC_CHARS;
|
|
65348
65866
|
MAX_COMPACTED_USER_COMMENTS_CHARS = 2e3;
|
|
65349
65867
|
GATE_BYPASS_TOOL_NAMES = /* @__PURE__ */ new Set([
|
|
65350
65868
|
"fn_heartbeat_done",
|
|
@@ -66613,7 +67131,7 @@ var init_research_step_runner = __esm({
|
|
|
66613
67131
|
// ../engine/src/agent-tools.ts
|
|
66614
67132
|
import { appendFile as appendFile3, mkdir as mkdir12, readFile as readFile14, readdir as readdir8, stat as stat5, writeFile as writeFile11 } from "node:fs/promises";
|
|
66615
67133
|
import { existsSync as existsSync24 } from "node:fs";
|
|
66616
|
-
import { createHash as
|
|
67134
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
66617
67135
|
import { join as join30, relative as relative6, resolve as resolve16 } from "node:path";
|
|
66618
67136
|
import { Type } from "@mariozechner/pi-ai";
|
|
66619
67137
|
function sanitizeAgentMemoryId(agentId) {
|
|
@@ -66656,7 +67174,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
|
|
|
66656
67174
|
}
|
|
66657
67175
|
}
|
|
66658
67176
|
function qmdAgentMemoryCollectionName2(rootDir, agentId) {
|
|
66659
|
-
const hash =
|
|
67177
|
+
const hash = createHash6("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
|
|
66660
67178
|
return `fusion-agent-memory-${sanitizeAgentMemoryId(agentId).toLowerCase()}-${hash}`;
|
|
66661
67179
|
}
|
|
66662
67180
|
function buildQmdAgentMemoryCollectionAddArgs(rootDir, agentId) {
|
|
@@ -66785,11 +67303,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
|
|
|
66785
67303
|
return;
|
|
66786
67304
|
}
|
|
66787
67305
|
const promise = (async () => {
|
|
66788
|
-
const { execFile:
|
|
66789
|
-
const { promisify:
|
|
66790
|
-
const
|
|
67306
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
67307
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
67308
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
66791
67309
|
try {
|
|
66792
|
-
await
|
|
67310
|
+
await execFileAsync9("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
|
|
66793
67311
|
cwd: rootDir,
|
|
66794
67312
|
timeout: 4e3,
|
|
66795
67313
|
maxBuffer: 512 * 1024
|
|
@@ -66802,8 +67320,8 @@ ${stderr}`)) {
|
|
|
66802
67320
|
throw error;
|
|
66803
67321
|
}
|
|
66804
67322
|
}
|
|
66805
|
-
await
|
|
66806
|
-
await
|
|
67323
|
+
await execFileAsync9("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
67324
|
+
await execFileAsync9("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
66807
67325
|
})();
|
|
66808
67326
|
agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
66809
67327
|
try {
|
|
@@ -66858,10 +67376,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
|
|
|
66858
67376
|
}
|
|
66859
67377
|
try {
|
|
66860
67378
|
await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
|
|
66861
|
-
const { execFile:
|
|
66862
|
-
const { promisify:
|
|
66863
|
-
const
|
|
66864
|
-
const { stdout } = await
|
|
67379
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
67380
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
67381
|
+
const execFileAsync9 = promisify21(execFile12);
|
|
67382
|
+
const { stdout } = await execFileAsync9("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
|
|
66865
67383
|
cwd: rootDir,
|
|
66866
67384
|
timeout: 4e3,
|
|
66867
67385
|
maxBuffer: 1024 * 1024
|
|
@@ -66940,24 +67458,36 @@ function createTaskCreateTool(store, provenance, options) {
|
|
|
66940
67458
|
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).",
|
|
66941
67459
|
parameters: taskCreateParams,
|
|
66942
67460
|
execute: async (_id, params) => {
|
|
66943
|
-
|
|
66944
|
-
|
|
66945
|
-
|
|
66946
|
-
|
|
66947
|
-
|
|
66948
|
-
|
|
66949
|
-
|
|
66950
|
-
|
|
66951
|
-
|
|
66952
|
-
|
|
66953
|
-
|
|
66954
|
-
|
|
66955
|
-
|
|
66956
|
-
|
|
66957
|
-
|
|
66958
|
-
|
|
66959
|
-
|
|
66960
|
-
|
|
67461
|
+
try {
|
|
67462
|
+
const task = await createAgentTask(store, {
|
|
67463
|
+
description: params.description,
|
|
67464
|
+
dependencies: params.dependencies,
|
|
67465
|
+
column: "triage",
|
|
67466
|
+
priority: params.priority,
|
|
67467
|
+
source: provenance ? {
|
|
67468
|
+
sourceType: provenance.sourceType,
|
|
67469
|
+
sourceAgentId: provenance.sourceAgentId,
|
|
67470
|
+
sourceRunId: provenance.sourceRunId
|
|
67471
|
+
} : void 0
|
|
67472
|
+
}, options);
|
|
67473
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
67474
|
+
return {
|
|
67475
|
+
content: [{
|
|
67476
|
+
type: "text",
|
|
67477
|
+
text: `Created ${task.id}: ${params.description}${deps}`
|
|
67478
|
+
}],
|
|
67479
|
+
details: { taskId: task.id }
|
|
67480
|
+
};
|
|
67481
|
+
} catch (err) {
|
|
67482
|
+
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
|
67483
|
+
return {
|
|
67484
|
+
content: [{ type: "text", text: `ERROR: ${err.message}` }],
|
|
67485
|
+
details: {},
|
|
67486
|
+
isError: true
|
|
67487
|
+
};
|
|
67488
|
+
}
|
|
67489
|
+
throw err;
|
|
67490
|
+
}
|
|
66961
67491
|
}
|
|
66962
67492
|
};
|
|
66963
67493
|
}
|
|
@@ -67868,24 +68398,35 @@ function createDelegateTaskTool(agentStore, taskStore, options) {
|
|
|
67868
68398
|
details: {}
|
|
67869
68399
|
};
|
|
67870
68400
|
}
|
|
67871
|
-
|
|
67872
|
-
|
|
67873
|
-
|
|
67874
|
-
|
|
67875
|
-
|
|
67876
|
-
|
|
67877
|
-
|
|
67878
|
-
|
|
68401
|
+
try {
|
|
68402
|
+
const task = await createAgentTask(taskStore, {
|
|
68403
|
+
description: params.description,
|
|
68404
|
+
dependencies: params.dependencies,
|
|
68405
|
+
column: "todo",
|
|
68406
|
+
assignedAgentId: params.agent_id,
|
|
68407
|
+
source: {
|
|
68408
|
+
sourceType: "api",
|
|
68409
|
+
...override ? { sourceMetadata: { executorRoleOverride: true } } : {}
|
|
68410
|
+
}
|
|
68411
|
+
}, options);
|
|
68412
|
+
const deps = task.dependencies.length ? ` (depends on: ${task.dependencies.join(", ")})` : "";
|
|
68413
|
+
return {
|
|
68414
|
+
content: [{
|
|
68415
|
+
type: "text",
|
|
68416
|
+
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.`
|
|
68417
|
+
}],
|
|
68418
|
+
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
68419
|
+
};
|
|
68420
|
+
} catch (err) {
|
|
68421
|
+
if (err instanceof Error && err.message.startsWith("Task ID already exists:")) {
|
|
68422
|
+
return {
|
|
68423
|
+
content: [{ type: "text", text: `ERROR: ${err.message}` }],
|
|
68424
|
+
details: {},
|
|
68425
|
+
isError: true
|
|
68426
|
+
};
|
|
67879
68427
|
}
|
|
67880
|
-
|
|
67881
|
-
|
|
67882
|
-
return {
|
|
67883
|
-
content: [{
|
|
67884
|
-
type: "text",
|
|
67885
|
-
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.`
|
|
67886
|
-
}],
|
|
67887
|
-
details: { taskId: task.id, agentId: agent.id, agentName: agent.name }
|
|
67888
|
-
};
|
|
68428
|
+
throw err;
|
|
68429
|
+
}
|
|
67889
68430
|
}
|
|
67890
68431
|
};
|
|
67891
68432
|
}
|
|
@@ -68225,7 +68766,7 @@ ${lines.join("\n")}`
|
|
|
68225
68766
|
}
|
|
68226
68767
|
};
|
|
68227
68768
|
}
|
|
68228
|
-
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;
|
|
68769
|
+
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;
|
|
68229
68770
|
var init_agent_tools = __esm({
|
|
68230
68771
|
"../engine/src/agent-tools.ts"() {
|
|
68231
68772
|
"use strict";
|
|
@@ -68236,10 +68777,16 @@ var init_agent_tools = __esm({
|
|
|
68236
68777
|
init_logger2();
|
|
68237
68778
|
init_web_fetch();
|
|
68238
68779
|
init_agent_action_gate();
|
|
68780
|
+
TASK_CREATE_PRIORITY_VALUES = ["low", "normal", "high", "urgent"];
|
|
68239
68781
|
taskCreateParams = Type.Object({
|
|
68240
68782
|
description: Type.String({ description: "What needs to be done" }),
|
|
68241
68783
|
dependencies: Type.Optional(
|
|
68242
68784
|
Type.Array(Type.String(), { description: 'Task IDs this new task depends on (e.g. ["KB-001"])' })
|
|
68785
|
+
),
|
|
68786
|
+
priority: Type.Optional(
|
|
68787
|
+
Type.Union(TASK_CREATE_PRIORITY_VALUES.map((priority) => Type.Literal(priority)), {
|
|
68788
|
+
description: "Task priority (low, normal, high, urgent)"
|
|
68789
|
+
})
|
|
68243
68790
|
)
|
|
68244
68791
|
});
|
|
68245
68792
|
taskLogParams = Type.Object({
|
|
@@ -69477,6 +70024,7 @@ var init_ntfy_provider = __esm({
|
|
|
69477
70024
|
);
|
|
69478
70025
|
const response = await sendNtfyNotificationWithResult({
|
|
69479
70026
|
ntfyBaseUrl: this.config.ntfyBaseUrl,
|
|
70027
|
+
ntfyAccessToken: this.config.ntfyAccessToken,
|
|
69480
70028
|
topic: this.config.topic,
|
|
69481
70029
|
title: content.title,
|
|
69482
70030
|
message: content.message,
|
|
@@ -69781,7 +70329,7 @@ var init_notification_service = __esm({
|
|
|
69781
70329
|
handleSettingsUpdated = async (data) => {
|
|
69782
70330
|
const { settings, previous } = data;
|
|
69783
70331
|
this.setNotificationsEnabledFromSettings(settings);
|
|
69784
|
-
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)) {
|
|
70332
|
+
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)) {
|
|
69785
70333
|
const wasEnabled = Boolean(previous.ntfyEnabled && previous.ntfyTopic);
|
|
69786
70334
|
const isEnabled = Boolean(settings.ntfyEnabled && settings.ntfyTopic);
|
|
69787
70335
|
await this.syncNtfyProvider(settings);
|
|
@@ -69793,6 +70341,8 @@ var init_notification_service = __esm({
|
|
|
69793
70341
|
schedulerLog.log("NotificationService ntfy topic updated");
|
|
69794
70342
|
} else if (settings.ntfyBaseUrl !== previous.ntfyBaseUrl) {
|
|
69795
70343
|
schedulerLog.log("NotificationService ntfy base URL updated");
|
|
70344
|
+
} else if (settings.ntfyAccessToken !== previous.ntfyAccessToken) {
|
|
70345
|
+
schedulerLog.log("NotificationService ntfy access token updated");
|
|
69796
70346
|
} else if (settings.ntfyDashboardHost !== previous.ntfyDashboardHost) {
|
|
69797
70347
|
schedulerLog.log("NotificationService ntfy dashboard host updated");
|
|
69798
70348
|
} else if (JSON.stringify(settings.ntfyEvents) !== JSON.stringify(previous.ntfyEvents)) {
|
|
@@ -69821,6 +70371,7 @@ var init_notification_service = __esm({
|
|
|
69821
70371
|
await this.ntfyProvider.initialize?.({
|
|
69822
70372
|
topic: settings.ntfyTopic,
|
|
69823
70373
|
ntfyBaseUrl: settings.ntfyBaseUrl ?? this.options.ntfyBaseUrl,
|
|
70374
|
+
ntfyAccessToken: settings.ntfyAccessToken,
|
|
69824
70375
|
dashboardHost: settings.ntfyDashboardHost,
|
|
69825
70376
|
events: settings.ntfyEvents ?? [...DEFAULT_NTFY_EVENTS],
|
|
69826
70377
|
projectId: this.options.projectId
|
|
@@ -70027,6 +70578,7 @@ function buildNtfyClickUrl(options) {
|
|
|
70027
70578
|
}
|
|
70028
70579
|
async function sendNtfyNotificationWithResult({
|
|
70029
70580
|
ntfyBaseUrl,
|
|
70581
|
+
ntfyAccessToken,
|
|
70030
70582
|
topic,
|
|
70031
70583
|
title,
|
|
70032
70584
|
message,
|
|
@@ -70043,6 +70595,10 @@ async function sendNtfyNotificationWithResult({
|
|
|
70043
70595
|
if (clickUrl) {
|
|
70044
70596
|
headers.Click = clickUrl;
|
|
70045
70597
|
}
|
|
70598
|
+
const trimmedToken = ntfyAccessToken?.trim();
|
|
70599
|
+
if (trimmedToken) {
|
|
70600
|
+
headers.Authorization = `Bearer ${trimmedToken}`;
|
|
70601
|
+
}
|
|
70046
70602
|
const resolvedBaseUrl = resolveNtfyBaseUrl(ntfyBaseUrl);
|
|
70047
70603
|
const response = await fetch(`${resolvedBaseUrl}/${topic}`, {
|
|
70048
70604
|
method: "POST",
|
|
@@ -70358,9 +70914,33 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
70358
70914
|
this.reason = reason;
|
|
70359
70915
|
}
|
|
70360
70916
|
}
|
|
70361
|
-
|
|
70362
|
-
|
|
70363
|
-
|
|
70917
|
+
const activeSessions = /* @__PURE__ */ new Set();
|
|
70918
|
+
let reviewText = "";
|
|
70919
|
+
const endSession = (session2) => {
|
|
70920
|
+
if (!activeSessions.delete(session2)) {
|
|
70921
|
+
return;
|
|
70922
|
+
}
|
|
70923
|
+
session2.dispose();
|
|
70924
|
+
options.onSessionEnded?.(session2);
|
|
70925
|
+
};
|
|
70926
|
+
const buildPauseUnavailableResult = async (reason) => {
|
|
70927
|
+
reviewerLog.log(
|
|
70928
|
+
`${taskId}: ${reviewType} review for Step ${stepNumber} aborted before spawn \u2014 ${reason} active`
|
|
70929
|
+
);
|
|
70930
|
+
if (options.store && options.taskId) {
|
|
70931
|
+
await options.store.logEntry(
|
|
70932
|
+
options.taskId,
|
|
70933
|
+
`${reviewType} review aborted before spawn \u2014 ${reason} active`
|
|
70934
|
+
).catch(() => void 0);
|
|
70935
|
+
}
|
|
70936
|
+
return {
|
|
70937
|
+
verdict: "UNAVAILABLE",
|
|
70938
|
+
review: `${reason} active \u2014 reviewer not spawned. Stop calling fn_review_* and exit cleanly; the parent task will resume after unpause.`,
|
|
70939
|
+
summary: `Skipped: ${reason}`
|
|
70940
|
+
};
|
|
70941
|
+
};
|
|
70942
|
+
const createReviewerSession = async () => {
|
|
70943
|
+
const { session: session2 } = await createResolvedAgentSession({
|
|
70364
70944
|
sessionPurpose: "reviewer",
|
|
70365
70945
|
runtimeHint: extractRuntimeHint(memoryAgent?.runtimeConfig),
|
|
70366
70946
|
pluginRunner: options.pluginRunner,
|
|
@@ -70378,7 +70958,6 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
70378
70958
|
fallbackProvider: validatorFallbackProvider,
|
|
70379
70959
|
fallbackModelId: validatorFallbackModelId,
|
|
70380
70960
|
defaultThinkingLevel: options.defaultThinkingLevel,
|
|
70381
|
-
// Skill selection: use assigned agent skills if available, otherwise role fallback
|
|
70382
70961
|
...skillContext?.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {},
|
|
70383
70962
|
taskId: options.taskId,
|
|
70384
70963
|
taskTitle: options.taskTitle,
|
|
@@ -70402,52 +70981,138 @@ async function reviewStep(cwd, taskId, stepNumber, stepName, reviewType, promptC
|
|
|
70402
70981
|
throw new ReviewerPauseAbortError(reason);
|
|
70403
70982
|
}
|
|
70404
70983
|
}
|
|
70405
|
-
})
|
|
70984
|
+
});
|
|
70985
|
+
const reviewerModelDesc = describeModel(session2);
|
|
70986
|
+
const reviewerModelMarker = `Reviewer using model: ${reviewerModelDesc}`;
|
|
70987
|
+
reviewerLog.log(`${taskId}: reviewer using model ${reviewerModelDesc}`);
|
|
70988
|
+
if (options.store && options.taskId) {
|
|
70989
|
+
await options.store.logEntry(options.taskId, reviewerModelMarker);
|
|
70990
|
+
await options.store.appendAgentLog(options.taskId, reviewerModelMarker, "text", void 0, "reviewer").catch(() => void 0);
|
|
70991
|
+
}
|
|
70992
|
+
activeSessions.add(session2);
|
|
70993
|
+
options.onSessionCreated?.(session2);
|
|
70994
|
+
session2.subscribe((event) => {
|
|
70995
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
70996
|
+
reviewText += event.assistantMessageEvent.delta;
|
|
70997
|
+
}
|
|
70998
|
+
});
|
|
70999
|
+
return session2;
|
|
71000
|
+
};
|
|
71001
|
+
const runReviewPrompt = async (session2, prompt) => {
|
|
71002
|
+
await promptWithFallback(session2, prompt);
|
|
71003
|
+
checkSessionError(session2);
|
|
71004
|
+
};
|
|
71005
|
+
let session;
|
|
71006
|
+
try {
|
|
71007
|
+
session = await createReviewerSession();
|
|
70406
71008
|
} catch (err) {
|
|
70407
71009
|
if (err instanceof ReviewerPauseAbortError) {
|
|
70408
|
-
|
|
70409
|
-
`${taskId}: ${reviewType} review for Step ${stepNumber} aborted before spawn \u2014 ${err.reason} active`
|
|
70410
|
-
);
|
|
70411
|
-
if (options.store && options.taskId) {
|
|
70412
|
-
await options.store.logEntry(
|
|
70413
|
-
options.taskId,
|
|
70414
|
-
`${reviewType} review aborted before spawn \u2014 ${err.reason} active`
|
|
70415
|
-
).catch(() => void 0);
|
|
70416
|
-
}
|
|
70417
|
-
return {
|
|
70418
|
-
verdict: "UNAVAILABLE",
|
|
70419
|
-
review: `${err.reason} active \u2014 reviewer not spawned. Stop calling fn_review_* and exit cleanly; the parent task will resume after unpause.`,
|
|
70420
|
-
summary: `Skipped: ${err.reason}`
|
|
70421
|
-
};
|
|
71010
|
+
return buildPauseUnavailableResult(err.reason);
|
|
70422
71011
|
}
|
|
70423
71012
|
throw err;
|
|
70424
71013
|
}
|
|
70425
|
-
const reviewerModelDesc = describeModel(session);
|
|
70426
|
-
const reviewerModelMarker = `Reviewer using model: ${reviewerModelDesc}`;
|
|
70427
|
-
reviewerLog.log(`${taskId}: reviewer using model ${reviewerModelDesc}`);
|
|
70428
|
-
if (options.store && options.taskId) {
|
|
70429
|
-
await options.store.logEntry(options.taskId, reviewerModelMarker);
|
|
70430
|
-
await options.store.appendAgentLog(options.taskId, reviewerModelMarker, "text", void 0, "reviewer").catch(() => void 0);
|
|
70431
|
-
}
|
|
70432
|
-
options.onSessionCreated?.(session);
|
|
70433
|
-
let reviewText = "";
|
|
70434
|
-
session.subscribe((event) => {
|
|
70435
|
-
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
70436
|
-
reviewText += event.assistantMessageEvent.delta;
|
|
70437
|
-
}
|
|
70438
|
-
});
|
|
70439
71014
|
try {
|
|
70440
|
-
|
|
70441
|
-
|
|
71015
|
+
try {
|
|
71016
|
+
await runReviewPrompt(session, request3);
|
|
71017
|
+
} catch (err) {
|
|
71018
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
71019
|
+
if (!isContextLimitError(errorMessage)) {
|
|
71020
|
+
throw err;
|
|
71021
|
+
}
|
|
71022
|
+
const retryLogMessage = reviewType === "code" ? "code review hit context limit \u2014 retrying with compacted request" : `${reviewType} review hit context limit \u2014 retrying with compacted request`;
|
|
71023
|
+
reviewerLog.warn(`${taskId}: ${retryLogMessage}`);
|
|
71024
|
+
if (options.store && options.taskId) {
|
|
71025
|
+
await options.store.logEntry(options.taskId, retryLogMessage).catch(() => void 0);
|
|
71026
|
+
}
|
|
71027
|
+
reviewText = "";
|
|
71028
|
+
const reducedRequest = buildReducedReviewRequest(
|
|
71029
|
+
taskId,
|
|
71030
|
+
stepNumber,
|
|
71031
|
+
stepName,
|
|
71032
|
+
reviewType,
|
|
71033
|
+
promptContent,
|
|
71034
|
+
cwd,
|
|
71035
|
+
baseline
|
|
71036
|
+
);
|
|
71037
|
+
try {
|
|
71038
|
+
await runReviewPrompt(session, reducedRequest);
|
|
71039
|
+
} catch (retryErr) {
|
|
71040
|
+
if (!isReviewerSessionReuseError(retryErr)) {
|
|
71041
|
+
throw retryErr;
|
|
71042
|
+
}
|
|
71043
|
+
endSession(session);
|
|
71044
|
+
try {
|
|
71045
|
+
session = await createReviewerSession();
|
|
71046
|
+
} catch (recreateErr) {
|
|
71047
|
+
if (recreateErr instanceof ReviewerPauseAbortError) {
|
|
71048
|
+
return buildPauseUnavailableResult(recreateErr.reason);
|
|
71049
|
+
}
|
|
71050
|
+
throw recreateErr;
|
|
71051
|
+
}
|
|
71052
|
+
await runReviewPrompt(session, reducedRequest);
|
|
71053
|
+
}
|
|
71054
|
+
}
|
|
70442
71055
|
} finally {
|
|
70443
|
-
if (agentLogger)
|
|
70444
|
-
|
|
70445
|
-
|
|
71056
|
+
if (agentLogger) {
|
|
71057
|
+
await agentLogger.flush();
|
|
71058
|
+
}
|
|
71059
|
+
for (const activeSession of [...activeSessions]) {
|
|
71060
|
+
endSession(activeSession);
|
|
71061
|
+
}
|
|
70446
71062
|
}
|
|
70447
71063
|
const verdict = extractVerdict(reviewText);
|
|
70448
71064
|
const summary = extractSummary(reviewText);
|
|
70449
71065
|
return { verdict, review: reviewText, summary };
|
|
70450
71066
|
}
|
|
71067
|
+
function isReviewerSessionReuseError(error) {
|
|
71068
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71069
|
+
return /prompt is in progress|session (?:is )?(?:closed|disposed|ended)|conversation already active/i.test(message);
|
|
71070
|
+
}
|
|
71071
|
+
function extractPromptSection(promptContent, sectionName) {
|
|
71072
|
+
const heading = `## ${sectionName}`;
|
|
71073
|
+
const start = promptContent.indexOf(heading);
|
|
71074
|
+
if (start === -1) {
|
|
71075
|
+
return "";
|
|
71076
|
+
}
|
|
71077
|
+
const afterHeading = start + heading.length;
|
|
71078
|
+
const nextH2 = promptContent.indexOf("\n## ", afterHeading);
|
|
71079
|
+
const nextH1 = promptContent.indexOf("\n# ", afterHeading);
|
|
71080
|
+
const endCandidates = [nextH2, nextH1].filter((value) => value !== -1);
|
|
71081
|
+
const end = endCandidates.length > 0 ? Math.min(...endCandidates) : promptContent.length;
|
|
71082
|
+
return promptContent.slice(start, end).trim();
|
|
71083
|
+
}
|
|
71084
|
+
function summarizePromptSteps(promptContent) {
|
|
71085
|
+
const stepTitles = Array.from(promptContent.matchAll(/^### Step \d+:.*$/gm), (match) => match[0].trim());
|
|
71086
|
+
if (stepTitles.length === 0) {
|
|
71087
|
+
return "";
|
|
71088
|
+
}
|
|
71089
|
+
return ["## Steps", ...stepTitles].join("\n");
|
|
71090
|
+
}
|
|
71091
|
+
function buildReducedTaskPromptSummary(promptContent) {
|
|
71092
|
+
const firstSectionIndex = promptContent.indexOf("\n## ");
|
|
71093
|
+
const header = (firstSectionIndex === -1 ? promptContent : promptContent.slice(0, firstSectionIndex)).trim();
|
|
71094
|
+
const sections = [
|
|
71095
|
+
header,
|
|
71096
|
+
extractPromptSection(promptContent, "Mission"),
|
|
71097
|
+
extractPromptSection(promptContent, "Dependencies"),
|
|
71098
|
+
extractPromptSection(promptContent, "File Scope"),
|
|
71099
|
+
summarizePromptSteps(promptContent),
|
|
71100
|
+
"_... additional PROMPT.md sections omitted after context-limit retry ..._"
|
|
71101
|
+
].filter(Boolean);
|
|
71102
|
+
return sections.join("\n\n").trim();
|
|
71103
|
+
}
|
|
71104
|
+
function buildReducedReviewRequest(taskId, stepNumber, stepName, reviewType, promptContent, cwd, baseline) {
|
|
71105
|
+
return buildReviewRequest(
|
|
71106
|
+
taskId,
|
|
71107
|
+
stepNumber,
|
|
71108
|
+
stepName,
|
|
71109
|
+
reviewType,
|
|
71110
|
+
buildReducedTaskPromptSummary(promptContent),
|
|
71111
|
+
cwd,
|
|
71112
|
+
baseline,
|
|
71113
|
+
void 0
|
|
71114
|
+
);
|
|
71115
|
+
}
|
|
70451
71116
|
function buildReviewRequest(taskId, stepNumber, stepName, reviewType, promptContent, cwd, baseline, userComments) {
|
|
70452
71117
|
const parts = [
|
|
70453
71118
|
`Review request for task ${taskId}, Step ${stepNumber}: ${stepName}`,
|
|
@@ -70572,6 +71237,7 @@ var init_reviewer = __esm({
|
|
|
70572
71237
|
"use strict";
|
|
70573
71238
|
init_src();
|
|
70574
71239
|
init_pi();
|
|
71240
|
+
init_context_limit_detector();
|
|
70575
71241
|
init_agent_session_helpers();
|
|
70576
71242
|
init_session_skill_context();
|
|
70577
71243
|
init_agent_logger();
|
|
@@ -72548,11 +73214,17 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
|
|
|
72548
73214
|
const taskGetParams = Type2.Object({
|
|
72549
73215
|
id: Type2.String({ description: "Task ID (e.g. KB-001)" })
|
|
72550
73216
|
});
|
|
73217
|
+
const taskCreatePriorityValues = ["low", "normal", "high", "urgent"];
|
|
72551
73218
|
const taskCreateParams3 = Type2.Object({
|
|
72552
73219
|
title: Type2.Optional(Type2.String({ description: "Short child task title" })),
|
|
72553
73220
|
description: Type2.String({ description: "Child task description/mission" }),
|
|
72554
73221
|
dependencies: Type2.Optional(
|
|
72555
73222
|
Type2.Array(Type2.String({ description: "Task ID dependency (e.g. KB-001)" }))
|
|
73223
|
+
),
|
|
73224
|
+
priority: Type2.Optional(
|
|
73225
|
+
Type2.Union(taskCreatePriorityValues.map((priority) => Type2.Literal(priority)), {
|
|
73226
|
+
description: "Task priority (low, normal, high, urgent)"
|
|
73227
|
+
})
|
|
72556
73228
|
)
|
|
72557
73229
|
});
|
|
72558
73230
|
const taskList = {
|
|
@@ -72674,6 +73346,7 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
72674
73346
|
description: params.description,
|
|
72675
73347
|
dependencies: validDeps,
|
|
72676
73348
|
column: "triage",
|
|
73349
|
+
priority: params.priority,
|
|
72677
73350
|
// Inherit parent's model settings if available
|
|
72678
73351
|
modelProvider: parentTask?.modelProvider,
|
|
72679
73352
|
modelId: parentTask?.modelId,
|
|
@@ -73489,11 +74162,146 @@ var init_run_audit = __esm({
|
|
|
73489
74162
|
}
|
|
73490
74163
|
});
|
|
73491
74164
|
|
|
73492
|
-
// ../engine/src/merger.ts
|
|
73493
|
-
import {
|
|
74165
|
+
// ../engine/src/merger-squash-audit.ts
|
|
74166
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
73494
74167
|
import { promisify as promisify4 } from "node:util";
|
|
74168
|
+
async function auditSquashMerge({
|
|
74169
|
+
rootDir,
|
|
74170
|
+
squashSha,
|
|
74171
|
+
lookback = DEFAULT_LOOKBACK
|
|
74172
|
+
}) {
|
|
74173
|
+
const normalizedLookback = normalizeLookback(lookback);
|
|
74174
|
+
const parentSha = await git(rootDir, ["rev-parse", `${squashSha}^`]);
|
|
74175
|
+
const squashSubject = await git(rootDir, ["log", "-1", "--format=%s", squashSha]);
|
|
74176
|
+
const branchSubjects = normalizeLines(await git(rootDir, ["log", "-1", "--format=%b", squashSha])).map((line) => line.replace(/^- /, "").trim()).filter(Boolean);
|
|
74177
|
+
const recentMainCommits = await listRecentMainCommits(rootDir, parentSha, normalizedLookback);
|
|
74178
|
+
const recentMainSubjects = recentMainCommits.map((entry) => entry.subject);
|
|
74179
|
+
const duplicateSubjects = branchSubjects.filter((subject) => recentMainSubjects.includes(subject)).map((subject) => ({ type: "duplicate-subject", subject }));
|
|
74180
|
+
const touchedFiles = normalizeLines(await git(rootDir, ["diff", "--name-only", parentSha, squashSha]));
|
|
74181
|
+
const touchedFileOverlaps = [];
|
|
74182
|
+
for (const file of touchedFiles) {
|
|
74183
|
+
const overlappingCommits = [];
|
|
74184
|
+
for (const commit of recentMainCommits) {
|
|
74185
|
+
const touchedInCommit = await git(rootDir, ["diff-tree", "--no-commit-id", "--name-only", "-r", commit.sha, "--", file]);
|
|
74186
|
+
if (normalizeLines(touchedInCommit).includes(file)) {
|
|
74187
|
+
overlappingCommits.push({ sha: commit.shortSha, subject: commit.subject });
|
|
74188
|
+
}
|
|
74189
|
+
}
|
|
74190
|
+
if (overlappingCommits.length > 0) {
|
|
74191
|
+
touchedFileOverlaps.push({
|
|
74192
|
+
type: "touched-file-overlap",
|
|
74193
|
+
file,
|
|
74194
|
+
recentMainCommits: overlappingCommits
|
|
74195
|
+
});
|
|
74196
|
+
}
|
|
74197
|
+
}
|
|
74198
|
+
const findings = [...duplicateSubjects, ...touchedFileOverlaps];
|
|
74199
|
+
return {
|
|
74200
|
+
squashSha,
|
|
74201
|
+
parentSha,
|
|
74202
|
+
squashSubject,
|
|
74203
|
+
lookback: normalizedLookback,
|
|
74204
|
+
branchSubjects,
|
|
74205
|
+
recentMainSubjects,
|
|
74206
|
+
duplicateSubjects,
|
|
74207
|
+
touchedFiles,
|
|
74208
|
+
touchedFileOverlaps,
|
|
74209
|
+
findings,
|
|
74210
|
+
issueCount: findings.length,
|
|
74211
|
+
clean: findings.length === 0
|
|
74212
|
+
};
|
|
74213
|
+
}
|
|
74214
|
+
function formatSquashAuditReport(findings) {
|
|
74215
|
+
const lines = [
|
|
74216
|
+
`Auditing squash: ${findings.squashSha} \u2014 ${findings.squashSubject}`,
|
|
74217
|
+
`Parent (main before squash): ${findings.parentSha}`,
|
|
74218
|
+
`Lookback window on main: ${findings.lookback} commits`,
|
|
74219
|
+
"",
|
|
74220
|
+
"=== Duplicate-cherry-pick risk ==="
|
|
74221
|
+
];
|
|
74222
|
+
if (findings.duplicateSubjects.length === 0) {
|
|
74223
|
+
lines.push("(none \u2014 no branch commit subjects match recent main commits)", "");
|
|
74224
|
+
} else {
|
|
74225
|
+
lines.push(
|
|
74226
|
+
"WARN: branch contains commits whose subjects match recent main commits.",
|
|
74227
|
+
"Auto-resolve may have picked the older side, dropping refinements.",
|
|
74228
|
+
"Action: diff each main commit below against HEAD and confirm its",
|
|
74229
|
+
"net contribution survived. Restore anything dropped as a follow-up.",
|
|
74230
|
+
"",
|
|
74231
|
+
...findings.duplicateSubjects.map((entry) => ` - ${entry.subject}`),
|
|
74232
|
+
""
|
|
74233
|
+
);
|
|
74234
|
+
}
|
|
74235
|
+
lines.push(`=== Touched-file overlap (${findings.touchedFiles.length} files in squash) ===`);
|
|
74236
|
+
if (findings.touchedFileOverlaps.length === 0) {
|
|
74237
|
+
lines.push("(none \u2014 squash touches files no recent main commit touched)", "");
|
|
74238
|
+
} else {
|
|
74239
|
+
lines.push(
|
|
74240
|
+
"Files the squash touched that also have recent main activity.",
|
|
74241
|
+
"Action: for each commit below, verify its changes still appear",
|
|
74242
|
+
"in HEAD. Reapply any silently dropped changes on the same branch.",
|
|
74243
|
+
""
|
|
74244
|
+
);
|
|
74245
|
+
for (const overlap of findings.touchedFileOverlaps) {
|
|
74246
|
+
lines.push(` ${overlap.file}`);
|
|
74247
|
+
for (const commit of overlap.recentMainCommits) {
|
|
74248
|
+
lines.push(` - ${commit.sha} ${commit.subject}`);
|
|
74249
|
+
}
|
|
74250
|
+
}
|
|
74251
|
+
lines.push("");
|
|
74252
|
+
}
|
|
74253
|
+
lines.push(`Audit complete. ${findings.issueCount} item(s) for the calling agent to review.`);
|
|
74254
|
+
return lines.join("\n");
|
|
74255
|
+
}
|
|
74256
|
+
async function git(rootDir, args) {
|
|
74257
|
+
const { stdout } = await execFileAsync("git", args, {
|
|
74258
|
+
cwd: rootDir,
|
|
74259
|
+
encoding: "utf-8",
|
|
74260
|
+
maxBuffer: GIT_OUTPUT_MAX_BUFFER
|
|
74261
|
+
});
|
|
74262
|
+
return stdout.trim();
|
|
74263
|
+
}
|
|
74264
|
+
function normalizeLines(value) {
|
|
74265
|
+
const trimmed = value.trim();
|
|
74266
|
+
if (!trimmed) return [];
|
|
74267
|
+
return trimmed.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
74268
|
+
}
|
|
74269
|
+
async function listRecentMainCommits(rootDir, parentSha, lookback) {
|
|
74270
|
+
const entries = normalizeLines(await git(rootDir, ["log", `--format=%H~%h~%s`, `-n`, String(lookback), parentSha]));
|
|
74271
|
+
return entries.map((entry) => {
|
|
74272
|
+
const [sha, shortSha, ...subjectParts] = entry.split("~");
|
|
74273
|
+
const subject = subjectParts.join("~").trim();
|
|
74274
|
+
if (!sha?.trim() || !shortSha?.trim() || !subject) {
|
|
74275
|
+
return null;
|
|
74276
|
+
}
|
|
74277
|
+
return {
|
|
74278
|
+
sha: sha.trim(),
|
|
74279
|
+
shortSha: shortSha.trim(),
|
|
74280
|
+
subject
|
|
74281
|
+
};
|
|
74282
|
+
}).filter((entry) => entry !== null);
|
|
74283
|
+
}
|
|
74284
|
+
function normalizeLookback(value) {
|
|
74285
|
+
if (!Number.isFinite(value) || !value || value < 1) {
|
|
74286
|
+
return DEFAULT_LOOKBACK;
|
|
74287
|
+
}
|
|
74288
|
+
return Math.trunc(value);
|
|
74289
|
+
}
|
|
74290
|
+
var execFileAsync, DEFAULT_LOOKBACK, GIT_OUTPUT_MAX_BUFFER;
|
|
74291
|
+
var init_merger_squash_audit = __esm({
|
|
74292
|
+
"../engine/src/merger-squash-audit.ts"() {
|
|
74293
|
+
"use strict";
|
|
74294
|
+
execFileAsync = promisify4(execFile3);
|
|
74295
|
+
DEFAULT_LOOKBACK = 30;
|
|
74296
|
+
GIT_OUTPUT_MAX_BUFFER = 10 * 1024 * 1024;
|
|
74297
|
+
}
|
|
74298
|
+
});
|
|
74299
|
+
|
|
74300
|
+
// ../engine/src/merger.ts
|
|
74301
|
+
import { execSync, exec as exec3, execFile as execFile4 } from "node:child_process";
|
|
74302
|
+
import { promisify as promisify5 } from "node:util";
|
|
73495
74303
|
import { existsSync as existsSync25, readFileSync as readFileSync12, writeFileSync as writeFileSync2, unlinkSync, renameSync as renameSync2 } from "node:fs";
|
|
73496
|
-
import { createHash as
|
|
74304
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
73497
74305
|
import { join as join32 } from "node:path";
|
|
73498
74306
|
import { hostname } from "node:os";
|
|
73499
74307
|
import { Type as Type3 } from "typebox";
|
|
@@ -73562,7 +74370,7 @@ function computeLockfileHash(rootDir) {
|
|
|
73562
74370
|
const p = join32(rootDir, name);
|
|
73563
74371
|
if (existsSync25(p)) {
|
|
73564
74372
|
try {
|
|
73565
|
-
return
|
|
74373
|
+
return createHash7("sha256").update(readFileSync12(p)).digest("hex");
|
|
73566
74374
|
} catch {
|
|
73567
74375
|
return null;
|
|
73568
74376
|
}
|
|
@@ -73620,8 +74428,8 @@ function commitOwnedByTask(taskId, subject, body) {
|
|
|
73620
74428
|
async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
73621
74429
|
const tryHydrate = async (sha) => {
|
|
73622
74430
|
try {
|
|
73623
|
-
await
|
|
73624
|
-
const { stdout } = await
|
|
74431
|
+
await execFileAsync2("git", ["merge-base", "--is-ancestor", sha, "HEAD"], { cwd: rootDir });
|
|
74432
|
+
const { stdout } = await execFileAsync2("git", ["log", "-1", "--format=%H%x1f%s%x1f%b", sha], {
|
|
73625
74433
|
cwd: rootDir,
|
|
73626
74434
|
encoding: "utf-8"
|
|
73627
74435
|
});
|
|
@@ -73629,7 +74437,7 @@ async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
|
73629
74437
|
if (!resolvedSha || !commitOwnedByTask(task.id, subject, body)) return null;
|
|
73630
74438
|
const owned = { sha: resolvedSha, subject };
|
|
73631
74439
|
try {
|
|
73632
|
-
const { stdout: statsOut } = await
|
|
74440
|
+
const { stdout: statsOut } = await execFileAsync2("git", ["show", "--shortstat", "--format=", resolvedSha], {
|
|
73633
74441
|
cwd: rootDir,
|
|
73634
74442
|
encoding: "utf-8"
|
|
73635
74443
|
});
|
|
@@ -73652,7 +74460,7 @@ async function findOwnedLandedCommitForTask(rootDir, task) {
|
|
|
73652
74460
|
];
|
|
73653
74461
|
for (const args of searches) {
|
|
73654
74462
|
try {
|
|
73655
|
-
const { stdout } = await
|
|
74463
|
+
const { stdout } = await execFileAsync2("git", args, { cwd: rootDir, encoding: "utf-8" });
|
|
73656
74464
|
const first = stdout.trim().split("\n").find(Boolean);
|
|
73657
74465
|
if (!first) continue;
|
|
73658
74466
|
const [sha] = first.split("");
|
|
@@ -73715,15 +74523,15 @@ async function snapshotDirtyFiles(rootDir) {
|
|
|
73715
74523
|
const paths = /* @__PURE__ */ new Set();
|
|
73716
74524
|
try {
|
|
73717
74525
|
const [unstagedOut, stagedOut, porcelainOut] = await Promise.all([
|
|
73718
|
-
|
|
74526
|
+
execFileAsync2("git", ["diff", "-z", "--name-only"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
73719
74527
|
(r) => r.stdout,
|
|
73720
74528
|
() => ""
|
|
73721
74529
|
),
|
|
73722
|
-
|
|
74530
|
+
execFileAsync2("git", ["diff", "-z", "--cached", "--name-only"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
73723
74531
|
(r) => r.stdout,
|
|
73724
74532
|
() => ""
|
|
73725
74533
|
),
|
|
73726
|
-
|
|
74534
|
+
execFileAsync2("git", ["status", "-z", "--porcelain"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
73727
74535
|
(r) => r.stdout,
|
|
73728
74536
|
() => ""
|
|
73729
74537
|
)
|
|
@@ -73748,18 +74556,18 @@ async function snapshotDirtyFiles(rootDir) {
|
|
|
73748
74556
|
async function gitDirtyFingerprint(rootDir) {
|
|
73749
74557
|
try {
|
|
73750
74558
|
const [diffOut, statusOut] = await Promise.all([
|
|
73751
|
-
|
|
74559
|
+
execFileAsync2("git", ["diff", "HEAD"], {
|
|
73752
74560
|
cwd: rootDir,
|
|
73753
74561
|
encoding: "utf-8",
|
|
73754
74562
|
maxBuffer: 64 * 1024 * 1024
|
|
73755
74563
|
}).then((r) => r.stdout, () => ""),
|
|
73756
|
-
|
|
74564
|
+
execFileAsync2("git", ["status", "-z", "--porcelain"], { cwd: rootDir, encoding: "utf-8" }).then(
|
|
73757
74565
|
(r) => r.stdout,
|
|
73758
74566
|
() => ""
|
|
73759
74567
|
)
|
|
73760
74568
|
]);
|
|
73761
74569
|
if (!diffOut && !statusOut) return "";
|
|
73762
|
-
return
|
|
74570
|
+
return createHash7("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
|
|
73763
74571
|
} catch {
|
|
73764
74572
|
return "";
|
|
73765
74573
|
}
|
|
@@ -75438,7 +76246,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
75438
76246
|
encoding: "utf-8"
|
|
75439
76247
|
});
|
|
75440
76248
|
const unstaged = new Set(unstagedOut.split("\n").map((l) => l.trim()).filter(Boolean));
|
|
75441
|
-
const { stdout: porcelainOut } = await
|
|
76249
|
+
const { stdout: porcelainOut } = await execFileAsync2("git", ["status", "-z", "--porcelain"], {
|
|
75442
76250
|
cwd: rootDir,
|
|
75443
76251
|
encoding: "utf-8"
|
|
75444
76252
|
});
|
|
@@ -75459,7 +76267,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
75459
76267
|
}
|
|
75460
76268
|
}
|
|
75461
76269
|
if (unstagedToStage.length > 0) {
|
|
75462
|
-
await
|
|
76270
|
+
await execFileAsync2("git", ["add", "--", ...unstagedToStage], { cwd: rootDir });
|
|
75463
76271
|
}
|
|
75464
76272
|
const untrackedToStage = [];
|
|
75465
76273
|
for (const p of untracked) {
|
|
@@ -75472,7 +76280,7 @@ async function commitOrAmendMergeWithFixes(rootDir, taskId, branch, commitLog, i
|
|
|
75472
76280
|
}
|
|
75473
76281
|
}
|
|
75474
76282
|
if (untrackedToStage.length > 0) {
|
|
75475
|
-
await
|
|
76283
|
+
await execFileAsync2("git", ["add", "--", ...untrackedToStage], { cwd: rootDir });
|
|
75476
76284
|
}
|
|
75477
76285
|
const cap = (arr, n = 20) => arr.length <= n ? arr.join(", ") : `${arr.slice(0, n).join(", ")} ... (+${arr.length - n} more)`;
|
|
75478
76286
|
mergerLog.log(
|
|
@@ -75975,8 +76783,8 @@ async function classifyConflict(filePath, cwd) {
|
|
|
75975
76783
|
}
|
|
75976
76784
|
async function resolveWithOurs(filePath, cwd) {
|
|
75977
76785
|
try {
|
|
75978
|
-
await
|
|
75979
|
-
await
|
|
76786
|
+
await execFileAsync2("git", ["checkout", "--ours", "--", filePath], { cwd });
|
|
76787
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
75980
76788
|
mergerLog.log(`Auto-resolved ${filePath} using --ours`);
|
|
75981
76789
|
} catch (error) {
|
|
75982
76790
|
throw new Error(`Failed to auto-resolve ${filePath} with ours: ${error}`);
|
|
@@ -75984,8 +76792,8 @@ async function resolveWithOurs(filePath, cwd) {
|
|
|
75984
76792
|
}
|
|
75985
76793
|
async function resolveWithTheirs(filePath, cwd) {
|
|
75986
76794
|
try {
|
|
75987
|
-
await
|
|
75988
|
-
await
|
|
76795
|
+
await execFileAsync2("git", ["checkout", "--theirs", "--", filePath], { cwd });
|
|
76796
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
75989
76797
|
mergerLog.log(`Auto-resolved ${filePath} using --theirs`);
|
|
75990
76798
|
} catch (error) {
|
|
75991
76799
|
throw new Error(`Failed to auto-resolve ${filePath} with theirs: ${error}`);
|
|
@@ -75993,7 +76801,7 @@ async function resolveWithTheirs(filePath, cwd) {
|
|
|
75993
76801
|
}
|
|
75994
76802
|
async function resolveTrivialWhitespace(filePath, cwd) {
|
|
75995
76803
|
try {
|
|
75996
|
-
await
|
|
76804
|
+
await execFileAsync2("git", ["add", "--", filePath], { cwd });
|
|
75997
76805
|
mergerLog.log(`Auto-resolved ${filePath} (trivial whitespace)`);
|
|
75998
76806
|
} catch (error) {
|
|
75999
76807
|
throw new Error(`Failed to auto-resolve ${filePath} trivial conflict: ${error}`);
|
|
@@ -76187,6 +76995,43 @@ async function findWorktreeUser(store, worktreePath, excludeTaskId) {
|
|
|
76187
76995
|
function quoteArg(value) {
|
|
76188
76996
|
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
76189
76997
|
}
|
|
76998
|
+
function shouldRunPostSquashAudit(result, mergeWasEmpty, isEmptyCommit, commitSha) {
|
|
76999
|
+
if (mergeWasEmpty || isEmptyCommit || !commitSha) {
|
|
77000
|
+
return false;
|
|
77001
|
+
}
|
|
77002
|
+
return (result.autoResolvedCount ?? 0) > 0 || result.attemptsMade === 3;
|
|
77003
|
+
}
|
|
77004
|
+
function buildSquashAuditBlockingMessage(taskId, squashSha, findings) {
|
|
77005
|
+
const riskParts = [];
|
|
77006
|
+
if (findings.duplicateSubjects.length > 0) {
|
|
77007
|
+
riskParts.push(`${findings.duplicateSubjects.length} duplicate-subject risk${findings.duplicateSubjects.length === 1 ? "" : "s"}`);
|
|
77008
|
+
}
|
|
77009
|
+
if (findings.touchedFileOverlaps.length > 0) {
|
|
77010
|
+
riskParts.push(`${findings.touchedFileOverlaps.length} touched-file overlap risk${findings.touchedFileOverlaps.length === 1 ? "" : "s"}`);
|
|
77011
|
+
}
|
|
77012
|
+
const summary = riskParts.length > 0 ? riskParts.join(", ") : `${findings.issueCount} audit finding(s)`;
|
|
77013
|
+
return `${taskId}: post-squash audit blocked auto-completion for ${squashSha.slice(0, 8)} (${summary})`;
|
|
77014
|
+
}
|
|
77015
|
+
function formatSquashAuditAgentLog(findings) {
|
|
77016
|
+
const lines = [];
|
|
77017
|
+
if (findings.duplicateSubjects.length > 0) {
|
|
77018
|
+
lines.push("Duplicate-subject risks:");
|
|
77019
|
+
for (const duplicate of findings.duplicateSubjects) {
|
|
77020
|
+
lines.push(`- ${duplicate.subject}`);
|
|
77021
|
+
}
|
|
77022
|
+
}
|
|
77023
|
+
if (findings.touchedFileOverlaps.length > 0) {
|
|
77024
|
+
if (lines.length > 0) lines.push("");
|
|
77025
|
+
lines.push("Touched-file overlap risks:");
|
|
77026
|
+
for (const overlap of findings.touchedFileOverlaps) {
|
|
77027
|
+
lines.push(`- ${overlap.file}`);
|
|
77028
|
+
for (const commit of overlap.recentMainCommits) {
|
|
77029
|
+
lines.push(` - ${commit.sha} ${commit.subject}`);
|
|
77030
|
+
}
|
|
77031
|
+
}
|
|
77032
|
+
}
|
|
77033
|
+
return lines.join("\n");
|
|
77034
|
+
}
|
|
76190
77035
|
async function resolveSafeCommitBody(opts) {
|
|
76191
77036
|
const cleanLog = opts.commitLog.trim();
|
|
76192
77037
|
if (cleanLog.length > 0) return cleanLog;
|
|
@@ -77518,6 +78363,26 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
77518
78363
|
}
|
|
77519
78364
|
const isEmptyCommit = filesChanged === 0;
|
|
77520
78365
|
const recordedSha = isEmptyCommit || mergeWasEmpty ? void 0 : commitSha;
|
|
78366
|
+
const auditSha = recordedSha;
|
|
78367
|
+
if (auditSha && shouldRunPostSquashAudit(result, mergeWasEmpty, isEmptyCommit, auditSha)) {
|
|
78368
|
+
const auditFindings = await auditSquashMerge({
|
|
78369
|
+
rootDir,
|
|
78370
|
+
squashSha: auditSha
|
|
78371
|
+
});
|
|
78372
|
+
if (!auditFindings.clean) {
|
|
78373
|
+
const auditError = new SquashAuditError(taskId, auditSha, auditFindings);
|
|
78374
|
+
await store.appendAgentLog(
|
|
78375
|
+
taskId,
|
|
78376
|
+
auditError.message,
|
|
78377
|
+
"tool_error",
|
|
78378
|
+
formatSquashAuditAgentLog(auditFindings),
|
|
78379
|
+
"merger"
|
|
78380
|
+
);
|
|
78381
|
+
await store.updateTask(taskId, { status: null });
|
|
78382
|
+
throw auditError;
|
|
78383
|
+
}
|
|
78384
|
+
await store.appendAgentLog(taskId, "post-squash audit clean", "text", void 0, "merger");
|
|
78385
|
+
}
|
|
77521
78386
|
if (isEmptyCommit) {
|
|
77522
78387
|
mergerLog.warn(
|
|
77523
78388
|
`${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.`
|
|
@@ -77582,6 +78447,9 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
|
|
|
77582
78447
|
"merger"
|
|
77583
78448
|
);
|
|
77584
78449
|
} catch (err) {
|
|
78450
|
+
if (err instanceof SquashAuditError || err?.name === "SquashAuditError") {
|
|
78451
|
+
throw err;
|
|
78452
|
+
}
|
|
77585
78453
|
mergerLog.warn(`${taskId}: failed to collect/store merge details: ${err.message}`);
|
|
77586
78454
|
}
|
|
77587
78455
|
try {
|
|
@@ -78817,7 +79685,7 @@ async function completeTask(store, taskId, result) {
|
|
|
78817
79685
|
result.task = task;
|
|
78818
79686
|
store.emit("task:merged", result);
|
|
78819
79687
|
}
|
|
78820
|
-
var execAsync2,
|
|
79688
|
+
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;
|
|
78821
79689
|
var init_merger = __esm({
|
|
78822
79690
|
"../engine/src/merger.ts"() {
|
|
78823
79691
|
"use strict";
|
|
@@ -78837,8 +79705,9 @@ var init_merger = __esm({
|
|
|
78837
79705
|
init_agent_instructions();
|
|
78838
79706
|
init_run_audit();
|
|
78839
79707
|
init_agent_tools();
|
|
78840
|
-
|
|
78841
|
-
|
|
79708
|
+
init_merger_squash_audit();
|
|
79709
|
+
execAsync2 = promisify5(exec3);
|
|
79710
|
+
execFileAsync2 = promisify5(execFile4);
|
|
78842
79711
|
LOCKFILE_PATTERNS = [
|
|
78843
79712
|
"package-lock.json",
|
|
78844
79713
|
"pnpm-lock.yaml",
|
|
@@ -78895,6 +79764,14 @@ var init_merger = __esm({
|
|
|
78895
79764
|
this.name = "MergeAbortedError";
|
|
78896
79765
|
}
|
|
78897
79766
|
};
|
|
79767
|
+
SquashAuditError = class extends Error {
|
|
79768
|
+
constructor(taskId, squashSha, findings) {
|
|
79769
|
+
super(buildSquashAuditBlockingMessage(taskId, squashSha, findings));
|
|
79770
|
+
this.squashSha = squashSha;
|
|
79771
|
+
this.findings = findings;
|
|
79772
|
+
this.name = "SquashAuditError";
|
|
79773
|
+
}
|
|
79774
|
+
};
|
|
78898
79775
|
VERIFICATION_EXTRA_ENV = Object.fromEntries(
|
|
78899
79776
|
[
|
|
78900
79777
|
["FUSION_TEST_TOTAL_WORKERS", "8"],
|
|
@@ -79096,7 +79973,7 @@ __export(worktree_pool_exports, {
|
|
|
79096
79973
|
scanOrphanedBranches: () => scanOrphanedBranches
|
|
79097
79974
|
});
|
|
79098
79975
|
import { exec as exec4 } from "node:child_process";
|
|
79099
|
-
import { promisify as
|
|
79976
|
+
import { promisify as promisify6 } from "node:util";
|
|
79100
79977
|
import { existsSync as existsSync27, lstatSync, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
79101
79978
|
import { join as join34, relative as relative8, resolve as resolve18, isAbsolute as isAbsolute10 } from "node:path";
|
|
79102
79979
|
function getExecStdout(result) {
|
|
@@ -79307,7 +80184,7 @@ var init_worktree_pool = __esm({
|
|
|
79307
80184
|
"../engine/src/worktree-pool.ts"() {
|
|
79308
80185
|
"use strict";
|
|
79309
80186
|
init_logger2();
|
|
79310
|
-
execAsync3 =
|
|
80187
|
+
execAsync3 = promisify6(exec4);
|
|
79311
80188
|
WorktreePool = class {
|
|
79312
80189
|
idle = /* @__PURE__ */ new Set();
|
|
79313
80190
|
/**
|
|
@@ -79499,7 +80376,7 @@ var init_token_cap_detector = __esm({
|
|
|
79499
80376
|
|
|
79500
80377
|
// ../engine/src/step-session-executor.ts
|
|
79501
80378
|
import { exec as exec5 } from "node:child_process";
|
|
79502
|
-
import { promisify as
|
|
80379
|
+
import { promisify as promisify7 } from "node:util";
|
|
79503
80380
|
import { existsSync as existsSync28 } from "node:fs";
|
|
79504
80381
|
import { join as join35 } from "node:path";
|
|
79505
80382
|
function parseStepFileScopes(prompt) {
|
|
@@ -79762,7 +80639,7 @@ var init_step_session_executor = __esm({
|
|
|
79762
80639
|
init_context_limit_detector();
|
|
79763
80640
|
init_usage_limit_detector();
|
|
79764
80641
|
init_agent_tools();
|
|
79765
|
-
execAsync4 =
|
|
80642
|
+
execAsync4 = promisify7(exec5);
|
|
79766
80643
|
stepExecLog = createLogger2("step-session-executor");
|
|
79767
80644
|
MAX_STEP_RETRIES = 3;
|
|
79768
80645
|
RETRY_DELAYS_MS = [1e3, 5e3, 15e3];
|
|
@@ -80416,7 +81293,9 @@ async function hydrateWorktreeDb({
|
|
|
80416
81293
|
ensureWorktreeSchema(worktreePath);
|
|
80417
81294
|
}
|
|
80418
81295
|
srcDb = new DatabaseSync(srcDbPath);
|
|
81296
|
+
srcDb.exec("PRAGMA busy_timeout = 5000");
|
|
80419
81297
|
dstDb = openWorktreeDbWithRecovery(dstDbPath, worktreePath);
|
|
81298
|
+
dstDb.exec("PRAGMA busy_timeout = 5000");
|
|
80420
81299
|
dstDb.exec("PRAGMA journal_mode = WAL");
|
|
80421
81300
|
const srcTaskCols = getColumns(srcDb, "tasks");
|
|
80422
81301
|
const dstTaskCols = getColumns(dstDb, "tasks");
|
|
@@ -80865,12 +81744,120 @@ var init_run_verification_tool = __esm({
|
|
|
80865
81744
|
|
|
80866
81745
|
// ../engine/src/executor.ts
|
|
80867
81746
|
import { exec as exec6 } from "node:child_process";
|
|
80868
|
-
import { promisify as
|
|
81747
|
+
import { promisify as promisify8 } from "node:util";
|
|
80869
81748
|
import { delimiter, isAbsolute as isAbsolute12, join as join39, relative as relative9, resolve as resolvePath } from "node:path";
|
|
80870
81749
|
import { existsSync as existsSync31 } from "node:fs";
|
|
80871
81750
|
import { readFile as readFile17, writeFile as writeFile13 } from "node:fs/promises";
|
|
80872
81751
|
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
80873
81752
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
81753
|
+
function normalizeWorkflowScopePath(pathValue) {
|
|
81754
|
+
return pathValue.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+/g, "/").replace(/\/$/, "");
|
|
81755
|
+
}
|
|
81756
|
+
function stripTrailingPathPunctuation(pathValue) {
|
|
81757
|
+
return pathValue.replace(/[),.:;!?]+$/g, "");
|
|
81758
|
+
}
|
|
81759
|
+
function extractReferencedPathsFromWorkflowFeedback(feedback) {
|
|
81760
|
+
const extracted = [];
|
|
81761
|
+
const seen = /* @__PURE__ */ new Set();
|
|
81762
|
+
for (const match of feedback.matchAll(WORKFLOW_FEEDBACK_PATH_REGEX)) {
|
|
81763
|
+
const candidate = stripTrailingPathPunctuation(match[1] ?? match[2] ?? "");
|
|
81764
|
+
const normalized = normalizeWorkflowScopePath(candidate);
|
|
81765
|
+
if (!normalized.includes("/") || !normalized) continue;
|
|
81766
|
+
if (seen.has(normalized)) continue;
|
|
81767
|
+
seen.add(normalized);
|
|
81768
|
+
extracted.push(normalized);
|
|
81769
|
+
}
|
|
81770
|
+
return extracted;
|
|
81771
|
+
}
|
|
81772
|
+
function workflowPathMatchesDeclaredScope(filePath, scopePatterns) {
|
|
81773
|
+
const normalizedPath = normalizeWorkflowScopePath(filePath);
|
|
81774
|
+
for (const rawPattern of scopePatterns) {
|
|
81775
|
+
const pattern = normalizeWorkflowScopePath(rawPattern);
|
|
81776
|
+
if (!pattern) continue;
|
|
81777
|
+
if (/\/\*+$/.test(pattern)) {
|
|
81778
|
+
const directory = pattern.replace(/\/\*+$/, "");
|
|
81779
|
+
if (normalizedPath === directory || normalizedPath.startsWith(`${directory}/`)) return true;
|
|
81780
|
+
continue;
|
|
81781
|
+
}
|
|
81782
|
+
if (pattern.endsWith("/")) {
|
|
81783
|
+
if (normalizedPath.startsWith(pattern)) return true;
|
|
81784
|
+
continue;
|
|
81785
|
+
}
|
|
81786
|
+
if (normalizedPath === pattern) return true;
|
|
81787
|
+
}
|
|
81788
|
+
return false;
|
|
81789
|
+
}
|
|
81790
|
+
function partitionWorkflowRevisionFeedback(feedback, declaredFileScope) {
|
|
81791
|
+
const trimmedFeedback = feedback.trim();
|
|
81792
|
+
if (!trimmedFeedback || declaredFileScope.length === 0) {
|
|
81793
|
+
return {
|
|
81794
|
+
inScopeFeedback: trimmedFeedback,
|
|
81795
|
+
outOfScopeFeedback: "",
|
|
81796
|
+
inScopeSegments: trimmedFeedback ? [trimmedFeedback] : [],
|
|
81797
|
+
outOfScopeSegments: [],
|
|
81798
|
+
detectedPaths: extractReferencedPathsFromWorkflowFeedback(trimmedFeedback)
|
|
81799
|
+
};
|
|
81800
|
+
}
|
|
81801
|
+
const segments = trimmedFeedback.split(/\n\s*\n/).map((segment) => segment.trim()).filter(Boolean);
|
|
81802
|
+
const allDetectedPaths = extractReferencedPathsFromWorkflowFeedback(trimmedFeedback);
|
|
81803
|
+
if (allDetectedPaths.length === 0) {
|
|
81804
|
+
return {
|
|
81805
|
+
inScopeFeedback: trimmedFeedback,
|
|
81806
|
+
outOfScopeFeedback: "",
|
|
81807
|
+
inScopeSegments: trimmedFeedback ? [trimmedFeedback] : [],
|
|
81808
|
+
outOfScopeSegments: [],
|
|
81809
|
+
detectedPaths: []
|
|
81810
|
+
};
|
|
81811
|
+
}
|
|
81812
|
+
const inScopeSegments = [];
|
|
81813
|
+
const outOfScopeSegments = [];
|
|
81814
|
+
for (const segment of segments) {
|
|
81815
|
+
const segmentPaths = extractReferencedPathsFromWorkflowFeedback(segment);
|
|
81816
|
+
if (segmentPaths.length === 0) {
|
|
81817
|
+
inScopeSegments.push(segment);
|
|
81818
|
+
continue;
|
|
81819
|
+
}
|
|
81820
|
+
const hasOutOfScopePath = segmentPaths.some((path6) => !workflowPathMatchesDeclaredScope(path6, declaredFileScope));
|
|
81821
|
+
if (hasOutOfScopePath) {
|
|
81822
|
+
outOfScopeSegments.push(segment);
|
|
81823
|
+
} else {
|
|
81824
|
+
inScopeSegments.push(segment);
|
|
81825
|
+
}
|
|
81826
|
+
}
|
|
81827
|
+
return {
|
|
81828
|
+
inScopeFeedback: inScopeSegments.join("\n\n"),
|
|
81829
|
+
outOfScopeFeedback: outOfScopeSegments.join("\n\n"),
|
|
81830
|
+
inScopeSegments,
|
|
81831
|
+
outOfScopeSegments,
|
|
81832
|
+
detectedPaths: allDetectedPaths
|
|
81833
|
+
};
|
|
81834
|
+
}
|
|
81835
|
+
function normalizeWorktreePath(pathValue) {
|
|
81836
|
+
return resolvePath(pathValue).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
81837
|
+
}
|
|
81838
|
+
async function extractPersistedSessionWorktreePath(sessionFile) {
|
|
81839
|
+
try {
|
|
81840
|
+
const content = await readFile17(sessionFile, "utf-8");
|
|
81841
|
+
const matches = content.match(SESSION_WORKTREE_PATH_REGEX) ?? [];
|
|
81842
|
+
if (matches.length === 0) return null;
|
|
81843
|
+
const normalizedCounts = /* @__PURE__ */ new Map();
|
|
81844
|
+
for (const match of matches) {
|
|
81845
|
+
const normalized = normalizeWorktreePath(match);
|
|
81846
|
+
normalizedCounts.set(normalized, (normalizedCounts.get(normalized) ?? 0) + 1);
|
|
81847
|
+
}
|
|
81848
|
+
let best = null;
|
|
81849
|
+
for (const [path6, count] of normalizedCounts.entries()) {
|
|
81850
|
+
if (!best || count > best.count) best = { path: path6, count };
|
|
81851
|
+
}
|
|
81852
|
+
return best?.path ?? null;
|
|
81853
|
+
} catch {
|
|
81854
|
+
return null;
|
|
81855
|
+
}
|
|
81856
|
+
}
|
|
81857
|
+
function isSessionWorktreeCompatible(persistedWorktreePath, currentWorktreePath) {
|
|
81858
|
+
if (!persistedWorktreePath) return true;
|
|
81859
|
+
return persistedWorktreePath === normalizeWorktreePath(currentWorktreePath);
|
|
81860
|
+
}
|
|
80874
81861
|
function truncateWorkflowScriptOutput2(output) {
|
|
80875
81862
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
|
|
80876
81863
|
return `... output truncated to last ${WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2} characters ...
|
|
@@ -81150,7 +82137,7 @@ function detectReviewHandoffIntent(commentText) {
|
|
|
81150
82137
|
];
|
|
81151
82138
|
return handoffPhrases.some((phrase) => text.includes(phrase));
|
|
81152
82139
|
}
|
|
81153
|
-
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;
|
|
82140
|
+
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;
|
|
81154
82141
|
var init_executor = __esm({
|
|
81155
82142
|
"../engine/src/executor.ts"() {
|
|
81156
82143
|
"use strict";
|
|
@@ -81187,7 +82174,7 @@ var init_executor = __esm({
|
|
|
81187
82174
|
init_fallback_model_observer();
|
|
81188
82175
|
init_agent_logger();
|
|
81189
82176
|
init_agent_tools();
|
|
81190
|
-
execAsync5 =
|
|
82177
|
+
execAsync5 = promisify8(exec6);
|
|
81191
82178
|
STEP_STATUSES = ["pending", "in-progress", "done", "skipped"];
|
|
81192
82179
|
MAX_WORKFLOW_STEP_RETRIES = 3;
|
|
81193
82180
|
MAX_TASK_DONE_SESSION_RETRIES = 3;
|
|
@@ -81195,8 +82182,10 @@ var init_executor = __esm({
|
|
|
81195
82182
|
COMPLETED_TASK_WATCHDOG_MS = 6e4;
|
|
81196
82183
|
WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
|
|
81197
82184
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
82185
|
+
WORKFLOW_FEEDBACK_PATH_REGEX = /`([^`\n]+)`|(?<![A-Za-z0-9_.-])((?:\.\.?\/)?(?:@?[A-Za-z0-9._-]+\/)+[A-Za-z0-9._-]+(?:\.[A-Za-z0-9._-]+)?)/g;
|
|
81198
82186
|
NonRetryableWorktreeError = class extends Error {
|
|
81199
82187
|
};
|
|
82188
|
+
SESSION_WORKTREE_PATH_REGEX = /([A-Za-z]:)?[^"'\s]*\.worktrees[\\/][^"'\s]+/g;
|
|
81200
82189
|
taskUpdateParams = Type5.Object({
|
|
81201
82190
|
step: Type5.Number({ description: "Step number (1-indexed)" }),
|
|
81202
82191
|
status: Type5.Union(
|
|
@@ -83270,15 +84259,18 @@ ${summary}`,
|
|
|
83270
84259
|
}
|
|
83271
84260
|
if (!workflowResult.allPassed) {
|
|
83272
84261
|
if (workflowResult.revisionRequested) {
|
|
83273
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
83274
|
-
|
|
83275
|
-
|
|
83276
|
-
|
|
83277
|
-
|
|
84262
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
84263
|
+
if (rerunScheduled) {
|
|
84264
|
+
return;
|
|
84265
|
+
}
|
|
84266
|
+
} else {
|
|
84267
|
+
const retried = await this.handleWorkflowStepFailure(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown");
|
|
84268
|
+
if (retried) {
|
|
84269
|
+
return;
|
|
84270
|
+
}
|
|
84271
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
83278
84272
|
return;
|
|
83279
84273
|
}
|
|
83280
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
83281
|
-
return;
|
|
83282
84274
|
}
|
|
83283
84275
|
} else {
|
|
83284
84276
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -83534,7 +84526,23 @@ ${summary}`,
|
|
|
83534
84526
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
83535
84527
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
83536
84528
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
83537
|
-
|
|
84529
|
+
let isResuming = !!task.sessionFile && existsSync31(task.sessionFile);
|
|
84530
|
+
if (isResuming) {
|
|
84531
|
+
const persistedWorktreePath = await extractPersistedSessionWorktreePath(task.sessionFile);
|
|
84532
|
+
if (!isSessionWorktreeCompatible(persistedWorktreePath, worktreePath)) {
|
|
84533
|
+
executorLog.warn(
|
|
84534
|
+
`${task.id}: stale sessionFile worktree mismatch (session=${persistedWorktreePath}, task=${worktreePath}); starting fresh session`
|
|
84535
|
+
);
|
|
84536
|
+
await this.store.logEntry(
|
|
84537
|
+
task.id,
|
|
84538
|
+
`Detected stale persisted session metadata (worktree mismatch: ${persistedWorktreePath} vs ${worktreePath}) \u2014 discarded resume state and started fresh session`,
|
|
84539
|
+
void 0,
|
|
84540
|
+
this.currentRunContext
|
|
84541
|
+
);
|
|
84542
|
+
await this.store.updateTask(task.id, { sessionFile: null });
|
|
84543
|
+
isResuming = false;
|
|
84544
|
+
}
|
|
84545
|
+
}
|
|
83538
84546
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
83539
84547
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
83540
84548
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -83764,15 +84772,18 @@ ${summary}`,
|
|
|
83764
84772
|
}
|
|
83765
84773
|
if (!workflowResult.allPassed) {
|
|
83766
84774
|
if (workflowResult.revisionRequested) {
|
|
83767
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
83768
|
-
|
|
83769
|
-
|
|
83770
|
-
|
|
83771
|
-
|
|
84775
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
84776
|
+
if (rerunScheduled) {
|
|
84777
|
+
return;
|
|
84778
|
+
}
|
|
84779
|
+
} else {
|
|
84780
|
+
const retried = await this.handleWorkflowStepFailure(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown");
|
|
84781
|
+
if (retried) {
|
|
84782
|
+
return;
|
|
84783
|
+
}
|
|
84784
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
83772
84785
|
return;
|
|
83773
84786
|
}
|
|
83774
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed");
|
|
83775
|
-
return;
|
|
83776
84787
|
}
|
|
83777
84788
|
} else {
|
|
83778
84789
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -83932,11 +84943,14 @@ ${summary}`,
|
|
|
83932
84943
|
}
|
|
83933
84944
|
if (!workflowResult.allPassed) {
|
|
83934
84945
|
if (workflowResult.revisionRequested) {
|
|
83935
|
-
await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName);
|
|
84946
|
+
const rerunScheduled = await this.handleWorkflowRevisionRequest(task, worktreePath, workflowResult.feedback, workflowResult.stepName, settings);
|
|
84947
|
+
if (rerunScheduled) {
|
|
84948
|
+
return;
|
|
84949
|
+
}
|
|
84950
|
+
} else {
|
|
84951
|
+
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed on retry");
|
|
83936
84952
|
return;
|
|
83937
84953
|
}
|
|
83938
|
-
await this.sendTaskBackForFix(task, worktreePath, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed on retry");
|
|
83939
|
-
return;
|
|
83940
84954
|
}
|
|
83941
84955
|
} else {
|
|
83942
84956
|
executorLog.log(`${task.id}: fast mode \u2014 skipping pre-merge workflow steps`);
|
|
@@ -84736,18 +85750,38 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
84736
85750
|
* the injected feedback from PROMPT.md and applies an in-place fix rather
|
|
84737
85751
|
* than redoing any completed step.
|
|
84738
85752
|
*/
|
|
84739
|
-
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName) {
|
|
85753
|
+
async handleWorkflowRevisionRequest(task, worktreePath, feedback, stepName, settings) {
|
|
84740
85754
|
executorLog.log(`${task.id}: workflow revision requested by step "${stepName}"`);
|
|
84741
85755
|
this.clearCompletedTaskWatchdog(task.id);
|
|
85756
|
+
const shouldForkOnScopeMismatch = settings.workflowRevisionForkOnScopeMismatch !== false;
|
|
85757
|
+
let inScopeFeedback = feedback.trim();
|
|
85758
|
+
let outOfScopeFeedback = "";
|
|
85759
|
+
let followUpTaskId;
|
|
85760
|
+
if (shouldForkOnScopeMismatch) {
|
|
85761
|
+
const declaredFileScope = await this.store.parseFileScopeFromPrompt(task.id).catch(() => []);
|
|
85762
|
+
const partition = partitionWorkflowRevisionFeedback(feedback, declaredFileScope);
|
|
85763
|
+
inScopeFeedback = partition.inScopeFeedback;
|
|
85764
|
+
outOfScopeFeedback = partition.outOfScopeFeedback;
|
|
85765
|
+
if (outOfScopeFeedback) {
|
|
85766
|
+
const followUpTask = await this.createWorkflowRevisionFollowUpTask(task, stepName, outOfScopeFeedback);
|
|
85767
|
+
followUpTaskId = followUpTask.id;
|
|
85768
|
+
}
|
|
85769
|
+
}
|
|
85770
|
+
if (!inScopeFeedback) {
|
|
85771
|
+
await this.store.logEntry(
|
|
85772
|
+
task.id,
|
|
85773
|
+
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`,
|
|
85774
|
+
outOfScopeFeedback || feedback,
|
|
85775
|
+
this.currentRunContext
|
|
85776
|
+
);
|
|
85777
|
+
return false;
|
|
85778
|
+
}
|
|
84742
85779
|
const updatedTask = await this.store.getTask(task.id);
|
|
84743
85780
|
const reopen = await this.reopenLastStepForRevision(task.id, updatedTask);
|
|
84744
85781
|
const reopenSummary = reopen ? `re-opening Step ${reopen.index + 1} ("${reopen.name}") for in-place fix` : "no step to re-open (none were completed)";
|
|
84745
|
-
|
|
84746
|
-
|
|
84747
|
-
|
|
84748
|
-
feedback
|
|
84749
|
-
);
|
|
84750
|
-
await this.injectWorkflowRevisionInstructions(task, feedback);
|
|
85782
|
+
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}`;
|
|
85783
|
+
await this.store.logEntry(task.id, logMessage, inScopeFeedback, this.currentRunContext);
|
|
85784
|
+
await this.injectWorkflowRevisionInstructions(task, inScopeFeedback);
|
|
84751
85785
|
await this.store.updateTask(task.id, {
|
|
84752
85786
|
status: null,
|
|
84753
85787
|
sessionFile: null
|
|
@@ -84758,6 +85792,35 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
84758
85792
|
worktreePath,
|
|
84759
85793
|
`${task.id}: revision rerun scheduled \u2014 moved to todo then in-progress`
|
|
84760
85794
|
);
|
|
85795
|
+
return true;
|
|
85796
|
+
}
|
|
85797
|
+
async createWorkflowRevisionFollowUpTask(task, stepName, feedback) {
|
|
85798
|
+
const title = `${task.id}: workflow follow-up from ${stepName}`;
|
|
85799
|
+
const description = [
|
|
85800
|
+
`Follow-up work forked from workflow revision feedback on ${task.id}.`,
|
|
85801
|
+
"",
|
|
85802
|
+
`Original task: ${task.id}${task.title ? ` \u2014 ${task.title}` : ""}`,
|
|
85803
|
+
`Workflow step: ${stepName}`,
|
|
85804
|
+
"",
|
|
85805
|
+
"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.",
|
|
85806
|
+
"",
|
|
85807
|
+
"## Out-of-Scope Workflow Revision Feedback",
|
|
85808
|
+
"",
|
|
85809
|
+
feedback
|
|
85810
|
+
].join("\n");
|
|
85811
|
+
return this.store.createTask({
|
|
85812
|
+
title,
|
|
85813
|
+
description,
|
|
85814
|
+
dependencies: [task.id],
|
|
85815
|
+
source: {
|
|
85816
|
+
sourceType: "workflow_step",
|
|
85817
|
+
sourceParentTaskId: task.id,
|
|
85818
|
+
sourceMetadata: {
|
|
85819
|
+
workflowStepName: stepName,
|
|
85820
|
+
routing: "scope-mismatch-fork"
|
|
85821
|
+
}
|
|
85822
|
+
}
|
|
85823
|
+
});
|
|
84761
85824
|
}
|
|
84762
85825
|
/**
|
|
84763
85826
|
* Re-open the last non-pending step so a revision/failure handler gives the
|
|
@@ -90054,7 +91117,7 @@ Follow this process:
|
|
|
90054
91117
|
|
|
90055
91118
|
// ../engine/src/agent-heartbeat.ts
|
|
90056
91119
|
import { Type as Type6 } from "@mariozechner/pi-ai";
|
|
90057
|
-
import { createHash as
|
|
91120
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
90058
91121
|
function formatDuration(ms) {
|
|
90059
91122
|
const totalMinutes = Math.floor(ms / 6e4);
|
|
90060
91123
|
if (totalMinutes < 1) return "<1m";
|
|
@@ -90098,7 +91161,7 @@ function truncatePrompt(text, maxChars) {
|
|
|
90098
91161
|
... (truncated, ${text.length} chars)`;
|
|
90099
91162
|
}
|
|
90100
91163
|
function shortContentHash(value) {
|
|
90101
|
-
return
|
|
91164
|
+
return createHash8("sha256").update(value).digest("hex").slice(0, 8);
|
|
90102
91165
|
}
|
|
90103
91166
|
function buildIdentitySnapshot(args) {
|
|
90104
91167
|
const { agent, resolvedInstructions, workspaceMemory } = args;
|
|
@@ -91950,13 +93013,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
|
|
|
91950
93013
|
});
|
|
91951
93014
|
}
|
|
91952
93015
|
async buildReportsHealthSection(agentId, agentStore) {
|
|
91953
|
-
const
|
|
91954
|
-
if (typeof
|
|
93016
|
+
const storeWithReports = agentStore;
|
|
93017
|
+
if (typeof storeWithReports.getAgentsByReportsTo !== "function") {
|
|
91955
93018
|
return null;
|
|
91956
93019
|
}
|
|
91957
93020
|
let reports;
|
|
91958
93021
|
try {
|
|
91959
|
-
reports = await
|
|
93022
|
+
reports = await storeWithReports.getAgentsByReportsTo(agentId);
|
|
91960
93023
|
} catch (err) {
|
|
91961
93024
|
heartbeatLog.warn(`Failed to load reports for ${agentId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
91962
93025
|
return null;
|
|
@@ -94351,7 +95414,7 @@ var init_cron_runner = __esm({
|
|
|
94351
95414
|
|
|
94352
95415
|
// ../engine/src/routine-runner.ts
|
|
94353
95416
|
import { exec as exec8 } from "node:child_process";
|
|
94354
|
-
import { promisify as
|
|
95417
|
+
import { promisify as promisify9 } from "node:util";
|
|
94355
95418
|
function truncateOutput3(stdout, stderr) {
|
|
94356
95419
|
let output = stdout;
|
|
94357
95420
|
if (stderr) {
|
|
@@ -94373,7 +95436,7 @@ var init_routine_runner = __esm({
|
|
|
94373
95436
|
init_logger2();
|
|
94374
95437
|
init_shell_utils();
|
|
94375
95438
|
log16 = createLogger2("routine-runner");
|
|
94376
|
-
execAsync6 =
|
|
95439
|
+
execAsync6 = promisify9(exec8);
|
|
94377
95440
|
DEFAULT_TIMEOUT_MS8 = 5 * 60 * 1e3;
|
|
94378
95441
|
MAX_BUFFER2 = 1024 * 1024;
|
|
94379
95442
|
MAX_OUTPUT_LENGTH2 = 10 * 1024;
|
|
@@ -95310,17 +96373,25 @@ function isMissingWorktreeSessionStartFailure(error) {
|
|
|
95310
96373
|
if (typeof error !== "string") {
|
|
95311
96374
|
return false;
|
|
95312
96375
|
}
|
|
95313
|
-
return error.includes(
|
|
96376
|
+
return error.includes(MISSING_WORKTREE_SESSION_PREFIX);
|
|
96377
|
+
}
|
|
96378
|
+
function extractMissingWorktreePathFromSessionStartFailure(error) {
|
|
96379
|
+
if (typeof error !== "string") return null;
|
|
96380
|
+
const idx = error.indexOf(MISSING_WORKTREE_SESSION_PREFIX);
|
|
96381
|
+
if (idx < 0) return null;
|
|
96382
|
+
const pathPart = error.slice(idx + MISSING_WORKTREE_SESSION_PREFIX.length).trim();
|
|
96383
|
+
return pathPart.length > 0 ? pathPart : null;
|
|
95314
96384
|
}
|
|
95315
96385
|
function isRecoverableMissingWorktreeReviewFailure(task) {
|
|
95316
96386
|
return task.column === "in-review" && !task.paused && task.status === "failed" && isMissingWorktreeSessionStartFailure(task.error) && hasStepProgress(task);
|
|
95317
96387
|
}
|
|
95318
|
-
var log17, RestartRecoveryCoordinator;
|
|
96388
|
+
var log17, MISSING_WORKTREE_SESSION_PREFIX, RestartRecoveryCoordinator;
|
|
95319
96389
|
var init_restart_recovery_coordinator = __esm({
|
|
95320
96390
|
"../engine/src/restart-recovery-coordinator.ts"() {
|
|
95321
96391
|
"use strict";
|
|
95322
96392
|
init_logger2();
|
|
95323
96393
|
log17 = createLogger2("restart-recovery");
|
|
96394
|
+
MISSING_WORKTREE_SESSION_PREFIX = "Refusing to start coding agent in missing worktree:";
|
|
95324
96395
|
RestartRecoveryCoordinator = class {
|
|
95325
96396
|
constructor(store, executor) {
|
|
95326
96397
|
this.store = store;
|
|
@@ -95364,8 +96435,8 @@ var init_restart_recovery_coordinator = __esm({
|
|
|
95364
96435
|
|
|
95365
96436
|
// ../engine/src/self-healing.ts
|
|
95366
96437
|
import { exec as exec9, execSync as execSync2 } from "node:child_process";
|
|
95367
|
-
import { promisify as
|
|
95368
|
-
import { existsSync as existsSync33, readdirSync as readdirSync5, rmSync as rmSync3, statSync as
|
|
96438
|
+
import { promisify as promisify10 } from "node:util";
|
|
96439
|
+
import { existsSync as existsSync33, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync6 } from "node:fs";
|
|
95369
96440
|
import { isAbsolute as isAbsolute14, join as join41, relative as relative10, resolve as resolve20 } from "node:path";
|
|
95370
96441
|
function commitOwnedByTask2(taskId, lineageId, subject, body) {
|
|
95371
96442
|
if (lineageId && body.includes(`Fusion-Task-Lineage: ${lineageId}`)) {
|
|
@@ -95458,7 +96529,7 @@ function isNoTaskDoneFailure2(task) {
|
|
|
95458
96529
|
function hasStepProgress2(task) {
|
|
95459
96530
|
return task.steps.some((step) => step.status !== "pending");
|
|
95460
96531
|
}
|
|
95461
|
-
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;
|
|
96532
|
+
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;
|
|
95462
96533
|
var init_self_healing = __esm({
|
|
95463
96534
|
"../engine/src/self-healing.ts"() {
|
|
95464
96535
|
"use strict";
|
|
@@ -95468,7 +96539,7 @@ var init_self_healing = __esm({
|
|
|
95468
96539
|
init_restart_recovery_coordinator();
|
|
95469
96540
|
init_transient_error_detector();
|
|
95470
96541
|
log18 = createLogger2("self-healing");
|
|
95471
|
-
execAsync7 =
|
|
96542
|
+
execAsync7 = promisify10(exec9);
|
|
95472
96543
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
95473
96544
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
95474
96545
|
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
|
|
@@ -95484,6 +96555,7 @@ var init_self_healing = __esm({
|
|
|
95484
96555
|
ORPHANED_WITH_WORKTREE_GRACE_MS = 3e5;
|
|
95485
96556
|
MAX_TASK_DONE_RETRIES = 3;
|
|
95486
96557
|
MAX_AUTO_MERGE_RETRIES = 3;
|
|
96558
|
+
MAX_STARVATION_DROPS = 3;
|
|
95487
96559
|
DEADLOCK_RECOVERY_COOLDOWN_MS = 15 * 6e4;
|
|
95488
96560
|
DEFAULT_STALE_MERGING_STATUS_MIN_AGE_MS = 5 * 6e4;
|
|
95489
96561
|
DURABLE_ERROR_RECOVERY_MAX_RETRIES = 5;
|
|
@@ -95506,6 +96578,7 @@ var init_self_healing = __esm({
|
|
|
95506
96578
|
settingsListener = null;
|
|
95507
96579
|
// ── Per-task deadlock recovery cooldown ─────────────────────────────
|
|
95508
96580
|
deadlockRecoveryCooldown = /* @__PURE__ */ new Map();
|
|
96581
|
+
mergeStarvationDrops = /* @__PURE__ */ new Map();
|
|
95509
96582
|
// ── Lifecycle ───────────────────────────────────────────────────────
|
|
95510
96583
|
start() {
|
|
95511
96584
|
this.settingsListener = ({ settings, previous }) => {
|
|
@@ -96229,6 +97302,7 @@ var init_self_healing = __esm({
|
|
|
96229
97302
|
try {
|
|
96230
97303
|
log18.warn(`Clearing stale merge status for ${task.id}: ${previousStatus}`);
|
|
96231
97304
|
await this.store.updateTask(task.id, { status: null });
|
|
97305
|
+
this.options.clearMergeActive?.(task.id);
|
|
96232
97306
|
await this.store.logEntry(
|
|
96233
97307
|
task.id,
|
|
96234
97308
|
`Auto-recovered: cleared stale '${previousStatus}' status (no active merger)`
|
|
@@ -96299,6 +97373,8 @@ var init_self_healing = __esm({
|
|
|
96299
97373
|
reason = `blocker ${blockerId} in-review + paused`;
|
|
96300
97374
|
} else if (blocker.column === "in-review" && blocker.status === "failed" && (blocker.mergeRetries ?? 0) >= MAX_AUTO_MERGE_RETRIES) {
|
|
96301
97375
|
reason = `blocker ${blockerId} in-review + failed (mergeRetries ${blocker.mergeRetries ?? 0}/${MAX_AUTO_MERGE_RETRIES})`;
|
|
97376
|
+
} else if (blocker.column === "in-review" && blocker.status === "failed" && isMissingWorktreeSessionStartFailure(blocker.error)) {
|
|
97377
|
+
reason = `blocker ${blockerId} in-review + failed (missing-worktree session start)`;
|
|
96302
97378
|
} else if (task.dependencies.length > 0 && !unresolvedDeps.includes(blockerId)) {
|
|
96303
97379
|
reason = `blocker ${blockerId} not among unresolved dependencies`;
|
|
96304
97380
|
}
|
|
@@ -96408,7 +97484,7 @@ var init_self_healing = __esm({
|
|
|
96408
97484
|
if (settings.globalPause || settings.enginePaused) return 0;
|
|
96409
97485
|
const tasks = await this.store.listTasks({ column: "in-review", slim: true });
|
|
96410
97486
|
const mergeable = tasks.filter(
|
|
96411
|
-
(t) => t.column === "in-review" && !t.paused && // Exclude transient merge statuses. Active merges should be left alone;
|
|
97487
|
+
(t) => t.column === "in-review" && !t.paused && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
|
|
96412
97488
|
// stale ones are handled by recoverStaleMergingStatus().
|
|
96413
97489
|
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
|
|
96414
97490
|
// exhausted, re-enqueueing here is a no-op and each recovery log write
|
|
@@ -96417,6 +97493,13 @@ var init_self_healing = __esm({
|
|
|
96417
97493
|
// in case updateTask(moveTask) is briefly out-of-order during recovery.
|
|
96418
97494
|
(t.mergeRetries ?? 0) < MAX_AUTO_MERGE_RETRIES && getTaskMergeBlocker(t) === void 0
|
|
96419
97495
|
);
|
|
97496
|
+
const inReviewIds = new Set(tasks.map((task) => task.id));
|
|
97497
|
+
const mergeableIds = new Set(mergeable.map((task) => task.id));
|
|
97498
|
+
for (const taskId of [...this.mergeStarvationDrops.keys()]) {
|
|
97499
|
+
if (!inReviewIds.has(taskId) || !mergeableIds.has(taskId)) {
|
|
97500
|
+
this.mergeStarvationDrops.delete(taskId);
|
|
97501
|
+
}
|
|
97502
|
+
}
|
|
96420
97503
|
if (mergeable.length === 0) return 0;
|
|
96421
97504
|
log18.warn(`Found ${mergeable.length} mergeable review task(s) stuck in in-review`);
|
|
96422
97505
|
const enqueueMerge = this.options.enqueueMerge;
|
|
@@ -96424,7 +97507,23 @@ var init_self_healing = __esm({
|
|
|
96424
97507
|
for (const task of mergeable) {
|
|
96425
97508
|
try {
|
|
96426
97509
|
if (enqueueMerge) {
|
|
96427
|
-
enqueueMerge(task.id);
|
|
97510
|
+
const queued = enqueueMerge(task.id);
|
|
97511
|
+
if (!queued) {
|
|
97512
|
+
const drops = (this.mergeStarvationDrops.get(task.id) ?? 0) + 1;
|
|
97513
|
+
this.mergeStarvationDrops.set(task.id, drops);
|
|
97514
|
+
log18.warn(
|
|
97515
|
+
`Auto-recovery enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); engine merge queue rejected re-enqueue`
|
|
97516
|
+
);
|
|
97517
|
+
if (drops >= MAX_STARVATION_DROPS) {
|
|
97518
|
+
const error = `Auto-merge starvation: ${MAX_STARVATION_DROPS} consecutive enqueue attempts were dropped by the engine merge queue; task requires manual intervention.`;
|
|
97519
|
+
await this.store.updateTask(task.id, { status: "failed", error });
|
|
97520
|
+
await this.store.logEntry(task.id, error);
|
|
97521
|
+
this.mergeStarvationDrops.delete(task.id);
|
|
97522
|
+
recovered++;
|
|
97523
|
+
}
|
|
97524
|
+
continue;
|
|
97525
|
+
}
|
|
97526
|
+
this.mergeStarvationDrops.delete(task.id);
|
|
96428
97527
|
} else {
|
|
96429
97528
|
await this.store.mergeTask(task.id);
|
|
96430
97529
|
}
|
|
@@ -97474,16 +98573,18 @@ var init_self_healing = __esm({
|
|
|
97474
98573
|
for (const task of candidates) {
|
|
97475
98574
|
try {
|
|
97476
98575
|
const staleWorktree = task.worktree;
|
|
98576
|
+
const missingWorktreePath = extractMissingWorktreePathFromSessionStartFailure(task.error);
|
|
98577
|
+
const hasMismatchedLiveWorktree = typeof staleWorktree === "string" && staleWorktree.length > 0 && typeof missingWorktreePath === "string" && missingWorktreePath.length > 0 && resolve20(staleWorktree) !== resolve20(missingWorktreePath);
|
|
97477
98578
|
await this.store.updateTask(task.id, {
|
|
97478
98579
|
status: null,
|
|
97479
98580
|
error: null,
|
|
97480
|
-
worktree: null,
|
|
97481
|
-
branch: null,
|
|
98581
|
+
worktree: hasMismatchedLiveWorktree ? staleWorktree : null,
|
|
98582
|
+
branch: hasMismatchedLiveWorktree ? task.branch ?? null : null,
|
|
97482
98583
|
sessionFile: null
|
|
97483
98584
|
});
|
|
97484
98585
|
await this.store.logEntry(
|
|
97485
98586
|
task.id,
|
|
97486
|
-
`Auto-recovered: retry/verification session targeted missing worktree${staleWorktree ? ` (${staleWorktree})` : ""} \u2014 cleared stale session metadata and requeued to todo`
|
|
98587
|
+
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`
|
|
97487
98588
|
);
|
|
97488
98589
|
await this.store.moveTask(task.id, "todo", { preserveProgress: true });
|
|
97489
98590
|
recovered++;
|
|
@@ -97877,7 +98978,7 @@ var init_self_healing = __esm({
|
|
|
97877
98978
|
if (idle.length === 0) return;
|
|
97878
98979
|
const withMtime = idle.map((p) => {
|
|
97879
98980
|
try {
|
|
97880
|
-
return { path: p, mtime:
|
|
98981
|
+
return { path: p, mtime: statSync6(p).mtimeMs };
|
|
97881
98982
|
} catch (err) {
|
|
97882
98983
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
97883
98984
|
log18.warn(`Failed to read mtime for worktree ${p}: ${errorMessage} \u2014 defaulting mtime to 0`);
|
|
@@ -99092,6 +100193,7 @@ var init_in_process_runtime = __esm({
|
|
|
99092
100193
|
* before `start()` via `setMergeEnqueuer`.
|
|
99093
100194
|
*/
|
|
99094
100195
|
mergeEnqueuer;
|
|
100196
|
+
clearMergeActive;
|
|
99095
100197
|
activeMergeTaskIdProvider;
|
|
99096
100198
|
/** Tracks whether startup recovery was intentionally deferred due to pause state. */
|
|
99097
100199
|
startupRecoveryDeferred = false;
|
|
@@ -99489,7 +100591,8 @@ var init_in_process_runtime = __esm({
|
|
|
99489
100591
|
recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
|
|
99490
100592
|
getPlanningTaskIds: () => this.triageProcessor?.getProcessingTaskIds() ?? /* @__PURE__ */ new Set(),
|
|
99491
100593
|
evictStaleTriageProcessing: () => this.triageProcessor?.evictStaleProcessing() ?? /* @__PURE__ */ new Set(),
|
|
99492
|
-
enqueueMerge: this.mergeEnqueuer ? (taskId) => this.mergeEnqueuer?.(taskId) : void 0,
|
|
100594
|
+
enqueueMerge: this.mergeEnqueuer ? (taskId) => this.mergeEnqueuer?.(taskId) ?? false : void 0,
|
|
100595
|
+
clearMergeActive: this.clearMergeActive ? (taskId) => this.clearMergeActive?.(taskId) : void 0,
|
|
99493
100596
|
getActiveMergeTaskId: () => this.activeMergeTaskIdProvider?.() ?? null,
|
|
99494
100597
|
leaseManager: this.leaseManager,
|
|
99495
100598
|
hasActiveAgentExecution: (agentId) => this.heartbeatMonitor?.getTrackedAgents().includes(agentId) ?? false,
|
|
@@ -99683,6 +100786,9 @@ var init_in_process_runtime = __esm({
|
|
|
99683
100786
|
setMergeEnqueuer(enqueueMerge) {
|
|
99684
100787
|
this.mergeEnqueuer = enqueueMerge;
|
|
99685
100788
|
}
|
|
100789
|
+
setMergeActiveClearer(clearMergeActive) {
|
|
100790
|
+
this.clearMergeActive = clearMergeActive;
|
|
100791
|
+
}
|
|
99686
100792
|
setActiveMergeTaskIdProvider(getActiveMergeTaskId) {
|
|
99687
100793
|
this.activeMergeTaskIdProvider = getActiveMergeTaskId;
|
|
99688
100794
|
}
|
|
@@ -101787,8 +102893,8 @@ var init_provider_adapters = __esm({
|
|
|
101787
102893
|
|
|
101788
102894
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
101789
102895
|
import { EventEmitter as EventEmitter24 } from "node:events";
|
|
101790
|
-
import { exec as exec10, execFile as
|
|
101791
|
-
import { promisify as
|
|
102896
|
+
import { exec as exec10, execFile as execFile5, spawn as spawn5 } from "node:child_process";
|
|
102897
|
+
import { promisify as promisify11 } from "node:util";
|
|
101792
102898
|
function nowIso2() {
|
|
101793
102899
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
101794
102900
|
}
|
|
@@ -101828,7 +102934,7 @@ function toStateError(code, err) {
|
|
|
101828
102934
|
at: nowIso2()
|
|
101829
102935
|
};
|
|
101830
102936
|
}
|
|
101831
|
-
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2,
|
|
102937
|
+
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, execFileAsync3, execAsync8, LineBuffer, TunnelProcessManager;
|
|
101832
102938
|
var init_tunnel_process_manager = __esm({
|
|
101833
102939
|
"../engine/src/remote-access/tunnel-process-manager.ts"() {
|
|
101834
102940
|
"use strict";
|
|
@@ -101836,8 +102942,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
101836
102942
|
init_provider_adapters();
|
|
101837
102943
|
DEFAULT_MAX_LOG_ENTRIES = 400;
|
|
101838
102944
|
DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
|
|
101839
|
-
|
|
101840
|
-
execAsync8 =
|
|
102945
|
+
execFileAsync3 = promisify11(execFile5);
|
|
102946
|
+
execAsync8 = promisify11(exec10);
|
|
101841
102947
|
LineBuffer = class {
|
|
101842
102948
|
pending = "";
|
|
101843
102949
|
push(chunk) {
|
|
@@ -101914,7 +103020,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
101914
103020
|
return null;
|
|
101915
103021
|
}
|
|
101916
103022
|
try {
|
|
101917
|
-
const { stdout } = await
|
|
103023
|
+
const { stdout } = await execFileAsync3("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
101918
103024
|
const data = JSON.parse(String(stdout));
|
|
101919
103025
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
101920
103026
|
if (!dnsName) {
|
|
@@ -101937,7 +103043,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
101937
103043
|
];
|
|
101938
103044
|
for (const resetCommand of resetCommands) {
|
|
101939
103045
|
try {
|
|
101940
|
-
await
|
|
103046
|
+
await execFileAsync3(resetCommand.command, resetCommand.args, { timeout: 5e3 });
|
|
101941
103047
|
return;
|
|
101942
103048
|
} catch {
|
|
101943
103049
|
}
|
|
@@ -102236,8 +103342,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
102236
103342
|
});
|
|
102237
103343
|
|
|
102238
103344
|
// ../engine/src/project-engine.ts
|
|
102239
|
-
import { execFile as
|
|
102240
|
-
import { promisify as
|
|
103345
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
103346
|
+
import { promisify as promisify12 } from "node:util";
|
|
102241
103347
|
function formatErrorDetails(error) {
|
|
102242
103348
|
if (error instanceof Error) {
|
|
102243
103349
|
return {
|
|
@@ -102252,7 +103358,7 @@ function isInvalidDoneTransitionError(error) {
|
|
|
102252
103358
|
const message = error instanceof Error ? error.message : String(error);
|
|
102253
103359
|
return message.includes("Invalid transition:") && message.includes("\u2192 'done'");
|
|
102254
103360
|
}
|
|
102255
|
-
var
|
|
103361
|
+
var execFileAsync4, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
102256
103362
|
var init_project_engine = __esm({
|
|
102257
103363
|
"../engine/src/project-engine.ts"() {
|
|
102258
103364
|
"use strict";
|
|
@@ -102270,7 +103376,7 @@ var init_project_engine = __esm({
|
|
|
102270
103376
|
init_research_orchestrator();
|
|
102271
103377
|
init_research_step_runner();
|
|
102272
103378
|
init_tunnel_process_manager();
|
|
102273
|
-
|
|
103379
|
+
execFileAsync4 = promisify12(execFile6);
|
|
102274
103380
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
102275
103381
|
isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
|
102276
103382
|
ProjectEngine = class _ProjectEngine {
|
|
@@ -102289,7 +103395,10 @@ var init_project_engine = __esm({
|
|
|
102289
103395
|
this.activeMergeTaskId = null;
|
|
102290
103396
|
}
|
|
102291
103397
|
this.mergeActive.delete(taskId);
|
|
102292
|
-
this.internalEnqueueMerge(taskId);
|
|
103398
|
+
return this.internalEnqueueMerge(taskId);
|
|
103399
|
+
});
|
|
103400
|
+
this.runtime.setMergeActiveClearer?.((taskId) => {
|
|
103401
|
+
this.mergeActive.delete(taskId);
|
|
102293
103402
|
});
|
|
102294
103403
|
}
|
|
102295
103404
|
runtime;
|
|
@@ -102324,6 +103433,7 @@ var init_project_engine = __esm({
|
|
|
102324
103433
|
mergeAbortController = null;
|
|
102325
103434
|
mergeRetryTimer = null;
|
|
102326
103435
|
autostashSweepTimer = null;
|
|
103436
|
+
mergeActiveReconcileTimer = null;
|
|
102327
103437
|
/**
|
|
102328
103438
|
* Pending manual merge resolvers — keyed by taskId.
|
|
102329
103439
|
* When `onMerge` is called, the task is enqueued like auto-merge but a
|
|
@@ -102519,6 +103629,7 @@ ${detail}`
|
|
|
102519
103629
|
this.wireAutostashOrphanRecovery(store);
|
|
102520
103630
|
await this.startupMergeSweep(store);
|
|
102521
103631
|
this.scheduleMergeRetry(store);
|
|
103632
|
+
this.scheduleMergeActiveReconciliation(settings.maintenanceIntervalMs ?? 9e5);
|
|
102522
103633
|
void this.runStaleAutostashSweep(store, "startup");
|
|
102523
103634
|
this.scheduleStaleAutostashSweep(store);
|
|
102524
103635
|
this.started = true;
|
|
@@ -102544,6 +103655,10 @@ ${detail}`
|
|
|
102544
103655
|
clearTimeout(this.autostashSweepTimer);
|
|
102545
103656
|
this.autostashSweepTimer = null;
|
|
102546
103657
|
}
|
|
103658
|
+
if (this.mergeActiveReconcileTimer) {
|
|
103659
|
+
clearInterval(this.mergeActiveReconcileTimer);
|
|
103660
|
+
this.mergeActiveReconcileTimer = null;
|
|
103661
|
+
}
|
|
102547
103662
|
this.mergeAbortController?.abort();
|
|
102548
103663
|
this.mergeAbortController = null;
|
|
102549
103664
|
this.activeMergeTaskId = null;
|
|
@@ -102765,7 +103880,7 @@ ${detail}`
|
|
|
102765
103880
|
* an external `onMerge` callback (e.g. dashboard's createServer call).
|
|
102766
103881
|
*/
|
|
102767
103882
|
enqueueMerge(taskId) {
|
|
102768
|
-
this.internalEnqueueMerge(taskId);
|
|
103883
|
+
return this.internalEnqueueMerge(taskId);
|
|
102769
103884
|
}
|
|
102770
103885
|
/**
|
|
102771
103886
|
* Perform an AI-powered merge for a task, serialized through the merge queue.
|
|
@@ -102782,7 +103897,10 @@ ${detail}`
|
|
|
102782
103897
|
}
|
|
102783
103898
|
return new Promise((resolve52, reject2) => {
|
|
102784
103899
|
this.manualMergeResolvers.set(taskId, { resolve: resolve52, reject: reject2 });
|
|
102785
|
-
this.internalEnqueueMerge(taskId)
|
|
103900
|
+
if (!this.internalEnqueueMerge(taskId)) {
|
|
103901
|
+
this.manualMergeResolvers.delete(taskId);
|
|
103902
|
+
reject2(new Error(`Merge enqueue rejected for ${taskId}`));
|
|
103903
|
+
}
|
|
102786
103904
|
});
|
|
102787
103905
|
}
|
|
102788
103906
|
setRestoreDiagnostics(outcome, reason, provider, message) {
|
|
@@ -102960,7 +104078,7 @@ ${detail}`
|
|
|
102960
104078
|
async checkExecutableAvailable(command) {
|
|
102961
104079
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
102962
104080
|
try {
|
|
102963
|
-
await
|
|
104081
|
+
await execFileAsync4(checker, [command]);
|
|
102964
104082
|
return { available: true };
|
|
102965
104083
|
} catch {
|
|
102966
104084
|
return {
|
|
@@ -103049,15 +104167,17 @@ ${detail}`
|
|
|
103049
104167
|
return void 0;
|
|
103050
104168
|
}
|
|
103051
104169
|
internalEnqueueMerge(taskId) {
|
|
103052
|
-
if (this.shuttingDown) return;
|
|
104170
|
+
if (this.shuttingDown) return false;
|
|
103053
104171
|
if (this.mergeActive.has(taskId)) {
|
|
103054
104172
|
const isActuallyLive = this.mergeQueue.includes(taskId) || this.activeMergeTaskId === taskId;
|
|
103055
104173
|
if (!isActuallyLive) {
|
|
103056
104174
|
runtimeLog.warn(
|
|
103057
|
-
`internalEnqueueMerge(${taskId}): skipped \u2014 mergeActive entry is leaked (not queued, not active).
|
|
104175
|
+
`internalEnqueueMerge(${taskId}): skipped \u2014 mergeActive entry is leaked (not queued, not active). Reconciling stale entry and retrying enqueue now.`
|
|
103058
104176
|
);
|
|
104177
|
+
this.mergeActive.delete(taskId);
|
|
104178
|
+
} else {
|
|
104179
|
+
return false;
|
|
103059
104180
|
}
|
|
103060
|
-
return;
|
|
103061
104181
|
}
|
|
103062
104182
|
this.mergeActive.add(taskId);
|
|
103063
104183
|
this.mergeQueue.push(taskId);
|
|
@@ -103066,6 +104186,7 @@ ${detail}`
|
|
|
103066
104186
|
`Merge queue drain failed unexpectedly: ${err instanceof Error ? err.message : String(err)}`
|
|
103067
104187
|
);
|
|
103068
104188
|
});
|
|
104189
|
+
return true;
|
|
103069
104190
|
}
|
|
103070
104191
|
/**
|
|
103071
104192
|
* Filter a sweep's listTasks() result to merge-eligible tasks, sort by
|
|
@@ -103084,6 +104205,27 @@ ${detail}`
|
|
|
103084
104205
|
}
|
|
103085
104206
|
return eligible.length;
|
|
103086
104207
|
}
|
|
104208
|
+
reconcileStaleMergeActive() {
|
|
104209
|
+
let cleared = 0;
|
|
104210
|
+
for (const taskId of [...this.mergeActive]) {
|
|
104211
|
+
if (taskId === this.activeMergeTaskId) continue;
|
|
104212
|
+
if (this.mergeQueue.includes(taskId)) continue;
|
|
104213
|
+
this.mergeActive.delete(taskId);
|
|
104214
|
+
cleared++;
|
|
104215
|
+
}
|
|
104216
|
+
return cleared;
|
|
104217
|
+
}
|
|
104218
|
+
scheduleMergeActiveReconciliation(intervalMs) {
|
|
104219
|
+
if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
|
|
104220
|
+
return;
|
|
104221
|
+
}
|
|
104222
|
+
this.mergeActiveReconcileTimer = setInterval(() => {
|
|
104223
|
+
const cleared = this.reconcileStaleMergeActive();
|
|
104224
|
+
if (cleared > 0) {
|
|
104225
|
+
runtimeLog.warn(`Reconciled ${cleared} stale mergeActive entr${cleared === 1 ? "y" : "ies"}`);
|
|
104226
|
+
}
|
|
104227
|
+
}, intervalMs);
|
|
104228
|
+
}
|
|
103087
104229
|
async findActiveRecoveryFollowUp(store, parentTaskId, branch) {
|
|
103088
104230
|
const tasks = await store.listTasks({ slim: true }).catch(() => []);
|
|
103089
104231
|
const activeRecoveryTasks = tasks.filter(
|
|
@@ -103103,6 +104245,7 @@ ${detail}`
|
|
|
103103
104245
|
if (this.mergeRunning) return;
|
|
103104
104246
|
this.mergeRunning = true;
|
|
103105
104247
|
try {
|
|
104248
|
+
this.reconcileStaleMergeActive();
|
|
103106
104249
|
const store = this.runtime.getTaskStore();
|
|
103107
104250
|
const cwd = this.config.workingDirectory;
|
|
103108
104251
|
while (this.mergeQueue.length > 0 && !this.shuttingDown) {
|
|
@@ -103882,6 +105025,21 @@ ${detail}`
|
|
|
103882
105025
|
};
|
|
103883
105026
|
store.on("settings:updated", onEngineUnpause);
|
|
103884
105027
|
this.settingsHandlers.push(onEngineUnpause);
|
|
105028
|
+
const onMaintenanceIntervalChange = ({
|
|
105029
|
+
settings: s,
|
|
105030
|
+
previous: prev
|
|
105031
|
+
}) => {
|
|
105032
|
+
if (s.maintenanceIntervalMs === prev.maintenanceIntervalMs) {
|
|
105033
|
+
return;
|
|
105034
|
+
}
|
|
105035
|
+
if (this.mergeActiveReconcileTimer) {
|
|
105036
|
+
clearInterval(this.mergeActiveReconcileTimer);
|
|
105037
|
+
this.mergeActiveReconcileTimer = null;
|
|
105038
|
+
}
|
|
105039
|
+
this.scheduleMergeActiveReconciliation(s.maintenanceIntervalMs ?? 9e5);
|
|
105040
|
+
};
|
|
105041
|
+
store.on("settings:updated", onMaintenanceIntervalChange);
|
|
105042
|
+
this.settingsHandlers.push(onMaintenanceIntervalChange);
|
|
103885
105043
|
const onStuckTimeoutChange = async ({
|
|
103886
105044
|
settings: s,
|
|
103887
105045
|
previous: prev
|
|
@@ -104981,6 +106139,7 @@ __export(src_exports2, {
|
|
|
104981
106139
|
applyAutostashBySha: () => applyAutostashBySha,
|
|
104982
106140
|
applyUnavailableNodePolicy: () => applyUnavailableNodePolicy,
|
|
104983
106141
|
assertSafeUrl: () => assertSafeUrl,
|
|
106142
|
+
auditSquashMerge: () => auditSquashMerge,
|
|
104984
106143
|
buildAgentChatPrompt: () => buildAgentChatPrompt,
|
|
104985
106144
|
buildNtfyClickUrl: () => buildNtfyClickUrl,
|
|
104986
106145
|
buildRuntimeResolutionContext: () => buildRuntimeResolutionContext,
|
|
@@ -105006,6 +106165,7 @@ __export(src_exports2, {
|
|
|
105006
106165
|
extractRuntimeHint: () => extractRuntimeHint,
|
|
105007
106166
|
extractRuntimeModel: () => extractRuntimeModel,
|
|
105008
106167
|
fetchWebContent: () => fetchWebContent,
|
|
106168
|
+
formatSquashAuditReport: () => formatSquashAuditReport,
|
|
105009
106169
|
formatTaskIdentifier: () => formatTaskIdentifier,
|
|
105010
106170
|
generateReservedWorktreeName: () => generateReservedWorktreeName,
|
|
105011
106171
|
generateWorktreeName: () => generateWorktreeName,
|
|
@@ -105060,6 +106220,7 @@ var init_src2 = __esm({
|
|
|
105060
106220
|
init_mission_autopilot();
|
|
105061
106221
|
init_mission_execution_loop();
|
|
105062
106222
|
init_merger();
|
|
106223
|
+
init_merger_squash_audit();
|
|
105063
106224
|
init_reviewer();
|
|
105064
106225
|
init_pi();
|
|
105065
106226
|
init_pi();
|
|
@@ -105153,6 +106314,7 @@ __export(planning_exports, {
|
|
|
105153
106314
|
getRateLimitResetTime: () => getRateLimitResetTime2,
|
|
105154
106315
|
getSession: () => getSession,
|
|
105155
106316
|
getSummary: () => getSummary,
|
|
106317
|
+
mergePlanningSubtaskDrafts: () => mergePlanningSubtaskDrafts,
|
|
105156
106318
|
parseAgentResponse: () => parseAgentResponse,
|
|
105157
106319
|
planningStreamManager: () => planningStreamManager,
|
|
105158
106320
|
rehydrateFromStore: () => rehydrateFromStore,
|
|
@@ -106505,6 +107667,37 @@ function generateSubtasksFromPlanning(sessionId) {
|
|
|
106505
107667
|
}
|
|
106506
107668
|
];
|
|
106507
107669
|
}
|
|
107670
|
+
function mergePlanningSubtaskDrafts(sessionId, drafts) {
|
|
107671
|
+
const generatedSubtasks = generateSubtasksFromPlanning(sessionId);
|
|
107672
|
+
const generatedById = new Map(generatedSubtasks.map((subtask) => [subtask.id, subtask]));
|
|
107673
|
+
return drafts.map((draft) => {
|
|
107674
|
+
const generated = generatedById.get(draft.id);
|
|
107675
|
+
const normalizedDependsOn = Array.isArray(draft.dependsOn) ? draft.dependsOn.filter((dependency) => typeof dependency === "string") : void 0;
|
|
107676
|
+
if (!generated) {
|
|
107677
|
+
const title = typeof draft.title === "string" ? draft.title.trim() : "";
|
|
107678
|
+
if (!title) {
|
|
107679
|
+
throw new Error(`Client-added subtask must have a title: ${draft.id}`);
|
|
107680
|
+
}
|
|
107681
|
+
const description = typeof draft.description === "string" ? draft.description : title;
|
|
107682
|
+
return {
|
|
107683
|
+
id: draft.id,
|
|
107684
|
+
title,
|
|
107685
|
+
description,
|
|
107686
|
+
suggestedSize: draft.suggestedSize === "S" || draft.suggestedSize === "M" || draft.suggestedSize === "L" ? draft.suggestedSize : "M",
|
|
107687
|
+
priority: draft.priority ?? DEFAULT_TASK_PRIORITY,
|
|
107688
|
+
dependsOn: normalizedDependsOn ?? []
|
|
107689
|
+
};
|
|
107690
|
+
}
|
|
107691
|
+
return {
|
|
107692
|
+
id: generated.id,
|
|
107693
|
+
title: typeof draft.title === "string" ? draft.title : generated.title,
|
|
107694
|
+
description: typeof draft.description === "string" ? draft.description : generated.description,
|
|
107695
|
+
suggestedSize: draft.suggestedSize === "S" || draft.suggestedSize === "M" || draft.suggestedSize === "L" ? draft.suggestedSize : generated.suggestedSize,
|
|
107696
|
+
priority: draft.priority ?? generated.priority ?? DEFAULT_TASK_PRIORITY,
|
|
107697
|
+
dependsOn: normalizedDependsOn ?? generated.dependsOn
|
|
107698
|
+
};
|
|
107699
|
+
});
|
|
107700
|
+
}
|
|
106508
107701
|
function cleanupSession(sessionId) {
|
|
106509
107702
|
cleanupInMemorySession(sessionId);
|
|
106510
107703
|
unpersistSession(sessionId);
|
|
@@ -111876,10 +113069,10 @@ var init_github_poll = __esm({
|
|
|
111876
113069
|
});
|
|
111877
113070
|
|
|
111878
113071
|
// ../dashboard/src/routes/resolve-diff-base.ts
|
|
111879
|
-
import { execFile as
|
|
111880
|
-
import { promisify as
|
|
113072
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
113073
|
+
import { promisify as promisify13 } from "node:util";
|
|
111881
113074
|
async function runGitCommand(args, cwd, timeout2 = 1e4) {
|
|
111882
|
-
const result = await
|
|
113075
|
+
const result = await execFileAsync5("git", args, {
|
|
111883
113076
|
cwd,
|
|
111884
113077
|
timeout: timeout2,
|
|
111885
113078
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -111953,11 +113146,11 @@ async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitComma
|
|
|
111953
113146
|
return void 0;
|
|
111954
113147
|
}
|
|
111955
113148
|
}
|
|
111956
|
-
var
|
|
113149
|
+
var execFileAsync5;
|
|
111957
113150
|
var init_resolve_diff_base = __esm({
|
|
111958
113151
|
"../dashboard/src/routes/resolve-diff-base.ts"() {
|
|
111959
113152
|
"use strict";
|
|
111960
|
-
|
|
113153
|
+
execFileAsync5 = promisify13(execFile7);
|
|
111961
113154
|
}
|
|
111962
113155
|
});
|
|
111963
113156
|
|
|
@@ -117122,7 +118315,7 @@ function registerPlanningSubtaskRoutes(ctx, deps) {
|
|
|
117122
118315
|
throw badRequest("subtasks must be a non-empty array");
|
|
117123
118316
|
}
|
|
117124
118317
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
117125
|
-
const { getSession: getSession2, cleanupSession: cleanupSession2, formatInterviewQA: formatInterviewQA2 } = await Promise.resolve().then(() => (init_planning(), planning_exports));
|
|
118318
|
+
const { getSession: getSession2, cleanupSession: cleanupSession2, formatInterviewQA: formatInterviewQA2, mergePlanningSubtaskDrafts: mergePlanningSubtaskDrafts2 } = await Promise.resolve().then(() => (init_planning(), planning_exports));
|
|
117126
118319
|
const session = getSession2(planningSessionId);
|
|
117127
118320
|
if (!session) {
|
|
117128
118321
|
throw notFound(`Planning session ${planningSessionId} not found or expired`);
|
|
@@ -117135,12 +118328,33 @@ function registerPlanningSubtaskRoutes(ctx, deps) {
|
|
|
117135
118328
|
|
|
117136
118329
|
${qaSection}` : `Source: ${session.initialPlan.slice(0, 200)}`;
|
|
117137
118330
|
for (const item of subtasks) {
|
|
117138
|
-
if (!item || typeof item.id !== "string" ||
|
|
117139
|
-
throw badRequest("Each subtask must include id
|
|
118331
|
+
if (!item || typeof item.id !== "string" || !item.id.trim()) {
|
|
118332
|
+
throw badRequest("Each subtask must include id");
|
|
118333
|
+
}
|
|
118334
|
+
if (item.title !== void 0 && (typeof item.title !== "string" || !item.title.trim())) {
|
|
118335
|
+
throw badRequest("Each edited subtask title must be a non-empty string");
|
|
118336
|
+
}
|
|
118337
|
+
if (item.description !== void 0 && typeof item.description !== "string") {
|
|
118338
|
+
throw badRequest("Each edited subtask description must be a string");
|
|
118339
|
+
}
|
|
118340
|
+
if (item.suggestedSize !== void 0 && item.suggestedSize !== "S" && item.suggestedSize !== "M" && item.suggestedSize !== "L") {
|
|
118341
|
+
throw badRequest("Each edited subtask suggestedSize must be S, M, or L");
|
|
117140
118342
|
}
|
|
117141
118343
|
if (item.priority !== void 0 && !isTaskPriority2(item.priority)) {
|
|
117142
118344
|
throw badRequest("Each subtask priority must be one of low, normal, high, urgent");
|
|
117143
118345
|
}
|
|
118346
|
+
if (item.dependsOn !== void 0 && (!Array.isArray(item.dependsOn) || item.dependsOn.some((dependency) => typeof dependency !== "string"))) {
|
|
118347
|
+
throw badRequest("Each edited subtask dependsOn value must be an array of ids");
|
|
118348
|
+
}
|
|
118349
|
+
}
|
|
118350
|
+
let mergedSubtasks;
|
|
118351
|
+
try {
|
|
118352
|
+
mergedSubtasks = mergePlanningSubtaskDrafts2(planningSessionId, subtasks);
|
|
118353
|
+
} catch (error) {
|
|
118354
|
+
throw badRequest(error instanceof Error ? error.message : "Invalid planning subtask edits");
|
|
118355
|
+
}
|
|
118356
|
+
if (mergedSubtasks.length !== subtasks.length) {
|
|
118357
|
+
throw badRequest("Could not resolve planning subtasks for task creation");
|
|
117144
118358
|
}
|
|
117145
118359
|
const { branch: resolvedBranch, baseBranch: resolvedBaseBranch } = resolveBranchSelection(branchSelection, branch, baseBranch);
|
|
117146
118360
|
const { mode: branchMode } = resolveBranchAssignmentContext(branchAssignment);
|
|
@@ -117152,7 +118366,7 @@ ${qaSection}` : `Source: ${session.initialPlan.slice(0, 200)}`;
|
|
|
117152
118366
|
};
|
|
117153
118367
|
const createdTasks = [];
|
|
117154
118368
|
const tempIdToTaskId = /* @__PURE__ */ new Map();
|
|
117155
|
-
for (const item of
|
|
118369
|
+
for (const item of mergedSubtasks) {
|
|
117156
118370
|
const taskBranch = branchMode === "per-task-derived" ? derivePerTaskBranch(resolvedBranch, item.title || item.id) : resolvedBranch;
|
|
117157
118371
|
const task = await scopedStore.createTask({
|
|
117158
118372
|
title: item.title.trim(),
|
|
@@ -117172,8 +118386,8 @@ ${qaSection}` : `Source: ${session.initialPlan.slice(0, 200)}`;
|
|
|
117172
118386
|
await scopedStore.updateTask(task.id, { size: item.suggestedSize });
|
|
117173
118387
|
}
|
|
117174
118388
|
}
|
|
117175
|
-
for (let index2 = 0; index2 <
|
|
117176
|
-
const item =
|
|
118389
|
+
for (let index2 = 0; index2 < mergedSubtasks.length; index2++) {
|
|
118390
|
+
const item = mergedSubtasks[index2];
|
|
117177
118391
|
const created = createdTasks[index2];
|
|
117178
118392
|
const resolvedDependencies = Array.isArray(item.dependsOn) ? item.dependsOn.map((dep) => tempIdToTaskId.get(dep)).filter((dep) => Boolean(dep)) : [];
|
|
117179
118393
|
if (resolvedDependencies.length > 0) {
|
|
@@ -117413,6 +118627,43 @@ function formatAttachmentSize(size) {
|
|
|
117413
118627
|
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)}KB`;
|
|
117414
118628
|
return `${(size / (1024 * 1024)).toFixed(1)}MB`;
|
|
117415
118629
|
}
|
|
118630
|
+
function normalizeFailureCode(code) {
|
|
118631
|
+
if (typeof code === "string" && code.trim()) {
|
|
118632
|
+
return code.trim();
|
|
118633
|
+
}
|
|
118634
|
+
if (typeof code === "number" && Number.isFinite(code)) {
|
|
118635
|
+
return String(code);
|
|
118636
|
+
}
|
|
118637
|
+
return void 0;
|
|
118638
|
+
}
|
|
118639
|
+
function buildChatFailureInfo(error, fallbackSummary = "AI processing failed") {
|
|
118640
|
+
if (typeof error === "string") {
|
|
118641
|
+
const summary = error.trim() || fallbackSummary;
|
|
118642
|
+
return { summary };
|
|
118643
|
+
}
|
|
118644
|
+
if (error && typeof error === "object") {
|
|
118645
|
+
const record = error;
|
|
118646
|
+
const summary = typeof record.message === "string" && record.message.trim() ? record.message.trim() : fallbackSummary;
|
|
118647
|
+
const detail = typeof record.stack === "string" && record.stack.trim() && record.stack.trim() !== summary ? record.stack.trim() : void 0;
|
|
118648
|
+
return {
|
|
118649
|
+
summary,
|
|
118650
|
+
...typeof record.name === "string" && record.name.trim() && record.name.trim() !== "Error" ? { errorClass: record.name.trim() } : {},
|
|
118651
|
+
...normalizeFailureCode(record.code) ? { code: normalizeFailureCode(record.code) } : {},
|
|
118652
|
+
...detail ? { detail } : {}
|
|
118653
|
+
};
|
|
118654
|
+
}
|
|
118655
|
+
return { summary: fallbackSummary };
|
|
118656
|
+
}
|
|
118657
|
+
function persistFailureMessage(chatStore, sessionId, failureInfo, metadata) {
|
|
118658
|
+
return chatStore.addMessage(sessionId, {
|
|
118659
|
+
role: "assistant",
|
|
118660
|
+
content: failureInfo.summary,
|
|
118661
|
+
metadata: {
|
|
118662
|
+
failureInfo,
|
|
118663
|
+
...metadata ?? {}
|
|
118664
|
+
}
|
|
118665
|
+
});
|
|
118666
|
+
}
|
|
117416
118667
|
function validateFilePath(basePath, filePath) {
|
|
117417
118668
|
if (filePath.includes("\0")) {
|
|
117418
118669
|
throw new Error(`Access denied: Invalid characters in path`);
|
|
@@ -118074,18 +119325,26 @@ ${CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE}`;
|
|
|
118074
119325
|
"Latest user message to answer:",
|
|
118075
119326
|
input.content
|
|
118076
119327
|
].join("\n\n");
|
|
119328
|
+
const responderRuntimeModel = extractRuntimeModel(input.responder.runtimeConfig);
|
|
119329
|
+
const effectiveModelProvider = input.modelProvider ?? responderRuntimeModel.provider;
|
|
119330
|
+
const effectiveModelId = input.modelId ?? responderRuntimeModel.modelId;
|
|
119331
|
+
const chatModelSettings = await this.getChatModelSettings();
|
|
119332
|
+
const allowFallback = !(input.modelProvider && input.modelId) && !(responderRuntimeModel.provider && responderRuntimeModel.modelId);
|
|
118077
119333
|
const resolvedSession = await createResolvedAgentSession2({
|
|
118078
|
-
|
|
118079
|
-
|
|
118080
|
-
|
|
118081
|
-
|
|
118082
|
-
|
|
118083
|
-
|
|
118084
|
-
|
|
118085
|
-
|
|
118086
|
-
|
|
118087
|
-
|
|
118088
|
-
|
|
119334
|
+
sessionPurpose: "heartbeat",
|
|
119335
|
+
pluginRunner: this.pluginRunner,
|
|
119336
|
+
runtimeHint: extractRuntimeHint(input.responder.runtimeConfig),
|
|
119337
|
+
cwd: this.rootDir,
|
|
119338
|
+
systemPrompt,
|
|
119339
|
+
tools: "coding",
|
|
119340
|
+
...effectiveModelProvider && effectiveModelId ? {
|
|
119341
|
+
defaultProvider: effectiveModelProvider,
|
|
119342
|
+
defaultModelId: effectiveModelId
|
|
119343
|
+
} : {},
|
|
119344
|
+
...allowFallback && chatModelSettings.fallbackProvider && chatModelSettings.fallbackModelId ? {
|
|
119345
|
+
fallbackProvider: chatModelSettings.fallbackProvider,
|
|
119346
|
+
fallbackModelId: chatModelSettings.fallbackModelId
|
|
119347
|
+
} : {}
|
|
118089
119348
|
});
|
|
118090
119349
|
try {
|
|
118091
119350
|
await promptWithFallback(resolvedSession.session, roomPrompt);
|
|
@@ -118400,9 +119659,12 @@ ${mentionContext}`;
|
|
|
118400
119659
|
}
|
|
118401
119660
|
const sessionErrorMessage = agentResult.session.state.errorMessage;
|
|
118402
119661
|
if (typeof sessionErrorMessage === "string" && sessionErrorMessage.trim().length > 0 && !accumulatedText && !accumulatedThinking && toolCallsAccum.length === 0) {
|
|
119662
|
+
const failureInfo = buildChatFailureInfo(sessionErrorMessage, "Model response failed");
|
|
119663
|
+
persistFailureMessage(this.chatStore, sessionId, failureInfo);
|
|
119664
|
+
this.flushInFlightGenerationPersist(sessionId, null);
|
|
118403
119665
|
chatStreamManager.broadcast(sessionId, {
|
|
118404
119666
|
type: "error",
|
|
118405
|
-
data:
|
|
119667
|
+
data: failureInfo
|
|
118406
119668
|
}, broadcastOptions);
|
|
118407
119669
|
return;
|
|
118408
119670
|
}
|
|
@@ -118456,7 +119718,7 @@ ${mentionContext}`;
|
|
|
118456
119718
|
}, broadcastOptions);
|
|
118457
119719
|
return;
|
|
118458
119720
|
}
|
|
118459
|
-
const
|
|
119721
|
+
const failureInfo = buildChatFailureInfo(err, "AI processing failed");
|
|
118460
119722
|
diagnostics6.error(`Error in sendMessage for session ${sessionId}:`, err);
|
|
118461
119723
|
if (accumulatedText || accumulatedThinking || toolCallsAccum.length > 0) {
|
|
118462
119724
|
try {
|
|
@@ -118474,10 +119736,15 @@ ${mentionContext}`;
|
|
|
118474
119736
|
diagnostics6.error(`Failed to persist partial response for session ${sessionId}:`, persistErr);
|
|
118475
119737
|
}
|
|
118476
119738
|
}
|
|
119739
|
+
try {
|
|
119740
|
+
persistFailureMessage(this.chatStore, sessionId, failureInfo, fallbackInfo ? { fallback: fallbackInfo } : void 0);
|
|
119741
|
+
} catch (persistErr) {
|
|
119742
|
+
diagnostics6.error(`Failed to persist failure message for session ${sessionId}:`, persistErr);
|
|
119743
|
+
}
|
|
118477
119744
|
this.flushInFlightGenerationPersist(sessionId, null);
|
|
118478
119745
|
chatStreamManager.broadcast(sessionId, {
|
|
118479
119746
|
type: "error",
|
|
118480
|
-
data:
|
|
119747
|
+
data: failureInfo
|
|
118481
119748
|
}, broadcastOptions);
|
|
118482
119749
|
} finally {
|
|
118483
119750
|
const current = this.activeGenerations.get(sessionId);
|
|
@@ -124157,16 +125424,16 @@ var init_remote_auth = __esm({
|
|
|
124157
125424
|
|
|
124158
125425
|
// ../dashboard/src/routes/register-settings-memory-routes.ts
|
|
124159
125426
|
import crypto from "node:crypto";
|
|
124160
|
-
import { execFile as
|
|
125427
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
124161
125428
|
import { homedir as homedir8 } from "node:os";
|
|
124162
|
-
import { promisify as
|
|
125429
|
+
import { promisify as promisify14 } from "node:util";
|
|
124163
125430
|
function registerSettingsMemoryRoutes(ctx, deps) {
|
|
124164
125431
|
const { router, options, store, runtimeLogger, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
124165
125432
|
const { githubToken, validateModelPresets: validateModelPresets2, sanitizeOverlapIgnorePaths: sanitizeOverlapIgnorePaths2, discoverDashboardPiExtensions: discoverDashboardPiExtensions2 } = deps;
|
|
124166
|
-
const
|
|
125433
|
+
const execFileAsync9 = promisify14(execFile8);
|
|
124167
125434
|
async function queryTailscaleFunnelUrl(targetPort) {
|
|
124168
125435
|
try {
|
|
124169
|
-
const { stdout } = await
|
|
125436
|
+
const { stdout } = await execFileAsync9("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
124170
125437
|
const data = JSON.parse(stdout);
|
|
124171
125438
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
124172
125439
|
if (!dnsName) return null;
|
|
@@ -124179,7 +125446,7 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
124179
125446
|
async function isCloudflaredAvailable() {
|
|
124180
125447
|
const command = process.platform === "win32" ? "where" : "which";
|
|
124181
125448
|
try {
|
|
124182
|
-
await
|
|
125449
|
+
await execFileAsync9(command, ["cloudflared"]);
|
|
124183
125450
|
return true;
|
|
124184
125451
|
} catch {
|
|
124185
125452
|
return false;
|
|
@@ -124234,7 +125501,7 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
124234
125501
|
if (process.platform === "win32") {
|
|
124235
125502
|
const command = resolveCloudflaredInstallCommand();
|
|
124236
125503
|
try {
|
|
124237
|
-
await
|
|
125504
|
+
await execFileAsync9("winget", ["install", "Cloudflare.cloudflared"], { timeout: 12e4 });
|
|
124238
125505
|
return { success: true, command };
|
|
124239
125506
|
} catch (error) {
|
|
124240
125507
|
return { success: false, command, error: formatExecError(error) };
|
|
@@ -124246,21 +125513,21 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
124246
125513
|
const tempPath = "/tmp/cloudflared";
|
|
124247
125514
|
const installFromDirectDownload = async () => {
|
|
124248
125515
|
attemptedCommands.push(`curl -L --output ${tempPath} ${downloadUrl}`);
|
|
124249
|
-
await
|
|
125516
|
+
await execFileAsync9("curl", ["-L", "--output", tempPath, downloadUrl], { timeout: 12e4 });
|
|
124250
125517
|
attemptedCommands.push(`chmod +x ${tempPath}`);
|
|
124251
|
-
await
|
|
125518
|
+
await execFileAsync9("chmod", ["+x", tempPath], { timeout: 3e4 });
|
|
124252
125519
|
const globalInstallPath = "/usr/local/bin/cloudflared";
|
|
124253
125520
|
attemptedCommands.push(`mv ${tempPath} ${globalInstallPath}`);
|
|
124254
125521
|
try {
|
|
124255
|
-
await
|
|
125522
|
+
await execFileAsync9("mv", [tempPath, globalInstallPath], { timeout: 3e4 });
|
|
124256
125523
|
} catch (error) {
|
|
124257
125524
|
const localBinDir = `${homedir8()}/.local/bin`;
|
|
124258
125525
|
const localInstallPath = `${localBinDir}/cloudflared`;
|
|
124259
125526
|
attemptedCommands.push(`mkdir -p ${localBinDir}`);
|
|
124260
125527
|
attemptedCommands.push(`mv ${tempPath} ${localInstallPath}`);
|
|
124261
|
-
await
|
|
125528
|
+
await execFileAsync9("mkdir", ["-p", localBinDir], { timeout: 3e4 });
|
|
124262
125529
|
try {
|
|
124263
|
-
await
|
|
125530
|
+
await execFileAsync9("mv", [tempPath, localInstallPath], { timeout: 3e4 });
|
|
124264
125531
|
} catch (fallbackError) {
|
|
124265
125532
|
throw new Error(
|
|
124266
125533
|
`Failed to install cloudflared to /usr/local/bin and ~/.local/bin (${formatExecError(error)}; fallback: ${formatExecError(fallbackError)})`
|
|
@@ -124271,9 +125538,9 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
124271
125538
|
if (process.platform === "darwin") {
|
|
124272
125539
|
attemptedCommands.push("which brew");
|
|
124273
125540
|
try {
|
|
124274
|
-
await
|
|
125541
|
+
await execFileAsync9("which", ["brew"], { timeout: 15e3 });
|
|
124275
125542
|
attemptedCommands.push("brew install cloudflared");
|
|
124276
|
-
await
|
|
125543
|
+
await execFileAsync9("brew", ["install", "cloudflared"], { timeout: 12e4 });
|
|
124277
125544
|
return { success: true, command: attemptedCommands.join(" && ") };
|
|
124278
125545
|
} catch {
|
|
124279
125546
|
try {
|
|
@@ -125490,15 +126757,26 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
125490
126757
|
}
|
|
125491
126758
|
const requestOverride = typeof overrideValue === "string" && overrideValue.trim() ? normalizeNtfyBaseUrl(overrideValue, "request") : void 0;
|
|
125492
126759
|
const storedServer = typeof settings.ntfyBaseUrl === "string" && settings.ntfyBaseUrl.trim() ? normalizeNtfyBaseUrl(settings.ntfyBaseUrl, "settings") : void 0;
|
|
126760
|
+
const tokenOverride = req.body?.ntfyAccessToken;
|
|
126761
|
+
if (tokenOverride !== void 0 && tokenOverride !== null && typeof tokenOverride !== "string") {
|
|
126762
|
+
throw badRequest("ntfy access token must be a string");
|
|
126763
|
+
}
|
|
126764
|
+
const requestToken = typeof tokenOverride === "string" && tokenOverride.trim() ? tokenOverride.trim() : void 0;
|
|
126765
|
+
const storedToken = typeof settings.ntfyAccessToken === "string" && settings.ntfyAccessToken.trim() ? settings.ntfyAccessToken.trim() : void 0;
|
|
125493
126766
|
const ntfyBaseUrl = requestOverride ?? storedServer ?? "https://ntfy.sh";
|
|
125494
126767
|
const url = `${ntfyBaseUrl}/${topic}`;
|
|
126768
|
+
const headers = {
|
|
126769
|
+
"Title": "Fusion test notification",
|
|
126770
|
+
"Priority": "default",
|
|
126771
|
+
"Content-Type": "text/plain"
|
|
126772
|
+
};
|
|
126773
|
+
const ntfyAccessToken = requestToken ?? storedToken;
|
|
126774
|
+
if (ntfyAccessToken) {
|
|
126775
|
+
headers.Authorization = `Bearer ${ntfyAccessToken}`;
|
|
126776
|
+
}
|
|
125495
126777
|
const response = await fetch(url, {
|
|
125496
126778
|
method: "POST",
|
|
125497
|
-
headers
|
|
125498
|
-
"Title": "Fusion test notification",
|
|
125499
|
-
"Priority": "default",
|
|
125500
|
-
"Content-Type": "text/plain"
|
|
125501
|
-
},
|
|
126779
|
+
headers,
|
|
125502
126780
|
body: "Fusion test notification \u2014 your notifications are working!"
|
|
125503
126781
|
});
|
|
125504
126782
|
if (!response.ok) {
|
|
@@ -125592,15 +126870,26 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
125592
126870
|
}
|
|
125593
126871
|
const requestOverride = typeof overrideValue === "string" && overrideValue.trim() ? normalizeNtfyBaseUrl(overrideValue, "request") : void 0;
|
|
125594
126872
|
const storedServer = typeof settings.ntfyBaseUrl === "string" && settings.ntfyBaseUrl.trim() ? normalizeNtfyBaseUrl(settings.ntfyBaseUrl, "settings") : void 0;
|
|
126873
|
+
const tokenOverride = config.ntfyAccessToken ?? body.ntfyAccessToken;
|
|
126874
|
+
if (tokenOverride !== void 0 && tokenOverride !== null && typeof tokenOverride !== "string") {
|
|
126875
|
+
throw badRequest("ntfy access token must be a string");
|
|
126876
|
+
}
|
|
126877
|
+
const requestToken = typeof tokenOverride === "string" && tokenOverride.trim() ? tokenOverride.trim() : void 0;
|
|
126878
|
+
const storedToken = typeof settings.ntfyAccessToken === "string" && settings.ntfyAccessToken.trim() ? settings.ntfyAccessToken.trim() : void 0;
|
|
125595
126879
|
const ntfyBaseUrl = requestOverride ?? storedServer ?? "https://ntfy.sh";
|
|
125596
126880
|
const url = `${ntfyBaseUrl}/${topic}`;
|
|
126881
|
+
const headers = {
|
|
126882
|
+
"Title": "Fusion test notification",
|
|
126883
|
+
"Priority": "default",
|
|
126884
|
+
"Content-Type": "text/plain"
|
|
126885
|
+
};
|
|
126886
|
+
const ntfyAccessToken = requestToken ?? storedToken;
|
|
126887
|
+
if (ntfyAccessToken) {
|
|
126888
|
+
headers.Authorization = `Bearer ${ntfyAccessToken}`;
|
|
126889
|
+
}
|
|
125597
126890
|
const response = await fetch(url, {
|
|
125598
126891
|
method: "POST",
|
|
125599
|
-
headers
|
|
125600
|
-
"Title": "Fusion test notification",
|
|
125601
|
-
"Priority": "default",
|
|
125602
|
-
"Content-Type": "text/plain"
|
|
125603
|
-
},
|
|
126892
|
+
headers,
|
|
125604
126893
|
body: "Fusion test notification \u2014 your notifications are working!"
|
|
125605
126894
|
});
|
|
125606
126895
|
if (!response.ok) {
|
|
@@ -154429,13 +155718,13 @@ var init_register_agents_projects_nodes = __esm({
|
|
|
154429
155718
|
});
|
|
154430
155719
|
|
|
154431
155720
|
// ../dashboard/src/exec-file.ts
|
|
154432
|
-
import { execFile as
|
|
154433
|
-
import { promisify as
|
|
154434
|
-
var
|
|
155721
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
155722
|
+
import { promisify as promisify15 } from "node:util";
|
|
155723
|
+
var execFileAsync6;
|
|
154435
155724
|
var init_exec_file = __esm({
|
|
154436
155725
|
"../dashboard/src/exec-file.ts"() {
|
|
154437
155726
|
"use strict";
|
|
154438
|
-
|
|
155727
|
+
execFileAsync6 = promisify15(execFile9);
|
|
154439
155728
|
}
|
|
154440
155729
|
});
|
|
154441
155730
|
|
|
@@ -154646,7 +155935,7 @@ var init_register_project_routes = __esm({
|
|
|
154646
155935
|
throw badRequest("cloneUrl must be a non-empty string when provided");
|
|
154647
155936
|
}
|
|
154648
155937
|
try {
|
|
154649
|
-
await
|
|
155938
|
+
await execFileAsync6("git", ["clone", cloneSource, normalizedPath], {
|
|
154650
155939
|
timeout: 9e4,
|
|
154651
155940
|
maxBuffer: 10 * 1024 * 1024,
|
|
154652
155941
|
encoding: "utf-8"
|
|
@@ -156358,8 +157647,8 @@ var init_register_settings_sync_routes = __esm({
|
|
|
156358
157647
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
156359
157648
|
version: 1
|
|
156360
157649
|
};
|
|
156361
|
-
const { createHash:
|
|
156362
|
-
const checksum =
|
|
157650
|
+
const { createHash: createHash9 } = await import("node:crypto");
|
|
157651
|
+
const checksum = createHash9("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
156363
157652
|
await fetchFromRemoteNode(node, "/api/settings/sync-receive", {
|
|
156364
157653
|
method: "POST",
|
|
156365
157654
|
body: { ...payload, checksum }
|
|
@@ -156418,7 +157707,7 @@ var init_register_settings_sync_routes = __esm({
|
|
|
156418
157707
|
});
|
|
156419
157708
|
return;
|
|
156420
157709
|
}
|
|
156421
|
-
const { createHash:
|
|
157710
|
+
const { createHash: createHash9 } = await import("node:crypto");
|
|
156422
157711
|
const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
156423
157712
|
const payloadWithoutChecksum = {
|
|
156424
157713
|
global: remoteSettings.global,
|
|
@@ -156426,7 +157715,7 @@ var init_register_settings_sync_routes = __esm({
|
|
|
156426
157715
|
exportedAt,
|
|
156427
157716
|
version: 1
|
|
156428
157717
|
};
|
|
156429
|
-
const checksum =
|
|
157718
|
+
const checksum = createHash9("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
|
|
156430
157719
|
const result = await central.applyRemoteSettings({
|
|
156431
157720
|
...payloadWithoutChecksum,
|
|
156432
157721
|
checksum
|
|
@@ -166247,7 +167536,7 @@ import * as child_process from "node:child_process";
|
|
|
166247
167536
|
function getHomeDir6() {
|
|
166248
167537
|
return process.env.HOME || process.env.USERPROFILE || os4.homedir();
|
|
166249
167538
|
}
|
|
166250
|
-
function
|
|
167539
|
+
function execFileAsync7(file, args, options) {
|
|
166251
167540
|
return new Promise((resolve52, reject2) => {
|
|
166252
167541
|
child_process.execFile(file, args, options, (error, stdout, stderr) => {
|
|
166253
167542
|
if (error) {
|
|
@@ -166396,7 +167685,7 @@ async function readConfiguredApiKey(provider, authStorage) {
|
|
|
166396
167685
|
}
|
|
166397
167686
|
async function readClaudeKeychainCredentials() {
|
|
166398
167687
|
try {
|
|
166399
|
-
const { stdout } = await
|
|
167688
|
+
const { stdout } = await execFileAsync7(
|
|
166400
167689
|
"security",
|
|
166401
167690
|
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
166402
167691
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
@@ -167289,13 +168578,13 @@ async function fetchGitHubCopilotUsage() {
|
|
|
167289
168578
|
windows: []
|
|
167290
168579
|
};
|
|
167291
168580
|
try {
|
|
167292
|
-
await
|
|
168581
|
+
await execFileAsync7("gh", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
|
|
167293
168582
|
} catch {
|
|
167294
168583
|
usage.error = "GitHub CLI not authenticated \u2014 run 'gh auth login'";
|
|
167295
168584
|
return usage;
|
|
167296
168585
|
}
|
|
167297
168586
|
try {
|
|
167298
|
-
const { stdout } = await
|
|
168587
|
+
const { stdout } = await execFileAsync7("gh", ["api", "/user/copilot", "--jq", "."], {
|
|
167299
168588
|
encoding: "utf-8",
|
|
167300
168589
|
timeout: 1e4
|
|
167301
168590
|
});
|
|
@@ -179180,9 +180469,9 @@ function createApiRoutes(store, options) {
|
|
|
179180
180469
|
return Math.max(0, Number((usedMicros / elapsedMicros * 100).toFixed(1)));
|
|
179181
180470
|
};
|
|
179182
180471
|
const getVitestProcessIds = async () => {
|
|
179183
|
-
const { execFile:
|
|
180472
|
+
const { execFile: execFile12 } = await import("node:child_process");
|
|
179184
180473
|
const stdout = await new Promise((resolve52) => {
|
|
179185
|
-
|
|
180474
|
+
execFile12("pgrep", ["-f", "vitest"], { encoding: "utf8" }, (err, out) => {
|
|
179186
180475
|
resolve52(err ? "" : typeof out === "string" ? out : "");
|
|
179187
180476
|
});
|
|
179188
180477
|
});
|
|
@@ -181499,8 +182788,8 @@ function truncateAutomationOutput(stdout, stderr) {
|
|
|
181499
182788
|
}
|
|
181500
182789
|
async function executeSingleCommand(command, timeoutMs, startedAt) {
|
|
181501
182790
|
const { exec: exec15 } = await import("node:child_process");
|
|
181502
|
-
const { promisify:
|
|
181503
|
-
const execAsyncFn =
|
|
182791
|
+
const { promisify: promisify21 } = await import("node:util");
|
|
182792
|
+
const execAsyncFn = promisify21(exec15);
|
|
181504
182793
|
const isWindows = process.platform === "win32";
|
|
181505
182794
|
try {
|
|
181506
182795
|
const { stdout, stderr } = await execAsyncFn(command, {
|
|
@@ -184062,7 +185351,7 @@ var require_websocket = __commonJS({
|
|
|
184062
185351
|
var http = __require("http");
|
|
184063
185352
|
var net = __require("net");
|
|
184064
185353
|
var tls = __require("tls");
|
|
184065
|
-
var { randomBytes: randomBytes4, createHash:
|
|
185354
|
+
var { randomBytes: randomBytes4, createHash: createHash9 } = __require("crypto");
|
|
184066
185355
|
var { Duplex, Readable: Readable2 } = __require("stream");
|
|
184067
185356
|
var { URL: URL2 } = __require("url");
|
|
184068
185357
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
@@ -184722,7 +186011,7 @@ var require_websocket = __commonJS({
|
|
|
184722
186011
|
abortHandshake(websocket, socket, "Invalid Upgrade header");
|
|
184723
186012
|
return;
|
|
184724
186013
|
}
|
|
184725
|
-
const digest =
|
|
186014
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
184726
186015
|
if (res.headers["sec-websocket-accept"] !== digest) {
|
|
184727
186016
|
abortHandshake(websocket, socket, "Invalid Sec-WebSocket-Accept header");
|
|
184728
186017
|
return;
|
|
@@ -185089,7 +186378,7 @@ var require_websocket_server = __commonJS({
|
|
|
185089
186378
|
var EventEmitter38 = __require("events");
|
|
185090
186379
|
var http = __require("http");
|
|
185091
186380
|
var { Duplex } = __require("stream");
|
|
185092
|
-
var { createHash:
|
|
186381
|
+
var { createHash: createHash9 } = __require("crypto");
|
|
185093
186382
|
var extension2 = require_extension();
|
|
185094
186383
|
var PerMessageDeflate2 = require_permessage_deflate();
|
|
185095
186384
|
var subprotocol2 = require_subprotocol();
|
|
@@ -185390,7 +186679,7 @@ var require_websocket_server = __commonJS({
|
|
|
185390
186679
|
);
|
|
185391
186680
|
}
|
|
185392
186681
|
if (this._state > RUNNING) return abortHandshake(socket, 503);
|
|
185393
|
-
const digest =
|
|
186682
|
+
const digest = createHash9("sha1").update(key + GUID).digest("base64");
|
|
185394
186683
|
const headers = [
|
|
185395
186684
|
"HTTP/1.1 101 Switching Protocols",
|
|
185396
186685
|
"Upgrade: websocket",
|
|
@@ -186679,7 +187968,7 @@ data: ${JSON.stringify({ type: event.type, data: event.data })}
|
|
|
186679
187968
|
app.get("/api/health", (_req, res) => {
|
|
186680
187969
|
const database = store.getDatabaseHealth();
|
|
186681
187970
|
res.json({
|
|
186682
|
-
status: database.
|
|
187971
|
+
status: database.healthy ? "ok" : "degraded",
|
|
186683
187972
|
version: cliPackageVersion,
|
|
186684
187973
|
uptime: Math.floor(process.uptime()),
|
|
186685
187974
|
database
|
|
@@ -187791,7 +189080,7 @@ var init_src6 = __esm({
|
|
|
187791
189080
|
|
|
187792
189081
|
// src/commands/task-lifecycle.ts
|
|
187793
189082
|
import { exec as exec11 } from "node:child_process";
|
|
187794
|
-
import { promisify as
|
|
189083
|
+
import { promisify as promisify16 } from "node:util";
|
|
187795
189084
|
function getMergeStrategy(settings) {
|
|
187796
189085
|
return settings.mergeStrategy ?? "direct";
|
|
187797
189086
|
}
|
|
@@ -187995,7 +189284,7 @@ var init_task_lifecycle = __esm({
|
|
|
187995
189284
|
"src/commands/task-lifecycle.ts"() {
|
|
187996
189285
|
"use strict";
|
|
187997
189286
|
init_src();
|
|
187998
|
-
execAsync9 =
|
|
189287
|
+
execAsync9 = promisify16(exec11);
|
|
187999
189288
|
}
|
|
188000
189289
|
});
|
|
188001
189290
|
|
|
@@ -193113,7 +194402,7 @@ var init_app = __esm({
|
|
|
193113
194402
|
// src/commands/dashboard-tui/controller.ts
|
|
193114
194403
|
import os6 from "node:os";
|
|
193115
194404
|
import v82 from "node:v8";
|
|
193116
|
-
import { execFile as
|
|
194405
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
193117
194406
|
import { appendFileSync as appendFileSync2 } from "node:fs";
|
|
193118
194407
|
function getAvailableMemory() {
|
|
193119
194408
|
const fn = os6.availableMemory;
|
|
@@ -193377,7 +194666,7 @@ var init_controller = __esm({
|
|
|
193377
194666
|
}
|
|
193378
194667
|
const selfPid = process.pid;
|
|
193379
194668
|
const stdout = await new Promise((resolve52) => {
|
|
193380
|
-
|
|
194669
|
+
execFile11("pgrep", ["-f", "vitest"], { encoding: "utf8" }, (err, out) => {
|
|
193381
194670
|
resolve52(err ? "" : typeof out === "string" ? out : "");
|
|
193382
194671
|
});
|
|
193383
194672
|
});
|
|
@@ -193963,7 +195252,7 @@ __export(dashboard_exports, {
|
|
|
193963
195252
|
});
|
|
193964
195253
|
import { join as join66, resolve as pathResolve } from "node:path";
|
|
193965
195254
|
import { execFile as execFileCb } from "node:child_process";
|
|
193966
|
-
import { promisify as
|
|
195255
|
+
import { promisify as promisify17 } from "node:util";
|
|
193967
195256
|
import { stat as stat12, readdir as readdir13, readFile as fsReadFile3 } from "node:fs/promises";
|
|
193968
195257
|
import { AuthStorage as AuthStorage2, DefaultPackageManager as DefaultPackageManager2, ModelRegistry as ModelRegistry3, discoverAndLoadExtensions as discoverAndLoadExtensions2, createExtensionRuntime as createExtensionRuntime2 } from "@mariozechner/pi-coding-agent";
|
|
193969
195258
|
function formatRuntimeContext(context) {
|
|
@@ -194117,7 +195406,7 @@ function setDiagnosticStoreListenerCheck(check) {
|
|
|
194117
195406
|
diagnosticStoreListenerCheck = check;
|
|
194118
195407
|
}
|
|
194119
195408
|
async function gitExec(cwd, args) {
|
|
194120
|
-
const { stdout } = await
|
|
195409
|
+
const { stdout } = await execFileAsync8("git", args, { cwd, maxBuffer: 4 * 1024 * 1024 });
|
|
194121
195410
|
return stdout;
|
|
194122
195411
|
}
|
|
194123
195412
|
async function buildGitStatus(projectPath) {
|
|
@@ -195698,7 +196987,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
195698
196987
|
listWorktrees: (projectPath) => buildGitWorktrees(projectPath),
|
|
195699
196988
|
push: async (projectPath) => {
|
|
195700
196989
|
try {
|
|
195701
|
-
const { stdout, stderr } = await
|
|
196990
|
+
const { stdout, stderr } = await execFileAsync8("git", ["push"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
|
|
195702
196991
|
return { success: true, output: (stdout + stderr).trim() };
|
|
195703
196992
|
} catch (err) {
|
|
195704
196993
|
const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
|
|
@@ -195707,7 +196996,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
195707
196996
|
},
|
|
195708
196997
|
fetch: async (projectPath) => {
|
|
195709
196998
|
try {
|
|
195710
|
-
const { stdout, stderr } = await
|
|
196999
|
+
const { stdout, stderr } = await execFileAsync8("git", ["fetch"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
|
|
195711
197000
|
return { success: true, output: (stdout + stderr).trim() };
|
|
195712
197001
|
} catch (err) {
|
|
195713
197002
|
const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
|
|
@@ -195836,7 +197125,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
195836
197125
|
});
|
|
195837
197126
|
return { dispose };
|
|
195838
197127
|
}
|
|
195839
|
-
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer,
|
|
197128
|
+
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer, execFileAsync8, FILES_DENYLIST2, FILE_SIZE_LIMIT, BINARY_CHECK_BYTES, MAX_PREVIEW_LINES;
|
|
195840
197129
|
var init_dashboard = __esm({
|
|
195841
197130
|
"src/commands/dashboard.ts"() {
|
|
195842
197131
|
"use strict";
|
|
@@ -195919,7 +197208,7 @@ var init_dashboard = __esm({
|
|
|
195919
197208
|
}
|
|
195920
197209
|
}
|
|
195921
197210
|
};
|
|
195922
|
-
|
|
197211
|
+
execFileAsync8 = promisify17(execFileCb);
|
|
195923
197212
|
FILES_DENYLIST2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "target", "build"]);
|
|
195924
197213
|
FILE_SIZE_LIMIT = 1024 * 1024;
|
|
195925
197214
|
BINARY_CHECK_BYTES = 8 * 1024;
|
|
@@ -197789,7 +199078,7 @@ __export(task_exports, {
|
|
|
197789
199078
|
runTaskUpdate: () => runTaskUpdate
|
|
197790
199079
|
});
|
|
197791
199080
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
197792
|
-
import { watchFile, unwatchFile, statSync as
|
|
199081
|
+
import { watchFile, unwatchFile, statSync as statSync7, existsSync as existsSync48, readFileSync as readFileSync24 } from "node:fs";
|
|
197793
199082
|
import { basename as basename17, join as join70 } from "node:path";
|
|
197794
199083
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
197795
199084
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
@@ -198111,7 +199400,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
198111
199400
|
let lastPosition = 0;
|
|
198112
199401
|
let lastSize = 0;
|
|
198113
199402
|
try {
|
|
198114
|
-
const stats =
|
|
199403
|
+
const stats = statSync7(logPath);
|
|
198115
199404
|
lastSize = stats.size;
|
|
198116
199405
|
lastPosition = lastSize;
|
|
198117
199406
|
} catch {
|
|
@@ -198128,7 +199417,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
198128
199417
|
watchFile(logPath, { interval: 1e3 }, () => {
|
|
198129
199418
|
if (isShuttingDown) return;
|
|
198130
199419
|
try {
|
|
198131
|
-
const stats =
|
|
199420
|
+
const stats = statSync7(logPath);
|
|
198132
199421
|
if (stats.size < lastPosition) {
|
|
198133
199422
|
lastPosition = 0;
|
|
198134
199423
|
}
|
|
@@ -199552,7 +200841,7 @@ __export(git_exports, {
|
|
|
199552
200841
|
runGitStatus: () => runGitStatus
|
|
199553
200842
|
});
|
|
199554
200843
|
import { exec as exec12 } from "node:child_process";
|
|
199555
|
-
import { promisify as
|
|
200844
|
+
import { promisify as promisify18 } from "node:util";
|
|
199556
200845
|
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
199557
200846
|
async function resolveGitCwd(projectName) {
|
|
199558
200847
|
if (projectName) {
|
|
@@ -199826,7 +201115,7 @@ var init_git = __esm({
|
|
|
199826
201115
|
"src/commands/git.ts"() {
|
|
199827
201116
|
"use strict";
|
|
199828
201117
|
init_project_context();
|
|
199829
|
-
execAsync10 =
|
|
201118
|
+
execAsync10 = promisify18(exec12);
|
|
199830
201119
|
}
|
|
199831
201120
|
});
|
|
199832
201121
|
|
|
@@ -200013,7 +201302,7 @@ var init_memory_backup2 = __esm({
|
|
|
200013
201302
|
});
|
|
200014
201303
|
|
|
200015
201304
|
// src/project-resolver.ts
|
|
200016
|
-
import { existsSync as existsSync50, statSync as
|
|
201305
|
+
import { existsSync as existsSync50, statSync as statSync8 } from "node:fs";
|
|
200017
201306
|
import { basename as basename18, dirname as dirname32, resolve as resolve42, normalize as normalize6 } from "node:path";
|
|
200018
201307
|
import { createInterface as createInterface5 } from "node:readline/promises";
|
|
200019
201308
|
async function getCentralCore() {
|
|
@@ -200613,7 +201902,7 @@ __export(project_exports, {
|
|
|
200613
201902
|
runProjectShow: () => runProjectShow
|
|
200614
201903
|
});
|
|
200615
201904
|
import { resolve as resolve43, isAbsolute as isAbsolute21, relative as relative16, basename as basename19 } from "node:path";
|
|
200616
|
-
import { existsSync as existsSync51, statSync as
|
|
201905
|
+
import { existsSync as existsSync51, statSync as statSync9 } from "node:fs";
|
|
200617
201906
|
import { createInterface as createInterface7 } from "node:readline/promises";
|
|
200618
201907
|
function formatDisplayPath(projectPath) {
|
|
200619
201908
|
const rel = relative16(process.cwd(), projectPath);
|
|
@@ -200748,7 +202037,7 @@ async function runProjectAdd(name, path6, options = {}) {
|
|
|
200748
202037
|
rl.close();
|
|
200749
202038
|
process.exit(1);
|
|
200750
202039
|
}
|
|
200751
|
-
if (!
|
|
202040
|
+
if (!statSync9(absolutePath2).isDirectory()) {
|
|
200752
202041
|
console.error(`
|
|
200753
202042
|
\u2717 Path is not a directory: ${projectPath}`);
|
|
200754
202043
|
rl.close();
|
|
@@ -200793,7 +202082,7 @@ async function runProjectAdd(name, path6, options = {}) {
|
|
|
200793
202082
|
`);
|
|
200794
202083
|
process.exit(1);
|
|
200795
202084
|
}
|
|
200796
|
-
if (!
|
|
202085
|
+
if (!statSync9(absolutePath).isDirectory()) {
|
|
200797
202086
|
console.error(`
|
|
200798
202087
|
\u2717 Path is not a directory: ${projectPath}
|
|
200799
202088
|
`);
|
|
@@ -201130,7 +202419,7 @@ __export(init_exports, {
|
|
|
201130
202419
|
import { existsSync as existsSync53, mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, readFileSync as readFileSync25 } from "node:fs";
|
|
201131
202420
|
import { join as join73, resolve as resolve45, basename as basename20 } from "node:path";
|
|
201132
202421
|
import { exec as exec13 } from "node:child_process";
|
|
201133
|
-
import { promisify as
|
|
202422
|
+
import { promisify as promisify19 } from "node:util";
|
|
201134
202423
|
async function runInit(options = {}) {
|
|
201135
202424
|
const cwd = options.path ? resolve45(options.path) : process.cwd();
|
|
201136
202425
|
const fusionDir = join73(cwd, ".fusion");
|
|
@@ -201345,7 +202634,7 @@ var init_init = __esm({
|
|
|
201345
202634
|
init_claude_skills_runner();
|
|
201346
202635
|
init_git();
|
|
201347
202636
|
init_skill_installation();
|
|
201348
|
-
execAsync11 =
|
|
202637
|
+
execAsync11 = promisify19(exec13);
|
|
201349
202638
|
}
|
|
201350
202639
|
});
|
|
201351
202640
|
|
|
@@ -201432,7 +202721,7 @@ var agent_import_exports = {};
|
|
|
201432
202721
|
__export(agent_import_exports, {
|
|
201433
202722
|
runAgentImport: () => runAgentImport
|
|
201434
202723
|
});
|
|
201435
|
-
import { existsSync as existsSync54, mkdirSync as mkdirSync12, readFileSync as readFileSync26, statSync as
|
|
202724
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync12, readFileSync as readFileSync26, statSync as statSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
201436
202725
|
import { resolve as resolve46 } from "node:path";
|
|
201437
202726
|
function slugifyPathSegment(input) {
|
|
201438
202727
|
if (!input || typeof input !== "string") {
|
|
@@ -201590,7 +202879,7 @@ async function runAgentImport(source, options) {
|
|
|
201590
202879
|
let skills = [];
|
|
201591
202880
|
let isPackageImport = false;
|
|
201592
202881
|
try {
|
|
201593
|
-
const sourceStats =
|
|
202882
|
+
const sourceStats = statSync10(sourcePath);
|
|
201594
202883
|
if (sourceStats.isDirectory()) {
|
|
201595
202884
|
const pkg = parseCompanyDirectory(sourcePath);
|
|
201596
202885
|
companyName = pkg.company?.name;
|
|
@@ -203007,7 +204296,7 @@ __export(update_exports, {
|
|
|
203007
204296
|
});
|
|
203008
204297
|
import { exec as exec14 } from "node:child_process";
|
|
203009
204298
|
import { existsSync as existsSync57, readFileSync as readFileSync27 } from "node:fs";
|
|
203010
|
-
import { promisify as
|
|
204299
|
+
import { promisify as promisify20 } from "node:util";
|
|
203011
204300
|
import { dirname as dirname35, resolve as resolve50 } from "node:path";
|
|
203012
204301
|
import { fileURLToPath as fileURLToPath16 } from "node:url";
|
|
203013
204302
|
function readOwnCliVersion() {
|
|
@@ -203173,7 +204462,7 @@ var init_update = __esm({
|
|
|
203173
204462
|
"src/commands/update.ts"() {
|
|
203174
204463
|
"use strict";
|
|
203175
204464
|
init_update_cache();
|
|
203176
|
-
execAsync12 =
|
|
204465
|
+
execAsync12 = promisify20(exec14);
|
|
203177
204466
|
REGISTRY_URL2 = "https://registry.npmjs.org/@runfusion%2Ffusion";
|
|
203178
204467
|
INSTALL_COMMAND = "npm install -g @runfusion/fusion@latest";
|
|
203179
204468
|
}
|
|
@@ -203840,8 +205129,8 @@ async function main() {
|
|
|
203840
205129
|
const name = nameIdx !== -1 && nameIdx + 1 < args.length ? args[nameIdx + 1] : void 0;
|
|
203841
205130
|
const pathIdx = args.indexOf("--path");
|
|
203842
205131
|
const path6 = pathIdx !== -1 && pathIdx + 1 < args.length ? args[pathIdx + 1] : void 0;
|
|
203843
|
-
const
|
|
203844
|
-
await runInit2({ name, path: path6, git });
|
|
205132
|
+
const git2 = args.includes("--git");
|
|
205133
|
+
await runInit2({ name, path: path6, git: git2 });
|
|
203845
205134
|
break;
|
|
203846
205135
|
}
|
|
203847
205136
|
case "dashboard": {
|