ai-whisper 0.2.1 → 0.4.0
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/broker-daemon.js +346 -103
- package/dist/bin/companion-agent.js +344 -101
- package/dist/bin/relay-monitor.js +352 -104
- package/dist/bin/whisper.js +7942 -6626
- package/dist/skills/ai-whisper-bugfix/SKILL.md +18 -0
- package/dist/skills/ai-whisper-ralph/SKILL.md +18 -0
- package/dist/skills/ai-whisper-sdd/SKILL.md +18 -0
- package/package.json +2 -2
- package/skills/ai-whisper-bugfix/SKILL.md +18 -0
- package/skills/ai-whisper-ralph/SKILL.md +18 -0
- package/skills/ai-whisper-sdd/SKILL.md +18 -0
|
@@ -549,6 +549,100 @@ function getCollab(db, collabId2) {
|
|
|
549
549
|
});
|
|
550
550
|
}
|
|
551
551
|
|
|
552
|
+
// ../broker/dist/storage/repositories/workflow-repository.js
|
|
553
|
+
function rowToRecord(row) {
|
|
554
|
+
return {
|
|
555
|
+
workflowId: row.workflow_id,
|
|
556
|
+
collabId: row.collab_id,
|
|
557
|
+
workflowType: row.workflow_type,
|
|
558
|
+
name: row.name,
|
|
559
|
+
specPath: row.spec_path,
|
|
560
|
+
roleBindings: JSON.parse(row.role_bindings),
|
|
561
|
+
status: row.status,
|
|
562
|
+
currentPhaseIndex: row.current_phase_index,
|
|
563
|
+
haltReason: row.halt_reason,
|
|
564
|
+
workflowContext: JSON.parse(row.workflow_context),
|
|
565
|
+
createdAt: row.created_at,
|
|
566
|
+
updatedAt: row.updated_at
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function insertWorkflow(db, input) {
|
|
570
|
+
db.prepare(`INSERT INTO workflows
|
|
571
|
+
(workflow_id, collab_id, workflow_type, name, spec_path, role_bindings, status,
|
|
572
|
+
current_phase_index, halt_reason, workflow_context, created_at, updated_at)
|
|
573
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`).run(input.workflowId, input.collabId, input.workflowType, input.name, input.specPath, JSON.stringify(input.roleBindings), input.status, input.currentPhaseIndex, JSON.stringify(input.workflowContext), input.now, input.now);
|
|
574
|
+
}
|
|
575
|
+
function getWorkflowById(db, workflowId) {
|
|
576
|
+
const row = db.prepare("SELECT * FROM workflows WHERE workflow_id = ?").get(workflowId);
|
|
577
|
+
return row ? rowToRecord(row) : null;
|
|
578
|
+
}
|
|
579
|
+
function listWorkflows(db, filter = {}) {
|
|
580
|
+
const clauses = [];
|
|
581
|
+
const args = [];
|
|
582
|
+
if (filter.collabId) {
|
|
583
|
+
clauses.push("collab_id = ?");
|
|
584
|
+
args.push(filter.collabId);
|
|
585
|
+
}
|
|
586
|
+
if (filter.status) {
|
|
587
|
+
clauses.push("status = ?");
|
|
588
|
+
args.push(filter.status);
|
|
589
|
+
}
|
|
590
|
+
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
591
|
+
const rows = db.prepare(`SELECT * FROM workflows ${where} ORDER BY created_at DESC`).all(...args);
|
|
592
|
+
return rows.map(rowToRecord);
|
|
593
|
+
}
|
|
594
|
+
function setWorkflowStatus(db, input) {
|
|
595
|
+
db.prepare(`UPDATE workflows
|
|
596
|
+
SET status = ?, halt_reason = ?, updated_at = ?
|
|
597
|
+
WHERE workflow_id = ?`).run(input.status, input.haltReason, input.now, input.workflowId);
|
|
598
|
+
}
|
|
599
|
+
function updateWorkflowContext(db, input) {
|
|
600
|
+
const existing = getWorkflowById(db, input.workflowId);
|
|
601
|
+
if (!existing) {
|
|
602
|
+
throw new Error(`updateWorkflowContext: unknown workflowId ${input.workflowId}`);
|
|
603
|
+
}
|
|
604
|
+
const merged = { ...existing.workflowContext, ...input.patch };
|
|
605
|
+
db.prepare("UPDATE workflows SET workflow_context = ?, updated_at = ? WHERE workflow_id = ?").run(JSON.stringify(merged), input.now, input.workflowId);
|
|
606
|
+
}
|
|
607
|
+
function incrementCurrentPhaseIndex(db, input) {
|
|
608
|
+
db.prepare(`UPDATE workflows
|
|
609
|
+
SET current_phase_index = current_phase_index + 1, updated_at = ?
|
|
610
|
+
WHERE workflow_id = ?`).run(input.now, input.workflowId);
|
|
611
|
+
}
|
|
612
|
+
function countActiveWorkflowsForCollab(db, collabId2) {
|
|
613
|
+
const row = db.prepare("SELECT COUNT(*) AS n FROM workflows WHERE collab_id = ? AND status IN ('running', 'paused')").get(collabId2);
|
|
614
|
+
return row.n;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ../broker/dist/runtime/workspace-snapshot.js
|
|
618
|
+
import { execFileSync } from "node:child_process";
|
|
619
|
+
function captureWorkspaceSnapshotSync(workspaceRoot) {
|
|
620
|
+
try {
|
|
621
|
+
const ref = execFileSync("git", ["-C", workspaceRoot, "stash", "create"], {
|
|
622
|
+
encoding: "utf8",
|
|
623
|
+
timeout: 1e4
|
|
624
|
+
}).trim();
|
|
625
|
+
if (ref)
|
|
626
|
+
return ref;
|
|
627
|
+
const head = execFileSync("git", ["-C", workspaceRoot, "rev-parse", "HEAD"], {
|
|
628
|
+
encoding: "utf8",
|
|
629
|
+
timeout: 1e4
|
|
630
|
+
}).trim();
|
|
631
|
+
return head || null;
|
|
632
|
+
} catch {
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
function diffChangedFilesSince(workspaceRoot, sinceRef) {
|
|
637
|
+
try {
|
|
638
|
+
const stdout = execFileSync("git", ["-C", workspaceRoot, "diff", "--name-only", sinceRef], { encoding: "utf8", timeout: 1e4 });
|
|
639
|
+
const files = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !l.startsWith(".ai-whisper/"));
|
|
640
|
+
return [...new Set(files)].sort();
|
|
641
|
+
} catch {
|
|
642
|
+
return [];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
552
646
|
// ../broker/dist/storage/repositories/reply-repository.js
|
|
553
647
|
function insertReply(db, reply) {
|
|
554
648
|
db.prepare(`INSERT INTO reply (
|
|
@@ -1014,7 +1108,7 @@ function upsertRelayTurnState(db, input) {
|
|
|
1014
1108
|
}
|
|
1015
1109
|
|
|
1016
1110
|
// ../broker/dist/storage/repositories/relay-capture-diagnostics-repository.js
|
|
1017
|
-
function
|
|
1111
|
+
function rowToRecord2(row) {
|
|
1018
1112
|
return {
|
|
1019
1113
|
captureId: row.capture_id,
|
|
1020
1114
|
handoffId: row.handoff_id,
|
|
@@ -1051,32 +1145,32 @@ function listCaptureDiagnosticsByCollab(db, collabId2, limit, opts) {
|
|
|
1051
1145
|
const rows2 = db.prepare(`SELECT * FROM relay_capture_diagnostics
|
|
1052
1146
|
WHERE collab_id = ?${filterClause}
|
|
1053
1147
|
ORDER BY created_at DESC`).all(collabId2, ...filterArgs);
|
|
1054
|
-
return rows2.map(
|
|
1148
|
+
return rows2.map(rowToRecord2);
|
|
1055
1149
|
}
|
|
1056
1150
|
const rows = db.prepare(`SELECT * FROM relay_capture_diagnostics
|
|
1057
1151
|
WHERE collab_id = ?${filterClause}
|
|
1058
1152
|
ORDER BY created_at DESC
|
|
1059
1153
|
LIMIT ?`).all(collabId2, ...filterArgs, limit);
|
|
1060
|
-
return rows.map(
|
|
1154
|
+
return rows.map(rowToRecord2);
|
|
1061
1155
|
}
|
|
1062
1156
|
function listCaptureDiagnosticsByCollabAndChain(db, collabId2, chainId, limit) {
|
|
1063
1157
|
if (limit === null) {
|
|
1064
1158
|
const rows2 = db.prepare(`SELECT * FROM relay_capture_diagnostics
|
|
1065
1159
|
WHERE collab_id = ? AND chain_id = ?
|
|
1066
1160
|
ORDER BY created_at DESC`).all(collabId2, chainId);
|
|
1067
|
-
return rows2.map(
|
|
1161
|
+
return rows2.map(rowToRecord2);
|
|
1068
1162
|
}
|
|
1069
1163
|
const rows = db.prepare(`SELECT * FROM relay_capture_diagnostics
|
|
1070
1164
|
WHERE collab_id = ? AND chain_id = ?
|
|
1071
1165
|
ORDER BY created_at DESC
|
|
1072
1166
|
LIMIT ?`).all(collabId2, chainId, limit);
|
|
1073
|
-
return rows.map(
|
|
1167
|
+
return rows.map(rowToRecord2);
|
|
1074
1168
|
}
|
|
1075
1169
|
function listCaptureDiagnosticsByHandoff(db, handoffId) {
|
|
1076
1170
|
const rows = db.prepare(`SELECT * FROM relay_capture_diagnostics
|
|
1077
1171
|
WHERE handoff_id = ?
|
|
1078
1172
|
ORDER BY created_at ASC`).all(handoffId);
|
|
1079
|
-
return rows.map(
|
|
1173
|
+
return rows.map(rowToRecord2);
|
|
1080
1174
|
}
|
|
1081
1175
|
function deleteCaptureDiagnosticsOlderThan(db, cutoffIso) {
|
|
1082
1176
|
const result = db.prepare("DELETE FROM relay_capture_diagnostics WHERE created_at < ?").run(cutoffIso);
|
|
@@ -1084,7 +1178,7 @@ function deleteCaptureDiagnosticsOlderThan(db, cutoffIso) {
|
|
|
1084
1178
|
}
|
|
1085
1179
|
|
|
1086
1180
|
// ../broker/dist/storage/repositories/relay-evaluator-diagnostics-repository.js
|
|
1087
|
-
function
|
|
1181
|
+
function rowToRecord3(row) {
|
|
1088
1182
|
return {
|
|
1089
1183
|
evaluatorId: row.evaluator_id,
|
|
1090
1184
|
handoffId: row.handoff_id,
|
|
@@ -1129,32 +1223,32 @@ function listEvaluatorDiagnosticsByCollab(db, collabId2, limit, opts) {
|
|
|
1129
1223
|
const rows2 = db.prepare(`SELECT * FROM relay_evaluator_diagnostics
|
|
1130
1224
|
WHERE collab_id = ?${filterClause}
|
|
1131
1225
|
ORDER BY created_at DESC`).all(collabId2, ...filterArgs);
|
|
1132
|
-
return rows2.map(
|
|
1226
|
+
return rows2.map(rowToRecord3);
|
|
1133
1227
|
}
|
|
1134
1228
|
const rows = db.prepare(`SELECT * FROM relay_evaluator_diagnostics
|
|
1135
1229
|
WHERE collab_id = ?${filterClause}
|
|
1136
1230
|
ORDER BY created_at DESC
|
|
1137
1231
|
LIMIT ?`).all(collabId2, ...filterArgs, limit);
|
|
1138
|
-
return rows.map(
|
|
1232
|
+
return rows.map(rowToRecord3);
|
|
1139
1233
|
}
|
|
1140
1234
|
function listEvaluatorDiagnosticsByCollabAndChain(db, collabId2, chainId, limit) {
|
|
1141
1235
|
if (limit === null) {
|
|
1142
1236
|
const rows2 = db.prepare(`SELECT * FROM relay_evaluator_diagnostics
|
|
1143
1237
|
WHERE collab_id = ? AND chain_id = ?
|
|
1144
1238
|
ORDER BY created_at DESC`).all(collabId2, chainId);
|
|
1145
|
-
return rows2.map(
|
|
1239
|
+
return rows2.map(rowToRecord3);
|
|
1146
1240
|
}
|
|
1147
1241
|
const rows = db.prepare(`SELECT * FROM relay_evaluator_diagnostics
|
|
1148
1242
|
WHERE collab_id = ? AND chain_id = ?
|
|
1149
1243
|
ORDER BY created_at DESC
|
|
1150
1244
|
LIMIT ?`).all(collabId2, chainId, limit);
|
|
1151
|
-
return rows.map(
|
|
1245
|
+
return rows.map(rowToRecord3);
|
|
1152
1246
|
}
|
|
1153
1247
|
function listEvaluatorDiagnosticsByHandoff(db, handoffId) {
|
|
1154
1248
|
const rows = db.prepare(`SELECT * FROM relay_evaluator_diagnostics
|
|
1155
1249
|
WHERE handoff_id = ?
|
|
1156
1250
|
ORDER BY created_at ASC`).all(handoffId);
|
|
1157
|
-
return rows.map(
|
|
1251
|
+
return rows.map(rowToRecord3);
|
|
1158
1252
|
}
|
|
1159
1253
|
function deleteEvaluatorDiagnosticsOlderThan(db, cutoffIso) {
|
|
1160
1254
|
const result = db.prepare("DELETE FROM relay_evaluator_diagnostics WHERE created_at < ?").run(cutoffIso);
|
|
@@ -1162,7 +1256,7 @@ function deleteEvaluatorDiagnosticsOlderThan(db, cutoffIso) {
|
|
|
1162
1256
|
}
|
|
1163
1257
|
|
|
1164
1258
|
// ../broker/dist/storage/repositories/relay-handoff-repository.js
|
|
1165
|
-
function
|
|
1259
|
+
function rowToRecord4(row) {
|
|
1166
1260
|
return {
|
|
1167
1261
|
handoffId: row.handoff_id,
|
|
1168
1262
|
collabId: row.collab_id,
|
|
@@ -1199,7 +1293,7 @@ function queryRelayHandoff(db, handoffId) {
|
|
|
1199
1293
|
FROM relay_handoff h
|
|
1200
1294
|
JOIN collab c ON c.collab_id = h.collab_id
|
|
1201
1295
|
WHERE h.handoff_id = ?`).get(handoffId);
|
|
1202
|
-
return row ?
|
|
1296
|
+
return row ? rowToRecord4(row) : null;
|
|
1203
1297
|
}
|
|
1204
1298
|
function createRelayHandoffTxn(db, input) {
|
|
1205
1299
|
return db.transaction(() => {
|
|
@@ -1351,7 +1445,7 @@ function queryLatestHandedBackHandoff(db, collabId2) {
|
|
|
1351
1445
|
WHERE h.collab_id = ? AND h.status = 'handed_back'
|
|
1352
1446
|
ORDER BY h.resolved_at DESC
|
|
1353
1447
|
LIMIT 1`).get(collabId2);
|
|
1354
|
-
return row ?
|
|
1448
|
+
return row ? rowToRecord4(row) : null;
|
|
1355
1449
|
}
|
|
1356
1450
|
function failRelayHandoffOnDisconnectTxn(db, input) {
|
|
1357
1451
|
db.transaction(() => {
|
|
@@ -1375,6 +1469,30 @@ function failRelayHandoffOnDisconnectTxn(db, input) {
|
|
|
1375
1469
|
}
|
|
1376
1470
|
})();
|
|
1377
1471
|
}
|
|
1472
|
+
function hasInFlightAcceptedHandoffForWorkflow(db, workflowId) {
|
|
1473
|
+
const row = db.prepare("SELECT COUNT(*) AS n FROM relay_handoff WHERE workflow_id = ? AND status = 'accepted'").get(workflowId);
|
|
1474
|
+
return row.n > 0;
|
|
1475
|
+
}
|
|
1476
|
+
function resetStrandedOrchestrationForWorkflow(db, workflowId) {
|
|
1477
|
+
const result = db.prepare(`UPDATE relay_handoff
|
|
1478
|
+
SET orchestrator_status = 'idle', orchestrator_claimed_at = NULL
|
|
1479
|
+
WHERE workflow_id = ?
|
|
1480
|
+
AND status = 'handed_back'
|
|
1481
|
+
AND orchestrator_status = 'pending'
|
|
1482
|
+
AND evaluator_verdict IS NULL`).run(workflowId);
|
|
1483
|
+
return result.changes;
|
|
1484
|
+
}
|
|
1485
|
+
function prependToPendingHandoffRequestText(db, input) {
|
|
1486
|
+
const row = db.prepare(`SELECT handoff_id, request_text FROM relay_handoff
|
|
1487
|
+
WHERE workflow_id = ? AND status = 'pending'
|
|
1488
|
+
ORDER BY created_at DESC LIMIT 1`).get(input.workflowId);
|
|
1489
|
+
if (!row)
|
|
1490
|
+
return false;
|
|
1491
|
+
db.prepare("UPDATE relay_handoff SET request_text = ?, last_activity_at = ? WHERE handoff_id = ?").run(`${input.prefix}
|
|
1492
|
+
|
|
1493
|
+
${row.request_text}`, input.now, row.handoff_id);
|
|
1494
|
+
return true;
|
|
1495
|
+
}
|
|
1378
1496
|
function claimRelayHandoffForOrchestrationTxn(db, input) {
|
|
1379
1497
|
const result = db.prepare(`UPDATE relay_handoff
|
|
1380
1498
|
SET orchestrator_status = 'pending',
|
|
@@ -1393,10 +1511,12 @@ function listRelayHandoffsPendingOrchestration(db, collabId2) {
|
|
|
1393
1511
|
c.orchestrator_max_rounds AS max_rounds
|
|
1394
1512
|
FROM relay_handoff h
|
|
1395
1513
|
JOIN collab c ON c.collab_id = h.collab_id
|
|
1514
|
+
LEFT JOIN workflows w ON w.workflow_id = h.workflow_id
|
|
1396
1515
|
WHERE h.collab_id = ?
|
|
1397
1516
|
AND h.status = 'handed_back'
|
|
1398
|
-
AND h.orchestrator_status = 'idle'
|
|
1399
|
-
|
|
1517
|
+
AND h.orchestrator_status = 'idle'
|
|
1518
|
+
AND (w.status IS NULL OR w.status <> 'paused')`).all(collabId2);
|
|
1519
|
+
return rows.map(rowToRecord4);
|
|
1400
1520
|
}
|
|
1401
1521
|
function createLoopRelayHandoffTxn(db, input) {
|
|
1402
1522
|
return db.transaction(() => {
|
|
@@ -1592,7 +1712,7 @@ function getHandoffWithWorkflowMetaById(db, handoffId) {
|
|
|
1592
1712
|
WHERE h.handoff_id = ?`).get(handoffId);
|
|
1593
1713
|
if (!row)
|
|
1594
1714
|
return null;
|
|
1595
|
-
const base =
|
|
1715
|
+
const base = rowToRecord4(row);
|
|
1596
1716
|
return {
|
|
1597
1717
|
...base,
|
|
1598
1718
|
handoffStep: row.handoff_step,
|
|
@@ -1694,7 +1814,8 @@ function cleanupOrchestrationOnShutdownTxn(db, input) {
|
|
|
1694
1814
|
import { basename } from "node:path";
|
|
1695
1815
|
function listActiveCollabSummaries(db, input) {
|
|
1696
1816
|
const nowMs = Date.parse(input.now ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
1697
|
-
const
|
|
1817
|
+
const base = Number.isFinite(nowMs) ? nowMs : Date.now();
|
|
1818
|
+
const cutoff = new Date(Math.max(0, base - input.sinceMs)).toISOString();
|
|
1698
1819
|
const eligible = db.prepare(`SELECT c.collab_id AS collabId,
|
|
1699
1820
|
COALESCE(MAX(h.last_activity_at), '') AS lastAct
|
|
1700
1821
|
FROM collab c
|
|
@@ -1733,7 +1854,8 @@ function buildCollabSummary(db, collabId2) {
|
|
|
1733
1854
|
const collab2 = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1734
1855
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1735
1856
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
1736
|
-
name, status, current_phase_index AS currentPhaseIndex
|
|
1857
|
+
name, status, current_phase_index AS currentPhaseIndex,
|
|
1858
|
+
created_at AS createdAt
|
|
1737
1859
|
FROM workflows WHERE collab_id = ?
|
|
1738
1860
|
ORDER BY (status = 'running') DESC, created_at DESC
|
|
1739
1861
|
LIMIT 1`).get(e.collabId);
|
|
@@ -1801,6 +1923,7 @@ function buildCollabSummary(db, collabId2) {
|
|
|
1801
1923
|
handoffState: turn?.handoffState ?? "idle"
|
|
1802
1924
|
},
|
|
1803
1925
|
sessions,
|
|
1926
|
+
workflowCreatedAt: wf?.createdAt ?? null,
|
|
1804
1927
|
lastActivityAt: runLastAct
|
|
1805
1928
|
};
|
|
1806
1929
|
}
|
|
@@ -1842,71 +1965,6 @@ function listRunCostRows(db, input) {
|
|
|
1842
1965
|
// ../broker/dist/control/workflow-control.js
|
|
1843
1966
|
import { randomUUID } from "node:crypto";
|
|
1844
1967
|
|
|
1845
|
-
// ../broker/dist/storage/repositories/workflow-repository.js
|
|
1846
|
-
function rowToRecord4(row) {
|
|
1847
|
-
return {
|
|
1848
|
-
workflowId: row.workflow_id,
|
|
1849
|
-
collabId: row.collab_id,
|
|
1850
|
-
workflowType: row.workflow_type,
|
|
1851
|
-
name: row.name,
|
|
1852
|
-
specPath: row.spec_path,
|
|
1853
|
-
roleBindings: JSON.parse(row.role_bindings),
|
|
1854
|
-
status: row.status,
|
|
1855
|
-
currentPhaseIndex: row.current_phase_index,
|
|
1856
|
-
haltReason: row.halt_reason,
|
|
1857
|
-
workflowContext: JSON.parse(row.workflow_context),
|
|
1858
|
-
createdAt: row.created_at,
|
|
1859
|
-
updatedAt: row.updated_at
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
function insertWorkflow(db, input) {
|
|
1863
|
-
db.prepare(`INSERT INTO workflows
|
|
1864
|
-
(workflow_id, collab_id, workflow_type, name, spec_path, role_bindings, status,
|
|
1865
|
-
current_phase_index, halt_reason, workflow_context, created_at, updated_at)
|
|
1866
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)`).run(input.workflowId, input.collabId, input.workflowType, input.name, input.specPath, JSON.stringify(input.roleBindings), input.status, input.currentPhaseIndex, JSON.stringify(input.workflowContext), input.now, input.now);
|
|
1867
|
-
}
|
|
1868
|
-
function getWorkflowById(db, workflowId) {
|
|
1869
|
-
const row = db.prepare("SELECT * FROM workflows WHERE workflow_id = ?").get(workflowId);
|
|
1870
|
-
return row ? rowToRecord4(row) : null;
|
|
1871
|
-
}
|
|
1872
|
-
function listWorkflows(db, filter = {}) {
|
|
1873
|
-
const clauses = [];
|
|
1874
|
-
const args = [];
|
|
1875
|
-
if (filter.collabId) {
|
|
1876
|
-
clauses.push("collab_id = ?");
|
|
1877
|
-
args.push(filter.collabId);
|
|
1878
|
-
}
|
|
1879
|
-
if (filter.status) {
|
|
1880
|
-
clauses.push("status = ?");
|
|
1881
|
-
args.push(filter.status);
|
|
1882
|
-
}
|
|
1883
|
-
const where = clauses.length ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
1884
|
-
const rows = db.prepare(`SELECT * FROM workflows ${where} ORDER BY created_at DESC`).all(...args);
|
|
1885
|
-
return rows.map(rowToRecord4);
|
|
1886
|
-
}
|
|
1887
|
-
function setWorkflowStatus(db, input) {
|
|
1888
|
-
db.prepare(`UPDATE workflows
|
|
1889
|
-
SET status = ?, halt_reason = ?, updated_at = ?
|
|
1890
|
-
WHERE workflow_id = ?`).run(input.status, input.haltReason, input.now, input.workflowId);
|
|
1891
|
-
}
|
|
1892
|
-
function updateWorkflowContext(db, input) {
|
|
1893
|
-
const existing = getWorkflowById(db, input.workflowId);
|
|
1894
|
-
if (!existing) {
|
|
1895
|
-
throw new Error(`updateWorkflowContext: unknown workflowId ${input.workflowId}`);
|
|
1896
|
-
}
|
|
1897
|
-
const merged = { ...existing.workflowContext, ...input.patch };
|
|
1898
|
-
db.prepare("UPDATE workflows SET workflow_context = ?, updated_at = ? WHERE workflow_id = ?").run(JSON.stringify(merged), input.now, input.workflowId);
|
|
1899
|
-
}
|
|
1900
|
-
function incrementCurrentPhaseIndex(db, input) {
|
|
1901
|
-
db.prepare(`UPDATE workflows
|
|
1902
|
-
SET current_phase_index = current_phase_index + 1, updated_at = ?
|
|
1903
|
-
WHERE workflow_id = ?`).run(input.now, input.workflowId);
|
|
1904
|
-
}
|
|
1905
|
-
function countRunningWorkflowsForCollab(db, collabId2) {
|
|
1906
|
-
const row = db.prepare("SELECT COUNT(*) AS n FROM workflows WHERE collab_id = ? AND status = 'running'").get(collabId2);
|
|
1907
|
-
return row.n;
|
|
1908
|
-
}
|
|
1909
|
-
|
|
1910
1968
|
// ../broker/dist/storage/repositories/workflow-phase-repository.js
|
|
1911
1969
|
function rowToRecord5(row) {
|
|
1912
1970
|
return {
|
|
@@ -2027,6 +2085,14 @@ Findings: (omit this block entirely if none)
|
|
|
2027
2085
|
Non-blocking risks:
|
|
2028
2086
|
- <quality risk that does NOT block this gate, or "None.">
|
|
2029
2087
|
--- end protocol ---`;
|
|
2088
|
+
var WORKFLOW_OPERATOR_CONTROL = `--- ai-whisper operator control ---
|
|
2089
|
+
If the operator interrupts you and asks to pause the workflow (e.g. "pause the workflow, I need to fix X"):
|
|
2090
|
+
1. Find the active workflow id: run \`whisper workflow list\`.
|
|
2091
|
+
2. Run \`whisper workflow pause <id>\`.
|
|
2092
|
+
3. Acknowledge and STOP working \u2014 do not start the next change.
|
|
2093
|
+
The operator will edit artifacts and later run \`whisper workflow resume <id> [--message "..."]\`; you will then receive a notice of what changed and must re-read those files before continuing.
|
|
2094
|
+
Provider note: the Codex CLI EXITS its session on Ctrl+C at an idle prompt (a mid-task Ctrl+C only interrupts the running task). The operator typically interrupts you while you are BUSY before issuing this instruction; do not assume Ctrl+C is a safe no-op.
|
|
2095
|
+
--- end operator control ---`;
|
|
2030
2096
|
var WORKFLOW_DIAGNOSIS_PROTOCOL = `--- ai-whisper diagnosis review protocol ---
|
|
2031
2097
|
You are the gatekeeper for an ai-whisper complex-bug-fixing DIAGNOSIS gate. No human is in the loop. Do NOT trust the implementer's diagnosis \u2014 verify it yourself. The gate stays shut until YOU independently agree on the root cause and that the fix is net-safe.
|
|
2032
2098
|
|
|
@@ -2265,10 +2331,21 @@ var COMPLEX_BUG_FIXING = {
|
|
|
2265
2331
|
}
|
|
2266
2332
|
]
|
|
2267
2333
|
};
|
|
2334
|
+
function withOperatorControl(def) {
|
|
2335
|
+
return {
|
|
2336
|
+
...def,
|
|
2337
|
+
phases: def.phases.map((p) => ({
|
|
2338
|
+
...p,
|
|
2339
|
+
kickoffTemplate: `${p.kickoffTemplate}
|
|
2340
|
+
|
|
2341
|
+
${WORKFLOW_OPERATOR_CONTROL}`
|
|
2342
|
+
}))
|
|
2343
|
+
};
|
|
2344
|
+
}
|
|
2268
2345
|
var REGISTRY = {
|
|
2269
|
-
[SPEC_DRIVEN_DEVELOPMENT.type]: SPEC_DRIVEN_DEVELOPMENT,
|
|
2270
|
-
[RALPH_LOOP.type]: RALPH_LOOP,
|
|
2271
|
-
[COMPLEX_BUG_FIXING.type]: COMPLEX_BUG_FIXING
|
|
2346
|
+
[SPEC_DRIVEN_DEVELOPMENT.type]: withOperatorControl(SPEC_DRIVEN_DEVELOPMENT),
|
|
2347
|
+
[RALPH_LOOP.type]: withOperatorControl(RALPH_LOOP),
|
|
2348
|
+
[COMPLEX_BUG_FIXING.type]: withOperatorControl(COMPLEX_BUG_FIXING)
|
|
2272
2349
|
};
|
|
2273
2350
|
function ralphRunDir(workspaceRoot, workflowId) {
|
|
2274
2351
|
return join(workspaceRoot, ".ai-whisper", "ralph", workflowId);
|
|
@@ -2324,6 +2401,23 @@ function safeDerivePlanPath(specPath, createdAt) {
|
|
|
2324
2401
|
return `${dir}${stem}.plan.md`;
|
|
2325
2402
|
}
|
|
2326
2403
|
}
|
|
2404
|
+
function composeResumeNotice(input) {
|
|
2405
|
+
const hasFiles = input.changedFiles.length > 0;
|
|
2406
|
+
const hasMessage = !!input.message && input.message.trim().length > 0;
|
|
2407
|
+
if (!hasFiles && !hasMessage)
|
|
2408
|
+
return null;
|
|
2409
|
+
const lines = [];
|
|
2410
|
+
if (hasFiles) {
|
|
2411
|
+
lines.push("While paused, the operator modified these files:");
|
|
2412
|
+
for (const f of input.changedFiles)
|
|
2413
|
+
lines.push(` - ${f}`);
|
|
2414
|
+
lines.push("Re-read them before continuing.");
|
|
2415
|
+
}
|
|
2416
|
+
if (hasMessage)
|
|
2417
|
+
lines.push(`Operator note: ${input.message.trim()}`);
|
|
2418
|
+
lines.push("Re-evaluate whether your current direction still holds; correct course before proceeding.");
|
|
2419
|
+
return lines.join("\n");
|
|
2420
|
+
}
|
|
2327
2421
|
function createWorkflowControl(deps) {
|
|
2328
2422
|
const { db, events } = deps;
|
|
2329
2423
|
function createWorkflow(input) {
|
|
@@ -2346,8 +2440,9 @@ function createWorkflowControl(deps) {
|
|
|
2346
2440
|
}
|
|
2347
2441
|
const workflowId = `wf_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
2348
2442
|
const tx = db.transaction(() => {
|
|
2349
|
-
if (
|
|
2350
|
-
|
|
2443
|
+
if (countActiveWorkflowsForCollab(db, input.collabId) > 0) {
|
|
2444
|
+
const active = listWorkflows(db, { collabId: input.collabId }).find((w) => w.status === "running" || w.status === "paused");
|
|
2445
|
+
throw new Error(`another workflow is already active on this collab (${input.collabId})` + (active ? `: ${active.workflowId} (${active.status})` : ""));
|
|
2351
2446
|
}
|
|
2352
2447
|
insertWorkflow(db, {
|
|
2353
2448
|
workflowId,
|
|
@@ -2515,6 +2610,24 @@ function createWorkflowControl(deps) {
|
|
|
2515
2610
|
postmortemPath: bf.postmortemPath
|
|
2516
2611
|
});
|
|
2517
2612
|
}
|
|
2613
|
+
function consumeResumeNotice(input) {
|
|
2614
|
+
const wf = getWorkflowById(db, input.workflowId);
|
|
2615
|
+
const notice = wf?.workflowContext?.resumeNotice ?? null;
|
|
2616
|
+
if (notice) {
|
|
2617
|
+
updateWorkflowContext(db, {
|
|
2618
|
+
workflowId: input.workflowId,
|
|
2619
|
+
patch: { resumeNotice: null },
|
|
2620
|
+
now: input.now
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
return notice;
|
|
2624
|
+
}
|
|
2625
|
+
function withResumeNotice(workflowId, requestText, now) {
|
|
2626
|
+
const notice = consumeResumeNotice({ workflowId, now });
|
|
2627
|
+
return notice ? `${notice}
|
|
2628
|
+
|
|
2629
|
+
${requestText}` : requestText;
|
|
2630
|
+
}
|
|
2518
2631
|
function createContinuationHandoff(input) {
|
|
2519
2632
|
const handoffId = `ho_${randomUUID().replace(/-/g, "").slice(0, 16)}`;
|
|
2520
2633
|
insertWorkflowOwnedRelayHandoff(db, {
|
|
@@ -2522,7 +2635,9 @@ function createWorkflowControl(deps) {
|
|
|
2522
2635
|
collabId: input.workflow.collabId,
|
|
2523
2636
|
senderAgent: input.sender,
|
|
2524
2637
|
targetAgent: input.target,
|
|
2525
|
-
|
|
2638
|
+
// Prepend the one-time resume notice (if any) to the FIRST outgoing request
|
|
2639
|
+
// after a resume, then it is cleared — so the agent re-reads changed files.
|
|
2640
|
+
requestText: withResumeNotice(input.workflow.workflowId, input.requestText, input.now),
|
|
2526
2641
|
chainId: input.chain.chainId,
|
|
2527
2642
|
roundNumber: input.incrementRound ? input.chain.currentRound + 1 : input.chain.currentRound,
|
|
2528
2643
|
maxRounds: input.chain.maxRounds,
|
|
@@ -2582,7 +2697,9 @@ function createWorkflowControl(deps) {
|
|
|
2582
2697
|
collabId: input.workflow.collabId,
|
|
2583
2698
|
senderAgent: sender,
|
|
2584
2699
|
targetAgent: target,
|
|
2585
|
-
|
|
2700
|
+
// Prepend a pending resume notice (consumed once) so a phase-advance kickoff
|
|
2701
|
+
// after a resume also carries the operator's change notice.
|
|
2702
|
+
requestText: withResumeNotice(input.workflow.workflowId, kickoffText, input.now),
|
|
2586
2703
|
chainId,
|
|
2587
2704
|
roundNumber: 1,
|
|
2588
2705
|
maxRounds: phase.maxRounds,
|
|
@@ -3043,20 +3160,100 @@ ${findingsText}`;
|
|
|
3043
3160
|
reason: input.reason
|
|
3044
3161
|
});
|
|
3045
3162
|
}
|
|
3163
|
+
function maybeCaptureQuiesceSnapshot(input) {
|
|
3164
|
+
const wf = getWorkflowById(db, input.workflowId);
|
|
3165
|
+
if (!wf || wf.status !== "paused")
|
|
3166
|
+
return;
|
|
3167
|
+
if (wf.workflowContext.pauseSnapshotRef !== void 0) {
|
|
3168
|
+
return;
|
|
3169
|
+
}
|
|
3170
|
+
if (hasInFlightAcceptedHandoffForWorkflow(db, input.workflowId))
|
|
3171
|
+
return;
|
|
3172
|
+
const ref = deps.captureSnapshotRef ? deps.captureSnapshotRef(input.workflowId) : null;
|
|
3173
|
+
updateWorkflowContext(db, {
|
|
3174
|
+
workflowId: input.workflowId,
|
|
3175
|
+
patch: { pauseSnapshotRef: ref },
|
|
3176
|
+
now: input.now
|
|
3177
|
+
});
|
|
3178
|
+
}
|
|
3179
|
+
function pauseWorkflow(input) {
|
|
3180
|
+
const workflow = getWorkflowById(db, input.workflowId);
|
|
3181
|
+
if (!workflow) {
|
|
3182
|
+
throw new Error(`pauseWorkflow: unknown workflowId ${input.workflowId}`);
|
|
3183
|
+
}
|
|
3184
|
+
if (workflow.status !== "running") {
|
|
3185
|
+
throw new Error(`pauseWorkflow: workflow ${input.workflowId} is ${workflow.status}, only running workflows can be paused`);
|
|
3186
|
+
}
|
|
3187
|
+
const tx = db.transaction(() => {
|
|
3188
|
+
const current = getWorkflowById(db, input.workflowId);
|
|
3189
|
+
if (!current || current.status !== "running")
|
|
3190
|
+
return;
|
|
3191
|
+
setWorkflowStatus(db, {
|
|
3192
|
+
workflowId: input.workflowId,
|
|
3193
|
+
status: "paused",
|
|
3194
|
+
haltReason: null,
|
|
3195
|
+
now: input.now
|
|
3196
|
+
});
|
|
3197
|
+
updateWorkflowContext(db, {
|
|
3198
|
+
workflowId: input.workflowId,
|
|
3199
|
+
patch: { pausedAt: input.now },
|
|
3200
|
+
now: input.now
|
|
3201
|
+
});
|
|
3202
|
+
});
|
|
3203
|
+
tx.immediate();
|
|
3204
|
+
events.emit("workflow.paused", { workflowId: input.workflowId });
|
|
3205
|
+
maybeCaptureQuiesceSnapshot({ workflowId: input.workflowId, now: input.now });
|
|
3206
|
+
}
|
|
3207
|
+
function resumeHaltedWorkflow(workflow, now) {
|
|
3208
|
+
const tx = db.transaction(() => {
|
|
3209
|
+
const others = listWorkflows(db, { collabId: workflow.collabId }).filter((w) => w.workflowId !== workflow.workflowId && (w.status === "running" || w.status === "paused"));
|
|
3210
|
+
if (others.length > 0) {
|
|
3211
|
+
throw new Error(`resumeWorkflow: another workflow is already active on collab ${workflow.collabId}`);
|
|
3212
|
+
}
|
|
3213
|
+
setWorkflowStatus(db, {
|
|
3214
|
+
workflowId: workflow.workflowId,
|
|
3215
|
+
status: "running",
|
|
3216
|
+
haltReason: null,
|
|
3217
|
+
now
|
|
3218
|
+
});
|
|
3219
|
+
});
|
|
3220
|
+
tx.immediate();
|
|
3221
|
+
events.emit("workflow.resumed", {
|
|
3222
|
+
workflowId: workflow.workflowId,
|
|
3223
|
+
phaseIndex: workflow.currentPhaseIndex
|
|
3224
|
+
});
|
|
3225
|
+
}
|
|
3046
3226
|
function resumeWorkflow(input) {
|
|
3047
3227
|
const workflow = getWorkflowById(db, input.workflowId);
|
|
3048
3228
|
if (!workflow) {
|
|
3049
3229
|
throw new Error(`resumeWorkflow: unknown workflowId ${input.workflowId}`);
|
|
3050
3230
|
}
|
|
3051
|
-
if (workflow.status === "
|
|
3052
|
-
|
|
3231
|
+
if (workflow.status === "halted") {
|
|
3232
|
+
resumeHaltedWorkflow(workflow, input.now);
|
|
3233
|
+
return;
|
|
3053
3234
|
}
|
|
3054
|
-
if (workflow.status !== "
|
|
3055
|
-
throw new Error(`resumeWorkflow: workflow ${input.workflowId} is
|
|
3235
|
+
if (workflow.status !== "paused") {
|
|
3236
|
+
throw new Error(`resumeWorkflow: workflow ${input.workflowId} is ${workflow.status}, only paused or halted workflows can be resumed`);
|
|
3056
3237
|
}
|
|
3238
|
+
const ref = workflow.workflowContext.pauseSnapshotRef ?? null;
|
|
3239
|
+
const changedFiles = ref && deps.diffChangedFilesSinceSnapshot ? deps.diffChangedFilesSinceSnapshot(input.workflowId, ref) : [];
|
|
3240
|
+
const notice = composeResumeNotice({
|
|
3241
|
+
changedFiles,
|
|
3242
|
+
message: input.message ?? null
|
|
3243
|
+
});
|
|
3057
3244
|
const tx = db.transaction(() => {
|
|
3058
|
-
|
|
3059
|
-
|
|
3245
|
+
const others = listWorkflows(db, { collabId: workflow.collabId }).filter((w) => w.workflowId !== input.workflowId && (w.status === "running" || w.status === "paused"));
|
|
3246
|
+
if (others.length > 0) {
|
|
3247
|
+
throw new Error(`resumeWorkflow: another workflow is already active on collab ${workflow.collabId}`);
|
|
3248
|
+
}
|
|
3249
|
+
resetStrandedOrchestrationForWorkflow(db, input.workflowId);
|
|
3250
|
+
let bakedIntoPending = false;
|
|
3251
|
+
if (notice) {
|
|
3252
|
+
bakedIntoPending = prependToPendingHandoffRequestText(db, {
|
|
3253
|
+
workflowId: input.workflowId,
|
|
3254
|
+
prefix: notice,
|
|
3255
|
+
now: input.now
|
|
3256
|
+
});
|
|
3060
3257
|
}
|
|
3061
3258
|
setWorkflowStatus(db, {
|
|
3062
3259
|
workflowId: input.workflowId,
|
|
@@ -3064,6 +3261,15 @@ ${findingsText}`;
|
|
|
3064
3261
|
haltReason: null,
|
|
3065
3262
|
now: input.now
|
|
3066
3263
|
});
|
|
3264
|
+
updateWorkflowContext(db, {
|
|
3265
|
+
workflowId: input.workflowId,
|
|
3266
|
+
patch: {
|
|
3267
|
+
resumeNotice: bakedIntoPending ? null : notice,
|
|
3268
|
+
pausedAt: null,
|
|
3269
|
+
pauseSnapshotRef: null
|
|
3270
|
+
},
|
|
3271
|
+
now: input.now
|
|
3272
|
+
});
|
|
3067
3273
|
});
|
|
3068
3274
|
tx.immediate();
|
|
3069
3275
|
events.emit("workflow.resumed", {
|
|
@@ -3164,6 +3370,9 @@ ${findingsText}`;
|
|
|
3164
3370
|
beginPhaseRun,
|
|
3165
3371
|
applyOrchestratorVerdict,
|
|
3166
3372
|
haltWorkflow,
|
|
3373
|
+
pauseWorkflow,
|
|
3374
|
+
maybeCaptureQuiesceSnapshot,
|
|
3375
|
+
consumeResumeNotice,
|
|
3167
3376
|
resumeWorkflow,
|
|
3168
3377
|
cancelWorkflow,
|
|
3169
3378
|
getHandoffWithWorkflowMeta,
|
|
@@ -3179,8 +3388,32 @@ function buildEventId(kind, subjectId, timestamp) {
|
|
|
3179
3388
|
return `evt_${kind}_${subjectId}_${normalizeTimestampForEventId(timestamp)}`;
|
|
3180
3389
|
}
|
|
3181
3390
|
function createControlService(db, events) {
|
|
3182
|
-
const
|
|
3391
|
+
const resolveWorkspaceRoot = (workflowId) => {
|
|
3392
|
+
const wf = getWorkflowById(db, workflowId);
|
|
3393
|
+
if (!wf)
|
|
3394
|
+
return null;
|
|
3395
|
+
return getCollab(db, wf.collabId)?.workspaceRoot ?? null;
|
|
3396
|
+
};
|
|
3397
|
+
const workflowControl = createWorkflowControl({
|
|
3398
|
+
db,
|
|
3399
|
+
events,
|
|
3400
|
+
captureSnapshotRef: (workflowId) => {
|
|
3401
|
+
const root = resolveWorkspaceRoot(workflowId);
|
|
3402
|
+
return root ? captureWorkspaceSnapshotSync(root) : null;
|
|
3403
|
+
},
|
|
3404
|
+
diffChangedFilesSinceSnapshot: (workflowId, sinceRef) => {
|
|
3405
|
+
const root = resolveWorkspaceRoot(workflowId);
|
|
3406
|
+
return root ? diffChangedFilesSince(root, sinceRef) : [];
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
const isWorkflowDeliverySuspended = (handoffId) => {
|
|
3410
|
+
const meta = workflowControl.getHandoffWithWorkflowMeta(handoffId);
|
|
3411
|
+
if (!meta?.workflowId)
|
|
3412
|
+
return false;
|
|
3413
|
+
return workflowControl.getWorkflow(meta.workflowId)?.status === "paused";
|
|
3414
|
+
};
|
|
3183
3415
|
return Object.assign({
|
|
3416
|
+
isWorkflowDeliverySuspended,
|
|
3184
3417
|
startCollab(input) {
|
|
3185
3418
|
const collab2 = collabSchema.parse({
|
|
3186
3419
|
version: 1,
|
|
@@ -3808,6 +4041,8 @@ function createControlService(db, events) {
|
|
|
3808
4041
|
return createRelayHandoffTxn(db, input);
|
|
3809
4042
|
},
|
|
3810
4043
|
acceptRelayHandoff(input) {
|
|
4044
|
+
if (isWorkflowDeliverySuspended(input.handoffId))
|
|
4045
|
+
return;
|
|
3811
4046
|
return acceptRelayHandoffTxn(db, input);
|
|
3812
4047
|
},
|
|
3813
4048
|
deferRelayHandoff(input) {
|
|
@@ -3820,7 +4055,12 @@ function createControlService(db, events) {
|
|
|
3820
4055
|
return markRelayHandoffStaleTxn(db, input);
|
|
3821
4056
|
},
|
|
3822
4057
|
handoffBackRelay(input) {
|
|
3823
|
-
|
|
4058
|
+
const result = handoffBackRelayTxn(db, input);
|
|
4059
|
+
const meta = workflowControl.getHandoffWithWorkflowMeta(input.handoffId);
|
|
4060
|
+
if (meta?.workflowId && workflowControl.getWorkflow(meta.workflowId)?.status === "paused") {
|
|
4061
|
+
workflowControl.maybeCaptureQuiesceSnapshot({ workflowId: meta.workflowId, now: input.now });
|
|
4062
|
+
}
|
|
4063
|
+
return result;
|
|
3824
4064
|
},
|
|
3825
4065
|
failRelayHandoffOnDisconnect(input) {
|
|
3826
4066
|
return failRelayHandoffOnDisconnectTxn(db, input);
|
|
@@ -3832,6 +4072,8 @@ function createControlService(db, events) {
|
|
|
3832
4072
|
return queryLatestHandedBackHandoff(db, collabId2);
|
|
3833
4073
|
},
|
|
3834
4074
|
claimRelayHandoffForOrchestration(input) {
|
|
4075
|
+
if (isWorkflowDeliverySuspended(input.handoffId))
|
|
4076
|
+
return null;
|
|
3835
4077
|
return claimRelayHandoffForOrchestrationTxn(db, input);
|
|
3836
4078
|
},
|
|
3837
4079
|
listRelayHandoffsPendingOrchestration(collabId2) {
|
|
@@ -4285,8 +4527,9 @@ CREATE TABLE IF NOT EXISTS workflows (
|
|
|
4285
4527
|
updated_at TEXT NOT NULL
|
|
4286
4528
|
);
|
|
4287
4529
|
|
|
4530
|
+
DROP INDEX IF EXISTS workflows_one_running_per_collab;
|
|
4288
4531
|
CREATE UNIQUE INDEX IF NOT EXISTS workflows_one_running_per_collab
|
|
4289
|
-
ON workflows(collab_id) WHERE status
|
|
4532
|
+
ON workflows(collab_id) WHERE status IN ('running', 'paused');
|
|
4290
4533
|
|
|
4291
4534
|
CREATE TABLE IF NOT EXISTS workflow_phases (
|
|
4292
4535
|
phase_run_id TEXT PRIMARY KEY,
|
|
@@ -5820,7 +6063,7 @@ ${JSON.stringify(event.payload)}`;
|
|
|
5820
6063
|
}
|
|
5821
6064
|
|
|
5822
6065
|
// src/runtime/process-start-time.ts
|
|
5823
|
-
import { execFileSync } from "node:child_process";
|
|
6066
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
5824
6067
|
import { readFileSync } from "node:fs";
|
|
5825
6068
|
function readProcessStartTime(pid) {
|
|
5826
6069
|
if (process.platform === "linux") {
|
|
@@ -5834,7 +6077,7 @@ function readProcessStartTime(pid) {
|
|
|
5834
6077
|
}
|
|
5835
6078
|
if (process.platform === "darwin") {
|
|
5836
6079
|
try {
|
|
5837
|
-
const out =
|
|
6080
|
+
const out = execFileSync2("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
5838
6081
|
encoding: "utf8"
|
|
5839
6082
|
});
|
|
5840
6083
|
const trimmed = out.trim();
|