arisa 4.0.0 → 4.0.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "4.0.0",
3
+ "version": "4.0.3",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -0,0 +1,94 @@
1
+ import { createPiRuntime, hasProviderAuth, supportsProviderOAuth } from "./pi-runtime.js";
2
+
3
+ const authInvalidatedPatterns = [
4
+ /authentication token has been invalidated/i,
5
+ /token (?:has been )?invalidated/i,
6
+ /try signing in again/i,
7
+ /auth(?:entication)? token (?:expired|revoked|invalid)/i
8
+ ];
9
+
10
+ const missingAuthPatterns = [
11
+ /no auth found/i,
12
+ /auth(?:entication)? .*missing/i
13
+ ];
14
+
15
+ export function getErrorMessage(error) {
16
+ return error instanceof Error ? error.message : String(error);
17
+ }
18
+
19
+ export function getPiAuthIssue(error) {
20
+ const message = getErrorMessage(error);
21
+ if (!message) return null;
22
+
23
+ if (authInvalidatedPatterns.some((pattern) => pattern.test(message))) {
24
+ return { kind: "invalidated-token", message };
25
+ }
26
+
27
+ if (missingAuthPatterns.some((pattern) => pattern.test(message))) {
28
+ return { kind: "missing-auth", message };
29
+ }
30
+
31
+ return null;
32
+ }
33
+
34
+ export function getPiAuthStatus(config) {
35
+ const runtime = createPiRuntime({
36
+ provider: config.pi.provider,
37
+ apiKey: config.pi.apiKey
38
+ });
39
+
40
+ return {
41
+ provider: config.pi.provider,
42
+ model: config.pi.model,
43
+ hasApiKey: Boolean(config.pi.apiKey),
44
+ hasStoredAuth: hasProviderAuth(config.pi.provider, runtime),
45
+ supportsOAuth: supportsProviderOAuth(config.pi.provider, runtime)
46
+ };
47
+ }
48
+
49
+ export function buildPiAuthTelegramMessage({ config, issue = null, verified = false }) {
50
+ const status = getPiAuthStatus(config);
51
+ let title = `Pi authentication status for ${status.provider}/${status.model}.`;
52
+ if (issue) {
53
+ title = `Pi authentication needs attention for ${status.provider}/${status.model}.`;
54
+ } else if (verified) {
55
+ title = `Pi authentication is working for ${status.provider}/${status.model}.`;
56
+ }
57
+ const lines = [title];
58
+
59
+ if (issue?.kind === "invalidated-token") {
60
+ lines.push("The provider says the current authentication token was invalidated.");
61
+ } else if (issue?.kind === "missing-auth") {
62
+ lines.push("Arisa could not find usable authentication for this provider.");
63
+ } else if (issue?.kind === "validation-failed") {
64
+ lines.push("Arisa could not validate the current Pi authentication.");
65
+ } else if (verified) {
66
+ lines.push(status.hasApiKey
67
+ ? "The configured Pi API key was accepted by the provider."
68
+ : "The stored auth was accepted by the provider.");
69
+ } else {
70
+ lines.push(`Stored auth record: ${status.hasStoredAuth ? "detected" : "not detected"}.`);
71
+ lines.push("This only checks whether credentials are stored, not whether the provider will accept them.");
72
+ }
73
+
74
+ if (issue?.message) {
75
+ lines.push(`Details: ${issue.message}`);
76
+ }
77
+
78
+ if (verified) {
79
+ lines.push("No action needed.");
80
+ } else if (!issue) {
81
+ lines.push("Run `/auth` to validate these credentials against the provider.");
82
+ } else if (status.hasApiKey) {
83
+ lines.push("A Pi API key is configured, but the provider rejected the current request. Update the key and restart Arisa.");
84
+ } else if (status.supportsOAuth) {
85
+ lines.push("For now, re-run `arisa --bootstrap` on the host and complete Pi login again.");
86
+ } else {
87
+ lines.push("This provider needs a Pi API key. Re-run `arisa --bootstrap`, provide a key, and restart Arisa.");
88
+ }
89
+
90
+ if (issue) {
91
+ lines.push("Telegram-based renewal is not wired yet, but this /auth path is ready for that flow.");
92
+ }
93
+ return lines.join("\n");
94
+ }
@@ -64,7 +64,12 @@ export async function createApp({ logger, runtimeOverrides, webhookUrl, setHttpR
64
64
  return {
65
65
  async start() {
66
66
  logger?.log("app", `validating Pi model ${config.pi.provider}/${config.pi.model}`);
67
- await agentManager.validatePiAgent();
67
+ try {
68
+ await agentManager.validatePiAgent();
69
+ } catch (error) {
70
+ await bot.notifyPiAuthIssue?.(error);
71
+ throw error;
72
+ }
68
73
  await toolProcessSupervisor.start();
69
74
  logger?.log("app", "starting Telegram bot");
70
75
  try {
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { authorizeChat } from "./auth.js";
4
4
  import { captureIncomingArtifact } from "./media.js";
5
5
  import { renderTelegramHtml } from "./text-format.js";
6
+ import { buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue } from "../../core/agent/auth-flow.js";
6
7
  import { normalizeArtifactForReasoning, shouldNormalizeArtifactToText } from "../../core/artifacts/normalize-for-reasoning.js";
7
8
 
8
9
  const slowPromptNoticeMs = 300_000;
@@ -185,6 +186,7 @@ function sessionEventLogMessage(event) {
185
186
 
186
187
  async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {}) {
187
188
  let text = "";
189
+ let assistantErrorMessage = "";
188
190
  let shouldSeparateAssistantMessage = false;
189
191
  let slowPromptTimer = null;
190
192
  const unsubscribe = session.subscribe((event) => {
@@ -198,6 +200,9 @@ async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {
198
200
  }
199
201
  text += event.assistantMessageEvent.delta;
200
202
  }
203
+ if (event.type === "message_end" && event.message?.stopReason === "error") {
204
+ assistantErrorMessage = event.message.errorMessage || "assistant message ended with error";
205
+ }
201
206
  const logMessage = sessionEventLogMessage(event);
202
207
  if (logMessage) logger?.log("agent", `chat ${chatId} ${logMessage}`);
203
208
  });
@@ -218,6 +223,10 @@ async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {
218
223
  unsubscribe();
219
224
  }
220
225
 
226
+ if (assistantErrorMessage) {
227
+ throw new Error(assistantErrorMessage);
228
+ }
229
+
221
230
  return text.trim();
222
231
  }
223
232
 
@@ -237,8 +246,31 @@ async function withTyping(ctx, work) {
237
246
  export async function createTelegramBot({ config, artifactStore, toolRegistry, taskStore, agentManager, saveConfig, updateConfig, logger, webhookUrl, setHttpRequestHandler }) {
238
247
  const bot = new Bot(config.telegram.token);
239
248
  const perChatState = new Map();
249
+ const notifiedPromptErrors = new WeakSet();
240
250
  let taskTimer = null;
241
251
 
252
+ function wasPromptErrorNotified(error) {
253
+ return error instanceof Error && notifiedPromptErrors.has(error);
254
+ }
255
+
256
+ function markPromptErrorNotified(error) {
257
+ if (error instanceof Error) notifiedPromptErrors.add(error);
258
+ }
259
+
260
+ async function notifyPiAuthIssueIfNeeded(chatId, error) {
261
+ const issue = getPiAuthIssue(error);
262
+ if (!issue) return false;
263
+
264
+ try {
265
+ await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, issue }));
266
+ markPromptErrorNotified(error);
267
+ return true;
268
+ } catch (notifyError) {
269
+ logger?.error("telegram", `auth issue notice failed for chat ${chatId}: ${getErrorMessage(notifyError)}`);
270
+ return false;
271
+ }
272
+ }
273
+
242
274
  function getIncomingChatMeta(ctx) {
243
275
  return {
244
276
  languageCode: ctx.from?.language_code || "",
@@ -358,8 +390,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
358
390
  logger?.log("telegram", `prompt dispatch for chat ${chatId}`);
359
391
  await processPromptForChat({ chatId, prompt: currentPrompt, ctx: currentCtx });
360
392
  } catch (error) {
361
- const message = error instanceof Error ? error.message : String(error);
393
+ const message = getErrorMessage(error);
362
394
  logger?.error("telegram", `${label} failed for chat ${chatId}: ${message}`);
395
+ await notifyPiAuthIssueIfNeeded(chatId, error);
363
396
  throw error;
364
397
  } finally {
365
398
  currentCtx = null;
@@ -493,6 +526,20 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
493
526
  await handleNewCommand(ctx);
494
527
  });
495
528
 
529
+ bot.command("auth", async (ctx) => {
530
+ const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
531
+ if (!auth.ok) return;
532
+ await withTyping(ctx, async () => {
533
+ try {
534
+ await agentManager.validatePiAgent();
535
+ await ctx.reply(buildPiAuthTelegramMessage({ config, verified: true }));
536
+ } catch (error) {
537
+ const issue = getPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
538
+ await ctx.reply(buildPiAuthTelegramMessage({ config, issue }));
539
+ }
540
+ });
541
+ });
542
+
496
543
  bot.on("message", async (ctx) => {
497
544
  const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
498
545
  if (!auth.ok) return;
@@ -505,8 +552,11 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
505
552
  } catch (error) {
506
553
  const chatState = getChatState(ctx.chat.id);
507
554
  chatState.processing = false;
508
- const message = error instanceof Error ? error.message : String(error);
509
- await ctx.reply(message);
555
+ if (wasPromptErrorNotified(error)) return;
556
+ const issue = getPiAuthIssue(error);
557
+ await ctx.reply(issue
558
+ ? buildPiAuthTelegramMessage({ config, issue })
559
+ : getErrorMessage(error));
510
560
  }
511
561
  });
512
562
 
@@ -534,7 +584,8 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
534
584
  }
535
585
  }
536
586
  await bot.api.setMyCommands([
537
- { command: "new", description: "Start a new chat context" }
587
+ { command: "new", description: "Start a new chat context" },
588
+ { command: "auth", description: "Show Pi authentication status" }
538
589
  ]);
539
590
  if (!taskTimer) {
540
591
  taskTimer = setInterval(() => {
@@ -572,6 +623,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
572
623
  try {
573
624
  bot.stop();
574
625
  } catch {}
626
+ },
627
+
628
+ async notifyPiAuthIssue(error) {
629
+ let notified = false;
630
+ for (const chatId of config.telegram.authorizedChatIds || []) {
631
+ notified = await notifyPiAuthIssueIfNeeded(chatId, error) || notified;
632
+ }
633
+ return notified;
575
634
  }
576
635
  };
577
636
  }