@open-code-review/cli 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +926 -461
- package/dist/index.js +1344 -323
- package/package.json +5 -38
- 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";
|
|
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
|
+
}
|
|
34527
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,14 @@ 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";
|
|
35772
|
+
var MAX_INSTANCES_PER_PERSONA = 50;
|
|
35460
35773
|
function loadTeamConfig(ocrDir) {
|
|
35461
|
-
const configPath =
|
|
35774
|
+
const configPath = join16(ocrDir, "config.yaml");
|
|
35462
35775
|
if (!existsSync12(configPath)) {
|
|
35463
35776
|
return { team: [], aliases: {}, defaultModel: null };
|
|
35464
35777
|
}
|
|
@@ -35498,6 +35811,9 @@ function parseTeamConfigYaml(content) {
|
|
|
35498
35811
|
aliases,
|
|
35499
35812
|
defaultModel
|
|
35500
35813
|
);
|
|
35814
|
+
if (resolvedModel !== null) {
|
|
35815
|
+
assertSafeModelId(resolvedModel, `default_team.${persona}[${i}]`);
|
|
35816
|
+
}
|
|
35501
35817
|
team.push({
|
|
35502
35818
|
persona,
|
|
35503
35819
|
instance_index: i + 1,
|
|
@@ -35515,6 +35831,11 @@ function parseEntry(persona, entry) {
|
|
|
35515
35831
|
`default_team.${persona}: count must be a positive integer (got ${entry})`
|
|
35516
35832
|
);
|
|
35517
35833
|
}
|
|
35834
|
+
if (entry > MAX_INSTANCES_PER_PERSONA) {
|
|
35835
|
+
throw new Error(
|
|
35836
|
+
`default_team.${persona}: count must be <= ${MAX_INSTANCES_PER_PERSONA} (got ${entry})`
|
|
35837
|
+
);
|
|
35838
|
+
}
|
|
35518
35839
|
return Array.from({ length: entry }, () => ({}));
|
|
35519
35840
|
}
|
|
35520
35841
|
if (Array.isArray(entry)) {
|
|
@@ -35523,6 +35844,11 @@ function parseEntry(persona, entry) {
|
|
|
35523
35844
|
`default_team.${persona}: list form must contain at least one instance`
|
|
35524
35845
|
);
|
|
35525
35846
|
}
|
|
35847
|
+
if (entry.length > MAX_INSTANCES_PER_PERSONA) {
|
|
35848
|
+
throw new Error(
|
|
35849
|
+
`default_team.${persona}: list form must contain <= ${MAX_INSTANCES_PER_PERSONA} instances (got ${entry.length})`
|
|
35850
|
+
);
|
|
35851
|
+
}
|
|
35526
35852
|
return entry.map((item, idx) => parseListItem(persona, idx, item));
|
|
35527
35853
|
}
|
|
35528
35854
|
if (entry && typeof entry === "object") {
|
|
@@ -35544,6 +35870,11 @@ function parseEntry(persona, entry) {
|
|
|
35544
35870
|
`default_team.${persona}: count must be a positive integer (got ${String(count)})`
|
|
35545
35871
|
);
|
|
35546
35872
|
}
|
|
35873
|
+
if (count > MAX_INSTANCES_PER_PERSONA) {
|
|
35874
|
+
throw new Error(
|
|
35875
|
+
`default_team.${persona}: count must be <= ${MAX_INSTANCES_PER_PERSONA} (got ${count})`
|
|
35876
|
+
);
|
|
35877
|
+
}
|
|
35547
35878
|
const teamModel = readOptionalString(obj, "model", `default_team.${persona}.model`);
|
|
35548
35879
|
return Array.from({ length: count }, () => ({ teamModel }));
|
|
35549
35880
|
}
|
|
@@ -35578,6 +35909,16 @@ function readOptionalString(obj, key, pathLabel) {
|
|
|
35578
35909
|
}
|
|
35579
35910
|
return value;
|
|
35580
35911
|
}
|
|
35912
|
+
var SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._/:@[\]+-]{0,255}$/;
|
|
35913
|
+
function assertSafeModelId(value, pathLabel) {
|
|
35914
|
+
if (SAFE_MODEL_ID.test(value)) return;
|
|
35915
|
+
const allowed = /[A-Za-z0-9._/:@[\]+-]/;
|
|
35916
|
+
const offending = [...value].find((ch) => !allowed.test(ch));
|
|
35917
|
+
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])}`;
|
|
35918
|
+
throw new Error(
|
|
35919
|
+
`${pathLabel}: model id ${detail} \u2014 no vendor model id uses that. Allowed: letters and digits plus . _ / : @ [ ] + - (max 256 chars).`
|
|
35920
|
+
);
|
|
35921
|
+
}
|
|
35581
35922
|
function readAliases(root) {
|
|
35582
35923
|
const models = root["models"];
|
|
35583
35924
|
if (!models || typeof models !== "object" || Array.isArray(models)) return {};
|
|
@@ -35619,12 +35960,15 @@ function resolveTeamComposition(team, override) {
|
|
|
35619
35960
|
result.push(inst);
|
|
35620
35961
|
}
|
|
35621
35962
|
for (const inst of override) {
|
|
35963
|
+
if (inst.model !== null) {
|
|
35964
|
+
assertSafeModelId(inst.model, `override ${inst.persona}#${inst.instance_index}`);
|
|
35965
|
+
}
|
|
35622
35966
|
result.push(inst);
|
|
35623
35967
|
}
|
|
35624
35968
|
return result;
|
|
35625
35969
|
}
|
|
35626
35970
|
|
|
35627
|
-
// ../
|
|
35971
|
+
// ../shared/config/src/models.ts
|
|
35628
35972
|
function parseOpenCodeModelList(stdout) {
|
|
35629
35973
|
const models = [];
|
|
35630
35974
|
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
@@ -35758,6 +36102,7 @@ async function listModelsForVendor(vendor) {
|
|
|
35758
36102
|
}
|
|
35759
36103
|
|
|
35760
36104
|
// src/server/routes/team.ts
|
|
36105
|
+
import { dirname as dirname10 } from "node:path";
|
|
35761
36106
|
function isReviewerInstanceArray(input) {
|
|
35762
36107
|
if (!Array.isArray(input)) return false;
|
|
35763
36108
|
for (const entry of input) {
|
|
@@ -35810,32 +36155,25 @@ function createTeamRouter(ocrDir) {
|
|
|
35810
36155
|
return;
|
|
35811
36156
|
}
|
|
35812
36157
|
try {
|
|
35813
|
-
|
|
36158
|
+
execBinary("ocr", ["team", "set", "--stdin"], {
|
|
35814
36159
|
input: JSON.stringify(body.team),
|
|
35815
36160
|
encoding: "utf-8",
|
|
35816
|
-
|
|
36161
|
+
// Run from the project root (parent of `.ocr`). `dirname` is
|
|
36162
|
+
// separator-correct on every platform — a prior `/\/\.ocr$/` regex
|
|
36163
|
+
// silently no-op'd on Windows (join builds the path with `\`), running
|
|
36164
|
+
// `ocr team set` inside the `.ocr` dir itself (blocker B2). Matches the
|
|
36165
|
+
// `dirname(ocrDir)` derivation used across the socket handlers.
|
|
36166
|
+
cwd: dirname10(ocrDir),
|
|
35817
36167
|
timeout: 1e4
|
|
35818
36168
|
});
|
|
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
36169
|
res.json({ ok: true, team: body.team });
|
|
35834
36170
|
} catch (err) {
|
|
35835
36171
|
console.error("Failed to persist team:", err);
|
|
36172
|
+
const e = err;
|
|
35836
36173
|
res.status(500).json({
|
|
35837
36174
|
error: "Failed to persist team",
|
|
35838
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
36175
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
36176
|
+
...typeof e.stderr === "string" && e.stderr ? { stderr: e.stderr } : {}
|
|
35839
36177
|
});
|
|
35840
36178
|
}
|
|
35841
36179
|
});
|
|
@@ -35952,6 +36290,7 @@ function recoverFromEventsJsonl(ocrDir, db, workflowId) {
|
|
|
35952
36290
|
function createSessionCaptureService(deps) {
|
|
35953
36291
|
const { db, ocrDir, aiCliService } = deps;
|
|
35954
36292
|
const driftLoggedFor = /* @__PURE__ */ new Set();
|
|
36293
|
+
const rejectLoggedFor = /* @__PURE__ */ new Set();
|
|
35955
36294
|
function readBoundSessionId(executionId) {
|
|
35956
36295
|
const result = db.exec(
|
|
35957
36296
|
"SELECT vendor_session_id FROM command_executions WHERE id = ?",
|
|
@@ -35962,6 +36301,15 @@ function createSessionCaptureService(deps) {
|
|
|
35962
36301
|
}
|
|
35963
36302
|
function recordSessionId(executionId, vendorSessionId) {
|
|
35964
36303
|
try {
|
|
36304
|
+
if (!isSafeVendorSessionId(vendorSessionId)) {
|
|
36305
|
+
if (!rejectLoggedFor.has(executionId)) {
|
|
36306
|
+
rejectLoggedFor.add(executionId);
|
|
36307
|
+
console.warn(
|
|
36308
|
+
`[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.`
|
|
36309
|
+
);
|
|
36310
|
+
}
|
|
36311
|
+
return;
|
|
36312
|
+
}
|
|
35965
36313
|
const existing = readBoundSessionId(executionId);
|
|
35966
36314
|
if (existing === vendorSessionId) return;
|
|
35967
36315
|
if (existing) {
|
|
@@ -36141,7 +36489,7 @@ function buildDiagnostics(input) {
|
|
|
36141
36489
|
|
|
36142
36490
|
// src/server/services/filesystem-sync.ts
|
|
36143
36491
|
import { readdirSync as readdirSync2, readFileSync as readFileSync9, statSync as statSync3, existsSync as existsSync13 } from "node:fs";
|
|
36144
|
-
import { join as
|
|
36492
|
+
import { join as join17, basename as basename3, dirname as dirname11, relative } from "node:path";
|
|
36145
36493
|
import { watch as watch2 } from "chokidar";
|
|
36146
36494
|
|
|
36147
36495
|
// src/server/services/parsers/reviewer-parser.ts
|
|
@@ -36236,21 +36584,12 @@ var VERDICT_RE = /^\*?\*?\s*(?:##\s*)?Verdict\s*\*?\*?\s*:?\s*\*?\*?\s*(.*)/im;
|
|
|
36236
36584
|
var BLOCKERS_RE = /^\*\*Blockers?\*\*\s*:?\s*(\d+)/im;
|
|
36237
36585
|
var SHOULD_FIX_RE = /^\*\*Should\s*Fix\*\*\s*:?\s*(\d+)/im;
|
|
36238
36586
|
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) {
|
|
36587
|
+
function extractVerdictLabel(raw) {
|
|
36251
36588
|
const cleaned = raw.trim().replace(/^\*+|\*+$/g, "").trim();
|
|
36589
|
+
const canonical = normalizeVerdict(cleaned);
|
|
36590
|
+
if (canonical) return canonical;
|
|
36252
36591
|
const upper = cleaned.toUpperCase();
|
|
36253
|
-
for (const verdict of
|
|
36592
|
+
for (const verdict of CANONICAL_VERDICTS) {
|
|
36254
36593
|
if (upper.startsWith(verdict)) return verdict;
|
|
36255
36594
|
}
|
|
36256
36595
|
const truncated = cleaned.split(/\s+[—:.]\s+|\n/, 1)[0] ?? cleaned;
|
|
@@ -36262,7 +36601,7 @@ function parseFinalMd(content) {
|
|
|
36262
36601
|
if (verdictMatch) {
|
|
36263
36602
|
const captured = (verdictMatch[1] ?? "").trim();
|
|
36264
36603
|
if (captured.length > 0) {
|
|
36265
|
-
verdict =
|
|
36604
|
+
verdict = extractVerdictLabel(captured);
|
|
36266
36605
|
}
|
|
36267
36606
|
}
|
|
36268
36607
|
const blockerMatch = content.match(BLOCKERS_RE);
|
|
@@ -36349,13 +36688,13 @@ var FilesystemSync = class {
|
|
|
36349
36688
|
for (const entry of entries) {
|
|
36350
36689
|
if (!entry.isDirectory()) continue;
|
|
36351
36690
|
const sessionId = entry.name;
|
|
36352
|
-
const sessionDir =
|
|
36691
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
36353
36692
|
this.syncSession(sessionId, sessionDir);
|
|
36354
36693
|
}
|
|
36355
36694
|
}
|
|
36356
36695
|
syncSession(sessionId, sessionDir) {
|
|
36357
36696
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
36358
|
-
const roundsDir =
|
|
36697
|
+
const roundsDir = join17(sessionDir, "rounds");
|
|
36359
36698
|
if (existsSync13(roundsDir)) {
|
|
36360
36699
|
const rounds = readdirSync2(roundsDir, { withFileTypes: true });
|
|
36361
36700
|
for (const roundEntry of rounds) {
|
|
@@ -36363,34 +36702,34 @@ var FilesystemSync = class {
|
|
|
36363
36702
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
36364
36703
|
if (!roundMatch) continue;
|
|
36365
36704
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
36366
|
-
const roundDir =
|
|
36367
|
-
const reviewsDir =
|
|
36705
|
+
const roundDir = join17(roundsDir, roundEntry.name);
|
|
36706
|
+
const reviewsDir = join17(roundDir, "reviews");
|
|
36368
36707
|
if (existsSync13(reviewsDir)) {
|
|
36369
36708
|
const reviewFiles = readdirSync2(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
36370
36709
|
for (const reviewFile of reviewFiles) {
|
|
36371
|
-
const filePath =
|
|
36710
|
+
const filePath = join17(reviewsDir, reviewFile);
|
|
36372
36711
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
36373
36712
|
}
|
|
36374
36713
|
}
|
|
36375
|
-
const roundMetaPath =
|
|
36714
|
+
const roundMetaPath = join17(roundDir, "round-meta.json");
|
|
36376
36715
|
if (existsSync13(roundMetaPath)) {
|
|
36377
36716
|
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
36378
36717
|
}
|
|
36379
|
-
const finalPath =
|
|
36718
|
+
const finalPath = join17(roundDir, "final.md");
|
|
36380
36719
|
if (existsSync13(finalPath)) {
|
|
36381
36720
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
36382
36721
|
}
|
|
36383
|
-
const finalHumanPath =
|
|
36722
|
+
const finalHumanPath = join17(roundDir, "final-human.md");
|
|
36384
36723
|
if (existsSync13(finalHumanPath)) {
|
|
36385
36724
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
36386
36725
|
}
|
|
36387
|
-
const discoursePath =
|
|
36726
|
+
const discoursePath = join17(roundDir, "discourse.md");
|
|
36388
36727
|
if (existsSync13(discoursePath)) {
|
|
36389
36728
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
36390
36729
|
}
|
|
36391
36730
|
}
|
|
36392
36731
|
}
|
|
36393
|
-
const mapDir =
|
|
36732
|
+
const mapDir = join17(sessionDir, "map", "runs");
|
|
36394
36733
|
if (existsSync13(mapDir)) {
|
|
36395
36734
|
const runs = readdirSync2(mapDir, { withFileTypes: true });
|
|
36396
36735
|
for (const runEntry of runs) {
|
|
@@ -36398,12 +36737,12 @@ var FilesystemSync = class {
|
|
|
36398
36737
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
36399
36738
|
if (!runMatch) continue;
|
|
36400
36739
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
36401
|
-
const runDir =
|
|
36402
|
-
const mapMetaPath =
|
|
36740
|
+
const runDir = join17(mapDir, runEntry.name);
|
|
36741
|
+
const mapMetaPath = join17(runDir, "map-meta.json");
|
|
36403
36742
|
if (existsSync13(mapMetaPath)) {
|
|
36404
36743
|
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
36405
36744
|
}
|
|
36406
|
-
const mapPath =
|
|
36745
|
+
const mapPath = join17(runDir, "map.md");
|
|
36407
36746
|
if (existsSync13(mapPath)) {
|
|
36408
36747
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
36409
36748
|
}
|
|
@@ -36413,7 +36752,7 @@ var FilesystemSync = class {
|
|
|
36413
36752
|
["requirements-mapping.md", "requirements-mapping"]
|
|
36414
36753
|
];
|
|
36415
36754
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
36416
|
-
const filePath =
|
|
36755
|
+
const filePath = join17(runDir, fileName);
|
|
36417
36756
|
if (existsSync13(filePath)) {
|
|
36418
36757
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
36419
36758
|
}
|
|
@@ -36425,68 +36764,113 @@ var FilesystemSync = class {
|
|
|
36425
36764
|
["discovered-standards.md", "discovered-standards"]
|
|
36426
36765
|
];
|
|
36427
36766
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
36428
|
-
const filePath =
|
|
36767
|
+
const filePath = join17(sessionDir, fileName);
|
|
36429
36768
|
if (existsSync13(filePath)) {
|
|
36430
36769
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
36431
36770
|
}
|
|
36432
36771
|
}
|
|
36433
36772
|
}
|
|
36773
|
+
// ── Terminal-completion evidence (defect D1) ──
|
|
36774
|
+
//
|
|
36775
|
+
// The dashboard read/sync path NEVER originates terminal workflow completion.
|
|
36776
|
+
// A `final.md` / `map.md` artifact on disk is evidence of the **synthesis**
|
|
36777
|
+
// phase only; terminal completion is the CLI's to declare and is recognized
|
|
36778
|
+
// solely from the CLI-produced evidence — a `round_completed` / `map_completed`
|
|
36779
|
+
// orchestration event. Closing on artifact presence alone is the fabrication
|
|
36780
|
+
// these helpers exist to prevent.
|
|
36781
|
+
/** Whether the CLI has recorded a `round_completed` event for this round. */
|
|
36782
|
+
hasRoundCompletedEvent(sessionId, round) {
|
|
36783
|
+
return queryFirst(
|
|
36784
|
+
this.db,
|
|
36785
|
+
`SELECT 1 FROM orchestration_events
|
|
36786
|
+
WHERE session_id = ? AND event_type = 'round_completed' AND round = ? LIMIT 1`,
|
|
36787
|
+
[sessionId, round]
|
|
36788
|
+
) != null;
|
|
36789
|
+
}
|
|
36790
|
+
/** Whether the CLI has recorded a `map_completed` event for this map run. */
|
|
36791
|
+
hasMapCompletedEvent(sessionId, mapRun) {
|
|
36792
|
+
return queryFirst(
|
|
36793
|
+
this.db,
|
|
36794
|
+
`SELECT 1 FROM orchestration_events
|
|
36795
|
+
WHERE session_id = ? AND event_type = 'map_completed' AND round = ? LIMIT 1`,
|
|
36796
|
+
[sessionId, mapRun]
|
|
36797
|
+
) != null;
|
|
36798
|
+
}
|
|
36799
|
+
/**
|
|
36800
|
+
* Full CLI terminal evidence for a review round: a `round_completed` event AND
|
|
36801
|
+
* a validated `round-meta.json` on disk. Used by the backfill reconciler to
|
|
36802
|
+
* decide whether a discovered-on-disk session is genuinely complete.
|
|
36803
|
+
*/
|
|
36804
|
+
hasTerminalRoundEvidence(sessionId, round, roundDir) {
|
|
36805
|
+
return existsSync13(join17(roundDir, "round-meta.json")) && this.hasRoundCompletedEvent(sessionId, round);
|
|
36806
|
+
}
|
|
36807
|
+
/** Full CLI terminal evidence for a map run: a `map_completed` event AND a
|
|
36808
|
+
* validated `map-meta.json` on disk. */
|
|
36809
|
+
hasTerminalMapEvidence(sessionId, mapRun, runDir) {
|
|
36810
|
+
return existsSync13(join17(runDir, "map-meta.json")) && this.hasMapCompletedEvent(sessionId, mapRun);
|
|
36811
|
+
}
|
|
36434
36812
|
// ── Session Backfill ──
|
|
36435
36813
|
ensureSessionRow(sessionId, sessionDir) {
|
|
36436
36814
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
36437
36815
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
36438
|
-
const hasRoundsDir = existsSync13(
|
|
36439
|
-
const hasMapDir = existsSync13(
|
|
36816
|
+
const hasRoundsDir = existsSync13(join17(sessionDir, "rounds"));
|
|
36817
|
+
const hasMapDir = existsSync13(join17(sessionDir, "map"));
|
|
36440
36818
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
36441
36819
|
let currentRound = 1;
|
|
36442
36820
|
if (hasRoundsDir) {
|
|
36443
|
-
const roundDirs = readdirSync2(
|
|
36821
|
+
const roundDirs = readdirSync2(join17(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
36444
36822
|
currentRound = Math.max(1, roundDirs.length);
|
|
36445
36823
|
}
|
|
36446
36824
|
let currentMapRun = 1;
|
|
36447
|
-
const mapRunsDir =
|
|
36825
|
+
const mapRunsDir = join17(sessionDir, "map", "runs");
|
|
36448
36826
|
if (existsSync13(mapRunsDir)) {
|
|
36449
36827
|
const runDirs = readdirSync2(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
36450
36828
|
currentMapRun = Math.max(1, runDirs.length);
|
|
36451
36829
|
}
|
|
36452
36830
|
let phase = "context";
|
|
36453
36831
|
let phaseNumber = 1;
|
|
36454
|
-
let status = "
|
|
36832
|
+
let status = "active";
|
|
36455
36833
|
if (workflowType === "review" && hasRoundsDir) {
|
|
36456
|
-
const roundDir =
|
|
36457
|
-
if (existsSync13(
|
|
36834
|
+
const roundDir = join17(sessionDir, "rounds", `round-${currentRound}`);
|
|
36835
|
+
if (existsSync13(join17(roundDir, "final.md")) && this.hasTerminalRoundEvidence(sessionId, currentRound, roundDir)) {
|
|
36458
36836
|
phase = "complete";
|
|
36459
36837
|
phaseNumber = 8;
|
|
36460
36838
|
status = "closed";
|
|
36461
|
-
} else if (existsSync13(
|
|
36839
|
+
} else if (existsSync13(join17(roundDir, "final.md"))) {
|
|
36462
36840
|
phase = "synthesis";
|
|
36463
36841
|
phaseNumber = 7;
|
|
36464
|
-
} else if (existsSync13(
|
|
36842
|
+
} else if (existsSync13(join17(roundDir, "discourse.md"))) {
|
|
36843
|
+
phase = "synthesis";
|
|
36844
|
+
phaseNumber = 7;
|
|
36845
|
+
} else if (existsSync13(join17(roundDir, "reviews")) && readdirSync2(join17(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
36465
36846
|
phase = "reviews";
|
|
36466
36847
|
phaseNumber = 4;
|
|
36467
|
-
} else if (existsSync13(
|
|
36848
|
+
} else if (existsSync13(join17(sessionDir, "context.md"))) {
|
|
36468
36849
|
phase = "analysis";
|
|
36469
36850
|
phaseNumber = 3;
|
|
36470
|
-
} else if (existsSync13(
|
|
36851
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36471
36852
|
phase = "change-context";
|
|
36472
36853
|
phaseNumber = 2;
|
|
36473
36854
|
}
|
|
36474
36855
|
} else if (workflowType === "map" && hasMapDir) {
|
|
36475
|
-
const runDir =
|
|
36476
|
-
if (existsSync13(
|
|
36856
|
+
const runDir = join17(mapRunsDir, `run-${currentMapRun}`);
|
|
36857
|
+
if (existsSync13(join17(runDir, "map.md")) && this.hasTerminalMapEvidence(sessionId, currentMapRun, runDir)) {
|
|
36477
36858
|
phase = "complete";
|
|
36478
36859
|
phaseNumber = 6;
|
|
36479
36860
|
status = "closed";
|
|
36480
|
-
} else if (existsSync13(
|
|
36861
|
+
} else if (existsSync13(join17(runDir, "map.md"))) {
|
|
36481
36862
|
phase = "synthesis";
|
|
36482
36863
|
phaseNumber = 5;
|
|
36483
|
-
} else if (existsSync13(
|
|
36864
|
+
} else if (existsSync13(join17(runDir, "requirements-mapping.md"))) {
|
|
36865
|
+
phase = "synthesis";
|
|
36866
|
+
phaseNumber = 5;
|
|
36867
|
+
} else if (existsSync13(join17(runDir, "flow-analysis.md"))) {
|
|
36484
36868
|
phase = "requirements-mapping";
|
|
36485
36869
|
phaseNumber = 4;
|
|
36486
|
-
} else if (existsSync13(
|
|
36870
|
+
} else if (existsSync13(join17(runDir, "topology.md"))) {
|
|
36487
36871
|
phase = "flow-analysis";
|
|
36488
36872
|
phaseNumber = 3;
|
|
36489
|
-
} else if (existsSync13(
|
|
36873
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36490
36874
|
phase = "topology";
|
|
36491
36875
|
phaseNumber = 2;
|
|
36492
36876
|
}
|
|
@@ -36541,7 +36925,7 @@ var FilesystemSync = class {
|
|
|
36541
36925
|
try {
|
|
36542
36926
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
36543
36927
|
if (entry.isDirectory()) {
|
|
36544
|
-
if (this.hasArtifacts(
|
|
36928
|
+
if (this.hasArtifacts(join17(dir, entry.name))) return true;
|
|
36545
36929
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
36546
36930
|
return true;
|
|
36547
36931
|
}
|
|
@@ -36687,7 +37071,7 @@ var FilesystemSync = class {
|
|
|
36687
37071
|
"SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
|
|
36688
37072
|
[sessionId]
|
|
36689
37073
|
);
|
|
36690
|
-
if (session && session["workflow_type"] === "map" && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
37074
|
+
if (session && session["workflow_type"] === "map" && this.hasMapCompletedEvent(sessionId, runNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
36691
37075
|
commitReasonClose(
|
|
36692
37076
|
this.db,
|
|
36693
37077
|
sessionId,
|
|
@@ -36845,13 +37229,8 @@ var FilesystemSync = class {
|
|
|
36845
37229
|
console.error(`[FilesystemSync] Invalid round-meta.json at ${filePath}`);
|
|
36846
37230
|
return;
|
|
36847
37231
|
}
|
|
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;
|
|
37232
|
+
const normalizedVerdict = normalizeVerdict(meta.verdict) ?? meta.verdict;
|
|
37233
|
+
const { blockerCount, shouldFixCount, suggestionCount, reviewerCount, totalFindingCount } = resolveRoundCounts(meta);
|
|
36855
37234
|
this.db.run("BEGIN TRANSACTION");
|
|
36856
37235
|
try {
|
|
36857
37236
|
this.db.run(
|
|
@@ -36860,7 +37239,7 @@ var FilesystemSync = class {
|
|
|
36860
37239
|
reviewer_count = ?, total_finding_count = ?, source = 'orchestrator', parsed_at = ?
|
|
36861
37240
|
WHERE session_id = ? AND round_number = ?`,
|
|
36862
37241
|
[
|
|
36863
|
-
|
|
37242
|
+
normalizedVerdict,
|
|
36864
37243
|
blockerCount,
|
|
36865
37244
|
suggestionCount,
|
|
36866
37245
|
shouldFixCount,
|
|
@@ -36881,12 +37260,12 @@ var FilesystemSync = class {
|
|
|
36881
37260
|
this.db.run("COMMIT");
|
|
36882
37261
|
return;
|
|
36883
37262
|
}
|
|
36884
|
-
const roundDir =
|
|
37263
|
+
const roundDir = dirname11(filePath);
|
|
36885
37264
|
for (const reviewer of meta.reviewers) {
|
|
36886
37265
|
const reviewerType = reviewer.type ?? "unknown";
|
|
36887
37266
|
const instanceNumber = reviewer.instance ?? 1;
|
|
36888
37267
|
const findings = reviewer.findings ?? [];
|
|
36889
|
-
const reviewerMdPath =
|
|
37268
|
+
const reviewerMdPath = join17(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
36890
37269
|
this.db.run(
|
|
36891
37270
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
36892
37271
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -36960,7 +37339,7 @@ var FilesystemSync = class {
|
|
|
36960
37339
|
this.io?.to(`session:${sessionId}`).emit("round:updated", {
|
|
36961
37340
|
sessionId,
|
|
36962
37341
|
roundNumber,
|
|
36963
|
-
verdict:
|
|
37342
|
+
verdict: normalizedVerdict,
|
|
36964
37343
|
blockerCount,
|
|
36965
37344
|
shouldFixCount,
|
|
36966
37345
|
suggestionCount,
|
|
@@ -37121,11 +37500,12 @@ var FilesystemSync = class {
|
|
|
37121
37500
|
);
|
|
37122
37501
|
} else {
|
|
37123
37502
|
const parsed = parseFinalMd(content);
|
|
37503
|
+
const parsedVerdict = parsed.verdict ? normalizeVerdict(parsed.verdict) ?? parsed.verdict : parsed.verdict;
|
|
37124
37504
|
this.db.run(
|
|
37125
37505
|
`UPDATE review_rounds SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?, final_md_path = ?, parsed_at = ?, source = 'parser'
|
|
37126
37506
|
WHERE session_id = ? AND round_number = ?`,
|
|
37127
37507
|
[
|
|
37128
|
-
|
|
37508
|
+
parsedVerdict,
|
|
37129
37509
|
parsed.blockerCount,
|
|
37130
37510
|
parsed.suggestionCount,
|
|
37131
37511
|
parsed.shouldFixCount,
|
|
@@ -37155,7 +37535,7 @@ var FilesystemSync = class {
|
|
|
37155
37535
|
"SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
|
|
37156
37536
|
[sessionId]
|
|
37157
37537
|
);
|
|
37158
|
-
if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37538
|
+
if (session && this.hasRoundCompletedEvent(sessionId, roundNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37159
37539
|
commitReasonClose(
|
|
37160
37540
|
this.db,
|
|
37161
37541
|
sessionId,
|
|
@@ -37249,7 +37629,7 @@ var FilesystemSync = class {
|
|
|
37249
37629
|
const parts = relFromSessions.split("/");
|
|
37250
37630
|
const sessionId = parts[0];
|
|
37251
37631
|
if (!sessionId) return;
|
|
37252
|
-
const sessionDir =
|
|
37632
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
37253
37633
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
37254
37634
|
const fileName = basename3(filePath);
|
|
37255
37635
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
@@ -37322,7 +37702,7 @@ var FilesystemSync = class {
|
|
|
37322
37702
|
|
|
37323
37703
|
// src/server/services/db-sync-watcher.ts
|
|
37324
37704
|
import { existsSync as existsSync14 } from "node:fs";
|
|
37325
|
-
import { dirname as
|
|
37705
|
+
import { dirname as dirname12, basename as basename4 } from "node:path";
|
|
37326
37706
|
import { watch as watch3 } from "chokidar";
|
|
37327
37707
|
function col(row, key) {
|
|
37328
37708
|
return row[key] ?? null;
|
|
@@ -37364,7 +37744,7 @@ var DbSyncWatcher = class {
|
|
|
37364
37744
|
/** Start watching the DB file (and its WAL sidecar) for external writes. */
|
|
37365
37745
|
startWatching() {
|
|
37366
37746
|
if (!existsSync14(this.dbFilePath)) return;
|
|
37367
|
-
const watchDir =
|
|
37747
|
+
const watchDir = dirname12(this.dbFilePath);
|
|
37368
37748
|
const dbFile = basename4(this.dbFilePath);
|
|
37369
37749
|
const walFile = `${dbFile}-wal`;
|
|
37370
37750
|
this.watcher = watch3(watchDir, {
|
|
@@ -37626,20 +38006,20 @@ function commandFingerprint(row) {
|
|
|
37626
38006
|
}
|
|
37627
38007
|
|
|
37628
38008
|
// src/server/socket/chat-handler.ts
|
|
37629
|
-
import { dirname as
|
|
38009
|
+
import { dirname as dirname13 } from "node:path";
|
|
37630
38010
|
|
|
37631
38011
|
// src/server/services/chat-context.ts
|
|
37632
38012
|
import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync15 } from "node:fs";
|
|
37633
|
-
import { join as
|
|
38013
|
+
import { join as join18 } from "node:path";
|
|
37634
38014
|
function buildChatContext(ocrDir, target) {
|
|
37635
|
-
const sessionsDir =
|
|
38015
|
+
const sessionsDir = join18(ocrDir, "sessions");
|
|
37636
38016
|
if (target.type === "map_run") {
|
|
37637
38017
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
37638
38018
|
}
|
|
37639
38019
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
37640
38020
|
}
|
|
37641
38021
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
37642
|
-
const mapPath =
|
|
38022
|
+
const mapPath = join18(
|
|
37643
38023
|
sessionsDir,
|
|
37644
38024
|
sessionId,
|
|
37645
38025
|
"map",
|
|
@@ -37666,9 +38046,9 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
37666
38046
|
return parts.join("\n");
|
|
37667
38047
|
}
|
|
37668
38048
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
37669
|
-
const roundDir =
|
|
37670
|
-
const finalPath =
|
|
37671
|
-
const reviewersDir =
|
|
38049
|
+
const roundDir = join18(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
38050
|
+
const finalPath = join18(roundDir, "final.md");
|
|
38051
|
+
const reviewersDir = join18(roundDir, "reviews");
|
|
37672
38052
|
const parts = [
|
|
37673
38053
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
37674
38054
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
@@ -37685,7 +38065,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
37685
38065
|
if (existsSync15(reviewersDir)) {
|
|
37686
38066
|
const files = readdirSync3(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
37687
38067
|
for (const file of files) {
|
|
37688
|
-
const content = readFileSync10(
|
|
38068
|
+
const content = readFileSync10(join18(reviewersDir, file), "utf-8");
|
|
37689
38069
|
const reviewerName = file.replace(/\.md$/, "");
|
|
37690
38070
|
parts.push("");
|
|
37691
38071
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -37749,7 +38129,7 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
37749
38129
|
db.run(
|
|
37750
38130
|
`UPDATE command_executions
|
|
37751
38131
|
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
37752
|
-
WHERE id =
|
|
38132
|
+
WHERE id = ? AND finished_at IS NULL`,
|
|
37753
38133
|
[exitCode, finishedAt, outputBuffer, executionId]
|
|
37754
38134
|
);
|
|
37755
38135
|
appendCommandLog(ocrDir, {
|
|
@@ -37839,7 +38219,7 @@ User: ${message}`;
|
|
|
37839
38219
|
});
|
|
37840
38220
|
return;
|
|
37841
38221
|
}
|
|
37842
|
-
const repoRoot =
|
|
38222
|
+
const repoRoot = dirname13(ocrDir);
|
|
37843
38223
|
const spawnResult = adapter.spawn({
|
|
37844
38224
|
prompt,
|
|
37845
38225
|
cwd: repoRoot,
|
|
@@ -38015,13 +38395,13 @@ function cleanupAllChats() {
|
|
|
38015
38395
|
}
|
|
38016
38396
|
|
|
38017
38397
|
// src/server/socket/post-handler.ts
|
|
38018
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
38398
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
38019
38399
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
38020
|
-
import { join as
|
|
38400
|
+
import { join as join19, dirname as dirname14, isAbsolute as isAbsolute2 } from "node:path";
|
|
38021
38401
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
38022
38402
|
function resolveSessionDir2(sessionDir, ocrDir) {
|
|
38023
38403
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
38024
|
-
return
|
|
38404
|
+
return join19(dirname14(ocrDir), sessionDir);
|
|
38025
38405
|
}
|
|
38026
38406
|
var BRANCH_PREFIXES = [
|
|
38027
38407
|
"feat",
|
|
@@ -38090,7 +38470,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38090
38470
|
return;
|
|
38091
38471
|
}
|
|
38092
38472
|
const branch = session.branch;
|
|
38093
|
-
const repoRoot =
|
|
38473
|
+
const repoRoot = dirname14(ocrDir);
|
|
38094
38474
|
try {
|
|
38095
38475
|
await execBinaryAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot, encoding: "utf-8" });
|
|
38096
38476
|
} catch {
|
|
@@ -38191,16 +38571,16 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38191
38571
|
socket.emit("post:error", { error: "Session not found" });
|
|
38192
38572
|
return;
|
|
38193
38573
|
}
|
|
38194
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38195
|
-
const roundDir =
|
|
38196
|
-
const finalPath =
|
|
38574
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38575
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38576
|
+
const finalPath = join19(roundDir, "final.md");
|
|
38197
38577
|
if (!existsSync16(finalPath)) {
|
|
38198
38578
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
38199
38579
|
return;
|
|
38200
38580
|
}
|
|
38201
|
-
const humanReviewPath =
|
|
38202
|
-
const repoRoot =
|
|
38203
|
-
const commandMdPath =
|
|
38581
|
+
const humanReviewPath = join19(roundDir, "final-human.md");
|
|
38582
|
+
const repoRoot = dirname14(ocrDir);
|
|
38583
|
+
const commandMdPath = join19(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
38204
38584
|
let commandContent;
|
|
38205
38585
|
try {
|
|
38206
38586
|
commandContent = readFileSync11(commandMdPath, "utf-8");
|
|
@@ -38360,10 +38740,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38360
38740
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
38361
38741
|
return;
|
|
38362
38742
|
}
|
|
38363
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38364
|
-
const roundDir =
|
|
38365
|
-
|
|
38366
|
-
const filePath =
|
|
38743
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38744
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38745
|
+
mkdirSync7(roundDir, { recursive: true });
|
|
38746
|
+
const filePath = join19(roundDir, "final-human.md");
|
|
38367
38747
|
writeFileSync5(filePath, content, { mode: 420 });
|
|
38368
38748
|
socket.emit("post:save-result", { success: true });
|
|
38369
38749
|
} catch (err) {
|
|
@@ -38390,14 +38770,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38390
38770
|
);
|
|
38391
38771
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
38392
38772
|
`);
|
|
38393
|
-
const tmpDir =
|
|
38773
|
+
const tmpDir = join19(tmpdir2(), "ocr-post-comments");
|
|
38394
38774
|
try {
|
|
38395
|
-
|
|
38775
|
+
mkdirSync7(tmpDir, { recursive: true, mode: 448 });
|
|
38396
38776
|
} catch {
|
|
38397
38777
|
}
|
|
38398
|
-
const tmpFile =
|
|
38778
|
+
const tmpFile = join19(tmpDir, `${randomUUID2()}.md`);
|
|
38399
38779
|
writeFileSync5(tmpFile, content, { mode: 384 });
|
|
38400
|
-
const repoRoot =
|
|
38780
|
+
const repoRoot = dirname14(ocrDir);
|
|
38401
38781
|
try {
|
|
38402
38782
|
const { stdout } = await execBinaryAsync(
|
|
38403
38783
|
"gh",
|
|
@@ -38440,9 +38820,67 @@ function cleanupAllPostGenerations() {
|
|
|
38440
38820
|
}
|
|
38441
38821
|
}
|
|
38442
38822
|
|
|
38823
|
+
// src/server/services/forward-resume-sweep.ts
|
|
38824
|
+
function hasPositiveDeathEvidence(db, sessionId, isAlive) {
|
|
38825
|
+
const instances = listAgentSessionsForWorkflow(db, sessionId);
|
|
38826
|
+
if (instances.length === 0) return false;
|
|
38827
|
+
return instances.every(
|
|
38828
|
+
(s) => s.ended_at != null || s.pid != null && !isAlive(s.pid)
|
|
38829
|
+
);
|
|
38830
|
+
}
|
|
38831
|
+
function planForwardResume(db, cfg) {
|
|
38832
|
+
const isAlive = cfg.isAlive ?? defaultIsAlive;
|
|
38833
|
+
const plan = [];
|
|
38834
|
+
for (const session of getAllSessions(db)) {
|
|
38835
|
+
if (session.status !== "active") continue;
|
|
38836
|
+
const events = getEventsForSession(db, session.id);
|
|
38837
|
+
const workflowType = session.workflow_type === "map" ? "map" : "review";
|
|
38838
|
+
if (hasTerminalArtifactEvent2(events, workflowType, session.current_round)) {
|
|
38839
|
+
continue;
|
|
38840
|
+
}
|
|
38841
|
+
if (!hasPositiveDeathEvidence(db, session.id, isAlive)) continue;
|
|
38842
|
+
const stranded = strandedActionByCap(db, session, cfg.maxAttempts);
|
|
38843
|
+
if (stranded.action === "abort_or_fresh") {
|
|
38844
|
+
plan.push({ sessionId: session.id, action: "cap_close" });
|
|
38845
|
+
continue;
|
|
38846
|
+
}
|
|
38847
|
+
const latest = getLatestAgentSessionWithVendorId(db, session.id);
|
|
38848
|
+
plan.push({
|
|
38849
|
+
sessionId: session.id,
|
|
38850
|
+
action: latest?.vendor_session_id ? "resume" : "handoff"
|
|
38851
|
+
});
|
|
38852
|
+
}
|
|
38853
|
+
return plan;
|
|
38854
|
+
}
|
|
38855
|
+
function runForwardResumeSweep(deps) {
|
|
38856
|
+
const plan = planForwardResume(deps.db, deps.config);
|
|
38857
|
+
for (const item of plan) {
|
|
38858
|
+
try {
|
|
38859
|
+
if (item.action === "cap_close") {
|
|
38860
|
+
closeForwardResumeExhausted(deps.db, item.sessionId, deps.maxAttempts);
|
|
38861
|
+
deps.log?.(
|
|
38862
|
+
`[ForwardResume] ${item.sessionId}: attempts exhausted \u2192 closed non-success`
|
|
38863
|
+
);
|
|
38864
|
+
} else if (item.action === "resume") {
|
|
38865
|
+
deps.spawnResume(item.sessionId);
|
|
38866
|
+
deps.log?.(`[ForwardResume] ${item.sessionId}: auto-resuming (ocr review --resume)`);
|
|
38867
|
+
} else {
|
|
38868
|
+
deps.log?.(
|
|
38869
|
+
`[ForwardResume] ${item.sessionId}: stranded, no resume adapter \u2014 pick up in terminal`
|
|
38870
|
+
);
|
|
38871
|
+
}
|
|
38872
|
+
} catch (err) {
|
|
38873
|
+
deps.log?.(
|
|
38874
|
+
`[ForwardResume] ${item.sessionId}: ${err instanceof Error ? err.message : String(err)}`
|
|
38875
|
+
);
|
|
38876
|
+
}
|
|
38877
|
+
}
|
|
38878
|
+
return plan;
|
|
38879
|
+
}
|
|
38880
|
+
|
|
38443
38881
|
// src/server/index.ts
|
|
38444
38882
|
import { homedir } from "node:os";
|
|
38445
|
-
var __dirname3 =
|
|
38883
|
+
var __dirname3 = dirname15(fileURLToPath3(import.meta.url));
|
|
38446
38884
|
function shortenPath(p) {
|
|
38447
38885
|
const home = homedir();
|
|
38448
38886
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
@@ -38509,7 +38947,7 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
38509
38947
|
function isOcrDashboardProcess(pid) {
|
|
38510
38948
|
if (process.platform === "win32") return false;
|
|
38511
38949
|
try {
|
|
38512
|
-
const cmd =
|
|
38950
|
+
const cmd = execBinary("ps", ["-p", String(pid), "-o", "command="], {
|
|
38513
38951
|
encoding: "utf-8",
|
|
38514
38952
|
timeout: 3e3
|
|
38515
38953
|
}).trim();
|
|
@@ -38524,23 +38962,23 @@ async function startServer(options = {}) {
|
|
|
38524
38962
|
process.title = "ocr-dashboard";
|
|
38525
38963
|
const ocrDir = resolveOcrDir();
|
|
38526
38964
|
const aiCliService = new AiCliService(ocrDir);
|
|
38527
|
-
const dbPathForCheckpoint =
|
|
38965
|
+
const dbPathForCheckpoint = join20(ocrDir, "data", "ocr.db");
|
|
38528
38966
|
const walResult = walCheckpointTruncate(dbPathForCheckpoint);
|
|
38529
38967
|
if (walResult === "checkpointed") {
|
|
38530
38968
|
console.log(" WAL checkpoint: truncated stale write-ahead-log file");
|
|
38531
38969
|
}
|
|
38532
|
-
for (const reaped of reapOrphanDbFiles(
|
|
38970
|
+
for (const reaped of reapOrphanDbFiles(join20(ocrDir, "data"))) {
|
|
38533
38971
|
console.log(` Orphan reap: removed stale ${reaped}`);
|
|
38534
38972
|
}
|
|
38535
|
-
const staleLogs = reapStaleExecLogs(
|
|
38973
|
+
const staleLogs = reapStaleExecLogs(join20(ocrDir, "data", "exec-logs"));
|
|
38536
38974
|
if (staleLogs.length > 0) {
|
|
38537
38975
|
console.log(` Exec-log reap: removed ${staleLogs.length} stale agent log(s)`);
|
|
38538
38976
|
}
|
|
38539
38977
|
const db = await openDb(ocrDir);
|
|
38540
|
-
const dataDir =
|
|
38541
|
-
const pidFilePath =
|
|
38542
|
-
const portFilePath =
|
|
38543
|
-
|
|
38978
|
+
const dataDir = join20(ocrDir, "data");
|
|
38979
|
+
const pidFilePath = join20(dataDir, "dashboard.pid");
|
|
38980
|
+
const portFilePath = join20(dataDir, "server-port");
|
|
38981
|
+
mkdirSync8(dataDir, { recursive: true });
|
|
38544
38982
|
try {
|
|
38545
38983
|
unlinkSync5(portFilePath);
|
|
38546
38984
|
} catch {
|
|
@@ -38638,12 +39076,39 @@ async function startServer(options = {}) {
|
|
|
38638
39076
|
}
|
|
38639
39077
|
};
|
|
38640
39078
|
await reconcileCompleted();
|
|
39079
|
+
const forwardResumeMaxAttempts = getForwardResumeMaxAttempts(ocrDir);
|
|
39080
|
+
const spawnResume = (sessionId) => {
|
|
39081
|
+
const child = spawnBinary("ocr", ["review", "--resume", sessionId], {
|
|
39082
|
+
cwd: ocrDir.replace(/\.ocr$/, "") || process.cwd(),
|
|
39083
|
+
stdio: "ignore",
|
|
39084
|
+
detached: true
|
|
39085
|
+
});
|
|
39086
|
+
child.on("error", (err) => {
|
|
39087
|
+
console.error(`[ForwardResume] spawn failed for ${sessionId}:`, err.message);
|
|
39088
|
+
});
|
|
39089
|
+
child.unref();
|
|
39090
|
+
};
|
|
39091
|
+
const runForwardResume = () => {
|
|
39092
|
+
try {
|
|
39093
|
+
runForwardResumeSweep({
|
|
39094
|
+
db,
|
|
39095
|
+
config: { maxAttempts: forwardResumeMaxAttempts, heartbeatMs: heartbeatSeconds * 1e3 },
|
|
39096
|
+
maxAttempts: forwardResumeMaxAttempts,
|
|
39097
|
+
spawnResume,
|
|
39098
|
+
log: (m) => console.log(` ${m}`)
|
|
39099
|
+
});
|
|
39100
|
+
} catch (err) {
|
|
39101
|
+
console.error("[ForwardResume] sweep failed:", err);
|
|
39102
|
+
}
|
|
39103
|
+
};
|
|
39104
|
+
runForwardResume();
|
|
38641
39105
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
38642
39106
|
const sweepTimer = setInterval(() => {
|
|
38643
39107
|
try {
|
|
38644
39108
|
logAgentSweep(sweepStaleAgentSessions(db, heartbeatSeconds, defaultIsAlive));
|
|
38645
39109
|
sweepStaleSessions(db, STALE_SESSION_THRESHOLD_SECONDS);
|
|
38646
39110
|
void reconcileCompleted();
|
|
39111
|
+
runForwardResume();
|
|
38647
39112
|
} catch (err) {
|
|
38648
39113
|
console.error("[sweep] periodic sweep failed:", err);
|
|
38649
39114
|
}
|
|
@@ -38679,10 +39144,10 @@ async function startServer(options = {}) {
|
|
|
38679
39144
|
app.use("/api/agent-sessions", createAgentSessionsRouter(db, () => pullSync()));
|
|
38680
39145
|
app.use("/api/sessions", createHandoffRouter(sessionCapture, ocrDir, () => pullSync()));
|
|
38681
39146
|
app.use("/api/team", createTeamRouter(ocrDir));
|
|
38682
|
-
const clientDir =
|
|
39147
|
+
const clientDir = join20(__dirname3, "client");
|
|
38683
39148
|
if (process.env.NODE_ENV === "production" && existsSync17(clientDir)) {
|
|
38684
39149
|
app.use(import_express15.default.static(clientDir, { index: false }));
|
|
38685
|
-
const indexHtmlPath =
|
|
39150
|
+
const indexHtmlPath = join20(clientDir, "index.html");
|
|
38686
39151
|
const rawIndexHtml = existsSync17(indexHtmlPath) ? readFileSync12(indexHtmlPath, "utf-8") : "";
|
|
38687
39152
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
38688
39153
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
@@ -38702,7 +39167,7 @@ async function startServer(options = {}) {
|
|
|
38702
39167
|
registerChatHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38703
39168
|
registerPostHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38704
39169
|
});
|
|
38705
|
-
const dbFilePath =
|
|
39170
|
+
const dbFilePath = join20(ocrDir, "data", "ocr.db");
|
|
38706
39171
|
const dbSyncWatcher = new DbSyncWatcher(
|
|
38707
39172
|
db,
|
|
38708
39173
|
dbFilePath,
|
|
@@ -38718,7 +39183,7 @@ async function startServer(options = {}) {
|
|
|
38718
39183
|
dbSyncWatcher.startWatching();
|
|
38719
39184
|
pullSync = () => dbSyncWatcher.syncFromDisk();
|
|
38720
39185
|
console.log(` Watching DB: ${shortenPath(dbFilePath)}`);
|
|
38721
|
-
const sessionsDir =
|
|
39186
|
+
const sessionsDir = join20(ocrDir, "sessions");
|
|
38722
39187
|
const fsSync = new FilesystemSync(db, sessionsDir, io);
|
|
38723
39188
|
await fsSync.fullScan();
|
|
38724
39189
|
fsSync.startWatching();
|
|
@@ -38782,7 +39247,7 @@ async function startServer(options = {}) {
|
|
|
38782
39247
|
unlinkSync5(portFilePath);
|
|
38783
39248
|
} catch {
|
|
38784
39249
|
}
|
|
38785
|
-
|
|
39250
|
+
clearAllSpawnMarkers(ocrDir);
|
|
38786
39251
|
try {
|
|
38787
39252
|
const activeResult = db.exec(
|
|
38788
39253
|
"SELECT id, pid FROM command_executions WHERE pid IS NOT NULL AND finished_at IS NULL"
|