@open-code-review/cli 2.0.0 → 2.2.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/README.md +2 -0
- package/dist/dashboard/client/assets/{_basePickBy-B3ALyupE.js → _basePickBy-BBPb8BJA.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-b2RALAWc.js → _baseUniq-CFHdos6T.js} +1 -1
- package/dist/dashboard/client/assets/{arc-DcSVvhUd.js → arc-BKGGWA2F.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-BNUlmSCS.js → architectureDiagram-VXUJARFQ-B_ovNjX1.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-BmhiQVwa.js → blockDiagram-VD42YOAC-C2M-avVp.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-jyJ3WOv5.js → c4Diagram-YG6GDRKO-BtOBpAzH.js} +1 -1
- package/dist/dashboard/client/assets/channel-rgw7C1e7.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-x1dQU_s3.js → chunk-4BX2VUAB-Cz2EbHPl.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-CwbsE2XQ.js → chunk-55IACEB6-C8xpXw9G.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BaE7c-ti.js → chunk-B4BG7PRW-BSRfOovX.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-Bw5PUaMK.js → chunk-DI55MBZ5-CEUbYQWn.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-B7cF6P3s.js → chunk-FMBD7UC4-5xWP6GRj.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-OY4evNHd.js → chunk-QN33PNHL-DfNCVcy8.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-BpjQwIWz.js → chunk-QZHKN3VN--OdToKKu.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-D8b_Oq9B.js → chunk-TZMSLE5B-B_0K0Qso.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +1 -0
- package/dist/dashboard/client/assets/clone-Cz7hswqi.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-C-sfP8PN.js → cose-bilkent-S5V4N54A-Cc_Dmnxz.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-Cqfo0NRg.js → dagre-6UL2VRFP-DaAfvUXU.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-BR3ppxqI.js → diagram-PSM6KHXK-7idwN0rC.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-Dvcx6x3R.js → diagram-QEK2KX5R-D9j9H13n.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-DoyBLnVN.js → diagram-S2PKOQOG-SMF5SB0K.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-hy77l1cL.js → erDiagram-Q2GNP2WA-EVJ4Qa2F.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-Bz0B1rKM.js → flowDiagram-NV44I4VS-tZ7SFE77.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-CLgrZPoC.js → ganttDiagram-JELNMOA3-DFSqguY7.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-DwJ-1f-v.js → gitGraphDiagram-V2S2FVAM-CqHdP3HE.js} +1 -1
- package/dist/dashboard/client/assets/{graph-DDBMM_t2.js → graph-C0XnkNkk.js} +1 -1
- package/dist/dashboard/client/assets/{index-Cr9yEo_B.js → index-C3NEq704.js} +133 -138
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-Bhn1FmAk.js → infoDiagram-HS3SLOUP-DlXZo9U2.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CzGbjX1y.js → journeyDiagram-XKPGCS4Q-CgC8_7eN.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-Da77-WYk.js → kanban-definition-3W4ZIXB7-BMAw_jNp.js} +1 -1
- package/dist/dashboard/client/assets/{layout-CVwSB-GS.js → layout-XjM3Q-ka.js} +1 -1
- package/dist/dashboard/client/assets/{linear-CTRAc5Jn.js → linear-CMUrrr1X.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-Bjo170ax.js → mermaid-renderer-D2jYNs7K.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-B55C2odl.js → mindmap-definition-VGOIOE7T-CL4hv-vg.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-5lrQLrSz.js → pieDiagram-ADFJNKIX-DTqv-1h1.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-Bg55gC30.js → quadrantDiagram-AYHSOK5B-BpFlSW9N.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-CyR4YFJY.js → requirementDiagram-UZGBJVZJ-BqYqqXL4.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BVWKr9_-.js → sankeyDiagram-TZEHDZUN-kEI9kntR.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-D0AJg_tE.js → sequenceDiagram-WL72ISMW-Cnu_1j-N.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BuHpTgim.js → stateDiagram-FKZM4ZOC-BoC-rqoG.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-LDhpAmDd.js → timeline-definition-IT6M3QCI-CXMWuzDL.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-Dd4gjvUl.js → treemap-GDKQZRPO-o9ZFgpbJ.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-B9RDod39.js → xychartDiagram-PRI3JC2R-CfIuUpeA.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +1175 -450
- package/dist/index.js +1489 -312
- package/dist/lib/db/index.js +666 -48
- package/dist/lib/runtime-config.js +29 -13
- package/dist/lib/state/index.js +2196 -0
- package/package.json +9 -5
- package/dist/dashboard/client/assets/channel-D3J8-GF_.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-tkFUL-1Y.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-tkFUL-1Y.js +0 -1
- package/dist/dashboard/client/assets/clone-CkY5ajLr.js +0 -1
- package/dist/dashboard/client/assets/index-Z1pPudAt.css +0 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-DwAPhteN.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -1579,14 +1579,14 @@ var require_internal = __commonJS({
|
|
|
1579
1579
|
}
|
|
1580
1580
|
InternalCodec.prototype.encoder = InternalEncoder;
|
|
1581
1581
|
InternalCodec.prototype.decoder = InternalDecoder;
|
|
1582
|
-
var
|
|
1583
|
-
if (!
|
|
1584
|
-
|
|
1582
|
+
var StringDecoder2 = __require("string_decoder").StringDecoder;
|
|
1583
|
+
if (!StringDecoder2.prototype.end)
|
|
1584
|
+
StringDecoder2.prototype.end = function() {
|
|
1585
1585
|
};
|
|
1586
1586
|
function InternalDecoder(options, codec) {
|
|
1587
|
-
|
|
1587
|
+
StringDecoder2.call(this, codec.enc);
|
|
1588
1588
|
}
|
|
1589
|
-
InternalDecoder.prototype =
|
|
1589
|
+
InternalDecoder.prototype = StringDecoder2.prototype;
|
|
1590
1590
|
function InternalEncoder(options, codec) {
|
|
1591
1591
|
this.enc = codec.enc;
|
|
1592
1592
|
}
|
|
@@ -18410,10 +18410,10 @@ var require_view = __commonJS({
|
|
|
18410
18410
|
var debug = require_src()("express:view");
|
|
18411
18411
|
var path2 = __require("path");
|
|
18412
18412
|
var fs6 = __require("fs");
|
|
18413
|
-
var
|
|
18414
|
-
var
|
|
18413
|
+
var dirname15 = path2.dirname;
|
|
18414
|
+
var basename5 = path2.basename;
|
|
18415
18415
|
var extname = path2.extname;
|
|
18416
|
-
var
|
|
18416
|
+
var join20 = path2.join;
|
|
18417
18417
|
var resolve3 = path2.resolve;
|
|
18418
18418
|
module.exports = View;
|
|
18419
18419
|
function View(name, options) {
|
|
@@ -18449,8 +18449,8 @@ var require_view = __commonJS({
|
|
|
18449
18449
|
for (var i = 0; i < roots.length && !path3; i++) {
|
|
18450
18450
|
var root = roots[i];
|
|
18451
18451
|
var loc = resolve3(root, name);
|
|
18452
|
-
var dir =
|
|
18453
|
-
var file =
|
|
18452
|
+
var dir = dirname15(loc);
|
|
18453
|
+
var file = basename5(loc);
|
|
18454
18454
|
path3 = this.resolve(dir, file);
|
|
18455
18455
|
}
|
|
18456
18456
|
return path3;
|
|
@@ -18461,12 +18461,12 @@ var require_view = __commonJS({
|
|
|
18461
18461
|
};
|
|
18462
18462
|
View.prototype.resolve = function resolve4(dir, file) {
|
|
18463
18463
|
var ext = this.ext;
|
|
18464
|
-
var path3 =
|
|
18464
|
+
var path3 = join20(dir, file);
|
|
18465
18465
|
var stat = tryStat(path3);
|
|
18466
18466
|
if (stat && stat.isFile()) {
|
|
18467
18467
|
return path3;
|
|
18468
18468
|
}
|
|
18469
|
-
path3 =
|
|
18469
|
+
path3 = join20(dir, basename5(file, ext), "index" + ext);
|
|
18470
18470
|
stat = tryStat(path3);
|
|
18471
18471
|
if (stat && stat.isFile()) {
|
|
18472
18472
|
return path3;
|
|
@@ -18547,7 +18547,7 @@ var require_content_disposition = __commonJS({
|
|
|
18547
18547
|
"use strict";
|
|
18548
18548
|
module.exports = contentDisposition;
|
|
18549
18549
|
module.exports.parse = parse;
|
|
18550
|
-
var
|
|
18550
|
+
var basename5 = __require("path").basename;
|
|
18551
18551
|
var Buffer3 = require_safe_buffer().Buffer;
|
|
18552
18552
|
var ENCODE_URL_ATTR_CHAR_REGEXP = /[\x00-\x20"'()*,/:;<=>?@[\\\]{}\x7f]/g;
|
|
18553
18553
|
var HEX_ESCAPE_REGEXP = /%[0-9A-Fa-f]{2}/;
|
|
@@ -18583,9 +18583,9 @@ var require_content_disposition = __commonJS({
|
|
|
18583
18583
|
if (typeof fallback === "string" && NON_LATIN1_REGEXP.test(fallback)) {
|
|
18584
18584
|
throw new TypeError("fallback must be ISO-8859-1 string");
|
|
18585
18585
|
}
|
|
18586
|
-
var name =
|
|
18586
|
+
var name = basename5(filename);
|
|
18587
18587
|
var isQuotedString = TEXT_REGEXP.test(name);
|
|
18588
|
-
var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) :
|
|
18588
|
+
var fallbackName = typeof fallback !== "string" ? fallback && getlatin1(name) : basename5(fallback);
|
|
18589
18589
|
var hasFallback = typeof fallbackName === "string" && fallbackName !== name;
|
|
18590
18590
|
if (hasFallback || !isQuotedString || HEX_ESCAPE_REGEXP.test(name)) {
|
|
18591
18591
|
params["filename*"] = name;
|
|
@@ -19099,7 +19099,7 @@ var require_send = __commonJS({
|
|
|
19099
19099
|
var Stream = __require("stream");
|
|
19100
19100
|
var util = __require("util");
|
|
19101
19101
|
var extname = path2.extname;
|
|
19102
|
-
var
|
|
19102
|
+
var join20 = path2.join;
|
|
19103
19103
|
var normalize = path2.normalize;
|
|
19104
19104
|
var resolve3 = path2.resolve;
|
|
19105
19105
|
var sep = path2.sep;
|
|
@@ -19318,7 +19318,7 @@ var require_send = __commonJS({
|
|
|
19318
19318
|
return res;
|
|
19319
19319
|
}
|
|
19320
19320
|
parts = path3.split(sep);
|
|
19321
|
-
path3 = normalize(
|
|
19321
|
+
path3 = normalize(join20(root, path3));
|
|
19322
19322
|
} else {
|
|
19323
19323
|
if (UP_PATH_REGEXP.test(path3)) {
|
|
19324
19324
|
debug('malicious path "%s"', path3);
|
|
@@ -19453,7 +19453,7 @@ var require_send = __commonJS({
|
|
|
19453
19453
|
if (err) return self.onStatError(err);
|
|
19454
19454
|
return self.error(404);
|
|
19455
19455
|
}
|
|
19456
|
-
var p =
|
|
19456
|
+
var p = join20(path3, self._index[i]);
|
|
19457
19457
|
debug('stat "%s"', p);
|
|
19458
19458
|
fs6.stat(p, function(err2, stat) {
|
|
19459
19459
|
if (err2) return next(err2);
|
|
@@ -30483,10 +30483,152 @@ var init_open = __esm({
|
|
|
30483
30483
|
// src/server/index.ts
|
|
30484
30484
|
var import_express15 = __toESM(require_express2(), 1);
|
|
30485
30485
|
import { createServer } from "node:http";
|
|
30486
|
-
import { existsSync as
|
|
30487
|
-
import { join as
|
|
30486
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, mkdirSync as mkdirSync7 } from "node:fs";
|
|
30487
|
+
import { join as join19, dirname as dirname14, resolve as resolve2 } from "node:path";
|
|
30488
|
+
|
|
30489
|
+
// ../shared/platform/src/index.ts
|
|
30490
|
+
import {
|
|
30491
|
+
execFile,
|
|
30492
|
+
execFileSync,
|
|
30493
|
+
spawn
|
|
30494
|
+
} from "node:child_process";
|
|
30495
|
+
import { promisify } from "node:util";
|
|
30496
|
+
var execFilePromise = promisify(execFile);
|
|
30497
|
+
var isWindows = process.platform === "win32";
|
|
30498
|
+
function execBinary(binary, args, opts) {
|
|
30499
|
+
return execFileSync(binary, args, {
|
|
30500
|
+
...opts,
|
|
30501
|
+
shell: isWindows
|
|
30502
|
+
});
|
|
30503
|
+
}
|
|
30504
|
+
async function execBinaryAsync(binary, args, opts) {
|
|
30505
|
+
return execFilePromise(binary, args, {
|
|
30506
|
+
...opts,
|
|
30507
|
+
shell: isWindows
|
|
30508
|
+
});
|
|
30509
|
+
}
|
|
30510
|
+
function spawnBinary(binary, args, opts) {
|
|
30511
|
+
return spawn(binary, args, {
|
|
30512
|
+
...opts,
|
|
30513
|
+
...isWindows && { shell: true, windowsHide: true }
|
|
30514
|
+
});
|
|
30515
|
+
}
|
|
30516
|
+
function isProcessAlive(pid) {
|
|
30517
|
+
try {
|
|
30518
|
+
process.kill(pid, 0);
|
|
30519
|
+
return true;
|
|
30520
|
+
} catch (err) {
|
|
30521
|
+
return !(err instanceof Error && "code" in err && err.code === "ESRCH");
|
|
30522
|
+
}
|
|
30523
|
+
}
|
|
30524
|
+
function walkDescendants(rootPid) {
|
|
30525
|
+
if (isWindows) return { pids: [], psAvailable: false };
|
|
30526
|
+
let out;
|
|
30527
|
+
try {
|
|
30528
|
+
out = execFileSync("ps", ["-A", "-o", "pid=,ppid="], {
|
|
30529
|
+
encoding: "utf-8",
|
|
30530
|
+
timeout: 5e3
|
|
30531
|
+
});
|
|
30532
|
+
} catch {
|
|
30533
|
+
return { pids: [], psAvailable: false };
|
|
30534
|
+
}
|
|
30535
|
+
const children = /* @__PURE__ */ new Map();
|
|
30536
|
+
for (const line of out.split("\n")) {
|
|
30537
|
+
const m = line.trim().match(/^(\d+)\s+(\d+)$/);
|
|
30538
|
+
if (!m) continue;
|
|
30539
|
+
const pid = Number(m[1]);
|
|
30540
|
+
const ppid = Number(m[2]);
|
|
30541
|
+
if (!children.has(ppid)) children.set(ppid, []);
|
|
30542
|
+
children.get(ppid).push(pid);
|
|
30543
|
+
}
|
|
30544
|
+
const acc = [];
|
|
30545
|
+
const queue = [rootPid];
|
|
30546
|
+
const seen = /* @__PURE__ */ new Set([rootPid]);
|
|
30547
|
+
while (queue.length) {
|
|
30548
|
+
const p = queue.shift();
|
|
30549
|
+
for (const c of children.get(p) ?? []) {
|
|
30550
|
+
if (seen.has(c)) continue;
|
|
30551
|
+
seen.add(c);
|
|
30552
|
+
acc.push(c);
|
|
30553
|
+
queue.push(c);
|
|
30554
|
+
}
|
|
30555
|
+
}
|
|
30556
|
+
return { pids: acc, psAvailable: true };
|
|
30557
|
+
}
|
|
30558
|
+
function descendantPids(rootPid) {
|
|
30559
|
+
return walkDescendants(rootPid).pids;
|
|
30560
|
+
}
|
|
30561
|
+
function reapTree(rootPid, graceMs = 5e3) {
|
|
30562
|
+
if (isWindows) {
|
|
30563
|
+
try {
|
|
30564
|
+
execFileSync("taskkill", ["/PID", String(rootPid), "/T", "/F"], {
|
|
30565
|
+
timeout: 5e3
|
|
30566
|
+
});
|
|
30567
|
+
} catch {
|
|
30568
|
+
}
|
|
30569
|
+
return { signaled: 1, psAvailable: false };
|
|
30570
|
+
}
|
|
30571
|
+
const { pids: descendants, psAvailable } = walkDescendants(rootPid);
|
|
30572
|
+
if (!psAvailable) {
|
|
30573
|
+
console.warn(
|
|
30574
|
+
`[reapTree] 'ps' unavailable \u2014 signalling only the root (PID ${rootPid}); escaped descendants cannot be enumerated on this system.`
|
|
30575
|
+
);
|
|
30576
|
+
}
|
|
30577
|
+
const term = [...descendants, rootPid];
|
|
30578
|
+
for (const pid of term) {
|
|
30579
|
+
try {
|
|
30580
|
+
process.kill(pid, "SIGTERM");
|
|
30581
|
+
} catch {
|
|
30582
|
+
}
|
|
30583
|
+
}
|
|
30584
|
+
setTimeout(() => {
|
|
30585
|
+
const kill = [...descendantPids(rootPid), rootPid];
|
|
30586
|
+
let stragglers = 0;
|
|
30587
|
+
for (const pid of kill) {
|
|
30588
|
+
if (!isProcessAlive(pid)) continue;
|
|
30589
|
+
stragglers++;
|
|
30590
|
+
try {
|
|
30591
|
+
process.kill(pid, "SIGKILL");
|
|
30592
|
+
} catch {
|
|
30593
|
+
}
|
|
30594
|
+
}
|
|
30595
|
+
if (stragglers > 0) {
|
|
30596
|
+
console.warn(
|
|
30597
|
+
`[reapTree] ${stragglers} process(es) under PID ${rootPid} survived SIGTERM after ${graceMs}ms; sending SIGKILL \u2014 investigate a leaked daemon.`
|
|
30598
|
+
);
|
|
30599
|
+
}
|
|
30600
|
+
}, graceMs).unref();
|
|
30601
|
+
return { signaled: term.length, psAvailable };
|
|
30602
|
+
}
|
|
30603
|
+
var BUILTIN_ICON_MAP = {
|
|
30604
|
+
architect: "blocks",
|
|
30605
|
+
fullstack: "layers",
|
|
30606
|
+
reliability: "activity",
|
|
30607
|
+
"staff-engineer": "compass",
|
|
30608
|
+
principal: "crown",
|
|
30609
|
+
frontend: "layout",
|
|
30610
|
+
backend: "server",
|
|
30611
|
+
infrastructure: "cloud",
|
|
30612
|
+
performance: "gauge",
|
|
30613
|
+
accessibility: "accessibility",
|
|
30614
|
+
data: "database",
|
|
30615
|
+
devops: "rocket",
|
|
30616
|
+
dx: "terminal",
|
|
30617
|
+
mobile: "smartphone",
|
|
30618
|
+
security: "shield-alert",
|
|
30619
|
+
quality: "sparkles",
|
|
30620
|
+
testing: "test-tubes",
|
|
30621
|
+
ai: "bot",
|
|
30622
|
+
"docs-writer": "file-text"
|
|
30623
|
+
};
|
|
30624
|
+
function defaultIconFor(id, tier) {
|
|
30625
|
+
return BUILTIN_ICON_MAP[id] ?? (tier === "persona" ? "brain" : "user");
|
|
30626
|
+
}
|
|
30627
|
+
|
|
30628
|
+
// src/server/index.ts
|
|
30488
30629
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
30489
30630
|
import { randomBytes } from "node:crypto";
|
|
30631
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
30490
30632
|
import { Server as SocketIOServer } from "socket.io";
|
|
30491
30633
|
|
|
30492
30634
|
// src/server/services/ocr-resolver.ts
|
|
@@ -30510,37 +30652,98 @@ function resolveOcrDir(startDir) {
|
|
|
30510
30652
|
}
|
|
30511
30653
|
|
|
30512
30654
|
// ../cli/src/lib/db/index.ts
|
|
30513
|
-
import {
|
|
30514
|
-
|
|
30655
|
+
import {
|
|
30656
|
+
existsSync as existsSync5,
|
|
30657
|
+
mkdirSync as mkdirSync2,
|
|
30658
|
+
copyFileSync as copyFileSync2,
|
|
30659
|
+
statSync as statSync2,
|
|
30660
|
+
mkdtempSync,
|
|
30661
|
+
rmSync
|
|
30662
|
+
} from "node:fs";
|
|
30663
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
30664
|
+
|
|
30665
|
+
// ../cli/src/lib/db/engine.ts
|
|
30666
|
+
import { createRequire } from "node:module";
|
|
30667
|
+
|
|
30668
|
+
// ../cli/src/lib/runtime-checks.ts
|
|
30669
|
+
var NODE_FLOOR = { major: 22, minor: 5 };
|
|
30670
|
+
function isSupportedNode(version) {
|
|
30671
|
+
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
30672
|
+
return major > NODE_FLOOR.major || major === NODE_FLOOR.major && minor >= NODE_FLOOR.minor;
|
|
30673
|
+
}
|
|
30674
|
+
function nodeVersionGuardMessage(version) {
|
|
30675
|
+
return `
|
|
30676
|
+
Open Code Review requires Node.js >= ${NODE_FLOOR.major}.${NODE_FLOOR.minor} (it uses Node's built-in SQLite, \`node:sqlite\`).
|
|
30677
|
+
You have Node ${version}. Upgrade Node (e.g. \`nvm install 22 && nvm use 22\`) and re-run.
|
|
30678
|
+
|
|
30679
|
+
`;
|
|
30680
|
+
}
|
|
30681
|
+
function isSuppressibleSqliteWarning(warning) {
|
|
30682
|
+
const message = typeof warning === "string" ? warning : warning?.message;
|
|
30683
|
+
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
30684
|
+
}
|
|
30515
30685
|
|
|
30516
30686
|
// ../cli/src/lib/db/engine.ts
|
|
30517
|
-
|
|
30687
|
+
var SQLITE_BUSY = 5;
|
|
30688
|
+
var SQLITE_BUSY_SNAPSHOT = 261;
|
|
30518
30689
|
var BUSY_RETRY_ATTEMPTS = 5;
|
|
30519
30690
|
var BUSY_RETRY_BACKOFF_MS = 50;
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30691
|
+
var savepointName = (depth) => `ocr_sp_${depth}`;
|
|
30692
|
+
var nodeRequire = createRequire(import.meta.url);
|
|
30693
|
+
var _preconditionsApplied = false;
|
|
30694
|
+
function applyEnginePreconditions() {
|
|
30695
|
+
if (_preconditionsApplied) return;
|
|
30696
|
+
_preconditionsApplied = true;
|
|
30697
|
+
const originalEmitWarning = process.emitWarning.bind(process);
|
|
30698
|
+
process.emitWarning = (warning, ...args) => {
|
|
30699
|
+
if (isSuppressibleSqliteWarning(warning)) return;
|
|
30700
|
+
originalEmitWarning(warning, ...args);
|
|
30701
|
+
};
|
|
30702
|
+
}
|
|
30703
|
+
var _DatabaseSyncCtor;
|
|
30704
|
+
function newDatabase(path2) {
|
|
30705
|
+
if (!_DatabaseSyncCtor) {
|
|
30706
|
+
applyEnginePreconditions();
|
|
30707
|
+
try {
|
|
30708
|
+
_DatabaseSyncCtor = nodeRequire("node:sqlite").DatabaseSync;
|
|
30709
|
+
} catch (e) {
|
|
30710
|
+
if (!isSupportedNode(process.versions.node)) {
|
|
30711
|
+
throw new Error(nodeVersionGuardMessage(process.versions.node).trim());
|
|
30712
|
+
}
|
|
30713
|
+
throw e;
|
|
30714
|
+
}
|
|
30523
30715
|
}
|
|
30524
|
-
|
|
30525
|
-
|
|
30716
|
+
return new _DatabaseSyncCtor(path2);
|
|
30717
|
+
}
|
|
30718
|
+
function isBusyError(e) {
|
|
30719
|
+
const errcode = e?.errcode;
|
|
30720
|
+
return errcode === SQLITE_BUSY || errcode === SQLITE_BUSY_SNAPSHOT;
|
|
30526
30721
|
}
|
|
30722
|
+
var SLEEP_BUF = new Int32Array(new SharedArrayBuffer(4));
|
|
30527
30723
|
function sleepSync(ms) {
|
|
30528
|
-
Atomics.wait(
|
|
30724
|
+
Atomics.wait(SLEEP_BUF, 0, 0, ms);
|
|
30529
30725
|
}
|
|
30530
|
-
var
|
|
30726
|
+
var NodeSqliteAdapter = class {
|
|
30531
30727
|
raw;
|
|
30728
|
+
/**
|
|
30729
|
+
* Transaction nesting depth. `node:sqlite` has no transaction helper, so we
|
|
30730
|
+
* drive `BEGIN IMMEDIATE` ourselves and use SAVEPOINTs for nested calls
|
|
30731
|
+
* (better-sqlite3 did this automatically). 0 = no transaction open.
|
|
30732
|
+
*/
|
|
30733
|
+
txnDepth = 0;
|
|
30532
30734
|
constructor(db) {
|
|
30533
30735
|
this.raw = db;
|
|
30534
30736
|
}
|
|
30535
30737
|
exec(sql, params) {
|
|
30536
30738
|
const stmt = this.raw.prepare(sql);
|
|
30537
|
-
|
|
30739
|
+
const cols = stmt.columns();
|
|
30740
|
+
if (cols.length === 0) {
|
|
30538
30741
|
stmt.run(...params ?? []);
|
|
30539
30742
|
return [];
|
|
30540
30743
|
}
|
|
30541
|
-
|
|
30542
|
-
const values = stmt.
|
|
30543
|
-
return values.length > 0 ? [{ columns, values }] : [];
|
|
30744
|
+
stmt.setReturnArrays(true);
|
|
30745
|
+
const values = stmt.all(...params ?? []);
|
|
30746
|
+
return values.length > 0 ? [{ columns: cols.map((c) => c.name), values }] : [];
|
|
30544
30747
|
}
|
|
30545
30748
|
run(sql, params) {
|
|
30546
30749
|
if (params !== void 0) {
|
|
@@ -30553,34 +30756,93 @@ var BetterSqliteAdapter = class {
|
|
|
30553
30756
|
return this.raw.prepare(sql);
|
|
30554
30757
|
}
|
|
30555
30758
|
transaction(fn) {
|
|
30556
|
-
|
|
30557
|
-
|
|
30759
|
+
return this.txnDepth > 0 ? this.runNested(fn) : this.runOuter(fn);
|
|
30760
|
+
}
|
|
30761
|
+
/**
|
|
30762
|
+
* Nested call: a SAVEPOINT within the outer transaction's write lock. No
|
|
30763
|
+
* busy-retry — the outer transaction already holds the lock. The savepoint
|
|
30764
|
+
* lets the inner block roll back independently while the outer continues.
|
|
30765
|
+
*/
|
|
30766
|
+
runNested(fn) {
|
|
30767
|
+
const name = savepointName(this.txnDepth);
|
|
30768
|
+
this.raw.exec(`SAVEPOINT ${name}`);
|
|
30769
|
+
this.txnDepth++;
|
|
30770
|
+
try {
|
|
30771
|
+
const result = fn();
|
|
30772
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
30773
|
+
return result;
|
|
30774
|
+
} catch (e) {
|
|
30558
30775
|
try {
|
|
30559
|
-
|
|
30776
|
+
this.raw.exec(`ROLLBACK TO ${name}`);
|
|
30777
|
+
this.raw.exec(`RELEASE ${name}`);
|
|
30778
|
+
} catch {
|
|
30779
|
+
}
|
|
30780
|
+
throw e;
|
|
30781
|
+
} finally {
|
|
30782
|
+
this.txnDepth--;
|
|
30783
|
+
}
|
|
30784
|
+
}
|
|
30785
|
+
/**
|
|
30786
|
+
* Outer transaction: `BEGIN IMMEDIATE` acquires the write lock up front so
|
|
30787
|
+
* cross-process writers serialize cleanly under WAL instead of failing late
|
|
30788
|
+
* on upgrade. `busy_timeout` covers most contention; a bounded synchronous
|
|
30789
|
+
* retry absorbs the residual SQLITE_BUSY (another connection holds the lock
|
|
30790
|
+
* past the timeout, or BUSY_SNAPSHOT). Non-busy errors and the final attempt
|
|
30791
|
+
* re-throw so genuine failures propagate.
|
|
30792
|
+
*/
|
|
30793
|
+
runOuter(fn) {
|
|
30794
|
+
for (let attempt = 0; attempt < BUSY_RETRY_ATTEMPTS; attempt++) {
|
|
30795
|
+
try {
|
|
30796
|
+
return this.runOnce(fn);
|
|
30560
30797
|
} catch (e) {
|
|
30561
|
-
if (!isBusyError(e) || attempt
|
|
30798
|
+
if (!isBusyError(e) || attempt === BUSY_RETRY_ATTEMPTS - 1) throw e;
|
|
30562
30799
|
sleepSync(BUSY_RETRY_BACKOFF_MS);
|
|
30563
30800
|
}
|
|
30564
30801
|
}
|
|
30802
|
+
throw new Error("transaction retry budget exhausted");
|
|
30803
|
+
}
|
|
30804
|
+
/** One `BEGIN IMMEDIATE` / `COMMIT` / `ROLLBACK` lifecycle. */
|
|
30805
|
+
runOnce(fn) {
|
|
30806
|
+
this.raw.exec("BEGIN IMMEDIATE");
|
|
30807
|
+
this.txnDepth = 1;
|
|
30808
|
+
try {
|
|
30809
|
+
const result = fn();
|
|
30810
|
+
this.raw.exec("COMMIT");
|
|
30811
|
+
return result;
|
|
30812
|
+
} catch (e) {
|
|
30813
|
+
try {
|
|
30814
|
+
this.raw.exec("ROLLBACK");
|
|
30815
|
+
} catch {
|
|
30816
|
+
}
|
|
30817
|
+
throw e;
|
|
30818
|
+
} finally {
|
|
30819
|
+
this.txnDepth = 0;
|
|
30820
|
+
}
|
|
30565
30821
|
}
|
|
30566
30822
|
pragma(source) {
|
|
30567
|
-
|
|
30823
|
+
this.raw.exec(`PRAGMA ${source}`);
|
|
30824
|
+
return void 0;
|
|
30568
30825
|
}
|
|
30569
30826
|
close() {
|
|
30570
30827
|
try {
|
|
30571
|
-
this.raw.
|
|
30828
|
+
this.raw.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
30572
30829
|
} catch {
|
|
30573
30830
|
}
|
|
30574
|
-
|
|
30831
|
+
try {
|
|
30832
|
+
this.raw.close();
|
|
30833
|
+
} catch (e) {
|
|
30834
|
+
const message = e?.message ?? "";
|
|
30835
|
+
if (!/database is not open/i.test(message)) throw e;
|
|
30836
|
+
}
|
|
30575
30837
|
}
|
|
30576
30838
|
};
|
|
30577
30839
|
function openEngine(dbPath) {
|
|
30578
|
-
const native =
|
|
30579
|
-
native.
|
|
30580
|
-
native.
|
|
30581
|
-
native.
|
|
30582
|
-
native.
|
|
30583
|
-
return new
|
|
30840
|
+
const native = newDatabase(dbPath);
|
|
30841
|
+
native.exec("PRAGMA journal_mode = WAL");
|
|
30842
|
+
native.exec("PRAGMA foreign_keys = ON");
|
|
30843
|
+
native.exec("PRAGMA busy_timeout = 5000");
|
|
30844
|
+
native.exec("PRAGMA synchronous = NORMAL");
|
|
30845
|
+
return new NodeSqliteAdapter(native);
|
|
30584
30846
|
}
|
|
30585
30847
|
|
|
30586
30848
|
// ../cli/src/lib/db/migrations.ts
|
|
@@ -31047,6 +31309,35 @@ var MIGRATIONS = [
|
|
|
31047
31309
|
db.run("DROP INDEX IF EXISTS idx_command_executions_parent;");
|
|
31048
31310
|
db.run("ALTER TABLE command_executions DROP COLUMN parent_id;");
|
|
31049
31311
|
}
|
|
31312
|
+
},
|
|
31313
|
+
{
|
|
31314
|
+
version: 14,
|
|
31315
|
+
description: "Self-heal markdown_artifacts duplication: collapse NULL-round duplicate rows and add a NULL-safe unique index so the dedup bug cannot recur",
|
|
31316
|
+
// The table's `UNIQUE(session_id, artifact_type, round_number, file_path)`
|
|
31317
|
+
// never deduped session-level artifacts because SQLite treats NULL ≠ NULL,
|
|
31318
|
+
// and the writer used `INSERT OR REPLACE` — so every re-parse of a
|
|
31319
|
+
// NULL-round artifact (context.md, map.md, …) appended a duplicate (one
|
|
31320
|
+
// context.md reached 775 identical rows, ~177 MB). The writer is now an
|
|
31321
|
+
// explicit UPDATE-or-INSERT; this migration heals existing DBs and adds a
|
|
31322
|
+
// NULL-collapsing unique index as a DB-level backstop.
|
|
31323
|
+
//
|
|
31324
|
+
// Orphan-row sweep (FK-dangling children from the pre-FK-enforcement era)
|
|
31325
|
+
// is intentionally NOT done here — it needs `PRAGMA foreign_keys = OFF`,
|
|
31326
|
+
// which is a no-op inside the migration transaction. `ocr db doctor --fix`
|
|
31327
|
+
// performs it outside a transaction.
|
|
31328
|
+
run: (db) => {
|
|
31329
|
+
db.run(`
|
|
31330
|
+
DELETE FROM markdown_artifacts
|
|
31331
|
+
WHERE rowid NOT IN (
|
|
31332
|
+
SELECT MAX(rowid) FROM markdown_artifacts
|
|
31333
|
+
GROUP BY session_id, artifact_type, IFNULL(round_number, -1), file_path
|
|
31334
|
+
)
|
|
31335
|
+
`);
|
|
31336
|
+
db.run(`
|
|
31337
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_markdown_artifacts_logical
|
|
31338
|
+
ON markdown_artifacts(session_id, artifact_type, IFNULL(round_number, -1), file_path)
|
|
31339
|
+
`);
|
|
31340
|
+
}
|
|
31050
31341
|
}
|
|
31051
31342
|
];
|
|
31052
31343
|
function columnExists(db, table, column) {
|
|
@@ -31361,9 +31652,28 @@ function sqliteUtcMs(ts) {
|
|
|
31361
31652
|
}
|
|
31362
31653
|
|
|
31363
31654
|
// ../cli/src/lib/state/exit-codes.ts
|
|
31655
|
+
var STATE_EXIT = {
|
|
31656
|
+
OK: 0,
|
|
31657
|
+
USAGE: 2,
|
|
31658
|
+
AMBIGUOUS: 3,
|
|
31659
|
+
NOT_FOUND: 4,
|
|
31660
|
+
ILLEGAL_TRANSITION: 5,
|
|
31661
|
+
INVARIANT_UNMET: 6,
|
|
31662
|
+
SCHEMA_INVALID: 7,
|
|
31663
|
+
/** Database was locked past the bounded retry budget (SQLITE_BUSY). */
|
|
31664
|
+
BUSY: 8
|
|
31665
|
+
};
|
|
31666
|
+
var StateError = class extends Error {
|
|
31667
|
+
constructor(code, message) {
|
|
31668
|
+
super(message);
|
|
31669
|
+
this.code = code;
|
|
31670
|
+
this.name = "StateError";
|
|
31671
|
+
}
|
|
31672
|
+
};
|
|
31364
31673
|
var CANCELLED_EXIT_CODE = -2;
|
|
31365
31674
|
var ORPHAN_EXIT_CODE = -3;
|
|
31366
31675
|
var CASCADE_CLOSE_EXIT_CODE = -4;
|
|
31676
|
+
var WATCHDOG_DEADLINE_EXIT_CODE = -5;
|
|
31367
31677
|
|
|
31368
31678
|
// ../cli/src/lib/db/agent-sessions.ts
|
|
31369
31679
|
var NOTE_ORPHAN_PREFIX = "orphaned by liveness sweep";
|
|
@@ -31558,9 +31868,84 @@ function sweepStaleSessions(db, thresholdSeconds) {
|
|
|
31558
31868
|
return { closedSessionIds: rows.map((r) => r.id) };
|
|
31559
31869
|
}
|
|
31560
31870
|
|
|
31871
|
+
// ../cli/src/lib/db/maintenance.ts
|
|
31872
|
+
import {
|
|
31873
|
+
existsSync as existsSync3,
|
|
31874
|
+
readdirSync,
|
|
31875
|
+
statSync,
|
|
31876
|
+
unlinkSync,
|
|
31877
|
+
copyFileSync
|
|
31878
|
+
} from "node:fs";
|
|
31879
|
+
import { dirname as dirname3, join as join3, basename } from "node:path";
|
|
31880
|
+
var ONE_HOUR_MS = 60 * 60 * 1e3;
|
|
31881
|
+
function scanOrphanTempFiles(dataDir) {
|
|
31882
|
+
let entries;
|
|
31883
|
+
try {
|
|
31884
|
+
entries = readdirSync(dataDir);
|
|
31885
|
+
} catch {
|
|
31886
|
+
return [];
|
|
31887
|
+
}
|
|
31888
|
+
const out = [];
|
|
31889
|
+
for (const name of entries) {
|
|
31890
|
+
const m = name.match(/^ocr\.db\.(\d+)\.tmp$/);
|
|
31891
|
+
if (!m) continue;
|
|
31892
|
+
const pid = Number(m[1]);
|
|
31893
|
+
let ageMs = 0;
|
|
31894
|
+
try {
|
|
31895
|
+
ageMs = Date.now() - statSync(join3(dataDir, name)).mtimeMs;
|
|
31896
|
+
} catch {
|
|
31897
|
+
continue;
|
|
31898
|
+
}
|
|
31899
|
+
const alive = isProcessAlive(pid);
|
|
31900
|
+
out.push({
|
|
31901
|
+
name,
|
|
31902
|
+
pid,
|
|
31903
|
+
ageMs,
|
|
31904
|
+
// Reapable only when the writer PID is dead AND the file is old enough
|
|
31905
|
+
// that no live mid-write could plausibly own it.
|
|
31906
|
+
reapable: !alive && ageMs > ONE_HOUR_MS
|
|
31907
|
+
});
|
|
31908
|
+
}
|
|
31909
|
+
return out;
|
|
31910
|
+
}
|
|
31911
|
+
function reapOrphanDbFiles(dataDir) {
|
|
31912
|
+
const reaped = [];
|
|
31913
|
+
for (const f of scanOrphanTempFiles(dataDir)) {
|
|
31914
|
+
if (!f.reapable) continue;
|
|
31915
|
+
try {
|
|
31916
|
+
unlinkSync(join3(dataDir, f.name));
|
|
31917
|
+
reaped.push(f.name);
|
|
31918
|
+
} catch {
|
|
31919
|
+
}
|
|
31920
|
+
}
|
|
31921
|
+
return reaped;
|
|
31922
|
+
}
|
|
31923
|
+
var SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
31924
|
+
function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
31925
|
+
let entries;
|
|
31926
|
+
try {
|
|
31927
|
+
entries = readdirSync(execLogsDir);
|
|
31928
|
+
} catch {
|
|
31929
|
+
return [];
|
|
31930
|
+
}
|
|
31931
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
31932
|
+
const reaped = [];
|
|
31933
|
+
for (const name of entries) {
|
|
31934
|
+
if (!name.endsWith(".log")) continue;
|
|
31935
|
+
const full = join3(execLogsDir, name);
|
|
31936
|
+
try {
|
|
31937
|
+
if (statSync(full).mtimeMs > cutoff) continue;
|
|
31938
|
+
unlinkSync(full);
|
|
31939
|
+
reaped.push(name);
|
|
31940
|
+
} catch {
|
|
31941
|
+
}
|
|
31942
|
+
}
|
|
31943
|
+
return reaped;
|
|
31944
|
+
}
|
|
31945
|
+
|
|
31561
31946
|
// ../cli/src/lib/db/command-log.ts
|
|
31562
|
-
import { appendFileSync, existsSync as
|
|
31563
|
-
import { dirname as
|
|
31947
|
+
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
31948
|
+
import { dirname as dirname4, join as join4 } from "node:path";
|
|
31564
31949
|
import { randomUUID } from "node:crypto";
|
|
31565
31950
|
var CACHE_DIR = ".cache";
|
|
31566
31951
|
var FILENAME = "command-history.jsonl";
|
|
@@ -31571,16 +31956,16 @@ function generateCommandUid() {
|
|
|
31571
31956
|
return randomUUID();
|
|
31572
31957
|
}
|
|
31573
31958
|
function cacheDir(ocrDir) {
|
|
31574
|
-
return
|
|
31959
|
+
return join4(ocrDir, "data", CACHE_DIR);
|
|
31575
31960
|
}
|
|
31576
31961
|
function commandLogPath(ocrDir) {
|
|
31577
|
-
return
|
|
31962
|
+
return join4(cacheDir(ocrDir), FILENAME);
|
|
31578
31963
|
}
|
|
31579
31964
|
function appendCommandLog(ocrDir, entry) {
|
|
31580
31965
|
try {
|
|
31581
31966
|
const filePath = commandLogPath(ocrDir);
|
|
31582
|
-
const dir =
|
|
31583
|
-
if (!
|
|
31967
|
+
const dir = dirname4(filePath);
|
|
31968
|
+
if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
|
|
31584
31969
|
const line = JSON.stringify(entry) + "\n";
|
|
31585
31970
|
appendFileSync(filePath, line, { encoding: "utf-8" });
|
|
31586
31971
|
if (approxLineCount >= 0) approxLineCount++;
|
|
@@ -31590,7 +31975,7 @@ function appendCommandLog(ocrDir, entry) {
|
|
|
31590
31975
|
}
|
|
31591
31976
|
function readCommandLog(ocrDir) {
|
|
31592
31977
|
const filePath = commandLogPath(ocrDir);
|
|
31593
|
-
if (!
|
|
31978
|
+
if (!existsSync4(filePath)) return [];
|
|
31594
31979
|
const content = readFileSync(filePath, "utf-8");
|
|
31595
31980
|
const entries = [];
|
|
31596
31981
|
for (const line of content.split("\n")) {
|
|
@@ -31660,11 +32045,11 @@ var V2_SCHEMA_VERSION = 12;
|
|
|
31660
32045
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
31661
32046
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
31662
32047
|
const bakPath = `${dbPath}.bak.v${fromVersion}`;
|
|
31663
|
-
if (
|
|
32048
|
+
if (existsSync5(bakPath)) return bakPath;
|
|
31664
32049
|
try {
|
|
31665
|
-
if (!
|
|
32050
|
+
if (!existsSync5(dbPath) || statSync2(dbPath).size === 0) return null;
|
|
31666
32051
|
db.pragma("wal_checkpoint(TRUNCATE)");
|
|
31667
|
-
|
|
32052
|
+
copyFileSync2(dbPath, bakPath);
|
|
31668
32053
|
return bakPath;
|
|
31669
32054
|
} catch {
|
|
31670
32055
|
return null;
|
|
@@ -31698,8 +32083,8 @@ async function openDatabase(dbPath) {
|
|
|
31698
32083
|
if (cached) {
|
|
31699
32084
|
return cached;
|
|
31700
32085
|
}
|
|
31701
|
-
const dir =
|
|
31702
|
-
if (!
|
|
32086
|
+
const dir = dirname5(dbPath);
|
|
32087
|
+
if (!existsSync5(dir)) {
|
|
31703
32088
|
mkdirSync2(dir, { recursive: true });
|
|
31704
32089
|
}
|
|
31705
32090
|
const db = openEngine(dbPath);
|
|
@@ -31707,11 +32092,11 @@ async function openDatabase(dbPath) {
|
|
|
31707
32092
|
return db;
|
|
31708
32093
|
}
|
|
31709
32094
|
async function ensureDatabase(ocrDir) {
|
|
31710
|
-
const dataDir =
|
|
31711
|
-
if (!
|
|
32095
|
+
const dataDir = join5(ocrDir, "data");
|
|
32096
|
+
if (!existsSync5(dataDir)) {
|
|
31712
32097
|
mkdirSync2(dataDir, { recursive: true });
|
|
31713
32098
|
}
|
|
31714
|
-
const dbPath =
|
|
32099
|
+
const dbPath = join5(dataDir, "ocr.db");
|
|
31715
32100
|
const db = await openDatabase(dbPath);
|
|
31716
32101
|
let before = 0;
|
|
31717
32102
|
try {
|
|
@@ -31739,7 +32124,7 @@ async function ensureDatabase(ocrDir) {
|
|
|
31739
32124
|
return db;
|
|
31740
32125
|
}
|
|
31741
32126
|
function walCheckpointTruncate(dbPath) {
|
|
31742
|
-
if (!
|
|
32127
|
+
if (!existsSync5(dbPath)) {
|
|
31743
32128
|
return "skipped";
|
|
31744
32129
|
}
|
|
31745
32130
|
const cached = connections.get(dbPath);
|
|
@@ -31760,7 +32145,7 @@ function walCheckpointTruncate(dbPath) {
|
|
|
31760
32145
|
return "failed";
|
|
31761
32146
|
} finally {
|
|
31762
32147
|
try {
|
|
31763
|
-
transient?.
|
|
32148
|
+
transient?.close();
|
|
31764
32149
|
} catch {
|
|
31765
32150
|
}
|
|
31766
32151
|
}
|
|
@@ -31774,11 +32159,11 @@ function closeDatabase(dbPath) {
|
|
|
31774
32159
|
}
|
|
31775
32160
|
|
|
31776
32161
|
// src/server/db.ts
|
|
31777
|
-
import { join as
|
|
32162
|
+
import { join as join6 } from "node:path";
|
|
31778
32163
|
var cachedDb = null;
|
|
31779
32164
|
var cachedDbPath = null;
|
|
31780
32165
|
async function openDb(ocrDir) {
|
|
31781
|
-
const dbPath =
|
|
32166
|
+
const dbPath = join6(ocrDir, "data", "ocr.db");
|
|
31782
32167
|
const db = await ensureDatabase(ocrDir);
|
|
31783
32168
|
cachedDb = db;
|
|
31784
32169
|
cachedDbPath = dbPath;
|
|
@@ -32960,37 +33345,9 @@ function createStatsRouter(db) {
|
|
|
32960
33345
|
// src/server/routes/commands.ts
|
|
32961
33346
|
var import_express8 = __toESM(require_express2(), 1);
|
|
32962
33347
|
|
|
32963
|
-
// ../shared/platform/src/index.ts
|
|
32964
|
-
import {
|
|
32965
|
-
execFile,
|
|
32966
|
-
execFileSync,
|
|
32967
|
-
spawn
|
|
32968
|
-
} from "node:child_process";
|
|
32969
|
-
import { promisify } from "node:util";
|
|
32970
|
-
var execFilePromise = promisify(execFile);
|
|
32971
|
-
var isWindows = process.platform === "win32";
|
|
32972
|
-
function execBinary(binary, args, opts) {
|
|
32973
|
-
return execFileSync(binary, args, {
|
|
32974
|
-
...opts,
|
|
32975
|
-
shell: isWindows
|
|
32976
|
-
});
|
|
32977
|
-
}
|
|
32978
|
-
async function execBinaryAsync(binary, args, opts) {
|
|
32979
|
-
return execFilePromise(binary, args, {
|
|
32980
|
-
...opts,
|
|
32981
|
-
shell: isWindows
|
|
32982
|
-
});
|
|
32983
|
-
}
|
|
32984
|
-
function spawnBinary(binary, args, opts) {
|
|
32985
|
-
return spawn(binary, args, {
|
|
32986
|
-
...opts,
|
|
32987
|
-
...isWindows && { shell: true, windowsHide: true }
|
|
32988
|
-
});
|
|
32989
|
-
}
|
|
32990
|
-
|
|
32991
33348
|
// src/server/socket/command-runner.ts
|
|
32992
|
-
import { readFileSync as
|
|
32993
|
-
import { dirname as
|
|
33349
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "node:fs";
|
|
33350
|
+
import { dirname as dirname7, join as join12 } from "node:path";
|
|
32994
33351
|
|
|
32995
33352
|
// src/server/services/command-outcome.ts
|
|
32996
33353
|
function deriveCommandOutcome(exitCode, completeness) {
|
|
@@ -32998,6 +33355,7 @@ function deriveCommandOutcome(exitCode, completeness) {
|
|
|
32998
33355
|
if (exitCode === CANCELLED_EXIT_CODE || exitCode === CASCADE_CLOSE_EXIT_CODE) {
|
|
32999
33356
|
return "cancelled";
|
|
33000
33357
|
}
|
|
33358
|
+
if (exitCode === WATCHDOG_DEADLINE_EXIT_CODE) return "failed";
|
|
33001
33359
|
if (exitCode !== 0) return "failed";
|
|
33002
33360
|
if (completeness === null || completeness === "complete") return "success";
|
|
33003
33361
|
return "incomplete";
|
|
@@ -33026,7 +33384,50 @@ function getWorkflowCompletenessForExecution(db, executionId) {
|
|
|
33026
33384
|
|
|
33027
33385
|
// src/server/services/ai-cli/index.ts
|
|
33028
33386
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
33029
|
-
import { join as
|
|
33387
|
+
import { join as join9 } from "node:path";
|
|
33388
|
+
|
|
33389
|
+
// src/server/services/ai-cli/helpers.ts
|
|
33390
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, openSync, closeSync } from "node:fs";
|
|
33391
|
+
import { tmpdir } from "node:os";
|
|
33392
|
+
import { join as join7 } from "node:path";
|
|
33393
|
+
function buildFileStdio(stdin, logFile) {
|
|
33394
|
+
if (!logFile) {
|
|
33395
|
+
return { stdio: [stdin, "pipe", "pipe"], logFd: null, logPath: void 0 };
|
|
33396
|
+
}
|
|
33397
|
+
const logFd = openSync(logFile, "a");
|
|
33398
|
+
return { stdio: [stdin, logFd, logFd], logFd, logPath: logFile };
|
|
33399
|
+
}
|
|
33400
|
+
function closeFileStdio(logFd) {
|
|
33401
|
+
if (logFd === null) return;
|
|
33402
|
+
try {
|
|
33403
|
+
closeSync(logFd);
|
|
33404
|
+
} catch {
|
|
33405
|
+
}
|
|
33406
|
+
}
|
|
33407
|
+
function formatToolDetail(tool, input) {
|
|
33408
|
+
switch (tool) {
|
|
33409
|
+
case "Read":
|
|
33410
|
+
return `Reading ${input["file_path"] ?? "file"}`;
|
|
33411
|
+
case "Write":
|
|
33412
|
+
return `Writing ${input["file_path"] ?? "file"}`;
|
|
33413
|
+
case "Edit":
|
|
33414
|
+
return `Editing ${input["file_path"] ?? "file"}`;
|
|
33415
|
+
case "Grep":
|
|
33416
|
+
return `Searching for "${input["pattern"] ?? "..."}"`;
|
|
33417
|
+
case "Glob":
|
|
33418
|
+
return `Finding files matching ${input["pattern"] ?? "..."}`;
|
|
33419
|
+
case "Bash": {
|
|
33420
|
+
let cmd = input["command"] ?? "...";
|
|
33421
|
+
cmd = cmd.replace(/^cd\s+\S+\s*&&\s*/, "");
|
|
33422
|
+
return `Running: ${cmd.slice(0, 120)}`;
|
|
33423
|
+
}
|
|
33424
|
+
case "Agent":
|
|
33425
|
+
return `Spawning agent: ${input["description"] ?? "..."}`;
|
|
33426
|
+
default:
|
|
33427
|
+
return `Using ${tool}`;
|
|
33428
|
+
}
|
|
33429
|
+
}
|
|
33430
|
+
var TEMP_BASE = join7(tmpdir(), "ocr-ai-prompts");
|
|
33030
33431
|
|
|
33031
33432
|
// src/server/socket/env.ts
|
|
33032
33433
|
var ENV_ALLOWLIST = [
|
|
@@ -33099,6 +33500,8 @@ var ClaudeCodeAdapter = class {
|
|
|
33099
33500
|
// Claude Code subagent definitions support per-subagent model frontmatter,
|
|
33100
33501
|
// so per-task model overrides are honored at the host level.
|
|
33101
33502
|
supportsPerTaskModel = true;
|
|
33503
|
+
// Claude Code can spawn reviewer sub-agents via its Task tool.
|
|
33504
|
+
supportsSubagentSpawn = true;
|
|
33102
33505
|
buildResumeArgs(vendorSessionId) {
|
|
33103
33506
|
return buildResumeArgs("claude", vendorSessionId);
|
|
33104
33507
|
}
|
|
@@ -33139,15 +33542,25 @@ var ClaudeCodeAdapter = class {
|
|
|
33139
33542
|
if (opts.model) {
|
|
33140
33543
|
flags.push("--model", opts.model);
|
|
33141
33544
|
}
|
|
33545
|
+
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33546
|
+
"pipe",
|
|
33547
|
+
isWorkflow ? opts.logFile : void 0
|
|
33548
|
+
);
|
|
33142
33549
|
const proc = spawnBinary("claude", flags, {
|
|
33143
33550
|
cwd: opts.cwd,
|
|
33144
33551
|
env: { ...cleanEnv(), ...opts.env ?? {} },
|
|
33145
33552
|
detached: isWorkflow,
|
|
33146
|
-
stdio
|
|
33553
|
+
stdio
|
|
33147
33554
|
});
|
|
33555
|
+
closeFileStdio(logFd);
|
|
33556
|
+
if (isWorkflow) proc.unref();
|
|
33148
33557
|
proc.stdin?.write(opts.prompt);
|
|
33149
33558
|
proc.stdin?.end();
|
|
33150
|
-
return {
|
|
33559
|
+
return {
|
|
33560
|
+
process: proc,
|
|
33561
|
+
detached: isWorkflow,
|
|
33562
|
+
...logPath ? { logPath } : {}
|
|
33563
|
+
};
|
|
33151
33564
|
}
|
|
33152
33565
|
async listModels() {
|
|
33153
33566
|
try {
|
|
@@ -33306,6 +33719,13 @@ var ClaudeLineParser = class {
|
|
|
33306
33719
|
const message = typeof parsed["message"] === "string" ? parsed["message"] : "Agent error";
|
|
33307
33720
|
events.push({ type: "error", source: "agent", message });
|
|
33308
33721
|
}
|
|
33722
|
+
if (type === "result") {
|
|
33723
|
+
events.push({
|
|
33724
|
+
type: "result",
|
|
33725
|
+
isError: parsed["is_error"] === true,
|
|
33726
|
+
subtype: typeof parsed["subtype"] === "string" ? parsed["subtype"] : void 0
|
|
33727
|
+
});
|
|
33728
|
+
}
|
|
33309
33729
|
return events;
|
|
33310
33730
|
}
|
|
33311
33731
|
};
|
|
@@ -33341,6 +33761,9 @@ var OpenCodeAdapter = class {
|
|
|
33341
33761
|
// until OpenCode adds per-task model support; OCR surfaces a warning to
|
|
33342
33762
|
// the user when this happens.
|
|
33343
33763
|
supportsPerTaskModel = false;
|
|
33764
|
+
// OpenCode exposes a sub-agent primitive (`--agent`), so reviewer sub-agents
|
|
33765
|
+
// can be spawned in-agent (uniform model — see supportsPerTaskModel above).
|
|
33766
|
+
supportsSubagentSpawn = true;
|
|
33344
33767
|
buildResumeArgs(vendorSessionId) {
|
|
33345
33768
|
return buildResumeArgs("opencode", vendorSessionId);
|
|
33346
33769
|
}
|
|
@@ -33378,13 +33801,23 @@ var OpenCodeAdapter = class {
|
|
|
33378
33801
|
if (opts.model) {
|
|
33379
33802
|
args.push("--model", opts.model);
|
|
33380
33803
|
}
|
|
33804
|
+
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33805
|
+
"ignore",
|
|
33806
|
+
isWorkflow ? opts.logFile : void 0
|
|
33807
|
+
);
|
|
33381
33808
|
const proc = spawnBinary("opencode", args, {
|
|
33382
33809
|
cwd: opts.cwd,
|
|
33383
33810
|
env: { ...cleanEnv(), ...opts.env ?? {} },
|
|
33384
33811
|
detached: isWorkflow,
|
|
33385
|
-
stdio
|
|
33812
|
+
stdio
|
|
33386
33813
|
});
|
|
33387
|
-
|
|
33814
|
+
closeFileStdio(logFd);
|
|
33815
|
+
if (isWorkflow) proc.unref();
|
|
33816
|
+
return {
|
|
33817
|
+
process: proc,
|
|
33818
|
+
detached: isWorkflow,
|
|
33819
|
+
...logPath ? { logPath } : {}
|
|
33820
|
+
};
|
|
33388
33821
|
}
|
|
33389
33822
|
async listModels() {
|
|
33390
33823
|
try {
|
|
@@ -33515,20 +33948,20 @@ function extractToolOutput(part) {
|
|
|
33515
33948
|
// src/server/services/event-journal.ts
|
|
33516
33949
|
import {
|
|
33517
33950
|
createWriteStream,
|
|
33518
|
-
existsSync as
|
|
33519
|
-
mkdirSync as
|
|
33951
|
+
existsSync as existsSync6,
|
|
33952
|
+
mkdirSync as mkdirSync4,
|
|
33520
33953
|
readFileSync as readFileSync2
|
|
33521
33954
|
} from "node:fs";
|
|
33522
|
-
import { join as
|
|
33955
|
+
import { join as join8 } from "node:path";
|
|
33523
33956
|
function eventsDir(ocrDir) {
|
|
33524
|
-
const dir =
|
|
33525
|
-
if (!
|
|
33526
|
-
|
|
33957
|
+
const dir = join8(ocrDir, "data", "events");
|
|
33958
|
+
if (!existsSync6(dir)) {
|
|
33959
|
+
mkdirSync4(dir, { recursive: true });
|
|
33527
33960
|
}
|
|
33528
33961
|
return dir;
|
|
33529
33962
|
}
|
|
33530
33963
|
function eventJournalPath(ocrDir, executionId) {
|
|
33531
|
-
return
|
|
33964
|
+
return join8(eventsDir(ocrDir), `${executionId}.jsonl`);
|
|
33532
33965
|
}
|
|
33533
33966
|
var EventJournalAppender = class {
|
|
33534
33967
|
stream;
|
|
@@ -33565,7 +33998,7 @@ var EventJournalAppender = class {
|
|
|
33565
33998
|
};
|
|
33566
33999
|
function readEventJournal(ocrDir, executionId) {
|
|
33567
34000
|
const path2 = eventJournalPath(ocrDir, executionId);
|
|
33568
|
-
if (!
|
|
34001
|
+
if (!existsSync6(path2)) return [];
|
|
33569
34002
|
let raw;
|
|
33570
34003
|
try {
|
|
33571
34004
|
raw = readFileSync2(path2, "utf-8");
|
|
@@ -33585,38 +34018,10 @@ function readEventJournal(ocrDir, executionId) {
|
|
|
33585
34018
|
return events;
|
|
33586
34019
|
}
|
|
33587
34020
|
|
|
33588
|
-
// src/server/services/ai-cli/helpers.ts
|
|
33589
|
-
import { tmpdir } from "node:os";
|
|
33590
|
-
import { join as join7 } from "node:path";
|
|
33591
|
-
function formatToolDetail(tool, input) {
|
|
33592
|
-
switch (tool) {
|
|
33593
|
-
case "Read":
|
|
33594
|
-
return `Reading ${input["file_path"] ?? "file"}`;
|
|
33595
|
-
case "Write":
|
|
33596
|
-
return `Writing ${input["file_path"] ?? "file"}`;
|
|
33597
|
-
case "Edit":
|
|
33598
|
-
return `Editing ${input["file_path"] ?? "file"}`;
|
|
33599
|
-
case "Grep":
|
|
33600
|
-
return `Searching for "${input["pattern"] ?? "..."}"`;
|
|
33601
|
-
case "Glob":
|
|
33602
|
-
return `Finding files matching ${input["pattern"] ?? "..."}`;
|
|
33603
|
-
case "Bash": {
|
|
33604
|
-
let cmd = input["command"] ?? "...";
|
|
33605
|
-
cmd = cmd.replace(/^cd\s+\S+\s*&&\s*/, "");
|
|
33606
|
-
return `Running: ${cmd.slice(0, 120)}`;
|
|
33607
|
-
}
|
|
33608
|
-
case "Agent":
|
|
33609
|
-
return `Spawning agent: ${input["description"] ?? "..."}`;
|
|
33610
|
-
default:
|
|
33611
|
-
return `Using ${tool}`;
|
|
33612
|
-
}
|
|
33613
|
-
}
|
|
33614
|
-
var TEMP_BASE = join7(tmpdir(), "ocr-ai-prompts");
|
|
33615
|
-
|
|
33616
34021
|
// src/server/services/ai-cli/index.ts
|
|
33617
34022
|
function readAiCliPreference(ocrDir) {
|
|
33618
34023
|
try {
|
|
33619
|
-
const configPath =
|
|
34024
|
+
const configPath = join9(ocrDir, "config.yaml");
|
|
33620
34025
|
const content = readFileSync3(configPath, "utf-8");
|
|
33621
34026
|
const match = content.match(/^\s*ai_cli:\s*(\S+)/m);
|
|
33622
34027
|
const value = match?.[1] ?? "auto";
|
|
@@ -33725,31 +34130,245 @@ var AiCliService = class {
|
|
|
33725
34130
|
}
|
|
33726
34131
|
};
|
|
33727
34132
|
|
|
34133
|
+
// src/server/services/ai-cli/file-tailer.ts
|
|
34134
|
+
import { openSync as openSync2, readSync, closeSync as closeSync2, existsSync as existsSync7 } from "node:fs";
|
|
34135
|
+
import { StringDecoder } from "node:string_decoder";
|
|
34136
|
+
var DEFAULT_POLL_MS = 100;
|
|
34137
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
34138
|
+
var FileTailer = class {
|
|
34139
|
+
constructor(path2, onChunk, pollMs = DEFAULT_POLL_MS) {
|
|
34140
|
+
this.path = path2;
|
|
34141
|
+
this.onChunk = onChunk;
|
|
34142
|
+
this.pollMs = pollMs;
|
|
34143
|
+
}
|
|
34144
|
+
fd = null;
|
|
34145
|
+
offset = 0;
|
|
34146
|
+
decoder = new StringDecoder("utf8");
|
|
34147
|
+
timer = null;
|
|
34148
|
+
buf = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
34149
|
+
stopped = false;
|
|
34150
|
+
/** Begin polling for appended bytes. Idempotent. */
|
|
34151
|
+
start() {
|
|
34152
|
+
if (this.timer || this.stopped) return;
|
|
34153
|
+
this.timer = setInterval(() => this.poll(), this.pollMs);
|
|
34154
|
+
this.timer.unref?.();
|
|
34155
|
+
}
|
|
34156
|
+
ensureOpen() {
|
|
34157
|
+
if (this.fd !== null) return true;
|
|
34158
|
+
if (!existsSync7(this.path)) return false;
|
|
34159
|
+
try {
|
|
34160
|
+
this.fd = openSync2(this.path, "r");
|
|
34161
|
+
} catch {
|
|
34162
|
+
return false;
|
|
34163
|
+
}
|
|
34164
|
+
return true;
|
|
34165
|
+
}
|
|
34166
|
+
/** Read everything currently available from `offset` to EOF. */
|
|
34167
|
+
poll() {
|
|
34168
|
+
if (!this.ensureOpen()) return;
|
|
34169
|
+
let bytes;
|
|
34170
|
+
do {
|
|
34171
|
+
try {
|
|
34172
|
+
bytes = readSync(this.fd, this.buf, 0, this.buf.length, this.offset);
|
|
34173
|
+
} catch {
|
|
34174
|
+
return;
|
|
34175
|
+
}
|
|
34176
|
+
if (bytes > 0) {
|
|
34177
|
+
this.offset += bytes;
|
|
34178
|
+
const chunk = this.decoder.write(this.buf.subarray(0, bytes));
|
|
34179
|
+
if (chunk) this.onChunk(chunk);
|
|
34180
|
+
}
|
|
34181
|
+
} while (bytes === this.buf.length);
|
|
34182
|
+
}
|
|
34183
|
+
/**
|
|
34184
|
+
* Stop tailing: do one final drain to EOF, flush any partial multi-byte
|
|
34185
|
+
* remainder, and close the fd. Safe to call more than once. Synchronous so a
|
|
34186
|
+
* `close` handler can finalize the stream with no lost-tail race.
|
|
34187
|
+
*/
|
|
34188
|
+
stop() {
|
|
34189
|
+
if (this.stopped) return;
|
|
34190
|
+
this.stopped = true;
|
|
34191
|
+
if (this.timer) {
|
|
34192
|
+
clearInterval(this.timer);
|
|
34193
|
+
this.timer = null;
|
|
34194
|
+
}
|
|
34195
|
+
this.poll();
|
|
34196
|
+
const tail = this.decoder.end();
|
|
34197
|
+
if (tail) this.onChunk(tail);
|
|
34198
|
+
if (this.fd !== null) {
|
|
34199
|
+
try {
|
|
34200
|
+
closeSync2(this.fd);
|
|
34201
|
+
} catch {
|
|
34202
|
+
}
|
|
34203
|
+
this.fd = null;
|
|
34204
|
+
}
|
|
34205
|
+
}
|
|
34206
|
+
};
|
|
34207
|
+
|
|
33728
34208
|
// src/server/socket/cli-resolver.ts
|
|
33729
|
-
import { existsSync as
|
|
33730
|
-
import { dirname as
|
|
34209
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
34210
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
33731
34211
|
import { fileURLToPath } from "node:url";
|
|
33732
|
-
var __dirname =
|
|
34212
|
+
var __dirname = dirname6(fileURLToPath(import.meta.url));
|
|
33733
34213
|
function resolveLocalCli() {
|
|
33734
|
-
const parentDir =
|
|
33735
|
-
const bundledCli =
|
|
33736
|
-
if (
|
|
34214
|
+
const parentDir = join10(__dirname, "..");
|
|
34215
|
+
const bundledCli = join10(parentDir, "index.js");
|
|
34216
|
+
if (existsSync8(bundledCli) && existsSync8(join10(parentDir, "dashboard", "server.js"))) {
|
|
33737
34217
|
return bundledCli;
|
|
33738
34218
|
}
|
|
33739
34219
|
let dir = __dirname;
|
|
33740
34220
|
for (let i = 0; i < 8; i++) {
|
|
33741
|
-
if (
|
|
33742
|
-
const candidate =
|
|
33743
|
-
if (
|
|
34221
|
+
if (existsSync8(join10(dir, "nx.json"))) {
|
|
34222
|
+
const candidate = join10(dir, "packages", "cli", "dist", "index.js");
|
|
34223
|
+
if (existsSync8(candidate)) return candidate;
|
|
33744
34224
|
break;
|
|
33745
34225
|
}
|
|
33746
|
-
const parent =
|
|
34226
|
+
const parent = dirname6(dir);
|
|
33747
34227
|
if (parent === dir) break;
|
|
33748
34228
|
dir = parent;
|
|
33749
34229
|
}
|
|
33750
34230
|
return null;
|
|
33751
34231
|
}
|
|
33752
34232
|
|
|
34233
|
+
// ../cli/src/lib/state/projection.ts
|
|
34234
|
+
var REASON_EVENT_TYPES = [
|
|
34235
|
+
"session_aborted",
|
|
34236
|
+
"session_auto_closed_stale",
|
|
34237
|
+
"session_synced",
|
|
34238
|
+
"session_legacy_import"
|
|
34239
|
+
];
|
|
34240
|
+
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
34241
|
+
"session_closed",
|
|
34242
|
+
...REASON_EVENT_TYPES
|
|
34243
|
+
]);
|
|
34244
|
+
function hasCompletionInvariant(db, session) {
|
|
34245
|
+
const eventType = session.workflow_type === "map" ? "map_completed" : "round_completed";
|
|
34246
|
+
const round = session.workflow_type === "map" ? session.current_map_run : session.current_round;
|
|
34247
|
+
const r = db.exec(
|
|
34248
|
+
`SELECT 1 FROM orchestration_events
|
|
34249
|
+
WHERE session_id = ? AND event_type = ? AND round = ? LIMIT 1`,
|
|
34250
|
+
[session.id, eventType, round]
|
|
34251
|
+
);
|
|
34252
|
+
return (r[0]?.values.length ?? 0) > 0;
|
|
34253
|
+
}
|
|
34254
|
+
|
|
34255
|
+
// ../cli/src/lib/state/index.ts
|
|
34256
|
+
async function stateClose(params) {
|
|
34257
|
+
const { sessionId, ocrDir, abort } = params;
|
|
34258
|
+
const db = await ensureDatabase(ocrDir);
|
|
34259
|
+
const existing = getSession(db, sessionId);
|
|
34260
|
+
if (!existing) {
|
|
34261
|
+
throw new StateError(STATE_EXIT.NOT_FOUND, `Session not found: ${sessionId}`);
|
|
34262
|
+
}
|
|
34263
|
+
if (existing.status === "closed") {
|
|
34264
|
+
console.error(`[ocr] Session already closed: ${sessionId}`);
|
|
34265
|
+
return;
|
|
34266
|
+
}
|
|
34267
|
+
if (!abort && !hasCompletionInvariant(db, existing)) {
|
|
34268
|
+
const what = existing.workflow_type === "map" ? `map run ${existing.current_map_run} has no map_completed event` : `round ${existing.current_round} has no round_completed event`;
|
|
34269
|
+
throw new StateError(
|
|
34270
|
+
STATE_EXIT.INVARIANT_UNMET,
|
|
34271
|
+
`Cannot close session ${sessionId}: ${what}. Run 'ocr state complete-round' to finalize it, or pass --abort to record an abandoned session.`
|
|
34272
|
+
);
|
|
34273
|
+
}
|
|
34274
|
+
const note = "closed by parent workflow close";
|
|
34275
|
+
db.transaction(() => {
|
|
34276
|
+
if (abort) {
|
|
34277
|
+
insertEvent(db, {
|
|
34278
|
+
session_id: sessionId,
|
|
34279
|
+
event_type: "session_aborted",
|
|
34280
|
+
phase: existing.current_phase,
|
|
34281
|
+
phase_number: existing.phase_number,
|
|
34282
|
+
round: existing.current_round
|
|
34283
|
+
});
|
|
34284
|
+
}
|
|
34285
|
+
updateSession(db, sessionId, {
|
|
34286
|
+
status: "closed",
|
|
34287
|
+
current_phase: "complete"
|
|
34288
|
+
});
|
|
34289
|
+
if (!abort) {
|
|
34290
|
+
insertEvent(db, {
|
|
34291
|
+
session_id: sessionId,
|
|
34292
|
+
event_type: "session_closed",
|
|
34293
|
+
phase: "complete",
|
|
34294
|
+
phase_number: existing.phase_number,
|
|
34295
|
+
round: existing.current_round
|
|
34296
|
+
});
|
|
34297
|
+
}
|
|
34298
|
+
cascadeTerminateExecutions(db, sessionId, CASCADE_CLOSE_EXIT_CODE, note);
|
|
34299
|
+
});
|
|
34300
|
+
}
|
|
34301
|
+
async function reconcileWorkflowOnExit(ocrDir, sessionId, db) {
|
|
34302
|
+
db ??= await ensureDatabase(ocrDir);
|
|
34303
|
+
const existing = getSession(db, sessionId);
|
|
34304
|
+
if (!existing) return "not-found";
|
|
34305
|
+
if (existing.status === "closed") return "already-closed";
|
|
34306
|
+
if (!hasCompletionInvariant(db, existing)) return "incomplete";
|
|
34307
|
+
if (hasInFlightDependents(db, sessionId)) return "in-flight";
|
|
34308
|
+
await stateClose({ sessionId, ocrDir, abort: false });
|
|
34309
|
+
return "closed";
|
|
34310
|
+
}
|
|
34311
|
+
async function reconcileCompletedSessions(ocrDir) {
|
|
34312
|
+
const db = await ensureDatabase(ocrDir);
|
|
34313
|
+
const closed = [];
|
|
34314
|
+
for (const s of getAllSessions(db)) {
|
|
34315
|
+
if (s.status !== "active") continue;
|
|
34316
|
+
const outcome = await reconcileWorkflowOnExit(ocrDir, s.id, db);
|
|
34317
|
+
if (outcome === "closed") closed.push(s.id);
|
|
34318
|
+
}
|
|
34319
|
+
return closed;
|
|
34320
|
+
}
|
|
34321
|
+
|
|
34322
|
+
// ../cli/src/lib/runtime-config.ts
|
|
34323
|
+
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "node:fs";
|
|
34324
|
+
import { join as join11 } from "node:path";
|
|
34325
|
+
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34326
|
+
var DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES = 60;
|
|
34327
|
+
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
34328
|
+
const configPath = join11(ocrDir, "config.yaml");
|
|
34329
|
+
if (!existsSync9(configPath)) return defaultValue;
|
|
34330
|
+
let content;
|
|
34331
|
+
try {
|
|
34332
|
+
content = readFileSync4(configPath, "utf-8");
|
|
34333
|
+
} catch {
|
|
34334
|
+
return defaultValue;
|
|
34335
|
+
}
|
|
34336
|
+
const blockMatch = content.match(
|
|
34337
|
+
new RegExp(
|
|
34338
|
+
String.raw`^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+${key}:\s*([^\s#\n]+)`,
|
|
34339
|
+
"m"
|
|
34340
|
+
)
|
|
34341
|
+
);
|
|
34342
|
+
const inlineMatch = content.match(
|
|
34343
|
+
new RegExp(String.raw`^runtime:\s*\{[^}]*\b${key}:\s*([^\s,}]+)`, "m")
|
|
34344
|
+
);
|
|
34345
|
+
const raw = blockMatch?.[1] ?? inlineMatch?.[1];
|
|
34346
|
+
if (!raw) return defaultValue;
|
|
34347
|
+
const parsed = Number(raw);
|
|
34348
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
34349
|
+
process.stderr.write(
|
|
34350
|
+
`[ocr] runtime.${key} is not a positive integer (got "${raw}"); falling back to ${defaultValue}.
|
|
34351
|
+
`
|
|
34352
|
+
);
|
|
34353
|
+
return defaultValue;
|
|
34354
|
+
}
|
|
34355
|
+
return parsed;
|
|
34356
|
+
}
|
|
34357
|
+
function getAgentHeartbeatSeconds(ocrDir) {
|
|
34358
|
+
return readRuntimePositiveInt(
|
|
34359
|
+
ocrDir,
|
|
34360
|
+
"agent_heartbeat_seconds",
|
|
34361
|
+
DEFAULT_AGENT_HEARTBEAT_SECONDS
|
|
34362
|
+
);
|
|
34363
|
+
}
|
|
34364
|
+
function getWorkflowHardDeadlineMs(ocrDir) {
|
|
34365
|
+
return readRuntimePositiveInt(
|
|
34366
|
+
ocrDir,
|
|
34367
|
+
"workflow_hard_deadline_minutes",
|
|
34368
|
+
DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES
|
|
34369
|
+
) * 60 * 1e3;
|
|
34370
|
+
}
|
|
34371
|
+
|
|
33753
34372
|
// src/server/socket/command-runner.ts
|
|
33754
34373
|
function shellSplit(str) {
|
|
33755
34374
|
const tokens = [];
|
|
@@ -33783,7 +34402,7 @@ var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
33783
34402
|
]);
|
|
33784
34403
|
var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
|
|
33785
34404
|
function escapeUserHeaders(value) {
|
|
33786
|
-
return value.replace(/^([ \t]{0,3})(#+)/gm, "$1\\$2").replace(/^([ \t]{0,3})(#+)/gm, "$1\\$2").replace(/^([ \t]{0,3})(={3,}|-{3,})\s*$/gm, "$1\\$2").replace(/^([ \t]{0,3})(```+)/gm, "$1\\$2");
|
|
34405
|
+
return value.normalize("NFKC").replace(/[\u2028\u2029]/g, "\n").replace(/\p{Cf}/gu, "").replace(/^([ \t]{0,3})(#+)/gm, "$1\\$2").replace(/^([ \t]{0,3})(#+)/gm, "$1\\$2").replace(/^([ \t]{0,3})(={3,}|-{3,})\s*$/gm, "$1\\$2").replace(/^([ \t]{0,3})(```+)/gm, "$1\\$2");
|
|
33787
34406
|
}
|
|
33788
34407
|
function buildPrompt(opts) {
|
|
33789
34408
|
const { baseCommand, subArgs, commandContent, executionUid, localCli } = opts;
|
|
@@ -33930,23 +34549,45 @@ function extractPerInstanceModels(subArgs) {
|
|
|
33930
34549
|
return [...models];
|
|
33931
34550
|
}
|
|
33932
34551
|
var MAX_CONCURRENT = 3;
|
|
34552
|
+
var WATCHDOG_TICK_MS = 1e4;
|
|
34553
|
+
var POST_RESULT_GRACE_MS = 3e4;
|
|
34554
|
+
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
34555
|
+
function decideWatchdogTick(i) {
|
|
34556
|
+
if (i.resultSeenAt !== void 0 && i.nowMs - i.resultSeenAt > i.postResultGraceMs) {
|
|
34557
|
+
return {
|
|
34558
|
+
action: "finalize",
|
|
34559
|
+
reap: !i.exited,
|
|
34560
|
+
exitCode: i.resultIsError ? 1 : 0,
|
|
34561
|
+
reason: "result-grace"
|
|
34562
|
+
};
|
|
34563
|
+
}
|
|
34564
|
+
if (i.nowMs - i.startedAtMs > i.hardDeadlineMs) {
|
|
34565
|
+
return {
|
|
34566
|
+
action: "finalize",
|
|
34567
|
+
reap: !i.exited,
|
|
34568
|
+
exitCode: WATCHDOG_DEADLINE_EXIT_CODE,
|
|
34569
|
+
reason: "hard-deadline"
|
|
34570
|
+
};
|
|
34571
|
+
}
|
|
34572
|
+
return i.exited ? { action: "wait" } : { action: "beat" };
|
|
34573
|
+
}
|
|
33933
34574
|
var activeCommands = /* @__PURE__ */ new Map();
|
|
33934
34575
|
function spawnMarkerPath(ocrDir) {
|
|
33935
|
-
return
|
|
34576
|
+
return join12(ocrDir, "data", "dashboard-active-spawn.json");
|
|
33936
34577
|
}
|
|
33937
34578
|
function writeSpawnMarker(ocrDir, executionUid, pid) {
|
|
33938
|
-
const dataDir =
|
|
33939
|
-
if (!
|
|
34579
|
+
const dataDir = join12(ocrDir, "data");
|
|
34580
|
+
if (!existsSync10(dataDir)) mkdirSync5(dataDir, { recursive: true });
|
|
33940
34581
|
const payload = JSON.stringify({
|
|
33941
34582
|
execution_uid: executionUid,
|
|
33942
34583
|
pid,
|
|
33943
34584
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
33944
34585
|
});
|
|
33945
|
-
|
|
34586
|
+
writeFileSync3(spawnMarkerPath(ocrDir), payload, { mode: 384 });
|
|
33946
34587
|
}
|
|
33947
34588
|
function clearSpawnMarker(ocrDir) {
|
|
33948
34589
|
try {
|
|
33949
|
-
|
|
34590
|
+
unlinkSync3(spawnMarkerPath(ocrDir));
|
|
33950
34591
|
} catch {
|
|
33951
34592
|
}
|
|
33952
34593
|
}
|
|
@@ -34073,25 +34714,14 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService, sessionC
|
|
|
34073
34714
|
if (!proc) return;
|
|
34074
34715
|
const pid = proc.pid;
|
|
34075
34716
|
if (entry.detached && pid) {
|
|
34076
|
-
|
|
34077
|
-
process.kill(-pid, "SIGTERM");
|
|
34078
|
-
} catch {
|
|
34079
|
-
proc.kill("SIGTERM");
|
|
34080
|
-
}
|
|
34717
|
+
reapTree(pid);
|
|
34081
34718
|
} else {
|
|
34082
34719
|
proc.kill("SIGTERM");
|
|
34720
|
+
const killTimer = setTimeout(() => {
|
|
34721
|
+
if (activeCommands.has(targetId)) proc.kill("SIGKILL");
|
|
34722
|
+
}, 5e3);
|
|
34723
|
+
proc.once("close", () => clearTimeout(killTimer));
|
|
34083
34724
|
}
|
|
34084
|
-
const killTimer = setTimeout(() => {
|
|
34085
|
-
if (!activeCommands.has(targetId)) return;
|
|
34086
|
-
if (entry.detached && pid) {
|
|
34087
|
-
try {
|
|
34088
|
-
process.kill(-pid, "SIGKILL");
|
|
34089
|
-
} catch {
|
|
34090
|
-
}
|
|
34091
|
-
}
|
|
34092
|
-
proc.kill("SIGKILL");
|
|
34093
|
-
}, 5e3);
|
|
34094
|
-
proc.once("close", () => clearTimeout(killTimer));
|
|
34095
34725
|
} catch (err) {
|
|
34096
34726
|
console.error("Error in command:cancel handler:", err);
|
|
34097
34727
|
socket.emit("error", { message: "Internal error" });
|
|
@@ -34100,7 +34730,7 @@ function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService, sessionC
|
|
|
34100
34730
|
}
|
|
34101
34731
|
function spawnCliCommand(io2, db, ocrDir, executionId, baseCommand, subArgs, entry) {
|
|
34102
34732
|
const localCli = resolveLocalCli();
|
|
34103
|
-
const repoRoot =
|
|
34733
|
+
const repoRoot = dirname7(ocrDir);
|
|
34104
34734
|
const proc = localCli ? spawnBinary("node", [localCli, baseCommand, ...subArgs], {
|
|
34105
34735
|
cwd: repoRoot,
|
|
34106
34736
|
env: cleanEnv()
|
|
@@ -34126,8 +34756,7 @@ function spawnCliCommand(io2, db, ocrDir, executionId, baseCommand, subArgs, ent
|
|
|
34126
34756
|
io2.emit("command:output", { execution_id: executionId, content: chunk });
|
|
34127
34757
|
});
|
|
34128
34758
|
proc.on("close", (code) => {
|
|
34129
|
-
|
|
34130
|
-
finishExecution(io2, db, ocrDir, executionId, finalCode, entry.outputBuffer);
|
|
34759
|
+
finishExecution(io2, db, ocrDir, executionId, code ?? -1, entry.outputBuffer);
|
|
34131
34760
|
});
|
|
34132
34761
|
proc.on("error", (err) => {
|
|
34133
34762
|
entry.outputBuffer += `Process error: ${err.message}`;
|
|
@@ -34150,10 +34779,10 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34150
34779
|
io2.emit("command:output", { execution_id: executionId, content: warning });
|
|
34151
34780
|
}
|
|
34152
34781
|
}
|
|
34153
|
-
const commandMdPath =
|
|
34782
|
+
const commandMdPath = join12(ocrDir, "commands", `${baseCommand}.md`);
|
|
34154
34783
|
let commandContent;
|
|
34155
34784
|
try {
|
|
34156
|
-
commandContent =
|
|
34785
|
+
commandContent = readFileSync5(commandMdPath, "utf-8");
|
|
34157
34786
|
} catch {
|
|
34158
34787
|
const content = `Error: Could not read command file at ${commandMdPath}
|
|
34159
34788
|
`;
|
|
@@ -34197,7 +34826,20 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34197
34826
|
console.error("Failed to resolve resume context:", err);
|
|
34198
34827
|
}
|
|
34199
34828
|
}
|
|
34200
|
-
const repoRoot =
|
|
34829
|
+
const repoRoot = dirname7(ocrDir);
|
|
34830
|
+
let logFile;
|
|
34831
|
+
if (entry.uid) {
|
|
34832
|
+
try {
|
|
34833
|
+
const logDir = join12(ocrDir, "data", "exec-logs");
|
|
34834
|
+
mkdirSync5(logDir, { recursive: true });
|
|
34835
|
+
logFile = join12(logDir, `${entry.uid}.log`);
|
|
34836
|
+
} catch (err) {
|
|
34837
|
+
console.error(
|
|
34838
|
+
"[command-runner] could not prepare exec-log dir \u2014 falling back to pipe stdio (degraded: close may be withheld by a leaked grandchild; watchdog deadlines still finalize):",
|
|
34839
|
+
err
|
|
34840
|
+
);
|
|
34841
|
+
}
|
|
34842
|
+
}
|
|
34201
34843
|
const spawnOpts = {
|
|
34202
34844
|
mode: "workflow",
|
|
34203
34845
|
prompt,
|
|
@@ -34207,7 +34849,10 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34207
34849
|
if (resumeSessionId) {
|
|
34208
34850
|
spawnOpts.resumeSessionId = resumeSessionId;
|
|
34209
34851
|
}
|
|
34210
|
-
|
|
34852
|
+
if (logFile) {
|
|
34853
|
+
spawnOpts.logFile = logFile;
|
|
34854
|
+
}
|
|
34855
|
+
const { process: proc, detached, logPath } = adapter.spawn(spawnOpts);
|
|
34211
34856
|
entry.process = proc;
|
|
34212
34857
|
entry.detached = detached;
|
|
34213
34858
|
if (proc.pid) {
|
|
@@ -34243,6 +34888,61 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34243
34888
|
}
|
|
34244
34889
|
}, POLL_INTERVAL_MS);
|
|
34245
34890
|
entry.linkPoll = linkPoll;
|
|
34891
|
+
const bumpHeartbeat = () => {
|
|
34892
|
+
if (entry.finalized) return;
|
|
34893
|
+
const now = Date.now();
|
|
34894
|
+
if (now - (entry.lastBeatWrite ?? 0) < HEARTBEAT_THROTTLE_MS) return;
|
|
34895
|
+
entry.lastBeatWrite = now;
|
|
34896
|
+
try {
|
|
34897
|
+
db.run(
|
|
34898
|
+
`UPDATE command_executions SET last_heartbeat_at = datetime('now') WHERE id = ? AND finished_at IS NULL`,
|
|
34899
|
+
[executionId]
|
|
34900
|
+
);
|
|
34901
|
+
} catch (err) {
|
|
34902
|
+
console.error("[command-runner] heartbeat bump failed:", err);
|
|
34903
|
+
}
|
|
34904
|
+
};
|
|
34905
|
+
const hardDeadlineMs = getWorkflowHardDeadlineMs(ocrDir);
|
|
34906
|
+
entry.watchdog = setInterval(() => {
|
|
34907
|
+
if (entry.finalized) return;
|
|
34908
|
+
const child = entry.process;
|
|
34909
|
+
const pid = child?.pid;
|
|
34910
|
+
if (!child || !pid) return;
|
|
34911
|
+
const exited = child.exitCode !== null || child.signalCode !== null;
|
|
34912
|
+
const decision = decideWatchdogTick({
|
|
34913
|
+
exited,
|
|
34914
|
+
resultSeenAt: entry.resultSeenAt,
|
|
34915
|
+
resultIsError: entry.resultIsError,
|
|
34916
|
+
startedAtMs: Date.parse(entry.startedAt),
|
|
34917
|
+
nowMs: Date.now(),
|
|
34918
|
+
postResultGraceMs: POST_RESULT_GRACE_MS,
|
|
34919
|
+
hardDeadlineMs
|
|
34920
|
+
});
|
|
34921
|
+
switch (decision.action) {
|
|
34922
|
+
case "beat":
|
|
34923
|
+
bumpHeartbeat();
|
|
34924
|
+
return;
|
|
34925
|
+
case "wait":
|
|
34926
|
+
return;
|
|
34927
|
+
case "finalize": {
|
|
34928
|
+
if (decision.reason === "hard-deadline") {
|
|
34929
|
+
const minutes = Math.round(hardDeadlineMs / 6e4);
|
|
34930
|
+
console.warn(`[watchdog] execution ${executionId}: exceeded hard deadline (${minutes}m) \u2014 finalizing${decision.reap ? " + reaping tree" : ""}`);
|
|
34931
|
+
const notice = `
|
|
34932
|
+
[watchdog] Reaped after exceeding the ${minutes}-minute hard deadline. Raise runtime.workflow_hard_deadline_minutes in .ocr/config.yaml for large reviewer fleets.
|
|
34933
|
+
`;
|
|
34934
|
+
entry.outputBuffer += notice;
|
|
34935
|
+
io2.emit("command:output", { execution_id: executionId, content: notice });
|
|
34936
|
+
} else {
|
|
34937
|
+
console.warn(`[watchdog] execution ${executionId}: result seen but no close after grace \u2014 finalizing${decision.reap ? " + reaping tree" : ""}`);
|
|
34938
|
+
}
|
|
34939
|
+
if (decision.reap) reapTree(pid);
|
|
34940
|
+
finishExecution(io2, db, ocrDir, executionId, decision.exitCode, entry.outputBuffer);
|
|
34941
|
+
return;
|
|
34942
|
+
}
|
|
34943
|
+
}
|
|
34944
|
+
}, WATCHDOG_TICK_MS);
|
|
34945
|
+
entry.watchdog.unref();
|
|
34246
34946
|
io2.emit("command:output", {
|
|
34247
34947
|
execution_id: executionId,
|
|
34248
34948
|
content: `\u25B8 Starting OCR ${baseCommand} workflow...
|
|
@@ -34307,11 +35007,16 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34307
35007
|
emitStreamEvent(evt);
|
|
34308
35008
|
break;
|
|
34309
35009
|
}
|
|
35010
|
+
case "result": {
|
|
35011
|
+
entry.resultSeenAt = Date.now();
|
|
35012
|
+
entry.resultIsError = evt.isError;
|
|
35013
|
+
emitStreamEvent(evt);
|
|
35014
|
+
break;
|
|
35015
|
+
}
|
|
34310
35016
|
}
|
|
34311
35017
|
}
|
|
34312
|
-
|
|
34313
|
-
|
|
34314
|
-
proc.stdout?.on("data", (chunk) => {
|
|
35018
|
+
function onOutputChunk(chunk) {
|
|
35019
|
+
bumpHeartbeat();
|
|
34315
35020
|
lineBuffer += chunk;
|
|
34316
35021
|
const lines = lineBuffer.split("\n");
|
|
34317
35022
|
lineBuffer = lines.pop() ?? "";
|
|
@@ -34326,17 +35031,30 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34326
35031
|
handleEvent(evt);
|
|
34327
35032
|
}
|
|
34328
35033
|
}
|
|
34329
|
-
}
|
|
35034
|
+
}
|
|
34330
35035
|
let stderrBuffer = "";
|
|
34331
|
-
|
|
34332
|
-
|
|
34333
|
-
|
|
35036
|
+
if (logPath) {
|
|
35037
|
+
const tailer = new FileTailer(logPath, onOutputChunk);
|
|
35038
|
+
tailer.start();
|
|
35039
|
+
entry.tailer = tailer;
|
|
35040
|
+
} else {
|
|
35041
|
+
proc.stdout?.setEncoding("utf-8");
|
|
35042
|
+
proc.stderr?.setEncoding("utf-8");
|
|
35043
|
+
proc.stdout?.on("data", onOutputChunk);
|
|
35044
|
+
proc.stderr?.on("data", (chunk) => {
|
|
35045
|
+
stderrBuffer += chunk;
|
|
35046
|
+
});
|
|
35047
|
+
}
|
|
34334
35048
|
proc.on("close", (code) => {
|
|
34335
35049
|
if (entry.linkPoll) {
|
|
34336
35050
|
clearInterval(entry.linkPoll);
|
|
34337
35051
|
entry.linkPoll = void 0;
|
|
34338
35052
|
}
|
|
34339
35053
|
clearSpawnMarker(ocrDir);
|
|
35054
|
+
if (entry.tailer) {
|
|
35055
|
+
entry.tailer.stop();
|
|
35056
|
+
entry.tailer = void 0;
|
|
35057
|
+
}
|
|
34340
35058
|
if (lineBuffer.trim()) {
|
|
34341
35059
|
const events = parser.parseLine(lineBuffer);
|
|
34342
35060
|
for (const evt of events) {
|
|
@@ -34360,8 +35078,7 @@ ${stderrBuffer}`;
|
|
|
34360
35078
|
journal.close().catch((err) => {
|
|
34361
35079
|
console.error("[event-journal] close failed:", err);
|
|
34362
35080
|
});
|
|
34363
|
-
|
|
34364
|
-
finishExecution(io2, db, ocrDir, executionId, finalCode, entry.outputBuffer);
|
|
35081
|
+
finishExecution(io2, db, ocrDir, executionId, code ?? -1, entry.outputBuffer);
|
|
34365
35082
|
});
|
|
34366
35083
|
proc.on("error", (err) => {
|
|
34367
35084
|
if (entry.linkPoll) {
|
|
@@ -34375,15 +35092,28 @@ ${stderrBuffer}`;
|
|
|
34375
35092
|
finishExecution(io2, db, ocrDir, executionId, -1, entry.outputBuffer);
|
|
34376
35093
|
});
|
|
34377
35094
|
}
|
|
34378
|
-
function finishExecution(io2, db, ocrDir, executionId,
|
|
35095
|
+
function finishExecution(io2, db, ocrDir, executionId, rawCode, output) {
|
|
34379
35096
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34380
35097
|
const entry = activeCommands.get(executionId);
|
|
34381
|
-
|
|
35098
|
+
const code = entry?.cancelled ? CANCELLED_EXIT_CODE : rawCode;
|
|
35099
|
+
if (entry?.finalized) return;
|
|
35100
|
+
if (entry) {
|
|
35101
|
+
entry.finalized = true;
|
|
35102
|
+
if (entry.watchdog) {
|
|
35103
|
+
clearInterval(entry.watchdog);
|
|
35104
|
+
entry.watchdog = void 0;
|
|
35105
|
+
}
|
|
35106
|
+
if (entry.tailer) {
|
|
35107
|
+
entry.tailer.stop();
|
|
35108
|
+
entry.tailer = void 0;
|
|
35109
|
+
}
|
|
35110
|
+
}
|
|
35111
|
+
const res = db.prepare(
|
|
34382
35112
|
`UPDATE command_executions
|
|
34383
|
-
|
|
34384
|
-
|
|
34385
|
-
|
|
34386
|
-
);
|
|
35113
|
+
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
35114
|
+
WHERE id = ? AND finished_at IS NULL`
|
|
35115
|
+
).run(code, finishedAt, output, executionId);
|
|
35116
|
+
if (Number(res.changes) === 0 && !entry) return;
|
|
34387
35117
|
const completeness = getWorkflowCompletenessForExecution(db, executionId);
|
|
34388
35118
|
const outcome = deriveCommandOutcome(code, completeness);
|
|
34389
35119
|
const cancellationReason = deriveCancellationReason(code);
|
|
@@ -34398,7 +35128,7 @@ function finishExecution(io2, db, ocrDir, executionId, code, output) {
|
|
|
34398
35128
|
started_at: entry.startedAt,
|
|
34399
35129
|
finished_at: finishedAt,
|
|
34400
35130
|
is_detached: entry.detached ? 1 : 0,
|
|
34401
|
-
event: code ===
|
|
35131
|
+
event: code === CANCELLED_EXIT_CODE ? "cancel" : "finish",
|
|
34402
35132
|
writer: "dashboard"
|
|
34403
35133
|
});
|
|
34404
35134
|
}
|
|
@@ -34410,6 +35140,27 @@ function finishExecution(io2, db, ocrDir, executionId, code, output) {
|
|
|
34410
35140
|
cancellation_reason: cancellationReason
|
|
34411
35141
|
});
|
|
34412
35142
|
activeCommands.delete(executionId);
|
|
35143
|
+
const workflowRow = db.exec(
|
|
35144
|
+
"SELECT workflow_id FROM command_executions WHERE id = ?",
|
|
35145
|
+
[executionId]
|
|
35146
|
+
);
|
|
35147
|
+
const workflowId = workflowRow[0]?.values[0]?.[0];
|
|
35148
|
+
if (typeof workflowId === "string" && workflowId.length > 0) {
|
|
35149
|
+
void reconcileWorkflowOnExit(ocrDir, workflowId, db).then((outcome2) => {
|
|
35150
|
+
if (outcome2 === "closed") {
|
|
35151
|
+
console.log(`[command-runner] auto-finalized workflow ${workflowId}`);
|
|
35152
|
+
} else if (outcome2 === "incomplete" || outcome2 === "in-flight") {
|
|
35153
|
+
console.debug(
|
|
35154
|
+
`[command-runner] workflow ${workflowId} not finalized: ${outcome2}`
|
|
35155
|
+
);
|
|
35156
|
+
}
|
|
35157
|
+
}).catch((err) => {
|
|
35158
|
+
console.error(
|
|
35159
|
+
`[command-runner] reconcileWorkflowOnExit(${workflowId}) failed:`,
|
|
35160
|
+
err instanceof Error ? err.message : err
|
|
35161
|
+
);
|
|
35162
|
+
});
|
|
35163
|
+
}
|
|
34413
35164
|
}
|
|
34414
35165
|
|
|
34415
35166
|
// src/server/routes/commands.ts
|
|
@@ -34497,8 +35248,8 @@ function createCommandsRouter(db, ocrDir) {
|
|
|
34497
35248
|
|
|
34498
35249
|
// src/server/routes/config.ts
|
|
34499
35250
|
var import_express9 = __toESM(require_express2(), 1);
|
|
34500
|
-
import { readFileSync as
|
|
34501
|
-
import { join as
|
|
35251
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
35252
|
+
import { join as join13, dirname as dirname8, basename as basename2 } from "node:path";
|
|
34502
35253
|
var VALID_IDES = ["vscode", "cursor", "windsurf", "jetbrains", "sublime"];
|
|
34503
35254
|
function detectIde() {
|
|
34504
35255
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
@@ -34515,8 +35266,8 @@ function detectIde() {
|
|
|
34515
35266
|
}
|
|
34516
35267
|
function readIdeFromConfig(ocrDir) {
|
|
34517
35268
|
try {
|
|
34518
|
-
const configPath =
|
|
34519
|
-
const content =
|
|
35269
|
+
const configPath = join13(ocrDir, "config.yaml");
|
|
35270
|
+
const content = readFileSync6(configPath, "utf-8");
|
|
34520
35271
|
const match = content.match(/^\s*ide:\s*(\S+)/m);
|
|
34521
35272
|
return match?.[1] ?? "auto";
|
|
34522
35273
|
} catch {
|
|
@@ -34543,8 +35294,8 @@ function detectGitBranch(cwd) {
|
|
|
34543
35294
|
}
|
|
34544
35295
|
function createConfigRouter(ocrDir, aiCliService) {
|
|
34545
35296
|
const router = (0, import_express9.Router)();
|
|
34546
|
-
const projectRoot =
|
|
34547
|
-
const workspaceName =
|
|
35297
|
+
const projectRoot = dirname8(ocrDir);
|
|
35298
|
+
const workspaceName = basename2(projectRoot);
|
|
34548
35299
|
const gitBranch = detectGitBranch(projectRoot);
|
|
34549
35300
|
router.get("/", (_req, res) => {
|
|
34550
35301
|
res.json({
|
|
@@ -34562,8 +35313,8 @@ function createConfigRouter(ocrDir, aiCliService) {
|
|
|
34562
35313
|
return;
|
|
34563
35314
|
}
|
|
34564
35315
|
try {
|
|
34565
|
-
const configPath =
|
|
34566
|
-
let content =
|
|
35316
|
+
const configPath = join13(ocrDir, "config.yaml");
|
|
35317
|
+
let content = readFileSync6(configPath, "utf-8");
|
|
34567
35318
|
if (content.match(/^\s*ide:\s*\S+/m)) {
|
|
34568
35319
|
content = content.replace(/^(\s*ide:\s*)\S+/m, `$1${ide}`);
|
|
34569
35320
|
} else if (content.includes("dashboard:")) {
|
|
@@ -34575,7 +35326,7 @@ dashboard:
|
|
|
34575
35326
|
ide: ${ide}
|
|
34576
35327
|
`;
|
|
34577
35328
|
}
|
|
34578
|
-
|
|
35329
|
+
writeFileSync4(configPath, content);
|
|
34579
35330
|
res.json({ ide });
|
|
34580
35331
|
} catch (err) {
|
|
34581
35332
|
console.error("Failed to update config:", err);
|
|
@@ -34651,17 +35402,20 @@ function createChatRouter(db) {
|
|
|
34651
35402
|
|
|
34652
35403
|
// src/server/routes/reviewers.ts
|
|
34653
35404
|
var import_express11 = __toESM(require_express2(), 1);
|
|
34654
|
-
import { readFileSync as
|
|
34655
|
-
import { join as
|
|
35405
|
+
import { readFileSync as readFileSync7, existsSync as existsSync11, watch } from "node:fs";
|
|
35406
|
+
import { join as join14 } from "node:path";
|
|
34656
35407
|
function readReviewersMeta(ocrDir) {
|
|
34657
|
-
const metaPath =
|
|
34658
|
-
if (!
|
|
35408
|
+
const metaPath = join14(ocrDir, "reviewers-meta.json");
|
|
35409
|
+
if (!existsSync11(metaPath)) {
|
|
34659
35410
|
return { reviewers: [], defaults: [] };
|
|
34660
35411
|
}
|
|
34661
35412
|
try {
|
|
34662
|
-
const raw =
|
|
35413
|
+
const raw = readFileSync7(metaPath, "utf-8");
|
|
34663
35414
|
const meta = JSON.parse(raw);
|
|
34664
|
-
const reviewers = meta.reviewers ?? []
|
|
35415
|
+
const reviewers = (meta.reviewers ?? []).map((r) => ({
|
|
35416
|
+
...r,
|
|
35417
|
+
icon: r.icon || defaultIconFor(r.id, r.tier)
|
|
35418
|
+
}));
|
|
34665
35419
|
const defaults = reviewers.filter((r) => r.is_default).map((r) => r.id);
|
|
34666
35420
|
return { reviewers, defaults };
|
|
34667
35421
|
} catch {
|
|
@@ -34680,13 +35434,13 @@ function createReviewersRouter(ocrDir) {
|
|
|
34680
35434
|
res.status(400).json({ error: "Invalid reviewer ID" });
|
|
34681
35435
|
return;
|
|
34682
35436
|
}
|
|
34683
|
-
const filePath =
|
|
34684
|
-
if (!
|
|
35437
|
+
const filePath = join14(ocrDir, "skills", "references", "reviewers", `${id}.md`);
|
|
35438
|
+
if (!existsSync11(filePath)) {
|
|
34685
35439
|
res.status(404).json({ error: "Reviewer not found", id });
|
|
34686
35440
|
return;
|
|
34687
35441
|
}
|
|
34688
35442
|
try {
|
|
34689
|
-
const content =
|
|
35443
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
34690
35444
|
res.json({ id, content });
|
|
34691
35445
|
} catch {
|
|
34692
35446
|
res.status(500).json({ error: "Failed to read reviewer file", id });
|
|
@@ -34695,7 +35449,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
34695
35449
|
return router;
|
|
34696
35450
|
}
|
|
34697
35451
|
function watchReviewersMeta(ocrDir, io2) {
|
|
34698
|
-
const metaPath =
|
|
35452
|
+
const metaPath = join14(ocrDir, "reviewers-meta.json");
|
|
34699
35453
|
let watcher = null;
|
|
34700
35454
|
let debounce;
|
|
34701
35455
|
try {
|
|
@@ -34741,11 +35495,11 @@ function createAgentSessionsRouter(db, syncFromDisk = () => {
|
|
|
34741
35495
|
|
|
34742
35496
|
// src/server/routes/handoff.ts
|
|
34743
35497
|
var import_express13 = __toESM(require_express2(), 1);
|
|
34744
|
-
import { dirname as
|
|
35498
|
+
import { dirname as dirname9 } from "node:path";
|
|
34745
35499
|
function createHandoffRouter(sessionCapture, ocrDir, syncFromDisk = () => {
|
|
34746
35500
|
}) {
|
|
34747
35501
|
const router = (0, import_express13.Router)();
|
|
34748
|
-
const projectDir =
|
|
35502
|
+
const projectDir = dirname9(ocrDir);
|
|
34749
35503
|
router.get("/:id/handoff", (req, res) => {
|
|
34750
35504
|
const workflowId = req.params["id"];
|
|
34751
35505
|
if (!workflowId) {
|
|
@@ -34775,14 +35529,14 @@ import { spawnSync } from "node:child_process";
|
|
|
34775
35529
|
|
|
34776
35530
|
// ../cli/src/lib/team-config.ts
|
|
34777
35531
|
var import_yaml = __toESM(require_dist(), 1);
|
|
34778
|
-
import { existsSync as
|
|
34779
|
-
import { join as
|
|
35532
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
35533
|
+
import { join as join15 } from "node:path";
|
|
34780
35534
|
function loadTeamConfig(ocrDir) {
|
|
34781
|
-
const configPath =
|
|
34782
|
-
if (!
|
|
35535
|
+
const configPath = join15(ocrDir, "config.yaml");
|
|
35536
|
+
if (!existsSync12(configPath)) {
|
|
34783
35537
|
return { team: [], aliases: {}, defaultModel: null };
|
|
34784
35538
|
}
|
|
34785
|
-
const content =
|
|
35539
|
+
const content = readFileSync8(configPath, "utf-8");
|
|
34786
35540
|
return parseTeamConfigYaml(content);
|
|
34787
35541
|
}
|
|
34788
35542
|
function parseTeamConfigYaml(content) {
|
|
@@ -35382,8 +36136,8 @@ function buildDiagnostics(input) {
|
|
|
35382
36136
|
}
|
|
35383
36137
|
|
|
35384
36138
|
// src/server/services/filesystem-sync.ts
|
|
35385
|
-
import { readdirSync, readFileSync as
|
|
35386
|
-
import { join as
|
|
36139
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync9, statSync as statSync3, existsSync as existsSync13 } from "node:fs";
|
|
36140
|
+
import { join as join16, basename as basename3, dirname as dirname10, relative } from "node:path";
|
|
35387
36141
|
import { watch as watch2 } from "chokidar";
|
|
35388
36142
|
|
|
35389
36143
|
// src/server/services/parsers/reviewer-parser.ts
|
|
@@ -35586,67 +36340,67 @@ var FilesystemSync = class {
|
|
|
35586
36340
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
35587
36341
|
// ── 6.1: Full Scan ──
|
|
35588
36342
|
async fullScan() {
|
|
35589
|
-
if (!
|
|
35590
|
-
const entries =
|
|
36343
|
+
if (!existsSync13(this.sessionsDir)) return;
|
|
36344
|
+
const entries = readdirSync2(this.sessionsDir, { withFileTypes: true });
|
|
35591
36345
|
for (const entry of entries) {
|
|
35592
36346
|
if (!entry.isDirectory()) continue;
|
|
35593
36347
|
const sessionId = entry.name;
|
|
35594
|
-
const sessionDir =
|
|
36348
|
+
const sessionDir = join16(this.sessionsDir, sessionId);
|
|
35595
36349
|
this.syncSession(sessionId, sessionDir);
|
|
35596
36350
|
}
|
|
35597
36351
|
}
|
|
35598
36352
|
syncSession(sessionId, sessionDir) {
|
|
35599
36353
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
35600
|
-
const roundsDir =
|
|
35601
|
-
if (
|
|
35602
|
-
const rounds =
|
|
36354
|
+
const roundsDir = join16(sessionDir, "rounds");
|
|
36355
|
+
if (existsSync13(roundsDir)) {
|
|
36356
|
+
const rounds = readdirSync2(roundsDir, { withFileTypes: true });
|
|
35603
36357
|
for (const roundEntry of rounds) {
|
|
35604
36358
|
if (!roundEntry.isDirectory()) continue;
|
|
35605
36359
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
35606
36360
|
if (!roundMatch) continue;
|
|
35607
36361
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
35608
|
-
const roundDir =
|
|
35609
|
-
const reviewsDir =
|
|
35610
|
-
if (
|
|
35611
|
-
const reviewFiles =
|
|
36362
|
+
const roundDir = join16(roundsDir, roundEntry.name);
|
|
36363
|
+
const reviewsDir = join16(roundDir, "reviews");
|
|
36364
|
+
if (existsSync13(reviewsDir)) {
|
|
36365
|
+
const reviewFiles = readdirSync2(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
35612
36366
|
for (const reviewFile of reviewFiles) {
|
|
35613
|
-
const filePath =
|
|
36367
|
+
const filePath = join16(reviewsDir, reviewFile);
|
|
35614
36368
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
35615
36369
|
}
|
|
35616
36370
|
}
|
|
35617
|
-
const roundMetaPath =
|
|
35618
|
-
if (
|
|
36371
|
+
const roundMetaPath = join16(roundDir, "round-meta.json");
|
|
36372
|
+
if (existsSync13(roundMetaPath)) {
|
|
35619
36373
|
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
35620
36374
|
}
|
|
35621
|
-
const finalPath =
|
|
35622
|
-
if (
|
|
36375
|
+
const finalPath = join16(roundDir, "final.md");
|
|
36376
|
+
if (existsSync13(finalPath)) {
|
|
35623
36377
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
35624
36378
|
}
|
|
35625
|
-
const finalHumanPath =
|
|
35626
|
-
if (
|
|
36379
|
+
const finalHumanPath = join16(roundDir, "final-human.md");
|
|
36380
|
+
if (existsSync13(finalHumanPath)) {
|
|
35627
36381
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
35628
36382
|
}
|
|
35629
|
-
const discoursePath =
|
|
35630
|
-
if (
|
|
36383
|
+
const discoursePath = join16(roundDir, "discourse.md");
|
|
36384
|
+
if (existsSync13(discoursePath)) {
|
|
35631
36385
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
35632
36386
|
}
|
|
35633
36387
|
}
|
|
35634
36388
|
}
|
|
35635
|
-
const mapDir =
|
|
35636
|
-
if (
|
|
35637
|
-
const runs =
|
|
36389
|
+
const mapDir = join16(sessionDir, "map", "runs");
|
|
36390
|
+
if (existsSync13(mapDir)) {
|
|
36391
|
+
const runs = readdirSync2(mapDir, { withFileTypes: true });
|
|
35638
36392
|
for (const runEntry of runs) {
|
|
35639
36393
|
if (!runEntry.isDirectory()) continue;
|
|
35640
36394
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
35641
36395
|
if (!runMatch) continue;
|
|
35642
36396
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
35643
|
-
const runDir =
|
|
35644
|
-
const mapMetaPath =
|
|
35645
|
-
if (
|
|
36397
|
+
const runDir = join16(mapDir, runEntry.name);
|
|
36398
|
+
const mapMetaPath = join16(runDir, "map-meta.json");
|
|
36399
|
+
if (existsSync13(mapMetaPath)) {
|
|
35646
36400
|
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
35647
36401
|
}
|
|
35648
|
-
const mapPath =
|
|
35649
|
-
if (
|
|
36402
|
+
const mapPath = join16(runDir, "map.md");
|
|
36403
|
+
if (existsSync13(mapPath)) {
|
|
35650
36404
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
35651
36405
|
}
|
|
35652
36406
|
const mapArtifacts = [
|
|
@@ -35655,8 +36409,8 @@ var FilesystemSync = class {
|
|
|
35655
36409
|
["requirements-mapping.md", "requirements-mapping"]
|
|
35656
36410
|
];
|
|
35657
36411
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
35658
|
-
const filePath =
|
|
35659
|
-
if (
|
|
36412
|
+
const filePath = join16(runDir, fileName);
|
|
36413
|
+
if (existsSync13(filePath)) {
|
|
35660
36414
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
35661
36415
|
}
|
|
35662
36416
|
}
|
|
@@ -35667,8 +36421,8 @@ var FilesystemSync = class {
|
|
|
35667
36421
|
["discovered-standards.md", "discovered-standards"]
|
|
35668
36422
|
];
|
|
35669
36423
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
35670
|
-
const filePath =
|
|
35671
|
-
if (
|
|
36424
|
+
const filePath = join16(sessionDir, fileName);
|
|
36425
|
+
if (existsSync13(filePath)) {
|
|
35672
36426
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
35673
36427
|
}
|
|
35674
36428
|
}
|
|
@@ -35677,58 +36431,58 @@ var FilesystemSync = class {
|
|
|
35677
36431
|
ensureSessionRow(sessionId, sessionDir) {
|
|
35678
36432
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
35679
36433
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
35680
|
-
const hasRoundsDir =
|
|
35681
|
-
const hasMapDir =
|
|
36434
|
+
const hasRoundsDir = existsSync13(join16(sessionDir, "rounds"));
|
|
36435
|
+
const hasMapDir = existsSync13(join16(sessionDir, "map"));
|
|
35682
36436
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
35683
36437
|
let currentRound = 1;
|
|
35684
36438
|
if (hasRoundsDir) {
|
|
35685
|
-
const roundDirs =
|
|
36439
|
+
const roundDirs = readdirSync2(join16(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
35686
36440
|
currentRound = Math.max(1, roundDirs.length);
|
|
35687
36441
|
}
|
|
35688
36442
|
let currentMapRun = 1;
|
|
35689
|
-
const mapRunsDir =
|
|
35690
|
-
if (
|
|
35691
|
-
const runDirs =
|
|
36443
|
+
const mapRunsDir = join16(sessionDir, "map", "runs");
|
|
36444
|
+
if (existsSync13(mapRunsDir)) {
|
|
36445
|
+
const runDirs = readdirSync2(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
35692
36446
|
currentMapRun = Math.max(1, runDirs.length);
|
|
35693
36447
|
}
|
|
35694
36448
|
let phase = "context";
|
|
35695
36449
|
let phaseNumber = 1;
|
|
35696
36450
|
let status = "closed";
|
|
35697
36451
|
if (workflowType === "review" && hasRoundsDir) {
|
|
35698
|
-
const roundDir =
|
|
35699
|
-
if (
|
|
36452
|
+
const roundDir = join16(sessionDir, "rounds", `round-${currentRound}`);
|
|
36453
|
+
if (existsSync13(join16(roundDir, "final.md"))) {
|
|
35700
36454
|
phase = "complete";
|
|
35701
36455
|
phaseNumber = 8;
|
|
35702
36456
|
status = "closed";
|
|
35703
|
-
} else if (
|
|
36457
|
+
} else if (existsSync13(join16(roundDir, "discourse.md"))) {
|
|
35704
36458
|
phase = "synthesis";
|
|
35705
36459
|
phaseNumber = 7;
|
|
35706
|
-
} else if (
|
|
36460
|
+
} else if (existsSync13(join16(roundDir, "reviews")) && readdirSync2(join16(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
35707
36461
|
phase = "reviews";
|
|
35708
36462
|
phaseNumber = 4;
|
|
35709
|
-
} else if (
|
|
36463
|
+
} else if (existsSync13(join16(sessionDir, "context.md"))) {
|
|
35710
36464
|
phase = "analysis";
|
|
35711
36465
|
phaseNumber = 3;
|
|
35712
|
-
} else if (
|
|
36466
|
+
} else if (existsSync13(join16(sessionDir, "discovered-standards.md"))) {
|
|
35713
36467
|
phase = "change-context";
|
|
35714
36468
|
phaseNumber = 2;
|
|
35715
36469
|
}
|
|
35716
36470
|
} else if (workflowType === "map" && hasMapDir) {
|
|
35717
|
-
const runDir =
|
|
35718
|
-
if (
|
|
36471
|
+
const runDir = join16(mapRunsDir, `run-${currentMapRun}`);
|
|
36472
|
+
if (existsSync13(join16(runDir, "map.md"))) {
|
|
35719
36473
|
phase = "complete";
|
|
35720
36474
|
phaseNumber = 6;
|
|
35721
36475
|
status = "closed";
|
|
35722
|
-
} else if (
|
|
36476
|
+
} else if (existsSync13(join16(runDir, "requirements-mapping.md"))) {
|
|
35723
36477
|
phase = "synthesis";
|
|
35724
36478
|
phaseNumber = 5;
|
|
35725
|
-
} else if (
|
|
36479
|
+
} else if (existsSync13(join16(runDir, "flow-analysis.md"))) {
|
|
35726
36480
|
phase = "requirements-mapping";
|
|
35727
36481
|
phaseNumber = 4;
|
|
35728
|
-
} else if (
|
|
36482
|
+
} else if (existsSync13(join16(runDir, "topology.md"))) {
|
|
35729
36483
|
phase = "flow-analysis";
|
|
35730
36484
|
phaseNumber = 3;
|
|
35731
|
-
} else if (
|
|
36485
|
+
} else if (existsSync13(join16(sessionDir, "discovered-standards.md"))) {
|
|
35732
36486
|
phase = "topology";
|
|
35733
36487
|
phaseNumber = 2;
|
|
35734
36488
|
}
|
|
@@ -35781,9 +36535,9 @@ var FilesystemSync = class {
|
|
|
35781
36535
|
/** Returns true if the directory contains at least one .md or .json file (recursively). */
|
|
35782
36536
|
hasArtifacts(dir) {
|
|
35783
36537
|
try {
|
|
35784
|
-
for (const entry of
|
|
36538
|
+
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
35785
36539
|
if (entry.isDirectory()) {
|
|
35786
|
-
if (this.hasArtifacts(
|
|
36540
|
+
if (this.hasArtifacts(join16(dir, entry.name))) return true;
|
|
35787
36541
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
35788
36542
|
return true;
|
|
35789
36543
|
}
|
|
@@ -35796,7 +36550,7 @@ var FilesystemSync = class {
|
|
|
35796
36550
|
shouldSkip(filePath, existingParsedAt) {
|
|
35797
36551
|
if (!existingParsedAt) return false;
|
|
35798
36552
|
try {
|
|
35799
|
-
const mtime =
|
|
36553
|
+
const mtime = statSync3(filePath).mtime;
|
|
35800
36554
|
const parsedAt = new Date(existingParsedAt);
|
|
35801
36555
|
return mtime <= parsedAt;
|
|
35802
36556
|
} catch {
|
|
@@ -35811,13 +36565,19 @@ var FilesystemSync = class {
|
|
|
35811
36565
|
"SELECT id FROM markdown_artifacts WHERE session_id = ? AND artifact_type = ? AND round_number IS ? AND file_path = ?",
|
|
35812
36566
|
[sessionId, artifactType, roundNumber ?? null, relPath]
|
|
35813
36567
|
);
|
|
35814
|
-
|
|
36568
|
+
if (existing !== null) {
|
|
36569
|
+
this.db.run(
|
|
36570
|
+
`UPDATE markdown_artifacts SET content = ?, parsed_at = datetime('now') WHERE id = ?`,
|
|
36571
|
+
[content, existing]
|
|
36572
|
+
);
|
|
36573
|
+
return "updated";
|
|
36574
|
+
}
|
|
35815
36575
|
this.db.run(
|
|
35816
|
-
`INSERT
|
|
36576
|
+
`INSERT INTO markdown_artifacts (session_id, artifact_type, round_number, file_path, content, parsed_at)
|
|
35817
36577
|
VALUES (?, ?, ?, ?, ?, datetime('now'))`,
|
|
35818
36578
|
[sessionId, artifactType, roundNumber ?? null, relPath, content]
|
|
35819
36579
|
);
|
|
35820
|
-
return
|
|
36580
|
+
return "created";
|
|
35821
36581
|
}
|
|
35822
36582
|
// ── 6.7: Socket.IO Emission ──
|
|
35823
36583
|
emitArtifactEvent(action, event) {
|
|
@@ -35833,12 +36593,12 @@ var FilesystemSync = class {
|
|
|
35833
36593
|
);
|
|
35834
36594
|
if (existingRun && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
35835
36595
|
if (existingRun?.["source"] === "orchestrator") {
|
|
35836
|
-
const content2 =
|
|
36596
|
+
const content2 = readFileSync9(filePath, "utf-8");
|
|
35837
36597
|
const action2 = this.upsertMarkdownArtifact(sessionId, "map", filePath, content2, void 0);
|
|
35838
36598
|
this.emitArtifactEvent(action2, { sessionId, artifactType: "map", filePath });
|
|
35839
36599
|
return;
|
|
35840
36600
|
}
|
|
35841
|
-
const content =
|
|
36601
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
35842
36602
|
const parsed = parseMapMd(content);
|
|
35843
36603
|
this.db.run(
|
|
35844
36604
|
`INSERT OR REPLACE INTO map_runs (session_id, run_number, file_count, map_md_path, parsed_at, source)
|
|
@@ -35964,7 +36724,7 @@ var FilesystemSync = class {
|
|
|
35964
36724
|
const roundId = roundRow?.["id"];
|
|
35965
36725
|
if (!roundId) return;
|
|
35966
36726
|
if (roundRow?.["source"] === "orchestrator") {
|
|
35967
|
-
const content2 =
|
|
36727
|
+
const content2 = readFileSync9(filePath, "utf-8");
|
|
35968
36728
|
const action2 = this.upsertMarkdownArtifact(sessionId, "reviewer-output", filePath, content2, roundNumber);
|
|
35969
36729
|
this.emitArtifactEvent(action2, {
|
|
35970
36730
|
sessionId,
|
|
@@ -35983,7 +36743,7 @@ var FilesystemSync = class {
|
|
|
35983
36743
|
[roundId, reviewerType, instanceNumber]
|
|
35984
36744
|
);
|
|
35985
36745
|
if (existingOutput && this.shouldSkip(filePath, existingOutput["parsed_at"] ?? null)) return;
|
|
35986
|
-
const content =
|
|
36746
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
35987
36747
|
const parsed = parseReviewerOutput(content);
|
|
35988
36748
|
this.db.run(
|
|
35989
36749
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
@@ -36071,7 +36831,7 @@ var FilesystemSync = class {
|
|
|
36071
36831
|
if (existingRound?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
36072
36832
|
let raw;
|
|
36073
36833
|
try {
|
|
36074
|
-
raw = JSON.parse(
|
|
36834
|
+
raw = JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
36075
36835
|
} catch {
|
|
36076
36836
|
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
36077
36837
|
return;
|
|
@@ -36117,12 +36877,12 @@ var FilesystemSync = class {
|
|
|
36117
36877
|
this.db.run("COMMIT");
|
|
36118
36878
|
return;
|
|
36119
36879
|
}
|
|
36120
|
-
const roundDir =
|
|
36880
|
+
const roundDir = dirname10(filePath);
|
|
36121
36881
|
for (const reviewer of meta.reviewers) {
|
|
36122
36882
|
const reviewerType = reviewer.type ?? "unknown";
|
|
36123
36883
|
const instanceNumber = reviewer.instance ?? 1;
|
|
36124
36884
|
const findings = reviewer.findings ?? [];
|
|
36125
|
-
const reviewerMdPath =
|
|
36885
|
+
const reviewerMdPath = join16(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
36126
36886
|
this.db.run(
|
|
36127
36887
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
36128
36888
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -36220,7 +36980,7 @@ var FilesystemSync = class {
|
|
|
36220
36980
|
if (existingRun?.["source"] === "orchestrator" && this.shouldSkip(filePath, existingRun["parsed_at"] ?? null)) return;
|
|
36221
36981
|
let raw;
|
|
36222
36982
|
try {
|
|
36223
|
-
raw = JSON.parse(
|
|
36983
|
+
raw = JSON.parse(readFileSync9(filePath, "utf-8"));
|
|
36224
36984
|
} catch {
|
|
36225
36985
|
console.error(`[FilesystemSync] Failed to parse ${filePath}`);
|
|
36226
36986
|
return;
|
|
@@ -36348,7 +37108,7 @@ var FilesystemSync = class {
|
|
|
36348
37108
|
);
|
|
36349
37109
|
const isOrchestratorSource = existingRound?.["source"] === "orchestrator";
|
|
36350
37110
|
if (!isOrchestratorSource && existingRound && this.shouldSkip(filePath, existingRound["parsed_at"] ?? null)) return;
|
|
36351
|
-
const content =
|
|
37111
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
36352
37112
|
if (isOrchestratorSource) {
|
|
36353
37113
|
this.db.run(
|
|
36354
37114
|
`UPDATE review_rounds SET final_md_path = ?, parsed_at = ?
|
|
@@ -36427,7 +37187,7 @@ var FilesystemSync = class {
|
|
|
36427
37187
|
[sessionId, artifactType, relPath]
|
|
36428
37188
|
);
|
|
36429
37189
|
if (existing && this.shouldSkip(filePath, existing["parsed_at"] ?? null)) return;
|
|
36430
|
-
const content =
|
|
37190
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
36431
37191
|
const action = this.upsertMarkdownArtifact(sessionId, artifactType, filePath, content, roundNumber);
|
|
36432
37192
|
this.emitArtifactEvent(action, {
|
|
36433
37193
|
sessionId,
|
|
@@ -36446,7 +37206,7 @@ var FilesystemSync = class {
|
|
|
36446
37206
|
ignored: [
|
|
36447
37207
|
// Only ignore entries whose own name starts with a dot — the old regex
|
|
36448
37208
|
// /(^|[/\\])\../ matched `.ocr` in the parent path, silencing ALL events.
|
|
36449
|
-
(filePath) =>
|
|
37209
|
+
(filePath) => basename3(filePath).startsWith("."),
|
|
36450
37210
|
/node_modules/,
|
|
36451
37211
|
/\.db$/
|
|
36452
37212
|
]
|
|
@@ -36485,9 +37245,9 @@ var FilesystemSync = class {
|
|
|
36485
37245
|
const parts = relFromSessions.split("/");
|
|
36486
37246
|
const sessionId = parts[0];
|
|
36487
37247
|
if (!sessionId) return;
|
|
36488
|
-
const sessionDir =
|
|
37248
|
+
const sessionDir = join16(this.sessionsDir, sessionId);
|
|
36489
37249
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
36490
|
-
const fileName =
|
|
37250
|
+
const fileName = basename3(filePath);
|
|
36491
37251
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
36492
37252
|
if (reviewerMatch) {
|
|
36493
37253
|
const roundNumber = parseInt(reviewerMatch[1] ?? "0", 10);
|
|
@@ -36557,8 +37317,8 @@ var FilesystemSync = class {
|
|
|
36557
37317
|
};
|
|
36558
37318
|
|
|
36559
37319
|
// src/server/services/db-sync-watcher.ts
|
|
36560
|
-
import { existsSync as
|
|
36561
|
-
import { dirname as
|
|
37320
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
37321
|
+
import { dirname as dirname11, basename as basename4 } from "node:path";
|
|
36562
37322
|
import { watch as watch3 } from "chokidar";
|
|
36563
37323
|
function col(row, key) {
|
|
36564
37324
|
return row[key] ?? null;
|
|
@@ -36599,9 +37359,9 @@ var DbSyncWatcher = class {
|
|
|
36599
37359
|
}
|
|
36600
37360
|
/** Start watching the DB file (and its WAL sidecar) for external writes. */
|
|
36601
37361
|
startWatching() {
|
|
36602
|
-
if (!
|
|
36603
|
-
const watchDir =
|
|
36604
|
-
const dbFile =
|
|
37362
|
+
if (!existsSync14(this.dbFilePath)) return;
|
|
37363
|
+
const watchDir = dirname11(this.dbFilePath);
|
|
37364
|
+
const dbFile = basename4(this.dbFilePath);
|
|
36605
37365
|
const walFile = `${dbFile}-wal`;
|
|
36606
37366
|
this.watcher = watch3(watchDir, {
|
|
36607
37367
|
persistent: true,
|
|
@@ -36611,7 +37371,7 @@ var DbSyncWatcher = class {
|
|
|
36611
37371
|
interval: 200
|
|
36612
37372
|
});
|
|
36613
37373
|
const onAnyEvent = (path2) => {
|
|
36614
|
-
const name =
|
|
37374
|
+
const name = basename4(path2);
|
|
36615
37375
|
if (name === dbFile || name === walFile) this.debouncedSync();
|
|
36616
37376
|
};
|
|
36617
37377
|
this.watcher.on("change", onAnyEvent);
|
|
@@ -36862,20 +37622,20 @@ function commandFingerprint(row) {
|
|
|
36862
37622
|
}
|
|
36863
37623
|
|
|
36864
37624
|
// src/server/socket/chat-handler.ts
|
|
36865
|
-
import { dirname as
|
|
37625
|
+
import { dirname as dirname12 } from "node:path";
|
|
36866
37626
|
|
|
36867
37627
|
// src/server/services/chat-context.ts
|
|
36868
|
-
import { readFileSync as
|
|
36869
|
-
import { join as
|
|
37628
|
+
import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync15 } from "node:fs";
|
|
37629
|
+
import { join as join17 } from "node:path";
|
|
36870
37630
|
function buildChatContext(ocrDir, target) {
|
|
36871
|
-
const sessionsDir =
|
|
37631
|
+
const sessionsDir = join17(ocrDir, "sessions");
|
|
36872
37632
|
if (target.type === "map_run") {
|
|
36873
37633
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
36874
37634
|
}
|
|
36875
37635
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
36876
37636
|
}
|
|
36877
37637
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
36878
|
-
const mapPath =
|
|
37638
|
+
const mapPath = join17(
|
|
36879
37639
|
sessionsDir,
|
|
36880
37640
|
sessionId,
|
|
36881
37641
|
"map",
|
|
@@ -36889,8 +37649,8 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
36889
37649
|
"",
|
|
36890
37650
|
`Below is the Code Review Map that organizes the changeset into reviewable sections:`
|
|
36891
37651
|
];
|
|
36892
|
-
if (
|
|
36893
|
-
const content =
|
|
37652
|
+
if (existsSync15(mapPath)) {
|
|
37653
|
+
const content = readFileSync10(mapPath, "utf-8");
|
|
36894
37654
|
parts.push("");
|
|
36895
37655
|
parts.push("<map>");
|
|
36896
37656
|
parts.push(content);
|
|
@@ -36902,26 +37662,26 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
36902
37662
|
return parts.join("\n");
|
|
36903
37663
|
}
|
|
36904
37664
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
36905
|
-
const roundDir =
|
|
36906
|
-
const finalPath =
|
|
36907
|
-
const reviewersDir =
|
|
37665
|
+
const roundDir = join17(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
37666
|
+
const finalPath = join17(roundDir, "final.md");
|
|
37667
|
+
const reviewersDir = join17(roundDir, "reviews");
|
|
36908
37668
|
const parts = [
|
|
36909
37669
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
36910
37670
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
36911
37671
|
"",
|
|
36912
37672
|
`Below are the review artifacts for this round:`
|
|
36913
37673
|
];
|
|
36914
|
-
if (
|
|
36915
|
-
const content =
|
|
37674
|
+
if (existsSync15(finalPath)) {
|
|
37675
|
+
const content = readFileSync10(finalPath, "utf-8");
|
|
36916
37676
|
parts.push("");
|
|
36917
37677
|
parts.push("<final-synthesis>");
|
|
36918
37678
|
parts.push(content);
|
|
36919
37679
|
parts.push("</final-synthesis>");
|
|
36920
37680
|
}
|
|
36921
|
-
if (
|
|
36922
|
-
const files =
|
|
37681
|
+
if (existsSync15(reviewersDir)) {
|
|
37682
|
+
const files = readdirSync3(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
36923
37683
|
for (const file of files) {
|
|
36924
|
-
const content =
|
|
37684
|
+
const content = readFileSync10(join17(reviewersDir, file), "utf-8");
|
|
36925
37685
|
const reviewerName = file.replace(/\.md$/, "");
|
|
36926
37686
|
parts.push("");
|
|
36927
37687
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -36929,7 +37689,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
36929
37689
|
parts.push("</reviewer>");
|
|
36930
37690
|
}
|
|
36931
37691
|
}
|
|
36932
|
-
if (!
|
|
37692
|
+
if (!existsSync15(finalPath) && !existsSync15(reviewersDir)) {
|
|
36933
37693
|
parts.push("");
|
|
36934
37694
|
parts.push("(No review artifacts found on disk for this round.)");
|
|
36935
37695
|
}
|
|
@@ -37075,7 +37835,7 @@ User: ${message}`;
|
|
|
37075
37835
|
});
|
|
37076
37836
|
return;
|
|
37077
37837
|
}
|
|
37078
|
-
const repoRoot =
|
|
37838
|
+
const repoRoot = dirname12(ocrDir);
|
|
37079
37839
|
const spawnResult = adapter.spawn({
|
|
37080
37840
|
prompt,
|
|
37081
37841
|
cwd: repoRoot,
|
|
@@ -37251,13 +38011,13 @@ function cleanupAllChats() {
|
|
|
37251
38011
|
}
|
|
37252
38012
|
|
|
37253
38013
|
// src/server/socket/post-handler.ts
|
|
37254
|
-
import { existsSync as
|
|
38014
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
37255
38015
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
37256
|
-
import { join as
|
|
38016
|
+
import { join as join18, dirname as dirname13, isAbsolute as isAbsolute2 } from "node:path";
|
|
37257
38017
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
37258
38018
|
function resolveSessionDir2(sessionDir, ocrDir) {
|
|
37259
38019
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
37260
|
-
return
|
|
38020
|
+
return join18(dirname13(ocrDir), sessionDir);
|
|
37261
38021
|
}
|
|
37262
38022
|
var BRANCH_PREFIXES = [
|
|
37263
38023
|
"feat",
|
|
@@ -37326,7 +38086,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37326
38086
|
return;
|
|
37327
38087
|
}
|
|
37328
38088
|
const branch = session.branch;
|
|
37329
|
-
const repoRoot =
|
|
38089
|
+
const repoRoot = dirname13(ocrDir);
|
|
37330
38090
|
try {
|
|
37331
38091
|
await execBinaryAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot, encoding: "utf-8" });
|
|
37332
38092
|
} catch {
|
|
@@ -37427,19 +38187,19 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37427
38187
|
socket.emit("post:error", { error: "Session not found" });
|
|
37428
38188
|
return;
|
|
37429
38189
|
}
|
|
37430
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
37431
|
-
const roundDir =
|
|
37432
|
-
const finalPath =
|
|
37433
|
-
if (!
|
|
38190
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join18(ocrDir, "sessions", sessionId);
|
|
38191
|
+
const roundDir = join18(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38192
|
+
const finalPath = join18(roundDir, "final.md");
|
|
38193
|
+
if (!existsSync16(finalPath)) {
|
|
37434
38194
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
37435
38195
|
return;
|
|
37436
38196
|
}
|
|
37437
|
-
const humanReviewPath =
|
|
37438
|
-
const repoRoot =
|
|
37439
|
-
const commandMdPath =
|
|
38197
|
+
const humanReviewPath = join18(roundDir, "final-human.md");
|
|
38198
|
+
const repoRoot = dirname13(ocrDir);
|
|
38199
|
+
const commandMdPath = join18(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
37440
38200
|
let commandContent;
|
|
37441
38201
|
try {
|
|
37442
|
-
commandContent =
|
|
38202
|
+
commandContent = readFileSync11(commandMdPath, "utf-8");
|
|
37443
38203
|
} catch {
|
|
37444
38204
|
socket.emit("post:error", {
|
|
37445
38205
|
error: `Command file not found: ${commandMdPath}. Run \`ocr init\` to set up.`
|
|
@@ -37519,9 +38279,9 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37519
38279
|
}
|
|
37520
38280
|
}
|
|
37521
38281
|
let generatedContent = "";
|
|
37522
|
-
if (
|
|
38282
|
+
if (existsSync16(humanReviewPath)) {
|
|
37523
38283
|
try {
|
|
37524
|
-
generatedContent =
|
|
38284
|
+
generatedContent = readFileSync11(humanReviewPath, "utf-8").trim();
|
|
37525
38285
|
} catch {
|
|
37526
38286
|
}
|
|
37527
38287
|
}
|
|
@@ -37596,11 +38356,11 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37596
38356
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
37597
38357
|
return;
|
|
37598
38358
|
}
|
|
37599
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
37600
|
-
const roundDir =
|
|
37601
|
-
|
|
37602
|
-
const filePath =
|
|
37603
|
-
|
|
38359
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join18(ocrDir, "sessions", sessionId);
|
|
38360
|
+
const roundDir = join18(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38361
|
+
mkdirSync6(roundDir, { recursive: true });
|
|
38362
|
+
const filePath = join18(roundDir, "final-human.md");
|
|
38363
|
+
writeFileSync5(filePath, content, { mode: 420 });
|
|
37604
38364
|
socket.emit("post:save-result", { success: true });
|
|
37605
38365
|
} catch (err) {
|
|
37606
38366
|
console.error("Error in post:save handler:", err);
|
|
@@ -37626,14 +38386,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37626
38386
|
);
|
|
37627
38387
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
37628
38388
|
`);
|
|
37629
|
-
const tmpDir =
|
|
38389
|
+
const tmpDir = join18(tmpdir2(), "ocr-post-comments");
|
|
37630
38390
|
try {
|
|
37631
|
-
|
|
38391
|
+
mkdirSync6(tmpDir, { recursive: true, mode: 448 });
|
|
37632
38392
|
} catch {
|
|
37633
38393
|
}
|
|
37634
|
-
const tmpFile =
|
|
37635
|
-
|
|
37636
|
-
const repoRoot =
|
|
38394
|
+
const tmpFile = join18(tmpDir, `${randomUUID2()}.md`);
|
|
38395
|
+
writeFileSync5(tmpFile, content, { mode: 384 });
|
|
38396
|
+
const repoRoot = dirname13(ocrDir);
|
|
37637
38397
|
try {
|
|
37638
38398
|
const { stdout } = await execBinaryAsync(
|
|
37639
38399
|
"gh",
|
|
@@ -37656,7 +38416,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
37656
38416
|
});
|
|
37657
38417
|
} finally {
|
|
37658
38418
|
try {
|
|
37659
|
-
|
|
38419
|
+
unlinkSync4(tmpFile);
|
|
37660
38420
|
} catch {
|
|
37661
38421
|
}
|
|
37662
38422
|
}
|
|
@@ -37676,45 +38436,9 @@ function cleanupAllPostGenerations() {
|
|
|
37676
38436
|
}
|
|
37677
38437
|
}
|
|
37678
38438
|
|
|
37679
|
-
// ../cli/src/lib/runtime-config.ts
|
|
37680
|
-
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "node:fs";
|
|
37681
|
-
import { join as join17 } from "node:path";
|
|
37682
|
-
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
37683
|
-
function getAgentHeartbeatSeconds(ocrDir) {
|
|
37684
|
-
const configPath = join17(ocrDir, "config.yaml");
|
|
37685
|
-
if (!existsSync14(configPath)) {
|
|
37686
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
37687
|
-
}
|
|
37688
|
-
let content;
|
|
37689
|
-
try {
|
|
37690
|
-
content = readFileSync11(configPath, "utf-8");
|
|
37691
|
-
} catch {
|
|
37692
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
37693
|
-
}
|
|
37694
|
-
const blockMatch = content.match(
|
|
37695
|
-
/^runtime:\s*\n(?:\s+[^\n]*\n)*?\s+agent_heartbeat_seconds:\s*([^\s#\n]+)/m
|
|
37696
|
-
);
|
|
37697
|
-
const inlineMatch = content.match(
|
|
37698
|
-
/^runtime:\s*\{[^}]*\bagent_heartbeat_seconds:\s*([^\s,}]+)/m
|
|
37699
|
-
);
|
|
37700
|
-
const raw = blockMatch?.[1] ?? inlineMatch?.[1];
|
|
37701
|
-
if (!raw) {
|
|
37702
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
37703
|
-
}
|
|
37704
|
-
const parsed = Number(raw);
|
|
37705
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
|
|
37706
|
-
process.stderr.write(
|
|
37707
|
-
`[ocr] runtime.agent_heartbeat_seconds is not a positive integer (got "${raw}"); falling back to ${DEFAULT_AGENT_HEARTBEAT_SECONDS}s.
|
|
37708
|
-
`
|
|
37709
|
-
);
|
|
37710
|
-
return DEFAULT_AGENT_HEARTBEAT_SECONDS;
|
|
37711
|
-
}
|
|
37712
|
-
return parsed;
|
|
37713
|
-
}
|
|
37714
|
-
|
|
37715
38439
|
// src/server/index.ts
|
|
37716
38440
|
import { homedir } from "node:os";
|
|
37717
|
-
var __dirname3 =
|
|
38441
|
+
var __dirname3 = dirname14(fileURLToPath3(import.meta.url));
|
|
37718
38442
|
function shortenPath(p) {
|
|
37719
38443
|
const home = homedir();
|
|
37720
38444
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
@@ -37778,40 +38502,61 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
37778
38502
|
res.json({ token: AUTH_TOKEN });
|
|
37779
38503
|
});
|
|
37780
38504
|
}
|
|
38505
|
+
function isOcrDashboardProcess(pid) {
|
|
38506
|
+
if (process.platform === "win32") return false;
|
|
38507
|
+
try {
|
|
38508
|
+
const cmd = execFileSync3("ps", ["-p", String(pid), "-o", "command="], {
|
|
38509
|
+
encoding: "utf-8",
|
|
38510
|
+
timeout: 3e3
|
|
38511
|
+
}).trim();
|
|
38512
|
+
if (/^ocr-dashboard\b/.test(cmd)) return true;
|
|
38513
|
+
return /dashboard\/server\.js|server\/index\.ts/.test(cmd);
|
|
38514
|
+
} catch {
|
|
38515
|
+
return false;
|
|
38516
|
+
}
|
|
38517
|
+
}
|
|
37781
38518
|
async function startServer(options = {}) {
|
|
37782
38519
|
const port = options.port ?? parseInt(process.env.PORT ?? "4173", 10);
|
|
38520
|
+
process.title = "ocr-dashboard";
|
|
37783
38521
|
const ocrDir = resolveOcrDir();
|
|
37784
38522
|
const aiCliService = new AiCliService(ocrDir);
|
|
37785
|
-
const dbPathForCheckpoint =
|
|
38523
|
+
const dbPathForCheckpoint = join19(ocrDir, "data", "ocr.db");
|
|
37786
38524
|
const walResult = walCheckpointTruncate(dbPathForCheckpoint);
|
|
37787
38525
|
if (walResult === "checkpointed") {
|
|
37788
38526
|
console.log(" WAL checkpoint: truncated stale write-ahead-log file");
|
|
37789
38527
|
}
|
|
38528
|
+
for (const reaped of reapOrphanDbFiles(join19(ocrDir, "data"))) {
|
|
38529
|
+
console.log(` Orphan reap: removed stale ${reaped}`);
|
|
38530
|
+
}
|
|
38531
|
+
const staleLogs = reapStaleExecLogs(join19(ocrDir, "data", "exec-logs"));
|
|
38532
|
+
if (staleLogs.length > 0) {
|
|
38533
|
+
console.log(` Exec-log reap: removed ${staleLogs.length} stale agent log(s)`);
|
|
38534
|
+
}
|
|
37790
38535
|
const db = await openDb(ocrDir);
|
|
37791
|
-
const dataDir =
|
|
37792
|
-
const pidFilePath =
|
|
37793
|
-
const portFilePath =
|
|
37794
|
-
|
|
38536
|
+
const dataDir = join19(ocrDir, "data");
|
|
38537
|
+
const pidFilePath = join19(dataDir, "dashboard.pid");
|
|
38538
|
+
const portFilePath = join19(dataDir, "server-port");
|
|
38539
|
+
mkdirSync7(dataDir, { recursive: true });
|
|
37795
38540
|
try {
|
|
37796
|
-
|
|
38541
|
+
unlinkSync5(portFilePath);
|
|
37797
38542
|
} catch {
|
|
37798
38543
|
}
|
|
37799
|
-
if (
|
|
38544
|
+
if (existsSync17(pidFilePath)) {
|
|
37800
38545
|
try {
|
|
37801
38546
|
const oldPid = parseInt(readFileSync12(pidFilePath, "utf-8").trim(), 10);
|
|
37802
|
-
if (!isNaN(oldPid)) {
|
|
37803
|
-
|
|
37804
|
-
|
|
37805
|
-
|
|
37806
|
-
|
|
37807
|
-
);
|
|
37808
|
-
} catch {
|
|
38547
|
+
if (!isNaN(oldPid) && oldPid !== process.pid && isProcessAlive(oldPid) && isOcrDashboardProcess(oldPid)) {
|
|
38548
|
+
console.log(` Single-instance: reaping prior dashboard server (PID ${oldPid}) and taking over`);
|
|
38549
|
+
reapTree(oldPid);
|
|
38550
|
+
const deadline = Date.now() + 6e3;
|
|
38551
|
+
while (isProcessAlive(oldPid) && Date.now() < deadline) {
|
|
38552
|
+
await new Promise((resolve3) => setTimeout(resolve3, 100));
|
|
37809
38553
|
}
|
|
38554
|
+
walCheckpointTruncate(dbPathForCheckpoint);
|
|
37810
38555
|
}
|
|
37811
38556
|
} catch {
|
|
37812
38557
|
}
|
|
37813
38558
|
}
|
|
37814
|
-
|
|
38559
|
+
writeFileSync6(pidFilePath, String(process.pid), { mode: 384 });
|
|
37815
38560
|
const cmdCountResult = db.exec("SELECT COUNT(*) as c FROM command_executions");
|
|
37816
38561
|
const totalCmds = cmdCountResult[0]?.values[0]?.[0] ?? 0;
|
|
37817
38562
|
if (totalCmds === 0) {
|
|
@@ -37821,7 +38566,7 @@ async function startServer(options = {}) {
|
|
|
37821
38566
|
}
|
|
37822
38567
|
}
|
|
37823
38568
|
const orphanResult = db.exec(
|
|
37824
|
-
`SELECT id, pid,
|
|
38569
|
+
`SELECT id, pid, started_at FROM command_executions
|
|
37825
38570
|
WHERE pid IS NOT NULL AND finished_at IS NULL`
|
|
37826
38571
|
);
|
|
37827
38572
|
if (orphanResult.length > 0 && orphanResult[0]) {
|
|
@@ -37831,37 +38576,15 @@ async function startServer(options = {}) {
|
|
|
37831
38576
|
let killedCount = 0;
|
|
37832
38577
|
for (const row of orphanRows) {
|
|
37833
38578
|
const pid = row[colIdx["pid"]];
|
|
37834
|
-
const isDetached = row[colIdx["is_detached"]] === 1;
|
|
37835
38579
|
const startedAt = row[colIdx["started_at"]];
|
|
37836
38580
|
if (sqliteUtcMs(startedAt) < cutoff) continue;
|
|
37837
38581
|
if (defaultIsAlive(pid)) {
|
|
37838
|
-
|
|
37839
|
-
try {
|
|
37840
|
-
process.kill(-pid, "SIGTERM");
|
|
37841
|
-
} catch {
|
|
37842
|
-
process.kill(pid, "SIGTERM");
|
|
37843
|
-
}
|
|
37844
|
-
} else {
|
|
37845
|
-
process.kill(pid, "SIGTERM");
|
|
37846
|
-
}
|
|
38582
|
+
reapTree(pid);
|
|
37847
38583
|
killedCount++;
|
|
37848
|
-
setTimeout(() => {
|
|
37849
|
-
try {
|
|
37850
|
-
process.kill(pid, 0);
|
|
37851
|
-
if (isDetached) {
|
|
37852
|
-
try {
|
|
37853
|
-
process.kill(-pid, "SIGKILL");
|
|
37854
|
-
} catch {
|
|
37855
|
-
}
|
|
37856
|
-
}
|
|
37857
|
-
process.kill(pid, "SIGKILL");
|
|
37858
|
-
} catch {
|
|
37859
|
-
}
|
|
37860
|
-
}, 2e3);
|
|
37861
38584
|
}
|
|
37862
38585
|
}
|
|
37863
38586
|
if (killedCount > 0) {
|
|
37864
|
-
console.log(`
|
|
38587
|
+
console.log(` Reaped ${killedCount} orphaned process tree(s)`);
|
|
37865
38588
|
}
|
|
37866
38589
|
}
|
|
37867
38590
|
const legacyResult = db.exec(
|
|
@@ -37871,10 +38594,11 @@ async function startServer(options = {}) {
|
|
|
37871
38594
|
if (legacyCount > 0) {
|
|
37872
38595
|
db.run(
|
|
37873
38596
|
`UPDATE command_executions
|
|
37874
|
-
SET exit_code =
|
|
38597
|
+
SET exit_code = ?,
|
|
37875
38598
|
output = COALESCE(output, '') || '
|
|
37876
38599
|
[Cancelled]'
|
|
37877
|
-
WHERE finished_at IS NOT NULL AND exit_code IS NULL
|
|
38600
|
+
WHERE finished_at IS NOT NULL AND exit_code IS NULL`,
|
|
38601
|
+
[CANCELLED_EXIT_CODE]
|
|
37878
38602
|
);
|
|
37879
38603
|
console.log(` Backfilled ${legacyCount} finished command(s) missing an exit code`);
|
|
37880
38604
|
}
|
|
@@ -37897,11 +38621,25 @@ async function startServer(options = {}) {
|
|
|
37897
38621
|
` Auto-closed ${staleSessionResult.closedSessionIds.length} stale active session(s) (threshold 7 days)`
|
|
37898
38622
|
);
|
|
37899
38623
|
}
|
|
38624
|
+
const reconcileCompleted = async () => {
|
|
38625
|
+
try {
|
|
38626
|
+
const closed = await reconcileCompletedSessions(ocrDir);
|
|
38627
|
+
if (closed.length > 0) {
|
|
38628
|
+
console.log(
|
|
38629
|
+
` Auto-finalized ${closed.length} completed-but-open session(s)`
|
|
38630
|
+
);
|
|
38631
|
+
}
|
|
38632
|
+
} catch (err) {
|
|
38633
|
+
console.error("[reconcile] completed-session reconciliation failed:", err);
|
|
38634
|
+
}
|
|
38635
|
+
};
|
|
38636
|
+
await reconcileCompleted();
|
|
37900
38637
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
37901
38638
|
const sweepTimer = setInterval(() => {
|
|
37902
38639
|
try {
|
|
37903
38640
|
logAgentSweep(sweepStaleAgentSessions(db, heartbeatSeconds, defaultIsAlive));
|
|
37904
38641
|
sweepStaleSessions(db, STALE_SESSION_THRESHOLD_SECONDS);
|
|
38642
|
+
void reconcileCompleted();
|
|
37905
38643
|
} catch (err) {
|
|
37906
38644
|
console.error("[sweep] periodic sweep failed:", err);
|
|
37907
38645
|
}
|
|
@@ -37937,11 +38675,11 @@ async function startServer(options = {}) {
|
|
|
37937
38675
|
app.use("/api/agent-sessions", createAgentSessionsRouter(db, () => pullSync()));
|
|
37938
38676
|
app.use("/api/sessions", createHandoffRouter(sessionCapture, ocrDir, () => pullSync()));
|
|
37939
38677
|
app.use("/api/team", createTeamRouter(ocrDir));
|
|
37940
|
-
const clientDir =
|
|
37941
|
-
if (process.env.NODE_ENV === "production" &&
|
|
38678
|
+
const clientDir = join19(__dirname3, "client");
|
|
38679
|
+
if (process.env.NODE_ENV === "production" && existsSync17(clientDir)) {
|
|
37942
38680
|
app.use(import_express15.default.static(clientDir, { index: false }));
|
|
37943
|
-
const indexHtmlPath =
|
|
37944
|
-
const rawIndexHtml =
|
|
38681
|
+
const indexHtmlPath = join19(clientDir, "index.html");
|
|
38682
|
+
const rawIndexHtml = existsSync17(indexHtmlPath) ? readFileSync12(indexHtmlPath, "utf-8") : "";
|
|
37945
38683
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
37946
38684
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
37947
38685
|
"</head>",
|
|
@@ -37960,7 +38698,7 @@ async function startServer(options = {}) {
|
|
|
37960
38698
|
registerChatHandlers(io, socket, db, ocrDir, aiCliService);
|
|
37961
38699
|
registerPostHandlers(io, socket, db, ocrDir, aiCliService);
|
|
37962
38700
|
});
|
|
37963
|
-
const dbFilePath =
|
|
38701
|
+
const dbFilePath = join19(ocrDir, "data", "ocr.db");
|
|
37964
38702
|
const dbSyncWatcher = new DbSyncWatcher(
|
|
37965
38703
|
db,
|
|
37966
38704
|
dbFilePath,
|
|
@@ -37976,7 +38714,7 @@ async function startServer(options = {}) {
|
|
|
37976
38714
|
dbSyncWatcher.startWatching();
|
|
37977
38715
|
pullSync = () => dbSyncWatcher.syncFromDisk();
|
|
37978
38716
|
console.log(` Watching DB: ${shortenPath(dbFilePath)}`);
|
|
37979
|
-
const sessionsDir =
|
|
38717
|
+
const sessionsDir = join19(ocrDir, "sessions");
|
|
37980
38718
|
const fsSync = new FilesystemSync(db, sessionsDir, io);
|
|
37981
38719
|
await fsSync.fullScan();
|
|
37982
38720
|
fsSync.startWatching();
|
|
@@ -38015,7 +38753,7 @@ async function startServer(options = {}) {
|
|
|
38015
38753
|
if (actualPort !== port) {
|
|
38016
38754
|
console.log(` Note: using port ${actualPort} (${port} was in use)`);
|
|
38017
38755
|
}
|
|
38018
|
-
|
|
38756
|
+
writeFileSync6(portFilePath, String(actualPort), { mode: 384 });
|
|
38019
38757
|
console.log(` Server: http://localhost:${actualPort}`);
|
|
38020
38758
|
console.log(` OCR directory: ${shortenPath(ocrDir)}`);
|
|
38021
38759
|
console.log();
|
|
@@ -38028,53 +38766,40 @@ async function startServer(options = {}) {
|
|
|
38028
38766
|
} catch {
|
|
38029
38767
|
}
|
|
38030
38768
|
}
|
|
38031
|
-
const shutdown = (signal) => {
|
|
38769
|
+
const shutdown = async (signal) => {
|
|
38032
38770
|
console.log(
|
|
38033
38771
|
`Shutting down dashboard server${signal ? ` (received ${signal})` : ""}...`
|
|
38034
38772
|
);
|
|
38035
38773
|
try {
|
|
38036
|
-
|
|
38037
|
-
} catch {
|
|
38038
|
-
}
|
|
38039
|
-
try {
|
|
38040
|
-
unlinkSync3(portFilePath);
|
|
38774
|
+
unlinkSync5(pidFilePath);
|
|
38041
38775
|
} catch {
|
|
38042
38776
|
}
|
|
38043
38777
|
try {
|
|
38044
|
-
|
|
38778
|
+
unlinkSync5(portFilePath);
|
|
38045
38779
|
} catch {
|
|
38046
38780
|
}
|
|
38781
|
+
clearSpawnMarker(ocrDir);
|
|
38047
38782
|
try {
|
|
38048
38783
|
const activeResult = db.exec(
|
|
38049
|
-
"SELECT id, pid
|
|
38784
|
+
"SELECT id, pid FROM command_executions WHERE pid IS NOT NULL AND finished_at IS NULL"
|
|
38050
38785
|
);
|
|
38051
38786
|
if (activeResult.length > 0 && activeResult[0]) {
|
|
38052
38787
|
const { columns, values: activeRows } = activeResult[0];
|
|
38053
38788
|
const colIdx = Object.fromEntries(columns.map((c, i) => [c, i]));
|
|
38054
38789
|
for (const row of activeRows) {
|
|
38055
38790
|
const pid = row[colIdx["pid"]];
|
|
38056
|
-
|
|
38057
|
-
|
|
38058
|
-
if (isDetached) {
|
|
38059
|
-
try {
|
|
38060
|
-
process.kill(-pid, "SIGTERM");
|
|
38061
|
-
} catch {
|
|
38062
|
-
process.kill(pid, "SIGTERM");
|
|
38063
|
-
}
|
|
38064
|
-
} else {
|
|
38065
|
-
process.kill(pid, "SIGTERM");
|
|
38066
|
-
}
|
|
38067
|
-
console.log(`Sent SIGTERM to child process (PID ${pid})`);
|
|
38068
|
-
} catch {
|
|
38069
|
-
}
|
|
38791
|
+
reapTree(pid, 750);
|
|
38792
|
+
console.log(`Reaping child process tree (PID ${pid})`);
|
|
38070
38793
|
}
|
|
38794
|
+
await new Promise((resolve3) => setTimeout(resolve3, 1e3));
|
|
38071
38795
|
db.run(
|
|
38072
38796
|
`UPDATE command_executions
|
|
38073
|
-
SET exit_code =
|
|
38797
|
+
SET exit_code = ?, finished_at = datetime('now'),
|
|
38074
38798
|
output = COALESCE(output, '') || '
|
|
38075
38799
|
[Cancelled \u2014 server shutdown]',
|
|
38076
38800
|
pid = NULL
|
|
38077
|
-
WHERE pid IS NOT NULL AND finished_at IS NULL
|
|
38801
|
+
WHERE pid IS NOT NULL AND finished_at IS NULL`,
|
|
38802
|
+
[CANCELLED_EXIT_CODE]
|
|
38078
38803
|
);
|
|
38079
38804
|
}
|
|
38080
38805
|
} catch (err) {
|
|
@@ -38097,9 +38822,9 @@ async function startServer(options = {}) {
|
|
|
38097
38822
|
process.exit(1);
|
|
38098
38823
|
}, 2e3).unref();
|
|
38099
38824
|
};
|
|
38100
|
-
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
38101
|
-
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
38102
|
-
process.on("SIGHUP", () => shutdown("SIGHUP"));
|
|
38825
|
+
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
38826
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
38827
|
+
process.on("SIGHUP", () => void shutdown("SIGHUP"));
|
|
38103
38828
|
process.on("uncaughtException", (err) => {
|
|
38104
38829
|
console.error("[dashboard] uncaughtException:", err);
|
|
38105
38830
|
});
|