arisa 3.0.8 → 3.0.10
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 +22 -11
- package/README.md +19 -6
- package/package.json +1 -1
- package/src/core/agent/agent-manager.js +7 -11
- package/src/core/agent/runtime-context.js +3 -4
- package/src/index.js +23 -0
- package/src/runtime/paths.js +0 -8
- package/src/runtime/pi-package-manager.js +49 -0
- package/src/transport/telegram/bot.js +33 -4
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
91
|
-
|
|
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
|
|
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,13 +99,26 @@ arisa
|
|
|
99
99
|
Command modes:
|
|
100
100
|
|
|
101
101
|
```bash
|
|
102
|
-
arisa
|
|
103
|
-
arisa start
|
|
104
|
-
arisa stop
|
|
105
|
-
arisa status
|
|
106
|
-
arisa flush
|
|
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
|
|
|
111
|
+
## Experimental features
|
|
112
|
+
|
|
113
|
+
### Pi Agent packages
|
|
114
|
+
|
|
115
|
+
Arisa can install **Pi Agent packages** from the public registry into your user runtime (`~/.arisa/`), using the same package manager as Pi Agent. Browse and discover packages at [pi.dev/packages](https://pi.dev/packages).
|
|
116
|
+
|
|
117
|
+
- `arisa install <source>` installs a package (by registry name or other source supported by Pi).
|
|
118
|
+
- `arisa remove <source>` removes a previously installed package.
|
|
119
|
+
|
|
120
|
+
Treat this as **experimental**: the registry, package formats, and install behavior follow Pi Agent and may change. Not every listed package is tailored to Arisa’s Telegram transport and artifact-based tools; prefer packages you understand and verify after install.
|
|
121
|
+
|
|
109
122
|
## Bootstrap flow
|
|
110
123
|
|
|
111
124
|
On first run, Arisa will:
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
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 {
|
|
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.
|
|
72
|
+
sessionManager: SessionManager.inMemory()
|
|
77
73
|
});
|
|
78
74
|
|
|
79
75
|
const instructions = await loadProjectInstructions();
|
|
80
|
-
const runtimeContext = buildAgentRuntimeContext(
|
|
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,
|
|
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(
|
|
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
|
|
package/src/runtime/paths.js
CHANGED
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Bot, InputFile } from "grammy";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { authorizeChat } from "./auth.js";
|
|
3
4
|
import { captureIncomingArtifact } from "./media.js";
|
|
4
5
|
import { renderTelegramHtml, splitTelegramText } from "./text-format.js";
|
|
@@ -146,10 +147,28 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
146
147
|
return buildPrompt({ ctx, artifact, transcript });
|
|
147
148
|
}
|
|
148
149
|
|
|
149
|
-
async function sendTextReply(
|
|
150
|
+
async function sendTextReply({ sendText, sendDocument, chatId, text }) {
|
|
151
|
+
const attachmentThreshold = 12000;
|
|
152
|
+
|
|
153
|
+
if (text.length > attachmentThreshold) {
|
|
154
|
+
logger?.log("telegram", `sending long reply as markdown attachment for chat ${chatId}`);
|
|
155
|
+
const artifact = await artifactStore.createGeneratedFile({
|
|
156
|
+
fileName: `reply-${Date.now()}.md`,
|
|
157
|
+
content: text,
|
|
158
|
+
kind: "document",
|
|
159
|
+
mimeType: "text/markdown",
|
|
160
|
+
source: { type: "assistant", chatId },
|
|
161
|
+
metadata: { delivery: "telegram-document" }
|
|
162
|
+
});
|
|
163
|
+
await sendDocument(new InputFile(artifact.path, path.basename(artifact.path)), {
|
|
164
|
+
caption: "Response attached as Markdown."
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
150
169
|
logger?.log("telegram", `sending text reply for chat ${chatId}`);
|
|
151
170
|
for (const chunk of splitTelegramText(text)) {
|
|
152
|
-
await
|
|
171
|
+
await sendText(renderTelegramHtml(chunk), { parse_mode: "HTML" });
|
|
153
172
|
}
|
|
154
173
|
}
|
|
155
174
|
|
|
@@ -167,7 +186,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
167
186
|
const { session } = await agentManager.getSessionContext(ctx.chat.id, telegram);
|
|
168
187
|
const text = await collectText(session, prompt);
|
|
169
188
|
if (text) {
|
|
170
|
-
await sendTextReply(
|
|
189
|
+
await sendTextReply({
|
|
190
|
+
sendText: (message, extra) => ctx.reply(message, extra),
|
|
191
|
+
sendDocument: (file, extra) => ctx.replyWithDocument(file, extra),
|
|
192
|
+
chatId: ctx.chat.id,
|
|
193
|
+
text
|
|
194
|
+
});
|
|
171
195
|
}
|
|
172
196
|
});
|
|
173
197
|
}
|
|
@@ -260,7 +284,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, a
|
|
|
260
284
|
].filter(Boolean).join("\n");
|
|
261
285
|
const text = await collectText(session, welcomePrompt);
|
|
262
286
|
if (text) {
|
|
263
|
-
await sendTextReply(
|
|
287
|
+
await sendTextReply({
|
|
288
|
+
sendText: (message, extra) => bot.api.sendMessage(chatId, message, extra),
|
|
289
|
+
sendDocument: (file, extra) => bot.api.sendDocument(chatId, file, extra),
|
|
290
|
+
chatId,
|
|
291
|
+
text
|
|
292
|
+
});
|
|
264
293
|
}
|
|
265
294
|
} catch (error) {
|
|
266
295
|
logger?.log("telegram", `startup message failed for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`);
|