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.
@@ -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" });
@@ -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], /campaign-draft-runner/);
38
- assert.match(rows[1], /gmail-workspace/);
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));