@open-code-review/cli 2.2.1 → 2.3.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 +9 -0
- package/dist/dashboard/client/assets/{_basePickBy-BAlGnwHG.js → _basePickBy-CyrHyeyN.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-CoauyOeL.js → _baseUniq-Bg7NJSGS.js} +1 -1
- package/dist/dashboard/client/assets/{arc-DtS0aHfP.js → arc-zDGAKMur.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-CnWmtRTh.js → architectureDiagram-VXUJARFQ-BxlGxm0Q.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-DgPp4oGV.js → blockDiagram-VD42YOAC-BskTNyX5.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO--LV4qQaE.js → c4Diagram-YG6GDRKO-Dr9QQ-dn.js} +1 -1
- package/dist/dashboard/client/assets/channel-BUnm_-UQ.js +1 -0
- package/dist/dashboard/client/assets/{chunk-4BX2VUAB-BRglpc7Z.js → chunk-4BX2VUAB-xq9xoCTv.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-Bgx06_CV.js → chunk-55IACEB6-DYdXYVh5.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-D6HN3Yiy.js → chunk-B4BG7PRW-BGAyFRFS.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-NH9EgN9T.js → chunk-DI55MBZ5-C5ul9stk.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-xriO6WNP.js → chunk-FMBD7UC4-BSaPo2xa.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-CV1h6_Zl.js → chunk-QN33PNHL-CyzabUv0.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN-CV4VzxNq.js → chunk-QZHKN3VN-CceRbxt_.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-isdklocW.js → chunk-TZMSLE5B-Bjg9IoOQ.js} +1 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-D_fkmNvU.js +1 -0
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-D_fkmNvU.js +1 -0
- package/dist/dashboard/client/assets/clone-DTyrNOLZ.js +1 -0
- package/dist/dashboard/client/assets/{cose-bilkent-S5V4N54A-CCzlFSJf.js → cose-bilkent-S5V4N54A-DEdXBrCt.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-DVN3PkjZ.js → dagre-6UL2VRFP-DRdIiP58.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-SzJVoSsb.js → diagram-PSM6KHXK-Bo7Q2VlK.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-CgGn7ts-.js → diagram-QEK2KX5R-2Fmc2o5x.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-Bz1ukSx8.js → diagram-S2PKOQOG-5WE8f0p7.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-CpstUTMZ.js → erDiagram-Q2GNP2WA-DD-iXWd_.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-aYVydGhp.js → flowDiagram-NV44I4VS-CCWo8Ue9.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-Cb2DUSRk.js → ganttDiagram-JELNMOA3-CNY4d5UK.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-BUOnwA2w.js → gitGraphDiagram-V2S2FVAM-Dq5SBEJJ.js} +1 -1
- package/dist/dashboard/client/assets/{graph-4X5ddhLp.js → graph-BTt9lokK.js} +1 -1
- package/dist/dashboard/client/assets/{index-CKWqYAfu.js → index-B0k81q2b.js} +138 -138
- package/dist/dashboard/client/assets/index-Czwdh6UA.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-BlMqcrwm.js → infoDiagram-HS3SLOUP-AnKZja-G.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-DF2ew7ju.js → journeyDiagram-XKPGCS4Q-nC-_WjPN.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-BKQMx0-n.js → kanban-definition-3W4ZIXB7-BEY73sWU.js} +1 -1
- package/dist/dashboard/client/assets/{layout-DNcn2g9w.js → layout-D4DfNpzH.js} +1 -1
- package/dist/dashboard/client/assets/{linear-Bqy9gvqb.js → linear-ZpGvKjeP.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-dJ71wgld.js → mermaid-renderer-BCDxmS9g.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-BARc8sqJ.js → mindmap-definition-VGOIOE7T-MzAaKESA.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-CULlNZTd.js → pieDiagram-ADFJNKIX-B_X1kySF.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-BJEZPVe9.js → quadrantDiagram-AYHSOK5B-CMoIEMLN.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BhMsmUIs.js → requirementDiagram-UZGBJVZJ-v4CRsn1w.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-BYbNgogG.js → sankeyDiagram-TZEHDZUN-CPcyN8Jj.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-MoM_NwWk.js → sequenceDiagram-WL72ISMW-CTg0Vx1H.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-ditrlbM3.js → stateDiagram-FKZM4ZOC-BMWBN6Nq.js} +1 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-C9Jk1xd0.js +1 -0
- package/dist/dashboard/client/assets/{timeline-definition-IT6M3QCI-DOAJyjuz.js → timeline-definition-IT6M3QCI-B8xFcSGb.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-BBJkjnJl.js → treemap-GDKQZRPO-HQQuGl9w.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CPW4s5vm.js → xychartDiagram-PRI3JC2R-Drz0SW3I.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +910 -461
- package/dist/index.js +1257 -321
- package/package.json +6 -39
- package/dist/dashboard/client/assets/channel-BU2129fl.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-CVftFGiR.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-CVftFGiR.js +0 -1
- package/dist/dashboard/client/assets/clone-DC6LEEC5.js +0 -1
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +0 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-SqoG2LCn.js +0 -1
- package/dist/lib/db/index.js +0 -2177
- package/dist/lib/models.js +0 -160
- package/dist/lib/runtime-config.js +0 -55
- package/dist/lib/state/index.js +0 -2196
- package/dist/lib/team-config.js +0 -175
- package/dist/lib/vendor-resume.js +0 -31
package/dist/dashboard/server.js
CHANGED
|
@@ -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
|
|
18413
|
+
var dirname16 = path2.dirname;
|
|
18414
18414
|
var basename5 = path2.basename;
|
|
18415
18415
|
var extname = path2.extname;
|
|
18416
|
-
var
|
|
18416
|
+
var join21 = path2.join;
|
|
18417
18417
|
var resolve3 = path2.resolve;
|
|
18418
18418
|
module.exports = View;
|
|
18419
18419
|
function View(name, options) {
|
|
@@ -18449,7 +18449,7 @@ 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 =
|
|
18452
|
+
var dir = dirname16(loc);
|
|
18453
18453
|
var file = basename5(loc);
|
|
18454
18454
|
path3 = this.resolve(dir, file);
|
|
18455
18455
|
}
|
|
@@ -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 = join21(dir, file);
|
|
18465
18465
|
var stat = tryStat(path3);
|
|
18466
18466
|
if (stat && stat.isFile()) {
|
|
18467
18467
|
return path3;
|
|
18468
18468
|
}
|
|
18469
|
-
path3 =
|
|
18469
|
+
path3 = join21(dir, basename5(file, ext), "index" + ext);
|
|
18470
18470
|
stat = tryStat(path3);
|
|
18471
18471
|
if (stat && stat.isFile()) {
|
|
18472
18472
|
return path3;
|
|
@@ -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 join21 = 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(join21(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 = join21(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);
|
|
@@ -30047,9 +30047,9 @@ var init_define_lazy_prop = __esm({
|
|
|
30047
30047
|
});
|
|
30048
30048
|
|
|
30049
30049
|
// ../../node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
|
|
30050
|
-
import { promisify
|
|
30050
|
+
import { promisify } from "node:util";
|
|
30051
30051
|
import process4 from "node:process";
|
|
30052
|
-
import { execFile
|
|
30052
|
+
import { execFile } from "node:child_process";
|
|
30053
30053
|
async function defaultBrowserId() {
|
|
30054
30054
|
if (process4.platform !== "darwin") {
|
|
30055
30055
|
throw new Error("macOS only");
|
|
@@ -30065,14 +30065,14 @@ async function defaultBrowserId() {
|
|
|
30065
30065
|
var execFileAsync;
|
|
30066
30066
|
var init_default_browser_id = __esm({
|
|
30067
30067
|
"../../node_modules/.pnpm/default-browser-id@5.0.1/node_modules/default-browser-id/index.js"() {
|
|
30068
|
-
execFileAsync =
|
|
30068
|
+
execFileAsync = promisify(execFile);
|
|
30069
30069
|
}
|
|
30070
30070
|
});
|
|
30071
30071
|
|
|
30072
30072
|
// ../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js
|
|
30073
30073
|
import process5 from "node:process";
|
|
30074
|
-
import { promisify as
|
|
30075
|
-
import { execFile as
|
|
30074
|
+
import { promisify as promisify2 } from "node:util";
|
|
30075
|
+
import { execFile as execFile2, execFileSync as execFileSync2 } from "node:child_process";
|
|
30076
30076
|
async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
|
|
30077
30077
|
if (process5.platform !== "darwin") {
|
|
30078
30078
|
throw new Error("macOS only");
|
|
@@ -30088,7 +30088,7 @@ async function runAppleScript(script, { humanReadableOutput = true, signal } = {
|
|
|
30088
30088
|
var execFileAsync2;
|
|
30089
30089
|
var init_run_applescript = __esm({
|
|
30090
30090
|
"../../node_modules/.pnpm/run-applescript@7.1.0/node_modules/run-applescript/index.js"() {
|
|
30091
|
-
execFileAsync2 =
|
|
30091
|
+
execFileAsync2 = promisify2(execFile2);
|
|
30092
30092
|
}
|
|
30093
30093
|
});
|
|
30094
30094
|
|
|
@@ -30104,8 +30104,8 @@ var init_bundle_name = __esm({
|
|
|
30104
30104
|
});
|
|
30105
30105
|
|
|
30106
30106
|
// ../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/windows.js
|
|
30107
|
-
import { promisify as
|
|
30108
|
-
import { execFile as
|
|
30107
|
+
import { promisify as promisify3 } from "node:util";
|
|
30108
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
30109
30109
|
async function defaultBrowser(_execFileAsync = execFileAsync3) {
|
|
30110
30110
|
const { stdout } = await _execFileAsync("reg", [
|
|
30111
30111
|
"QUERY",
|
|
@@ -30127,7 +30127,7 @@ async function defaultBrowser(_execFileAsync = execFileAsync3) {
|
|
|
30127
30127
|
var execFileAsync3, windowsBrowserProgIds, _windowsBrowserProgIdMap, UnknownBrowserError;
|
|
30128
30128
|
var init_windows = __esm({
|
|
30129
30129
|
"../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/windows.js"() {
|
|
30130
|
-
execFileAsync3 =
|
|
30130
|
+
execFileAsync3 = promisify3(execFile3);
|
|
30131
30131
|
windowsBrowserProgIds = {
|
|
30132
30132
|
MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
|
|
30133
30133
|
// The missing `L` is correct.
|
|
@@ -30154,9 +30154,9 @@ var init_windows = __esm({
|
|
|
30154
30154
|
});
|
|
30155
30155
|
|
|
30156
30156
|
// ../../node_modules/.pnpm/default-browser@5.5.0/node_modules/default-browser/index.js
|
|
30157
|
-
import { promisify as
|
|
30157
|
+
import { promisify as promisify4 } from "node:util";
|
|
30158
30158
|
import process6 from "node:process";
|
|
30159
|
-
import { execFile as
|
|
30159
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
30160
30160
|
async function defaultBrowser2() {
|
|
30161
30161
|
if (process6.platform === "darwin") {
|
|
30162
30162
|
const id = await defaultBrowserId();
|
|
@@ -30180,7 +30180,7 @@ var init_default_browser = __esm({
|
|
|
30180
30180
|
init_default_browser_id();
|
|
30181
30181
|
init_bundle_name();
|
|
30182
30182
|
init_windows();
|
|
30183
|
-
execFileAsync4 =
|
|
30183
|
+
execFileAsync4 = promisify4(execFile4);
|
|
30184
30184
|
titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
|
|
30185
30185
|
}
|
|
30186
30186
|
});
|
|
@@ -30196,14 +30196,14 @@ import process7 from "node:process";
|
|
|
30196
30196
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
30197
30197
|
import path from "node:path";
|
|
30198
30198
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
30199
|
-
import { promisify as
|
|
30199
|
+
import { promisify as promisify5 } from "node:util";
|
|
30200
30200
|
import childProcess from "node:child_process";
|
|
30201
30201
|
import fs5, { constants as fsConstants2 } from "node:fs/promises";
|
|
30202
30202
|
async function getWindowsDefaultBrowserFromWsl() {
|
|
30203
30203
|
const powershellPath = await powerShellPath();
|
|
30204
30204
|
const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
|
|
30205
30205
|
const encodedCommand = Buffer2.from(rawCommand, "utf16le").toString("base64");
|
|
30206
|
-
const { stdout } = await
|
|
30206
|
+
const { stdout } = await execFile5(
|
|
30207
30207
|
powershellPath,
|
|
30208
30208
|
[
|
|
30209
30209
|
"-NoProfile",
|
|
@@ -30243,14 +30243,14 @@ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
|
|
|
30243
30243
|
}
|
|
30244
30244
|
return detectArchBinary(platformBinary);
|
|
30245
30245
|
}
|
|
30246
|
-
var
|
|
30246
|
+
var execFile5, __dirname2, localXdgOpenPath, platform, arch, pTryEach, baseOpen, open, openApp, apps, open_default;
|
|
30247
30247
|
var init_open = __esm({
|
|
30248
30248
|
"../../node_modules/.pnpm/open@10.2.0/node_modules/open/index.js"() {
|
|
30249
30249
|
init_wsl_utils();
|
|
30250
30250
|
init_define_lazy_prop();
|
|
30251
30251
|
init_default_browser();
|
|
30252
30252
|
init_is_inside_container();
|
|
30253
|
-
|
|
30253
|
+
execFile5 = promisify5(childProcess.execFile);
|
|
30254
30254
|
__dirname2 = path.dirname(fileURLToPath2(import.meta.url));
|
|
30255
30255
|
localXdgOpenPath = path.join(__dirname2, "xdg-open");
|
|
30256
30256
|
({ platform, arch } = process7);
|
|
@@ -30483,42 +30483,197 @@ 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 existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, mkdirSync as
|
|
30487
|
-
import { join as
|
|
30486
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync5, mkdirSync as mkdirSync8 } from "node:fs";
|
|
30487
|
+
import { join as join20, dirname as dirname15, resolve as resolve2 } from "node:path";
|
|
30488
30488
|
|
|
30489
30489
|
// ../shared/platform/src/index.ts
|
|
30490
|
-
import {
|
|
30491
|
-
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
|
|
30495
|
-
import { promisify } from "node:util";
|
|
30496
|
-
var execFilePromise = promisify(execFile);
|
|
30497
|
-
var isWindows = process.platform === "win32";
|
|
30490
|
+
import { execFileSync } from "node:child_process";
|
|
30491
|
+
|
|
30492
|
+
// ../shared/platform/src/spawn.ts
|
|
30493
|
+
import crossSpawn from "cross-spawn";
|
|
30494
|
+
var DEFAULT_MAX_BUFFER = 1024 * 1024;
|
|
30498
30495
|
function execBinary(binary, args, opts) {
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30496
|
+
const result = crossSpawn.sync(binary, args, {
|
|
30497
|
+
maxBuffer: DEFAULT_MAX_BUFFER,
|
|
30498
|
+
...opts
|
|
30502
30499
|
});
|
|
30500
|
+
if (result.error) throw result.error;
|
|
30501
|
+
if (result.status !== 0) {
|
|
30502
|
+
throw Object.assign(
|
|
30503
|
+
new Error(
|
|
30504
|
+
`Command failed: ${binary} ${args.join(" ")}
|
|
30505
|
+
${String(result.stderr ?? "")}`
|
|
30506
|
+
),
|
|
30507
|
+
{
|
|
30508
|
+
status: result.status,
|
|
30509
|
+
code: result.status,
|
|
30510
|
+
signal: result.signal,
|
|
30511
|
+
stdout: result.stdout,
|
|
30512
|
+
stderr: result.stderr,
|
|
30513
|
+
pid: result.pid
|
|
30514
|
+
}
|
|
30515
|
+
);
|
|
30516
|
+
}
|
|
30517
|
+
return result.stdout;
|
|
30503
30518
|
}
|
|
30504
30519
|
async function execBinaryAsync(binary, args, opts) {
|
|
30505
|
-
return
|
|
30506
|
-
|
|
30507
|
-
|
|
30520
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
30521
|
+
const child = crossSpawn(binary, args, {
|
|
30522
|
+
cwd: opts.cwd,
|
|
30523
|
+
env: opts.env,
|
|
30524
|
+
windowsHide: true
|
|
30525
|
+
});
|
|
30526
|
+
const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
30527
|
+
let stdout = "";
|
|
30528
|
+
let stderr = "";
|
|
30529
|
+
let killed = false;
|
|
30530
|
+
let settled = false;
|
|
30531
|
+
const settle = (fn) => {
|
|
30532
|
+
if (settled) return;
|
|
30533
|
+
settled = true;
|
|
30534
|
+
if (timer) clearTimeout(timer);
|
|
30535
|
+
fn();
|
|
30536
|
+
};
|
|
30537
|
+
const overflow = () => {
|
|
30538
|
+
killed = true;
|
|
30539
|
+
child.kill();
|
|
30540
|
+
};
|
|
30541
|
+
child.stdout?.setEncoding(opts.encoding);
|
|
30542
|
+
child.stderr?.setEncoding(opts.encoding);
|
|
30543
|
+
child.stdout?.on("data", (chunk) => {
|
|
30544
|
+
stdout += chunk;
|
|
30545
|
+
if (stdout.length > maxBuffer) overflow();
|
|
30546
|
+
});
|
|
30547
|
+
child.stderr?.on("data", (chunk) => {
|
|
30548
|
+
stderr += chunk;
|
|
30549
|
+
if (stderr.length > maxBuffer) overflow();
|
|
30550
|
+
});
|
|
30551
|
+
const timer = opts.timeout ? setTimeout(() => {
|
|
30552
|
+
killed = true;
|
|
30553
|
+
child.kill();
|
|
30554
|
+
}, opts.timeout) : void 0;
|
|
30555
|
+
child.on("error", (err) => {
|
|
30556
|
+
settle(
|
|
30557
|
+
() => rejectPromise(Object.assign(err, { stdout, stderr, killed }))
|
|
30558
|
+
);
|
|
30559
|
+
});
|
|
30560
|
+
child.on("close", (code, signal) => {
|
|
30561
|
+
settle(() => {
|
|
30562
|
+
if (code === 0 && !killed) {
|
|
30563
|
+
resolvePromise({ stdout, stderr });
|
|
30564
|
+
return;
|
|
30565
|
+
}
|
|
30566
|
+
rejectPromise(
|
|
30567
|
+
Object.assign(
|
|
30568
|
+
new Error(
|
|
30569
|
+
`Command failed: ${binary} ${args.join(" ")}
|
|
30570
|
+
${stderr}`
|
|
30571
|
+
),
|
|
30572
|
+
{
|
|
30573
|
+
code: code ?? void 0,
|
|
30574
|
+
signal,
|
|
30575
|
+
stdout,
|
|
30576
|
+
stderr,
|
|
30577
|
+
killed
|
|
30578
|
+
}
|
|
30579
|
+
)
|
|
30580
|
+
);
|
|
30581
|
+
});
|
|
30582
|
+
});
|
|
30508
30583
|
});
|
|
30509
30584
|
}
|
|
30510
30585
|
function spawnBinary(binary, args, opts) {
|
|
30511
|
-
return
|
|
30586
|
+
return crossSpawn(binary, args, {
|
|
30512
30587
|
...opts,
|
|
30513
|
-
|
|
30588
|
+
windowsHide: true
|
|
30514
30589
|
});
|
|
30515
30590
|
}
|
|
30591
|
+
|
|
30592
|
+
// ../shared/platform/src/verdict.ts
|
|
30593
|
+
var CANONICAL_VERDICTS = [
|
|
30594
|
+
"APPROVE",
|
|
30595
|
+
"REQUEST CHANGES",
|
|
30596
|
+
"NEEDS DISCUSSION"
|
|
30597
|
+
];
|
|
30598
|
+
var VERDICT_SET = new Set(CANONICAL_VERDICTS);
|
|
30599
|
+
function isCanonicalVerdict(v) {
|
|
30600
|
+
return VERDICT_SET.has(v);
|
|
30601
|
+
}
|
|
30602
|
+
var VERDICT_ALIASES = {
|
|
30603
|
+
// Approve-gate aliases (including the retired composites)
|
|
30604
|
+
APPROVED: "APPROVE",
|
|
30605
|
+
LGTM: "APPROVE",
|
|
30606
|
+
"APPROVE WITH SUGGESTIONS": "APPROVE",
|
|
30607
|
+
APPROVE_WITH_SUGGESTIONS: "APPROVE",
|
|
30608
|
+
"ACCEPT WITH FOLLOW-UPS": "APPROVE",
|
|
30609
|
+
"ACCEPT WITH FOLLOWUPS": "APPROVE",
|
|
30610
|
+
ACCEPT_WITH_FOLLOWUPS: "APPROVE",
|
|
30611
|
+
ACCEPT_WITH_FOLLOW_UPS: "APPROVE",
|
|
30612
|
+
// Request-changes-gate aliases
|
|
30613
|
+
"CHANGES REQUESTED": "REQUEST CHANGES",
|
|
30614
|
+
REQUEST_CHANGES: "REQUEST CHANGES",
|
|
30615
|
+
BLOCK: "REQUEST CHANGES",
|
|
30616
|
+
REJECT: "REQUEST CHANGES",
|
|
30617
|
+
// Needs-discussion-gate aliases
|
|
30618
|
+
"NEEDS WORK": "NEEDS DISCUSSION",
|
|
30619
|
+
NEEDS_DISCUSSION: "NEEDS DISCUSSION"
|
|
30620
|
+
};
|
|
30621
|
+
function normalizeVerdict(raw) {
|
|
30622
|
+
const key = raw.trim().toUpperCase();
|
|
30623
|
+
if (isCanonicalVerdict(key)) return key;
|
|
30624
|
+
return VERDICT_ALIASES[key] ?? null;
|
|
30625
|
+
}
|
|
30626
|
+
|
|
30627
|
+
// ../shared/platform/src/counts.ts
|
|
30628
|
+
function deriveCounts(findings) {
|
|
30629
|
+
const counts = {
|
|
30630
|
+
blocker: 0,
|
|
30631
|
+
should_fix: 0,
|
|
30632
|
+
suggestion: 0,
|
|
30633
|
+
style: 0
|
|
30634
|
+
};
|
|
30635
|
+
for (const finding of findings) {
|
|
30636
|
+
const category = finding?.category;
|
|
30637
|
+
if (category === "blocker" || category === "should_fix" || category === "suggestion" || category === "style") {
|
|
30638
|
+
counts[category]++;
|
|
30639
|
+
}
|
|
30640
|
+
}
|
|
30641
|
+
return counts;
|
|
30642
|
+
}
|
|
30643
|
+
function collectFindings(meta) {
|
|
30644
|
+
const all = [];
|
|
30645
|
+
for (const reviewer of meta.reviewers ?? []) {
|
|
30646
|
+
for (const finding of reviewer?.findings ?? []) all.push(finding);
|
|
30647
|
+
}
|
|
30648
|
+
return all;
|
|
30649
|
+
}
|
|
30650
|
+
function preferred(scValue, derivedValue) {
|
|
30651
|
+
return typeof scValue === "number" && Number.isFinite(scValue) ? scValue : derivedValue;
|
|
30652
|
+
}
|
|
30653
|
+
function resolveRoundCounts(meta) {
|
|
30654
|
+
const allFindings = collectFindings(meta);
|
|
30655
|
+
const derived = deriveCounts(allFindings);
|
|
30656
|
+
const sc = meta.synthesis_counts ?? void 0;
|
|
30657
|
+
return {
|
|
30658
|
+
blockerCount: sc ? preferred(sc.blockers, derived.blocker) : derived.blocker,
|
|
30659
|
+
shouldFixCount: sc ? preferred(sc.should_fix, derived.should_fix) : derived.should_fix,
|
|
30660
|
+
suggestionCount: sc ? preferred(sc.suggestions, derived.suggestion) : derived.suggestion,
|
|
30661
|
+
reviewerCount: (meta.reviewers ?? []).length,
|
|
30662
|
+
totalFindingCount: allFindings.length
|
|
30663
|
+
};
|
|
30664
|
+
}
|
|
30665
|
+
|
|
30666
|
+
// ../shared/platform/src/index.ts
|
|
30667
|
+
var isWindows = process.platform === "win32";
|
|
30668
|
+
function killErrorMeansDead(err) {
|
|
30669
|
+
return err instanceof Error && "code" in err && err.code === "ESRCH";
|
|
30670
|
+
}
|
|
30516
30671
|
function isProcessAlive(pid) {
|
|
30517
30672
|
try {
|
|
30518
30673
|
process.kill(pid, 0);
|
|
30519
30674
|
return true;
|
|
30520
30675
|
} catch (err) {
|
|
30521
|
-
return !(err
|
|
30676
|
+
return !killErrorMeansDead(err);
|
|
30522
30677
|
}
|
|
30523
30678
|
}
|
|
30524
30679
|
function walkDescendants(rootPid) {
|
|
@@ -30538,14 +30693,15 @@ function walkDescendants(rootPid) {
|
|
|
30538
30693
|
if (!m) continue;
|
|
30539
30694
|
const pid = Number(m[1]);
|
|
30540
30695
|
const ppid = Number(m[2]);
|
|
30541
|
-
|
|
30542
|
-
|
|
30696
|
+
const siblings = children.get(ppid) ?? [];
|
|
30697
|
+
siblings.push(pid);
|
|
30698
|
+
children.set(ppid, siblings);
|
|
30543
30699
|
}
|
|
30544
30700
|
const acc = [];
|
|
30545
30701
|
const queue = [rootPid];
|
|
30546
30702
|
const seen = /* @__PURE__ */ new Set([rootPid]);
|
|
30547
|
-
|
|
30548
|
-
|
|
30703
|
+
let p;
|
|
30704
|
+
while ((p = queue.shift()) !== void 0) {
|
|
30549
30705
|
for (const c of children.get(p) ?? []) {
|
|
30550
30706
|
if (seen.has(c)) continue;
|
|
30551
30707
|
seen.add(c);
|
|
@@ -30628,7 +30784,6 @@ function defaultIconFor(id, tier) {
|
|
|
30628
30784
|
// src/server/index.ts
|
|
30629
30785
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
30630
30786
|
import { randomBytes } from "node:crypto";
|
|
30631
|
-
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
30632
30787
|
import { Server as SocketIOServer } from "socket.io";
|
|
30633
30788
|
|
|
30634
30789
|
// src/server/services/ocr-resolver.ts
|
|
@@ -30651,7 +30806,7 @@ function resolveOcrDir(startDir) {
|
|
|
30651
30806
|
}
|
|
30652
30807
|
}
|
|
30653
30808
|
|
|
30654
|
-
// ../
|
|
30809
|
+
// ../shared/persistence/src/db/index.ts
|
|
30655
30810
|
import {
|
|
30656
30811
|
existsSync as existsSync5,
|
|
30657
30812
|
mkdirSync as mkdirSync2,
|
|
@@ -30662,10 +30817,10 @@ import {
|
|
|
30662
30817
|
} from "node:fs";
|
|
30663
30818
|
import { dirname as dirname5, join as join5 } from "node:path";
|
|
30664
30819
|
|
|
30665
|
-
// ../
|
|
30820
|
+
// ../shared/persistence/src/db/engine.ts
|
|
30666
30821
|
import { createRequire } from "node:module";
|
|
30667
30822
|
|
|
30668
|
-
// ../
|
|
30823
|
+
// ../shared/persistence/src/runtime-checks.ts
|
|
30669
30824
|
var NODE_FLOOR = { major: 22, minor: 5 };
|
|
30670
30825
|
function isSupportedNode(version) {
|
|
30671
30826
|
const [major = 0, minor = 0] = version.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
@@ -30683,7 +30838,7 @@ function isSuppressibleSqliteWarning(warning) {
|
|
|
30683
30838
|
return typeof message === "string" && message.includes("SQLite is an experimental feature");
|
|
30684
30839
|
}
|
|
30685
30840
|
|
|
30686
|
-
// ../
|
|
30841
|
+
// ../shared/persistence/src/db/engine.ts
|
|
30687
30842
|
var SQLITE_BUSY = 5;
|
|
30688
30843
|
var SQLITE_BUSY_SNAPSHOT = 261;
|
|
30689
30844
|
var BUSY_RETRY_ATTEMPTS = 5;
|
|
@@ -30845,7 +31000,7 @@ function openEngine(dbPath) {
|
|
|
30845
31000
|
return new NodeSqliteAdapter(native);
|
|
30846
31001
|
}
|
|
30847
31002
|
|
|
30848
|
-
// ../
|
|
31003
|
+
// ../shared/persistence/src/db/migrations.ts
|
|
30849
31004
|
var MIGRATIONS = [
|
|
30850
31005
|
{
|
|
30851
31006
|
version: 1,
|
|
@@ -31393,11 +31548,11 @@ function runMigrations(db) {
|
|
|
31393
31548
|
}
|
|
31394
31549
|
}
|
|
31395
31550
|
|
|
31396
|
-
// ../
|
|
31551
|
+
// ../shared/persistence/src/db/reconcile.ts
|
|
31397
31552
|
import { existsSync as existsSync2 } from "node:fs";
|
|
31398
31553
|
import { isAbsolute, join as join2, dirname as dirname2 } from "node:path";
|
|
31399
31554
|
|
|
31400
|
-
// ../
|
|
31555
|
+
// ../shared/persistence/src/db/result-mapper.ts
|
|
31401
31556
|
function resultToRows(result) {
|
|
31402
31557
|
if (result.length === 0 || !result[0]) {
|
|
31403
31558
|
return [];
|
|
@@ -31416,7 +31571,7 @@ function resultToRow(result) {
|
|
|
31416
31571
|
return rows[0];
|
|
31417
31572
|
}
|
|
31418
31573
|
|
|
31419
|
-
// ../
|
|
31574
|
+
// ../shared/persistence/src/db/queries.ts
|
|
31420
31575
|
function insertSession(db, params) {
|
|
31421
31576
|
const {
|
|
31422
31577
|
id,
|
|
@@ -31499,6 +31654,14 @@ function insertEvent(db, params) {
|
|
|
31499
31654
|
]
|
|
31500
31655
|
);
|
|
31501
31656
|
}
|
|
31657
|
+
function getEventsForSession(db, sessionId) {
|
|
31658
|
+
return resultToRows(
|
|
31659
|
+
db.exec(
|
|
31660
|
+
"SELECT * FROM orchestration_events WHERE session_id = ? ORDER BY id ASC",
|
|
31661
|
+
[sessionId]
|
|
31662
|
+
)
|
|
31663
|
+
);
|
|
31664
|
+
}
|
|
31502
31665
|
function commitReasonClose(db, sessionId, reasonEvent, projectionUpdates) {
|
|
31503
31666
|
db.transaction(() => {
|
|
31504
31667
|
insertEvent(db, { session_id: sessionId, ...reasonEvent });
|
|
@@ -31506,7 +31669,7 @@ function commitReasonClose(db, sessionId, reasonEvent, projectionUpdates) {
|
|
|
31506
31669
|
});
|
|
31507
31670
|
}
|
|
31508
31671
|
|
|
31509
|
-
// ../
|
|
31672
|
+
// ../shared/persistence/src/db/reconcile.ts
|
|
31510
31673
|
var DEFAULT_STALE_THRESHOLD_SECONDS = 7 * 24 * 60 * 60;
|
|
31511
31674
|
function hasTerminalArtifactEvent(db, sessionId, workflowType, currentRound, currentMapRun) {
|
|
31512
31675
|
const eventType = workflowType === "map" ? "map_completed" : "round_completed";
|
|
@@ -31636,14 +31799,14 @@ function reconcileLegacyState(db, ocrDir, opts = {}) {
|
|
|
31636
31799
|
return { dryRun, actions };
|
|
31637
31800
|
}
|
|
31638
31801
|
|
|
31639
|
-
// ../
|
|
31802
|
+
// ../shared/persistence/src/db/liveness.ts
|
|
31640
31803
|
var PID_REUSE_GUARD_MS = 24 * 60 * 60 * 1e3;
|
|
31641
31804
|
function defaultIsAlive(pid) {
|
|
31642
31805
|
try {
|
|
31643
31806
|
process.kill(pid, 0);
|
|
31644
31807
|
return true;
|
|
31645
31808
|
} catch (err) {
|
|
31646
|
-
return !(err
|
|
31809
|
+
return !killErrorMeansDead(err);
|
|
31647
31810
|
}
|
|
31648
31811
|
}
|
|
31649
31812
|
function sqliteUtcMs(ts) {
|
|
@@ -31651,7 +31814,7 @@ function sqliteUtcMs(ts) {
|
|
|
31651
31814
|
return new Date(sqliteShape ? ts.replace(" ", "T") + "Z" : ts).getTime();
|
|
31652
31815
|
}
|
|
31653
31816
|
|
|
31654
|
-
// ../
|
|
31817
|
+
// ../shared/persistence/src/state/exit-codes.ts
|
|
31655
31818
|
var STATE_EXIT = {
|
|
31656
31819
|
OK: 0,
|
|
31657
31820
|
USAGE: 2,
|
|
@@ -31675,7 +31838,7 @@ var ORPHAN_EXIT_CODE = -3;
|
|
|
31675
31838
|
var CASCADE_CLOSE_EXIT_CODE = -4;
|
|
31676
31839
|
var WATCHDOG_DEADLINE_EXIT_CODE = -5;
|
|
31677
31840
|
|
|
31678
|
-
// ../
|
|
31841
|
+
// ../shared/persistence/src/db/agent-sessions.ts
|
|
31679
31842
|
var NOTE_ORPHAN_PREFIX = "orphaned by liveness sweep";
|
|
31680
31843
|
var INSTANCE_COMMAND = "session-instance";
|
|
31681
31844
|
function cascadeTerminateExecutions(db, workflowId, exitCode, note) {
|
|
@@ -31747,6 +31910,10 @@ function getLatestAgentSessionWithVendorId(db, workflowId) {
|
|
|
31747
31910
|
);
|
|
31748
31911
|
return row ? rowToAgentSession(row) : void 0;
|
|
31749
31912
|
}
|
|
31913
|
+
var SAFE_VENDOR_SESSION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
31914
|
+
function isSafeVendorSessionId(id) {
|
|
31915
|
+
return SAFE_VENDOR_SESSION_ID.test(id);
|
|
31916
|
+
}
|
|
31750
31917
|
function recordVendorSessionIdForExecution(db, executionId, vendorSessionId) {
|
|
31751
31918
|
db.run(
|
|
31752
31919
|
`UPDATE command_executions
|
|
@@ -31868,7 +32035,7 @@ function sweepStaleSessions(db, thresholdSeconds) {
|
|
|
31868
32035
|
return { closedSessionIds: rows.map((r) => r.id) };
|
|
31869
32036
|
}
|
|
31870
32037
|
|
|
31871
|
-
// ../
|
|
32038
|
+
// ../shared/persistence/src/db/maintenance.ts
|
|
31872
32039
|
import {
|
|
31873
32040
|
existsSync as existsSync3,
|
|
31874
32041
|
readdirSync,
|
|
@@ -31943,7 +32110,7 @@ function reapStaleExecLogs(execLogsDir, maxAgeMs = SEVEN_DAYS_MS) {
|
|
|
31943
32110
|
return reaped;
|
|
31944
32111
|
}
|
|
31945
32112
|
|
|
31946
|
-
// ../
|
|
32113
|
+
// ../shared/persistence/src/db/command-log.ts
|
|
31947
32114
|
import { appendFileSync, existsSync as existsSync4, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
31948
32115
|
import { dirname as dirname4, join as join4 } from "node:path";
|
|
31949
32116
|
import { randomUUID } from "node:crypto";
|
|
@@ -32040,7 +32207,7 @@ function rotateIfNeeded(filePath) {
|
|
|
32040
32207
|
}
|
|
32041
32208
|
}
|
|
32042
32209
|
|
|
32043
|
-
// ../
|
|
32210
|
+
// ../shared/persistence/src/db/index.ts
|
|
32044
32211
|
var V2_SCHEMA_VERSION = 12;
|
|
32045
32212
|
function maybeSnapshotBeforeUpgrade(db, dbPath, fromVersion) {
|
|
32046
32213
|
if (fromVersion < 1 || fromVersion >= V2_SCHEMA_VERSION) return null;
|
|
@@ -33346,41 +33513,8 @@ function createStatsRouter(db) {
|
|
|
33346
33513
|
var import_express8 = __toESM(require_express2(), 1);
|
|
33347
33514
|
|
|
33348
33515
|
// src/server/socket/command-runner.ts
|
|
33349
|
-
import { readFileSync as readFileSync5,
|
|
33350
|
-
import { dirname as dirname7, join as
|
|
33351
|
-
|
|
33352
|
-
// src/server/services/command-outcome.ts
|
|
33353
|
-
function deriveCommandOutcome(exitCode, completeness) {
|
|
33354
|
-
if (exitCode === null) return null;
|
|
33355
|
-
if (exitCode === CANCELLED_EXIT_CODE || exitCode === CASCADE_CLOSE_EXIT_CODE) {
|
|
33356
|
-
return "cancelled";
|
|
33357
|
-
}
|
|
33358
|
-
if (exitCode === WATCHDOG_DEADLINE_EXIT_CODE) return "failed";
|
|
33359
|
-
if (exitCode !== 0) return "failed";
|
|
33360
|
-
if (completeness === null || completeness === "complete") return "success";
|
|
33361
|
-
return "incomplete";
|
|
33362
|
-
}
|
|
33363
|
-
function deriveCancellationReason(exitCode) {
|
|
33364
|
-
if (exitCode === CANCELLED_EXIT_CODE) return "user";
|
|
33365
|
-
if (exitCode === CASCADE_CLOSE_EXIT_CODE) return "cascade";
|
|
33366
|
-
return null;
|
|
33367
|
-
}
|
|
33368
|
-
function getWorkflowCompletenessForExecution(db, executionId) {
|
|
33369
|
-
const result = db.exec(
|
|
33370
|
-
`SELECT sc.completeness_state
|
|
33371
|
-
FROM command_executions ce
|
|
33372
|
-
LEFT JOIN session_completeness sc ON sc.session_id = ce.workflow_id
|
|
33373
|
-
WHERE ce.id = ?`,
|
|
33374
|
-
[executionId]
|
|
33375
|
-
);
|
|
33376
|
-
const row = result[0]?.values[0];
|
|
33377
|
-
if (!row) return null;
|
|
33378
|
-
const state = row[0];
|
|
33379
|
-
if (state === "complete" || state === "closed_without_artifact" || state === "in_flight" || state === "open_no_artifact") {
|
|
33380
|
-
return state;
|
|
33381
|
-
}
|
|
33382
|
-
return null;
|
|
33383
|
-
}
|
|
33516
|
+
import { readFileSync as readFileSync5, mkdirSync as mkdirSync6 } from "node:fs";
|
|
33517
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
33384
33518
|
|
|
33385
33519
|
// src/server/services/ai-cli/index.ts
|
|
33386
33520
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
@@ -33390,12 +33524,24 @@ import { join as join9 } from "node:path";
|
|
|
33390
33524
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, openSync, closeSync } from "node:fs";
|
|
33391
33525
|
import { tmpdir } from "node:os";
|
|
33392
33526
|
import { join as join7 } from "node:path";
|
|
33393
|
-
function buildFileStdio(
|
|
33527
|
+
function buildFileStdio(logFile) {
|
|
33394
33528
|
if (!logFile) {
|
|
33395
|
-
return { stdio: [
|
|
33529
|
+
return { stdio: ["pipe", "pipe", "pipe"], logFd: null, logPath: void 0 };
|
|
33396
33530
|
}
|
|
33397
33531
|
const logFd = openSync(logFile, "a");
|
|
33398
|
-
return { stdio: [
|
|
33532
|
+
return { stdio: ["pipe", logFd, logFd], logFd, logPath: logFile };
|
|
33533
|
+
}
|
|
33534
|
+
function assertNonEmptyPrompt(prompt) {
|
|
33535
|
+
if (prompt.length === 0) {
|
|
33536
|
+
throw new Error("refusing to spawn with an empty prompt");
|
|
33537
|
+
}
|
|
33538
|
+
}
|
|
33539
|
+
function deliverPrompt(proc, prompt) {
|
|
33540
|
+
assertNonEmptyPrompt(prompt);
|
|
33541
|
+
proc.stdin?.on("error", () => {
|
|
33542
|
+
});
|
|
33543
|
+
proc.stdin?.write(prompt);
|
|
33544
|
+
proc.stdin?.end();
|
|
33399
33545
|
}
|
|
33400
33546
|
function closeFileStdio(logFd) {
|
|
33401
33547
|
if (logFd === null) return;
|
|
@@ -33459,7 +33605,7 @@ function cleanEnv() {
|
|
|
33459
33605
|
return env;
|
|
33460
33606
|
}
|
|
33461
33607
|
|
|
33462
|
-
// ../
|
|
33608
|
+
// ../shared/persistence/src/vendor-resume.ts
|
|
33463
33609
|
var VENDOR_BINARIES = {
|
|
33464
33610
|
claude: "claude",
|
|
33465
33611
|
opencode: "opencode"
|
|
@@ -33517,6 +33663,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33517
33663
|
}
|
|
33518
33664
|
}
|
|
33519
33665
|
spawn(opts) {
|
|
33666
|
+
assertNonEmptyPrompt(opts.prompt);
|
|
33520
33667
|
const isWorkflow = opts.mode === "workflow";
|
|
33521
33668
|
const maxTurns = opts.maxTurns ?? (isWorkflow ? 500 : 1);
|
|
33522
33669
|
const tools = opts.allowedTools ?? (isWorkflow ? WORKFLOW_TOOLS : QUERY_TOOLS);
|
|
@@ -33538,7 +33685,6 @@ var ClaudeCodeAdapter = class {
|
|
|
33538
33685
|
flags.push("--model", opts.model);
|
|
33539
33686
|
}
|
|
33540
33687
|
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33541
|
-
"pipe",
|
|
33542
33688
|
isWorkflow ? opts.logFile : void 0
|
|
33543
33689
|
);
|
|
33544
33690
|
const proc = spawnBinary("claude", flags, {
|
|
@@ -33549,8 +33695,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33549
33695
|
});
|
|
33550
33696
|
closeFileStdio(logFd);
|
|
33551
33697
|
if (isWorkflow) proc.unref();
|
|
33552
|
-
proc
|
|
33553
|
-
proc.stdin?.end();
|
|
33698
|
+
deliverPrompt(proc, opts.prompt);
|
|
33554
33699
|
return {
|
|
33555
33700
|
process: proc,
|
|
33556
33701
|
detached: isWorkflow,
|
|
@@ -33742,11 +33887,11 @@ var OpenCodeAdapter = class {
|
|
|
33742
33887
|
}
|
|
33743
33888
|
}
|
|
33744
33889
|
spawn(opts) {
|
|
33890
|
+
assertNonEmptyPrompt(opts.prompt);
|
|
33745
33891
|
const isWorkflow = opts.mode === "workflow";
|
|
33746
33892
|
const agent = opts.allowedTools ? void 0 : isWorkflow ? "build" : "plan";
|
|
33747
33893
|
const args = [
|
|
33748
33894
|
"run",
|
|
33749
|
-
opts.prompt,
|
|
33750
33895
|
"--format",
|
|
33751
33896
|
"json"
|
|
33752
33897
|
];
|
|
@@ -33760,7 +33905,6 @@ var OpenCodeAdapter = class {
|
|
|
33760
33905
|
args.push("--model", opts.model);
|
|
33761
33906
|
}
|
|
33762
33907
|
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33763
|
-
"ignore",
|
|
33764
33908
|
isWorkflow ? opts.logFile : void 0
|
|
33765
33909
|
);
|
|
33766
33910
|
const proc = spawnBinary("opencode", args, {
|
|
@@ -33771,6 +33915,7 @@ var OpenCodeAdapter = class {
|
|
|
33771
33915
|
});
|
|
33772
33916
|
closeFileStdio(logFd);
|
|
33773
33917
|
if (isWorkflow) proc.unref();
|
|
33918
|
+
deliverPrompt(proc, opts.prompt);
|
|
33774
33919
|
return {
|
|
33775
33920
|
process: proc,
|
|
33776
33921
|
detached: isWorkflow,
|
|
@@ -34044,8 +34189,8 @@ var AiCliService = class {
|
|
|
34044
34189
|
const available = this.entries.filter((e) => e.detection.found);
|
|
34045
34190
|
if (available.length === 0) return null;
|
|
34046
34191
|
if (this.preference !== "auto") {
|
|
34047
|
-
const
|
|
34048
|
-
if (
|
|
34192
|
+
const preferred2 = available.find((e) => e.adapter.binary === this.preference);
|
|
34193
|
+
if (preferred2) return preferred2.adapter;
|
|
34049
34194
|
console.warn(
|
|
34050
34195
|
` AI CLI: Preferred "${this.preference}" not found, falling back to auto-detection`
|
|
34051
34196
|
);
|
|
@@ -34092,10 +34237,12 @@ var FileTailer = class {
|
|
|
34092
34237
|
/** Read everything currently available from `offset` to EOF. */
|
|
34093
34238
|
poll() {
|
|
34094
34239
|
if (!this.ensureOpen()) return;
|
|
34240
|
+
const fd = this.fd;
|
|
34241
|
+
if (fd === null) return;
|
|
34095
34242
|
let bytes;
|
|
34096
34243
|
do {
|
|
34097
34244
|
try {
|
|
34098
|
-
bytes = readSync(
|
|
34245
|
+
bytes = readSync(fd, this.buf, 0, this.buf.length, this.offset);
|
|
34099
34246
|
} catch {
|
|
34100
34247
|
return;
|
|
34101
34248
|
}
|
|
@@ -34156,100 +34303,12 @@ function resolveLocalCli() {
|
|
|
34156
34303
|
return null;
|
|
34157
34304
|
}
|
|
34158
34305
|
|
|
34159
|
-
// ../
|
|
34160
|
-
var REASON_EVENT_TYPES = [
|
|
34161
|
-
"session_aborted",
|
|
34162
|
-
"session_auto_closed_stale",
|
|
34163
|
-
"session_synced",
|
|
34164
|
-
"session_legacy_import"
|
|
34165
|
-
];
|
|
34166
|
-
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
34167
|
-
"session_closed",
|
|
34168
|
-
...REASON_EVENT_TYPES
|
|
34169
|
-
]);
|
|
34170
|
-
function hasCompletionInvariant(db, session) {
|
|
34171
|
-
const eventType = session.workflow_type === "map" ? "map_completed" : "round_completed";
|
|
34172
|
-
const round = session.workflow_type === "map" ? session.current_map_run : session.current_round;
|
|
34173
|
-
const r = db.exec(
|
|
34174
|
-
`SELECT 1 FROM orchestration_events
|
|
34175
|
-
WHERE session_id = ? AND event_type = ? AND round = ? LIMIT 1`,
|
|
34176
|
-
[session.id, eventType, round]
|
|
34177
|
-
);
|
|
34178
|
-
return (r[0]?.values.length ?? 0) > 0;
|
|
34179
|
-
}
|
|
34180
|
-
|
|
34181
|
-
// ../cli/src/lib/state/index.ts
|
|
34182
|
-
async function stateClose(params) {
|
|
34183
|
-
const { sessionId, ocrDir, abort } = params;
|
|
34184
|
-
const db = await ensureDatabase(ocrDir);
|
|
34185
|
-
const existing = getSession(db, sessionId);
|
|
34186
|
-
if (!existing) {
|
|
34187
|
-
throw new StateError(STATE_EXIT.NOT_FOUND, `Session not found: ${sessionId}`);
|
|
34188
|
-
}
|
|
34189
|
-
if (existing.status === "closed") {
|
|
34190
|
-
console.error(`[ocr] Session already closed: ${sessionId}`);
|
|
34191
|
-
return;
|
|
34192
|
-
}
|
|
34193
|
-
if (!abort && !hasCompletionInvariant(db, existing)) {
|
|
34194
|
-
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`;
|
|
34195
|
-
throw new StateError(
|
|
34196
|
-
STATE_EXIT.INVARIANT_UNMET,
|
|
34197
|
-
`Cannot close session ${sessionId}: ${what}. Run 'ocr state complete-round' to finalize it, or pass --abort to record an abandoned session.`
|
|
34198
|
-
);
|
|
34199
|
-
}
|
|
34200
|
-
const note = "closed by parent workflow close";
|
|
34201
|
-
db.transaction(() => {
|
|
34202
|
-
if (abort) {
|
|
34203
|
-
insertEvent(db, {
|
|
34204
|
-
session_id: sessionId,
|
|
34205
|
-
event_type: "session_aborted",
|
|
34206
|
-
phase: existing.current_phase,
|
|
34207
|
-
phase_number: existing.phase_number,
|
|
34208
|
-
round: existing.current_round
|
|
34209
|
-
});
|
|
34210
|
-
}
|
|
34211
|
-
updateSession(db, sessionId, {
|
|
34212
|
-
status: "closed",
|
|
34213
|
-
current_phase: "complete"
|
|
34214
|
-
});
|
|
34215
|
-
if (!abort) {
|
|
34216
|
-
insertEvent(db, {
|
|
34217
|
-
session_id: sessionId,
|
|
34218
|
-
event_type: "session_closed",
|
|
34219
|
-
phase: "complete",
|
|
34220
|
-
phase_number: existing.phase_number,
|
|
34221
|
-
round: existing.current_round
|
|
34222
|
-
});
|
|
34223
|
-
}
|
|
34224
|
-
cascadeTerminateExecutions(db, sessionId, CASCADE_CLOSE_EXIT_CODE, note);
|
|
34225
|
-
});
|
|
34226
|
-
}
|
|
34227
|
-
async function reconcileWorkflowOnExit(ocrDir, sessionId, db) {
|
|
34228
|
-
db ??= await ensureDatabase(ocrDir);
|
|
34229
|
-
const existing = getSession(db, sessionId);
|
|
34230
|
-
if (!existing) return "not-found";
|
|
34231
|
-
if (existing.status === "closed") return "already-closed";
|
|
34232
|
-
if (!hasCompletionInvariant(db, existing)) return "incomplete";
|
|
34233
|
-
if (hasInFlightDependents(db, sessionId)) return "in-flight";
|
|
34234
|
-
await stateClose({ sessionId, ocrDir, abort: false });
|
|
34235
|
-
return "closed";
|
|
34236
|
-
}
|
|
34237
|
-
async function reconcileCompletedSessions(ocrDir) {
|
|
34238
|
-
const db = await ensureDatabase(ocrDir);
|
|
34239
|
-
const closed = [];
|
|
34240
|
-
for (const s of getAllSessions(db)) {
|
|
34241
|
-
if (s.status !== "active") continue;
|
|
34242
|
-
const outcome = await reconcileWorkflowOnExit(ocrDir, s.id, db);
|
|
34243
|
-
if (outcome === "closed") closed.push(s.id);
|
|
34244
|
-
}
|
|
34245
|
-
return closed;
|
|
34246
|
-
}
|
|
34247
|
-
|
|
34248
|
-
// ../cli/src/lib/runtime-config.ts
|
|
34306
|
+
// ../shared/config/src/runtime-config.ts
|
|
34249
34307
|
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "node:fs";
|
|
34250
34308
|
import { join as join11 } from "node:path";
|
|
34251
34309
|
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34252
34310
|
var DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES = 60;
|
|
34311
|
+
var DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS = 2;
|
|
34253
34312
|
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
34254
34313
|
const configPath = join11(ocrDir, "config.yaml");
|
|
34255
34314
|
if (!existsSync9(configPath)) return defaultValue;
|
|
@@ -34294,8 +34353,15 @@ function getWorkflowHardDeadlineMs(ocrDir) {
|
|
|
34294
34353
|
DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES
|
|
34295
34354
|
) * 60 * 1e3;
|
|
34296
34355
|
}
|
|
34356
|
+
function getForwardResumeMaxAttempts(ocrDir) {
|
|
34357
|
+
return readRuntimePositiveInt(
|
|
34358
|
+
ocrDir,
|
|
34359
|
+
"forward_resume_max_attempts",
|
|
34360
|
+
DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS
|
|
34361
|
+
);
|
|
34362
|
+
}
|
|
34297
34363
|
|
|
34298
|
-
// src/server/socket/
|
|
34364
|
+
// src/server/socket/prompt-builder.ts
|
|
34299
34365
|
function shellSplit(str) {
|
|
34300
34366
|
const tokens = [];
|
|
34301
34367
|
let current = "";
|
|
@@ -34322,11 +34388,6 @@ function shellSplit(str) {
|
|
|
34322
34388
|
if (current) tokens.push(current);
|
|
34323
34389
|
return tokens;
|
|
34324
34390
|
}
|
|
34325
|
-
var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
34326
|
-
"progress",
|
|
34327
|
-
"state"
|
|
34328
|
-
]);
|
|
34329
|
-
var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
|
|
34330
34391
|
function escapeUserHeaders(value) {
|
|
34331
34392
|
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");
|
|
34332
34393
|
}
|
|
@@ -34351,8 +34412,8 @@ function buildPrompt(opts) {
|
|
|
34351
34412
|
options.push("--fresh");
|
|
34352
34413
|
i++;
|
|
34353
34414
|
} else if (arg === "--requirements" && i + 1 < subArgs.length) {
|
|
34354
|
-
requirements = subArgs
|
|
34355
|
-
|
|
34415
|
+
requirements = subArgs[i + 1] ?? "";
|
|
34416
|
+
i += 2;
|
|
34356
34417
|
} else if (arg === "--team" && i + 1 < subArgs.length) {
|
|
34357
34418
|
team = subArgs[i + 1] ?? "";
|
|
34358
34419
|
i += 2;
|
|
@@ -34362,8 +34423,9 @@ function buildPrompt(opts) {
|
|
|
34362
34423
|
} else if (arg === "--reviewer" && i + 1 < subArgs.length) {
|
|
34363
34424
|
const raw = subArgs[i + 1] ?? "";
|
|
34364
34425
|
const countMatch = raw.match(/^(\d+):(.+)$/);
|
|
34365
|
-
|
|
34366
|
-
|
|
34426
|
+
const [, countStr, description] = countMatch ?? [];
|
|
34427
|
+
if (countStr && description) {
|
|
34428
|
+
reviewerDescriptions.push({ description, count: parseInt(countStr, 10) });
|
|
34367
34429
|
} else {
|
|
34368
34430
|
reviewerDescriptions.push({ description: raw, count: 1 });
|
|
34369
34431
|
}
|
|
@@ -34474,7 +34536,60 @@ function extractPerInstanceModels(subArgs) {
|
|
|
34474
34536
|
}
|
|
34475
34537
|
return [...models];
|
|
34476
34538
|
}
|
|
34539
|
+
|
|
34540
|
+
// src/server/socket/process-registry.ts
|
|
34477
34541
|
var MAX_CONCURRENT = 3;
|
|
34542
|
+
var activeCommands = /* @__PURE__ */ new Map();
|
|
34543
|
+
function getActiveCommands() {
|
|
34544
|
+
return Array.from(activeCommands.values()).map((entry) => ({
|
|
34545
|
+
execution_id: entry.executionId,
|
|
34546
|
+
command: entry.commandStr,
|
|
34547
|
+
started_at: entry.startedAt,
|
|
34548
|
+
output: entry.outputBuffer
|
|
34549
|
+
}));
|
|
34550
|
+
}
|
|
34551
|
+
|
|
34552
|
+
// src/server/socket/spawn-markers.ts
|
|
34553
|
+
import { writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync5, existsSync as existsSync10, rmSync as rmSync2 } from "node:fs";
|
|
34554
|
+
import { join as join12 } from "node:path";
|
|
34555
|
+
function spawnMarkerDir(ocrDir) {
|
|
34556
|
+
return join12(ocrDir, "data", "dashboard-active-spawn");
|
|
34557
|
+
}
|
|
34558
|
+
function spawnMarkerPath(ocrDir, executionUid) {
|
|
34559
|
+
const safe = executionUid.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
34560
|
+
return join12(spawnMarkerDir(ocrDir), `${safe}.json`);
|
|
34561
|
+
}
|
|
34562
|
+
function legacySpawnMarkerPath(ocrDir) {
|
|
34563
|
+
return join12(ocrDir, "data", "dashboard-active-spawn.json");
|
|
34564
|
+
}
|
|
34565
|
+
function writeSpawnMarker(ocrDir, executionUid, pid) {
|
|
34566
|
+
const dir = spawnMarkerDir(ocrDir);
|
|
34567
|
+
if (!existsSync10(dir)) mkdirSync5(dir, { recursive: true });
|
|
34568
|
+
const payload = JSON.stringify({
|
|
34569
|
+
execution_uid: executionUid,
|
|
34570
|
+
pid,
|
|
34571
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
34572
|
+
});
|
|
34573
|
+
writeFileSync3(spawnMarkerPath(ocrDir, executionUid), payload, { mode: 384 });
|
|
34574
|
+
}
|
|
34575
|
+
function clearSpawnMarker(ocrDir, executionUid) {
|
|
34576
|
+
try {
|
|
34577
|
+
unlinkSync3(spawnMarkerPath(ocrDir, executionUid));
|
|
34578
|
+
} catch {
|
|
34579
|
+
}
|
|
34580
|
+
}
|
|
34581
|
+
function clearAllSpawnMarkers(ocrDir) {
|
|
34582
|
+
try {
|
|
34583
|
+
rmSync2(spawnMarkerDir(ocrDir), { recursive: true, force: true });
|
|
34584
|
+
} catch {
|
|
34585
|
+
}
|
|
34586
|
+
try {
|
|
34587
|
+
unlinkSync3(legacySpawnMarkerPath(ocrDir));
|
|
34588
|
+
} catch {
|
|
34589
|
+
}
|
|
34590
|
+
}
|
|
34591
|
+
|
|
34592
|
+
// src/server/socket/watchdog.ts
|
|
34478
34593
|
var WATCHDOG_TICK_MS = 1e4;
|
|
34479
34594
|
var POST_RESULT_GRACE_MS = 3e4;
|
|
34480
34595
|
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
@@ -34497,34 +34612,296 @@ function decideWatchdogTick(i) {
|
|
|
34497
34612
|
}
|
|
34498
34613
|
return i.exited ? { action: "wait" } : { action: "beat" };
|
|
34499
34614
|
}
|
|
34500
|
-
|
|
34501
|
-
|
|
34502
|
-
|
|
34615
|
+
function makeHeartbeatBumper(db, executionId, entry) {
|
|
34616
|
+
return () => {
|
|
34617
|
+
if (entry.finalized) return;
|
|
34618
|
+
const now = Date.now();
|
|
34619
|
+
if (now - (entry.lastBeatWrite ?? 0) < HEARTBEAT_THROTTLE_MS) return;
|
|
34620
|
+
entry.lastBeatWrite = now;
|
|
34621
|
+
try {
|
|
34622
|
+
db.run(
|
|
34623
|
+
`UPDATE command_executions SET last_heartbeat_at = datetime('now') WHERE id = ? AND finished_at IS NULL`,
|
|
34624
|
+
[executionId]
|
|
34625
|
+
);
|
|
34626
|
+
} catch (err) {
|
|
34627
|
+
console.error("[command-runner] heartbeat bump failed:", err);
|
|
34628
|
+
}
|
|
34629
|
+
};
|
|
34503
34630
|
}
|
|
34504
|
-
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
|
|
34508
|
-
|
|
34509
|
-
|
|
34510
|
-
|
|
34511
|
-
|
|
34512
|
-
|
|
34513
|
-
|
|
34514
|
-
|
|
34631
|
+
|
|
34632
|
+
// ../shared/persistence/src/state/phase-graph.ts
|
|
34633
|
+
var REVIEW_PHASE_NUMBERS = {
|
|
34634
|
+
context: 1,
|
|
34635
|
+
"change-context": 2,
|
|
34636
|
+
analysis: 3,
|
|
34637
|
+
reviews: 4,
|
|
34638
|
+
aggregation: 5,
|
|
34639
|
+
discourse: 6,
|
|
34640
|
+
synthesis: 7,
|
|
34641
|
+
complete: 8
|
|
34642
|
+
};
|
|
34643
|
+
var MAP_PHASE_NUMBERS = {
|
|
34644
|
+
"map-context": 1,
|
|
34645
|
+
topology: 2,
|
|
34646
|
+
"flow-analysis": 3,
|
|
34647
|
+
"requirements-mapping": 4,
|
|
34648
|
+
synthesis: 5,
|
|
34649
|
+
complete: 6
|
|
34650
|
+
};
|
|
34651
|
+
|
|
34652
|
+
// ../shared/persistence/src/state/forward-resume.ts
|
|
34653
|
+
var FORWARD_RESUME_KIND = "forward_resume";
|
|
34654
|
+
var FORWARD_RESUME_EXHAUSTED_REASON = "forward_resume_exhausted";
|
|
34655
|
+
function parseLeaseMetadata(e) {
|
|
34656
|
+
if (e.event_type !== "session_resumed" || !e.metadata) return null;
|
|
34515
34657
|
try {
|
|
34516
|
-
|
|
34658
|
+
return JSON.parse(e.metadata);
|
|
34517
34659
|
} catch {
|
|
34660
|
+
return null;
|
|
34518
34661
|
}
|
|
34519
34662
|
}
|
|
34520
|
-
function
|
|
34521
|
-
|
|
34522
|
-
|
|
34523
|
-
|
|
34524
|
-
|
|
34525
|
-
|
|
34526
|
-
|
|
34663
|
+
function remainingPhasesAfter(workflowType, currentPhase) {
|
|
34664
|
+
const numbers = workflowType === "map" ? MAP_PHASE_NUMBERS : REVIEW_PHASE_NUMBERS;
|
|
34665
|
+
const cur = numbers[currentPhase];
|
|
34666
|
+
if (cur === void 0) return [];
|
|
34667
|
+
return Object.entries(numbers).filter(([, n]) => n > cur).sort((a, b) => a[1] - b[1]).map(([phase]) => phase);
|
|
34668
|
+
}
|
|
34669
|
+
function hasTerminalArtifactEvent2(events, workflowType, round) {
|
|
34670
|
+
const terminal = workflowType === "map" ? "map_completed" : "round_completed";
|
|
34671
|
+
return events.some((e) => e.event_type === terminal && e.round === round);
|
|
34672
|
+
}
|
|
34673
|
+
function countForwardResumeLeases(events, round) {
|
|
34674
|
+
return events.filter((e) => parseLeaseMetadata(e)?.kind === FORWARD_RESUME_KIND && parseLeaseMetadata(e)?.round === round).length;
|
|
34675
|
+
}
|
|
34676
|
+
function strandedActionByCap(db, session, maxAttempts) {
|
|
34677
|
+
const events = getEventsForSession(db, session.id);
|
|
34678
|
+
const leaseCount = countForwardResumeLeases(events, session.current_round);
|
|
34679
|
+
const workflowType = session.workflow_type === "map" ? "map" : "review";
|
|
34680
|
+
return {
|
|
34681
|
+
action: leaseCount >= maxAttempts ? "abort_or_fresh" : "forward_resume",
|
|
34682
|
+
remainingPhases: remainingPhasesAfter(workflowType, session.current_phase),
|
|
34683
|
+
attemptsRemaining: Math.max(0, maxAttempts - leaseCount)
|
|
34684
|
+
};
|
|
34685
|
+
}
|
|
34686
|
+
function closeForwardResumeExhausted(db, sessionId, attempts) {
|
|
34687
|
+
commitReasonClose(
|
|
34688
|
+
db,
|
|
34689
|
+
sessionId,
|
|
34690
|
+
{
|
|
34691
|
+
event_type: "session_auto_closed_stale",
|
|
34692
|
+
phase: "complete",
|
|
34693
|
+
metadata: JSON.stringify({
|
|
34694
|
+
reason: FORWARD_RESUME_EXHAUSTED_REASON,
|
|
34695
|
+
attempts
|
|
34696
|
+
})
|
|
34697
|
+
},
|
|
34698
|
+
{ status: "closed", current_phase: "complete" }
|
|
34699
|
+
);
|
|
34700
|
+
}
|
|
34701
|
+
|
|
34702
|
+
// ../shared/persistence/src/state/projection.ts
|
|
34703
|
+
var REASON_EVENT_TYPES = [
|
|
34704
|
+
"session_aborted",
|
|
34705
|
+
"session_auto_closed_stale",
|
|
34706
|
+
"session_synced",
|
|
34707
|
+
"session_legacy_import"
|
|
34708
|
+
];
|
|
34709
|
+
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
34710
|
+
"session_closed",
|
|
34711
|
+
...REASON_EVENT_TYPES
|
|
34712
|
+
]);
|
|
34713
|
+
function hasCompletionInvariant(db, session) {
|
|
34714
|
+
const eventType = session.workflow_type === "map" ? "map_completed" : "round_completed";
|
|
34715
|
+
const round = session.workflow_type === "map" ? session.current_map_run : session.current_round;
|
|
34716
|
+
const r = db.exec(
|
|
34717
|
+
`SELECT 1 FROM orchestration_events
|
|
34718
|
+
WHERE session_id = ? AND event_type = ? AND round = ? LIMIT 1`,
|
|
34719
|
+
[session.id, eventType, round]
|
|
34720
|
+
);
|
|
34721
|
+
return (r[0]?.values.length ?? 0) > 0;
|
|
34722
|
+
}
|
|
34723
|
+
|
|
34724
|
+
// ../shared/persistence/src/state/index.ts
|
|
34725
|
+
async function stateClose(params) {
|
|
34726
|
+
const { sessionId, ocrDir, abort } = params;
|
|
34727
|
+
const db = await ensureDatabase(ocrDir);
|
|
34728
|
+
const existing = getSession(db, sessionId);
|
|
34729
|
+
if (!existing) {
|
|
34730
|
+
throw new StateError(STATE_EXIT.NOT_FOUND, `Session not found: ${sessionId}`);
|
|
34731
|
+
}
|
|
34732
|
+
if (existing.status === "closed") {
|
|
34733
|
+
console.error(`[ocr] Session already closed: ${sessionId}`);
|
|
34734
|
+
return;
|
|
34735
|
+
}
|
|
34736
|
+
if (!abort && !hasCompletionInvariant(db, existing)) {
|
|
34737
|
+
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`;
|
|
34738
|
+
throw new StateError(
|
|
34739
|
+
STATE_EXIT.INVARIANT_UNMET,
|
|
34740
|
+
`Cannot close session ${sessionId}: ${what}. Run 'ocr state complete-round' to finalize it, or pass --abort to record an abandoned session.`
|
|
34741
|
+
);
|
|
34742
|
+
}
|
|
34743
|
+
const note = "closed by parent workflow close";
|
|
34744
|
+
db.transaction(() => {
|
|
34745
|
+
if (abort) {
|
|
34746
|
+
insertEvent(db, {
|
|
34747
|
+
session_id: sessionId,
|
|
34748
|
+
event_type: "session_aborted",
|
|
34749
|
+
phase: existing.current_phase,
|
|
34750
|
+
phase_number: existing.phase_number,
|
|
34751
|
+
round: existing.current_round
|
|
34752
|
+
});
|
|
34753
|
+
}
|
|
34754
|
+
updateSession(db, sessionId, {
|
|
34755
|
+
status: "closed",
|
|
34756
|
+
current_phase: "complete"
|
|
34757
|
+
});
|
|
34758
|
+
if (!abort) {
|
|
34759
|
+
insertEvent(db, {
|
|
34760
|
+
session_id: sessionId,
|
|
34761
|
+
event_type: "session_closed",
|
|
34762
|
+
phase: "complete",
|
|
34763
|
+
phase_number: existing.phase_number,
|
|
34764
|
+
round: existing.current_round
|
|
34765
|
+
});
|
|
34766
|
+
}
|
|
34767
|
+
cascadeTerminateExecutions(db, sessionId, CASCADE_CLOSE_EXIT_CODE, note);
|
|
34768
|
+
});
|
|
34769
|
+
}
|
|
34770
|
+
async function reconcileWorkflowOnExit(ocrDir, sessionId, db) {
|
|
34771
|
+
db ??= await ensureDatabase(ocrDir);
|
|
34772
|
+
const existing = getSession(db, sessionId);
|
|
34773
|
+
if (!existing) return "not-found";
|
|
34774
|
+
if (existing.status === "closed") return "already-closed";
|
|
34775
|
+
if (!hasCompletionInvariant(db, existing)) return "incomplete";
|
|
34776
|
+
if (hasInFlightDependents(db, sessionId)) return "in-flight";
|
|
34777
|
+
await stateClose({ sessionId, ocrDir, abort: false });
|
|
34778
|
+
return "closed";
|
|
34779
|
+
}
|
|
34780
|
+
async function reconcileCompletedSessions(ocrDir) {
|
|
34781
|
+
const db = await ensureDatabase(ocrDir);
|
|
34782
|
+
const closed = [];
|
|
34783
|
+
for (const s of getAllSessions(db)) {
|
|
34784
|
+
if (s.status !== "active") continue;
|
|
34785
|
+
const outcome = await reconcileWorkflowOnExit(ocrDir, s.id, db);
|
|
34786
|
+
if (outcome === "closed") closed.push(s.id);
|
|
34787
|
+
}
|
|
34788
|
+
return closed;
|
|
34789
|
+
}
|
|
34790
|
+
|
|
34791
|
+
// src/server/services/command-outcome.ts
|
|
34792
|
+
function deriveCommandOutcome(exitCode, completeness) {
|
|
34793
|
+
if (exitCode === null) return null;
|
|
34794
|
+
if (exitCode === CANCELLED_EXIT_CODE || exitCode === CASCADE_CLOSE_EXIT_CODE) {
|
|
34795
|
+
return "cancelled";
|
|
34796
|
+
}
|
|
34797
|
+
if (exitCode === WATCHDOG_DEADLINE_EXIT_CODE) return "failed";
|
|
34798
|
+
if (exitCode !== 0) return "failed";
|
|
34799
|
+
if (completeness === null || completeness === "complete") return "success";
|
|
34800
|
+
return "incomplete";
|
|
34527
34801
|
}
|
|
34802
|
+
function deriveCancellationReason(exitCode) {
|
|
34803
|
+
if (exitCode === CANCELLED_EXIT_CODE) return "user";
|
|
34804
|
+
if (exitCode === CASCADE_CLOSE_EXIT_CODE) return "cascade";
|
|
34805
|
+
return null;
|
|
34806
|
+
}
|
|
34807
|
+
function getWorkflowCompletenessForExecution(db, executionId) {
|
|
34808
|
+
const result = db.exec(
|
|
34809
|
+
`SELECT sc.completeness_state
|
|
34810
|
+
FROM command_executions ce
|
|
34811
|
+
LEFT JOIN session_completeness sc ON sc.session_id = ce.workflow_id
|
|
34812
|
+
WHERE ce.id = ?`,
|
|
34813
|
+
[executionId]
|
|
34814
|
+
);
|
|
34815
|
+
const row = result[0]?.values[0];
|
|
34816
|
+
if (!row) return null;
|
|
34817
|
+
const state = row[0];
|
|
34818
|
+
if (state === "complete" || state === "closed_without_artifact" || state === "in_flight" || state === "open_no_artifact") {
|
|
34819
|
+
return state;
|
|
34820
|
+
}
|
|
34821
|
+
return null;
|
|
34822
|
+
}
|
|
34823
|
+
|
|
34824
|
+
// src/server/socket/finalizer.ts
|
|
34825
|
+
function tryClaimFinalization(entry) {
|
|
34826
|
+
if (!entry) return true;
|
|
34827
|
+
if (entry.finalized) return false;
|
|
34828
|
+
entry.finalized = true;
|
|
34829
|
+
if (entry.watchdog) {
|
|
34830
|
+
clearInterval(entry.watchdog);
|
|
34831
|
+
entry.watchdog = void 0;
|
|
34832
|
+
}
|
|
34833
|
+
if (entry.tailer) {
|
|
34834
|
+
entry.tailer.stop();
|
|
34835
|
+
entry.tailer = void 0;
|
|
34836
|
+
}
|
|
34837
|
+
return true;
|
|
34838
|
+
}
|
|
34839
|
+
function finishExecution(io2, db, ocrDir, executionId, rawCode, output) {
|
|
34840
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34841
|
+
const entry = activeCommands.get(executionId);
|
|
34842
|
+
const code = entry?.cancelled ? CANCELLED_EXIT_CODE : rawCode;
|
|
34843
|
+
if (!tryClaimFinalization(entry)) return;
|
|
34844
|
+
const res = db.prepare(
|
|
34845
|
+
`UPDATE command_executions
|
|
34846
|
+
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
34847
|
+
WHERE id = ? AND finished_at IS NULL`
|
|
34848
|
+
).run(code, finishedAt, output, executionId);
|
|
34849
|
+
if (Number(res.changes) === 0 && !entry) return;
|
|
34850
|
+
const completeness = getWorkflowCompletenessForExecution(db, executionId);
|
|
34851
|
+
const outcome = deriveCommandOutcome(code, completeness);
|
|
34852
|
+
const cancellationReason = deriveCancellationReason(code);
|
|
34853
|
+
if (entry?.uid) {
|
|
34854
|
+
appendCommandLog(ocrDir, {
|
|
34855
|
+
v: 1,
|
|
34856
|
+
uid: entry.uid,
|
|
34857
|
+
db_id: executionId,
|
|
34858
|
+
command: entry.commandStr,
|
|
34859
|
+
args: entry.argsJson ?? null,
|
|
34860
|
+
exit_code: code,
|
|
34861
|
+
started_at: entry.startedAt,
|
|
34862
|
+
finished_at: finishedAt,
|
|
34863
|
+
is_detached: entry.detached ? 1 : 0,
|
|
34864
|
+
event: code === CANCELLED_EXIT_CODE ? "cancel" : "finish",
|
|
34865
|
+
writer: "dashboard"
|
|
34866
|
+
});
|
|
34867
|
+
}
|
|
34868
|
+
io2.emit("command:finished", {
|
|
34869
|
+
execution_id: executionId,
|
|
34870
|
+
exitCode: code,
|
|
34871
|
+
finished_at: finishedAt,
|
|
34872
|
+
outcome,
|
|
34873
|
+
cancellation_reason: cancellationReason
|
|
34874
|
+
});
|
|
34875
|
+
activeCommands.delete(executionId);
|
|
34876
|
+
const workflowRow = db.exec(
|
|
34877
|
+
"SELECT workflow_id FROM command_executions WHERE id = ?",
|
|
34878
|
+
[executionId]
|
|
34879
|
+
);
|
|
34880
|
+
const workflowId = workflowRow[0]?.values[0]?.[0];
|
|
34881
|
+
if (typeof workflowId === "string" && workflowId.length > 0) {
|
|
34882
|
+
void reconcileWorkflowOnExit(ocrDir, workflowId, db).then((outcome2) => {
|
|
34883
|
+
if (outcome2 === "closed") {
|
|
34884
|
+
console.log(`[command-runner] auto-finalized workflow ${workflowId}`);
|
|
34885
|
+
} else if (outcome2 === "incomplete" || outcome2 === "in-flight") {
|
|
34886
|
+
console.debug(
|
|
34887
|
+
`[command-runner] workflow ${workflowId} not finalized: ${outcome2}`
|
|
34888
|
+
);
|
|
34889
|
+
}
|
|
34890
|
+
}).catch((err) => {
|
|
34891
|
+
console.error(
|
|
34892
|
+
`[command-runner] reconcileWorkflowOnExit(${workflowId}) failed:`,
|
|
34893
|
+
err instanceof Error ? err.message : err
|
|
34894
|
+
);
|
|
34895
|
+
});
|
|
34896
|
+
}
|
|
34897
|
+
}
|
|
34898
|
+
|
|
34899
|
+
// src/server/socket/command-runner.ts
|
|
34900
|
+
var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
34901
|
+
"progress",
|
|
34902
|
+
"state"
|
|
34903
|
+
]);
|
|
34904
|
+
var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
|
|
34528
34905
|
function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService, sessionCapture) {
|
|
34529
34906
|
socket.on("command:run", (payload) => {
|
|
34530
34907
|
try {
|
|
@@ -34697,15 +35074,14 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34697
35074
|
finishExecution(io2, db, ocrDir, executionId, 1, content);
|
|
34698
35075
|
return;
|
|
34699
35076
|
}
|
|
35077
|
+
let capabilityWarning = null;
|
|
34700
35078
|
if (adapter.supportsPerTaskModel === false) {
|
|
34701
35079
|
const perInstanceModels = extractPerInstanceModels(subArgs);
|
|
34702
35080
|
if (perInstanceModels.length > 0) {
|
|
34703
|
-
|
|
34704
|
-
`;
|
|
34705
|
-
io2.emit("command:output", { execution_id: executionId, content: warning });
|
|
35081
|
+
capabilityWarning = `[ocr] Warning: ${adapter.name} does not support per-subagent model overrides. The configured per-instance models (${perInstanceModels.join(", ")}) will be ignored \u2014 all reviewers will run on the parent process model.`;
|
|
34706
35082
|
}
|
|
34707
35083
|
}
|
|
34708
|
-
const commandMdPath =
|
|
35084
|
+
const commandMdPath = join13(ocrDir, "commands", `${baseCommand}.md`);
|
|
34709
35085
|
let commandContent;
|
|
34710
35086
|
try {
|
|
34711
35087
|
commandContent = readFileSync5(commandMdPath, "utf-8");
|
|
@@ -34756,9 +35132,9 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34756
35132
|
let logFile;
|
|
34757
35133
|
if (entry.uid) {
|
|
34758
35134
|
try {
|
|
34759
|
-
const logDir =
|
|
34760
|
-
|
|
34761
|
-
logFile =
|
|
35135
|
+
const logDir = join13(ocrDir, "data", "exec-logs");
|
|
35136
|
+
mkdirSync6(logDir, { recursive: true });
|
|
35137
|
+
logFile = join13(logDir, `${entry.uid}.log`);
|
|
34762
35138
|
} catch (err) {
|
|
34763
35139
|
console.error(
|
|
34764
35140
|
"[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):",
|
|
@@ -34814,20 +35190,7 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34814
35190
|
}
|
|
34815
35191
|
}, POLL_INTERVAL_MS);
|
|
34816
35192
|
entry.linkPoll = linkPoll;
|
|
34817
|
-
const bumpHeartbeat = (
|
|
34818
|
-
if (entry.finalized) return;
|
|
34819
|
-
const now = Date.now();
|
|
34820
|
-
if (now - (entry.lastBeatWrite ?? 0) < HEARTBEAT_THROTTLE_MS) return;
|
|
34821
|
-
entry.lastBeatWrite = now;
|
|
34822
|
-
try {
|
|
34823
|
-
db.run(
|
|
34824
|
-
`UPDATE command_executions SET last_heartbeat_at = datetime('now') WHERE id = ? AND finished_at IS NULL`,
|
|
34825
|
-
[executionId]
|
|
34826
|
-
);
|
|
34827
|
-
} catch (err) {
|
|
34828
|
-
console.error("[command-runner] heartbeat bump failed:", err);
|
|
34829
|
-
}
|
|
34830
|
-
};
|
|
35193
|
+
const bumpHeartbeat = makeHeartbeatBumper(db, executionId, entry);
|
|
34831
35194
|
const hardDeadlineMs = getWorkflowHardDeadlineMs(ocrDir);
|
|
34832
35195
|
entry.watchdog = setInterval(() => {
|
|
34833
35196
|
if (entry.finalized) return;
|
|
@@ -34859,6 +35222,12 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34859
35222
|
`;
|
|
34860
35223
|
entry.outputBuffer += notice;
|
|
34861
35224
|
io2.emit("command:output", { execution_id: executionId, content: notice });
|
|
35225
|
+
emitStreamEvent({
|
|
35226
|
+
type: "notice",
|
|
35227
|
+
level: "warning",
|
|
35228
|
+
code: "hard_deadline_reaped",
|
|
35229
|
+
message: notice.trim()
|
|
35230
|
+
});
|
|
34862
35231
|
} else {
|
|
34863
35232
|
console.warn(`[watchdog] execution ${executionId}: result seen but no close after grace \u2014 finalizing${decision.reap ? " + reaping tree" : ""}`);
|
|
34864
35233
|
}
|
|
@@ -34866,6 +35235,10 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34866
35235
|
finishExecution(io2, db, ocrDir, executionId, decision.exitCode, entry.outputBuffer);
|
|
34867
35236
|
return;
|
|
34868
35237
|
}
|
|
35238
|
+
default: {
|
|
35239
|
+
const _exhaustive = decision;
|
|
35240
|
+
throw new Error(`unhandled watchdog action: ${JSON.stringify(_exhaustive)}`);
|
|
35241
|
+
}
|
|
34869
35242
|
}
|
|
34870
35243
|
}, WATCHDOG_TICK_MS);
|
|
34871
35244
|
entry.watchdog.unref();
|
|
@@ -34893,6 +35266,16 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34893
35266
|
journal.append(stream);
|
|
34894
35267
|
io2.emit("command:event", stream);
|
|
34895
35268
|
}
|
|
35269
|
+
if (capabilityWarning) {
|
|
35270
|
+
emitContent(`${capabilityWarning}
|
|
35271
|
+
`);
|
|
35272
|
+
emitStreamEvent({
|
|
35273
|
+
type: "notice",
|
|
35274
|
+
level: "warning",
|
|
35275
|
+
code: "per_instance_model_unsupported",
|
|
35276
|
+
message: capabilityWarning
|
|
35277
|
+
});
|
|
35278
|
+
}
|
|
34896
35279
|
function handleEvent(evt) {
|
|
34897
35280
|
switch (evt.type) {
|
|
34898
35281
|
case "text_delta":
|
|
@@ -34976,7 +35359,7 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34976
35359
|
clearInterval(entry.linkPoll);
|
|
34977
35360
|
entry.linkPoll = void 0;
|
|
34978
35361
|
}
|
|
34979
|
-
clearSpawnMarker(ocrDir);
|
|
35362
|
+
clearSpawnMarker(ocrDir, entry.uid);
|
|
34980
35363
|
if (entry.tailer) {
|
|
34981
35364
|
entry.tailer.stop();
|
|
34982
35365
|
entry.tailer = void 0;
|
|
@@ -35018,76 +35401,6 @@ ${stderrBuffer}`;
|
|
|
35018
35401
|
finishExecution(io2, db, ocrDir, executionId, -1, entry.outputBuffer);
|
|
35019
35402
|
});
|
|
35020
35403
|
}
|
|
35021
|
-
function finishExecution(io2, db, ocrDir, executionId, rawCode, output) {
|
|
35022
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
35023
|
-
const entry = activeCommands.get(executionId);
|
|
35024
|
-
const code = entry?.cancelled ? CANCELLED_EXIT_CODE : rawCode;
|
|
35025
|
-
if (entry?.finalized) return;
|
|
35026
|
-
if (entry) {
|
|
35027
|
-
entry.finalized = true;
|
|
35028
|
-
if (entry.watchdog) {
|
|
35029
|
-
clearInterval(entry.watchdog);
|
|
35030
|
-
entry.watchdog = void 0;
|
|
35031
|
-
}
|
|
35032
|
-
if (entry.tailer) {
|
|
35033
|
-
entry.tailer.stop();
|
|
35034
|
-
entry.tailer = void 0;
|
|
35035
|
-
}
|
|
35036
|
-
}
|
|
35037
|
-
const res = db.prepare(
|
|
35038
|
-
`UPDATE command_executions
|
|
35039
|
-
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
35040
|
-
WHERE id = ? AND finished_at IS NULL`
|
|
35041
|
-
).run(code, finishedAt, output, executionId);
|
|
35042
|
-
if (Number(res.changes) === 0 && !entry) return;
|
|
35043
|
-
const completeness = getWorkflowCompletenessForExecution(db, executionId);
|
|
35044
|
-
const outcome = deriveCommandOutcome(code, completeness);
|
|
35045
|
-
const cancellationReason = deriveCancellationReason(code);
|
|
35046
|
-
if (entry?.uid) {
|
|
35047
|
-
appendCommandLog(ocrDir, {
|
|
35048
|
-
v: 1,
|
|
35049
|
-
uid: entry.uid,
|
|
35050
|
-
db_id: executionId,
|
|
35051
|
-
command: entry.commandStr,
|
|
35052
|
-
args: entry.argsJson ?? null,
|
|
35053
|
-
exit_code: code,
|
|
35054
|
-
started_at: entry.startedAt,
|
|
35055
|
-
finished_at: finishedAt,
|
|
35056
|
-
is_detached: entry.detached ? 1 : 0,
|
|
35057
|
-
event: code === CANCELLED_EXIT_CODE ? "cancel" : "finish",
|
|
35058
|
-
writer: "dashboard"
|
|
35059
|
-
});
|
|
35060
|
-
}
|
|
35061
|
-
io2.emit("command:finished", {
|
|
35062
|
-
execution_id: executionId,
|
|
35063
|
-
exitCode: code,
|
|
35064
|
-
finished_at: finishedAt,
|
|
35065
|
-
outcome,
|
|
35066
|
-
cancellation_reason: cancellationReason
|
|
35067
|
-
});
|
|
35068
|
-
activeCommands.delete(executionId);
|
|
35069
|
-
const workflowRow = db.exec(
|
|
35070
|
-
"SELECT workflow_id FROM command_executions WHERE id = ?",
|
|
35071
|
-
[executionId]
|
|
35072
|
-
);
|
|
35073
|
-
const workflowId = workflowRow[0]?.values[0]?.[0];
|
|
35074
|
-
if (typeof workflowId === "string" && workflowId.length > 0) {
|
|
35075
|
-
void reconcileWorkflowOnExit(ocrDir, workflowId, db).then((outcome2) => {
|
|
35076
|
-
if (outcome2 === "closed") {
|
|
35077
|
-
console.log(`[command-runner] auto-finalized workflow ${workflowId}`);
|
|
35078
|
-
} else if (outcome2 === "incomplete" || outcome2 === "in-flight") {
|
|
35079
|
-
console.debug(
|
|
35080
|
-
`[command-runner] workflow ${workflowId} not finalized: ${outcome2}`
|
|
35081
|
-
);
|
|
35082
|
-
}
|
|
35083
|
-
}).catch((err) => {
|
|
35084
|
-
console.error(
|
|
35085
|
-
`[command-runner] reconcileWorkflowOnExit(${workflowId}) failed:`,
|
|
35086
|
-
err instanceof Error ? err.message : err
|
|
35087
|
-
);
|
|
35088
|
-
});
|
|
35089
|
-
}
|
|
35090
|
-
}
|
|
35091
35404
|
|
|
35092
35405
|
// src/server/routes/commands.ts
|
|
35093
35406
|
var AVAILABLE_COMMANDS = [
|
|
@@ -35175,7 +35488,7 @@ function createCommandsRouter(db, ocrDir) {
|
|
|
35175
35488
|
// src/server/routes/config.ts
|
|
35176
35489
|
var import_express9 = __toESM(require_express2(), 1);
|
|
35177
35490
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
35178
|
-
import { join as
|
|
35491
|
+
import { join as join14, dirname as dirname8, basename as basename2 } from "node:path";
|
|
35179
35492
|
var VALID_IDES = ["vscode", "cursor", "windsurf", "jetbrains", "sublime"];
|
|
35180
35493
|
function detectIde() {
|
|
35181
35494
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
@@ -35192,7 +35505,7 @@ function detectIde() {
|
|
|
35192
35505
|
}
|
|
35193
35506
|
function readIdeFromConfig(ocrDir) {
|
|
35194
35507
|
try {
|
|
35195
|
-
const configPath =
|
|
35508
|
+
const configPath = join14(ocrDir, "config.yaml");
|
|
35196
35509
|
const content = readFileSync6(configPath, "utf-8");
|
|
35197
35510
|
const match = content.match(/^\s*ide:\s*(\S+)/m);
|
|
35198
35511
|
return match?.[1] ?? "auto";
|
|
@@ -35239,7 +35552,7 @@ function createConfigRouter(ocrDir, aiCliService) {
|
|
|
35239
35552
|
return;
|
|
35240
35553
|
}
|
|
35241
35554
|
try {
|
|
35242
|
-
const configPath =
|
|
35555
|
+
const configPath = join14(ocrDir, "config.yaml");
|
|
35243
35556
|
let content = readFileSync6(configPath, "utf-8");
|
|
35244
35557
|
if (content.match(/^\s*ide:\s*\S+/m)) {
|
|
35245
35558
|
content = content.replace(/^(\s*ide:\s*)\S+/m, `$1${ide}`);
|
|
@@ -35329,9 +35642,9 @@ function createChatRouter(db) {
|
|
|
35329
35642
|
// src/server/routes/reviewers.ts
|
|
35330
35643
|
var import_express11 = __toESM(require_express2(), 1);
|
|
35331
35644
|
import { readFileSync as readFileSync7, existsSync as existsSync11, watch } from "node:fs";
|
|
35332
|
-
import { join as
|
|
35645
|
+
import { join as join15 } from "node:path";
|
|
35333
35646
|
function readReviewersMeta(ocrDir) {
|
|
35334
|
-
const metaPath =
|
|
35647
|
+
const metaPath = join15(ocrDir, "reviewers-meta.json");
|
|
35335
35648
|
if (!existsSync11(metaPath)) {
|
|
35336
35649
|
return { reviewers: [], defaults: [] };
|
|
35337
35650
|
}
|
|
@@ -35360,7 +35673,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
35360
35673
|
res.status(400).json({ error: "Invalid reviewer ID" });
|
|
35361
35674
|
return;
|
|
35362
35675
|
}
|
|
35363
|
-
const filePath =
|
|
35676
|
+
const filePath = join15(ocrDir, "skills", "references", "reviewers", `${id}.md`);
|
|
35364
35677
|
if (!existsSync11(filePath)) {
|
|
35365
35678
|
res.status(404).json({ error: "Reviewer not found", id });
|
|
35366
35679
|
return;
|
|
@@ -35375,7 +35688,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
35375
35688
|
return router;
|
|
35376
35689
|
}
|
|
35377
35690
|
function watchReviewersMeta(ocrDir, io2) {
|
|
35378
|
-
const metaPath =
|
|
35691
|
+
const metaPath = join15(ocrDir, "reviewers-meta.json");
|
|
35379
35692
|
let watcher = null;
|
|
35380
35693
|
let debounce;
|
|
35381
35694
|
try {
|
|
@@ -35451,14 +35764,13 @@ function createHandoffRouter(sessionCapture, ocrDir, syncFromDisk = () => {
|
|
|
35451
35764
|
|
|
35452
35765
|
// src/server/routes/team.ts
|
|
35453
35766
|
var import_express14 = __toESM(require_express2(), 1);
|
|
35454
|
-
import { spawnSync } from "node:child_process";
|
|
35455
35767
|
|
|
35456
|
-
// ../
|
|
35768
|
+
// ../shared/config/src/team-config.ts
|
|
35457
35769
|
var import_yaml = __toESM(require_dist(), 1);
|
|
35458
35770
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
35459
|
-
import { join as
|
|
35771
|
+
import { join as join16 } from "node:path";
|
|
35460
35772
|
function loadTeamConfig(ocrDir) {
|
|
35461
|
-
const configPath =
|
|
35773
|
+
const configPath = join16(ocrDir, "config.yaml");
|
|
35462
35774
|
if (!existsSync12(configPath)) {
|
|
35463
35775
|
return { team: [], aliases: {}, defaultModel: null };
|
|
35464
35776
|
}
|
|
@@ -35498,6 +35810,9 @@ function parseTeamConfigYaml(content) {
|
|
|
35498
35810
|
aliases,
|
|
35499
35811
|
defaultModel
|
|
35500
35812
|
);
|
|
35813
|
+
if (resolvedModel !== null) {
|
|
35814
|
+
assertSafeModelId(resolvedModel, `default_team.${persona}[${i}]`);
|
|
35815
|
+
}
|
|
35501
35816
|
team.push({
|
|
35502
35817
|
persona,
|
|
35503
35818
|
instance_index: i + 1,
|
|
@@ -35578,6 +35893,16 @@ function readOptionalString(obj, key, pathLabel) {
|
|
|
35578
35893
|
}
|
|
35579
35894
|
return value;
|
|
35580
35895
|
}
|
|
35896
|
+
var SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._/:@[\]+-]{0,255}$/;
|
|
35897
|
+
function assertSafeModelId(value, pathLabel) {
|
|
35898
|
+
if (SAFE_MODEL_ID.test(value)) return;
|
|
35899
|
+
const allowed = /[A-Za-z0-9._/:@[\]+-]/;
|
|
35900
|
+
const offending = [...value].find((ch) => !allowed.test(ch));
|
|
35901
|
+
const detail = value.length === 0 ? "empty string" : value.length > 256 ? "longer than 256 characters" : offending !== void 0 ? `contains ${JSON.stringify(offending)}` : `starts with ${JSON.stringify(value[0])}`;
|
|
35902
|
+
throw new Error(
|
|
35903
|
+
`${pathLabel}: model id ${detail} \u2014 no vendor model id uses that. Allowed: letters and digits plus . _ / : @ [ ] + - (max 256 chars).`
|
|
35904
|
+
);
|
|
35905
|
+
}
|
|
35581
35906
|
function readAliases(root) {
|
|
35582
35907
|
const models = root["models"];
|
|
35583
35908
|
if (!models || typeof models !== "object" || Array.isArray(models)) return {};
|
|
@@ -35619,12 +35944,15 @@ function resolveTeamComposition(team, override) {
|
|
|
35619
35944
|
result.push(inst);
|
|
35620
35945
|
}
|
|
35621
35946
|
for (const inst of override) {
|
|
35947
|
+
if (inst.model !== null) {
|
|
35948
|
+
assertSafeModelId(inst.model, `override ${inst.persona}#${inst.instance_index}`);
|
|
35949
|
+
}
|
|
35622
35950
|
result.push(inst);
|
|
35623
35951
|
}
|
|
35624
35952
|
return result;
|
|
35625
35953
|
}
|
|
35626
35954
|
|
|
35627
|
-
// ../
|
|
35955
|
+
// ../shared/config/src/models.ts
|
|
35628
35956
|
function parseOpenCodeModelList(stdout) {
|
|
35629
35957
|
const models = [];
|
|
35630
35958
|
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
@@ -35758,6 +36086,7 @@ async function listModelsForVendor(vendor) {
|
|
|
35758
36086
|
}
|
|
35759
36087
|
|
|
35760
36088
|
// src/server/routes/team.ts
|
|
36089
|
+
import { dirname as dirname10 } from "node:path";
|
|
35761
36090
|
function isReviewerInstanceArray(input) {
|
|
35762
36091
|
if (!Array.isArray(input)) return false;
|
|
35763
36092
|
for (const entry of input) {
|
|
@@ -35810,32 +36139,25 @@ function createTeamRouter(ocrDir) {
|
|
|
35810
36139
|
return;
|
|
35811
36140
|
}
|
|
35812
36141
|
try {
|
|
35813
|
-
|
|
36142
|
+
execBinary("ocr", ["team", "set", "--stdin"], {
|
|
35814
36143
|
input: JSON.stringify(body.team),
|
|
35815
36144
|
encoding: "utf-8",
|
|
35816
|
-
|
|
36145
|
+
// Run from the project root (parent of `.ocr`). `dirname` is
|
|
36146
|
+
// separator-correct on every platform — a prior `/\/\.ocr$/` regex
|
|
36147
|
+
// silently no-op'd on Windows (join builds the path with `\`), running
|
|
36148
|
+
// `ocr team set` inside the `.ocr` dir itself (blocker B2). Matches the
|
|
36149
|
+
// `dirname(ocrDir)` derivation used across the socket handlers.
|
|
36150
|
+
cwd: dirname10(ocrDir),
|
|
35817
36151
|
timeout: 1e4
|
|
35818
36152
|
});
|
|
35819
|
-
if (result.error) {
|
|
35820
|
-
res.status(500).json({
|
|
35821
|
-
error: "Failed to invoke ocr team set",
|
|
35822
|
-
detail: result.error.message
|
|
35823
|
-
});
|
|
35824
|
-
return;
|
|
35825
|
-
}
|
|
35826
|
-
if (result.status !== 0) {
|
|
35827
|
-
res.status(500).json({
|
|
35828
|
-
error: "ocr team set exited non-zero",
|
|
35829
|
-
stderr: result.stderr
|
|
35830
|
-
});
|
|
35831
|
-
return;
|
|
35832
|
-
}
|
|
35833
36153
|
res.json({ ok: true, team: body.team });
|
|
35834
36154
|
} catch (err) {
|
|
35835
36155
|
console.error("Failed to persist team:", err);
|
|
36156
|
+
const e = err;
|
|
35836
36157
|
res.status(500).json({
|
|
35837
36158
|
error: "Failed to persist team",
|
|
35838
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
36159
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
36160
|
+
...typeof e.stderr === "string" && e.stderr ? { stderr: e.stderr } : {}
|
|
35839
36161
|
});
|
|
35840
36162
|
}
|
|
35841
36163
|
});
|
|
@@ -35952,6 +36274,7 @@ function recoverFromEventsJsonl(ocrDir, db, workflowId) {
|
|
|
35952
36274
|
function createSessionCaptureService(deps) {
|
|
35953
36275
|
const { db, ocrDir, aiCliService } = deps;
|
|
35954
36276
|
const driftLoggedFor = /* @__PURE__ */ new Set();
|
|
36277
|
+
const rejectLoggedFor = /* @__PURE__ */ new Set();
|
|
35955
36278
|
function readBoundSessionId(executionId) {
|
|
35956
36279
|
const result = db.exec(
|
|
35957
36280
|
"SELECT vendor_session_id FROM command_executions WHERE id = ?",
|
|
@@ -35962,6 +36285,15 @@ function createSessionCaptureService(deps) {
|
|
|
35962
36285
|
}
|
|
35963
36286
|
function recordSessionId(executionId, vendorSessionId) {
|
|
35964
36287
|
try {
|
|
36288
|
+
if (!isSafeVendorSessionId(vendorSessionId)) {
|
|
36289
|
+
if (!rejectLoggedFor.has(executionId)) {
|
|
36290
|
+
rejectLoggedFor.add(executionId);
|
|
36291
|
+
console.warn(
|
|
36292
|
+
`[session-capture] rejecting implausible vendor session id on execution ${executionId}: ${JSON.stringify(vendorSessionId)} (allowed: letters/digits plus . _ : - , max 256 chars). Not recorded \u2014 resume for this execution is unavailable.`
|
|
36293
|
+
);
|
|
36294
|
+
}
|
|
36295
|
+
return;
|
|
36296
|
+
}
|
|
35965
36297
|
const existing = readBoundSessionId(executionId);
|
|
35966
36298
|
if (existing === vendorSessionId) return;
|
|
35967
36299
|
if (existing) {
|
|
@@ -36141,7 +36473,7 @@ function buildDiagnostics(input) {
|
|
|
36141
36473
|
|
|
36142
36474
|
// src/server/services/filesystem-sync.ts
|
|
36143
36475
|
import { readdirSync as readdirSync2, readFileSync as readFileSync9, statSync as statSync3, existsSync as existsSync13 } from "node:fs";
|
|
36144
|
-
import { join as
|
|
36476
|
+
import { join as join17, basename as basename3, dirname as dirname11, relative } from "node:path";
|
|
36145
36477
|
import { watch as watch2 } from "chokidar";
|
|
36146
36478
|
|
|
36147
36479
|
// src/server/services/parsers/reviewer-parser.ts
|
|
@@ -36236,21 +36568,12 @@ var VERDICT_RE = /^\*?\*?\s*(?:##\s*)?Verdict\s*\*?\*?\s*:?\s*\*?\*?\s*(.*)/im;
|
|
|
36236
36568
|
var BLOCKERS_RE = /^\*\*Blockers?\*\*\s*:?\s*(\d+)/im;
|
|
36237
36569
|
var SHOULD_FIX_RE = /^\*\*Should\s*Fix\*\*\s*:?\s*(\d+)/im;
|
|
36238
36570
|
var SUGGESTIONS_RE = /^\*\*Suggestions?\*\*\s*:?\s*(\d+)/im;
|
|
36239
|
-
|
|
36240
|
-
"REQUEST CHANGES",
|
|
36241
|
-
"CHANGES REQUESTED",
|
|
36242
|
-
"NEEDS DISCUSSION",
|
|
36243
|
-
"NEEDS WORK",
|
|
36244
|
-
"APPROVED",
|
|
36245
|
-
"APPROVE",
|
|
36246
|
-
"LGTM",
|
|
36247
|
-
"BLOCK",
|
|
36248
|
-
"REJECT"
|
|
36249
|
-
];
|
|
36250
|
-
function normalizeVerdict(raw) {
|
|
36571
|
+
function extractVerdictLabel(raw) {
|
|
36251
36572
|
const cleaned = raw.trim().replace(/^\*+|\*+$/g, "").trim();
|
|
36573
|
+
const canonical = normalizeVerdict(cleaned);
|
|
36574
|
+
if (canonical) return canonical;
|
|
36252
36575
|
const upper = cleaned.toUpperCase();
|
|
36253
|
-
for (const verdict of
|
|
36576
|
+
for (const verdict of CANONICAL_VERDICTS) {
|
|
36254
36577
|
if (upper.startsWith(verdict)) return verdict;
|
|
36255
36578
|
}
|
|
36256
36579
|
const truncated = cleaned.split(/\s+[—:.]\s+|\n/, 1)[0] ?? cleaned;
|
|
@@ -36262,7 +36585,7 @@ function parseFinalMd(content) {
|
|
|
36262
36585
|
if (verdictMatch) {
|
|
36263
36586
|
const captured = (verdictMatch[1] ?? "").trim();
|
|
36264
36587
|
if (captured.length > 0) {
|
|
36265
|
-
verdict =
|
|
36588
|
+
verdict = extractVerdictLabel(captured);
|
|
36266
36589
|
}
|
|
36267
36590
|
}
|
|
36268
36591
|
const blockerMatch = content.match(BLOCKERS_RE);
|
|
@@ -36349,13 +36672,13 @@ var FilesystemSync = class {
|
|
|
36349
36672
|
for (const entry of entries) {
|
|
36350
36673
|
if (!entry.isDirectory()) continue;
|
|
36351
36674
|
const sessionId = entry.name;
|
|
36352
|
-
const sessionDir =
|
|
36675
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
36353
36676
|
this.syncSession(sessionId, sessionDir);
|
|
36354
36677
|
}
|
|
36355
36678
|
}
|
|
36356
36679
|
syncSession(sessionId, sessionDir) {
|
|
36357
36680
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
36358
|
-
const roundsDir =
|
|
36681
|
+
const roundsDir = join17(sessionDir, "rounds");
|
|
36359
36682
|
if (existsSync13(roundsDir)) {
|
|
36360
36683
|
const rounds = readdirSync2(roundsDir, { withFileTypes: true });
|
|
36361
36684
|
for (const roundEntry of rounds) {
|
|
@@ -36363,34 +36686,34 @@ var FilesystemSync = class {
|
|
|
36363
36686
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
36364
36687
|
if (!roundMatch) continue;
|
|
36365
36688
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
36366
|
-
const roundDir =
|
|
36367
|
-
const reviewsDir =
|
|
36689
|
+
const roundDir = join17(roundsDir, roundEntry.name);
|
|
36690
|
+
const reviewsDir = join17(roundDir, "reviews");
|
|
36368
36691
|
if (existsSync13(reviewsDir)) {
|
|
36369
36692
|
const reviewFiles = readdirSync2(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
36370
36693
|
for (const reviewFile of reviewFiles) {
|
|
36371
|
-
const filePath =
|
|
36694
|
+
const filePath = join17(reviewsDir, reviewFile);
|
|
36372
36695
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
36373
36696
|
}
|
|
36374
36697
|
}
|
|
36375
|
-
const roundMetaPath =
|
|
36698
|
+
const roundMetaPath = join17(roundDir, "round-meta.json");
|
|
36376
36699
|
if (existsSync13(roundMetaPath)) {
|
|
36377
36700
|
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
36378
36701
|
}
|
|
36379
|
-
const finalPath =
|
|
36702
|
+
const finalPath = join17(roundDir, "final.md");
|
|
36380
36703
|
if (existsSync13(finalPath)) {
|
|
36381
36704
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
36382
36705
|
}
|
|
36383
|
-
const finalHumanPath =
|
|
36706
|
+
const finalHumanPath = join17(roundDir, "final-human.md");
|
|
36384
36707
|
if (existsSync13(finalHumanPath)) {
|
|
36385
36708
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
36386
36709
|
}
|
|
36387
|
-
const discoursePath =
|
|
36710
|
+
const discoursePath = join17(roundDir, "discourse.md");
|
|
36388
36711
|
if (existsSync13(discoursePath)) {
|
|
36389
36712
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
36390
36713
|
}
|
|
36391
36714
|
}
|
|
36392
36715
|
}
|
|
36393
|
-
const mapDir =
|
|
36716
|
+
const mapDir = join17(sessionDir, "map", "runs");
|
|
36394
36717
|
if (existsSync13(mapDir)) {
|
|
36395
36718
|
const runs = readdirSync2(mapDir, { withFileTypes: true });
|
|
36396
36719
|
for (const runEntry of runs) {
|
|
@@ -36398,12 +36721,12 @@ var FilesystemSync = class {
|
|
|
36398
36721
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
36399
36722
|
if (!runMatch) continue;
|
|
36400
36723
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
36401
|
-
const runDir =
|
|
36402
|
-
const mapMetaPath =
|
|
36724
|
+
const runDir = join17(mapDir, runEntry.name);
|
|
36725
|
+
const mapMetaPath = join17(runDir, "map-meta.json");
|
|
36403
36726
|
if (existsSync13(mapMetaPath)) {
|
|
36404
36727
|
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
36405
36728
|
}
|
|
36406
|
-
const mapPath =
|
|
36729
|
+
const mapPath = join17(runDir, "map.md");
|
|
36407
36730
|
if (existsSync13(mapPath)) {
|
|
36408
36731
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
36409
36732
|
}
|
|
@@ -36413,7 +36736,7 @@ var FilesystemSync = class {
|
|
|
36413
36736
|
["requirements-mapping.md", "requirements-mapping"]
|
|
36414
36737
|
];
|
|
36415
36738
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
36416
|
-
const filePath =
|
|
36739
|
+
const filePath = join17(runDir, fileName);
|
|
36417
36740
|
if (existsSync13(filePath)) {
|
|
36418
36741
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
36419
36742
|
}
|
|
@@ -36425,68 +36748,113 @@ var FilesystemSync = class {
|
|
|
36425
36748
|
["discovered-standards.md", "discovered-standards"]
|
|
36426
36749
|
];
|
|
36427
36750
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
36428
|
-
const filePath =
|
|
36751
|
+
const filePath = join17(sessionDir, fileName);
|
|
36429
36752
|
if (existsSync13(filePath)) {
|
|
36430
36753
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
36431
36754
|
}
|
|
36432
36755
|
}
|
|
36433
36756
|
}
|
|
36757
|
+
// ── Terminal-completion evidence (defect D1) ──
|
|
36758
|
+
//
|
|
36759
|
+
// The dashboard read/sync path NEVER originates terminal workflow completion.
|
|
36760
|
+
// A `final.md` / `map.md` artifact on disk is evidence of the **synthesis**
|
|
36761
|
+
// phase only; terminal completion is the CLI's to declare and is recognized
|
|
36762
|
+
// solely from the CLI-produced evidence — a `round_completed` / `map_completed`
|
|
36763
|
+
// orchestration event. Closing on artifact presence alone is the fabrication
|
|
36764
|
+
// these helpers exist to prevent.
|
|
36765
|
+
/** Whether the CLI has recorded a `round_completed` event for this round. */
|
|
36766
|
+
hasRoundCompletedEvent(sessionId, round) {
|
|
36767
|
+
return queryFirst(
|
|
36768
|
+
this.db,
|
|
36769
|
+
`SELECT 1 FROM orchestration_events
|
|
36770
|
+
WHERE session_id = ? AND event_type = 'round_completed' AND round = ? LIMIT 1`,
|
|
36771
|
+
[sessionId, round]
|
|
36772
|
+
) != null;
|
|
36773
|
+
}
|
|
36774
|
+
/** Whether the CLI has recorded a `map_completed` event for this map run. */
|
|
36775
|
+
hasMapCompletedEvent(sessionId, mapRun) {
|
|
36776
|
+
return queryFirst(
|
|
36777
|
+
this.db,
|
|
36778
|
+
`SELECT 1 FROM orchestration_events
|
|
36779
|
+
WHERE session_id = ? AND event_type = 'map_completed' AND round = ? LIMIT 1`,
|
|
36780
|
+
[sessionId, mapRun]
|
|
36781
|
+
) != null;
|
|
36782
|
+
}
|
|
36783
|
+
/**
|
|
36784
|
+
* Full CLI terminal evidence for a review round: a `round_completed` event AND
|
|
36785
|
+
* a validated `round-meta.json` on disk. Used by the backfill reconciler to
|
|
36786
|
+
* decide whether a discovered-on-disk session is genuinely complete.
|
|
36787
|
+
*/
|
|
36788
|
+
hasTerminalRoundEvidence(sessionId, round, roundDir) {
|
|
36789
|
+
return existsSync13(join17(roundDir, "round-meta.json")) && this.hasRoundCompletedEvent(sessionId, round);
|
|
36790
|
+
}
|
|
36791
|
+
/** Full CLI terminal evidence for a map run: a `map_completed` event AND a
|
|
36792
|
+
* validated `map-meta.json` on disk. */
|
|
36793
|
+
hasTerminalMapEvidence(sessionId, mapRun, runDir) {
|
|
36794
|
+
return existsSync13(join17(runDir, "map-meta.json")) && this.hasMapCompletedEvent(sessionId, mapRun);
|
|
36795
|
+
}
|
|
36434
36796
|
// ── Session Backfill ──
|
|
36435
36797
|
ensureSessionRow(sessionId, sessionDir) {
|
|
36436
36798
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
36437
36799
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
36438
|
-
const hasRoundsDir = existsSync13(
|
|
36439
|
-
const hasMapDir = existsSync13(
|
|
36800
|
+
const hasRoundsDir = existsSync13(join17(sessionDir, "rounds"));
|
|
36801
|
+
const hasMapDir = existsSync13(join17(sessionDir, "map"));
|
|
36440
36802
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
36441
36803
|
let currentRound = 1;
|
|
36442
36804
|
if (hasRoundsDir) {
|
|
36443
|
-
const roundDirs = readdirSync2(
|
|
36805
|
+
const roundDirs = readdirSync2(join17(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
36444
36806
|
currentRound = Math.max(1, roundDirs.length);
|
|
36445
36807
|
}
|
|
36446
36808
|
let currentMapRun = 1;
|
|
36447
|
-
const mapRunsDir =
|
|
36809
|
+
const mapRunsDir = join17(sessionDir, "map", "runs");
|
|
36448
36810
|
if (existsSync13(mapRunsDir)) {
|
|
36449
36811
|
const runDirs = readdirSync2(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
36450
36812
|
currentMapRun = Math.max(1, runDirs.length);
|
|
36451
36813
|
}
|
|
36452
36814
|
let phase = "context";
|
|
36453
36815
|
let phaseNumber = 1;
|
|
36454
|
-
let status = "
|
|
36816
|
+
let status = "active";
|
|
36455
36817
|
if (workflowType === "review" && hasRoundsDir) {
|
|
36456
|
-
const roundDir =
|
|
36457
|
-
if (existsSync13(
|
|
36818
|
+
const roundDir = join17(sessionDir, "rounds", `round-${currentRound}`);
|
|
36819
|
+
if (existsSync13(join17(roundDir, "final.md")) && this.hasTerminalRoundEvidence(sessionId, currentRound, roundDir)) {
|
|
36458
36820
|
phase = "complete";
|
|
36459
36821
|
phaseNumber = 8;
|
|
36460
36822
|
status = "closed";
|
|
36461
|
-
} else if (existsSync13(
|
|
36823
|
+
} else if (existsSync13(join17(roundDir, "final.md"))) {
|
|
36824
|
+
phase = "synthesis";
|
|
36825
|
+
phaseNumber = 7;
|
|
36826
|
+
} else if (existsSync13(join17(roundDir, "discourse.md"))) {
|
|
36462
36827
|
phase = "synthesis";
|
|
36463
36828
|
phaseNumber = 7;
|
|
36464
|
-
} else if (existsSync13(
|
|
36829
|
+
} else if (existsSync13(join17(roundDir, "reviews")) && readdirSync2(join17(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
36465
36830
|
phase = "reviews";
|
|
36466
36831
|
phaseNumber = 4;
|
|
36467
|
-
} else if (existsSync13(
|
|
36832
|
+
} else if (existsSync13(join17(sessionDir, "context.md"))) {
|
|
36468
36833
|
phase = "analysis";
|
|
36469
36834
|
phaseNumber = 3;
|
|
36470
|
-
} else if (existsSync13(
|
|
36835
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36471
36836
|
phase = "change-context";
|
|
36472
36837
|
phaseNumber = 2;
|
|
36473
36838
|
}
|
|
36474
36839
|
} else if (workflowType === "map" && hasMapDir) {
|
|
36475
|
-
const runDir =
|
|
36476
|
-
if (existsSync13(
|
|
36840
|
+
const runDir = join17(mapRunsDir, `run-${currentMapRun}`);
|
|
36841
|
+
if (existsSync13(join17(runDir, "map.md")) && this.hasTerminalMapEvidence(sessionId, currentMapRun, runDir)) {
|
|
36477
36842
|
phase = "complete";
|
|
36478
36843
|
phaseNumber = 6;
|
|
36479
36844
|
status = "closed";
|
|
36480
|
-
} else if (existsSync13(
|
|
36845
|
+
} else if (existsSync13(join17(runDir, "map.md"))) {
|
|
36846
|
+
phase = "synthesis";
|
|
36847
|
+
phaseNumber = 5;
|
|
36848
|
+
} else if (existsSync13(join17(runDir, "requirements-mapping.md"))) {
|
|
36481
36849
|
phase = "synthesis";
|
|
36482
36850
|
phaseNumber = 5;
|
|
36483
|
-
} else if (existsSync13(
|
|
36851
|
+
} else if (existsSync13(join17(runDir, "flow-analysis.md"))) {
|
|
36484
36852
|
phase = "requirements-mapping";
|
|
36485
36853
|
phaseNumber = 4;
|
|
36486
|
-
} else if (existsSync13(
|
|
36854
|
+
} else if (existsSync13(join17(runDir, "topology.md"))) {
|
|
36487
36855
|
phase = "flow-analysis";
|
|
36488
36856
|
phaseNumber = 3;
|
|
36489
|
-
} else if (existsSync13(
|
|
36857
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36490
36858
|
phase = "topology";
|
|
36491
36859
|
phaseNumber = 2;
|
|
36492
36860
|
}
|
|
@@ -36541,7 +36909,7 @@ var FilesystemSync = class {
|
|
|
36541
36909
|
try {
|
|
36542
36910
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
36543
36911
|
if (entry.isDirectory()) {
|
|
36544
|
-
if (this.hasArtifacts(
|
|
36912
|
+
if (this.hasArtifacts(join17(dir, entry.name))) return true;
|
|
36545
36913
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
36546
36914
|
return true;
|
|
36547
36915
|
}
|
|
@@ -36687,7 +37055,7 @@ var FilesystemSync = class {
|
|
|
36687
37055
|
"SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
|
|
36688
37056
|
[sessionId]
|
|
36689
37057
|
);
|
|
36690
|
-
if (session && session["workflow_type"] === "map" && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
37058
|
+
if (session && session["workflow_type"] === "map" && this.hasMapCompletedEvent(sessionId, runNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
36691
37059
|
commitReasonClose(
|
|
36692
37060
|
this.db,
|
|
36693
37061
|
sessionId,
|
|
@@ -36845,13 +37213,8 @@ var FilesystemSync = class {
|
|
|
36845
37213
|
console.error(`[FilesystemSync] Invalid round-meta.json at ${filePath}`);
|
|
36846
37214
|
return;
|
|
36847
37215
|
}
|
|
36848
|
-
const
|
|
36849
|
-
const
|
|
36850
|
-
const blockerCount = sc?.blockers ?? allFindings.filter((f) => f.category === "blocker").length;
|
|
36851
|
-
const shouldFixCount = sc?.should_fix ?? allFindings.filter((f) => f.category === "should_fix").length;
|
|
36852
|
-
const suggestionCount = sc?.suggestions ?? allFindings.filter((f) => f.category === "suggestion").length;
|
|
36853
|
-
const reviewerCount = meta.reviewers.length;
|
|
36854
|
-
const totalFindingCount = allFindings.length;
|
|
37216
|
+
const normalizedVerdict = normalizeVerdict(meta.verdict) ?? meta.verdict;
|
|
37217
|
+
const { blockerCount, shouldFixCount, suggestionCount, reviewerCount, totalFindingCount } = resolveRoundCounts(meta);
|
|
36855
37218
|
this.db.run("BEGIN TRANSACTION");
|
|
36856
37219
|
try {
|
|
36857
37220
|
this.db.run(
|
|
@@ -36860,7 +37223,7 @@ var FilesystemSync = class {
|
|
|
36860
37223
|
reviewer_count = ?, total_finding_count = ?, source = 'orchestrator', parsed_at = ?
|
|
36861
37224
|
WHERE session_id = ? AND round_number = ?`,
|
|
36862
37225
|
[
|
|
36863
|
-
|
|
37226
|
+
normalizedVerdict,
|
|
36864
37227
|
blockerCount,
|
|
36865
37228
|
suggestionCount,
|
|
36866
37229
|
shouldFixCount,
|
|
@@ -36881,12 +37244,12 @@ var FilesystemSync = class {
|
|
|
36881
37244
|
this.db.run("COMMIT");
|
|
36882
37245
|
return;
|
|
36883
37246
|
}
|
|
36884
|
-
const roundDir =
|
|
37247
|
+
const roundDir = dirname11(filePath);
|
|
36885
37248
|
for (const reviewer of meta.reviewers) {
|
|
36886
37249
|
const reviewerType = reviewer.type ?? "unknown";
|
|
36887
37250
|
const instanceNumber = reviewer.instance ?? 1;
|
|
36888
37251
|
const findings = reviewer.findings ?? [];
|
|
36889
|
-
const reviewerMdPath =
|
|
37252
|
+
const reviewerMdPath = join17(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
36890
37253
|
this.db.run(
|
|
36891
37254
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
36892
37255
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -36960,7 +37323,7 @@ var FilesystemSync = class {
|
|
|
36960
37323
|
this.io?.to(`session:${sessionId}`).emit("round:updated", {
|
|
36961
37324
|
sessionId,
|
|
36962
37325
|
roundNumber,
|
|
36963
|
-
verdict:
|
|
37326
|
+
verdict: normalizedVerdict,
|
|
36964
37327
|
blockerCount,
|
|
36965
37328
|
shouldFixCount,
|
|
36966
37329
|
suggestionCount,
|
|
@@ -37121,11 +37484,12 @@ var FilesystemSync = class {
|
|
|
37121
37484
|
);
|
|
37122
37485
|
} else {
|
|
37123
37486
|
const parsed = parseFinalMd(content);
|
|
37487
|
+
const parsedVerdict = parsed.verdict ? normalizeVerdict(parsed.verdict) ?? parsed.verdict : parsed.verdict;
|
|
37124
37488
|
this.db.run(
|
|
37125
37489
|
`UPDATE review_rounds SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?, final_md_path = ?, parsed_at = ?, source = 'parser'
|
|
37126
37490
|
WHERE session_id = ? AND round_number = ?`,
|
|
37127
37491
|
[
|
|
37128
|
-
|
|
37492
|
+
parsedVerdict,
|
|
37129
37493
|
parsed.blockerCount,
|
|
37130
37494
|
parsed.suggestionCount,
|
|
37131
37495
|
parsed.shouldFixCount,
|
|
@@ -37155,7 +37519,7 @@ var FilesystemSync = class {
|
|
|
37155
37519
|
"SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
|
|
37156
37520
|
[sessionId]
|
|
37157
37521
|
);
|
|
37158
|
-
if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37522
|
+
if (session && this.hasRoundCompletedEvent(sessionId, roundNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37159
37523
|
commitReasonClose(
|
|
37160
37524
|
this.db,
|
|
37161
37525
|
sessionId,
|
|
@@ -37249,7 +37613,7 @@ var FilesystemSync = class {
|
|
|
37249
37613
|
const parts = relFromSessions.split("/");
|
|
37250
37614
|
const sessionId = parts[0];
|
|
37251
37615
|
if (!sessionId) return;
|
|
37252
|
-
const sessionDir =
|
|
37616
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
37253
37617
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
37254
37618
|
const fileName = basename3(filePath);
|
|
37255
37619
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
@@ -37322,7 +37686,7 @@ var FilesystemSync = class {
|
|
|
37322
37686
|
|
|
37323
37687
|
// src/server/services/db-sync-watcher.ts
|
|
37324
37688
|
import { existsSync as existsSync14 } from "node:fs";
|
|
37325
|
-
import { dirname as
|
|
37689
|
+
import { dirname as dirname12, basename as basename4 } from "node:path";
|
|
37326
37690
|
import { watch as watch3 } from "chokidar";
|
|
37327
37691
|
function col(row, key) {
|
|
37328
37692
|
return row[key] ?? null;
|
|
@@ -37364,7 +37728,7 @@ var DbSyncWatcher = class {
|
|
|
37364
37728
|
/** Start watching the DB file (and its WAL sidecar) for external writes. */
|
|
37365
37729
|
startWatching() {
|
|
37366
37730
|
if (!existsSync14(this.dbFilePath)) return;
|
|
37367
|
-
const watchDir =
|
|
37731
|
+
const watchDir = dirname12(this.dbFilePath);
|
|
37368
37732
|
const dbFile = basename4(this.dbFilePath);
|
|
37369
37733
|
const walFile = `${dbFile}-wal`;
|
|
37370
37734
|
this.watcher = watch3(watchDir, {
|
|
@@ -37626,20 +37990,20 @@ function commandFingerprint(row) {
|
|
|
37626
37990
|
}
|
|
37627
37991
|
|
|
37628
37992
|
// src/server/socket/chat-handler.ts
|
|
37629
|
-
import { dirname as
|
|
37993
|
+
import { dirname as dirname13 } from "node:path";
|
|
37630
37994
|
|
|
37631
37995
|
// src/server/services/chat-context.ts
|
|
37632
37996
|
import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync15 } from "node:fs";
|
|
37633
|
-
import { join as
|
|
37997
|
+
import { join as join18 } from "node:path";
|
|
37634
37998
|
function buildChatContext(ocrDir, target) {
|
|
37635
|
-
const sessionsDir =
|
|
37999
|
+
const sessionsDir = join18(ocrDir, "sessions");
|
|
37636
38000
|
if (target.type === "map_run") {
|
|
37637
38001
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
37638
38002
|
}
|
|
37639
38003
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
37640
38004
|
}
|
|
37641
38005
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
37642
|
-
const mapPath =
|
|
38006
|
+
const mapPath = join18(
|
|
37643
38007
|
sessionsDir,
|
|
37644
38008
|
sessionId,
|
|
37645
38009
|
"map",
|
|
@@ -37666,9 +38030,9 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
37666
38030
|
return parts.join("\n");
|
|
37667
38031
|
}
|
|
37668
38032
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
37669
|
-
const roundDir =
|
|
37670
|
-
const finalPath =
|
|
37671
|
-
const reviewersDir =
|
|
38033
|
+
const roundDir = join18(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
38034
|
+
const finalPath = join18(roundDir, "final.md");
|
|
38035
|
+
const reviewersDir = join18(roundDir, "reviews");
|
|
37672
38036
|
const parts = [
|
|
37673
38037
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
37674
38038
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
@@ -37685,7 +38049,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
37685
38049
|
if (existsSync15(reviewersDir)) {
|
|
37686
38050
|
const files = readdirSync3(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
37687
38051
|
for (const file of files) {
|
|
37688
|
-
const content = readFileSync10(
|
|
38052
|
+
const content = readFileSync10(join18(reviewersDir, file), "utf-8");
|
|
37689
38053
|
const reviewerName = file.replace(/\.md$/, "");
|
|
37690
38054
|
parts.push("");
|
|
37691
38055
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -37749,7 +38113,7 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
37749
38113
|
db.run(
|
|
37750
38114
|
`UPDATE command_executions
|
|
37751
38115
|
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
37752
|
-
WHERE id =
|
|
38116
|
+
WHERE id = ? AND finished_at IS NULL`,
|
|
37753
38117
|
[exitCode, finishedAt, outputBuffer, executionId]
|
|
37754
38118
|
);
|
|
37755
38119
|
appendCommandLog(ocrDir, {
|
|
@@ -37839,7 +38203,7 @@ User: ${message}`;
|
|
|
37839
38203
|
});
|
|
37840
38204
|
return;
|
|
37841
38205
|
}
|
|
37842
|
-
const repoRoot =
|
|
38206
|
+
const repoRoot = dirname13(ocrDir);
|
|
37843
38207
|
const spawnResult = adapter.spawn({
|
|
37844
38208
|
prompt,
|
|
37845
38209
|
cwd: repoRoot,
|
|
@@ -38015,13 +38379,13 @@ function cleanupAllChats() {
|
|
|
38015
38379
|
}
|
|
38016
38380
|
|
|
38017
38381
|
// src/server/socket/post-handler.ts
|
|
38018
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
38382
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
38019
38383
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
38020
|
-
import { join as
|
|
38384
|
+
import { join as join19, dirname as dirname14, isAbsolute as isAbsolute2 } from "node:path";
|
|
38021
38385
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
38022
38386
|
function resolveSessionDir2(sessionDir, ocrDir) {
|
|
38023
38387
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
38024
|
-
return
|
|
38388
|
+
return join19(dirname14(ocrDir), sessionDir);
|
|
38025
38389
|
}
|
|
38026
38390
|
var BRANCH_PREFIXES = [
|
|
38027
38391
|
"feat",
|
|
@@ -38090,7 +38454,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38090
38454
|
return;
|
|
38091
38455
|
}
|
|
38092
38456
|
const branch = session.branch;
|
|
38093
|
-
const repoRoot =
|
|
38457
|
+
const repoRoot = dirname14(ocrDir);
|
|
38094
38458
|
try {
|
|
38095
38459
|
await execBinaryAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot, encoding: "utf-8" });
|
|
38096
38460
|
} catch {
|
|
@@ -38191,16 +38555,16 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38191
38555
|
socket.emit("post:error", { error: "Session not found" });
|
|
38192
38556
|
return;
|
|
38193
38557
|
}
|
|
38194
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38195
|
-
const roundDir =
|
|
38196
|
-
const finalPath =
|
|
38558
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38559
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38560
|
+
const finalPath = join19(roundDir, "final.md");
|
|
38197
38561
|
if (!existsSync16(finalPath)) {
|
|
38198
38562
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
38199
38563
|
return;
|
|
38200
38564
|
}
|
|
38201
|
-
const humanReviewPath =
|
|
38202
|
-
const repoRoot =
|
|
38203
|
-
const commandMdPath =
|
|
38565
|
+
const humanReviewPath = join19(roundDir, "final-human.md");
|
|
38566
|
+
const repoRoot = dirname14(ocrDir);
|
|
38567
|
+
const commandMdPath = join19(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
38204
38568
|
let commandContent;
|
|
38205
38569
|
try {
|
|
38206
38570
|
commandContent = readFileSync11(commandMdPath, "utf-8");
|
|
@@ -38360,10 +38724,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38360
38724
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
38361
38725
|
return;
|
|
38362
38726
|
}
|
|
38363
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38364
|
-
const roundDir =
|
|
38365
|
-
|
|
38366
|
-
const filePath =
|
|
38727
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38728
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38729
|
+
mkdirSync7(roundDir, { recursive: true });
|
|
38730
|
+
const filePath = join19(roundDir, "final-human.md");
|
|
38367
38731
|
writeFileSync5(filePath, content, { mode: 420 });
|
|
38368
38732
|
socket.emit("post:save-result", { success: true });
|
|
38369
38733
|
} catch (err) {
|
|
@@ -38390,14 +38754,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38390
38754
|
);
|
|
38391
38755
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
38392
38756
|
`);
|
|
38393
|
-
const tmpDir =
|
|
38757
|
+
const tmpDir = join19(tmpdir2(), "ocr-post-comments");
|
|
38394
38758
|
try {
|
|
38395
|
-
|
|
38759
|
+
mkdirSync7(tmpDir, { recursive: true, mode: 448 });
|
|
38396
38760
|
} catch {
|
|
38397
38761
|
}
|
|
38398
|
-
const tmpFile =
|
|
38762
|
+
const tmpFile = join19(tmpDir, `${randomUUID2()}.md`);
|
|
38399
38763
|
writeFileSync5(tmpFile, content, { mode: 384 });
|
|
38400
|
-
const repoRoot =
|
|
38764
|
+
const repoRoot = dirname14(ocrDir);
|
|
38401
38765
|
try {
|
|
38402
38766
|
const { stdout } = await execBinaryAsync(
|
|
38403
38767
|
"gh",
|
|
@@ -38440,9 +38804,67 @@ function cleanupAllPostGenerations() {
|
|
|
38440
38804
|
}
|
|
38441
38805
|
}
|
|
38442
38806
|
|
|
38807
|
+
// src/server/services/forward-resume-sweep.ts
|
|
38808
|
+
function hasPositiveDeathEvidence(db, sessionId, isAlive) {
|
|
38809
|
+
const instances = listAgentSessionsForWorkflow(db, sessionId);
|
|
38810
|
+
if (instances.length === 0) return false;
|
|
38811
|
+
return instances.every(
|
|
38812
|
+
(s) => s.ended_at != null || s.pid != null && !isAlive(s.pid)
|
|
38813
|
+
);
|
|
38814
|
+
}
|
|
38815
|
+
function planForwardResume(db, cfg) {
|
|
38816
|
+
const isAlive = cfg.isAlive ?? defaultIsAlive;
|
|
38817
|
+
const plan = [];
|
|
38818
|
+
for (const session of getAllSessions(db)) {
|
|
38819
|
+
if (session.status !== "active") continue;
|
|
38820
|
+
const events = getEventsForSession(db, session.id);
|
|
38821
|
+
const workflowType = session.workflow_type === "map" ? "map" : "review";
|
|
38822
|
+
if (hasTerminalArtifactEvent2(events, workflowType, session.current_round)) {
|
|
38823
|
+
continue;
|
|
38824
|
+
}
|
|
38825
|
+
if (!hasPositiveDeathEvidence(db, session.id, isAlive)) continue;
|
|
38826
|
+
const stranded = strandedActionByCap(db, session, cfg.maxAttempts);
|
|
38827
|
+
if (stranded.action === "abort_or_fresh") {
|
|
38828
|
+
plan.push({ sessionId: session.id, action: "cap_close" });
|
|
38829
|
+
continue;
|
|
38830
|
+
}
|
|
38831
|
+
const latest = getLatestAgentSessionWithVendorId(db, session.id);
|
|
38832
|
+
plan.push({
|
|
38833
|
+
sessionId: session.id,
|
|
38834
|
+
action: latest?.vendor_session_id ? "resume" : "handoff"
|
|
38835
|
+
});
|
|
38836
|
+
}
|
|
38837
|
+
return plan;
|
|
38838
|
+
}
|
|
38839
|
+
function runForwardResumeSweep(deps) {
|
|
38840
|
+
const plan = planForwardResume(deps.db, deps.config);
|
|
38841
|
+
for (const item of plan) {
|
|
38842
|
+
try {
|
|
38843
|
+
if (item.action === "cap_close") {
|
|
38844
|
+
closeForwardResumeExhausted(deps.db, item.sessionId, deps.maxAttempts);
|
|
38845
|
+
deps.log?.(
|
|
38846
|
+
`[ForwardResume] ${item.sessionId}: attempts exhausted \u2192 closed non-success`
|
|
38847
|
+
);
|
|
38848
|
+
} else if (item.action === "resume") {
|
|
38849
|
+
deps.spawnResume(item.sessionId);
|
|
38850
|
+
deps.log?.(`[ForwardResume] ${item.sessionId}: auto-resuming (ocr review --resume)`);
|
|
38851
|
+
} else {
|
|
38852
|
+
deps.log?.(
|
|
38853
|
+
`[ForwardResume] ${item.sessionId}: stranded, no resume adapter \u2014 pick up in terminal`
|
|
38854
|
+
);
|
|
38855
|
+
}
|
|
38856
|
+
} catch (err) {
|
|
38857
|
+
deps.log?.(
|
|
38858
|
+
`[ForwardResume] ${item.sessionId}: ${err instanceof Error ? err.message : String(err)}`
|
|
38859
|
+
);
|
|
38860
|
+
}
|
|
38861
|
+
}
|
|
38862
|
+
return plan;
|
|
38863
|
+
}
|
|
38864
|
+
|
|
38443
38865
|
// src/server/index.ts
|
|
38444
38866
|
import { homedir } from "node:os";
|
|
38445
|
-
var __dirname3 =
|
|
38867
|
+
var __dirname3 = dirname15(fileURLToPath3(import.meta.url));
|
|
38446
38868
|
function shortenPath(p) {
|
|
38447
38869
|
const home = homedir();
|
|
38448
38870
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
@@ -38509,7 +38931,7 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
38509
38931
|
function isOcrDashboardProcess(pid) {
|
|
38510
38932
|
if (process.platform === "win32") return false;
|
|
38511
38933
|
try {
|
|
38512
|
-
const cmd =
|
|
38934
|
+
const cmd = execBinary("ps", ["-p", String(pid), "-o", "command="], {
|
|
38513
38935
|
encoding: "utf-8",
|
|
38514
38936
|
timeout: 3e3
|
|
38515
38937
|
}).trim();
|
|
@@ -38524,23 +38946,23 @@ async function startServer(options = {}) {
|
|
|
38524
38946
|
process.title = "ocr-dashboard";
|
|
38525
38947
|
const ocrDir = resolveOcrDir();
|
|
38526
38948
|
const aiCliService = new AiCliService(ocrDir);
|
|
38527
|
-
const dbPathForCheckpoint =
|
|
38949
|
+
const dbPathForCheckpoint = join20(ocrDir, "data", "ocr.db");
|
|
38528
38950
|
const walResult = walCheckpointTruncate(dbPathForCheckpoint);
|
|
38529
38951
|
if (walResult === "checkpointed") {
|
|
38530
38952
|
console.log(" WAL checkpoint: truncated stale write-ahead-log file");
|
|
38531
38953
|
}
|
|
38532
|
-
for (const reaped of reapOrphanDbFiles(
|
|
38954
|
+
for (const reaped of reapOrphanDbFiles(join20(ocrDir, "data"))) {
|
|
38533
38955
|
console.log(` Orphan reap: removed stale ${reaped}`);
|
|
38534
38956
|
}
|
|
38535
|
-
const staleLogs = reapStaleExecLogs(
|
|
38957
|
+
const staleLogs = reapStaleExecLogs(join20(ocrDir, "data", "exec-logs"));
|
|
38536
38958
|
if (staleLogs.length > 0) {
|
|
38537
38959
|
console.log(` Exec-log reap: removed ${staleLogs.length} stale agent log(s)`);
|
|
38538
38960
|
}
|
|
38539
38961
|
const db = await openDb(ocrDir);
|
|
38540
|
-
const dataDir =
|
|
38541
|
-
const pidFilePath =
|
|
38542
|
-
const portFilePath =
|
|
38543
|
-
|
|
38962
|
+
const dataDir = join20(ocrDir, "data");
|
|
38963
|
+
const pidFilePath = join20(dataDir, "dashboard.pid");
|
|
38964
|
+
const portFilePath = join20(dataDir, "server-port");
|
|
38965
|
+
mkdirSync8(dataDir, { recursive: true });
|
|
38544
38966
|
try {
|
|
38545
38967
|
unlinkSync5(portFilePath);
|
|
38546
38968
|
} catch {
|
|
@@ -38638,12 +39060,39 @@ async function startServer(options = {}) {
|
|
|
38638
39060
|
}
|
|
38639
39061
|
};
|
|
38640
39062
|
await reconcileCompleted();
|
|
39063
|
+
const forwardResumeMaxAttempts = getForwardResumeMaxAttempts(ocrDir);
|
|
39064
|
+
const spawnResume = (sessionId) => {
|
|
39065
|
+
const child = spawnBinary("ocr", ["review", "--resume", sessionId], {
|
|
39066
|
+
cwd: ocrDir.replace(/\.ocr$/, "") || process.cwd(),
|
|
39067
|
+
stdio: "ignore",
|
|
39068
|
+
detached: true
|
|
39069
|
+
});
|
|
39070
|
+
child.on("error", (err) => {
|
|
39071
|
+
console.error(`[ForwardResume] spawn failed for ${sessionId}:`, err.message);
|
|
39072
|
+
});
|
|
39073
|
+
child.unref();
|
|
39074
|
+
};
|
|
39075
|
+
const runForwardResume = () => {
|
|
39076
|
+
try {
|
|
39077
|
+
runForwardResumeSweep({
|
|
39078
|
+
db,
|
|
39079
|
+
config: { maxAttempts: forwardResumeMaxAttempts, heartbeatMs: heartbeatSeconds * 1e3 },
|
|
39080
|
+
maxAttempts: forwardResumeMaxAttempts,
|
|
39081
|
+
spawnResume,
|
|
39082
|
+
log: (m) => console.log(` ${m}`)
|
|
39083
|
+
});
|
|
39084
|
+
} catch (err) {
|
|
39085
|
+
console.error("[ForwardResume] sweep failed:", err);
|
|
39086
|
+
}
|
|
39087
|
+
};
|
|
39088
|
+
runForwardResume();
|
|
38641
39089
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
38642
39090
|
const sweepTimer = setInterval(() => {
|
|
38643
39091
|
try {
|
|
38644
39092
|
logAgentSweep(sweepStaleAgentSessions(db, heartbeatSeconds, defaultIsAlive));
|
|
38645
39093
|
sweepStaleSessions(db, STALE_SESSION_THRESHOLD_SECONDS);
|
|
38646
39094
|
void reconcileCompleted();
|
|
39095
|
+
runForwardResume();
|
|
38647
39096
|
} catch (err) {
|
|
38648
39097
|
console.error("[sweep] periodic sweep failed:", err);
|
|
38649
39098
|
}
|
|
@@ -38679,10 +39128,10 @@ async function startServer(options = {}) {
|
|
|
38679
39128
|
app.use("/api/agent-sessions", createAgentSessionsRouter(db, () => pullSync()));
|
|
38680
39129
|
app.use("/api/sessions", createHandoffRouter(sessionCapture, ocrDir, () => pullSync()));
|
|
38681
39130
|
app.use("/api/team", createTeamRouter(ocrDir));
|
|
38682
|
-
const clientDir =
|
|
39131
|
+
const clientDir = join20(__dirname3, "client");
|
|
38683
39132
|
if (process.env.NODE_ENV === "production" && existsSync17(clientDir)) {
|
|
38684
39133
|
app.use(import_express15.default.static(clientDir, { index: false }));
|
|
38685
|
-
const indexHtmlPath =
|
|
39134
|
+
const indexHtmlPath = join20(clientDir, "index.html");
|
|
38686
39135
|
const rawIndexHtml = existsSync17(indexHtmlPath) ? readFileSync12(indexHtmlPath, "utf-8") : "";
|
|
38687
39136
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
38688
39137
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
@@ -38702,7 +39151,7 @@ async function startServer(options = {}) {
|
|
|
38702
39151
|
registerChatHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38703
39152
|
registerPostHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38704
39153
|
});
|
|
38705
|
-
const dbFilePath =
|
|
39154
|
+
const dbFilePath = join20(ocrDir, "data", "ocr.db");
|
|
38706
39155
|
const dbSyncWatcher = new DbSyncWatcher(
|
|
38707
39156
|
db,
|
|
38708
39157
|
dbFilePath,
|
|
@@ -38718,7 +39167,7 @@ async function startServer(options = {}) {
|
|
|
38718
39167
|
dbSyncWatcher.startWatching();
|
|
38719
39168
|
pullSync = () => dbSyncWatcher.syncFromDisk();
|
|
38720
39169
|
console.log(` Watching DB: ${shortenPath(dbFilePath)}`);
|
|
38721
|
-
const sessionsDir =
|
|
39170
|
+
const sessionsDir = join20(ocrDir, "sessions");
|
|
38722
39171
|
const fsSync = new FilesystemSync(db, sessionsDir, io);
|
|
38723
39172
|
await fsSync.fullScan();
|
|
38724
39173
|
fsSync.startWatching();
|
|
@@ -38782,7 +39231,7 @@ async function startServer(options = {}) {
|
|
|
38782
39231
|
unlinkSync5(portFilePath);
|
|
38783
39232
|
} catch {
|
|
38784
39233
|
}
|
|
38785
|
-
|
|
39234
|
+
clearAllSpawnMarkers(ocrDir);
|
|
38786
39235
|
try {
|
|
38787
39236
|
const activeResult = db.exec(
|
|
38788
39237
|
"SELECT id, pid FROM command_executions WHERE pid IS NOT NULL AND finished_at IS NULL"
|