nestor-sh 2.7.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nestor.mjs CHANGED
@@ -6567,18 +6567,51 @@ var init_store = __esm({
6567
6567
  }
6568
6568
  /**
6569
6569
  * Search missions by objective text (LIKE query).
6570
+ * Supports optional filters for type, status, date range.
6571
+ * Returns results sorted by relevance (title matches ranked higher).
6570
6572
  */
6571
- searchMissions(query, limit = 5) {
6572
- const pattern = `%${query}%`;
6573
- const rows = queryAll(
6574
- this.db,
6575
- `SELECT * FROM missions
6576
- WHERE objective LIKE ? OR title LIKE ?
6577
- ORDER BY created_at DESC
6578
- LIMIT ?`,
6579
- [pattern, pattern, limit]
6580
- );
6581
- return rows.map((row) => this.rowToMission(row));
6573
+ searchMissions(query, opts) {
6574
+ let sql = "SELECT * FROM missions WHERE 1=1";
6575
+ const params = [];
6576
+ if (query) {
6577
+ sql += " AND (title LIKE ? OR objective LIKE ? OR findings LIKE ?)";
6578
+ const q = `%${query}%`;
6579
+ params.push(q, q, q);
6580
+ }
6581
+ if (opts?.type) {
6582
+ sql += " AND type = ?";
6583
+ params.push(opts.type);
6584
+ }
6585
+ if (opts?.status) {
6586
+ sql += " AND status = ?";
6587
+ params.push(opts.status);
6588
+ }
6589
+ if (opts?.from) {
6590
+ sql += " AND created_at >= ?";
6591
+ params.push(new Date(opts.from).getTime());
6592
+ }
6593
+ if (opts?.to) {
6594
+ sql += " AND created_at <= ?";
6595
+ params.push(new Date(opts.to).getTime());
6596
+ }
6597
+ sql += " ORDER BY created_at DESC";
6598
+ sql += ` LIMIT ${opts?.limit || 20}`;
6599
+ const rows = queryAll(this.db, sql, params);
6600
+ const missions = rows.map((row) => this.rowToMission(row));
6601
+ if (query) {
6602
+ const lowerQ = query.toLowerCase();
6603
+ missions.sort((a, b) => {
6604
+ const aInTitle = a.title.toLowerCase().includes(lowerQ) ? 1 : 0;
6605
+ const bInTitle = b.title.toLowerCase().includes(lowerQ) ? 1 : 0;
6606
+ if (aInTitle !== bInTitle) return bInTitle - aInTitle;
6607
+ return b.createdAt - a.createdAt;
6608
+ });
6609
+ }
6610
+ return missions.map((m) => ({
6611
+ ...m,
6612
+ findingCount: Array.isArray(m.findings) ? m.findings.length : 0,
6613
+ qualityScore: m.evaluation && typeof m.evaluation === "object" && "overall" in m.evaluation ? m.evaluation.overall : null
6614
+ }));
6582
6615
  }
6583
6616
  /**
6584
6617
  * List all missions, optionally filtered by tenant.
@@ -6589,6 +6622,44 @@ var init_store = __esm({
6589
6622
  const rows = queryAll(this.db, sql, params);
6590
6623
  return rows.map((row) => this.rowToMission(row));
6591
6624
  }
6625
+ /**
6626
+ * Get aggregate mission statistics.
6627
+ */
6628
+ getMissionStats(tenantId) {
6629
+ const baseSql = tenantId ? "SELECT * FROM missions WHERE tenant_id = ?" : "SELECT * FROM missions";
6630
+ const params = tenantId ? [tenantId] : [];
6631
+ const rows = queryAll(this.db, baseSql, params);
6632
+ const missions = rows.map((row) => this.rowToMission(row));
6633
+ const byType = {};
6634
+ const byStatus = {};
6635
+ let totalCost = 0;
6636
+ let totalFindings = 0;
6637
+ let qualitySum = 0;
6638
+ let qualityCount = 0;
6639
+ for (const m of missions) {
6640
+ byType[m.type] = (byType[m.type] || 0) + 1;
6641
+ byStatus[m.status] = (byStatus[m.status] || 0) + 1;
6642
+ totalCost += m.spentUsd || 0;
6643
+ totalFindings += Array.isArray(m.findings) ? m.findings.length : 0;
6644
+ if (m.evaluation && typeof m.evaluation === "object" && "overall" in m.evaluation) {
6645
+ const overall = m.evaluation.overall;
6646
+ if (typeof overall === "number") {
6647
+ qualitySum += overall;
6648
+ qualityCount++;
6649
+ }
6650
+ }
6651
+ }
6652
+ const topCategories = Object.entries(byType).sort((a, b) => b[1] - a[1]).map(([cat]) => cat);
6653
+ return {
6654
+ total: missions.length,
6655
+ byType,
6656
+ byStatus,
6657
+ totalCost: Math.round(totalCost * 100) / 100,
6658
+ totalFindings,
6659
+ avgQuality: qualityCount > 0 ? Math.round(qualitySum / qualityCount * 100) / 100 : 0,
6660
+ topCategories
6661
+ };
6662
+ }
6592
6663
  /**
6593
6664
  * Search findings across all missions (LIKE query).
6594
6665
  */
@@ -7738,9 +7809,9 @@ var init_request_validator = __esm({
7738
7809
  });
7739
7810
 
7740
7811
  // ../server/src/security/input-sanitizer.ts
7741
- function resolvePath(base, relative6) {
7742
- if (relative6.startsWith("/")) return relative6;
7743
- const parts = (base + "/" + relative6).split("/");
7812
+ function resolvePath(base, relative7) {
7813
+ if (relative7.startsWith("/")) return relative7;
7814
+ const parts = (base + "/" + relative7).split("/");
7744
7815
  const resolved = [];
7745
7816
  for (const part of parts) {
7746
7817
  if (part === "..") {
@@ -9279,7 +9350,7 @@ import path from "node:path";
9279
9350
  import fs2 from "node:fs";
9280
9351
  function validateSkillFile(filePath, content) {
9281
9352
  const ext = path.extname(filePath).toLowerCase();
9282
- const basename5 = path.basename(filePath);
9353
+ const basename6 = path.basename(filePath);
9283
9354
  const extensionlessAllowed = /* @__PURE__ */ new Set([
9284
9355
  "LICENSE",
9285
9356
  "LICENCE",
@@ -9295,14 +9366,14 @@ function validateSkillFile(filePath, content) {
9295
9366
  ".prettierrc",
9296
9367
  ".eslintrc"
9297
9368
  ]);
9298
- if (!ext && !extensionlessAllowed.has(basename5)) {
9369
+ if (!ext && !extensionlessAllowed.has(basename6)) {
9299
9370
  return {
9300
9371
  valid: false,
9301
- reason: `File without extension is not in the allowlist: ${basename5}`,
9372
+ reason: `File without extension is not in the allowlist: ${basename6}`,
9302
9373
  extension: ""
9303
9374
  };
9304
9375
  }
9305
- if (!ext && extensionlessAllowed.has(basename5)) {
9376
+ if (!ext && extensionlessAllowed.has(basename6)) {
9306
9377
  if (content && content.length >= 4) {
9307
9378
  const binaryCheck = checkForBinaryContent(content, "");
9308
9379
  if (binaryCheck) {
@@ -11430,7 +11501,7 @@ var SERVER_VERSION, startTime;
11430
11501
  var init_health = __esm({
11431
11502
  "../server/src/routes/health.ts"() {
11432
11503
  "use strict";
11433
- SERVER_VERSION = "2.7.1";
11504
+ SERVER_VERSION = "3.0.0";
11434
11505
  startTime = Date.now();
11435
11506
  }
11436
11507
  });
@@ -12843,7 +12914,7 @@ var init_system = __esm({
12843
12914
  init_error_handler();
12844
12915
  init_broadcaster();
12845
12916
  init_approval_service();
12846
- SERVER_VERSION2 = "2.7.1";
12917
+ SERVER_VERSION2 = "3.0.0";
12847
12918
  startTime2 = Date.now();
12848
12919
  UpdateConfigSchema = z9.object({
12849
12920
  server: z9.object({
@@ -20273,7 +20344,7 @@ function isNativeAvailable() {
20273
20344
  return nativeModule !== null;
20274
20345
  }
20275
20346
  function getNativeVersion() {
20276
- return nativeModule ? "2.7.1" : null;
20347
+ return nativeModule ? "3.0.0" : null;
20277
20348
  }
20278
20349
  function validateSsrf(url, allowPrivate = false) {
20279
20350
  if (nativeModule) {
@@ -71039,7 +71110,7 @@ var require_util2 = __commonJS({
71039
71110
  return path30;
71040
71111
  }
71041
71112
  exports2.normalize = normalize4;
71042
- function join30(aRoot, aPath) {
71113
+ function join31(aRoot, aPath) {
71043
71114
  if (aRoot === "") {
71044
71115
  aRoot = ".";
71045
71116
  }
@@ -71071,11 +71142,11 @@ var require_util2 = __commonJS({
71071
71142
  }
71072
71143
  return joined;
71073
71144
  }
71074
- exports2.join = join30;
71145
+ exports2.join = join31;
71075
71146
  exports2.isAbsolute = function(aPath) {
71076
71147
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
71077
71148
  };
71078
- function relative6(aRoot, aPath) {
71149
+ function relative7(aRoot, aPath) {
71079
71150
  if (aRoot === "") {
71080
71151
  aRoot = ".";
71081
71152
  }
@@ -71094,7 +71165,7 @@ var require_util2 = __commonJS({
71094
71165
  }
71095
71166
  return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
71096
71167
  }
71097
- exports2.relative = relative6;
71168
+ exports2.relative = relative7;
71098
71169
  var supportsNullProto = function() {
71099
71170
  var obj = /* @__PURE__ */ Object.create(null);
71100
71171
  return !("__proto__" in obj);
@@ -71244,7 +71315,7 @@ var require_util2 = __commonJS({
71244
71315
  parsed.path = parsed.path.substring(0, index + 1);
71245
71316
  }
71246
71317
  }
71247
- sourceURL = join30(urlGenerate(parsed), sourceURL);
71318
+ sourceURL = join31(urlGenerate(parsed), sourceURL);
71248
71319
  }
71249
71320
  return normalize4(sourceURL);
71250
71321
  }
@@ -73046,7 +73117,7 @@ var require_escodegen = __commonJS({
73046
73117
  function noEmptySpace() {
73047
73118
  return space ? space : " ";
73048
73119
  }
73049
- function join30(left2, right2) {
73120
+ function join31(left2, right2) {
73050
73121
  var leftSource, rightSource, leftCharCode, rightCharCode;
73051
73122
  leftSource = toSourceNodeWhenNeeded(left2).toString();
73052
73123
  if (leftSource.length === 0) {
@@ -73377,8 +73448,8 @@ var require_escodegen = __commonJS({
73377
73448
  } else {
73378
73449
  result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT));
73379
73450
  }
73380
- result = join30(result, operator);
73381
- result = [join30(
73451
+ result = join31(result, operator);
73452
+ result = [join31(
73382
73453
  result,
73383
73454
  that.generateExpression(stmt.right, Precedence.Assignment, E_TTT)
73384
73455
  ), ")"];
@@ -73521,11 +73592,11 @@ var require_escodegen = __commonJS({
73521
73592
  var result, fragment;
73522
73593
  result = ["class"];
73523
73594
  if (stmt.id) {
73524
- result = join30(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
73595
+ result = join31(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
73525
73596
  }
73526
73597
  if (stmt.superClass) {
73527
- fragment = join30("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
73528
- result = join30(result, fragment);
73598
+ fragment = join31("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
73599
+ result = join31(result, fragment);
73529
73600
  }
73530
73601
  result.push(space);
73531
73602
  result.push(this.generateStatement(stmt.body, S_TFFT));
@@ -73538,9 +73609,9 @@ var require_escodegen = __commonJS({
73538
73609
  return escapeDirective(stmt.directive) + this.semicolon(flags);
73539
73610
  },
73540
73611
  DoWhileStatement: function(stmt, flags) {
73541
- var result = join30("do", this.maybeBlock(stmt.body, S_TFFF));
73612
+ var result = join31("do", this.maybeBlock(stmt.body, S_TFFF));
73542
73613
  result = this.maybeBlockSuffix(stmt.body, result);
73543
- return join30(result, [
73614
+ return join31(result, [
73544
73615
  "while" + space + "(",
73545
73616
  this.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
73546
73617
  ")" + this.semicolon(flags)
@@ -73576,11 +73647,11 @@ var require_escodegen = __commonJS({
73576
73647
  ExportDefaultDeclaration: function(stmt, flags) {
73577
73648
  var result = ["export"], bodyFlags;
73578
73649
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
73579
- result = join30(result, "default");
73650
+ result = join31(result, "default");
73580
73651
  if (isStatement(stmt.declaration)) {
73581
- result = join30(result, this.generateStatement(stmt.declaration, bodyFlags));
73652
+ result = join31(result, this.generateStatement(stmt.declaration, bodyFlags));
73582
73653
  } else {
73583
- result = join30(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
73654
+ result = join31(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
73584
73655
  }
73585
73656
  return result;
73586
73657
  },
@@ -73588,15 +73659,15 @@ var require_escodegen = __commonJS({
73588
73659
  var result = ["export"], bodyFlags, that = this;
73589
73660
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
73590
73661
  if (stmt.declaration) {
73591
- return join30(result, this.generateStatement(stmt.declaration, bodyFlags));
73662
+ return join31(result, this.generateStatement(stmt.declaration, bodyFlags));
73592
73663
  }
73593
73664
  if (stmt.specifiers) {
73594
73665
  if (stmt.specifiers.length === 0) {
73595
- result = join30(result, "{" + space + "}");
73666
+ result = join31(result, "{" + space + "}");
73596
73667
  } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) {
73597
- result = join30(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
73668
+ result = join31(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
73598
73669
  } else {
73599
- result = join30(result, "{");
73670
+ result = join31(result, "{");
73600
73671
  withIndent(function(indent2) {
73601
73672
  var i, iz;
73602
73673
  result.push(newline);
@@ -73614,7 +73685,7 @@ var require_escodegen = __commonJS({
73614
73685
  result.push(base + "}");
73615
73686
  }
73616
73687
  if (stmt.source) {
73617
- result = join30(result, [
73688
+ result = join31(result, [
73618
73689
  "from" + space,
73619
73690
  // ModuleSpecifier
73620
73691
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -73702,7 +73773,7 @@ var require_escodegen = __commonJS({
73702
73773
  ];
73703
73774
  cursor = 0;
73704
73775
  if (stmt.specifiers[cursor].type === Syntax.ImportDefaultSpecifier) {
73705
- result = join30(result, [
73776
+ result = join31(result, [
73706
73777
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
73707
73778
  ]);
73708
73779
  ++cursor;
@@ -73712,7 +73783,7 @@ var require_escodegen = __commonJS({
73712
73783
  result.push(",");
73713
73784
  }
73714
73785
  if (stmt.specifiers[cursor].type === Syntax.ImportNamespaceSpecifier) {
73715
- result = join30(result, [
73786
+ result = join31(result, [
73716
73787
  space,
73717
73788
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
73718
73789
  ]);
@@ -73741,7 +73812,7 @@ var require_escodegen = __commonJS({
73741
73812
  }
73742
73813
  }
73743
73814
  }
73744
- result = join30(result, [
73815
+ result = join31(result, [
73745
73816
  "from" + space,
73746
73817
  // ModuleSpecifier
73747
73818
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -73795,7 +73866,7 @@ var require_escodegen = __commonJS({
73795
73866
  return result;
73796
73867
  },
73797
73868
  ThrowStatement: function(stmt, flags) {
73798
- return [join30(
73869
+ return [join31(
73799
73870
  "throw",
73800
73871
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
73801
73872
  ), this.semicolon(flags)];
@@ -73806,7 +73877,7 @@ var require_escodegen = __commonJS({
73806
73877
  result = this.maybeBlockSuffix(stmt.block, result);
73807
73878
  if (stmt.handlers) {
73808
73879
  for (i = 0, iz = stmt.handlers.length; i < iz; ++i) {
73809
- result = join30(result, this.generateStatement(stmt.handlers[i], S_TFFF));
73880
+ result = join31(result, this.generateStatement(stmt.handlers[i], S_TFFF));
73810
73881
  if (stmt.finalizer || i + 1 !== iz) {
73811
73882
  result = this.maybeBlockSuffix(stmt.handlers[i].body, result);
73812
73883
  }
@@ -73814,7 +73885,7 @@ var require_escodegen = __commonJS({
73814
73885
  } else {
73815
73886
  guardedHandlers = stmt.guardedHandlers || [];
73816
73887
  for (i = 0, iz = guardedHandlers.length; i < iz; ++i) {
73817
- result = join30(result, this.generateStatement(guardedHandlers[i], S_TFFF));
73888
+ result = join31(result, this.generateStatement(guardedHandlers[i], S_TFFF));
73818
73889
  if (stmt.finalizer || i + 1 !== iz) {
73819
73890
  result = this.maybeBlockSuffix(guardedHandlers[i].body, result);
73820
73891
  }
@@ -73822,13 +73893,13 @@ var require_escodegen = __commonJS({
73822
73893
  if (stmt.handler) {
73823
73894
  if (Array.isArray(stmt.handler)) {
73824
73895
  for (i = 0, iz = stmt.handler.length; i < iz; ++i) {
73825
- result = join30(result, this.generateStatement(stmt.handler[i], S_TFFF));
73896
+ result = join31(result, this.generateStatement(stmt.handler[i], S_TFFF));
73826
73897
  if (stmt.finalizer || i + 1 !== iz) {
73827
73898
  result = this.maybeBlockSuffix(stmt.handler[i].body, result);
73828
73899
  }
73829
73900
  }
73830
73901
  } else {
73831
- result = join30(result, this.generateStatement(stmt.handler, S_TFFF));
73902
+ result = join31(result, this.generateStatement(stmt.handler, S_TFFF));
73832
73903
  if (stmt.finalizer) {
73833
73904
  result = this.maybeBlockSuffix(stmt.handler.body, result);
73834
73905
  }
@@ -73836,7 +73907,7 @@ var require_escodegen = __commonJS({
73836
73907
  }
73837
73908
  }
73838
73909
  if (stmt.finalizer) {
73839
- result = join30(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
73910
+ result = join31(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
73840
73911
  }
73841
73912
  return result;
73842
73913
  },
@@ -73870,7 +73941,7 @@ var require_escodegen = __commonJS({
73870
73941
  withIndent(function() {
73871
73942
  if (stmt.test) {
73872
73943
  result = [
73873
- join30("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
73944
+ join31("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
73874
73945
  ":"
73875
73946
  ];
73876
73947
  } else {
@@ -73918,9 +73989,9 @@ var require_escodegen = __commonJS({
73918
73989
  result.push(this.maybeBlock(stmt.consequent, S_TFFF));
73919
73990
  result = this.maybeBlockSuffix(stmt.consequent, result);
73920
73991
  if (stmt.alternate.type === Syntax.IfStatement) {
73921
- result = join30(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
73992
+ result = join31(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
73922
73993
  } else {
73923
- result = join30(result, join30("else", this.maybeBlock(stmt.alternate, bodyFlags)));
73994
+ result = join31(result, join31("else", this.maybeBlock(stmt.alternate, bodyFlags)));
73924
73995
  }
73925
73996
  } else {
73926
73997
  result.push(this.maybeBlock(stmt.consequent, bodyFlags));
@@ -74021,7 +74092,7 @@ var require_escodegen = __commonJS({
74021
74092
  },
74022
74093
  ReturnStatement: function(stmt, flags) {
74023
74094
  if (stmt.argument) {
74024
- return [join30(
74095
+ return [join31(
74025
74096
  "return",
74026
74097
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
74027
74098
  ), this.semicolon(flags)];
@@ -74110,14 +74181,14 @@ var require_escodegen = __commonJS({
74110
74181
  if (leftSource.charCodeAt(leftSource.length - 1) === 47 && esutils.code.isIdentifierPartES5(expr.operator.charCodeAt(0))) {
74111
74182
  result = [fragment, noEmptySpace(), expr.operator];
74112
74183
  } else {
74113
- result = join30(fragment, expr.operator);
74184
+ result = join31(fragment, expr.operator);
74114
74185
  }
74115
74186
  fragment = this.generateExpression(expr.right, rightPrecedence, flags);
74116
74187
  if (expr.operator === "/" && fragment.toString().charAt(0) === "/" || expr.operator.slice(-1) === "<" && fragment.toString().slice(0, 3) === "!--") {
74117
74188
  result.push(noEmptySpace());
74118
74189
  result.push(fragment);
74119
74190
  } else {
74120
- result = join30(result, fragment);
74191
+ result = join31(result, fragment);
74121
74192
  }
74122
74193
  if (expr.operator === "in" && !(flags & F_ALLOW_IN)) {
74123
74194
  return ["(", result, ")"];
@@ -74157,7 +74228,7 @@ var require_escodegen = __commonJS({
74157
74228
  var result, length, i, iz, itemFlags;
74158
74229
  length = expr["arguments"].length;
74159
74230
  itemFlags = flags & F_ALLOW_UNPARATH_NEW && !parentheses && length === 0 ? E_TFT : E_TFF;
74160
- result = join30(
74231
+ result = join31(
74161
74232
  "new",
74162
74233
  this.generateExpression(expr.callee, Precedence.New, itemFlags)
74163
74234
  );
@@ -74207,11 +74278,11 @@ var require_escodegen = __commonJS({
74207
74278
  var result, fragment, rightCharCode, leftSource, leftCharCode;
74208
74279
  fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT);
74209
74280
  if (space === "") {
74210
- result = join30(expr.operator, fragment);
74281
+ result = join31(expr.operator, fragment);
74211
74282
  } else {
74212
74283
  result = [expr.operator];
74213
74284
  if (expr.operator.length > 2) {
74214
- result = join30(result, fragment);
74285
+ result = join31(result, fragment);
74215
74286
  } else {
74216
74287
  leftSource = toSourceNodeWhenNeeded(result).toString();
74217
74288
  leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
@@ -74234,7 +74305,7 @@ var require_escodegen = __commonJS({
74234
74305
  result = "yield";
74235
74306
  }
74236
74307
  if (expr.argument) {
74237
- result = join30(
74308
+ result = join31(
74238
74309
  result,
74239
74310
  this.generateExpression(expr.argument, Precedence.Yield, E_TTT)
74240
74311
  );
@@ -74242,7 +74313,7 @@ var require_escodegen = __commonJS({
74242
74313
  return parenthesize(result, Precedence.Yield, precedence);
74243
74314
  },
74244
74315
  AwaitExpression: function(expr, precedence, flags) {
74245
- var result = join30(
74316
+ var result = join31(
74246
74317
  expr.all ? "await*" : "await",
74247
74318
  this.generateExpression(expr.argument, Precedence.Await, E_TTT)
74248
74319
  );
@@ -74325,11 +74396,11 @@ var require_escodegen = __commonJS({
74325
74396
  var result, fragment;
74326
74397
  result = ["class"];
74327
74398
  if (expr.id) {
74328
- result = join30(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
74399
+ result = join31(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
74329
74400
  }
74330
74401
  if (expr.superClass) {
74331
- fragment = join30("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
74332
- result = join30(result, fragment);
74402
+ fragment = join31("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
74403
+ result = join31(result, fragment);
74333
74404
  }
74334
74405
  result.push(space);
74335
74406
  result.push(this.generateStatement(expr.body, S_TFFT));
@@ -74344,7 +74415,7 @@ var require_escodegen = __commonJS({
74344
74415
  }
74345
74416
  if (expr.kind === "get" || expr.kind === "set") {
74346
74417
  fragment = [
74347
- join30(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
74418
+ join31(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
74348
74419
  this.generateFunctionBody(expr.value)
74349
74420
  ];
74350
74421
  } else {
@@ -74354,7 +74425,7 @@ var require_escodegen = __commonJS({
74354
74425
  this.generateFunctionBody(expr.value)
74355
74426
  ];
74356
74427
  }
74357
- return join30(result, fragment);
74428
+ return join31(result, fragment);
74358
74429
  },
74359
74430
  Property: function(expr, precedence, flags) {
74360
74431
  if (expr.kind === "get" || expr.kind === "set") {
@@ -74549,7 +74620,7 @@ var require_escodegen = __commonJS({
74549
74620
  for (i = 0, iz = expr.blocks.length; i < iz; ++i) {
74550
74621
  fragment = that.generateExpression(expr.blocks[i], Precedence.Sequence, E_TTT);
74551
74622
  if (i > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) {
74552
- result = join30(result, fragment);
74623
+ result = join31(result, fragment);
74553
74624
  } else {
74554
74625
  result.push(fragment);
74555
74626
  }
@@ -74557,13 +74628,13 @@ var require_escodegen = __commonJS({
74557
74628
  });
74558
74629
  }
74559
74630
  if (expr.filter) {
74560
- result = join30(result, "if" + space);
74631
+ result = join31(result, "if" + space);
74561
74632
  fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT);
74562
- result = join30(result, ["(", fragment, ")"]);
74633
+ result = join31(result, ["(", fragment, ")"]);
74563
74634
  }
74564
74635
  if (!extra.moz.comprehensionExpressionStartsWithAssignment) {
74565
74636
  fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT);
74566
- result = join30(result, fragment);
74637
+ result = join31(result, fragment);
74567
74638
  }
74568
74639
  result.push(expr.type === Syntax.GeneratorExpression ? ")" : "]");
74569
74640
  return result;
@@ -74579,8 +74650,8 @@ var require_escodegen = __commonJS({
74579
74650
  } else {
74580
74651
  fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT);
74581
74652
  }
74582
- fragment = join30(fragment, expr.of ? "of" : "in");
74583
- fragment = join30(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
74653
+ fragment = join31(fragment, expr.of ? "of" : "in");
74654
+ fragment = join31(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
74584
74655
  return ["for" + space + "(", fragment, ")"];
74585
74656
  },
74586
74657
  SpreadElement: function(expr, precedence, flags) {
@@ -102139,8 +102210,8 @@ function findChromePath() {
102139
102210
  for (const p8 of paths) {
102140
102211
  if (p8) {
102141
102212
  try {
102142
- const { existsSync: existsSync26 } = __require("node:fs");
102143
- if (existsSync26(p8)) return p8;
102213
+ const { existsSync: existsSync28 } = __require("node:fs");
102214
+ if (existsSync28(p8)) return p8;
102144
102215
  } catch {
102145
102216
  }
102146
102217
  }
@@ -102155,8 +102226,8 @@ function findChromePath() {
102155
102226
  ];
102156
102227
  for (const p8 of paths) {
102157
102228
  try {
102158
- const { existsSync: existsSync26 } = __require("node:fs");
102159
- if (existsSync26(p8)) return p8;
102229
+ const { existsSync: existsSync28 } = __require("node:fs");
102230
+ if (existsSync28(p8)) return p8;
102160
102231
  } catch {
102161
102232
  }
102162
102233
  }
@@ -102171,8 +102242,8 @@ function findChromePath() {
102171
102242
  ];
102172
102243
  for (const p8 of linuxPaths) {
102173
102244
  try {
102174
- const { existsSync: existsSync26 } = __require("node:fs");
102175
- if (existsSync26(p8)) return p8;
102245
+ const { existsSync: existsSync28 } = __require("node:fs");
102246
+ if (existsSync28(p8)) return p8;
102176
102247
  } catch {
102177
102248
  }
102178
102249
  }
@@ -103436,11 +103507,305 @@ var init_meta_tool_factory = __esm({
103436
103507
  }
103437
103508
  });
103438
103509
 
103510
+ // ../agent/src/memory/layered-memory.ts
103511
+ var LayeredMemory, EpisodicLayer, SemanticLayer;
103512
+ var init_layered_memory = __esm({
103513
+ "../agent/src/memory/layered-memory.ts"() {
103514
+ "use strict";
103515
+ LayeredMemory = class {
103516
+ constructor(store) {
103517
+ this.store = store;
103518
+ this.layers = [
103519
+ new EpisodicLayer(store),
103520
+ new SemanticLayer(store)
103521
+ ];
103522
+ }
103523
+ layers = [];
103524
+ /** Query all layers and merge results by relevance */
103525
+ async query(input, limit = 10) {
103526
+ const allResults = [];
103527
+ for (const layer of this.layers) {
103528
+ try {
103529
+ const results = await layer.query(input, limit);
103530
+ allResults.push(...results);
103531
+ } catch {
103532
+ }
103533
+ }
103534
+ return allResults.sort((a, b) => b.relevance - a.relevance).slice(0, limit);
103535
+ }
103536
+ /** Get stats about each memory layer */
103537
+ stats() {
103538
+ return [
103539
+ { name: "Working", type: "working", count: 0 },
103540
+ // managed externally
103541
+ { name: "Episodic", type: "episodic", count: this.getEpisodicCount() },
103542
+ { name: "Semantic", type: "semantic", count: this.getSemanticCount() }
103543
+ ];
103544
+ }
103545
+ getEpisodicCount() {
103546
+ try {
103547
+ const missions = this.store.listMissions("default");
103548
+ return missions.length;
103549
+ } catch {
103550
+ return 0;
103551
+ }
103552
+ }
103553
+ getSemanticCount() {
103554
+ try {
103555
+ const stats = this.store.getKgStats();
103556
+ return stats.totalEntities + stats.totalFacts;
103557
+ } catch {
103558
+ return 0;
103559
+ }
103560
+ }
103561
+ };
103562
+ EpisodicLayer = class {
103563
+ constructor(store) {
103564
+ this.store = store;
103565
+ }
103566
+ name = "Episodic";
103567
+ type = "episodic";
103568
+ async query(input, limit = 5) {
103569
+ try {
103570
+ const missions = this.store.searchMissions(input, { limit });
103571
+ return missions.map((m) => ({
103572
+ content: `Mission "${m.title}" (${m.type}, ${m.status}): ${(m.objective ?? "").substring(0, 200)}. ${m.findingCount} findings, score: ${m.qualityScore ?? "N/A"}`,
103573
+ source: `mission:${m.id}`,
103574
+ layer: "episodic",
103575
+ relevance: 0.6,
103576
+ timestamp: m.createdAt
103577
+ }));
103578
+ } catch {
103579
+ return [];
103580
+ }
103581
+ }
103582
+ };
103583
+ SemanticLayer = class {
103584
+ constructor(store) {
103585
+ this.store = store;
103586
+ }
103587
+ name = "Semantic";
103588
+ type = "semantic";
103589
+ async query(input, limit = 5) {
103590
+ const results = [];
103591
+ try {
103592
+ const entities = this.store.searchKgEntities(input);
103593
+ for (const e of entities.slice(0, limit)) {
103594
+ results.push({
103595
+ content: `Entity: ${e.canonicalName} (${e.type})${e.aliases?.length ? ` \u2014 aliases: ${e.aliases.join(", ")}` : ""}`,
103596
+ source: `kg:entity:${e.id}`,
103597
+ layer: "semantic",
103598
+ relevance: 0.7
103599
+ });
103600
+ }
103601
+ const facts = this.store.getKgFacts(input.substring(0, 50));
103602
+ for (const f of facts.slice(0, limit)) {
103603
+ results.push({
103604
+ content: `Fact: ${f.predicate} \u2192 ${f.object.substring(0, 150)} (confidence: ${f.confidence})`,
103605
+ source: `kg:fact:${f.sourceMissionId ?? "unknown"}`,
103606
+ layer: "semantic",
103607
+ relevance: 0.8 * (f.confidence || 0.5)
103608
+ });
103609
+ }
103610
+ } catch {
103611
+ }
103612
+ return results;
103613
+ }
103614
+ };
103615
+ }
103616
+ });
103617
+
103618
+ // ../agent/src/integrations/obsidian.ts
103619
+ import { existsSync as existsSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
103620
+ import { join as join8, basename as basename3, relative as relative3 } from "node:path";
103621
+ var ObsidianIntegration;
103622
+ var init_obsidian = __esm({
103623
+ "../agent/src/integrations/obsidian.ts"() {
103624
+ "use strict";
103625
+ ObsidianIntegration = class {
103626
+ constructor(config2) {
103627
+ this.config = config2;
103628
+ this.nestorDir = join8(config2.vaultPath, config2.nestorFolder || "Nestor");
103629
+ if (!existsSync5(this.nestorDir)) {
103630
+ mkdirSync3(this.nestorDir, { recursive: true });
103631
+ }
103632
+ }
103633
+ nestorDir;
103634
+ /** Check if vault exists and is accessible */
103635
+ isAvailable() {
103636
+ return existsSync5(this.config.vaultPath);
103637
+ }
103638
+ /** Write a note to the vault */
103639
+ writeNote(opts) {
103640
+ const folder5 = opts.folder ? join8(this.nestorDir, opts.folder) : this.nestorDir;
103641
+ if (!existsSync5(folder5)) mkdirSync3(folder5, { recursive: true });
103642
+ const filename = this.sanitizeFilename(opts.title) + ".md";
103643
+ const filepath = join8(folder5, filename);
103644
+ const fm = {
103645
+ created: (/* @__PURE__ */ new Date()).toISOString(),
103646
+ source: "nestor",
103647
+ tags: [...this.config.defaultTags || [], ...opts.tags || []],
103648
+ ...opts.frontmatter
103649
+ };
103650
+ const frontmatter = `---
103651
+ ${Object.entries(fm).map(
103652
+ ([k, v]) => `${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`
103653
+ ).join("\n")}
103654
+ ---
103655
+
103656
+ `;
103657
+ writeFileSync3(filepath, frontmatter + opts.content);
103658
+ return filepath;
103659
+ }
103660
+ /** Read a note from the vault */
103661
+ readNote(path30) {
103662
+ const fullPath = path30.startsWith("/") || path30.includes(":") ? path30 : join8(this.nestorDir, path30);
103663
+ if (!existsSync5(fullPath)) return null;
103664
+ const raw = readFileSync8(fullPath, "utf-8");
103665
+ const fmMatch = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
103666
+ if (fmMatch) {
103667
+ const frontmatter = {};
103668
+ for (const line of fmMatch[1].split("\n")) {
103669
+ const [key, ...rest] = line.split(": ");
103670
+ if (key) frontmatter[key.trim()] = rest.join(": ").trim();
103671
+ }
103672
+ return { frontmatter, content: fmMatch[2] };
103673
+ }
103674
+ return { frontmatter: {}, content: raw };
103675
+ }
103676
+ /** Search notes in the vault */
103677
+ searchNotes(query, limit = 10) {
103678
+ const results = [];
103679
+ const q = query.toLowerCase();
103680
+ this.walkDir(this.nestorDir, (filepath) => {
103681
+ if (!filepath.endsWith(".md") || results.length >= limit) return;
103682
+ try {
103683
+ const content = readFileSync8(filepath, "utf-8");
103684
+ if (content.toLowerCase().includes(q)) {
103685
+ const relPath = relative3(this.nestorDir, filepath);
103686
+ const title = basename3(filepath, ".md");
103687
+ const idx = content.toLowerCase().indexOf(q);
103688
+ const snippet = content.substring(Math.max(0, idx - 50), idx + 100).replace(/\n/g, " ");
103689
+ results.push({ path: relPath, title, snippet });
103690
+ }
103691
+ } catch {
103692
+ }
103693
+ });
103694
+ return results;
103695
+ }
103696
+ /** List all notes in a folder */
103697
+ listNotes(folder5) {
103698
+ const dir = folder5 ? join8(this.nestorDir, folder5) : this.nestorDir;
103699
+ if (!existsSync5(dir)) return [];
103700
+ const notes = [];
103701
+ this.walkDir(dir, (filepath) => {
103702
+ if (!filepath.endsWith(".md")) return;
103703
+ const stat = statSync3(filepath);
103704
+ notes.push({
103705
+ path: relative3(this.nestorDir, filepath),
103706
+ title: basename3(filepath, ".md"),
103707
+ modified: stat.mtime
103708
+ });
103709
+ });
103710
+ return notes.sort((a, b) => b.modified.getTime() - a.modified.getTime());
103711
+ }
103712
+ /** Write a mission report to the vault */
103713
+ writeMissionReport(mission) {
103714
+ return this.writeNote({
103715
+ title: mission.title,
103716
+ content: mission.report,
103717
+ folder: `missions/${mission.type}`,
103718
+ tags: ["mission", mission.type],
103719
+ frontmatter: {
103720
+ type: mission.type,
103721
+ quality: mission.evaluation?.overall
103722
+ }
103723
+ });
103724
+ }
103725
+ sanitizeFilename(name) {
103726
+ return name.replace(/[<>:"/\\|?*]/g, "-").substring(0, 100);
103727
+ }
103728
+ walkDir(dir, callback) {
103729
+ if (!existsSync5(dir)) return;
103730
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
103731
+ const fullPath = join8(dir, entry.name);
103732
+ if (entry.isDirectory()) {
103733
+ this.walkDir(fullPath, callback);
103734
+ } else {
103735
+ callback(fullPath);
103736
+ }
103737
+ }
103738
+ }
103739
+ };
103740
+ }
103741
+ });
103742
+
103743
+ // ../agent/src/integrations/n8n.ts
103744
+ var N8nIntegration;
103745
+ var init_n8n = __esm({
103746
+ "../agent/src/integrations/n8n.ts"() {
103747
+ "use strict";
103748
+ N8nIntegration = class {
103749
+ constructor(config2) {
103750
+ this.config = config2;
103751
+ }
103752
+ /** Check if n8n is accessible */
103753
+ async isAvailable() {
103754
+ try {
103755
+ const res = await fetch(`${this.config.baseUrl}/api/v1/workflows`, {
103756
+ headers: this.headers(),
103757
+ signal: AbortSignal.timeout(5e3)
103758
+ });
103759
+ return res.ok;
103760
+ } catch {
103761
+ return false;
103762
+ }
103763
+ }
103764
+ /** Trigger a webhook workflow */
103765
+ async triggerWebhook(webhookPath, data) {
103766
+ const url = `${this.config.baseUrl}/webhook/${webhookPath}`;
103767
+ const res = await fetch(url, {
103768
+ method: "POST",
103769
+ headers: { "Content-Type": "application/json" },
103770
+ body: JSON.stringify(data),
103771
+ signal: AbortSignal.timeout(3e4)
103772
+ });
103773
+ return res.json();
103774
+ }
103775
+ /** List workflows */
103776
+ async listWorkflows() {
103777
+ const res = await fetch(`${this.config.baseUrl}/api/v1/workflows`, {
103778
+ headers: this.headers(),
103779
+ signal: AbortSignal.timeout(1e4)
103780
+ });
103781
+ if (!res.ok) return [];
103782
+ const data = await res.json();
103783
+ return data.data || [];
103784
+ }
103785
+ /** Execute a workflow by ID */
103786
+ async executeWorkflow(id, data) {
103787
+ const res = await fetch(`${this.config.baseUrl}/api/v1/workflows/${id}/execute`, {
103788
+ method: "POST",
103789
+ headers: this.headers(),
103790
+ body: data ? JSON.stringify(data) : void 0,
103791
+ signal: AbortSignal.timeout(6e4)
103792
+ });
103793
+ return res.json();
103794
+ }
103795
+ headers() {
103796
+ const h = { "Content-Type": "application/json" };
103797
+ if (this.config.apiKey) h["X-N8N-API-KEY"] = this.config.apiKey;
103798
+ return h;
103799
+ }
103800
+ };
103801
+ }
103802
+ });
103803
+
103439
103804
  // ../agent/src/tools/system-tools.ts
103440
103805
  import { z as z28 } from "zod";
103441
103806
  import { randomUUID as randomUUID23 } from "node:crypto";
103442
- import { readFileSync as readFileSync8 } from "node:fs";
103443
- import { join as join8 } from "node:path";
103807
+ import { readFileSync as readFileSync9 } from "node:fs";
103808
+ import { join as join9 } from "node:path";
103444
103809
  import { homedir as homedir3 } from "node:os";
103445
103810
  function registerSystemTools(registry, store, defaultTenantId = "default") {
103446
103811
  const tid = defaultTenantId;
@@ -103680,9 +104045,9 @@ Direct sub-agent execution from chat is not yet supported. Use the Studio UI or
103680
104045
  description: "Read the current Nestor configuration from ~/.nestor/nestor.config.json.",
103681
104046
  inputSchema: emptySchema,
103682
104047
  handler: async (_input, _ctx) => {
103683
- const configPath = join8(homedir3(), ".nestor", "nestor.config.json");
104048
+ const configPath = join9(homedir3(), ".nestor", "nestor.config.json");
103684
104049
  try {
103685
- const config2 = readFileSync8(configPath, "utf-8");
104050
+ const config2 = readFileSync9(configPath, "utf-8");
103686
104051
  return { output: config2, isError: false };
103687
104052
  } catch {
103688
104053
  return { output: "No config file found at ~/.nestor/nestor.config.json", isError: false };
@@ -103827,11 +104192,363 @@ ${summary}`, isError: false };
103827
104192
  }
103828
104193
  }
103829
104194
  });
104195
+ const searchMissionsSchema = z28.object({
104196
+ query: z28.string().describe("Search query (searches titles, objectives, findings)"),
104197
+ type: z28.string().optional().describe("Filter by mission type (osint, research, code, audit, creation, analysis)"),
104198
+ limit: z28.number().optional().describe("Max results (default: 5)")
104199
+ });
104200
+ registry.register({
104201
+ name: "nestor_search_missions",
104202
+ description: "Search past missions by topic, type, or content. Returns matching missions with their findings count and quality scores. Use this to find relevant past work before starting a new mission.",
104203
+ inputSchema: searchMissionsSchema,
104204
+ handler: async (input, _ctx) => {
104205
+ const args2 = input;
104206
+ try {
104207
+ const results = store.searchMissions(args2.query, {
104208
+ type: args2.type,
104209
+ limit: args2.limit || 5
104210
+ });
104211
+ if (results.length === 0) {
104212
+ return { output: `No missions found matching "${args2.query}".`, isError: false };
104213
+ }
104214
+ const summary = results.map((m) => {
104215
+ const quality = m.qualityScore !== null ? ` \u2014 quality: ${(m.qualityScore * 100).toFixed(0)}%` : "";
104216
+ return `\u2022 [${m.status}] ${m.title} (${m.type}) \u2014 ${m.findingCount} findings, $${m.spentUsd.toFixed(4)}${quality}
104217
+ ID: ${m.id}
104218
+ Objective: ${m.objective.substring(0, 200)}`;
104219
+ }).join("\n\n");
104220
+ return {
104221
+ output: `Found ${results.length} mission(s) matching "${args2.query}":
104222
+
104223
+ ${summary}`,
104224
+ isError: false
104225
+ };
104226
+ } catch (err) {
104227
+ const msg = err instanceof Error ? err.message : String(err);
104228
+ return { output: `Error searching missions: ${msg}`, isError: true };
104229
+ }
104230
+ }
104231
+ });
104232
+ registry.register({
104233
+ name: "nestor_get_mission_report",
104234
+ description: "Get the full report of a specific past mission by ID. Returns the markdown report, evaluation scores, and key findings.",
104235
+ inputSchema: missionIdSchema,
104236
+ handler: async (input, _ctx) => {
104237
+ const args2 = input;
104238
+ try {
104239
+ const mission = store.getMission(args2.missionId);
104240
+ if (!mission) {
104241
+ return { output: `Mission "${args2.missionId}" not found.`, isError: true };
104242
+ }
104243
+ let output = `# Mission: ${mission.title}
104244
+ `;
104245
+ output += `Type: ${mission.type} | Status: ${mission.status}
104246
+ `;
104247
+ output += `Objective: ${mission.objective}
104248
+ `;
104249
+ output += `Cost: $${mission.spentUsd.toFixed(4)} | Findings: ${Array.isArray(mission.findings) ? mission.findings.length : 0}
104250
+ `;
104251
+ if (mission.evaluation && typeof mission.evaluation === "object") {
104252
+ const eval_ = mission.evaluation;
104253
+ output += `
104254
+ ## Evaluation
104255
+ `;
104256
+ if (typeof eval_.overall === "number") output += `Overall: ${(eval_.overall * 100).toFixed(0)}%
104257
+ `;
104258
+ if (typeof eval_.completeness === "number") output += `Completeness: ${(eval_.completeness * 100).toFixed(0)}%
104259
+ `;
104260
+ if (typeof eval_.accuracy === "number") output += `Accuracy: ${(eval_.accuracy * 100).toFixed(0)}%
104261
+ `;
104262
+ if (typeof eval_.depth === "number") output += `Depth: ${(eval_.depth * 100).toFixed(0)}%
104263
+ `;
104264
+ if (typeof eval_.relevance === "number") output += `Relevance: ${(eval_.relevance * 100).toFixed(0)}%
104265
+ `;
104266
+ }
104267
+ if (mission.report) {
104268
+ output += `
104269
+ ## Report
104270
+ ${mission.report}`;
104271
+ } else {
104272
+ output += `
104273
+ ## Findings Summary
104274
+ `;
104275
+ const findings = Array.isArray(mission.findings) ? mission.findings : [];
104276
+ for (const f of findings.slice(0, 20)) {
104277
+ const finding = f;
104278
+ output += `- [${finding.type || "finding"}] ${finding.title || "Untitled"}: ${String(finding.content || "").substring(0, 300)}
104279
+ `;
104280
+ }
104281
+ if (findings.length > 20) {
104282
+ output += `
104283
+ ... and ${findings.length - 20} more findings.`;
104284
+ }
104285
+ }
104286
+ return { output, isError: false };
104287
+ } catch (err) {
104288
+ const msg = err instanceof Error ? err.message : String(err);
104289
+ return { output: `Error getting mission report: ${msg}`, isError: true };
104290
+ }
104291
+ }
104292
+ });
104293
+ registry.register({
104294
+ name: "nestor_query_memory",
104295
+ description: "Query all memory layers (episodic: past missions, semantic: knowledge graph) for relevant information. Returns merged results sorted by relevance.",
104296
+ inputSchema: queryMemorySchema,
104297
+ handler: async (input, _ctx) => {
104298
+ const args2 = input;
104299
+ try {
104300
+ const memory = new LayeredMemory(store);
104301
+ const results = await memory.query(String(args2.query), 10);
104302
+ const layerStats = memory.stats();
104303
+ return {
104304
+ output: JSON.stringify({ results, layers: layerStats }, null, 2),
104305
+ isError: false
104306
+ };
104307
+ } catch (err) {
104308
+ const msg = err instanceof Error ? err.message : String(err);
104309
+ return { output: `Memory query failed: ${msg}`, isError: true };
104310
+ }
104311
+ }
104312
+ });
104313
+ function loadIntegrationConfig() {
104314
+ const configPath = join9(homedir3(), ".nestor", "nestor.config.json");
104315
+ try {
104316
+ const raw = readFileSync9(configPath, "utf-8");
104317
+ const config2 = JSON.parse(raw);
104318
+ return {
104319
+ obsidian: config2.obsidian,
104320
+ n8n: config2.n8n
104321
+ };
104322
+ } catch {
104323
+ return {};
104324
+ }
104325
+ }
104326
+ const obsidianWriteNoteSchema = z28.object({
104327
+ title: z28.string().describe("Note title (used as filename)"),
104328
+ content: z28.string().describe("Markdown content of the note"),
104329
+ folder: z28.string().optional().describe('Subfolder within Nestor dir (e.g., "research", "missions/osint")'),
104330
+ tags: z28.array(z28.string()).optional().describe("Tags to add to the note frontmatter")
104331
+ });
104332
+ const obsidianSearchSchema = z28.object({
104333
+ query: z28.string().describe("Search query (full-text search across note titles and content)"),
104334
+ limit: z28.number().optional().describe("Max results (default: 10)")
104335
+ });
104336
+ const obsidianReadNoteSchema = z28.object({
104337
+ path: z28.string().describe('Relative path within the Nestor folder (e.g., "research/AI Frameworks.md") or absolute path')
104338
+ });
104339
+ const obsidianListNotesSchema = z28.object({
104340
+ folder: z28.string().optional().describe('Subfolder to list (e.g., "missions"). Omit for root Nestor folder.')
104341
+ });
104342
+ registry.register({
104343
+ name: "obsidian_write_note",
104344
+ description: 'Write a note to the connected Obsidian vault. Creates a .md file with YAML frontmatter in the Nestor subfolder. Configure vault path in nestor.config.json under "obsidian.vaultPath".',
104345
+ inputSchema: obsidianWriteNoteSchema,
104346
+ handler: async (input, _ctx) => {
104347
+ const args2 = input;
104348
+ const cfg = loadIntegrationConfig();
104349
+ if (!cfg.obsidian?.vaultPath) {
104350
+ return {
104351
+ output: 'Obsidian integration not configured. Add "obsidian": { "vaultPath": "/path/to/vault" } to ~/.nestor/nestor.config.json',
104352
+ isError: true
104353
+ };
104354
+ }
104355
+ try {
104356
+ const obsidian = new ObsidianIntegration(cfg.obsidian);
104357
+ if (!obsidian.isAvailable()) {
104358
+ return { output: `Obsidian vault not found at: ${cfg.obsidian.vaultPath}`, isError: true };
104359
+ }
104360
+ const filepath = obsidian.writeNote({
104361
+ title: args2.title,
104362
+ content: args2.content,
104363
+ folder: args2.folder,
104364
+ tags: args2.tags
104365
+ });
104366
+ return { output: `Note written: ${filepath}`, isError: false };
104367
+ } catch (err) {
104368
+ const msg = err instanceof Error ? err.message : String(err);
104369
+ return { output: `Failed to write note: ${msg}`, isError: true };
104370
+ }
104371
+ }
104372
+ });
104373
+ registry.register({
104374
+ name: "obsidian_search",
104375
+ description: "Search notes in the connected Obsidian vault. Full-text search across all .md files in the Nestor subfolder.",
104376
+ inputSchema: obsidianSearchSchema,
104377
+ handler: async (input, _ctx) => {
104378
+ const args2 = input;
104379
+ const cfg = loadIntegrationConfig();
104380
+ if (!cfg.obsidian?.vaultPath) {
104381
+ return {
104382
+ output: 'Obsidian integration not configured. Add "obsidian": { "vaultPath": "/path/to/vault" } to ~/.nestor/nestor.config.json',
104383
+ isError: true
104384
+ };
104385
+ }
104386
+ try {
104387
+ const obsidian = new ObsidianIntegration(cfg.obsidian);
104388
+ if (!obsidian.isAvailable()) {
104389
+ return { output: `Obsidian vault not found at: ${cfg.obsidian.vaultPath}`, isError: true };
104390
+ }
104391
+ const results = obsidian.searchNotes(args2.query, args2.limit ?? 10);
104392
+ if (results.length === 0) {
104393
+ return { output: `No notes found matching "${args2.query}".`, isError: false };
104394
+ }
104395
+ return { output: JSON.stringify(results, null, 2), isError: false };
104396
+ } catch (err) {
104397
+ const msg = err instanceof Error ? err.message : String(err);
104398
+ return { output: `Search failed: ${msg}`, isError: true };
104399
+ }
104400
+ }
104401
+ });
104402
+ registry.register({
104403
+ name: "obsidian_read_note",
104404
+ description: "Read a note from the connected Obsidian vault. Returns frontmatter and content.",
104405
+ inputSchema: obsidianReadNoteSchema,
104406
+ handler: async (input, _ctx) => {
104407
+ const args2 = input;
104408
+ const cfg = loadIntegrationConfig();
104409
+ if (!cfg.obsidian?.vaultPath) {
104410
+ return {
104411
+ output: 'Obsidian integration not configured. Add "obsidian": { "vaultPath": "/path/to/vault" } to ~/.nestor/nestor.config.json',
104412
+ isError: true
104413
+ };
104414
+ }
104415
+ try {
104416
+ const obsidian = new ObsidianIntegration(cfg.obsidian);
104417
+ const note2 = obsidian.readNote(args2.path);
104418
+ if (!note2) {
104419
+ return { output: `Note not found: ${args2.path}`, isError: true };
104420
+ }
104421
+ return { output: JSON.stringify(note2, null, 2), isError: false };
104422
+ } catch (err) {
104423
+ const msg = err instanceof Error ? err.message : String(err);
104424
+ return { output: `Failed to read note: ${msg}`, isError: true };
104425
+ }
104426
+ }
104427
+ });
104428
+ registry.register({
104429
+ name: "obsidian_list_notes",
104430
+ description: "List all notes in the Obsidian vault Nestor folder, sorted by most recently modified.",
104431
+ inputSchema: obsidianListNotesSchema,
104432
+ handler: async (input, _ctx) => {
104433
+ const args2 = input;
104434
+ const cfg = loadIntegrationConfig();
104435
+ if (!cfg.obsidian?.vaultPath) {
104436
+ return {
104437
+ output: 'Obsidian integration not configured. Add "obsidian": { "vaultPath": "/path/to/vault" } to ~/.nestor/nestor.config.json',
104438
+ isError: true
104439
+ };
104440
+ }
104441
+ try {
104442
+ const obsidian = new ObsidianIntegration(cfg.obsidian);
104443
+ if (!obsidian.isAvailable()) {
104444
+ return { output: `Obsidian vault not found at: ${cfg.obsidian.vaultPath}`, isError: true };
104445
+ }
104446
+ const notes = obsidian.listNotes(args2.folder);
104447
+ if (notes.length === 0) {
104448
+ return { output: "No notes found.", isError: false };
104449
+ }
104450
+ const list = notes.map((n) => `- ${n.title} (${n.path}) \u2014 modified: ${n.modified.toISOString()}`).join("\n");
104451
+ return { output: `Found ${notes.length} note(s):
104452
+ ${list}`, isError: false };
104453
+ } catch (err) {
104454
+ const msg = err instanceof Error ? err.message : String(err);
104455
+ return { output: `Failed to list notes: ${msg}`, isError: true };
104456
+ }
104457
+ }
104458
+ });
104459
+ const n8nTriggerSchema = z28.object({
104460
+ webhook: z28.string().describe('Webhook path (e.g., "nestor-report" \u2014 will call {baseUrl}/webhook/nestor-report)'),
104461
+ data: z28.record(z28.unknown()).optional().describe("JSON data to send to the webhook")
104462
+ });
104463
+ const n8nExecuteWorkflowSchema = z28.object({
104464
+ workflowId: z28.string().describe("n8n workflow ID to execute"),
104465
+ data: z28.record(z28.unknown()).optional().describe("Input data for the workflow")
104466
+ });
104467
+ registry.register({
104468
+ name: "n8n_trigger",
104469
+ description: 'Trigger an n8n webhook workflow. Sends JSON data to a webhook endpoint. Configure n8n in nestor.config.json under "n8n": { "baseUrl": "http://localhost:5678", "apiKey": "..." }.',
104470
+ inputSchema: n8nTriggerSchema,
104471
+ handler: async (input, _ctx) => {
104472
+ const args2 = input;
104473
+ const cfg = loadIntegrationConfig();
104474
+ if (!cfg.n8n?.baseUrl) {
104475
+ return {
104476
+ output: 'n8n integration not configured. Add "n8n": { "baseUrl": "http://localhost:5678" } to ~/.nestor/nestor.config.json',
104477
+ isError: true
104478
+ };
104479
+ }
104480
+ try {
104481
+ const n8n = new N8nIntegration(cfg.n8n);
104482
+ const result = await n8n.triggerWebhook(args2.webhook, args2.data || {});
104483
+ return { output: JSON.stringify(result, null, 2), isError: false };
104484
+ } catch (err) {
104485
+ const msg = err instanceof Error ? err.message : String(err);
104486
+ return { output: `n8n webhook trigger failed: ${msg}`, isError: true };
104487
+ }
104488
+ }
104489
+ });
104490
+ registry.register({
104491
+ name: "n8n_list_workflows",
104492
+ description: "List available n8n workflows with their ID, name, and active status.",
104493
+ inputSchema: emptySchema,
104494
+ handler: async (_input, _ctx) => {
104495
+ const cfg = loadIntegrationConfig();
104496
+ if (!cfg.n8n?.baseUrl) {
104497
+ return {
104498
+ output: 'n8n integration not configured. Add "n8n": { "baseUrl": "http://localhost:5678" } to ~/.nestor/nestor.config.json',
104499
+ isError: true
104500
+ };
104501
+ }
104502
+ try {
104503
+ const n8n = new N8nIntegration(cfg.n8n);
104504
+ const available = await n8n.isAvailable();
104505
+ if (!available) {
104506
+ return { output: `n8n is not reachable at: ${cfg.n8n.baseUrl}`, isError: true };
104507
+ }
104508
+ const workflows = await n8n.listWorkflows();
104509
+ if (workflows.length === 0) {
104510
+ return { output: "No workflows found.", isError: false };
104511
+ }
104512
+ const list = workflows.map((w) => `- [${w.active ? "ACTIVE" : "inactive"}] ${w.name} (id: ${w.id})`).join("\n");
104513
+ return { output: `Found ${workflows.length} workflow(s):
104514
+ ${list}`, isError: false };
104515
+ } catch (err) {
104516
+ const msg = err instanceof Error ? err.message : String(err);
104517
+ return { output: `Failed to list n8n workflows: ${msg}`, isError: true };
104518
+ }
104519
+ }
104520
+ });
104521
+ registry.register({
104522
+ name: "n8n_execute_workflow",
104523
+ description: "Execute an n8n workflow by its ID. Optionally pass input data.",
104524
+ inputSchema: n8nExecuteWorkflowSchema,
104525
+ handler: async (input, _ctx) => {
104526
+ const args2 = input;
104527
+ const cfg = loadIntegrationConfig();
104528
+ if (!cfg.n8n?.baseUrl) {
104529
+ return {
104530
+ output: 'n8n integration not configured. Add "n8n": { "baseUrl": "http://localhost:5678" } to ~/.nestor/nestor.config.json',
104531
+ isError: true
104532
+ };
104533
+ }
104534
+ try {
104535
+ const n8n = new N8nIntegration(cfg.n8n);
104536
+ const result = await n8n.executeWorkflow(args2.workflowId, args2.data);
104537
+ return { output: JSON.stringify(result, null, 2), isError: false };
104538
+ } catch (err) {
104539
+ const msg = err instanceof Error ? err.message : String(err);
104540
+ return { output: `n8n workflow execution failed: ${msg}`, isError: true };
104541
+ }
104542
+ }
104543
+ });
103830
104544
  }
103831
- var emptySchema, createAgentSchema, runAgentSchema, deleteAgentSchema, createWorkflowSchema, searchMemorySchema, storeMemorySchema, createGuardrailSchema, createMissionSchema, missionIdSchema;
104545
+ var emptySchema, createAgentSchema, runAgentSchema, deleteAgentSchema, createWorkflowSchema, searchMemorySchema, storeMemorySchema, createGuardrailSchema, createMissionSchema, missionIdSchema, queryMemorySchema;
103832
104546
  var init_system_tools = __esm({
103833
104547
  "../agent/src/tools/system-tools.ts"() {
103834
104548
  "use strict";
104549
+ init_layered_memory();
104550
+ init_obsidian();
104551
+ init_n8n();
103835
104552
  emptySchema = z28.object({}).passthrough();
103836
104553
  createAgentSchema = z28.object({
103837
104554
  name: z28.string().describe("Agent name"),
@@ -103879,6 +104596,9 @@ var init_system_tools = __esm({
103879
104596
  missionIdSchema = z28.object({
103880
104597
  missionId: z28.string().describe("The mission ID")
103881
104598
  });
104599
+ queryMemorySchema = z28.object({
104600
+ query: z28.string().describe("Natural language query to search across all memory layers (episodic: past missions, semantic: knowledge graph)")
104601
+ });
103882
104602
  }
103883
104603
  });
103884
104604
 
@@ -103886,7 +104606,7 @@ var init_system_tools = __esm({
103886
104606
  import { z as z29 } from "zod";
103887
104607
  import { promises as fs14 } from "node:fs";
103888
104608
  import { execFile as execFile6 } from "node:child_process";
103889
- import { resolve as resolve10, join as join9 } from "node:path";
104609
+ import { resolve as resolve10, join as join10 } from "node:path";
103890
104610
  function buildSandboxConfig(context3) {
103891
104611
  const s = context3.sandbox;
103892
104612
  return {
@@ -104167,11 +104887,11 @@ async function fileListHandler(input, context3) {
104167
104887
  const items = await fs14.readdir(dir, { withFileTypes: true });
104168
104888
  for (const item of items) {
104169
104889
  if (item.name.startsWith(".") || item.name === "node_modules") continue;
104170
- const relativePath = join9(dir, item.name).replace(absolutePath, "").replace(/^[/\\]/, "");
104890
+ const relativePath = join10(dir, item.name).replace(absolutePath, "").replace(/^[/\\]/, "");
104171
104891
  if (item.isDirectory()) {
104172
104892
  entries.push(`${relativePath}/`);
104173
104893
  if (recursive) {
104174
- await listDir(join9(dir, item.name), depth + 1);
104894
+ await listDir(join10(dir, item.name), depth + 1);
104175
104895
  }
104176
104896
  } else {
104177
104897
  entries.push(relativePath);
@@ -104264,32 +104984,19 @@ async function webFetchHandler(input, context3) {
104264
104984
  return { output: `Fetch failed: ${message}`, isError: true };
104265
104985
  }
104266
104986
  }
104267
- async function webSearchHandler(input, context3) {
104268
- const { query, engine, maxResults } = input;
104269
- const max = maxResults || 5;
104270
- const searchEngine = engine || "duckduckgo";
104271
- let searchUrl;
104272
- switch (searchEngine) {
104273
- case "google":
104274
- searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&num=${max}`;
104275
- break;
104276
- case "bing":
104277
- searchUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${max}`;
104278
- break;
104279
- case "duckduckgo":
104280
- default:
104281
- searchUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
104282
- break;
104987
+ function pickUserAgent(attempt = 0) {
104988
+ return SEARCH_USER_AGENTS[attempt % SEARCH_USER_AGENTS.length];
104989
+ }
104990
+ async function fetchSearchHtml(searchUrl, context3, userAgent, timeoutMs = 15e3) {
104991
+ const controller = new AbortController();
104992
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
104993
+ if (context3.signal) {
104994
+ context3.signal.addEventListener("abort", () => controller.abort(), { once: true });
104283
104995
  }
104284
104996
  try {
104285
- const controller = new AbortController();
104286
- const timer2 = setTimeout(() => controller.abort(), 15e3);
104287
- if (context3.signal) {
104288
- context3.signal.addEventListener("abort", () => controller.abort(), { once: true });
104289
- }
104290
104997
  const resp = await fetch(searchUrl, {
104291
104998
  headers: {
104292
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
104999
+ "User-Agent": userAgent,
104293
105000
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
104294
105001
  "Accept-Language": "en-US,en;q=0.9,fr;q=0.8"
104295
105002
  },
@@ -104297,96 +105004,217 @@ async function webSearchHandler(input, context3) {
104297
105004
  redirect: "follow"
104298
105005
  });
104299
105006
  clearTimeout(timer2);
104300
- if (!resp.ok) {
104301
- return { output: `Search failed: HTTP ${resp.status}. Try a different search engine with engine:"google" or engine:"bing".`, isError: true };
104302
- }
104303
105007
  const html = await resp.text();
104304
- const results = [];
104305
- if (searchEngine === "duckduckgo") {
104306
- const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
104307
- const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
104308
- let linkMatch;
104309
- const links = [];
104310
- while ((linkMatch = linkRegex.exec(html)) !== null && links.length < max) {
104311
- let url = linkMatch[1];
104312
- const uddg = url.match(/uddg=([^&]*)/);
104313
- if (uddg) url = decodeURIComponent(uddg[1]);
104314
- const title = linkMatch[2].replace(/<[^>]*>/g, "").trim();
104315
- if (url.startsWith("http") && !url.includes("duckduckgo.com/y.js") && !url.includes("ad_domain=")) {
104316
- links.push({ url, title });
104317
- }
104318
- }
104319
- let snippetMatch;
104320
- const snippets = [];
104321
- while ((snippetMatch = snippetRegex.exec(html)) !== null) {
104322
- snippets.push(snippetMatch[1].replace(/<[^>]*>/g, "").trim());
104323
- }
104324
- for (let i = 0; i < links.length; i++) {
104325
- results.push({
104326
- title: links[i].title,
104327
- url: links[i].url,
104328
- snippet: snippets[i] || ""
104329
- });
104330
- }
104331
- } else if (searchEngine === "google") {
104332
- const resultBlockRegex = /<div class="[^"]*g[^"]*"[^>]*>[\s\S]*?<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<span[^>]*>([\s\S]*?)<\/span>)?/gi;
104333
- let blockMatch;
104334
- while ((blockMatch = resultBlockRegex.exec(html)) !== null && results.length < max) {
104335
- const url = blockMatch[1];
104336
- const title = blockMatch[2].replace(/<[^>]*>/g, "").trim();
104337
- const snippet = (blockMatch[3] || "").replace(/<[^>]*>/g, "").trim();
104338
- if (title.length > 3 && !url.includes("google.com")) {
104339
- results.push({ title, url, snippet });
104340
- }
104341
- }
104342
- } else if (searchEngine === "bing") {
104343
- const bingRegex = /<li class="b_algo"[^>]*>[\s\S]*?<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/gi;
104344
- let bingMatch;
104345
- while ((bingMatch = bingRegex.exec(html)) !== null && results.length < max) {
104346
- const url = bingMatch[1];
104347
- const title = bingMatch[2].replace(/<[^>]*>/g, "").trim();
104348
- const snippet = bingMatch[3].replace(/<[^>]*>/g, "").trim();
104349
- if (title.length > 3) {
104350
- results.push({ title, url, snippet });
104351
- }
105008
+ return { ok: resp.ok, status: resp.status, html };
105009
+ } catch {
105010
+ clearTimeout(timer2);
105011
+ return { ok: false, status: 0, html: "" };
105012
+ }
105013
+ }
105014
+ function parseDuckDuckGo(html, max) {
105015
+ const results = [];
105016
+ const linkRegex = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi;
105017
+ const snippetRegex = /<a[^>]*class="result__snippet"[^>]*>(.*?)<\/a>/gi;
105018
+ let linkMatch;
105019
+ const links = [];
105020
+ while ((linkMatch = linkRegex.exec(html)) !== null && links.length < max) {
105021
+ let url = linkMatch[1];
105022
+ const uddg = url.match(/uddg=([^&]*)/);
105023
+ if (uddg) url = decodeURIComponent(uddg[1]);
105024
+ const title = linkMatch[2].replace(/<[^>]*>/g, "").trim();
105025
+ if (url.startsWith("http") && !url.includes("duckduckgo.com/y.js") && !url.includes("ad_domain=")) {
105026
+ links.push({ url, title });
105027
+ }
105028
+ }
105029
+ let snippetMatch;
105030
+ const snippets = [];
105031
+ while ((snippetMatch = snippetRegex.exec(html)) !== null) {
105032
+ snippets.push(snippetMatch[1].replace(/<[^>]*>/g, "").trim());
105033
+ }
105034
+ for (let i = 0; i < links.length; i++) {
105035
+ results.push({
105036
+ title: links[i].title,
105037
+ url: links[i].url,
105038
+ snippet: snippets[i] || ""
105039
+ });
105040
+ }
105041
+ return results;
105042
+ }
105043
+ function parseGoogle(html, max) {
105044
+ const results = [];
105045
+ const googleRegex = /<a[^>]*href="(https?:\/\/[^"]*)"[^>]*><h3[^>]*>([^<]+)<\/h3>/gi;
105046
+ let gMatch;
105047
+ while ((gMatch = googleRegex.exec(html)) !== null && results.length < max) {
105048
+ const url = gMatch[1];
105049
+ const title = gMatch[2].trim();
105050
+ if (title.length > 3 && !url.includes("google.com")) {
105051
+ results.push({ title, url, snippet: "" });
105052
+ }
105053
+ }
105054
+ if (results.length > 0) {
105055
+ const snippetRegex = /<div[^>]*class="[^"]*VwiC3b[^"]*"[^>]*>([\s\S]*?)<\/div>/gi;
105056
+ let sMatch;
105057
+ let idx = 0;
105058
+ while ((sMatch = snippetRegex.exec(html)) !== null && idx < results.length) {
105059
+ const snippet = sMatch[1].replace(/<[^>]*>/g, "").trim();
105060
+ if (snippet.length > 10) {
105061
+ results[idx].snippet = snippet;
105062
+ idx++;
104352
105063
  }
104353
105064
  }
104354
- if (results.length === 0) {
104355
- const anyLinkRegex = /<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>(.*?)<\/a>/gi;
104356
- let anyMatch;
104357
- while ((anyMatch = anyLinkRegex.exec(html)) !== null && results.length < max) {
104358
- const url = anyMatch[1];
104359
- const title = anyMatch[2].replace(/<[^>]*>/g, "").trim();
104360
- if (title.length > 5 && !url.includes("duckduckgo.com") && !url.includes("google.com") && !url.includes("bing.com")) {
104361
- results.push({ title, url, snippet: "" });
104362
- }
105065
+ return results;
105066
+ }
105067
+ const resultBlockRegex = /<div class="[^"]*g[^"]*"[^>]*>[\s\S]*?<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?(?:<span[^>]*>([\s\S]*?)<\/span>)?/gi;
105068
+ let blockMatch;
105069
+ while ((blockMatch = resultBlockRegex.exec(html)) !== null && results.length < max) {
105070
+ const url = blockMatch[1];
105071
+ const title = blockMatch[2].replace(/<[^>]*>/g, "").trim();
105072
+ const snippet = (blockMatch[3] || "").replace(/<[^>]*>/g, "").trim();
105073
+ if (title.length > 3 && !url.includes("google.com")) {
105074
+ results.push({ title, url, snippet });
105075
+ }
105076
+ }
105077
+ return results;
105078
+ }
105079
+ function parseBing(html, max) {
105080
+ const results = [];
105081
+ const bingRegex = /<li[^>]*class="b_algo"[^>]*>[\s\S]*?<a[^>]*href="([^"]*)"[^>]*>([^<]+)<\/a>[\s\S]*?<p[^>]*>([^<]*)/gi;
105082
+ let bingMatch;
105083
+ while ((bingMatch = bingRegex.exec(html)) !== null && results.length < max) {
105084
+ const url = bingMatch[1];
105085
+ const title = bingMatch[2].replace(/<[^>]*>/g, "").trim();
105086
+ const snippet = bingMatch[3].replace(/<[^>]*>/g, "").trim();
105087
+ if (title.length > 3) {
105088
+ results.push({ title, url, snippet });
105089
+ }
105090
+ }
105091
+ if (results.length === 0) {
105092
+ const fallbackRegex = /<li class="b_algo"[^>]*>[\s\S]*?<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<p[^>]*>([\s\S]*?)<\/p>/gi;
105093
+ let fbMatch;
105094
+ while ((fbMatch = fallbackRegex.exec(html)) !== null && results.length < max) {
105095
+ const url = fbMatch[1];
105096
+ const title = fbMatch[2].replace(/<[^>]*>/g, "").trim();
105097
+ const snippet = fbMatch[3].replace(/<[^>]*>/g, "").trim();
105098
+ if (title.length > 3) {
105099
+ results.push({ title, url, snippet });
104363
105100
  }
104364
105101
  }
105102
+ }
105103
+ return results;
105104
+ }
105105
+ function parseAnyLinks(html, max) {
105106
+ const results = [];
105107
+ const anyLinkRegex = /<a[^>]*href="(https?:\/\/[^"]*)"[^>]*>(.*?)<\/a>/gi;
105108
+ let anyMatch;
105109
+ while ((anyMatch = anyLinkRegex.exec(html)) !== null && results.length < max) {
105110
+ const url = anyMatch[1];
105111
+ const title = anyMatch[2].replace(/<[^>]*>/g, "").trim();
105112
+ if (title.length > 5 && !url.includes("duckduckgo.com") && !url.includes("google.com") && !url.includes("bing.com")) {
105113
+ results.push({ title, url, snippet: "" });
105114
+ }
105115
+ }
105116
+ return results;
105117
+ }
105118
+ async function searchSingleEngine(engineName, query, max, context3) {
105119
+ let searchUrl;
105120
+ let parser2;
105121
+ switch (engineName) {
105122
+ case "google":
105123
+ searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}&num=${max}`;
105124
+ parser2 = parseGoogle;
105125
+ break;
105126
+ case "bing":
105127
+ searchUrl = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${max}`;
105128
+ parser2 = parseBing;
105129
+ break;
105130
+ case "duckduckgo":
105131
+ default:
105132
+ searchUrl = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
105133
+ parser2 = parseDuckDuckGo;
105134
+ break;
105135
+ }
105136
+ for (let attempt = 0; attempt < 2; attempt++) {
105137
+ const ua = pickUserAgent(attempt);
105138
+ const { ok, status, html } = await fetchSearchHtml(searchUrl, context3, ua);
105139
+ if (!ok) {
105140
+ if (attempt === 0) continue;
105141
+ return { results: [], engineUsed: engineName, error: `HTTP ${status}` };
105142
+ }
105143
+ let results = parser2(html, max);
104365
105144
  if (results.length === 0) {
105145
+ results = parseAnyLinks(html, max);
105146
+ }
105147
+ if (results.length > 0) {
105148
+ return { results, engineUsed: engineName };
105149
+ }
105150
+ }
105151
+ return { results: [], engineUsed: engineName, error: "0 results after UA rotation" };
105152
+ }
105153
+ async function webSearchHandler(input, context3) {
105154
+ const { query, engine, maxResults } = input;
105155
+ const max = maxResults || 5;
105156
+ const searchEngine = engine || "auto";
105157
+ if (searchEngine !== "auto") {
105158
+ try {
105159
+ const { results, error } = await searchSingleEngine(searchEngine, query, max, context3);
105160
+ if (results.length === 0) {
105161
+ return {
105162
+ output: `No results found for "${query}" on ${searchEngine}${error ? ` (${error})` : ""}. Try a different query or use engine:"auto" for multi-engine fallback. You can also try web_fetch directly on a relevant URL.`,
105163
+ isError: false
105164
+ };
105165
+ }
105166
+ const formatted = results.map(
105167
+ (r, i) => `${i + 1}. ${r.title}
105168
+ ${r.url}
105169
+ ${r.snippet}`
105170
+ ).join("\n\n");
104366
105171
  return {
104367
- output: `No results found for "${query}". Try a different query or use a different engine (engine:"google" or engine:"bing"). You can also try web_fetch directly on a relevant URL.`,
105172
+ output: `Search results for "${query}" (${searchEngine}):
105173
+
105174
+ ${formatted}
105175
+
105176
+ Use web_fetch on any URL above to get the full page content.`,
104368
105177
  isError: false
104369
105178
  };
105179
+ } catch (err) {
105180
+ if (err.name === "AbortError") {
105181
+ return { output: `Search timed out for "${query}". Try a simpler query or different engine.`, isError: true };
105182
+ }
105183
+ return { output: `Search error: ${err.message}. Try engine:"auto" for multi-engine fallback.`, isError: true };
104370
105184
  }
104371
- const formatted = results.map(
104372
- (r, i) => `${i + 1}. ${r.title}
105185
+ }
105186
+ const fallbackChain = ["duckduckgo", "google", "bing"];
105187
+ const errors = [];
105188
+ for (const eng of fallbackChain) {
105189
+ try {
105190
+ const { results, engineUsed, error } = await searchSingleEngine(eng, query, max, context3);
105191
+ if (results.length > 0) {
105192
+ const formatted = results.map(
105193
+ (r, i) => `${i + 1}. ${r.title}
104373
105194
  ${r.url}
104374
105195
  ${r.snippet}`
104375
- ).join("\n\n");
104376
- return {
104377
- output: `Search results for "${query}":
105196
+ ).join("\n\n");
105197
+ const fallbackNote = eng !== "duckduckgo" ? `
105198
+
105199
+ (Note: Results from ${engineUsed} \u2014 earlier engines returned 0 results)` : "";
105200
+ return {
105201
+ output: `Search results for "${query}" (${engineUsed}):
104378
105202
 
104379
105203
  ${formatted}
104380
105204
 
104381
- Use web_fetch on any URL above to get the full page content.`,
104382
- isError: false
104383
- };
104384
- } catch (err) {
104385
- if (err.name === "AbortError") {
104386
- return { output: `Search timed out for "${query}". Try a simpler query or different engine.`, isError: true };
105205
+ Use web_fetch on any URL above to get the full page content.${fallbackNote}`,
105206
+ isError: false
105207
+ };
105208
+ }
105209
+ errors.push(`${eng}: ${error || "0 results"}`);
105210
+ } catch (err) {
105211
+ errors.push(`${eng}: ${err.message || "unknown error"}`);
104387
105212
  }
104388
- return { output: `Search error: ${err.message}. Try a different search engine with engine:"google" or engine:"bing".`, isError: true };
104389
105213
  }
105214
+ return {
105215
+ output: `All search engines failed for "${query}". Tried: ${errors.join("; ")}. Try web_fetch directly on known URLs (e.g., Wikipedia, company sites, social profiles) instead of searching.`,
105216
+ isError: true
105217
+ };
104390
105218
  }
104391
105219
  async function messageSendHandler(input, context3) {
104392
105220
  const { to, subject, body } = input;
@@ -104525,7 +105353,7 @@ function registerBrowserTools(registry, config2) {
104525
105353
  }
104526
105354
  return manager;
104527
105355
  }
104528
- var shellExecSchema, sandboxCache, DANGEROUS_METACHARACTERS, shellExecTool, fileReadSchema, fileReadTool, fileWriteSchema, fileWriteTool, fileListSchema, fileListTool, webFetchSchema, webFetchTool, webSearchSchema, webSearchTool, messageSendSchema, messageSendTool, messageReadSchema, messageReadTool, handoffSchema, handoffTool, undoSchema;
105356
+ var shellExecSchema, sandboxCache, DANGEROUS_METACHARACTERS, shellExecTool, fileReadSchema, fileReadTool, fileWriteSchema, fileWriteTool, fileListSchema, fileListTool, webFetchSchema, webFetchTool, webSearchSchema, SEARCH_USER_AGENTS, webSearchTool, messageSendSchema, messageSendTool, messageReadSchema, messageReadTool, handoffSchema, handoffTool, undoSchema;
104529
105357
  var init_builtin = __esm({
104530
105358
  "../agent/src/tools/builtin.ts"() {
104531
105359
  "use strict";
@@ -104605,12 +105433,17 @@ var init_builtin = __esm({
104605
105433
  };
104606
105434
  webSearchSchema = z29.object({
104607
105435
  query: z29.string().describe("The search query"),
104608
- engine: z29.string().optional().describe("Search engine: google, duckduckgo, bing. Default: duckduckgo"),
105436
+ engine: z29.string().optional().describe('Search engine: google, duckduckgo, bing, or "auto" for multi-engine fallback (default). Default: auto'),
104609
105437
  maxResults: z29.number().optional().describe("Max results to return. Default: 5")
104610
105438
  });
105439
+ SEARCH_USER_AGENTS = [
105440
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
105441
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15",
105442
+ "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0"
105443
+ ];
104611
105444
  webSearchTool = {
104612
105445
  name: "web_search",
104613
- description: "Search the internet using DuckDuckGo, Google, or Bing. Returns titles, URLs, and snippets. Use web_fetch on result URLs to read full page content. Always try this FIRST when you need to find information online.",
105446
+ description: 'Search the internet using DuckDuckGo, Google, or Bing with automatic multi-engine fallback. Returns titles, URLs, and snippets. Use web_fetch on result URLs to read full page content. Default mode "auto" tries DuckDuckGo first, then Google, then Bing if previous engines return 0 results.',
104614
105447
  inputSchema: webSearchSchema,
104615
105448
  handler: webSearchHandler
104616
105449
  };
@@ -106534,8 +107367,8 @@ var init_store2 = __esm({
106534
107367
  });
106535
107368
 
106536
107369
  // ../agent/src/memory/files.ts
106537
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "node:fs";
106538
- import { join as join10, dirname as dirname5 } from "node:path";
107370
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "node:fs";
107371
+ import { join as join11, dirname as dirname5 } from "node:path";
106539
107372
  var MemoryFiles;
106540
107373
  var init_files = __esm({
106541
107374
  "../agent/src/memory/files.ts"() {
@@ -106543,8 +107376,8 @@ var init_files = __esm({
106543
107376
  MemoryFiles = class {
106544
107377
  constructor(workDir) {
106545
107378
  this.workDir = workDir;
106546
- this.memoryPath = join10(workDir, "MEMORY.md");
106547
- this.userPath = join10(workDir, "USER.md");
107379
+ this.memoryPath = join11(workDir, "MEMORY.md");
107380
+ this.userPath = join11(workDir, "USER.md");
106548
107381
  }
106549
107382
  memoryPath;
106550
107383
  userPath;
@@ -106553,8 +107386,8 @@ var init_files = __esm({
106553
107386
  */
106554
107387
  readMemoryFile() {
106555
107388
  try {
106556
- if (!existsSync5(this.memoryPath)) return null;
106557
- return readFileSync9(this.memoryPath, "utf-8");
107389
+ if (!existsSync6(this.memoryPath)) return null;
107390
+ return readFileSync10(this.memoryPath, "utf-8");
106558
107391
  } catch {
106559
107392
  return null;
106560
107393
  }
@@ -106564,8 +107397,8 @@ var init_files = __esm({
106564
107397
  */
106565
107398
  readUserFile() {
106566
107399
  try {
106567
- if (!existsSync5(this.userPath)) return null;
106568
- return readFileSync9(this.userPath, "utf-8");
107400
+ if (!existsSync6(this.userPath)) return null;
107401
+ return readFileSync10(this.userPath, "utf-8");
106569
107402
  } catch {
106570
107403
  return null;
106571
107404
  }
@@ -106575,16 +107408,16 @@ var init_files = __esm({
106575
107408
  */
106576
107409
  appendMemory(observation) {
106577
107410
  const dir = dirname5(this.memoryPath);
106578
- if (!existsSync5(dir)) {
106579
- mkdirSync3(dir, { recursive: true });
107411
+ if (!existsSync6(dir)) {
107412
+ mkdirSync4(dir, { recursive: true });
106580
107413
  }
106581
107414
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
106582
107415
  const entry = `
106583
107416
  - [${timestamp}] ${observation}
106584
107417
  `;
106585
- if (existsSync5(this.memoryPath)) {
106586
- const existing = readFileSync9(this.memoryPath, "utf-8");
106587
- writeFileSync3(this.memoryPath, existing + entry, "utf-8");
107418
+ if (existsSync6(this.memoryPath)) {
107419
+ const existing = readFileSync10(this.memoryPath, "utf-8");
107420
+ writeFileSync4(this.memoryPath, existing + entry, "utf-8");
106588
107421
  } else {
106589
107422
  const header = `# Agent Memory
106590
107423
 
@@ -106592,7 +107425,7 @@ Persistent observations and notes.
106592
107425
 
106593
107426
  ## Observations
106594
107427
  `;
106595
- writeFileSync3(this.memoryPath, header + entry, "utf-8");
107428
+ writeFileSync4(this.memoryPath, header + entry, "utf-8");
106596
107429
  }
106597
107430
  }
106598
107431
  /**
@@ -106600,8 +107433,8 @@ Persistent observations and notes.
106600
107433
  */
106601
107434
  updateUserProfile(info) {
106602
107435
  const dir = dirname5(this.userPath);
106603
- if (!existsSync5(dir)) {
106604
- mkdirSync3(dir, { recursive: true });
107436
+ if (!existsSync6(dir)) {
107437
+ mkdirSync4(dir, { recursive: true });
106605
107438
  }
106606
107439
  const lines = ["# User Profile\n"];
106607
107440
  for (const [key, value] of Object.entries(info)) {
@@ -106610,7 +107443,7 @@ Persistent observations and notes.
106610
107443
  }
106611
107444
  }
106612
107445
  lines.push("");
106613
- writeFileSync3(this.userPath, lines.join("\n"), "utf-8");
107446
+ writeFileSync4(this.userPath, lines.join("\n"), "utf-8");
106614
107447
  }
106615
107448
  /**
106616
107449
  * Parse MEMORY.md into structured entries.
@@ -107896,6 +108729,7 @@ var init_memory = __esm({
107896
108729
  init_vector_store();
107897
108730
  init_persistent_vector_store();
107898
108731
  init_embedding_provider();
108732
+ init_layered_memory();
107899
108733
  AgentMemory = class {
107900
108734
  constructor(store, agentId) {
107901
108735
  this.store = store;
@@ -108365,6 +109199,217 @@ ${e.reasoning}${ctx}`;
108365
109199
  }
108366
109200
  });
108367
109201
 
109202
+ // ../agent/src/safety/context-rotator.ts
109203
+ var ContextRotator;
109204
+ var init_context_rotator = __esm({
109205
+ "../agent/src/safety/context-rotator.ts"() {
109206
+ "use strict";
109207
+ ContextRotator = class {
109208
+ cfg;
109209
+ messageCount = 0;
109210
+ estimatedTokens = 0;
109211
+ constructor(config2 = {}) {
109212
+ this.cfg = {
109213
+ maxMessages: config2.maxMessages ?? 50,
109214
+ maxTokensEstimate: config2.maxTokensEstimate ?? 1e5,
109215
+ charsPerToken: config2.charsPerToken ?? 4,
109216
+ recentToKeep: config2.recentToKeep ?? 10
109217
+ };
109218
+ }
109219
+ // ── Recording ───────────────────────────────────────────────────────
109220
+ /** Record a message added to context. */
109221
+ recordMessage(content) {
109222
+ this.messageCount++;
109223
+ this.estimatedTokens += Math.ceil(content.length / this.cfg.charsPerToken);
109224
+ }
109225
+ // ── Checking ────────────────────────────────────────────────────────
109226
+ /** Check if context should be rotated. */
109227
+ shouldRotate() {
109228
+ if (this.messageCount >= this.cfg.maxMessages) {
109229
+ return {
109230
+ rotate: true,
109231
+ reason: `${this.messageCount} messages (limit: ${this.cfg.maxMessages})`
109232
+ };
109233
+ }
109234
+ if (this.estimatedTokens >= this.cfg.maxTokensEstimate) {
109235
+ return {
109236
+ rotate: true,
109237
+ reason: `~${this.estimatedTokens} tokens (limit: ${this.cfg.maxTokensEstimate})`
109238
+ };
109239
+ }
109240
+ return { rotate: false };
109241
+ }
109242
+ // ── Rotation helpers ────────────────────────────────────────────────
109243
+ /**
109244
+ * Build a compact summary for context rotation.
109245
+ *
109246
+ * Strategy:
109247
+ * - Preserve the system message verbatim
109248
+ * - Summarize the middle messages into a compact block
109249
+ * - Keep the last N recent messages verbatim
109250
+ *
109251
+ * Returns the summary text to inject as a single assistant message
109252
+ * between the system prompt and the recent messages.
109253
+ */
109254
+ buildRotationSummary(messages) {
109255
+ const keepCount = this.cfg.recentToKeep;
109256
+ const system = messages.find((m) => m.role === "system");
109257
+ const nonSystem = messages.filter((m) => m.role !== "system");
109258
+ const recent = nonSystem.slice(-keepCount);
109259
+ const middle = nonSystem.slice(0, Math.max(0, nonSystem.length - keepCount));
109260
+ const parts = [];
109261
+ if (system) {
109262
+ parts.push("[System prompt preserved]");
109263
+ }
109264
+ if (middle.length > 0) {
109265
+ const toolCalls = middle.filter(
109266
+ (m) => m.content.includes("tool_use") || m.content.includes("function_call") || m.content.includes("tool_result")
109267
+ ).length;
109268
+ const findings = middle.filter(
109269
+ (m) => m.content.includes("finding") || m.content.includes("result") || m.content.includes("discovered")
109270
+ ).length;
109271
+ const errors = middle.filter(
109272
+ (m) => m.content.includes("error") || m.content.includes("failed")
109273
+ ).length;
109274
+ parts.push(
109275
+ `[${middle.length} earlier messages summarized: ${toolCalls} tool interactions, ${findings} results/findings, ${errors} errors]`
109276
+ );
109277
+ }
109278
+ parts.push(`[${recent.length} recent messages preserved below]`);
109279
+ return parts.join("\n");
109280
+ }
109281
+ // ── Lifecycle ───────────────────────────────────────────────────────
109282
+ /** Reset counters after rotation. */
109283
+ reset() {
109284
+ this.messageCount = 0;
109285
+ this.estimatedTokens = 0;
109286
+ }
109287
+ /** Get current stats (useful for logging/telemetry). */
109288
+ getStats() {
109289
+ return {
109290
+ messageCount: this.messageCount,
109291
+ estimatedTokens: this.estimatedTokens
109292
+ };
109293
+ }
109294
+ };
109295
+ }
109296
+ });
109297
+
109298
+ // ../agent/src/safety/stuck-detector.ts
109299
+ var StuckDetector;
109300
+ var init_stuck_detector = __esm({
109301
+ "../agent/src/safety/stuck-detector.ts"() {
109302
+ "use strict";
109303
+ StuckDetector = class {
109304
+ cfg;
109305
+ toolCallHistory = [];
109306
+ findingsCount = 0;
109307
+ iterationsWithoutProgress = 0;
109308
+ consecutiveErrors = 0;
109309
+ startTime = Date.now();
109310
+ constructor(config2 = {}) {
109311
+ this.cfg = {
109312
+ maxRepeatedCalls: config2.maxRepeatedCalls ?? 3,
109313
+ maxIterationsWithoutProgress: config2.maxIterationsWithoutProgress ?? 5,
109314
+ maxConsecutiveErrors: config2.maxConsecutiveErrors ?? 3,
109315
+ maxSubObjectiveTimeMs: config2.maxSubObjectiveTimeMs ?? 3e5
109316
+ };
109317
+ }
109318
+ // ── Recording ───────────────────────────────────────────────────────
109319
+ /** Record a tool call for pattern detection. */
109320
+ recordToolCall(name, args2) {
109321
+ this.toolCallHistory.push({
109322
+ name,
109323
+ args: JSON.stringify(args2),
109324
+ timestamp: Date.now()
109325
+ });
109326
+ }
109327
+ /** Record a tool result (success or error). */
109328
+ recordResult(isError) {
109329
+ if (isError) {
109330
+ this.consecutiveErrors++;
109331
+ } else {
109332
+ this.consecutiveErrors = 0;
109333
+ }
109334
+ }
109335
+ /** Update findings count — call after each iteration of the agent loop. */
109336
+ updateProgress(currentFindings) {
109337
+ if (currentFindings > this.findingsCount) {
109338
+ this.findingsCount = currentFindings;
109339
+ this.iterationsWithoutProgress = 0;
109340
+ } else {
109341
+ this.iterationsWithoutProgress++;
109342
+ }
109343
+ }
109344
+ // ── Checking ────────────────────────────────────────────────────────
109345
+ /** Check if the agent appears stuck. */
109346
+ check() {
109347
+ const elapsed = Date.now() - this.startTime;
109348
+ if (elapsed > this.cfg.maxSubObjectiveTimeMs) {
109349
+ return {
109350
+ isStuck: true,
109351
+ reason: "timeout",
109352
+ details: `Sub-objective exceeded ${this.cfg.maxSubObjectiveTimeMs}ms (elapsed: ${elapsed}ms)`,
109353
+ suggestedAction: "skip"
109354
+ };
109355
+ }
109356
+ const windowSize = this.cfg.maxRepeatedCalls;
109357
+ if (this.toolCallHistory.length >= windowSize) {
109358
+ const recent = this.toolCallHistory.slice(-windowSize);
109359
+ const first2 = recent[0];
109360
+ const allSame = recent.every(
109361
+ (c) => c.name === first2.name && c.args === first2.args
109362
+ );
109363
+ if (allSame) {
109364
+ return {
109365
+ isStuck: true,
109366
+ reason: "repeated_calls",
109367
+ details: `Tool "${first2.name}" called ${windowSize}x with identical args`,
109368
+ suggestedAction: "retry_different"
109369
+ };
109370
+ }
109371
+ }
109372
+ if (this.iterationsWithoutProgress >= this.cfg.maxIterationsWithoutProgress) {
109373
+ return {
109374
+ isStuck: true,
109375
+ reason: "no_progress",
109376
+ details: `${this.iterationsWithoutProgress} iterations without new findings`,
109377
+ suggestedAction: "escalate_model"
109378
+ };
109379
+ }
109380
+ if (this.consecutiveErrors >= this.cfg.maxConsecutiveErrors) {
109381
+ return {
109382
+ isStuck: true,
109383
+ reason: "error_loop",
109384
+ details: `${this.consecutiveErrors} consecutive tool errors`,
109385
+ suggestedAction: "retry_different"
109386
+ };
109387
+ }
109388
+ return { isStuck: false };
109389
+ }
109390
+ // ── Lifecycle ───────────────────────────────────────────────────────
109391
+ /** Reset all state for reuse (e.g., new sub-objective). */
109392
+ reset() {
109393
+ this.toolCallHistory = [];
109394
+ this.findingsCount = 0;
109395
+ this.iterationsWithoutProgress = 0;
109396
+ this.consecutiveErrors = 0;
109397
+ this.startTime = Date.now();
109398
+ }
109399
+ /** Get a diagnostic snapshot (useful for logging/telemetry). */
109400
+ getSnapshot() {
109401
+ return {
109402
+ totalCalls: this.toolCallHistory.length,
109403
+ consecutiveErrors: this.consecutiveErrors,
109404
+ iterationsWithoutProgress: this.iterationsWithoutProgress,
109405
+ findingsCount: this.findingsCount,
109406
+ elapsedMs: Date.now() - this.startTime
109407
+ };
109408
+ }
109409
+ };
109410
+ }
109411
+ });
109412
+
108368
109413
  // ../agent/src/runtime.ts
108369
109414
  function estimateCost(model, inputTokens, outputTokens) {
108370
109415
  let costs = COST_PER_1M_TOKENS[model];
@@ -108432,6 +109477,8 @@ var init_runtime = __esm({
108432
109477
  init_hot_swap();
108433
109478
  init_auto_downgrade();
108434
109479
  init_reason_logger();
109480
+ init_context_rotator();
109481
+ init_stuck_detector();
108435
109482
  COST_PER_1M_TOKENS = {
108436
109483
  "claude-sonnet-4-20250514": { input: 3, output: 15 },
108437
109484
  "claude-3-5-sonnet": { input: 3, output: 15 },
@@ -108466,6 +109513,10 @@ var init_runtime = __esm({
108466
109513
  autoDowngrade = null;
108467
109514
  /** Explainability: logs the agent's reasoning at each step. */
108468
109515
  reasonLogger = new ReasonLogger();
109516
+ /** Context rotator for fresh-context pattern (Ralph methodology). */
109517
+ contextRotator;
109518
+ /** Stuck detector for loop/stall detection. */
109519
+ stuckDetector;
108469
109520
  constructor(config2) {
108470
109521
  this.config = config2;
108471
109522
  this.hotSwap = new HotSwapAdapter(config2.adapter);
@@ -108550,6 +109601,8 @@ var init_runtime = __esm({
108550
109601
  workspaceContext,
108551
109602
  errorCorrections
108552
109603
  });
109604
+ this.contextRotator = new ContextRotator(config2.contextRotation);
109605
+ this.stuckDetector = new StuckDetector(config2.stuckDetection);
108553
109606
  }
108554
109607
  /**
108555
109608
  * Run the agent loop for a given task.
@@ -108577,6 +109630,9 @@ var init_runtime = __esm({
108577
109630
  let lastOutput = "";
108578
109631
  let exitReason = "completed";
108579
109632
  let error;
109633
+ let consecutiveStucks = 0;
109634
+ this.contextRotator.reset();
109635
+ this.stuckDetector.reset();
108580
109636
  if (this.config.dryRun) {
108581
109637
  this.dryRunInterceptor = new DryRunInterceptor();
108582
109638
  } else {
@@ -108901,8 +109957,51 @@ ${this.config.orgChartContext}` : this.config.orgChartContext;
108901
109957
  }
108902
109958
  ]);
108903
109959
  messages.push(...toolResultMessages);
109960
+ this.stuckDetector.recordToolCall(toolCall.name, toolCall.arguments);
109961
+ this.stuckDetector.recordResult(result2.isError ?? false);
109962
+ const stuckCheck = this.stuckDetector.check();
109963
+ if (stuckCheck.isStuck) {
109964
+ this.log.warn("Agent stuck detected", {
109965
+ reason: stuckCheck.reason,
109966
+ suggestion: stuckCheck.suggestedAction
109967
+ });
109968
+ messages.push({
109969
+ role: "user",
109970
+ content: `[System] You appear to be stuck (${stuckCheck.reason}). ${stuckCheck.suggestedAction === "retry_different" ? "Try a completely different approach." : stuckCheck.suggestedAction === "skip" ? "Skip this and move to the next task." : stuckCheck.suggestedAction === "escalate_model" ? "This task may require deeper analysis." : "Consider aborting this approach."}`
109971
+ });
109972
+ if (stuckCheck.reason === "timeout" || consecutiveStucks >= 2) {
109973
+ exitReason = "error";
109974
+ error = `Agent stuck: ${stuckCheck.reason} \u2014 ${stuckCheck.details ?? ""}`;
109975
+ break;
109976
+ }
109977
+ consecutiveStucks++;
109978
+ } else {
109979
+ consecutiveStucks = 0;
109980
+ }
108904
109981
  }
108905
109982
  if (exitReason === "aborted") break;
109983
+ if (exitReason === "error") break;
109984
+ this.contextRotator.recordMessage(response.content);
109985
+ const rotationCheck = this.contextRotator.shouldRotate();
109986
+ if (rotationCheck.rotate) {
109987
+ const summary = this.contextRotator.buildRotationSummary(messages);
109988
+ const systemMsg = messages[0];
109989
+ const recent = messages.slice(-3);
109990
+ messages.length = 0;
109991
+ messages.push(
109992
+ systemMsg,
109993
+ {
109994
+ role: "user",
109995
+ content: `[Context rotation \u2014 previous messages summarized]
109996
+ ${summary}
109997
+
109998
+ Continue from where you left off.`
109999
+ },
110000
+ ...recent
110001
+ );
110002
+ this.contextRotator.reset();
110003
+ this.log.info("Context rotated", { reason: rotationCheck.reason });
110004
+ }
108906
110005
  }
108907
110006
  if (iterations >= maxIterations && exitReason === "completed") {
108908
110007
  exitReason = "max_iterations";
@@ -109144,6 +110243,9 @@ ${this.config.orgChartContext}` : this.config.orgChartContext;
109144
110243
  let consecutiveFailures = 0;
109145
110244
  let lastFailedToolName = "";
109146
110245
  const MAX_CONSECUTIVE_FAILURES = 3;
110246
+ let consecutiveStucks = 0;
110247
+ this.contextRotator.reset();
110248
+ this.stuckDetector.reset();
109147
110249
  const messages = this.contextBuilder.buildMessages(
109148
110250
  task.prompt,
109149
110251
  this.config.initialMessages
@@ -109421,8 +110523,51 @@ ${this.config.orgChartContext}` : this.config.orgChartContext;
109421
110523
  }
109422
110524
  ]);
109423
110525
  messages.push(...toolResultMessages);
110526
+ this.stuckDetector.recordToolCall(toolCall.name, toolCall.arguments);
110527
+ this.stuckDetector.recordResult(result.isError ?? false);
110528
+ const stuckCheck = this.stuckDetector.check();
110529
+ if (stuckCheck.isStuck) {
110530
+ this.log.warn("Agent stuck detected (streaming)", {
110531
+ reason: stuckCheck.reason,
110532
+ suggestion: stuckCheck.suggestedAction
110533
+ });
110534
+ messages.push({
110535
+ role: "user",
110536
+ content: `[System] You appear to be stuck (${stuckCheck.reason}). ${stuckCheck.suggestedAction === "retry_different" ? "Try a completely different approach." : stuckCheck.suggestedAction === "skip" ? "Skip this and move to the next task." : stuckCheck.suggestedAction === "escalate_model" ? "This task may require deeper analysis." : "Consider aborting this approach."}`
110537
+ });
110538
+ if (stuckCheck.reason === "timeout" || consecutiveStucks >= 2) {
110539
+ exitReason = "error";
110540
+ error = `Agent stuck: ${stuckCheck.reason} \u2014 ${stuckCheck.details ?? ""}`;
110541
+ break;
110542
+ }
110543
+ consecutiveStucks++;
110544
+ } else {
110545
+ consecutiveStucks = 0;
110546
+ }
109424
110547
  }
109425
110548
  if (exitReason === "aborted") break;
110549
+ if (exitReason === "error") break;
110550
+ this.contextRotator.recordMessage(response.content);
110551
+ const rotationCheck = this.contextRotator.shouldRotate();
110552
+ if (rotationCheck.rotate) {
110553
+ const summary = this.contextRotator.buildRotationSummary(messages);
110554
+ const systemMsg = messages[0];
110555
+ const recent = messages.slice(-3);
110556
+ messages.length = 0;
110557
+ messages.push(
110558
+ systemMsg,
110559
+ {
110560
+ role: "user",
110561
+ content: `[Context rotation \u2014 previous messages summarized]
110562
+ ${summary}
110563
+
110564
+ Continue from where you left off.`
110565
+ },
110566
+ ...recent
110567
+ );
110568
+ this.contextRotator.reset();
110569
+ this.log.info("Context rotated (streaming)", { reason: rotationCheck.reason });
110570
+ }
109426
110571
  }
109427
110572
  if (iterations >= maxIterations && exitReason === "completed") {
109428
110573
  exitReason = "max_iterations";
@@ -110511,8 +111656,8 @@ var init_embeddings = __esm({
110511
111656
  });
110512
111657
 
110513
111658
  // ../agent/src/rag/indexer.ts
110514
- import { readFileSync as readFileSync10, statSync as statSync3, existsSync as existsSync6 } from "node:fs";
110515
- import { join as join11, relative as relative3, resolve as resolve11 } from "node:path";
111659
+ import { readFileSync as readFileSync11, statSync as statSync4, existsSync as existsSync7 } from "node:fs";
111660
+ import { join as join12, relative as relative4, resolve as resolve11 } from "node:path";
110516
111661
  function matchesGlob(filePath, pattern) {
110517
111662
  const regexStr = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
110518
111663
  return new RegExp(`^${regexStr}$`).test(filePath) || new RegExp(`(^|/)${regexStr}$`).test(filePath);
@@ -110538,14 +111683,14 @@ function walkDirectory2(dir, excludePatterns) {
110538
111683
  const currentDir = stack.pop();
110539
111684
  let entries;
110540
111685
  try {
110541
- const { readdirSync: readdirSync7 } = __require("node:fs");
110542
- entries = readdirSync7(currentDir);
111686
+ const { readdirSync: readdirSync8 } = __require("node:fs");
111687
+ entries = readdirSync8(currentDir);
110543
111688
  } catch {
110544
111689
  continue;
110545
111690
  }
110546
111691
  for (const entry of entries) {
110547
- const fullPath = join11(currentDir, entry);
110548
- const relFromRoot = relative3(dir, fullPath).replace(/\\/g, "/");
111692
+ const fullPath = join12(currentDir, entry);
111693
+ const relFromRoot = relative4(dir, fullPath).replace(/\\/g, "/");
110549
111694
  let excluded = false;
110550
111695
  for (const pattern of excludePatterns) {
110551
111696
  if (!pattern.includes("*") && !pattern.includes(".")) {
@@ -110557,7 +111702,7 @@ function walkDirectory2(dir, excludePatterns) {
110557
111702
  }
110558
111703
  if (excluded) continue;
110559
111704
  try {
110560
- const stat = statSync3(fullPath);
111705
+ const stat = statSync4(fullPath);
110561
111706
  if (stat.isDirectory()) {
110562
111707
  stack.push(fullPath);
110563
111708
  } else if (stat.isFile()) {
@@ -110649,13 +111794,13 @@ var init_indexer = __esm({
110649
111794
  const tenantId = opts?.tenantId ?? "default";
110650
111795
  const onProgress = opts?.onProgress;
110651
111796
  const absDir = resolve11(dir);
110652
- if (!existsSync6(absDir)) {
111797
+ if (!existsSync7(absDir)) {
110653
111798
  throw new Error(`Directory not found: ${absDir}`);
110654
111799
  }
110655
111800
  const allFiles = walkDirectory2(absDir, excludePatterns);
110656
111801
  const matchingFiles = [];
110657
111802
  for (const file of allFiles) {
110658
- const relPath = relative3(absDir, file).replace(/\\/g, "/");
111803
+ const relPath = relative4(absDir, file).replace(/\\/g, "/");
110659
111804
  if (shouldInclude(relPath, includePatterns, excludePatterns)) {
110660
111805
  matchingFiles.push(file);
110661
111806
  }
@@ -110671,11 +111816,11 @@ var init_indexer = __esm({
110671
111816
  let totalFilesIndexed = 0;
110672
111817
  const allChunks = [];
110673
111818
  for (const filePath of matchingFiles) {
110674
- const relPath = relative3(absDir, filePath).replace(/\\/g, "/");
111819
+ const relPath = relative4(absDir, filePath).replace(/\\/g, "/");
110675
111820
  progress.currentFile = relPath;
110676
111821
  onProgress?.(progress);
110677
111822
  try {
110678
- const content = readFileSync10(filePath, "utf-8");
111823
+ const content = readFileSync11(filePath, "utf-8");
110679
111824
  if (content.includes("\0")) {
110680
111825
  progress.processedFiles++;
110681
111826
  continue;
@@ -110684,7 +111829,7 @@ var init_indexer = __esm({
110684
111829
  progress.processedFiles++;
110685
111830
  continue;
110686
111831
  }
110687
- const mtime = statSync3(filePath).mtimeMs;
111832
+ const mtime = statSync4(filePath).mtimeMs;
110688
111833
  const chunks = this.chunker.chunk(relPath, content);
110689
111834
  for (const chunk of chunks) {
110690
111835
  allChunks.push({ ...chunk, fileMtime: Math.floor(mtime) });
@@ -110746,7 +111891,7 @@ ${c.content}`);
110746
111891
  const tenantId = opts?.tenantId ?? "default";
110747
111892
  const onProgress = opts?.onProgress;
110748
111893
  const absDir = resolve11(dir);
110749
- if (!existsSync6(absDir)) {
111894
+ if (!existsSync7(absDir)) {
110750
111895
  throw new Error(`Directory not found: ${absDir}`);
110751
111896
  }
110752
111897
  const existingMtimes = this.store.getChunkMtimes(tenantId);
@@ -110757,16 +111902,16 @@ ${c.content}`);
110757
111902
  const allFiles = walkDirectory2(absDir, excludePatterns);
110758
111903
  const matchingFiles = [];
110759
111904
  for (const file of allFiles) {
110760
- const relPath = relative3(absDir, file).replace(/\\/g, "/");
111905
+ const relPath = relative4(absDir, file).replace(/\\/g, "/");
110761
111906
  if (shouldInclude(relPath, includePatterns, excludePatterns)) {
110762
111907
  matchingFiles.push(file);
110763
111908
  }
110764
111909
  }
110765
111910
  const filesToProcess = [];
110766
111911
  for (const filePath of matchingFiles) {
110767
- const relPath = relative3(absDir, filePath).replace(/\\/g, "/");
111912
+ const relPath = relative4(absDir, filePath).replace(/\\/g, "/");
110768
111913
  try {
110769
- const mtime = Math.floor(statSync3(filePath).mtimeMs);
111914
+ const mtime = Math.floor(statSync4(filePath).mtimeMs);
110770
111915
  const existingMtime = mtimeMap.get(relPath);
110771
111916
  if (existingMtime === void 0 || mtime > existingMtime) {
110772
111917
  filesToProcess.push(filePath);
@@ -110788,16 +111933,16 @@ ${c.content}`);
110788
111933
  let totalChunksCreated = 0;
110789
111934
  let totalFilesIndexed = 0;
110790
111935
  for (const filePath of filesToProcess) {
110791
- const relPath = relative3(absDir, filePath).replace(/\\/g, "/");
111936
+ const relPath = relative4(absDir, filePath).replace(/\\/g, "/");
110792
111937
  progress.currentFile = relPath;
110793
111938
  onProgress?.(progress);
110794
111939
  try {
110795
- const content = readFileSync10(filePath, "utf-8");
111940
+ const content = readFileSync11(filePath, "utf-8");
110796
111941
  if (content.includes("\0") || content.length > 5e5) {
110797
111942
  progress.processedFiles++;
110798
111943
  continue;
110799
111944
  }
110800
- const mtime = Math.floor(statSync3(filePath).mtimeMs);
111945
+ const mtime = Math.floor(statSync4(filePath).mtimeMs);
110801
111946
  this.store.clearChunksByFile(relPath, tenantId);
110802
111947
  const chunks = this.chunker.chunk(relPath, content);
110803
111948
  const texts = chunks.map((c) => `${c.name ?? c.type} in ${c.filePath}
@@ -110980,8 +112125,8 @@ var init_search = __esm({
110980
112125
  });
110981
112126
 
110982
112127
  // ../agent/src/rag/mental-model.ts
110983
- import { readFileSync as readFileSync11, statSync as statSync4, readdirSync as readdirSync2, writeFileSync as writeFileSync4, existsSync as existsSync7 } from "node:fs";
110984
- import { join as join12, relative as relative4, resolve as resolve12, extname as extname3, basename as basename3, dirname as dirname6 } from "node:path";
112128
+ import { readFileSync as readFileSync12, statSync as statSync5, readdirSync as readdirSync3, writeFileSync as writeFileSync5, existsSync as existsSync8 } from "node:fs";
112129
+ import { join as join13, relative as relative5, resolve as resolve12, extname as extname3, basename as basename4, dirname as dirname6 } from "node:path";
110985
112130
  var EXCLUDE_DIRS, SOURCE_EXTENSIONS, MentalModelBuilder;
110986
112131
  var init_mental_model = __esm({
110987
112132
  "../agent/src/rag/mental-model.ts"() {
@@ -111045,10 +112190,10 @@ var init_mental_model = __esm({
111045
112190
  const files = await this.findSourceFiles(absRoot);
111046
112191
  const nodes = /* @__PURE__ */ new Map();
111047
112192
  for (const file of files) {
111048
- const relPath = relative4(absRoot, file).replace(/\\/g, "/");
112193
+ const relPath = relative5(absRoot, file).replace(/\\/g, "/");
111049
112194
  let content;
111050
112195
  try {
111051
- content = readFileSync11(file, "utf-8");
112196
+ content = readFileSync12(file, "utf-8");
111052
112197
  } catch {
111053
112198
  continue;
111054
112199
  }
@@ -111056,14 +112201,14 @@ var init_mental_model = __esm({
111056
112201
  const imports = this.extractImports(content, file, absRoot);
111057
112202
  let mtime;
111058
112203
  try {
111059
- mtime = statSync4(file).mtimeMs;
112204
+ mtime = statSync5(file).mtimeMs;
111060
112205
  } catch {
111061
112206
  mtime = Date.now();
111062
112207
  }
111063
112208
  nodes.set(relPath, {
111064
112209
  path: relPath,
111065
112210
  type: "file",
111066
- name: basename3(file),
112211
+ name: basename4(file),
111067
112212
  dependencies: imports,
111068
112213
  dependents: [],
111069
112214
  lastModified: mtime,
@@ -111096,7 +112241,7 @@ var init_mental_model = __esm({
111096
112241
  );
111097
112242
  const hubs = [...graph.nodes.values()].sort((a, b) => b.dependents.length - a.dependents.length).slice(0, 5);
111098
112243
  const lines = [];
111099
- lines.push(`Project: ${basename3(graph.rootDir)}`);
112244
+ lines.push(`Project: ${basename4(graph.rootDir)}`);
111100
112245
  lines.push(`Language: ${graph.language}`);
111101
112246
  if (graph.framework) lines.push(`Framework: ${graph.framework}`);
111102
112247
  lines.push(`Files: ${moduleCount}`);
@@ -111183,15 +112328,15 @@ var init_mental_model = __esm({
111183
112328
  */
111184
112329
  save(graph, filePath) {
111185
112330
  const json = this.toJson(graph);
111186
- writeFileSync4(filePath, JSON.stringify(json, null, 2), "utf-8");
112331
+ writeFileSync5(filePath, JSON.stringify(json, null, 2), "utf-8");
111187
112332
  }
111188
112333
  /**
111189
112334
  * Load a graph from a JSON file.
111190
112335
  */
111191
112336
  load(filePath) {
111192
- if (!existsSync7(filePath)) return null;
112337
+ if (!existsSync8(filePath)) return null;
111193
112338
  try {
111194
- const content = readFileSync11(filePath, "utf-8");
112339
+ const content = readFileSync12(filePath, "utf-8");
111195
112340
  const json = JSON.parse(content);
111196
112341
  return this.fromJson(json);
111197
112342
  } catch {
@@ -111270,7 +112415,7 @@ var init_mental_model = __esm({
111270
112415
  }
111271
112416
  const fromDir = dirname6(fromFile);
111272
112417
  const resolved = resolve12(fromDir, specifier);
111273
- const relPath = relative4(rootDir, resolved).replace(/\\/g, "/");
112418
+ const relPath = relative5(rootDir, resolved).replace(/\\/g, "/");
111274
112419
  const candidates = [
111275
112420
  relPath,
111276
112421
  relPath + ".ts",
@@ -111291,7 +112436,7 @@ var init_mental_model = __esm({
111291
112436
  for (const candidate of candidates) {
111292
112437
  const fullPath = resolve12(rootDir, candidate);
111293
112438
  try {
111294
- const stat = statSync4(fullPath);
112439
+ const stat = statSync5(fullPath);
111295
112440
  if (stat.isFile()) {
111296
112441
  return candidate;
111297
112442
  }
@@ -111301,7 +112446,7 @@ var init_mental_model = __esm({
111301
112446
  return relPath;
111302
112447
  }
111303
112448
  resolveRelativePy(modulePath, fromFile, rootDir) {
111304
- const relPath = relative4(rootDir, resolve12(dirname6(fromFile), modulePath)).replace(/\\/g, "/");
112449
+ const relPath = relative5(rootDir, resolve12(dirname6(fromFile), modulePath)).replace(/\\/g, "/");
111305
112450
  const candidates = [
111306
112451
  relPath + ".py",
111307
112452
  relPath + "/__init__.py"
@@ -111309,7 +112454,7 @@ var init_mental_model = __esm({
111309
112454
  for (const candidate of candidates) {
111310
112455
  const fullPath = resolve12(rootDir, candidate);
111311
112456
  try {
111312
- if (statSync4(fullPath).isFile()) return candidate;
112457
+ if (statSync5(fullPath).isFile()) return candidate;
111313
112458
  } catch {
111314
112459
  }
111315
112460
  }
@@ -111384,10 +112529,10 @@ var init_mental_model = __esm({
111384
112529
  return "Unknown";
111385
112530
  }
111386
112531
  detectFramework(rootDir) {
111387
- const pkgPath = join12(rootDir, "package.json");
111388
- if (existsSync7(pkgPath)) {
112532
+ const pkgPath = join13(rootDir, "package.json");
112533
+ if (existsSync8(pkgPath)) {
111389
112534
  try {
111390
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
112535
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
111391
112536
  const deps = {
111392
112537
  ...pkg.dependencies ?? {},
111393
112538
  ...pkg.devDependencies ?? {}
@@ -111406,10 +112551,10 @@ var init_mental_model = __esm({
111406
112551
  } catch {
111407
112552
  }
111408
112553
  }
111409
- const cargoPath = join12(rootDir, "Cargo.toml");
111410
- if (existsSync7(cargoPath)) {
112554
+ const cargoPath = join13(rootDir, "Cargo.toml");
112555
+ if (existsSync8(cargoPath)) {
111411
112556
  try {
111412
- const cargo = readFileSync11(cargoPath, "utf-8");
112557
+ const cargo = readFileSync12(cargoPath, "utf-8");
111413
112558
  if (cargo.includes("actix-web")) return "Actix";
111414
112559
  if (cargo.includes("axum")) return "Axum";
111415
112560
  if (cargo.includes("rocket")) return "Rocket";
@@ -111417,10 +112562,10 @@ var init_mental_model = __esm({
111417
112562
  } catch {
111418
112563
  }
111419
112564
  }
111420
- const goModPath = join12(rootDir, "go.mod");
111421
- if (existsSync7(goModPath)) {
112565
+ const goModPath = join13(rootDir, "go.mod");
112566
+ if (existsSync8(goModPath)) {
111422
112567
  try {
111423
- const goMod = readFileSync11(goModPath, "utf-8");
112568
+ const goMod = readFileSync12(goModPath, "utf-8");
111424
112569
  if (goMod.includes("gin-gonic")) return "Gin";
111425
112570
  if (goMod.includes("echo")) return "Echo";
111426
112571
  if (goMod.includes("fiber")) return "Fiber";
@@ -111428,10 +112573,10 @@ var init_mental_model = __esm({
111428
112573
  }
111429
112574
  }
111430
112575
  for (const pyFile of ["requirements.txt", "pyproject.toml"]) {
111431
- const pyPath = join12(rootDir, pyFile);
111432
- if (existsSync7(pyPath)) {
112576
+ const pyPath = join13(rootDir, pyFile);
112577
+ if (existsSync8(pyPath)) {
111433
112578
  try {
111434
- const content = readFileSync11(pyPath, "utf-8");
112579
+ const content = readFileSync12(pyPath, "utf-8");
111435
112580
  if (content.includes("django")) return "Django";
111436
112581
  if (content.includes("flask")) return "Flask";
111437
112582
  if (content.includes("fastapi")) return "FastAPI";
@@ -111485,16 +112630,16 @@ var init_mental_model = __esm({
111485
112630
  const currentDir = stack.pop();
111486
112631
  let entries;
111487
112632
  try {
111488
- entries = readdirSync2(currentDir);
112633
+ entries = readdirSync3(currentDir);
111489
112634
  } catch {
111490
112635
  continue;
111491
112636
  }
111492
112637
  for (const entry of entries) {
111493
112638
  if (EXCLUDE_DIRS.has(entry)) continue;
111494
112639
  if (entry.startsWith(".") && entry !== ".") continue;
111495
- const fullPath = join12(currentDir, entry);
112640
+ const fullPath = join13(currentDir, entry);
111496
112641
  try {
111497
- const stat = statSync4(fullPath);
112642
+ const stat = statSync5(fullPath);
111498
112643
  if (stat.isDirectory()) {
111499
112644
  stack.push(fullPath);
111500
112645
  } else if (stat.isFile()) {
@@ -111527,7 +112672,7 @@ var init_rag = __esm({
111527
112672
  });
111528
112673
 
111529
112674
  // ../agent/src/testing/mock-adapter.ts
111530
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "node:fs";
112675
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "node:fs";
111531
112676
  var MockLlmAdapter, RecordingAdapter;
111532
112677
  var init_mock_adapter = __esm({
111533
112678
  "../agent/src/testing/mock-adapter.ts"() {
@@ -111579,7 +112724,7 @@ var init_mock_adapter = __esm({
111579
112724
  }
111580
112725
  /** Create from a JSON file of recorded responses. */
111581
112726
  static fromFile(filePath) {
111582
- const data = JSON.parse(readFileSync12(filePath, "utf-8"));
112727
+ const data = JSON.parse(readFileSync13(filePath, "utf-8"));
111583
112728
  return new _MockLlmAdapter2(data);
111584
112729
  }
111585
112730
  };
@@ -111611,11 +112756,11 @@ var init_mock_adapter = __esm({
111611
112756
  /** Save recordings to a JSON file. */
111612
112757
  save(filePath) {
111613
112758
  const responses = this.recordings.map((r) => r.response);
111614
- writeFileSync5(filePath, JSON.stringify(responses, null, 2), "utf-8");
112759
+ writeFileSync6(filePath, JSON.stringify(responses, null, 2), "utf-8");
111615
112760
  }
111616
112761
  /** Load responses from a JSON file (returns the responses array for MockLlmAdapter). */
111617
112762
  static load(filePath) {
111618
- return JSON.parse(readFileSync12(filePath, "utf-8"));
112763
+ return JSON.parse(readFileSync13(filePath, "utf-8"));
111619
112764
  }
111620
112765
  /** Get just the response objects for replaying. */
111621
112766
  getResponses() {
@@ -111777,8 +112922,8 @@ var init_assertions = __esm({
111777
112922
  });
111778
112923
 
111779
112924
  // ../agent/src/testing/runner.ts
111780
- import { readFileSync as readFileSync13, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, rmSync, existsSync as existsSync8 } from "node:fs";
111781
- import { join as join13 } from "node:path";
112925
+ import { readFileSync as readFileSync14, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7, rmSync, existsSync as existsSync9 } from "node:fs";
112926
+ import { join as join14 } from "node:path";
111782
112927
  import { tmpdir as tmpdir3 } from "node:os";
111783
112928
  import { randomUUID as randomUUID27 } from "node:crypto";
111784
112929
  function parseYaml(content) {
@@ -111880,7 +113025,7 @@ var init_runner = __esm({
111880
113025
  AgentTestRunner = class {
111881
113026
  /** Load test suite from a YAML or JSON file. */
111882
113027
  static loadSuite(filePath) {
111883
- const content = readFileSync13(filePath, "utf-8");
113028
+ const content = readFileSync14(filePath, "utf-8");
111884
113029
  const parsed = parseYaml(content);
111885
113030
  return {
111886
113031
  name: parsed.name ?? "Unnamed Suite",
@@ -111903,18 +113048,18 @@ var init_runner = __esm({
111903
113048
  async runTest(testCase) {
111904
113049
  const startTime4 = Date.now();
111905
113050
  try {
111906
- const tempDir = join13(tmpdir3(), `nestor-test-${randomUUID27()}`);
111907
- mkdirSync4(tempDir, { recursive: true });
113051
+ const tempDir = join14(tmpdir3(), `nestor-test-${randomUUID27()}`);
113052
+ mkdirSync5(tempDir, { recursive: true });
111908
113053
  if (testCase.files) {
111909
113054
  for (const [path30, content] of Object.entries(testCase.files)) {
111910
- const fullPath = join13(tempDir, path30);
111911
- mkdirSync4(join13(fullPath, ".."), { recursive: true });
111912
- writeFileSync6(fullPath, content, "utf-8");
113055
+ const fullPath = join14(tempDir, path30);
113056
+ mkdirSync5(join14(fullPath, ".."), { recursive: true });
113057
+ writeFileSync7(fullPath, content, "utf-8");
111913
113058
  }
111914
113059
  }
111915
113060
  let responses;
111916
- if (testCase.recordings && existsSync8(testCase.recordings)) {
111917
- responses = JSON.parse(readFileSync13(testCase.recordings, "utf-8"));
113061
+ if (testCase.recordings && existsSync9(testCase.recordings)) {
113062
+ responses = JSON.parse(readFileSync14(testCase.recordings, "utf-8"));
111918
113063
  } else {
111919
113064
  responses = [{
111920
113065
  content: "Test completed successfully.",
@@ -114276,121 +115421,6 @@ var init_knowledge = __esm({
114276
115421
  }
114277
115422
  });
114278
115423
 
114279
- // ../agent/src/safety/stuck-detector.ts
114280
- var StuckDetector;
114281
- var init_stuck_detector = __esm({
114282
- "../agent/src/safety/stuck-detector.ts"() {
114283
- "use strict";
114284
- StuckDetector = class {
114285
- cfg;
114286
- toolCallHistory = [];
114287
- findingsCount = 0;
114288
- iterationsWithoutProgress = 0;
114289
- consecutiveErrors = 0;
114290
- startTime = Date.now();
114291
- constructor(config2 = {}) {
114292
- this.cfg = {
114293
- maxRepeatedCalls: config2.maxRepeatedCalls ?? 3,
114294
- maxIterationsWithoutProgress: config2.maxIterationsWithoutProgress ?? 5,
114295
- maxConsecutiveErrors: config2.maxConsecutiveErrors ?? 3,
114296
- maxSubObjectiveTimeMs: config2.maxSubObjectiveTimeMs ?? 3e5
114297
- };
114298
- }
114299
- // ── Recording ───────────────────────────────────────────────────────
114300
- /** Record a tool call for pattern detection. */
114301
- recordToolCall(name, args2) {
114302
- this.toolCallHistory.push({
114303
- name,
114304
- args: JSON.stringify(args2),
114305
- timestamp: Date.now()
114306
- });
114307
- }
114308
- /** Record a tool result (success or error). */
114309
- recordResult(isError) {
114310
- if (isError) {
114311
- this.consecutiveErrors++;
114312
- } else {
114313
- this.consecutiveErrors = 0;
114314
- }
114315
- }
114316
- /** Update findings count — call after each iteration of the agent loop. */
114317
- updateProgress(currentFindings) {
114318
- if (currentFindings > this.findingsCount) {
114319
- this.findingsCount = currentFindings;
114320
- this.iterationsWithoutProgress = 0;
114321
- } else {
114322
- this.iterationsWithoutProgress++;
114323
- }
114324
- }
114325
- // ── Checking ────────────────────────────────────────────────────────
114326
- /** Check if the agent appears stuck. */
114327
- check() {
114328
- const elapsed = Date.now() - this.startTime;
114329
- if (elapsed > this.cfg.maxSubObjectiveTimeMs) {
114330
- return {
114331
- isStuck: true,
114332
- reason: "timeout",
114333
- details: `Sub-objective exceeded ${this.cfg.maxSubObjectiveTimeMs}ms (elapsed: ${elapsed}ms)`,
114334
- suggestedAction: "skip"
114335
- };
114336
- }
114337
- const windowSize = this.cfg.maxRepeatedCalls;
114338
- if (this.toolCallHistory.length >= windowSize) {
114339
- const recent = this.toolCallHistory.slice(-windowSize);
114340
- const first2 = recent[0];
114341
- const allSame = recent.every(
114342
- (c) => c.name === first2.name && c.args === first2.args
114343
- );
114344
- if (allSame) {
114345
- return {
114346
- isStuck: true,
114347
- reason: "repeated_calls",
114348
- details: `Tool "${first2.name}" called ${windowSize}x with identical args`,
114349
- suggestedAction: "retry_different"
114350
- };
114351
- }
114352
- }
114353
- if (this.iterationsWithoutProgress >= this.cfg.maxIterationsWithoutProgress) {
114354
- return {
114355
- isStuck: true,
114356
- reason: "no_progress",
114357
- details: `${this.iterationsWithoutProgress} iterations without new findings`,
114358
- suggestedAction: "escalate_model"
114359
- };
114360
- }
114361
- if (this.consecutiveErrors >= this.cfg.maxConsecutiveErrors) {
114362
- return {
114363
- isStuck: true,
114364
- reason: "error_loop",
114365
- details: `${this.consecutiveErrors} consecutive tool errors`,
114366
- suggestedAction: "retry_different"
114367
- };
114368
- }
114369
- return { isStuck: false };
114370
- }
114371
- // ── Lifecycle ───────────────────────────────────────────────────────
114372
- /** Reset all state for reuse (e.g., new sub-objective). */
114373
- reset() {
114374
- this.toolCallHistory = [];
114375
- this.findingsCount = 0;
114376
- this.iterationsWithoutProgress = 0;
114377
- this.consecutiveErrors = 0;
114378
- this.startTime = Date.now();
114379
- }
114380
- /** Get a diagnostic snapshot (useful for logging/telemetry). */
114381
- getSnapshot() {
114382
- return {
114383
- totalCalls: this.toolCallHistory.length,
114384
- consecutiveErrors: this.consecutiveErrors,
114385
- iterationsWithoutProgress: this.iterationsWithoutProgress,
114386
- findingsCount: this.findingsCount,
114387
- elapsedMs: Date.now() - this.startTime
114388
- };
114389
- }
114390
- };
114391
- }
114392
- });
114393
-
114394
115424
  // ../agent/src/safety/circuit-breaker.ts
114395
115425
  var CircuitBreaker;
114396
115426
  var init_circuit_breaker = __esm({
@@ -114512,98 +115542,278 @@ var init_circuit_breaker = __esm({
114512
115542
  }
114513
115543
  });
114514
115544
 
114515
- // ../agent/src/safety/context-rotator.ts
114516
- var ContextRotator;
114517
- var init_context_rotator = __esm({
114518
- "../agent/src/safety/context-rotator.ts"() {
115545
+ // ../agent/src/safety/question-detector.ts
115546
+ var QuestionDetector;
115547
+ var init_question_detector = __esm({
115548
+ "../agent/src/safety/question-detector.ts"() {
114519
115549
  "use strict";
114520
- ContextRotator = class {
114521
- cfg;
114522
- messageCount = 0;
114523
- estimatedTokens = 0;
114524
- constructor(config2 = {}) {
114525
- this.cfg = {
114526
- maxMessages: config2.maxMessages ?? 50,
114527
- maxTokensEstimate: config2.maxTokensEstimate ?? 1e5,
114528
- charsPerToken: config2.charsPerToken ?? 4,
114529
- recentToKeep: config2.recentToKeep ?? 10
114530
- };
114531
- }
114532
- // ── Recording ───────────────────────────────────────────────────────
114533
- /** Record a message added to context. */
114534
- recordMessage(content) {
114535
- this.messageCount++;
114536
- this.estimatedTokens += Math.ceil(content.length / this.cfg.charsPerToken);
115550
+ QuestionDetector = class {
115551
+ questionCount = 0;
115552
+ maxQuestionsBeforeCorrection;
115553
+ constructor(opts) {
115554
+ this.maxQuestionsBeforeCorrection = opts?.maxQuestions ?? 2;
114537
115555
  }
114538
- // ── Checking ────────────────────────────────────────────────────────
114539
- /** Check if context should be rotated. */
114540
- shouldRotate() {
114541
- if (this.messageCount >= this.cfg.maxMessages) {
114542
- return {
114543
- rotate: true,
114544
- reason: `${this.messageCount} messages (limit: ${this.cfg.maxMessages})`
114545
- };
115556
+ /**
115557
+ * Analyze an assistant message for question patterns.
115558
+ * Returns detection result with optional correction prompt.
115559
+ */
115560
+ detect(assistantMessage) {
115561
+ const questions = this.extractQuestions(assistantMessage);
115562
+ if (questions.length === 0) {
115563
+ this.questionCount = 0;
115564
+ return { isQuestion: false, questions: [] };
114546
115565
  }
114547
- if (this.estimatedTokens >= this.cfg.maxTokensEstimate) {
115566
+ this.questionCount++;
115567
+ if (this.questionCount >= this.maxQuestionsBeforeCorrection) {
114548
115568
  return {
114549
- rotate: true,
114550
- reason: `~${this.estimatedTokens} tokens (limit: ${this.cfg.maxTokensEstimate})`
115569
+ isQuestion: true,
115570
+ questions,
115571
+ correctionPrompt: this.buildCorrectionPrompt(questions)
114551
115572
  };
114552
115573
  }
114553
- return { rotate: false };
115574
+ return { isQuestion: true, questions };
115575
+ }
115576
+ extractQuestions(text7) {
115577
+ const questions = [];
115578
+ const questionLines = text7.split("\n").filter((line) => {
115579
+ const trimmed = line.trim();
115580
+ return trimmed.endsWith("?") && trimmed.length > 10;
115581
+ });
115582
+ questions.push(...questionLines.map((q) => q.trim()));
115583
+ const indirectPatterns = [
115584
+ /(?:could you|would you|can you|do you want|should I|shall I|do you prefer|which (?:one|option)|please (?:confirm|specify|clarify|tell me|let me know))/gi,
115585
+ /(?:voulez-vous|souhaitez-vous|préférez-vous|dois-je|faut-il|quel(?:le)? (?:est|sont)|pouvez-vous (?:me dire|confirmer|préciser))/gi
115586
+ ];
115587
+ for (const pattern of indirectPatterns) {
115588
+ const matches = text7.match(pattern);
115589
+ if (matches) {
115590
+ questions.push(...matches.map((m) => m.trim()));
115591
+ }
115592
+ }
115593
+ return questions;
115594
+ }
115595
+ buildCorrectionPrompt(questions) {
115596
+ return `[System \u2014 Autonomous Mode Correction]
115597
+ You are in AUTONOMOUS mode. Do NOT ask questions \u2014 take action instead.
115598
+
115599
+ You asked: ${questions.slice(0, 3).map((q) => `"${q}"`).join(", ")}
115600
+
115601
+ Instead of asking:
115602
+ - Make a reasonable decision based on available information
115603
+ - If multiple options exist, choose the most common/safe one
115604
+ - If information is missing, use your tools to find it (web_search, file_read, etc.)
115605
+ - If truly blocked, document what you need and move on to the next task
115606
+
115607
+ DO NOT ask any more questions. Take action now.`;
115608
+ }
115609
+ /** Reset counter */
115610
+ reset() {
115611
+ this.questionCount = 0;
115612
+ }
115613
+ };
115614
+ }
115615
+ });
115616
+
115617
+ // ../agent/src/safety/completion-detector.ts
115618
+ var CompletionDetector;
115619
+ var init_completion_detector = __esm({
115620
+ "../agent/src/safety/completion-detector.ts"() {
115621
+ "use strict";
115622
+ CompletionDetector = class {
115623
+ noToolCallStreak = 0;
115624
+ minNoToolStreak;
115625
+ constructor(opts) {
115626
+ this.minNoToolStreak = opts?.minNoToolStreak ?? 2;
114554
115627
  }
114555
- // ── Rotation helpers ────────────────────────────────────────────────
114556
115628
  /**
114557
- * Build a compact summary for context rotation.
114558
- *
114559
- * Strategy:
114560
- * - Preserve the system message verbatim
114561
- * - Summarize the middle messages into a compact block
114562
- * - Keep the last N recent messages verbatim
114563
- *
114564
- * Returns the summary text to inject as a single assistant message
114565
- * between the system prompt and the recent messages.
115629
+ * Check if the agent has truly completed its task.
115630
+ * Requires both heuristic indicators and an explicit signal.
114566
115631
  */
114567
- buildRotationSummary(messages) {
114568
- const keepCount = this.cfg.recentToKeep;
114569
- const system = messages.find((m) => m.role === "system");
114570
- const nonSystem = messages.filter((m) => m.role !== "system");
114571
- const recent = nonSystem.slice(-keepCount);
114572
- const middle = nonSystem.slice(0, Math.max(0, nonSystem.length - keepCount));
114573
- const parts = [];
114574
- if (system) {
114575
- parts.push("[System prompt preserved]");
114576
- }
114577
- if (middle.length > 0) {
114578
- const toolCalls = middle.filter(
114579
- (m) => m.content.includes("tool_use") || m.content.includes("function_call") || m.content.includes("tool_result")
114580
- ).length;
114581
- const findings = middle.filter(
114582
- (m) => m.content.includes("finding") || m.content.includes("result") || m.content.includes("discovered")
114583
- ).length;
114584
- const errors = middle.filter(
114585
- (m) => m.content.includes("error") || m.content.includes("failed")
114586
- ).length;
114587
- parts.push(
114588
- `[${middle.length} earlier messages summarized: ${toolCalls} tool interactions, ${findings} results/findings, ${errors} errors]`
114589
- );
115632
+ check(assistantMessage, hadToolCalls) {
115633
+ if (hadToolCalls) {
115634
+ this.noToolCallStreak = 0;
115635
+ } else {
115636
+ this.noToolCallStreak++;
114590
115637
  }
114591
- parts.push(`[${recent.length} recent messages preserved below]`);
114592
- return parts.join("\n");
115638
+ const heuristicScore = this.computeHeuristic(assistantMessage, hadToolCalls);
115639
+ const hasExplicitSignal = this.hasExplicitCompletionSignal(assistantMessage);
115640
+ const isComplete = heuristicScore >= 0.7 && hasExplicitSignal;
115641
+ return {
115642
+ isComplete,
115643
+ heuristicScore,
115644
+ hasExplicitSignal,
115645
+ reason: isComplete ? "Both heuristic and explicit signals indicate completion" : !hasExplicitSignal ? "No explicit completion signal found" : `Heuristic score too low (${heuristicScore.toFixed(2)})`
115646
+ };
115647
+ }
115648
+ computeHeuristic(message, hadToolCalls) {
115649
+ let score = 0;
115650
+ if (!hadToolCalls) score += 0.3;
115651
+ if (this.noToolCallStreak >= this.minNoToolStreak) score += 0.3;
115652
+ const conclusionPatterns = [
115653
+ /(?:in summary|en résumé|to summarize|pour résumer|in conclusion|en conclusion)/i,
115654
+ /(?:here are the (?:results|findings)|voici les (?:résultats|conclusions))/i,
115655
+ /(?:the (?:task|mission|objective|work) is (?:complete|done|finished))/i,
115656
+ /(?:la (?:tâche|mission) est (?:terminée|complète|finie))/i,
115657
+ /(?:I have (?:completed|finished|done)|j'ai (?:terminé|fini|complété))/i
115658
+ ];
115659
+ const conclusionMatches = conclusionPatterns.filter((p8) => p8.test(message)).length;
115660
+ score += Math.min(conclusionMatches * 0.15, 0.4);
115661
+ return Math.min(score, 1);
115662
+ }
115663
+ hasExplicitCompletionSignal(message) {
115664
+ const explicitSignals = [
115665
+ /\[COMPLETE\]/i,
115666
+ /\[DONE\]/i,
115667
+ /\[TASK_COMPLETE\]/i,
115668
+ /\[MISSION_COMPLETE\]/i,
115669
+ /EXIT_SIGNAL:\s*COMPLETE/i,
115670
+ // JSON output with findings (structured output = done)
115671
+ /^\s*\[[\s\S]*"type"[\s\S]*"title"[\s\S]*"confidence"[\s\S]*\]\s*$/m,
115672
+ // Markdown report header (report = done)
115673
+ /^#\s+.*(?:Report|Rapport|Results|Résultats)/m
115674
+ ];
115675
+ return explicitSignals.some((p8) => p8.test(message));
114593
115676
  }
114594
- // ── Lifecycle ───────────────────────────────────────────────────────
114595
- /** Reset counters after rotation. */
115677
+ /** Reset state */
114596
115678
  reset() {
114597
- this.messageCount = 0;
114598
- this.estimatedTokens = 0;
115679
+ this.noToolCallStreak = 0;
114599
115680
  }
114600
- /** Get current stats (useful for logging/telemetry). */
114601
- getStats() {
115681
+ };
115682
+ }
115683
+ });
115684
+
115685
+ // ../agent/src/safety/handoff-generator.ts
115686
+ var HandoffGenerator;
115687
+ var init_handoff_generator = __esm({
115688
+ "../agent/src/safety/handoff-generator.ts"() {
115689
+ "use strict";
115690
+ HandoffGenerator = class {
115691
+ /**
115692
+ * Generate a handoff document from the agent's message history.
115693
+ * This is a heuristic extraction — no LLM call needed.
115694
+ */
115695
+ generate(opts) {
115696
+ const { objective, messages, toolCalls, exitReason, costUsd } = opts;
115697
+ const accomplished = this.extractAccomplished(messages);
115698
+ const remaining = this.extractRemaining(messages, exitReason);
115699
+ const currentState = this.buildCurrentState(toolCalls);
115700
+ const decisions = this.extractDecisions(messages);
115701
+ const errors = toolCalls.filter((tc) => tc.isError).map((tc) => ({ error: `${tc.name}: ${tc.result.substring(0, 200)}`, resolution: "Attempted" }));
115702
+ const toolUsage = {};
115703
+ for (const tc of toolCalls) {
115704
+ toolUsage[tc.name] = (toolUsage[tc.name] || 0) + 1;
115705
+ }
115706
+ const nextSteps = this.suggestNextSteps(remaining, errors, exitReason);
114602
115707
  return {
114603
- messageCount: this.messageCount,
114604
- estimatedTokens: this.estimatedTokens
115708
+ objective,
115709
+ accomplished,
115710
+ remaining,
115711
+ currentState,
115712
+ decisions,
115713
+ errors,
115714
+ nextSteps,
115715
+ toolUsage,
115716
+ costUsd,
115717
+ timestamp: Date.now()
114605
115718
  };
114606
115719
  }
115720
+ /** Convert handoff to Markdown for storage/display */
115721
+ toMarkdown(handoff) {
115722
+ const sections = [
115723
+ `# Session Handoff`,
115724
+ ``,
115725
+ `**Objective:** ${handoff.objective}`,
115726
+ `**Cost:** $${handoff.costUsd.toFixed(4)}`,
115727
+ `**Date:** ${new Date(handoff.timestamp).toISOString()}`,
115728
+ ``,
115729
+ `## Accomplished`,
115730
+ ...handoff.accomplished.map((a) => `- ${a}`),
115731
+ ``,
115732
+ `## Remaining`,
115733
+ ...handoff.remaining.map((r) => `- ${r}`),
115734
+ ``,
115735
+ `## Current State`,
115736
+ handoff.currentState,
115737
+ ``,
115738
+ `## Decisions`,
115739
+ ...handoff.decisions.map((d) => `- **${d.decision}**: ${d.reason}`),
115740
+ ``,
115741
+ `## Errors (${handoff.errors.length})`,
115742
+ ...handoff.errors.map((e) => `- ${e.error}`),
115743
+ ``,
115744
+ `## Next Steps`,
115745
+ ...handoff.nextSteps.map((s) => `- ${s}`),
115746
+ ``,
115747
+ `## Tool Usage`,
115748
+ ...Object.entries(handoff.toolUsage).map(([name, count]) => `- ${name}: ${count}x`)
115749
+ ];
115750
+ return sections.join("\n");
115751
+ }
115752
+ extractAccomplished(messages) {
115753
+ const accomplished = [];
115754
+ for (const msg of messages) {
115755
+ if (msg.role !== "assistant") continue;
115756
+ const lines = msg.content.split("\n");
115757
+ for (const line of lines) {
115758
+ if (/(?:✅|done|completed|finished|created|wrote|found|discovered|built|implemented)/i.test(line) && line.length > 10 && line.length < 200) {
115759
+ accomplished.push(line.replace(/^[-*•]\s*/, "").trim());
115760
+ }
115761
+ }
115762
+ }
115763
+ return accomplished.slice(0, 10);
115764
+ }
115765
+ extractRemaining(messages, exitReason) {
115766
+ const remaining = [];
115767
+ if (exitReason === "max_iterations") remaining.push("Max iterations reached \u2014 work may be incomplete");
115768
+ if (exitReason === "budget_exceeded") remaining.push("Budget exhausted \u2014 work may be incomplete");
115769
+ if (exitReason === "aborted") remaining.push("Execution was aborted");
115770
+ const lastAssistant = [...messages].reverse().find((m) => m.role === "assistant");
115771
+ if (lastAssistant) {
115772
+ const todoPattern = /(?:todo|remaining|still need|next|à faire|reste)/i;
115773
+ const lines = lastAssistant.content.split("\n");
115774
+ for (const line of lines) {
115775
+ if (todoPattern.test(line) && line.length > 10 && line.length < 200) {
115776
+ remaining.push(line.replace(/^[-*•]\s*/, "").trim());
115777
+ }
115778
+ }
115779
+ }
115780
+ return remaining.slice(0, 10);
115781
+ }
115782
+ buildCurrentState(toolCalls) {
115783
+ const filesWritten = toolCalls.filter((tc) => tc.name === "file_write").map((tc) => String(tc.args.path || "")).filter(Boolean);
115784
+ const commandsRun = toolCalls.filter((tc) => tc.name === "shell_exec").map((tc) => String(tc.args.command || "").substring(0, 100)).filter(Boolean);
115785
+ let state = "";
115786
+ if (filesWritten.length > 0) state += `Files modified: ${filesWritten.join(", ")}
115787
+ `;
115788
+ if (commandsRun.length > 0) state += `Commands run: ${commandsRun.slice(-5).join("; ")}`;
115789
+ return state || "No filesystem changes detected.";
115790
+ }
115791
+ extractDecisions(messages) {
115792
+ const decisions = [];
115793
+ for (const msg of messages) {
115794
+ if (msg.role !== "assistant") continue;
115795
+ const patterns = [
115796
+ /I (?:chose|decided|selected|went with|picked) (.{10,80}) (?:because|since|as|due to) (.{10,120})/gi,
115797
+ /(?:j'ai choisi|j'ai décidé|j'ai opté pour) (.{10,80}) (?:car|parce que|puisque) (.{10,120})/gi
115798
+ ];
115799
+ for (const pattern of patterns) {
115800
+ let match;
115801
+ while ((match = pattern.exec(msg.content)) !== null && decisions.length < 5) {
115802
+ decisions.push({ decision: match[1].trim(), reason: match[2].trim() });
115803
+ }
115804
+ }
115805
+ }
115806
+ return decisions;
115807
+ }
115808
+ suggestNextSteps(remaining, errors, exitReason) {
115809
+ const steps = [];
115810
+ if (remaining.length > 0) steps.push(`Continue with: ${remaining[0]}`);
115811
+ if (errors.length > 0) steps.push(`Investigate ${errors.length} error(s) from previous session`);
115812
+ if (exitReason === "budget_exceeded") steps.push("Consider increasing budget for next run");
115813
+ if (exitReason === "max_iterations") steps.push("Consider increasing max iterations or simplifying the objective");
115814
+ if (steps.length === 0) steps.push("Review results and plan next action");
115815
+ return steps;
115816
+ }
114607
115817
  };
114608
115818
  }
114609
115819
  });
@@ -114615,16 +115825,19 @@ var init_safety = __esm({
114615
115825
  init_stuck_detector();
114616
115826
  init_circuit_breaker();
114617
115827
  init_context_rotator();
115828
+ init_question_detector();
115829
+ init_completion_detector();
115830
+ init_handoff_generator();
114618
115831
  }
114619
115832
  });
114620
115833
 
114621
115834
  // ../agent/src/mission/tool-factory.ts
114622
115835
  import { randomUUID as randomUUID33 } from "node:crypto";
114623
- import { writeFileSync as writeFileSync7, mkdirSync as mkdirSync5, existsSync as existsSync10 } from "node:fs";
115836
+ import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, existsSync as existsSync11 } from "node:fs";
114624
115837
  import { homedir as homedir4 } from "node:os";
114625
- import { join as join16 } from "node:path";
115838
+ import { join as join17 } from "node:path";
114626
115839
  function getDefaultToolsDir() {
114627
- return join16(homedir4(), ".nestor", "tools");
115840
+ return join17(homedir4(), ".nestor", "tools");
114628
115841
  }
114629
115842
  function createDefaultToolFactory(adapter, toolsDir) {
114630
115843
  return new ToolFactory(adapter, toolsDir ?? getDefaultToolsDir());
@@ -114637,7 +115850,7 @@ var init_tool_factory = __esm({
114637
115850
  constructor(adapter, toolsDir) {
114638
115851
  this.adapter = adapter;
114639
115852
  this.toolsDir = toolsDir;
114640
- if (!existsSync10(toolsDir)) mkdirSync5(toolsDir, { recursive: true });
115853
+ if (!existsSync11(toolsDir)) mkdirSync6(toolsDir, { recursive: true });
114641
115854
  }
114642
115855
  /**
114643
115856
  * Generate a new tool from a natural language description.
@@ -114814,13 +116027,13 @@ Fix the tool code so it passes its test. Respond ONLY with valid JSON:
114814
116027
  * Save tool to disk: source, compiled JS, test, and manifest.
114815
116028
  */
114816
116029
  saveTool(tool) {
114817
- const toolDir = join16(this.toolsDir, tool.name);
114818
- if (!existsSync10(toolDir)) mkdirSync5(toolDir, { recursive: true });
114819
- writeFileSync7(join16(toolDir, "tool.ts"), tool.code);
114820
- writeFileSync7(join16(toolDir, "tool.js"), tool.compiledCode);
114821
- writeFileSync7(join16(toolDir, "test.js"), tool.testCode);
114822
- writeFileSync7(
114823
- join16(toolDir, "manifest.json"),
116030
+ const toolDir = join17(this.toolsDir, tool.name);
116031
+ if (!existsSync11(toolDir)) mkdirSync6(toolDir, { recursive: true });
116032
+ writeFileSync8(join17(toolDir, "tool.ts"), tool.code);
116033
+ writeFileSync8(join17(toolDir, "tool.js"), tool.compiledCode);
116034
+ writeFileSync8(join17(toolDir, "test.js"), tool.testCode);
116035
+ writeFileSync8(
116036
+ join17(toolDir, "manifest.json"),
114824
116037
  JSON.stringify(
114825
116038
  {
114826
116039
  id: tool.id,
@@ -115395,13 +116608,172 @@ Analyze and return the JSON structure described in the system prompt.`;
115395
116608
  }
115396
116609
  });
115397
116610
 
116611
+ // ../agent/src/mission/capability-research.ts
116612
+ var capability_research_exports = {};
116613
+ __export(capability_research_exports, {
116614
+ CapabilityResearchEngine: () => CapabilityResearchEngine
116615
+ });
116616
+ var CapabilityResearchEngine;
116617
+ var init_capability_research = __esm({
116618
+ "../agent/src/mission/capability-research.ts"() {
116619
+ "use strict";
116620
+ CapabilityResearchEngine = class {
116621
+ constructor(adapter, orchestratorStore) {
116622
+ this.adapter = adapter;
116623
+ this.orchestratorStore = orchestratorStore;
116624
+ }
116625
+ cache = /* @__PURE__ */ new Map();
116626
+ /**
116627
+ * Research capabilities for a mission type.
116628
+ * Returns cached results if still valid, otherwise does fresh research.
116629
+ */
116630
+ async research(type, objective) {
116631
+ const cached = this.cache.get(type);
116632
+ if (cached && Date.now() - cached.timestamp < cached.ttlMs) {
116633
+ return cached;
116634
+ }
116635
+ if (this.orchestratorStore) {
116636
+ const orch = this.orchestratorStore.get(type);
116637
+ if (orch.toolRecommendations.length > 0 && orch.missionCount > 3) {
116638
+ const fromOrch = {
116639
+ domain: type,
116640
+ recommendedTools: orch.toolRecommendations.map((t) => ({
116641
+ name: t,
116642
+ description: "",
116643
+ source: "builtin",
116644
+ priority: "recommended"
116645
+ })),
116646
+ methodologies: [],
116647
+ pitfalls: orch.knowledgeBase.failedPatterns.map(
116648
+ (p8) => `Previously failed pattern: ${p8.subObjectiveNames.join(" \u2192 ")} (used ${p8.usageCount} time${p8.usageCount > 1 ? "s" : ""}, score: ${p8.score.toFixed(2)})`
116649
+ ),
116650
+ resources: [],
116651
+ timestamp: Date.now(),
116652
+ ttlMs: 7 * 24 * 60 * 60 * 1e3
116653
+ };
116654
+ this.cache.set(type, fromOrch);
116655
+ return fromOrch;
116656
+ }
116657
+ }
116658
+ const result = await this.doResearch(type, objective);
116659
+ this.cache.set(type, result);
116660
+ return result;
116661
+ }
116662
+ async doResearch(type, objective) {
116663
+ const domainHints = this.getDomainHints(type);
116664
+ const prompt = `You are a capability researcher for an AI agent platform.
116665
+ Given a mission type and objective, recommend the best tools, methodologies, and resources.
116666
+
116667
+ Mission type: ${type}
116668
+ Objective: ${objective}
116669
+
116670
+ Based on your knowledge of ${type} best practices, respond with JSON:
116671
+ {
116672
+ "recommendedTools": [
116673
+ { "name": "tool_name", "description": "what it does", "source": "builtin|npm|github|api", "url": "optional", "priority": "essential|recommended|optional" }
116674
+ ],
116675
+ "methodologies": [
116676
+ { "name": "method name", "description": "brief description", "steps": ["step1", "step2"] }
116677
+ ],
116678
+ "pitfalls": ["common mistake 1", "common mistake 2"],
116679
+ "resources": [
116680
+ { "title": "resource title", "url": "https://...", "type": "article|repo|docs|tutorial" }
116681
+ ]
116682
+ }
116683
+
116684
+ ${domainHints}
116685
+
116686
+ Focus on tools that work locally (no cloud-only services) and can be used by an AI agent.
116687
+ Respond ONLY with JSON.`;
116688
+ try {
116689
+ const response = await this.adapter.chat([
116690
+ {
116691
+ role: "system",
116692
+ content: "You are a capability researcher. Respond only with JSON."
116693
+ },
116694
+ { role: "user", content: prompt }
116695
+ ]);
116696
+ let jsonStr = response.content;
116697
+ const codeBlockMatch = jsonStr.match(/```(?:json)?\s*([\s\S]*?)```/);
116698
+ if (codeBlockMatch) jsonStr = codeBlockMatch[1].trim();
116699
+ const match = jsonStr.match(/\{[\s\S]*\}/);
116700
+ if (match) jsonStr = match[0];
116701
+ const parsed = JSON.parse(jsonStr);
116702
+ return {
116703
+ domain: type,
116704
+ recommendedTools: Array.isArray(parsed.recommendedTools) ? parsed.recommendedTools : [],
116705
+ methodologies: Array.isArray(parsed.methodologies) ? parsed.methodologies : [],
116706
+ pitfalls: Array.isArray(parsed.pitfalls) ? parsed.pitfalls : [],
116707
+ resources: Array.isArray(parsed.resources) ? parsed.resources : [],
116708
+ timestamp: Date.now(),
116709
+ ttlMs: 7 * 24 * 60 * 60 * 1e3
116710
+ // 7 days
116711
+ };
116712
+ } catch {
116713
+ return {
116714
+ domain: type,
116715
+ recommendedTools: [],
116716
+ methodologies: [],
116717
+ pitfalls: [],
116718
+ resources: [],
116719
+ timestamp: Date.now(),
116720
+ ttlMs: 24 * 60 * 60 * 1e3
116721
+ // retry after 1 day on failure
116722
+ };
116723
+ }
116724
+ }
116725
+ /**
116726
+ * Inject research results into the mission planning context.
116727
+ * Returns a context block to prepend to the decomposition prompt.
116728
+ */
116729
+ toContextBlock(result) {
116730
+ if (result.recommendedTools.length === 0 && result.methodologies.length === 0) {
116731
+ return "";
116732
+ }
116733
+ const sections = ["CAPABILITY RESEARCH (pre-mission analysis):"];
116734
+ if (result.recommendedTools.length > 0) {
116735
+ sections.push("Recommended tools:");
116736
+ for (const t of result.recommendedTools.slice(0, 8)) {
116737
+ sections.push(` - ${t.name} (${t.priority}): ${t.description}`);
116738
+ }
116739
+ }
116740
+ if (result.methodologies.length > 0) {
116741
+ sections.push("Recommended methodology:");
116742
+ const best = result.methodologies[0];
116743
+ sections.push(` ${best.name}: ${best.steps.join(" \u2192 ")}`);
116744
+ }
116745
+ if (result.pitfalls.length > 0) {
116746
+ sections.push("Known pitfalls to avoid:");
116747
+ for (const p8 of result.pitfalls.slice(0, 5)) {
116748
+ sections.push(` - ${p8}`);
116749
+ }
116750
+ }
116751
+ return sections.join("\n");
116752
+ }
116753
+ // ─── Private Helpers ────────────────────────────────────────────────
116754
+ getDomainHints(type) {
116755
+ const hints = {
116756
+ osint: "For osint missions, consider: OSINT tools like Maltego alternatives, Wayback Machine, WHOIS, social media scrapers, SIRENE, public records databases",
116757
+ research: "For research missions, consider: Google Scholar, arxiv, Semantic Scholar, news aggregators, RSS feeds, citation graphs",
116758
+ code: "For code missions, consider: linters, formatters, test frameworks, build tools, CI/CD, static analysis",
116759
+ audit: "For audit missions, consider: static analyzers, dependency checkers, fuzzing tools, CVE databases, SAST/DAST",
116760
+ creation: "For creation missions, consider: writing assistants, image generators, template engines, CMS tools, Markdown processors",
116761
+ analysis: "For analysis missions, consider: data processing libraries, visualization tools, statistical frameworks, ML frameworks",
116762
+ custom: "Consider what specialized tools and methods would best serve this specific objective."
116763
+ };
116764
+ return hints[type] || hints.custom;
116765
+ }
116766
+ };
116767
+ }
116768
+ });
116769
+
115398
116770
  // ../agent/src/mission/orchestrator-store.ts
115399
116771
  var orchestrator_store_exports = {};
115400
116772
  __export(orchestrator_store_exports, {
115401
116773
  OrchestratorStore: () => OrchestratorStore
115402
116774
  });
115403
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "node:fs";
115404
- import { join as join17 } from "node:path";
116775
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "node:fs";
116776
+ import { join as join18 } from "node:path";
115405
116777
  import { homedir as homedir5 } from "node:os";
115406
116778
  function arraysEqual(a, b) {
115407
116779
  if (a.length !== b.length) return false;
@@ -115411,11 +116783,11 @@ var ORCHESTRATORS_DIR, OrchestratorStore;
115411
116783
  var init_orchestrator_store = __esm({
115412
116784
  "../agent/src/mission/orchestrator-store.ts"() {
115413
116785
  "use strict";
115414
- ORCHESTRATORS_DIR = join17(homedir5(), ".nestor", "orchestrators");
116786
+ ORCHESTRATORS_DIR = join18(homedir5(), ".nestor", "orchestrators");
115415
116787
  OrchestratorStore = class {
115416
116788
  constructor(baseDir = ORCHESTRATORS_DIR) {
115417
116789
  this.baseDir = baseDir;
115418
- if (!existsSync11(baseDir)) mkdirSync6(baseDir, { recursive: true });
116790
+ if (!existsSync12(baseDir)) mkdirSync7(baseDir, { recursive: true });
115419
116791
  }
115420
116792
  cache = /* @__PURE__ */ new Map();
115421
116793
  /**
@@ -115425,9 +116797,9 @@ var init_orchestrator_store = __esm({
115425
116797
  get(type) {
115426
116798
  if (this.cache.has(type)) return this.cache.get(type);
115427
116799
  const filePath = this.filePath(type);
115428
- if (existsSync11(filePath)) {
116800
+ if (existsSync12(filePath)) {
115429
116801
  try {
115430
- const data = JSON.parse(readFileSync15(filePath, "utf-8"));
116802
+ const data = JSON.parse(readFileSync16(filePath, "utf-8"));
115431
116803
  this.cache.set(type, data);
115432
116804
  return data;
115433
116805
  } catch {
@@ -115525,7 +116897,7 @@ var init_orchestrator_store = __esm({
115525
116897
  ];
115526
116898
  return types.map((type) => {
115527
116899
  const filePath = this.filePath(type);
115528
- if (!existsSync11(filePath)) return null;
116900
+ if (!existsSync12(filePath)) return null;
115529
116901
  const orch = this.get(type);
115530
116902
  return {
115531
116903
  type: orch.type,
@@ -115562,12 +116934,12 @@ var init_orchestrator_store = __esm({
115562
116934
  }
115563
116935
  // ─── Private ──────────────────────────────────────────────────────────
115564
116936
  filePath(type) {
115565
- return join17(this.baseDir, type, "orchestrator.json");
116937
+ return join18(this.baseDir, type, "orchestrator.json");
115566
116938
  }
115567
116939
  save(orch) {
115568
- const dir = join17(this.baseDir, orch.type);
115569
- if (!existsSync11(dir)) mkdirSync6(dir, { recursive: true });
115570
- writeFileSync8(this.filePath(orch.type), JSON.stringify(orch, null, 2));
116940
+ const dir = join18(this.baseDir, orch.type);
116941
+ if (!existsSync12(dir)) mkdirSync7(dir, { recursive: true });
116942
+ writeFileSync9(this.filePath(orch.type), JSON.stringify(orch, null, 2));
115571
116943
  this.cache.set(orch.type, orch);
115572
116944
  }
115573
116945
  createDefault(type) {
@@ -115782,8 +117154,19 @@ var init_controller = __esm({
115782
117154
  async plan(input, options) {
115783
117155
  const tenantId = options?.tenantId ?? "default";
115784
117156
  const type = this.detectMissionType(input);
117157
+ let capabilityContext = "";
117158
+ try {
117159
+ const { CapabilityResearchEngine: CapabilityResearchEngine2 } = await Promise.resolve().then(() => (init_capability_research(), capability_research_exports));
117160
+ const researcher = new CapabilityResearchEngine2(
117161
+ this.agentFactory.getDefaultAdapter()
117162
+ // orchestratorStore could be wired here if available on the controller
117163
+ );
117164
+ const research = await researcher.research(type, input);
117165
+ capabilityContext = researcher.toContextBlock(research);
117166
+ } catch {
117167
+ }
115785
117168
  const similar = await this.inventory.findSimilarMissions(input);
115786
- const decomposition = await this.decompose(input, type, similar);
117169
+ const decomposition = await this.decompose(input, type, similar, capabilityContext);
115787
117170
  const defaultMaxIterations = (() => {
115788
117171
  switch (type) {
115789
117172
  case "osint":
@@ -116151,7 +117534,7 @@ Report generation failed. Mission completed with ${mission.findings.length} find
116151
117534
  return "custom";
116152
117535
  }
116153
117536
  // ─── Decompose ─────────────────────────────────────────────────────
116154
- async decompose(input, type, similar) {
117537
+ async decompose(input, type, similar, capabilityContext) {
116155
117538
  const methodGuide = {
116156
117539
  osint: `For OSINT missions, structure sub-objectives by METHOD, not by category:
116157
117540
  1. Entity disambiguation \u2014 multi-engine search to find the correct subject among homonyms
@@ -116214,7 +117597,8 @@ ${methodGuide[type] || methodGuide.custom}
116214
117597
  ${similar.length > 0 ? `Similar past missions found (learn from their structure):
116215
117598
  ${similar.map((m) => `- ${m.title} (${m.subObjectives.length} sub-objectives, score: ${m.evaluation?.overall ?? "N/A"})`).join("\n")}` : "No similar past missions found."}
116216
117599
 
116217
- The "type" field of each sub-objective should be one of:
117600
+ ${capabilityContext ? `${capabilityContext}
117601
+ ` : ""}The "type" field of each sub-objective should be one of:
116218
117602
  search \u2014 broad web search with multiple engines
116219
117603
  scrape \u2014 detailed scrape of specific page(s)
116220
117604
  analyze \u2014 analysis of data already collected (no external fetch)
@@ -116951,7 +118335,7 @@ var init_inventory = __esm({
116951
118335
  /** Find similar past missions for planning. */
116952
118336
  async findSimilarMissions(objective) {
116953
118337
  try {
116954
- return this.store.searchMissions(objective, 3);
118338
+ return this.store.searchMissions(objective, { limit: 3 });
116955
118339
  } catch {
116956
118340
  return [];
116957
118341
  }
@@ -117029,8 +118413,8 @@ var init_inventory = __esm({
117029
118413
  });
117030
118414
 
117031
118415
  // ../agent/src/mission/mcp-factory.ts
117032
- import { writeFileSync as writeFileSync9, mkdirSync as mkdirSync7, existsSync as existsSync12 } from "node:fs";
117033
- import { join as join18 } from "node:path";
118416
+ import { writeFileSync as writeFileSync10, mkdirSync as mkdirSync8, existsSync as existsSync13 } from "node:fs";
118417
+ import { join as join19 } from "node:path";
117034
118418
  var McpFactory;
117035
118419
  var init_mcp_factory = __esm({
117036
118420
  "../agent/src/mission/mcp-factory.ts"() {
@@ -117038,7 +118422,7 @@ var init_mcp_factory = __esm({
117038
118422
  McpFactory = class {
117039
118423
  constructor(mcpDir) {
117040
118424
  this.mcpDir = mcpDir;
117041
- if (!existsSync12(mcpDir)) mkdirSync7(mcpDir, { recursive: true });
118425
+ if (!existsSync13(mcpDir)) mkdirSync8(mcpDir, { recursive: true });
117042
118426
  }
117043
118427
  /**
117044
118428
  * Generate an MCP server that exposes one or more custom tools.
@@ -117046,13 +118430,13 @@ var init_mcp_factory = __esm({
117046
118430
  */
117047
118431
  generate(tools, serverName) {
117048
118432
  const safeName = this.sanitizeName(serverName);
117049
- const serverDir = join18(this.mcpDir, safeName);
117050
- if (!existsSync12(serverDir)) mkdirSync7(serverDir, { recursive: true });
118433
+ const serverDir = join19(this.mcpDir, safeName);
118434
+ if (!existsSync13(serverDir)) mkdirSync8(serverDir, { recursive: true });
117051
118435
  const serverCode = this.generateServerCode(tools, safeName);
117052
- const entrypoint = join18(serverDir, "server.mjs");
117053
- writeFileSync9(entrypoint, serverCode);
117054
- writeFileSync9(
117055
- join18(serverDir, "package.json"),
118436
+ const entrypoint = join19(serverDir, "server.mjs");
118437
+ writeFileSync10(entrypoint, serverCode);
118438
+ writeFileSync10(
118439
+ join19(serverDir, "package.json"),
117056
118440
  JSON.stringify(
117057
118441
  {
117058
118442
  name: `@nestor/mcp-${safeName}`,
@@ -117066,11 +118450,11 @@ var init_mcp_factory = __esm({
117066
118450
  2
117067
118451
  )
117068
118452
  );
117069
- writeFileSync9(join18(serverDir, "Dockerfile"), this.generateDockerfile(safeName));
117070
- writeFileSync9(join18(serverDir, "README.md"), this.generateReadme(tools, safeName));
118453
+ writeFileSync10(join19(serverDir, "Dockerfile"), this.generateDockerfile(safeName));
118454
+ writeFileSync10(join19(serverDir, "README.md"), this.generateReadme(tools, safeName));
117071
118455
  for (const tool of tools) {
117072
118456
  const toolModuleCode = this.wrapToolAsModule(tool);
117073
- writeFileSync9(join18(serverDir, `${tool.name}.js`), toolModuleCode);
118457
+ writeFileSync10(join19(serverDir, `${tool.name}.js`), toolModuleCode);
117074
118458
  }
117075
118459
  return {
117076
118460
  id: safeName,
@@ -117304,8 +118688,8 @@ npx nestor-sh mcp add ${serverName} --command "node ${serverName}/server.mjs"
117304
118688
  // ../agent/src/mission/docker-deployer.ts
117305
118689
  import { execFile as execFile7 } from "node:child_process";
117306
118690
  import { promisify as promisify5 } from "node:util";
117307
- import { existsSync as existsSync13 } from "node:fs";
117308
- import { join as join19 } from "node:path";
118691
+ import { existsSync as existsSync14 } from "node:fs";
118692
+ import { join as join20 } from "node:path";
117309
118693
  var execFileAsync4, DockerDeployer;
117310
118694
  var init_docker_deployer = __esm({
117311
118695
  "../agent/src/mission/docker-deployer.ts"() {
@@ -117342,9 +118726,9 @@ var init_docker_deployer = __esm({
117342
118726
  * Returns the image name (tag).
117343
118727
  */
117344
118728
  async build(server) {
117345
- const serverDir = join19(server.entrypoint, "..");
118729
+ const serverDir = join20(server.entrypoint, "..");
117346
118730
  const imageName = `nestor-mcp-${server.name}:latest`;
117347
- if (!existsSync13(join19(serverDir, "Dockerfile"))) {
118731
+ if (!existsSync14(join20(serverDir, "Dockerfile"))) {
117348
118732
  throw new Error(`No Dockerfile found in ${serverDir}`);
117349
118733
  }
117350
118734
  const container = {
@@ -117567,6 +118951,7 @@ var init_mission = __esm({
117567
118951
  init_mcp_factory();
117568
118952
  init_docker_deployer();
117569
118953
  init_orchestrator_store();
118954
+ init_capability_research();
117570
118955
  }
117571
118956
  });
117572
118957
 
@@ -117817,6 +119202,15 @@ var init_intent = __esm({
117817
119202
  }
117818
119203
  });
117819
119204
 
119205
+ // ../agent/src/integrations/index.ts
119206
+ var init_integrations = __esm({
119207
+ "../agent/src/integrations/index.ts"() {
119208
+ "use strict";
119209
+ init_obsidian();
119210
+ init_n8n();
119211
+ }
119212
+ });
119213
+
117820
119214
  // ../agent/src/index.ts
117821
119215
  var src_exports3 = {};
117822
119216
  __export(src_exports3, {
@@ -117831,9 +119225,11 @@ __export(src_exports3, {
117831
119225
  AutoDowngradeAdapter: () => AutoDowngradeAdapter,
117832
119226
  AutoDowngradeMonitor: () => AutoDowngradeMonitor,
117833
119227
  BrowserManager: () => BrowserManager,
119228
+ CapabilityResearchEngine: () => CapabilityResearchEngine,
117834
119229
  CircuitBreaker: () => CircuitBreaker,
117835
119230
  ClaudeAdapter: () => ClaudeAdapter,
117836
119231
  CodeChunker: () => CodeChunker,
119232
+ CompletionDetector: () => CompletionDetector,
117837
119233
  ContextBuilder: () => ContextBuilder,
117838
119234
  ContextRotator: () => ContextRotator,
117839
119235
  DockerDeployer: () => DockerDeployer,
@@ -117847,11 +119243,13 @@ __export(src_exports3, {
117847
119243
  GeminiAdapter: () => GeminiAdapter,
117848
119244
  GeminiEmbeddingProvider: () => GeminiEmbeddingProvider,
117849
119245
  GrokAdapter: () => GrokAdapter,
119246
+ HandoffGenerator: () => HandoffGenerator,
117850
119247
  HotSwapAdapter: () => HotSwapAdapter,
117851
119248
  IntentAnalyzer: () => IntentAnalyzer,
117852
119249
  InventoryManager: () => InventoryManager,
117853
119250
  K8sExecutor: () => K8sExecutor,
117854
119251
  KnowledgeGraph: () => KnowledgeGraph,
119252
+ LayeredMemory: () => LayeredMemory,
117855
119253
  LocalEmbeddings: () => LocalEmbeddings,
117856
119254
  LocalSandbox: () => LocalSandbox,
117857
119255
  McpFactory: () => McpFactory,
@@ -117863,6 +119261,8 @@ __export(src_exports3, {
117863
119261
  MistralAdapter: () => MistralAdapter,
117864
119262
  MockLlmAdapter: () => MockLlmAdapter,
117865
119263
  ModelRouter: () => ModelRouter,
119264
+ N8nIntegration: () => N8nIntegration,
119265
+ ObsidianIntegration: () => ObsidianIntegration,
117866
119266
  OllamaAdapter: () => OllamaAdapter,
117867
119267
  OllamaEmbeddingProvider: () => OllamaEmbeddingProvider,
117868
119268
  OllamaEmbeddings: () => OllamaEmbeddings,
@@ -117874,6 +119274,7 @@ __export(src_exports3, {
117874
119274
  PluginManifestSchema: () => PluginManifestSchema,
117875
119275
  PluginSandbox: () => PluginSandbox,
117876
119276
  ProcessSandbox: () => ProcessSandbox,
119277
+ QuestionDetector: () => QuestionDetector,
117877
119278
  RagIndexer: () => RagIndexer,
117878
119279
  RagSearch: () => RagSearch,
117879
119280
  ReasonLogger: () => ReasonLogger,
@@ -118017,6 +119418,7 @@ var init_src5 = __esm({
118017
119418
  init_mission();
118018
119419
  init_meta_tool_factory();
118019
119420
  init_intent();
119421
+ init_integrations();
118020
119422
  }
118021
119423
  });
118022
119424
 
@@ -119286,8 +120688,8 @@ var init_experiments = __esm({
119286
120688
 
119287
120689
  // ../server/src/services/telemetry-anon.ts
119288
120690
  import { randomUUID as randomUUID38 } from "node:crypto";
119289
- import { existsSync as existsSync14, readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync8 } from "node:fs";
119290
- import { join as join20 } from "node:path";
120691
+ import { existsSync as existsSync15, readFileSync as readFileSync17, writeFileSync as writeFileSync11, mkdirSync as mkdirSync9 } from "node:fs";
120692
+ import { join as join21 } from "node:path";
119291
120693
  import { homedir as homedir6 } from "node:os";
119292
120694
  function getAnonTelemetry(config2) {
119293
120695
  if (!_anonInstance) {
@@ -119303,8 +120705,8 @@ var init_telemetry_anon = __esm({
119303
120705
  "../server/src/services/telemetry-anon.ts"() {
119304
120706
  "use strict";
119305
120707
  NESTOR_VERSION = "0.4.1";
119306
- DATA_DIR2 = join20(homedir6(), ".nestor");
119307
- ANON_ID_FILE = join20(DATA_DIR2, "anon-telemetry-id");
120708
+ DATA_DIR2 = join21(homedir6(), ".nestor");
120709
+ ANON_ID_FILE = join21(DATA_DIR2, "anon-telemetry-id");
119308
120710
  DEFAULT_ENDPOINT = "https://telemetry.nestor.sh/v1/events";
119309
120711
  FLUSH_BATCH_SIZE = 25;
119310
120712
  AnonTelemetryService = class {
@@ -119408,18 +120810,18 @@ var init_telemetry_anon = __esm({
119408
120810
  // ─── Private ──────────────────────────────────────────────────────────
119409
120811
  getOrCreateInstallId() {
119410
120812
  try {
119411
- if (existsSync14(ANON_ID_FILE)) {
119412
- const id = readFileSync16(ANON_ID_FILE, "utf-8").trim();
120813
+ if (existsSync15(ANON_ID_FILE)) {
120814
+ const id = readFileSync17(ANON_ID_FILE, "utf-8").trim();
119413
120815
  if (id.length > 0) return id;
119414
120816
  }
119415
120817
  } catch {
119416
120818
  }
119417
120819
  const newId = randomUUID38();
119418
120820
  try {
119419
- if (!existsSync14(DATA_DIR2)) {
119420
- mkdirSync8(DATA_DIR2, { recursive: true });
120821
+ if (!existsSync15(DATA_DIR2)) {
120822
+ mkdirSync9(DATA_DIR2, { recursive: true });
119421
120823
  }
119422
- writeFileSync10(ANON_ID_FILE, newId + "\n", "utf-8");
120824
+ writeFileSync11(ANON_ID_FILE, newId + "\n", "utf-8");
119423
120825
  } catch {
119424
120826
  }
119425
120827
  return newId;
@@ -149749,7 +151151,7 @@ var init_admin2 = __esm({
149749
151151
  "../server/src/routes/admin.ts"() {
149750
151152
  "use strict";
149751
151153
  init_rate_limit();
149752
- SERVER_VERSION3 = "2.7.1";
151154
+ SERVER_VERSION3 = "3.0.0";
149753
151155
  startTime3 = Date.now();
149754
151156
  }
149755
151157
  });
@@ -151437,19 +152839,19 @@ var init_providers = __esm({
151437
152839
 
151438
152840
  // ../server/src/services/key-vault.ts
151439
152841
  import { createCipheriv as createCipheriv2, createDecipheriv as createDecipheriv2, randomBytes as randomBytes7, scryptSync as scryptSync2 } from "node:crypto";
151440
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "node:fs";
151441
- import { join as join21, dirname as dirname8 } from "node:path";
152842
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "node:fs";
152843
+ import { join as join22, dirname as dirname8 } from "node:path";
151442
152844
  import { homedir as homedir7 } from "node:os";
151443
152845
  function getVaultKey() {
151444
- if (!existsSync15(KEY_FILE)) {
152846
+ if (!existsSync16(KEY_FILE)) {
151445
152847
  const dir = dirname8(KEY_FILE);
151446
- if (!existsSync15(dir)) {
151447
- mkdirSync9(dir, { recursive: true });
152848
+ if (!existsSync16(dir)) {
152849
+ mkdirSync10(dir, { recursive: true });
151448
152850
  }
151449
152851
  const masterSecret = randomBytes7(32).toString("hex");
151450
- writeFileSync11(KEY_FILE, masterSecret, { mode: 384 });
152852
+ writeFileSync12(KEY_FILE, masterSecret, { mode: 384 });
151451
152853
  }
151452
- const secret = readFileSync17(KEY_FILE, "utf-8").trim();
152854
+ const secret = readFileSync18(KEY_FILE, "utf-8").trim();
151453
152855
  return scryptSync2(secret, "nestor-vault-salt", 32);
151454
152856
  }
151455
152857
  function encryptApiKey(plainKey) {
@@ -151501,7 +152903,7 @@ var init_key_vault = __esm({
151501
152903
  "../server/src/services/key-vault.ts"() {
151502
152904
  "use strict";
151503
152905
  ALGORITHM2 = "aes-256-gcm";
151504
- KEY_FILE = join21(homedir7(), ".nestor", ".vault-key");
152906
+ KEY_FILE = join22(homedir7(), ".nestor", ".vault-key");
151505
152907
  PROVIDER_ENV_MAP = {
151506
152908
  claude: "ANTHROPIC_API_KEY",
151507
152909
  openai: "OPENAI_API_KEY",
@@ -151886,6 +153288,36 @@ function missionRoutes(store, logger) {
151886
153288
  next(err);
151887
153289
  }
151888
153290
  });
153291
+ router.get("/search", async (req, res, next) => {
153292
+ try {
153293
+ const q = req.query.q || "";
153294
+ const type = req.query.type;
153295
+ const status = req.query.status;
153296
+ const from2 = req.query.from;
153297
+ const to = req.query.to;
153298
+ const limit = req.query.limit ? parseInt(req.query.limit, 10) : 20;
153299
+ const results = store.searchMissions(q, { type, status, from: from2, to, limit });
153300
+ res.json({
153301
+ data: results,
153302
+ meta: {
153303
+ count: results.length,
153304
+ query: q,
153305
+ filters: { type, status, from: from2, to }
153306
+ }
153307
+ });
153308
+ } catch (err) {
153309
+ next(err);
153310
+ }
153311
+ });
153312
+ router.get("/stats", async (req, res, next) => {
153313
+ try {
153314
+ const tenantId = req.tenantId || "default";
153315
+ const stats = store.getMissionStats(tenantId);
153316
+ res.json({ data: stats });
153317
+ } catch (err) {
153318
+ next(err);
153319
+ }
153320
+ });
151889
153321
  router.get("/:id", async (req, res, next) => {
151890
153322
  try {
151891
153323
  const mission = store.getMission(req.params.id);
@@ -152456,8 +153888,8 @@ var init_seed_defaults = __esm({
152456
153888
 
152457
153889
  // ../server/src/app.ts
152458
153890
  import express from "express";
152459
- import { resolve as resolve13, join as join22 } from "node:path";
152460
- import { existsSync as existsSync16 } from "node:fs";
153891
+ import { resolve as resolve13, join as join23 } from "node:path";
153892
+ import { existsSync as existsSync17 } from "node:fs";
152461
153893
  import compression from "compression";
152462
153894
  import cors from "cors";
152463
153895
  function getStudioState() {
@@ -152787,7 +154219,7 @@ function createApp(config2) {
152787
154219
  }
152788
154220
  for (const dir of candidateDirs) {
152789
154221
  try {
152790
- if (existsSync16(join22(dir, "index.html"))) {
154222
+ if (existsSync17(join23(dir, "index.html"))) {
152791
154223
  studioDistDir = dir;
152792
154224
  break;
152793
154225
  }
@@ -152811,7 +154243,7 @@ function createApp(config2) {
152811
154243
  if (!studioState.enabled) {
152812
154244
  return res.status(403).json({ error: { code: "STUDIO_DISABLED", message: "Studio is disabled." } });
152813
154245
  }
152814
- res.sendFile(join22(studioDistDir, "index.html"));
154246
+ res.sendFile(join23(studioDistDir, "index.html"));
152815
154247
  });
152816
154248
  app.use("/studio", express.static(studioDistDir, { index: false }));
152817
154249
  app.use(express.static(studioDistDir, { index: false }));
@@ -152819,7 +154251,7 @@ function createApp(config2) {
152819
154251
  if (!studioState.enabled) {
152820
154252
  return res.status(403).json({ error: { code: "STUDIO_DISABLED", message: "Studio is disabled." } });
152821
154253
  }
152822
- res.sendFile(join22(studioDistDir, "index.html"));
154254
+ res.sendFile(join23(studioDistDir, "index.html"));
152823
154255
  });
152824
154256
  } else {
152825
154257
  app.get("/studio", (_req, res) => {
@@ -152847,7 +154279,7 @@ function createApp(config2) {
152847
154279
  if (!studioState.enabled) {
152848
154280
  return res.status(403).json({ error: { code: "STUDIO_DISABLED", message: "Studio is disabled." } });
152849
154281
  }
152850
- res.sendFile(join22(studioDistDir, "index.html"));
154282
+ res.sendFile(join23(studioDistDir, "index.html"));
152851
154283
  });
152852
154284
  }
152853
154285
  app.use(errorHandler());
@@ -152926,7 +154358,7 @@ var init_app = __esm({
152926
154358
  });
152927
154359
 
152928
154360
  // ../server/src/services/config-watcher.ts
152929
- import { watch, existsSync as existsSync17 } from "node:fs";
154361
+ import { watch, existsSync as existsSync18 } from "node:fs";
152930
154362
  import { readFile as readFile2 } from "node:fs/promises";
152931
154363
  var ConfigWatcher;
152932
154364
  var init_config_watcher = __esm({
@@ -152950,7 +154382,7 @@ var init_config_watcher = __esm({
152950
154382
  */
152951
154383
  async start() {
152952
154384
  await this.reload();
152953
- if (existsSync17(this.configPath)) {
154385
+ if (existsSync18(this.configPath)) {
152954
154386
  this.watcher = watch(this.configPath, { persistent: false }, (eventType) => {
152955
154387
  if (eventType === "change") {
152956
154388
  if (this.debounceTimer) clearTimeout(this.debounceTimer);
@@ -152986,7 +154418,7 @@ var init_config_watcher = __esm({
152986
154418
  // ─── Internal ───────────────────────────────────────────────────────
152987
154419
  async reload() {
152988
154420
  try {
152989
- if (!existsSync17(this.configPath)) {
154421
+ if (!existsSync18(this.configPath)) {
152990
154422
  console.warn("[config-watcher] Config file not found, keeping current config.");
152991
154423
  return;
152992
154424
  }
@@ -153030,8 +154462,8 @@ __export(telemetry_exports, {
153030
154462
  resetTelemetry: () => resetTelemetry
153031
154463
  });
153032
154464
  import { randomUUID as randomUUID42 } from "node:crypto";
153033
- import { existsSync as existsSync18, readFileSync as readFileSync18, writeFileSync as writeFileSync12, appendFileSync as appendFileSync2, mkdirSync as mkdirSync10 } from "node:fs";
153034
- import { join as join23 } from "node:path";
154465
+ import { existsSync as existsSync19, readFileSync as readFileSync19, writeFileSync as writeFileSync13, appendFileSync as appendFileSync2, mkdirSync as mkdirSync11 } from "node:fs";
154466
+ import { join as join24 } from "node:path";
153035
154467
  import { homedir as homedir8 } from "node:os";
153036
154468
  function getTelemetry(config2) {
153037
154469
  if (!_instance2) {
@@ -153046,10 +154478,10 @@ var VERSION2, DATA_DIR3, TELEMETRY_ID_FILE, TELEMETRY_LOG_FILE, DEFAULT_FLUSH_TH
153046
154478
  var init_telemetry2 = __esm({
153047
154479
  "../server/src/services/telemetry.ts"() {
153048
154480
  "use strict";
153049
- VERSION2 = "2.7.1";
153050
- DATA_DIR3 = join23(homedir8(), ".nestor");
153051
- TELEMETRY_ID_FILE = join23(DATA_DIR3, "telemetry-id");
153052
- TELEMETRY_LOG_FILE = join23(DATA_DIR3, "telemetry.jsonl");
154481
+ VERSION2 = "3.0.0";
154482
+ DATA_DIR3 = join24(homedir8(), ".nestor");
154483
+ TELEMETRY_ID_FILE = join24(DATA_DIR3, "telemetry-id");
154484
+ TELEMETRY_LOG_FILE = join24(DATA_DIR3, "telemetry.jsonl");
153053
154485
  DEFAULT_FLUSH_THRESHOLD = 10;
153054
154486
  TelemetryService = class {
153055
154487
  enabled;
@@ -153142,8 +154574,8 @@ var init_telemetry2 = __esm({
153142
154574
  // ─── Private ─────────────────────────────────────────────────────────
153143
154575
  getOrCreateInstallId() {
153144
154576
  try {
153145
- if (existsSync18(TELEMETRY_ID_FILE)) {
153146
- const id = readFileSync18(TELEMETRY_ID_FILE, "utf-8").trim();
154577
+ if (existsSync19(TELEMETRY_ID_FILE)) {
154578
+ const id = readFileSync19(TELEMETRY_ID_FILE, "utf-8").trim();
153147
154579
  if (id.length > 0) return id;
153148
154580
  }
153149
154581
  } catch {
@@ -153151,14 +154583,14 @@ var init_telemetry2 = __esm({
153151
154583
  const newId = randomUUID42();
153152
154584
  try {
153153
154585
  this.ensureDataDir();
153154
- writeFileSync12(TELEMETRY_ID_FILE, newId + "\n", "utf-8");
154586
+ writeFileSync13(TELEMETRY_ID_FILE, newId + "\n", "utf-8");
153155
154587
  } catch {
153156
154588
  }
153157
154589
  return newId;
153158
154590
  }
153159
154591
  ensureDataDir() {
153160
- if (!existsSync18(DATA_DIR3)) {
153161
- mkdirSync10(DATA_DIR3, { recursive: true });
154592
+ if (!existsSync19(DATA_DIR3)) {
154593
+ mkdirSync11(DATA_DIR3, { recursive: true });
153162
154594
  }
153163
154595
  }
153164
154596
  startFlushTimer() {
@@ -157365,7 +158797,7 @@ var init_src6 = __esm({
157365
158797
  await this._handle.listen();
157366
158798
  const authMode = config2.apiKey ? "API key" : "open (no auth)";
157367
158799
  console.log(`
157368
- Nestor Server v2.7.1`);
158800
+ Nestor Server v3.0.0`);
157369
158801
  console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
157370
158802
  console.log(` HTTP : http://${this._host}:${this._port}`);
157371
158803
  console.log(` WS : ws://${this._host}:${this._port}/ws`);
@@ -164744,7 +166176,7 @@ var require_dist12 = __commonJS({
164744
166176
  });
164745
166177
 
164746
166178
  // ../skill-tester/src/yaml-loader.ts
164747
- import { readFileSync as readFileSync19 } from "node:fs";
166179
+ import { readFileSync as readFileSync20 } from "node:fs";
164748
166180
  function parseYamlContent(content) {
164749
166181
  try {
164750
166182
  return JSON.parse(content);
@@ -164860,7 +166292,7 @@ var init_yaml_loader = __esm({
164860
166292
  * Load a YAML test suite from a file path.
164861
166293
  */
164862
166294
  static loadFile(filePath) {
164863
- const content = readFileSync19(filePath, "utf-8");
166295
+ const content = readFileSync20(filePath, "utf-8");
164864
166296
  return _YamlTestLoader.parse(content);
164865
166297
  }
164866
166298
  /**
@@ -164939,8 +166371,8 @@ var init_yaml_loader = __esm({
164939
166371
  });
164940
166372
 
164941
166373
  // ../skill-tester/src/runner.ts
164942
- import { readFileSync as readFileSync20, readdirSync as readdirSync4, statSync as statSync5, existsSync as existsSync20, mkdirSync as mkdirSync12, rmSync as rmSync2 } from "node:fs";
164943
- import { join as join25, resolve as resolve14 } from "node:path";
166374
+ import { readFileSync as readFileSync21, readdirSync as readdirSync5, statSync as statSync6, existsSync as existsSync21, mkdirSync as mkdirSync13, rmSync as rmSync2 } from "node:fs";
166375
+ import { join as join26, resolve as resolve14 } from "node:path";
164944
166376
  import { tmpdir as tmpdir4 } from "node:os";
164945
166377
  import { randomUUID as randomUUID44 } from "node:crypto";
164946
166378
  var SequentialMockAdapter, SkillTestRunner;
@@ -165015,25 +166447,25 @@ var init_runner2 = __esm({
165015
166447
  try {
165016
166448
  const mockResponses = test.mockResponses.length > 0 ? test.mockResponses : [{ content: "Test completed." }];
165017
166449
  const adapter = new SequentialMockAdapter(mockResponses);
165018
- const tempDir = join25(tmpdir4(), `nestor-skill-test-${randomUUID44()}`);
165019
- mkdirSync12(tempDir, { recursive: true });
166450
+ const tempDir = join26(tmpdir4(), `nestor-skill-test-${randomUUID44()}`);
166451
+ mkdirSync13(tempDir, { recursive: true });
165020
166452
  try {
165021
166453
  const agentModule = await Promise.resolve().then(() => (init_src5(), src_exports3));
165022
166454
  const events = new agentModule.RuntimeEventBus();
165023
166455
  const tools = new agentModule.ToolRegistry();
165024
166456
  const toolExecutor = new agentModule.ToolExecutor();
165025
166457
  let instructions = "You are a helpful AI assistant.";
165026
- if (test.skill && existsSync20(test.skill)) {
165027
- instructions = readFileSync20(test.skill, "utf-8");
166458
+ if (test.skill && existsSync21(test.skill)) {
166459
+ instructions = readFileSync21(test.skill, "utf-8");
165028
166460
  } else if (test.skill) {
165029
166461
  const skillPaths = [
165030
- join25(process.cwd(), ".nestor", "skills", test.skill, "SKILL.md"),
165031
- join25(process.cwd(), "skills", test.skill, "SKILL.md"),
165032
- join25(process.cwd(), "skills-registry", test.skill, "SKILL.md")
166462
+ join26(process.cwd(), ".nestor", "skills", test.skill, "SKILL.md"),
166463
+ join26(process.cwd(), "skills", test.skill, "SKILL.md"),
166464
+ join26(process.cwd(), "skills-registry", test.skill, "SKILL.md")
165033
166465
  ];
165034
166466
  for (const p8 of skillPaths) {
165035
- if (existsSync20(p8)) {
165036
- instructions = readFileSync20(p8, "utf-8");
166467
+ if (existsSync21(p8)) {
166468
+ instructions = readFileSync21(p8, "utf-8");
165037
166469
  break;
165038
166470
  }
165039
166471
  }
@@ -165243,23 +166675,23 @@ var init_runner2 = __esm({
165243
166675
  discoverTestFiles(dir) {
165244
166676
  const files = [];
165245
166677
  const resolvedDir = resolve14(dir);
165246
- if (!existsSync20(resolvedDir)) return files;
165247
- const stat = statSync5(resolvedDir);
166678
+ if (!existsSync21(resolvedDir)) return files;
166679
+ const stat = statSync6(resolvedDir);
165248
166680
  if (!stat.isDirectory()) {
165249
166681
  return [resolvedDir];
165250
166682
  }
165251
166683
  function walk(current) {
165252
166684
  let entries;
165253
166685
  try {
165254
- entries = readdirSync4(current);
166686
+ entries = readdirSync5(current);
165255
166687
  } catch {
165256
166688
  return;
165257
166689
  }
165258
166690
  for (const entry of entries) {
165259
166691
  if (entry === "node_modules" || entry === ".git" || entry === "dist") continue;
165260
- const fullPath = join25(current, entry);
166692
+ const fullPath = join26(current, entry);
165261
166693
  try {
165262
- const s = statSync5(fullPath);
166694
+ const s = statSync6(fullPath);
165263
166695
  if (s.isDirectory()) {
165264
166696
  walk(fullPath);
165265
166697
  } else if (s.isFile() && entry.endsWith(".test.yaml")) {
@@ -167127,7 +168559,7 @@ var init_server = __esm({
167127
168559
  MCP_PROTOCOL_VERSION = "2024-11-05";
167128
168560
  SERVER_INFO = {
167129
168561
  name: "nestor",
167130
- version: "2.7.1"
168562
+ version: "3.0.0"
167131
168563
  };
167132
168564
  SERVER_CAPABILITIES = {
167133
168565
  tools: { listChanged: false },
@@ -167313,10 +168745,10 @@ __export(shell_exports, {
167313
168745
  registerShellCommand: () => registerShellCommand
167314
168746
  });
167315
168747
  import * as readline3 from "node:readline";
167316
- import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync13, appendFileSync as appendFileSync3 } from "node:fs";
167317
- import { join as join26, resolve as resolve15, dirname as dirname10, basename as basename4 } from "node:path";
168748
+ import { existsSync as existsSync22, readFileSync as readFileSync22, writeFileSync as writeFileSync15, mkdirSync as mkdirSync14, appendFileSync as appendFileSync3 } from "node:fs";
168749
+ import { join as join27, resolve as resolve15, dirname as dirname10, basename as basename5 } from "node:path";
167318
168750
  import { homedir as homedir9 } from "node:os";
167319
- import { readdirSync as readdirSync5 } from "node:fs";
168751
+ import { readdirSync as readdirSync6 } from "node:fs";
167320
168752
  import { randomUUID as randomUUID50 } from "node:crypto";
167321
168753
  import chalk11 from "chalk";
167322
168754
  import * as p5 from "@clack/prompts";
@@ -167347,8 +168779,8 @@ async function startShell() {
167347
168779
  currentRouter: null,
167348
168780
  verbose: false
167349
168781
  };
167350
- if (!existsSync21(NESTOR_DIR)) {
167351
- mkdirSync13(NESTOR_DIR, { recursive: true });
168782
+ if (!existsSync22(NESTOR_DIR)) {
168783
+ mkdirSync14(NESTOR_DIR, { recursive: true });
167352
168784
  }
167353
168785
  refreshAgentNameCache(session);
167354
168786
  const history = loadHistory();
@@ -167571,7 +169003,7 @@ function printWelcome() {
167571
169003
  console.log(chalk11.cyan(` | .\` | | _| \\__ \\ | | | (_) | | / _ \\__ \\ | __ |`));
167572
169004
  console.log(chalk11.cyan(` |_|\\_| |___| |___/ |_| \\___/ |_|_\\ (_) |___/ |_||_|`));
167573
169005
  console.log("");
167574
- console.log(chalk11.dim(" Interactive Shell \u2014 v2.7.1"));
169006
+ console.log(chalk11.dim(" Interactive Shell \u2014 v3.0.0"));
167575
169007
  console.log(chalk11.dim(" Type /help for commands, /exit to quit."));
167576
169008
  console.log(chalk11.dim(" Multiline: end a line with \\ or use ``` code blocks."));
167577
169009
  console.log("");
@@ -168068,8 +169500,8 @@ function cmdExport(args2, session) {
168068
169500
  timestamp: new Date(e.timestamp).toISOString()
168069
169501
  }))
168070
169502
  };
168071
- const filename = join26(NESTOR_DIR, `conversation-${timestamp}.json`);
168072
- writeFileSync14(filename, JSON.stringify(data, null, 2), "utf-8");
169503
+ const filename = join27(NESTOR_DIR, `conversation-${timestamp}.json`);
169504
+ writeFileSync15(filename, JSON.stringify(data, null, 2), "utf-8");
168073
169505
  console.log(chalk11.green(`Exported to: ${filename}`));
168074
169506
  return;
168075
169507
  }
@@ -168093,8 +169525,8 @@ function cmdExport(args2, session) {
168093
169525
  lines.push(entry.content);
168094
169526
  lines.push("");
168095
169527
  }
168096
- const filename = join26(NESTOR_DIR, `conversation-${timestamp}.md`);
168097
- writeFileSync14(filename, lines.join("\n"), "utf-8");
169528
+ const filename = join27(NESTOR_DIR, `conversation-${timestamp}.md`);
169529
+ writeFileSync15(filename, lines.join("\n"), "utf-8");
168098
169530
  console.log(chalk11.green(`Exported to: ${filename}`));
168099
169531
  return;
168100
169532
  }
@@ -168130,8 +169562,8 @@ ${rows}
168130
169562
  <div class="stats">Tokens: ${fmtNum(session.totalTokensIn)} in / ${fmtNum(session.totalTokensOut)} out | Cost: $${session.totalCostUsd.toFixed(4)} | Tool calls: ${session.totalToolCalls}</div>
168131
169563
  </body>
168132
169564
  </html>`;
168133
- const filename = join26(NESTOR_DIR, `conversation-${timestamp}.html`);
168134
- writeFileSync14(filename, html, "utf-8");
169565
+ const filename = join27(NESTOR_DIR, `conversation-${timestamp}.html`);
169566
+ writeFileSync15(filename, html, "utf-8");
168135
169567
  console.log(chalk11.green(`Exported to: ${filename}`));
168136
169568
  }
168137
169569
  }
@@ -168638,7 +170070,7 @@ function createUnifiedDiff(oldText, newText, fileName) {
168638
170070
  function showDiffForStep(filePath, newContent) {
168639
170071
  try {
168640
170072
  const absolutePath = resolve15(process.cwd(), filePath);
168641
- const oldContent = readFileSync21(absolutePath, "utf-8");
170073
+ const oldContent = readFileSync22(absolutePath, "utf-8");
168642
170074
  const diff = createUnifiedDiff(oldContent, newContent, filePath);
168643
170075
  for (const line of diff.split("\n")) {
168644
170076
  if (line.startsWith("+") && !line.startsWith("+++")) {
@@ -169051,8 +170483,8 @@ function completeFilePath(partial) {
169051
170483
  dir = dirname10(resolved);
169052
170484
  prefix = partial.substring(0, Math.max(partial.lastIndexOf("/"), partial.lastIndexOf("\\")) + 1);
169053
170485
  }
169054
- const entries = readdirSync5(dir, { withFileTypes: true });
169055
- const base = basename4(partial);
170486
+ const entries = readdirSync6(dir, { withFileTypes: true });
170487
+ const base = basename5(partial);
169056
170488
  const matches = [];
169057
170489
  for (const entry of entries) {
169058
170490
  if (entry.name.startsWith(".")) continue;
@@ -169082,8 +170514,8 @@ function refreshAgentNameCache(session) {
169082
170514
  }
169083
170515
  function loadHistory() {
169084
170516
  try {
169085
- if (existsSync21(HISTORY_FILE)) {
169086
- const raw = readFileSync21(HISTORY_FILE, "utf-8");
170517
+ if (existsSync22(HISTORY_FILE)) {
170518
+ const raw = readFileSync22(HISTORY_FILE, "utf-8");
169087
170519
  return raw.split("\n").filter((l) => l.trim().length > 0).slice(-MAX_HISTORY);
169088
170520
  }
169089
170521
  } catch {
@@ -169094,8 +170526,8 @@ function saveHistory(rl) {
169094
170526
  }
169095
170527
  function appendToHistoryFile(line) {
169096
170528
  try {
169097
- if (!existsSync21(NESTOR_DIR)) {
169098
- mkdirSync13(NESTOR_DIR, { recursive: true });
170529
+ if (!existsSync22(NESTOR_DIR)) {
170530
+ mkdirSync14(NESTOR_DIR, { recursive: true });
169099
170531
  }
169100
170532
  appendFileSync3(HISTORY_FILE, line + "\n", "utf-8");
169101
170533
  } catch {
@@ -169437,8 +170869,8 @@ var init_shell = __esm({
169437
170869
  init_config2();
169438
170870
  init_spinner();
169439
170871
  init_table();
169440
- NESTOR_DIR = join26(homedir9(), ".nestor");
169441
- HISTORY_FILE = join26(NESTOR_DIR, "shell_history");
170872
+ NESTOR_DIR = join27(homedir9(), ".nestor");
170873
+ HISTORY_FILE = join27(NESTOR_DIR, "shell_history");
169442
170874
  MAX_HISTORY = 1e3;
169443
170875
  SLASH_COMMANDS = {
169444
170876
  "/help": { description: "Show all commands", usage: "/help" },
@@ -169490,8 +170922,8 @@ var init_shell = __esm({
169490
170922
 
169491
170923
  // src/index.ts
169492
170924
  import { Command } from "commander";
169493
- import { existsSync as existsSync25, readFileSync as readFileSync25 } from "node:fs";
169494
- import { join as join29 } from "node:path";
170925
+ import { existsSync as existsSync27, readFileSync as readFileSync27 } from "node:fs";
170926
+ import { join as join30 } from "node:path";
169495
170927
  import { homedir as homedir12 } from "node:os";
169496
170928
 
169497
170929
  // src/commands/start.ts
@@ -169511,7 +170943,7 @@ var BANNER = `
169511
170943
  function registerStartCommand(program2) {
169512
170944
  program2.command("start").description("Start the Nestor server").option("-p, --port <port>", "Server port").option("-H, --host <host>", "Server host").option("--no-studio", "Disable the Studio web UI").action(async (options) => {
169513
170945
  console.log(chalk.cyan(BANNER));
169514
- console.log(chalk.dim(` v2.7.1
170946
+ console.log(chalk.dim(` v3.0.0
169515
170947
  `));
169516
170948
  let config2 = readConfigFile();
169517
170949
  if (!config2) {
@@ -169636,8 +171068,8 @@ init_config();
169636
171068
  init_config2();
169637
171069
  import * as p from "@clack/prompts";
169638
171070
  import chalk2 from "chalk";
169639
- import { mkdirSync as mkdirSync11, existsSync as existsSync19 } from "node:fs";
169640
- import { join as join24 } from "node:path";
171071
+ import { mkdirSync as mkdirSync12, existsSync as existsSync20 } from "node:fs";
171072
+ import { join as join25 } from "node:path";
169641
171073
  import { randomBytes as randomBytes8 } from "node:crypto";
169642
171074
  function generateApiKey() {
169643
171075
  return `nst_${randomBytes8(32).toString("hex")}`;
@@ -169656,7 +171088,7 @@ function generateBetaKey() {
169656
171088
  }
169657
171089
  async function initializeDatabase(dataDir) {
169658
171090
  const { NestorStore: NestorStore2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
169659
- const dbPath = join24(dataDir, "nestor.db");
171091
+ const dbPath = join25(dataDir, "nestor.db");
169660
171092
  const store = await NestorStore2.open(dbPath);
169661
171093
  store.close();
169662
171094
  }
@@ -169704,31 +171136,31 @@ async function runNonInteractiveInstall() {
169704
171136
  console.log();
169705
171137
  const config2 = buildConfigFromDefaults();
169706
171138
  const dataDir = expandPath(config2.server.dataDir);
169707
- if (!existsSync19(dataDir)) {
169708
- mkdirSync11(dataDir, { recursive: true });
171139
+ if (!existsSync20(dataDir)) {
171140
+ mkdirSync12(dataDir, { recursive: true });
169709
171141
  console.log(chalk2.green(" Created data directory:"), dataDir);
169710
171142
  }
169711
171143
  for (const sub of ["adapters", "skills", "plugins", "logs"]) {
169712
- const subDir = join24(dataDir, sub);
169713
- if (!existsSync19(subDir)) {
169714
- mkdirSync11(subDir, { recursive: true });
171144
+ const subDir = join25(dataDir, sub);
171145
+ if (!existsSync20(subDir)) {
171146
+ mkdirSync12(subDir, { recursive: true });
169715
171147
  }
169716
171148
  }
169717
171149
  writeConfigFile(config2);
169718
171150
  console.log(chalk2.green(" Configuration saved:"), getConfigPath());
169719
171151
  try {
169720
171152
  await initializeDatabase(dataDir);
169721
- console.log(chalk2.green(" Database initialized:"), join24(dataDir, "nestor.db"));
171153
+ console.log(chalk2.green(" Database initialized:"), join25(dataDir, "nestor.db"));
169722
171154
  } catch (err) {
169723
171155
  console.log(chalk2.yellow(" Database initialization skipped (install @nestor/db first)"));
169724
171156
  }
169725
171157
  const apiKey = generateApiKey();
169726
- const apiKeyPath = join24(dataDir, ".api-keys");
169727
- const { writeFileSync: writeFileSync17 } = await import("node:fs");
169728
- writeFileSync17(apiKeyPath, JSON.stringify({ admin: apiKey }, null, 2) + "\n", "utf-8");
171158
+ const apiKeyPath = join25(dataDir, ".api-keys");
171159
+ const { writeFileSync: writeFileSync18 } = await import("node:fs");
171160
+ writeFileSync18(apiKeyPath, JSON.stringify({ admin: apiKey }, null, 2) + "\n", "utf-8");
169729
171161
  console.log(chalk2.green(" Admin API key created"));
169730
171162
  const betaKey = generateBetaKey();
169731
- writeFileSync17(join24(dataDir, "license.key"), betaKey, "utf-8");
171163
+ writeFileSync18(join25(dataDir, "license.key"), betaKey, "utf-8");
169732
171164
  console.log(chalk2.green(" Beta key created"));
169733
171165
  console.log();
169734
171166
  console.log(chalk2.bold("Admin API Key:"), chalk2.yellow(apiKey));
@@ -169824,9 +171256,9 @@ async function runInteractiveInstall() {
169824
171256
  const dataDir = expandPath(config2.server.dataDir);
169825
171257
  const spinner3 = p.spinner();
169826
171258
  spinner3.start("Creating directories...");
169827
- for (const dir of [dataDir, join24(dataDir, "adapters"), join24(dataDir, "skills"), join24(dataDir, "plugins"), join24(dataDir, "logs")]) {
169828
- if (!existsSync19(dir)) {
169829
- mkdirSync11(dir, { recursive: true });
171259
+ for (const dir of [dataDir, join25(dataDir, "adapters"), join25(dataDir, "skills"), join25(dataDir, "plugins"), join25(dataDir, "logs")]) {
171260
+ if (!existsSync20(dir)) {
171261
+ mkdirSync12(dir, { recursive: true });
169830
171262
  }
169831
171263
  }
169832
171264
  spinner3.stop("Directories created");
@@ -169851,14 +171283,14 @@ async function runInteractiveInstall() {
169851
171283
  const spinner4 = p.spinner();
169852
171284
  spinner4.start("Generating admin API key...");
169853
171285
  const apiKey = generateApiKey();
169854
- const apiKeyPath = join24(dataDir, ".api-keys");
169855
- const { writeFileSync: writeFileSync17 } = await import("node:fs");
169856
- writeFileSync17(apiKeyPath, JSON.stringify({ admin: apiKey }, null, 2) + "\n", "utf-8");
171286
+ const apiKeyPath = join25(dataDir, ".api-keys");
171287
+ const { writeFileSync: writeFileSync18 } = await import("node:fs");
171288
+ writeFileSync18(apiKeyPath, JSON.stringify({ admin: apiKey }, null, 2) + "\n", "utf-8");
169857
171289
  spinner4.stop("Admin API key created!");
169858
171290
  const spinner5 = p.spinner();
169859
171291
  spinner5.start("Generating beta key...");
169860
171292
  const betaKey = generateBetaKey();
169861
- writeFileSync17(join24(dataDir, "license.key"), betaKey, "utf-8");
171293
+ writeFileSync18(join25(dataDir, "license.key"), betaKey, "utf-8");
169862
171294
  spinner5.stop("Beta key created!");
169863
171295
  p.note(
169864
171296
  `Config file: ${getConfigPath()}
@@ -171151,23 +172583,23 @@ init_db();
171151
172583
  init_spinner();
171152
172584
  init_table();
171153
172585
  import {
171154
- existsSync as existsSync22,
171155
- readFileSync as readFileSync22,
171156
- writeFileSync as writeFileSync15,
171157
- mkdirSync as mkdirSync14,
172586
+ existsSync as existsSync23,
172587
+ readFileSync as readFileSync23,
172588
+ writeFileSync as writeFileSync16,
172589
+ mkdirSync as mkdirSync15,
171158
172590
  unlinkSync,
171159
172591
  appendFileSync as appendFileSync4
171160
172592
  } from "node:fs";
171161
- import { join as join27 } from "node:path";
172593
+ import { join as join28 } from "node:path";
171162
172594
  import { homedir as homedir10 } from "node:os";
171163
172595
  import { fork as fork2 } from "node:child_process";
171164
172596
  import "node:readline";
171165
172597
  import chalk12 from "chalk";
171166
- var NESTOR_DIR2 = join27(homedir10(), ".nestor");
171167
- var PID_FILE = join27(NESTOR_DIR2, "daemon.pid");
171168
- var LOG_DIR = join27(NESTOR_DIR2, "logs");
171169
- var LOG_FILE = join27(LOG_DIR, "daemon.log");
171170
- var HEARTBEAT_FILE = join27(NESTOR_DIR2, "daemon.heartbeat");
172598
+ var NESTOR_DIR2 = join28(homedir10(), ".nestor");
172599
+ var PID_FILE = join28(NESTOR_DIR2, "daemon.pid");
172600
+ var LOG_DIR = join28(NESTOR_DIR2, "logs");
172601
+ var LOG_FILE = join28(LOG_DIR, "daemon.log");
172602
+ var HEARTBEAT_FILE = join28(NESTOR_DIR2, "daemon.heartbeat");
171171
172603
  var HEARTBEAT_INTERVAL_MS2 = 3e4;
171172
172604
  function registerDaemonCommand(program2) {
171173
172605
  const daemon = program2.command("daemon").description("Manage the Nestor background daemon");
@@ -171203,7 +172635,7 @@ async function startForeground() {
171203
172635
  console.log(chalk12.cyan(` | .\` | | _| \\__ \\ | | | (_) | | / _ \\__ \\ | __ |`));
171204
172636
  console.log(chalk12.cyan(` |_|\\_| |___| |___/ |_| \\___/ |_|_\\ (_) |___/ |_||_|`));
171205
172637
  console.log("");
171206
- console.log(chalk12.dim(" Daemon Mode \u2014 v2.7.1"));
172638
+ console.log(chalk12.dim(" Daemon Mode \u2014 v3.0.0"));
171207
172639
  console.log(chalk12.dim(` PID: ${process.pid}`));
171208
172640
  console.log(chalk12.dim(` Log: ${LOG_FILE}`));
171209
172641
  console.log("");
@@ -171335,13 +172767,13 @@ async function showStatus() {
171335
172767
  console.log("");
171336
172768
  }
171337
172769
  async function showLogs(follow, lineCount) {
171338
- if (!existsSync22(LOG_FILE)) {
172770
+ if (!existsSync23(LOG_FILE)) {
171339
172771
  console.log(chalk12.yellow("No daemon log file found."));
171340
172772
  console.log(chalk12.dim(`Expected at: ${LOG_FILE}`));
171341
172773
  return;
171342
172774
  }
171343
172775
  if (!follow) {
171344
- const content = readFileSync22(LOG_FILE, "utf-8");
172776
+ const content = readFileSync23(LOG_FILE, "utf-8");
171345
172777
  const lines = content.split("\n").filter((l) => l.length > 0);
171346
172778
  const lastLines = lines.slice(-lineCount);
171347
172779
  if (lastLines.length === 0) {
@@ -171355,19 +172787,19 @@ async function showLogs(follow, lineCount) {
171355
172787
  }
171356
172788
  console.log(chalk12.dim(`Following ${LOG_FILE}... (Ctrl+C to stop)`));
171357
172789
  console.log("");
171358
- if (existsSync22(LOG_FILE)) {
171359
- const content = readFileSync22(LOG_FILE, "utf-8");
172790
+ if (existsSync23(LOG_FILE)) {
172791
+ const content = readFileSync23(LOG_FILE, "utf-8");
171360
172792
  const lines = content.split("\n").filter((l) => l.length > 0);
171361
172793
  const lastLines = lines.slice(-10);
171362
172794
  for (const line of lastLines) {
171363
172795
  console.log(colorizeLogLine(line));
171364
172796
  }
171365
172797
  }
171366
- let lastSize = existsSync22(LOG_FILE) ? readFileSync22(LOG_FILE).length : 0;
172798
+ let lastSize = existsSync23(LOG_FILE) ? readFileSync23(LOG_FILE).length : 0;
171367
172799
  const watcher = setInterval(() => {
171368
172800
  try {
171369
- if (!existsSync22(LOG_FILE)) return;
171370
- const content = readFileSync22(LOG_FILE, "utf-8");
172801
+ if (!existsSync23(LOG_FILE)) return;
172802
+ const content = readFileSync23(LOG_FILE, "utf-8");
171371
172803
  if (content.length > lastSize) {
171372
172804
  const newContent = content.substring(lastSize);
171373
172805
  const newLines = newContent.split("\n").filter((l) => l.length > 0);
@@ -171566,9 +172998,9 @@ var DaemonRunner = class {
171566
172998
  try {
171567
172999
  const store = await getStore();
171568
173000
  try {
171569
- const { randomUUID: randomUUID51 } = await import("node:crypto");
173001
+ const { randomUUID: randomUUID52 } = await import("node:crypto");
171570
173002
  store.createRun({
171571
- id: randomUUID51(),
173003
+ id: randomUUID52(),
171572
173004
  workflowId: `agent:${agentId}`,
171573
173005
  workflowVersion: 1,
171574
173006
  status: result.exitReason === "completed" ? "completed" : "failed",
@@ -171604,9 +173036,9 @@ var DaemonRunner = class {
171604
173036
  try {
171605
173037
  const store = await getStore();
171606
173038
  try {
171607
- const { randomUUID: randomUUID51 } = await import("node:crypto");
173039
+ const { randomUUID: randomUUID52 } = await import("node:crypto");
171608
173040
  store.createRun({
171609
- id: randomUUID51(),
173041
+ id: randomUUID52(),
171610
173042
  workflowId: `agent:${agentId}`,
171611
173043
  workflowVersion: 1,
171612
173044
  status: "failed",
@@ -171679,7 +173111,7 @@ var DaemonRunner = class {
171679
173111
  activeAgents: this.runningAgents.size,
171680
173112
  nextScheduledRun: void 0
171681
173113
  };
171682
- writeFileSync15(HEARTBEAT_FILE, JSON.stringify(data), "utf-8");
173114
+ writeFileSync16(HEARTBEAT_FILE, JSON.stringify(data), "utf-8");
171683
173115
  } catch {
171684
173116
  }
171685
173117
  }
@@ -171697,8 +173129,8 @@ async function importDaemonAgentModule() {
171697
173129
  }
171698
173130
  function readHeartbeat() {
171699
173131
  try {
171700
- if (existsSync22(HEARTBEAT_FILE)) {
171701
- const raw = readFileSync22(HEARTBEAT_FILE, "utf-8");
173132
+ if (existsSync23(HEARTBEAT_FILE)) {
173133
+ const raw = readFileSync23(HEARTBEAT_FILE, "utf-8");
171702
173134
  return JSON.parse(raw);
171703
173135
  }
171704
173136
  } catch {
@@ -171707,8 +173139,8 @@ function readHeartbeat() {
171707
173139
  }
171708
173140
  function readPid() {
171709
173141
  try {
171710
- if (existsSync22(PID_FILE)) {
171711
- const raw = readFileSync22(PID_FILE, "utf-8").trim();
173142
+ if (existsSync23(PID_FILE)) {
173143
+ const raw = readFileSync23(PID_FILE, "utf-8").trim();
171712
173144
  const pid = parseInt(raw, 10);
171713
173145
  return isNaN(pid) ? null : pid;
171714
173146
  }
@@ -171718,11 +173150,11 @@ function readPid() {
171718
173150
  }
171719
173151
  function writePid(pid) {
171720
173152
  ensureDirs();
171721
- writeFileSync15(PID_FILE, String(pid), "utf-8");
173153
+ writeFileSync16(PID_FILE, String(pid), "utf-8");
171722
173154
  }
171723
173155
  function cleanupPid() {
171724
173156
  try {
171725
- if (existsSync22(PID_FILE)) {
173157
+ if (existsSync23(PID_FILE)) {
171726
173158
  unlinkSync(PID_FILE);
171727
173159
  }
171728
173160
  } catch {
@@ -171762,11 +173194,11 @@ function colorizeLogLine(line) {
171762
173194
  return line;
171763
173195
  }
171764
173196
  function ensureDirs() {
171765
- if (!existsSync22(NESTOR_DIR2)) {
171766
- mkdirSync14(NESTOR_DIR2, { recursive: true });
173197
+ if (!existsSync23(NESTOR_DIR2)) {
173198
+ mkdirSync15(NESTOR_DIR2, { recursive: true });
171767
173199
  }
171768
- if (!existsSync22(LOG_DIR)) {
171769
- mkdirSync14(LOG_DIR, { recursive: true });
173200
+ if (!existsSync23(LOG_DIR)) {
173201
+ mkdirSync15(LOG_DIR, { recursive: true });
171770
173202
  }
171771
173203
  }
171772
173204
  function delay(ms) {
@@ -171807,13 +173239,13 @@ if (process.argv[2] === "__daemon_child__" || process.env.NESTOR_DAEMON_MODE ===
171807
173239
 
171808
173240
  // src/commands/watch.ts
171809
173241
  init_db();
171810
- import { existsSync as existsSync23, readFileSync as readFileSync23 } from "node:fs";
171811
- import { join as join28 } from "node:path";
173242
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
173243
+ import { join as join29 } from "node:path";
171812
173244
  import { homedir as homedir11 } from "node:os";
171813
173245
  import chalk13 from "chalk";
171814
- var NESTOR_DIR3 = join28(homedir11(), ".nestor");
171815
- var HEARTBEAT_FILE2 = join28(NESTOR_DIR3, "daemon.heartbeat");
171816
- var LOG_FILE2 = join28(NESTOR_DIR3, "logs", "daemon.log");
173246
+ var NESTOR_DIR3 = join29(homedir11(), ".nestor");
173247
+ var HEARTBEAT_FILE2 = join29(NESTOR_DIR3, "daemon.heartbeat");
173248
+ var LOG_FILE2 = join29(NESTOR_DIR3, "logs", "daemon.log");
171817
173249
  var REFRESH_INTERVAL_MS = 1e3;
171818
173250
  var MAX_EVENTS = 12;
171819
173251
  function registerWatchCommand(program2) {
@@ -171881,8 +173313,8 @@ async function refreshData(state) {
171881
173313
  } catch {
171882
173314
  }
171883
173315
  try {
171884
- if (existsSync23(HEARTBEAT_FILE2)) {
171885
- const raw = readFileSync23(HEARTBEAT_FILE2, "utf-8");
173316
+ if (existsSync24(HEARTBEAT_FILE2)) {
173317
+ const raw = readFileSync24(HEARTBEAT_FILE2, "utf-8");
171886
173318
  const heartbeat = JSON.parse(raw);
171887
173319
  state.daemonRunning = isDaemonAlive(heartbeat.pid);
171888
173320
  state.daemonPid = heartbeat.pid;
@@ -171899,8 +173331,8 @@ async function refreshData(state) {
171899
173331
  }
171900
173332
  function loadRecentEvents(state) {
171901
173333
  try {
171902
- if (!existsSync23(LOG_FILE2)) return;
171903
- const content = readFileSync23(LOG_FILE2, "utf-8");
173334
+ if (!existsSync24(LOG_FILE2)) return;
173335
+ const content = readFileSync24(LOG_FILE2, "utf-8");
171904
173336
  const lines = content.split("\n").filter((l) => l.length > 0);
171905
173337
  const recent = lines.slice(-MAX_EVENTS);
171906
173338
  state.recentEvents = recent.map((line) => {
@@ -172226,10 +173658,10 @@ function registerTelemetryCommand(program2) {
172226
173658
  }
172227
173659
  console.log(` ${chalk15.dim("Flush:")} every ${(config2.telemetry?.flushIntervalMs ?? 6e4) / 1e3}s`);
172228
173660
  }
172229
- const { join: join30 } = __require("node:path");
173661
+ const { join: join31 } = __require("node:path");
172230
173662
  const { homedir: homedir13 } = __require("node:os");
172231
- const telemetryFile = join30(homedir13(), ".nestor", "telemetry.jsonl");
172232
- const idFile = join30(homedir13(), ".nestor", "telemetry-id");
173663
+ const telemetryFile = join31(homedir13(), ".nestor", "telemetry.jsonl");
173664
+ const idFile = join31(homedir13(), ".nestor", "telemetry-id");
172233
173665
  if (fs28.existsSync(idFile)) {
172234
173666
  const installId = fs28.readFileSync(idFile, "utf-8").trim();
172235
173667
  console.log(` ${chalk15.dim("Install ID:")} ${installId.slice(0, 8)}...`);
@@ -172270,7 +173702,7 @@ function setTelemetryEnabled(enabled) {
172270
173702
  init_db();
172271
173703
  init_table();
172272
173704
  import chalk16 from "chalk";
172273
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync16 } from "node:fs";
173705
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync17 } from "node:fs";
172274
173706
  function registerMemoryCommand(program2) {
172275
173707
  const memory = program2.command("memory").description("Manage agent persistent memories");
172276
173708
  memory.command("list").description("List memories for an agent").option("-a, --agent <id>", "Agent ID to filter by").option("-t, --type <type>", "Memory type filter (observation, fact, instruction, reflection)").option("-l, --limit <n>", "Maximum results", "20").action(async (options) => {
@@ -172474,13 +173906,13 @@ Memory Statistics for ${agentId}`));
172474
173906
  }
172475
173907
  }
172476
173908
  const json = JSON.stringify(allMemories, null, 2);
172477
- writeFileSync16(outputFile, json, "utf-8");
173909
+ writeFileSync17(outputFile, json, "utf-8");
172478
173910
  console.log(chalk16.green(`Exported ${allMemories.length} memories to ${outputFile}`));
172479
173911
  });
172480
173912
  memory.command("import <file>").description("Import memories from JSON").action(async (file) => {
172481
173913
  const store = await getStore();
172482
173914
  try {
172483
- const raw = readFileSync24(file, "utf-8");
173915
+ const raw = readFileSync25(file, "utf-8");
172484
173916
  const entries = JSON.parse(raw);
172485
173917
  if (!Array.isArray(entries)) {
172486
173918
  console.error(chalk16.red("Invalid format: expected a JSON array of memory entries."));
@@ -173000,8 +174432,8 @@ ${results.length} results for "${query}":
173000
174432
  }
173001
174433
 
173002
174434
  // src/commands/test.ts
173003
- import { resolve as resolve17, relative as relative5 } from "node:path";
173004
- import { readdirSync as readdirSync6, statSync as statSync6, existsSync as existsSync24 } from "node:fs";
174435
+ import { resolve as resolve17, relative as relative6 } from "node:path";
174436
+ import { readdirSync as readdirSync7, statSync as statSync7, existsSync as existsSync25 } from "node:fs";
173005
174437
  import chalk20 from "chalk";
173006
174438
  var TEST_FILE_PATTERNS = [
173007
174439
  ".nestor-test.yaml",
@@ -173014,7 +174446,7 @@ function registerTestCommand(program2) {
173014
174446
  program2.command("test [path]").description("Run skill and agent test suites").option("-l, --list", "List discovered test files", false).option("-f, --file <file>", "Run a specific test file").option("-r, --record <file>", "Record LLM responses to a fixture file").option("-p, --prompt <prompt>", "Prompt for recording mode").option("-v, --verbose", "Show detailed test output", false).option("-j, --json", "Output results as JSON (for CI)", false).action(async (path30, opts) => {
173015
174447
  if (opts.file) {
173016
174448
  const filePath = resolve17(opts.file);
173017
- if (!existsSync24(filePath)) {
174449
+ if (!existsSync25(filePath)) {
173018
174450
  console.error(chalk20.red(`File not found: ${opts.file}`));
173019
174451
  process.exit(1);
173020
174452
  }
@@ -173045,7 +174477,7 @@ function registerTestCommand(program2) {
173045
174477
  ];
173046
174478
  const allFiles = [];
173047
174479
  for (const testDir of defaultTestDirs) {
173048
- if (existsSync24(testDir)) {
174480
+ if (existsSync25(testDir)) {
173049
174481
  allFiles.push(...discoverTestFiles(testDir));
173050
174482
  }
173051
174483
  }
@@ -173064,7 +174496,7 @@ function discoverTestFiles(dir) {
173064
174496
  function walk(current) {
173065
174497
  let entries;
173066
174498
  try {
173067
- entries = readdirSync6(current);
174499
+ entries = readdirSync7(current);
173068
174500
  } catch {
173069
174501
  return;
173070
174502
  }
@@ -173072,7 +174504,7 @@ function discoverTestFiles(dir) {
173072
174504
  if (entry === "node_modules" || entry === ".git" || entry === "dist") continue;
173073
174505
  const fullPath = resolve17(current, entry);
173074
174506
  try {
173075
- const stat = statSync6(fullPath);
174507
+ const stat = statSync7(fullPath);
173076
174508
  if (stat.isDirectory()) {
173077
174509
  walk(fullPath);
173078
174510
  } else if (stat.isFile()) {
@@ -173088,7 +174520,7 @@ function discoverTestFiles(dir) {
173088
174520
  }
173089
174521
  }
173090
174522
  }
173091
- if (existsSync24(dir) && !statSync6(dir).isDirectory()) {
174523
+ if (existsSync25(dir) && !statSync7(dir).isDirectory()) {
173092
174524
  return [dir];
173093
174525
  }
173094
174526
  walk(dir);
@@ -173097,7 +174529,7 @@ function discoverTestFiles(dir) {
173097
174529
  async function listTestFiles(dir) {
173098
174530
  const files = discoverTestFiles(dir);
173099
174531
  const nestorTestDir = resolve17(dir, ".nestor", "tests");
173100
- if (existsSync24(nestorTestDir)) {
174532
+ if (existsSync25(nestorTestDir)) {
173101
174533
  files.push(...discoverTestFiles(nestorTestDir));
173102
174534
  }
173103
174535
  const unique = [...new Set(files)];
@@ -173109,7 +174541,7 @@ async function listTestFiles(dir) {
173109
174541
  Discovered ${unique.length} test file(s):
173110
174542
  `));
173111
174543
  for (const file of unique) {
173112
- console.log(chalk20.dim(" ") + chalk20.cyan(relative5(dir, file)));
174544
+ console.log(chalk20.dim(" ") + chalk20.cyan(relative6(dir, file)));
173113
174545
  }
173114
174546
  console.log("");
173115
174547
  }
@@ -173153,7 +174585,7 @@ async function runSkillTestsByName(skillName, verbose, json) {
173153
174585
  ];
173154
174586
  const matchingFiles = [];
173155
174587
  for (const dir of searchDirs) {
173156
- if (!existsSync24(dir)) continue;
174588
+ if (!existsSync25(dir)) continue;
173157
174589
  const files = discoverTestFiles(dir);
173158
174590
  for (const file of files) {
173159
174591
  if (file.includes(skillName)) {
@@ -173179,7 +174611,7 @@ Running ${files.length} test suite(s) (legacy runner)...
173179
174611
  let totalFailed = 0;
173180
174612
  const startTime4 = Date.now();
173181
174613
  for (const file of files) {
173182
- const relPath = relative5(process.cwd(), file);
174614
+ const relPath = relative6(process.cwd(), file);
173183
174615
  let suite;
173184
174616
  try {
173185
174617
  suite = agentModule.AgentTestRunner.loadSuite(file);
@@ -173349,11 +174781,289 @@ function registerGuardrailCommand(program2) {
173349
174781
  });
173350
174782
  }
173351
174783
 
174784
+ // src/commands/loop.ts
174785
+ init_db();
174786
+ init_config2();
174787
+ import chalk22 from "chalk";
174788
+ import { randomUUID as randomUUID51 } from "node:crypto";
174789
+ import { readFileSync as readFileSync26, existsSync as existsSync26 } from "node:fs";
174790
+ import { execSync as execSync3 } from "node:child_process";
174791
+ function runShellCommand(cmd, cwd) {
174792
+ try {
174793
+ const stdout = execSync3(cmd, {
174794
+ cwd,
174795
+ encoding: "utf-8",
174796
+ timeout: 6e4,
174797
+ stdio: ["pipe", "pipe", "pipe"]
174798
+ });
174799
+ return { stdout: stdout.trim(), exitCode: 0 };
174800
+ } catch (err) {
174801
+ const execErr = err;
174802
+ return {
174803
+ stdout: (execErr.stdout ?? "").toString().trim(),
174804
+ exitCode: execErr.status ?? 1
174805
+ };
174806
+ }
174807
+ }
174808
+ function getGitDiff(cwd) {
174809
+ try {
174810
+ const diff = execSync3("git diff --stat", { cwd, encoding: "utf-8", timeout: 1e4 });
174811
+ return diff.trim() || "(no changes)";
174812
+ } catch {
174813
+ return "(git not available)";
174814
+ }
174815
+ }
174816
+ function getTestOutput(verifyCmd, cwd) {
174817
+ const result = runShellCommand(verifyCmd, cwd);
174818
+ const maxLen = 2e3;
174819
+ const output = result.stdout.length > maxLen ? result.stdout.slice(-maxLen) : result.stdout;
174820
+ return output;
174821
+ }
174822
+ function buildIterationPrompt(opts) {
174823
+ const parts = [];
174824
+ parts.push(`# Objective (iteration ${opts.iteration}/${opts.maxIterations})`);
174825
+ parts.push("");
174826
+ parts.push(opts.spec);
174827
+ parts.push("");
174828
+ if (opts.previousResults.length > 0) {
174829
+ parts.push("## Previous iterations summary");
174830
+ for (const r of opts.previousResults) {
174831
+ const status = r.specMet ? "PASSED" : "FAILED";
174832
+ parts.push(`- Iteration ${r.iteration}: ${status} (${r.exitReason}) \u2014 ${r.output.slice(0, 200)}`);
174833
+ }
174834
+ parts.push("");
174835
+ }
174836
+ if (opts.gitDiff !== "(no changes)" && opts.gitDiff !== "(git not available)") {
174837
+ parts.push("## Current state (git diff --stat)");
174838
+ parts.push("```");
174839
+ parts.push(opts.gitDiff);
174840
+ parts.push("```");
174841
+ parts.push("");
174842
+ }
174843
+ if (opts.verifyOutput) {
174844
+ parts.push("## Verification output (from last check)");
174845
+ parts.push("```");
174846
+ parts.push(opts.verifyOutput);
174847
+ parts.push("```");
174848
+ parts.push("");
174849
+ }
174850
+ parts.push("## Instructions");
174851
+ parts.push("Analyze the current state and work towards achieving the objective.");
174852
+ parts.push("If previous iterations failed, try a DIFFERENT approach.");
174853
+ parts.push("When done, commit your changes with a descriptive message.");
174854
+ return parts.join("\n");
174855
+ }
174856
+ function registerLoopCommand(program2) {
174857
+ program2.command("loop").description("Run an agent in a fresh-context loop until a specification is met (Ralph pattern)").option("--agent <name>", "Agent name to use", "default").option("--spec <text>", "Specification to achieve").option("--spec-file <path>", "File containing the specification").option("--verify <command>", "Shell command to verify spec (exit 0 = done)").option("--max-iterations <n>", "Max loop iterations", "10").option("--budget <usd>", "Max total budget in USD", "5").option("--cwd <path>", "Working directory", process.cwd()).action(async (opts) => {
174858
+ let spec = opts.spec;
174859
+ if (!spec && opts.specFile) {
174860
+ if (!existsSync26(opts.specFile)) {
174861
+ console.error(chalk22.red(`Spec file not found: ${opts.specFile}`));
174862
+ process.exit(1);
174863
+ }
174864
+ spec = readFileSync26(opts.specFile, "utf-8").trim();
174865
+ }
174866
+ if (!spec) {
174867
+ console.error(chalk22.red("Must provide --spec or --spec-file"));
174868
+ process.exit(1);
174869
+ }
174870
+ const maxIterations = parseInt(opts.maxIterations, 10);
174871
+ const totalBudget = parseFloat(opts.budget);
174872
+ const cwd = opts.cwd;
174873
+ console.log(chalk22.cyan("\n Ralph Fresh-Context Loop"));
174874
+ console.log(chalk22.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
174875
+ console.log(chalk22.white(" Spec:"), spec.slice(0, 100) + (spec.length > 100 ? "..." : ""));
174876
+ console.log(chalk22.white(" Agent:"), opts.agent);
174877
+ console.log(chalk22.white(" Max iterations:"), maxIterations);
174878
+ console.log(chalk22.white(" Budget:"), `$${totalBudget}`);
174879
+ if (opts.verify) {
174880
+ console.log(chalk22.white(" Verify cmd:"), opts.verify);
174881
+ }
174882
+ console.log("");
174883
+ let agentModule;
174884
+ try {
174885
+ agentModule = await Promise.resolve().then(() => (init_src5(), src_exports3));
174886
+ } catch {
174887
+ console.error(chalk22.red("Failed to import @nestor/agent. Is it installed?"));
174888
+ process.exit(1);
174889
+ }
174890
+ const store = await getStore();
174891
+ let resolvedAgent;
174892
+ try {
174893
+ const agents = store.listAgents();
174894
+ resolvedAgent = agents.find((a) => a.name === opts.agent);
174895
+ } catch {
174896
+ }
174897
+ const agent = resolvedAgent ?? {
174898
+ id: randomUUID51(),
174899
+ name: opts.agent,
174900
+ adapterType: "claude",
174901
+ adapterConfig: {
174902
+ model: "claude-sonnet-4-20250514",
174903
+ instructions: "You are an expert software engineer. Complete the given specification precisely."
174904
+ },
174905
+ trustLevel: "community",
174906
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
174907
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
174908
+ };
174909
+ let router;
174910
+ try {
174911
+ router = await agentModule.createDefaultRouter();
174912
+ } catch {
174913
+ console.error(chalk22.red("Could not initialize LLM adapter. Is the API key configured?"));
174914
+ process.exit(1);
174915
+ }
174916
+ const summary = {
174917
+ totalIterations: 0,
174918
+ totalCostUsd: 0,
174919
+ totalTokens: 0,
174920
+ totalDurationMs: 0,
174921
+ specMet: false,
174922
+ iterationResults: []
174923
+ };
174924
+ for (let i = 1; i <= maxIterations; i++) {
174925
+ if (summary.totalCostUsd >= totalBudget) {
174926
+ console.log(chalk22.yellow(`
174927
+ Budget exhausted ($${summary.totalCostUsd.toFixed(4)} / $${totalBudget}). Stopping.`));
174928
+ break;
174929
+ }
174930
+ const remainingBudget = totalBudget - summary.totalCostUsd;
174931
+ console.log(chalk22.cyan(`
174932
+ \u2500\u2500 Iteration ${i}/${maxIterations} `) + chalk22.dim(`(budget remaining: $${remainingBudget.toFixed(4)}) \u2500\u2500`));
174933
+ if (opts.verify && i > 1) {
174934
+ const verifyResult = runShellCommand(opts.verify, cwd);
174935
+ if (verifyResult.exitCode === 0) {
174936
+ console.log(chalk22.green(" Specification met! Verify command exited 0."));
174937
+ summary.specMet = true;
174938
+ break;
174939
+ }
174940
+ }
174941
+ const gitDiff = getGitDiff(cwd);
174942
+ const verifyOutput = opts.verify ? getTestOutput(opts.verify, cwd) : "";
174943
+ const prompt = buildIterationPrompt({
174944
+ spec,
174945
+ iteration: i,
174946
+ maxIterations,
174947
+ previousResults: summary.iterationResults,
174948
+ gitDiff,
174949
+ verifyOutput
174950
+ });
174951
+ const modelId = agent.adapterConfig.model ?? "claude-sonnet-4-20250514";
174952
+ const adapter = router.resolve(modelId);
174953
+ const events = new agentModule.RuntimeEventBus();
174954
+ const tools = new agentModule.ToolRegistry();
174955
+ agentModule.registerBuiltinTools(tools);
174956
+ const runtime = new agentModule.AgentRuntime({
174957
+ adapter,
174958
+ tools,
174959
+ toolExecutor: new agentModule.ToolExecutor(),
174960
+ role: {
174961
+ name: agent.name,
174962
+ description: agent.description ?? "Loop agent",
174963
+ instructions: agent.adapterConfig.instructions ?? "You are an expert software engineer.",
174964
+ constraints: []
174965
+ },
174966
+ workingDir: cwd,
174967
+ events,
174968
+ maxCostUsd: remainingBudget,
174969
+ maxIterations: 50,
174970
+ db: store,
174971
+ modelRouter: router,
174972
+ // Enable context rotation within each iteration too
174973
+ contextRotation: { maxMessages: 40, maxTokensEstimate: 8e4 },
174974
+ stuckDetection: { maxRepeatedCalls: 3, maxConsecutiveErrors: 3 }
174975
+ });
174976
+ const taskId = randomUUID51();
174977
+ let iterOutput = "";
174978
+ try {
174979
+ for await (const event of runtime.runStreaming({
174980
+ prompt,
174981
+ agentId: agent.id,
174982
+ taskId
174983
+ })) {
174984
+ switch (event.type) {
174985
+ case "token":
174986
+ process.stdout.write(event.text);
174987
+ iterOutput += event.text;
174988
+ break;
174989
+ case "tool_start":
174990
+ console.log(chalk22.dim(`
174991
+ [tool] ${event.name}`));
174992
+ break;
174993
+ case "tool_result":
174994
+ if (!event.success) {
174995
+ console.log(chalk22.red(` [tool error] ${event.name}: ${event.result.slice(0, 100)}`));
174996
+ }
174997
+ break;
174998
+ case "error":
174999
+ console.log(chalk22.red(`
175000
+ [error] ${event.message}`));
175001
+ break;
175002
+ case "done": {
175003
+ const r = event.result;
175004
+ const iterResult = {
175005
+ iteration: i,
175006
+ output: r.output || iterOutput,
175007
+ exitReason: r.exitReason,
175008
+ costUsd: r.usage.estimatedCostUsd,
175009
+ tokens: r.usage.totalTokens,
175010
+ durationMs: r.durationMs,
175011
+ specMet: false
175012
+ };
175013
+ summary.totalCostUsd += r.usage.estimatedCostUsd;
175014
+ summary.totalTokens += r.usage.totalTokens;
175015
+ summary.totalDurationMs += r.durationMs;
175016
+ summary.totalIterations = i;
175017
+ if (opts.verify) {
175018
+ const postVerify = runShellCommand(opts.verify, cwd);
175019
+ iterResult.specMet = postVerify.exitCode === 0;
175020
+ }
175021
+ summary.iterationResults.push(iterResult);
175022
+ console.log("");
175023
+ console.log(chalk22.dim(` [${r.exitReason}] ${r.usage.totalTokens} tokens, $${r.usage.estimatedCostUsd.toFixed(4)}, ${(r.durationMs / 1e3).toFixed(1)}s`));
175024
+ if (iterResult.specMet) {
175025
+ console.log(chalk22.green(" Specification met!"));
175026
+ summary.specMet = true;
175027
+ }
175028
+ break;
175029
+ }
175030
+ }
175031
+ }
175032
+ } catch (err) {
175033
+ console.error(chalk22.red(`
175034
+ Iteration ${i} crashed: ${err instanceof Error ? err.message : String(err)}`));
175035
+ summary.iterationResults.push({
175036
+ iteration: i,
175037
+ output: `Error: ${err instanceof Error ? err.message : String(err)}`,
175038
+ exitReason: "error",
175039
+ costUsd: 0,
175040
+ tokens: 0,
175041
+ durationMs: 0,
175042
+ specMet: false
175043
+ });
175044
+ }
175045
+ if (summary.specMet) break;
175046
+ }
175047
+ console.log(chalk22.cyan("\n \u2500\u2500 Loop Summary \u2500\u2500"));
175048
+ console.log(chalk22.white(" Iterations:"), summary.totalIterations);
175049
+ console.log(chalk22.white(" Total cost:"), `$${summary.totalCostUsd.toFixed(4)}`);
175050
+ console.log(chalk22.white(" Total tokens:"), summary.totalTokens.toLocaleString());
175051
+ console.log(chalk22.white(" Total time:"), `${(summary.totalDurationMs / 1e3).toFixed(1)}s`);
175052
+ console.log(
175053
+ chalk22.white(" Spec met:"),
175054
+ summary.specMet ? chalk22.green("YES") : chalk22.red("NO")
175055
+ );
175056
+ console.log("");
175057
+ await store.close();
175058
+ process.exit(summary.specMet ? 0 : 1);
175059
+ });
175060
+ }
175061
+
173352
175062
  // src/index.ts
173353
175063
  function checkBetaAccess() {
173354
- const licenseFile = join29(homedir12(), ".nestor", "license.key");
173355
- if (!existsSync25(licenseFile)) return false;
173356
- const key = readFileSync25(licenseFile, "utf-8").trim();
175064
+ const licenseFile = join30(homedir12(), ".nestor", "license.key");
175065
+ if (!existsSync27(licenseFile)) return false;
175066
+ const key = readFileSync27(licenseFile, "utf-8").trim();
173357
175067
  return /^NESTOR-BETA-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(key);
173358
175068
  }
173359
175069
  var command2 = process.argv[2];
@@ -173373,7 +175083,7 @@ if (command2 && !["--help", "-h", "--version", "-V", "install"].includes(command
173373
175083
  }
173374
175084
  }
173375
175085
  var program = new Command();
173376
- program.name("nestor-sh").description("Nestor AI Agent Platform \u2014 orchestrate, secure and monitor AI agents").version("2.7.1");
175086
+ program.name("nestor-sh").description("Nestor AI Agent Platform \u2014 orchestrate, secure and monitor AI agents").version("3.0.0");
173377
175087
  registerStartCommand(program);
173378
175088
  registerInstallCommand(program);
173379
175089
  registerAgentCommand(program);
@@ -173393,6 +175103,7 @@ registerTemplateCommand(program);
173393
175103
  registerRagCommand(program);
173394
175104
  registerTestCommand(program);
173395
175105
  registerGuardrailCommand(program);
175106
+ registerLoopCommand(program);
173396
175107
  var args = process.argv.slice(2);
173397
175108
  if (args.length === 0) {
173398
175109
  Promise.resolve().then(() => (init_shell(), shell_exports)).then(({ registerShellCommand: _ }) => {