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,18 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import { Type } from "@sinclair/typebox";
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
5
3
|
import { createPiRuntime, hasProviderAuth } from "./pi-runtime.js";
|
|
6
4
|
import { resolveChatModelSelection } from "./model-selection.js";
|
|
7
5
|
import { appendArisaAgentsFile, arisaAgentsFile, arisaInstallDir, buildAgentRuntimeContext } from "./runtime-context.js";
|
|
8
6
|
import { withTimeout } from "./prompt-timeout.js";
|
|
9
|
-
import { buildPiToolPolicy
|
|
7
|
+
import { buildPiToolPolicy } from "./core-tools.js";
|
|
10
8
|
import { createSystemShellTool } from "./system-shell-tool.js";
|
|
11
9
|
import { clampModelThinkingLevel } from "./pi-runtime.js";
|
|
12
10
|
import { clampModelSpeed, createModelSpeedController } from "./model-speed.js";
|
|
13
|
-
import { arisaHomeDir
|
|
14
|
-
import {
|
|
11
|
+
import { arisaHomeDir } from "../../runtime/paths.js";
|
|
12
|
+
import { AgentSessionLifecycle } from "./agent-session-lifecycle.js";
|
|
13
|
+
import { createPiCapabilityTools } from "./pi-capability-tools.js";
|
|
15
14
|
import { ToolResourceNoteStore } from "../tools/tool-resource-note-store.js";
|
|
15
|
+
import { materializeToolOutput } from "../tools/tool-output-materializer.js";
|
|
16
16
|
|
|
17
17
|
const piValidationTimeoutMs = 60_000;
|
|
18
18
|
const arisaToolNames = [
|
|
@@ -25,35 +25,11 @@ const arisaToolNames = [
|
|
|
25
25
|
"list_scheduled_tasks",
|
|
26
26
|
"cancel_scheduled_task",
|
|
27
27
|
"cancel_all_scheduled_tasks",
|
|
28
|
+
"create_telegram_topic",
|
|
29
|
+
"initialize_telegram_topic",
|
|
28
30
|
"send_artifact"
|
|
29
31
|
];
|
|
30
32
|
|
|
31
|
-
function messageText(content) {
|
|
32
|
-
if (typeof content === "string") return content.trim();
|
|
33
|
-
if (!Array.isArray(content)) return "";
|
|
34
|
-
return content
|
|
35
|
-
.filter((item) => item?.type === "text" && typeof item.text === "string")
|
|
36
|
-
.map((item) => item.text.trim())
|
|
37
|
-
.filter(Boolean)
|
|
38
|
-
.join("\n");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function formatPortableSessionHistory(messages = []) {
|
|
42
|
-
return messages
|
|
43
|
-
.map((message) => {
|
|
44
|
-
const text = messageText(message?.content);
|
|
45
|
-
if (!text) return "";
|
|
46
|
-
const role = message.role === "assistant"
|
|
47
|
-
? "Assistant"
|
|
48
|
-
: message.role === "user"
|
|
49
|
-
? "User"
|
|
50
|
-
: (message.customType ? `Session memory (${message.customType})` : "Session context");
|
|
51
|
-
return `${role}:\n${text}`;
|
|
52
|
-
})
|
|
53
|
-
.filter(Boolean)
|
|
54
|
-
.join("\n\n");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
33
|
const estimatedImageTokens = 1_200;
|
|
58
34
|
|
|
59
35
|
function estimateContentTokens(content) {
|
|
@@ -102,37 +78,14 @@ function summarizeRetainedContext(messages = []) {
|
|
|
102
78
|
};
|
|
103
79
|
}
|
|
104
80
|
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
export function selectScheduledTasks(tasks = [], { status, limit = defaultScheduledTaskListLimit } = {}) {
|
|
115
|
-
const parsedLimit = Number(limit);
|
|
116
|
-
const resolvedLimit = Math.min(
|
|
117
|
-
Math.max(Number.isFinite(parsedLimit) ? Math.trunc(parsedLimit) : defaultScheduledTaskListLimit, 1),
|
|
118
|
-
maxScheduledTaskListLimit
|
|
119
|
-
);
|
|
120
|
-
const allTasks = Array.isArray(tasks) ? tasks : [];
|
|
121
|
-
const orderedTasks = status
|
|
122
|
-
? [...allTasks].reverse()
|
|
123
|
-
: [
|
|
124
|
-
...allTasks.filter((task) => task.status === "pending" || task.status === "running").reverse(),
|
|
125
|
-
...allTasks.filter((task) => task.status !== "pending" && task.status !== "running").reverse()
|
|
126
|
-
];
|
|
127
|
-
const visibleTasks = orderedTasks.slice(0, resolvedLimit);
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
tasks: visibleTasks,
|
|
131
|
-
total: allTasks.length,
|
|
132
|
-
returned: visibleTasks.length,
|
|
133
|
-
limit: resolvedLimit,
|
|
134
|
-
truncated: visibleTasks.length < allTasks.length
|
|
135
|
-
};
|
|
81
|
+
function guardTools(tools, accessGuard) {
|
|
82
|
+
return tools.map((tool) => ({
|
|
83
|
+
...tool,
|
|
84
|
+
execute: async (...args) => {
|
|
85
|
+
await accessGuard();
|
|
86
|
+
return tool.execute(...args);
|
|
87
|
+
}
|
|
88
|
+
}));
|
|
136
89
|
}
|
|
137
90
|
|
|
138
91
|
function isLocalBaseUrl(value) {
|
|
@@ -168,32 +121,6 @@ async function promptAndThrowOnAssistantError(session, prompt) {
|
|
|
168
121
|
}
|
|
169
122
|
}
|
|
170
123
|
|
|
171
|
-
function inferDeliveryMethod(artifact) {
|
|
172
|
-
if (artifact.kind === "audio" || (artifact.mimeType || "").startsWith("audio/")) return "audio";
|
|
173
|
-
if (artifact.kind === "image" || (artifact.mimeType || "").startsWith("image/")) return "photo";
|
|
174
|
-
if (artifact.kind === "video" || (artifact.mimeType || "").startsWith("video/")) return "video";
|
|
175
|
-
return "document";
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function containsAbsolutePath(value) {
|
|
179
|
-
if (typeof value !== "string") return false;
|
|
180
|
-
return /(^|\s)(\/[^\s]|[A-Za-z]:[\\/])/.test(value);
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function resolveMediaCaption(caption) {
|
|
184
|
-
if (caption && !containsAbsolutePath(caption)) return caption;
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function deliverArtifactToChat({ artifact, telegram, caption, method, logger }) {
|
|
189
|
-
const resolvedMethod = method || artifact.metadata?.delivery?.method || inferDeliveryMethod(artifact);
|
|
190
|
-
const fileName = path.basename(artifact.path);
|
|
191
|
-
const resolvedCaption = resolveMediaCaption(caption);
|
|
192
|
-
logger?.log("agent", `deliver artifact ${artifact.id} as ${resolvedMethod}`);
|
|
193
|
-
await telegram.sendMedia(artifact.path, { method: resolvedMethod, caption: resolvedCaption, filename: fileName });
|
|
194
|
-
return { method: resolvedMethod, fileName, artifactId: artifact.id };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
124
|
async function assertDirectory(dir, label) {
|
|
198
125
|
const stats = await stat(dir);
|
|
199
126
|
if (!stats.isDirectory()) {
|
|
@@ -225,11 +152,21 @@ export class AgentManager {
|
|
|
225
152
|
this.taskStore = taskStore;
|
|
226
153
|
this.logger = logger;
|
|
227
154
|
this.resourceNotes = new ToolResourceNoteStore();
|
|
228
|
-
this.
|
|
229
|
-
|
|
230
|
-
|
|
155
|
+
this.sessionLifecycle = new AgentSessionLifecycle({
|
|
156
|
+
logger,
|
|
157
|
+
summarizeContext: summarizeRetainedContext
|
|
158
|
+
});
|
|
159
|
+
this.sessions = this.sessionLifecycle.sessions;
|
|
160
|
+
this.pendingNewSessions = this.sessionLifecycle.pendingNewSessions;
|
|
161
|
+
this.pendingSessionHandoffs = this.sessionLifecycle.pendingSessionHandoffs;
|
|
162
|
+
this.sessionClosePromises = this.sessionLifecycle.sessionClosePromises;
|
|
231
163
|
this.artifactDeliveryHandler = null;
|
|
232
|
-
this.
|
|
164
|
+
this.capabilityService = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setCapabilityService(capabilityService) {
|
|
168
|
+
if (!capabilityService?.execute) throw new Error("AgentManager requires CapabilityService");
|
|
169
|
+
this.capabilityService = capabilityService;
|
|
233
170
|
}
|
|
234
171
|
|
|
235
172
|
setArtifactDeliveryHandler(handler) {
|
|
@@ -242,114 +179,32 @@ export class AgentManager {
|
|
|
242
179
|
}
|
|
243
180
|
|
|
244
181
|
closeCachedSession(sessionKey) {
|
|
245
|
-
|
|
246
|
-
const existing = this.sessions.get(key);
|
|
247
|
-
this.sessions.delete(key);
|
|
248
|
-
const closeSession = (existing?.session?.close || existing?.session?.dispose)
|
|
249
|
-
? () => closeAgentSession(existing.session)
|
|
250
|
-
: null;
|
|
251
|
-
if (!closeSession) {
|
|
252
|
-
return this.sessionClosePromises.get(key) || Promise.resolve();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const previousClose = this.sessionClosePromises.get(key);
|
|
256
|
-
const closePromise = Promise.resolve(previousClose)
|
|
257
|
-
.catch(() => {})
|
|
258
|
-
.then(closeSession)
|
|
259
|
-
.catch((error) => {
|
|
260
|
-
this.logger?.error?.("agent", `session close failed for chat ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
261
|
-
})
|
|
262
|
-
.finally(() => {
|
|
263
|
-
if (this.sessionClosePromises.get(key) === closePromise) {
|
|
264
|
-
this.sessionClosePromises.delete(key);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
this.sessionClosePromises.set(key, closePromise);
|
|
268
|
-
return closePromise;
|
|
182
|
+
return this.sessionLifecycle.closeCached(sessionKey);
|
|
269
183
|
}
|
|
270
184
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
let closing = this.sessionClosePromises.get(key);
|
|
274
|
-
while (closing) {
|
|
275
|
-
await closing;
|
|
276
|
-
closing = this.sessionClosePromises.get(key);
|
|
277
|
-
}
|
|
185
|
+
waitForSessionClose(sessionKey) {
|
|
186
|
+
return this.sessionLifecycle.waitForClose(sessionKey);
|
|
278
187
|
}
|
|
279
188
|
|
|
280
189
|
setConfig(config) {
|
|
281
|
-
|
|
190
|
+
this.sessionLifecycle.resetConfigState();
|
|
282
191
|
this.config = config;
|
|
283
|
-
this.pendingNewSessions.clear();
|
|
284
|
-
this.pendingSessionHandoffs.clear();
|
|
285
192
|
}
|
|
286
193
|
|
|
287
|
-
resetSession(chatId,
|
|
288
|
-
|
|
289
|
-
this.closeCachedSession(sessionKey);
|
|
290
|
-
this.pendingNewSessions.add(sessionKey);
|
|
291
|
-
const text = String(handoff || "").trim();
|
|
292
|
-
const parent = String(parentSession || "").trim();
|
|
293
|
-
if (text || parent) {
|
|
294
|
-
this.pendingSessionHandoffs.set(sessionKey, { text, parentSession: parent });
|
|
295
|
-
} else {
|
|
296
|
-
this.pendingSessionHandoffs.delete(sessionKey);
|
|
297
|
-
}
|
|
194
|
+
resetSession(chatId, options = {}) {
|
|
195
|
+
this.sessionLifecycle.resetSession(chatId, options);
|
|
298
196
|
}
|
|
299
197
|
|
|
300
198
|
clearSessionCache(chatId) {
|
|
301
|
-
this.
|
|
199
|
+
this.sessionLifecycle.closeCached(String(chatId));
|
|
302
200
|
}
|
|
303
201
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const base = { chatId };
|
|
307
|
-
try {
|
|
308
|
-
const stats = context.session.getSessionStats();
|
|
309
|
-
const retained = summarizeRetainedContext(context.session.messages);
|
|
310
|
-
return {
|
|
311
|
-
...base,
|
|
312
|
-
...retained,
|
|
313
|
-
tokens: stats.contextUsage?.tokens ?? null,
|
|
314
|
-
contextWindow: stats.contextUsage?.contextWindow ?? null,
|
|
315
|
-
percent: stats.contextUsage?.percent ?? null
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
return { ...base, error: error instanceof Error ? error.message : String(error) };
|
|
319
|
-
}
|
|
320
|
-
}));
|
|
321
|
-
return {
|
|
322
|
-
harness: "pi",
|
|
323
|
-
sessions: this.sessions.size,
|
|
324
|
-
closingSessions: this.sessionClosePromises.size,
|
|
325
|
-
managedProcessIds: [],
|
|
326
|
-
contexts
|
|
327
|
-
};
|
|
202
|
+
getRuntimeDiagnostic() {
|
|
203
|
+
return this.sessionLifecycle.getDiagnostic();
|
|
328
204
|
}
|
|
329
205
|
|
|
330
206
|
createSessionManager(chatId, workspaceDir = arisaInstallDir, sessionRevision = 0) {
|
|
331
|
-
|
|
332
|
-
const sessionDir = getChatPiSessionsDir(sessionKey, sessionRevision);
|
|
333
|
-
if (this.pendingNewSessions.has(sessionKey)) {
|
|
334
|
-
this.logger?.log("agent", `starting new persisted session for chat ${sessionKey}`);
|
|
335
|
-
const handoff = this.pendingSessionHandoffs.get(sessionKey);
|
|
336
|
-
const sessionManager = SessionManager.create(
|
|
337
|
-
workspaceDir,
|
|
338
|
-
sessionDir,
|
|
339
|
-
handoff?.parentSession ? { parentSession: handoff.parentSession } : undefined
|
|
340
|
-
);
|
|
341
|
-
if (handoff?.text) {
|
|
342
|
-
sessionManager.appendCustomMessageEntry(
|
|
343
|
-
"arisa-session-handoff",
|
|
344
|
-
handoff.text,
|
|
345
|
-
false,
|
|
346
|
-
{ source: "telegram-new" }
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
return { sessionManager, isNewSession: true };
|
|
350
|
-
}
|
|
351
|
-
this.logger?.log("agent", `recovering persisted session for chat ${sessionKey}`);
|
|
352
|
-
return { sessionManager: SessionManager.continueRecent(workspaceDir, sessionDir), isNewSession: false };
|
|
207
|
+
return this.sessionLifecycle.createSessionManager(chatId, workspaceDir, sessionRevision);
|
|
353
208
|
}
|
|
354
209
|
|
|
355
210
|
async validatePiAgent(config = this.config) {
|
|
@@ -388,7 +243,7 @@ export class AgentManager {
|
|
|
388
243
|
return this.validatePiAgent(config);
|
|
389
244
|
}
|
|
390
245
|
|
|
391
|
-
async getSessionContext(chatId, telegram) {
|
|
246
|
+
async getSessionContext(chatId, telegram, { scopeChatId = chatId, accessGuard = async () => {} } = {}) {
|
|
392
247
|
const sessionKey = String(chatId);
|
|
393
248
|
const modelSelection = resolveChatModelSelection(this.config, sessionKey);
|
|
394
249
|
const effectiveModelId = modelSelection.model;
|
|
@@ -406,6 +261,8 @@ export class AgentManager {
|
|
|
406
261
|
this.logger?.log("agent", `updating speed for chat ${sessionKey}: ${existing.speedController.speed}x -> ${desiredSpeed}x`);
|
|
407
262
|
existing.speedController.setSpeed(desiredSpeed);
|
|
408
263
|
}
|
|
264
|
+
existing.telegramTarget.current = telegram;
|
|
265
|
+
existing.accessGuardTarget.current = accessGuard;
|
|
409
266
|
this.logger?.log("agent", `reusing session for chat ${sessionKey}`);
|
|
410
267
|
return existing;
|
|
411
268
|
}
|
|
@@ -438,10 +295,26 @@ export class AgentManager {
|
|
|
438
295
|
);
|
|
439
296
|
const hasExistingSession = sessionManager.buildSessionContext().messages.length > 0;
|
|
440
297
|
this.logger?.log("agent", `${hasExistingSession ? "resuming" : "creating"} session for chat ${sessionKey} with model ${effectiveModelId} effort ${thinkingLevel} speed ${speed}x`);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
298
|
+
const telegramTarget = { current: telegram };
|
|
299
|
+
const accessGuardTarget = { current: accessGuard };
|
|
300
|
+
const telegramProxy = {
|
|
301
|
+
sendMedia: (...args) => telegramTarget.current.sendMedia(...args),
|
|
302
|
+
createForumTopic: (...args) => telegramTarget.current.createForumTopic(...args),
|
|
303
|
+
initializeForumTopic: (...args) => telegramTarget.current.initializeForumTopic(...args),
|
|
304
|
+
prepareRestartReceipt: (...args) => telegramTarget.current.prepareRestartReceipt(...args),
|
|
305
|
+
cancelRestartReceipt: (...args) => telegramTarget.current.cancelRestartReceipt(...args),
|
|
306
|
+
getTaskContext: (...args) => telegramTarget.current.getTaskContext?.(...args) || null
|
|
307
|
+
};
|
|
308
|
+
const assertAccess = () => accessGuardTarget.current();
|
|
309
|
+
const customTools = guardTools([
|
|
310
|
+
...this.createTools(telegramProxy, scopeChatId, policy),
|
|
311
|
+
createSystemShellTool({
|
|
312
|
+
workspaceDir: policy.workspaceDir,
|
|
313
|
+
shell: policy.shell,
|
|
314
|
+
beforeRestart: (summary) => telegramProxy.prepareRestartReceipt(summary),
|
|
315
|
+
cancelRestart: (receiptId) => telegramProxy.cancelRestartReceipt(receiptId)
|
|
316
|
+
})
|
|
317
|
+
], assertAccess);
|
|
445
318
|
const settingsManager = createPiSettingsManager(this.config);
|
|
446
319
|
const resourceLoader = await createArisaResourceLoader({
|
|
447
320
|
cwd: policy.workspaceDir,
|
|
@@ -473,12 +346,16 @@ export class AgentManager {
|
|
|
473
346
|
})}`);
|
|
474
347
|
}
|
|
475
348
|
|
|
476
|
-
const ctx = {
|
|
349
|
+
const ctx = {
|
|
350
|
+
session,
|
|
351
|
+
modelId: effectiveModelId,
|
|
352
|
+
modelKey: effectiveModelKey,
|
|
353
|
+
speedController,
|
|
354
|
+
telegramTarget,
|
|
355
|
+
accessGuardTarget
|
|
356
|
+
};
|
|
477
357
|
this.sessions.set(sessionKey, ctx);
|
|
478
|
-
if (isNewSession)
|
|
479
|
-
this.pendingNewSessions.delete(sessionKey);
|
|
480
|
-
this.pendingSessionHandoffs.delete(sessionKey);
|
|
481
|
-
}
|
|
358
|
+
if (isNewSession) this.sessionLifecycle.completeNewSession(sessionKey);
|
|
482
359
|
return ctx;
|
|
483
360
|
}
|
|
484
361
|
|
|
@@ -497,18 +374,12 @@ export class AgentManager {
|
|
|
497
374
|
}
|
|
498
375
|
|
|
499
376
|
async close() {
|
|
500
|
-
|
|
501
|
-
this.sessions.clear();
|
|
502
|
-
await Promise.allSettled([
|
|
503
|
-
...this.sessionClosePromises.values(),
|
|
504
|
-
...contexts.map((context) => closeAgentSession(context.session))
|
|
505
|
-
]);
|
|
377
|
+
await this.sessionLifecycle.closeAll();
|
|
506
378
|
}
|
|
507
379
|
|
|
508
|
-
async runTool({ name, request, chatId }) {
|
|
380
|
+
async runTool({ name, request, chatId, taskContext = null }) {
|
|
509
381
|
await this.toolRegistry.load();
|
|
510
382
|
this.logger?.log("agent", `run_tool ${name}`);
|
|
511
|
-
const chatArtifactStore = this.artifactStore.forChat(chatId);
|
|
512
383
|
const resourceId = String(request?.resourceId || "").trim();
|
|
513
384
|
const resourceNote = resourceId
|
|
514
385
|
? await this.resourceNotes.get(chatId, name, resourceId)
|
|
@@ -516,283 +387,24 @@ export class AgentManager {
|
|
|
516
387
|
const enrichedRequest = resourceNote ? { ...request, resourceId, resourceNote } : request;
|
|
517
388
|
const result = await this.toolRegistry.run({ name, request: enrichedRequest, chatId });
|
|
518
389
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
if (result.output?.filePath) {
|
|
529
|
-
const generated = await chatArtifactStore.createFromFile({
|
|
530
|
-
originalPath: result.output.filePath,
|
|
531
|
-
fileName: result.output.fileName || path.basename(result.output.filePath),
|
|
532
|
-
kind: result.output.kind || "file",
|
|
533
|
-
mimeType: result.output.mimeType || "application/octet-stream",
|
|
534
|
-
source: { type: "tool", toolName: name },
|
|
535
|
-
metadata: { tool: name, delivery: result.output.delivery }
|
|
536
|
-
});
|
|
537
|
-
result.output.artifactId = generated.id;
|
|
538
|
-
await unlink(result.output.filePath).catch(() => {});
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (result.asyncTask || result.asyncTasks?.length) {
|
|
542
|
-
const scheduled = await this.taskStore.addMany(
|
|
543
|
-
result.asyncTasks || [result.asyncTask],
|
|
544
|
-
{
|
|
545
|
-
payload: { chatId },
|
|
546
|
-
source: { type: "tool", toolName: name, chatId }
|
|
547
|
-
}
|
|
548
|
-
);
|
|
549
|
-
result.asyncTasks = scheduled;
|
|
550
|
-
delete result.asyncTask;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return result;
|
|
390
|
+
return materializeToolOutput({
|
|
391
|
+
result,
|
|
392
|
+
name,
|
|
393
|
+
chatId,
|
|
394
|
+
artifactStore: this.artifactStore,
|
|
395
|
+
taskStore: this.taskStore,
|
|
396
|
+
taskContext
|
|
397
|
+
});
|
|
554
398
|
}
|
|
555
399
|
|
|
556
400
|
createTools(telegram, chatId, policy = buildPiToolPolicy({ config: this.config, customToolNames: arisaToolNames })) {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
parameters: Type.Object({ query: Type.Optional(Type.String()) }),
|
|
565
|
-
execute: async (_id, params) => {
|
|
566
|
-
await this.toolRegistry.load();
|
|
567
|
-
const coreTools = getCoreCodingTools({
|
|
568
|
-
tools: policy.tools,
|
|
569
|
-
excludeTools: policy.excludeTools
|
|
570
|
-
});
|
|
571
|
-
const nativeTools = [{
|
|
572
|
-
name: "system_shell",
|
|
573
|
-
source: "arisa-native",
|
|
574
|
-
description: "Run native system shell commands in the active Arisa workspace.",
|
|
575
|
-
workspaceDir: policy.workspaceDir,
|
|
576
|
-
shell: policy.shell.shellPath || (process.platform === "win32" ? "powershell" : "sh"),
|
|
577
|
-
enabled: !(policy.excludeTools || []).includes("system_shell")
|
|
578
|
-
}];
|
|
579
|
-
const query = params.query?.trim() || "";
|
|
580
|
-
let catalogFallback = null;
|
|
581
|
-
const cliTools = query
|
|
582
|
-
? this.toolRegistry.search(query).map((tool) => ({
|
|
583
|
-
...tool,
|
|
584
|
-
source: "arisa-modular",
|
|
585
|
-
invocation: "run_tool"
|
|
586
|
-
}))
|
|
587
|
-
: (await this.toolRegistry.listWithRuntime(chatId)).map((tool) => ({
|
|
588
|
-
...tool,
|
|
589
|
-
source: "arisa-modular",
|
|
590
|
-
invocation: "run_tool"
|
|
591
|
-
}));
|
|
592
|
-
if (query && cliTools.length === 0) {
|
|
593
|
-
try {
|
|
594
|
-
catalogFallback = await searchOfficialToolCatalog(query);
|
|
595
|
-
} catch (error) {
|
|
596
|
-
catalogFallback = { unavailable: true, error: error?.message || String(error), matches: [] };
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
const result = {
|
|
600
|
-
query: query || null,
|
|
601
|
-
workspaceDir: policy.workspaceDir,
|
|
602
|
-
coreTools: query ? [] : coreTools,
|
|
603
|
-
nativeTools: query ? [] : nativeTools,
|
|
604
|
-
cliTools,
|
|
605
|
-
officialCatalogMatches: Array.isArray(catalogFallback) ? catalogFallback : catalogFallback?.matches || [],
|
|
606
|
-
catalogFallback: catalogFallback && !Array.isArray(catalogFallback) ? catalogFallback : null,
|
|
607
|
-
tools: query ? cliTools : [...coreTools.filter((tool) => tool.enabled), ...nativeTools.filter((tool) => tool.enabled), ...cliTools]
|
|
608
|
-
};
|
|
609
|
-
return {
|
|
610
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
611
|
-
details: result
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
}),
|
|
615
|
-
defineTool({
|
|
616
|
-
name: "tool_help",
|
|
617
|
-
label: "Tool help",
|
|
618
|
-
description: "Show --help text for a CLI tool.",
|
|
619
|
-
parameters: Type.Object({ name: Type.String() }),
|
|
620
|
-
execute: async (_id, params) => {
|
|
621
|
-
await this.toolRegistry.load();
|
|
622
|
-
const help = await this.toolRegistry.help(params.name);
|
|
623
|
-
return { content: [{ type: "text", text: help }], details: { help } };
|
|
624
|
-
}
|
|
625
|
-
}),
|
|
626
|
-
defineTool({
|
|
627
|
-
name: "tool_skills",
|
|
628
|
-
label: "Tool skills",
|
|
629
|
-
description: "Show skills assigned to a CLI tool via its manifest skillHints.",
|
|
630
|
-
parameters: Type.Object({ name: Type.String() }),
|
|
631
|
-
execute: async (_id, params) => {
|
|
632
|
-
await this.toolRegistry.load();
|
|
633
|
-
const skills = await this.toolRegistry.resolveSkills(params.name);
|
|
634
|
-
const visible = skills.map(({ content, ...item }) => item);
|
|
635
|
-
return { content: [{ type: "text", text: JSON.stringify(visible, null, 2) }], details: visible };
|
|
636
|
-
}
|
|
637
|
-
}),
|
|
638
|
-
defineTool({
|
|
639
|
-
name: "set_tool_config",
|
|
640
|
-
label: "Set tool config",
|
|
641
|
-
description: "Write a tool config value scoped to the current chat.",
|
|
642
|
-
parameters: Type.Object({ name: Type.String(), field: Type.String(), value: Type.String() }),
|
|
643
|
-
execute: async (_id, params) => {
|
|
644
|
-
await this.toolRegistry.load();
|
|
645
|
-
const result = await this.toolRegistry.setConfig(params.name, params.field, params.value, chatId);
|
|
646
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
647
|
-
}
|
|
648
|
-
}),
|
|
649
|
-
defineTool({
|
|
650
|
-
name: "set_tool_resource_note",
|
|
651
|
-
label: "Set tool resource note",
|
|
652
|
-
description: "Set or clear a deterministic chat-scoped note of up to 200 characters for one tool resource.",
|
|
653
|
-
parameters: Type.Object({
|
|
654
|
-
name: Type.String(),
|
|
655
|
-
resourceId: Type.String(),
|
|
656
|
-
note: Type.String()
|
|
657
|
-
}),
|
|
658
|
-
execute: async (_id, params) => {
|
|
659
|
-
const result = await this.resourceNotes.set(chatId, params.name, params.resourceId, params.note);
|
|
660
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
661
|
-
}
|
|
662
|
-
}),
|
|
663
|
-
defineTool({
|
|
664
|
-
name: "run_tool",
|
|
665
|
-
label: "Run tool",
|
|
666
|
-
description: "Run a CLI tool using text input or an artifactId. Inspect the returned status/resolution fields. If a tool reports missing config, ask the user naturally, use set_tool_config, and retry. Set `deliver: true` to also send the generated file to the chat in one step (only when you want the user to receive it now, not for intermediate pipe steps).",
|
|
667
|
-
parameters: Type.Object({
|
|
668
|
-
name: Type.String(),
|
|
669
|
-
artifactId: Type.Optional(Type.String()),
|
|
670
|
-
text: Type.Optional(Type.String()),
|
|
671
|
-
resourceId: Type.Optional(Type.String()),
|
|
672
|
-
args: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
673
|
-
deliver: Type.Optional(Type.Boolean())
|
|
674
|
-
}),
|
|
675
|
-
execute: async (_id, params) => {
|
|
676
|
-
let artifact = null;
|
|
677
|
-
if (params.artifactId) {
|
|
678
|
-
artifact = await chatArtifactStore.get(params.artifactId);
|
|
679
|
-
if (!artifact) {
|
|
680
|
-
return { content: [{ type: "text", text: `Artifact not found: ${params.artifactId}` }], details: { ok: false } };
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
const result = await this.runTool({
|
|
684
|
-
name: params.name,
|
|
685
|
-
request: {
|
|
686
|
-
artifact,
|
|
687
|
-
text: params.text,
|
|
688
|
-
resourceId: params.resourceId,
|
|
689
|
-
args: params.args || {}
|
|
690
|
-
},
|
|
691
|
-
chatId
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
if (params.deliver && result.output?.artifactId) {
|
|
695
|
-
const generated = await chatArtifactStore.get(result.output.artifactId);
|
|
696
|
-
if (generated?.path) {
|
|
697
|
-
result.sent = await deliverArtifactToChat({ artifact: generated, telegram, logger: this.logger });
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
return {
|
|
702
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
703
|
-
details: result
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
}),
|
|
707
|
-
defineTool({
|
|
708
|
-
name: "list_scheduled_tasks",
|
|
709
|
-
label: "List scheduled tasks",
|
|
710
|
-
description: "List scheduled async tasks for the current Telegram chat. Results default to 50 tasks, always include pending/running tasks, and accept an optional limit up to 100.",
|
|
711
|
-
parameters: Type.Object({
|
|
712
|
-
status: Type.Optional(Type.String()),
|
|
713
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: maxScheduledTaskListLimit }))
|
|
714
|
-
}),
|
|
715
|
-
execute: async (_id, params) => {
|
|
716
|
-
const tasks = await this.taskStore.list({ chatId, status: params.status });
|
|
717
|
-
const result = selectScheduledTasks(tasks, {
|
|
718
|
-
status: params.status,
|
|
719
|
-
limit: params.limit
|
|
720
|
-
});
|
|
721
|
-
return {
|
|
722
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
723
|
-
details: result
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
}),
|
|
727
|
-
defineTool({
|
|
728
|
-
name: "cancel_scheduled_task",
|
|
729
|
-
label: "Cancel scheduled task",
|
|
730
|
-
description: "Cancel one scheduled async task by id for the current Telegram chat.",
|
|
731
|
-
parameters: Type.Object({ id: Type.String() }),
|
|
732
|
-
execute: async (_id, params) => {
|
|
733
|
-
const existing = await this.taskStore.get(params.id);
|
|
734
|
-
if (!existing || existing.payload?.chatId !== chatId) {
|
|
735
|
-
return {
|
|
736
|
-
content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Task not found" }) }],
|
|
737
|
-
details: { ok: false, error: "Task not found" }
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
const task = await this.taskStore.cancel(params.id);
|
|
741
|
-
return {
|
|
742
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, task }, null, 2) }],
|
|
743
|
-
details: { ok: true, task }
|
|
744
|
-
};
|
|
745
|
-
}
|
|
746
|
-
}),
|
|
747
|
-
defineTool({
|
|
748
|
-
name: "cancel_all_scheduled_tasks",
|
|
749
|
-
label: "Cancel all scheduled tasks",
|
|
750
|
-
description: "Cancel all pending or running async tasks for the current Telegram chat.",
|
|
751
|
-
parameters: Type.Object({}),
|
|
752
|
-
execute: async () => {
|
|
753
|
-
const tasks = await this.taskStore.cancelAll({ chatId });
|
|
754
|
-
return {
|
|
755
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, cancelled: tasks.length }, null, 2) }],
|
|
756
|
-
details: { ok: true, tasks }
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
}),
|
|
760
|
-
defineTool({
|
|
761
|
-
name: "send_artifact",
|
|
762
|
-
label: "Send artifact",
|
|
763
|
-
description: "Deliver an existing chat artifact to the current Telegram chat. Pass the `artifactId` returned by run_tool or from an inbound file. The delivery method and filename are derived from the artifact (its delivery hint, kind, and stored name); internal local paths are never exposed. No caption is shown by default, since the filename already appears on the attachment; set `caption` only to add a separate visible label, or `method` to override the delivery method. The artifact is not deleted.",
|
|
764
|
-
parameters: Type.Object({
|
|
765
|
-
artifactId: Type.String(),
|
|
766
|
-
caption: Type.Optional(Type.String()),
|
|
767
|
-
method: Type.Optional(Type.Union([
|
|
768
|
-
Type.Literal("voice"),
|
|
769
|
-
Type.Literal("audio"),
|
|
770
|
-
Type.Literal("document")
|
|
771
|
-
]))
|
|
772
|
-
}),
|
|
773
|
-
execute: async (_id, params) => {
|
|
774
|
-
const artifact = await chatArtifactStore.get(params.artifactId);
|
|
775
|
-
if (!artifact) {
|
|
776
|
-
const result = { ok: false, status: "failed", error: `Artifact not found: ${params.artifactId}` };
|
|
777
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
778
|
-
}
|
|
779
|
-
if (!artifact.path) {
|
|
780
|
-
const result = { ok: false, status: "failed", error: `Artifact ${params.artifactId} has no file to deliver.` };
|
|
781
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
782
|
-
}
|
|
783
|
-
const sent = await deliverArtifactToChat({
|
|
784
|
-
artifact,
|
|
785
|
-
telegram,
|
|
786
|
-
caption: params.caption,
|
|
787
|
-
method: params.method,
|
|
788
|
-
logger: this.logger
|
|
789
|
-
});
|
|
790
|
-
return {
|
|
791
|
-
content: [{ type: "text", text: `Media sent to Telegram as ${sent.method}.` }],
|
|
792
|
-
details: { ok: true, sent }
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
})
|
|
796
|
-
];
|
|
401
|
+
return createPiCapabilityTools({
|
|
402
|
+
capabilityService: this.capabilityService,
|
|
403
|
+
telegram,
|
|
404
|
+
chatId,
|
|
405
|
+
policy,
|
|
406
|
+
logger: this.logger
|
|
407
|
+
});
|
|
797
408
|
}
|
|
409
|
+
|
|
798
410
|
}
|