arisa 5.1.13 → 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 CHANGED
@@ -47,6 +47,7 @@ Each tool declares in `tool.manifest.json`:
47
47
  - `category`: optional broad capability bucket for discovery
48
48
  - `keywords`: optional intent tags for capability discovery
49
49
  - `skillHints`: optional skills to apply when using or editing the tool
50
+ - `toolDependencies`: optional required Arisa tools mapped to exact or caret semantic versions; use this only for hard runtime dependencies
50
51
 
51
52
  ## Text encoding
52
53
  All textual content generated or sent by Arisa or its tools must use UTF-8.
@@ -78,6 +79,8 @@ const result = await arisa.tools.run({
78
79
 
79
80
  The IPC channel is a local socket under `~/.arisa/state`. Every request must include `toolName`; chat-scoped capabilities also require `chatId`. Exposed capabilities are explicit: tools (`list`, `help`, `skills`, `setConfig`, `setResourceNote`, `getResourceNote`, `run`), artifacts (`createText`, `listRecent`, `get`, `deliver`), tasks (`add`, `list`, `cancel`, `cancelAll`), agent events (`enqueueEvent`), and runtime paths (`getChatToolStateDir`, `getToolStateDir`, `getChatToolTmpDir`, `getToolTmpDir`, `getChatArtifactsDir`). Do not expose raw `agentManager`, `taskStore`, `artifactStore`, or `toolRegistry` access.
80
81
 
82
+ `agent.enqueueEvent` may include an optional `acknowledgement` of at most 500 characters. Telegram delivers it before the event enters agent reasoning, so explicit authorization receipts cannot be delayed behind follow-up work. Keep it deterministic, concise, and free of credentials; the event prompt must not repeat it.
83
+
81
84
  ## Tool resource notes
82
85
  A tool resource may have one deterministic chat-scoped operational note of at most 200 characters, keyed by the exact pair `toolName:resourceId`. Notes live in the chat state file returned by `getChatToolResourceNotesFile(chatId)`. They are separate from semantic memory and contain no credentials.
83
86
 
@@ -130,6 +133,7 @@ When such a tool is built, implement it with the shared daemon runtime instead o
130
133
  - expose normal CLI behavior through `run --request-file`; callers should not manage daemon internals
131
134
  - use the runtime for `daemon.pid`, `daemon.log`, `status.json`, and `commands/*.request|processing|result.json`
132
135
  - keep one daemon owner per tool/session and avoid opening a second client over the same resource
136
+ - daemons that receive unsolicited external traffic (webhooks, bridge imports, persistent sockets, or active watchers) must use `daemon.autoStart: true` and disable idle shutdown while ingress is expected; idle shutdown is only for capabilities that can be restarted by the request initiating their next use
133
137
  - use `beforeStart` only for tool-specific cleanup such as stale browser locks, without deleting persistent session/model data
134
138
  - keep daemon tools headless/server-safe by default when they are meant to run on VPS machines
135
139
  - if the daemon exposes an HTTP server, keep that server inside the tool; Arisa core does not discover or mount tool routes
package/README.md CHANGED
@@ -76,6 +76,8 @@ Each tool folder contains:
76
76
 
77
77
  `tool.manifest.json` may also include optional `skillHints`; Arisa resolves installed skills and passes them into `run_tool` requests as `skills` guidance, not runtime dependencies.
78
78
 
79
+ A tool that requires another Arisa tool declares a versioned `toolDependencies` map, for example `{ "mcp-client": "^0.1.0" }`. Official installation resolves these dependencies first, rejects missing or cyclic lock entries, and validates installed versions. The registry blocks execution when a required tool is missing or incompatible, and `/doctor` reports the issue.
80
+
79
81
  Each tool is isolated from the root project and from other tools.
80
82
  That isolation is part of the architecture:
81
83
 
package/package.json CHANGED
@@ -1,17 +1,12 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "5.1.13",
3
+ "version": "5.1.24",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
8
  "arisa": "bin/arisa.js"
9
9
  },
10
- "scripts": {
11
- "start": "node src/index.js",
12
- "bootstrap": "node src/index.js --bootstrap",
13
- "test": "node --test"
14
- },
15
10
  "keywords": [
16
11
  "telegram",
17
12
  "pi-agent",
@@ -44,11 +39,15 @@
44
39
  "url": "https://github.com/clasen/Arisa/issues"
45
40
  },
46
41
  "homepage": "https://arisa.sh",
47
- "packageManager": "pnpm@11.3.0+sha512.2c403d6594527287672b1f7056343a1f7c3634036a67ffabfcc2b3d7595d843768f8787148d1b57cf7956c90606bbd192857c363af19e96d2d0ec9ec5741d215",
48
42
  "dependencies": {
49
43
  "@earendil-works/pi-coding-agent": "0.80.6",
50
44
  "@sinclair/typebox": "^0.34.41",
51
45
  "grammy": "^1.42.0",
52
46
  "typebox": "1.1.38"
47
+ },
48
+ "scripts": {
49
+ "start": "node src/index.js",
50
+ "bootstrap": "node src/index.js --bootstrap",
51
+ "test": "node --test"
53
52
  }
54
- }
53
+ }
@@ -14,6 +14,7 @@ import {
14
14
  import os from "node:os";
15
15
  import path from "node:path";
16
16
  import { getToolDir } from "../../runtime/paths.js";
17
+ import { normalizeToolDependencies, resolveToolDependencyPlan, satisfiesToolVersion } from "./tool-dependencies.js";
17
18
 
18
19
  const bundledLockFile = new URL("../../official-tools.lock.json", import.meta.url);
19
20
 
@@ -46,7 +47,8 @@ export function validateOfficialToolLock(lock, toolName) {
46
47
  if (!COMMIT_PATTERN.test(String(lock.commit || ""))) {
47
48
  throw new Error("Official tool lock requires an immutable 40-character commit");
48
49
  }
49
- const files = lock.tools?.[toolName]?.files;
50
+ const entry = lock.tools?.[toolName];
51
+ const files = entry?.files;
50
52
  if (!files || typeof files !== "object" || Array.isArray(files) || !Object.keys(files).length) {
51
53
  throw new Error(`Official tool lock has no files for ${toolName}`);
52
54
  }
@@ -56,7 +58,12 @@ export function validateOfficialToolLock(lock, toolName) {
56
58
  throw new Error(`Invalid SHA-256 digest for ${toolName}/${file}`);
57
59
  }
58
60
  }
59
- return { repository: lock.repository, commit: lock.commit, files };
61
+ const toolDependencies = normalizeToolDependencies(entry.toolDependencies);
62
+ const toolVersion = entry.version == null ? null : String(entry.version);
63
+ if (toolVersion && !satisfiesToolVersion(toolVersion, toolVersion)) {
64
+ throw new Error(`Invalid locked tool version for ${toolName}: ${toolVersion}`);
65
+ }
66
+ return { repository: lock.repository, commit: lock.commit, files, toolVersion, toolDependencies };
60
67
  }
61
68
 
62
69
  async function walkFiles(root, relative = "") {
@@ -133,11 +140,36 @@ async function checkoutRepository({ repository, commit, checkoutDir }) {
133
140
  if (resolved !== commit) throw new Error(`Official tool checkout resolved ${resolved}, expected ${commit}`);
134
141
  }
135
142
 
136
- async function validateEntrypoint(toolDir, toolName) {
143
+ async function installPackageDependencies(toolDir) {
144
+ let packageJson;
145
+ try {
146
+ packageJson = JSON.parse(await readFile(path.join(toolDir, "package.json"), "utf8"));
147
+ } catch (error) {
148
+ if (error?.code === "ENOENT") return { installed: false };
149
+ throw error;
150
+ }
151
+ const dependencyCount = Object.keys(packageJson.dependencies || {}).length + Object.keys(packageJson.optionalDependencies || {}).length;
152
+ if (!dependencyCount) return { installed: false };
153
+ if (await exists(path.join(toolDir, "package-lock.json"))) {
154
+ await runCommand("npm", ["ci", "--omit=dev"], { cwd: toolDir });
155
+ } else {
156
+ await runCommand("npm", ["install", "--omit=dev"], { cwd: toolDir });
157
+ }
158
+ return { installed: true, dependencyCount };
159
+ }
160
+
161
+ async function validateEntrypoint(toolDir, toolName, locked) {
137
162
  const manifest = JSON.parse(await readFile(path.join(toolDir, "tool.manifest.json"), "utf8"));
138
163
  if (manifest.name !== toolName) {
139
164
  throw new Error(`Official tool manifest mismatch: expected ${toolName}, got ${manifest.name || "missing"}`);
140
165
  }
166
+ if (locked.toolVersion && manifest.version !== locked.toolVersion) {
167
+ throw new Error(`Official tool version mismatch: expected ${locked.toolVersion}, got ${manifest.version || "missing"}`);
168
+ }
169
+ const manifestDependencies = normalizeToolDependencies(manifest.toolDependencies);
170
+ if (JSON.stringify(manifestDependencies) !== JSON.stringify(locked.toolDependencies)) {
171
+ throw new Error(`Official tool dependency metadata mismatch: ${toolName}`);
172
+ }
141
173
  const entry = manifest.entry || "index.js";
142
174
  const entryPath = path.join(toolDir, entry);
143
175
  if (!(await exists(entryPath))) throw new Error(`Official tool entry does not exist: ${entry}`);
@@ -151,6 +183,7 @@ export async function installLockedOfficialTool({
151
183
  destination,
152
184
  scratchRoot = os.tmpdir(),
153
185
  checkout = checkoutRepository,
186
+ installDependencies = installPackageDependencies,
154
187
  validate = validateEntrypoint
155
188
  }) {
156
189
  const locked = validateOfficialToolLock(lock, toolName);
@@ -165,7 +198,8 @@ export async function installLockedOfficialTool({
165
198
  const sourceDir = path.join(checkoutDir, "tools", toolName);
166
199
  await verifyOfficialToolTree(sourceDir, locked.files);
167
200
  await cp(sourceDir, stageDir, { recursive: true, errorOnExist: true, force: false });
168
- await validate(stageDir, toolName);
201
+ await installDependencies(stageDir, toolName);
202
+ await validate(stageDir, toolName, locked);
169
203
  await mkdir(path.dirname(destination), { recursive: true });
170
204
  await rename(stageDir, destination);
171
205
  return { toolName, destination, commit: locked.commit, files: Object.keys(locked.files).length };
@@ -174,10 +208,48 @@ export async function installLockedOfficialTool({
174
208
  }
175
209
  }
176
210
 
211
+ async function installedToolVersion(toolName) {
212
+ try {
213
+ const manifest = JSON.parse(await readFile(path.join(getToolDir(toolName), "tool.manifest.json"), "utf8"));
214
+ return typeof manifest.version === "string" ? manifest.version : null;
215
+ } catch (error) {
216
+ if (error?.code === "ENOENT") return undefined;
217
+ throw error;
218
+ }
219
+ }
220
+
177
221
  export async function installBundledOfficialTool(toolName, {
178
222
  lockFile = bundledLockFile,
179
- install = installLockedOfficialTool
223
+ install = installLockedOfficialTool,
224
+ resolveInstalledVersion = installedToolVersion
180
225
  } = {}) {
181
226
  const lock = JSON.parse(await readFile(lockFile, "utf8"));
182
- return install({ toolName, lock, destination: getToolDir(toolName) });
227
+ const entries = new Map(Object.entries(lock.tools || {}).map(([name, entry]) => [name, {
228
+ version: entry.version || null,
229
+ toolDependencies: normalizeToolDependencies(entry.toolDependencies)
230
+ }]));
231
+ const plan = resolveToolDependencyPlan(entries, toolName);
232
+ const dependencies = [];
233
+ let result = null;
234
+ for (const name of plan) {
235
+ const locked = validateOfficialToolLock(lock, name);
236
+ if (name !== toolName) {
237
+ const installedVersion = await resolveInstalledVersion(name);
238
+ if (installedVersion !== undefined) {
239
+ const requiredRanges = plan
240
+ .map((dependentName) => entries.get(dependentName)?.toolDependencies?.[name])
241
+ .filter(Boolean);
242
+ const incompatibleRange = requiredRanges.find((range) => !satisfiesToolVersion(installedVersion, range));
243
+ if (incompatibleRange || (!requiredRanges.length && locked.toolVersion !== installedVersion)) {
244
+ throw new Error(`Installed tool dependency is incompatible: ${name}@${installedVersion} does not satisfy ${incompatibleRange || locked.toolVersion || "locked version"}`);
245
+ }
246
+ dependencies.push({ name, version: installedVersion, status: "already-installed" });
247
+ continue;
248
+ }
249
+ }
250
+ const installed = await install({ toolName: name, lock, destination: getToolDir(name) });
251
+ if (name === toolName) result = installed;
252
+ else dependencies.push({ name, version: locked.toolVersion, status: "installed" });
253
+ }
254
+ return { ...result, dependencies };
183
255
  }
@@ -0,0 +1,99 @@
1
+ const TOOL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
2
+ const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
3
+ const RANGE_PATTERN = /^(\^)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
4
+
5
+ function versionParts(version) {
6
+ const match = VERSION_PATTERN.exec(String(version || ""));
7
+ return match ? match.slice(1).map(Number) : null;
8
+ }
9
+
10
+ function compareVersions(left, right) {
11
+ for (let index = 0; index < 3; index += 1) {
12
+ if (left[index] !== right[index]) return left[index] - right[index];
13
+ }
14
+ return 0;
15
+ }
16
+
17
+ export function normalizeToolDependencies(value) {
18
+ if (value == null) return {};
19
+ if (typeof value !== "object" || Array.isArray(value)) throw new Error("toolDependencies must be an object");
20
+ const normalized = {};
21
+ for (const [name, range] of Object.entries(value)) {
22
+ if (!TOOL_NAME_PATTERN.test(name)) throw new Error(`Invalid tool dependency name: ${name}`);
23
+ if (!RANGE_PATTERN.test(String(range || ""))) throw new Error(`Unsupported tool dependency range for ${name}: ${range || "empty"}`);
24
+ normalized[name] = String(range);
25
+ }
26
+ return normalized;
27
+ }
28
+
29
+ export function satisfiesToolVersion(version, range) {
30
+ const actual = versionParts(version);
31
+ const match = RANGE_PATTERN.exec(String(range || ""));
32
+ if (!actual || !match) return false;
33
+ const expected = match.slice(2).map(Number);
34
+ if (!match[1]) return compareVersions(actual, expected) === 0;
35
+ if (compareVersions(actual, expected) < 0) return false;
36
+ if (expected[0] > 0) return actual[0] === expected[0];
37
+ if (expected[1] > 0) return actual[0] === 0 && actual[1] === expected[1];
38
+ return actual[0] === 0 && actual[1] === 0 && actual[2] === expected[2];
39
+ }
40
+
41
+ export function inspectToolDependencies(tools, rootName = null) {
42
+ const issues = [];
43
+ const visiting = new Set();
44
+ const visited = new Set();
45
+ const visit = (name, chain = []) => {
46
+ if (visiting.has(name)) {
47
+ issues.push({ tool: name, type: "cycle", dependency: name, chain: [...chain, name] });
48
+ return;
49
+ }
50
+ if (visited.has(name)) return;
51
+ const tool = tools.get(name);
52
+ if (!tool) return;
53
+ visiting.add(name);
54
+ for (const [dependency, range] of Object.entries(normalizeToolDependencies(tool.toolDependencies))) {
55
+ const installed = tools.get(dependency);
56
+ if (!installed) {
57
+ issues.push({ tool: name, type: "missing", dependency, range });
58
+ continue;
59
+ }
60
+ if (!satisfiesToolVersion(installed.version, range)) {
61
+ issues.push({ tool: name, type: "incompatible", dependency, range, installedVersion: installed.version || null });
62
+ continue;
63
+ }
64
+ visit(dependency, [...chain, name]);
65
+ }
66
+ visiting.delete(name);
67
+ visited.add(name);
68
+ };
69
+ if (rootName) visit(rootName);
70
+ else for (const name of tools.keys()) visit(name);
71
+ return issues;
72
+ }
73
+
74
+ export function resolveToolDependencyPlan(entries, rootName) {
75
+ const tools = entries instanceof Map ? entries : new Map(Object.entries(entries || {}));
76
+ const order = [];
77
+ const visiting = new Set();
78
+ const visited = new Set();
79
+ const visit = (name, chain = []) => {
80
+ const tool = tools.get(name);
81
+ if (!tool) throw new Error(`Official tool dependency is not locked: ${name}`);
82
+ if (visiting.has(name)) throw new Error(`Circular official tool dependency: ${[...chain, name].join(" -> ")}`);
83
+ if (visited.has(name)) return;
84
+ visiting.add(name);
85
+ for (const [dependency, range] of Object.entries(normalizeToolDependencies(tool.toolDependencies))) {
86
+ const lockedDependency = tools.get(dependency);
87
+ if (!lockedDependency) throw new Error(`Official tool dependency is not locked: ${name} -> ${dependency}`);
88
+ if (!satisfiesToolVersion(lockedDependency.version, range)) {
89
+ throw new Error(`Locked tool dependency is incompatible: ${name} requires ${dependency}@${range}, lock has ${lockedDependency.version || "no version"}`);
90
+ }
91
+ visit(dependency, [...chain, name]);
92
+ }
93
+ visiting.delete(name);
94
+ visited.add(name);
95
+ order.push(name);
96
+ };
97
+ visit(rootName);
98
+ return order;
99
+ }
@@ -2,7 +2,7 @@ import { mkdir, readdir, readFile, rmdir, unlink, writeFile } from "node:fs/prom
2
2
  import path from "node:path";
3
3
  import { spawn } from "node:child_process";
4
4
  import { randomUUID } from "node:crypto";
5
- import { arisaIpcSocketFile, arisaPackageDir, getToolConfigPath, getToolTmpDir, getChatToolTmpDir, toolsDir as userToolsRoot } from "../../runtime/paths.js";
5
+ import { arisaIpcSocketFile, arisaPackageDir, getToolConfigPath, getToolStateDir, getToolTmpDir, getChatToolTmpDir, toolsDir as userToolsRoot } from "../../runtime/paths.js";
6
6
  import { loadToolConfig, parseConfigModule, writeToolConfig } from "./tool-config.js";
7
7
  import { normalizeToolResult } from "./tool-result.js";
8
8
  import { readDaemonDiagnostic } from "./daemon-processes.js";
@@ -10,6 +10,7 @@ import { createDaemonRuntime, DAEMON_EVENT_TYPES, DAEMON_PROTOCOL_VERSION } from
10
10
  import { daemonConfigDefaults } from "../config/config-defaults.js";
11
11
  import { SkillRegistry } from "../skills/skill-registry.js";
12
12
  import { ToolUsageStore } from "./tool-usage-store.js";
13
+ import { inspectToolDependencies, normalizeToolDependencies } from "./tool-dependencies.js";
13
14
 
14
15
  function toolEnv() {
15
16
  return { ...process.env, ARISA_PACKAGE_DIR: arisaPackageDir, ARISA_IPC_SOCKET: arisaIpcSocketFile };
@@ -203,12 +204,36 @@ function formatSemanticMetadata(tool) {
203
204
  ].join("\n");
204
205
  }
205
206
 
207
+ function formatToolDependencies(tool, tools) {
208
+ const dependencies = Object.entries(tool.toolDependencies || {});
209
+ if (!dependencies.length) return null;
210
+ const lines = dependencies.map(([name, range]) => {
211
+ const issue = inspectToolDependencies(tools, tool.name).find((item) => item.dependency === name);
212
+ return `- ${name}@${range}: ${issue ? issue.type : "ready"}`;
213
+ });
214
+ return `Tool dependencies:\n${lines.join("\n")}`;
215
+ }
216
+
217
+ async function readOfficialToolNames() {
218
+ const baselinesDir = path.join(getToolStateDir("official-tool-sync"), "baselines");
219
+ try {
220
+ const entries = await readdir(baselinesDir, { withFileTypes: true });
221
+ return new Set(entries
222
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
223
+ .map((entry) => entry.name.slice(0, -5)));
224
+ } catch (error) {
225
+ if (error?.code === "ENOENT") return new Set();
226
+ throw error;
227
+ }
228
+ }
229
+
206
230
  export class ToolRegistry {
207
- constructor({ logger, usageStore = new ToolUsageStore() } = {}) {
231
+ constructor({ logger, usageStore = new ToolUsageStore(), resolveOfficialToolNames = readOfficialToolNames } = {}) {
208
232
  this.logger = logger;
209
233
  this.tools = new Map();
210
234
  this.skillRegistry = new SkillRegistry();
211
235
  this.usageStore = usageStore;
236
+ this.resolveOfficialToolNames = resolveOfficialToolNames;
212
237
  }
213
238
 
214
239
  async load() {
@@ -235,6 +260,7 @@ export class ToolRegistry {
235
260
  const skillHints = this.skillRegistry.normalizeHints(manifest);
236
261
  this.tools.set(manifest.name, {
237
262
  ...manifest,
263
+ toolDependencies: normalizeToolDependencies(manifest.toolDependencies),
238
264
  category: normalizeCategory(manifest.category),
239
265
  keywords: normalizeKeywords(manifest.keywords),
240
266
  skillHints,
@@ -259,6 +285,7 @@ export class ToolRegistry {
259
285
  version: typeof tool.version === "string" ? tool.version : null,
260
286
  packageDigest: typeof tool.packageDigest === "string" ? tool.packageDigest : null,
261
287
  requirements: requirementNames(tool.requirements),
288
+ toolDependencies: tool.toolDependencies || {},
262
289
  description: tool.description,
263
290
  input: tool.input,
264
291
  output: tool.output,
@@ -311,7 +338,8 @@ export class ToolRegistry {
311
338
  const skills = await this.resolveSkills(name);
312
339
  const sections = [
313
340
  help.trimEnd(),
314
- formatSemanticMetadata(tool)
341
+ formatSemanticMetadata(tool),
342
+ formatToolDependencies(tool, this.tools)
315
343
  ];
316
344
  if (skills.length) {
317
345
  const skillHelp = skills.map((item) => [
@@ -360,16 +388,32 @@ export class ToolRegistry {
360
388
  return { ok: true, tool: name, field, configPath };
361
389
  }
362
390
 
391
+ dependencyIssues(name = null) {
392
+ return inspectToolDependencies(this.tools, name);
393
+ }
394
+
363
395
  async usage(chatId) {
364
- const counts = await this.usageStore.counts(chatId);
365
- return this.list()
366
- .map((tool) => ({ name: tool.name, count: counts[tool.name] || 0 }))
396
+ const [counts, officialNames] = await Promise.all([
397
+ this.usageStore.counts(chatId),
398
+ this.resolveOfficialToolNames()
399
+ ]);
400
+ const names = new Set([
401
+ ...this.list().map((tool) => tool.name),
402
+ ...Object.keys(counts)
403
+ ]);
404
+ return [...names]
405
+ .map((name) => ({ name, count: counts[name] || 0, official: officialNames.has(name) }))
367
406
  .sort((left, right) => left.name.localeCompare(right.name));
368
407
  }
369
408
 
370
409
  async run({ name, request, chatId = null, onEvent = null }) {
371
410
  const tool = this.get(name);
372
411
  if (!tool) throw new Error(`Tool not found: ${name}`);
412
+ const dependencyIssue = this.dependencyIssues(name)[0];
413
+ if (dependencyIssue) {
414
+ const version = dependencyIssue.installedVersion ? `; installed ${dependencyIssue.installedVersion}` : "";
415
+ throw new Error(`Tool dependency ${dependencyIssue.type}: ${dependencyIssue.tool} requires ${dependencyIssue.dependency}@${dependencyIssue.range || "valid"}${version}`);
416
+ }
373
417
  await this.usageStore.record(chatId, name).catch((error) => {
374
418
  this.logger?.error("tools", `could not record ${name} usage: ${error?.message || String(error)}`);
375
419
  });
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "version": 1,
3
3
  "repository": "https://github.com/clasen/Arisa.git",
4
- "commit": "8438bffbc656de7f85e9a98fe7d8804b3c0224ea",
4
+ "commit": "eff37798c546e33cc4386c878ee01baa5dbd701c",
5
5
  "tools": {
6
6
  "master-slave": {
7
7
  "files": {
8
- "README.md": "8533804fda909c2f7bb0d05d40e2d6be9ef9126215bde70cd0ac4dda4afe9c55",
9
- "batch-runner.js": "be3939efe9c5800d3078c15a094f53db1094ef6e809d7826db7c47e3bec0c31b",
8
+ "README.md": "84d2841d8df31c07065d4fee6e434dc43cad241f3abf63e95efafad51ed69fcb",
9
+ "batch-runner.js": "97756a03b36278cd0315cc16da0c34a7821c322149c51ba6666f1c5081dbecbc",
10
10
  "chat-state-store.js": "50de39aa33432733bb688eb01968e314a32b169324a66d0f0089b2a19ed9c880",
11
11
  "config.js": "ff1c1cb6701ae1da7a6eb1519ba96a416e48daa73c2155a6f93cf5d64f65cef8",
12
12
  "index.js": "8586303b3d1bf937932159adbe9b69c1031e6a393300e7d3aca93910ddfab082",
@@ -19,11 +19,11 @@
19
19
  "lib/secure-store.js": "b8f365197f8e60d7f81fde6ed1cfc8851b603f6b0783e4cdc9036bb5c99726e1",
20
20
  "master-domain.js": "10b7dfb43eb9939eb5baec54f742076cc73a2add6589a0ff57c51bc79c236faf",
21
21
  "network-session.js": "db2d81a7ef47af29236d963b98e21414a11fef68e8b20895126eaad3f696ba3d",
22
- "package.json": "8e9177238ba878dd4b51e32e0ed58a39c5789186c22b19ad7a43ab6caf9dc469",
22
+ "package.json": "ef4824ccb46ddb3f901dd6e7a94abbd458152a2e73292ec409470643f45ff396",
23
23
  "remote-runtime.js": "55dd7e577a822be17c2f337798a2d7f44ad0c5b6d68c46490e058282135c4658",
24
24
  "slave-operations.js": "fec2e8addfff98f080284f24f722c1516124e5f6d12d8c03bb3c9cc0c475d1d6",
25
25
  "state-store.js": "c56849a3f1b9c990128276e17963034a7836abacedc44f2e500d5732ebf9e451",
26
- "test/batch-runner.test.js": "f14c6fddff5d6240008eef388df6a153e82490e534d06419d7bb267e4f4693a7",
26
+ "test/batch-runner.test.js": "69f69150eadd18c1ad1bc54f429e6cb5bc0c4492e3c6a6e76c2ce6827780ae41",
27
27
  "test/bootstrap-url.test.js": "62c112b1a38c59ea2c77f797e32873667b52db32386da661a6ae8410490126fe",
28
28
  "test/encrypted-frames.test.js": "cfb3195d3a58df7dc570bc7840a21c1841a53e0abc8b92840e0c24ed3435bcc1",
29
29
  "test/handshake-crypto.test.js": "f11d6e95658f2055d8256bd7dfb66b39bedf95bf62a3cec5525b109bb6f9bc95",
@@ -35,6 +35,158 @@
35
35
  "test/slave-operations.test.js": "a84526ad7554c7d03192a68655b4637bec18750559a5c3436f7471e912ca74cc",
36
36
  "tool.manifest.json": "abf24c67b1d9ef0331ded59e04dcc092772419e74691c0d753ab51b40286d6f6"
37
37
  }
38
+ },
39
+ "mcp-client": {
40
+ "version": "0.1.0",
41
+ "files": {
42
+ "README.md": "798125171049f939f4487e1a023cb3a11b3c28aae96b2687df1681a11bad8dc0",
43
+ "config.js": "b5b05fb242034e2d04b6301e848da9701c95be195ba1cdac2421964e9a7db8c9",
44
+ "index.js": "ba828f353ef23cd7acc3a064f859f553705836e4ede5d31ea0824a761cc88c2a",
45
+ "mcp-session.js": "51765b46f410e1cd49647235b611db0b10af20c9707efcacfbe0ff7dabe3c1ff",
46
+ "network-security.js": "60a020d4e3c7a4784a5a4e7b7246ee34e63ac73ee2316e09a09d10668dc8a7bc",
47
+ "oauth-device.js": "698aa9255bae47190c63527db7680aaab4f06392a108a0ff6444e32d31b7b89f",
48
+ "oauth-watch-plan.js": "b1bbed52eeb0e2e50dd52d733897ebd70573fea3ff5f023b3c63347f92633d3e",
49
+ "package-lock.json": "fd7b6bf4545de4056908bd656a4a4e21b9f84acc7b06d8c1d581a8c84c295e90",
50
+ "package.json": "16d423b77a32902671a751b1d673077c789a5bc3c79254bde296e4a4207fc4e3",
51
+ "state-store.js": "a8fb63096b8efdec8de1cb386eff371f1552f3fd71d96f25036e1c724285e0af",
52
+ "test/mcp-client.test.js": "188b253343f551303d1ee6ab2adc34e7153282461ba7399b598fe292e78a22a1",
53
+ "tool.manifest.json": "5659c398922c914c5bf893404519a30071ee685ab49f34a650ecafd585b4d6ec"
54
+ }
55
+ },
56
+ "magnific-mcp": {
57
+ "version": "0.1.0",
58
+ "toolDependencies": {
59
+ "mcp-client": "^0.1.0"
60
+ },
61
+ "files": {
62
+ "README.md": "00342fc9ae98f38716aeb332941524336c0e9ee4a5020104e52e7ed7b61882f9",
63
+ "config.js": "98a629781a1b2541806050952aa78a1e6e1b1671a6f1df7ba36c70dd116936a7",
64
+ "delivery-claims.js": "52f05a5bd1ece1ab21f7d9a49f362f9c55f101ef5723efd642dbecf0236921cd",
65
+ "generation-watch-plan.js": "eb60660341f254adc224181ca19dbbb969ef1d676aeaab60b71a8567f52648fe",
66
+ "index.js": "da7d35f2685fa0d5e716073f20725d7a5a4a0c477a1982b106a314662641b989",
67
+ "magnific-api.js": "39fa9a122577029111965c3e8487565c88ce46b61dec5ca685acf1c76cd3b636",
68
+ "network.js": "b45e43b49eea98ca7d80921ebfa6daf8b0a85d2079d4653f9eda682bc82101ae",
69
+ "package.json": "10ded9e89c13b500208717610984fd837e67e82ac3b31b21fe0058bd53c22a96",
70
+ "state-store.js": "c3ee744509f7a6be251cd895406bd1c70827f53cbdc1ac9d1c2a6e48479bfe1e",
71
+ "test/magnific-mcp.test.js": "c6345e70a7834a22df49abaf648c781f4da2e4d0790cbc13035c8af433c814c5",
72
+ "tool.manifest.json": "bbb24f54814c67f1ca1e119e6767e59d48f8e0cbc7f92444cf8e22fcc2fe06ed"
73
+ }
74
+ },
75
+ "campaign-draft-runner": {
76
+ "version": "0.1.0",
77
+ "toolDependencies": {
78
+ "pr-campaign": "^0.1.0",
79
+ "gmail-workspace": "^0.1.0"
80
+ },
81
+ "files": {
82
+ "README.md": "4e97db605c55c31c5fdb3ad83973f7733cef7a689b71b6d014fe07df631a4f99",
83
+ "config.js": "3c0c252baa7170e0bf0a9d1a7ecdb4febcf19f7baf0fcd320c0b852008f41b63",
84
+ "index.js": "0fd4574c70e7d008714f17eb54ccc34bc703a1fee7ab7eae768d79f86d2e747c",
85
+ "package.json": "a25b7ed1dd2c30cd68c3c10feb8ee1752cac8cd55cd4344690ffad649e231ec1",
86
+ "test/selection-policy.test.js": "b1bc804ba2be210230e78d7e4cfa5469e07e75c6ee944f58d77e6d00e88561cd",
87
+ "tool.manifest.json": "2609869985fd78000c999a9ae03f4b251c0aa945d19eed97e630f70da97c72ce"
88
+ }
89
+ },
90
+ "pr-campaign": {
91
+ "version": "0.1.0",
92
+ "files": {
93
+ "config.js": "f265f3a15721d3b04fcfd259d62e33c62c35ed03d7cd33e432cd32b1c44ac84f",
94
+ "contact-dedupe.js": "00cec722c5987c40eced127b0528004a5c54c0c6463c27c04ba37daf51c13f25",
95
+ "index.js": "9e858aed2510d3a2e93f38f693f0e0b5d8b2cac6965c84872aad91f785754d0a",
96
+ "package.json": "3919118d382b80153aab2dade602eb7ebad632642997c8d20b3561c517b2b7a6",
97
+ "test/add-contact-dedupe.test.js": "7a8f8c097181d5c42697e4f4c15e6bd080b4c9304a9878fd20d4b4b6a8300340",
98
+ "test/contact-dedupe.test.js": "be636643881c10105c731c923d0ac07db62fc4a663d7ed3badb2649119223a32",
99
+ "tool.manifest.json": "9c68d89276996039b055c43b4b4f82499ea684edbb36d9bfedcc909a3a23a80f"
100
+ }
101
+ },
102
+ "gmail-workspace": {
103
+ "version": "0.1.0",
104
+ "files": {
105
+ "config.js": "6ad295ec4cedbc936835d8f86c7528e0939ef09d5792c003308d2ee85dc5eea4",
106
+ "index.js": "495b5cec129a71638ab9b2682cf575eb8659e9a100b2e70deb0c523e60b69886",
107
+ "package.json": "1e88d42180a8dacd08c7ad1aa84d783991f901bb3bca7f0dddc4efd919470bdc",
108
+ "secretary-state.js": "0c4ef04db0d990a92b4b49ef62135baea76b7e4d5c232dfbf2087c61d1ce1948",
109
+ "test/secretary-state.test.mjs": "d8e866400853b39dc26e4b4ec09880c3cb1dd0b0091b2d25097388adf38559e7",
110
+ "tool.manifest.json": "93583b8c3a5f417435e3b3e88b129d2c610f2f168788e510f4cb2c03d4aef3a4"
111
+ }
112
+ },
113
+ "x-campaign-runner": {
114
+ "version": "0.1.0",
115
+ "toolDependencies": {
116
+ "x-dm": "^0.2.0"
117
+ },
118
+ "files": {
119
+ "README.md": "7cb7264446b9bef41332da9634cd6c94130f55c740142d084cddec776cf703c8",
120
+ "config.js": "5eb3c5667d01705d48504a4bf711347262b8a440605cb6b75ec519dd56cb1309",
121
+ "index.js": "19ca6dd9aa62b15e62481c6c7ce65023af6a25a21ffc622527acae12ff0171ed",
122
+ "package.json": "7a2dd72ce45cb1f3946b2459362ed39b82478d6df622a78690a0cc483fa03043",
123
+ "test/x-campaign-runner.test.mjs": "cf62245bccc7d7b5804117ca02753130e7eec4f56fcc671af82757f922117389",
124
+ "tool.manifest.json": "ebde15ad3666ed985377ae8ff67264567f3d5db408b5ec0823617f386ef46140"
125
+ }
126
+ },
127
+ "x-dm": {
128
+ "version": "0.2.0",
129
+ "files": {
130
+ "README.md": "f02835c10d822ce906df121441761de8c6d61c3a4aac3f9aa9a6d9211b73a6ea",
131
+ "config.js": "69a2dcccd10aa2f1ddcc24d52f4ce8fe4a6f5f5a85be64abbdf9dc9e19de0cd4",
132
+ "index.js": "cf4f92e94b9f21f6fcfed7d8d2926105a11b4ad621e2121c762d9c1e67a91456",
133
+ "package.json": "3a76a921b1e78e91b1d2a5ae1926b298dfd6b4ee3f716d5382159b474ee400f4",
134
+ "test/x-dm.test.mjs": "06352996e14777358eb62ad5e5d757b0dab5c0696927ed54ff40a5b7cdefcfd0",
135
+ "tool.manifest.json": "f3b2e613562e4dd0f88d196c4e3caf525a425483133a54351300ce284a5836a9"
136
+ }
137
+ },
138
+ "official-tool-sync": {
139
+ "version": "1.0.0",
140
+ "toolDependencies": {
141
+ "trash": "^1.0.0"
142
+ },
143
+ "files": {
144
+ ".gitignore": "4d56952b0fb13bf8f9b6c13a6d4c34a075bac3af447636a1df4335d7576e2f97",
145
+ "config.js": "744c445b1d2ff65b77f38a5473c24e50a32f4288c4a08c25a874a478086c08c8",
146
+ "index.js": "fdd4167c63cd33c8e7640cb7ce09fd618dbd416030aa920407392e4591543e94",
147
+ "package.json": "4447cb5d4e0a04526604dfb9820c44af88349c27e208f27baa148686846559da",
148
+ "sync-lib.js": "c9f2cd7e19370088ab5fd10f6b2ada52c07d2e405674a356d0989f9a0f4984f6",
149
+ "sync-lib.test.js": "c97cf850d9f42f6b121a458863b048bdcaa6d7d0475642f20742c5862ebf7cc4",
150
+ "tool.manifest.json": "8e0f94ad786bdaf9edb1a65cd03438e26c1e9927e8db9a5a39e349e7ef3c6cd3"
151
+ }
152
+ },
153
+ "trash": {
154
+ "version": "1.0.0",
155
+ "files": {
156
+ ".gitignore": "4d56952b0fb13bf8f9b6c13a6d4c34a075bac3af447636a1df4335d7576e2f97",
157
+ "config.js": "ad76578e9dcc05c2e45854de9fa2cd41285fd8dcdb9a7d9ee2056e0f8b2d68d6",
158
+ "index.js": "d562d2466bddc93dd9a3bc5455962a3457c50368ee5dde0607cae3ff5a88578a",
159
+ "package.json": "18ec594d43b6595c587b16125b9ce0c9426fdf00a28d7e342834e31e360575f8",
160
+ "tool.manifest.json": "4f236d274c891b55e4445d459cff6a3337a5d13adc5e8aca7fb965ff6aba4e4d",
161
+ "trash-store.js": "1318e0f9539292684c0c2ec2f1b7189f066fdba42f78e07821b067792f40e8c9",
162
+ "trash-store.test.js": "5647edba4ab5e5492764ccf8ba3e674e42b63942623d779b0bf69465c5adb53f"
163
+ }
164
+ },
165
+ "process-retrospective": {
166
+ "version": "1.0.0",
167
+ "files": {
168
+ "README.md": "7fa901adf532dbd1bb337a8019b0bd73978eef3b1ded51ffb24a3279d107056d",
169
+ "config.js": "30703e809f0a2c4b85ffb1e1f9cd7ee599224e9bc62c3bc19e71e271cbc72b5d",
170
+ "index.js": "9bd1ae078293211387ee3035b4c4dc7cc5852d08503cd55cf5fb3187cf1ace34",
171
+ "package.json": "e3978089354004e495b7c7f4b4edd9c0984b34f68229d0dfdafbb2e606d48c8d",
172
+ "reflection-plan.js": "ae78b47f5108993395fe9a6f1ff2eea0663a07ae1100780f9a63107fd6402314",
173
+ "state-store.js": "6d0d39bff7e5e16558123c088eb8946bf3758596ae974598936b94424618c7cf",
174
+ "test/reflection-plan.test.js": "d4e960948fc76aaed03651652a592ee4b3109abbb91693d415945cbb79642237",
175
+ "tool.manifest.json": "4304208986be075c95c6d71aa97d301fd266af18fef1b6ddac4cf004e38427e7"
176
+ }
177
+ },
178
+ "creator-scout": {
179
+ "version": "1.0.1",
180
+ "files": {
181
+ "README.md": "4f50850317c0875ad7ddea767c7dda112669112bec107a98e67a31a42787b82c",
182
+ "config.js": "bab728665650b986c0eaef8e1be1c259ec42b1f83df4217c9780f5468d3c7666",
183
+ "index.js": "d1266d622b6abaf77ce1fe93823d4f2faedb8a5991371997c364082a198dc728",
184
+ "package-lock.json": "ed479120b59c2d9b083991dea60eb7750ba36bc7b2c7defb01cfb598889b8d90",
185
+ "package.json": "ad463eeddea6a958ef291d58f216c0b42c5ff3b398b62ed9512b857ea5e4b672",
186
+ "reference-match.js": "762d822ad31f89c06cb5a40f84ba3f864436db486375ed30e48bde94de282c7c",
187
+ "test/reference-match.test.js": "701ce83396126b1e42bd3a54e331ff011a1fc27646a0f3aa99ce1c3e576aff6d",
188
+ "tool.manifest.json": "0617b0488035fd7f8a83a8e5a698558b3d0e956e7ea1e043e2e80bb224d4974f"
189
+ }
38
190
  }
39
191
  }
40
192
  }
@@ -43,6 +43,13 @@ function normalizeLimit(limit) {
43
43
  return Math.min(value, 100);
44
44
  }
45
45
 
46
+ function normalizeAcknowledgement(value) {
47
+ if (value == null || value === "") return "";
48
+ const acknowledgement = requireString(value, "acknowledgement").trim();
49
+ if (acknowledgement.length > 500) throw new Error("acknowledgement must be at most 500 characters");
50
+ return acknowledgement;
51
+ }
52
+
46
53
  export function createArisaCapabilities({
47
54
  artifactStore,
48
55
  taskStore,
@@ -206,7 +213,11 @@ export function createArisaCapabilities({
206
213
  const resourceId = String(params.resourceId || "").trim();
207
214
  return taskStore.add({
208
215
  kind: "agent_event",
209
- payload: { prompt: requireString(params.prompt, "prompt"), resourceId }
216
+ payload: {
217
+ prompt: requireString(params.prompt, "prompt"),
218
+ resourceId,
219
+ acknowledgement: normalizeAcknowledgement(params.acknowledgement)
220
+ }
210
221
  }, {
211
222
  payload: { chatId: scopedChatId },
212
223
  source: { type: "tool", toolName: scopedToolName, chatId: scopedChatId, resourceId }
@@ -135,6 +135,7 @@ export async function createApp({ logger, runtimeOverrides, requestRestart } = {
135
135
  toolProcessSupervisor,
136
136
  daemonPolicy: config.daemons,
137
137
  doctorPolicy: config.doctor,
138
+ inspectToolDependencies: async () => toolRegistry.dependencyIssues(),
138
139
  inspectInfrastructure: async () => {
139
140
  const tool = toolRegistry.get("master-slave");
140
141
  if (!tool) return null;