open-agents-ai 0.187.489 → 0.187.491

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
@@ -1199,6 +1199,219 @@ var init_tool_executor = __esm({
1199
1199
  }
1200
1200
  });
1201
1201
 
1202
+ // packages/execution/dist/tools/security-classifier.js
1203
+ function classifyTool(name10) {
1204
+ for (const rule of RULES) {
1205
+ const hit = typeof rule.match === "function" ? rule.match(name10) : rule.match.test(name10);
1206
+ if (hit)
1207
+ return applyOverrides(name10, rule.info);
1208
+ }
1209
+ return applyOverrides(name10, UNKNOWN_DEFAULT);
1210
+ }
1211
+ function applyOverrides(name10, base3) {
1212
+ const raw = process.env["OA_TOOL_OVERRIDES"];
1213
+ if (!raw)
1214
+ return base3;
1215
+ try {
1216
+ const overrides = JSON.parse(raw);
1217
+ const o2 = overrides[name10];
1218
+ if (!o2)
1219
+ return base3;
1220
+ return { ...base3, ...o2 };
1221
+ } catch {
1222
+ return base3;
1223
+ }
1224
+ }
1225
+ function canInvokeTool(args) {
1226
+ const info = classifyTool(args.toolName);
1227
+ const order = ["read", "run", "admin"];
1228
+ if (order.indexOf(args.scope) < order.indexOf(info.requires_scope)) {
1229
+ return {
1230
+ status: 403,
1231
+ reason: `Tool '${args.toolName}' requires '${info.requires_scope}' scope; caller has '${args.scope}'.`
1232
+ };
1233
+ }
1234
+ if (args.origin === "remote" && !info.off_device_allowed && args.scope !== "admin") {
1235
+ return {
1236
+ status: 403,
1237
+ reason: `Tool '${args.toolName}' is loopback-only (off_device_allowed=false). Remote callers must have 'admin' scope.`
1238
+ };
1239
+ }
1240
+ return null;
1241
+ }
1242
+ var CRITICAL_SENSITIVE, HARDWARE_DEVICE, AGENT_SPAWN, SHELL_EXEC, SANDBOXED_EXEC, NETWORK_OUTBOUND, NETWORK_BROWSER, LOCAL_WRITE, LOCAL_READ, NETWORK_READ, MEMORY_READ, MEMORY_WRITE, TASK_CONTROL, SCHEDULER_REMINDER, TASK_COMPLETE_NEUTRAL, RULES, UNKNOWN_DEFAULT;
1243
+ var init_security_classifier = __esm({
1244
+ "packages/execution/dist/tools/security-classifier.js"() {
1245
+ "use strict";
1246
+ CRITICAL_SENSITIVE = {
1247
+ categories: ["sensitive"],
1248
+ risk: "critical",
1249
+ requires_scope: "admin",
1250
+ off_device_allowed: false,
1251
+ rationale: "Modifies tool registry, identity, or sponsorship — admin only, loopback only."
1252
+ };
1253
+ HARDWARE_DEVICE = {
1254
+ categories: ["hardware"],
1255
+ risk: "high",
1256
+ requires_scope: "admin",
1257
+ off_device_allowed: false,
1258
+ rationale: "Hardware peripheral access (camera/mic/wifi/BT/SDR/desktop). Loopback-only by default."
1259
+ };
1260
+ AGENT_SPAWN = {
1261
+ categories: ["agent"],
1262
+ risk: "high",
1263
+ requires_scope: "admin",
1264
+ off_device_allowed: false,
1265
+ rationale: "Spawns sub-agents or sends inter-agent messages. Multiplies blast radius."
1266
+ };
1267
+ SHELL_EXEC = {
1268
+ categories: ["exec"],
1269
+ risk: "critical",
1270
+ requires_scope: "run",
1271
+ off_device_allowed: false,
1272
+ rationale: "Executes arbitrary shell commands. Loopback-only — remote callers need admin scope."
1273
+ };
1274
+ SANDBOXED_EXEC = {
1275
+ categories: ["exec"],
1276
+ risk: "high",
1277
+ requires_scope: "run",
1278
+ off_device_allowed: true,
1279
+ rationale: "Sandboxed code execution — bounded blast radius, OK for off-device with auth."
1280
+ };
1281
+ NETWORK_OUTBOUND = {
1282
+ categories: ["network"],
1283
+ risk: "medium",
1284
+ requires_scope: "run",
1285
+ off_device_allowed: true,
1286
+ rationale: "Outbound network call. Off-device callers may invoke with run scope."
1287
+ };
1288
+ NETWORK_BROWSER = {
1289
+ categories: ["network", "exec"],
1290
+ risk: "high",
1291
+ requires_scope: "run",
1292
+ off_device_allowed: false,
1293
+ rationale: "Headless browser — broad network + cookie / session access. Loopback-only."
1294
+ };
1295
+ LOCAL_WRITE = {
1296
+ categories: ["write"],
1297
+ risk: "medium",
1298
+ requires_scope: "run",
1299
+ off_device_allowed: true,
1300
+ rationale: "Local filesystem mutation. Run scope required."
1301
+ };
1302
+ LOCAL_READ = {
1303
+ categories: ["read"],
1304
+ risk: "low",
1305
+ requires_scope: "read",
1306
+ off_device_allowed: true,
1307
+ rationale: "Read-only inspection of local state."
1308
+ };
1309
+ NETWORK_READ = {
1310
+ categories: ["read", "network"],
1311
+ risk: "low",
1312
+ requires_scope: "read",
1313
+ off_device_allowed: true,
1314
+ rationale: "Network read (search/fetch). Safe to expose."
1315
+ };
1316
+ MEMORY_READ = {
1317
+ categories: ["read"],
1318
+ risk: "low",
1319
+ requires_scope: "read",
1320
+ off_device_allowed: true,
1321
+ rationale: "Reads associative memory (no mutation)."
1322
+ };
1323
+ MEMORY_WRITE = {
1324
+ categories: ["write"],
1325
+ risk: "medium",
1326
+ requires_scope: "run",
1327
+ off_device_allowed: true,
1328
+ rationale: "Mutates associative memory store."
1329
+ };
1330
+ TASK_CONTROL = {
1331
+ categories: ["exec"],
1332
+ risk: "medium",
1333
+ requires_scope: "run",
1334
+ off_device_allowed: true,
1335
+ rationale: "Controls background task lifecycle (start/stop/inspect)."
1336
+ };
1337
+ SCHEDULER_REMINDER = {
1338
+ categories: ["write"],
1339
+ risk: "medium",
1340
+ requires_scope: "run",
1341
+ off_device_allowed: true,
1342
+ rationale: "Schedules deferred actions or reminders."
1343
+ };
1344
+ TASK_COMPLETE_NEUTRAL = {
1345
+ categories: ["read"],
1346
+ risk: "low",
1347
+ requires_scope: "read",
1348
+ off_device_allowed: true,
1349
+ rationale: "Signals task completion. No side effects."
1350
+ };
1351
+ RULES = [
1352
+ // ── Critical sensitive: tool registry / identity / worktree / sponsorship
1353
+ { match: /^(create_tool|manage_tools|skill_build|tool_creator)$/, info: CRITICAL_SENSITIVE },
1354
+ { match: /^(identity_kernel|reflection_integrity|cohere_constraints)$/, info: CRITICAL_SENSITIVE },
1355
+ { match: /^(enter_worktree|exit_worktree)$/, info: CRITICAL_SENSITIVE },
1356
+ { match: /^(aiwg_setup|aiwg_health|aiwg_workflow)$/, info: CRITICAL_SENSITIVE },
1357
+ { match: /^(expose|sponsor|nexus_register)/, info: CRITICAL_SENSITIVE },
1358
+ // ── Hardware peripherals
1359
+ { match: /^(camera_capture|audio_capture|audio_playback|audio_analyze|asr_listen)$/, info: HARDWARE_DEVICE },
1360
+ { match: /^(wifi_control|bluetooth_scan|sdr_scan|flipper_zero|meshtastic|gps_location)$/, info: HARDWARE_DEVICE },
1361
+ { match: /^(desktop_click|desktop_describe|screenshot)$/, info: HARDWARE_DEVICE },
1362
+ { match: /^(jibberlink)$/, info: HARDWARE_DEVICE },
1363
+ // audio modem
1364
+ // ── Agent spawning + inter-agent messaging + P2P
1365
+ { match: /^(full_sub_agent|sub_agent|agent|send_message)$/, info: AGENT_SPAWN },
1366
+ { match: /^(factory|opencode|cron_agent|autoresearch)$/, info: AGENT_SPAWN },
1367
+ { match: /^nexus$/, info: AGENT_SPAWN },
1368
+ // P2P invocation
1369
+ { match: /^debate$/, info: AGENT_SPAWN },
1370
+ // multi-agent debate
1371
+ { match: /^replay_with_intervention$/, info: AGENT_SPAWN },
1372
+ // ── Shell + REPL (high risk exec, loopback only)
1373
+ { match: /^shell$/, info: SHELL_EXEC },
1374
+ { match: /^(repl|repl_exec|background_run)$/, info: SHELL_EXEC },
1375
+ // ── Sandboxed exec (OK for remote with auth)
1376
+ { match: /^(code_sandbox|cohere_sandbox)$/, info: SANDBOXED_EXEC },
1377
+ // ── Browser automation
1378
+ { match: /^(browser_action|playwright_browser|web_crawl)$/, info: NETWORK_BROWSER },
1379
+ // ── Network reads (safe)
1380
+ { match: /^(web_search|web_fetch)$/, info: NETWORK_READ },
1381
+ // ── Network outbound (mutating or remote inference)
1382
+ { match: /^(image_generate|vision|video_understand)$/, info: NETWORK_OUTBOUND },
1383
+ { match: /^(transcribe_file|transcribe_url|youtube_download)$/, info: NETWORK_OUTBOUND },
1384
+ { match: /^(fortemi_bridge)$/, info: NETWORK_OUTBOUND },
1385
+ // ── Memory tools
1386
+ { match: /^(memory_read|memory_search)$/, info: MEMORY_READ },
1387
+ { match: /^(memory_write|memory_metabolism|multimodal_memory|visual_memory|embedding_store)$/, info: MEMORY_WRITE },
1388
+ // ── Local writes (fs mutation)
1389
+ { match: /^(file_write|file_edit|file_patch|batch_edit|notebook_edit|structured_file|image_resize)$/, info: LOCAL_WRITE },
1390
+ { match: /^(working_notes|todo_write)$/, info: LOCAL_WRITE },
1391
+ { match: /^(scheduler|reminder|agenda)$/, info: SCHEDULER_REMINDER },
1392
+ { match: /^(exploration_culture)$/, info: LOCAL_WRITE },
1393
+ // ── Task control
1394
+ { match: /^(task_status|task_output|task_stop)$/, info: TASK_CONTROL },
1395
+ // ── Local reads (inspection)
1396
+ { match: /^(file_read|file_explore|list_directory|grep_search|glob_find)$/, info: LOCAL_READ },
1397
+ { match: /^(image_read|ocr|ocr_pdf|ocr_image_advanced|pdf_to_text|structured_read)$/, info: LOCAL_READ },
1398
+ { match: /^(symbol_search|impact_analysis|code_neighbors|repo_map|codebase_map|semantic_map|import_graph)$/, info: LOCAL_READ },
1399
+ { match: /^(diagnostic|git_info|environment_snapshot|process_health|todo_read|explore_tools)$/, info: LOCAL_READ },
1400
+ { match: /^(log_explore|log_packet|change_log|phase_recall|code_graph)$/, info: LOCAL_READ },
1401
+ { match: /^skill_(list|execute|read)$/, info: LOCAL_READ },
1402
+ // ── Task completion (neutral signal)
1403
+ { match: /^task_complete$/, info: TASK_COMPLETE_NEUTRAL }
1404
+ ];
1405
+ UNKNOWN_DEFAULT = {
1406
+ categories: ["sensitive"],
1407
+ risk: "high",
1408
+ requires_scope: "admin",
1409
+ off_device_allowed: false,
1410
+ rationale: "Unrecognized tool — defaulting to admin-only, loopback-only. Operators may override via OA_TOOL_OVERRIDES."
1411
+ };
1412
+ }
1413
+ });
1414
+
1202
1415
  // packages/execution/dist/process-kill.js
1203
1416
  import { execSync } from "node:child_process";
1204
1417
  function killProcessTree(pid, signal = "SIGKILL") {
@@ -510207,8 +510420,10 @@ __export(dist_exports, {
510207
510420
  buildMcpToolName: () => buildMcpToolName,
510208
510421
  buildSkillsSummary: () => buildSkillsSummary,
510209
510422
  buildSubProcessArgs: () => buildSubProcessArgs,
510423
+ canInvokeTool: () => canInvokeTool,
510210
510424
  checkConstraints: () => checkConstraints,
510211
510425
  checkDesktopDeps: () => checkDesktopDeps,
510426
+ classifyTool: () => classifyTool,
510212
510427
  clearExploreNotes: () => clearExploreNotes,
510213
510428
  clearImportGraphCache: () => clearImportGraphCache,
510214
510429
  clearWorkingNotes: () => clearWorkingNotes,
@@ -510321,6 +510536,7 @@ var init_dist5 = __esm({
510321
510536
  "packages/execution/dist/index.js"() {
510322
510537
  "use strict";
510323
510538
  init_tool_executor();
510539
+ init_security_classifier();
510324
510540
  init_shell();
510325
510541
  init_debate();
510326
510542
  init_replay_with_intervention();
@@ -576578,6 +576794,23 @@ async function tryRouteV1(ctx3) {
576578
576794
  if (pathname === "/v1/tools" && method === "GET") {
576579
576795
  return handleListTools(ctx3);
576580
576796
  }
576797
+ {
576798
+ const m2 = /^\/v1\/tools\/([^/]+)(\/call)?$/.exec(pathname);
576799
+ if (m2) {
576800
+ const toolName = decodeURIComponent(m2[1]);
576801
+ const isCall = !!m2[2];
576802
+ if (isCall && method === "POST") return handleCallTool(ctx3, toolName);
576803
+ if (!isCall && method === "GET") return handleGetTool(ctx3, toolName);
576804
+ sendProblem(ctx3.res, problemDetails({
576805
+ type: P.methodNotAllowed,
576806
+ status: 405,
576807
+ title: `Method ${method} not allowed for ${pathname}`,
576808
+ detail: isCall ? "POST /v1/tools/{name}/call to execute the tool." : "GET /v1/tools/{name} to fetch its schema.",
576809
+ instance: ctx3.requestId
576810
+ }));
576811
+ return true;
576812
+ }
576813
+ }
576581
576814
  if (pathname === "/v1/hooks" && method === "GET") {
576582
576815
  return handleListHooks(ctx3);
576583
576816
  }
@@ -577621,26 +577854,47 @@ async function handleListTools(ctx3) {
577621
577854
  sendJson(res, 200, { data: [], pagination: { limit: 50, offset: 0, total: 0, has_more: false } });
577622
577855
  return true;
577623
577856
  }
577857
+ const classify = mod2.classifyTool;
577624
577858
  const tools = [];
577625
577859
  for (const [key, value2] of Object.entries(mod2)) {
577626
577860
  if (typeof value2 !== "function") continue;
577627
577861
  const proto = value2.prototype;
577628
577862
  if (!proto || typeof proto.execute !== "function") continue;
577863
+ let inst = null;
577629
577864
  try {
577630
- const inst = new value2();
577631
- if (typeof inst.name === "string") {
577632
- tools.push({
577633
- name: inst.name,
577634
- class: key,
577635
- description: inst.description ?? "",
577636
- parameters: inst.parameters ?? null
577637
- });
577638
- }
577865
+ inst = new value2();
577639
577866
  } catch {
577867
+ try {
577868
+ inst = new value2(process.cwd());
577869
+ } catch {
577870
+ inst = null;
577871
+ }
577640
577872
  }
577873
+ if (!inst || typeof inst.name !== "string") continue;
577874
+ const name10 = inst.name;
577875
+ const security = classify ? classify(name10) : null;
577876
+ tools.push({
577877
+ name: name10,
577878
+ class: key,
577879
+ description: inst.description ?? "",
577880
+ parameters: inst.parameters ?? null,
577881
+ security: security ?? void 0,
577882
+ endpoints: {
577883
+ call: `/v1/tools/${encodeURIComponent(name10)}/call`,
577884
+ schema: `/v1/tools/${encodeURIComponent(name10)}`
577885
+ }
577886
+ });
577641
577887
  }
577888
+ tools.sort((a2, b) => a2.name.localeCompare(b.name));
577889
+ const filterCat = url.searchParams.get("category");
577890
+ const filterScope = url.searchParams.get("scope");
577891
+ const filterRisk = url.searchParams.get("risk");
577892
+ let filtered = tools;
577893
+ if (filterCat) filtered = filtered.filter((t2) => t2.security?.categories?.includes(filterCat));
577894
+ if (filterScope) filtered = filtered.filter((t2) => t2.security?.requires_scope === filterScope);
577895
+ if (filterRisk) filtered = filtered.filter((t2) => t2.security?.risk === filterRisk);
577642
577896
  const page2 = parsePagination(url.searchParams);
577643
- const envelope = paginated(tools, page2);
577897
+ const envelope = paginated(filtered, page2);
577644
577898
  const etag = computeEtag(envelope);
577645
577899
  if (checkNotModified(req2, res, etag)) return true;
577646
577900
  sendJson(res, 200, envelope, { etag, cacheControl: "private, max-age=60" });
@@ -577656,6 +577910,199 @@ async function handleListTools(ctx3) {
577656
577910
  return true;
577657
577911
  }
577658
577912
  }
577913
+ async function handleGetTool(ctx3, name10) {
577914
+ const { req: req2, res, requestId } = ctx3;
577915
+ try {
577916
+ const mod2 = await Promise.resolve().then(() => (init_dist5(), dist_exports)).catch(() => null);
577917
+ if (!mod2) {
577918
+ sendProblem(res, problemDetails({
577919
+ type: P.notFound,
577920
+ status: 404,
577921
+ title: "Tool registry unavailable",
577922
+ detail: "@open-agents/execution module failed to load.",
577923
+ instance: requestId
577924
+ }));
577925
+ return true;
577926
+ }
577927
+ const classify = mod2.classifyTool;
577928
+ let found = null;
577929
+ for (const [key, value2] of Object.entries(mod2)) {
577930
+ if (typeof value2 !== "function") continue;
577931
+ const proto = value2.prototype;
577932
+ if (!proto || typeof proto.execute !== "function") continue;
577933
+ let inst2 = null;
577934
+ try {
577935
+ inst2 = new value2();
577936
+ } catch {
577937
+ try {
577938
+ inst2 = new value2(process.cwd());
577939
+ } catch {
577940
+ inst2 = null;
577941
+ }
577942
+ }
577943
+ if (!inst2 || inst2.name !== name10) continue;
577944
+ found = { instance: inst2, className: key };
577945
+ break;
577946
+ }
577947
+ if (!found) {
577948
+ sendProblem(res, problemDetails({
577949
+ type: P.notFound,
577950
+ status: 404,
577951
+ title: `Tool '${name10}' not found`,
577952
+ detail: "No tool with that name is registered in @open-agents/execution.",
577953
+ instance: requestId
577954
+ }));
577955
+ return true;
577956
+ }
577957
+ const inst = found.instance;
577958
+ const security = classify ? classify(name10) : null;
577959
+ const body = {
577960
+ name: name10,
577961
+ class: found.className,
577962
+ description: inst.description ?? "",
577963
+ parameters: inst.parameters ?? null,
577964
+ security: security ?? void 0,
577965
+ endpoints: {
577966
+ call: `/v1/tools/${encodeURIComponent(name10)}/call`,
577967
+ schema: `/v1/tools/${encodeURIComponent(name10)}`
577968
+ }
577969
+ };
577970
+ const etag = computeEtag(body);
577971
+ if (checkNotModified(req2, res, etag)) return true;
577972
+ sendJson(res, 200, body, { etag, cacheControl: "private, max-age=60" });
577973
+ return true;
577974
+ } catch (err) {
577975
+ sendProblem(res, problemDetails({
577976
+ type: P.internalError,
577977
+ status: 500,
577978
+ title: "Tool schema fetch failed",
577979
+ detail: err instanceof Error ? err.message : String(err),
577980
+ instance: requestId
577981
+ }));
577982
+ return true;
577983
+ }
577984
+ }
577985
+ async function handleCallTool(ctx3, name10) {
577986
+ const { req: req2, res, requestId } = ctx3;
577987
+ try {
577988
+ const mod2 = await Promise.resolve().then(() => (init_dist5(), dist_exports)).catch(() => null);
577989
+ if (!mod2) {
577990
+ sendProblem(res, problemDetails({
577991
+ type: P.notFound,
577992
+ status: 404,
577993
+ title: "Tool registry unavailable",
577994
+ detail: "@open-agents/execution module failed to load.",
577995
+ instance: requestId
577996
+ }));
577997
+ return true;
577998
+ }
577999
+ let ToolClass = null;
578000
+ let className = "";
578001
+ for (const [key, value2] of Object.entries(mod2)) {
578002
+ if (typeof value2 !== "function") continue;
578003
+ const proto = value2.prototype;
578004
+ if (!proto || typeof proto.execute !== "function") continue;
578005
+ let probe = null;
578006
+ try {
578007
+ probe = new value2();
578008
+ } catch {
578009
+ try {
578010
+ probe = new value2(process.cwd());
578011
+ } catch {
578012
+ probe = null;
578013
+ }
578014
+ }
578015
+ if (probe?.name === name10) {
578016
+ ToolClass = value2;
578017
+ className = key;
578018
+ break;
578019
+ }
578020
+ }
578021
+ if (!ToolClass) {
578022
+ sendProblem(res, problemDetails({
578023
+ type: P.notFound,
578024
+ status: 404,
578025
+ title: `Tool '${name10}' not found`,
578026
+ detail: "Use GET /v1/tools to list available tools.",
578027
+ instance: requestId
578028
+ }));
578029
+ return true;
578030
+ }
578031
+ const remoteIp = (req2.socket?.remoteAddress || "").replace(/^::ffff:/, "");
578032
+ const origin = /^(127\.\d+\.\d+\.\d+|::1|localhost)$/.test(remoteIp) ? "loopback" : "remote";
578033
+ const reqAuth = req2;
578034
+ const scope = reqAuth._authScope ?? (origin === "loopback" ? "admin" : "read");
578035
+ const canInvoke = mod2.canInvokeTool;
578036
+ const denial = canInvoke ? canInvoke({ toolName: name10, origin, scope }) : null;
578037
+ if (denial) {
578038
+ sendProblem(res, problemDetails({
578039
+ type: P.forbidden,
578040
+ status: denial.status,
578041
+ title: "Tool invocation denied",
578042
+ detail: denial.reason,
578043
+ instance: requestId
578044
+ }));
578045
+ return true;
578046
+ }
578047
+ const body = await parseJsonBodyStrict(req2).catch(() => null);
578048
+ if (body !== null && typeof body !== "object") {
578049
+ sendProblem(res, problemDetails({
578050
+ type: P.invalidRequest,
578051
+ status: 400,
578052
+ title: "Body must be JSON object",
578053
+ detail: "POST {args: {...}, working_dir?: string}",
578054
+ instance: requestId
578055
+ }));
578056
+ return true;
578057
+ }
578058
+ const args = body?.args ?? {};
578059
+ const workingDir = typeof body?.working_dir === "string" ? body.working_dir : process.cwd();
578060
+ let tool = null;
578061
+ try {
578062
+ tool = new ToolClass(workingDir);
578063
+ } catch {
578064
+ try {
578065
+ tool = new ToolClass();
578066
+ } catch {
578067
+ tool = null;
578068
+ }
578069
+ }
578070
+ if (!tool) {
578071
+ sendProblem(res, problemDetails({
578072
+ type: P.internalError,
578073
+ status: 500,
578074
+ title: "Tool instantiation failed",
578075
+ detail: `Could not construct ${className}. Some tools require additional adapters (debate, replay) — use /v1/run for those.`,
578076
+ instance: requestId
578077
+ }));
578078
+ return true;
578079
+ }
578080
+ const result = await tool.execute(args).catch((e2) => ({
578081
+ success: false,
578082
+ output: "",
578083
+ error: e2 instanceof Error ? e2.message : String(e2),
578084
+ durationMs: 0
578085
+ }));
578086
+ sendJson(res, 200, {
578087
+ tool: name10,
578088
+ class: className,
578089
+ result,
578090
+ security: mod2.classifyTool ? mod2.classifyTool(name10) : void 0,
578091
+ origin,
578092
+ scope
578093
+ });
578094
+ return true;
578095
+ } catch (err) {
578096
+ sendProblem(res, problemDetails({
578097
+ type: P.internalError,
578098
+ status: 500,
578099
+ title: "Tool call failed",
578100
+ detail: err instanceof Error ? err.message : String(err),
578101
+ instance: requestId
578102
+ }));
578103
+ return true;
578104
+ }
578105
+ }
577659
578106
  async function handleListHooks(ctx3) {
577660
578107
  const { res } = ctx3;
577661
578108
  sendJson(res, 200, {
@@ -578303,7 +578750,8 @@ var init_routes_v1 = __esm({
578303
578750
  internalError: `${PROBLEM_BASE}/internal-error`,
578304
578751
  payloadTooLarge: `${PROBLEM_BASE}/payload-too-large`,
578305
578752
  notImplemented: `${PROBLEM_BASE}/not-implemented`,
578306
- aimsViolation: `${PROBLEM_BASE}/aims-violation`
578753
+ aimsViolation: `${PROBLEM_BASE}/aims-violation`,
578754
+ methodNotAllowed: `${PROBLEM_BASE}/method-not-allowed`
578307
578755
  };
578308
578756
  mcpManagerCache = /* @__PURE__ */ new Map();
578309
578757
  memoryStoresCache = null;
@@ -585527,7 +585975,39 @@ function getOpenApiSpec() {
585527
585975
  "/v1/evaluate": { post: { summary: "Evaluate a run by ID", tags: ["Agentic"], responses: { 200: { description: "Evaluation metrics" }, 404: { description: "Run not found" } } } },
585528
585976
  "/v1/index": { post: { summary: "Trigger repository indexing", tags: ["Agentic"], responses: { 202: { description: "Indexing started" } } } },
585529
585977
  // ───── P2: Tools / Hooks / Agents / Engines ─────
585530
- "/v1/tools": { get: { summary: "List agentic tool registry", tags: ["Tools"], responses: { 200: { description: "Paginated tools" } } } },
585978
+ "/v1/tools": {
585979
+ get: {
585980
+ summary: "List agentic tool registry with security metadata",
585981
+ tags: ["Tools"],
585982
+ description: "Returns each tool's name, class, description, JSON-schema parameters, security policy {categories, risk, requires_scope, off_device_allowed, rationale}, and per-tool endpoint hrefs. Filters: ?category=read|write|exec|network|hardware|agent|sensitive · ?scope=read|run|admin · ?risk=low|medium|high|critical. Pagination via ?limit=N&offset=N (default limit=50; pass limit=200 to see all).",
585983
+ responses: { 200: { description: "Paginated tools with security metadata" } }
585984
+ }
585985
+ },
585986
+ "/v1/tools/{name}": {
585987
+ get: {
585988
+ summary: "Single tool schema + security policy",
585989
+ tags: ["Tools"],
585990
+ parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" }, description: "Tool name (snake_case, e.g. file_read)" }],
585991
+ responses: {
585992
+ 200: { description: "Tool entry with security info" },
585993
+ 404: { description: "Unknown tool name" }
585994
+ }
585995
+ }
585996
+ },
585997
+ "/v1/tools/{name}/call": {
585998
+ post: {
585999
+ summary: "Execute a single tool directly (MCP-style, no agent loop)",
586000
+ tags: ["Tools"],
586001
+ description: "Body: {args: object, working_dir?: string}. Returns the tool's ToolResult {success, output, llmContent?, error?, durationMs} plus security metadata. Auth gating: request scope (read/run/admin) must satisfy tool.requires_scope; non-loopback callers need admin scope unless tool.off_device_allowed=true. Anonymous loopback callers default to admin scope; anonymous remote callers default to read scope.",
586002
+ parameters: [{ name: "name", in: "path", required: true, schema: { type: "string" } }],
586003
+ responses: {
586004
+ 200: { description: "Tool result envelope" },
586005
+ 403: { description: "Forbidden — scope insufficient or off-device gate" },
586006
+ 404: { description: "Unknown tool" },
586007
+ 500: { description: "Tool execution failed" }
586008
+ }
586009
+ }
586010
+ },
585531
586011
  "/v1/hooks": { get: { summary: "List hook types and counts", tags: ["Tools"], responses: { 200: { description: "Hook registry" } } } },
585532
586012
  "/v1/agents": { get: { summary: "List agent types", tags: ["Tools"], responses: { 200: { description: "Agent type registry" } } } },
585533
586013
  "/v1/engines": { get: { summary: "List long-running engines", tags: ["Engines"], responses: { 200: { description: "Engine status + state files" } } } },
@@ -587573,13 +588053,25 @@ function handleHelp(req2, res) {
587573
588053
  "DELETE /v1/runs/:id": "Cancel a running task"
587574
588054
  },
587575
588055
  tools_and_skills: {
587576
- "GET /v1/tools": "List available agentic tools",
588056
+ "GET /v1/tools": "List agentic tools w/ security metadata. Filters: ?category=read|write|exec|network|hardware|agent|sensitive ?scope=read|run|admin ?risk=low|medium|high|critical ?limit=N&offset=N",
588057
+ "GET /v1/tools/:name": "Get a single tool's schema + security policy (categories, risk, requires_scope, off_device_allowed)",
588058
+ "POST /v1/tools/:name/call": "Execute a single tool directly (MCP-style). Body: {args: object, working_dir?: string}. Auth-gated by per-tool scope; off-device callers need admin scope unless tool.off_device_allowed=true",
587577
588059
  "GET /v1/skills": "List available skills (slash commands)",
587578
588060
  "GET /v1/skills/:name": "Get skill details and content",
587579
588061
  "GET /v1/commands": "List slash commands",
587580
588062
  "POST /v1/commands/:cmd": "Execute a slash command",
587581
588063
  "GET /v1/agents": "List available agent types"
587582
588064
  },
588065
+ tool_security_model: {
588066
+ scopes: {
588067
+ read: "Pure inspection (file_read, web_search, list_directory, memory_read, ...)",
588068
+ run: "Local mutations + network outbound (file_write, shell, web_fetch, image_generate, ...)",
588069
+ admin: "Hardware peripherals, agent spawning, identity, tool registry mutations"
588070
+ },
588071
+ off_device_policy: "Tools with off_device_allowed=false are loopback-only; remote callers need admin scope to bypass.",
588072
+ anonymous_default: "Loopback callers default to admin scope. Remote callers default to read scope until authenticated.",
588073
+ override: "Operators can override per-tool classification via OA_TOOL_OVERRIDES env var (JSON map of name → partial security info)."
588074
+ },
587583
588075
  mcp: {
587584
588076
  "GET /v1/mcps": "List connected MCP (Model Context Protocol) servers",
587585
588077
  "GET /v1/mcps/:name": "Get MCP server details and available tools",
@@ -589567,7 +590059,9 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
589567
590059
  agentic: {
589568
590060
  run: { href: `${baseUrl}/v1/run`, description: "Submit autonomous task." },
589569
590061
  runs: { href: `${baseUrl}/v1/runs`, description: "List runs." },
589570
- tools: { href: `${baseUrl}/v1/tools`, description: "Agentic tool registry." },
590062
+ tools: { href: `${baseUrl}/v1/tools`, description: "Agentic tool registry with security metadata. Filters: ?category=, ?scope=, ?risk=." },
590063
+ tool_schema: { href: `${baseUrl}/v1/tools/{name}`, description: "Single-tool schema + security policy." },
590064
+ tool_call: { href: `${baseUrl}/v1/tools/{name}/call`, description: "Execute a single tool directly (MCP-style). POST {args: object, working_dir?: string}. Auth-gated per-tool." },
589571
590065
  mcps: { href: `${baseUrl}/v1/mcps`, description: "MCP server registry." }
589572
590066
  },
589573
590067
  memory: {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.489",
3
+ "version": "0.187.491",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.489",
9
+ "version": "0.187.491",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.489",
3
+ "version": "0.187.491",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",