open-agents-ai 0.184.38 → 0.184.40
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/README.md +86 -0
- package/dist/index.js +78 -7
- 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
|
@@ -60201,6 +60201,11 @@ var init_status_bar = __esm({
|
|
|
60201
60201
|
setHeaderButtonHandler(handler) {
|
|
60202
60202
|
this._headerButtonHandler = handler;
|
|
60203
60203
|
}
|
|
60204
|
+
/** Programmatically fire a header button command (used for deferred/queued clicks) */
|
|
60205
|
+
fireHeaderButton(command) {
|
|
60206
|
+
if (this._headerButtonHandler)
|
|
60207
|
+
this._headerButtonHandler(command);
|
|
60208
|
+
}
|
|
60204
60209
|
/** Get the text selection instance (for external keyboard shortcut wiring) */
|
|
60205
60210
|
get textSelection() {
|
|
60206
60211
|
return this._textSelection;
|
|
@@ -64759,10 +64764,17 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
64759
64764
|
});
|
|
64760
64765
|
let commandCtxRef = null;
|
|
64761
64766
|
let headerBtnActive = null;
|
|
64767
|
+
let headerBtnQueue = null;
|
|
64762
64768
|
statusBar.setHeaderButtonHandler((command) => {
|
|
64763
|
-
if (!commandCtxRef)
|
|
64769
|
+
if (!commandCtxRef) {
|
|
64770
|
+
headerBtnQueue = command;
|
|
64764
64771
|
return;
|
|
64765
|
-
|
|
64772
|
+
}
|
|
64773
|
+
if (headerBtnActive && headerBtnActive !== command) {
|
|
64774
|
+
headerBtnQueue = command;
|
|
64775
|
+
return;
|
|
64776
|
+
}
|
|
64777
|
+
if (headerBtnActive === command)
|
|
64766
64778
|
return;
|
|
64767
64779
|
headerBtnActive = command;
|
|
64768
64780
|
(async () => {
|
|
@@ -64777,6 +64789,11 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
64777
64789
|
banner.renderCurrentFrame();
|
|
64778
64790
|
statusBar.refreshDisplay();
|
|
64779
64791
|
}
|
|
64792
|
+
if (headerBtnQueue) {
|
|
64793
|
+
const queued = headerBtnQueue;
|
|
64794
|
+
headerBtnQueue = null;
|
|
64795
|
+
setTimeout(() => statusBar.fireHeaderButton?.(queued), 50);
|
|
64796
|
+
}
|
|
64780
64797
|
})();
|
|
64781
64798
|
});
|
|
64782
64799
|
rl.output = null;
|
|
@@ -65041,14 +65058,12 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
65041
65058
|
}
|
|
65042
65059
|
try {
|
|
65043
65060
|
const apiPort = parseInt(process.env["OA_PORT"] || "11435", 10);
|
|
65061
|
+
const isWin2 = process.platform === "win32";
|
|
65044
65062
|
const portFree = await new Promise((resolve36) => {
|
|
65045
65063
|
const net = __require("net");
|
|
65046
65064
|
const tester = net.createServer();
|
|
65047
65065
|
tester.once("error", (err) => {
|
|
65048
|
-
|
|
65049
|
-
resolve36(false);
|
|
65050
|
-
else
|
|
65051
|
-
resolve36(true);
|
|
65066
|
+
resolve36(err.code !== "EADDRINUSE");
|
|
65052
65067
|
});
|
|
65053
65068
|
tester.once("listening", () => {
|
|
65054
65069
|
tester.close(() => resolve36(true));
|
|
@@ -65056,7 +65071,58 @@ Rationale: ${proposal.rationale}${provenanceNote}`;
|
|
|
65056
65071
|
tester.listen(apiPort, "127.0.0.1");
|
|
65057
65072
|
});
|
|
65058
65073
|
if (!portFree) {
|
|
65059
|
-
|
|
65074
|
+
let isOurServer = false;
|
|
65075
|
+
try {
|
|
65076
|
+
const probe = await fetch(`http://127.0.0.1:${apiPort}/health`, { signal: AbortSignal.timeout(2e3) });
|
|
65077
|
+
if (probe.ok) {
|
|
65078
|
+
const data = await probe.json();
|
|
65079
|
+
isOurServer = data.status === "ok";
|
|
65080
|
+
}
|
|
65081
|
+
} catch {
|
|
65082
|
+
}
|
|
65083
|
+
if (isOurServer) {
|
|
65084
|
+
} else {
|
|
65085
|
+
try {
|
|
65086
|
+
const { execSync: nodeExec } = __require("child_process");
|
|
65087
|
+
if (isWin2) {
|
|
65088
|
+
const out = nodeExec(`netstat -ano | findstr :${apiPort} | findstr LISTENING`, { encoding: "utf8", timeout: 3e3 }).trim();
|
|
65089
|
+
const pid = out.split(/\s+/).pop();
|
|
65090
|
+
if (pid && parseInt(pid, 10) > 0 && parseInt(pid, 10) !== process.pid) {
|
|
65091
|
+
nodeExec(`taskkill /PID ${pid} /F`, { timeout: 3e3 });
|
|
65092
|
+
writeContent(() => renderInfo(`Killed orphaned process on port ${apiPort} (PID ${pid})`));
|
|
65093
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
65094
|
+
}
|
|
65095
|
+
} else {
|
|
65096
|
+
try {
|
|
65097
|
+
const out = nodeExec(`lsof -ti :${apiPort} 2>/dev/null || fuser ${apiPort}/tcp 2>/dev/null`, { encoding: "utf8", timeout: 3e3 }).trim();
|
|
65098
|
+
const pids = out.split(/\s+/).map((s) => parseInt(s, 10)).filter((p) => p > 0 && p !== process.pid);
|
|
65099
|
+
for (const pid of pids) {
|
|
65100
|
+
try {
|
|
65101
|
+
process.kill(pid, "SIGTERM");
|
|
65102
|
+
} catch {
|
|
65103
|
+
}
|
|
65104
|
+
}
|
|
65105
|
+
if (pids.length > 0) {
|
|
65106
|
+
writeContent(() => renderInfo(`Killed ${pids.length} orphaned process(es) on port ${apiPort}`));
|
|
65107
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
65108
|
+
}
|
|
65109
|
+
} catch {
|
|
65110
|
+
}
|
|
65111
|
+
}
|
|
65112
|
+
} catch {
|
|
65113
|
+
}
|
|
65114
|
+
}
|
|
65115
|
+
}
|
|
65116
|
+
const portNowFree = await new Promise((resolve36) => {
|
|
65117
|
+
const net = __require("net");
|
|
65118
|
+
const tester = net.createServer();
|
|
65119
|
+
tester.once("error", () => resolve36(false));
|
|
65120
|
+
tester.once("listening", () => {
|
|
65121
|
+
tester.close(() => resolve36(true));
|
|
65122
|
+
});
|
|
65123
|
+
tester.listen(apiPort, "127.0.0.1");
|
|
65124
|
+
});
|
|
65125
|
+
if (portNowFree) {
|
|
65060
65126
|
const { startApiServer: startApiServer2 } = await Promise.resolve().then(() => (init_serve(), serve_exports));
|
|
65061
65127
|
const apiServer = startApiServer2({
|
|
65062
65128
|
port: apiPort,
|
|
@@ -66410,6 +66476,11 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
66410
66476
|
}
|
|
66411
66477
|
};
|
|
66412
66478
|
commandCtxRef = commandCtx;
|
|
66479
|
+
if (headerBtnQueue) {
|
|
66480
|
+
const queued = headerBtnQueue;
|
|
66481
|
+
headerBtnQueue = null;
|
|
66482
|
+
setTimeout(() => statusBar.fireHeaderButton?.(queued), 100);
|
|
66483
|
+
}
|
|
66413
66484
|
showPrompt();
|
|
66414
66485
|
if (!isResumed) {
|
|
66415
66486
|
const savedCtx = loadSessionContext(repoRoot);
|
package/package.json
CHANGED