arisa 4.0.0 → 4.0.2

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.2",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -0,0 +1,79 @@
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 }) {
50
+ const status = getPiAuthStatus(config);
51
+ const lines = [
52
+ issue
53
+ ? `Pi authentication needs attention for ${status.provider}/${status.model}.`
54
+ : `Pi authentication status for ${status.provider}/${status.model}.`
55
+ ];
56
+
57
+ if (issue?.kind === "invalidated-token") {
58
+ lines.push("The provider says the current authentication token was invalidated.");
59
+ } else if (issue?.kind === "missing-auth") {
60
+ lines.push("Arisa could not find usable authentication for this provider.");
61
+ } else {
62
+ lines.push(`Stored auth: ${status.hasStoredAuth ? "detected" : "not detected"}.`);
63
+ }
64
+
65
+ if (issue?.message) {
66
+ lines.push(`Details: ${issue.message}`);
67
+ }
68
+
69
+ if (status.hasApiKey) {
70
+ lines.push("A Pi API key is configured, but the provider rejected the current request. Update the key and restart Arisa.");
71
+ } else if (status.supportsOAuth) {
72
+ lines.push("For now, re-run `arisa --bootstrap` on the host and complete Pi login again.");
73
+ } else {
74
+ lines.push("This provider needs a Pi API key. Re-run `arisa --bootstrap`, provide a key, and restart Arisa.");
75
+ }
76
+
77
+ lines.push("Telegram-based renewal is not wired yet, but this /auth path is ready for that flow.");
78
+ return lines.join("\n");
79
+ }
@@ -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,12 @@ 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 ctx.reply(buildPiAuthTelegramMessage({ config }));
533
+ });
534
+
496
535
  bot.on("message", async (ctx) => {
497
536
  const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
498
537
  if (!auth.ok) return;
@@ -505,8 +544,11 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
505
544
  } catch (error) {
506
545
  const chatState = getChatState(ctx.chat.id);
507
546
  chatState.processing = false;
508
- const message = error instanceof Error ? error.message : String(error);
509
- await ctx.reply(message);
547
+ if (wasPromptErrorNotified(error)) return;
548
+ const issue = getPiAuthIssue(error);
549
+ await ctx.reply(issue
550
+ ? buildPiAuthTelegramMessage({ config, issue })
551
+ : getErrorMessage(error));
510
552
  }
511
553
  });
512
554
 
@@ -534,7 +576,8 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
534
576
  }
535
577
  }
536
578
  await bot.api.setMyCommands([
537
- { command: "new", description: "Start a new chat context" }
579
+ { command: "new", description: "Start a new chat context" },
580
+ { command: "auth", description: "Show Pi authentication status" }
538
581
  ]);
539
582
  if (!taskTimer) {
540
583
  taskTimer = setInterval(() => {
@@ -572,6 +615,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
572
615
  try {
573
616
  bot.stop();
574
617
  } catch {}
618
+ },
619
+
620
+ async notifyPiAuthIssue(error) {
621
+ let notified = false;
622
+ for (const chatId of config.telegram.authorizedChatIds || []) {
623
+ notified = await notifyPiAuthIssueIfNeeded(chatId, error) || notified;
624
+ }
625
+ return notified;
575
626
  }
576
627
  };
577
628
  }