arisa 4.1.14 → 4.2.0

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
@@ -23,6 +23,15 @@ Do not build runtime paths by hand. Use `src/runtime/paths.js`:
23
23
 
24
24
  Tools receive `chatId` from the registry. Any persisted or indexed user content must be scoped by chat. Avoid ad hoc roots like `~/.arisa/state/<toolName>`, `~/.arisa/state/chats`, or runtime data inside `~/.arisa/tools/<toolName>`.
25
25
 
26
+ ## Tool config rules
27
+ Every tool has a local `config.js` that exports generic, non-user-specific defaults. Keep user credentials, deployment-specific URLs, account ids, phone numbers, and other per-user values empty or neutral there; document required values in `tool.manifest.json` `configSchema`.
28
+
29
+ Tools must consume config through `src/core/tools/tool-config.js`:
30
+ - `loadToolConfig(toolName, defaults)` for global tools and daemons that run as one shared process.
31
+ - `loadToolConfig(toolName, defaults, request.chatId)` inside `run()` for request-scoped tools that should honor per-chat overrides.
32
+
33
+ Config precedence is a shallow merge: tool defaults -> global tool config (`~/.arisa/tools/<toolName>/config.js`) -> chat tool config (`~/.arisa/chats/<chatId>/config/tools/<toolName>/config.js`). Per-request `args` may override loaded config for that invocation only; do not persist request args as config unless the user explicitly asked to set a default.
34
+
26
35
  ## Main rule: everything is piped through artifacts
27
36
  A pipe transforms one input artifact into one output artifact.
28
37
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "4.1.14",
3
+ "version": "4.2.0",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,9 +1,9 @@
1
1
  import path from "node:path";
2
- import { stat, unlink } from "node:fs/promises";
3
- import { createAgentSession, SessionManager, defineTool } from "@earendil-works/pi-coding-agent";
2
+ import { readFile, stat, unlink } from "node:fs/promises";
3
+ import { createAgentSession, DefaultResourceLoader, SessionManager, defineTool } from "@earendil-works/pi-coding-agent";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { createPiRuntime, hasProviderAuth } from "./pi-runtime.js";
6
- import { arisaInstallDir, buildAgentRuntimeContext } from "./runtime-context.js";
6
+ import { appendArisaAgentsFile, arisaAgentsFile, arisaInstallDir, buildAgentRuntimeContext } from "./runtime-context.js";
7
7
  import { withTimeout } from "./prompt-timeout.js";
8
8
  import { buildPiToolPolicy, getCoreCodingTools } from "./core-tools.js";
9
9
  import { createSystemShellTool } from "./system-shell-tool.js";
@@ -88,6 +88,17 @@ async function assertDirectory(dir, label) {
88
88
  }
89
89
  }
90
90
 
91
+ async function createArisaResourceLoader({ cwd, agentDir }) {
92
+ const arisaAgentsContent = await readFile(arisaAgentsFile, "utf8");
93
+ const resourceLoader = new DefaultResourceLoader({
94
+ cwd,
95
+ agentDir,
96
+ agentsFilesOverride: (current) => appendArisaAgentsFile(current, arisaAgentsContent)
97
+ });
98
+ await resourceLoader.reload();
99
+ return resourceLoader;
100
+ }
101
+
91
102
  export class AgentManager {
92
103
  constructor({ config, artifactStore, toolRegistry, taskStore, logger }) {
93
104
  this.config = config;
@@ -188,9 +199,14 @@ export class AgentManager {
188
199
  ...this.createTools(telegram, chatId, policy),
189
200
  createSystemShellTool({ workspaceDir: policy.workspaceDir, shell: policy.shell })
190
201
  ];
202
+ const resourceLoader = await createArisaResourceLoader({
203
+ cwd: policy.workspaceDir,
204
+ agentDir: arisaHomeDir
205
+ });
191
206
  const { session } = await createAgentSession({
192
207
  cwd: policy.workspaceDir,
193
208
  agentDir: arisaHomeDir,
209
+ resourceLoader,
194
210
  authStorage,
195
211
  modelRegistry,
196
212
  model,
@@ -1,7 +1,19 @@
1
+ import path from "node:path";
1
2
  import { fileURLToPath } from "node:url";
2
3
  import { arisaHomeDir, arisaPackageDir, chatsDir, stateDir, toolStateDir, toolsDir } from "../../runtime/paths.js";
3
4
 
4
5
  export const arisaInstallDir = fileURLToPath(new URL("../../..", import.meta.url));
6
+ export const arisaAgentsFile = path.join(arisaPackageDir, "AGENTS.md");
7
+
8
+ export function appendArisaAgentsFile(current, content, agentsFilePath = arisaAgentsFile) {
9
+ return {
10
+ ...current,
11
+ agentsFiles: [
12
+ ...(current.agentsFiles || []).filter((file) => path.resolve(file.path) !== path.resolve(agentsFilePath)),
13
+ { path: agentsFilePath, content }
14
+ ]
15
+ };
16
+ }
5
17
 
6
18
  function formatCoreTools(coreTools = []) {
7
19
  const enabled = coreTools
@@ -20,6 +32,7 @@ export function buildAgentRuntimeContext({ workspaceDir = arisaHomeDir, coreTool
20
32
  `toolStateDir: ${toolStateDir}`,
21
33
  `chatsDir: ${chatsDir}`,
22
34
  `stateDir: ${stateDir}`,
35
+ `arisaAgentsFile: ${arisaAgentsFile}`,
23
36
  `enabledCoreTools: ${formatCoreTools(coreTools)}`,
24
37
  "Guidance: create or edit user tools under userToolsDir. Treat arisaPackageDir/arisaInstallDir as Arisa core and modify it only when the user explicitly asks for core changes."
25
38
  ].join("\n");
@@ -7,6 +7,11 @@ function id() {
7
7
  return crypto.randomUUID();
8
8
  }
9
9
 
10
+ function writeArtifactFile(filePath, content) {
11
+ if (typeof content === "string") return writeFile(filePath, content, "utf8");
12
+ return writeFile(filePath, content);
13
+ }
14
+
10
15
  class ChatArtifactStore {
11
16
  constructor(chatId) {
12
17
  this.chatId = String(chatId);
@@ -81,7 +86,7 @@ class ChatArtifactStore {
81
86
  const dir = path.join(this.rootDir, artifactId);
82
87
  await mkdir(dir, { recursive: true });
83
88
  const destPath = path.join(dir, fileName);
84
- await writeFile(destPath, content);
89
+ await writeArtifactFile(destPath, content);
85
90
  const artifact = {
86
91
  id: artifactId,
87
92
  chatId: this.chatId,
@@ -4,6 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
6
  import { buildPiToolPolicy } from "../src/core/agent/core-tools.js";
7
+ import { appendArisaAgentsFile, arisaAgentsFile } from "../src/core/agent/runtime-context.js";
7
8
  import { createSystemShellTool } from "../src/core/agent/system-shell-tool.js";
8
9
  import { applyRuntimeOverrides } from "../src/runtime/create-app.js";
9
10
  import { arisaHomeDir } from "../src/runtime/paths.js";
@@ -89,3 +90,22 @@ test("system_shell runs commands from the configured workspace", async () => {
89
90
  assert.equal(result.details.exitCode, 0);
90
91
  assert.equal(result.details.stdout, realWorkspaceDir);
91
92
  });
93
+
94
+ test("adds Arisa AGENTS.md to Pi context files without duplicating it", () => {
95
+ const current = {
96
+ agentsFiles: [
97
+ { path: "/workspace/AGENTS.md", content: "# Workspace" },
98
+ { path: arisaAgentsFile, content: "old" }
99
+ ],
100
+ diagnostics: []
101
+ };
102
+
103
+ const next = appendArisaAgentsFile(current, "# Arisa AGENTS");
104
+
105
+ assert.deepEqual(next.diagnostics, []);
106
+ assert.deepEqual(
107
+ next.agentsFiles.map((file) => file.path),
108
+ ["/workspace/AGENTS.md", arisaAgentsFile]
109
+ );
110
+ assert.equal(next.agentsFiles.at(-1).content, "# Arisa AGENTS");
111
+ });
@@ -83,6 +83,20 @@ test("creates generated file artifacts", async () => {
83
83
  assert.equal(artifact.mimeType, "text/markdown");
84
84
  });
85
85
 
86
+ test("writes generated text file artifacts as UTF-8", async () => {
87
+ await resetHome();
88
+ const content = "# Español\nÑandú\n";
89
+ const artifact = await new ArtifactStore().forChat("chat-1").createGeneratedFile({
90
+ fileName: "reply.txt",
91
+ content,
92
+ kind: "document",
93
+ mimeType: "text/plain",
94
+ source: { type: "assistant" }
95
+ });
96
+
97
+ assert.deepEqual(await readFile(artifact.path), Buffer.from(content, "utf8"));
98
+ });
99
+
86
100
  test("keeps artifact indexes isolated by chat", async () => {
87
101
  await resetHome();
88
102
  const store = new ArtifactStore();