open-agents-ai 0.187.556 → 0.187.558

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/index.js CHANGED
@@ -1443,10 +1443,10 @@ var init_security_classifier = __esm({
1443
1443
  RULES = [
1444
1444
  // ── Critical sensitive: tool registry / identity / worktree / sponsorship
1445
1445
  { match: /^(create_tool|manage_tools|skill_build|tool_creator)$/, info: CRITICAL_SENSITIVE },
1446
- { match: /^(identity_kernel|reflection_integrity|cohere_constraints)$/, info: CRITICAL_SENSITIVE },
1446
+ { match: /^(identity_kernel|reflection_integrity|reflect|cohere_constraints)$/, info: CRITICAL_SENSITIVE },
1447
1447
  { match: /^(enter_worktree|exit_worktree)$/, info: CRITICAL_SENSITIVE },
1448
1448
  { match: /^(aiwg_setup|aiwg_health|aiwg_workflow)$/, info: CRITICAL_SENSITIVE },
1449
- { match: /^(expose|sponsor|nexus_register)/, info: CRITICAL_SENSITIVE },
1449
+ { match: /^(expose|sponsor|nexus_register|wallet_|x402|payment|spend)/, info: CRITICAL_SENSITIVE },
1450
1450
  // ── Hardware peripherals
1451
1451
  { match: /^(camera_capture|audio_capture|audio_playback|audio_analyze|asr_listen)$/, info: HARDWARE_DEVICE },
1452
1452
  { match: /^(wifi_control|bluetooth_scan|sdr_scan|flipper_zero|meshtastic|gps_location)$/, info: HARDWARE_DEVICE },
@@ -1471,22 +1471,22 @@ var init_security_classifier = __esm({
1471
1471
  // ── Network reads (safe)
1472
1472
  { match: /^(web_search|web_fetch)$/, info: NETWORK_READ },
1473
1473
  // ── Network outbound (mutating or remote inference)
1474
- { match: /^(image_generate|vision|video_understand)$/, info: NETWORK_OUTBOUND },
1474
+ { match: /^(image_generate|generate_image|vision|video_understand)$/, info: NETWORK_OUTBOUND },
1475
1475
  { match: /^(transcribe_file|transcribe_url|youtube_download)$/, info: NETWORK_OUTBOUND },
1476
1476
  { match: /^(fortemi_bridge)$/, info: NETWORK_OUTBOUND },
1477
1477
  // ── Memory tools
1478
1478
  { match: /^(memory_read|memory_search)$/, info: MEMORY_READ },
1479
- { match: /^(memory_write|memory_metabolism|multimodal_memory|visual_memory|embedding_store)$/, info: MEMORY_WRITE },
1479
+ { match: /^(memory_write|memory_metabolism|memory_metabolize|multimodal_memory|visual_memory|embedding_store)$/, info: MEMORY_WRITE },
1480
1480
  // ── Local writes (fs mutation)
1481
- { match: /^(file_write|file_edit|file_patch|batch_edit|notebook_edit|structured_file|image_resize)$/, info: LOCAL_WRITE },
1481
+ { match: /^(file_write|file_edit|file_patch|batch_edit|notebook_edit|structured_file|create_structured_file|image_resize)$/, info: LOCAL_WRITE },
1482
1482
  { match: /^(working_notes|todo_write)$/, info: LOCAL_WRITE },
1483
1483
  { match: /^(scheduler|reminder|agenda)$/, info: SCHEDULER_REMINDER },
1484
- { match: /^(exploration_culture)$/, info: LOCAL_WRITE },
1484
+ { match: /^(exploration_culture|explore)$/, info: LOCAL_WRITE },
1485
1485
  // ── Task control
1486
1486
  { match: /^(task_status|task_output|task_stop)$/, info: TASK_CONTROL },
1487
1487
  // ── Local reads (inspection)
1488
- { match: /^(file_read|file_explore|list_directory|grep_search|glob_find)$/, info: LOCAL_READ },
1489
- { match: /^(image_read|ocr|ocr_pdf|ocr_image_advanced|pdf_to_text|structured_read)$/, info: LOCAL_READ },
1488
+ { match: /^(file_read|file_explore|list_directory|grep_search|glob_find|find_files)$/, info: LOCAL_READ },
1489
+ { match: /^(image_read|ocr|ocr_pdf|ocr_image_advanced|pdf_to_text|structured_read|read_structured_file)$/, info: LOCAL_READ },
1490
1490
  { match: /^(symbol_search|impact_analysis|code_neighbors|repo_map|codebase_map|semantic_map|import_graph)$/, info: LOCAL_READ },
1491
1491
  { match: /^(diagnostic|git_info|environment_snapshot|process_health|todo_read|explore_tools)$/, info: LOCAL_READ },
1492
1492
  { match: /^(log_explore|log_packet|change_log|phase_recall|code_graph)$/, info: LOCAL_READ },
@@ -1504,6 +1504,106 @@ var init_security_classifier = __esm({
1504
1504
  }
1505
1505
  });
1506
1506
 
1507
+ // packages/execution/dist/tools/tool-manifest.js
1508
+ function clearToolManifestCache() {
1509
+ probeCache.clear();
1510
+ }
1511
+ function defaultExposureForTool(name10, security) {
1512
+ if (security.risk === "critical" || security.risk === "high" || security.categories.includes("hardware") || security.categories.includes("agent") || security.categories.includes("sensitive")) {
1513
+ return "profile-opt-in";
1514
+ }
1515
+ if (!security.off_device_allowed || security.categories.includes("exec")) {
1516
+ return "local-only";
1517
+ }
1518
+ if (/(^|_)(x402|wallet|payment|spend|sponsor|expose)(_|$)/.test(name10)) {
1519
+ return "profile-opt-in";
1520
+ }
1521
+ return "trusted-default";
1522
+ }
1523
+ function buildToolManifestFromModule(mod2, options2 = {}) {
1524
+ const cwd4 = options2.cwd ?? process.cwd();
1525
+ const ttlMs = options2.ttlMs ?? DEFAULT_TTL_MS;
1526
+ const now = options2.now ?? Date.now();
1527
+ const entries = [];
1528
+ for (const [className, value2] of Object.entries(mod2)) {
1529
+ if (!isExecutableToolClass(value2))
1530
+ continue;
1531
+ const probe = probeToolClass(className, value2, cwd4, now, ttlMs);
1532
+ if (!probe.result.available || !probe.result.name)
1533
+ continue;
1534
+ const security = classifyTool(probe.result.name);
1535
+ entries.push({
1536
+ name: probe.result.name,
1537
+ className,
1538
+ description: probe.result.description ?? "",
1539
+ parameters: probe.result.parameters ?? null,
1540
+ security,
1541
+ defaultExposure: defaultExposureForTool(probe.result.name, security),
1542
+ availability: {
1543
+ available: true,
1544
+ cached: probe.cached,
1545
+ checkedAt: probe.checkedAt
1546
+ }
1547
+ });
1548
+ }
1549
+ entries.sort((a2, b) => a2.name.localeCompare(b.name));
1550
+ return entries;
1551
+ }
1552
+ function toolCategoriesInclude(entry, category) {
1553
+ return entry.security.categories.includes(category);
1554
+ }
1555
+ function isExecutableToolClass(value2) {
1556
+ if (typeof value2 !== "function")
1557
+ return false;
1558
+ const proto = value2.prototype;
1559
+ return typeof proto?.execute === "function";
1560
+ }
1561
+ function probeToolClass(className, ToolClass, cwd4, now, ttlMs) {
1562
+ const cacheKey = `${className}:${cwd4}`;
1563
+ const cached = probeCache.get(cacheKey);
1564
+ if (cached && cached.expiresAt > now) {
1565
+ return { result: cached.result, cached: true, checkedAt: cached.checkedAt };
1566
+ }
1567
+ const checkedAt = new Date(now).toISOString();
1568
+ const result = instantiateForMetadata(ToolClass, cwd4);
1569
+ probeCache.set(cacheKey, { result, checkedAt, expiresAt: now + ttlMs });
1570
+ return { result, cached: false, checkedAt };
1571
+ }
1572
+ function instantiateForMetadata(ToolClass, cwd4) {
1573
+ let inst = null;
1574
+ let lastError = null;
1575
+ for (const args of [[], [cwd4]]) {
1576
+ try {
1577
+ inst = new ToolClass(...args);
1578
+ break;
1579
+ } catch (err) {
1580
+ lastError = err;
1581
+ }
1582
+ }
1583
+ if (!inst || typeof inst.name !== "string") {
1584
+ return {
1585
+ available: false,
1586
+ reason: lastError instanceof Error ? lastError.message : "tool did not expose a string name"
1587
+ };
1588
+ }
1589
+ const tool = inst;
1590
+ return {
1591
+ available: true,
1592
+ name: tool.name,
1593
+ description: typeof tool.description === "string" ? tool.description : "",
1594
+ parameters: tool.parameters ?? null
1595
+ };
1596
+ }
1597
+ var DEFAULT_TTL_MS, probeCache;
1598
+ var init_tool_manifest = __esm({
1599
+ "packages/execution/dist/tools/tool-manifest.js"() {
1600
+ "use strict";
1601
+ init_security_classifier();
1602
+ DEFAULT_TTL_MS = 3e4;
1603
+ probeCache = /* @__PURE__ */ new Map();
1604
+ }
1605
+ });
1606
+
1507
1607
  // packages/execution/dist/process-kill.js
1508
1608
  import { execSync } from "node:child_process";
1509
1609
  function killProcessTree(pid, signal = "SIGKILL") {
@@ -511573,12 +511673,14 @@ __export(dist_exports, {
511573
511673
  buildScaffoldedPrompt: () => buildScaffoldedPrompt,
511574
511674
  buildSkillsSummary: () => buildSkillsSummary,
511575
511675
  buildSubProcessArgs: () => buildSubProcessArgs,
511676
+ buildToolManifestFromModule: () => buildToolManifestFromModule,
511576
511677
  canInvokeTool: () => canInvokeTool,
511577
511678
  checkConstraints: () => checkConstraints,
511578
511679
  checkDesktopDeps: () => checkDesktopDeps,
511579
511680
  classifyTool: () => classifyTool,
511580
511681
  clearExploreNotes: () => clearExploreNotes,
511581
511682
  clearImportGraphCache: () => clearImportGraphCache,
511683
+ clearToolManifestCache: () => clearToolManifestCache,
511582
511684
  clearWorkingNotes: () => clearWorkingNotes,
511583
511685
  closeAllCodeGraphDBs: () => closeAllCodeGraphDBs,
511584
511686
  codeGraphDBExists: () => codeGraphDBExists,
@@ -511588,6 +511690,7 @@ __export(dist_exports, {
511588
511690
  createFortemiBridgeTools: () => createFortemiBridgeTools,
511589
511691
  createTransport: () => createTransport,
511590
511692
  createWorktree: () => createWorktree2,
511693
+ defaultExposureForTool: () => defaultExposureForTool,
511591
511694
  deleteTodos: () => deleteTodos,
511592
511695
  detectElevationMethod: () => detectElevationMethod,
511593
511696
  detectSearchProvider: () => detectSearchProvider,
@@ -511676,6 +511779,7 @@ __export(dist_exports, {
511676
511779
  stopFullSubAgent: () => stopFullSubAgent,
511677
511780
  summarizeLog: () => summarizeLog,
511678
511781
  todoDir: () => todoDir,
511782
+ toolCategoriesInclude: () => toolCategoriesInclude,
511679
511783
  touchFile: () => touchFile,
511680
511784
  validateSlug: () => validateSlug,
511681
511785
  venvBinDir: () => venvBinDir,
@@ -511690,6 +511794,7 @@ var init_dist5 = __esm({
511690
511794
  "use strict";
511691
511795
  init_tool_executor();
511692
511796
  init_security_classifier();
511797
+ init_tool_manifest();
511693
511798
  init_shell();
511694
511799
  init_debate();
511695
511800
  init_replay_with_intervention();
@@ -513951,12 +514056,12 @@ function buildStagnationDiagnostic(signals) {
513951
514056
  ``,
513952
514057
  `2. STATE A HYPOTHESIS in writing — what specifically is wrong? "I think X is failing because Y." Be concrete. Do NOT propose a fix yet.`,
513953
514058
  ``,
513954
- `3. VERIFY ONE ASSUMPTION — pick the ONE thing you most BELIEVE to be true and test it with the smallest possible command native to whatever ecosystem you're in. Examples of the *shape* (not the exact commands):`,
514059
+ `3. VERIFY ONE ASSUMPTION — pick the ONE local thing you most BELIEVE to be true and test it with the smallest possible read/grep/test command. Examples of the *shape* (not the exact commands):`,
513955
514060
  ` • Is this artifact present on disk? (one read of the path)`,
513956
514061
  ` • Does this import / reference resolve? (read 5 lines around it)`,
513957
514062
  ` • Is this environment value set? (one query)`,
513958
514063
  ` • Is this binary on PATH? (one which/where)`,
513959
- ` • Don't know what an error means? web_search("<exact error string>")`,
514064
+ ` • Don't know what an error means? Re-read the full local error, extract the implicated path/symbol/config key/command, and verify that target locally before changing anything.`,
513960
514065
  ``,
513961
514066
  `4. CHECK SILENT FAILURES — package managers and build systems frequently report "success" while silently dropping artifacts you needed. Don't trust summary output ("added N", "build complete") without verifying the SPECIFIC artifact exists.`,
513962
514067
  ``,
@@ -514086,7 +514191,7 @@ function renderReflectionMessage(r2) {
514086
514191
  }
514087
514192
  if (r2.attempts >= 3) {
514088
514193
  lines.push(``);
514089
- lines.push(`[FORCED your intrinsic knowledge has not resolved this in ${r2.attempts} attempts. Your NEXT call MUST be \`web_search("${r2.wentWrong.replace(/"/g, '\\"').slice(0, 120)}")\` (or close-equivalent). Read the top result before making another fix attempt.]`);
514194
+ lines.push(`[FORCED - local failure loop detected after ${r2.attempts} attempts. Your NEXT move must be anchored to the local error above: name one implicated path/symbol/config key or failing command, verify that target with the smallest local read/grep/test step, then make one targeted edit or declare the exact blocker. Do not web_search for a codebase-local failure unless the current user task explicitly requires external documentation.]`);
514090
514195
  }
514091
514196
  lines.push(`VERIFY this hypothesis with a single small command BEFORE retrying the same tool. If you retry without verifying, you will likely fail the same way.]`);
514092
514197
  return lines.join("\n");
@@ -523569,9 +523674,9 @@ function handoffEnabled() {
523569
523674
  function handoffTtlMs() {
523570
523675
  const raw = process.env["OA_TASK_HANDOFF_TTL_MS"];
523571
523676
  if (!raw)
523572
- return DEFAULT_TTL_MS;
523677
+ return DEFAULT_TTL_MS2;
523573
523678
  const n2 = Number.parseInt(raw, 10);
523574
- return Number.isFinite(n2) && n2 > 0 ? n2 : DEFAULT_TTL_MS;
523679
+ return Number.isFinite(n2) && n2 > 0 ? n2 : DEFAULT_TTL_MS2;
523575
523680
  }
523576
523681
  function handoffPath(oaDir) {
523577
523682
  return path5.join(oaDir, "handoffs", "latest.json");
@@ -523685,11 +523790,11 @@ If you need verbatim details from the prior task (specific code, exact error mes
523685
523790
  };
523686
523791
  return [boundary, summary];
523687
523792
  }
523688
- var DEFAULT_TTL_MS, MAX_SUMMARY_CHARS, MAX_FILES, MAX_TOOLS, MAX_LAST_ACTIONS;
523793
+ var DEFAULT_TTL_MS2, MAX_SUMMARY_CHARS, MAX_FILES, MAX_TOOLS, MAX_LAST_ACTIONS;
523689
523794
  var init_taskHandoff = __esm({
523690
523795
  "packages/orchestrator/dist/taskHandoff.js"() {
523691
523796
  "use strict";
523692
- DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
523797
+ DEFAULT_TTL_MS2 = 24 * 60 * 60 * 1e3;
523693
523798
  MAX_SUMMARY_CHARS = 8e3;
523694
523799
  MAX_FILES = 30;
523695
523800
  MAX_TOOLS = 24;
@@ -526133,6 +526238,7 @@ RECOVERY: cd to the directory containing '${file}', run a plain install with no
526133
526238
 
526134
526239
  // packages/orchestrator/dist/agenticRunner.js
526135
526240
  import { existsSync as _fsExistsSync, readFileSync as _fsReadFileSync, writeFileSync as _fsWriteFileSync, mkdirSync as _fsMkdirSync } from "node:fs";
526241
+ import { createHash as _createHash } from "node:crypto";
526136
526242
  import { join as _pathJoin } from "node:path";
526137
526243
  import { homedir as _osHomedir } from "node:os";
526138
526244
  import { z as z15 } from "zod";
@@ -526495,9 +526601,9 @@ var init_agenticRunner = __esm({
526495
526601
  _lastBuildSuccessCommand = "";
526496
526602
  // REG-31: prevent duplicate completion suggestion per turn
526497
526603
  _completionPromptInjectedThisTurn = false;
526498
- // REG-32: one-shot per-stem nudge toward web_search on opaque errors.
526604
+ // REG-32: one-shot per-stem nudge toward local triage on opaque errors.
526499
526605
  // Closes the gap where qwen3.6 pivots away from a single failure (different
526500
- // stem on next turn) and never triggers REG-26/28's retry-based web_search
526606
+ // stem on next turn) and never triggers REG-26/28's retry-based local
526501
526607
  // escalations. Fires on the FIRST failure of a stem when the local
526502
526608
  // diagnostic can't help (unknown category or long multi-line error with
526503
526609
  // no extractable subject).
@@ -526539,7 +526645,7 @@ var init_agenticRunner = __esm({
526539
526645
  // writes ARE happening (just earlier in the run). Detect: in last
526540
526646
  // 12 turns, ld+sh count >= 25 + fw growth <= 2 + recent shell
526541
526647
  // failure exists. Fire CRITICAL halt instructing the agent to stop
526542
- // exploring and either web_search or fix one specific thing.
526648
+ // exploring and either locally triage the failure or fix one specific thing.
526543
526649
  // Cooldown 8 turns after firing.
526544
526650
  _wideExplorationCooldownUntilTurn = -1;
526545
526651
  // REG-45: sticky cross-turn escalation. The dispatch-time reflection
@@ -526698,8 +526804,8 @@ var init_agenticRunner = __esm({
526698
526804
  //
526699
526805
  // Semantics: when REG-61 fires, this latch goes true. While true, every
526700
526806
  // non-bypass tool call gets BLOCKED with a synthetic error result. The
526701
- // bypass set includes the 4 creative-edit tools plus web_search /
526702
- // task_complete / ask_user (escape hatches). Any creative edit dispatch
526807
+ // bypass set includes the 4 creative-edit tools plus task_complete /
526808
+ // ask_user / explicit web-task escape hatches. Any creative edit dispatch
526703
526809
  // clears the latch ("directive satisfied"). Shell, file_read, todo_*,
526704
526810
  // grep_search, list_directory etc. are NOT in bypass — those are the
526705
526811
  // exact patterns batch528/529 agents used to ignore REG-61.
@@ -526710,10 +526816,11 @@ var init_agenticRunner = __esm({
526710
526816
  // sub_agent delegation. DECOMP-1's informational directive was ignored
526711
526817
  // (0 sub_agent calls in 466 tool-call run despite directive at turn 1).
526712
526818
  // Mirrors the BFC-61.G escalation arc: when the agent has edited
526713
- // ≥THRESHOLD distinct files in main context WITHOUT calling sub_agent,
526819
+ // ≥THRESHOLD distinct files in main context WITHOUT successful sub_agent,
526714
526820
  // the dispatcher BLOCKS edits to NEW files (paths not yet edited) until
526715
- // sub_agent is invoked. Edits to already-touched files are still allowed
526716
- // (current-module finishing work). sub_agent dispatch clears the gate.
526821
+ // sub_agent succeeds. Edits to already-touched files are still allowed
526822
+ // (current-module finishing work). Failed or malformed delegation does
526823
+ // not clear the gate.
526717
526824
  // Kill switch: OA_DISABLE_DECOMP2=1.
526718
526825
  _decomp2MainContextFiles = /* @__PURE__ */ new Set();
526719
526826
  _decomp2SubAgentCalls = 0;
@@ -527249,7 +527356,7 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
527249
527356
  * - kill-switch `OA_DISABLE_DECOMP2` is not set
527250
527357
  *
527251
527358
  * Already-touched paths pass through (current-module finishing work allowed).
527252
- * sub_agent / web_search / task_complete pass through (not creative-edit tools).
527359
+ * sub_agent / task_complete / explicit web-task tools pass through (not creative-edit tools).
527253
527360
  */
527254
527361
  _maybeDecomp2Block(tc, turn) {
527255
527362
  if (!this._decomp2GateActive)
@@ -527291,7 +527398,7 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
527291
527398
  ].join("\n");
527292
527399
  this.emit({
527293
527400
  type: "status",
527294
- content: `DECOMP-2 NEW-FILE BLOCK — rejected ${tc.name}('${_editPath}') at turn ${turn}; gate stays active until sub_agent is invoked`,
527401
+ content: `DECOMP-2 NEW-FILE BLOCK — rejected ${tc.name}('${_editPath}') at turn ${turn}; gate stays active until sub_agent succeeds`,
527295
527402
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
527296
527403
  });
527297
527404
  this._tagSyntheticFailure({
@@ -527312,8 +527419,9 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
527312
527419
  * Side effects when fired:
527313
527420
  * - On successful creative edit: adds path to `_decomp2MainContextFiles`,
527314
527421
  * possibly activates `_decomp2GateActive` (emits status).
527315
- * - On sub_agent / priority_delegate / background_run: increments
527316
- * counter, clears gate (emits status).
527422
+ * - On successful sub_agent / priority_delegate / background_run:
527423
+ * increments counter, clears gate (emits status). Failed or malformed
527424
+ * delegation attempts do not satisfy the gate.
527317
527425
  *
527318
527426
  * Pure post-dispatch: caller invokes AFTER the tool result is in hand,
527319
527427
  * regardless of which loop the dispatch happened in.
@@ -527330,13 +527438,23 @@ Pick the SMALLEST concrete deliverable from the spec — typically the project e
527330
527438
  this._decomp2GateActive = true;
527331
527439
  this.emit({
527332
527440
  type: "status",
527333
- content: `DECOMP-2 NEW-FILE GATE ACTIVATED — ${this._decomp2MainContextFiles.size} distinct files edited in main context, 0 sub_agent calls; further edits to NEW files will be blocked until sub_agent is invoked`,
527441
+ content: `DECOMP-2 NEW-FILE GATE ACTIVATED — ${this._decomp2MainContextFiles.size} distinct files edited in main context, 0 successful sub_agent calls; further edits to NEW files will be blocked until sub_agent succeeds`,
527334
527442
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
527335
527443
  });
527336
527444
  }
527337
527445
  }
527338
527446
  }
527339
527447
  if (tc.name === "sub_agent" || tc.name === "priority_delegate" || tc.name === "background_run") {
527448
+ if (result?.success !== true) {
527449
+ if (this._decomp2GateActive) {
527450
+ this.emit({
527451
+ type: "status",
527452
+ content: `DECOMP-2 DELEGATION FAILED — '${tc.name}' did not clear gate at turn ${turn}; fix delegation arguments/result before editing another new file`,
527453
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
527454
+ });
527455
+ }
527456
+ return;
527457
+ }
527340
527458
  this._decomp2SubAgentCalls++;
527341
527459
  if (this._decomp2GateActive) {
527342
527460
  this._decomp2GateActive = false;
@@ -527883,8 +528001,13 @@ ${body}`;
527883
528001
  scanTurn++;
527884
528002
  for (const tc of msg.tool_calls) {
527885
528003
  const name10 = tc.function.name;
527886
- const argsRaw = (tc.function.arguments ?? "").slice(0, 200);
527887
- const fp = `${name10}|${argsRaw}`;
528004
+ let parsedArgs;
528005
+ try {
528006
+ parsedArgs = JSON.parse(tc.function.arguments || "{}");
528007
+ } catch {
528008
+ parsedArgs = void 0;
528009
+ }
528010
+ const fp = parsedArgs ? this._buildToolFingerprint(name10, parsedArgs) : `${name10}:raw#sha256:${_createHash("sha256").update(tc.function.arguments ?? "").digest("hex").slice(0, 20)}`;
527888
528011
  let resultIdx = -1;
527889
528012
  for (let j = i2 + 1; j < Math.min(messages2.length, i2 + 8); j++) {
527890
528013
  const r2 = messages2[j];
@@ -528565,6 +528688,42 @@ ${body}`;
528565
528688
  lines.push(`(turn ${turn} — failures auto-expire after 10 turns; cleared on success or successful retry)`);
528566
528689
  return lines.join("\n");
528567
528690
  }
528691
+ /**
528692
+ * Render a short, local-code-first nudge from the freshest failure signal.
528693
+ * This is intentionally not a web-search suggestion: most coding-agent loops
528694
+ * are caused by ignoring local compiler/test output, not by missing internet
528695
+ * knowledge. The nudge points at the exact local error, path, symbol, or
528696
+ * command the next move should be anchored to.
528697
+ */
528698
+ _renderLocalFailureNudge(turn) {
528699
+ const fresh = this._recentFailures.filter((f2) => turn - f2.turn <= 10);
528700
+ const latest = fresh[fresh.length - 1];
528701
+ if (!latest) {
528702
+ return [
528703
+ `[LOCAL ERROR-INFORMED NUDGE]`,
528704
+ `No fresh tool failure is recorded. Use the active todo, files already touched, and the last visible test/build output to pick the smallest plausible local edit. If there is no local target, declare the specific blocker instead of looping.`
528705
+ ].join("\n");
528706
+ }
528707
+ const raw = `${latest.error || ""}
528708
+ ${latest.output || ""}`.trim();
528709
+ const firstLine = raw.split(/\r?\n/).find((l2) => l2.trim().length > 0)?.trim().slice(0, 220) || "(no error line captured)";
528710
+ const command = latest.tool === "shell" ? String(latest.args["command"] ?? latest.args["cmd"] ?? "").slice(0, 220) : "";
528711
+ const pathMatches = Array.from(new Set((raw.match(/(?:[A-Za-z]:)?(?:\.{1,2}\/|\/)?[\w@./-]+\.(?:[cm]?tsx?|jsx?|json|css|html|md|ya?ml|py|go|rs|java|kt|rb|php|c|cc|cpp|h|hpp|sh|sql)(?::\d+(?::\d+)?)?/g) ?? []).filter((p2) => !/^https?:\/\//i.test(p2)).map((p2) => p2.slice(0, 160)))).slice(0, 4);
528712
+ const quotedSubjects = Array.from(raw.matchAll(/["'`]([^"'`\n]{3,100})["'`]/g)).map((m2) => m2[1].trim()).filter((s2) => s2.length >= 3 && !/^https?:\/\//i.test(s2)).slice(0, 4);
528713
+ const lines = [
528714
+ `[LOCAL ERROR-INFORMED NUDGE]`,
528715
+ `Last failing tool: ${latest.tool} (turn ${latest.turn})`,
528716
+ `First error line: ${firstLine}`
528717
+ ];
528718
+ if (command)
528719
+ lines.push(`Failing local command: ${command}`);
528720
+ if (pathMatches.length > 0)
528721
+ lines.push(`Local paths named by error: ${pathMatches.join(", ")}`);
528722
+ if (quotedSubjects.length > 0)
528723
+ lines.push(`Quoted local subjects: ${quotedSubjects.join(", ")}`);
528724
+ lines.push(`Next useful move: anchor on the local signal above. Pick one implicated path/symbol or the file most directly connected to the failing command, make one small targeted edit, then run the failing local command once. If no local target exists after one focused local inspection, declare the blocker. Do not spend the next move on web search for a codebase-local failure.`);
528725
+ return lines.join("\n");
528726
+ }
528568
528727
  /**
528569
528728
  * REG-3: Render the current todo list as a compact transient block so the
528570
528729
  * agent can read its own plan without calling todo_read or re-emitting
@@ -528899,6 +529058,69 @@ ${blob}
528899
529058
  }
528900
529059
  return `${tool}#${(h >>> 0).toString(16)}`;
528901
529060
  }
529061
+ /**
529062
+ * Build an exact, deterministic tool-argument key for cache/dedupe identity.
529063
+ *
529064
+ * Older call sites truncated values before forming the fingerprint, so two
529065
+ * different long paths/queries/commands with the same prefix could be treated
529066
+ * as duplicate reads. This keeps the human-readable `path=...` shape for
529067
+ * diagnostics, but appends a hash whenever a value must be shortened for log
529068
+ * size. The hash is computed over the full canonical value.
529069
+ */
529070
+ _buildExactArgsKey(args) {
529071
+ return Object.entries(args ?? {}).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${this._formatExactArgValue(v)}`).join(",");
529072
+ }
529073
+ _buildToolFingerprint(name10, args) {
529074
+ return `${name10}:${this._buildExactArgsKey(args)}`;
529075
+ }
529076
+ _formatExactArgValue(value2) {
529077
+ const canonical = this._canonicalArgValue(value2);
529078
+ const oneLine = canonical.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
529079
+ if (oneLine.length <= 240) {
529080
+ return this._escapeArgsKeyValue(oneLine);
529081
+ }
529082
+ const head = oneLine.slice(0, 160);
529083
+ const tail = oneLine.slice(-40);
529084
+ const hash = _createHash("sha256").update(canonical).digest("hex").slice(0, 20);
529085
+ return `${this._escapeArgsKeyValue(head)}...${this._escapeArgsKeyValue(tail)}#sha256:${hash}`;
529086
+ }
529087
+ _escapeArgsKeyValue(value2) {
529088
+ return value2.replace(/\\/g, "\\\\").replace(/,/g, "\\,");
529089
+ }
529090
+ _canonicalArgValue(value2, seen = /* @__PURE__ */ new WeakSet()) {
529091
+ if (typeof value2 === "string")
529092
+ return value2;
529093
+ if (value2 === null)
529094
+ return "#null";
529095
+ if (value2 === void 0)
529096
+ return "#undefined";
529097
+ if (typeof value2 === "number")
529098
+ return `#number:${Number.isNaN(value2) ? "NaN" : String(value2)}`;
529099
+ if (typeof value2 === "boolean")
529100
+ return `#boolean:${String(value2)}`;
529101
+ if (typeof value2 === "bigint")
529102
+ return `#bigint:${String(value2)}`;
529103
+ if (typeof value2 === "symbol")
529104
+ return `#symbol:${String(value2.description ?? "")}`;
529105
+ if (typeof value2 === "function")
529106
+ return `#function:${value2.name || "anonymous"}`;
529107
+ if (value2 instanceof Date)
529108
+ return `#date:${value2.toISOString()}`;
529109
+ if (Array.isArray(value2)) {
529110
+ if (seen.has(value2))
529111
+ return "#cycle";
529112
+ seen.add(value2);
529113
+ return `#array:[${value2.map((v) => this._canonicalArgValue(v, seen)).join(",")}]`;
529114
+ }
529115
+ if (typeof value2 === "object") {
529116
+ if (seen.has(value2))
529117
+ return "#cycle";
529118
+ seen.add(value2);
529119
+ const entries = Object.entries(value2).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${this._canonicalArgValue(v, seen)}`);
529120
+ return `#object:{${entries.join(",")}}`;
529121
+ }
529122
+ return String(value2);
529123
+ }
528902
529124
  /** Register a tool for the agent to use */
528903
529125
  registerTool(tool) {
528904
529126
  this.tools.set(tool.name, tool);
@@ -530043,7 +530265,9 @@ If this matches your current shape, try it before continuing.`
530043
530265
  ``,
530044
530266
  ` (a) PRODUCE: emit a file_write / file_edit / file_patch / shell-mutation that creates or changes content. Even a partial draft of the next planned file is progress.`,
530045
530267
  ``,
530046
- ` (b) WEB SEARCH: if a SPECIFIC error string or unknown API is blocking you, run \`web_search\` with the exact error / API name. External knowledge is faster than guessing.`,
530268
+ ` (b) ERROR-INFORMED LOCAL TRIAGE: anchor on the freshest local failure, identify the implicated file/path/symbol/command, then make one targeted local move instead of broad exploration.`,
530269
+ ``,
530270
+ this._renderLocalFailureNudge(turn),
530047
530271
  ``,
530048
530272
  ` (c) DEBATE: if you've tried 3+ approaches and they all hit the same wall, invoke \`debate\` with the failed task as the prompt — get a second opinion.`,
530049
530273
  ``,
@@ -530212,7 +530436,9 @@ ${_staleSamples.join("\n")}` : ``,
530212
530436
  ``,
530213
530437
  ` (b) DELETE-AND-RESTART: If the file has been rewritten ${_wtWorstCount} times, the current approach is wrong. Either delete the file and try a fundamentally different design, OR revert to a known-good earlier version (use git, working_notes, or memory_search to find one).`,
530214
530438
  ``,
530215
- ` (c) WEB-SEARCH: If the error is framework- or version-specific (config files often are), web_search the EXACT error string + relevant tool name. External knowledge beats blind iteration.`,
530439
+ ` (c) ERROR-INFORMED LOCAL TRIAGE: use the exact failing local command and error line to identify one implicated path/symbol/config key, then inspect or change only that target. Do not search online for a codebase-local failure.`,
530440
+ ``,
530441
+ this._renderLocalFailureNudge(turn),
530216
530442
  ``,
530217
530443
  ` (d) DECLARE BLOCKED: If the file's correct shape genuinely isn't knowable from the spec + this codebase, call task_complete with a summary that names this specific file as the blocker. Don't burn more turns on it.`,
530218
530444
  ``,
@@ -531273,7 +531499,7 @@ ${memoryLines.join("\n")}`
531273
531499
  });
531274
531500
  } catch {
531275
531501
  }
531276
- const argsKey = Object.entries(tc.arguments ?? {}).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "string" ? v.slice(0, 160) : JSON.stringify(v).slice(0, 160)}`).join(",");
531502
+ const argsKey = this._buildExactArgsKey(tc.arguments ?? {});
531277
531503
  toolCallLog.push({ name: tc.name, argsKey, turn, timestampMs: Date.now() });
531278
531504
  const _toolLogTailIdx = toolCallLog.length - 1;
531279
531505
  this._toolLastUsedTurn.set(tc.name, turn);
@@ -531358,8 +531584,8 @@ ${memoryLines.join("\n")}`
531358
531584
  const REG61_BYPASS_TOOLS = /* @__PURE__ */ new Set([
531359
531585
  ...REG61_EDIT_TOOLS,
531360
531586
  // Escape hatches: explicitly allowed while gate is active so
531361
- // the agent can: search for unknown info (web_search), exit
531362
- // cleanly (task_complete), or escalate to human (ask_user).
531587
+ // the agent can exit cleanly (task_complete), escalate to human
531588
+ // (ask_user), or complete an explicit web task without deadlock.
531363
531589
  // shell, file_read, todo_*, grep_search, list_directory are
531364
531590
  // NOT in bypass — those are the exact patterns batch528/529
531365
531591
  // agents used to ignore REG-61.
@@ -531388,6 +531614,7 @@ ${memoryLines.join("\n")}`
531388
531614
  });
531389
531615
  const _dbgLoop = this._detectDebugLoop(toolCallLog);
531390
531616
  const _debugLoopSampleSafe = (_dbgLoop.repeatedSample ?? "").slice(0, 120);
531617
+ const _localFailureNudge = this._renderLocalFailureNudge(turn);
531391
531618
  const reg61BlockMsg = _dbgLoop.detected ? [
531392
531619
  `[BLOCKED — REG-61 directive in effect — REG-66 DEBUG-LOOP detected]`,
531393
531620
  ``,
@@ -531405,10 +531632,11 @@ ${memoryLines.join("\n")}`
531405
531632
  ``,
531406
531633
  `Issue EXACTLY ONE of: file_write / file_edit / batch_edit / file_patch on a single concrete change. The edit must actually change disk state; dry-runs and no-ops do not clear this directive.`,
531407
531634
  ``,
531408
- `Allowed bypasses (will not be blocked but will not clear the directive either):`,
531409
- ` • web_search — search the EXACT recurring error string`,
531410
- ` task_complete exit if you genuinely cannot identify any plausible perturbation`,
531411
- ` ask_user — escalate to human (if available)`,
531635
+ _localFailureNudge,
531636
+ ``,
531637
+ `Allowed exits (will not be blocked but will not clear the directive either):`,
531638
+ ` * task_complete - exit if you genuinely cannot identify any plausible local perturbation`,
531639
+ ` * ask_user - escalate to human (if available)`,
531412
531640
  ``,
531413
531641
  `Once you make a real edit, the directive clears and you'll see the new test result.`
531414
531642
  ].join("\n") : [
@@ -531422,10 +531650,11 @@ ${memoryLines.join("\n")}`
531422
531650
  ` • batch_edit — multiple find/replace edits in one call`,
531423
531651
  ` • file_patch — apply a version-checked line-range patch`,
531424
531652
  ``,
531425
- `These tools are also allowed while the directive is active (will not be blocked, will not clear the gate):`,
531426
- ` • web_search — for genuinely-unknown APIs / error strings`,
531427
- ` task_complete to exit if you cannot make any progress`,
531428
- ` ask_user — to escalate to human (if available)`,
531653
+ _localFailureNudge,
531654
+ ``,
531655
+ `These exits are also allowed while the directive is active (will not be blocked, will not clear the gate):`,
531656
+ ` * task_complete - to exit if you cannot make any local progress`,
531657
+ ` * ask_user - to escalate to human (if available)`,
531429
531658
  ``,
531430
531659
  `Until you issue a creative edit that actually changes disk, ALL of these will be BLOCKED again on every turn: file_read, file_explore, list_directory, grep_search, shell, todo_write, todo_read, memory_read, memory_write, etc. Pick the smallest concrete change that moves work forward.`
531431
531660
  ].join("\n");
@@ -531541,7 +531770,7 @@ ${memoryLines.join("\n")}`
531541
531770
  }
531542
531771
  toolCallBudget.set(tc.name, budgetRemaining - 1);
531543
531772
  }
531544
- const toolFingerprint = `${tc.name}:${argsKey}`;
531773
+ const toolFingerprint = this._buildToolFingerprint(tc.name, tc.arguments ?? {});
531545
531774
  const baseIsReadLike = ![
531546
531775
  "file_write",
531547
531776
  "file_edit",
@@ -532520,22 +532749,19 @@ Respond with EXACTLY this structure before your next tool call:
532520
532749
  }
532521
532750
  if (!_prior && !this._opaqueErrorHintInjected.has(_refStem) && _entry.subject == null && (categorizeError(_refErr) === "unknown" || _refErr.length > 100)) {
532522
532751
  this._opaqueErrorHintInjected.add(_refStem);
532523
- const _searchQuery = _entry.wentWrong.replace(/"/g, '\\"').slice(0, 200);
532524
532752
  pushSoftInjection("system", [
532525
- `[OPAQUE ERROR — local diagnostic patterns cannot pinpoint a specific target to verify. Error class is unrecognized or output is substantial enough that external context is likely needed.]`,
532753
+ `[OPAQUE LOCAL ERROR — local diagnostic patterns cannot yet pinpoint a specific target to verify. Error class is unrecognized or output is substantial enough that a structured local triage step is needed.]`,
532526
532754
  ``,
532527
532755
  `Tool: ${tc.name}`,
532528
532756
  `Error: "${_entry.wentWrong}"`,
532529
532757
  ``,
532530
- `Before attempting a fix or pivoting to a different command, run a web search of the exact error string:`,
532531
- ``,
532532
- ` web_search({"query": "${_searchQuery}"})`,
532758
+ `Before attempting a fix or pivoting to a different command, extract one local target from the failure: path, symbol, config key, import name, or failing command. Then inspect or edit only that target.`,
532533
532759
  ``,
532534
- `A 30-second external lookup is more reliable than local guesses for framework/version-specific errors your training data may not cover.`
532760
+ this._renderLocalFailureNudge(turn)
532535
532761
  ].join("\n"));
532536
532762
  this.emit({
532537
532763
  type: "status",
532538
- content: `REG-32 opaque-error nudge fired for stem '${_refStem.slice(0, 60)}' — suggested web_search('${_searchQuery.slice(0, 80)}')`,
532764
+ content: `REG-32 opaque-error local nudge fired for stem '${_refStem.slice(0, 60)}'`,
532539
532765
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
532540
532766
  });
532541
532767
  }
@@ -532924,11 +533150,7 @@ ${sr.result.output}`;
532924
533150
  break;
532925
533151
  const batchFingerprintFirstId = /* @__PURE__ */ new Map();
532926
533152
  const batchInFlight = /* @__PURE__ */ new Map();
532927
- const buildBatchFp = (call) => {
532928
- const args = call.args ?? {};
532929
- const argsKey = Object.entries(args).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "string" ? v.slice(0, 160) : JSON.stringify(v).slice(0, 160)}`).join(",");
532930
- return `${call.name}:${argsKey}`;
532931
- };
533153
+ const buildBatchFp = (call) => this._buildToolFingerprint(call.name, call.args ?? {});
532932
533154
  for (const call of batch2.calls) {
532933
533155
  const fp = buildBatchFp(call);
532934
533156
  if (!batchFingerprintFirstId.has(fp)) {
@@ -533321,7 +533543,7 @@ You have used ${totalTurns} turns and ${toolCallCount} tool calls so far. The ta
533321
533543
  1. ASSESS: What have you accomplished so far? What specific part remains?
533322
533544
  2. DIAGNOSE: If stuck, what exactly is blocking? Read error output carefully.
533323
533545
  3. PIVOT: If the current approach failed, try a different strategy.
533324
- 4. INVESTIGATE: Use grep_search, find_files, web_search to find answers.
533546
+ 4. INVESTIGATE: Use grep_search, find_files, file_read, and small local test commands to find answers. Use web_search only when the task explicitly depends on external documentation.
533325
533547
  5. SIMPLIFY: Break remaining work into the smallest possible steps.
533326
533548
  6. VERIFY: Run tests/build after EVERY change.
533327
533549
 
@@ -533471,7 +533693,7 @@ Integrate this guidance into your current approach. Continue working on the task
533471
533693
  if (this.aborted)
533472
533694
  break;
533473
533695
  toolCallCount++;
533474
- const bfArgsKey = Object.entries(tc.arguments ?? {}).sort(([a2], [b]) => a2.localeCompare(b)).map(([k, v]) => `${k}=${typeof v === "string" ? v.slice(0, 160) : JSON.stringify(v).slice(0, 160)}`).join(",");
533696
+ const bfArgsKey = this._buildExactArgsKey(tc.arguments ?? {});
533475
533697
  toolCallLog.push({ name: tc.name, argsKey: bfArgsKey });
533476
533698
  this._toolLastUsedTurn.set(tc.name, turn);
533477
533699
  if (this._contextTree) {
@@ -535049,19 +535271,27 @@ ${trimmedNew}`;
535049
535271
  const succeeded = !isError2;
535050
535272
  const preview = msg.content.slice(0, 80);
535051
535273
  let toolName = "unknown";
535274
+ let toolArgs;
535052
535275
  if (msg.tool_call_id) {
535053
535276
  for (const m2 of recent) {
535054
535277
  if (m2.tool_calls) {
535055
535278
  for (const tc of m2.tool_calls) {
535056
535279
  if (tc.id === msg.tool_call_id) {
535057
535280
  toolName = tc.function.name;
535281
+ try {
535282
+ toolArgs = JSON.parse(tc.function.arguments || "{}");
535283
+ } catch {
535284
+ toolArgs = void 0;
535285
+ }
535058
535286
  }
535059
535287
  }
535060
535288
  }
535061
535289
  }
535062
535290
  }
535063
- if (!this._littlemanToolOutcomes.some((o2) => o2.turn === turn && o2.tool === toolName)) {
535064
- this._littlemanToolOutcomes.push({ turn, tool: toolName, succeeded, preview });
535291
+ const argsKey = toolArgs ? this._buildExactArgsKey(toolArgs) : void 0;
535292
+ const fingerprint = toolArgs ? this._buildToolFingerprint(toolName, toolArgs) : void 0;
535293
+ if (!this._littlemanToolOutcomes.some((o2) => o2.turn === turn && o2.tool === toolName && o2.fingerprint === fingerprint)) {
535294
+ this._littlemanToolOutcomes.push({ turn, tool: toolName, argsKey, fingerprint, succeeded, preview });
535065
535295
  }
535066
535296
  }
535067
535297
  }
@@ -535115,14 +535345,14 @@ ${trimmedNew}`;
535115
535345
  args = JSON.parse(tc.function.arguments);
535116
535346
  } catch {
535117
535347
  }
535118
- const argsKey = name10 === "shell" ? String(args.command ?? "").slice(0, 60) : name10 === "web_fetch" ? String(args.url ?? "").slice(0, 80) : String(args.path ?? args.url ?? args.query ?? "").slice(0, 60);
535119
- const prior = this._littlemanToolOutcomes.find((o2) => o2.succeeded && o2.tool === name10 && o2.preview.includes(argsKey.slice(0, 30)) && o2.turn < turn);
535348
+ const argsKey = this._buildExactArgsKey(args);
535349
+ const fingerprint = this._buildToolFingerprint(name10, args);
535350
+ const prior = this._littlemanToolOutcomes.find((o2) => o2.succeeded && o2.tool === name10 && o2.fingerprint === fingerprint && o2.turn < turn);
535120
535351
  if (prior) {
535121
- const blockKey = `${name10}:${argsKey}`;
535122
- this._littlemanRedundantBlocks.add(blockKey);
535352
+ this._littlemanRedundantBlocks.add(fingerprint);
535123
535353
  emitReaction("redundant_action", `Already ran ${name10} successfully on turn ${prior.turn}`, 0.8, prior.preview);
535124
535354
  if (this._observerMode === "skillcoach" || this._observerMode === "both") {
535125
- this.pendingUserMessages.push(`⚠ You already ran ${name10} successfully on turn ${prior.turn} with similar arguments. Do NOT re-run it. Use the existing result and proceed.`);
535355
+ this.pendingUserMessages.push(`⚠ You already ran ${name10} successfully on turn ${prior.turn} with exact arguments (${argsKey.slice(0, 120)}). Do NOT re-run it. Use the existing result and proceed.`);
535126
535356
  }
535127
535357
  this.emit({
535128
535358
  type: "status",
@@ -589983,37 +590213,20 @@ async function handleListTools(ctx3) {
589983
590213
  sendJson(res, 200, { data: [], pagination: { limit: 50, offset: 0, total: 0, has_more: false } });
589984
590214
  return true;
589985
590215
  }
589986
- const classify = mod2.classifyTool;
589987
- const tools = [];
589988
- for (const [key, value2] of Object.entries(mod2)) {
589989
- if (typeof value2 !== "function") continue;
589990
- const proto = value2.prototype;
589991
- if (!proto || typeof proto.execute !== "function") continue;
589992
- let inst = null;
589993
- try {
589994
- inst = new value2();
589995
- } catch {
589996
- try {
589997
- inst = new value2(process.cwd());
589998
- } catch {
589999
- inst = null;
590000
- }
590216
+ const buildManifest = mod2.buildToolManifestFromModule;
590217
+ const tools = buildManifest ? buildManifest(mod2, { cwd: process.cwd() }).map((entry) => ({
590218
+ name: entry.name,
590219
+ class: entry.className,
590220
+ description: entry.description ?? "",
590221
+ parameters: entry.parameters ?? null,
590222
+ security: entry.security ?? void 0,
590223
+ default_exposure: entry.defaultExposure,
590224
+ availability: entry.availability,
590225
+ endpoints: {
590226
+ call: `/v1/tools/${encodeURIComponent(entry.name)}/call`,
590227
+ schema: `/v1/tools/${encodeURIComponent(entry.name)}`
590001
590228
  }
590002
- if (!inst || typeof inst.name !== "string") continue;
590003
- const name10 = inst.name;
590004
- const security = classify ? classify(name10) : null;
590005
- tools.push({
590006
- name: name10,
590007
- class: key,
590008
- description: inst.description ?? "",
590009
- parameters: inst.parameters ?? null,
590010
- security: security ?? void 0,
590011
- endpoints: {
590012
- call: `/v1/tools/${encodeURIComponent(name10)}/call`,
590013
- schema: `/v1/tools/${encodeURIComponent(name10)}`
590014
- }
590015
- });
590016
- }
590229
+ })) : [];
590017
590230
  tools.sort((a2, b) => a2.name.localeCompare(b.name));
590018
590231
  const filterCat = url.searchParams.get("category");
590019
590232
  const filterScope = url.searchParams.get("scope");
@@ -590058,26 +590271,8 @@ async function handleGetTool(ctx3, name10) {
590058
590271
  }));
590059
590272
  return true;
590060
590273
  }
590061
- const classify = mod2.classifyTool;
590062
- let found = null;
590063
- for (const [key, value2] of Object.entries(mod2)) {
590064
- if (typeof value2 !== "function") continue;
590065
- const proto = value2.prototype;
590066
- if (!proto || typeof proto.execute !== "function") continue;
590067
- let inst2 = null;
590068
- try {
590069
- inst2 = new value2();
590070
- } catch {
590071
- try {
590072
- inst2 = new value2(process.cwd());
590073
- } catch {
590074
- inst2 = null;
590075
- }
590076
- }
590077
- if (!inst2 || inst2.name !== name10) continue;
590078
- found = { instance: inst2, className: key };
590079
- break;
590080
- }
590274
+ const buildManifest = mod2.buildToolManifestFromModule;
590275
+ const found = buildManifest ? buildManifest(mod2, { cwd: process.cwd() }).find((entry) => entry.name === name10) : null;
590081
590276
  if (!found) {
590082
590277
  sendProblem(res, problemDetails({
590083
590278
  type: P.notFound,
@@ -590088,14 +590283,14 @@ async function handleGetTool(ctx3, name10) {
590088
590283
  }));
590089
590284
  return true;
590090
590285
  }
590091
- const inst = found.instance;
590092
- const security = classify ? classify(name10) : null;
590093
590286
  const body = {
590094
590287
  name: name10,
590095
590288
  class: found.className,
590096
- description: inst.description ?? "",
590097
- parameters: inst.parameters ?? null,
590098
- security: security ?? void 0,
590289
+ description: found.description ?? "",
590290
+ parameters: found.parameters ?? null,
590291
+ security: found.security ?? void 0,
590292
+ default_exposure: found.defaultExposure,
590293
+ availability: found.availability,
590099
590294
  endpoints: {
590100
590295
  call: `/v1/tools/${encodeURIComponent(name10)}/call`,
590101
590296
  schema: `/v1/tools/${encodeURIComponent(name10)}`