arisa 5.1.49 → 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 +1 -1
- package/src/core/agent/agent-manager.js +39 -489
- package/src/core/agent/agent-session-lifecycle.js +181 -0
- package/src/core/agent/pi-capability-tools.js +183 -0
- package/src/core/artifacts/artifact-store.js +73 -17
- package/src/core/capabilities/capability-service.js +340 -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/tool-output-materializer.js +5 -5
- package/src/official-tools.lock.json +7 -4
- 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/transport/telegram/bot.js +112 -368
- package/src/transport/telegram/chat-queue.js +72 -6
- package/src/transport/telegram/prompt-builders.js +9 -0
- package/src/transport/telegram/task-dispatcher.js +73 -36
- 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/test/agent-session-lifecycle.test.js +58 -0
- package/test/artifact-store.test.js +38 -2
- package/test/capabilities-security.test.js +58 -0
- package/test/context-and-task-bounds.test.js +76 -1
- package/test/device-code-message.test.js +9 -0
- package/test/media-caption.test.js +1 -1
- package/test/pi-capability-tools.test.js +65 -0
- package/test/session-start-operational-notes.test.js +1 -1
- 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-task-dispatcher.test.js +99 -23
- package/test/telegram-text-artifact.test.js +13 -2
- package/test/telegram-tools-command.test.js +47 -0
package/AGENTS.md
CHANGED
|
@@ -169,8 +169,6 @@ Beyond time-based scheduling, tools can drive an event queue that wakes the agen
|
|
|
169
169
|
- `poll_tool`: a recurring checker the poller **runs directly as a tool** (no agent turn spent). The poller materializes its output with the same logic as `run_tool`, so any `agent_event` the checker emits is enqueued for the next tick. Its `recurrence` reschedules the next poll.
|
|
170
170
|
- `agent_event`: an incoming event. The poller delivers it as a prompt so the active runtime evaluates it and decides the next action (it may stay silent).
|
|
171
171
|
|
|
172
|
-
Tasks without a `runAt` fire immediately, so `agent_event` and the first `poll_tool` run on the next tick.
|
|
173
|
-
|
|
174
172
|
The poller dispatches all three kinds, but only `agent_task` is exercised by a catalog tool today (`schedule-agent-task`). The following is the pattern to follow when a checker tool is built:
|
|
175
173
|
|
|
176
174
|
How a tool wires its own polling:
|
package/package.json
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
1
|
import { readFile, stat } from "node:fs/promises";
|
|
4
|
-
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager
|
|
5
|
-
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
6
3
|
import { createPiRuntime, hasProviderAuth } from "./pi-runtime.js";
|
|
7
4
|
import { resolveChatModelSelection } from "./model-selection.js";
|
|
8
5
|
import { appendArisaAgentsFile, arisaAgentsFile, arisaInstallDir, buildAgentRuntimeContext } from "./runtime-context.js";
|
|
9
6
|
import { withTimeout } from "./prompt-timeout.js";
|
|
10
|
-
import { buildPiToolPolicy
|
|
7
|
+
import { buildPiToolPolicy } from "./core-tools.js";
|
|
11
8
|
import { createSystemShellTool } from "./system-shell-tool.js";
|
|
12
9
|
import { clampModelThinkingLevel } from "./pi-runtime.js";
|
|
13
10
|
import { clampModelSpeed, createModelSpeedController } from "./model-speed.js";
|
|
14
|
-
import { arisaHomeDir
|
|
15
|
-
import {
|
|
11
|
+
import { arisaHomeDir } from "../../runtime/paths.js";
|
|
12
|
+
import { AgentSessionLifecycle } from "./agent-session-lifecycle.js";
|
|
13
|
+
import { createPiCapabilityTools } from "./pi-capability-tools.js";
|
|
16
14
|
import { ToolResourceNoteStore } from "../tools/tool-resource-note-store.js";
|
|
17
15
|
import { materializeToolOutput } from "../tools/tool-output-materializer.js";
|
|
18
16
|
|
|
@@ -32,35 +30,6 @@ const arisaToolNames = [
|
|
|
32
30
|
"send_artifact"
|
|
33
31
|
];
|
|
34
32
|
|
|
35
|
-
const operationalNoteMaxChars = 220;
|
|
36
|
-
|
|
37
|
-
function normalizeOperationalNote(note) {
|
|
38
|
-
const text = typeof note === "string" ? note : note?.text;
|
|
39
|
-
const trimmed = String(text || "").replace(/\s+/g, " ").trim();
|
|
40
|
-
if (!trimmed) return "";
|
|
41
|
-
return trimmed.length <= operationalNoteMaxChars ? trimmed : `${trimmed.slice(0, operationalNoteMaxChars - 1).trim()}…`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function loadSessionStartOperationalNotes() {
|
|
45
|
-
try {
|
|
46
|
-
const raw = readFileSync(sessionStartOperationalNotesFile, "utf8");
|
|
47
|
-
const parsed = JSON.parse(raw);
|
|
48
|
-
const notes = Array.isArray(parsed) ? parsed : parsed?.notes;
|
|
49
|
-
if (!Array.isArray(notes)) return [];
|
|
50
|
-
return notes.map(normalizeOperationalNote).filter(Boolean).slice(0, 20);
|
|
51
|
-
} catch {
|
|
52
|
-
return [];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function formatSessionStartOperationalNotes(notes) {
|
|
57
|
-
if (!notes.length) return "";
|
|
58
|
-
return [
|
|
59
|
-
"Durable operating notes for this Arisa session:",
|
|
60
|
-
...notes.map((note) => `- ${note}`)
|
|
61
|
-
].join("\n");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
33
|
const estimatedImageTokens = 1_200;
|
|
65
34
|
|
|
66
35
|
function estimateContentTokens(content) {
|
|
@@ -119,39 +88,6 @@ function guardTools(tools, accessGuard) {
|
|
|
119
88
|
}));
|
|
120
89
|
}
|
|
121
90
|
|
|
122
|
-
function closeAgentSession(session) {
|
|
123
|
-
if (session?.close) return session.close();
|
|
124
|
-
if (session?.dispose) return session.dispose();
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export const defaultScheduledTaskListLimit = 50;
|
|
129
|
-
export const maxScheduledTaskListLimit = 100;
|
|
130
|
-
|
|
131
|
-
export function selectScheduledTasks(tasks = [], { status, limit = defaultScheduledTaskListLimit } = {}) {
|
|
132
|
-
const parsedLimit = Number(limit);
|
|
133
|
-
const resolvedLimit = Math.min(
|
|
134
|
-
Math.max(Number.isFinite(parsedLimit) ? Math.trunc(parsedLimit) : defaultScheduledTaskListLimit, 1),
|
|
135
|
-
maxScheduledTaskListLimit
|
|
136
|
-
);
|
|
137
|
-
const allTasks = Array.isArray(tasks) ? tasks : [];
|
|
138
|
-
const orderedTasks = status
|
|
139
|
-
? [...allTasks].reverse()
|
|
140
|
-
: [
|
|
141
|
-
...allTasks.filter((task) => task.status === "pending" || task.status === "running").reverse(),
|
|
142
|
-
...allTasks.filter((task) => task.status !== "pending" && task.status !== "running").reverse()
|
|
143
|
-
];
|
|
144
|
-
const visibleTasks = orderedTasks.slice(0, resolvedLimit);
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
tasks: visibleTasks,
|
|
148
|
-
total: allTasks.length,
|
|
149
|
-
returned: visibleTasks.length,
|
|
150
|
-
limit: resolvedLimit,
|
|
151
|
-
truncated: visibleTasks.length < allTasks.length
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
91
|
function isLocalBaseUrl(value) {
|
|
156
92
|
if (typeof value !== "string" || !value.trim()) return false;
|
|
157
93
|
try {
|
|
@@ -185,32 +121,6 @@ async function promptAndThrowOnAssistantError(session, prompt) {
|
|
|
185
121
|
}
|
|
186
122
|
}
|
|
187
123
|
|
|
188
|
-
function inferDeliveryMethod(artifact) {
|
|
189
|
-
if (artifact.kind === "audio" || (artifact.mimeType || "").startsWith("audio/")) return "audio";
|
|
190
|
-
if (artifact.kind === "image" || (artifact.mimeType || "").startsWith("image/")) return "photo";
|
|
191
|
-
if (artifact.kind === "video" || (artifact.mimeType || "").startsWith("video/")) return "video";
|
|
192
|
-
return "document";
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function containsAbsolutePath(value) {
|
|
196
|
-
if (typeof value !== "string") return false;
|
|
197
|
-
return /(^|\s)(\/[^\s]|[A-Za-z]:[\\/])/.test(value);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function resolveMediaCaption(caption) {
|
|
201
|
-
if (caption && !containsAbsolutePath(caption)) return caption;
|
|
202
|
-
return undefined;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function deliverArtifactToChat({ artifact, telegram, caption, method, logger }) {
|
|
206
|
-
const resolvedMethod = method || artifact.metadata?.delivery?.method || inferDeliveryMethod(artifact);
|
|
207
|
-
const fileName = path.basename(artifact.path);
|
|
208
|
-
const resolvedCaption = resolveMediaCaption(caption);
|
|
209
|
-
logger?.log("agent", `deliver artifact ${artifact.id} as ${resolvedMethod}`);
|
|
210
|
-
await telegram.sendMedia(artifact.path, { method: resolvedMethod, caption: resolvedCaption, filename: fileName });
|
|
211
|
-
return { method: resolvedMethod, fileName, artifactId: artifact.id };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
124
|
async function assertDirectory(dir, label) {
|
|
215
125
|
const stats = await stat(dir);
|
|
216
126
|
if (!stats.isDirectory()) {
|
|
@@ -242,11 +152,21 @@ export class AgentManager {
|
|
|
242
152
|
this.taskStore = taskStore;
|
|
243
153
|
this.logger = logger;
|
|
244
154
|
this.resourceNotes = new ToolResourceNoteStore();
|
|
245
|
-
this.
|
|
246
|
-
|
|
247
|
-
|
|
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;
|
|
248
163
|
this.artifactDeliveryHandler = null;
|
|
249
|
-
this.
|
|
164
|
+
this.capabilityService = null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setCapabilityService(capabilityService) {
|
|
168
|
+
if (!capabilityService?.execute) throw new Error("AgentManager requires CapabilityService");
|
|
169
|
+
this.capabilityService = capabilityService;
|
|
250
170
|
}
|
|
251
171
|
|
|
252
172
|
setArtifactDeliveryHandler(handler) {
|
|
@@ -259,122 +179,32 @@ export class AgentManager {
|
|
|
259
179
|
}
|
|
260
180
|
|
|
261
181
|
closeCachedSession(sessionKey) {
|
|
262
|
-
|
|
263
|
-
const existing = this.sessions.get(key);
|
|
264
|
-
this.sessions.delete(key);
|
|
265
|
-
const closeSession = (existing?.session?.close || existing?.session?.dispose)
|
|
266
|
-
? () => closeAgentSession(existing.session)
|
|
267
|
-
: null;
|
|
268
|
-
if (!closeSession) {
|
|
269
|
-
return this.sessionClosePromises.get(key) || Promise.resolve();
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const previousClose = this.sessionClosePromises.get(key);
|
|
273
|
-
const closePromise = Promise.resolve(previousClose)
|
|
274
|
-
.catch(() => {})
|
|
275
|
-
.then(closeSession)
|
|
276
|
-
.catch((error) => {
|
|
277
|
-
this.logger?.error?.("agent", `session close failed for chat ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
278
|
-
})
|
|
279
|
-
.finally(() => {
|
|
280
|
-
if (this.sessionClosePromises.get(key) === closePromise) {
|
|
281
|
-
this.sessionClosePromises.delete(key);
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
this.sessionClosePromises.set(key, closePromise);
|
|
285
|
-
return closePromise;
|
|
182
|
+
return this.sessionLifecycle.closeCached(sessionKey);
|
|
286
183
|
}
|
|
287
184
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
let closing = this.sessionClosePromises.get(key);
|
|
291
|
-
while (closing) {
|
|
292
|
-
await closing;
|
|
293
|
-
closing = this.sessionClosePromises.get(key);
|
|
294
|
-
}
|
|
185
|
+
waitForSessionClose(sessionKey) {
|
|
186
|
+
return this.sessionLifecycle.waitForClose(sessionKey);
|
|
295
187
|
}
|
|
296
188
|
|
|
297
189
|
setConfig(config) {
|
|
298
|
-
|
|
190
|
+
this.sessionLifecycle.resetConfigState();
|
|
299
191
|
this.config = config;
|
|
300
|
-
this.pendingNewSessions.clear();
|
|
301
|
-
this.pendingSessionHandoffs.clear();
|
|
302
192
|
}
|
|
303
193
|
|
|
304
|
-
resetSession(chatId,
|
|
305
|
-
|
|
306
|
-
this.closeCachedSession(sessionKey);
|
|
307
|
-
this.pendingNewSessions.add(sessionKey);
|
|
308
|
-
const text = String(handoff || "").trim();
|
|
309
|
-
const parent = String(parentSession || "").trim();
|
|
310
|
-
if (text || parent) {
|
|
311
|
-
this.pendingSessionHandoffs.set(sessionKey, { text, parentSession: parent });
|
|
312
|
-
} else {
|
|
313
|
-
this.pendingSessionHandoffs.delete(sessionKey);
|
|
314
|
-
}
|
|
194
|
+
resetSession(chatId, options = {}) {
|
|
195
|
+
this.sessionLifecycle.resetSession(chatId, options);
|
|
315
196
|
}
|
|
316
197
|
|
|
317
198
|
clearSessionCache(chatId) {
|
|
318
|
-
this.
|
|
199
|
+
this.sessionLifecycle.closeCached(String(chatId));
|
|
319
200
|
}
|
|
320
201
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const base = { chatId };
|
|
324
|
-
try {
|
|
325
|
-
const stats = context.session.getSessionStats();
|
|
326
|
-
const retained = summarizeRetainedContext(context.session.messages);
|
|
327
|
-
return {
|
|
328
|
-
...base,
|
|
329
|
-
...retained,
|
|
330
|
-
tokens: stats.contextUsage?.tokens ?? null,
|
|
331
|
-
contextWindow: stats.contextUsage?.contextWindow ?? null,
|
|
332
|
-
percent: stats.contextUsage?.percent ?? null
|
|
333
|
-
};
|
|
334
|
-
} catch (error) {
|
|
335
|
-
return { ...base, error: error instanceof Error ? error.message : String(error) };
|
|
336
|
-
}
|
|
337
|
-
}));
|
|
338
|
-
return {
|
|
339
|
-
harness: "pi",
|
|
340
|
-
sessions: this.sessions.size,
|
|
341
|
-
closingSessions: this.sessionClosePromises.size,
|
|
342
|
-
contexts
|
|
343
|
-
};
|
|
202
|
+
getRuntimeDiagnostic() {
|
|
203
|
+
return this.sessionLifecycle.getDiagnostic();
|
|
344
204
|
}
|
|
345
205
|
|
|
346
206
|
createSessionManager(chatId, workspaceDir = arisaInstallDir, sessionRevision = 0) {
|
|
347
|
-
|
|
348
|
-
const sessionDir = getChatPiSessionsDir(sessionKey, sessionRevision);
|
|
349
|
-
if (this.pendingNewSessions.has(sessionKey)) {
|
|
350
|
-
this.logger?.log("agent", `starting new persisted session for chat ${sessionKey}`);
|
|
351
|
-
const handoff = this.pendingSessionHandoffs.get(sessionKey);
|
|
352
|
-
const sessionManager = SessionManager.create(
|
|
353
|
-
workspaceDir,
|
|
354
|
-
sessionDir,
|
|
355
|
-
handoff?.parentSession ? { parentSession: handoff.parentSession } : undefined
|
|
356
|
-
);
|
|
357
|
-
const operationalNotes = formatSessionStartOperationalNotes(loadSessionStartOperationalNotes());
|
|
358
|
-
if (operationalNotes) {
|
|
359
|
-
sessionManager.appendCustomMessageEntry(
|
|
360
|
-
"arisa-operational-notes",
|
|
361
|
-
operationalNotes,
|
|
362
|
-
false,
|
|
363
|
-
{ source: "session-start" }
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
if (handoff?.text) {
|
|
367
|
-
sessionManager.appendCustomMessageEntry(
|
|
368
|
-
"arisa-session-handoff",
|
|
369
|
-
handoff.text,
|
|
370
|
-
false,
|
|
371
|
-
{ source: "telegram-new" }
|
|
372
|
-
);
|
|
373
|
-
}
|
|
374
|
-
return { sessionManager, isNewSession: true };
|
|
375
|
-
}
|
|
376
|
-
this.logger?.log("agent", `recovering persisted session for chat ${sessionKey}`);
|
|
377
|
-
return { sessionManager: SessionManager.continueRecent(workspaceDir, sessionDir), isNewSession: false };
|
|
207
|
+
return this.sessionLifecycle.createSessionManager(chatId, workspaceDir, sessionRevision);
|
|
378
208
|
}
|
|
379
209
|
|
|
380
210
|
async validatePiAgent(config = this.config) {
|
|
@@ -525,10 +355,7 @@ export class AgentManager {
|
|
|
525
355
|
accessGuardTarget
|
|
526
356
|
};
|
|
527
357
|
this.sessions.set(sessionKey, ctx);
|
|
528
|
-
if (isNewSession)
|
|
529
|
-
this.pendingNewSessions.delete(sessionKey);
|
|
530
|
-
this.pendingSessionHandoffs.delete(sessionKey);
|
|
531
|
-
}
|
|
358
|
+
if (isNewSession) this.sessionLifecycle.completeNewSession(sessionKey);
|
|
532
359
|
return ctx;
|
|
533
360
|
}
|
|
534
361
|
|
|
@@ -547,12 +374,7 @@ export class AgentManager {
|
|
|
547
374
|
}
|
|
548
375
|
|
|
549
376
|
async close() {
|
|
550
|
-
|
|
551
|
-
this.sessions.clear();
|
|
552
|
-
await Promise.allSettled([
|
|
553
|
-
...this.sessionClosePromises.values(),
|
|
554
|
-
...contexts.map((context) => closeAgentSession(context.session))
|
|
555
|
-
]);
|
|
377
|
+
await this.sessionLifecycle.closeAll();
|
|
556
378
|
}
|
|
557
379
|
|
|
558
380
|
async runTool({ name, request, chatId, taskContext = null }) {
|
|
@@ -576,285 +398,13 @@ export class AgentManager {
|
|
|
576
398
|
}
|
|
577
399
|
|
|
578
400
|
createTools(telegram, chatId, policy = buildPiToolPolicy({ config: this.config, customToolNames: arisaToolNames })) {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
parameters: Type.Object({ query: Type.Optional(Type.String()) }),
|
|
587
|
-
execute: async (_id, params) => {
|
|
588
|
-
await this.toolRegistry.load();
|
|
589
|
-
const coreTools = getCoreCodingTools({
|
|
590
|
-
tools: policy.tools,
|
|
591
|
-
excludeTools: policy.excludeTools
|
|
592
|
-
});
|
|
593
|
-
const nativeTools = [{
|
|
594
|
-
name: "system_shell",
|
|
595
|
-
source: "arisa-native",
|
|
596
|
-
description: "Run native system shell commands in the active Arisa workspace.",
|
|
597
|
-
workspaceDir: policy.workspaceDir,
|
|
598
|
-
shell: policy.shell.shellPath || (process.platform === "win32" ? "powershell" : "sh"),
|
|
599
|
-
enabled: !(policy.excludeTools || []).includes("system_shell")
|
|
600
|
-
}];
|
|
601
|
-
const query = params.query?.trim() || "";
|
|
602
|
-
let catalogFallback = null;
|
|
603
|
-
const cliTools = query
|
|
604
|
-
? this.toolRegistry.search(query).map((tool) => ({
|
|
605
|
-
...tool,
|
|
606
|
-
source: "arisa-modular",
|
|
607
|
-
invocation: "run_tool"
|
|
608
|
-
}))
|
|
609
|
-
: (await this.toolRegistry.listWithRuntime(chatId)).map((tool) => ({
|
|
610
|
-
...tool,
|
|
611
|
-
source: "arisa-modular",
|
|
612
|
-
invocation: "run_tool"
|
|
613
|
-
}));
|
|
614
|
-
if (query && cliTools.length === 0) {
|
|
615
|
-
try {
|
|
616
|
-
catalogFallback = await searchOfficialToolCatalog(query);
|
|
617
|
-
} catch (error) {
|
|
618
|
-
catalogFallback = { unavailable: true, error: error?.message || String(error), matches: [] };
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
const result = {
|
|
622
|
-
query: query || null,
|
|
623
|
-
workspaceDir: policy.workspaceDir,
|
|
624
|
-
coreTools: query ? [] : coreTools,
|
|
625
|
-
nativeTools: query ? [] : nativeTools,
|
|
626
|
-
cliTools,
|
|
627
|
-
officialCatalogMatches: Array.isArray(catalogFallback) ? catalogFallback : catalogFallback?.matches || [],
|
|
628
|
-
catalogFallback: catalogFallback && !Array.isArray(catalogFallback) ? catalogFallback : null,
|
|
629
|
-
tools: query ? cliTools : [...coreTools.filter((tool) => tool.enabled), ...nativeTools.filter((tool) => tool.enabled), ...cliTools]
|
|
630
|
-
};
|
|
631
|
-
return {
|
|
632
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
633
|
-
details: result
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
}),
|
|
637
|
-
defineTool({
|
|
638
|
-
name: "tool_help",
|
|
639
|
-
label: "Tool help",
|
|
640
|
-
description: "Show --help text for a CLI tool.",
|
|
641
|
-
parameters: Type.Object({ name: Type.String() }),
|
|
642
|
-
execute: async (_id, params) => {
|
|
643
|
-
await this.toolRegistry.load();
|
|
644
|
-
const help = await this.toolRegistry.help(params.name);
|
|
645
|
-
return { content: [{ type: "text", text: help }], details: { help } };
|
|
646
|
-
}
|
|
647
|
-
}),
|
|
648
|
-
defineTool({
|
|
649
|
-
name: "tool_skills",
|
|
650
|
-
label: "Tool skills",
|
|
651
|
-
description: "Show skills assigned to a CLI tool via its manifest skillHints.",
|
|
652
|
-
parameters: Type.Object({ name: Type.String() }),
|
|
653
|
-
execute: async (_id, params) => {
|
|
654
|
-
await this.toolRegistry.load();
|
|
655
|
-
const skills = await this.toolRegistry.resolveSkills(params.name);
|
|
656
|
-
const visible = skills.map(({ content, ...item }) => item);
|
|
657
|
-
return { content: [{ type: "text", text: JSON.stringify(visible, null, 2) }], details: visible };
|
|
658
|
-
}
|
|
659
|
-
}),
|
|
660
|
-
defineTool({
|
|
661
|
-
name: "set_tool_config",
|
|
662
|
-
label: "Set tool config",
|
|
663
|
-
description: "Write a tool config value scoped to the current chat.",
|
|
664
|
-
parameters: Type.Object({ name: Type.String(), field: Type.String(), value: Type.String() }),
|
|
665
|
-
execute: async (_id, params) => {
|
|
666
|
-
await this.toolRegistry.load();
|
|
667
|
-
const result = await this.toolRegistry.setConfig(params.name, params.field, params.value, chatId);
|
|
668
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
669
|
-
}
|
|
670
|
-
}),
|
|
671
|
-
defineTool({
|
|
672
|
-
name: "set_tool_resource_note",
|
|
673
|
-
label: "Set tool resource note",
|
|
674
|
-
description: "Set or clear a deterministic chat-scoped note of up to 200 characters for one tool resource.",
|
|
675
|
-
parameters: Type.Object({
|
|
676
|
-
name: Type.String(),
|
|
677
|
-
resourceId: Type.String(),
|
|
678
|
-
note: Type.String()
|
|
679
|
-
}),
|
|
680
|
-
execute: async (_id, params) => {
|
|
681
|
-
const result = await this.resourceNotes.set(chatId, params.name, params.resourceId, params.note);
|
|
682
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
683
|
-
}
|
|
684
|
-
}),
|
|
685
|
-
defineTool({
|
|
686
|
-
name: "run_tool",
|
|
687
|
-
label: "Run tool",
|
|
688
|
-
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).",
|
|
689
|
-
parameters: Type.Object({
|
|
690
|
-
name: Type.String(),
|
|
691
|
-
artifactId: Type.Optional(Type.String()),
|
|
692
|
-
text: Type.Optional(Type.String()),
|
|
693
|
-
resourceId: Type.Optional(Type.String()),
|
|
694
|
-
args: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
695
|
-
deliver: Type.Optional(Type.Boolean())
|
|
696
|
-
}),
|
|
697
|
-
execute: async (_id, params) => {
|
|
698
|
-
let artifact = null;
|
|
699
|
-
if (params.artifactId) {
|
|
700
|
-
artifact = await chatArtifactStore.get(params.artifactId);
|
|
701
|
-
if (!artifact) {
|
|
702
|
-
return { content: [{ type: "text", text: `Artifact not found: ${params.artifactId}` }], details: { ok: false } };
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
const result = await this.runTool({
|
|
706
|
-
name: params.name,
|
|
707
|
-
request: {
|
|
708
|
-
artifact,
|
|
709
|
-
text: params.text,
|
|
710
|
-
resourceId: params.resourceId,
|
|
711
|
-
args: params.args || {}
|
|
712
|
-
},
|
|
713
|
-
chatId,
|
|
714
|
-
taskContext: telegram.getTaskContext()
|
|
715
|
-
});
|
|
716
|
-
|
|
717
|
-
if (params.deliver && result.output?.artifactId) {
|
|
718
|
-
const generated = await chatArtifactStore.get(result.output.artifactId);
|
|
719
|
-
if (generated?.path) {
|
|
720
|
-
result.sent = await deliverArtifactToChat({ artifact: generated, telegram, logger: this.logger });
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
return {
|
|
725
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
726
|
-
details: result
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
}),
|
|
730
|
-
defineTool({
|
|
731
|
-
name: "list_scheduled_tasks",
|
|
732
|
-
label: "List scheduled tasks",
|
|
733
|
-
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.",
|
|
734
|
-
parameters: Type.Object({
|
|
735
|
-
status: Type.Optional(Type.String()),
|
|
736
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: maxScheduledTaskListLimit }))
|
|
737
|
-
}),
|
|
738
|
-
execute: async (_id, params) => {
|
|
739
|
-
const tasks = await this.taskStore.list({ chatId, status: params.status });
|
|
740
|
-
const result = selectScheduledTasks(tasks, {
|
|
741
|
-
status: params.status,
|
|
742
|
-
limit: params.limit
|
|
743
|
-
});
|
|
744
|
-
return {
|
|
745
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
746
|
-
details: result
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
}),
|
|
750
|
-
defineTool({
|
|
751
|
-
name: "cancel_scheduled_task",
|
|
752
|
-
label: "Cancel scheduled task",
|
|
753
|
-
description: "Cancel one scheduled async task by id for the current Telegram chat.",
|
|
754
|
-
parameters: Type.Object({ id: Type.String() }),
|
|
755
|
-
execute: async (_id, params) => {
|
|
756
|
-
const existing = await this.taskStore.get(params.id);
|
|
757
|
-
if (!existing || existing.payload?.chatId !== chatId) {
|
|
758
|
-
return {
|
|
759
|
-
content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Task not found" }) }],
|
|
760
|
-
details: { ok: false, error: "Task not found" }
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
const task = await this.taskStore.cancel(params.id);
|
|
764
|
-
return {
|
|
765
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, task }, null, 2) }],
|
|
766
|
-
details: { ok: true, task }
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
}),
|
|
770
|
-
defineTool({
|
|
771
|
-
name: "cancel_all_scheduled_tasks",
|
|
772
|
-
label: "Cancel all scheduled tasks",
|
|
773
|
-
description: "Cancel all pending or running async tasks for the current Telegram chat.",
|
|
774
|
-
parameters: Type.Object({}),
|
|
775
|
-
execute: async () => {
|
|
776
|
-
const tasks = await this.taskStore.cancelAll({ chatId });
|
|
777
|
-
return {
|
|
778
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, cancelled: tasks.length }, null, 2) }],
|
|
779
|
-
details: { ok: true, tasks }
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
}),
|
|
783
|
-
defineTool({
|
|
784
|
-
name: "create_telegram_topic",
|
|
785
|
-
label: "Create Telegram topic",
|
|
786
|
-
description: "Create and initialize a new topic in the current owner-only Telegram forum. Topic names are dynamic, and context seeds the isolated session without copying unrelated history.",
|
|
787
|
-
parameters: Type.Object({
|
|
788
|
-
name: Type.String({ minLength: 1, maxLength: 128 }),
|
|
789
|
-
context: Type.String({ minLength: 1, maxLength: 4000 })
|
|
790
|
-
}),
|
|
791
|
-
execute: async (_id, params) => {
|
|
792
|
-
if (typeof telegram.createForumTopic !== "function") {
|
|
793
|
-
const result = { ok: false, error: "Telegram topic creation is unavailable in this chat." };
|
|
794
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
795
|
-
}
|
|
796
|
-
const result = await telegram.createForumTopic(params.name.trim(), params.context.trim());
|
|
797
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
798
|
-
}
|
|
799
|
-
}),
|
|
800
|
-
defineTool({
|
|
801
|
-
name: "initialize_telegram_topic",
|
|
802
|
-
label: "Initialize Telegram topic",
|
|
803
|
-
description: "Seed or replace the isolated context of an existing topic in the current owner-only Telegram forum.",
|
|
804
|
-
parameters: Type.Object({
|
|
805
|
-
messageThreadId: Type.Integer({ minimum: 2 }),
|
|
806
|
-
name: Type.String({ minLength: 1, maxLength: 128 }),
|
|
807
|
-
context: Type.String({ minLength: 1, maxLength: 4000 })
|
|
808
|
-
}),
|
|
809
|
-
execute: async (_id, params) => {
|
|
810
|
-
if (typeof telegram.initializeForumTopic !== "function") {
|
|
811
|
-
const result = { ok: false, error: "Telegram topic initialization is unavailable in this chat." };
|
|
812
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
|
|
813
|
-
}
|
|
814
|
-
const result = await telegram.initializeForumTopic({
|
|
815
|
-
messageThreadId: params.messageThreadId,
|
|
816
|
-
name: params.name.trim(),
|
|
817
|
-
context: params.context.trim()
|
|
818
|
-
});
|
|
819
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
820
|
-
}
|
|
821
|
-
}),
|
|
822
|
-
defineTool({
|
|
823
|
-
name: "send_artifact",
|
|
824
|
-
label: "Send artifact",
|
|
825
|
-
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.",
|
|
826
|
-
parameters: Type.Object({
|
|
827
|
-
artifactId: Type.String(),
|
|
828
|
-
caption: Type.Optional(Type.String()),
|
|
829
|
-
method: Type.Optional(Type.Union([
|
|
830
|
-
Type.Literal("voice"),
|
|
831
|
-
Type.Literal("audio"),
|
|
832
|
-
Type.Literal("document")
|
|
833
|
-
]))
|
|
834
|
-
}),
|
|
835
|
-
execute: async (_id, params) => {
|
|
836
|
-
const artifact = await chatArtifactStore.get(params.artifactId);
|
|
837
|
-
if (!artifact) {
|
|
838
|
-
const result = { ok: false, status: "failed", error: `Artifact not found: ${params.artifactId}` };
|
|
839
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
840
|
-
}
|
|
841
|
-
if (!artifact.path) {
|
|
842
|
-
const result = { ok: false, status: "failed", error: `Artifact ${params.artifactId} has no file to deliver.` };
|
|
843
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], details: result };
|
|
844
|
-
}
|
|
845
|
-
const sent = await deliverArtifactToChat({
|
|
846
|
-
artifact,
|
|
847
|
-
telegram,
|
|
848
|
-
caption: params.caption,
|
|
849
|
-
method: params.method,
|
|
850
|
-
logger: this.logger
|
|
851
|
-
});
|
|
852
|
-
return {
|
|
853
|
-
content: [{ type: "text", text: `Media sent to Telegram as ${sent.method}.` }],
|
|
854
|
-
details: { ok: true, sent }
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
})
|
|
858
|
-
];
|
|
401
|
+
return createPiCapabilityTools({
|
|
402
|
+
capabilityService: this.capabilityService,
|
|
403
|
+
telegram,
|
|
404
|
+
chatId,
|
|
405
|
+
policy,
|
|
406
|
+
logger: this.logger
|
|
407
|
+
});
|
|
859
408
|
}
|
|
409
|
+
|
|
860
410
|
}
|