iranti-control-plane 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/bundle.cjs +1223 -368
- package/migrations/004_create_fleet_ledger.sql +106 -0
- package/package.json +1 -1
- package/public/control-plane/assets/index-C4_wD-R6.js +77 -0
- package/public/control-plane/assets/{index-BTD8_zu4.css → index-CPsDyW96.css} +1 -1
- package/public/control-plane/index.html +2 -2
- package/public/control-plane/assets/index-CcG4ZB0K.js +0 -77
package/dist/server/bundle.cjs
CHANGED
|
@@ -18407,10 +18407,10 @@ var require_view = __commonJS({
|
|
|
18407
18407
|
var debug = require_src()("express:view");
|
|
18408
18408
|
var path3 = require("path");
|
|
18409
18409
|
var fs2 = require("fs");
|
|
18410
|
-
var
|
|
18410
|
+
var dirname10 = path3.dirname;
|
|
18411
18411
|
var basename7 = path3.basename;
|
|
18412
18412
|
var extname3 = path3.extname;
|
|
18413
|
-
var
|
|
18413
|
+
var join13 = path3.join;
|
|
18414
18414
|
var resolve10 = path3.resolve;
|
|
18415
18415
|
module2.exports = View;
|
|
18416
18416
|
function View(name, options) {
|
|
@@ -18446,7 +18446,7 @@ var require_view = __commonJS({
|
|
|
18446
18446
|
for (var i = 0; i < roots.length && !path4; i++) {
|
|
18447
18447
|
var root = roots[i];
|
|
18448
18448
|
var loc = resolve10(root, name);
|
|
18449
|
-
var dir =
|
|
18449
|
+
var dir = dirname10(loc);
|
|
18450
18450
|
var file2 = basename7(loc);
|
|
18451
18451
|
path4 = this.resolve(dir, file2);
|
|
18452
18452
|
}
|
|
@@ -18458,12 +18458,12 @@ var require_view = __commonJS({
|
|
|
18458
18458
|
};
|
|
18459
18459
|
View.prototype.resolve = function resolve11(dir, file2) {
|
|
18460
18460
|
var ext = this.ext;
|
|
18461
|
-
var path4 =
|
|
18461
|
+
var path4 = join13(dir, file2);
|
|
18462
18462
|
var stat2 = tryStat(path4);
|
|
18463
18463
|
if (stat2 && stat2.isFile()) {
|
|
18464
18464
|
return path4;
|
|
18465
18465
|
}
|
|
18466
|
-
path4 =
|
|
18466
|
+
path4 = join13(dir, basename7(file2, ext), "index" + ext);
|
|
18467
18467
|
stat2 = tryStat(path4);
|
|
18468
18468
|
if (stat2 && stat2.isFile()) {
|
|
18469
18469
|
return path4;
|
|
@@ -19096,7 +19096,7 @@ var require_send = __commonJS({
|
|
|
19096
19096
|
var Stream = require("stream");
|
|
19097
19097
|
var util2 = require("util");
|
|
19098
19098
|
var extname3 = path3.extname;
|
|
19099
|
-
var
|
|
19099
|
+
var join13 = path3.join;
|
|
19100
19100
|
var normalize = path3.normalize;
|
|
19101
19101
|
var resolve10 = path3.resolve;
|
|
19102
19102
|
var sep = path3.sep;
|
|
@@ -19315,7 +19315,7 @@ var require_send = __commonJS({
|
|
|
19315
19315
|
return res;
|
|
19316
19316
|
}
|
|
19317
19317
|
parts = path4.split(sep);
|
|
19318
|
-
path4 = normalize(
|
|
19318
|
+
path4 = normalize(join13(root, path4));
|
|
19319
19319
|
} else {
|
|
19320
19320
|
if (UP_PATH_REGEXP.test(path4)) {
|
|
19321
19321
|
debug('malicious path "%s"', path4);
|
|
@@ -19326,12 +19326,12 @@ var require_send = __commonJS({
|
|
|
19326
19326
|
path4 = resolve10(path4);
|
|
19327
19327
|
}
|
|
19328
19328
|
if (containsDotFile(parts)) {
|
|
19329
|
-
var
|
|
19330
|
-
if (
|
|
19331
|
-
|
|
19329
|
+
var access10 = this._dotfiles;
|
|
19330
|
+
if (access10 === void 0) {
|
|
19331
|
+
access10 = parts[parts.length - 1][0] === "." ? this._hidden ? "allow" : "ignore" : "allow";
|
|
19332
19332
|
}
|
|
19333
|
-
debug('%s dotfile "%s"',
|
|
19334
|
-
switch (
|
|
19333
|
+
debug('%s dotfile "%s"', access10, path4);
|
|
19334
|
+
switch (access10) {
|
|
19335
19335
|
case "allow":
|
|
19336
19336
|
break;
|
|
19337
19337
|
case "deny":
|
|
@@ -19450,7 +19450,7 @@ var require_send = __commonJS({
|
|
|
19450
19450
|
if (err) return self.onStatError(err);
|
|
19451
19451
|
return self.error(404);
|
|
19452
19452
|
}
|
|
19453
|
-
var p =
|
|
19453
|
+
var p = join13(path4, self._index[i]);
|
|
19454
19454
|
debug('stat "%s"', p);
|
|
19455
19455
|
fs2.stat(p, function(err2, stat2) {
|
|
19456
19456
|
if (err2) return next(err2);
|
|
@@ -20589,7 +20589,7 @@ var require_application = __commonJS({
|
|
|
20589
20589
|
"src/server/node_modules/express/lib/application.js"(exports2, module2) {
|
|
20590
20590
|
"use strict";
|
|
20591
20591
|
var finalhandler = require_finalhandler();
|
|
20592
|
-
var
|
|
20592
|
+
var Router33 = require_router();
|
|
20593
20593
|
var methods = require_methods();
|
|
20594
20594
|
var middleware = require_init();
|
|
20595
20595
|
var query2 = require_query();
|
|
@@ -20654,7 +20654,7 @@ var require_application = __commonJS({
|
|
|
20654
20654
|
};
|
|
20655
20655
|
app2.lazyrouter = function lazyrouter() {
|
|
20656
20656
|
if (!this._router) {
|
|
20657
|
-
this._router = new
|
|
20657
|
+
this._router = new Router33({
|
|
20658
20658
|
caseSensitive: this.enabled("case sensitive routing"),
|
|
20659
20659
|
strict: this.enabled("strict routing")
|
|
20660
20660
|
});
|
|
@@ -22518,7 +22518,7 @@ var require_express = __commonJS({
|
|
|
22518
22518
|
var mixin = require_merge_descriptors();
|
|
22519
22519
|
var proto = require_application();
|
|
22520
22520
|
var Route = require_route();
|
|
22521
|
-
var
|
|
22521
|
+
var Router33 = require_router();
|
|
22522
22522
|
var req = require_request();
|
|
22523
22523
|
var res = require_response();
|
|
22524
22524
|
exports2 = module2.exports = createApplication;
|
|
@@ -22541,7 +22541,7 @@ var require_express = __commonJS({
|
|
|
22541
22541
|
exports2.request = req;
|
|
22542
22542
|
exports2.response = res;
|
|
22543
22543
|
exports2.Route = Route;
|
|
22544
|
-
exports2.Router =
|
|
22544
|
+
exports2.Router = Router33;
|
|
22545
22545
|
exports2.json = bodyParser.json;
|
|
22546
22546
|
exports2.query = require_query();
|
|
22547
22547
|
exports2.raw = bodyParser.raw;
|
|
@@ -27129,7 +27129,7 @@ var require_pg_pool = __commonJS({
|
|
|
27129
27129
|
pool2.emit("error", err, client);
|
|
27130
27130
|
};
|
|
27131
27131
|
}
|
|
27132
|
-
var
|
|
27132
|
+
var Pool12 = class extends EventEmitter {
|
|
27133
27133
|
constructor(options, Client3) {
|
|
27134
27134
|
super();
|
|
27135
27135
|
this.options = Object.assign({}, options);
|
|
@@ -27496,7 +27496,7 @@ var require_pg_pool = __commonJS({
|
|
|
27496
27496
|
return this._clients.length;
|
|
27497
27497
|
}
|
|
27498
27498
|
};
|
|
27499
|
-
module2.exports =
|
|
27499
|
+
module2.exports = Pool12;
|
|
27500
27500
|
}
|
|
27501
27501
|
});
|
|
27502
27502
|
|
|
@@ -27911,12 +27911,12 @@ var require_lib5 = __commonJS({
|
|
|
27911
27911
|
var Connection2 = require_connection();
|
|
27912
27912
|
var Result2 = require_result();
|
|
27913
27913
|
var utils = require_utils3();
|
|
27914
|
-
var
|
|
27914
|
+
var Pool12 = require_pg_pool();
|
|
27915
27915
|
var TypeOverrides2 = require_type_overrides();
|
|
27916
27916
|
var { DatabaseError: DatabaseError2 } = require_dist();
|
|
27917
27917
|
var { escapeIdentifier: escapeIdentifier2, escapeLiteral: escapeLiteral2 } = require_utils3();
|
|
27918
27918
|
var poolFactory = (Client4) => {
|
|
27919
|
-
return class BoundPool extends
|
|
27919
|
+
return class BoundPool extends Pool12 {
|
|
27920
27920
|
constructor(options) {
|
|
27921
27921
|
super(options, Client4);
|
|
27922
27922
|
}
|
|
@@ -28474,11 +28474,11 @@ var require_codegen = __commonJS({
|
|
|
28474
28474
|
const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
|
|
28475
28475
|
return `${varKind} ${this.name}${rhs};` + _n;
|
|
28476
28476
|
}
|
|
28477
|
-
optimizeNames(names,
|
|
28477
|
+
optimizeNames(names, constants8) {
|
|
28478
28478
|
if (!names[this.name.str])
|
|
28479
28479
|
return;
|
|
28480
28480
|
if (this.rhs)
|
|
28481
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
28481
|
+
this.rhs = optimizeExpr(this.rhs, names, constants8);
|
|
28482
28482
|
return this;
|
|
28483
28483
|
}
|
|
28484
28484
|
get names() {
|
|
@@ -28495,10 +28495,10 @@ var require_codegen = __commonJS({
|
|
|
28495
28495
|
render({ _n }) {
|
|
28496
28496
|
return `${this.lhs} = ${this.rhs};` + _n;
|
|
28497
28497
|
}
|
|
28498
|
-
optimizeNames(names,
|
|
28498
|
+
optimizeNames(names, constants8) {
|
|
28499
28499
|
if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
|
|
28500
28500
|
return;
|
|
28501
|
-
this.rhs = optimizeExpr(this.rhs, names,
|
|
28501
|
+
this.rhs = optimizeExpr(this.rhs, names, constants8);
|
|
28502
28502
|
return this;
|
|
28503
28503
|
}
|
|
28504
28504
|
get names() {
|
|
@@ -28559,8 +28559,8 @@ var require_codegen = __commonJS({
|
|
|
28559
28559
|
optimizeNodes() {
|
|
28560
28560
|
return `${this.code}` ? this : void 0;
|
|
28561
28561
|
}
|
|
28562
|
-
optimizeNames(names,
|
|
28563
|
-
this.code = optimizeExpr(this.code, names,
|
|
28562
|
+
optimizeNames(names, constants8) {
|
|
28563
|
+
this.code = optimizeExpr(this.code, names, constants8);
|
|
28564
28564
|
return this;
|
|
28565
28565
|
}
|
|
28566
28566
|
get names() {
|
|
@@ -28589,12 +28589,12 @@ var require_codegen = __commonJS({
|
|
|
28589
28589
|
}
|
|
28590
28590
|
return nodes.length > 0 ? this : void 0;
|
|
28591
28591
|
}
|
|
28592
|
-
optimizeNames(names,
|
|
28592
|
+
optimizeNames(names, constants8) {
|
|
28593
28593
|
const { nodes } = this;
|
|
28594
28594
|
let i = nodes.length;
|
|
28595
28595
|
while (i--) {
|
|
28596
28596
|
const n = nodes[i];
|
|
28597
|
-
if (n.optimizeNames(names,
|
|
28597
|
+
if (n.optimizeNames(names, constants8))
|
|
28598
28598
|
continue;
|
|
28599
28599
|
subtractNames(names, n.names);
|
|
28600
28600
|
nodes.splice(i, 1);
|
|
@@ -28647,12 +28647,12 @@ var require_codegen = __commonJS({
|
|
|
28647
28647
|
return void 0;
|
|
28648
28648
|
return this;
|
|
28649
28649
|
}
|
|
28650
|
-
optimizeNames(names,
|
|
28650
|
+
optimizeNames(names, constants8) {
|
|
28651
28651
|
var _a2;
|
|
28652
|
-
this.else = (_a2 = this.else) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names,
|
|
28653
|
-
if (!(super.optimizeNames(names,
|
|
28652
|
+
this.else = (_a2 = this.else) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants8);
|
|
28653
|
+
if (!(super.optimizeNames(names, constants8) || this.else))
|
|
28654
28654
|
return;
|
|
28655
|
-
this.condition = optimizeExpr(this.condition, names,
|
|
28655
|
+
this.condition = optimizeExpr(this.condition, names, constants8);
|
|
28656
28656
|
return this;
|
|
28657
28657
|
}
|
|
28658
28658
|
get names() {
|
|
@@ -28675,10 +28675,10 @@ var require_codegen = __commonJS({
|
|
|
28675
28675
|
render(opts) {
|
|
28676
28676
|
return `for(${this.iteration})` + super.render(opts);
|
|
28677
28677
|
}
|
|
28678
|
-
optimizeNames(names,
|
|
28679
|
-
if (!super.optimizeNames(names,
|
|
28678
|
+
optimizeNames(names, constants8) {
|
|
28679
|
+
if (!super.optimizeNames(names, constants8))
|
|
28680
28680
|
return;
|
|
28681
|
-
this.iteration = optimizeExpr(this.iteration, names,
|
|
28681
|
+
this.iteration = optimizeExpr(this.iteration, names, constants8);
|
|
28682
28682
|
return this;
|
|
28683
28683
|
}
|
|
28684
28684
|
get names() {
|
|
@@ -28714,10 +28714,10 @@ var require_codegen = __commonJS({
|
|
|
28714
28714
|
render(opts) {
|
|
28715
28715
|
return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
|
|
28716
28716
|
}
|
|
28717
|
-
optimizeNames(names,
|
|
28718
|
-
if (!super.optimizeNames(names,
|
|
28717
|
+
optimizeNames(names, constants8) {
|
|
28718
|
+
if (!super.optimizeNames(names, constants8))
|
|
28719
28719
|
return;
|
|
28720
|
-
this.iterable = optimizeExpr(this.iterable, names,
|
|
28720
|
+
this.iterable = optimizeExpr(this.iterable, names, constants8);
|
|
28721
28721
|
return this;
|
|
28722
28722
|
}
|
|
28723
28723
|
get names() {
|
|
@@ -28759,11 +28759,11 @@ var require_codegen = __commonJS({
|
|
|
28759
28759
|
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
|
|
28760
28760
|
return this;
|
|
28761
28761
|
}
|
|
28762
|
-
optimizeNames(names,
|
|
28762
|
+
optimizeNames(names, constants8) {
|
|
28763
28763
|
var _a2, _b;
|
|
28764
|
-
super.optimizeNames(names,
|
|
28765
|
-
(_a2 = this.catch) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names,
|
|
28766
|
-
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names,
|
|
28764
|
+
super.optimizeNames(names, constants8);
|
|
28765
|
+
(_a2 = this.catch) === null || _a2 === void 0 ? void 0 : _a2.optimizeNames(names, constants8);
|
|
28766
|
+
(_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants8);
|
|
28767
28767
|
return this;
|
|
28768
28768
|
}
|
|
28769
28769
|
get names() {
|
|
@@ -29064,7 +29064,7 @@ var require_codegen = __commonJS({
|
|
|
29064
29064
|
function addExprNames(names, from) {
|
|
29065
29065
|
return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
|
|
29066
29066
|
}
|
|
29067
|
-
function optimizeExpr(expr, names,
|
|
29067
|
+
function optimizeExpr(expr, names, constants8) {
|
|
29068
29068
|
if (expr instanceof code_1.Name)
|
|
29069
29069
|
return replaceName(expr);
|
|
29070
29070
|
if (!canOptimize(expr))
|
|
@@ -29079,14 +29079,14 @@ var require_codegen = __commonJS({
|
|
|
29079
29079
|
return items;
|
|
29080
29080
|
}, []));
|
|
29081
29081
|
function replaceName(n) {
|
|
29082
|
-
const c =
|
|
29082
|
+
const c = constants8[n.str];
|
|
29083
29083
|
if (c === void 0 || names[n.str] !== 1)
|
|
29084
29084
|
return n;
|
|
29085
29085
|
delete names[n.str];
|
|
29086
29086
|
return c;
|
|
29087
29087
|
}
|
|
29088
29088
|
function canOptimize(e) {
|
|
29089
|
-
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 &&
|
|
29089
|
+
return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants8[c.str] !== void 0);
|
|
29090
29090
|
}
|
|
29091
29091
|
}
|
|
29092
29092
|
function subtractNames(names, from) {
|
|
@@ -35370,11 +35370,11 @@ __export(runner_exports, {
|
|
|
35370
35370
|
});
|
|
35371
35371
|
function resolveMigrationPath(file2) {
|
|
35372
35372
|
const candidates = [
|
|
35373
|
-
(0,
|
|
35374
|
-
(0,
|
|
35373
|
+
(0, import_path19.resolve)(__dirname2, file2),
|
|
35374
|
+
(0, import_path19.resolve)(__dirname2, "..", "..", "migrations", file2)
|
|
35375
35375
|
];
|
|
35376
35376
|
for (const candidate of candidates) {
|
|
35377
|
-
if ((0,
|
|
35377
|
+
if ((0, import_fs13.existsSync)(candidate)) return candidate;
|
|
35378
35378
|
}
|
|
35379
35379
|
return candidates[0];
|
|
35380
35380
|
}
|
|
@@ -35383,11 +35383,12 @@ async function run() {
|
|
|
35383
35383
|
const migrations = [
|
|
35384
35384
|
"001_create_staff_events.sql",
|
|
35385
35385
|
"002_create_archive_flags.sql",
|
|
35386
|
-
"003_staff_events_metrics_index.sql"
|
|
35386
|
+
"003_staff_events_metrics_index.sql",
|
|
35387
|
+
"004_create_fleet_ledger.sql"
|
|
35387
35388
|
];
|
|
35388
35389
|
try {
|
|
35389
35390
|
for (const file2 of migrations) {
|
|
35390
|
-
const sql = (0,
|
|
35391
|
+
const sql = (0, import_fs13.readFileSync)(resolveMigrationPath(file2), "utf8");
|
|
35391
35392
|
console.log(`[migrate] Running ${file2}`);
|
|
35392
35393
|
await migrationPool.query(sql);
|
|
35393
35394
|
console.log(`[migrate] Done: ${file2}`);
|
|
@@ -35397,17 +35398,17 @@ async function run() {
|
|
|
35397
35398
|
await migrationPool.end();
|
|
35398
35399
|
}
|
|
35399
35400
|
}
|
|
35400
|
-
var
|
|
35401
|
+
var import_fs13, import_path19, import_url2, __dirname2;
|
|
35401
35402
|
var init_runner = __esm({
|
|
35402
35403
|
"src/server/migrations/runner.ts"() {
|
|
35403
35404
|
"use strict";
|
|
35404
|
-
|
|
35405
|
-
|
|
35405
|
+
import_fs13 = require("fs");
|
|
35406
|
+
import_path19 = require("path");
|
|
35406
35407
|
import_url2 = require("url");
|
|
35407
35408
|
init_esm();
|
|
35408
35409
|
init_db();
|
|
35409
|
-
__dirname2 = (0,
|
|
35410
|
-
if (process.argv[1] && (0,
|
|
35410
|
+
__dirname2 = (0, import_path19.dirname)((0, import_url2.fileURLToPath)(__importmeta_url));
|
|
35411
|
+
if (process.argv[1] && (0, import_path19.resolve)(process.argv[1]) === (0, import_url2.fileURLToPath)(__importmeta_url)) {
|
|
35411
35412
|
run().catch((err) => {
|
|
35412
35413
|
console.error("[migrate] Failed:", err);
|
|
35413
35414
|
process.exitCode = 1;
|
|
@@ -35761,16 +35762,16 @@ __export(index_exports, {
|
|
|
35761
35762
|
VERSION: () => VERSION
|
|
35762
35763
|
});
|
|
35763
35764
|
module.exports = __toCommonJS(index_exports);
|
|
35764
|
-
var
|
|
35765
|
+
var import_express33 = __toESM(require_express2(), 1);
|
|
35765
35766
|
var import_cors = __toESM(require_lib3(), 1);
|
|
35766
35767
|
var import_net = __toESM(require("net"), 1);
|
|
35767
|
-
var
|
|
35768
|
-
var
|
|
35768
|
+
var import_path20 = require("path");
|
|
35769
|
+
var import_fs14 = require("fs");
|
|
35769
35770
|
var import_url3 = require("url");
|
|
35770
35771
|
var import_module = require("module");
|
|
35771
35772
|
|
|
35772
35773
|
// src/server/routes/control-plane/index.ts
|
|
35773
|
-
var
|
|
35774
|
+
var import_express32 = __toESM(require_express2(), 1);
|
|
35774
35775
|
|
|
35775
35776
|
// src/server/routes/control-plane/kb.ts
|
|
35776
35777
|
var import_express = __toESM(require_express2(), 1);
|
|
@@ -53953,11 +53954,105 @@ setupRouter.use((err, _req, res, _next) => {
|
|
|
53953
53954
|
|
|
53954
53955
|
// src/server/routes/control-plane/repair.ts
|
|
53955
53956
|
var import_express7 = __toESM(require_express2(), 1);
|
|
53957
|
+
var import_promises6 = require("fs/promises");
|
|
53958
|
+
var import_path9 = require("path");
|
|
53959
|
+
init_esm();
|
|
53960
|
+
|
|
53961
|
+
// src/server/lib/db-provision.ts
|
|
53962
|
+
var import_child_process2 = require("child_process");
|
|
53956
53963
|
var import_promises5 = require("fs/promises");
|
|
53964
|
+
var import_fs6 = require("fs");
|
|
53957
53965
|
var import_path8 = require("path");
|
|
53958
53966
|
init_esm();
|
|
53959
|
-
var repairRouter = (0, import_express7.Router)();
|
|
53960
53967
|
var { Pool: Pool6 } = esm_default;
|
|
53968
|
+
function escapePgIdentifier(value) {
|
|
53969
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
53970
|
+
}
|
|
53971
|
+
async function createDatabaseIfMissing(dbUrl) {
|
|
53972
|
+
const parsed = new URL(dbUrl);
|
|
53973
|
+
const databaseName = decodeURIComponent(parsed.pathname.replace(/^\//, "").trim());
|
|
53974
|
+
if (!databaseName) {
|
|
53975
|
+
throw new Error("DATABASE_URL does not include a database name.");
|
|
53976
|
+
}
|
|
53977
|
+
if (databaseName.toLowerCase() === "postgres") {
|
|
53978
|
+
throw new Error('Refusing to operate on the maintenance database "postgres".');
|
|
53979
|
+
}
|
|
53980
|
+
parsed.pathname = "/postgres";
|
|
53981
|
+
const adminUrl = parsed.toString();
|
|
53982
|
+
const pool2 = new Pool6({
|
|
53983
|
+
connectionString: adminUrl,
|
|
53984
|
+
max: 1,
|
|
53985
|
+
idleTimeoutMillis: 1e3,
|
|
53986
|
+
connectionTimeoutMillis: 4e3
|
|
53987
|
+
});
|
|
53988
|
+
try {
|
|
53989
|
+
const existing = await pool2.query("SELECT 1 FROM pg_database WHERE datname = $1", [databaseName]);
|
|
53990
|
+
if (existing.rows.length > 0) {
|
|
53991
|
+
return { databaseName, alreadyExisted: true };
|
|
53992
|
+
}
|
|
53993
|
+
await pool2.query(`CREATE DATABASE ${escapePgIdentifier(databaseName)}`);
|
|
53994
|
+
return { databaseName, alreadyExisted: false };
|
|
53995
|
+
} finally {
|
|
53996
|
+
await pool2.end().catch(() => {
|
|
53997
|
+
});
|
|
53998
|
+
}
|
|
53999
|
+
}
|
|
54000
|
+
async function runInstanceMigrations(dbUrl, instanceDir) {
|
|
54001
|
+
const resolution = await resolveIrantiCli();
|
|
54002
|
+
if (!resolution) {
|
|
54003
|
+
return { ran: false, note: "iranti CLI not found \u2014 run migrations manually." };
|
|
54004
|
+
}
|
|
54005
|
+
const cliEntry = resolution.args[0];
|
|
54006
|
+
if (!cliEntry || !cliEntry.toLowerCase().endsWith(".js")) {
|
|
54007
|
+
return { ran: false, note: "Could not locate bundled setup script \u2014 run migrations manually." };
|
|
54008
|
+
}
|
|
54009
|
+
const packageRoot = (0, import_path8.dirname)((0, import_path8.dirname)(cliEntry));
|
|
54010
|
+
const setupScript = (0, import_path8.join)(packageRoot, "dist", "scripts", "setup.js");
|
|
54011
|
+
try {
|
|
54012
|
+
await (0, import_promises5.access)(setupScript, import_fs6.constants.F_OK);
|
|
54013
|
+
} catch {
|
|
54014
|
+
return { ran: false, note: "Bundled setup script not found \u2014 run migrations manually." };
|
|
54015
|
+
}
|
|
54016
|
+
const escalationDir = (0, import_path8.join)(instanceDir, "escalation");
|
|
54017
|
+
await (0, import_promises5.mkdir)(escalationDir, { recursive: true }).catch(() => {
|
|
54018
|
+
});
|
|
54019
|
+
await new Promise((resolve10, reject) => {
|
|
54020
|
+
(0, import_child_process2.execFile)(
|
|
54021
|
+
process.execPath,
|
|
54022
|
+
[setupScript],
|
|
54023
|
+
{
|
|
54024
|
+
env: {
|
|
54025
|
+
...process.env,
|
|
54026
|
+
DATABASE_URL: dbUrl,
|
|
54027
|
+
IRANTI_ESCALATION_DIR: escalationDir
|
|
54028
|
+
},
|
|
54029
|
+
timeout: 6e4
|
|
54030
|
+
},
|
|
54031
|
+
(err) => {
|
|
54032
|
+
if (err) reject(err);
|
|
54033
|
+
else resolve10();
|
|
54034
|
+
}
|
|
54035
|
+
);
|
|
54036
|
+
});
|
|
54037
|
+
return { ran: true, note: "Migrations applied." };
|
|
54038
|
+
}
|
|
54039
|
+
async function provisionDatabase(dbUrl, instanceDir) {
|
|
54040
|
+
const { databaseName, alreadyExisted } = await createDatabaseIfMissing(dbUrl);
|
|
54041
|
+
let migrated = false;
|
|
54042
|
+
let migrationNote = "";
|
|
54043
|
+
try {
|
|
54044
|
+
const result = await runInstanceMigrations(dbUrl, instanceDir);
|
|
54045
|
+
migrated = result.ran;
|
|
54046
|
+
migrationNote = result.note;
|
|
54047
|
+
} catch (err) {
|
|
54048
|
+
migrationNote = `Migrations failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
54049
|
+
}
|
|
54050
|
+
return { databaseName, created: !alreadyExisted, migrated, migrationNote };
|
|
54051
|
+
}
|
|
54052
|
+
|
|
54053
|
+
// src/server/routes/control-plane/repair.ts
|
|
54054
|
+
var repairRouter = (0, import_express7.Router)();
|
|
54055
|
+
var { Pool: Pool7 } = esm_default;
|
|
53961
54056
|
async function doctorCheckRuntimeAvailability(scope) {
|
|
53962
54057
|
const controller = new AbortController();
|
|
53963
54058
|
const timeout = setTimeout(() => controller.abort(), 2500);
|
|
@@ -54041,7 +54136,7 @@ async function resolveProjectOr422(scope, projectId, res) {
|
|
|
54041
54136
|
}
|
|
54042
54137
|
async function writeAuditLog(scope, action, detail) {
|
|
54043
54138
|
if (!scope.databaseUrl) return;
|
|
54044
|
-
const pool2 = new
|
|
54139
|
+
const pool2 = new Pool7({
|
|
54045
54140
|
connectionString: scope.databaseUrl,
|
|
54046
54141
|
max: 1,
|
|
54047
54142
|
idleTimeoutMillis: 1e3,
|
|
@@ -54078,8 +54173,8 @@ repairRouter.post(
|
|
|
54078
54173
|
if (!scope) return;
|
|
54079
54174
|
const projectPath = await resolveProjectOr422(scope, req.params["projectId"], res);
|
|
54080
54175
|
if (!projectPath) return;
|
|
54081
|
-
await (0,
|
|
54082
|
-
const filePath = (0,
|
|
54176
|
+
await (0, import_promises6.access)(projectPath, import_promises6.constants.W_OK);
|
|
54177
|
+
const filePath = (0, import_path9.join)(projectPath, ".mcp.json");
|
|
54083
54178
|
const content = JSON.stringify(
|
|
54084
54179
|
{
|
|
54085
54180
|
mcpServers: {
|
|
@@ -54094,12 +54189,12 @@ repairRouter.post(
|
|
|
54094
54189
|
) + "\n";
|
|
54095
54190
|
let action = "created";
|
|
54096
54191
|
try {
|
|
54097
|
-
await (0,
|
|
54192
|
+
await (0, import_promises6.access)(filePath, import_promises6.constants.F_OK);
|
|
54098
54193
|
action = "replaced";
|
|
54099
54194
|
} catch {
|
|
54100
54195
|
action = "created";
|
|
54101
54196
|
}
|
|
54102
|
-
await (0,
|
|
54197
|
+
await (0, import_promises6.writeFile)(filePath, content, "utf8");
|
|
54103
54198
|
await writeAuditLog(scope, "repair_mcp_json", {
|
|
54104
54199
|
instanceId: scope.instanceId,
|
|
54105
54200
|
instanceName: scope.instanceName,
|
|
@@ -54148,12 +54243,12 @@ repairRouter.post(
|
|
|
54148
54243
|
if (!scope) return;
|
|
54149
54244
|
const projectPath = await resolveProjectOr422(scope, req.params["projectId"], res);
|
|
54150
54245
|
if (!projectPath) return;
|
|
54151
|
-
await (0,
|
|
54152
|
-
const filePath = (0,
|
|
54246
|
+
await (0, import_promises6.access)(projectPath, import_promises6.constants.W_OK);
|
|
54247
|
+
const filePath = (0, import_path9.join)(projectPath, "CLAUDE.md");
|
|
54153
54248
|
const block = buildIrantiBlock(scope);
|
|
54154
54249
|
let existingContent = null;
|
|
54155
54250
|
try {
|
|
54156
|
-
existingContent = await (0,
|
|
54251
|
+
existingContent = await (0, import_promises6.readFile)(filePath, "utf8");
|
|
54157
54252
|
} catch (err) {
|
|
54158
54253
|
if (err.code !== "ENOENT") throw err;
|
|
54159
54254
|
}
|
|
@@ -54182,7 +54277,7 @@ repairRouter.post(
|
|
|
54182
54277
|
}
|
|
54183
54278
|
}
|
|
54184
54279
|
const diff = buildDiff(existingContent, finalContent);
|
|
54185
|
-
await (0,
|
|
54280
|
+
await (0, import_promises6.writeFile)(filePath, finalContent, "utf8");
|
|
54186
54281
|
await writeAuditLog(scope, "repair_claude_md", {
|
|
54187
54282
|
instanceId: scope.instanceId,
|
|
54188
54283
|
instanceName: scope.instanceName,
|
|
@@ -54208,7 +54303,7 @@ async function doctorCheckDatabase(scope) {
|
|
|
54208
54303
|
commands: []
|
|
54209
54304
|
};
|
|
54210
54305
|
}
|
|
54211
|
-
const pool2 = new
|
|
54306
|
+
const pool2 = new Pool7({
|
|
54212
54307
|
connectionString: scope.databaseUrl,
|
|
54213
54308
|
max: 1,
|
|
54214
54309
|
idleTimeoutMillis: 1e3,
|
|
@@ -54277,7 +54372,7 @@ function projectRepairUrl(scope, projectPath, kind) {
|
|
|
54277
54372
|
async function doctorProjectChecks(scope) {
|
|
54278
54373
|
const checks = await Promise.all(scope.boundProjects.map(async (project) => {
|
|
54279
54374
|
const integration = await inspectProjectIntegration(project.projectPath);
|
|
54280
|
-
const projectName = (0,
|
|
54375
|
+
const projectName = (0, import_path9.basename)(project.projectPath);
|
|
54281
54376
|
const mcpCheck = {
|
|
54282
54377
|
id: `mcp_integration:${project.projectPath}`,
|
|
54283
54378
|
label: `MCP integration (${projectName})`,
|
|
@@ -54377,6 +54472,30 @@ repairRouter.post("/:instanceId/doctor", async (req, res, next) => {
|
|
|
54377
54472
|
next(err);
|
|
54378
54473
|
}
|
|
54379
54474
|
});
|
|
54475
|
+
repairRouter.post("/:instanceId/repair/provision-database", async (req, res, next) => {
|
|
54476
|
+
try {
|
|
54477
|
+
const scope = await resolveScopeOr404(req.params["instanceId"], res);
|
|
54478
|
+
if (!scope) return;
|
|
54479
|
+
if (!scope.databaseUrl) {
|
|
54480
|
+
res.status(422).json({
|
|
54481
|
+
error: "No DATABASE_URL configured for this instance.",
|
|
54482
|
+
code: "NO_DATABASE_URL"
|
|
54483
|
+
});
|
|
54484
|
+
return;
|
|
54485
|
+
}
|
|
54486
|
+
const result = await provisionDatabase(scope.databaseUrl, scope.instanceDir);
|
|
54487
|
+
await writeAuditLog(scope, "provision_database", {
|
|
54488
|
+
instanceId: scope.instanceId,
|
|
54489
|
+
instanceName: scope.instanceName,
|
|
54490
|
+
databaseName: result.databaseName,
|
|
54491
|
+
created: result.created,
|
|
54492
|
+
migrated: result.migrated
|
|
54493
|
+
});
|
|
54494
|
+
res.json({ ok: true, ...result });
|
|
54495
|
+
} catch (err) {
|
|
54496
|
+
next(err);
|
|
54497
|
+
}
|
|
54498
|
+
});
|
|
54380
54499
|
repairRouter.use((err, _req, res, _next) => {
|
|
54381
54500
|
const apiErr = err;
|
|
54382
54501
|
if (err?.code === "EACCES") {
|
|
@@ -54526,7 +54645,7 @@ escalationsRouter.get("/", async (req, res, next) => {
|
|
|
54526
54645
|
"supersededBy"::text AS "supersededBy",
|
|
54527
54646
|
"conflictLog"
|
|
54528
54647
|
FROM archive
|
|
54529
|
-
WHERE "
|
|
54648
|
+
WHERE "archivedReason" = 'escalated' AND "resolutionState" = 'pending'
|
|
54530
54649
|
ORDER BY "archivedAt" ASC`
|
|
54531
54650
|
);
|
|
54532
54651
|
if (archiveResult.rows.length === 0) {
|
|
@@ -54595,7 +54714,7 @@ escalationsRouter.get("/", async (req, res, next) => {
|
|
|
54595
54714
|
"archivedAt",
|
|
54596
54715
|
NULL::text AS "resolutionNote"
|
|
54597
54716
|
FROM archive
|
|
54598
|
-
WHERE "resolutionState"
|
|
54717
|
+
WHERE "archivedReason" = 'escalated' AND "resolutionState" = 'resolved'
|
|
54599
54718
|
ORDER BY "archivedAt" DESC
|
|
54600
54719
|
LIMIT 500`
|
|
54601
54720
|
);
|
|
@@ -54669,14 +54788,14 @@ escalationsRouter.post(
|
|
|
54669
54788
|
"resolutionState",
|
|
54670
54789
|
"conflictLog"
|
|
54671
54790
|
FROM archive
|
|
54672
|
-
WHERE id = $1::
|
|
54791
|
+
WHERE id = $1::int`,
|
|
54673
54792
|
[id]
|
|
54674
54793
|
);
|
|
54675
54794
|
if (archiveResult.rows.length === 0) {
|
|
54676
54795
|
throw createApiError(`Escalation not found: ${id}`, "NOT_FOUND", 404);
|
|
54677
54796
|
}
|
|
54678
54797
|
const archiveRow = archiveResult.rows[0];
|
|
54679
|
-
if (archiveRow.resolutionState != null) {
|
|
54798
|
+
if (archiveRow.resolutionState != null && archiveRow.resolutionState !== "pending") {
|
|
54680
54799
|
throw createApiError(
|
|
54681
54800
|
`Escalation ${id} is already resolved (resolutionState: ${archiveRow.resolutionState})`,
|
|
54682
54801
|
"ALREADY_RESOLVED",
|
|
@@ -54689,8 +54808,8 @@ escalationsRouter.post(
|
|
|
54689
54808
|
const resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
54690
54809
|
if (resolution === "keep_existing") {
|
|
54691
54810
|
await query(
|
|
54692
|
-
`UPDATE archive SET "resolutionState" = '
|
|
54693
|
-
[id]
|
|
54811
|
+
`UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
|
|
54812
|
+
[id, JSON.stringify({ resolutionDetail: "keep_existing", resolvedAt, resolvedBy: "control_plane_operator" })]
|
|
54694
54813
|
);
|
|
54695
54814
|
} else if (resolution === "accept_challenger") {
|
|
54696
54815
|
const valueRaw = archiveRow.valueRaw;
|
|
@@ -54717,8 +54836,8 @@ escalationsRouter.post(
|
|
|
54717
54836
|
[entityType, entityId, key, JSON.stringify(valueRaw), valueSummary, confidence, source, createdBy, validFrom, validUntil]
|
|
54718
54837
|
);
|
|
54719
54838
|
await query(
|
|
54720
|
-
`UPDATE archive SET "resolutionState" = '
|
|
54721
|
-
[id]
|
|
54839
|
+
`UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
|
|
54840
|
+
[id, JSON.stringify({ resolutionDetail: "accept_challenger", resolvedAt, resolvedBy: "control_plane_operator" })]
|
|
54722
54841
|
);
|
|
54723
54842
|
} else {
|
|
54724
54843
|
const parsedCustom = JSON.parse(customValue);
|
|
@@ -54740,8 +54859,8 @@ escalationsRouter.post(
|
|
|
54740
54859
|
[entityType, entityId, key, JSON.stringify(parsedCustom), createdBy]
|
|
54741
54860
|
);
|
|
54742
54861
|
await query(
|
|
54743
|
-
`UPDATE archive SET "resolutionState" = '
|
|
54744
|
-
[id]
|
|
54862
|
+
`UPDATE archive SET "resolutionState" = 'resolved', "conflictLog" = COALESCE("conflictLog", '{}'::jsonb) || $2::jsonb WHERE id = $1::int`,
|
|
54863
|
+
[id, JSON.stringify({ resolutionDetail: "custom", resolvedAt, resolvedBy: "control_plane_operator" })]
|
|
54745
54864
|
);
|
|
54746
54865
|
}
|
|
54747
54866
|
await writeAuditLog2("conflict_resolved", {
|
|
@@ -54769,8 +54888,8 @@ escalationsRouter.use(errorHandler2);
|
|
|
54769
54888
|
|
|
54770
54889
|
// src/server/routes/control-plane/providers.ts
|
|
54771
54890
|
var import_express9 = __toESM(require_express2(), 1);
|
|
54772
|
-
var
|
|
54773
|
-
var
|
|
54891
|
+
var import_fs7 = require("fs");
|
|
54892
|
+
var import_path10 = require("path");
|
|
54774
54893
|
init_db();
|
|
54775
54894
|
var providersRouter = (0, import_express9.Router)();
|
|
54776
54895
|
var PROVIDER_KEY_VARS = {
|
|
@@ -54807,8 +54926,8 @@ function scopeSummary3(scope) {
|
|
|
54807
54926
|
}
|
|
54808
54927
|
function writeEnvVarAtPath(filePath, key, value, syncLiveEnv) {
|
|
54809
54928
|
let raw = "";
|
|
54810
|
-
if ((0,
|
|
54811
|
-
raw = (0,
|
|
54929
|
+
if ((0, import_fs7.existsSync)(filePath)) {
|
|
54930
|
+
raw = (0, import_fs7.readFileSync)(filePath, "utf8");
|
|
54812
54931
|
}
|
|
54813
54932
|
const lineEnding = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
54814
54933
|
const rawLines = raw.split(/\r?\n/);
|
|
@@ -54835,7 +54954,7 @@ function writeEnvVarAtPath(filePath, key, value, syncLiveEnv) {
|
|
|
54835
54954
|
}
|
|
54836
54955
|
updated.push(`${key}=${value}`);
|
|
54837
54956
|
}
|
|
54838
|
-
(0,
|
|
54957
|
+
(0, import_fs7.writeFileSync)(filePath, updated.join(lineEnding), "utf8");
|
|
54839
54958
|
if (syncLiveEnv) {
|
|
54840
54959
|
if (value !== null) {
|
|
54841
54960
|
env[key] = value;
|
|
@@ -54852,7 +54971,7 @@ var reachabilityCache = /* @__PURE__ */ new Map();
|
|
|
54852
54971
|
var REACHABILITY_TTL_MS = 60 * 1e3;
|
|
54853
54972
|
function currentBindingEnvPath() {
|
|
54854
54973
|
const configured = env["IRANTI_INSTANCE_ENV"] ?? process.env["IRANTI_INSTANCE_ENV"] ?? "";
|
|
54855
|
-
return configured.trim() ? (0,
|
|
54974
|
+
return configured.trim() ? (0, import_path10.resolve)(configured) : null;
|
|
54856
54975
|
}
|
|
54857
54976
|
async function resolveScopeFromRequest2(req) {
|
|
54858
54977
|
const paramInstance = typeof req.params["instanceId"] === "string" ? req.params["instanceId"] : "";
|
|
@@ -54868,7 +54987,7 @@ async function resolveScopeFromRequest2(req) {
|
|
|
54868
54987
|
}
|
|
54869
54988
|
function shouldSyncLiveEnv(scope) {
|
|
54870
54989
|
const bindingEnvPath = currentBindingEnvPath();
|
|
54871
|
-
return bindingEnvPath !== null && bindingEnvPath === (0,
|
|
54990
|
+
return bindingEnvPath !== null && bindingEnvPath === (0, import_path10.resolve)(scope.instanceEnvPath);
|
|
54872
54991
|
}
|
|
54873
54992
|
function writeEnvVarForScope(scope, key, value) {
|
|
54874
54993
|
writeEnvVarAtPath(scope.instanceEnvPath, key, value, shouldSyncLiveEnv(scope));
|
|
@@ -56683,7 +56802,7 @@ whoknowsRouter.use((err, _req, res, _next) => {
|
|
|
56683
56802
|
var import_express14 = __toESM(require_express2(), 1);
|
|
56684
56803
|
init_esm();
|
|
56685
56804
|
var diagnosticsRouter = (0, import_express14.Router)();
|
|
56686
|
-
var { Pool:
|
|
56805
|
+
var { Pool: Pool8 } = esm_default;
|
|
56687
56806
|
var DIAGNOSTIC_ENTITY_TYPE = "__diagnostics__";
|
|
56688
56807
|
var DIAGNOSTIC_ENTITY_ID = "__probe__";
|
|
56689
56808
|
var ROUNDTRIP_KEY = "roundtrip_marker";
|
|
@@ -56768,7 +56887,7 @@ function buildKbRouteFailure(scope, routeLabel, statusCode, responseBody, authFi
|
|
|
56768
56887
|
}
|
|
56769
56888
|
async function withScopedPool3(databaseUrl2, fn) {
|
|
56770
56889
|
if (!databaseUrl2) return fn(null);
|
|
56771
|
-
const pool2 = new
|
|
56890
|
+
const pool2 = new Pool8({
|
|
56772
56891
|
connectionString: databaseUrl2,
|
|
56773
56892
|
max: 1,
|
|
56774
56893
|
idleTimeoutMillis: 1e3,
|
|
@@ -57509,8 +57628,28 @@ metricsRouter.use(
|
|
|
57509
57628
|
|
|
57510
57629
|
// src/server/routes/control-plane/overview.ts
|
|
57511
57630
|
var import_express16 = __toESM(require_express2(), 1);
|
|
57631
|
+
init_esm();
|
|
57512
57632
|
init_db();
|
|
57633
|
+
var { Pool: Pool9 } = esm_default;
|
|
57513
57634
|
var overviewRouter = (0, import_express16.Router)();
|
|
57635
|
+
async function withScopedPool4(databaseUrl2, fn) {
|
|
57636
|
+
if (!databaseUrl2) return fn(null);
|
|
57637
|
+
const pool2 = new Pool9({
|
|
57638
|
+
connectionString: databaseUrl2,
|
|
57639
|
+
max: 2,
|
|
57640
|
+
idleTimeoutMillis: 2e3,
|
|
57641
|
+
connectionTimeoutMillis: 5e3
|
|
57642
|
+
});
|
|
57643
|
+
try {
|
|
57644
|
+
return await fn(pool2);
|
|
57645
|
+
} finally {
|
|
57646
|
+
await pool2.end().catch(() => {
|
|
57647
|
+
});
|
|
57648
|
+
}
|
|
57649
|
+
}
|
|
57650
|
+
function scopedQuery(pool2) {
|
|
57651
|
+
return (text, params) => pool2.query(text, params);
|
|
57652
|
+
}
|
|
57514
57653
|
var HEALTH_FALLBACK = {
|
|
57515
57654
|
overall: "error",
|
|
57516
57655
|
checks: [],
|
|
@@ -57518,11 +57657,11 @@ var HEALTH_FALLBACK = {
|
|
|
57518
57657
|
};
|
|
57519
57658
|
var WRITE_ACTION_TYPES2 = ["write_created", "write_replaced"];
|
|
57520
57659
|
var ARCHIVAL_ACTION_TYPES2 = ["entry_archived", "entry_decayed"];
|
|
57521
|
-
async function fetchKBSummary() {
|
|
57660
|
+
async function fetchKBSummary(queryFn = query) {
|
|
57522
57661
|
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
57523
57662
|
const fallbackFromKnowledgeBase = async (truncated) => {
|
|
57524
57663
|
try {
|
|
57525
|
-
const result = await
|
|
57664
|
+
const result = await queryFn(
|
|
57526
57665
|
`SELECT
|
|
57527
57666
|
COUNT(*)::text AS total_facts,
|
|
57528
57667
|
COUNT(*) FILTER (WHERE "createdAt" >= NOW() - INTERVAL '24 hours')::text AS facts_last_24h,
|
|
@@ -57544,7 +57683,7 @@ async function fetchKBSummary() {
|
|
|
57544
57683
|
}
|
|
57545
57684
|
};
|
|
57546
57685
|
try {
|
|
57547
|
-
const existsResult = await
|
|
57686
|
+
const existsResult = await queryFn(
|
|
57548
57687
|
`SELECT EXISTS (
|
|
57549
57688
|
SELECT 1 FROM information_schema.tables
|
|
57550
57689
|
WHERE table_schema = 'public' AND table_name = 'staff_events'
|
|
@@ -57554,7 +57693,7 @@ async function fetchKBSummary() {
|
|
|
57554
57693
|
if (!tableExists) {
|
|
57555
57694
|
return await fallbackFromKnowledgeBase(true);
|
|
57556
57695
|
}
|
|
57557
|
-
const result = await
|
|
57696
|
+
const result = await queryFn(
|
|
57558
57697
|
`SELECT
|
|
57559
57698
|
COUNT(*) FILTER (WHERE action_type = ANY($1)) AS total_writes_all_time,
|
|
57560
57699
|
COUNT(*) FILTER (WHERE action_type = ANY($2)) AS total_archived_all_time,
|
|
@@ -57582,9 +57721,9 @@ async function fetchKBSummary() {
|
|
|
57582
57721
|
}
|
|
57583
57722
|
}
|
|
57584
57723
|
var PG_UNDEFINED_TABLE2 = "42P01";
|
|
57585
|
-
async function fetchRecentEvents() {
|
|
57724
|
+
async function fetchRecentEvents(queryFn = query) {
|
|
57586
57725
|
try {
|
|
57587
|
-
const result = await
|
|
57726
|
+
const result = await queryFn(
|
|
57588
57727
|
`SELECT id, staff_component, action_type, agent_id, entity_type, entity_id, key, reason, timestamp
|
|
57589
57728
|
FROM staff_events
|
|
57590
57729
|
ORDER BY timestamp DESC
|
|
@@ -57651,10 +57790,10 @@ function getIrantiUrl4() {
|
|
|
57651
57790
|
function getIrantiApiKey5() {
|
|
57652
57791
|
return env["IRANTI_API_KEY"] ?? process.env["IRANTI_API_KEY"] ?? "";
|
|
57653
57792
|
}
|
|
57654
|
-
async function fetchActiveAgents() {
|
|
57793
|
+
async function fetchActiveAgents(instanceBaseUrl, instanceApiKey) {
|
|
57655
57794
|
try {
|
|
57656
|
-
const baseUrl = getIrantiUrl4();
|
|
57657
|
-
const apiKey = getIrantiApiKey5();
|
|
57795
|
+
const baseUrl = instanceBaseUrl ?? getIrantiUrl4();
|
|
57796
|
+
const apiKey = instanceApiKey ?? getIrantiApiKey5();
|
|
57658
57797
|
const headers = { "Content-Type": "application/json" };
|
|
57659
57798
|
if (apiKey) headers["X-Iranti-Key"] = apiKey;
|
|
57660
57799
|
const controller = new AbortController();
|
|
@@ -57704,26 +57843,38 @@ async function fetchActiveAgents() {
|
|
|
57704
57843
|
return [];
|
|
57705
57844
|
}
|
|
57706
57845
|
}
|
|
57707
|
-
overviewRouter.get("/", async (
|
|
57846
|
+
overviewRouter.get("/", async (req, res) => {
|
|
57708
57847
|
const fetchedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
57709
|
-
const
|
|
57710
|
-
|
|
57711
|
-
|
|
57712
|
-
|
|
57713
|
-
|
|
57714
|
-
|
|
57715
|
-
|
|
57716
|
-
|
|
57717
|
-
|
|
57718
|
-
|
|
57719
|
-
|
|
57720
|
-
|
|
57721
|
-
|
|
57722
|
-
|
|
57723
|
-
|
|
57724
|
-
|
|
57725
|
-
|
|
57726
|
-
|
|
57848
|
+
const instanceId = typeof req.query["instanceId"] === "string" ? req.query["instanceId"] : void 0;
|
|
57849
|
+
let scope = null;
|
|
57850
|
+
if (instanceId) {
|
|
57851
|
+
scope = await resolveInstanceAuthority(instanceId).catch(() => null);
|
|
57852
|
+
}
|
|
57853
|
+
const buildResponse = async (pool2) => {
|
|
57854
|
+
const qFn = pool2 ? scopedQuery(pool2) : query;
|
|
57855
|
+
const instanceBaseUrl = scope?.apiBaseUrl;
|
|
57856
|
+
const instanceApiKey = scope?.apiKey ?? void 0;
|
|
57857
|
+
const [healthResult, kbResult, eventsResult, agentsResult] = await Promise.allSettled([
|
|
57858
|
+
(async () => {
|
|
57859
|
+
const full = await runAllHealthChecks(instanceId);
|
|
57860
|
+
return {
|
|
57861
|
+
overall: full.overall,
|
|
57862
|
+
checks: full.checks.map((c) => ({ name: c.name, status: c.status })),
|
|
57863
|
+
fetchedAt: full.checkedAt
|
|
57864
|
+
};
|
|
57865
|
+
})(),
|
|
57866
|
+
fetchKBSummary(qFn),
|
|
57867
|
+
fetchRecentEvents(qFn),
|
|
57868
|
+
fetchActiveAgents(instanceBaseUrl, instanceApiKey)
|
|
57869
|
+
]);
|
|
57870
|
+
const health = healthResult.status === "fulfilled" ? healthResult.value : { ...HEALTH_FALLBACK, fetchedAt };
|
|
57871
|
+
const kb = kbResult.status === "fulfilled" ? kbResult.value : { totalFacts: 0, factsLast24h: 0, activeAgentsLast7d: 0, truncated: true, fetchedAt };
|
|
57872
|
+
const recentEvents = eventsResult.status === "fulfilled" ? eventsResult.value : [];
|
|
57873
|
+
const activeAgents = agentsResult.status === "fulfilled" ? agentsResult.value : [];
|
|
57874
|
+
return { health, kb, recentEvents, activeAgents, fetchedAt };
|
|
57875
|
+
};
|
|
57876
|
+
const databaseUrl2 = scope?.databaseUrl ?? null;
|
|
57877
|
+
const response = scope ? await withScopedPool4(databaseUrl2, (pool2) => buildResponse(pool2)) : await buildResponse(null);
|
|
57727
57878
|
res.status(200).json(response);
|
|
57728
57879
|
});
|
|
57729
57880
|
|
|
@@ -57964,6 +58115,44 @@ function normalizeSort(value) {
|
|
|
57964
58115
|
return void 0;
|
|
57965
58116
|
}
|
|
57966
58117
|
}
|
|
58118
|
+
function normalizeLedgerLevel(value) {
|
|
58119
|
+
switch (String(value ?? "").trim().toLowerCase()) {
|
|
58120
|
+
case "audit":
|
|
58121
|
+
return "audit";
|
|
58122
|
+
case "debug":
|
|
58123
|
+
return "debug";
|
|
58124
|
+
default:
|
|
58125
|
+
return void 0;
|
|
58126
|
+
}
|
|
58127
|
+
}
|
|
58128
|
+
function parseLedgerLimit(value) {
|
|
58129
|
+
if (value === void 0 || value === null || String(value).trim() === "") {
|
|
58130
|
+
return 50;
|
|
58131
|
+
}
|
|
58132
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
58133
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
58134
|
+
throw new Error("limit must be an integer >= 1");
|
|
58135
|
+
}
|
|
58136
|
+
return Math.min(parsed, 200);
|
|
58137
|
+
}
|
|
58138
|
+
function normalizeLedgerEvent(raw) {
|
|
58139
|
+
const level = normalizeLedgerLevel(raw["level"]) ?? "audit";
|
|
58140
|
+
const metadata = raw["metadata"];
|
|
58141
|
+
return {
|
|
58142
|
+
eventId: String(raw["eventId"] ?? raw["event_id"] ?? raw["id"] ?? ""),
|
|
58143
|
+
timestamp: raw["timestamp"] instanceof Date ? raw["timestamp"].toISOString() : String(raw["timestamp"] ?? ""),
|
|
58144
|
+
staffComponent: String(raw["staffComponent"] ?? raw["staff_component"] ?? "Attendant"),
|
|
58145
|
+
actionType: String(raw["actionType"] ?? raw["action_type"] ?? ""),
|
|
58146
|
+
agentId: String(raw["agentId"] ?? raw["agent_id"] ?? ""),
|
|
58147
|
+
source: String(raw["source"] ?? ""),
|
|
58148
|
+
entityType: typeof raw["entityType"] === "string" ? raw["entityType"] : typeof raw["entity_type"] === "string" ? raw["entity_type"] : null,
|
|
58149
|
+
entityId: typeof raw["entityId"] === "string" ? raw["entityId"] : typeof raw["entity_id"] === "string" ? raw["entity_id"] : null,
|
|
58150
|
+
key: typeof raw["key"] === "string" ? raw["key"] : null,
|
|
58151
|
+
reason: typeof raw["reason"] === "string" ? raw["reason"] : null,
|
|
58152
|
+
level,
|
|
58153
|
+
metadata: metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null
|
|
58154
|
+
};
|
|
58155
|
+
}
|
|
57967
58156
|
function applySessionFilters(sessions, options) {
|
|
57968
58157
|
let filtered = sessions;
|
|
57969
58158
|
if (options.agentId) filtered = filtered.filter((entry) => entry.agentId === options.agentId);
|
|
@@ -58026,6 +58215,98 @@ async function fetchIrantiSessions(req) {
|
|
|
58026
58215
|
clearTimeout(timeout);
|
|
58027
58216
|
}
|
|
58028
58217
|
}
|
|
58218
|
+
async function fetchLedgerByParams(req, params) {
|
|
58219
|
+
const url2 = new URL(`${getIrantiUrl5()}/memory/ledger`);
|
|
58220
|
+
for (const [k, v] of Object.entries(params)) url2.searchParams.set(k, v);
|
|
58221
|
+
const controller = new AbortController();
|
|
58222
|
+
const timeout = setTimeout(() => controller.abort(), 4e3);
|
|
58223
|
+
try {
|
|
58224
|
+
const response = await fetch(url2.toString(), {
|
|
58225
|
+
method: "GET",
|
|
58226
|
+
headers: buildHeaders5(req),
|
|
58227
|
+
signal: controller.signal
|
|
58228
|
+
});
|
|
58229
|
+
const body = await response.json().catch(() => ({}));
|
|
58230
|
+
if (!response.ok) {
|
|
58231
|
+
const upstreamError = typeof body["error"] === "string" ? body["error"] : null;
|
|
58232
|
+
return {
|
|
58233
|
+
items: [],
|
|
58234
|
+
error: upstreamError ? `Iranti /memory/ledger returned HTTP ${response.status}: ${upstreamError}` : `Iranti /memory/ledger returned HTTP ${response.status}`
|
|
58235
|
+
};
|
|
58236
|
+
}
|
|
58237
|
+
const rawItems = Array.isArray(body["items"]) ? body["items"] : [];
|
|
58238
|
+
return {
|
|
58239
|
+
items: rawItems.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)).map(normalizeLedgerEvent)
|
|
58240
|
+
};
|
|
58241
|
+
} catch (err) {
|
|
58242
|
+
return { items: [], error: err instanceof Error ? err.message : String(err) };
|
|
58243
|
+
} finally {
|
|
58244
|
+
clearTimeout(timeout);
|
|
58245
|
+
}
|
|
58246
|
+
}
|
|
58247
|
+
async function fetchIrantiSessionLedger(req, sessionId) {
|
|
58248
|
+
const limit = parseLedgerLimit(req.query["limit"]);
|
|
58249
|
+
const agentId = typeof req.query["agentId"] === "string" ? req.query["agentId"].trim() : "";
|
|
58250
|
+
const actionType = typeof req.query["actionType"] === "string" ? req.query["actionType"].trim() : "";
|
|
58251
|
+
const host = typeof req.query["host"] === "string" ? req.query["host"].trim() : "";
|
|
58252
|
+
const level = normalizeLedgerLevel(req.query["level"]);
|
|
58253
|
+
const startedAt = typeof req.query["startedAt"] === "string" ? req.query["startedAt"].trim() : "";
|
|
58254
|
+
const lastHeartbeatAt = typeof req.query["lastHeartbeatAt"] === "string" ? req.query["lastHeartbeatAt"].trim() : "";
|
|
58255
|
+
const sharedParams = { limit: String(limit) };
|
|
58256
|
+
if (agentId) sharedParams["agentId"] = agentId;
|
|
58257
|
+
if (actionType) sharedParams["actionType"] = actionType;
|
|
58258
|
+
if (host) sharedParams["host"] = host;
|
|
58259
|
+
if (level) sharedParams["level"] = level;
|
|
58260
|
+
try {
|
|
58261
|
+
const bySessionId = fetchLedgerByParams(req, { ...sharedParams, sessionId });
|
|
58262
|
+
let byTimeRange = Promise.resolve({ items: [] });
|
|
58263
|
+
if (agentId && startedAt) {
|
|
58264
|
+
const after = new Date(new Date(startedAt).getTime() - 10 * 6e4).toISOString();
|
|
58265
|
+
const before = lastHeartbeatAt ? new Date(new Date(lastHeartbeatAt).getTime() + 2 * 6e4).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
|
|
58266
|
+
byTimeRange = fetchLedgerByParams(req, {
|
|
58267
|
+
...sharedParams,
|
|
58268
|
+
agentId,
|
|
58269
|
+
after,
|
|
58270
|
+
before,
|
|
58271
|
+
limit: String(limit)
|
|
58272
|
+
});
|
|
58273
|
+
}
|
|
58274
|
+
const [sessionResult, rangeResult] = await Promise.all([bySessionId, byTimeRange]);
|
|
58275
|
+
if (sessionResult.error && rangeResult.items.length === 0) {
|
|
58276
|
+
return {
|
|
58277
|
+
items: [],
|
|
58278
|
+
total: 0,
|
|
58279
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58280
|
+
note: "Source: Iranti /memory/ledger",
|
|
58281
|
+
error: sessionResult.error
|
|
58282
|
+
};
|
|
58283
|
+
}
|
|
58284
|
+
const seen = /* @__PURE__ */ new Set();
|
|
58285
|
+
const merged = [];
|
|
58286
|
+
for (const item of [...sessionResult.items, ...rangeResult.items]) {
|
|
58287
|
+
if (item.eventId && !seen.has(item.eventId)) {
|
|
58288
|
+
seen.add(item.eventId);
|
|
58289
|
+
merged.push(item);
|
|
58290
|
+
}
|
|
58291
|
+
}
|
|
58292
|
+
merged.sort((a, b) => b.timestamp > a.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0);
|
|
58293
|
+
const limited = merged.slice(0, limit);
|
|
58294
|
+
return {
|
|
58295
|
+
items: limited,
|
|
58296
|
+
total: merged.length,
|
|
58297
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58298
|
+
note: "Source: Iranti /memory/ledger"
|
|
58299
|
+
};
|
|
58300
|
+
} catch (error2) {
|
|
58301
|
+
return {
|
|
58302
|
+
items: [],
|
|
58303
|
+
total: 0,
|
|
58304
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58305
|
+
note: "Source: Iranti /memory/ledger",
|
|
58306
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
58307
|
+
};
|
|
58308
|
+
}
|
|
58309
|
+
}
|
|
58029
58310
|
async function fetchLocalFallbackSessions() {
|
|
58030
58311
|
const [legacyResult, attendantResult] = await Promise.all([
|
|
58031
58312
|
query(`
|
|
@@ -58095,6 +58376,29 @@ sessionsRouter.get("/", async (req, res) => {
|
|
|
58095
58376
|
});
|
|
58096
58377
|
}
|
|
58097
58378
|
});
|
|
58379
|
+
sessionsRouter.get("/:sessionId/ledger", async (req, res) => {
|
|
58380
|
+
try {
|
|
58381
|
+
const sessionId = typeof req.params["sessionId"] === "string" ? req.params["sessionId"].trim() : "";
|
|
58382
|
+
if (!sessionId) {
|
|
58383
|
+
res.status(400).json({
|
|
58384
|
+
items: [],
|
|
58385
|
+
total: 0,
|
|
58386
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58387
|
+
error: "sessionId path parameter is required."
|
|
58388
|
+
});
|
|
58389
|
+
return;
|
|
58390
|
+
}
|
|
58391
|
+
const ledger = await fetchIrantiSessionLedger(req, sessionId);
|
|
58392
|
+
res.json(ledger);
|
|
58393
|
+
} catch (error2) {
|
|
58394
|
+
res.status(400).json({
|
|
58395
|
+
items: [],
|
|
58396
|
+
total: 0,
|
|
58397
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
58398
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
58399
|
+
});
|
|
58400
|
+
}
|
|
58401
|
+
});
|
|
58098
58402
|
async function proxySessionAction(req, res, action) {
|
|
58099
58403
|
const agentId = typeof req.body?.agentId === "string" ? req.body.agentId.trim() : "";
|
|
58100
58404
|
if (!agentId) {
|
|
@@ -58149,7 +58453,7 @@ sessionsRouter.use((err, _req, res, _next) => {
|
|
|
58149
58453
|
|
|
58150
58454
|
// src/server/routes/control-plane/upgrade.ts
|
|
58151
58455
|
var import_express18 = __toESM(require_express2(), 1);
|
|
58152
|
-
var
|
|
58456
|
+
var import_child_process3 = require("child_process");
|
|
58153
58457
|
var upgradeRouter = (0, import_express18.Router)();
|
|
58154
58458
|
var JOB_STORE_MAX = 50;
|
|
58155
58459
|
var jobStore = /* @__PURE__ */ new Map();
|
|
@@ -58209,7 +58513,7 @@ upgradeRouter.post("/:name/upgrade", async (req, res) => {
|
|
|
58209
58513
|
if (runtimeRoot) cliArgs.push("--root", runtimeRoot);
|
|
58210
58514
|
let child;
|
|
58211
58515
|
try {
|
|
58212
|
-
child = (0,
|
|
58516
|
+
child = (0, import_child_process3.spawn)(launch.command, cliArgs, {
|
|
58213
58517
|
stdio: ["ignore", "pipe", "pipe"],
|
|
58214
58518
|
windowsHide: true
|
|
58215
58519
|
});
|
|
@@ -58417,12 +58721,12 @@ installStateRouter.get("/", async (_req, res) => {
|
|
|
58417
58721
|
|
|
58418
58722
|
// src/server/routes/control-plane/lifecycle.ts
|
|
58419
58723
|
var import_express21 = __toESM(require_express2(), 1);
|
|
58420
|
-
var
|
|
58421
|
-
var
|
|
58422
|
-
var
|
|
58724
|
+
var import_child_process4 = require("child_process");
|
|
58725
|
+
var import_path12 = require("path");
|
|
58726
|
+
var import_promises7 = require("fs/promises");
|
|
58423
58727
|
|
|
58424
58728
|
// src/server/routes/control-plane/instance-identifiers.ts
|
|
58425
|
-
var
|
|
58729
|
+
var import_path11 = require("path");
|
|
58426
58730
|
init_db();
|
|
58427
58731
|
function configuredInstanceEnvPath() {
|
|
58428
58732
|
const raw = process.env["IRANTI_INSTANCE_ENV"] ?? env["IRANTI_INSTANCE_ENV"] ?? "";
|
|
@@ -58431,19 +58735,19 @@ function configuredInstanceEnvPath() {
|
|
|
58431
58735
|
function configuredRuntimeRoot() {
|
|
58432
58736
|
const instanceEnvPath = configuredInstanceEnvPath();
|
|
58433
58737
|
if (!instanceEnvPath) return process.cwd();
|
|
58434
|
-
return (0,
|
|
58738
|
+
return (0, import_path11.resolve)((0, import_path11.dirname)(instanceEnvPath), "..", "..");
|
|
58435
58739
|
}
|
|
58436
58740
|
function configuredInstanceName() {
|
|
58437
58741
|
const explicit = process.env["IRANTI_INSTANCE"] ?? env["IRANTI_INSTANCE"] ?? "";
|
|
58438
58742
|
if (explicit.trim()) return explicit.trim();
|
|
58439
58743
|
const instanceEnvPath = configuredInstanceEnvPath();
|
|
58440
58744
|
if (!instanceEnvPath) return null;
|
|
58441
|
-
return (0,
|
|
58745
|
+
return (0, import_path11.basename)((0, import_path11.dirname)(instanceEnvPath));
|
|
58442
58746
|
}
|
|
58443
58747
|
function getConfiguredInstanceIdentifiers() {
|
|
58444
58748
|
const runtimeRoot = configuredRuntimeRoot();
|
|
58445
58749
|
const instanceName = configuredInstanceName();
|
|
58446
|
-
const instanceDir = instanceName ? (0,
|
|
58750
|
+
const instanceDir = instanceName ? (0, import_path11.join)(runtimeRoot, "instances", instanceName) : runtimeRoot;
|
|
58447
58751
|
const instanceId = deriveInstanceId(instanceDir);
|
|
58448
58752
|
const valid = /* @__PURE__ */ new Set([instanceId]);
|
|
58449
58753
|
if (instanceName) valid.add(instanceName);
|
|
@@ -58471,8 +58775,8 @@ function parseEnvContent2(content) {
|
|
|
58471
58775
|
return parsed;
|
|
58472
58776
|
}
|
|
58473
58777
|
async function resolveInstancePort(runtimeRoot, name) {
|
|
58474
|
-
const envPath = (0,
|
|
58475
|
-
const content = await (0,
|
|
58778
|
+
const envPath = (0, import_path12.join)(runtimeRoot, "instances", name, ".env");
|
|
58779
|
+
const content = await (0, import_promises7.readFile)(envPath, "utf8");
|
|
58476
58780
|
const parsed = parseEnvContent2(content);
|
|
58477
58781
|
const rawPort = parsed["IRANTI_PORT"] ?? parsed["PORT"] ?? "3001";
|
|
58478
58782
|
const port = Number.parseInt(rawPort, 10);
|
|
@@ -58520,7 +58824,7 @@ function sleep(ms) {
|
|
|
58520
58824
|
}
|
|
58521
58825
|
function execFileAsync2(command, args) {
|
|
58522
58826
|
return new Promise((resolve10, reject) => {
|
|
58523
|
-
(0,
|
|
58827
|
+
(0, import_child_process4.execFile)(command, args, (error2) => {
|
|
58524
58828
|
if (error2) {
|
|
58525
58829
|
reject(error2);
|
|
58526
58830
|
return;
|
|
@@ -58665,7 +58969,7 @@ lifecycleRouter.post("/:name/start", async (req, res) => {
|
|
|
58665
58969
|
});
|
|
58666
58970
|
return;
|
|
58667
58971
|
}
|
|
58668
|
-
const child = (0,
|
|
58972
|
+
const child = (0, import_child_process4.spawn)(
|
|
58669
58973
|
launch.command,
|
|
58670
58974
|
[...launch.args, "run", "--instance", name, "--root", runtimeRoot],
|
|
58671
58975
|
{
|
|
@@ -58863,11 +59167,11 @@ lifecycleRouter.get("/:name/process-status", (req, res) => {
|
|
|
58863
59167
|
|
|
58864
59168
|
// src/server/routes/control-plane/open-file.ts
|
|
58865
59169
|
var import_express22 = __toESM(require_express2(), 1);
|
|
58866
|
-
var
|
|
59170
|
+
var import_child_process5 = require("child_process");
|
|
58867
59171
|
var import_util7 = require("util");
|
|
58868
59172
|
var path2 = __toESM(require("path"), 1);
|
|
58869
59173
|
var fs = __toESM(require("fs/promises"), 1);
|
|
58870
|
-
var execAsync = (0, import_util7.promisify)(
|
|
59174
|
+
var execAsync = (0, import_util7.promisify)(import_child_process5.exec);
|
|
58871
59175
|
var openFileRouter = (0, import_express22.Router)();
|
|
58872
59176
|
var ALLOWED_PATHS = /* @__PURE__ */ new Set([
|
|
58873
59177
|
".env.iranti",
|
|
@@ -58934,20 +59238,21 @@ openFileRouter.post("/", async (req, res) => {
|
|
|
58934
59238
|
// src/server/routes/control-plane/auth-keys.ts
|
|
58935
59239
|
var import_express23 = __toESM(require_express2(), 1);
|
|
58936
59240
|
var import_crypto4 = require("crypto");
|
|
58937
|
-
var
|
|
58938
|
-
var
|
|
58939
|
-
|
|
59241
|
+
var import_fs8 = require("fs");
|
|
59242
|
+
var import_path13 = require("path");
|
|
59243
|
+
init_esm();
|
|
58940
59244
|
var authKeysRouter = (0, import_express23.Router)();
|
|
59245
|
+
var { Pool: Pool10 } = esm_default;
|
|
58941
59246
|
var REGISTRY_ENTITY_TYPE = "system";
|
|
58942
59247
|
var REGISTRY_ENTITY_ID = "auth";
|
|
58943
59248
|
var REGISTRY_KEY = "api_keys";
|
|
58944
59249
|
var REGISTRY_SOURCE = "system";
|
|
58945
59250
|
var REGISTRY_CREATED_BY = "system";
|
|
58946
|
-
function keyPepper() {
|
|
58947
|
-
return
|
|
59251
|
+
function keyPepper(scope) {
|
|
59252
|
+
return scope.env["IRANTI_API_KEY_PEPPER"]?.trim() ?? "";
|
|
58948
59253
|
}
|
|
58949
|
-
function hashSecret(secret) {
|
|
58950
|
-
return (0, import_crypto4.createHash)("sha256").update(`${secret}${keyPepper()}`).digest("hex");
|
|
59254
|
+
function hashSecret(secret, scope) {
|
|
59255
|
+
return (0, import_crypto4.createHash)("sha256").update(`${secret}${keyPepper(scope)}`).digest("hex");
|
|
58951
59256
|
}
|
|
58952
59257
|
function generateSecret(length = 32) {
|
|
58953
59258
|
return (0, import_crypto4.randomBytes)(length).toString("base64url");
|
|
@@ -58990,8 +59295,21 @@ function validateScopeList(scopes) {
|
|
|
58990
59295
|
}
|
|
58991
59296
|
}
|
|
58992
59297
|
}
|
|
58993
|
-
async function
|
|
58994
|
-
const
|
|
59298
|
+
async function withScopedPool5(scope, fn) {
|
|
59299
|
+
const pool2 = new Pool10({
|
|
59300
|
+
connectionString: scope.databaseUrl,
|
|
59301
|
+
max: 1,
|
|
59302
|
+
idleTimeoutMillis: 3e4,
|
|
59303
|
+
connectionTimeoutMillis: 5e3
|
|
59304
|
+
});
|
|
59305
|
+
try {
|
|
59306
|
+
return await fn(pool2);
|
|
59307
|
+
} finally {
|
|
59308
|
+
await Promise.resolve(pool2.end()).catch(() => void 0);
|
|
59309
|
+
}
|
|
59310
|
+
}
|
|
59311
|
+
async function loadRegistry(pool2) {
|
|
59312
|
+
const result = await pool2.query(
|
|
58995
59313
|
`SELECT "valueRaw" FROM knowledge_base
|
|
58996
59314
|
WHERE "entityType" = $1 AND "entityId" = $2 AND key = $3
|
|
58997
59315
|
LIMIT 1`,
|
|
@@ -59000,13 +59318,12 @@ async function loadRegistry() {
|
|
|
59000
59318
|
if (result.rows.length === 0) {
|
|
59001
59319
|
return { version: 1, keys: [] };
|
|
59002
59320
|
}
|
|
59003
|
-
|
|
59004
|
-
return normalizeRegistry(raw);
|
|
59321
|
+
return normalizeRegistry(result.rows[0]?.valueRaw);
|
|
59005
59322
|
}
|
|
59006
|
-
async function saveRegistry(registry2) {
|
|
59323
|
+
async function saveRegistry(pool2, registry2) {
|
|
59007
59324
|
const normalized = normalizeRegistry(registry2);
|
|
59008
59325
|
const valueSummary = `API key registry (${normalized.keys.length} keys)`;
|
|
59009
|
-
await query(
|
|
59326
|
+
await pool2.query(
|
|
59010
59327
|
`INSERT INTO knowledge_base
|
|
59011
59328
|
("entityType", "entityId", key, "valueRaw", "valueSummary",
|
|
59012
59329
|
confidence, source, "createdBy", "isProtected", "conflictLog", "createdAt", "updatedAt")
|
|
@@ -59056,10 +59373,10 @@ function normalizeRegistry(raw) {
|
|
|
59056
59373
|
};
|
|
59057
59374
|
}
|
|
59058
59375
|
function syncTokenToProject(projectRoot, token) {
|
|
59059
|
-
const envPath = (0,
|
|
59376
|
+
const envPath = (0, import_path13.resolve)(projectRoot, ".env.iranti");
|
|
59060
59377
|
let lines = [];
|
|
59061
|
-
if ((0,
|
|
59062
|
-
lines = (0,
|
|
59378
|
+
if ((0, import_fs8.existsSync)(envPath)) {
|
|
59379
|
+
lines = (0, import_fs8.readFileSync)(envPath, "utf8").split("\n");
|
|
59063
59380
|
}
|
|
59064
59381
|
let found = false;
|
|
59065
59382
|
const updated = [];
|
|
@@ -59078,25 +59395,32 @@ function syncTokenToProject(projectRoot, token) {
|
|
|
59078
59395
|
if (!found) {
|
|
59079
59396
|
updated.push(`IRANTI_API_KEY=${token}`);
|
|
59080
59397
|
}
|
|
59081
|
-
(0,
|
|
59398
|
+
(0, import_fs8.writeFileSync)(envPath, updated.join("\n"), "utf8");
|
|
59082
59399
|
}
|
|
59083
|
-
function
|
|
59084
|
-
|
|
59085
|
-
|
|
59086
|
-
|
|
59087
|
-
|
|
59400
|
+
async function resolveScopedInstance(req, res) {
|
|
59401
|
+
const instanceId = typeof req.query.instanceId === "string" ? req.query.instanceId.trim() : "";
|
|
59402
|
+
const scope = await resolveInstanceAuthority(instanceId || void 0);
|
|
59403
|
+
if (!scope) {
|
|
59404
|
+
res.status(404).json({
|
|
59405
|
+
error: instanceId ? `Iranti instance not found: ${instanceId}` : "No Iranti instance could be resolved. Select an instance first.",
|
|
59406
|
+
code: "INSTANCE_NOT_FOUND"
|
|
59407
|
+
});
|
|
59408
|
+
return null;
|
|
59409
|
+
}
|
|
59410
|
+
if (!scope.databaseUrl) {
|
|
59088
59411
|
res.status(503).json({
|
|
59089
|
-
error:
|
|
59412
|
+
error: `No DATABASE_URL configured for instance ${scope.instanceName}. Configure the instance database first.`,
|
|
59090
59413
|
code: "NO_DATABASE_URL"
|
|
59091
59414
|
});
|
|
59092
|
-
return
|
|
59415
|
+
return null;
|
|
59093
59416
|
}
|
|
59094
|
-
return
|
|
59417
|
+
return scope;
|
|
59095
59418
|
}
|
|
59096
|
-
authKeysRouter.get("/", async (
|
|
59097
|
-
|
|
59419
|
+
authKeysRouter.get("/", async (req, res) => {
|
|
59420
|
+
const scope = await resolveScopedInstance(req, res);
|
|
59421
|
+
if (!scope) return;
|
|
59098
59422
|
try {
|
|
59099
|
-
const registry2 = await loadRegistry();
|
|
59423
|
+
const registry2 = await withScopedPool5(scope, (pool2) => loadRegistry(pool2));
|
|
59100
59424
|
const keys = registry2.keys.map((k) => ({
|
|
59101
59425
|
keyId: k.keyId,
|
|
59102
59426
|
owner: k.owner,
|
|
@@ -59115,7 +59439,8 @@ authKeysRouter.get("/", async (_req, res) => {
|
|
|
59115
59439
|
}
|
|
59116
59440
|
});
|
|
59117
59441
|
authKeysRouter.post("/", async (req, res) => {
|
|
59118
|
-
|
|
59442
|
+
const scope = await resolveScopedInstance(req, res);
|
|
59443
|
+
if (!scope) return;
|
|
59119
59444
|
const { keyId: keyIdRaw, owner, scopes, description, syncToProject } = req.body;
|
|
59120
59445
|
if (!keyIdRaw || typeof keyIdRaw !== "string") {
|
|
59121
59446
|
res.status(400).json({ error: "keyId is required and must be a string." });
|
|
@@ -59123,7 +59448,7 @@ authKeysRouter.post("/", async (req, res) => {
|
|
|
59123
59448
|
}
|
|
59124
59449
|
const keyId = sanitizeKeyId(keyIdRaw);
|
|
59125
59450
|
if (!keyId) {
|
|
59126
|
-
res.status(400).json({ error: 'keyId is invalid
|
|
59451
|
+
res.status(400).json({ error: 'keyId is invalid - only letters, numbers, "_" and "-" are allowed.' });
|
|
59127
59452
|
return;
|
|
59128
59453
|
}
|
|
59129
59454
|
if (!owner || typeof owner !== "string" || !owner.trim()) {
|
|
@@ -59138,25 +59463,27 @@ authKeysRouter.post("/", async (req, res) => {
|
|
|
59138
59463
|
return;
|
|
59139
59464
|
}
|
|
59140
59465
|
try {
|
|
59141
|
-
const registry2 = await loadRegistry();
|
|
59142
59466
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
59143
59467
|
const secret = generateSecret();
|
|
59144
|
-
const secretHash = hashSecret(secret);
|
|
59145
|
-
const record2 = {
|
|
59146
|
-
keyId,
|
|
59147
|
-
owner: owner.trim(),
|
|
59148
|
-
secretHash,
|
|
59149
|
-
scopes: scopeList,
|
|
59150
|
-
isActive: true,
|
|
59151
|
-
createdAt: now,
|
|
59152
|
-
updatedAt: now,
|
|
59153
|
-
revokedAt: null,
|
|
59154
|
-
description: typeof description === "string" ? description.trim() || void 0 : void 0
|
|
59155
|
-
};
|
|
59156
|
-
const withoutExisting = registry2.keys.filter((k) => k.keyId !== keyId);
|
|
59157
|
-
withoutExisting.push(record2);
|
|
59158
|
-
await saveRegistry({ ...registry2, keys: withoutExisting });
|
|
59468
|
+
const secretHash = hashSecret(secret, scope);
|
|
59159
59469
|
const token = formatToken(keyId, secret);
|
|
59470
|
+
await withScopedPool5(scope, async (pool2) => {
|
|
59471
|
+
const registry2 = await loadRegistry(pool2);
|
|
59472
|
+
const record2 = {
|
|
59473
|
+
keyId,
|
|
59474
|
+
owner: owner.trim(),
|
|
59475
|
+
secretHash,
|
|
59476
|
+
scopes: scopeList,
|
|
59477
|
+
isActive: true,
|
|
59478
|
+
createdAt: now,
|
|
59479
|
+
updatedAt: now,
|
|
59480
|
+
revokedAt: null,
|
|
59481
|
+
description: typeof description === "string" ? description.trim() || void 0 : void 0
|
|
59482
|
+
};
|
|
59483
|
+
const withoutExisting = registry2.keys.filter((key) => key.keyId !== keyId);
|
|
59484
|
+
withoutExisting.push(record2);
|
|
59485
|
+
await saveRegistry(pool2, { ...registry2, keys: withoutExisting });
|
|
59486
|
+
});
|
|
59160
59487
|
if (typeof syncToProject === "string" && syncToProject.trim()) {
|
|
59161
59488
|
try {
|
|
59162
59489
|
syncTokenToProject(syncToProject.trim(), token);
|
|
@@ -59172,12 +59499,7 @@ authKeysRouter.post("/", async (req, res) => {
|
|
|
59172
59499
|
return;
|
|
59173
59500
|
}
|
|
59174
59501
|
}
|
|
59175
|
-
res.status(201).json({
|
|
59176
|
-
ok: true,
|
|
59177
|
-
keyId,
|
|
59178
|
-
token,
|
|
59179
|
-
scopes: scopeList
|
|
59180
|
-
});
|
|
59502
|
+
res.status(201).json({ ok: true, keyId, token, scopes: scopeList });
|
|
59181
59503
|
} catch (err) {
|
|
59182
59504
|
const message = err instanceof Error ? err.message : String(err);
|
|
59183
59505
|
console.error("[auth-keys] POST failed:", message);
|
|
@@ -59185,22 +59507,30 @@ authKeysRouter.post("/", async (req, res) => {
|
|
|
59185
59507
|
}
|
|
59186
59508
|
});
|
|
59187
59509
|
authKeysRouter.delete("/:keyId", async (req, res) => {
|
|
59188
|
-
|
|
59510
|
+
const scope = await resolveScopedInstance(req, res);
|
|
59511
|
+
if (!scope) return;
|
|
59189
59512
|
const keyId = sanitizeKeyId(req.params.keyId ?? "");
|
|
59190
59513
|
if (!keyId) {
|
|
59191
59514
|
res.status(400).json({ error: "keyId is required." });
|
|
59192
59515
|
return;
|
|
59193
59516
|
}
|
|
59194
59517
|
try {
|
|
59195
|
-
const
|
|
59196
|
-
|
|
59197
|
-
|
|
59518
|
+
const revoked = await withScopedPool5(scope, async (pool2) => {
|
|
59519
|
+
const registry2 = await loadRegistry(pool2);
|
|
59520
|
+
const target = registry2.keys.find((key) => key.keyId === keyId);
|
|
59521
|
+
if (!target) {
|
|
59522
|
+
return false;
|
|
59523
|
+
}
|
|
59524
|
+
target.isActive = false;
|
|
59525
|
+
target.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59526
|
+
target.updatedAt = target.revokedAt;
|
|
59527
|
+
await saveRegistry(pool2, registry2);
|
|
59528
|
+
return true;
|
|
59529
|
+
});
|
|
59530
|
+
if (!revoked) {
|
|
59198
59531
|
res.status(404).json({ error: `API key not found: ${keyId}` });
|
|
59199
59532
|
return;
|
|
59200
59533
|
}
|
|
59201
|
-
target.isActive = false;
|
|
59202
|
-
target.revokedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
59203
|
-
await saveRegistry(registry2);
|
|
59204
59534
|
res.json({ ok: true, keyId });
|
|
59205
59535
|
} catch (err) {
|
|
59206
59536
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -59212,16 +59542,16 @@ authKeysRouter.delete("/:keyId", async (req, res) => {
|
|
|
59212
59542
|
// src/server/routes/control-plane/instance-lifecycle.ts
|
|
59213
59543
|
var import_express24 = __toESM(require_express2(), 1);
|
|
59214
59544
|
var import_crypto5 = require("crypto");
|
|
59215
|
-
var
|
|
59216
|
-
var
|
|
59217
|
-
var
|
|
59545
|
+
var import_fs9 = require("fs");
|
|
59546
|
+
var import_promises8 = require("fs/promises");
|
|
59547
|
+
var import_path14 = require("path");
|
|
59218
59548
|
var import_os3 = require("os");
|
|
59219
59549
|
init_esm();
|
|
59220
59550
|
init_db();
|
|
59221
59551
|
var INSTANCE_NAME_RE3 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
59222
59552
|
var ALLOWED_PROVIDERS = ["openai", "claude", "gemini", "groq", "mistral", "ollama", "mock"];
|
|
59223
59553
|
var ALLOWED_DB_INTENTS = ["dedicated", "shared", "external"];
|
|
59224
|
-
var { Pool:
|
|
59554
|
+
var { Pool: Pool11 } = esm_default;
|
|
59225
59555
|
function isValidInstanceName2(name) {
|
|
59226
59556
|
return INSTANCE_NAME_RE3.test(name);
|
|
59227
59557
|
}
|
|
@@ -59229,16 +59559,16 @@ function normalizedInstanceCollisionKey(name) {
|
|
|
59229
59559
|
return name.trim().toLowerCase().replace(/[-_]+/g, "_");
|
|
59230
59560
|
}
|
|
59231
59561
|
function findSiblingInstanceCollision(runtimeRoot, desiredName) {
|
|
59232
|
-
const instancesDir = (0,
|
|
59233
|
-
if (!(0,
|
|
59562
|
+
const instancesDir = (0, import_path14.join)(runtimeRoot, "instances");
|
|
59563
|
+
if (!(0, import_fs9.existsSync)(instancesDir)) return null;
|
|
59234
59564
|
const desiredKey = normalizedInstanceCollisionKey(desiredName);
|
|
59235
|
-
for (const entry of (0,
|
|
59565
|
+
for (const entry of (0, import_fs9.readdirSync)(instancesDir, { withFileTypes: true })) {
|
|
59236
59566
|
if (!entry.isDirectory()) continue;
|
|
59237
59567
|
if (entry.name === desiredName) continue;
|
|
59238
59568
|
if (normalizedInstanceCollisionKey(entry.name) !== desiredKey) continue;
|
|
59239
59569
|
return {
|
|
59240
59570
|
name: entry.name,
|
|
59241
|
-
dir: (0,
|
|
59571
|
+
dir: (0, import_path14.join)(instancesDir, entry.name)
|
|
59242
59572
|
};
|
|
59243
59573
|
}
|
|
59244
59574
|
return null;
|
|
@@ -59264,21 +59594,28 @@ function validateDbUrl(dbUrl) {
|
|
|
59264
59594
|
}
|
|
59265
59595
|
return null;
|
|
59266
59596
|
}
|
|
59597
|
+
function sanitizeLegacyApiKeyId(input) {
|
|
59598
|
+
return input.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
59599
|
+
}
|
|
59600
|
+
function generateInstanceApiKey(name) {
|
|
59601
|
+
const keyId = sanitizeLegacyApiKeyId(name + "_control_plane") || "iranti";
|
|
59602
|
+
return keyId + "." + (0, import_crypto5.randomBytes)(32).toString("base64url");
|
|
59603
|
+
}
|
|
59267
59604
|
function preferredRuntimeRoot() {
|
|
59268
59605
|
const explicit = env["IRANTI_HOME"]?.trim() ?? process.env["IRANTI_HOME"]?.trim() ?? "";
|
|
59269
|
-
if (explicit) return (0,
|
|
59606
|
+
if (explicit) return (0, import_path14.resolve)(explicit);
|
|
59270
59607
|
const configuredInstanceEnv = env["IRANTI_INSTANCE_ENV"]?.trim() ?? process.env["IRANTI_INSTANCE_ENV"]?.trim() ?? "";
|
|
59271
59608
|
if (configuredInstanceEnv) {
|
|
59272
|
-
return (0,
|
|
59609
|
+
return (0, import_path14.resolve)((0, import_path14.dirname)(configuredInstanceEnv), "..", "..");
|
|
59273
59610
|
}
|
|
59274
59611
|
const candidates = runtimeRootCandidates();
|
|
59275
59612
|
if (candidates.length > 0) return candidates[0];
|
|
59276
|
-
return (0,
|
|
59613
|
+
return (0, import_path14.join)((0, import_os3.homedir)(), ".iranti-runtime");
|
|
59277
59614
|
}
|
|
59278
59615
|
function parseEnvFile2(filePath) {
|
|
59279
|
-
if (!(0,
|
|
59616
|
+
if (!(0, import_fs9.existsSync)(filePath)) return {};
|
|
59280
59617
|
const parsed = {};
|
|
59281
|
-
const lines = (0,
|
|
59618
|
+
const lines = (0, import_fs9.readFileSync)(filePath, "utf8").split(/\r?\n/);
|
|
59282
59619
|
for (const line of lines) {
|
|
59283
59620
|
const trimmed = line.trim();
|
|
59284
59621
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -59343,65 +59680,65 @@ async function isInstanceRunning(runtimeRoot, name) {
|
|
|
59343
59680
|
}
|
|
59344
59681
|
}
|
|
59345
59682
|
function instancePaths(runtimeRoot, name) {
|
|
59346
|
-
const instanceDir = (0,
|
|
59683
|
+
const instanceDir = (0, import_path14.join)(runtimeRoot, "instances", name);
|
|
59347
59684
|
return {
|
|
59348
59685
|
instanceDir,
|
|
59349
|
-
envFile: (0,
|
|
59686
|
+
envFile: (0, import_path14.join)(instanceDir, ".env")
|
|
59350
59687
|
};
|
|
59351
59688
|
}
|
|
59352
59689
|
async function moveDirectory(fromPath, toPath) {
|
|
59353
59690
|
try {
|
|
59354
|
-
await (0,
|
|
59691
|
+
await (0, import_promises8.rename)(fromPath, toPath);
|
|
59355
59692
|
} catch (error2) {
|
|
59356
59693
|
const code = error2.code;
|
|
59357
59694
|
if (code !== "EXDEV") throw error2;
|
|
59358
|
-
await (0,
|
|
59359
|
-
await (0,
|
|
59695
|
+
await (0, import_promises8.cp)(fromPath, toPath, { recursive: true, force: true });
|
|
59696
|
+
await (0, import_promises8.rm)(fromPath, { recursive: true, force: true });
|
|
59360
59697
|
}
|
|
59361
59698
|
}
|
|
59362
59699
|
async function rewriteProjectBindingInstanceEnv(projectPath, oldEnvPath, newEnvPath) {
|
|
59363
|
-
const bindingPath = (0,
|
|
59364
|
-
if (!(0,
|
|
59365
|
-
const parsed = parseSimpleEnv2(await (0,
|
|
59700
|
+
const bindingPath = (0, import_path14.join)(projectPath, ".env.iranti");
|
|
59701
|
+
if (!(0, import_fs9.existsSync)(bindingPath)) return;
|
|
59702
|
+
const parsed = parseSimpleEnv2(await (0, import_promises8.readFile)(bindingPath, "utf8"));
|
|
59366
59703
|
const current = parsed["IRANTI_INSTANCE_ENV"]?.trim() ?? "";
|
|
59367
59704
|
if (!current) return;
|
|
59368
|
-
if ((0,
|
|
59705
|
+
if ((0, import_path14.resolve)(current) !== (0, import_path14.resolve)(oldEnvPath)) return;
|
|
59369
59706
|
parsed["IRANTI_INSTANCE_ENV"] = newEnvPath;
|
|
59370
|
-
await (0,
|
|
59707
|
+
await (0, import_promises8.writeFile)(bindingPath, buildSimpleEnvFile(parsed), "utf8");
|
|
59371
59708
|
}
|
|
59372
59709
|
async function rewriteMovedInstanceEnv(instanceDir, previousInstanceDir) {
|
|
59373
|
-
const envPath = (0,
|
|
59374
|
-
if (!(0,
|
|
59375
|
-
const parsed = parseSimpleEnv2(await (0,
|
|
59376
|
-
const oldBase = (0,
|
|
59377
|
-
const nextEscalation = (0,
|
|
59378
|
-
const nextRequestLog = (0,
|
|
59710
|
+
const envPath = (0, import_path14.join)(instanceDir, ".env");
|
|
59711
|
+
if (!(0, import_fs9.existsSync)(envPath)) return;
|
|
59712
|
+
const parsed = parseSimpleEnv2(await (0, import_promises8.readFile)(envPath, "utf8"));
|
|
59713
|
+
const oldBase = (0, import_path14.resolve)(previousInstanceDir);
|
|
59714
|
+
const nextEscalation = (0, import_path14.join)(instanceDir, "escalation");
|
|
59715
|
+
const nextRequestLog = (0, import_path14.join)(instanceDir, "logs", "api-requests.log");
|
|
59379
59716
|
const currentEscalation = parsed["IRANTI_ESCALATION_DIR"]?.trim() ?? "";
|
|
59380
59717
|
const currentRequestLog = parsed["IRANTI_REQUEST_LOG_FILE"]?.trim() ?? "";
|
|
59381
|
-
if (!currentEscalation || (0,
|
|
59718
|
+
if (!currentEscalation || (0, import_path14.resolve)(currentEscalation).startsWith(oldBase)) {
|
|
59382
59719
|
parsed["IRANTI_ESCALATION_DIR"] = nextEscalation;
|
|
59383
59720
|
}
|
|
59384
|
-
if (!currentRequestLog || (0,
|
|
59721
|
+
if (!currentRequestLog || (0, import_path14.resolve)((0, import_path14.dirname)(currentRequestLog)).startsWith((0, import_path14.resolve)((0, import_path14.join)(previousInstanceDir, "logs")))) {
|
|
59385
59722
|
parsed["IRANTI_REQUEST_LOG_FILE"] = nextRequestLog;
|
|
59386
59723
|
}
|
|
59387
|
-
await (0,
|
|
59724
|
+
await (0, import_promises8.writeFile)(envPath, buildSimpleEnvFile(parsed), "utf8");
|
|
59388
59725
|
}
|
|
59389
59726
|
async function rewriteMovedInstanceMeta(instanceDir, name) {
|
|
59390
|
-
const metaPath = (0,
|
|
59391
|
-
if (!(0,
|
|
59392
|
-
const parsed = JSON.parse(await (0,
|
|
59727
|
+
const metaPath = (0, import_path14.join)(instanceDir, "instance.json");
|
|
59728
|
+
if (!(0, import_fs9.existsSync)(metaPath)) return;
|
|
59729
|
+
const parsed = JSON.parse(await (0, import_promises8.readFile)(metaPath, "utf8"));
|
|
59393
59730
|
parsed["name"] = name;
|
|
59394
59731
|
parsed["instanceDir"] = instanceDir;
|
|
59395
|
-
parsed["envFile"] = (0,
|
|
59396
|
-
await (0,
|
|
59732
|
+
parsed["envFile"] = (0, import_path14.join)(instanceDir, ".env");
|
|
59733
|
+
await (0, import_promises8.writeFile)(metaPath, `${JSON.stringify(parsed, null, 2)}
|
|
59397
59734
|
`, "utf8");
|
|
59398
59735
|
}
|
|
59399
59736
|
async function clearMovedRuntimeMetadata(instanceDir) {
|
|
59400
|
-
const runtimePath = (0,
|
|
59401
|
-
if (!(0,
|
|
59402
|
-
await (0,
|
|
59737
|
+
const runtimePath = (0, import_path14.join)(instanceDir, "runtime.json");
|
|
59738
|
+
if (!(0, import_fs9.existsSync)(runtimePath)) return;
|
|
59739
|
+
await (0, import_promises8.rm)(runtimePath, { force: true });
|
|
59403
59740
|
}
|
|
59404
|
-
function
|
|
59741
|
+
function escapePgIdentifier2(value) {
|
|
59405
59742
|
return `"${value.replace(/"/g, '""')}"`;
|
|
59406
59743
|
}
|
|
59407
59744
|
function parseDatabaseTarget2(dbUrl) {
|
|
@@ -59421,13 +59758,13 @@ function parseDatabaseTarget2(dbUrl) {
|
|
|
59421
59758
|
}
|
|
59422
59759
|
async function dropDatabase(dbUrl) {
|
|
59423
59760
|
const { adminUrl, databaseName } = parseDatabaseTarget2(dbUrl);
|
|
59424
|
-
const pool2 = new
|
|
59761
|
+
const pool2 = new Pool11({ connectionString: adminUrl });
|
|
59425
59762
|
try {
|
|
59426
59763
|
await pool2.query(
|
|
59427
59764
|
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = $1 AND pid <> pg_backend_pid()",
|
|
59428
59765
|
[databaseName]
|
|
59429
59766
|
);
|
|
59430
|
-
await pool2.query(`DROP DATABASE IF EXISTS ${
|
|
59767
|
+
await pool2.query(`DROP DATABASE IF EXISTS ${escapePgIdentifier2(databaseName)}`);
|
|
59431
59768
|
} finally {
|
|
59432
59769
|
await pool2.end();
|
|
59433
59770
|
}
|
|
@@ -59436,8 +59773,8 @@ async function dropDatabase(dbUrl) {
|
|
|
59436
59773
|
async function deleteBoundProjectFiles(projectPaths) {
|
|
59437
59774
|
const removed = [];
|
|
59438
59775
|
for (const projectPath of projectPaths) {
|
|
59439
|
-
const bindingPath = (0,
|
|
59440
|
-
await (0,
|
|
59776
|
+
const bindingPath = (0, import_path14.join)(projectPath, ".env.iranti");
|
|
59777
|
+
await (0, import_promises8.rm)(bindingPath, { force: true });
|
|
59441
59778
|
removed.push(bindingPath);
|
|
59442
59779
|
}
|
|
59443
59780
|
return removed;
|
|
@@ -59493,8 +59830,9 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
|
|
|
59493
59830
|
return;
|
|
59494
59831
|
}
|
|
59495
59832
|
const runtimeRoot = preferredRuntimeRoot();
|
|
59833
|
+
const generatedApiKey = generateInstanceApiKey(name);
|
|
59496
59834
|
const { instanceDir, envFile } = instancePaths(runtimeRoot, name);
|
|
59497
|
-
if ((0,
|
|
59835
|
+
if ((0, import_fs9.existsSync)(instanceDir)) {
|
|
59498
59836
|
res.status(409).json({
|
|
59499
59837
|
error: "Instance already exists.",
|
|
59500
59838
|
code: "INSTANCE_EXISTS"
|
|
@@ -59519,6 +59857,8 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
|
|
|
59519
59857
|
String(port),
|
|
59520
59858
|
"--db-url",
|
|
59521
59859
|
dbUrl.trim(),
|
|
59860
|
+
"--api-key",
|
|
59861
|
+
generatedApiKey,
|
|
59522
59862
|
"--provider",
|
|
59523
59863
|
provider
|
|
59524
59864
|
];
|
|
@@ -59539,7 +59879,7 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
|
|
|
59539
59879
|
try {
|
|
59540
59880
|
let envContent;
|
|
59541
59881
|
try {
|
|
59542
|
-
envContent = await (0,
|
|
59882
|
+
envContent = await (0, import_promises8.readFile)(envFile, "utf8");
|
|
59543
59883
|
} catch {
|
|
59544
59884
|
envContent = "";
|
|
59545
59885
|
}
|
|
@@ -59547,12 +59887,22 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
|
|
|
59547
59887
|
const pepper = (0, import_crypto5.randomBytes)(32).toString("hex");
|
|
59548
59888
|
const pepperLine = `IRANTI_API_KEY_PEPPER=${pepper}`;
|
|
59549
59889
|
envContent = envContent.endsWith("\n") ? envContent + pepperLine + "\n" : envContent + "\n" + pepperLine + "\n";
|
|
59550
|
-
await (0,
|
|
59890
|
+
await (0, import_promises8.writeFile)(envFile, envContent, "utf8");
|
|
59551
59891
|
}
|
|
59552
59892
|
} catch {
|
|
59553
59893
|
console.warn(`[instance-lifecycle] Failed to write IRANTI_API_KEY_PEPPER to ${envFile}`);
|
|
59554
59894
|
}
|
|
59555
59895
|
const instanceEnv = parseEnvFile2(envFile);
|
|
59896
|
+
let note = "Instance created. Open it in Control Plane to start the runtime, finish provider setup, and bind projects.";
|
|
59897
|
+
try {
|
|
59898
|
+
const prov = await provisionDatabase(dbUrl.trim(), instanceDir);
|
|
59899
|
+
if (prov.migrated) {
|
|
59900
|
+
note = prov.created ? `Instance and database "${prov.databaseName}" created \u2014 ready to start.` : `Instance created. Database "${prov.databaseName}" already existed \u2014 migrations applied.`;
|
|
59901
|
+
} else if (prov.created) {
|
|
59902
|
+
note = `Instance and database "${prov.databaseName}" created. ${prov.migrationNote} Use the setup or repair flow if migrations are still needed.`;
|
|
59903
|
+
}
|
|
59904
|
+
} catch {
|
|
59905
|
+
}
|
|
59556
59906
|
res.status(201).json({
|
|
59557
59907
|
ok: true,
|
|
59558
59908
|
name,
|
|
@@ -59560,7 +59910,7 @@ instanceLifecycleRouter.post("/instances", async (req, res) => {
|
|
|
59560
59910
|
envFile,
|
|
59561
59911
|
port,
|
|
59562
59912
|
provider: instanceEnv["LLM_PROVIDER"] ?? provider,
|
|
59563
|
-
note
|
|
59913
|
+
note
|
|
59564
59914
|
});
|
|
59565
59915
|
});
|
|
59566
59916
|
instanceLifecycleRouter.patch("/instances/:name", async (req, res) => {
|
|
@@ -59574,7 +59924,7 @@ instanceLifecycleRouter.patch("/instances/:name", async (req, res) => {
|
|
|
59574
59924
|
}
|
|
59575
59925
|
const runtimeRoot = preferredRuntimeRoot();
|
|
59576
59926
|
const { instanceDir, envFile } = instancePaths(runtimeRoot, name);
|
|
59577
|
-
if (!(0,
|
|
59927
|
+
if (!(0, import_fs9.existsSync)(instanceDir)) {
|
|
59578
59928
|
res.status(404).json({
|
|
59579
59929
|
error: `Instance "${name}" not found.`,
|
|
59580
59930
|
code: "NOT_FOUND"
|
|
@@ -59704,7 +60054,7 @@ instanceLifecycleRouter.delete("/instances/:name", async (req, res) => {
|
|
|
59704
60054
|
const resolvedAuthority = await resolveInstanceAuthority(name);
|
|
59705
60055
|
const runtimeRoot = resolvedAuthority?.runtimeRoot ?? preferredRuntimeRoot();
|
|
59706
60056
|
const { instanceDir, envFile } = resolvedAuthority ? { instanceDir: resolvedAuthority.instanceDir, envFile: resolvedAuthority.instanceEnvPath } : instancePaths(runtimeRoot, name);
|
|
59707
|
-
if (!(0,
|
|
60057
|
+
if (!(0, import_fs9.existsSync)(instanceDir)) {
|
|
59708
60058
|
res.status(404).json({
|
|
59709
60059
|
error: `Instance "${name}" not found.`,
|
|
59710
60060
|
code: "NOT_FOUND"
|
|
@@ -59737,7 +60087,7 @@ instanceLifecycleRouter.delete("/instances/:name", async (req, res) => {
|
|
|
59737
60087
|
if (dropDatabaseRequested) {
|
|
59738
60088
|
droppedDatabaseName = await dropDatabase(databaseUrl2);
|
|
59739
60089
|
}
|
|
59740
|
-
await (0,
|
|
60090
|
+
await (0, import_promises8.rm)(instanceDir, { recursive: true, force: true });
|
|
59741
60091
|
} catch (error2) {
|
|
59742
60092
|
res.status(500).json({
|
|
59743
60093
|
error: commandFailureMessage(error2),
|
|
@@ -59774,7 +60124,7 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
|
|
|
59774
60124
|
}
|
|
59775
60125
|
const currentRoot = resolvedAuthority.runtimeRoot;
|
|
59776
60126
|
const targetRoot = preferredRuntimeRoot();
|
|
59777
|
-
if ((0,
|
|
60127
|
+
if ((0, import_path14.resolve)(currentRoot) === (0, import_path14.resolve)(targetRoot)) {
|
|
59778
60128
|
res.status(200).json({
|
|
59779
60129
|
ok: true,
|
|
59780
60130
|
name,
|
|
@@ -59794,8 +60144,8 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
|
|
|
59794
60144
|
});
|
|
59795
60145
|
return;
|
|
59796
60146
|
}
|
|
59797
|
-
const targetInstanceDir = (0,
|
|
59798
|
-
if ((0,
|
|
60147
|
+
const targetInstanceDir = (0, import_path14.join)(targetRoot, "instances", name);
|
|
60148
|
+
if ((0, import_fs9.existsSync)(targetInstanceDir)) {
|
|
59799
60149
|
res.status(409).json({
|
|
59800
60150
|
error: `Target runtime root already contains an instance named "${name}".`,
|
|
59801
60151
|
code: "TARGET_EXISTS"
|
|
@@ -59803,9 +60153,9 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
|
|
|
59803
60153
|
return;
|
|
59804
60154
|
}
|
|
59805
60155
|
const oldEnvPath = resolvedAuthority.instanceEnvPath;
|
|
59806
|
-
const newEnvPath = (0,
|
|
60156
|
+
const newEnvPath = (0, import_path14.join)(targetInstanceDir, ".env");
|
|
59807
60157
|
try {
|
|
59808
|
-
await (0,
|
|
60158
|
+
await (0, import_promises8.mkdir)((0, import_path14.join)(targetRoot, "instances"), { recursive: true });
|
|
59809
60159
|
await moveDirectory(resolvedAuthority.instanceDir, targetInstanceDir);
|
|
59810
60160
|
await rewriteMovedInstanceEnv(targetInstanceDir, resolvedAuthority.instanceDir);
|
|
59811
60161
|
await rewriteMovedInstanceMeta(targetInstanceDir, name);
|
|
@@ -59835,20 +60185,20 @@ instanceLifecycleRouter.post("/instances/:name/migrate-root", async (req, res) =
|
|
|
59835
60185
|
|
|
59836
60186
|
// src/server/routes/control-plane/project-bindings.ts
|
|
59837
60187
|
var import_express25 = __toESM(require_express2(), 1);
|
|
59838
|
-
var
|
|
59839
|
-
var
|
|
60188
|
+
var import_fs10 = require("fs");
|
|
60189
|
+
var import_path15 = require("path");
|
|
59840
60190
|
var import_os4 = require("os");
|
|
59841
60191
|
var projectBindingsRouter = (0, import_express25.Router)();
|
|
59842
60192
|
var INSTANCE_NAME_RE4 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
59843
60193
|
function getRuntimeRoot() {
|
|
59844
|
-
return process.env.IRANTI_HOME ?? (0,
|
|
60194
|
+
return process.env.IRANTI_HOME ?? (0, import_path15.join)((0, import_os4.homedir)(), ".iranti-runtime");
|
|
59845
60195
|
}
|
|
59846
60196
|
function getInstanceEnvPath(runtimeRoot, instanceName) {
|
|
59847
|
-
return (0,
|
|
60197
|
+
return (0, import_path15.join)(runtimeRoot, "instances", instanceName, ".env");
|
|
59848
60198
|
}
|
|
59849
60199
|
function parseEnvFile3(filePath) {
|
|
59850
60200
|
const result = {};
|
|
59851
|
-
const lines = (0,
|
|
60201
|
+
const lines = (0, import_fs10.readFileSync)(filePath, "utf8").split("\n");
|
|
59852
60202
|
for (const line of lines) {
|
|
59853
60203
|
const trimmed = line.trim();
|
|
59854
60204
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
@@ -59864,12 +60214,12 @@ function buildEnvFileContent(pairs) {
|
|
|
59864
60214
|
return Object.entries(pairs).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
59865
60215
|
}
|
|
59866
60216
|
function getRegistryPath(runtimeRoot, instanceName) {
|
|
59867
|
-
return (0,
|
|
60217
|
+
return (0, import_path15.join)(runtimeRoot, "instances", instanceName, "projects.json");
|
|
59868
60218
|
}
|
|
59869
60219
|
function readRegistry2(registryPath) {
|
|
59870
|
-
if (!(0,
|
|
60220
|
+
if (!(0, import_fs10.existsSync)(registryPath)) return { projects: [] };
|
|
59871
60221
|
try {
|
|
59872
|
-
const raw = (0,
|
|
60222
|
+
const raw = (0, import_fs10.readFileSync)(registryPath, "utf8");
|
|
59873
60223
|
const parsed = JSON.parse(raw);
|
|
59874
60224
|
if (!Array.isArray(parsed.projects)) return { projects: [] };
|
|
59875
60225
|
return parsed;
|
|
@@ -59879,8 +60229,8 @@ function readRegistry2(registryPath) {
|
|
|
59879
60229
|
}
|
|
59880
60230
|
function writeRegistry(registryPath, registry2) {
|
|
59881
60231
|
const dir = registryPath.replace(/[/\\][^/\\]+$/, "");
|
|
59882
|
-
(0,
|
|
59883
|
-
(0,
|
|
60232
|
+
(0, import_fs10.mkdirSync)(dir, { recursive: true });
|
|
60233
|
+
(0, import_fs10.writeFileSync)(registryPath, JSON.stringify(registry2, null, 2) + "\n", "utf8");
|
|
59884
60234
|
}
|
|
59885
60235
|
function removeProjectFromRegistry(registryPath, projectPath) {
|
|
59886
60236
|
const registry2 = readRegistry2(registryPath);
|
|
@@ -59952,37 +60302,37 @@ function removeIrantiClaudeHooksFromValue(value) {
|
|
|
59952
60302
|
function cleanupProjectIntegrations(projectPath) {
|
|
59953
60303
|
const result = { removed: [], updated: [], warnings: [] };
|
|
59954
60304
|
const candidates = [
|
|
59955
|
-
(0,
|
|
59956
|
-
(0,
|
|
60305
|
+
(0, import_path15.join)(projectPath, ".mcp.json"),
|
|
60306
|
+
(0, import_path15.join)(projectPath, ".vscode", "mcp.json")
|
|
59957
60307
|
];
|
|
59958
60308
|
for (const candidate of candidates) {
|
|
59959
|
-
if (!(0,
|
|
60309
|
+
if (!(0, import_fs10.existsSync)(candidate)) continue;
|
|
59960
60310
|
try {
|
|
59961
|
-
const parsed = JSON.parse((0,
|
|
60311
|
+
const parsed = JSON.parse((0, import_fs10.readFileSync)(candidate, "utf8"));
|
|
59962
60312
|
const next = removeIrantiMcpServerFromValue(parsed);
|
|
59963
60313
|
if (next === parsed) continue;
|
|
59964
60314
|
if (!next) {
|
|
59965
|
-
(0,
|
|
60315
|
+
(0, import_fs10.rmSync)(candidate, { force: true });
|
|
59966
60316
|
result.removed.push(candidate);
|
|
59967
60317
|
} else {
|
|
59968
|
-
(0,
|
|
60318
|
+
(0, import_fs10.writeFileSync)(candidate, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
59969
60319
|
result.updated.push(candidate);
|
|
59970
60320
|
}
|
|
59971
60321
|
} catch {
|
|
59972
60322
|
result.warnings.push(`Skipped unreadable MCP file ${candidate}`);
|
|
59973
60323
|
}
|
|
59974
60324
|
}
|
|
59975
|
-
const claudeSettingsFile = (0,
|
|
59976
|
-
if ((0,
|
|
60325
|
+
const claudeSettingsFile = (0, import_path15.join)(projectPath, ".claude", "settings.local.json");
|
|
60326
|
+
if ((0, import_fs10.existsSync)(claudeSettingsFile)) {
|
|
59977
60327
|
try {
|
|
59978
|
-
const parsed = JSON.parse((0,
|
|
60328
|
+
const parsed = JSON.parse((0, import_fs10.readFileSync)(claudeSettingsFile, "utf8"));
|
|
59979
60329
|
const next = removeIrantiClaudeHooksFromValue(parsed);
|
|
59980
60330
|
if (next !== parsed) {
|
|
59981
60331
|
if (!next) {
|
|
59982
|
-
(0,
|
|
60332
|
+
(0, import_fs10.rmSync)(claudeSettingsFile, { force: true });
|
|
59983
60333
|
result.removed.push(claudeSettingsFile);
|
|
59984
60334
|
} else {
|
|
59985
|
-
(0,
|
|
60335
|
+
(0, import_fs10.writeFileSync)(claudeSettingsFile, JSON.stringify(next, null, 2) + "\n", "utf8");
|
|
59986
60336
|
result.updated.push(claudeSettingsFile);
|
|
59987
60337
|
}
|
|
59988
60338
|
}
|
|
@@ -59993,15 +60343,15 @@ function cleanupProjectIntegrations(projectPath) {
|
|
|
59993
60343
|
return result;
|
|
59994
60344
|
}
|
|
59995
60345
|
function ensureGitignoreEntry(projectPath, entry) {
|
|
59996
|
-
const gitignorePath = (0,
|
|
59997
|
-
if ((0,
|
|
59998
|
-
const content = (0,
|
|
60346
|
+
const gitignorePath = (0, import_path15.join)(projectPath, ".gitignore");
|
|
60347
|
+
if ((0, import_fs10.existsSync)(gitignorePath)) {
|
|
60348
|
+
const content = (0, import_fs10.readFileSync)(gitignorePath, "utf8");
|
|
59999
60349
|
const lines = content.split("\n").map((l) => l.trim());
|
|
60000
60350
|
if (lines.includes(entry)) return;
|
|
60001
60351
|
const updated = content.endsWith("\n") ? content + entry + "\n" : content + "\n" + entry + "\n";
|
|
60002
|
-
(0,
|
|
60352
|
+
(0, import_fs10.writeFileSync)(gitignorePath, updated, "utf8");
|
|
60003
60353
|
} else {
|
|
60004
|
-
(0,
|
|
60354
|
+
(0, import_fs10.writeFileSync)(gitignorePath, entry + "\n", "utf8");
|
|
60005
60355
|
}
|
|
60006
60356
|
}
|
|
60007
60357
|
projectBindingsRouter.post(
|
|
@@ -60025,16 +60375,16 @@ projectBindingsRouter.post(
|
|
|
60025
60375
|
res.status(400).json({ error: "projectPath is required" });
|
|
60026
60376
|
return;
|
|
60027
60377
|
}
|
|
60028
|
-
if (!(0,
|
|
60378
|
+
if (!(0, import_path15.isAbsolute)(projectPath)) {
|
|
60029
60379
|
res.status(400).json({ error: "projectPath must be an absolute path" });
|
|
60030
60380
|
return;
|
|
60031
60381
|
}
|
|
60032
|
-
if (!(0,
|
|
60382
|
+
if (!(0, import_fs10.existsSync)(projectPath)) {
|
|
60033
60383
|
res.status(400).json({ error: "projectPath does not exist" });
|
|
60034
60384
|
return;
|
|
60035
60385
|
}
|
|
60036
60386
|
try {
|
|
60037
|
-
const stat2 = (0,
|
|
60387
|
+
const stat2 = (0, import_fs10.statSync)(projectPath);
|
|
60038
60388
|
if (!stat2.isDirectory()) {
|
|
60039
60389
|
res.status(400).json({ error: "projectPath must be a directory" });
|
|
60040
60390
|
return;
|
|
@@ -60045,7 +60395,7 @@ projectBindingsRouter.post(
|
|
|
60045
60395
|
}
|
|
60046
60396
|
const runtimeRoot = getRuntimeRoot();
|
|
60047
60397
|
const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
|
|
60048
|
-
if (!(0,
|
|
60398
|
+
if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
|
|
60049
60399
|
res.status(404).json({
|
|
60050
60400
|
error: `Instance '${instanceName}' not found`,
|
|
60051
60401
|
instanceEnvPath
|
|
@@ -60070,7 +60420,7 @@ projectBindingsRouter.post(
|
|
|
60070
60420
|
return;
|
|
60071
60421
|
}
|
|
60072
60422
|
const resolvedAgentId = agentId ?? "main_agent";
|
|
60073
|
-
const resolvedMemoryEntity = memoryEntity ?? `project/${(0,
|
|
60423
|
+
const resolvedMemoryEntity = memoryEntity ?? `project/${(0, import_path15.basename)(projectPath)}`;
|
|
60074
60424
|
const resolvedPersonalMemoryEntity = personalMemoryEntity ?? "user/main";
|
|
60075
60425
|
const resolvedMode = mode === "isolated" || mode === "shared" ? mode : "isolated";
|
|
60076
60426
|
const resolvedAutoRemember = autoRemember === true ? "true" : "false";
|
|
@@ -60085,9 +60435,9 @@ projectBindingsRouter.post(
|
|
|
60085
60435
|
IRANTI_INSTANCE_ENV: instanceEnvPath,
|
|
60086
60436
|
IRANTI_AUTO_REMEMBER: resolvedAutoRemember
|
|
60087
60437
|
});
|
|
60088
|
-
const envIrantiPath = (0,
|
|
60438
|
+
const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
|
|
60089
60439
|
try {
|
|
60090
|
-
(0,
|
|
60440
|
+
(0, import_fs10.writeFileSync)(envIrantiPath, envIrantiContent, "utf8");
|
|
60091
60441
|
} catch (err) {
|
|
60092
60442
|
res.status(500).json({ error: "Failed to write .env.iranti", detail: String(err) });
|
|
60093
60443
|
return;
|
|
@@ -60147,18 +60497,18 @@ projectBindingsRouter.patch(
|
|
|
60147
60497
|
res.status(400).json({ error: "projectPath query param is required" });
|
|
60148
60498
|
return;
|
|
60149
60499
|
}
|
|
60150
|
-
if (!(0,
|
|
60500
|
+
if (!(0, import_path15.isAbsolute)(projectPath)) {
|
|
60151
60501
|
res.status(400).json({ error: "projectPath must be an absolute path" });
|
|
60152
60502
|
return;
|
|
60153
60503
|
}
|
|
60154
60504
|
const runtimeRoot = getRuntimeRoot();
|
|
60155
60505
|
const currentInstanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
|
|
60156
|
-
if (!(0,
|
|
60506
|
+
if (!(0, import_fs10.existsSync)(currentInstanceEnvPath)) {
|
|
60157
60507
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60158
60508
|
return;
|
|
60159
60509
|
}
|
|
60160
|
-
const envIrantiPath = (0,
|
|
60161
|
-
if (!(0,
|
|
60510
|
+
const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
|
|
60511
|
+
if (!(0, import_fs10.existsSync)(envIrantiPath)) {
|
|
60162
60512
|
res.status(404).json({
|
|
60163
60513
|
error: ".env.iranti not found at projectPath \u2014 bind the project first",
|
|
60164
60514
|
envIrantiPath
|
|
@@ -60181,7 +60531,7 @@ projectBindingsRouter.patch(
|
|
|
60181
60531
|
return;
|
|
60182
60532
|
}
|
|
60183
60533
|
const targetEnvPath = getInstanceEnvPath(runtimeRoot, targetInstanceName);
|
|
60184
|
-
if (!(0,
|
|
60534
|
+
if (!(0, import_fs10.existsSync)(targetEnvPath)) {
|
|
60185
60535
|
res.status(404).json({ error: `Target instance '${targetInstanceName}' not found` });
|
|
60186
60536
|
return;
|
|
60187
60537
|
}
|
|
@@ -60253,7 +60603,7 @@ projectBindingsRouter.patch(
|
|
|
60253
60603
|
if (!canonicalKeys.includes(k)) reordered[k] = v;
|
|
60254
60604
|
}
|
|
60255
60605
|
try {
|
|
60256
|
-
(0,
|
|
60606
|
+
(0, import_fs10.writeFileSync)(envIrantiPath, buildEnvFileContent(reordered), "utf8");
|
|
60257
60607
|
} catch (err) {
|
|
60258
60608
|
res.status(500).json({ error: "Failed to write updated .env.iranti", detail: String(err) });
|
|
60259
60609
|
return;
|
|
@@ -60275,7 +60625,7 @@ projectBindingsRouter.patch(
|
|
|
60275
60625
|
const updatedEntry = {
|
|
60276
60626
|
projectPath,
|
|
60277
60627
|
agentId: movedEntry?.agentId ?? updated["IRANTI_AGENT_ID"] ?? "main_agent",
|
|
60278
|
-
memoryEntity: movedEntry?.memoryEntity ?? updated["IRANTI_MEMORY_ENTITY"] ?? `project/${(0,
|
|
60628
|
+
memoryEntity: movedEntry?.memoryEntity ?? updated["IRANTI_MEMORY_ENTITY"] ?? `project/${(0, import_path15.basename)(projectPath)}`,
|
|
60279
60629
|
mode: mode ?? movedEntry?.mode ?? "isolated",
|
|
60280
60630
|
boundAt: movedEntry?.boundAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
60281
60631
|
};
|
|
@@ -60314,18 +60664,18 @@ projectBindingsRouter.delete(
|
|
|
60314
60664
|
res.status(400).json({ error: "projectPath query param is required" });
|
|
60315
60665
|
return;
|
|
60316
60666
|
}
|
|
60317
|
-
if (!(0,
|
|
60667
|
+
if (!(0, import_path15.isAbsolute)(projectPath)) {
|
|
60318
60668
|
res.status(400).json({ error: "projectPath must be an absolute path" });
|
|
60319
60669
|
return;
|
|
60320
60670
|
}
|
|
60321
60671
|
const runtimeRoot = getRuntimeRoot();
|
|
60322
60672
|
const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
|
|
60323
|
-
if (!(0,
|
|
60673
|
+
if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
|
|
60324
60674
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60325
60675
|
return;
|
|
60326
60676
|
}
|
|
60327
|
-
const envIrantiPath = (0,
|
|
60328
|
-
if (!(0,
|
|
60677
|
+
const envIrantiPath = (0, import_path15.join)(projectPath, ".env.iranti");
|
|
60678
|
+
if (!(0, import_fs10.existsSync)(envIrantiPath)) {
|
|
60329
60679
|
res.status(404).json({
|
|
60330
60680
|
error: ".env.iranti not found at projectPath \u2014 bind the project first",
|
|
60331
60681
|
envIrantiPath
|
|
@@ -60336,7 +60686,7 @@ projectBindingsRouter.delete(
|
|
|
60336
60686
|
const registryPath = getRegistryPath(runtimeRoot, instanceName);
|
|
60337
60687
|
const registryRemoved = removeProjectFromRegistry(registryPath, projectPath);
|
|
60338
60688
|
try {
|
|
60339
|
-
(0,
|
|
60689
|
+
(0, import_fs10.rmSync)(envIrantiPath, { force: true });
|
|
60340
60690
|
} catch (err) {
|
|
60341
60691
|
res.status(500).json({ error: "Failed to remove .env.iranti", detail: String(err) });
|
|
60342
60692
|
return;
|
|
@@ -60364,17 +60714,17 @@ projectBindingsRouter.get(
|
|
|
60364
60714
|
}
|
|
60365
60715
|
const runtimeRoot = getRuntimeRoot();
|
|
60366
60716
|
const instanceEnvPath = getInstanceEnvPath(runtimeRoot, instanceName);
|
|
60367
|
-
if (!(0,
|
|
60717
|
+
if (!(0, import_fs10.existsSync)(instanceEnvPath)) {
|
|
60368
60718
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60369
60719
|
return;
|
|
60370
60720
|
}
|
|
60371
60721
|
const registryPath = getRegistryPath(runtimeRoot, instanceName);
|
|
60372
60722
|
const registry2 = readRegistry2(registryPath);
|
|
60373
60723
|
const live = registry2.projects.filter(
|
|
60374
|
-
(entry) => (0,
|
|
60724
|
+
(entry) => (0, import_fs10.existsSync)((0, import_path15.join)(entry.projectPath, ".env.iranti"))
|
|
60375
60725
|
);
|
|
60376
|
-
const cwdBindingPath = (0,
|
|
60377
|
-
if ((0,
|
|
60726
|
+
const cwdBindingPath = (0, import_path15.join)(process.cwd(), ".env.iranti");
|
|
60727
|
+
if ((0, import_fs10.existsSync)(cwdBindingPath)) {
|
|
60378
60728
|
try {
|
|
60379
60729
|
const binding = parseEnvFile3(cwdBindingPath);
|
|
60380
60730
|
const bindingInstance = binding["IRANTI_INSTANCE"]?.trim() ?? "";
|
|
@@ -60384,9 +60734,9 @@ projectBindingsRouter.get(
|
|
|
60384
60734
|
live.push({
|
|
60385
60735
|
projectPath: process.cwd(),
|
|
60386
60736
|
agentId: binding["IRANTI_AGENT_ID"]?.trim() || "main_agent",
|
|
60387
|
-
memoryEntity: binding["IRANTI_MEMORY_ENTITY"]?.trim() || `project/${(0,
|
|
60737
|
+
memoryEntity: binding["IRANTI_MEMORY_ENTITY"]?.trim() || `project/${(0, import_path15.basename)(process.cwd())}`,
|
|
60388
60738
|
mode: binding["IRANTI_PROJECT_MODE"]?.trim() === "shared" ? "shared" : "isolated",
|
|
60389
|
-
boundAt: new Date((0,
|
|
60739
|
+
boundAt: new Date((0, import_fs10.statSync)(cwdBindingPath).mtimeMs).toISOString()
|
|
60390
60740
|
});
|
|
60391
60741
|
}
|
|
60392
60742
|
} catch {
|
|
@@ -60407,24 +60757,24 @@ projectBindingsRouter.get(
|
|
|
60407
60757
|
|
|
60408
60758
|
// src/server/routes/control-plane/claude-integration.ts
|
|
60409
60759
|
var import_express26 = __toESM(require_express2(), 1);
|
|
60410
|
-
var
|
|
60411
|
-
var
|
|
60760
|
+
var import_fs11 = require("fs");
|
|
60761
|
+
var import_path16 = require("path");
|
|
60412
60762
|
var import_os5 = require("os");
|
|
60413
60763
|
var claudeIntegrationRouter = (0, import_express26.Router)();
|
|
60414
60764
|
var INSTANCE_NAME_RE5 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
60415
60765
|
function getRuntimeRoot2() {
|
|
60416
|
-
return process.env.IRANTI_HOME ?? (0,
|
|
60766
|
+
return process.env.IRANTI_HOME ?? (0, import_path16.join)((0, import_os5.homedir)(), ".iranti-runtime");
|
|
60417
60767
|
}
|
|
60418
60768
|
function getInstanceEnvPath2(runtimeRoot, instanceName) {
|
|
60419
|
-
return (0,
|
|
60769
|
+
return (0, import_path16.join)(runtimeRoot, "instances", instanceName, ".env");
|
|
60420
60770
|
}
|
|
60421
60771
|
function getRegistryPath2(runtimeRoot, instanceName) {
|
|
60422
|
-
return (0,
|
|
60772
|
+
return (0, import_path16.join)(runtimeRoot, "instances", instanceName, "projects.json");
|
|
60423
60773
|
}
|
|
60424
60774
|
function readRegistry3(registryPath) {
|
|
60425
|
-
if (!(0,
|
|
60775
|
+
if (!(0, import_fs11.existsSync)(registryPath)) return { projects: [] };
|
|
60426
60776
|
try {
|
|
60427
|
-
const raw = (0,
|
|
60777
|
+
const raw = (0, import_fs11.readFileSync)(registryPath, "utf8");
|
|
60428
60778
|
const parsed = JSON.parse(raw);
|
|
60429
60779
|
if (!Array.isArray(parsed.projects)) return { projects: [] };
|
|
60430
60780
|
return parsed;
|
|
@@ -60433,9 +60783,9 @@ function readRegistry3(registryPath) {
|
|
|
60433
60783
|
}
|
|
60434
60784
|
}
|
|
60435
60785
|
function readJsonFile2(filePath) {
|
|
60436
|
-
if (!(0,
|
|
60786
|
+
if (!(0, import_fs11.existsSync)(filePath)) return null;
|
|
60437
60787
|
try {
|
|
60438
|
-
const raw = (0,
|
|
60788
|
+
const raw = (0, import_fs11.readFileSync)(filePath, "utf8");
|
|
60439
60789
|
const parsed = JSON.parse(raw);
|
|
60440
60790
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
60441
60791
|
return parsed;
|
|
@@ -60496,10 +60846,10 @@ function usesPortableIrantiCommand(command) {
|
|
|
60496
60846
|
return /(^|\/)iranti(\.cmd|\.exe)?$/.test(normalized);
|
|
60497
60847
|
}
|
|
60498
60848
|
async function checkProjectIntegration2(projectPath) {
|
|
60499
|
-
const mcpJsonPath = (0,
|
|
60500
|
-
const workspaceMcpJsonPath = (0,
|
|
60501
|
-
const hooksJsonPath = (0,
|
|
60502
|
-
const projectEnvPath = (0,
|
|
60849
|
+
const mcpJsonPath = (0, import_path16.join)(projectPath, ".mcp.json");
|
|
60850
|
+
const workspaceMcpJsonPath = (0, import_path16.join)(projectPath, ".vscode", "mcp.json");
|
|
60851
|
+
const hooksJsonPath = (0, import_path16.join)(projectPath, ".claude", "settings.local.json");
|
|
60852
|
+
const projectEnvPath = (0, import_path16.join)(projectPath, ".env.iranti");
|
|
60503
60853
|
const mcpJson = readJsonFile2(mcpJsonPath);
|
|
60504
60854
|
const workspaceMcpJson = readJsonFile2(workspaceMcpJsonPath);
|
|
60505
60855
|
const hooksJson = readJsonFile2(hooksJsonPath);
|
|
@@ -60507,9 +60857,9 @@ async function checkProjectIntegration2(projectPath) {
|
|
|
60507
60857
|
const irantiWorkspaceMcpEntry = workspaceMcpJson ? extractIrantiMcpEntry(workspaceMcpJson) : null;
|
|
60508
60858
|
const irantiHooks = hooksJson ? extractIrantiHooks(hooksJson) : { sessionStart: null, userPromptSubmit: null, stop: null };
|
|
60509
60859
|
const issues = [];
|
|
60510
|
-
const anyMcpPresent = (0,
|
|
60860
|
+
const anyMcpPresent = (0, import_fs11.existsSync)(mcpJsonPath) || (0, import_fs11.existsSync)(workspaceMcpJsonPath);
|
|
60511
60861
|
const anyMcpHasIranti = irantiMcpEntry !== null || irantiWorkspaceMcpEntry !== null;
|
|
60512
|
-
const mcpInitialize = anyMcpHasIranti && (0,
|
|
60862
|
+
const mcpInitialize = anyMcpHasIranti && (0, import_fs11.existsSync)(projectEnvPath) ? await probeIrantiMcpInitialize({
|
|
60513
60863
|
projectPath,
|
|
60514
60864
|
projectEnvPath
|
|
60515
60865
|
}) : null;
|
|
@@ -60525,7 +60875,7 @@ async function checkProjectIntegration2(projectPath) {
|
|
|
60525
60875
|
}
|
|
60526
60876
|
}
|
|
60527
60877
|
}
|
|
60528
|
-
if (!(0,
|
|
60878
|
+
if (!(0, import_fs11.existsSync)(hooksJsonPath)) {
|
|
60529
60879
|
issues.push(".claude/settings.local.json not found - hooks not configured");
|
|
60530
60880
|
} else if (!irantiHooks.sessionStart && !irantiHooks.userPromptSubmit && !irantiHooks.stop) {
|
|
60531
60881
|
issues.push("No Iranti hooks registered in settings.local.json");
|
|
@@ -60547,11 +60897,11 @@ async function checkProjectIntegration2(projectPath) {
|
|
|
60547
60897
|
return {
|
|
60548
60898
|
projectPath,
|
|
60549
60899
|
mcpJson,
|
|
60550
|
-
mcpJsonPath: (0,
|
|
60900
|
+
mcpJsonPath: (0, import_fs11.existsSync)(mcpJsonPath) ? mcpJsonPath : null,
|
|
60551
60901
|
workspaceMcpJson,
|
|
60552
|
-
workspaceMcpJsonPath: (0,
|
|
60902
|
+
workspaceMcpJsonPath: (0, import_fs11.existsSync)(workspaceMcpJsonPath) ? workspaceMcpJsonPath : null,
|
|
60553
60903
|
hooksJson,
|
|
60554
|
-
hooksJsonPath: (0,
|
|
60904
|
+
hooksJsonPath: (0, import_fs11.existsSync)(hooksJsonPath) ? hooksJsonPath : null,
|
|
60555
60905
|
irantiMcpEntry,
|
|
60556
60906
|
irantiWorkspaceMcpEntry,
|
|
60557
60907
|
irantiHooks,
|
|
@@ -60569,10 +60919,10 @@ async function scaffoldProjectFiles(opts) {
|
|
|
60569
60919
|
timeoutMs: 15e3
|
|
60570
60920
|
});
|
|
60571
60921
|
const written = [
|
|
60572
|
-
(0,
|
|
60573
|
-
(0,
|
|
60574
|
-
(0,
|
|
60575
|
-
].filter((filePath) => (0,
|
|
60922
|
+
(0, import_path16.join)(projectPath, ".mcp.json"),
|
|
60923
|
+
(0, import_path16.join)(projectPath, ".vscode", "mcp.json"),
|
|
60924
|
+
(0, import_path16.join)(projectPath, ".claude", "settings.local.json")
|
|
60925
|
+
].filter((filePath) => (0, import_fs11.existsSync)(filePath));
|
|
60576
60926
|
const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
|
|
60577
60927
|
return { ok: true, written, output };
|
|
60578
60928
|
} catch (err) {
|
|
@@ -60598,17 +60948,17 @@ claudeIntegrationRouter.get(
|
|
|
60598
60948
|
res.status(400).json({ error: "Invalid projectId encoding" });
|
|
60599
60949
|
return;
|
|
60600
60950
|
}
|
|
60601
|
-
if (!(0,
|
|
60951
|
+
if (!(0, import_path16.isAbsolute)(projectPath)) {
|
|
60602
60952
|
res.status(400).json({ error: "projectPath must be an absolute path" });
|
|
60603
60953
|
return;
|
|
60604
60954
|
}
|
|
60605
|
-
if (!(0,
|
|
60955
|
+
if (!(0, import_fs11.existsSync)(projectPath)) {
|
|
60606
60956
|
res.status(404).json({ error: `projectPath does not exist: ${projectPath}` });
|
|
60607
60957
|
return;
|
|
60608
60958
|
}
|
|
60609
60959
|
const runtimeRoot = getRuntimeRoot2();
|
|
60610
60960
|
const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
|
|
60611
|
-
if (!(0,
|
|
60961
|
+
if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
|
|
60612
60962
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60613
60963
|
return;
|
|
60614
60964
|
}
|
|
@@ -60631,23 +60981,23 @@ claudeIntegrationRouter.post(
|
|
|
60631
60981
|
res.status(400).json({ error: "Invalid projectId encoding" });
|
|
60632
60982
|
return;
|
|
60633
60983
|
}
|
|
60634
|
-
if (!(0,
|
|
60984
|
+
if (!(0, import_path16.isAbsolute)(projectPath)) {
|
|
60635
60985
|
res.status(400).json({ error: "projectPath must be an absolute path" });
|
|
60636
60986
|
return;
|
|
60637
60987
|
}
|
|
60638
|
-
if (!(0,
|
|
60988
|
+
if (!(0, import_fs11.existsSync)(projectPath)) {
|
|
60639
60989
|
res.status(404).json({ error: `projectPath does not exist: ${projectPath}` });
|
|
60640
60990
|
return;
|
|
60641
60991
|
}
|
|
60642
60992
|
const runtimeRoot = getRuntimeRoot2();
|
|
60643
60993
|
const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
|
|
60644
|
-
if (!(0,
|
|
60994
|
+
if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
|
|
60645
60995
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60646
60996
|
return;
|
|
60647
60997
|
}
|
|
60648
60998
|
const { force = false } = req.body;
|
|
60649
|
-
const projectEnvPath = (0,
|
|
60650
|
-
if (!(0,
|
|
60999
|
+
const projectEnvPath = (0, import_path16.join)(projectPath, ".env.iranti");
|
|
61000
|
+
if (!(0, import_fs11.existsSync)(projectEnvPath)) {
|
|
60651
61001
|
res.status(400).json({
|
|
60652
61002
|
error: `.env.iranti not found at ${projectEnvPath}. Bind the project first using the Project Bindings panel.`,
|
|
60653
61003
|
code: "IRANTI_PROJECT_BINDING_MISSING"
|
|
@@ -60669,13 +61019,13 @@ claudeIntegrationRouter.get(
|
|
|
60669
61019
|
}
|
|
60670
61020
|
const runtimeRoot = getRuntimeRoot2();
|
|
60671
61021
|
const instanceEnvPath = getInstanceEnvPath2(runtimeRoot, instanceName);
|
|
60672
|
-
if (!(0,
|
|
61022
|
+
if (!(0, import_fs11.existsSync)(instanceEnvPath)) {
|
|
60673
61023
|
res.status(404).json({ error: `Instance '${instanceName}' not found` });
|
|
60674
61024
|
return;
|
|
60675
61025
|
}
|
|
60676
61026
|
const registryPath = getRegistryPath2(runtimeRoot, instanceName);
|
|
60677
61027
|
const registry2 = readRegistry3(registryPath);
|
|
60678
|
-
const liveProjects = registry2.projects.filter((entry) => (0,
|
|
61028
|
+
const liveProjects = registry2.projects.filter((entry) => (0, import_fs11.existsSync)((0, import_path16.join)(entry.projectPath, ".env.iranti")));
|
|
60679
61029
|
const projects = await Promise.all(liveProjects.map(async (entry) => {
|
|
60680
61030
|
const check2 = await checkProjectIntegration2(entry.projectPath);
|
|
60681
61031
|
const irantiHooksCount = [check2.irantiHooks.sessionStart, check2.irantiHooks.userPromptSubmit, check2.irantiHooks.stop].filter(Boolean).length;
|
|
@@ -60700,13 +61050,13 @@ claudeIntegrationRouter.get(
|
|
|
60700
61050
|
var import_express27 = __toESM(require_express2(), 1);
|
|
60701
61051
|
|
|
60702
61052
|
// src/server/lib/codex-cli.ts
|
|
60703
|
-
var
|
|
60704
|
-
var
|
|
60705
|
-
var
|
|
60706
|
-
var
|
|
61053
|
+
var import_child_process6 = require("child_process");
|
|
61054
|
+
var import_promises9 = require("fs/promises");
|
|
61055
|
+
var import_fs12 = require("fs");
|
|
61056
|
+
var import_path17 = require("path");
|
|
60707
61057
|
var import_util8 = require("util");
|
|
60708
61058
|
init_path_utils();
|
|
60709
|
-
var execFileAsync3 = (0, import_util8.promisify)(
|
|
61059
|
+
var execFileAsync3 = (0, import_util8.promisify)(import_child_process6.execFile);
|
|
60710
61060
|
function candidateFromEnv2() {
|
|
60711
61061
|
const raw = process.env["CODEX_CLI_PATH"]?.trim() ?? process.env["IRANTI_CP_CODEX_CLI"]?.trim() ?? "";
|
|
60712
61062
|
return raw || null;
|
|
@@ -60714,7 +61064,7 @@ function candidateFromEnv2() {
|
|
|
60714
61064
|
async function firstPathHit2() {
|
|
60715
61065
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
60716
61066
|
return new Promise((resolveResult) => {
|
|
60717
|
-
const child = (0,
|
|
61067
|
+
const child = (0, import_child_process6.spawn)(checker, ["codex"], { stdio: ["ignore", "pipe", "ignore"] });
|
|
60718
61068
|
let stdout = "";
|
|
60719
61069
|
let settled = false;
|
|
60720
61070
|
const finish = (value) => {
|
|
@@ -60748,12 +61098,12 @@ async function firstPathHit2() {
|
|
|
60748
61098
|
async function normalizeInvocation2(candidate, source) {
|
|
60749
61099
|
const normalized = resolvePortable(candidate);
|
|
60750
61100
|
const lower = normalized.toLowerCase();
|
|
60751
|
-
const extension = (0,
|
|
61101
|
+
const extension = (0, import_path17.extname)(lower);
|
|
60752
61102
|
if (process.platform === "win32" && !extension) {
|
|
60753
61103
|
for (const suffix of [".exe", ".cmd", ".bat", ".ps1"]) {
|
|
60754
61104
|
const sibling = `${normalized}${suffix}`;
|
|
60755
61105
|
try {
|
|
60756
|
-
await (0,
|
|
61106
|
+
await (0, import_promises9.access)(sibling, import_fs12.constants.F_OK);
|
|
60757
61107
|
return normalizeInvocation2(sibling, source);
|
|
60758
61108
|
} catch {
|
|
60759
61109
|
}
|
|
@@ -60771,7 +61121,7 @@ async function normalizeInvocation2(candidate, source) {
|
|
|
60771
61121
|
const installDir = dirnamePortable(normalized);
|
|
60772
61122
|
const cliEntry = joinPortable(installDir, "node_modules", "@openai", "codex", "bin", "codex.js");
|
|
60773
61123
|
try {
|
|
60774
|
-
await (0,
|
|
61124
|
+
await (0, import_promises9.access)(cliEntry, import_fs12.constants.F_OK);
|
|
60775
61125
|
return {
|
|
60776
61126
|
command: process.execPath,
|
|
60777
61127
|
args: [cliEntry],
|
|
@@ -60781,7 +61131,7 @@ async function normalizeInvocation2(candidate, source) {
|
|
|
60781
61131
|
} catch {
|
|
60782
61132
|
const exeSibling = normalized.replace(/\.(cmd|bat)$/i, ".exe");
|
|
60783
61133
|
try {
|
|
60784
|
-
await (0,
|
|
61134
|
+
await (0, import_promises9.access)(exeSibling, import_fs12.constants.F_OK);
|
|
60785
61135
|
return {
|
|
60786
61136
|
command: exeSibling,
|
|
60787
61137
|
args: [],
|
|
@@ -61148,20 +61498,20 @@ attendantDebugRouter.use((err, _req, res, _next) => {
|
|
|
61148
61498
|
var import_express29 = __toESM(require_express2(), 1);
|
|
61149
61499
|
|
|
61150
61500
|
// src/server/lib/local-operator-tools.ts
|
|
61151
|
-
var
|
|
61152
|
-
var
|
|
61153
|
-
var
|
|
61501
|
+
var import_child_process7 = require("child_process");
|
|
61502
|
+
var import_promises10 = require("fs/promises");
|
|
61503
|
+
var import_path18 = require("path");
|
|
61154
61504
|
var RUNNABLE_NPM_SCRIPTS = /* @__PURE__ */ new Set(["migrate"]);
|
|
61155
61505
|
var RUNNABLE_GLOBAL_NPM_PACKAGES = [/^iranti(?:@[\w.-]+)?$/i];
|
|
61156
61506
|
function spawnAndCollect(command, args, options = {}) {
|
|
61157
61507
|
return new Promise((resolve10, reject) => {
|
|
61158
61508
|
const shouldUseCmdWrapper = process.platform === "win32" && /\.cmd$/i.test(command);
|
|
61159
|
-
const child = shouldUseCmdWrapper ? (0,
|
|
61509
|
+
const child = shouldUseCmdWrapper ? (0, import_child_process7.spawn)(process.env.ComSpec ?? "cmd.exe", ["/d", "/s", "/c", command, ...args], {
|
|
61160
61510
|
cwd: options.cwd,
|
|
61161
61511
|
shell: false,
|
|
61162
61512
|
windowsHide: true,
|
|
61163
61513
|
env: process.env
|
|
61164
|
-
}) : (0,
|
|
61514
|
+
}) : (0, import_child_process7.spawn)(command, args, {
|
|
61165
61515
|
cwd: options.cwd,
|
|
61166
61516
|
shell: false,
|
|
61167
61517
|
windowsHide: true,
|
|
@@ -61205,7 +61555,7 @@ function resolveWindowsCommand(executable) {
|
|
|
61205
61555
|
}
|
|
61206
61556
|
}
|
|
61207
61557
|
function splitEnvPath(pathValue) {
|
|
61208
|
-
return (pathValue ?? "").split(
|
|
61558
|
+
return (pathValue ?? "").split(import_path18.delimiter).map((part) => part.trim()).filter(Boolean);
|
|
61209
61559
|
}
|
|
61210
61560
|
async function hasExecutableOnPath(executable) {
|
|
61211
61561
|
const resolved = resolveWindowsCommand(executable);
|
|
@@ -61213,7 +61563,7 @@ async function hasExecutableOnPath(executable) {
|
|
|
61213
61563
|
for (const dir of splitEnvPath(process.env["PATH"])) {
|
|
61214
61564
|
for (const candidate of candidates) {
|
|
61215
61565
|
try {
|
|
61216
|
-
await (0,
|
|
61566
|
+
await (0, import_promises10.access)((0, import_path18.join)(dir, candidate), import_promises10.constants.F_OK);
|
|
61217
61567
|
return true;
|
|
61218
61568
|
} catch {
|
|
61219
61569
|
}
|
|
@@ -61283,7 +61633,7 @@ async function validateCwd(cwd) {
|
|
|
61283
61633
|
if (!cwd) return void 0;
|
|
61284
61634
|
const pathToUse = cwd.trim();
|
|
61285
61635
|
if (!pathToUse) return void 0;
|
|
61286
|
-
const stats = await (0,
|
|
61636
|
+
const stats = await (0, import_promises10.stat)(pathToUse);
|
|
61287
61637
|
if (!stats.isDirectory()) {
|
|
61288
61638
|
throw new Error("cwd must be an existing directory.");
|
|
61289
61639
|
}
|
|
@@ -61458,7 +61808,7 @@ localToolsRouter.post("/run-command", async (req, res) => {
|
|
|
61458
61808
|
});
|
|
61459
61809
|
|
|
61460
61810
|
// src/server/routes/control-plane/control-plane-self.ts
|
|
61461
|
-
var
|
|
61811
|
+
var import_child_process8 = require("child_process");
|
|
61462
61812
|
var import_express30 = __toESM(require_express2(), 1);
|
|
61463
61813
|
var controlPlaneSelfRouter = (0, import_express30.Router)();
|
|
61464
61814
|
var shutdownListeners = /* @__PURE__ */ new Set();
|
|
@@ -61491,7 +61841,7 @@ controlPlaneSelfRouter.get("/self/shutdown-stream", (req, res) => {
|
|
|
61491
61841
|
});
|
|
61492
61842
|
function scheduleSelfUninstall() {
|
|
61493
61843
|
if (process.platform === "win32") {
|
|
61494
|
-
const child2 = (0,
|
|
61844
|
+
const child2 = (0, import_child_process8.spawn)("cmd", ["/d", "/s", "/c", "ping 127.0.0.1 -n 3 >nul && npm.cmd uninstall -g iranti-control-plane"], {
|
|
61495
61845
|
detached: true,
|
|
61496
61846
|
stdio: "ignore",
|
|
61497
61847
|
windowsHide: true
|
|
@@ -61499,7 +61849,7 @@ function scheduleSelfUninstall() {
|
|
|
61499
61849
|
child2.unref();
|
|
61500
61850
|
return;
|
|
61501
61851
|
}
|
|
61502
|
-
const child = (0,
|
|
61852
|
+
const child = (0, import_child_process8.spawn)("sh", ["-lc", "sleep 2 && npm uninstall -g iranti-control-plane"], {
|
|
61503
61853
|
detached: true,
|
|
61504
61854
|
stdio: "ignore"
|
|
61505
61855
|
});
|
|
@@ -61525,8 +61875,316 @@ controlPlaneSelfRouter.post("/self/uninstall", (_req, res) => {
|
|
|
61525
61875
|
scheduleSelfStop();
|
|
61526
61876
|
});
|
|
61527
61877
|
|
|
61878
|
+
// src/server/routes/control-plane/session-ledger.ts
|
|
61879
|
+
var import_express31 = __toESM(require_express2(), 1);
|
|
61880
|
+
|
|
61881
|
+
// src/server/lib/fleet-ledger-repo.ts
|
|
61882
|
+
init_db();
|
|
61883
|
+
function toIso4(value) {
|
|
61884
|
+
if (value === null || value === void 0) return null;
|
|
61885
|
+
if (value instanceof Date) return value.toISOString();
|
|
61886
|
+
if (typeof value === "string") return value;
|
|
61887
|
+
return String(value);
|
|
61888
|
+
}
|
|
61889
|
+
function mapRowToMirroredEvent(row) {
|
|
61890
|
+
return {
|
|
61891
|
+
id: row.id,
|
|
61892
|
+
instanceId: row.instance_id,
|
|
61893
|
+
remoteEventId: row.remote_event_id,
|
|
61894
|
+
timestamp: toIso4(row.timestamp),
|
|
61895
|
+
staffComponent: row.staff_component ?? "",
|
|
61896
|
+
actionType: row.action_type ?? "",
|
|
61897
|
+
agentId: row.agent_id ?? null,
|
|
61898
|
+
source: row.source ?? null,
|
|
61899
|
+
host: row.host ?? null,
|
|
61900
|
+
sessionId: row.session_id ?? null,
|
|
61901
|
+
entityType: row.entity_type ?? null,
|
|
61902
|
+
entityId: row.entity_id ?? null,
|
|
61903
|
+
key: row.key ?? null,
|
|
61904
|
+
reason: row.reason ?? null,
|
|
61905
|
+
level: row.level,
|
|
61906
|
+
metadata: row.metadata ?? null,
|
|
61907
|
+
ingestedAt: toIso4(row.ingested_at)
|
|
61908
|
+
};
|
|
61909
|
+
}
|
|
61910
|
+
function mapRowToWatermark(row) {
|
|
61911
|
+
const lastRemoteEventId = row.last_remote_event_id ?? null;
|
|
61912
|
+
return {
|
|
61913
|
+
instanceId: row.instance_id,
|
|
61914
|
+
lastEventTimestamp: toIso4(row.last_event_timestamp),
|
|
61915
|
+
lastEventId: lastRemoteEventId,
|
|
61916
|
+
lastRemoteEventId,
|
|
61917
|
+
lastPolledAt: toIso4(row.last_polled_at),
|
|
61918
|
+
lastPollSucceeded: row.last_poll_succeeded,
|
|
61919
|
+
lastPollError: row.last_poll_error ?? null,
|
|
61920
|
+
consecutiveFailures: row.consecutive_failures,
|
|
61921
|
+
totalEventsIngested: Number(row.total_events_ingested),
|
|
61922
|
+
createdAt: toIso4(row.created_at),
|
|
61923
|
+
updatedAt: toIso4(row.updated_at)
|
|
61924
|
+
};
|
|
61925
|
+
}
|
|
61926
|
+
async function upsertMirroredEvents(events) {
|
|
61927
|
+
if (events.length === 0) return 0;
|
|
61928
|
+
const COLS = 15;
|
|
61929
|
+
const params = [];
|
|
61930
|
+
const valueClauses = [];
|
|
61931
|
+
for (const ev of events) {
|
|
61932
|
+
const host = ev.host !== null && ev.host !== void 0 ? ev.host : typeof ev.metadata?.host === "string" ? ev.metadata.host : null;
|
|
61933
|
+
const base = params.length + 1;
|
|
61934
|
+
valueClauses.push(
|
|
61935
|
+
`($${base},$${base + 1},$${base + 2}::TIMESTAMPTZ,$${base + 3},$${base + 4},$${base + 5},$${base + 6},$${base + 7},$${base + 8},$${base + 9},$${base + 10},$${base + 11},$${base + 12},$${base + 13},$${base + 14}::jsonb)`
|
|
61936
|
+
);
|
|
61937
|
+
params.push(
|
|
61938
|
+
ev.instanceId,
|
|
61939
|
+
// $base
|
|
61940
|
+
ev.remoteEventId,
|
|
61941
|
+
// $base+1
|
|
61942
|
+
ev.timestamp,
|
|
61943
|
+
// $base+2
|
|
61944
|
+
ev.staffComponent,
|
|
61945
|
+
// $base+3
|
|
61946
|
+
ev.actionType,
|
|
61947
|
+
// $base+4
|
|
61948
|
+
ev.agentId,
|
|
61949
|
+
// $base+5
|
|
61950
|
+
ev.source,
|
|
61951
|
+
// $base+6
|
|
61952
|
+
host,
|
|
61953
|
+
// $base+7
|
|
61954
|
+
ev.sessionId,
|
|
61955
|
+
// $base+8
|
|
61956
|
+
ev.entityType,
|
|
61957
|
+
// $base+9
|
|
61958
|
+
ev.entityId,
|
|
61959
|
+
// $base+10
|
|
61960
|
+
ev.key,
|
|
61961
|
+
// $base+11
|
|
61962
|
+
ev.reason,
|
|
61963
|
+
// $base+12
|
|
61964
|
+
ev.level,
|
|
61965
|
+
// $base+13
|
|
61966
|
+
ev.metadata !== null && ev.metadata !== void 0 ? JSON.stringify(ev.metadata) : null
|
|
61967
|
+
// $base+14
|
|
61968
|
+
);
|
|
61969
|
+
void COLS;
|
|
61970
|
+
}
|
|
61971
|
+
const sql = `
|
|
61972
|
+
INSERT INTO mirrored_staff_events
|
|
61973
|
+
(instance_id, remote_event_id, timestamp, staff_component, action_type,
|
|
61974
|
+
agent_id, source, host, session_id, entity_type, entity_id, key, reason,
|
|
61975
|
+
level, metadata)
|
|
61976
|
+
VALUES ${valueClauses.join(", ")}
|
|
61977
|
+
ON CONFLICT (instance_id, remote_event_id) DO NOTHING
|
|
61978
|
+
`;
|
|
61979
|
+
const result = await query(sql, params);
|
|
61980
|
+
return result.rowCount ?? 0;
|
|
61981
|
+
}
|
|
61982
|
+
async function advanceWatermark(instanceId, lastTimestamp, lastEventId, eventsCount) {
|
|
61983
|
+
await query(
|
|
61984
|
+
`
|
|
61985
|
+
INSERT INTO instance_ledger_watermarks
|
|
61986
|
+
(instance_id, last_event_timestamp, last_remote_event_id,
|
|
61987
|
+
last_polled_at, last_poll_succeeded, last_poll_error,
|
|
61988
|
+
consecutive_failures, total_events_ingested,
|
|
61989
|
+
created_at, updated_at)
|
|
61990
|
+
VALUES
|
|
61991
|
+
($1, $2::TIMESTAMPTZ, $3,
|
|
61992
|
+
now(), true, null,
|
|
61993
|
+
0, $4,
|
|
61994
|
+
now(), now())
|
|
61995
|
+
ON CONFLICT (instance_id) DO UPDATE SET
|
|
61996
|
+
last_event_timestamp = EXCLUDED.last_event_timestamp,
|
|
61997
|
+
last_remote_event_id = EXCLUDED.last_remote_event_id,
|
|
61998
|
+
last_polled_at = now(),
|
|
61999
|
+
last_poll_succeeded = true,
|
|
62000
|
+
last_poll_error = null,
|
|
62001
|
+
consecutive_failures = 0,
|
|
62002
|
+
total_events_ingested = instance_ledger_watermarks.total_events_ingested + $4,
|
|
62003
|
+
updated_at = now()
|
|
62004
|
+
`,
|
|
62005
|
+
[instanceId, lastTimestamp, lastEventId, eventsCount]
|
|
62006
|
+
);
|
|
62007
|
+
}
|
|
62008
|
+
async function recordPollFailure(instanceId, error2) {
|
|
62009
|
+
await query(
|
|
62010
|
+
`
|
|
62011
|
+
INSERT INTO instance_ledger_watermarks
|
|
62012
|
+
(instance_id, last_polled_at, last_poll_succeeded, last_poll_error,
|
|
62013
|
+
consecutive_failures, total_events_ingested,
|
|
62014
|
+
created_at, updated_at)
|
|
62015
|
+
VALUES
|
|
62016
|
+
($1, now(), false, $2,
|
|
62017
|
+
1, 0,
|
|
62018
|
+
now(), now())
|
|
62019
|
+
ON CONFLICT (instance_id) DO UPDATE SET
|
|
62020
|
+
last_polled_at = now(),
|
|
62021
|
+
last_poll_succeeded = false,
|
|
62022
|
+
last_poll_error = $2,
|
|
62023
|
+
consecutive_failures = instance_ledger_watermarks.consecutive_failures + 1,
|
|
62024
|
+
updated_at = now()
|
|
62025
|
+
`,
|
|
62026
|
+
[instanceId, error2]
|
|
62027
|
+
);
|
|
62028
|
+
}
|
|
62029
|
+
async function getWatermark(instanceId) {
|
|
62030
|
+
const result = await query(
|
|
62031
|
+
"SELECT * FROM instance_ledger_watermarks WHERE instance_id = $1",
|
|
62032
|
+
[instanceId]
|
|
62033
|
+
);
|
|
62034
|
+
if (result.rows.length === 0) return null;
|
|
62035
|
+
return mapRowToWatermark(result.rows[0]);
|
|
62036
|
+
}
|
|
62037
|
+
async function getAllWatermarks() {
|
|
62038
|
+
const result = await query(
|
|
62039
|
+
"SELECT * FROM instance_ledger_watermarks ORDER BY instance_id"
|
|
62040
|
+
);
|
|
62041
|
+
return result.rows.map(mapRowToWatermark);
|
|
62042
|
+
}
|
|
62043
|
+
async function queryFleetLedger(filters) {
|
|
62044
|
+
const limit = Math.min(filters.limit ?? 100, 250);
|
|
62045
|
+
const params = [];
|
|
62046
|
+
const conditions = [];
|
|
62047
|
+
function addFilter(col, value, cast) {
|
|
62048
|
+
if (value === void 0) return;
|
|
62049
|
+
params.push(value);
|
|
62050
|
+
const placeholder = cast ? `$${params.length}::${cast}` : `$${params.length}`;
|
|
62051
|
+
conditions.push(`${col} = ${placeholder}`);
|
|
62052
|
+
}
|
|
62053
|
+
addFilter("instance_id", filters.instanceId);
|
|
62054
|
+
addFilter("source", filters.source);
|
|
62055
|
+
addFilter("host", filters.host);
|
|
62056
|
+
addFilter("agent_id", filters.agentId);
|
|
62057
|
+
addFilter("session_id", filters.sessionId);
|
|
62058
|
+
addFilter("action_type", filters.actionType);
|
|
62059
|
+
addFilter("level", filters.level);
|
|
62060
|
+
if (filters.since !== void 0) {
|
|
62061
|
+
params.push(filters.since);
|
|
62062
|
+
conditions.push(`timestamp >= $${params.length}::TIMESTAMPTZ`);
|
|
62063
|
+
}
|
|
62064
|
+
if (filters.until !== void 0) {
|
|
62065
|
+
params.push(filters.until);
|
|
62066
|
+
conditions.push(`timestamp <= $${params.length}::TIMESTAMPTZ`);
|
|
62067
|
+
}
|
|
62068
|
+
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
62069
|
+
const countSql = `
|
|
62070
|
+
SELECT COUNT(*) AS total
|
|
62071
|
+
FROM mirrored_staff_events
|
|
62072
|
+
${whereClause}
|
|
62073
|
+
`;
|
|
62074
|
+
const countResult = await query(countSql, params);
|
|
62075
|
+
const total = parseInt(countResult.rows[0]?.total ?? "0", 10);
|
|
62076
|
+
params.push(limit);
|
|
62077
|
+
const dataSql = `
|
|
62078
|
+
SELECT id, instance_id, remote_event_id, timestamp, staff_component, action_type,
|
|
62079
|
+
agent_id, source, host, session_id, entity_type, entity_id, key, reason,
|
|
62080
|
+
level, metadata, ingested_at
|
|
62081
|
+
FROM mirrored_staff_events
|
|
62082
|
+
${whereClause}
|
|
62083
|
+
ORDER BY timestamp DESC, remote_event_id DESC
|
|
62084
|
+
LIMIT $${params.length}
|
|
62085
|
+
`;
|
|
62086
|
+
const dataResult = await query(dataSql, params);
|
|
62087
|
+
const items = dataResult.rows.map(mapRowToMirroredEvent);
|
|
62088
|
+
return { items, total };
|
|
62089
|
+
}
|
|
62090
|
+
|
|
62091
|
+
// src/server/routes/control-plane/session-ledger.ts
|
|
62092
|
+
var sessionLedgerRouter = (0, import_express31.Router)();
|
|
62093
|
+
function parseLimit(value, defaultVal = 100, max = 250) {
|
|
62094
|
+
if (value === void 0 || value === null || String(value).trim() === "") return defaultVal;
|
|
62095
|
+
const n = Number.parseInt(String(value), 10);
|
|
62096
|
+
if (!Number.isFinite(n) || n < 1) throw new Error("limit must be a positive integer");
|
|
62097
|
+
return Math.min(n, max);
|
|
62098
|
+
}
|
|
62099
|
+
function deriveIngestionStatus(w) {
|
|
62100
|
+
if (!w.lastPolledAt) return "never_polled";
|
|
62101
|
+
const failures = w.consecutiveFailures ?? 0;
|
|
62102
|
+
if (failures === 0) return "healthy";
|
|
62103
|
+
if (failures < 3) return "degraded";
|
|
62104
|
+
return "failing";
|
|
62105
|
+
}
|
|
62106
|
+
sessionLedgerRouter.get("/", async (req, res) => {
|
|
62107
|
+
let limit;
|
|
62108
|
+
try {
|
|
62109
|
+
limit = parseLimit(req.query["limit"]);
|
|
62110
|
+
} catch (err) {
|
|
62111
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
62112
|
+
return;
|
|
62113
|
+
}
|
|
62114
|
+
try {
|
|
62115
|
+
const rawLevel = String(req.query["level"] ?? "").trim().toLowerCase();
|
|
62116
|
+
let level;
|
|
62117
|
+
if (rawLevel === "audit") level = "audit";
|
|
62118
|
+
else if (rawLevel === "debug") level = "debug";
|
|
62119
|
+
else if (rawLevel !== "") {
|
|
62120
|
+
res.status(400).json({ error: "level must be 'audit' or 'debug'" });
|
|
62121
|
+
return;
|
|
62122
|
+
}
|
|
62123
|
+
const filters = {
|
|
62124
|
+
limit,
|
|
62125
|
+
instanceId: typeof req.query["instanceId"] === "string" && req.query["instanceId"].trim() ? req.query["instanceId"].trim() : void 0,
|
|
62126
|
+
source: typeof req.query["source"] === "string" && req.query["source"].trim() ? req.query["source"].trim() : void 0,
|
|
62127
|
+
host: typeof req.query["host"] === "string" && req.query["host"].trim() ? req.query["host"].trim() : void 0,
|
|
62128
|
+
agentId: typeof req.query["agentId"] === "string" && req.query["agentId"].trim() ? req.query["agentId"].trim() : void 0,
|
|
62129
|
+
sessionId: typeof req.query["sessionId"] === "string" && req.query["sessionId"].trim() ? req.query["sessionId"].trim() : void 0,
|
|
62130
|
+
actionType: typeof req.query["actionType"] === "string" && req.query["actionType"].trim() ? req.query["actionType"].trim() : void 0,
|
|
62131
|
+
since: typeof req.query["since"] === "string" && req.query["since"].trim() ? req.query["since"].trim() : void 0,
|
|
62132
|
+
until: typeof req.query["until"] === "string" && req.query["until"].trim() ? req.query["until"].trim() : void 0,
|
|
62133
|
+
level
|
|
62134
|
+
};
|
|
62135
|
+
const { items, total } = await queryFleetLedger(filters);
|
|
62136
|
+
const body = {
|
|
62137
|
+
items,
|
|
62138
|
+
total,
|
|
62139
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
62140
|
+
};
|
|
62141
|
+
res.json(body);
|
|
62142
|
+
} catch (error2) {
|
|
62143
|
+
res.json({
|
|
62144
|
+
items: [],
|
|
62145
|
+
total: 0,
|
|
62146
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
62147
|
+
note: error2 instanceof Error ? error2.message : String(error2)
|
|
62148
|
+
});
|
|
62149
|
+
}
|
|
62150
|
+
});
|
|
62151
|
+
sessionLedgerRouter.get("/ingestion-health", async (_req, res) => {
|
|
62152
|
+
try {
|
|
62153
|
+
const watermarks = await getAllWatermarks();
|
|
62154
|
+
const instances = watermarks.map((w) => ({
|
|
62155
|
+
instanceId: w.instanceId,
|
|
62156
|
+
lastEventTimestamp: w.lastEventTimestamp ?? null,
|
|
62157
|
+
lastRemoteEventId: w.lastRemoteEventId ?? null,
|
|
62158
|
+
lastPolledAt: w.lastPolledAt ?? null,
|
|
62159
|
+
lastPollSucceeded: w.lastPollSucceeded ?? false,
|
|
62160
|
+
lastPollError: w.lastPollError ?? null,
|
|
62161
|
+
consecutiveFailures: w.consecutiveFailures ?? 0,
|
|
62162
|
+
totalEventsIngested: w.totalEventsIngested ?? 0,
|
|
62163
|
+
status: deriveIngestionStatus(w)
|
|
62164
|
+
}));
|
|
62165
|
+
const body = {
|
|
62166
|
+
instances,
|
|
62167
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
62168
|
+
};
|
|
62169
|
+
res.json(body);
|
|
62170
|
+
} catch (error2) {
|
|
62171
|
+
res.json({
|
|
62172
|
+
instances: [],
|
|
62173
|
+
fetchedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
62174
|
+
note: error2 instanceof Error ? error2.message : String(error2)
|
|
62175
|
+
});
|
|
62176
|
+
}
|
|
62177
|
+
});
|
|
62178
|
+
sessionLedgerRouter.use((err, _req, res, _next) => {
|
|
62179
|
+
const apiErr = err;
|
|
62180
|
+
res.status(apiErr.statusCode ?? 500).json({
|
|
62181
|
+
error: apiErr.message ?? "Internal server error",
|
|
62182
|
+
code: apiErr.code ?? "INTERNAL_ERROR"
|
|
62183
|
+
});
|
|
62184
|
+
});
|
|
62185
|
+
|
|
61528
62186
|
// src/server/routes/control-plane/index.ts
|
|
61529
|
-
var controlPlaneRouter = (0,
|
|
62187
|
+
var controlPlaneRouter = (0, import_express32.Router)();
|
|
61530
62188
|
controlPlaneRouter.use("/", archivistRouter);
|
|
61531
62189
|
controlPlaneRouter.use("/", kbRouter);
|
|
61532
62190
|
controlPlaneRouter.use("/", whoknowsRouter);
|
|
@@ -61547,6 +62205,7 @@ controlPlaneRouter.use("/diagnostics", diagnosticsRouter);
|
|
|
61547
62205
|
controlPlaneRouter.use("/metrics", metricsRouter);
|
|
61548
62206
|
controlPlaneRouter.use("/overview", overviewRouter);
|
|
61549
62207
|
controlPlaneRouter.use("/sessions", sessionsRouter);
|
|
62208
|
+
controlPlaneRouter.use("/session-ledger", sessionLedgerRouter);
|
|
61550
62209
|
controlPlaneRouter.use("/instances", upgradeRouter);
|
|
61551
62210
|
controlPlaneRouter.use("/version-sync", versionSyncRouter);
|
|
61552
62211
|
controlPlaneRouter.use("/install-state", installStateRouter);
|
|
@@ -61746,6 +62405,200 @@ function stopAdapter() {
|
|
|
61746
62405
|
}
|
|
61747
62406
|
}
|
|
61748
62407
|
|
|
62408
|
+
// src/server/lib/fleet-ledger-poller.ts
|
|
62409
|
+
var FLEET_LEDGER_POLL_INTERVAL_MS = typeof process.env["FLEET_LEDGER_POLL_INTERVAL_MS"] === "string" && process.env["FLEET_LEDGER_POLL_INTERVAL_MS"].trim() !== "" ? parseInt(process.env["FLEET_LEDGER_POLL_INTERVAL_MS"], 10) : 6e4;
|
|
62410
|
+
var POLL_BATCH_LIMIT = 250;
|
|
62411
|
+
var FETCH_TIMEOUT_MS = 1e4;
|
|
62412
|
+
async function discoverInstances() {
|
|
62413
|
+
const discovered = await discoverAndAggregate();
|
|
62414
|
+
const instances = [];
|
|
62415
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
62416
|
+
for (const candidate of discovered.instances) {
|
|
62417
|
+
const authority = await resolveInstanceAuthority(candidate.instanceId) ?? await resolveInstanceAuthority(candidate.name);
|
|
62418
|
+
if (!authority) continue;
|
|
62419
|
+
if (seenIds.has(authority.instanceId)) continue;
|
|
62420
|
+
seenIds.add(authority.instanceId);
|
|
62421
|
+
instances.push({
|
|
62422
|
+
instanceId: authority.instanceId,
|
|
62423
|
+
apiBaseUrl: authority.apiBaseUrl,
|
|
62424
|
+
apiKey: authority.apiKey
|
|
62425
|
+
});
|
|
62426
|
+
}
|
|
62427
|
+
return instances;
|
|
62428
|
+
}
|
|
62429
|
+
function isRecord3(val) {
|
|
62430
|
+
return typeof val === "object" && val !== null;
|
|
62431
|
+
}
|
|
62432
|
+
function toLedgerResponse(raw) {
|
|
62433
|
+
if (!isRecord3(raw)) return null;
|
|
62434
|
+
const items = raw["items"];
|
|
62435
|
+
if (!Array.isArray(items)) return null;
|
|
62436
|
+
return { items };
|
|
62437
|
+
}
|
|
62438
|
+
function itemTimestamp(item) {
|
|
62439
|
+
return item?.timestamp ?? null;
|
|
62440
|
+
}
|
|
62441
|
+
function itemEventId(item) {
|
|
62442
|
+
return item?.eventId ?? item?.event_id ?? item?.id ?? null;
|
|
62443
|
+
}
|
|
62444
|
+
function decrementIsoTimestamp(iso) {
|
|
62445
|
+
if (!iso) return null;
|
|
62446
|
+
const millis = Date.parse(iso);
|
|
62447
|
+
if (Number.isNaN(millis)) return null;
|
|
62448
|
+
return new Date(millis - 1).toISOString();
|
|
62449
|
+
}
|
|
62450
|
+
function buildLedgerPollUrl(apiBaseUrl, since, until) {
|
|
62451
|
+
const params = new URLSearchParams({
|
|
62452
|
+
limit: String(POLL_BATCH_LIMIT),
|
|
62453
|
+
level: "audit"
|
|
62454
|
+
});
|
|
62455
|
+
if (since) params.set("since", since);
|
|
62456
|
+
if (until) params.set("until", until);
|
|
62457
|
+
return `${apiBaseUrl}/memory/ledger?${params.toString()}`;
|
|
62458
|
+
}
|
|
62459
|
+
function mapItem(item, instanceId) {
|
|
62460
|
+
const remoteEventId = item.eventId ?? item.event_id ?? item.id ?? null;
|
|
62461
|
+
const timestamp = item.timestamp ?? null;
|
|
62462
|
+
if (!remoteEventId || !timestamp) return null;
|
|
62463
|
+
const rawLevel = (item.level ?? "audit").toLowerCase();
|
|
62464
|
+
const level = rawLevel === "debug" ? "debug" : "audit";
|
|
62465
|
+
const metadata = isRecord3(item.metadata) ? item.metadata : null;
|
|
62466
|
+
return {
|
|
62467
|
+
instanceId,
|
|
62468
|
+
remoteEventId,
|
|
62469
|
+
timestamp,
|
|
62470
|
+
staffComponent: item.staffComponent ?? item.staff_component ?? null,
|
|
62471
|
+
actionType: item.actionType ?? item.action_type ?? null,
|
|
62472
|
+
agentId: item.agentId ?? item.agent_id ?? null,
|
|
62473
|
+
source: item.source ?? null,
|
|
62474
|
+
host: typeof metadata?.["host"] === "string" ? metadata["host"] : null,
|
|
62475
|
+
sessionId: typeof metadata?.["sessionId"] === "string" ? metadata["sessionId"] : null,
|
|
62476
|
+
entityType: item.entityType ?? item.entity_type ?? null,
|
|
62477
|
+
entityId: item.entityId ?? item.entity_id ?? null,
|
|
62478
|
+
key: item.key ?? null,
|
|
62479
|
+
reason: item.reason ?? null,
|
|
62480
|
+
level,
|
|
62481
|
+
metadata
|
|
62482
|
+
};
|
|
62483
|
+
}
|
|
62484
|
+
async function pollInstance(instanceId, apiBaseUrl, apiKey) {
|
|
62485
|
+
const watermark = await getWatermark(instanceId);
|
|
62486
|
+
const lowerBoundSince = decrementIsoTimestamp(watermark?.lastEventTimestamp ?? null);
|
|
62487
|
+
let upperBoundUntil = null;
|
|
62488
|
+
let latestTimestampSeen = watermark?.lastEventTimestamp ?? null;
|
|
62489
|
+
let latestEventIdSeen = watermark?.lastEventId ?? null;
|
|
62490
|
+
let capturedLatestFromPoll = false;
|
|
62491
|
+
while (true) {
|
|
62492
|
+
const url2 = buildLedgerPollUrl(apiBaseUrl, lowerBoundSince, upperBoundUntil);
|
|
62493
|
+
const headers = {};
|
|
62494
|
+
if (apiKey) headers["X-Iranti-Key"] = apiKey;
|
|
62495
|
+
let response;
|
|
62496
|
+
try {
|
|
62497
|
+
const controller = new AbortController();
|
|
62498
|
+
const timeoutHandle = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
62499
|
+
try {
|
|
62500
|
+
response = await fetch(url2, { headers, signal: controller.signal });
|
|
62501
|
+
} finally {
|
|
62502
|
+
clearTimeout(timeoutHandle);
|
|
62503
|
+
}
|
|
62504
|
+
} catch (err) {
|
|
62505
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62506
|
+
await recordPollFailure(instanceId, `Network error: ${message}`);
|
|
62507
|
+
return;
|
|
62508
|
+
}
|
|
62509
|
+
if (!response.ok) {
|
|
62510
|
+
await recordPollFailure(
|
|
62511
|
+
instanceId,
|
|
62512
|
+
`HTTP ${response.status} ${response.statusText} from ${url2}`
|
|
62513
|
+
);
|
|
62514
|
+
return;
|
|
62515
|
+
}
|
|
62516
|
+
let raw;
|
|
62517
|
+
try {
|
|
62518
|
+
raw = await response.json();
|
|
62519
|
+
} catch (err) {
|
|
62520
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62521
|
+
await recordPollFailure(instanceId, `JSON parse error: ${message}`);
|
|
62522
|
+
return;
|
|
62523
|
+
}
|
|
62524
|
+
const ledger = toLedgerResponse(raw);
|
|
62525
|
+
if (!ledger) {
|
|
62526
|
+
await recordPollFailure(instanceId, "Unexpected ledger response shape (missing items array)");
|
|
62527
|
+
return;
|
|
62528
|
+
}
|
|
62529
|
+
const { items } = ledger;
|
|
62530
|
+
if (items.length === 0) {
|
|
62531
|
+
await advanceWatermark(instanceId, latestTimestampSeen, latestEventIdSeen, 0);
|
|
62532
|
+
return;
|
|
62533
|
+
}
|
|
62534
|
+
const events = [];
|
|
62535
|
+
for (const item of items) {
|
|
62536
|
+
const mapped = mapItem(item, instanceId);
|
|
62537
|
+
if (mapped) events.push(mapped);
|
|
62538
|
+
}
|
|
62539
|
+
const insertedCount = events.length > 0 ? await upsertMirroredEvents(events) : 0;
|
|
62540
|
+
if (!capturedLatestFromPoll) {
|
|
62541
|
+
latestTimestampSeen = itemTimestamp(items[0]);
|
|
62542
|
+
latestEventIdSeen = itemEventId(items[0]);
|
|
62543
|
+
capturedLatestFromPoll = true;
|
|
62544
|
+
}
|
|
62545
|
+
await advanceWatermark(instanceId, latestTimestampSeen, latestEventIdSeen, insertedCount);
|
|
62546
|
+
if (items.length < POLL_BATCH_LIMIT) break;
|
|
62547
|
+
const oldestTimestamp = itemTimestamp(items[items.length - 1]);
|
|
62548
|
+
const nextUpperBoundUntil = decrementIsoTimestamp(oldestTimestamp);
|
|
62549
|
+
if (!nextUpperBoundUntil) break;
|
|
62550
|
+
if (lowerBoundSince && Date.parse(nextUpperBoundUntil) <= Date.parse(lowerBoundSince)) {
|
|
62551
|
+
break;
|
|
62552
|
+
}
|
|
62553
|
+
upperBoundUntil = nextUpperBoundUntil;
|
|
62554
|
+
}
|
|
62555
|
+
}
|
|
62556
|
+
async function pollAllInstancesOnce() {
|
|
62557
|
+
let instances;
|
|
62558
|
+
try {
|
|
62559
|
+
instances = await discoverInstances();
|
|
62560
|
+
} catch (err) {
|
|
62561
|
+
console.warn("[fleet-poller] Failed to discover instances:", err);
|
|
62562
|
+
return;
|
|
62563
|
+
}
|
|
62564
|
+
if (instances.length === 0) return;
|
|
62565
|
+
for (const instance of instances) {
|
|
62566
|
+
try {
|
|
62567
|
+
await pollInstance(instance.instanceId, instance.apiBaseUrl, instance.apiKey);
|
|
62568
|
+
} catch (err) {
|
|
62569
|
+
console.warn(
|
|
62570
|
+
`[fleet-poller] Error polling instance ${instance.instanceId} (${instance.apiBaseUrl}):`,
|
|
62571
|
+
err
|
|
62572
|
+
);
|
|
62573
|
+
}
|
|
62574
|
+
}
|
|
62575
|
+
}
|
|
62576
|
+
var _pollInterval = null;
|
|
62577
|
+
function startFleetLedgerPoller() {
|
|
62578
|
+
if (_pollInterval !== null) {
|
|
62579
|
+
console.warn("[fleet-poller] Already running - startFleetLedgerPoller() called twice.");
|
|
62580
|
+
return;
|
|
62581
|
+
}
|
|
62582
|
+
console.log(
|
|
62583
|
+
`[fleet-poller] Starting. Poll interval: ${FLEET_LEDGER_POLL_INTERVAL_MS}ms.`
|
|
62584
|
+
);
|
|
62585
|
+
pollAllInstancesOnce().catch((err) => {
|
|
62586
|
+
console.warn("[fleet-poller] Initial poll error:", err);
|
|
62587
|
+
});
|
|
62588
|
+
_pollInterval = setInterval(() => {
|
|
62589
|
+
pollAllInstancesOnce().catch((err) => {
|
|
62590
|
+
console.warn("[fleet-poller] Poll error:", err);
|
|
62591
|
+
});
|
|
62592
|
+
}, FLEET_LEDGER_POLL_INTERVAL_MS);
|
|
62593
|
+
}
|
|
62594
|
+
function stopFleetLedgerPoller() {
|
|
62595
|
+
if (_pollInterval !== null) {
|
|
62596
|
+
clearInterval(_pollInterval);
|
|
62597
|
+
_pollInterval = null;
|
|
62598
|
+
console.log("[fleet-poller] Stopped.");
|
|
62599
|
+
}
|
|
62600
|
+
}
|
|
62601
|
+
|
|
61749
62602
|
// src/server/index.ts
|
|
61750
62603
|
init_db();
|
|
61751
62604
|
|
|
@@ -61766,18 +62619,18 @@ function buildPortSelectionPlan(params) {
|
|
|
61766
62619
|
|
|
61767
62620
|
// src/server/index.ts
|
|
61768
62621
|
var _isSea = typeof process.isSea === "function" && process.isSea();
|
|
61769
|
-
var __dirname3 = _isSea ? (0,
|
|
61770
|
-
var clientDistCandidates = process.env.IRANTI_CP_ASSETS_DIR ? [(0,
|
|
61771
|
-
(0,
|
|
61772
|
-
(0,
|
|
61773
|
-
(0,
|
|
61774
|
-
(0,
|
|
62622
|
+
var __dirname3 = _isSea ? (0, import_path20.dirname)(process.execPath) : (0, import_path20.dirname)((0, import_url3.fileURLToPath)(__importmeta_url));
|
|
62623
|
+
var clientDistCandidates = process.env.IRANTI_CP_ASSETS_DIR ? [(0, import_path20.resolve)(process.env.IRANTI_CP_ASSETS_DIR)] : _isSea ? [(0, import_path20.resolve)((0, import_path20.dirname)(process.execPath), "public", "control-plane")] : [
|
|
62624
|
+
(0, import_path20.resolve)(__dirname3, "../../../public/control-plane"),
|
|
62625
|
+
(0, import_path20.resolve)(__dirname3, "../../public/control-plane"),
|
|
62626
|
+
(0, import_path20.resolve)(process.cwd(), "../../public/control-plane"),
|
|
62627
|
+
(0, import_path20.resolve)(process.cwd(), "../public/control-plane")
|
|
61775
62628
|
];
|
|
61776
|
-
var clientDist = clientDistCandidates.find((candidate) => (0,
|
|
62629
|
+
var clientDist = clientDistCandidates.find((candidate) => (0, import_fs14.existsSync)((0, import_path20.resolve)(candidate, "index.html"))) ?? clientDistCandidates[0];
|
|
61777
62630
|
var _version = "0.0.0";
|
|
61778
62631
|
try {
|
|
61779
62632
|
if (_isSea) {
|
|
61780
|
-
const pkgPath = (0,
|
|
62633
|
+
const pkgPath = (0, import_path20.resolve)((0, import_path20.dirname)(process.execPath), "package.json");
|
|
61781
62634
|
const _require = (0, import_module.createRequire)((0, import_url3.pathToFileURL)(process.execPath).href);
|
|
61782
62635
|
const pkg = _require(pkgPath);
|
|
61783
62636
|
_version = pkg.version ?? "0.0.0";
|
|
@@ -61820,9 +62673,9 @@ async function findAvailablePort(start, end) {
|
|
|
61820
62673
|
`[iranti-cp] No available port in range ${start}\u2013${end}. Free one of those ports and try again.`
|
|
61821
62674
|
);
|
|
61822
62675
|
}
|
|
61823
|
-
var app = (0,
|
|
62676
|
+
var app = (0, import_express33.default)();
|
|
61824
62677
|
app.use((0, import_cors.default)({ origin: `http://localhost:5173` }));
|
|
61825
|
-
app.use(
|
|
62678
|
+
app.use(import_express33.default.json());
|
|
61826
62679
|
app.get("/api/control-plane/ping", (_req, res) => {
|
|
61827
62680
|
res.json({
|
|
61828
62681
|
ok: true,
|
|
@@ -61831,9 +62684,9 @@ app.get("/api/control-plane/ping", (_req, res) => {
|
|
|
61831
62684
|
});
|
|
61832
62685
|
});
|
|
61833
62686
|
app.use("/api/control-plane", controlPlaneRouter);
|
|
61834
|
-
app.use("/control-plane",
|
|
62687
|
+
app.use("/control-plane", import_express33.default.static(clientDist));
|
|
61835
62688
|
app.get("/control-plane/*", (_req, res) => {
|
|
61836
|
-
res.sendFile((0,
|
|
62689
|
+
res.sendFile((0, import_path20.resolve)(clientDist, "index.html"));
|
|
61837
62690
|
});
|
|
61838
62691
|
app.get("/", (_req, res) => res.redirect("/control-plane"));
|
|
61839
62692
|
app.use(
|
|
@@ -61867,6 +62720,7 @@ async function main() {
|
|
|
61867
62720
|
startAdapter().catch((err) => {
|
|
61868
62721
|
console.warn("[adapter] Failed to start:", err.message);
|
|
61869
62722
|
});
|
|
62723
|
+
startFleetLedgerPoller();
|
|
61870
62724
|
if (!process.env["IRANTI_CP_NO_OPEN"]) {
|
|
61871
62725
|
Promise.resolve().then(() => __toESM(require_open(), 1)).then(({ default: open }) => {
|
|
61872
62726
|
void open(`http://localhost:${PORT}`);
|
|
@@ -61877,6 +62731,7 @@ async function main() {
|
|
|
61877
62731
|
function shutdown(signal) {
|
|
61878
62732
|
console.log(`[iranti-cp] Received ${signal} \u2014 shutting down gracefully.`);
|
|
61879
62733
|
stopAdapter();
|
|
62734
|
+
stopFleetLedgerPoller();
|
|
61880
62735
|
server.close(() => {
|
|
61881
62736
|
console.log("[iranti-cp] Server closed.");
|
|
61882
62737
|
process.exit(0);
|