arisa 4.2.4 → 4.2.8

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.2.4",
3
+ "version": "4.2.8",
4
4
  "description": "Telegram + Pi Agent modular assistant",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -37,7 +37,7 @@
37
37
  },
38
38
  "homepage": "https://arisa.sh",
39
39
  "dependencies": {
40
- "@earendil-works/pi-coding-agent": "^0.79.9",
40
+ "@earendil-works/pi-coding-agent": "^0.80.6",
41
41
  "@sinclair/typebox": "^0.34.41",
42
42
  "grammy": "^1.42.0"
43
43
  },
@@ -1,6 +1,12 @@
1
1
  allowBuilds:
2
2
  '@google/genai': false
3
3
 
4
+ minimumReleaseAgeExclude:
5
+ - '@earendil-works/pi-agent-core@0.80.6'
6
+ - '@earendil-works/pi-ai@0.80.6'
7
+ - '@earendil-works/pi-coding-agent@0.80.6'
8
+ - '@earendil-works/pi-tui@0.80.6'
9
+
4
10
  overrides:
5
11
  '@protobufjs/utf8@<=1.1.0': '>=1.1.1'
6
12
  basic-ftp@<=5.2.1: '>=5.2.2'
@@ -78,11 +78,11 @@ export function buildPiAuthTelegramMessage({ config, issue = null, verified = fa
78
78
  if (verified) {
79
79
  lines.push("No action needed.");
80
80
  } else if (!issue) {
81
- lines.push("Run `/auth` to validate these credentials against the provider.");
81
+ lines.push("Run /auth to validate these credentials against the provider.");
82
82
  } else if (status.hasApiKey) {
83
83
  lines.push("A Pi API key is configured, but the provider rejected the current request. Update the key and restart Arisa.");
84
84
  } else if (status.supportsOAuth) {
85
- lines.push("Run `/auth` here in Telegram to renew the Pi login.");
85
+ lines.push("Run /auth here in Telegram to renew the Pi login.");
86
86
  } else {
87
87
  lines.push("This provider needs a Pi API key. Re-run `arisa --bootstrap`, provide a key, and restart Arisa.");
88
88
  }
@@ -103,7 +103,7 @@ export function buildPiAuthRecoveryBlockedMessage({ config, issue = null, renewa
103
103
 
104
104
  lines.push(renewalActive
105
105
  ? "A Pi login is already in progress. Paste the redirect URL or code here when the provider gives it to you."
106
- : "Send `/auth` to start Pi login from Telegram.");
106
+ : "Send /auth to start Pi login from Telegram.");
107
107
 
108
108
  return lines.join("\n");
109
109
  }
@@ -6,6 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import { Bot } from "grammy";
7
7
  import { createPiOAuthLogin } from "../core/agent/pi-auth-login.js";
8
8
  import { createPiRuntime, hasProviderAuth, listPiProviders, listProviderModels, supportsProviderOAuth } from "../core/agent/pi-runtime.js";
9
+ import { buildDeviceCodeTelegramMessage } from "../transport/telegram/device-code-message.js";
9
10
  import { configFile, ensureArisaHome } from "./paths.js";
10
11
 
11
12
  const ARISA_BANNER = [
@@ -424,8 +425,8 @@ async function runTelegramBootstrap({ telegramApiKey, setupToken, botInfo }) {
424
425
  ].join("\n"));
425
426
  },
426
427
  onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
427
- const expiry = expiresInSeconds ? `\nExpires in ${Math.round(expiresInSeconds / 60)} minute(s).` : "";
428
- await sendSetupMessage(`Open this URL: ${verificationUri}\nThen enter code: ${userCode}${expiry}`);
428
+ const { text, ...options } = buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds });
429
+ await sendSetupMessage(text, options);
429
430
  },
430
431
  onPrompt: async ({ message, controller }) => {
431
432
  await sendSetupMessage(`${message}\nReply here with the value.`);
@@ -2,6 +2,7 @@ import { Bot, InputFile } from "grammy";
2
2
  import path from "node:path";
3
3
  import { authorizeChat } from "./auth.js";
4
4
  import { captureIncomingArtifact, formatLocationText } from "./media.js";
5
+ import { buildDeviceCodeTelegramMessage } from "./device-code-message.js";
5
6
  import { renderTelegramHtml } from "./text-format.js";
6
7
  import { buildPiAuthRecoveryBlockedMessage, buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue, getPiAuthStatus } from "../../core/agent/auth-flow.js";
7
8
  import { createPiOAuthLogin } from "../../core/agent/pi-auth-login.js";
@@ -365,8 +366,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
365
366
  ].join("\n"));
366
367
  },
367
368
  onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
368
- const expiry = expiresInSeconds ? `\nExpires in ${Math.round(expiresInSeconds / 60)} minute(s).` : "";
369
- await bot.api.sendMessage(chatId, `Open this URL: ${verificationUri}\nThen enter code: ${userCode}${expiry}`);
369
+ const payload = buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds });
370
+ const { text, ...options } = payload;
371
+ await bot.api.sendMessage(chatId, text, options);
370
372
  },
371
373
  onPrompt: async ({ message, controller }) => {
372
374
  await bot.api.sendMessage(chatId, `${message}\nReply here with the value.`);
@@ -0,0 +1,48 @@
1
+ import { escapeHtml } from "./text-format.js";
2
+
3
+ const maxCopyTextLength = 256;
4
+ const maxButtonLabelLength = 64;
5
+
6
+ function truncateButtonLabel(text = "") {
7
+ const source = String(text || "").trim();
8
+ if (source.length <= maxButtonLabelLength) return source;
9
+ return `${source.slice(0, maxButtonLabelLength - 1)}…`;
10
+ }
11
+
12
+ export function buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds } = {}) {
13
+ const code = String(userCode || "").trim();
14
+ const uri = String(verificationUri || "").trim();
15
+ const expiry = expiresInSeconds
16
+ ? `\nExpires in ${Math.round(expiresInSeconds / 60)} minute(s).`
17
+ : "";
18
+
19
+ const lines = [
20
+ "Open this URL:",
21
+ uri ? `<a href="${escapeHtml(uri)}">${escapeHtml(uri)}</a>` : "",
22
+ "Then enter code:",
23
+ code ? `<code>${escapeHtml(code)}</code>` : "",
24
+ escapeHtml(expiry.trim())
25
+ ].filter(Boolean);
26
+
27
+ const payload = {
28
+ text: lines.join("\n"),
29
+ parse_mode: "HTML",
30
+ link_preview_options: { is_disabled: true }
31
+ };
32
+
33
+ const buttons = [];
34
+ if (code && code.length <= maxCopyTextLength) {
35
+ buttons.push({
36
+ text: truncateButtonLabel(`Copy code: ${code}`),
37
+ copy_text: { text: code }
38
+ });
39
+ }
40
+ if (uri) {
41
+ buttons.push({ text: "Open login page", url: uri });
42
+ }
43
+ if (buttons.length) {
44
+ payload.reply_markup = { inline_keyboard: [buttons] };
45
+ }
46
+
47
+ return payload;
48
+ }
@@ -1,4 +1,4 @@
1
- function escapeHtml(text = "") {
1
+ export function escapeHtml(text = "") {
2
2
  return text
3
3
  .replace(/&/g, "&amp;")
4
4
  .replace(/</g, "&lt;")
@@ -0,0 +1,42 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { buildDeviceCodeTelegramMessage } from "../src/transport/telegram/device-code-message.js";
4
+
5
+ test("builds a copyable device-code Telegram message with HTML and inline buttons", () => {
6
+ const payload = buildDeviceCodeTelegramMessage({
7
+ userCode: "TR2S-K4GBZ",
8
+ verificationUri: "https://auth.openai.com/codex/device",
9
+ expiresInSeconds: 900
10
+ });
11
+
12
+ assert.equal(payload.parse_mode, "HTML");
13
+ assert.match(payload.text, /https:\/\/auth\.openai\.com\/codex\/device/);
14
+ assert.match(payload.text, /<code>TR2S-K4GBZ<\/code>/);
15
+ assert.match(payload.text, /Expires in 15 minute\(s\)\./);
16
+ assert.deepEqual(payload.reply_markup.inline_keyboard, [[
17
+ { text: "Copy code: TR2S-K4GBZ", copy_text: { text: "TR2S-K4GBZ" } },
18
+ { text: "Open login page", url: "https://auth.openai.com/codex/device" }
19
+ ]]);
20
+ assert.deepEqual(payload.link_preview_options, { is_disabled: true });
21
+ });
22
+
23
+ test("escapes HTML-sensitive device-code values", () => {
24
+ const payload = buildDeviceCodeTelegramMessage({
25
+ userCode: "A&B<1>",
26
+ verificationUri: "https://example.com/?q=\"x\""
27
+ });
28
+
29
+ assert.match(payload.text, /<code>A&amp;B&lt;1&gt;<\/code>/);
30
+ assert.match(payload.text, /href="https:\/\/example\.com\/\?q=&quot;x&quot;"/);
31
+ assert.equal(payload.reply_markup.inline_keyboard[0][0].copy_text.text, "A&B<1>");
32
+ });
33
+
34
+ test("omits copy button when code exceeds Telegram copy_text limit", () => {
35
+ const payload = buildDeviceCodeTelegramMessage({
36
+ userCode: "x".repeat(257),
37
+ verificationUri: "https://example.com/device"
38
+ });
39
+
40
+ assert.equal(payload.reply_markup.inline_keyboard[0].length, 1);
41
+ assert.equal(payload.reply_markup.inline_keyboard[0][0].text, "Open login page");
42
+ });