arisa 3.0.8 → 3.0.9

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
@@ -76,19 +76,30 @@ If `run_tool` returns `missingConfig`, the agent should:
76
76
  Do not assume a rigid question/answer protocol. Continue the conversation naturally and infer the config value from the user reply when possible.
77
77
 
78
78
  ## Tool creation
79
- If the user asks for a capability that is not currently available, first check whether an existing registered tool can satisfy the task.
80
- If no existing tool can do it, the default attitude should be to propose creating a new CLI tool following the project conventions.
81
- All newly created tools must document their help text, usage instructions, manifests, and user-facing operational strings in English.
82
- Do not stop at "I cannot do that" when the task is realistically implementable through a new tool.
83
- Prefer responses like:
84
- - identify that no current tool satisfies the request
85
- - state that the missing capability can be added
86
- - propose or start creating the tool needed to fulfill the request
79
+ Reason in terms of capabilities, not tool names.
87
80
 
88
- For example, if the user asks for live weather and no weather tool exists, the correct attitude is to propose building a weather tool for the bot rather than only saying real-time access is unavailable.
81
+ When the user asks for something new:
82
+ 1. check whether an existing registered tool can already satisfy the task
83
+ 2. also check whether the task can be satisfied indirectly through an existing capability
84
+ 3. only propose creating a new tool when the needed capability is truly missing
89
85
 
90
- When creating or editing tools, use the shared path helpers and the runtime paths provided in the prompt instead of assuming fixed locations.
91
- Consult the local skill for that workflow when building new tools.
86
+ Do not stop at "I cannot do that" when the task is realistically implementable through the tool architecture.
87
+ The default attitude is:
88
+ - identify that no current tool satisfies the request
89
+ - state that the missing capability can be added
90
+ - propose or start creating the needed tool
91
+
92
+ When creating or editing tools:
93
+ - use the shared path helpers and the runtime paths provided in the prompt instead of assuming fixed locations
94
+ - consult the local skill for that workflow when building new tools
95
+ - keep all help text, usage instructions, manifests, and user-facing operational strings in English
96
+ - follow the One Thing Rule: each function or method should do one thing well; if it mixes low-level operations with high-level policy, split it into smaller focused units
97
+
98
+ ## Dependency installation
99
+ Arisa installs tool dependencies itself.
100
+ - Prefer `pnpm install`.
101
+ - Fall back to `npm install`.
102
+ - Do not ask the user to do it manually.
92
103
 
93
104
  ## Safety
94
105
  - Do not install or run arbitrary tools outside registered tool manifests in V1.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Arisa is a personal Telegram assistant powered by Pi Agent.
4
4
 
5
5
  ## Origin
6
6
 
7
- The initial inspiration was [OpenClaw](https://github.com/openclaw/openclaw). OpenClaw has interesting ideas but carries too much weight: when it generates tools they end up disorganized, and the overall framework feels overloaded for personal use.
7
+ The initial inspiration was [OpenClaw](https://github.com/openclaw/openclaw). OpenClaw has interesting ideas but carries a lot of weight (about **185 MB**) compared to Arisa (**76.7 kB**): when it generates tools they end up disorganized, and the overall framework feels overloaded.
8
8
 
9
9
  The real heart of OpenClaw is Pi Agent: a [minimal terminal coding harness](https://www.youtube.com/watch?v=Dli5slNaJu0) that lets an AI agent reason and act with very little infrastructure. That part is genuinely good.
10
10
 
@@ -99,11 +99,13 @@ arisa
99
99
  Command modes:
100
100
 
101
101
  ```bash
102
- arisa # foreground, blocking
103
- arisa start # start in background
104
- arisa stop # stop background service
105
- arisa status # show background service status
106
- arisa flush # remove ~/.arisa
102
+ arisa # foreground, blocking
103
+ arisa start # start in background
104
+ arisa stop # stop background service
105
+ arisa status # show background service status
106
+ arisa flush # remove ~/.arisa
107
+ arisa install <source> # install a Pi package into Arisa's runtime
108
+ arisa remove <source> # remove a Pi package from Arisa's runtime
107
109
  ```
108
110
 
109
111
  ## Bootstrap flow
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "3.0.8",
3
+ "version": "3.0.9",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,11 +1,11 @@
1
1
  import path from "node:path";
2
- import { mkdir, unlink } from "node:fs/promises";
2
+ import { unlink } from "node:fs/promises";
3
3
  import { createAgentSession, SessionManager, defineTool } from "@mariozechner/pi-coding-agent";
4
4
  import { Type } from "@sinclair/typebox";
5
5
  import { createPiRuntime, hasProviderAuth } from "./pi-runtime.js";
6
6
  import { loadProjectInstructions } from "./project-instructions.js";
7
- import { buildAgentRuntimeContext } from "./runtime-context.js";
8
- import { getChatDir, piAgentDir as agentDir } from "../../runtime/paths.js";
7
+ import { arisaInstallDir, buildAgentRuntimeContext } from "./runtime-context.js";
8
+ import { arisaHomeDir } from "../../runtime/paths.js";
9
9
 
10
10
  export class AgentManager {
11
11
  constructor({ config, artifactStore, toolRegistry, logger }) {
@@ -50,7 +50,6 @@ export class AgentManager {
50
50
  return this.sessions.get(chatId);
51
51
  }
52
52
 
53
- await mkdir(agentDir, { recursive: true });
54
53
  const { authStorage, modelRegistry } = createPiRuntime({
55
54
  provider: this.config.pi.provider,
56
55
  apiKey: this.config.pi.apiKey
@@ -61,23 +60,20 @@ export class AgentManager {
61
60
  throw new Error(`No auth found for ${this.config.pi.provider}. Re-run bootstrap and complete login for this provider before Telegram starts.`);
62
61
  }
63
62
 
64
- const cwd = getChatDir(chatId);
65
- await mkdir(cwd, { recursive: true });
66
-
67
63
  this.logger?.log("agent", `creating session for chat ${chatId}`);
68
64
  const customTools = this.createTools(telegram);
69
65
  const { session } = await createAgentSession({
70
- cwd,
71
- agentDir,
66
+ cwd: arisaInstallDir,
67
+ agentDir: arisaHomeDir,
72
68
  authStorage,
73
69
  modelRegistry,
74
70
  model,
75
71
  customTools,
76
- sessionManager: SessionManager.continueRecent(cwd)
72
+ sessionManager: SessionManager.inMemory()
77
73
  });
78
74
 
79
75
  const instructions = await loadProjectInstructions();
80
- const runtimeContext = buildAgentRuntimeContext(chatId);
76
+ const runtimeContext = buildAgentRuntimeContext();
81
77
  this.logger?.log("agent", `injecting project instructions for chat ${chatId}`);
82
78
  this.logger?.log("agent", `runtime context for chat ${chatId}:\n${runtimeContext}`);
83
79
  await session.prompt(`${instructions}\n\n${runtimeContext}\n\nAcknowledge with exactly: OK`);
@@ -1,17 +1,16 @@
1
1
  import { fileURLToPath } from "node:url";
2
- import { arisaHomeDir, artifactsDir, getChatDir, stateDir, toolsDir } from "../../runtime/paths.js";
2
+ import { arisaHomeDir, artifactsDir, stateDir, toolsDir } from "../../runtime/paths.js";
3
3
 
4
4
  export const arisaInstallDir = fileURLToPath(new URL("../../..", import.meta.url));
5
5
  export const bundledToolsDir = fileURLToPath(new URL("../../../tools", import.meta.url));
6
6
 
7
- export function buildAgentRuntimeContext(chatId) {
7
+ export function buildAgentRuntimeContext() {
8
8
  return [
9
9
  `arisaHomeDir: ${arisaHomeDir}`,
10
10
  `arisaInstallDir: ${arisaInstallDir}`,
11
11
  `bundledToolsDir: ${bundledToolsDir}`,
12
12
  `userToolsDir: ${toolsDir}`,
13
13
  `artifactsDir: ${artifactsDir}`,
14
- `stateDir: ${stateDir}`,
15
- `chatWorkspaceDir: ${getChatDir(chatId)}`
14
+ `stateDir: ${stateDir}`
16
15
  ].join("\n");
17
16
  }
package/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { createApp } from "./runtime/create-app.js";
5
5
  import { createLogger } from "./runtime/logger.js";
6
6
  import { getServiceStatus, registerServiceProcess, startService, stopService } from "./runtime/service-manager.js";
7
7
  import { flushArisaHome } from "./runtime/flush.js";
8
+ import { installPiPackage, removePiPackage } from "./runtime/pi-package-manager.js";
8
9
 
9
10
  const args = process.argv.slice(2);
10
11
  const command = args.find((arg) => !arg.startsWith("--")) || "run";
@@ -83,6 +84,28 @@ async function main() {
83
84
  return;
84
85
  }
85
86
 
87
+ if (command === "install") {
88
+ const source = args.filter((arg) => !arg.startsWith("--")).slice(1)[0];
89
+ if (!source) {
90
+ console.log("Usage: arisa install <pi-package-source>");
91
+ return;
92
+ }
93
+ const result = await installPiPackage(source);
94
+ process.exitCode = result.ok ? 0 : result.code;
95
+ return;
96
+ }
97
+
98
+ if (command === "remove") {
99
+ const source = args.filter((arg) => !arg.startsWith("--")).slice(1)[0];
100
+ if (!source) {
101
+ console.log("Usage: arisa remove <pi-package-source>");
102
+ return;
103
+ }
104
+ const result = await removePiPackage(source);
105
+ process.exitCode = result.ok ? 0 : result.code;
106
+ return;
107
+ }
108
+
86
109
  await runForeground();
87
110
  }
88
111
 
@@ -9,15 +9,9 @@ export const servicePidFile = path.join(stateDir, "arisa.pid");
9
9
  export const serviceLogFile = path.join(stateDir, "arisa.log");
10
10
  export const artifactsDir = path.join(arisaHomeDir, "artifacts");
11
11
  export const artifactsIndexFile = path.join(stateDir, "artifacts.json");
12
- export const piAgentDir = path.join(stateDir, "pi-agent");
13
- export const chatsDir = path.join(arisaHomeDir, "chats");
14
12
  export const toolsDir = path.join(arisaHomeDir, "tools");
15
13
  export const tmpDir = path.join(arisaHomeDir, "tmp");
16
14
 
17
- export function getChatDir(chatId) {
18
- return path.join(chatsDir, String(chatId));
19
- }
20
-
21
15
  export function getToolDir(toolName) {
22
16
  return path.join(toolsDir, toolName);
23
17
  }
@@ -41,8 +35,6 @@ export function getToolTmpDir(toolName) {
41
35
  export async function ensureArisaHome() {
42
36
  await mkdir(stateDir, { recursive: true });
43
37
  await mkdir(artifactsDir, { recursive: true });
44
- await mkdir(piAgentDir, { recursive: true });
45
- await mkdir(chatsDir, { recursive: true });
46
38
  await mkdir(toolsDir, { recursive: true });
47
39
  await mkdir(tmpDir, { recursive: true });
48
40
  }
@@ -0,0 +1,49 @@
1
+ import { DefaultPackageManager, SettingsManager } from "@mariozechner/pi-coding-agent";
2
+ import { arisaHomeDir } from "./paths.js";
3
+
4
+ function createPackageManager() {
5
+ const settingsManager = SettingsManager.create(arisaHomeDir, arisaHomeDir);
6
+ const packageManager = new DefaultPackageManager({
7
+ cwd: arisaHomeDir,
8
+ agentDir: arisaHomeDir,
9
+ settingsManager
10
+ });
11
+
12
+ packageManager.setProgressCallback((event) => {
13
+ if (event.type === "start") {
14
+ process.stdout.write(`${event.message}\n`);
15
+ }
16
+ });
17
+
18
+ return packageManager;
19
+ }
20
+
21
+ export async function installPiPackage(source) {
22
+ const packageManager = createPackageManager();
23
+
24
+ try {
25
+ await packageManager.installAndPersist(source, { local: false });
26
+ console.log(`Installed ${source}`);
27
+ return { ok: true, code: 0 };
28
+ } catch (error) {
29
+ console.error(error instanceof Error ? error.message : String(error));
30
+ return { ok: false, code: 1 };
31
+ }
32
+ }
33
+
34
+ export async function removePiPackage(source) {
35
+ const packageManager = createPackageManager();
36
+
37
+ try {
38
+ const removed = await packageManager.removeAndPersist(source, { local: false });
39
+ if (!removed) {
40
+ console.error(`No matching package found for ${source}`);
41
+ return { ok: false, code: 1 };
42
+ }
43
+ console.log(`Removed ${source}`);
44
+ return { ok: true, code: 0 };
45
+ } catch (error) {
46
+ console.error(error instanceof Error ? error.message : String(error));
47
+ return { ok: false, code: 1 };
48
+ }
49
+ }