arisa 4.1.10 → 4.1.14
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 +1 -1
- package/package.json +1 -1
- package/src/core/agent/agent-manager.js +5 -6
- package/src/transport/telegram/bot.js +33 -10
- package/src/transport/telegram/media.js +1 -1
- package/test/artifact-store.test.js +99 -0
- package/test/auth-flow.test.js +40 -0
- package/test/auth.test.js +101 -0
- package/test/capabilities-security.test.js +249 -0
- package/test/media-caption.test.js +15 -0
- package/test/normalize-for-reasoning.test.js +173 -0
- package/test/paths.test.js +54 -0
- package/test/task-store.test.js +119 -0
- package/test/telegram-text-artifact.test.js +81 -0
- package/test/text-format.test.js +61 -0
- package/test/tool-config.test.js +72 -0
- package/test/tool-registry-run.test.js +136 -0
- package/test/tool-result.test.js +127 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { access, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
const homeDir = await mkdtemp(path.join(os.tmpdir(), "arisa-tool-registry-home-"));
|
|
8
|
+
process.env.HOME = homeDir;
|
|
9
|
+
process.env.USERPROFILE = homeDir;
|
|
10
|
+
|
|
11
|
+
const { ToolRegistry } = await import("../src/core/tools/tool-registry.js");
|
|
12
|
+
const {
|
|
13
|
+
arisaHomeDir,
|
|
14
|
+
arisaPackageDir,
|
|
15
|
+
arisaIpcSocketFile,
|
|
16
|
+
toolsDir
|
|
17
|
+
} = await import("../src/runtime/paths.js");
|
|
18
|
+
|
|
19
|
+
async function resetHome() {
|
|
20
|
+
await rm(arisaHomeDir, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function createFakeTool(name = "fake-tool") {
|
|
24
|
+
const dir = path.join(toolsDir, name);
|
|
25
|
+
await mkdir(dir, { recursive: true });
|
|
26
|
+
await writeFile(path.join(dir, "tool.manifest.json"), `${JSON.stringify({
|
|
27
|
+
name,
|
|
28
|
+
description: "Fake test tool",
|
|
29
|
+
entry: "index.js",
|
|
30
|
+
input: ["text/plain"],
|
|
31
|
+
output: ["text/plain"],
|
|
32
|
+
configSchema: {
|
|
33
|
+
apiKey: { type: "string", required: false }
|
|
34
|
+
},
|
|
35
|
+
skillHints: [{ name: "missing-skill", when: "testing" }]
|
|
36
|
+
}, null, 2)}\n`, "utf8");
|
|
37
|
+
await writeFile(path.join(dir, "config.js"), "export default {\n apiKey: \"default-key\"\n};\n", "utf8");
|
|
38
|
+
await writeFile(path.join(dir, "index.js"), `import { readFile } from "node:fs/promises";
|
|
39
|
+
|
|
40
|
+
const requestFileIndex = process.argv.indexOf("--request-file");
|
|
41
|
+
if (process.argv.includes("--help")) {
|
|
42
|
+
process.stdout.write("Fake test tool help\\n");
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
if (requestFileIndex === -1) {
|
|
46
|
+
process.stdout.write(JSON.stringify({ ok: false, error: "missing request file" }));
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const requestFile = process.argv[requestFileIndex + 1];
|
|
51
|
+
const request = JSON.parse(await readFile(requestFile, "utf8"));
|
|
52
|
+
process.stdout.write(JSON.stringify({
|
|
53
|
+
ok: true,
|
|
54
|
+
output: {
|
|
55
|
+
text: "tool completed",
|
|
56
|
+
request,
|
|
57
|
+
requestFile,
|
|
58
|
+
env: {
|
|
59
|
+
ARISA_PACKAGE_DIR: process.env.ARISA_PACKAGE_DIR,
|
|
60
|
+
ARISA_IPC_SOCKET: process.env.ARISA_IPC_SOCKET
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
`, "utf8");
|
|
65
|
+
return dir;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test("loads and lists installed tools from the user tools directory", async () => {
|
|
69
|
+
await resetHome();
|
|
70
|
+
await createFakeTool("fake-tool");
|
|
71
|
+
|
|
72
|
+
const registry = new ToolRegistry();
|
|
73
|
+
await registry.load();
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(registry.list(), [{
|
|
76
|
+
name: "fake-tool",
|
|
77
|
+
description: "Fake test tool",
|
|
78
|
+
input: ["text/plain"],
|
|
79
|
+
output: ["text/plain"],
|
|
80
|
+
configSchema: {
|
|
81
|
+
apiKey: { type: "string", required: false }
|
|
82
|
+
},
|
|
83
|
+
skillHints: [{ name: "missing-skill", when: "testing" }]
|
|
84
|
+
}]);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("runs a registered tool process with an enriched request and cleans up request files", async () => {
|
|
88
|
+
await resetHome();
|
|
89
|
+
await createFakeTool("fake-tool");
|
|
90
|
+
|
|
91
|
+
const registry = new ToolRegistry();
|
|
92
|
+
await registry.load();
|
|
93
|
+
|
|
94
|
+
const result = await registry.run({
|
|
95
|
+
name: "fake-tool",
|
|
96
|
+
chatId: "chat-1",
|
|
97
|
+
request: {
|
|
98
|
+
text: "hello",
|
|
99
|
+
args: { count: 2 }
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.equal(result.ok, true);
|
|
104
|
+
assert.equal(result.status, "ok");
|
|
105
|
+
assert.equal(result.output.text, "tool completed");
|
|
106
|
+
assert.deepEqual(result.output.request, {
|
|
107
|
+
text: "hello",
|
|
108
|
+
args: { count: 2 },
|
|
109
|
+
chatId: "chat-1",
|
|
110
|
+
skills: [{
|
|
111
|
+
name: "missing-skill",
|
|
112
|
+
when: "testing",
|
|
113
|
+
found: false,
|
|
114
|
+
description: "",
|
|
115
|
+
path: "",
|
|
116
|
+
content: ""
|
|
117
|
+
}]
|
|
118
|
+
});
|
|
119
|
+
assert.equal(result.output.env.ARISA_PACKAGE_DIR, arisaPackageDir);
|
|
120
|
+
assert.equal(result.output.env.ARISA_IPC_SOCKET, arisaIpcSocketFile);
|
|
121
|
+
|
|
122
|
+
const requestFile = result.output.requestFile;
|
|
123
|
+
await assert.rejects(() => access(requestFile), { code: "ENOENT" });
|
|
124
|
+
await assert.rejects(() => access(path.dirname(requestFile)), { code: "ENOENT" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("rejects unknown tools", async () => {
|
|
128
|
+
await resetHome();
|
|
129
|
+
const registry = new ToolRegistry();
|
|
130
|
+
await registry.load();
|
|
131
|
+
|
|
132
|
+
await assert.rejects(
|
|
133
|
+
() => registry.run({ name: "missing-tool", request: {}, chatId: "chat-1" }),
|
|
134
|
+
/Tool not found: missing-tool/
|
|
135
|
+
);
|
|
136
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import {
|
|
4
|
+
normalizeToolResult,
|
|
5
|
+
toolError,
|
|
6
|
+
toolNeedsConfig,
|
|
7
|
+
toolOk
|
|
8
|
+
} from "../src/core/tools/tool-result.js";
|
|
9
|
+
|
|
10
|
+
test("builds standard successful tool responses", () => {
|
|
11
|
+
assert.deepEqual(
|
|
12
|
+
toolOk({ text: "done" }, { asyncTasks: [{ id: "task-1" }] }),
|
|
13
|
+
{ ok: true, output: { text: "done" }, asyncTasks: [{ id: "task-1" }] }
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("builds standard failed tool responses", () => {
|
|
18
|
+
assert.deepEqual(
|
|
19
|
+
toolError("boom", { status: "bad_request", details: { field: "name" } }),
|
|
20
|
+
{
|
|
21
|
+
ok: false,
|
|
22
|
+
status: "bad_request",
|
|
23
|
+
error: "boom",
|
|
24
|
+
details: { field: "name" }
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("builds missing-config tool responses", () => {
|
|
30
|
+
assert.deepEqual(
|
|
31
|
+
toolNeedsConfig({
|
|
32
|
+
tool: "demo",
|
|
33
|
+
missingConfig: ["apiKey"],
|
|
34
|
+
configPath: "/tmp/config.js"
|
|
35
|
+
}),
|
|
36
|
+
{
|
|
37
|
+
ok: false,
|
|
38
|
+
status: "needs_config",
|
|
39
|
+
error: "Missing tool configuration for demo.",
|
|
40
|
+
missingConfig: ["apiKey"],
|
|
41
|
+
configPath: "/tmp/config.js",
|
|
42
|
+
resolution: {
|
|
43
|
+
type: "user_config_required",
|
|
44
|
+
tool: "demo",
|
|
45
|
+
missingConfig: ["apiKey"],
|
|
46
|
+
configPath: "/tmp/config.js"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("normalizes non-object responses into failed results", () => {
|
|
53
|
+
const result = normalizeToolResult("demo", "not-json");
|
|
54
|
+
|
|
55
|
+
assert.equal(result.ok, false);
|
|
56
|
+
assert.equal(result.status, "failed");
|
|
57
|
+
assert.equal(result.error, "Invalid tool response for demo");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("normalizes missing-config failures", () => {
|
|
61
|
+
const result = normalizeToolResult("demo", {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: "Need API key",
|
|
64
|
+
missingConfig: ["apiKey"],
|
|
65
|
+
configPath: "/tmp/config.js"
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
assert.equal(result.ok, false);
|
|
69
|
+
assert.equal(result.status, "needs_config");
|
|
70
|
+
assert.equal(result.error, "Need API key");
|
|
71
|
+
assert.deepEqual(result.resolution, {
|
|
72
|
+
type: "user_config_required",
|
|
73
|
+
tool: "demo",
|
|
74
|
+
missingConfig: ["apiKey"],
|
|
75
|
+
configPath: "/tmp/config.js"
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("preserves explicit missing-config status and resolution", () => {
|
|
80
|
+
const result = normalizeToolResult("demo", {
|
|
81
|
+
ok: false,
|
|
82
|
+
status: "blocked",
|
|
83
|
+
error: "Need consent",
|
|
84
|
+
missingConfig: ["consent"],
|
|
85
|
+
resolution: { type: "manual", url: "https://example.com" }
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
assert.equal(result.status, "blocked");
|
|
89
|
+
assert.deepEqual(result.resolution, { type: "manual", url: "https://example.com" });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("normalizes regular failures", () => {
|
|
93
|
+
const result = normalizeToolResult("demo", {
|
|
94
|
+
ok: false,
|
|
95
|
+
error: "Tool exploded",
|
|
96
|
+
stdout: "",
|
|
97
|
+
stderr: "stack"
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
assert.equal(result.ok, false);
|
|
101
|
+
assert.equal(result.status, "failed");
|
|
102
|
+
assert.equal(result.error, "Tool exploded");
|
|
103
|
+
assert.equal(result.stderr, "stack");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("normalizes successful responses", () => {
|
|
107
|
+
const result = normalizeToolResult("demo", {
|
|
108
|
+
ok: true,
|
|
109
|
+
output: { text: "hello" },
|
|
110
|
+
delivery: { method: "document" }
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
assert.equal(result.ok, true);
|
|
114
|
+
assert.equal(result.status, "ok");
|
|
115
|
+
assert.deepEqual(result.output, { text: "hello" });
|
|
116
|
+
assert.deepEqual(result.delivery, { method: "document" });
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("rejects object responses without an ok contract", () => {
|
|
120
|
+
const rawResult = { output: { text: "hello" } };
|
|
121
|
+
const result = normalizeToolResult("demo", rawResult);
|
|
122
|
+
|
|
123
|
+
assert.equal(result.ok, false);
|
|
124
|
+
assert.equal(result.status, "failed");
|
|
125
|
+
assert.equal(result.error, "Invalid tool response for demo");
|
|
126
|
+
assert.deepEqual(result.rawResult, rawResult);
|
|
127
|
+
});
|