arisa 5.1.24 → 5.1.49
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/package.json +2 -3
- package/src/core/agent/agent-manager.js +132 -70
- 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 +17 -18
- package/src/core/config/config-defaults.js +2 -2
- package/src/core/conversation/session-seed-store.js +85 -0
- 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 +145 -93
- 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 +390 -1034
- package/src/transport/telegram/chat-queue.js +132 -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 +372 -0
- package/src/transport/telegram/task-dispatcher.js +94 -0
- package/src/transport/telegram/update-command.js +1 -1
- package/src/transport/telegram/workspace-group.js +83 -0
- package/test/agent-tool-policy.test.js +7 -1
- package/test/context-and-task-bounds.test.js +9 -7
- package/test/doctor.test.js +2 -4
- 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/restart-receipt.test.js +39 -0
- package/test/session-start-operational-notes.test.js +47 -0
- package/test/telegram-prompt-builders.test.js +33 -0
- package/test/telegram-task-dispatcher.test.js +102 -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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arisa",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.49",
|
|
4
4
|
"description": "Telegram + Pi Agent modular assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -42,8 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": "0.80.6",
|
|
44
44
|
"@sinclair/typebox": "^0.34.41",
|
|
45
|
-
"grammy": "^1.42.0"
|
|
46
|
-
"typebox": "1.1.38"
|
|
45
|
+
"grammy": "^1.42.0"
|
|
47
46
|
},
|
|
48
47
|
"scripts": {
|
|
49
48
|
"start": "node src/index.js",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
4
|
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, defineTool } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { Type } from "@sinclair/typebox";
|
|
5
6
|
import { createPiRuntime, hasProviderAuth } from "./pi-runtime.js";
|
|
@@ -10,9 +11,10 @@ import { buildPiToolPolicy, getCoreCodingTools } from "./core-tools.js";
|
|
|
10
11
|
import { createSystemShellTool } from "./system-shell-tool.js";
|
|
11
12
|
import { clampModelThinkingLevel } from "./pi-runtime.js";
|
|
12
13
|
import { clampModelSpeed, createModelSpeedController } from "./model-speed.js";
|
|
13
|
-
import { arisaHomeDir, getChatPiSessionsDir } from "../../runtime/paths.js";
|
|
14
|
+
import { arisaHomeDir, getChatPiSessionsDir, sessionStartOperationalNotesFile } from "../../runtime/paths.js";
|
|
14
15
|
import { searchOfficialToolCatalog } from "../tools/official-tool-catalog.js";
|
|
15
16
|
import { ToolResourceNoteStore } from "../tools/tool-resource-note-store.js";
|
|
17
|
+
import { materializeToolOutput } from "../tools/tool-output-materializer.js";
|
|
16
18
|
|
|
17
19
|
const piValidationTimeoutMs = 60_000;
|
|
18
20
|
const arisaToolNames = [
|
|
@@ -25,33 +27,38 @@ const arisaToolNames = [
|
|
|
25
27
|
"list_scheduled_tasks",
|
|
26
28
|
"cancel_scheduled_task",
|
|
27
29
|
"cancel_all_scheduled_tasks",
|
|
30
|
+
"create_telegram_topic",
|
|
31
|
+
"initialize_telegram_topic",
|
|
28
32
|
"send_artifact"
|
|
29
33
|
];
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
+
}
|
|
39
54
|
}
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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");
|
|
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");
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
const estimatedImageTokens = 1_200;
|
|
@@ -102,6 +109,16 @@ function summarizeRetainedContext(messages = []) {
|
|
|
102
109
|
};
|
|
103
110
|
}
|
|
104
111
|
|
|
112
|
+
function guardTools(tools, accessGuard) {
|
|
113
|
+
return tools.map((tool) => ({
|
|
114
|
+
...tool,
|
|
115
|
+
execute: async (...args) => {
|
|
116
|
+
await accessGuard();
|
|
117
|
+
return tool.execute(...args);
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
|
|
105
122
|
function closeAgentSession(session) {
|
|
106
123
|
if (session?.close) return session.close();
|
|
107
124
|
if (session?.dispose) return session.dispose();
|
|
@@ -301,7 +318,7 @@ export class AgentManager {
|
|
|
301
318
|
this.closeCachedSession(String(chatId));
|
|
302
319
|
}
|
|
303
320
|
|
|
304
|
-
async getRuntimeDiagnostic(
|
|
321
|
+
async getRuntimeDiagnostic() {
|
|
305
322
|
const contexts = await Promise.all([...this.sessions.entries()].map(async ([chatId, context]) => {
|
|
306
323
|
const base = { chatId };
|
|
307
324
|
try {
|
|
@@ -322,7 +339,6 @@ export class AgentManager {
|
|
|
322
339
|
harness: "pi",
|
|
323
340
|
sessions: this.sessions.size,
|
|
324
341
|
closingSessions: this.sessionClosePromises.size,
|
|
325
|
-
managedProcessIds: [],
|
|
326
342
|
contexts
|
|
327
343
|
};
|
|
328
344
|
}
|
|
@@ -338,6 +354,15 @@ export class AgentManager {
|
|
|
338
354
|
sessionDir,
|
|
339
355
|
handoff?.parentSession ? { parentSession: handoff.parentSession } : undefined
|
|
340
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
|
+
}
|
|
341
366
|
if (handoff?.text) {
|
|
342
367
|
sessionManager.appendCustomMessageEntry(
|
|
343
368
|
"arisa-session-handoff",
|
|
@@ -388,7 +413,7 @@ export class AgentManager {
|
|
|
388
413
|
return this.validatePiAgent(config);
|
|
389
414
|
}
|
|
390
415
|
|
|
391
|
-
async getSessionContext(chatId, telegram) {
|
|
416
|
+
async getSessionContext(chatId, telegram, { scopeChatId = chatId, accessGuard = async () => {} } = {}) {
|
|
392
417
|
const sessionKey = String(chatId);
|
|
393
418
|
const modelSelection = resolveChatModelSelection(this.config, sessionKey);
|
|
394
419
|
const effectiveModelId = modelSelection.model;
|
|
@@ -406,6 +431,8 @@ export class AgentManager {
|
|
|
406
431
|
this.logger?.log("agent", `updating speed for chat ${sessionKey}: ${existing.speedController.speed}x -> ${desiredSpeed}x`);
|
|
407
432
|
existing.speedController.setSpeed(desiredSpeed);
|
|
408
433
|
}
|
|
434
|
+
existing.telegramTarget.current = telegram;
|
|
435
|
+
existing.accessGuardTarget.current = accessGuard;
|
|
409
436
|
this.logger?.log("agent", `reusing session for chat ${sessionKey}`);
|
|
410
437
|
return existing;
|
|
411
438
|
}
|
|
@@ -438,10 +465,26 @@ export class AgentManager {
|
|
|
438
465
|
);
|
|
439
466
|
const hasExistingSession = sessionManager.buildSessionContext().messages.length > 0;
|
|
440
467
|
this.logger?.log("agent", `${hasExistingSession ? "resuming" : "creating"} session for chat ${sessionKey} with model ${effectiveModelId} effort ${thinkingLevel} speed ${speed}x`);
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
468
|
+
const telegramTarget = { current: telegram };
|
|
469
|
+
const accessGuardTarget = { current: accessGuard };
|
|
470
|
+
const telegramProxy = {
|
|
471
|
+
sendMedia: (...args) => telegramTarget.current.sendMedia(...args),
|
|
472
|
+
createForumTopic: (...args) => telegramTarget.current.createForumTopic(...args),
|
|
473
|
+
initializeForumTopic: (...args) => telegramTarget.current.initializeForumTopic(...args),
|
|
474
|
+
prepareRestartReceipt: (...args) => telegramTarget.current.prepareRestartReceipt(...args),
|
|
475
|
+
cancelRestartReceipt: (...args) => telegramTarget.current.cancelRestartReceipt(...args),
|
|
476
|
+
getTaskContext: (...args) => telegramTarget.current.getTaskContext?.(...args) || null
|
|
477
|
+
};
|
|
478
|
+
const assertAccess = () => accessGuardTarget.current();
|
|
479
|
+
const customTools = guardTools([
|
|
480
|
+
...this.createTools(telegramProxy, scopeChatId, policy),
|
|
481
|
+
createSystemShellTool({
|
|
482
|
+
workspaceDir: policy.workspaceDir,
|
|
483
|
+
shell: policy.shell,
|
|
484
|
+
beforeRestart: (summary) => telegramProxy.prepareRestartReceipt(summary),
|
|
485
|
+
cancelRestart: (receiptId) => telegramProxy.cancelRestartReceipt(receiptId)
|
|
486
|
+
})
|
|
487
|
+
], assertAccess);
|
|
445
488
|
const settingsManager = createPiSettingsManager(this.config);
|
|
446
489
|
const resourceLoader = await createArisaResourceLoader({
|
|
447
490
|
cwd: policy.workspaceDir,
|
|
@@ -473,7 +516,14 @@ export class AgentManager {
|
|
|
473
516
|
})}`);
|
|
474
517
|
}
|
|
475
518
|
|
|
476
|
-
const ctx = {
|
|
519
|
+
const ctx = {
|
|
520
|
+
session,
|
|
521
|
+
modelId: effectiveModelId,
|
|
522
|
+
modelKey: effectiveModelKey,
|
|
523
|
+
speedController,
|
|
524
|
+
telegramTarget,
|
|
525
|
+
accessGuardTarget
|
|
526
|
+
};
|
|
477
527
|
this.sessions.set(sessionKey, ctx);
|
|
478
528
|
if (isNewSession) {
|
|
479
529
|
this.pendingNewSessions.delete(sessionKey);
|
|
@@ -505,10 +555,9 @@ export class AgentManager {
|
|
|
505
555
|
]);
|
|
506
556
|
}
|
|
507
557
|
|
|
508
|
-
async runTool({ name, request, chatId }) {
|
|
558
|
+
async runTool({ name, request, chatId, taskContext = null }) {
|
|
509
559
|
await this.toolRegistry.load();
|
|
510
560
|
this.logger?.log("agent", `run_tool ${name}`);
|
|
511
|
-
const chatArtifactStore = this.artifactStore.forChat(chatId);
|
|
512
561
|
const resourceId = String(request?.resourceId || "").trim();
|
|
513
562
|
const resourceNote = resourceId
|
|
514
563
|
? await this.resourceNotes.get(chatId, name, resourceId)
|
|
@@ -516,41 +565,14 @@ export class AgentManager {
|
|
|
516
565
|
const enrichedRequest = resourceNote ? { ...request, resourceId, resourceNote } : request;
|
|
517
566
|
const result = await this.toolRegistry.run({ name, request: enrichedRequest, chatId });
|
|
518
567
|
|
|
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;
|
|
568
|
+
return materializeToolOutput({
|
|
569
|
+
result,
|
|
570
|
+
name,
|
|
571
|
+
chatId,
|
|
572
|
+
artifactStore: this.artifactStore,
|
|
573
|
+
taskStore: this.taskStore,
|
|
574
|
+
taskContext
|
|
575
|
+
});
|
|
554
576
|
}
|
|
555
577
|
|
|
556
578
|
createTools(telegram, chatId, policy = buildPiToolPolicy({ config: this.config, customToolNames: arisaToolNames })) {
|
|
@@ -688,7 +710,8 @@ export class AgentManager {
|
|
|
688
710
|
resourceId: params.resourceId,
|
|
689
711
|
args: params.args || {}
|
|
690
712
|
},
|
|
691
|
-
chatId
|
|
713
|
+
chatId,
|
|
714
|
+
taskContext: telegram.getTaskContext()
|
|
692
715
|
});
|
|
693
716
|
|
|
694
717
|
if (params.deliver && result.output?.artifactId) {
|
|
@@ -757,6 +780,45 @@ export class AgentManager {
|
|
|
757
780
|
};
|
|
758
781
|
}
|
|
759
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
|
+
}),
|
|
760
822
|
defineTool({
|
|
761
823
|
name: "send_artifact",
|
|
762
824
|
label: "Send artifact",
|
|
@@ -101,11 +101,3 @@ export function clampModelThinkingLevel(model, level) {
|
|
|
101
101
|
export function modelSupportsThinking(model) {
|
|
102
102
|
return listModelThinkingLevels(model).some((level) => level !== "off");
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
export function findPiModel({ provider, model, apiKey } = {}) {
|
|
106
|
-
const runtime = createPiRuntime({ provider, apiKey });
|
|
107
|
-
return {
|
|
108
|
-
...runtime,
|
|
109
|
-
model: provider && model ? runtime.modelRegistry.find(provider, model) : null
|
|
110
|
-
};
|
|
111
|
-
}
|
|
@@ -164,25 +164,36 @@ async function runShellCommand({ command, cwd, shellPath, timeoutMs }) {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
export function
|
|
167
|
+
export function isArisaRestartCommand(command) {
|
|
168
|
+
return /(?:^|[;&|]\s*)arisa\s+restart(?:\s*(?:$|[;&|]))/i.test(String(command || ""));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function createSystemShellTool({ workspaceDir, shell = {}, beforeRestart, cancelRestart }) {
|
|
168
172
|
return defineTool({
|
|
169
173
|
name: "system_shell",
|
|
170
174
|
label: "System Shell",
|
|
171
175
|
description: "Run a command in the active Arisa workspace using the native system shell: PowerShell on Windows, and sh/bash-compatible shell on Unix.",
|
|
172
176
|
parameters: Type.Object({
|
|
173
177
|
command: Type.String({ description: "Command to execute in the active workspace." }),
|
|
174
|
-
timeoutMs: Type.Optional(Type.Number({ description: "Optional timeout in milliseconds for this command." }))
|
|
178
|
+
timeoutMs: Type.Optional(Type.Number({ description: "Optional timeout in milliseconds for this command." })),
|
|
179
|
+
restartSummary: Type.Optional(Type.String({ maxLength: 500, description: "Concrete user-facing result to report after an arisa restart succeeds." }))
|
|
175
180
|
}),
|
|
176
181
|
execute: async (_id, params) => {
|
|
177
182
|
const timeoutMs = Number.isFinite(Number(params.timeoutMs)) && Number(params.timeoutMs) > 0
|
|
178
183
|
? Math.floor(Number(params.timeoutMs))
|
|
179
184
|
: (shell.timeoutMs || defaultTimeoutMs);
|
|
185
|
+
const restartReceipt = isArisaRestartCommand(params.command) && typeof beforeRestart === "function"
|
|
186
|
+
? await beforeRestart(params.restartSummary)
|
|
187
|
+
: null;
|
|
180
188
|
const result = await runShellCommand({
|
|
181
189
|
command: params.command,
|
|
182
190
|
cwd: workspaceDir,
|
|
183
191
|
shellPath: shell.shellPath,
|
|
184
192
|
timeoutMs
|
|
185
193
|
});
|
|
194
|
+
if (!result.ok && restartReceipt?.id && typeof cancelRestart === "function") {
|
|
195
|
+
await cancelRestart(restartReceipt.id).catch(() => {});
|
|
196
|
+
}
|
|
186
197
|
const details = {
|
|
187
198
|
stdout: result.stdout,
|
|
188
199
|
stderr: result.stderr,
|
|
@@ -86,14 +86,14 @@ class ChatArtifactStore {
|
|
|
86
86
|
return artifact;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
async
|
|
89
|
+
async createFileArtifact({ fileName, kind, mimeType, source, metadata = {}, writeFileContent }) {
|
|
90
90
|
await this.init();
|
|
91
91
|
await this.reload();
|
|
92
92
|
const artifactId = id();
|
|
93
93
|
const dir = path.join(this.rootDir, artifactId);
|
|
94
94
|
await mkdir(dir, { recursive: true });
|
|
95
95
|
const destPath = path.join(dir, fileName);
|
|
96
|
-
await
|
|
96
|
+
await writeFileContent(destPath);
|
|
97
97
|
const artifact = {
|
|
98
98
|
id: artifactId,
|
|
99
99
|
chatId: this.chatId,
|
|
@@ -109,27 +109,26 @@ class ChatArtifactStore {
|
|
|
109
109
|
return artifact;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
async createFromFile({ originalPath, fileName, kind, mimeType, source, metadata = {} }) {
|
|
113
|
+
return this.createFileArtifact({
|
|
114
|
+
fileName,
|
|
115
|
+
kind,
|
|
116
|
+
mimeType,
|
|
117
|
+
source,
|
|
118
|
+
metadata,
|
|
119
|
+
writeFileContent: (destPath) => copyArtifactFile(originalPath, destPath, mimeType)
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
112
123
|
async createGeneratedFile({ fileName, content, kind, mimeType, source, metadata = {} }) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const artifactId = id();
|
|
116
|
-
const dir = path.join(this.rootDir, artifactId);
|
|
117
|
-
await mkdir(dir, { recursive: true });
|
|
118
|
-
const destPath = path.join(dir, fileName);
|
|
119
|
-
await writeArtifactFile(destPath, content);
|
|
120
|
-
const artifact = {
|
|
121
|
-
id: artifactId,
|
|
122
|
-
chatId: this.chatId,
|
|
124
|
+
return this.createFileArtifact({
|
|
125
|
+
fileName,
|
|
123
126
|
kind,
|
|
124
127
|
mimeType,
|
|
125
|
-
path: destPath,
|
|
126
128
|
source,
|
|
127
129
|
metadata,
|
|
128
|
-
|
|
129
|
-
};
|
|
130
|
-
this.items.push(artifact);
|
|
131
|
-
await this.saveIndex();
|
|
132
|
-
return artifact;
|
|
130
|
+
writeFileContent: (destPath) => writeArtifactFile(destPath, content)
|
|
131
|
+
});
|
|
133
132
|
}
|
|
134
133
|
|
|
135
134
|
async get(artifactId) {
|
|
@@ -18,11 +18,11 @@ export const daemonConfigDefaults = Object.freeze({
|
|
|
18
18
|
|
|
19
19
|
export const telegramConfigDefaults = Object.freeze({
|
|
20
20
|
modelPickerPageSize: 8,
|
|
21
|
-
busyMessageMode: "steer"
|
|
21
|
+
busyMessageMode: "steer",
|
|
22
|
+
ownerWorkspaceGroups: Object.freeze({})
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
export const doctorConfigDefaults = Object.freeze({
|
|
25
|
-
contextInspectionTimeoutMs: 5_000,
|
|
26
26
|
contextWarningPercent: 70,
|
|
27
27
|
contextCriticalPercent: 90,
|
|
28
28
|
contextInefficientMinTokens: 32_000,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
getChatLegacyConversationHistoryFile,
|
|
5
|
+
getChatSessionSeedFile
|
|
6
|
+
} from "../../runtime/paths.js";
|
|
7
|
+
|
|
8
|
+
function parseRecords(contents) {
|
|
9
|
+
return String(contents || "")
|
|
10
|
+
.replace(/^\uFEFF/, "")
|
|
11
|
+
.split(/\r?\n/)
|
|
12
|
+
.filter(Boolean)
|
|
13
|
+
.map((line) => JSON.parse(line));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function seedText(record) {
|
|
17
|
+
return String(record?.context || record?.history || "").trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class SessionSeedStore {
|
|
21
|
+
constructor({
|
|
22
|
+
seedFile = getChatSessionSeedFile,
|
|
23
|
+
legacyFile = getChatLegacyConversationHistoryFile
|
|
24
|
+
} = {}) {
|
|
25
|
+
this.seedFile = seedFile;
|
|
26
|
+
this.legacyFile = legacyFile;
|
|
27
|
+
this.locks = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async withChatLock(chatId, work) {
|
|
31
|
+
const key = String(chatId);
|
|
32
|
+
const previous = this.locks.get(key) || Promise.resolve();
|
|
33
|
+
const current = previous.catch(() => {}).then(work);
|
|
34
|
+
this.locks.set(key, current);
|
|
35
|
+
try {
|
|
36
|
+
return await current;
|
|
37
|
+
} finally {
|
|
38
|
+
if (this.locks.get(key) === current) this.locks.delete(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async write(chatId, record = null) {
|
|
43
|
+
const file = this.seedFile(chatId);
|
|
44
|
+
await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
45
|
+
const body = record ? `${JSON.stringify(record)}\n` : "";
|
|
46
|
+
await writeFile(file, body, { encoding: "utf8", mode: 0o600 });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async set(chatId, context) {
|
|
50
|
+
const normalized = String(context || "").trim();
|
|
51
|
+
return this.withChatLock(chatId, async () => {
|
|
52
|
+
await this.write(chatId, normalized ? { kind: "seed", context: normalized } : null);
|
|
53
|
+
return Boolean(normalized);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async clear(chatId) {
|
|
58
|
+
return this.withChatLock(chatId, () => this.write(chatId));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async readPendingRecords(chatId) {
|
|
62
|
+
try {
|
|
63
|
+
return parseRecords(await readFile(this.seedFile(chatId), "utf8"));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error?.code !== "ENOENT") throw error;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
return parseRecords(await readFile(this.legacyFile(chatId), "utf8"));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error?.code === "ENOENT") return [];
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async consume(chatId) {
|
|
76
|
+
return this.withChatLock(chatId, async () => {
|
|
77
|
+
const records = await this.readPendingRecords(chatId);
|
|
78
|
+
const pendingSeed = records.length === 1 && records[0]?.kind === "seed"
|
|
79
|
+
? seedText(records[0])
|
|
80
|
+
: "";
|
|
81
|
+
await this.write(chatId);
|
|
82
|
+
return pendingSeed;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -56,7 +56,6 @@ function requestIpc({ socketPath, request, timeoutMs = DEFAULT_TIMEOUT_MS }) {
|
|
|
56
56
|
export function createArisaClient({
|
|
57
57
|
toolName,
|
|
58
58
|
chatId = null,
|
|
59
|
-
capabilityToken = process.env.ARISA_IPC_TOKEN || "",
|
|
60
59
|
socketPath = process.env.ARISA_IPC_SOCKET || arisaIpcSocketFile
|
|
61
60
|
} = {}) {
|
|
62
61
|
if (typeof toolName !== "string" || !toolName.trim()) {
|
|
@@ -71,7 +70,6 @@ export function createArisaClient({
|
|
|
71
70
|
method,
|
|
72
71
|
toolName,
|
|
73
72
|
chatId,
|
|
74
|
-
capabilityToken,
|
|
75
73
|
params
|
|
76
74
|
}
|
|
77
75
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
export async function materializeToolOutput({ result, name, chatId, artifactStore, taskStore, taskContext = null }) {
|
|
5
|
+
const chatArtifactStore = artifactStore.forChat(chatId);
|
|
6
|
+
|
|
7
|
+
if (result.output?.text) {
|
|
8
|
+
const artifact = await chatArtifactStore.createText({
|
|
9
|
+
text: result.output.text,
|
|
10
|
+
source: { type: "tool", toolName: name },
|
|
11
|
+
metadata: { tool: name }
|
|
12
|
+
});
|
|
13
|
+
result.output.artifactId = artifact.id;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (result.output?.filePath) {
|
|
17
|
+
const generated = await chatArtifactStore.createFromFile({
|
|
18
|
+
originalPath: result.output.filePath,
|
|
19
|
+
fileName: result.output.fileName || path.basename(result.output.filePath),
|
|
20
|
+
kind: result.output.kind || "file",
|
|
21
|
+
mimeType: result.output.mimeType || "application/octet-stream",
|
|
22
|
+
source: { type: "tool", toolName: name },
|
|
23
|
+
metadata: { tool: name, delivery: result.output.delivery }
|
|
24
|
+
});
|
|
25
|
+
result.output.artifactId = generated.id;
|
|
26
|
+
await unlink(result.output.filePath).catch(() => {});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (result.asyncTask || result.asyncTasks?.length) {
|
|
30
|
+
result.asyncTasks = await taskStore.addMany(result.asyncTasks || [result.asyncTask], {
|
|
31
|
+
payload: {
|
|
32
|
+
chatId,
|
|
33
|
+
...(taskContext ? { telegramContext: taskContext } : {})
|
|
34
|
+
},
|
|
35
|
+
source: { type: "tool", toolName: name, chatId }
|
|
36
|
+
});
|
|
37
|
+
delete result.asyncTask;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|