arisa 5.1.24 → 5.1.60
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 +0 -2
- package/package.json +2 -3
- package/src/core/agent/agent-manager.js +91 -479
- package/src/core/agent/agent-session-lifecycle.js +181 -0
- package/src/core/agent/pi-capability-tools.js +183 -0
- package/src/core/agent/pi-runtime.js +0 -8
- package/src/core/agent/system-shell-tool.js +13 -2
- package/src/core/artifacts/artifact-store.js +90 -35
- package/src/core/capabilities/capability-service.js +340 -0
- package/src/core/config/config-defaults.js +2 -2
- package/src/core/conversation/session-seed-store.js +85 -0
- package/src/core/tasks/task-routing.js +7 -0
- package/src/core/tasks/task-runner.js +53 -0
- package/src/core/tasks/task-store.js +316 -92
- package/src/core/tools/ipc-client.js +0 -2
- package/src/core/tools/tool-output-materializer.js +41 -0
- package/src/core/tools/tool-registry.js +96 -23
- package/src/official-tools.lock.json +151 -96
- package/src/runtime/arisa-capabilities.js +51 -242
- package/src/runtime/create-app.js +10 -1
- package/src/runtime/create-headless-app.js +5 -2
- package/src/runtime/doctor.js +1 -4
- package/src/runtime/headless-tool-executor.js +2 -32
- package/src/runtime/paths.js +7 -1
- package/src/runtime/restart-receipt.js +90 -0
- package/src/transport/telegram/bot.js +373 -1273
- package/src/transport/telegram/chat-queue.js +198 -0
- package/src/transport/telegram/media.js +2 -2
- package/src/transport/telegram/model-callback.js +211 -0
- package/src/transport/telegram/model-controls.js +164 -0
- package/src/transport/telegram/prompt-builders.js +381 -0
- package/src/transport/telegram/task-dispatcher.js +131 -0
- package/src/transport/telegram/telegram-auth-controller.js +180 -0
- package/src/transport/telegram/telegram-session-bridge.js +170 -0
- package/src/transport/telegram/telegram-tools-command.js +28 -0
- package/src/transport/telegram/telegram-workspace-controller.js +66 -0
- package/src/transport/telegram/update-command.js +1 -1
- package/src/transport/telegram/workspace-group.js +83 -0
- package/test/agent-session-lifecycle.test.js +58 -0
- package/test/agent-tool-policy.test.js +7 -1
- package/test/artifact-store.test.js +38 -2
- package/test/capabilities-security.test.js +58 -0
- package/test/context-and-task-bounds.test.js +85 -8
- package/test/device-code-message.test.js +9 -0
- package/test/doctor.test.js +2 -4
- package/test/media-caption.test.js +1 -1
- package/test/model-selection.test.js +47 -1
- package/test/official-tool-dependencies.test.js +2 -0
- package/test/paths.test.js +4 -4
- package/test/pi-capability-tools.test.js +65 -0
- package/test/restart-receipt.test.js +39 -0
- package/test/session-start-operational-notes.test.js +47 -0
- package/test/task-idempotency.test.js +40 -0
- package/test/task-routing.test.js +62 -0
- package/test/task-store.test.js +178 -6
- package/test/telegram-prompt-builders.test.js +33 -0
- package/test/telegram-task-dispatcher.test.js +178 -0
- package/test/telegram-text-artifact.test.js +13 -2
- package/test/telegram-tools-command.test.js +47 -0
- package/test/telegram-workspace-group.test.js +76 -0
- package/test/tool-registry-run.test.js +62 -0
- package/test/topic-initialization.test.js +66 -0
- package/src/core/conversation/conversation-history-store.js +0 -142
|
@@ -1,251 +1,60 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
throw new Error(`${fieldName} is required`);
|
|
28
|
-
}
|
|
29
|
-
return value;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function normalizeArgs(args) {
|
|
33
|
-
if (args == null) return {};
|
|
34
|
-
if (typeof args !== "object" || Array.isArray(args)) {
|
|
35
|
-
throw new Error("args must be an object");
|
|
36
|
-
}
|
|
37
|
-
return args;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function normalizeLimit(limit) {
|
|
41
|
-
const value = Number(limit);
|
|
42
|
-
if (!Number.isInteger(value) || value <= 0) return 20;
|
|
43
|
-
return Math.min(value, 100);
|
|
44
|
-
}
|
|
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
|
-
}
|
|
1
|
+
import { createCapabilityService } from "../core/capabilities/capability-service.js";
|
|
2
|
+
|
|
3
|
+
const ipcMethods = new Set([
|
|
4
|
+
"tools.list",
|
|
5
|
+
"tools.help",
|
|
6
|
+
"tools.skills",
|
|
7
|
+
"tools.setConfig",
|
|
8
|
+
"tools.setResourceNote",
|
|
9
|
+
"tools.getResourceNote",
|
|
10
|
+
"tools.run",
|
|
11
|
+
"tools.installOfficial",
|
|
12
|
+
"artifacts.createText",
|
|
13
|
+
"artifacts.listRecent",
|
|
14
|
+
"artifacts.get",
|
|
15
|
+
"artifacts.deliver",
|
|
16
|
+
"tasks.add",
|
|
17
|
+
"tasks.list",
|
|
18
|
+
"tasks.cancel",
|
|
19
|
+
"tasks.cancelAll",
|
|
20
|
+
"agent.enqueueEvent",
|
|
21
|
+
"paths.getChatToolStateDir",
|
|
22
|
+
"paths.getToolStateDir",
|
|
23
|
+
"paths.getChatToolTmpDir",
|
|
24
|
+
"paths.getToolTmpDir",
|
|
25
|
+
"paths.getChatArtifactsDir"
|
|
26
|
+
]);
|
|
52
27
|
|
|
53
28
|
export function createArisaCapabilities({
|
|
29
|
+
capabilityService,
|
|
54
30
|
artifactStore,
|
|
55
31
|
taskStore,
|
|
56
32
|
toolRegistry,
|
|
57
33
|
agentManager,
|
|
58
|
-
resourceNotes
|
|
59
|
-
installOfficialTool
|
|
34
|
+
resourceNotes,
|
|
35
|
+
installOfficialTool,
|
|
36
|
+
logger
|
|
60
37
|
} = {}) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
await toolRegistry.load();
|
|
81
|
-
return toolRegistry.setConfig(
|
|
82
|
-
requireString(params.name, "name"),
|
|
83
|
-
requireString(params.field, "field"),
|
|
84
|
-
requireString(params.value, "value"),
|
|
85
|
-
requireChatId(chatId, method)
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (method === "tools.setResourceNote") {
|
|
90
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
91
|
-
return resourceNotes.set(
|
|
92
|
-
scopedChatId,
|
|
93
|
-
scopedToolName,
|
|
94
|
-
requireString(params.resourceId, "resourceId"),
|
|
95
|
-
String(params.note ?? "")
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (method === "tools.getResourceNote") {
|
|
100
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
101
|
-
return {
|
|
102
|
-
toolName: scopedToolName,
|
|
103
|
-
resourceId: requireString(params.resourceId, "resourceId"),
|
|
104
|
-
note: await resourceNotes.get(scopedChatId, scopedToolName, params.resourceId)
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (method === "tools.run") {
|
|
109
|
-
if (!agentManager?.runTool) {
|
|
110
|
-
throw new Error("tools.run requires agentManager");
|
|
111
|
-
}
|
|
112
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
113
|
-
const targetToolName = requireString(params.name, "name");
|
|
114
|
-
const chatArtifactStore = artifactStore.forChat(scopedChatId);
|
|
115
|
-
const artifact = params.artifactId
|
|
116
|
-
? await chatArtifactStore.get(requireString(params.artifactId, "artifactId"))
|
|
117
|
-
: null;
|
|
118
|
-
if (params.artifactId && !artifact) {
|
|
119
|
-
throw new Error(`Artifact not found: ${params.artifactId}`);
|
|
38
|
+
const service = capabilityService || createCapabilityService({
|
|
39
|
+
artifactStore,
|
|
40
|
+
taskStore,
|
|
41
|
+
toolRegistry,
|
|
42
|
+
toolExecutor: agentManager,
|
|
43
|
+
resourceNotes,
|
|
44
|
+
installOfficialTool,
|
|
45
|
+
logger
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
dispatch: ({ method, toolName, chatId = null, params = {} } = {}) => service.execute({
|
|
50
|
+
method,
|
|
51
|
+
actorToolName: toolName,
|
|
52
|
+
chatId,
|
|
53
|
+
params,
|
|
54
|
+
context: {
|
|
55
|
+
allowedMethods: ipcMethods,
|
|
56
|
+
unknownMethodLabel: "IPC"
|
|
120
57
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
name: targetToolName,
|
|
124
|
-
request: {
|
|
125
|
-
artifact,
|
|
126
|
-
text: params.text,
|
|
127
|
-
resourceId: params.resourceId,
|
|
128
|
-
args: normalizeArgs(params.args)
|
|
129
|
-
},
|
|
130
|
-
chatId: scopedChatId
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (method === "tools.installOfficial") {
|
|
135
|
-
const name = requireString(params.name, "name");
|
|
136
|
-
if (params.confirmName !== name) {
|
|
137
|
-
throw new Error("tools.installOfficial requires confirmName equal to name");
|
|
138
|
-
}
|
|
139
|
-
const installed = await installOfficialTool(name);
|
|
140
|
-
await toolRegistry.load();
|
|
141
|
-
return installed;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (method === "artifacts.createText") {
|
|
145
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
146
|
-
return artifactStore.forChat(scopedChatId).createText({
|
|
147
|
-
text: requireString(params.text, "text"),
|
|
148
|
-
mimeType: params.mimeType || "text/plain",
|
|
149
|
-
source: { type: "tool", toolName: scopedToolName, chatId: scopedChatId },
|
|
150
|
-
metadata: params.metadata || {}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (method === "artifacts.listRecent") {
|
|
155
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
156
|
-
return artifactStore.forChat(scopedChatId).listRecent(normalizeLimit(params.limit));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (method === "artifacts.get") {
|
|
160
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
161
|
-
return artifactStore.forChat(scopedChatId).get(requireString(params.artifactId, "artifactId"));
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (method === "artifacts.deliver") {
|
|
165
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
166
|
-
const artifactId = requireString(params.artifactId, "artifactId");
|
|
167
|
-
const artifact = await artifactStore.forChat(scopedChatId).get(artifactId);
|
|
168
|
-
if (!artifact?.path) throw new Error(`Artifact not found or has no file: ${artifactId}`);
|
|
169
|
-
if (!agentManager?.deliverArtifact) throw new Error("artifact delivery is unavailable");
|
|
170
|
-
return agentManager.deliverArtifact({
|
|
171
|
-
chatId: scopedChatId,
|
|
172
|
-
artifact,
|
|
173
|
-
caption: params.caption,
|
|
174
|
-
method: params.method
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
if (method === "tasks.add") {
|
|
179
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
180
|
-
return taskStore.add(params.task || {}, {
|
|
181
|
-
payload: { chatId: scopedChatId },
|
|
182
|
-
source: { type: "tool", toolName: scopedToolName, chatId: scopedChatId }
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
if (method === "tasks.list") {
|
|
187
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
188
|
-
return taskStore.list({
|
|
189
|
-
chatId: scopedChatId,
|
|
190
|
-
status: params.status || undefined,
|
|
191
|
-
kind: params.kind || undefined
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (method === "tasks.cancel") {
|
|
196
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
197
|
-
const taskId = requireString(params.taskId, "taskId");
|
|
198
|
-
const task = await taskStore.get(taskId);
|
|
199
|
-
if (!task) return null;
|
|
200
|
-
if (String(task.payload?.chatId) !== String(scopedChatId)) {
|
|
201
|
-
throw new Error("task does not belong to chatId");
|
|
202
|
-
}
|
|
203
|
-
return taskStore.cancel(taskId);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (method === "tasks.cancelAll") {
|
|
207
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
208
|
-
return taskStore.cancelAll({ chatId: scopedChatId });
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (method === "agent.enqueueEvent") {
|
|
212
|
-
const scopedChatId = requireChatId(chatId, method);
|
|
213
|
-
const resourceId = String(params.resourceId || "").trim();
|
|
214
|
-
return taskStore.add({
|
|
215
|
-
kind: "agent_event",
|
|
216
|
-
payload: {
|
|
217
|
-
prompt: requireString(params.prompt, "prompt"),
|
|
218
|
-
resourceId,
|
|
219
|
-
acknowledgement: normalizeAcknowledgement(params.acknowledgement)
|
|
220
|
-
}
|
|
221
|
-
}, {
|
|
222
|
-
payload: { chatId: scopedChatId },
|
|
223
|
-
source: { type: "tool", toolName: scopedToolName, chatId: scopedChatId, resourceId }
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (method === "paths.getChatToolStateDir") {
|
|
228
|
-
return getChatToolStateDir(requireChatId(chatId, method), scopedToolName);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (method === "paths.getToolStateDir") {
|
|
232
|
-
return getToolStateDir(scopedToolName);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (method === "paths.getChatToolTmpDir") {
|
|
236
|
-
return getChatToolTmpDir(requireChatId(chatId, method), scopedToolName);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (method === "paths.getToolTmpDir") {
|
|
240
|
-
return getToolTmpDir(scopedToolName);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (method === "paths.getChatArtifactsDir") {
|
|
244
|
-
return getChatArtifactsDir(requireChatId(chatId, method));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
throw new Error(`unknown IPC method: ${method}`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return { dispatch };
|
|
58
|
+
})
|
|
59
|
+
};
|
|
251
60
|
}
|
|
@@ -3,6 +3,7 @@ import { ArtifactStore } from "../core/artifacts/artifact-store.js";
|
|
|
3
3
|
import { ToolRegistry } from "../core/tools/tool-registry.js";
|
|
4
4
|
import { TaskStore } from "../core/tasks/task-store.js";
|
|
5
5
|
import { AgentManager } from "../core/agent/agent-manager.js";
|
|
6
|
+
import { createCapabilityService } from "../core/capabilities/capability-service.js";
|
|
6
7
|
import { getErrorMessage, getPiAuthIssue } from "../core/agent/auth-flow.js";
|
|
7
8
|
import { createTelegramBot } from "../transport/telegram/bot.js";
|
|
8
9
|
import { createToolProcessSupervisor } from "./tool-process-supervisor.js";
|
|
@@ -120,7 +121,15 @@ export async function createApp({ logger, runtimeOverrides, requestRestart } = {
|
|
|
120
121
|
logger?.log("app", `loaded ${toolRegistry.list().length} tools`);
|
|
121
122
|
|
|
122
123
|
const agentManager = new AgentManager({ config, artifactStore, toolRegistry, taskStore, logger });
|
|
123
|
-
const
|
|
124
|
+
const capabilityService = createCapabilityService({
|
|
125
|
+
artifactStore,
|
|
126
|
+
taskStore,
|
|
127
|
+
toolRegistry,
|
|
128
|
+
toolExecutor: agentManager,
|
|
129
|
+
logger
|
|
130
|
+
});
|
|
131
|
+
agentManager.setCapabilityService(capabilityService);
|
|
132
|
+
const arisaCapabilities = createArisaCapabilities({ capabilityService });
|
|
124
133
|
const ipcServer = createIpcServer({ capabilities: arisaCapabilities, logger });
|
|
125
134
|
const bot = await createTelegramBot({
|
|
126
135
|
config,
|
|
@@ -3,6 +3,7 @@ import { loadConfig } from "../core/config/config-store.js";
|
|
|
3
3
|
import { TaskStore } from "../core/tasks/task-store.js";
|
|
4
4
|
import { ToolRegistry } from "../core/tools/tool-registry.js";
|
|
5
5
|
import { createDaemonRuntime } from "../core/tools/daemon-runtime.js";
|
|
6
|
+
import { createCapabilityService } from "../core/capabilities/capability-service.js";
|
|
6
7
|
import { createArisaCapabilities } from "./arisa-capabilities.js";
|
|
7
8
|
import { createHeadlessToolExecutor } from "./headless-tool-executor.js";
|
|
8
9
|
import { createIpcServer } from "./ipc/ipc-server.js";
|
|
@@ -26,12 +27,14 @@ export async function createHeadlessApp({
|
|
|
26
27
|
await toolRegistry.load();
|
|
27
28
|
const toolProcessSupervisor = supervisorFactory({ logger, policy: config.daemons, toolRegistry });
|
|
28
29
|
const toolExecutor = createHeadlessToolExecutor({ artifactStore, taskStore, toolRegistry });
|
|
29
|
-
const
|
|
30
|
+
const capabilityService = createCapabilityService({
|
|
30
31
|
artifactStore,
|
|
31
32
|
taskStore,
|
|
32
33
|
toolRegistry,
|
|
33
|
-
|
|
34
|
+
toolExecutor,
|
|
35
|
+
logger
|
|
34
36
|
});
|
|
37
|
+
const capabilities = capabilitiesFactory({ capabilityService, agentManager: toolExecutor });
|
|
35
38
|
const ipcServer = ipcServerFactory({ capabilities, logger });
|
|
36
39
|
const autoStartedDaemons = [];
|
|
37
40
|
|
package/src/runtime/doctor.js
CHANGED
|
@@ -193,7 +193,6 @@ export async function inspectSystemResources({ diskPath = arisaHomeDir } = {}) {
|
|
|
193
193
|
|
|
194
194
|
function assertDoctorPolicy(policy) {
|
|
195
195
|
const positiveValues = [
|
|
196
|
-
"contextInspectionTimeoutMs",
|
|
197
196
|
"contextWarningPercent",
|
|
198
197
|
"contextCriticalPercent",
|
|
199
198
|
"contextInefficientMinTokens",
|
|
@@ -318,9 +317,7 @@ export async function runDoctor({
|
|
|
318
317
|
inspectToolDependencies = null
|
|
319
318
|
}) {
|
|
320
319
|
assertDoctorPolicy(doctorPolicy);
|
|
321
|
-
const runtime = await agentManager.getRuntimeDiagnostic(
|
|
322
|
-
contextInspectionTimeoutMs: doctorPolicy.contextInspectionTimeoutMs
|
|
323
|
-
});
|
|
320
|
+
const runtime = await agentManager.getRuntimeDiagnostic();
|
|
324
321
|
const report = {
|
|
325
322
|
runtime,
|
|
326
323
|
contexts: runtime.contexts.map((context) => evaluateContext(context, doctorPolicy)),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { unlink } from "node:fs/promises";
|
|
1
|
+
import { materializeToolOutput } from "../core/tools/tool-output-materializer.js";
|
|
3
2
|
|
|
4
3
|
export function createHeadlessToolExecutor({ artifactStore, taskStore, toolRegistry } = {}) {
|
|
5
4
|
if (!artifactStore || !taskStore || !toolRegistry) {
|
|
@@ -10,36 +9,7 @@ export function createHeadlessToolExecutor({ artifactStore, taskStore, toolRegis
|
|
|
10
9
|
async runTool({ name, request, chatId }) {
|
|
11
10
|
await toolRegistry.load();
|
|
12
11
|
const result = await toolRegistry.run({ name, request, chatId });
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (result.output?.text) {
|
|
16
|
-
const artifact = await chatArtifacts.createText({
|
|
17
|
-
text: result.output.text,
|
|
18
|
-
source: { type: "tool", toolName: name },
|
|
19
|
-
metadata: { tool: name }
|
|
20
|
-
});
|
|
21
|
-
result.output.artifactId = artifact.id;
|
|
22
|
-
}
|
|
23
|
-
if (result.output?.filePath) {
|
|
24
|
-
const generated = await chatArtifacts.createFromFile({
|
|
25
|
-
originalPath: result.output.filePath,
|
|
26
|
-
fileName: result.output.fileName || path.basename(result.output.filePath),
|
|
27
|
-
kind: result.output.kind || "file",
|
|
28
|
-
mimeType: result.output.mimeType || "application/octet-stream",
|
|
29
|
-
source: { type: "tool", toolName: name },
|
|
30
|
-
metadata: { tool: name, delivery: result.output.delivery }
|
|
31
|
-
});
|
|
32
|
-
result.output.artifactId = generated.id;
|
|
33
|
-
await unlink(result.output.filePath).catch(() => {});
|
|
34
|
-
}
|
|
35
|
-
if (result.asyncTask || result.asyncTasks?.length) {
|
|
36
|
-
result.asyncTasks = await taskStore.addMany(result.asyncTasks || [result.asyncTask], {
|
|
37
|
-
payload: { chatId },
|
|
38
|
-
source: { type: "tool", toolName: name, chatId }
|
|
39
|
-
});
|
|
40
|
-
delete result.asyncTask;
|
|
41
|
-
}
|
|
42
|
-
return result;
|
|
12
|
+
return materializeToolOutput({ result, name, chatId, artifactStore, taskStore });
|
|
43
13
|
}
|
|
44
14
|
};
|
|
45
15
|
}
|
package/src/runtime/paths.js
CHANGED
|
@@ -13,6 +13,8 @@ export const configFile = path.join(stateDir, "config.json");
|
|
|
13
13
|
export const piAuthFile = path.join(stateDir, "pi-auth.json");
|
|
14
14
|
export const servicePidFile = path.join(stateDir, "arisa.pid");
|
|
15
15
|
export const serviceLogFile = path.join(stateDir, "arisa.log");
|
|
16
|
+
export const restartReceiptFile = path.join(stateDir, "restart-receipt.json");
|
|
17
|
+
export const sessionStartOperationalNotesFile = path.join(stateDir, "session-start-operational-notes.json");
|
|
16
18
|
export function createIpcSocketPath({ homeDir = arisaHomeDir, platform = process.platform } = {}) {
|
|
17
19
|
if (platform === "win32") {
|
|
18
20
|
const suffix = crypto.createHash("sha256").update(homeDir).digest("hex").slice(0, 16);
|
|
@@ -47,7 +49,11 @@ export function getChatArtifactsIndexFile(chatId) {
|
|
|
47
49
|
return path.join(getChatDir(chatId), "state", "artifacts.json");
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
export function
|
|
52
|
+
export function getChatSessionSeedFile(chatId) {
|
|
53
|
+
return path.join(getChatDir(chatId), "state", "session-seed.jsonl");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getChatLegacyConversationHistoryFile(chatId) {
|
|
51
57
|
return path.join(getChatDir(chatId), "state", "conversation.jsonl");
|
|
52
58
|
}
|
|
53
59
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { arisaPackageDir, restartReceiptFile } from "./paths.js";
|
|
7
|
+
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
|
|
10
|
+
function normalizeDestination({ transportChatId, threadId = null }) {
|
|
11
|
+
const chatId = Number(transportChatId);
|
|
12
|
+
if (!Number.isSafeInteger(chatId)) throw new Error("Restart receipt requires a valid Telegram chat id");
|
|
13
|
+
const topic = threadId == null ? null : Number(threadId);
|
|
14
|
+
if (topic != null && (!Number.isSafeInteger(topic) || topic <= 0)) {
|
|
15
|
+
throw new Error("Restart receipt requires a valid Telegram topic id");
|
|
16
|
+
}
|
|
17
|
+
return { transportChatId: chatId, threadId: topic };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runtimeIdentity() {
|
|
21
|
+
const packageJson = JSON.parse(await readFile(path.join(arisaPackageDir, "package.json"), "utf8"));
|
|
22
|
+
let commit = null;
|
|
23
|
+
try {
|
|
24
|
+
const result = await execFileAsync("git", ["rev-parse", "--short=12", "HEAD"], { cwd: arisaPackageDir });
|
|
25
|
+
commit = String(result.stdout || "").trim() || null;
|
|
26
|
+
} catch {}
|
|
27
|
+
return { version: String(packageJson.version || "unknown"), commit };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function prepareRestartReceipt(destination, { reason = "requested restart" } = {}, {
|
|
31
|
+
receiptFile = restartReceiptFile,
|
|
32
|
+
getIdentity = runtimeIdentity
|
|
33
|
+
} = {}) {
|
|
34
|
+
const target = normalizeDestination(destination);
|
|
35
|
+
const identity = await getIdentity();
|
|
36
|
+
const receipt = {
|
|
37
|
+
id: crypto.randomUUID(),
|
|
38
|
+
...target,
|
|
39
|
+
reason: String(reason || "requested restart").slice(0, 500),
|
|
40
|
+
expectedVersion: identity.version,
|
|
41
|
+
expectedCommit: identity.commit,
|
|
42
|
+
requestedAt: new Date().toISOString()
|
|
43
|
+
};
|
|
44
|
+
await mkdir(path.dirname(receiptFile), { recursive: true, mode: 0o700 });
|
|
45
|
+
const temporaryFile = `${receiptFile}.${process.pid}.tmp`;
|
|
46
|
+
await writeFile(temporaryFile, `${JSON.stringify(receipt, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
47
|
+
await rename(temporaryFile, receiptFile);
|
|
48
|
+
return receipt;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function cancelRestartReceipt(receiptId, { receiptFile = restartReceiptFile } = {}) {
|
|
52
|
+
try {
|
|
53
|
+
const receipt = JSON.parse(await readFile(receiptFile, "utf8"));
|
|
54
|
+
if (receipt.id !== receiptId) return false;
|
|
55
|
+
await rm(receiptFile, { force: true });
|
|
56
|
+
return true;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error?.code === "ENOENT") return false;
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function deliverRestartReceipt(sendMessage, {
|
|
64
|
+
receiptFile = restartReceiptFile,
|
|
65
|
+
getIdentity = runtimeIdentity
|
|
66
|
+
} = {}) {
|
|
67
|
+
let receipt;
|
|
68
|
+
try {
|
|
69
|
+
receipt = JSON.parse(await readFile(receiptFile, "utf8"));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error?.code === "ENOENT") return null;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const actual = await getIdentity();
|
|
76
|
+
const versionMatches = receipt.expectedVersion === actual.version;
|
|
77
|
+
const commitMatches = !receipt.expectedCommit || receipt.expectedCommit === actual.commit;
|
|
78
|
+
const genericReasons = new Set(["requested restart", "Agent-requested restart", "Telegram restart", "Telegram /restart", "Telegram update restart"]);
|
|
79
|
+
const resultSummary = String(receipt.reason || "").trim();
|
|
80
|
+
const lines = [
|
|
81
|
+
versionMatches && commitMatches ? "Restart completed." : "Restart completed with an unexpected runtime identity.",
|
|
82
|
+
resultSummary && !genericReasons.has(resultSummary) ? resultSummary : null,
|
|
83
|
+
`Arisa ${actual.version} is running${actual.commit ? ` at commit ${actual.commit}` : ""}.`,
|
|
84
|
+
versionMatches && commitMatches ? null : `Expected: ${receipt.expectedVersion}${receipt.expectedCommit ? ` at ${receipt.expectedCommit}` : ""}.`
|
|
85
|
+
].filter(Boolean);
|
|
86
|
+
const options = receipt.threadId ? { message_thread_id: receipt.threadId } : {};
|
|
87
|
+
await sendMessage(receipt.transportChatId, lines.join("\n"), options);
|
|
88
|
+
await rm(receiptFile, { force: true });
|
|
89
|
+
return { receipt, actual, verified: versionMatches && commitMatches };
|
|
90
|
+
}
|