arisa 5.1.12 → 5.1.24
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/AGENTS.md +4 -0
- package/ARISA-MASTER-SLAVE-SPEC.md +7 -4
- package/README.md +2 -0
- package/package.json +7 -8
- package/src/core/tools/official-tool-installer.js +78 -6
- package/src/core/tools/tool-dependencies.js +99 -0
- package/src/core/tools/tool-registry.js +50 -6
- package/src/official-tools.lock.json +160 -8
- package/src/runtime/arisa-capabilities.js +12 -1
- package/src/runtime/create-app.js +1 -0
- package/src/runtime/doctor.js +71 -19
- package/src/runtime/tool-usage-report.js +25 -10
- package/src/transport/telegram/bot.js +51 -15
- package/test/capabilities-security.test.js +21 -0
- package/test/context-and-task-bounds.test.js +66 -2
- package/test/doctor.test.js +55 -0
- package/test/official-tool-dependencies.test.js +23 -0
- package/test/official-tool-installer.test.js +41 -2
- package/test/tool-dependencies.test.js +53 -0
- package/test/tool-registry-run.test.js +19 -1
- package/test/tool-usage.test.js +26 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
inspectToolDependencies,
|
|
5
|
+
normalizeToolDependencies,
|
|
6
|
+
resolveToolDependencyPlan,
|
|
7
|
+
satisfiesToolVersion
|
|
8
|
+
} from "../src/core/tools/tool-dependencies.js";
|
|
9
|
+
|
|
10
|
+
test("normalizes strict tool dependency maps and supports exact and caret versions", () => {
|
|
11
|
+
assert.deepEqual(normalizeToolDependencies({ "mcp-client": "^0.1.0" }), { "mcp-client": "^0.1.0" });
|
|
12
|
+
assert.equal(satisfiesToolVersion("0.1.9", "^0.1.0"), true);
|
|
13
|
+
assert.equal(satisfiesToolVersion("0.2.0", "^0.1.0"), false);
|
|
14
|
+
assert.equal(satisfiesToolVersion("1.4.0", "^1.2.3"), true);
|
|
15
|
+
assert.equal(satisfiesToolVersion("2.0.0", "^1.2.3"), false);
|
|
16
|
+
assert.throws(() => normalizeToolDependencies({ "../bad": "^1.0.0" }), /Invalid tool dependency name/);
|
|
17
|
+
assert.throws(() => normalizeToolDependencies({ valid: ">=1.0.0" }), /Unsupported tool dependency range/);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("resolves dependencies before dependents and detects invalid graphs", () => {
|
|
21
|
+
const entries = {
|
|
22
|
+
"mcp-client": { version: "0.1.0", toolDependencies: {} },
|
|
23
|
+
"magnific-mcp": { version: "0.1.0", toolDependencies: { "mcp-client": "^0.1.0" } }
|
|
24
|
+
};
|
|
25
|
+
assert.deepEqual(resolveToolDependencyPlan(entries, "magnific-mcp"), ["mcp-client", "magnific-mcp"]);
|
|
26
|
+
assert.throws(
|
|
27
|
+
() => resolveToolDependencyPlan({ a: { version: "1.0.0", toolDependencies: { b: "^1.0.0" } } }, "a"),
|
|
28
|
+
/not locked/
|
|
29
|
+
);
|
|
30
|
+
assert.throws(
|
|
31
|
+
() => resolveToolDependencyPlan({
|
|
32
|
+
a: { version: "1.0.0", toolDependencies: { b: "^1.0.0" } },
|
|
33
|
+
b: { version: "1.0.0", toolDependencies: { a: "^1.0.0" } }
|
|
34
|
+
}, "a"),
|
|
35
|
+
/Circular/
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("reports missing and incompatible installed tool dependencies", () => {
|
|
40
|
+
const tools = new Map([
|
|
41
|
+
["magnific-mcp", { name: "magnific-mcp", version: "0.1.0", toolDependencies: { "mcp-client": "^0.1.0" } }]
|
|
42
|
+
]);
|
|
43
|
+
assert.deepEqual(inspectToolDependencies(tools), [{
|
|
44
|
+
tool: "magnific-mcp",
|
|
45
|
+
type: "missing",
|
|
46
|
+
dependency: "mcp-client",
|
|
47
|
+
range: "^0.1.0"
|
|
48
|
+
}]);
|
|
49
|
+
tools.set("mcp-client", { name: "mcp-client", version: "0.2.0", toolDependencies: {} });
|
|
50
|
+
assert.equal(inspectToolDependencies(tools)[0].type, "incompatible");
|
|
51
|
+
tools.get("mcp-client").version = "0.1.4";
|
|
52
|
+
assert.deepEqual(inspectToolDependencies(tools), []);
|
|
53
|
+
});
|
|
@@ -104,6 +104,7 @@ test("loads and lists installed tools from the user tools directory", async () =
|
|
|
104
104
|
version: null,
|
|
105
105
|
packageDigest: null,
|
|
106
106
|
requirements: [],
|
|
107
|
+
toolDependencies: {},
|
|
107
108
|
description: "Fake test tool",
|
|
108
109
|
input: ["text/plain"],
|
|
109
110
|
output: ["text/plain"],
|
|
@@ -144,6 +145,23 @@ test("shows semantic metadata in tool help", async () => {
|
|
|
144
145
|
assert.match(help, /Assigned skills:/);
|
|
145
146
|
});
|
|
146
147
|
|
|
148
|
+
test("reports dependency status in help and blocks a tool with a missing dependency", async () => {
|
|
149
|
+
await resetHome();
|
|
150
|
+
await createFakeTool("dependent-tool", {
|
|
151
|
+
version: "1.0.0",
|
|
152
|
+
toolDependencies: { "base-tool": "^1.0.0" }
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const registry = new ToolRegistry();
|
|
156
|
+
await registry.load();
|
|
157
|
+
|
|
158
|
+
assert.match(await registry.help("dependent-tool"), /base-tool@\^1\.0\.0: missing/);
|
|
159
|
+
await assert.rejects(
|
|
160
|
+
() => registry.run({ name: "dependent-tool", request: { args: {} } }),
|
|
161
|
+
/Tool dependency missing/
|
|
162
|
+
);
|
|
163
|
+
});
|
|
164
|
+
|
|
147
165
|
test("runs a registered tool process with an enriched request and cleans up request files", async () => {
|
|
148
166
|
await resetHome();
|
|
149
167
|
await createFakeTool("fake-tool");
|
|
@@ -178,7 +196,7 @@ test("runs a registered tool process with an enriched request and cleans up requ
|
|
|
178
196
|
});
|
|
179
197
|
assert.equal(result.output.env.ARISA_PACKAGE_DIR, arisaPackageDir);
|
|
180
198
|
assert.equal(result.output.env.ARISA_IPC_SOCKET, arisaIpcSocketFile);
|
|
181
|
-
assert.deepEqual(await registry.usage("chat-1"), [{ name: "fake-tool", count: 1 }]);
|
|
199
|
+
assert.deepEqual(await registry.usage("chat-1"), [{ name: "fake-tool", count: 1, official: false }]);
|
|
182
200
|
|
|
183
201
|
const requestFile = result.output.requestFile;
|
|
184
202
|
await assert.rejects(() => access(requestFile), { code: "ENOENT" });
|
package/test/tool-usage.test.js
CHANGED
|
@@ -3,6 +3,7 @@ import { mkdtemp, rm } from "node:fs/promises";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
|
+
import { ToolRegistry } from "../src/core/tools/tool-registry.js";
|
|
6
7
|
import { ToolUsageStore } from "../src/core/tools/tool-usage-store.js";
|
|
7
8
|
import { formatToolUsageReport } from "../src/runtime/tool-usage-report.js";
|
|
8
9
|
|
|
@@ -26,16 +27,37 @@ test("counts concurrent tool uses per chat", async () => {
|
|
|
26
27
|
}
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
test("reports recorded usage for local tools not present in the startup registry", async () => {
|
|
31
|
+
const registry = new ToolRegistry({
|
|
32
|
+
usageStore: {
|
|
33
|
+
counts: async () => ({ "creator-scout": 4 })
|
|
34
|
+
},
|
|
35
|
+
resolveOfficialToolNames: async () => new Set(["gmail-workspace"])
|
|
36
|
+
});
|
|
37
|
+
registry.tools.set("gmail-workspace", {
|
|
38
|
+
name: "gmail-workspace",
|
|
39
|
+
input: ["application/json"],
|
|
40
|
+
output: ["application/json"]
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
assert.deepEqual(await registry.usage("chat-1"), [
|
|
44
|
+
{ name: "creator-scout", count: 4, official: false },
|
|
45
|
+
{ name: "gmail-workspace", count: 0, official: true }
|
|
46
|
+
]);
|
|
47
|
+
});
|
|
48
|
+
|
|
29
49
|
test("formats narrow tool usage counts with bullets and right-aligned numbers", () => {
|
|
30
50
|
const report = formatToolUsageReport([
|
|
31
|
-
{ name: "gmail-workspace", count: 3 },
|
|
32
|
-
{ name: "campaign-draft-runner", count: 12 }
|
|
51
|
+
{ name: "gmail-workspace", count: 3, official: true },
|
|
52
|
+
{ name: "campaign-draft-runner", count: 12, official: false }
|
|
33
53
|
]);
|
|
54
|
+
assert.match(report, /Official\n- gmail-workspace/);
|
|
55
|
+
assert.match(report, /Local\n- campaign-draft-runner/);
|
|
34
56
|
assert.match(report, /- campaign-draft-runner 12/);
|
|
35
57
|
assert.match(report, /- gmail-workspace\s+3/);
|
|
36
58
|
const rows = report.split("\n").filter((line) => line.startsWith("- "));
|
|
37
|
-
assert.match(rows[0], /
|
|
38
|
-
assert.match(rows[1], /
|
|
59
|
+
assert.match(rows[0], /gmail-workspace/);
|
|
60
|
+
assert.match(rows[1], /campaign-draft-runner/);
|
|
39
61
|
assert.deepEqual(rows.map((line) => line.match(/\d+$/).index + line.match(/\d+$/)[0].length), [27, 27]);
|
|
40
62
|
assert.deepEqual(rows.map((line) => line.length), [27, 27]);
|
|
41
63
|
assert.ok(report.split("\n").every((line) => [...line].length <= 35));
|