arisa 4.0.5 → 4.0.6
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
|
@@ -82,13 +82,28 @@ export function buildPiAuthTelegramMessage({ config, issue = null, verified = fa
|
|
|
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("
|
|
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
|
}
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function buildPiAuthRecoveryBlockedMessage({ config, issue = null, renewalActive = false }) {
|
|
94
|
+
const status = getPiAuthStatus(config);
|
|
95
|
+
const lines = [
|
|
96
|
+
`Pi authentication is not ready for ${status.provider}/${status.model}.`,
|
|
97
|
+
"I did not send your message to the agent."
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
if (issue?.message) {
|
|
101
|
+
lines.push(`Details: ${issue.message}`);
|
|
92
102
|
}
|
|
103
|
+
|
|
104
|
+
lines.push(renewalActive
|
|
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.");
|
|
107
|
+
|
|
93
108
|
return lines.join("\n");
|
|
94
109
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export function createPiOAuthLogin({ provider, onAuth, onDeviceCode, onPrompt, onProgress, onSelect } = {}) {
|
|
4
|
+
const authStorage = AuthStorage.create();
|
|
5
|
+
const oauthProvider = authStorage.getOAuthProviders().find((item) => item.id === provider);
|
|
6
|
+
if (!oauthProvider) {
|
|
7
|
+
throw new Error(`No internal OAuth login flow is available for ${provider}.`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let resolveManualCode;
|
|
11
|
+
const manualCodePromise = new Promise((resolve) => {
|
|
12
|
+
resolveManualCode = resolve;
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const controller = {
|
|
16
|
+
provider,
|
|
17
|
+
oauthProvider,
|
|
18
|
+
manualInputRequested: false,
|
|
19
|
+
submitManualCode(value) {
|
|
20
|
+
if (!resolveManualCode) return false;
|
|
21
|
+
controller.manualInputRequested = false;
|
|
22
|
+
resolveManualCode(String(value || "").trim());
|
|
23
|
+
resolveManualCode = null;
|
|
24
|
+
return true;
|
|
25
|
+
},
|
|
26
|
+
waitForManualCode() {
|
|
27
|
+
controller.manualInputRequested = true;
|
|
28
|
+
return manualCodePromise;
|
|
29
|
+
},
|
|
30
|
+
promise: null
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
controller.promise = authStorage.login(provider, {
|
|
34
|
+
onAuth: async (params) => {
|
|
35
|
+
await onAuth?.({ ...params, controller });
|
|
36
|
+
},
|
|
37
|
+
onDeviceCode: async (params) => {
|
|
38
|
+
await onDeviceCode?.({ ...params, controller });
|
|
39
|
+
},
|
|
40
|
+
onPrompt: async (params) => {
|
|
41
|
+
if (!onPrompt) return "";
|
|
42
|
+
return onPrompt({ ...params, controller });
|
|
43
|
+
},
|
|
44
|
+
onProgress: (message) => {
|
|
45
|
+
onProgress?.(message);
|
|
46
|
+
},
|
|
47
|
+
onSelect: async (params) => {
|
|
48
|
+
if (!onSelect) return params.options?.[0]?.id;
|
|
49
|
+
return onSelect({ ...params, controller });
|
|
50
|
+
},
|
|
51
|
+
onManualCodeInput: () => controller.waitForManualCode()
|
|
52
|
+
}).finally(() => {
|
|
53
|
+
controller.submitManualCode("");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return controller;
|
|
57
|
+
}
|
package/src/runtime/bootstrap.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import readline from "node:readline/promises";
|
|
3
3
|
import { stdin as input, stdout as output } from "node:process";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import {
|
|
5
|
+
import { createPiOAuthLogin } from "../core/agent/pi-auth-login.js";
|
|
6
6
|
import { createPiRuntime, hasProviderAuth, listPiProviders, listProviderModels, supportsProviderOAuth } from "../core/agent/pi-runtime.js";
|
|
7
7
|
import { configFile, ensureArisaHome } from "./paths.js";
|
|
8
8
|
|
|
@@ -194,37 +194,19 @@ function installAuthRelay(httpPort, setHttpRequestHandler) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
async function runInternalPiLogin(provider, { rl = null, authRelay = null } = {}) {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
throw new Error(`No internal OAuth login flow is available for ${provider}.`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
let manualCodeResolve;
|
|
204
|
-
let manualCodeReject;
|
|
205
|
-
const manualCodePromise = new Promise((resolve, reject) => {
|
|
206
|
-
manualCodeResolve = resolve;
|
|
207
|
-
manualCodeReject = reject;
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
await authStorage.login(provider, {
|
|
211
|
-
onAuth: async ({ url, instructions }) => {
|
|
197
|
+
const login = createPiOAuthLogin({
|
|
198
|
+
provider,
|
|
199
|
+
onAuth: async ({ url, instructions, controller }) => {
|
|
212
200
|
console.log(`${instructions || "Open this URL to continue authentication:"}\n${url}\n`);
|
|
213
201
|
await maybeOpenExternal(url);
|
|
214
202
|
if (authRelay) {
|
|
215
203
|
authRelay.setAuthUrl(url);
|
|
216
204
|
console.log("Waiting for authentication via the web relay...");
|
|
217
205
|
const redirectUrl = await authRelay.waitForRedirectUrl();
|
|
218
|
-
if (redirectUrl
|
|
219
|
-
|
|
220
|
-
manualCodeResolve = undefined;
|
|
221
|
-
}
|
|
222
|
-
} else if (selected.usesCallbackServer && rl) {
|
|
206
|
+
if (redirectUrl) controller.submitManualCode(redirectUrl);
|
|
207
|
+
} else if (controller.oauthProvider.usesCallbackServer && rl) {
|
|
223
208
|
const pasted = (await rl.question("Paste the redirect URL here if the browser does not return automatically, or press Enter to keep waiting: ")).trim();
|
|
224
|
-
if (pasted
|
|
225
|
-
manualCodeResolve(pasted);
|
|
226
|
-
manualCodeResolve = undefined;
|
|
227
|
-
}
|
|
209
|
+
if (pasted) controller.submitManualCode(pasted);
|
|
228
210
|
}
|
|
229
211
|
},
|
|
230
212
|
onDeviceCode: async ({ userCode, verificationUri }) => {
|
|
@@ -240,15 +222,9 @@ async function runInternalPiLogin(provider, { rl = null, authRelay = null } = {}
|
|
|
240
222
|
},
|
|
241
223
|
onProgress: (message) => {
|
|
242
224
|
console.log(message);
|
|
243
|
-
},
|
|
244
|
-
onManualCodeInput: () => manualCodePromise,
|
|
245
|
-
}).finally(() => {
|
|
246
|
-
if (manualCodeResolve) {
|
|
247
|
-
manualCodeResolve("");
|
|
248
|
-
manualCodeResolve = undefined;
|
|
249
225
|
}
|
|
250
|
-
manualCodeReject = undefined;
|
|
251
226
|
});
|
|
227
|
+
await login.promise;
|
|
252
228
|
}
|
|
253
229
|
|
|
254
230
|
export async function bootstrapIfNeeded({ force = false, cliConfigOverrides = {}, httpPort = 0, setHttpRequestHandler } = {}) {
|
|
@@ -3,7 +3,8 @@ 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
|
+
import { buildPiAuthRecoveryBlockedMessage, buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue, getPiAuthStatus } from "../../core/agent/auth-flow.js";
|
|
7
|
+
import { createPiOAuthLogin } from "../../core/agent/pi-auth-login.js";
|
|
7
8
|
import { normalizeArtifactForReasoning, shouldNormalizeArtifactToText } from "../../core/artifacts/normalize-for-reasoning.js";
|
|
8
9
|
|
|
9
10
|
const slowPromptNoticeMs = 300_000;
|
|
@@ -247,8 +248,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
247
248
|
const bot = new Bot(config.telegram.token);
|
|
248
249
|
const perChatState = new Map();
|
|
249
250
|
const notifiedPromptErrors = new WeakSet();
|
|
251
|
+
const authRenewals = new Map();
|
|
252
|
+
let piAuthIssue = null;
|
|
250
253
|
let taskTimer = null;
|
|
251
254
|
|
|
255
|
+
function chatKey(chatId) {
|
|
256
|
+
return String(chatId);
|
|
257
|
+
}
|
|
258
|
+
|
|
252
259
|
function wasPromptErrorNotified(error) {
|
|
253
260
|
return error instanceof Error && notifiedPromptErrors.has(error);
|
|
254
261
|
}
|
|
@@ -257,8 +264,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
257
264
|
if (error instanceof Error) notifiedPromptErrors.add(error);
|
|
258
265
|
}
|
|
259
266
|
|
|
260
|
-
|
|
267
|
+
function rememberPiAuthIssue(error) {
|
|
261
268
|
const issue = getPiAuthIssue(error);
|
|
269
|
+
if (issue) piAuthIssue = issue;
|
|
270
|
+
return issue;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function notifyPiAuthIssueIfNeeded(chatId, error) {
|
|
274
|
+
const issue = rememberPiAuthIssue(error);
|
|
262
275
|
if (!issue) return false;
|
|
263
276
|
|
|
264
277
|
try {
|
|
@@ -271,6 +284,84 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
271
284
|
}
|
|
272
285
|
}
|
|
273
286
|
|
|
287
|
+
function selectTelegramLoginOption(options = []) {
|
|
288
|
+
return options.find((option) => /device/i.test(`${option.id} ${option.label}`))
|
|
289
|
+
|| options.find((option) => /browser|oauth|web/i.test(`${option.id} ${option.label}`))
|
|
290
|
+
|| options[0]
|
|
291
|
+
|| null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function finishAuthRenewal(chatId, renewal) {
|
|
295
|
+
try {
|
|
296
|
+
await renewal.promise;
|
|
297
|
+
await agentManager.validatePiAgent();
|
|
298
|
+
agentManager.clearSessionCache(chatId);
|
|
299
|
+
piAuthIssue = null;
|
|
300
|
+
logger?.log("telegram", `Pi auth renewal completed for chat ${chatId}`);
|
|
301
|
+
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, verified: true }));
|
|
302
|
+
} catch (error) {
|
|
303
|
+
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
304
|
+
piAuthIssue = issue;
|
|
305
|
+
logger?.error("telegram", `Pi auth renewal failed for chat ${chatId}: ${getErrorMessage(error)}`);
|
|
306
|
+
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, issue })).catch((notifyError) => {
|
|
307
|
+
logger?.error("telegram", `auth renewal failure notice failed for chat ${chatId}: ${getErrorMessage(notifyError)}`);
|
|
308
|
+
});
|
|
309
|
+
} finally {
|
|
310
|
+
authRenewals.delete(chatKey(chatId));
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function startAuthRenewal(chatId) {
|
|
315
|
+
const key = chatKey(chatId);
|
|
316
|
+
const existing = authRenewals.get(key);
|
|
317
|
+
if (existing) {
|
|
318
|
+
return { started: false, renewal: existing };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const renewal = createPiOAuthLogin({
|
|
322
|
+
provider: config.pi.provider,
|
|
323
|
+
onSelect: async ({ message, options }) => {
|
|
324
|
+
const selected = selectTelegramLoginOption(options);
|
|
325
|
+
if (!selected) return undefined;
|
|
326
|
+
logger?.log("telegram", `Pi auth option for chat ${chatId}: ${selected.id}`);
|
|
327
|
+
await bot.api.sendMessage(chatId, `${message}\nUsing: ${selected.label || selected.id}`);
|
|
328
|
+
return selected.id;
|
|
329
|
+
},
|
|
330
|
+
onAuth: async ({ url, instructions }) => {
|
|
331
|
+
await bot.api.sendMessage(chatId, [
|
|
332
|
+
instructions || "Open this URL to continue Pi authentication:",
|
|
333
|
+
url,
|
|
334
|
+
"After login, paste the full redirect URL back here."
|
|
335
|
+
].join("\n"));
|
|
336
|
+
},
|
|
337
|
+
onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
|
|
338
|
+
const expiry = expiresInSeconds ? `\nExpires in ${Math.round(expiresInSeconds / 60)} minute(s).` : "";
|
|
339
|
+
await bot.api.sendMessage(chatId, `Open this URL: ${verificationUri}\nThen enter code: ${userCode}${expiry}`);
|
|
340
|
+
},
|
|
341
|
+
onPrompt: async ({ message, controller }) => {
|
|
342
|
+
await bot.api.sendMessage(chatId, `${message}\nReply here with the value.`);
|
|
343
|
+
return controller.waitForManualCode();
|
|
344
|
+
},
|
|
345
|
+
onProgress: (message) => {
|
|
346
|
+
if (message) logger?.log("telegram", `Pi auth progress for chat ${chatId}: ${message}`);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
authRenewals.set(key, renewal);
|
|
351
|
+
finishAuthRenewal(chatId, renewal);
|
|
352
|
+
return { started: true, renewal };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function submitAuthRenewalInput(ctx) {
|
|
356
|
+
const renewal = authRenewals.get(chatKey(ctx.chat.id));
|
|
357
|
+
const text = getIncomingMessageText(ctx.message).trim();
|
|
358
|
+
if (!renewal || !renewal.manualInputRequested || !text) return false;
|
|
359
|
+
|
|
360
|
+
if (!renewal.submitManualCode(text)) return false;
|
|
361
|
+
await ctx.reply("Got it. Finishing Pi login now...");
|
|
362
|
+
return true;
|
|
363
|
+
}
|
|
364
|
+
|
|
274
365
|
function getIncomingChatMeta(ctx) {
|
|
275
366
|
return {
|
|
276
367
|
languageCode: ctx.from?.language_code || "",
|
|
@@ -523,22 +614,48 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
523
614
|
bot.command("new", async (ctx) => {
|
|
524
615
|
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
525
616
|
if (!auth.ok) return;
|
|
617
|
+
if (piAuthIssue) {
|
|
618
|
+
await ctx.reply(buildPiAuthRecoveryBlockedMessage({
|
|
619
|
+
config,
|
|
620
|
+
issue: piAuthIssue,
|
|
621
|
+
renewalActive: authRenewals.has(chatKey(ctx.chat.id))
|
|
622
|
+
}));
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
526
625
|
await handleNewCommand(ctx);
|
|
527
626
|
});
|
|
528
627
|
|
|
529
628
|
bot.command("auth", async (ctx) => {
|
|
530
629
|
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
531
630
|
if (!auth.ok) return;
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
631
|
+
|
|
632
|
+
const status = getPiAuthStatus(config);
|
|
633
|
+
if (status.hasApiKey || !status.supportsOAuth) {
|
|
634
|
+
await withTyping(ctx, async () => {
|
|
635
|
+
try {
|
|
636
|
+
await agentManager.validatePiAgent();
|
|
637
|
+
agentManager.clearSessionCache(ctx.chat.id);
|
|
638
|
+
piAuthIssue = null;
|
|
639
|
+
await ctx.reply(buildPiAuthTelegramMessage({ config, verified: true }));
|
|
640
|
+
} catch (error) {
|
|
641
|
+
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
642
|
+
piAuthIssue = issue;
|
|
643
|
+
await ctx.reply(buildPiAuthTelegramMessage({ config, issue }));
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
try {
|
|
650
|
+
const { started } = await startAuthRenewal(ctx.chat.id);
|
|
651
|
+
await ctx.reply(started
|
|
652
|
+
? "Starting Pi login from Telegram..."
|
|
653
|
+
: "Pi login is already in progress. Paste the redirect URL or code here when you have it.");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
656
|
+
piAuthIssue = issue;
|
|
657
|
+
await ctx.reply(buildPiAuthTelegramMessage({ config, issue }));
|
|
658
|
+
}
|
|
542
659
|
});
|
|
543
660
|
|
|
544
661
|
bot.on("message", async (ctx) => {
|
|
@@ -548,6 +665,17 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
548
665
|
const command = getTelegramCommand(ctx);
|
|
549
666
|
if (command) return;
|
|
550
667
|
|
|
668
|
+
if (await submitAuthRenewalInput(ctx)) return;
|
|
669
|
+
|
|
670
|
+
if (piAuthIssue) {
|
|
671
|
+
await ctx.reply(buildPiAuthRecoveryBlockedMessage({
|
|
672
|
+
config,
|
|
673
|
+
issue: piAuthIssue,
|
|
674
|
+
renewalActive: authRenewals.has(chatKey(ctx.chat.id))
|
|
675
|
+
}));
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
551
679
|
try {
|
|
552
680
|
await enqueueOrProcess(ctx);
|
|
553
681
|
} catch (error) {
|