maqam 0.1.3 → 0.1.4
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 +18 -1
- package/app/assets/maqam-cli-agent-flow.png +0 -0
- package/docs/usage.md +190 -1
- package/package.json +3 -1
- package/src/framework/cli-agent-tool.js +185 -0
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Full documentation: [docs/usage.md](https://github.com/AjnasNB/maqam/blob/main/d
|
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
+

|
|
14
|
+
|
|
13
15
|
## What Ships
|
|
14
16
|
|
|
15
17
|
- `AgentRuntime`: sequential workflow execution with retries, trace events, task outputs, and policy preflight.
|
|
@@ -17,6 +19,7 @@ Full documentation: [docs/usage.md](https://github.com/AjnasNB/maqam/blob/main/d
|
|
|
17
19
|
- `EvidenceLedger`: provenance records, claim links, source hashes, confidence, and unsupported-claim checks.
|
|
18
20
|
- `ToolGateway`: one governed path for external tool execution.
|
|
19
21
|
- `createAgentTool`: wraps any function agent or object agent so Maqam can control it through policy, trace, approval, and evidence.
|
|
22
|
+
- `createCliAgentTool`: wraps fixed command-line workers with timeout, approximate input-token limits, output byte limits, and no shell execution by default.
|
|
20
23
|
- `SkillRegistry`: lightweight skill metadata registration and selection.
|
|
21
24
|
- `createResearchWorkflow`: crawler-backed source collection, synthesis, and quality checks.
|
|
22
25
|
- `maqam`: local web console for running governed research workflows.
|
|
@@ -82,6 +85,7 @@ import {
|
|
|
82
85
|
PolicyEngine,
|
|
83
86
|
ToolGateway,
|
|
84
87
|
createAgentTool,
|
|
88
|
+
createCliAgentTool,
|
|
85
89
|
createCrawlerTool,
|
|
86
90
|
createResearchWorkflow
|
|
87
91
|
} from "maqam";
|
|
@@ -97,6 +101,15 @@ gateway.registerTool("crawler", createCrawlerTool());
|
|
|
97
101
|
gateway.registerTool("summarizer", createAgentTool(async (input) => ({
|
|
98
102
|
summary: `Reviewed ${input.topic}`
|
|
99
103
|
}), { name: "summarizer" }));
|
|
104
|
+
gateway.registerTool("localWorker", createCliAgentTool({
|
|
105
|
+
name: "localWorker",
|
|
106
|
+
command: process.execPath,
|
|
107
|
+
args: ["--version"],
|
|
108
|
+
stdin: "none",
|
|
109
|
+
timeoutMs: 5000,
|
|
110
|
+
maxInputTokens: 20,
|
|
111
|
+
maxOutputBytes: 2048
|
|
112
|
+
}));
|
|
100
113
|
|
|
101
114
|
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway: gateway });
|
|
102
115
|
const result = await runtime.runWorkflow(
|
|
@@ -154,7 +167,7 @@ Brand assets live in `app/assets/`, including `maqam-logo.svg` and `maqam-brand-
|
|
|
154
167
|
- Use a clear user agent.
|
|
155
168
|
- Rate-limit per origin.
|
|
156
169
|
- Avoid bypassing access controls, paywalls, anti-bot systems, or private content.
|
|
157
|
-
- No required
|
|
170
|
+
- No required model provider dependency.
|
|
158
171
|
- No required external hosted service.
|
|
159
172
|
- Produce JSON/JSONL output that agents can consume directly.
|
|
160
173
|
|
|
@@ -181,3 +194,7 @@ Publishing requires an authenticated npm session with permission to publish the
|
|
|
181
194
|
## License
|
|
182
195
|
|
|
183
196
|
MIT
|
|
197
|
+
|
|
198
|
+
## Open Development
|
|
199
|
+
|
|
200
|
+
Maqam is open source under MIT and open for development, issues, ideas, and contributions.
|
|
Binary file
|
package/docs/usage.md
CHANGED
|
@@ -15,6 +15,7 @@ This guide covers installation, CLI usage, SDK usage, the local console, crawler
|
|
|
15
15
|
- [API Reference](#api-reference)
|
|
16
16
|
- [Build A Custom Workflow](#build-a-custom-workflow)
|
|
17
17
|
- [Control Any Agent](#control-any-agent)
|
|
18
|
+
- [Control CLI Workers](#control-cli-workers)
|
|
18
19
|
- [Register A Custom Tool](#register-a-custom-tool)
|
|
19
20
|
- [Use Policy And Approvals](#use-policy-and-approvals)
|
|
20
21
|
- [Use Evidence And Claims](#use-evidence-and-claims)
|
|
@@ -79,6 +80,7 @@ import {
|
|
|
79
80
|
PolicyEngine,
|
|
80
81
|
ToolGateway,
|
|
81
82
|
createAgentTool,
|
|
83
|
+
createCliAgentTool,
|
|
82
84
|
createCrawlerTool,
|
|
83
85
|
createResearchWorkflow
|
|
84
86
|
} from "maqam";
|
|
@@ -94,6 +96,15 @@ toolGateway.registerTool("crawler", createCrawlerTool());
|
|
|
94
96
|
toolGateway.registerTool("summarizer", createAgentTool(async (input) => ({
|
|
95
97
|
summary: `Reviewed ${input.topic}`
|
|
96
98
|
}), { name: "summarizer" }));
|
|
99
|
+
toolGateway.registerTool("localWorker", createCliAgentTool({
|
|
100
|
+
name: "localWorker",
|
|
101
|
+
command: process.execPath,
|
|
102
|
+
args: ["--version"],
|
|
103
|
+
stdin: "none",
|
|
104
|
+
timeoutMs: 5000,
|
|
105
|
+
maxInputTokens: 20,
|
|
106
|
+
maxOutputBytes: 2048
|
|
107
|
+
}));
|
|
97
108
|
|
|
98
109
|
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
|
|
99
110
|
const result = await runtime.runWorkflow(
|
|
@@ -216,6 +227,7 @@ import {
|
|
|
216
227
|
ToolGateway,
|
|
217
228
|
SkillRegistry,
|
|
218
229
|
createAgentTool,
|
|
230
|
+
createCliAgentTool,
|
|
219
231
|
createCrawlerTool,
|
|
220
232
|
createResearchWorkflow,
|
|
221
233
|
crawl,
|
|
@@ -252,6 +264,7 @@ Core objects:
|
|
|
252
264
|
- `PolicyEngine`: decides what is allowed, denied, or approval-gated.
|
|
253
265
|
- `ToolGateway`: routes all external tool calls through policy.
|
|
254
266
|
- `createAgentTool`: wraps arbitrary agents so they can be governed like any other tool.
|
|
267
|
+
- `createCliAgentTool`: wraps fixed command-line workers with timeout, approximate input-token limits, output byte limits, and no shell execution by default.
|
|
255
268
|
- `EvidenceLedger`: stores source evidence and claim support.
|
|
256
269
|
- `SkillRegistry`: stores skill metadata and selects matching skills.
|
|
257
270
|
- `createResearchWorkflow`: bundled workflow for public web research.
|
|
@@ -643,6 +656,93 @@ const workflow = createResearchWorkflow({
|
|
|
643
656
|
});
|
|
644
657
|
```
|
|
645
658
|
|
|
659
|
+
### `createCliAgentTool(options)`
|
|
660
|
+
|
|
661
|
+
Wraps a fixed command-line worker so it can run through Maqam policy and trace capture.
|
|
662
|
+
|
|
663
|
+
```js
|
|
664
|
+
const localWorker = createCliAgentTool({
|
|
665
|
+
name: "localWorker",
|
|
666
|
+
command: process.execPath,
|
|
667
|
+
args: ["--input-type=module", "-e", "let body=''; for await (const c of process.stdin) body += c; const input = JSON.parse(body); console.log(JSON.stringify({ artifact: `built:${input.name}` }));"],
|
|
668
|
+
stdin: "json",
|
|
669
|
+
parseJson: true,
|
|
670
|
+
timeoutMs: 5000,
|
|
671
|
+
maxInputTokens: 50,
|
|
672
|
+
maxOutputBytes: 2048
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
toolGateway.registerTool("localWorker", localWorker);
|
|
676
|
+
|
|
677
|
+
const result = await toolGateway.call("localWorker", {
|
|
678
|
+
name: "demo-widget"
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
console.log(result.json.artifact);
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
Options:
|
|
685
|
+
|
|
686
|
+
| Field | Type | Description |
|
|
687
|
+
| --- | --- | --- |
|
|
688
|
+
| `name` | `string` | Name used in result metadata. |
|
|
689
|
+
| `command` | `string` | Fixed executable path or command. Required. |
|
|
690
|
+
| `args` | `string[]` | Fixed argument list. Dynamic user input should go through stdin, not command args. |
|
|
691
|
+
| `cwd` | `string` | Optional working directory. |
|
|
692
|
+
| `env` | `object` | Extra environment variables. |
|
|
693
|
+
| `inheritEnv` | `boolean` | Inherit `process.env`. Default: `true`. |
|
|
694
|
+
| `stdin` | `"json" | "text" | "none"` | How input is passed to the worker. Default: `"json"`. |
|
|
695
|
+
| `parseJson` | `boolean` | Parse stdout as JSON and expose it as `result.json`. |
|
|
696
|
+
| `timeoutMs` | `number` | Hard runtime timeout. Default: `30000`. |
|
|
697
|
+
| `maxInputTokens` | `number` | Approximate input token limit. Default: `4000`. |
|
|
698
|
+
| `maxOutputBytes` | `number` | Maximum combined stdout/stderr bytes. Default: `65536`. |
|
|
699
|
+
| `rejectOnNonZero` | `boolean` | Reject when exit code is not zero. Default: `true`. |
|
|
700
|
+
| `shell` | `boolean` | Run through a shell. Default: `false`. Use only when a platform wrapper requires it. |
|
|
701
|
+
|
|
702
|
+
Result shape:
|
|
703
|
+
|
|
704
|
+
```json
|
|
705
|
+
{
|
|
706
|
+
"name": "localWorker",
|
|
707
|
+
"command": "node",
|
|
708
|
+
"args": ["--version"],
|
|
709
|
+
"exitCode": 0,
|
|
710
|
+
"signal": null,
|
|
711
|
+
"timedOut": false,
|
|
712
|
+
"stdout": "v20.0.0\n",
|
|
713
|
+
"stderr": "",
|
|
714
|
+
"durationMs": 42,
|
|
715
|
+
"approxInputTokens": 0,
|
|
716
|
+
"outputBytes": 9,
|
|
717
|
+
"limits": {
|
|
718
|
+
"maxInputTokens": 50,
|
|
719
|
+
"maxOutputBytes": 2048,
|
|
720
|
+
"timeoutMs": 5000
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
Limit errors:
|
|
726
|
+
|
|
727
|
+
| Code | Meaning |
|
|
728
|
+
| --- | --- |
|
|
729
|
+
| `CLI_INPUT_LIMIT_EXCEEDED` | Input was too large before execution. |
|
|
730
|
+
| `CLI_OUTPUT_LIMIT_EXCEEDED` | stdout/stderr exceeded the configured byte limit. |
|
|
731
|
+
| `CLI_TIMEOUT` | Process exceeded `timeoutMs`. |
|
|
732
|
+
| `CLI_EXIT_NONZERO` | Process exited with a non-zero code. |
|
|
733
|
+
| `CLI_JSON_PARSE_FAILED` | `parseJson` was enabled but stdout was not valid JSON. |
|
|
734
|
+
| `CLI_SPAWN_FAILED` | The process could not be started. |
|
|
735
|
+
|
|
736
|
+
Security notes:
|
|
737
|
+
|
|
738
|
+
- Maqam does not use a shell for CLI workers by default.
|
|
739
|
+
- Keep `command` and `args` fixed in code.
|
|
740
|
+
- Send user input through stdin.
|
|
741
|
+
- Use narrow `allowedTools`.
|
|
742
|
+
- Set short `timeoutMs` and small `maxOutputBytes` for untrusted workers.
|
|
743
|
+
- Use approval gates for workers that write, publish, send, or modify state.
|
|
744
|
+
- Prefer direct executable paths over platform wrapper scripts. On Windows, some `.cmd` or `.ps1` shims may require `shell: true` or a direct underlying executable path.
|
|
745
|
+
|
|
646
746
|
Tasks:
|
|
647
747
|
|
|
648
748
|
| Task ID | Purpose |
|
|
@@ -895,7 +995,7 @@ What Maqam can control:
|
|
|
895
995
|
|
|
896
996
|
- Function agents.
|
|
897
997
|
- LangChain/LangGraph-style agents if exposed through `invoke` or wrapped in a function.
|
|
898
|
-
-
|
|
998
|
+
- External SDK-style functions if wrapped in a function.
|
|
899
999
|
- Browser agents.
|
|
900
1000
|
- Research agents.
|
|
901
1001
|
- GitHub/npm/internal API agents.
|
|
@@ -907,6 +1007,91 @@ What Maqam cannot do automatically:
|
|
|
907
1007
|
- It cannot make an unsafe third-party agent safe if that agent bypasses the wrapper and performs side effects internally.
|
|
908
1008
|
- It cannot approve risky actions by itself; approval-gated actions should be routed to humans.
|
|
909
1009
|
|
|
1010
|
+
## Control CLI Workers
|
|
1011
|
+
|
|
1012
|
+
Maqam can govern command-line workers the same way it governs function agents.
|
|
1013
|
+
|
|
1014
|
+
The pattern is:
|
|
1015
|
+
|
|
1016
|
+
1. Create a fixed CLI adapter with `createCliAgentTool`.
|
|
1017
|
+
2. Register it with `ToolGateway`.
|
|
1018
|
+
3. Add the worker name to `allowedTools`.
|
|
1019
|
+
4. Configure `timeoutMs`, `maxInputTokens`, and `maxOutputBytes`.
|
|
1020
|
+
5. Call the worker from a workflow task.
|
|
1021
|
+
|
|
1022
|
+
Example workflow:
|
|
1023
|
+
|
|
1024
|
+
```js
|
|
1025
|
+
import {
|
|
1026
|
+
AgentRuntime,
|
|
1027
|
+
EvidenceLedger,
|
|
1028
|
+
PolicyEngine,
|
|
1029
|
+
ToolGateway,
|
|
1030
|
+
createCliAgentTool
|
|
1031
|
+
} from "maqam";
|
|
1032
|
+
|
|
1033
|
+
const evidenceLedger = new EvidenceLedger();
|
|
1034
|
+
const policyEngine = new PolicyEngine({
|
|
1035
|
+
allowedTools: ["builderWorker"]
|
|
1036
|
+
});
|
|
1037
|
+
const toolGateway = new ToolGateway({ policyEngine, evidenceLedger });
|
|
1038
|
+
|
|
1039
|
+
toolGateway.registerTool("builderWorker", createCliAgentTool({
|
|
1040
|
+
name: "builderWorker",
|
|
1041
|
+
command: process.execPath,
|
|
1042
|
+
args: ["--input-type=module", "-e", "let body=''; for await (const c of process.stdin) body += c; const input = JSON.parse(body); console.log(JSON.stringify({ fileName: `${input.name}.txt`, content: `Built ${input.name}` }));"],
|
|
1043
|
+
stdin: "json",
|
|
1044
|
+
parseJson: true,
|
|
1045
|
+
timeoutMs: 5000,
|
|
1046
|
+
maxInputTokens: 100,
|
|
1047
|
+
maxOutputBytes: 4096
|
|
1048
|
+
}));
|
|
1049
|
+
|
|
1050
|
+
const workflow = {
|
|
1051
|
+
name: "governed_cli_build",
|
|
1052
|
+
tasks: [
|
|
1053
|
+
{
|
|
1054
|
+
id: "build",
|
|
1055
|
+
run: (context) => context.tools.call("builderWorker", {
|
|
1056
|
+
name: "demo-widget"
|
|
1057
|
+
}, context)
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
id: "record",
|
|
1061
|
+
run: (context) => {
|
|
1062
|
+
const output = context.outputs.build.json;
|
|
1063
|
+
const evidence = context.evidence.addEvidence({
|
|
1064
|
+
runId: context.runId,
|
|
1065
|
+
taskId: "record",
|
|
1066
|
+
sourceType: "cli_worker_output",
|
|
1067
|
+
source: "builderWorker",
|
|
1068
|
+
excerpt: output.content,
|
|
1069
|
+
tool: "builderWorker",
|
|
1070
|
+
confidence: 0.8
|
|
1071
|
+
});
|
|
1072
|
+
context.evidence.addClaim({
|
|
1073
|
+
runId: context.runId,
|
|
1074
|
+
taskId: "record",
|
|
1075
|
+
text: `The worker created ${output.fileName}.`,
|
|
1076
|
+
evidenceIds: [evidence.evidenceId],
|
|
1077
|
+
confidence: 0.8
|
|
1078
|
+
});
|
|
1079
|
+
return output;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
1084
|
+
|
|
1085
|
+
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
|
|
1086
|
+
const result = await runtime.runWorkflow(workflow, {
|
|
1087
|
+
objective: "Build a small artifact through a governed CLI worker",
|
|
1088
|
+
allowedTools: ["builderWorker"]
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
console.log(result.outputs.record);
|
|
1092
|
+
console.log(result.evidence.unsupportedClaims);
|
|
1093
|
+
```
|
|
1094
|
+
|
|
910
1095
|
## Register A Custom Tool
|
|
911
1096
|
|
|
912
1097
|
Tools should be small and explicit. The gateway handles policy and trace capture.
|
|
@@ -1258,3 +1443,7 @@ Useful next packages or modules:
|
|
|
1258
1443
|
- Browser automation connector.
|
|
1259
1444
|
- GitHub and npm metadata connectors.
|
|
1260
1445
|
- Tenant-aware configuration and audit export.
|
|
1446
|
+
|
|
1447
|
+
## Open Development
|
|
1448
|
+
|
|
1449
|
+
Maqam is open source under MIT and open for development, issues, ideas, and contributions.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maqam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Maqam is an MIT-licensed Ajnas agent framework for governed workflows, policy, evidence, skills, and crawler-backed research.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -43,6 +43,8 @@
|
|
|
43
43
|
"skills",
|
|
44
44
|
"tool-orchestration",
|
|
45
45
|
"human-approval",
|
|
46
|
+
"cli-agent",
|
|
47
|
+
"command-runner",
|
|
46
48
|
"crawler",
|
|
47
49
|
"agent",
|
|
48
50
|
"web-crawler",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { AjnasFrameworkError } from "./errors.js";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
5
|
+
const DEFAULT_MAX_INPUT_TOKENS = 4_000;
|
|
6
|
+
const DEFAULT_MAX_OUTPUT_BYTES = 64 * 1024;
|
|
7
|
+
|
|
8
|
+
function estimateTokens(value) {
|
|
9
|
+
return Math.ceil(Buffer.byteLength(value || "", "utf8") / 4);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function buildStdin(input, mode) {
|
|
13
|
+
if (mode === "none") return null;
|
|
14
|
+
if (mode === "text") return String(input.prompt ?? input.text ?? "");
|
|
15
|
+
return JSON.stringify(input);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cliError(message, code, details = {}) {
|
|
19
|
+
return new AjnasFrameworkError(message, {
|
|
20
|
+
code,
|
|
21
|
+
details
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createCliAgentTool(options = {}) {
|
|
26
|
+
const {
|
|
27
|
+
name = "cliAgent",
|
|
28
|
+
command,
|
|
29
|
+
args = [],
|
|
30
|
+
cwd,
|
|
31
|
+
env = {},
|
|
32
|
+
inheritEnv = true,
|
|
33
|
+
stdin = "json",
|
|
34
|
+
parseJson = false,
|
|
35
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
36
|
+
maxInputTokens = DEFAULT_MAX_INPUT_TOKENS,
|
|
37
|
+
maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
|
|
38
|
+
rejectOnNonZero = true,
|
|
39
|
+
shell = false
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
if (!command || typeof command !== "string") {
|
|
43
|
+
throw new TypeError("createCliAgentTool requires a fixed command string.");
|
|
44
|
+
}
|
|
45
|
+
if (!Array.isArray(args) || args.some((arg) => typeof arg !== "string")) {
|
|
46
|
+
throw new TypeError("createCliAgentTool args must be an array of strings.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return async function cliAgentTool(input = {}) {
|
|
50
|
+
const stdinBody = buildStdin(input, stdin);
|
|
51
|
+
const approxInputTokens = estimateTokens(stdinBody || "");
|
|
52
|
+
if (maxInputTokens && approxInputTokens > maxInputTokens) {
|
|
53
|
+
throw cliError(`CLI input exceeds maxInputTokens (${approxInputTokens} > ${maxInputTokens}).`, "CLI_INPUT_LIMIT_EXCEEDED", {
|
|
54
|
+
name,
|
|
55
|
+
approxInputTokens,
|
|
56
|
+
maxInputTokens
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const startedAt = Date.now();
|
|
62
|
+
let child;
|
|
63
|
+
try {
|
|
64
|
+
child = spawn(command, args, {
|
|
65
|
+
cwd,
|
|
66
|
+
env: inheritEnv ? { ...process.env, ...env } : { ...env },
|
|
67
|
+
shell,
|
|
68
|
+
windowsHide: true,
|
|
69
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
reject(cliError(error.message, "CLI_SPAWN_FAILED", {
|
|
73
|
+
name,
|
|
74
|
+
command,
|
|
75
|
+
cause: error.code || error.name
|
|
76
|
+
}));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const stdout = [];
|
|
81
|
+
const stderr = [];
|
|
82
|
+
let outputBytes = 0;
|
|
83
|
+
let settled = false;
|
|
84
|
+
|
|
85
|
+
const finish = (callback, value) => {
|
|
86
|
+
if (settled) return;
|
|
87
|
+
settled = true;
|
|
88
|
+
clearTimeout(timer);
|
|
89
|
+
callback(value);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const stopWithError = (error) => {
|
|
93
|
+
if (!child.killed) child.kill();
|
|
94
|
+
finish(reject, error);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
stopWithError(cliError(`CLI agent '${name}' timed out after ${timeoutMs}ms.`, "CLI_TIMEOUT", {
|
|
99
|
+
name,
|
|
100
|
+
timeoutMs
|
|
101
|
+
}));
|
|
102
|
+
}, timeoutMs);
|
|
103
|
+
|
|
104
|
+
const collect = (target, chunk) => {
|
|
105
|
+
outputBytes += chunk.byteLength;
|
|
106
|
+
if (outputBytes > maxOutputBytes) {
|
|
107
|
+
stopWithError(cliError(`CLI output exceeds maxOutputBytes (${outputBytes} > ${maxOutputBytes}).`, "CLI_OUTPUT_LIMIT_EXCEEDED", {
|
|
108
|
+
name,
|
|
109
|
+
maxOutputBytes,
|
|
110
|
+
outputBytes
|
|
111
|
+
}));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
target.push(Buffer.from(chunk));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
child.stdout.on("data", (chunk) => collect(stdout, chunk));
|
|
118
|
+
child.stderr.on("data", (chunk) => collect(stderr, chunk));
|
|
119
|
+
|
|
120
|
+
child.on("error", (error) => {
|
|
121
|
+
finish(reject, cliError(error.message, "CLI_SPAWN_FAILED", {
|
|
122
|
+
name,
|
|
123
|
+
command,
|
|
124
|
+
cause: error.code || error.name
|
|
125
|
+
}));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
child.on("close", (exitCode, signal) => {
|
|
129
|
+
if (settled) return;
|
|
130
|
+
|
|
131
|
+
const stdoutText = Buffer.concat(stdout).toString("utf8");
|
|
132
|
+
const stderrText = Buffer.concat(stderr).toString("utf8");
|
|
133
|
+
const result = {
|
|
134
|
+
name,
|
|
135
|
+
command,
|
|
136
|
+
args,
|
|
137
|
+
exitCode,
|
|
138
|
+
signal,
|
|
139
|
+
timedOut: false,
|
|
140
|
+
stdout: stdoutText,
|
|
141
|
+
stderr: stderrText,
|
|
142
|
+
durationMs: Date.now() - startedAt,
|
|
143
|
+
approxInputTokens,
|
|
144
|
+
outputBytes,
|
|
145
|
+
limits: {
|
|
146
|
+
maxInputTokens,
|
|
147
|
+
maxOutputBytes,
|
|
148
|
+
timeoutMs
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
if (parseJson && stdoutText.trim()) {
|
|
153
|
+
try {
|
|
154
|
+
result.json = JSON.parse(stdoutText.trim());
|
|
155
|
+
} catch (error) {
|
|
156
|
+
finish(reject, cliError("CLI stdout was not valid JSON.", "CLI_JSON_PARSE_FAILED", {
|
|
157
|
+
name,
|
|
158
|
+
message: error.message
|
|
159
|
+
}));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (rejectOnNonZero && exitCode !== 0) {
|
|
165
|
+
finish(reject, cliError(`CLI agent '${name}' exited with code ${exitCode}.`, "CLI_EXIT_NONZERO", {
|
|
166
|
+
...result,
|
|
167
|
+
stdout: stdoutText.slice(0, 2048),
|
|
168
|
+
stderr: stderrText.slice(0, 2048)
|
|
169
|
+
}));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
finish(resolve, result);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
if (stdinBody === null) {
|
|
177
|
+
child.stdin.end();
|
|
178
|
+
} else {
|
|
179
|
+
child.stdin.end(stdinBody);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { estimateTokens as estimateCliInputTokens };
|
package/src/index.js
CHANGED
|
@@ -340,6 +340,7 @@ export { ToolGateway } from "./framework/tool-gateway.js";
|
|
|
340
340
|
export { SkillRegistry } from "./framework/skill-registry.js";
|
|
341
341
|
export { AgentRuntime } from "./framework/runtime.js";
|
|
342
342
|
export { createAgentTool } from "./framework/agent-tool.js";
|
|
343
|
+
export { createCliAgentTool, estimateCliInputTokens } from "./framework/cli-agent-tool.js";
|
|
343
344
|
export { createResearchWorkflow } from "./framework/research-workflow.js";
|
|
344
345
|
|
|
345
346
|
export function createCrawlerTool(defaultOptions = {}) {
|