@open-code-review/cli 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/dashboard/client/assets/{_basePickBy-BBPb8BJA.js → _basePickBy-CyrHyeyN.js} +1 -1
- package/dist/dashboard/client/assets/{_baseUniq-CFHdos6T.js → _baseUniq-Bg7NJSGS.js} +1 -1
- package/dist/dashboard/client/assets/{arc-BKGGWA2F.js → arc-zDGAKMur.js} +1 -1
- package/dist/dashboard/client/assets/{architectureDiagram-VXUJARFQ-B_ovNjX1.js → architectureDiagram-VXUJARFQ-BxlGxm0Q.js} +1 -1
- package/dist/dashboard/client/assets/{blockDiagram-VD42YOAC-C2M-avVp.js → blockDiagram-VD42YOAC-BskTNyX5.js} +1 -1
- package/dist/dashboard/client/assets/{c4Diagram-YG6GDRKO-BtOBpAzH.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-Cz2EbHPl.js → chunk-4BX2VUAB-xq9xoCTv.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-55IACEB6-C8xpXw9G.js → chunk-55IACEB6-DYdXYVh5.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-B4BG7PRW-BSRfOovX.js → chunk-B4BG7PRW-BGAyFRFS.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-DI55MBZ5-CEUbYQWn.js → chunk-DI55MBZ5-C5ul9stk.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-FMBD7UC4-5xWP6GRj.js → chunk-FMBD7UC4-BSaPo2xa.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QN33PNHL-DfNCVcy8.js → chunk-QN33PNHL-CyzabUv0.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-QZHKN3VN--OdToKKu.js → chunk-QZHKN3VN-CceRbxt_.js} +1 -1
- package/dist/dashboard/client/assets/{chunk-TZMSLE5B-B_0K0Qso.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-Cc_Dmnxz.js → cose-bilkent-S5V4N54A-DEdXBrCt.js} +1 -1
- package/dist/dashboard/client/assets/{dagre-6UL2VRFP-DaAfvUXU.js → dagre-6UL2VRFP-DRdIiP58.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-PSM6KHXK-7idwN0rC.js → diagram-PSM6KHXK-Bo7Q2VlK.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-QEK2KX5R-D9j9H13n.js → diagram-QEK2KX5R-2Fmc2o5x.js} +1 -1
- package/dist/dashboard/client/assets/{diagram-S2PKOQOG-SMF5SB0K.js → diagram-S2PKOQOG-5WE8f0p7.js} +1 -1
- package/dist/dashboard/client/assets/{erDiagram-Q2GNP2WA-EVJ4Qa2F.js → erDiagram-Q2GNP2WA-DD-iXWd_.js} +1 -1
- package/dist/dashboard/client/assets/{flowDiagram-NV44I4VS-tZ7SFE77.js → flowDiagram-NV44I4VS-CCWo8Ue9.js} +1 -1
- package/dist/dashboard/client/assets/{ganttDiagram-JELNMOA3-DFSqguY7.js → ganttDiagram-JELNMOA3-CNY4d5UK.js} +1 -1
- package/dist/dashboard/client/assets/{gitGraphDiagram-V2S2FVAM-CqHdP3HE.js → gitGraphDiagram-V2S2FVAM-Dq5SBEJJ.js} +1 -1
- package/dist/dashboard/client/assets/{graph-C0XnkNkk.js → graph-BTt9lokK.js} +1 -1
- package/dist/dashboard/client/assets/index-B0k81q2b.js +581 -0
- package/dist/dashboard/client/assets/index-Czwdh6UA.css +1 -0
- package/dist/dashboard/client/assets/{infoDiagram-HS3SLOUP-DlXZo9U2.js → infoDiagram-HS3SLOUP-AnKZja-G.js} +1 -1
- package/dist/dashboard/client/assets/{journeyDiagram-XKPGCS4Q-CgC8_7eN.js → journeyDiagram-XKPGCS4Q-nC-_WjPN.js} +1 -1
- package/dist/dashboard/client/assets/{kanban-definition-3W4ZIXB7-BMAw_jNp.js → kanban-definition-3W4ZIXB7-BEY73sWU.js} +1 -1
- package/dist/dashboard/client/assets/{layout-XjM3Q-ka.js → layout-D4DfNpzH.js} +1 -1
- package/dist/dashboard/client/assets/{linear-CMUrrr1X.js → linear-ZpGvKjeP.js} +1 -1
- package/dist/dashboard/client/assets/{mermaid-renderer-D2jYNs7K.js → mermaid-renderer-BCDxmS9g.js} +4 -4
- package/dist/dashboard/client/assets/{mindmap-definition-VGOIOE7T-CL4hv-vg.js → mindmap-definition-VGOIOE7T-MzAaKESA.js} +1 -1
- package/dist/dashboard/client/assets/{pieDiagram-ADFJNKIX-DTqv-1h1.js → pieDiagram-ADFJNKIX-B_X1kySF.js} +1 -1
- package/dist/dashboard/client/assets/{quadrantDiagram-AYHSOK5B-BpFlSW9N.js → quadrantDiagram-AYHSOK5B-CMoIEMLN.js} +1 -1
- package/dist/dashboard/client/assets/{requirementDiagram-UZGBJVZJ-BqYqqXL4.js → requirementDiagram-UZGBJVZJ-v4CRsn1w.js} +1 -1
- package/dist/dashboard/client/assets/{sankeyDiagram-TZEHDZUN-kEI9kntR.js → sankeyDiagram-TZEHDZUN-CPcyN8Jj.js} +1 -1
- package/dist/dashboard/client/assets/{sequenceDiagram-WL72ISMW-Cnu_1j-N.js → sequenceDiagram-WL72ISMW-CTg0Vx1H.js} +1 -1
- package/dist/dashboard/client/assets/{stateDiagram-FKZM4ZOC-BoC-rqoG.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-CXMWuzDL.js → timeline-definition-IT6M3QCI-B8xFcSGb.js} +1 -1
- package/dist/dashboard/client/assets/{treemap-GDKQZRPO-o9ZFgpbJ.js → treemap-GDKQZRPO-HQQuGl9w.js} +1 -1
- package/dist/dashboard/client/assets/{xychartDiagram-PRI3JC2R-CfIuUpeA.js → xychartDiagram-PRI3JC2R-Drz0SW3I.js} +1 -1
- package/dist/dashboard/client/index.html +2 -2
- package/dist/dashboard/server.js +1085 -632
- package/dist/index.js +1395 -383
- package/package.json +6 -39
- package/dist/dashboard/client/assets/channel-rgw7C1e7.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-2ON5EDUG-DTGi7d9X.js +0 -1
- package/dist/dashboard/client/assets/classDiagram-v2-WZHVMYZB-DTGi7d9X.js +0 -1
- package/dist/dashboard/client/assets/clone-Cz7hswqi.js +0 -1
- package/dist/dashboard/client/assets/index-C3NEq704.js +0 -571
- package/dist/dashboard/client/assets/index-CzxeSSaQ.css +0 -1
- package/dist/dashboard/client/assets/stateDiagram-v2-4FDKWEC3-COR3QD3v.js +0 -1
- package/dist/lib/db/index.js +0 -2177
- package/dist/lib/models.js +0 -85
- 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);
|
|
@@ -20806,7 +20806,7 @@ var require_application = __commonJS({
|
|
|
20806
20806
|
};
|
|
20807
20807
|
app2.del = deprecate.function(app2.delete, "app.del: Use app.delete instead");
|
|
20808
20808
|
app2.render = function render(name, options, callback) {
|
|
20809
|
-
var
|
|
20809
|
+
var cache2 = this.cache;
|
|
20810
20810
|
var done = callback;
|
|
20811
20811
|
var engines = this.engines;
|
|
20812
20812
|
var opts = options;
|
|
@@ -20825,7 +20825,7 @@ var require_application = __commonJS({
|
|
|
20825
20825
|
renderOptions.cache = this.enabled("view cache");
|
|
20826
20826
|
}
|
|
20827
20827
|
if (renderOptions.cache) {
|
|
20828
|
-
view =
|
|
20828
|
+
view = cache2[name];
|
|
20829
20829
|
}
|
|
20830
20830
|
if (!view) {
|
|
20831
20831
|
var View2 = this.get("view");
|
|
@@ -20841,7 +20841,7 @@ var require_application = __commonJS({
|
|
|
20841
20841
|
return done(err);
|
|
20842
20842
|
}
|
|
20843
20843
|
if (renderOptions.cache) {
|
|
20844
|
-
|
|
20844
|
+
cache2[name] = view;
|
|
20845
20845
|
}
|
|
20846
20846
|
}
|
|
20847
20847
|
tryRender(view, renderOptions, done);
|
|
@@ -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"
|
|
@@ -33489,11 +33635,6 @@ function buildResumeCommand(vendor, vendorSessionId) {
|
|
|
33489
33635
|
// src/server/services/ai-cli/claude-adapter.ts
|
|
33490
33636
|
var WORKFLOW_TOOLS = ["Read", "Write", "Edit", "Bash", "Glob", "Grep", "TodoWrite", "TodoRead", "Task"];
|
|
33491
33637
|
var QUERY_TOOLS = ["Read", "Grep", "Glob"];
|
|
33492
|
-
var BUNDLED_CLAUDE_MODELS = [
|
|
33493
|
-
{ id: "claude-opus-4-7", displayName: "Claude Opus 4.7" },
|
|
33494
|
-
{ id: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6" },
|
|
33495
|
-
{ id: "claude-haiku-4-5-20251001", displayName: "Claude Haiku 4.5" }
|
|
33496
|
-
];
|
|
33497
33638
|
var ClaudeCodeAdapter = class {
|
|
33498
33639
|
name = "Claude Code";
|
|
33499
33640
|
binary = "claude";
|
|
@@ -33522,6 +33663,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33522
33663
|
}
|
|
33523
33664
|
}
|
|
33524
33665
|
spawn(opts) {
|
|
33666
|
+
assertNonEmptyPrompt(opts.prompt);
|
|
33525
33667
|
const isWorkflow = opts.mode === "workflow";
|
|
33526
33668
|
const maxTurns = opts.maxTurns ?? (isWorkflow ? 500 : 1);
|
|
33527
33669
|
const tools = opts.allowedTools ?? (isWorkflow ? WORKFLOW_TOOLS : QUERY_TOOLS);
|
|
@@ -33543,7 +33685,6 @@ var ClaudeCodeAdapter = class {
|
|
|
33543
33685
|
flags.push("--model", opts.model);
|
|
33544
33686
|
}
|
|
33545
33687
|
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33546
|
-
"pipe",
|
|
33547
33688
|
isWorkflow ? opts.logFile : void 0
|
|
33548
33689
|
);
|
|
33549
33690
|
const proc = spawnBinary("claude", flags, {
|
|
@@ -33554,46 +33695,13 @@ var ClaudeCodeAdapter = class {
|
|
|
33554
33695
|
});
|
|
33555
33696
|
closeFileStdio(logFd);
|
|
33556
33697
|
if (isWorkflow) proc.unref();
|
|
33557
|
-
proc
|
|
33558
|
-
proc.stdin?.end();
|
|
33698
|
+
deliverPrompt(proc, opts.prompt);
|
|
33559
33699
|
return {
|
|
33560
33700
|
process: proc,
|
|
33561
33701
|
detached: isWorkflow,
|
|
33562
33702
|
...logPath ? { logPath } : {}
|
|
33563
33703
|
};
|
|
33564
33704
|
}
|
|
33565
|
-
async listModels() {
|
|
33566
|
-
try {
|
|
33567
|
-
const output = execBinary("claude", ["models", "--json"], {
|
|
33568
|
-
encoding: "utf-8",
|
|
33569
|
-
timeout: 5e3,
|
|
33570
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
33571
|
-
});
|
|
33572
|
-
const parsed = JSON.parse(output);
|
|
33573
|
-
if (Array.isArray(parsed)) {
|
|
33574
|
-
const models = [];
|
|
33575
|
-
for (const item of parsed) {
|
|
33576
|
-
if (typeof item === "string") {
|
|
33577
|
-
models.push({ id: item });
|
|
33578
|
-
} else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
|
|
33579
|
-
const obj = item;
|
|
33580
|
-
const desc = { id: obj.id };
|
|
33581
|
-
if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
|
|
33582
|
-
if (typeof obj.provider === "string") desc.provider = obj.provider;
|
|
33583
|
-
if (Array.isArray(obj.tags)) {
|
|
33584
|
-
desc.tags = obj.tags.filter((t) => typeof t === "string");
|
|
33585
|
-
}
|
|
33586
|
-
models.push(desc);
|
|
33587
|
-
}
|
|
33588
|
-
}
|
|
33589
|
-
if (models.length > 0) {
|
|
33590
|
-
return models;
|
|
33591
|
-
}
|
|
33592
|
-
}
|
|
33593
|
-
} catch {
|
|
33594
|
-
}
|
|
33595
|
-
return BUNDLED_CLAUDE_MODELS;
|
|
33596
|
-
}
|
|
33597
33705
|
createParser() {
|
|
33598
33706
|
return new ClaudeLineParser();
|
|
33599
33707
|
}
|
|
@@ -33747,11 +33855,6 @@ function extractToolResultOutput(content) {
|
|
|
33747
33855
|
function capitalize(s) {
|
|
33748
33856
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
33749
33857
|
}
|
|
33750
|
-
var BUNDLED_OPENCODE_MODELS = [
|
|
33751
|
-
{ id: "anthropic/claude-opus-4-7", provider: "anthropic" },
|
|
33752
|
-
{ id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
|
|
33753
|
-
{ id: "anthropic/claude-haiku-4-5-20251001", provider: "anthropic" }
|
|
33754
|
-
];
|
|
33755
33858
|
var OpenCodeAdapter = class {
|
|
33756
33859
|
name = "OpenCode";
|
|
33757
33860
|
binary = "opencode";
|
|
@@ -33784,11 +33887,11 @@ var OpenCodeAdapter = class {
|
|
|
33784
33887
|
}
|
|
33785
33888
|
}
|
|
33786
33889
|
spawn(opts) {
|
|
33890
|
+
assertNonEmptyPrompt(opts.prompt);
|
|
33787
33891
|
const isWorkflow = opts.mode === "workflow";
|
|
33788
33892
|
const agent = opts.allowedTools ? void 0 : isWorkflow ? "build" : "plan";
|
|
33789
33893
|
const args = [
|
|
33790
33894
|
"run",
|
|
33791
|
-
opts.prompt,
|
|
33792
33895
|
"--format",
|
|
33793
33896
|
"json"
|
|
33794
33897
|
];
|
|
@@ -33802,7 +33905,6 @@ var OpenCodeAdapter = class {
|
|
|
33802
33905
|
args.push("--model", opts.model);
|
|
33803
33906
|
}
|
|
33804
33907
|
const { stdio, logFd, logPath } = buildFileStdio(
|
|
33805
|
-
"ignore",
|
|
33806
33908
|
isWorkflow ? opts.logFile : void 0
|
|
33807
33909
|
);
|
|
33808
33910
|
const proc = spawnBinary("opencode", args, {
|
|
@@ -33813,44 +33915,13 @@ var OpenCodeAdapter = class {
|
|
|
33813
33915
|
});
|
|
33814
33916
|
closeFileStdio(logFd);
|
|
33815
33917
|
if (isWorkflow) proc.unref();
|
|
33918
|
+
deliverPrompt(proc, opts.prompt);
|
|
33816
33919
|
return {
|
|
33817
33920
|
process: proc,
|
|
33818
33921
|
detached: isWorkflow,
|
|
33819
33922
|
...logPath ? { logPath } : {}
|
|
33820
33923
|
};
|
|
33821
33924
|
}
|
|
33822
|
-
async listModels() {
|
|
33823
|
-
try {
|
|
33824
|
-
const output = execBinary("opencode", ["models", "--json"], {
|
|
33825
|
-
encoding: "utf-8",
|
|
33826
|
-
timeout: 5e3,
|
|
33827
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
33828
|
-
});
|
|
33829
|
-
const parsed = JSON.parse(output);
|
|
33830
|
-
if (Array.isArray(parsed)) {
|
|
33831
|
-
const models = [];
|
|
33832
|
-
for (const item of parsed) {
|
|
33833
|
-
if (typeof item === "string") {
|
|
33834
|
-
models.push({ id: item });
|
|
33835
|
-
} else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
|
|
33836
|
-
const obj = item;
|
|
33837
|
-
const desc = { id: obj.id };
|
|
33838
|
-
if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
|
|
33839
|
-
if (typeof obj.provider === "string") desc.provider = obj.provider;
|
|
33840
|
-
if (Array.isArray(obj.tags)) {
|
|
33841
|
-
desc.tags = obj.tags.filter((t) => typeof t === "string");
|
|
33842
|
-
}
|
|
33843
|
-
models.push(desc);
|
|
33844
|
-
}
|
|
33845
|
-
}
|
|
33846
|
-
if (models.length > 0) {
|
|
33847
|
-
return models;
|
|
33848
|
-
}
|
|
33849
|
-
}
|
|
33850
|
-
} catch {
|
|
33851
|
-
}
|
|
33852
|
-
return BUNDLED_OPENCODE_MODELS;
|
|
33853
|
-
}
|
|
33854
33925
|
/**
|
|
33855
33926
|
* OpenCode emits each event with all its content already resolved (tool
|
|
33856
33927
|
* results arrive in the same event as the call), so the parser is
|
|
@@ -34019,6 +34090,9 @@ function readEventJournal(ocrDir, executionId) {
|
|
|
34019
34090
|
}
|
|
34020
34091
|
|
|
34021
34092
|
// src/server/services/ai-cli/index.ts
|
|
34093
|
+
function createRegisteredAdapters() {
|
|
34094
|
+
return [new ClaudeCodeAdapter(), new OpenCodeAdapter()];
|
|
34095
|
+
}
|
|
34022
34096
|
function readAiCliPreference(ocrDir) {
|
|
34023
34097
|
try {
|
|
34024
34098
|
const configPath = join9(ocrDir, "config.yaml");
|
|
@@ -34038,10 +34112,7 @@ var AiCliService = class {
|
|
|
34038
34112
|
status;
|
|
34039
34113
|
constructor(ocrDir) {
|
|
34040
34114
|
this.preference = readAiCliPreference(ocrDir);
|
|
34041
|
-
const adapters =
|
|
34042
|
-
new ClaudeCodeAdapter(),
|
|
34043
|
-
new OpenCodeAdapter()
|
|
34044
|
-
];
|
|
34115
|
+
const adapters = createRegisteredAdapters();
|
|
34045
34116
|
this.entries = adapters.map((adapter) => ({
|
|
34046
34117
|
adapter,
|
|
34047
34118
|
detection: adapter.detect()
|
|
@@ -34118,8 +34189,8 @@ var AiCliService = class {
|
|
|
34118
34189
|
const available = this.entries.filter((e) => e.detection.found);
|
|
34119
34190
|
if (available.length === 0) return null;
|
|
34120
34191
|
if (this.preference !== "auto") {
|
|
34121
|
-
const
|
|
34122
|
-
if (
|
|
34192
|
+
const preferred2 = available.find((e) => e.adapter.binary === this.preference);
|
|
34193
|
+
if (preferred2) return preferred2.adapter;
|
|
34123
34194
|
console.warn(
|
|
34124
34195
|
` AI CLI: Preferred "${this.preference}" not found, falling back to auto-detection`
|
|
34125
34196
|
);
|
|
@@ -34166,10 +34237,12 @@ var FileTailer = class {
|
|
|
34166
34237
|
/** Read everything currently available from `offset` to EOF. */
|
|
34167
34238
|
poll() {
|
|
34168
34239
|
if (!this.ensureOpen()) return;
|
|
34240
|
+
const fd = this.fd;
|
|
34241
|
+
if (fd === null) return;
|
|
34169
34242
|
let bytes;
|
|
34170
34243
|
do {
|
|
34171
34244
|
try {
|
|
34172
|
-
bytes = readSync(
|
|
34245
|
+
bytes = readSync(fd, this.buf, 0, this.buf.length, this.offset);
|
|
34173
34246
|
} catch {
|
|
34174
34247
|
return;
|
|
34175
34248
|
}
|
|
@@ -34230,100 +34303,12 @@ function resolveLocalCli() {
|
|
|
34230
34303
|
return null;
|
|
34231
34304
|
}
|
|
34232
34305
|
|
|
34233
|
-
// ../
|
|
34234
|
-
var REASON_EVENT_TYPES = [
|
|
34235
|
-
"session_aborted",
|
|
34236
|
-
"session_auto_closed_stale",
|
|
34237
|
-
"session_synced",
|
|
34238
|
-
"session_legacy_import"
|
|
34239
|
-
];
|
|
34240
|
-
var TERMINAL_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
34241
|
-
"session_closed",
|
|
34242
|
-
...REASON_EVENT_TYPES
|
|
34243
|
-
]);
|
|
34244
|
-
function hasCompletionInvariant(db, session) {
|
|
34245
|
-
const eventType = session.workflow_type === "map" ? "map_completed" : "round_completed";
|
|
34246
|
-
const round = session.workflow_type === "map" ? session.current_map_run : session.current_round;
|
|
34247
|
-
const r = db.exec(
|
|
34248
|
-
`SELECT 1 FROM orchestration_events
|
|
34249
|
-
WHERE session_id = ? AND event_type = ? AND round = ? LIMIT 1`,
|
|
34250
|
-
[session.id, eventType, round]
|
|
34251
|
-
);
|
|
34252
|
-
return (r[0]?.values.length ?? 0) > 0;
|
|
34253
|
-
}
|
|
34254
|
-
|
|
34255
|
-
// ../cli/src/lib/state/index.ts
|
|
34256
|
-
async function stateClose(params) {
|
|
34257
|
-
const { sessionId, ocrDir, abort } = params;
|
|
34258
|
-
const db = await ensureDatabase(ocrDir);
|
|
34259
|
-
const existing = getSession(db, sessionId);
|
|
34260
|
-
if (!existing) {
|
|
34261
|
-
throw new StateError(STATE_EXIT.NOT_FOUND, `Session not found: ${sessionId}`);
|
|
34262
|
-
}
|
|
34263
|
-
if (existing.status === "closed") {
|
|
34264
|
-
console.error(`[ocr] Session already closed: ${sessionId}`);
|
|
34265
|
-
return;
|
|
34266
|
-
}
|
|
34267
|
-
if (!abort && !hasCompletionInvariant(db, existing)) {
|
|
34268
|
-
const what = existing.workflow_type === "map" ? `map run ${existing.current_map_run} has no map_completed event` : `round ${existing.current_round} has no round_completed event`;
|
|
34269
|
-
throw new StateError(
|
|
34270
|
-
STATE_EXIT.INVARIANT_UNMET,
|
|
34271
|
-
`Cannot close session ${sessionId}: ${what}. Run 'ocr state complete-round' to finalize it, or pass --abort to record an abandoned session.`
|
|
34272
|
-
);
|
|
34273
|
-
}
|
|
34274
|
-
const note = "closed by parent workflow close";
|
|
34275
|
-
db.transaction(() => {
|
|
34276
|
-
if (abort) {
|
|
34277
|
-
insertEvent(db, {
|
|
34278
|
-
session_id: sessionId,
|
|
34279
|
-
event_type: "session_aborted",
|
|
34280
|
-
phase: existing.current_phase,
|
|
34281
|
-
phase_number: existing.phase_number,
|
|
34282
|
-
round: existing.current_round
|
|
34283
|
-
});
|
|
34284
|
-
}
|
|
34285
|
-
updateSession(db, sessionId, {
|
|
34286
|
-
status: "closed",
|
|
34287
|
-
current_phase: "complete"
|
|
34288
|
-
});
|
|
34289
|
-
if (!abort) {
|
|
34290
|
-
insertEvent(db, {
|
|
34291
|
-
session_id: sessionId,
|
|
34292
|
-
event_type: "session_closed",
|
|
34293
|
-
phase: "complete",
|
|
34294
|
-
phase_number: existing.phase_number,
|
|
34295
|
-
round: existing.current_round
|
|
34296
|
-
});
|
|
34297
|
-
}
|
|
34298
|
-
cascadeTerminateExecutions(db, sessionId, CASCADE_CLOSE_EXIT_CODE, note);
|
|
34299
|
-
});
|
|
34300
|
-
}
|
|
34301
|
-
async function reconcileWorkflowOnExit(ocrDir, sessionId, db) {
|
|
34302
|
-
db ??= await ensureDatabase(ocrDir);
|
|
34303
|
-
const existing = getSession(db, sessionId);
|
|
34304
|
-
if (!existing) return "not-found";
|
|
34305
|
-
if (existing.status === "closed") return "already-closed";
|
|
34306
|
-
if (!hasCompletionInvariant(db, existing)) return "incomplete";
|
|
34307
|
-
if (hasInFlightDependents(db, sessionId)) return "in-flight";
|
|
34308
|
-
await stateClose({ sessionId, ocrDir, abort: false });
|
|
34309
|
-
return "closed";
|
|
34310
|
-
}
|
|
34311
|
-
async function reconcileCompletedSessions(ocrDir) {
|
|
34312
|
-
const db = await ensureDatabase(ocrDir);
|
|
34313
|
-
const closed = [];
|
|
34314
|
-
for (const s of getAllSessions(db)) {
|
|
34315
|
-
if (s.status !== "active") continue;
|
|
34316
|
-
const outcome = await reconcileWorkflowOnExit(ocrDir, s.id, db);
|
|
34317
|
-
if (outcome === "closed") closed.push(s.id);
|
|
34318
|
-
}
|
|
34319
|
-
return closed;
|
|
34320
|
-
}
|
|
34321
|
-
|
|
34322
|
-
// ../cli/src/lib/runtime-config.ts
|
|
34306
|
+
// ../shared/config/src/runtime-config.ts
|
|
34323
34307
|
import { existsSync as existsSync9, readFileSync as readFileSync4 } from "node:fs";
|
|
34324
34308
|
import { join as join11 } from "node:path";
|
|
34325
34309
|
var DEFAULT_AGENT_HEARTBEAT_SECONDS = 60;
|
|
34326
34310
|
var DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES = 60;
|
|
34311
|
+
var DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS = 2;
|
|
34327
34312
|
function readRuntimePositiveInt(ocrDir, key, defaultValue) {
|
|
34328
34313
|
const configPath = join11(ocrDir, "config.yaml");
|
|
34329
34314
|
if (!existsSync9(configPath)) return defaultValue;
|
|
@@ -34368,8 +34353,15 @@ function getWorkflowHardDeadlineMs(ocrDir) {
|
|
|
34368
34353
|
DEFAULT_WORKFLOW_HARD_DEADLINE_MINUTES
|
|
34369
34354
|
) * 60 * 1e3;
|
|
34370
34355
|
}
|
|
34356
|
+
function getForwardResumeMaxAttempts(ocrDir) {
|
|
34357
|
+
return readRuntimePositiveInt(
|
|
34358
|
+
ocrDir,
|
|
34359
|
+
"forward_resume_max_attempts",
|
|
34360
|
+
DEFAULT_FORWARD_RESUME_MAX_ATTEMPTS
|
|
34361
|
+
);
|
|
34362
|
+
}
|
|
34371
34363
|
|
|
34372
|
-
// src/server/socket/
|
|
34364
|
+
// src/server/socket/prompt-builder.ts
|
|
34373
34365
|
function shellSplit(str) {
|
|
34374
34366
|
const tokens = [];
|
|
34375
34367
|
let current = "";
|
|
@@ -34396,11 +34388,6 @@ function shellSplit(str) {
|
|
|
34396
34388
|
if (current) tokens.push(current);
|
|
34397
34389
|
return tokens;
|
|
34398
34390
|
}
|
|
34399
|
-
var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
34400
|
-
"progress",
|
|
34401
|
-
"state"
|
|
34402
|
-
]);
|
|
34403
|
-
var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
|
|
34404
34391
|
function escapeUserHeaders(value) {
|
|
34405
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");
|
|
34406
34393
|
}
|
|
@@ -34425,8 +34412,8 @@ function buildPrompt(opts) {
|
|
|
34425
34412
|
options.push("--fresh");
|
|
34426
34413
|
i++;
|
|
34427
34414
|
} else if (arg === "--requirements" && i + 1 < subArgs.length) {
|
|
34428
|
-
requirements = subArgs
|
|
34429
|
-
|
|
34415
|
+
requirements = subArgs[i + 1] ?? "";
|
|
34416
|
+
i += 2;
|
|
34430
34417
|
} else if (arg === "--team" && i + 1 < subArgs.length) {
|
|
34431
34418
|
team = subArgs[i + 1] ?? "";
|
|
34432
34419
|
i += 2;
|
|
@@ -34436,8 +34423,9 @@ function buildPrompt(opts) {
|
|
|
34436
34423
|
} else if (arg === "--reviewer" && i + 1 < subArgs.length) {
|
|
34437
34424
|
const raw = subArgs[i + 1] ?? "";
|
|
34438
34425
|
const countMatch = raw.match(/^(\d+):(.+)$/);
|
|
34439
|
-
|
|
34440
|
-
|
|
34426
|
+
const [, countStr, description] = countMatch ?? [];
|
|
34427
|
+
if (countStr && description) {
|
|
34428
|
+
reviewerDescriptions.push({ description, count: parseInt(countStr, 10) });
|
|
34441
34429
|
} else {
|
|
34442
34430
|
reviewerDescriptions.push({ description: raw, count: 1 });
|
|
34443
34431
|
}
|
|
@@ -34548,57 +34536,372 @@ function extractPerInstanceModels(subArgs) {
|
|
|
34548
34536
|
}
|
|
34549
34537
|
return [...models];
|
|
34550
34538
|
}
|
|
34539
|
+
|
|
34540
|
+
// src/server/socket/process-registry.ts
|
|
34551
34541
|
var MAX_CONCURRENT = 3;
|
|
34552
|
-
var WATCHDOG_TICK_MS = 1e4;
|
|
34553
|
-
var POST_RESULT_GRACE_MS = 3e4;
|
|
34554
|
-
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
34555
|
-
function decideWatchdogTick(i) {
|
|
34556
|
-
if (i.resultSeenAt !== void 0 && i.nowMs - i.resultSeenAt > i.postResultGraceMs) {
|
|
34557
|
-
return {
|
|
34558
|
-
action: "finalize",
|
|
34559
|
-
reap: !i.exited,
|
|
34560
|
-
exitCode: i.resultIsError ? 1 : 0,
|
|
34561
|
-
reason: "result-grace"
|
|
34562
|
-
};
|
|
34563
|
-
}
|
|
34564
|
-
if (i.nowMs - i.startedAtMs > i.hardDeadlineMs) {
|
|
34565
|
-
return {
|
|
34566
|
-
action: "finalize",
|
|
34567
|
-
reap: !i.exited,
|
|
34568
|
-
exitCode: WATCHDOG_DEADLINE_EXIT_CODE,
|
|
34569
|
-
reason: "hard-deadline"
|
|
34570
|
-
};
|
|
34571
|
-
}
|
|
34572
|
-
return i.exited ? { action: "wait" } : { action: "beat" };
|
|
34573
|
-
}
|
|
34574
34542
|
var activeCommands = /* @__PURE__ */ new Map();
|
|
34575
|
-
function
|
|
34576
|
-
return
|
|
34577
|
-
|
|
34578
|
-
|
|
34579
|
-
|
|
34580
|
-
|
|
34581
|
-
|
|
34582
|
-
|
|
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,
|
|
34583
34570
|
pid,
|
|
34584
34571
|
started_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
34585
34572
|
});
|
|
34586
|
-
writeFileSync3(spawnMarkerPath(ocrDir), payload, { mode: 384 });
|
|
34573
|
+
writeFileSync3(spawnMarkerPath(ocrDir, executionUid), payload, { mode: 384 });
|
|
34587
34574
|
}
|
|
34588
|
-
function clearSpawnMarker(ocrDir) {
|
|
34575
|
+
function clearSpawnMarker(ocrDir, executionUid) {
|
|
34589
34576
|
try {
|
|
34590
|
-
unlinkSync3(spawnMarkerPath(ocrDir));
|
|
34577
|
+
unlinkSync3(spawnMarkerPath(ocrDir, executionUid));
|
|
34591
34578
|
} catch {
|
|
34592
34579
|
}
|
|
34593
34580
|
}
|
|
34594
|
-
function
|
|
34595
|
-
|
|
34596
|
-
|
|
34597
|
-
|
|
34598
|
-
|
|
34599
|
-
|
|
34600
|
-
|
|
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
|
|
34593
|
+
var WATCHDOG_TICK_MS = 1e4;
|
|
34594
|
+
var POST_RESULT_GRACE_MS = 3e4;
|
|
34595
|
+
var HEARTBEAT_THROTTLE_MS = 5e3;
|
|
34596
|
+
function decideWatchdogTick(i) {
|
|
34597
|
+
if (i.resultSeenAt !== void 0 && i.nowMs - i.resultSeenAt > i.postResultGraceMs) {
|
|
34598
|
+
return {
|
|
34599
|
+
action: "finalize",
|
|
34600
|
+
reap: !i.exited,
|
|
34601
|
+
exitCode: i.resultIsError ? 1 : 0,
|
|
34602
|
+
reason: "result-grace"
|
|
34603
|
+
};
|
|
34604
|
+
}
|
|
34605
|
+
if (i.nowMs - i.startedAtMs > i.hardDeadlineMs) {
|
|
34606
|
+
return {
|
|
34607
|
+
action: "finalize",
|
|
34608
|
+
reap: !i.exited,
|
|
34609
|
+
exitCode: WATCHDOG_DEADLINE_EXIT_CODE,
|
|
34610
|
+
reason: "hard-deadline"
|
|
34611
|
+
};
|
|
34612
|
+
}
|
|
34613
|
+
return i.exited ? { action: "wait" } : { action: "beat" };
|
|
34614
|
+
}
|
|
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
|
+
};
|
|
34630
|
+
}
|
|
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;
|
|
34657
|
+
try {
|
|
34658
|
+
return JSON.parse(e.metadata);
|
|
34659
|
+
} catch {
|
|
34660
|
+
return null;
|
|
34661
|
+
}
|
|
34662
|
+
}
|
|
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;
|
|
34601
34838
|
}
|
|
34839
|
+
function finishExecution(io2, db, ocrDir, executionId, rawCode, output) {
|
|
34840
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
34841
|
+
const entry = activeCommands.get(executionId);
|
|
34842
|
+
const code = entry?.cancelled ? CANCELLED_EXIT_CODE : rawCode;
|
|
34843
|
+
if (!tryClaimFinalization(entry)) return;
|
|
34844
|
+
const res = db.prepare(
|
|
34845
|
+
`UPDATE command_executions
|
|
34846
|
+
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
34847
|
+
WHERE id = ? AND finished_at IS NULL`
|
|
34848
|
+
).run(code, finishedAt, output, executionId);
|
|
34849
|
+
if (Number(res.changes) === 0 && !entry) return;
|
|
34850
|
+
const completeness = getWorkflowCompletenessForExecution(db, executionId);
|
|
34851
|
+
const outcome = deriveCommandOutcome(code, completeness);
|
|
34852
|
+
const cancellationReason = deriveCancellationReason(code);
|
|
34853
|
+
if (entry?.uid) {
|
|
34854
|
+
appendCommandLog(ocrDir, {
|
|
34855
|
+
v: 1,
|
|
34856
|
+
uid: entry.uid,
|
|
34857
|
+
db_id: executionId,
|
|
34858
|
+
command: entry.commandStr,
|
|
34859
|
+
args: entry.argsJson ?? null,
|
|
34860
|
+
exit_code: code,
|
|
34861
|
+
started_at: entry.startedAt,
|
|
34862
|
+
finished_at: finishedAt,
|
|
34863
|
+
is_detached: entry.detached ? 1 : 0,
|
|
34864
|
+
event: code === CANCELLED_EXIT_CODE ? "cancel" : "finish",
|
|
34865
|
+
writer: "dashboard"
|
|
34866
|
+
});
|
|
34867
|
+
}
|
|
34868
|
+
io2.emit("command:finished", {
|
|
34869
|
+
execution_id: executionId,
|
|
34870
|
+
exitCode: code,
|
|
34871
|
+
finished_at: finishedAt,
|
|
34872
|
+
outcome,
|
|
34873
|
+
cancellation_reason: cancellationReason
|
|
34874
|
+
});
|
|
34875
|
+
activeCommands.delete(executionId);
|
|
34876
|
+
const workflowRow = db.exec(
|
|
34877
|
+
"SELECT workflow_id FROM command_executions WHERE id = ?",
|
|
34878
|
+
[executionId]
|
|
34879
|
+
);
|
|
34880
|
+
const workflowId = workflowRow[0]?.values[0]?.[0];
|
|
34881
|
+
if (typeof workflowId === "string" && workflowId.length > 0) {
|
|
34882
|
+
void reconcileWorkflowOnExit(ocrDir, workflowId, db).then((outcome2) => {
|
|
34883
|
+
if (outcome2 === "closed") {
|
|
34884
|
+
console.log(`[command-runner] auto-finalized workflow ${workflowId}`);
|
|
34885
|
+
} else if (outcome2 === "incomplete" || outcome2 === "in-flight") {
|
|
34886
|
+
console.debug(
|
|
34887
|
+
`[command-runner] workflow ${workflowId} not finalized: ${outcome2}`
|
|
34888
|
+
);
|
|
34889
|
+
}
|
|
34890
|
+
}).catch((err) => {
|
|
34891
|
+
console.error(
|
|
34892
|
+
`[command-runner] reconcileWorkflowOnExit(${workflowId}) failed:`,
|
|
34893
|
+
err instanceof Error ? err.message : err
|
|
34894
|
+
);
|
|
34895
|
+
});
|
|
34896
|
+
}
|
|
34897
|
+
}
|
|
34898
|
+
|
|
34899
|
+
// src/server/socket/command-runner.ts
|
|
34900
|
+
var ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
34901
|
+
"progress",
|
|
34902
|
+
"state"
|
|
34903
|
+
]);
|
|
34904
|
+
var AI_COMMANDS = /* @__PURE__ */ new Set(["map", "review", "translate-review-to-single-human", "address", "create-reviewer", "sync-reviewers"]);
|
|
34602
34905
|
function registerCommandHandlers(io2, socket, db, ocrDir, aiCliService, sessionCapture) {
|
|
34603
34906
|
socket.on("command:run", (payload) => {
|
|
34604
34907
|
try {
|
|
@@ -34771,15 +35074,14 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34771
35074
|
finishExecution(io2, db, ocrDir, executionId, 1, content);
|
|
34772
35075
|
return;
|
|
34773
35076
|
}
|
|
35077
|
+
let capabilityWarning = null;
|
|
34774
35078
|
if (adapter.supportsPerTaskModel === false) {
|
|
34775
35079
|
const perInstanceModels = extractPerInstanceModels(subArgs);
|
|
34776
35080
|
if (perInstanceModels.length > 0) {
|
|
34777
|
-
|
|
34778
|
-
`;
|
|
34779
|
-
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.`;
|
|
34780
35082
|
}
|
|
34781
35083
|
}
|
|
34782
|
-
const commandMdPath =
|
|
35084
|
+
const commandMdPath = join13(ocrDir, "commands", `${baseCommand}.md`);
|
|
34783
35085
|
let commandContent;
|
|
34784
35086
|
try {
|
|
34785
35087
|
commandContent = readFileSync5(commandMdPath, "utf-8");
|
|
@@ -34830,9 +35132,9 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34830
35132
|
let logFile;
|
|
34831
35133
|
if (entry.uid) {
|
|
34832
35134
|
try {
|
|
34833
|
-
const logDir =
|
|
34834
|
-
|
|
34835
|
-
logFile =
|
|
35135
|
+
const logDir = join13(ocrDir, "data", "exec-logs");
|
|
35136
|
+
mkdirSync6(logDir, { recursive: true });
|
|
35137
|
+
logFile = join13(logDir, `${entry.uid}.log`);
|
|
34836
35138
|
} catch (err) {
|
|
34837
35139
|
console.error(
|
|
34838
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):",
|
|
@@ -34888,20 +35190,7 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34888
35190
|
}
|
|
34889
35191
|
}, POLL_INTERVAL_MS);
|
|
34890
35192
|
entry.linkPoll = linkPoll;
|
|
34891
|
-
const bumpHeartbeat = (
|
|
34892
|
-
if (entry.finalized) return;
|
|
34893
|
-
const now = Date.now();
|
|
34894
|
-
if (now - (entry.lastBeatWrite ?? 0) < HEARTBEAT_THROTTLE_MS) return;
|
|
34895
|
-
entry.lastBeatWrite = now;
|
|
34896
|
-
try {
|
|
34897
|
-
db.run(
|
|
34898
|
-
`UPDATE command_executions SET last_heartbeat_at = datetime('now') WHERE id = ? AND finished_at IS NULL`,
|
|
34899
|
-
[executionId]
|
|
34900
|
-
);
|
|
34901
|
-
} catch (err) {
|
|
34902
|
-
console.error("[command-runner] heartbeat bump failed:", err);
|
|
34903
|
-
}
|
|
34904
|
-
};
|
|
35193
|
+
const bumpHeartbeat = makeHeartbeatBumper(db, executionId, entry);
|
|
34905
35194
|
const hardDeadlineMs = getWorkflowHardDeadlineMs(ocrDir);
|
|
34906
35195
|
entry.watchdog = setInterval(() => {
|
|
34907
35196
|
if (entry.finalized) return;
|
|
@@ -34933,6 +35222,12 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34933
35222
|
`;
|
|
34934
35223
|
entry.outputBuffer += notice;
|
|
34935
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
|
+
});
|
|
34936
35231
|
} else {
|
|
34937
35232
|
console.warn(`[watchdog] execution ${executionId}: result seen but no close after grace \u2014 finalizing${decision.reap ? " + reaping tree" : ""}`);
|
|
34938
35233
|
}
|
|
@@ -34940,6 +35235,10 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34940
35235
|
finishExecution(io2, db, ocrDir, executionId, decision.exitCode, entry.outputBuffer);
|
|
34941
35236
|
return;
|
|
34942
35237
|
}
|
|
35238
|
+
default: {
|
|
35239
|
+
const _exhaustive = decision;
|
|
35240
|
+
throw new Error(`unhandled watchdog action: ${JSON.stringify(_exhaustive)}`);
|
|
35241
|
+
}
|
|
34943
35242
|
}
|
|
34944
35243
|
}, WATCHDOG_TICK_MS);
|
|
34945
35244
|
entry.watchdog.unref();
|
|
@@ -34967,6 +35266,16 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
34967
35266
|
journal.append(stream);
|
|
34968
35267
|
io2.emit("command:event", stream);
|
|
34969
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
|
+
}
|
|
34970
35279
|
function handleEvent(evt) {
|
|
34971
35280
|
switch (evt.type) {
|
|
34972
35281
|
case "text_delta":
|
|
@@ -35050,7 +35359,7 @@ function spawnAiCommand(io2, _socket, db, ocrDir, executionId, baseCommand, subA
|
|
|
35050
35359
|
clearInterval(entry.linkPoll);
|
|
35051
35360
|
entry.linkPoll = void 0;
|
|
35052
35361
|
}
|
|
35053
|
-
clearSpawnMarker(ocrDir);
|
|
35362
|
+
clearSpawnMarker(ocrDir, entry.uid);
|
|
35054
35363
|
if (entry.tailer) {
|
|
35055
35364
|
entry.tailer.stop();
|
|
35056
35365
|
entry.tailer = void 0;
|
|
@@ -35092,76 +35401,6 @@ ${stderrBuffer}`;
|
|
|
35092
35401
|
finishExecution(io2, db, ocrDir, executionId, -1, entry.outputBuffer);
|
|
35093
35402
|
});
|
|
35094
35403
|
}
|
|
35095
|
-
function finishExecution(io2, db, ocrDir, executionId, rawCode, output) {
|
|
35096
|
-
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
35097
|
-
const entry = activeCommands.get(executionId);
|
|
35098
|
-
const code = entry?.cancelled ? CANCELLED_EXIT_CODE : rawCode;
|
|
35099
|
-
if (entry?.finalized) return;
|
|
35100
|
-
if (entry) {
|
|
35101
|
-
entry.finalized = true;
|
|
35102
|
-
if (entry.watchdog) {
|
|
35103
|
-
clearInterval(entry.watchdog);
|
|
35104
|
-
entry.watchdog = void 0;
|
|
35105
|
-
}
|
|
35106
|
-
if (entry.tailer) {
|
|
35107
|
-
entry.tailer.stop();
|
|
35108
|
-
entry.tailer = void 0;
|
|
35109
|
-
}
|
|
35110
|
-
}
|
|
35111
|
-
const res = db.prepare(
|
|
35112
|
-
`UPDATE command_executions
|
|
35113
|
-
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
35114
|
-
WHERE id = ? AND finished_at IS NULL`
|
|
35115
|
-
).run(code, finishedAt, output, executionId);
|
|
35116
|
-
if (Number(res.changes) === 0 && !entry) return;
|
|
35117
|
-
const completeness = getWorkflowCompletenessForExecution(db, executionId);
|
|
35118
|
-
const outcome = deriveCommandOutcome(code, completeness);
|
|
35119
|
-
const cancellationReason = deriveCancellationReason(code);
|
|
35120
|
-
if (entry?.uid) {
|
|
35121
|
-
appendCommandLog(ocrDir, {
|
|
35122
|
-
v: 1,
|
|
35123
|
-
uid: entry.uid,
|
|
35124
|
-
db_id: executionId,
|
|
35125
|
-
command: entry.commandStr,
|
|
35126
|
-
args: entry.argsJson ?? null,
|
|
35127
|
-
exit_code: code,
|
|
35128
|
-
started_at: entry.startedAt,
|
|
35129
|
-
finished_at: finishedAt,
|
|
35130
|
-
is_detached: entry.detached ? 1 : 0,
|
|
35131
|
-
event: code === CANCELLED_EXIT_CODE ? "cancel" : "finish",
|
|
35132
|
-
writer: "dashboard"
|
|
35133
|
-
});
|
|
35134
|
-
}
|
|
35135
|
-
io2.emit("command:finished", {
|
|
35136
|
-
execution_id: executionId,
|
|
35137
|
-
exitCode: code,
|
|
35138
|
-
finished_at: finishedAt,
|
|
35139
|
-
outcome,
|
|
35140
|
-
cancellation_reason: cancellationReason
|
|
35141
|
-
});
|
|
35142
|
-
activeCommands.delete(executionId);
|
|
35143
|
-
const workflowRow = db.exec(
|
|
35144
|
-
"SELECT workflow_id FROM command_executions WHERE id = ?",
|
|
35145
|
-
[executionId]
|
|
35146
|
-
);
|
|
35147
|
-
const workflowId = workflowRow[0]?.values[0]?.[0];
|
|
35148
|
-
if (typeof workflowId === "string" && workflowId.length > 0) {
|
|
35149
|
-
void reconcileWorkflowOnExit(ocrDir, workflowId, db).then((outcome2) => {
|
|
35150
|
-
if (outcome2 === "closed") {
|
|
35151
|
-
console.log(`[command-runner] auto-finalized workflow ${workflowId}`);
|
|
35152
|
-
} else if (outcome2 === "incomplete" || outcome2 === "in-flight") {
|
|
35153
|
-
console.debug(
|
|
35154
|
-
`[command-runner] workflow ${workflowId} not finalized: ${outcome2}`
|
|
35155
|
-
);
|
|
35156
|
-
}
|
|
35157
|
-
}).catch((err) => {
|
|
35158
|
-
console.error(
|
|
35159
|
-
`[command-runner] reconcileWorkflowOnExit(${workflowId}) failed:`,
|
|
35160
|
-
err instanceof Error ? err.message : err
|
|
35161
|
-
);
|
|
35162
|
-
});
|
|
35163
|
-
}
|
|
35164
|
-
}
|
|
35165
35404
|
|
|
35166
35405
|
// src/server/routes/commands.ts
|
|
35167
35406
|
var AVAILABLE_COMMANDS = [
|
|
@@ -35249,7 +35488,7 @@ function createCommandsRouter(db, ocrDir) {
|
|
|
35249
35488
|
// src/server/routes/config.ts
|
|
35250
35489
|
var import_express9 = __toESM(require_express2(), 1);
|
|
35251
35490
|
import { readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
35252
|
-
import { join as
|
|
35491
|
+
import { join as join14, dirname as dirname8, basename as basename2 } from "node:path";
|
|
35253
35492
|
var VALID_IDES = ["vscode", "cursor", "windsurf", "jetbrains", "sublime"];
|
|
35254
35493
|
function detectIde() {
|
|
35255
35494
|
const termProgram = process.env.TERM_PROGRAM?.toLowerCase() ?? "";
|
|
@@ -35266,7 +35505,7 @@ function detectIde() {
|
|
|
35266
35505
|
}
|
|
35267
35506
|
function readIdeFromConfig(ocrDir) {
|
|
35268
35507
|
try {
|
|
35269
|
-
const configPath =
|
|
35508
|
+
const configPath = join14(ocrDir, "config.yaml");
|
|
35270
35509
|
const content = readFileSync6(configPath, "utf-8");
|
|
35271
35510
|
const match = content.match(/^\s*ide:\s*(\S+)/m);
|
|
35272
35511
|
return match?.[1] ?? "auto";
|
|
@@ -35313,7 +35552,7 @@ function createConfigRouter(ocrDir, aiCliService) {
|
|
|
35313
35552
|
return;
|
|
35314
35553
|
}
|
|
35315
35554
|
try {
|
|
35316
|
-
const configPath =
|
|
35555
|
+
const configPath = join14(ocrDir, "config.yaml");
|
|
35317
35556
|
let content = readFileSync6(configPath, "utf-8");
|
|
35318
35557
|
if (content.match(/^\s*ide:\s*\S+/m)) {
|
|
35319
35558
|
content = content.replace(/^(\s*ide:\s*)\S+/m, `$1${ide}`);
|
|
@@ -35403,9 +35642,9 @@ function createChatRouter(db) {
|
|
|
35403
35642
|
// src/server/routes/reviewers.ts
|
|
35404
35643
|
var import_express11 = __toESM(require_express2(), 1);
|
|
35405
35644
|
import { readFileSync as readFileSync7, existsSync as existsSync11, watch } from "node:fs";
|
|
35406
|
-
import { join as
|
|
35645
|
+
import { join as join15 } from "node:path";
|
|
35407
35646
|
function readReviewersMeta(ocrDir) {
|
|
35408
|
-
const metaPath =
|
|
35647
|
+
const metaPath = join15(ocrDir, "reviewers-meta.json");
|
|
35409
35648
|
if (!existsSync11(metaPath)) {
|
|
35410
35649
|
return { reviewers: [], defaults: [] };
|
|
35411
35650
|
}
|
|
@@ -35434,7 +35673,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
35434
35673
|
res.status(400).json({ error: "Invalid reviewer ID" });
|
|
35435
35674
|
return;
|
|
35436
35675
|
}
|
|
35437
|
-
const filePath =
|
|
35676
|
+
const filePath = join15(ocrDir, "skills", "references", "reviewers", `${id}.md`);
|
|
35438
35677
|
if (!existsSync11(filePath)) {
|
|
35439
35678
|
res.status(404).json({ error: "Reviewer not found", id });
|
|
35440
35679
|
return;
|
|
@@ -35449,7 +35688,7 @@ function createReviewersRouter(ocrDir) {
|
|
|
35449
35688
|
return router;
|
|
35450
35689
|
}
|
|
35451
35690
|
function watchReviewersMeta(ocrDir, io2) {
|
|
35452
|
-
const metaPath =
|
|
35691
|
+
const metaPath = join15(ocrDir, "reviewers-meta.json");
|
|
35453
35692
|
let watcher = null;
|
|
35454
35693
|
let debounce;
|
|
35455
35694
|
try {
|
|
@@ -35525,14 +35764,13 @@ function createHandoffRouter(sessionCapture, ocrDir, syncFromDisk = () => {
|
|
|
35525
35764
|
|
|
35526
35765
|
// src/server/routes/team.ts
|
|
35527
35766
|
var import_express14 = __toESM(require_express2(), 1);
|
|
35528
|
-
import { spawnSync } from "node:child_process";
|
|
35529
35767
|
|
|
35530
|
-
// ../
|
|
35768
|
+
// ../shared/config/src/team-config.ts
|
|
35531
35769
|
var import_yaml = __toESM(require_dist(), 1);
|
|
35532
35770
|
import { existsSync as existsSync12, readFileSync as readFileSync8 } from "node:fs";
|
|
35533
|
-
import { join as
|
|
35771
|
+
import { join as join16 } from "node:path";
|
|
35534
35772
|
function loadTeamConfig(ocrDir) {
|
|
35535
|
-
const configPath =
|
|
35773
|
+
const configPath = join16(ocrDir, "config.yaml");
|
|
35536
35774
|
if (!existsSync12(configPath)) {
|
|
35537
35775
|
return { team: [], aliases: {}, defaultModel: null };
|
|
35538
35776
|
}
|
|
@@ -35572,6 +35810,9 @@ function parseTeamConfigYaml(content) {
|
|
|
35572
35810
|
aliases,
|
|
35573
35811
|
defaultModel
|
|
35574
35812
|
);
|
|
35813
|
+
if (resolvedModel !== null) {
|
|
35814
|
+
assertSafeModelId(resolvedModel, `default_team.${persona}[${i}]`);
|
|
35815
|
+
}
|
|
35575
35816
|
team.push({
|
|
35576
35817
|
persona,
|
|
35577
35818
|
instance_index: i + 1,
|
|
@@ -35652,6 +35893,16 @@ function readOptionalString(obj, key, pathLabel) {
|
|
|
35652
35893
|
}
|
|
35653
35894
|
return value;
|
|
35654
35895
|
}
|
|
35896
|
+
var SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._/:@[\]+-]{0,255}$/;
|
|
35897
|
+
function assertSafeModelId(value, pathLabel) {
|
|
35898
|
+
if (SAFE_MODEL_ID.test(value)) return;
|
|
35899
|
+
const allowed = /[A-Za-z0-9._/:@[\]+-]/;
|
|
35900
|
+
const offending = [...value].find((ch) => !allowed.test(ch));
|
|
35901
|
+
const detail = value.length === 0 ? "empty string" : value.length > 256 ? "longer than 256 characters" : offending !== void 0 ? `contains ${JSON.stringify(offending)}` : `starts with ${JSON.stringify(value[0])}`;
|
|
35902
|
+
throw new Error(
|
|
35903
|
+
`${pathLabel}: model id ${detail} \u2014 no vendor model id uses that. Allowed: letters and digits plus . _ / : @ [ ] + - (max 256 chars).`
|
|
35904
|
+
);
|
|
35905
|
+
}
|
|
35655
35906
|
function readAliases(root) {
|
|
35656
35907
|
const models = root["models"];
|
|
35657
35908
|
if (!models || typeof models !== "object" || Array.isArray(models)) return {};
|
|
@@ -35693,29 +35944,71 @@ function resolveTeamComposition(team, override) {
|
|
|
35693
35944
|
result.push(inst);
|
|
35694
35945
|
}
|
|
35695
35946
|
for (const inst of override) {
|
|
35947
|
+
if (inst.model !== null) {
|
|
35948
|
+
assertSafeModelId(inst.model, `override ${inst.persona}#${inst.instance_index}`);
|
|
35949
|
+
}
|
|
35696
35950
|
result.push(inst);
|
|
35697
35951
|
}
|
|
35698
35952
|
return result;
|
|
35699
35953
|
}
|
|
35700
35954
|
|
|
35701
|
-
// ../
|
|
35702
|
-
|
|
35703
|
-
|
|
35704
|
-
|
|
35705
|
-
|
|
35706
|
-
];
|
|
35707
|
-
|
|
35708
|
-
|
|
35709
|
-
|
|
35710
|
-
|
|
35711
|
-
|
|
35712
|
-
|
|
35713
|
-
|
|
35955
|
+
// ../shared/config/src/models.ts
|
|
35956
|
+
function parseOpenCodeModelList(stdout) {
|
|
35957
|
+
const models = [];
|
|
35958
|
+
for (const rawLine of stdout.split(/\r?\n/)) {
|
|
35959
|
+
const line = rawLine.trim();
|
|
35960
|
+
if (!/^[^\s:]+\/\S+$/.test(line)) continue;
|
|
35961
|
+
const provider = line.slice(0, line.indexOf("/"));
|
|
35962
|
+
models.push({ id: line, provider });
|
|
35963
|
+
}
|
|
35964
|
+
return models.length > 0 ? models : null;
|
|
35965
|
+
}
|
|
35966
|
+
var VENDOR_MODEL_STRATEGIES = {
|
|
35967
|
+
claude: {
|
|
35968
|
+
displayName: "Claude Code",
|
|
35969
|
+
native: {
|
|
35970
|
+
// Verified against Claude Code 2.1.x: the CLI has no model-listing
|
|
35971
|
+
// subcommand (`claude models --json` → "unknown option"). Revisit if
|
|
35972
|
+
// a future release adds one.
|
|
35973
|
+
unavailableReason: "Claude Code does not provide a model-listing command; showing its documented model aliases instead"
|
|
35974
|
+
},
|
|
35975
|
+
// Vendor-documented aliases that always track the latest generation —
|
|
35976
|
+
// dated ids here would go stale by construction (the exact bug class of
|
|
35977
|
+
// issue #39). Pinned dated ids remain available via free-text entry.
|
|
35978
|
+
bundled: [
|
|
35979
|
+
{ id: "opus", displayName: "Claude Opus (latest)" },
|
|
35980
|
+
{ id: "sonnet", displayName: "Claude Sonnet (latest)" },
|
|
35981
|
+
{ id: "haiku", displayName: "Claude Haiku (latest)" }
|
|
35982
|
+
]
|
|
35983
|
+
},
|
|
35984
|
+
opencode: {
|
|
35985
|
+
displayName: "OpenCode",
|
|
35986
|
+
native: {
|
|
35987
|
+
// Plain `opencode models` — newline-delimited ids. (`--json` is not a
|
|
35988
|
+
// real flag, and `--verbose` interleaves JSON metadata blocks that
|
|
35989
|
+
// defeat line parsing.)
|
|
35990
|
+
args: ["models"],
|
|
35991
|
+
parse: parseOpenCodeModelList
|
|
35992
|
+
},
|
|
35993
|
+
bundled: [
|
|
35994
|
+
{ id: "anthropic/claude-opus-4-8", provider: "anthropic" },
|
|
35995
|
+
{ id: "anthropic/claude-sonnet-4-6", provider: "anthropic" },
|
|
35996
|
+
{ id: "anthropic/claude-haiku-4-5", provider: "anthropic" }
|
|
35997
|
+
]
|
|
35998
|
+
}
|
|
35999
|
+
};
|
|
36000
|
+
var SUPPORTED_VENDORS = Object.keys(
|
|
36001
|
+
VENDOR_MODEL_STRATEGIES
|
|
36002
|
+
);
|
|
36003
|
+
function isModelVendor(value) {
|
|
36004
|
+
return Object.hasOwn(VENDOR_MODEL_STRATEGIES, value);
|
|
36005
|
+
}
|
|
36006
|
+
async function detectActiveVendor() {
|
|
36007
|
+
for (const vendor of SUPPORTED_VENDORS) {
|
|
35714
36008
|
try {
|
|
35715
|
-
|
|
36009
|
+
await execBinaryAsync(vendor, ["--version"], {
|
|
35716
36010
|
encoding: "utf-8",
|
|
35717
|
-
timeout: 3e3
|
|
35718
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
36011
|
+
timeout: 3e3
|
|
35719
36012
|
});
|
|
35720
36013
|
return vendor;
|
|
35721
36014
|
} catch {
|
|
@@ -35723,48 +36016,77 @@ function detectActiveVendor() {
|
|
|
35723
36016
|
}
|
|
35724
36017
|
return null;
|
|
35725
36018
|
}
|
|
35726
|
-
function
|
|
36019
|
+
function describeProbeFailure(vendor, args, err) {
|
|
36020
|
+
const command = `${vendor} ${args.join(" ")}`;
|
|
36021
|
+
const e = err;
|
|
36022
|
+
if (e.code === "ENOENT") {
|
|
36023
|
+
return `\`${vendor}\` is not installed or not on PATH`;
|
|
36024
|
+
}
|
|
36025
|
+
if (e.killed) {
|
|
36026
|
+
return `\`${command}\` timed out or exceeded output limits`;
|
|
36027
|
+
}
|
|
36028
|
+
const stderr = typeof e.stderr === "string" ? e.stderr.trim() : "";
|
|
36029
|
+
const firstLine = (stderr.split(/\r?\n/)[0] ?? "").replace(/\u001b\[[0-9;]*[A-Za-z]/g, "").replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 200);
|
|
36030
|
+
const detail = firstLine ? `: ${firstLine}` : "";
|
|
36031
|
+
const exit = typeof e.code === "number" ? ` with exit code ${e.code}` : "";
|
|
36032
|
+
return `\`${command}\` failed${exit}${detail}`;
|
|
36033
|
+
}
|
|
36034
|
+
async function tryNativeEnumeration(vendor, probe) {
|
|
36035
|
+
let stdout;
|
|
35727
36036
|
try {
|
|
35728
|
-
const
|
|
36037
|
+
const result = await execBinaryAsync(vendor, probe.args, {
|
|
35729
36038
|
encoding: "utf-8",
|
|
35730
|
-
timeout: 5e3
|
|
35731
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
36039
|
+
timeout: 5e3
|
|
35732
36040
|
});
|
|
35733
|
-
|
|
35734
|
-
|
|
35735
|
-
|
|
35736
|
-
for (const item of parsed) {
|
|
35737
|
-
if (typeof item === "string") {
|
|
35738
|
-
models.push({ id: item });
|
|
35739
|
-
} else if (typeof item === "object" && item !== null && "id" in item && typeof item.id === "string") {
|
|
35740
|
-
const obj = item;
|
|
35741
|
-
const desc = { id: obj.id };
|
|
35742
|
-
if (typeof obj.displayName === "string") desc.displayName = obj.displayName;
|
|
35743
|
-
if (typeof obj.provider === "string") desc.provider = obj.provider;
|
|
35744
|
-
if (Array.isArray(obj.tags)) {
|
|
35745
|
-
desc.tags = obj.tags.filter((t) => typeof t === "string");
|
|
35746
|
-
}
|
|
35747
|
-
models.push(desc);
|
|
35748
|
-
}
|
|
35749
|
-
}
|
|
35750
|
-
return models.length > 0 ? models : null;
|
|
35751
|
-
} catch {
|
|
35752
|
-
return null;
|
|
36041
|
+
stdout = result.stdout;
|
|
36042
|
+
} catch (err) {
|
|
36043
|
+
return { models: null, reason: describeProbeFailure(vendor, probe.args, err) };
|
|
35753
36044
|
}
|
|
36045
|
+
const models = probe.parse(stdout);
|
|
36046
|
+
if (!models) {
|
|
36047
|
+
return {
|
|
36048
|
+
models: null,
|
|
36049
|
+
reason: `\`${vendor} ${probe.args.join(" ")}\` output did not contain any model identifiers`
|
|
36050
|
+
};
|
|
36051
|
+
}
|
|
36052
|
+
return { models };
|
|
35754
36053
|
}
|
|
35755
|
-
|
|
35756
|
-
|
|
35757
|
-
|
|
35758
|
-
|
|
35759
|
-
|
|
35760
|
-
|
|
35761
|
-
|
|
35762
|
-
|
|
36054
|
+
var SUCCESS_TTL_MS = 6e4;
|
|
36055
|
+
var FAILURE_TTL_MS = 1e4;
|
|
36056
|
+
var cache = /* @__PURE__ */ new Map();
|
|
36057
|
+
async function listModelsForVendor(vendor) {
|
|
36058
|
+
const cached = cache.get(vendor);
|
|
36059
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
36060
|
+
return cached.result;
|
|
36061
|
+
}
|
|
36062
|
+
const strategy = VENDOR_MODEL_STRATEGIES[vendor];
|
|
36063
|
+
if (!strategy) {
|
|
36064
|
+
throw new Error(`Unknown vendor: ${vendor}`);
|
|
36065
|
+
}
|
|
36066
|
+
let result;
|
|
36067
|
+
if ("unavailableReason" in strategy.native) {
|
|
36068
|
+
result = {
|
|
36069
|
+
vendor,
|
|
36070
|
+
source: "bundled",
|
|
36071
|
+
models: strategy.bundled,
|
|
36072
|
+
nativeUnavailableReason: strategy.native.unavailableReason
|
|
36073
|
+
};
|
|
36074
|
+
} else {
|
|
36075
|
+
const native = await tryNativeEnumeration(vendor, strategy.native);
|
|
36076
|
+
result = native.models ? { vendor, source: "native", models: native.models } : {
|
|
36077
|
+
vendor,
|
|
36078
|
+
source: "bundled",
|
|
36079
|
+
models: strategy.bundled,
|
|
36080
|
+
nativeUnavailableReason: native.reason
|
|
36081
|
+
};
|
|
35763
36082
|
}
|
|
35764
|
-
|
|
36083
|
+
const ttl = result.source === "native" ? SUCCESS_TTL_MS : FAILURE_TTL_MS;
|
|
36084
|
+
cache.set(vendor, { result, expiresAt: Date.now() + ttl });
|
|
36085
|
+
return result;
|
|
35765
36086
|
}
|
|
35766
36087
|
|
|
35767
36088
|
// src/server/routes/team.ts
|
|
36089
|
+
import { dirname as dirname10 } from "node:path";
|
|
35768
36090
|
function isReviewerInstanceArray(input) {
|
|
35769
36091
|
if (!Array.isArray(input)) return false;
|
|
35770
36092
|
for (const entry of input) {
|
|
@@ -35817,60 +36139,64 @@ function createTeamRouter(ocrDir) {
|
|
|
35817
36139
|
return;
|
|
35818
36140
|
}
|
|
35819
36141
|
try {
|
|
35820
|
-
|
|
36142
|
+
execBinary("ocr", ["team", "set", "--stdin"], {
|
|
35821
36143
|
input: JSON.stringify(body.team),
|
|
35822
36144
|
encoding: "utf-8",
|
|
35823
|
-
|
|
36145
|
+
// Run from the project root (parent of `.ocr`). `dirname` is
|
|
36146
|
+
// separator-correct on every platform — a prior `/\/\.ocr$/` regex
|
|
36147
|
+
// silently no-op'd on Windows (join builds the path with `\`), running
|
|
36148
|
+
// `ocr team set` inside the `.ocr` dir itself (blocker B2). Matches the
|
|
36149
|
+
// `dirname(ocrDir)` derivation used across the socket handlers.
|
|
36150
|
+
cwd: dirname10(ocrDir),
|
|
35824
36151
|
timeout: 1e4
|
|
35825
36152
|
});
|
|
35826
|
-
if (result.error) {
|
|
35827
|
-
res.status(500).json({
|
|
35828
|
-
error: "Failed to invoke ocr team set",
|
|
35829
|
-
detail: result.error.message
|
|
35830
|
-
});
|
|
35831
|
-
return;
|
|
35832
|
-
}
|
|
35833
|
-
if (result.status !== 0) {
|
|
35834
|
-
res.status(500).json({
|
|
35835
|
-
error: "ocr team set exited non-zero",
|
|
35836
|
-
stderr: result.stderr
|
|
35837
|
-
});
|
|
35838
|
-
return;
|
|
35839
|
-
}
|
|
35840
36153
|
res.json({ ok: true, team: body.team });
|
|
35841
36154
|
} catch (err) {
|
|
35842
36155
|
console.error("Failed to persist team:", err);
|
|
36156
|
+
const e = err;
|
|
35843
36157
|
res.status(500).json({
|
|
35844
36158
|
error: "Failed to persist team",
|
|
35845
|
-
detail: err instanceof Error ? err.message : String(err)
|
|
36159
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
36160
|
+
...typeof e.stderr === "string" && e.stderr ? { stderr: e.stderr } : {}
|
|
35846
36161
|
});
|
|
35847
36162
|
}
|
|
35848
36163
|
});
|
|
35849
36164
|
router.get("/models", (req, res) => {
|
|
35850
|
-
|
|
35851
|
-
|
|
35852
|
-
|
|
35853
|
-
|
|
35854
|
-
|
|
35855
|
-
|
|
35856
|
-
|
|
35857
|
-
|
|
35858
|
-
|
|
35859
|
-
|
|
35860
|
-
|
|
35861
|
-
|
|
35862
|
-
|
|
35863
|
-
|
|
35864
|
-
|
|
35865
|
-
|
|
35866
|
-
|
|
35867
|
-
|
|
35868
|
-
|
|
35869
|
-
|
|
35870
|
-
|
|
35871
|
-
|
|
35872
|
-
|
|
35873
|
-
|
|
36165
|
+
void (async () => {
|
|
36166
|
+
try {
|
|
36167
|
+
const raw = req.query["vendor"];
|
|
36168
|
+
if (raw !== void 0 && typeof raw !== "string") {
|
|
36169
|
+
res.status(400).json({ error: "vendor must be a single string" });
|
|
36170
|
+
return;
|
|
36171
|
+
}
|
|
36172
|
+
const requested = raw?.toLowerCase();
|
|
36173
|
+
let vendor;
|
|
36174
|
+
if (requested && isModelVendor(requested)) {
|
|
36175
|
+
vendor = requested;
|
|
36176
|
+
} else if (!requested || requested === "auto") {
|
|
36177
|
+
vendor = await detectActiveVendor();
|
|
36178
|
+
} else {
|
|
36179
|
+
res.status(400).json({
|
|
36180
|
+
error: `Unknown vendor: ${requested}. Supported: ${SUPPORTED_VENDORS.join(", ")}`
|
|
36181
|
+
});
|
|
36182
|
+
return;
|
|
36183
|
+
}
|
|
36184
|
+
if (!vendor) {
|
|
36185
|
+
res.json({ vendor: null, source: null, models: [] });
|
|
36186
|
+
return;
|
|
36187
|
+
}
|
|
36188
|
+
const result = await listModelsForVendor(vendor);
|
|
36189
|
+
res.json(result);
|
|
36190
|
+
} catch (err) {
|
|
36191
|
+
console.error("Failed to list models:", err);
|
|
36192
|
+
if (!res.headersSent) {
|
|
36193
|
+
res.status(500).json({
|
|
36194
|
+
error: "Failed to list models",
|
|
36195
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
36196
|
+
});
|
|
36197
|
+
}
|
|
36198
|
+
}
|
|
36199
|
+
})();
|
|
35874
36200
|
});
|
|
35875
36201
|
return router;
|
|
35876
36202
|
}
|
|
@@ -35948,6 +36274,7 @@ function recoverFromEventsJsonl(ocrDir, db, workflowId) {
|
|
|
35948
36274
|
function createSessionCaptureService(deps) {
|
|
35949
36275
|
const { db, ocrDir, aiCliService } = deps;
|
|
35950
36276
|
const driftLoggedFor = /* @__PURE__ */ new Set();
|
|
36277
|
+
const rejectLoggedFor = /* @__PURE__ */ new Set();
|
|
35951
36278
|
function readBoundSessionId(executionId) {
|
|
35952
36279
|
const result = db.exec(
|
|
35953
36280
|
"SELECT vendor_session_id FROM command_executions WHERE id = ?",
|
|
@@ -35958,6 +36285,15 @@ function createSessionCaptureService(deps) {
|
|
|
35958
36285
|
}
|
|
35959
36286
|
function recordSessionId(executionId, vendorSessionId) {
|
|
35960
36287
|
try {
|
|
36288
|
+
if (!isSafeVendorSessionId(vendorSessionId)) {
|
|
36289
|
+
if (!rejectLoggedFor.has(executionId)) {
|
|
36290
|
+
rejectLoggedFor.add(executionId);
|
|
36291
|
+
console.warn(
|
|
36292
|
+
`[session-capture] rejecting implausible vendor session id on execution ${executionId}: ${JSON.stringify(vendorSessionId)} (allowed: letters/digits plus . _ : - , max 256 chars). Not recorded \u2014 resume for this execution is unavailable.`
|
|
36293
|
+
);
|
|
36294
|
+
}
|
|
36295
|
+
return;
|
|
36296
|
+
}
|
|
35961
36297
|
const existing = readBoundSessionId(executionId);
|
|
35962
36298
|
if (existing === vendorSessionId) return;
|
|
35963
36299
|
if (existing) {
|
|
@@ -36137,7 +36473,7 @@ function buildDiagnostics(input) {
|
|
|
36137
36473
|
|
|
36138
36474
|
// src/server/services/filesystem-sync.ts
|
|
36139
36475
|
import { readdirSync as readdirSync2, readFileSync as readFileSync9, statSync as statSync3, existsSync as existsSync13 } from "node:fs";
|
|
36140
|
-
import { join as
|
|
36476
|
+
import { join as join17, basename as basename3, dirname as dirname11, relative } from "node:path";
|
|
36141
36477
|
import { watch as watch2 } from "chokidar";
|
|
36142
36478
|
|
|
36143
36479
|
// src/server/services/parsers/reviewer-parser.ts
|
|
@@ -36232,21 +36568,12 @@ var VERDICT_RE = /^\*?\*?\s*(?:##\s*)?Verdict\s*\*?\*?\s*:?\s*\*?\*?\s*(.*)/im;
|
|
|
36232
36568
|
var BLOCKERS_RE = /^\*\*Blockers?\*\*\s*:?\s*(\d+)/im;
|
|
36233
36569
|
var SHOULD_FIX_RE = /^\*\*Should\s*Fix\*\*\s*:?\s*(\d+)/im;
|
|
36234
36570
|
var SUGGESTIONS_RE = /^\*\*Suggestions?\*\*\s*:?\s*(\d+)/im;
|
|
36235
|
-
|
|
36236
|
-
"REQUEST CHANGES",
|
|
36237
|
-
"CHANGES REQUESTED",
|
|
36238
|
-
"NEEDS DISCUSSION",
|
|
36239
|
-
"NEEDS WORK",
|
|
36240
|
-
"APPROVED",
|
|
36241
|
-
"APPROVE",
|
|
36242
|
-
"LGTM",
|
|
36243
|
-
"BLOCK",
|
|
36244
|
-
"REJECT"
|
|
36245
|
-
];
|
|
36246
|
-
function normalizeVerdict(raw) {
|
|
36571
|
+
function extractVerdictLabel(raw) {
|
|
36247
36572
|
const cleaned = raw.trim().replace(/^\*+|\*+$/g, "").trim();
|
|
36573
|
+
const canonical = normalizeVerdict(cleaned);
|
|
36574
|
+
if (canonical) return canonical;
|
|
36248
36575
|
const upper = cleaned.toUpperCase();
|
|
36249
|
-
for (const verdict of
|
|
36576
|
+
for (const verdict of CANONICAL_VERDICTS) {
|
|
36250
36577
|
if (upper.startsWith(verdict)) return verdict;
|
|
36251
36578
|
}
|
|
36252
36579
|
const truncated = cleaned.split(/\s+[—:.]\s+|\n/, 1)[0] ?? cleaned;
|
|
@@ -36258,7 +36585,7 @@ function parseFinalMd(content) {
|
|
|
36258
36585
|
if (verdictMatch) {
|
|
36259
36586
|
const captured = (verdictMatch[1] ?? "").trim();
|
|
36260
36587
|
if (captured.length > 0) {
|
|
36261
|
-
verdict =
|
|
36588
|
+
verdict = extractVerdictLabel(captured);
|
|
36262
36589
|
}
|
|
36263
36590
|
}
|
|
36264
36591
|
const blockerMatch = content.match(BLOCKERS_RE);
|
|
@@ -36345,13 +36672,13 @@ var FilesystemSync = class {
|
|
|
36345
36672
|
for (const entry of entries) {
|
|
36346
36673
|
if (!entry.isDirectory()) continue;
|
|
36347
36674
|
const sessionId = entry.name;
|
|
36348
|
-
const sessionDir =
|
|
36675
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
36349
36676
|
this.syncSession(sessionId, sessionDir);
|
|
36350
36677
|
}
|
|
36351
36678
|
}
|
|
36352
36679
|
syncSession(sessionId, sessionDir) {
|
|
36353
36680
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
36354
|
-
const roundsDir =
|
|
36681
|
+
const roundsDir = join17(sessionDir, "rounds");
|
|
36355
36682
|
if (existsSync13(roundsDir)) {
|
|
36356
36683
|
const rounds = readdirSync2(roundsDir, { withFileTypes: true });
|
|
36357
36684
|
for (const roundEntry of rounds) {
|
|
@@ -36359,34 +36686,34 @@ var FilesystemSync = class {
|
|
|
36359
36686
|
const roundMatch = roundEntry.name.match(/^round-(\d+)$/);
|
|
36360
36687
|
if (!roundMatch) continue;
|
|
36361
36688
|
const roundNumber = parseInt(roundMatch[1] ?? "0", 10);
|
|
36362
|
-
const roundDir =
|
|
36363
|
-
const reviewsDir =
|
|
36689
|
+
const roundDir = join17(roundsDir, roundEntry.name);
|
|
36690
|
+
const reviewsDir = join17(roundDir, "reviews");
|
|
36364
36691
|
if (existsSync13(reviewsDir)) {
|
|
36365
36692
|
const reviewFiles = readdirSync2(reviewsDir).filter((f) => f.endsWith(".md"));
|
|
36366
36693
|
for (const reviewFile of reviewFiles) {
|
|
36367
|
-
const filePath =
|
|
36694
|
+
const filePath = join17(reviewsDir, reviewFile);
|
|
36368
36695
|
this.processReviewerOutput(sessionId, roundNumber, filePath, reviewFile);
|
|
36369
36696
|
}
|
|
36370
36697
|
}
|
|
36371
|
-
const roundMetaPath =
|
|
36698
|
+
const roundMetaPath = join17(roundDir, "round-meta.json");
|
|
36372
36699
|
if (existsSync13(roundMetaPath)) {
|
|
36373
36700
|
this.processRoundMeta(sessionId, roundNumber, roundMetaPath);
|
|
36374
36701
|
}
|
|
36375
|
-
const finalPath =
|
|
36702
|
+
const finalPath = join17(roundDir, "final.md");
|
|
36376
36703
|
if (existsSync13(finalPath)) {
|
|
36377
36704
|
this.processFinalMd(sessionId, roundNumber, finalPath);
|
|
36378
36705
|
}
|
|
36379
|
-
const finalHumanPath =
|
|
36706
|
+
const finalHumanPath = join17(roundDir, "final-human.md");
|
|
36380
36707
|
if (existsSync13(finalHumanPath)) {
|
|
36381
36708
|
this.processGenericArtifact(sessionId, "final-human", finalHumanPath, roundNumber);
|
|
36382
36709
|
}
|
|
36383
|
-
const discoursePath =
|
|
36710
|
+
const discoursePath = join17(roundDir, "discourse.md");
|
|
36384
36711
|
if (existsSync13(discoursePath)) {
|
|
36385
36712
|
this.processGenericArtifact(sessionId, "discourse", discoursePath, roundNumber);
|
|
36386
36713
|
}
|
|
36387
36714
|
}
|
|
36388
36715
|
}
|
|
36389
|
-
const mapDir =
|
|
36716
|
+
const mapDir = join17(sessionDir, "map", "runs");
|
|
36390
36717
|
if (existsSync13(mapDir)) {
|
|
36391
36718
|
const runs = readdirSync2(mapDir, { withFileTypes: true });
|
|
36392
36719
|
for (const runEntry of runs) {
|
|
@@ -36394,12 +36721,12 @@ var FilesystemSync = class {
|
|
|
36394
36721
|
const runMatch = runEntry.name.match(/^run-(\d+)$/);
|
|
36395
36722
|
if (!runMatch) continue;
|
|
36396
36723
|
const runNumber = parseInt(runMatch[1] ?? "0", 10);
|
|
36397
|
-
const runDir =
|
|
36398
|
-
const mapMetaPath =
|
|
36724
|
+
const runDir = join17(mapDir, runEntry.name);
|
|
36725
|
+
const mapMetaPath = join17(runDir, "map-meta.json");
|
|
36399
36726
|
if (existsSync13(mapMetaPath)) {
|
|
36400
36727
|
this.processMapMeta(sessionId, runNumber, mapMetaPath);
|
|
36401
36728
|
}
|
|
36402
|
-
const mapPath =
|
|
36729
|
+
const mapPath = join17(runDir, "map.md");
|
|
36403
36730
|
if (existsSync13(mapPath)) {
|
|
36404
36731
|
this.processMapMd(sessionId, runNumber, mapPath);
|
|
36405
36732
|
}
|
|
@@ -36409,7 +36736,7 @@ var FilesystemSync = class {
|
|
|
36409
36736
|
["requirements-mapping.md", "requirements-mapping"]
|
|
36410
36737
|
];
|
|
36411
36738
|
for (const [fileName, artifactType] of mapArtifacts) {
|
|
36412
|
-
const filePath =
|
|
36739
|
+
const filePath = join17(runDir, fileName);
|
|
36413
36740
|
if (existsSync13(filePath)) {
|
|
36414
36741
|
this.processGenericArtifact(sessionId, artifactType, filePath, void 0, runNumber);
|
|
36415
36742
|
}
|
|
@@ -36421,68 +36748,113 @@ var FilesystemSync = class {
|
|
|
36421
36748
|
["discovered-standards.md", "discovered-standards"]
|
|
36422
36749
|
];
|
|
36423
36750
|
for (const [fileName, artifactType] of sessionArtifacts) {
|
|
36424
|
-
const filePath =
|
|
36751
|
+
const filePath = join17(sessionDir, fileName);
|
|
36425
36752
|
if (existsSync13(filePath)) {
|
|
36426
36753
|
this.processGenericArtifact(sessionId, artifactType, filePath);
|
|
36427
36754
|
}
|
|
36428
36755
|
}
|
|
36429
36756
|
}
|
|
36757
|
+
// ── Terminal-completion evidence (defect D1) ──
|
|
36758
|
+
//
|
|
36759
|
+
// The dashboard read/sync path NEVER originates terminal workflow completion.
|
|
36760
|
+
// A `final.md` / `map.md` artifact on disk is evidence of the **synthesis**
|
|
36761
|
+
// phase only; terminal completion is the CLI's to declare and is recognized
|
|
36762
|
+
// solely from the CLI-produced evidence — a `round_completed` / `map_completed`
|
|
36763
|
+
// orchestration event. Closing on artifact presence alone is the fabrication
|
|
36764
|
+
// these helpers exist to prevent.
|
|
36765
|
+
/** Whether the CLI has recorded a `round_completed` event for this round. */
|
|
36766
|
+
hasRoundCompletedEvent(sessionId, round) {
|
|
36767
|
+
return queryFirst(
|
|
36768
|
+
this.db,
|
|
36769
|
+
`SELECT 1 FROM orchestration_events
|
|
36770
|
+
WHERE session_id = ? AND event_type = 'round_completed' AND round = ? LIMIT 1`,
|
|
36771
|
+
[sessionId, round]
|
|
36772
|
+
) != null;
|
|
36773
|
+
}
|
|
36774
|
+
/** Whether the CLI has recorded a `map_completed` event for this map run. */
|
|
36775
|
+
hasMapCompletedEvent(sessionId, mapRun) {
|
|
36776
|
+
return queryFirst(
|
|
36777
|
+
this.db,
|
|
36778
|
+
`SELECT 1 FROM orchestration_events
|
|
36779
|
+
WHERE session_id = ? AND event_type = 'map_completed' AND round = ? LIMIT 1`,
|
|
36780
|
+
[sessionId, mapRun]
|
|
36781
|
+
) != null;
|
|
36782
|
+
}
|
|
36783
|
+
/**
|
|
36784
|
+
* Full CLI terminal evidence for a review round: a `round_completed` event AND
|
|
36785
|
+
* a validated `round-meta.json` on disk. Used by the backfill reconciler to
|
|
36786
|
+
* decide whether a discovered-on-disk session is genuinely complete.
|
|
36787
|
+
*/
|
|
36788
|
+
hasTerminalRoundEvidence(sessionId, round, roundDir) {
|
|
36789
|
+
return existsSync13(join17(roundDir, "round-meta.json")) && this.hasRoundCompletedEvent(sessionId, round);
|
|
36790
|
+
}
|
|
36791
|
+
/** Full CLI terminal evidence for a map run: a `map_completed` event AND a
|
|
36792
|
+
* validated `map-meta.json` on disk. */
|
|
36793
|
+
hasTerminalMapEvidence(sessionId, mapRun, runDir) {
|
|
36794
|
+
return existsSync13(join17(runDir, "map-meta.json")) && this.hasMapCompletedEvent(sessionId, mapRun);
|
|
36795
|
+
}
|
|
36430
36796
|
// ── Session Backfill ──
|
|
36431
36797
|
ensureSessionRow(sessionId, sessionDir) {
|
|
36432
36798
|
const branchMatch = sessionId.match(/^\d{4}-\d{2}-\d{2}-(.+)$/);
|
|
36433
36799
|
const branch = branchMatch?.[1] ?? "unknown";
|
|
36434
|
-
const hasRoundsDir = existsSync13(
|
|
36435
|
-
const hasMapDir = existsSync13(
|
|
36800
|
+
const hasRoundsDir = existsSync13(join17(sessionDir, "rounds"));
|
|
36801
|
+
const hasMapDir = existsSync13(join17(sessionDir, "map"));
|
|
36436
36802
|
const workflowType = hasMapDir && !hasRoundsDir ? "map" : "review";
|
|
36437
36803
|
let currentRound = 1;
|
|
36438
36804
|
if (hasRoundsDir) {
|
|
36439
|
-
const roundDirs = readdirSync2(
|
|
36805
|
+
const roundDirs = readdirSync2(join17(sessionDir, "rounds")).filter((d) => d.match(/^round-\d+$/));
|
|
36440
36806
|
currentRound = Math.max(1, roundDirs.length);
|
|
36441
36807
|
}
|
|
36442
36808
|
let currentMapRun = 1;
|
|
36443
|
-
const mapRunsDir =
|
|
36809
|
+
const mapRunsDir = join17(sessionDir, "map", "runs");
|
|
36444
36810
|
if (existsSync13(mapRunsDir)) {
|
|
36445
36811
|
const runDirs = readdirSync2(mapRunsDir).filter((d) => d.match(/^run-\d+$/));
|
|
36446
36812
|
currentMapRun = Math.max(1, runDirs.length);
|
|
36447
36813
|
}
|
|
36448
36814
|
let phase = "context";
|
|
36449
36815
|
let phaseNumber = 1;
|
|
36450
|
-
let status = "
|
|
36816
|
+
let status = "active";
|
|
36451
36817
|
if (workflowType === "review" && hasRoundsDir) {
|
|
36452
|
-
const roundDir =
|
|
36453
|
-
if (existsSync13(
|
|
36818
|
+
const roundDir = join17(sessionDir, "rounds", `round-${currentRound}`);
|
|
36819
|
+
if (existsSync13(join17(roundDir, "final.md")) && this.hasTerminalRoundEvidence(sessionId, currentRound, roundDir)) {
|
|
36454
36820
|
phase = "complete";
|
|
36455
36821
|
phaseNumber = 8;
|
|
36456
36822
|
status = "closed";
|
|
36457
|
-
} else if (existsSync13(
|
|
36823
|
+
} else if (existsSync13(join17(roundDir, "final.md"))) {
|
|
36458
36824
|
phase = "synthesis";
|
|
36459
36825
|
phaseNumber = 7;
|
|
36460
|
-
} else if (existsSync13(
|
|
36826
|
+
} else if (existsSync13(join17(roundDir, "discourse.md"))) {
|
|
36827
|
+
phase = "synthesis";
|
|
36828
|
+
phaseNumber = 7;
|
|
36829
|
+
} else if (existsSync13(join17(roundDir, "reviews")) && readdirSync2(join17(roundDir, "reviews")).filter((f) => f.endsWith(".md")).length > 0) {
|
|
36461
36830
|
phase = "reviews";
|
|
36462
36831
|
phaseNumber = 4;
|
|
36463
|
-
} else if (existsSync13(
|
|
36832
|
+
} else if (existsSync13(join17(sessionDir, "context.md"))) {
|
|
36464
36833
|
phase = "analysis";
|
|
36465
36834
|
phaseNumber = 3;
|
|
36466
|
-
} else if (existsSync13(
|
|
36835
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36467
36836
|
phase = "change-context";
|
|
36468
36837
|
phaseNumber = 2;
|
|
36469
36838
|
}
|
|
36470
36839
|
} else if (workflowType === "map" && hasMapDir) {
|
|
36471
|
-
const runDir =
|
|
36472
|
-
if (existsSync13(
|
|
36840
|
+
const runDir = join17(mapRunsDir, `run-${currentMapRun}`);
|
|
36841
|
+
if (existsSync13(join17(runDir, "map.md")) && this.hasTerminalMapEvidence(sessionId, currentMapRun, runDir)) {
|
|
36473
36842
|
phase = "complete";
|
|
36474
36843
|
phaseNumber = 6;
|
|
36475
36844
|
status = "closed";
|
|
36476
|
-
} else if (existsSync13(
|
|
36845
|
+
} else if (existsSync13(join17(runDir, "map.md"))) {
|
|
36846
|
+
phase = "synthesis";
|
|
36847
|
+
phaseNumber = 5;
|
|
36848
|
+
} else if (existsSync13(join17(runDir, "requirements-mapping.md"))) {
|
|
36477
36849
|
phase = "synthesis";
|
|
36478
36850
|
phaseNumber = 5;
|
|
36479
|
-
} else if (existsSync13(
|
|
36851
|
+
} else if (existsSync13(join17(runDir, "flow-analysis.md"))) {
|
|
36480
36852
|
phase = "requirements-mapping";
|
|
36481
36853
|
phaseNumber = 4;
|
|
36482
|
-
} else if (existsSync13(
|
|
36854
|
+
} else if (existsSync13(join17(runDir, "topology.md"))) {
|
|
36483
36855
|
phase = "flow-analysis";
|
|
36484
36856
|
phaseNumber = 3;
|
|
36485
|
-
} else if (existsSync13(
|
|
36857
|
+
} else if (existsSync13(join17(sessionDir, "discovered-standards.md"))) {
|
|
36486
36858
|
phase = "topology";
|
|
36487
36859
|
phaseNumber = 2;
|
|
36488
36860
|
}
|
|
@@ -36537,7 +36909,7 @@ var FilesystemSync = class {
|
|
|
36537
36909
|
try {
|
|
36538
36910
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
36539
36911
|
if (entry.isDirectory()) {
|
|
36540
|
-
if (this.hasArtifacts(
|
|
36912
|
+
if (this.hasArtifacts(join17(dir, entry.name))) return true;
|
|
36541
36913
|
} else if (/\.(md|json)$/.test(entry.name)) {
|
|
36542
36914
|
return true;
|
|
36543
36915
|
}
|
|
@@ -36683,7 +37055,7 @@ var FilesystemSync = class {
|
|
|
36683
37055
|
"SELECT current_phase, phase_number, workflow_type FROM sessions WHERE id = ?",
|
|
36684
37056
|
[sessionId]
|
|
36685
37057
|
);
|
|
36686
|
-
if (session && session["workflow_type"] === "map" && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
37058
|
+
if (session && session["workflow_type"] === "map" && this.hasMapCompletedEvent(sessionId, runNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 6)) {
|
|
36687
37059
|
commitReasonClose(
|
|
36688
37060
|
this.db,
|
|
36689
37061
|
sessionId,
|
|
@@ -36841,13 +37213,8 @@ var FilesystemSync = class {
|
|
|
36841
37213
|
console.error(`[FilesystemSync] Invalid round-meta.json at ${filePath}`);
|
|
36842
37214
|
return;
|
|
36843
37215
|
}
|
|
36844
|
-
const
|
|
36845
|
-
const
|
|
36846
|
-
const blockerCount = sc?.blockers ?? allFindings.filter((f) => f.category === "blocker").length;
|
|
36847
|
-
const shouldFixCount = sc?.should_fix ?? allFindings.filter((f) => f.category === "should_fix").length;
|
|
36848
|
-
const suggestionCount = sc?.suggestions ?? allFindings.filter((f) => f.category === "suggestion").length;
|
|
36849
|
-
const reviewerCount = meta.reviewers.length;
|
|
36850
|
-
const totalFindingCount = allFindings.length;
|
|
37216
|
+
const normalizedVerdict = normalizeVerdict(meta.verdict) ?? meta.verdict;
|
|
37217
|
+
const { blockerCount, shouldFixCount, suggestionCount, reviewerCount, totalFindingCount } = resolveRoundCounts(meta);
|
|
36851
37218
|
this.db.run("BEGIN TRANSACTION");
|
|
36852
37219
|
try {
|
|
36853
37220
|
this.db.run(
|
|
@@ -36856,7 +37223,7 @@ var FilesystemSync = class {
|
|
|
36856
37223
|
reviewer_count = ?, total_finding_count = ?, source = 'orchestrator', parsed_at = ?
|
|
36857
37224
|
WHERE session_id = ? AND round_number = ?`,
|
|
36858
37225
|
[
|
|
36859
|
-
|
|
37226
|
+
normalizedVerdict,
|
|
36860
37227
|
blockerCount,
|
|
36861
37228
|
suggestionCount,
|
|
36862
37229
|
shouldFixCount,
|
|
@@ -36877,12 +37244,12 @@ var FilesystemSync = class {
|
|
|
36877
37244
|
this.db.run("COMMIT");
|
|
36878
37245
|
return;
|
|
36879
37246
|
}
|
|
36880
|
-
const roundDir =
|
|
37247
|
+
const roundDir = dirname11(filePath);
|
|
36881
37248
|
for (const reviewer of meta.reviewers) {
|
|
36882
37249
|
const reviewerType = reviewer.type ?? "unknown";
|
|
36883
37250
|
const instanceNumber = reviewer.instance ?? 1;
|
|
36884
37251
|
const findings = reviewer.findings ?? [];
|
|
36885
|
-
const reviewerMdPath =
|
|
37252
|
+
const reviewerMdPath = join17(roundDir, "reviews", `${reviewerType}-${instanceNumber}.md`);
|
|
36886
37253
|
this.db.run(
|
|
36887
37254
|
`INSERT OR REPLACE INTO reviewer_outputs (round_id, reviewer_type, instance_number, file_path, finding_count, parsed_at)
|
|
36888
37255
|
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
@@ -36956,7 +37323,7 @@ var FilesystemSync = class {
|
|
|
36956
37323
|
this.io?.to(`session:${sessionId}`).emit("round:updated", {
|
|
36957
37324
|
sessionId,
|
|
36958
37325
|
roundNumber,
|
|
36959
|
-
verdict:
|
|
37326
|
+
verdict: normalizedVerdict,
|
|
36960
37327
|
blockerCount,
|
|
36961
37328
|
shouldFixCount,
|
|
36962
37329
|
suggestionCount,
|
|
@@ -37117,11 +37484,12 @@ var FilesystemSync = class {
|
|
|
37117
37484
|
);
|
|
37118
37485
|
} else {
|
|
37119
37486
|
const parsed = parseFinalMd(content);
|
|
37487
|
+
const parsedVerdict = parsed.verdict ? normalizeVerdict(parsed.verdict) ?? parsed.verdict : parsed.verdict;
|
|
37120
37488
|
this.db.run(
|
|
37121
37489
|
`UPDATE review_rounds SET verdict = ?, blocker_count = ?, suggestion_count = ?, should_fix_count = ?, final_md_path = ?, parsed_at = ?, source = 'parser'
|
|
37122
37490
|
WHERE session_id = ? AND round_number = ?`,
|
|
37123
37491
|
[
|
|
37124
|
-
|
|
37492
|
+
parsedVerdict,
|
|
37125
37493
|
parsed.blockerCount,
|
|
37126
37494
|
parsed.suggestionCount,
|
|
37127
37495
|
parsed.shouldFixCount,
|
|
@@ -37151,7 +37519,7 @@ var FilesystemSync = class {
|
|
|
37151
37519
|
"SELECT current_phase, phase_number, status FROM sessions WHERE id = ?",
|
|
37152
37520
|
[sessionId]
|
|
37153
37521
|
);
|
|
37154
|
-
if (session && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37522
|
+
if (session && this.hasRoundCompletedEvent(sessionId, roundNumber) && (session["current_phase"] !== "complete" || session["phase_number"] < 8)) {
|
|
37155
37523
|
commitReasonClose(
|
|
37156
37524
|
this.db,
|
|
37157
37525
|
sessionId,
|
|
@@ -37245,7 +37613,7 @@ var FilesystemSync = class {
|
|
|
37245
37613
|
const parts = relFromSessions.split("/");
|
|
37246
37614
|
const sessionId = parts[0];
|
|
37247
37615
|
if (!sessionId) return;
|
|
37248
|
-
const sessionDir =
|
|
37616
|
+
const sessionDir = join17(this.sessionsDir, sessionId);
|
|
37249
37617
|
this.ensureSessionRow(sessionId, sessionDir);
|
|
37250
37618
|
const fileName = basename3(filePath);
|
|
37251
37619
|
const reviewerMatch = relFromSessions.match(/rounds\/round-(\d+)\/reviews\/(.+\.md)$/);
|
|
@@ -37318,7 +37686,7 @@ var FilesystemSync = class {
|
|
|
37318
37686
|
|
|
37319
37687
|
// src/server/services/db-sync-watcher.ts
|
|
37320
37688
|
import { existsSync as existsSync14 } from "node:fs";
|
|
37321
|
-
import { dirname as
|
|
37689
|
+
import { dirname as dirname12, basename as basename4 } from "node:path";
|
|
37322
37690
|
import { watch as watch3 } from "chokidar";
|
|
37323
37691
|
function col(row, key) {
|
|
37324
37692
|
return row[key] ?? null;
|
|
@@ -37360,7 +37728,7 @@ var DbSyncWatcher = class {
|
|
|
37360
37728
|
/** Start watching the DB file (and its WAL sidecar) for external writes. */
|
|
37361
37729
|
startWatching() {
|
|
37362
37730
|
if (!existsSync14(this.dbFilePath)) return;
|
|
37363
|
-
const watchDir =
|
|
37731
|
+
const watchDir = dirname12(this.dbFilePath);
|
|
37364
37732
|
const dbFile = basename4(this.dbFilePath);
|
|
37365
37733
|
const walFile = `${dbFile}-wal`;
|
|
37366
37734
|
this.watcher = watch3(watchDir, {
|
|
@@ -37622,20 +37990,20 @@ function commandFingerprint(row) {
|
|
|
37622
37990
|
}
|
|
37623
37991
|
|
|
37624
37992
|
// src/server/socket/chat-handler.ts
|
|
37625
|
-
import { dirname as
|
|
37993
|
+
import { dirname as dirname13 } from "node:path";
|
|
37626
37994
|
|
|
37627
37995
|
// src/server/services/chat-context.ts
|
|
37628
37996
|
import { readFileSync as readFileSync10, readdirSync as readdirSync3, existsSync as existsSync15 } from "node:fs";
|
|
37629
|
-
import { join as
|
|
37997
|
+
import { join as join18 } from "node:path";
|
|
37630
37998
|
function buildChatContext(ocrDir, target) {
|
|
37631
|
-
const sessionsDir =
|
|
37999
|
+
const sessionsDir = join18(ocrDir, "sessions");
|
|
37632
38000
|
if (target.type === "map_run") {
|
|
37633
38001
|
return buildMapRunContext(sessionsDir, target.sessionId, target.runNumber);
|
|
37634
38002
|
}
|
|
37635
38003
|
return buildReviewRoundContext(sessionsDir, target.sessionId, target.roundNumber);
|
|
37636
38004
|
}
|
|
37637
38005
|
function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
37638
|
-
const mapPath =
|
|
38006
|
+
const mapPath = join18(
|
|
37639
38007
|
sessionsDir,
|
|
37640
38008
|
sessionId,
|
|
37641
38009
|
"map",
|
|
@@ -37662,9 +38030,9 @@ function buildMapRunContext(sessionsDir, sessionId, runNumber) {
|
|
|
37662
38030
|
return parts.join("\n");
|
|
37663
38031
|
}
|
|
37664
38032
|
function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
37665
|
-
const roundDir =
|
|
37666
|
-
const finalPath =
|
|
37667
|
-
const reviewersDir =
|
|
38033
|
+
const roundDir = join18(sessionsDir, sessionId, "rounds", `round-${roundNumber}`);
|
|
38034
|
+
const finalPath = join18(roundDir, "final.md");
|
|
38035
|
+
const reviewersDir = join18(roundDir, "reviews");
|
|
37668
38036
|
const parts = [
|
|
37669
38037
|
`You are an expert code reviewer assisting with a code review session.`,
|
|
37670
38038
|
`You are looking at review round #${roundNumber} for session "${sessionId}".`,
|
|
@@ -37681,7 +38049,7 @@ function buildReviewRoundContext(sessionsDir, sessionId, roundNumber) {
|
|
|
37681
38049
|
if (existsSync15(reviewersDir)) {
|
|
37682
38050
|
const files = readdirSync3(reviewersDir).filter((f) => f.endsWith(".md")).sort();
|
|
37683
38051
|
for (const file of files) {
|
|
37684
|
-
const content = readFileSync10(
|
|
38052
|
+
const content = readFileSync10(join18(reviewersDir, file), "utf-8");
|
|
37685
38053
|
const reviewerName = file.replace(/\.md$/, "");
|
|
37686
38054
|
parts.push("");
|
|
37687
38055
|
parts.push(`<reviewer name="${reviewerName}">`);
|
|
@@ -37745,7 +38113,7 @@ function startTrackedExecution(io2, db, ocrDir, command, args = []) {
|
|
|
37745
38113
|
db.run(
|
|
37746
38114
|
`UPDATE command_executions
|
|
37747
38115
|
SET exit_code = ?, finished_at = ?, output = ?, pid = NULL
|
|
37748
|
-
WHERE id =
|
|
38116
|
+
WHERE id = ? AND finished_at IS NULL`,
|
|
37749
38117
|
[exitCode, finishedAt, outputBuffer, executionId]
|
|
37750
38118
|
);
|
|
37751
38119
|
appendCommandLog(ocrDir, {
|
|
@@ -37835,7 +38203,7 @@ User: ${message}`;
|
|
|
37835
38203
|
});
|
|
37836
38204
|
return;
|
|
37837
38205
|
}
|
|
37838
|
-
const repoRoot =
|
|
38206
|
+
const repoRoot = dirname13(ocrDir);
|
|
37839
38207
|
const spawnResult = adapter.spawn({
|
|
37840
38208
|
prompt,
|
|
37841
38209
|
cwd: repoRoot,
|
|
@@ -38011,13 +38379,13 @@ function cleanupAllChats() {
|
|
|
38011
38379
|
}
|
|
38012
38380
|
|
|
38013
38381
|
// src/server/socket/post-handler.ts
|
|
38014
|
-
import { existsSync as existsSync16, mkdirSync as
|
|
38382
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync11, unlinkSync as unlinkSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
38015
38383
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
38016
|
-
import { join as
|
|
38384
|
+
import { join as join19, dirname as dirname14, isAbsolute as isAbsolute2 } from "node:path";
|
|
38017
38385
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
38018
38386
|
function resolveSessionDir2(sessionDir, ocrDir) {
|
|
38019
38387
|
if (isAbsolute2(sessionDir)) return sessionDir;
|
|
38020
|
-
return
|
|
38388
|
+
return join19(dirname14(ocrDir), sessionDir);
|
|
38021
38389
|
}
|
|
38022
38390
|
var BRANCH_PREFIXES = [
|
|
38023
38391
|
"feat",
|
|
@@ -38086,7 +38454,7 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38086
38454
|
return;
|
|
38087
38455
|
}
|
|
38088
38456
|
const branch = session.branch;
|
|
38089
|
-
const repoRoot =
|
|
38457
|
+
const repoRoot = dirname14(ocrDir);
|
|
38090
38458
|
try {
|
|
38091
38459
|
await execBinaryAsync("gh", ["auth", "status"], { env: cleanEnv(), cwd: repoRoot, encoding: "utf-8" });
|
|
38092
38460
|
} catch {
|
|
@@ -38187,16 +38555,16 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38187
38555
|
socket.emit("post:error", { error: "Session not found" });
|
|
38188
38556
|
return;
|
|
38189
38557
|
}
|
|
38190
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38191
|
-
const roundDir =
|
|
38192
|
-
const finalPath =
|
|
38558
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38559
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38560
|
+
const finalPath = join19(roundDir, "final.md");
|
|
38193
38561
|
if (!existsSync16(finalPath)) {
|
|
38194
38562
|
socket.emit("post:error", { error: "final.md not found for this round" });
|
|
38195
38563
|
return;
|
|
38196
38564
|
}
|
|
38197
|
-
const humanReviewPath =
|
|
38198
|
-
const repoRoot =
|
|
38199
|
-
const commandMdPath =
|
|
38565
|
+
const humanReviewPath = join19(roundDir, "final-human.md");
|
|
38566
|
+
const repoRoot = dirname14(ocrDir);
|
|
38567
|
+
const commandMdPath = join19(ocrDir, "commands", "translate-review-to-single-human.md");
|
|
38200
38568
|
let commandContent;
|
|
38201
38569
|
try {
|
|
38202
38570
|
commandContent = readFileSync11(commandMdPath, "utf-8");
|
|
@@ -38356,10 +38724,10 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38356
38724
|
socket.emit("post:save-result", { success: false, error: "Session not found" });
|
|
38357
38725
|
return;
|
|
38358
38726
|
}
|
|
38359
|
-
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) :
|
|
38360
|
-
const roundDir =
|
|
38361
|
-
|
|
38362
|
-
const filePath =
|
|
38727
|
+
const sessionDir = session.session_dir ? resolveSessionDir2(session.session_dir, ocrDir) : join19(ocrDir, "sessions", sessionId);
|
|
38728
|
+
const roundDir = join19(sessionDir, "rounds", `round-${roundNumber}`);
|
|
38729
|
+
mkdirSync7(roundDir, { recursive: true });
|
|
38730
|
+
const filePath = join19(roundDir, "final-human.md");
|
|
38363
38731
|
writeFileSync5(filePath, content, { mode: 420 });
|
|
38364
38732
|
socket.emit("post:save-result", { success: true });
|
|
38365
38733
|
} catch (err) {
|
|
@@ -38386,14 +38754,14 @@ function registerPostHandlers(io2, socket, db, ocrDir, aiCliService) {
|
|
|
38386
38754
|
);
|
|
38387
38755
|
tracker.appendOutput(`\u25B8 Posting review to PR #${prNumber}...
|
|
38388
38756
|
`);
|
|
38389
|
-
const tmpDir =
|
|
38757
|
+
const tmpDir = join19(tmpdir2(), "ocr-post-comments");
|
|
38390
38758
|
try {
|
|
38391
|
-
|
|
38759
|
+
mkdirSync7(tmpDir, { recursive: true, mode: 448 });
|
|
38392
38760
|
} catch {
|
|
38393
38761
|
}
|
|
38394
|
-
const tmpFile =
|
|
38762
|
+
const tmpFile = join19(tmpDir, `${randomUUID2()}.md`);
|
|
38395
38763
|
writeFileSync5(tmpFile, content, { mode: 384 });
|
|
38396
|
-
const repoRoot =
|
|
38764
|
+
const repoRoot = dirname14(ocrDir);
|
|
38397
38765
|
try {
|
|
38398
38766
|
const { stdout } = await execBinaryAsync(
|
|
38399
38767
|
"gh",
|
|
@@ -38436,9 +38804,67 @@ function cleanupAllPostGenerations() {
|
|
|
38436
38804
|
}
|
|
38437
38805
|
}
|
|
38438
38806
|
|
|
38807
|
+
// src/server/services/forward-resume-sweep.ts
|
|
38808
|
+
function hasPositiveDeathEvidence(db, sessionId, isAlive) {
|
|
38809
|
+
const instances = listAgentSessionsForWorkflow(db, sessionId);
|
|
38810
|
+
if (instances.length === 0) return false;
|
|
38811
|
+
return instances.every(
|
|
38812
|
+
(s) => s.ended_at != null || s.pid != null && !isAlive(s.pid)
|
|
38813
|
+
);
|
|
38814
|
+
}
|
|
38815
|
+
function planForwardResume(db, cfg) {
|
|
38816
|
+
const isAlive = cfg.isAlive ?? defaultIsAlive;
|
|
38817
|
+
const plan = [];
|
|
38818
|
+
for (const session of getAllSessions(db)) {
|
|
38819
|
+
if (session.status !== "active") continue;
|
|
38820
|
+
const events = getEventsForSession(db, session.id);
|
|
38821
|
+
const workflowType = session.workflow_type === "map" ? "map" : "review";
|
|
38822
|
+
if (hasTerminalArtifactEvent2(events, workflowType, session.current_round)) {
|
|
38823
|
+
continue;
|
|
38824
|
+
}
|
|
38825
|
+
if (!hasPositiveDeathEvidence(db, session.id, isAlive)) continue;
|
|
38826
|
+
const stranded = strandedActionByCap(db, session, cfg.maxAttempts);
|
|
38827
|
+
if (stranded.action === "abort_or_fresh") {
|
|
38828
|
+
plan.push({ sessionId: session.id, action: "cap_close" });
|
|
38829
|
+
continue;
|
|
38830
|
+
}
|
|
38831
|
+
const latest = getLatestAgentSessionWithVendorId(db, session.id);
|
|
38832
|
+
plan.push({
|
|
38833
|
+
sessionId: session.id,
|
|
38834
|
+
action: latest?.vendor_session_id ? "resume" : "handoff"
|
|
38835
|
+
});
|
|
38836
|
+
}
|
|
38837
|
+
return plan;
|
|
38838
|
+
}
|
|
38839
|
+
function runForwardResumeSweep(deps) {
|
|
38840
|
+
const plan = planForwardResume(deps.db, deps.config);
|
|
38841
|
+
for (const item of plan) {
|
|
38842
|
+
try {
|
|
38843
|
+
if (item.action === "cap_close") {
|
|
38844
|
+
closeForwardResumeExhausted(deps.db, item.sessionId, deps.maxAttempts);
|
|
38845
|
+
deps.log?.(
|
|
38846
|
+
`[ForwardResume] ${item.sessionId}: attempts exhausted \u2192 closed non-success`
|
|
38847
|
+
);
|
|
38848
|
+
} else if (item.action === "resume") {
|
|
38849
|
+
deps.spawnResume(item.sessionId);
|
|
38850
|
+
deps.log?.(`[ForwardResume] ${item.sessionId}: auto-resuming (ocr review --resume)`);
|
|
38851
|
+
} else {
|
|
38852
|
+
deps.log?.(
|
|
38853
|
+
`[ForwardResume] ${item.sessionId}: stranded, no resume adapter \u2014 pick up in terminal`
|
|
38854
|
+
);
|
|
38855
|
+
}
|
|
38856
|
+
} catch (err) {
|
|
38857
|
+
deps.log?.(
|
|
38858
|
+
`[ForwardResume] ${item.sessionId}: ${err instanceof Error ? err.message : String(err)}`
|
|
38859
|
+
);
|
|
38860
|
+
}
|
|
38861
|
+
}
|
|
38862
|
+
return plan;
|
|
38863
|
+
}
|
|
38864
|
+
|
|
38439
38865
|
// src/server/index.ts
|
|
38440
38866
|
import { homedir } from "node:os";
|
|
38441
|
-
var __dirname3 =
|
|
38867
|
+
var __dirname3 = dirname15(fileURLToPath3(import.meta.url));
|
|
38442
38868
|
function shortenPath(p) {
|
|
38443
38869
|
const home = homedir();
|
|
38444
38870
|
return p.startsWith(home) ? "~" + p.slice(home.length) : p;
|
|
@@ -38505,7 +38931,7 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
38505
38931
|
function isOcrDashboardProcess(pid) {
|
|
38506
38932
|
if (process.platform === "win32") return false;
|
|
38507
38933
|
try {
|
|
38508
|
-
const cmd =
|
|
38934
|
+
const cmd = execBinary("ps", ["-p", String(pid), "-o", "command="], {
|
|
38509
38935
|
encoding: "utf-8",
|
|
38510
38936
|
timeout: 3e3
|
|
38511
38937
|
}).trim();
|
|
@@ -38520,23 +38946,23 @@ async function startServer(options = {}) {
|
|
|
38520
38946
|
process.title = "ocr-dashboard";
|
|
38521
38947
|
const ocrDir = resolveOcrDir();
|
|
38522
38948
|
const aiCliService = new AiCliService(ocrDir);
|
|
38523
|
-
const dbPathForCheckpoint =
|
|
38949
|
+
const dbPathForCheckpoint = join20(ocrDir, "data", "ocr.db");
|
|
38524
38950
|
const walResult = walCheckpointTruncate(dbPathForCheckpoint);
|
|
38525
38951
|
if (walResult === "checkpointed") {
|
|
38526
38952
|
console.log(" WAL checkpoint: truncated stale write-ahead-log file");
|
|
38527
38953
|
}
|
|
38528
|
-
for (const reaped of reapOrphanDbFiles(
|
|
38954
|
+
for (const reaped of reapOrphanDbFiles(join20(ocrDir, "data"))) {
|
|
38529
38955
|
console.log(` Orphan reap: removed stale ${reaped}`);
|
|
38530
38956
|
}
|
|
38531
|
-
const staleLogs = reapStaleExecLogs(
|
|
38957
|
+
const staleLogs = reapStaleExecLogs(join20(ocrDir, "data", "exec-logs"));
|
|
38532
38958
|
if (staleLogs.length > 0) {
|
|
38533
38959
|
console.log(` Exec-log reap: removed ${staleLogs.length} stale agent log(s)`);
|
|
38534
38960
|
}
|
|
38535
38961
|
const db = await openDb(ocrDir);
|
|
38536
|
-
const dataDir =
|
|
38537
|
-
const pidFilePath =
|
|
38538
|
-
const portFilePath =
|
|
38539
|
-
|
|
38962
|
+
const dataDir = join20(ocrDir, "data");
|
|
38963
|
+
const pidFilePath = join20(dataDir, "dashboard.pid");
|
|
38964
|
+
const portFilePath = join20(dataDir, "server-port");
|
|
38965
|
+
mkdirSync8(dataDir, { recursive: true });
|
|
38540
38966
|
try {
|
|
38541
38967
|
unlinkSync5(portFilePath);
|
|
38542
38968
|
} catch {
|
|
@@ -38634,12 +39060,39 @@ async function startServer(options = {}) {
|
|
|
38634
39060
|
}
|
|
38635
39061
|
};
|
|
38636
39062
|
await reconcileCompleted();
|
|
39063
|
+
const forwardResumeMaxAttempts = getForwardResumeMaxAttempts(ocrDir);
|
|
39064
|
+
const spawnResume = (sessionId) => {
|
|
39065
|
+
const child = spawnBinary("ocr", ["review", "--resume", sessionId], {
|
|
39066
|
+
cwd: ocrDir.replace(/\.ocr$/, "") || process.cwd(),
|
|
39067
|
+
stdio: "ignore",
|
|
39068
|
+
detached: true
|
|
39069
|
+
});
|
|
39070
|
+
child.on("error", (err) => {
|
|
39071
|
+
console.error(`[ForwardResume] spawn failed for ${sessionId}:`, err.message);
|
|
39072
|
+
});
|
|
39073
|
+
child.unref();
|
|
39074
|
+
};
|
|
39075
|
+
const runForwardResume = () => {
|
|
39076
|
+
try {
|
|
39077
|
+
runForwardResumeSweep({
|
|
39078
|
+
db,
|
|
39079
|
+
config: { maxAttempts: forwardResumeMaxAttempts, heartbeatMs: heartbeatSeconds * 1e3 },
|
|
39080
|
+
maxAttempts: forwardResumeMaxAttempts,
|
|
39081
|
+
spawnResume,
|
|
39082
|
+
log: (m) => console.log(` ${m}`)
|
|
39083
|
+
});
|
|
39084
|
+
} catch (err) {
|
|
39085
|
+
console.error("[ForwardResume] sweep failed:", err);
|
|
39086
|
+
}
|
|
39087
|
+
};
|
|
39088
|
+
runForwardResume();
|
|
38637
39089
|
const SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
38638
39090
|
const sweepTimer = setInterval(() => {
|
|
38639
39091
|
try {
|
|
38640
39092
|
logAgentSweep(sweepStaleAgentSessions(db, heartbeatSeconds, defaultIsAlive));
|
|
38641
39093
|
sweepStaleSessions(db, STALE_SESSION_THRESHOLD_SECONDS);
|
|
38642
39094
|
void reconcileCompleted();
|
|
39095
|
+
runForwardResume();
|
|
38643
39096
|
} catch (err) {
|
|
38644
39097
|
console.error("[sweep] periodic sweep failed:", err);
|
|
38645
39098
|
}
|
|
@@ -38675,10 +39128,10 @@ async function startServer(options = {}) {
|
|
|
38675
39128
|
app.use("/api/agent-sessions", createAgentSessionsRouter(db, () => pullSync()));
|
|
38676
39129
|
app.use("/api/sessions", createHandoffRouter(sessionCapture, ocrDir, () => pullSync()));
|
|
38677
39130
|
app.use("/api/team", createTeamRouter(ocrDir));
|
|
38678
|
-
const clientDir =
|
|
39131
|
+
const clientDir = join20(__dirname3, "client");
|
|
38679
39132
|
if (process.env.NODE_ENV === "production" && existsSync17(clientDir)) {
|
|
38680
39133
|
app.use(import_express15.default.static(clientDir, { index: false }));
|
|
38681
|
-
const indexHtmlPath =
|
|
39134
|
+
const indexHtmlPath = join20(clientDir, "index.html");
|
|
38682
39135
|
const rawIndexHtml = existsSync17(indexHtmlPath) ? readFileSync12(indexHtmlPath, "utf-8") : "";
|
|
38683
39136
|
const tokenScript = `<script>window.__OCR_TOKEN__=${JSON.stringify(AUTH_TOKEN)};</script>`;
|
|
38684
39137
|
const injectedIndexHtml = rawIndexHtml.replace(
|
|
@@ -38698,7 +39151,7 @@ async function startServer(options = {}) {
|
|
|
38698
39151
|
registerChatHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38699
39152
|
registerPostHandlers(io, socket, db, ocrDir, aiCliService);
|
|
38700
39153
|
});
|
|
38701
|
-
const dbFilePath =
|
|
39154
|
+
const dbFilePath = join20(ocrDir, "data", "ocr.db");
|
|
38702
39155
|
const dbSyncWatcher = new DbSyncWatcher(
|
|
38703
39156
|
db,
|
|
38704
39157
|
dbFilePath,
|
|
@@ -38714,7 +39167,7 @@ async function startServer(options = {}) {
|
|
|
38714
39167
|
dbSyncWatcher.startWatching();
|
|
38715
39168
|
pullSync = () => dbSyncWatcher.syncFromDisk();
|
|
38716
39169
|
console.log(` Watching DB: ${shortenPath(dbFilePath)}`);
|
|
38717
|
-
const sessionsDir =
|
|
39170
|
+
const sessionsDir = join20(ocrDir, "sessions");
|
|
38718
39171
|
const fsSync = new FilesystemSync(db, sessionsDir, io);
|
|
38719
39172
|
await fsSync.fullScan();
|
|
38720
39173
|
fsSync.startWatching();
|
|
@@ -38778,7 +39231,7 @@ async function startServer(options = {}) {
|
|
|
38778
39231
|
unlinkSync5(portFilePath);
|
|
38779
39232
|
} catch {
|
|
38780
39233
|
}
|
|
38781
|
-
|
|
39234
|
+
clearAllSpawnMarkers(ocrDir);
|
|
38782
39235
|
try {
|
|
38783
39236
|
const activeResult = db.exec(
|
|
38784
39237
|
"SELECT id, pid FROM command_executions WHERE pid IS NOT NULL AND finished_at IS NULL"
|