open-agents-ai 0.184.38 → 0.184.39

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.
Files changed (3) hide show
  1. package/README.md +86 -0
  2. package/dist/index.js +54 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -505,6 +505,88 @@ OA_API_KEYS="grafana-key:read:grafana,ci-key:run:github-actions,ops-key:admin:op
505
505
  curl -H "Authorization: Bearer ops-key" http://localhost:11435/v1/models
506
506
  ```
507
507
 
508
+ #### Tool-Use Profiles
509
+
510
+ Enterprise access control — define which tools, shell commands, and settings the agent can use per API key or per request.
511
+
512
+ **3 built-in presets:**
513
+
514
+ | Profile | Description | Tools |
515
+ |---------|-------------|-------|
516
+ | `full` | No restrictions | All tools and commands |
517
+ | `ci-safe` | CI/CD — read + test only | file_read, grep, shell (npm test only) |
518
+ | `readonly` | Read-only analysis | No writes, no shell mutations |
519
+
520
+ ```bash
521
+ # List all profiles (presets + custom)
522
+ curl -H "Authorization: Bearer $KEY" http://localhost:11435/v1/profiles
523
+ ```
524
+ ```json
525
+ {"profiles":[{"name":"readonly","description":"Read-only","encrypted":false,"source":"preset"},{"name":"ci-safe",...}]}
526
+ ```
527
+
528
+ ```bash
529
+ # Get profile details
530
+ curl -H "Authorization: Bearer $KEY" http://localhost:11435/v1/profiles/ci-safe
531
+ ```
532
+ ```json
533
+ {"profile":{"name":"ci-safe","tools":{"allow":["file_read","grep_search","shell"],"shell_allow":["npm test","npx eslint"]},"limits":{"max_turns":15}}}
534
+ ```
535
+
536
+ ```bash
537
+ # Create custom profile (admin only)
538
+ curl -X POST http://localhost:11435/v1/profiles \
539
+ -H "Authorization: Bearer $ADMIN_KEY" \
540
+ -H "Content-Type: application/json" \
541
+ -d '{
542
+ "name": "frontend-dev",
543
+ "description": "Frontend team — no backend access",
544
+ "tools": {
545
+ "allow": ["file_read", "file_write", "file_edit", "shell", "grep_search"],
546
+ "shell_deny": ["rm -rf", "sudo", "docker", "kubectl"]
547
+ },
548
+ "commands": { "deny": ["destroy", "expose", "sponsor"] },
549
+ "limits": { "max_turns": 20, "timeout_s": 300 }
550
+ }'
551
+ ```
552
+
553
+ ```bash
554
+ # Create password-protected profile (AES-256-GCM encrypted)
555
+ curl -X POST http://localhost:11435/v1/profiles \
556
+ -H "Authorization: Bearer $ADMIN_KEY" \
557
+ -H "Content-Type: application/json" \
558
+ -d '{"name":"prod-ops","password":"s3cret","tools":{"deny":["file_write"]}}'
559
+ ```
560
+
561
+ ```bash
562
+ # Use a profile with /v1/run (header or body)
563
+ curl -X POST http://localhost:11435/v1/run \
564
+ -H "Authorization: Bearer $KEY" \
565
+ -H "X-Tool-Profile: ci-safe" \
566
+ -H "X-Working-Directory: $(pwd)" \
567
+ -H "Content-Type: application/json" \
568
+ -d '{"task":"run the test suite and report failures"}'
569
+
570
+ # Or in the body:
571
+ curl -X POST http://localhost:11435/v1/run \
572
+ -H "Authorization: Bearer $KEY" \
573
+ -H "Content-Type: application/json" \
574
+ -d '{"task":"analyze code quality","profile":"readonly"}'
575
+ ```
576
+
577
+ ```bash
578
+ # Load encrypted profile (password in header)
579
+ curl -H "Authorization: Bearer $KEY" \
580
+ -H "X-Profile-Password: s3cret" \
581
+ http://localhost:11435/v1/profiles/prod-ops
582
+ ```
583
+
584
+ ```bash
585
+ # Delete a custom profile (admin only, presets cannot be deleted)
586
+ curl -X DELETE -H "Authorization: Bearer $ADMIN_KEY" \
587
+ http://localhost:11435/v1/profiles/frontend-dev
588
+ ```
589
+
508
590
  #### Endpoint Reference
509
591
 
510
592
  | Method | Path | Auth | Description |
@@ -529,6 +611,10 @@ curl -H "Authorization: Bearer ops-key" http://localhost:11435/v1/models
529
611
  | PUT | `/v1/config/endpoint` | admin | Switch endpoint |
530
612
  | GET | `/v1/commands` | read | List commands |
531
613
  | POST | `/v1/commands/:cmd` | run | Execute command |
614
+ | GET | `/v1/profiles` | read | List all profiles (presets + custom) |
615
+ | GET | `/v1/profiles/:name` | read | Get profile details (X-Profile-Password for encrypted) |
616
+ | POST | `/v1/profiles` | admin | Create/update profile (password field for encryption) |
617
+ | DELETE | `/v1/profiles/:name` | admin | Delete custom profile |
532
618
 
533
619
  ### Enterprise Licensing
534
620
 
package/dist/index.js CHANGED
@@ -65041,14 +65041,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
65041
65041
  }
65042
65042
  try {
65043
65043
  const apiPort = parseInt(process.env["OA_PORT"] || "11435", 10);
65044
+ const isWin2 = process.platform === "win32";
65044
65045
  const portFree = await new Promise((resolve36) => {
65045
65046
  const net = __require("net");
65046
65047
  const tester = net.createServer();
65047
65048
  tester.once("error", (err) => {
65048
- if (err.code === "EADDRINUSE")
65049
- resolve36(false);
65050
- else
65051
- resolve36(true);
65049
+ resolve36(err.code !== "EADDRINUSE");
65052
65050
  });
65053
65051
  tester.once("listening", () => {
65054
65052
  tester.close(() => resolve36(true));
@@ -65056,7 +65054,58 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
65056
65054
  tester.listen(apiPort, "127.0.0.1");
65057
65055
  });
65058
65056
  if (!portFree) {
65059
- } else {
65057
+ let isOurServer = false;
65058
+ try {
65059
+ const probe = await fetch(`http://127.0.0.1:${apiPort}/health`, { signal: AbortSignal.timeout(2e3) });
65060
+ if (probe.ok) {
65061
+ const data = await probe.json();
65062
+ isOurServer = data.status === "ok";
65063
+ }
65064
+ } catch {
65065
+ }
65066
+ if (isOurServer) {
65067
+ } else {
65068
+ try {
65069
+ const { execSync: nodeExec } = __require("child_process");
65070
+ if (isWin2) {
65071
+ const out = nodeExec(`netstat -ano | findstr :${apiPort} | findstr LISTENING`, { encoding: "utf8", timeout: 3e3 }).trim();
65072
+ const pid = out.split(/\s+/).pop();
65073
+ if (pid && parseInt(pid, 10) > 0 && parseInt(pid, 10) !== process.pid) {
65074
+ nodeExec(`taskkill /PID ${pid} /F`, { timeout: 3e3 });
65075
+ writeContent(() => renderInfo(`Killed orphaned process on port ${apiPort} (PID ${pid})`));
65076
+ await new Promise((r) => setTimeout(r, 1e3));
65077
+ }
65078
+ } else {
65079
+ try {
65080
+ const out = nodeExec(`lsof -ti :${apiPort} 2>/dev/null || fuser ${apiPort}/tcp 2>/dev/null`, { encoding: "utf8", timeout: 3e3 }).trim();
65081
+ const pids = out.split(/\s+/).map((s) => parseInt(s, 10)).filter((p) => p > 0 && p !== process.pid);
65082
+ for (const pid of pids) {
65083
+ try {
65084
+ process.kill(pid, "SIGTERM");
65085
+ } catch {
65086
+ }
65087
+ }
65088
+ if (pids.length > 0) {
65089
+ writeContent(() => renderInfo(`Killed ${pids.length} orphaned process(es) on port ${apiPort}`));
65090
+ await new Promise((r) => setTimeout(r, 1e3));
65091
+ }
65092
+ } catch {
65093
+ }
65094
+ }
65095
+ } catch {
65096
+ }
65097
+ }
65098
+ }
65099
+ const portNowFree = await new Promise((resolve36) => {
65100
+ const net = __require("net");
65101
+ const tester = net.createServer();
65102
+ tester.once("error", () => resolve36(false));
65103
+ tester.once("listening", () => {
65104
+ tester.close(() => resolve36(true));
65105
+ });
65106
+ tester.listen(apiPort, "127.0.0.1");
65107
+ });
65108
+ if (portNowFree) {
65060
65109
  const { startApiServer: startApiServer2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
65061
65110
  const apiServer = startApiServer2({
65062
65111
  port: apiPort,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.184.38",
3
+ "version": "0.184.39",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) \u2014 interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",