@vascend/talos 0.1.5 → 0.1.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/dist/core/agent.d.ts +16 -3
- package/dist/core/agent.js +114 -34
- package/dist/core/background.d.ts +10 -3
- package/dist/core/background.js +55 -9
- package/dist/core/bootstrap.js +1 -0
- package/dist/core/compaction.js +1 -1
- package/dist/core/config-storage.d.ts +5 -0
- package/dist/core/config-storage.js +83 -0
- package/dist/core/config.d.ts +11 -2
- package/dist/core/config.js +24 -8
- package/dist/core/context-ledger.js +2 -1
- package/dist/core/context-policy.d.ts +1 -1
- package/dist/core/context-policy.js +10 -7
- package/dist/core/credentials.js +29 -30
- package/dist/core/effort.d.ts +1 -1
- package/dist/core/effort.js +5 -1
- package/dist/core/execution-guidance.d.ts +1 -1
- package/dist/core/execution-guidance.js +2 -0
- package/dist/core/file-coordination.d.ts +3 -0
- package/dist/core/file-coordination.js +28 -0
- package/dist/core/file-lease-store.d.ts +3 -0
- package/dist/core/file-lease-store.js +28 -12
- package/dist/core/fusion.d.ts +24 -0
- package/dist/core/fusion.js +80 -0
- package/dist/core/model-limits.d.ts +1 -0
- package/dist/core/model-limits.js +5 -1
- package/dist/core/permission-queue.js +15 -15
- package/dist/core/request-plan.js +4 -3
- package/dist/core/session-naming.js +4 -0
- package/dist/core/session.d.ts +4 -2
- package/dist/core/session.js +13 -4
- package/dist/core/settings.d.ts +2 -0
- package/dist/core/settings.js +5 -16
- package/dist/core/step-policy.d.ts +18 -0
- package/dist/core/step-policy.js +6 -0
- package/dist/core/tool-artifacts.d.ts +2 -0
- package/dist/core/tool-artifacts.js +21 -3
- package/dist/core/tool-result-index.js +29 -33
- package/dist/core/tool-result-store.js +6 -5
- package/dist/core/types.d.ts +10 -3
- package/dist/core/user-inbox.d.ts +9 -0
- package/dist/core/user-inbox.js +22 -3
- package/dist/core/user-input.js +1 -1
- package/dist/fusion/autopilot-fast.d.ts +59 -0
- package/dist/fusion/autopilot-fast.js +213 -0
- package/dist/fusion/autopilot.d.ts +75 -0
- package/dist/fusion/autopilot.js +308 -0
- package/dist/fusion/focus.d.ts +30 -0
- package/dist/fusion/focus.js +122 -0
- package/dist/fusion/gate.d.ts +30 -0
- package/dist/fusion/gate.js +95 -0
- package/dist/fusion/jev.d.ts +71 -0
- package/dist/fusion/jev.js +164 -0
- package/dist/fusion/receipts.d.ts +25 -0
- package/dist/fusion/receipts.js +34 -0
- package/dist/fusion/route.d.ts +25 -0
- package/dist/fusion/route.js +84 -0
- package/dist/fusion/run.d.ts +4 -0
- package/dist/fusion/run.js +186 -0
- package/dist/horizon/engine.d.ts +20 -1
- package/dist/horizon/engine.js +57 -0
- package/dist/horizon/index.d.ts +2 -2
- package/dist/horizon/index.js +1 -1
- package/dist/horizon/types.d.ts +29 -0
- package/dist/index.js +12 -0
- package/dist/providers/anthropic-oauth.d.ts +8 -3
- package/dist/providers/anthropic-oauth.js +13 -10
- package/dist/providers/anthropic.d.ts +9 -2
- package/dist/providers/anthropic.js +64 -21
- package/dist/providers/cli-models.d.ts +30 -0
- package/dist/providers/cli-models.js +194 -0
- package/dist/providers/model-effort.js +1 -1
- package/dist/providers/openai-oauth.d.ts +8 -3
- package/dist/providers/openai-oauth.js +9 -3
- package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
- package/dist/providers/openrouter-manual-fallback.js +20 -20
- package/dist/providers/registry.js +3 -2
- package/dist/providers/retry.d.ts +3 -0
- package/dist/providers/retry.js +44 -1
- package/dist/remote/cifratura.d.ts +12 -0
- package/dist/remote/cifratura.js +66 -0
- package/dist/remote/index.d.ts +15 -0
- package/dist/remote/index.js +65 -0
- package/dist/remote/relay-client.d.ts +58 -0
- package/dist/remote/relay-client.js +315 -0
- package/dist/remote/sessione-remota.d.ts +60 -0
- package/dist/remote/sessione-remota.js +266 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +3 -1
- package/dist/tools/builtin.js +41 -20
- package/dist/tools/capabilities.d.ts +3 -1
- package/dist/tools/capabilities.js +5 -2
- package/dist/tools/computer-use.js +2 -1
- package/dist/tools/pagination.js +1 -1
- package/dist/tools/program.js +34 -21
- package/dist/tools/team-tools.js +11 -6
- package/dist/tui/account-entry.js +14 -9
- package/dist/tui/agent-label.d.ts +2 -0
- package/dist/tui/agent-label.js +5 -0
- package/dist/tui/agent-sessions-screen.d.ts +12 -2
- package/dist/tui/agent-sessions-screen.js +62 -16
- package/dist/tui/agent-sessions.d.ts +14 -0
- package/dist/tui/agent-sessions.js +67 -6
- package/dist/tui/bottom-input.d.ts +46 -2
- package/dist/tui/bottom-input.js +358 -59
- package/dist/tui/branding.js +8 -4
- package/dist/tui/capability-lists.d.ts +10 -0
- package/dist/tui/capability-lists.js +41 -0
- package/dist/tui/command-output-registry.js +4 -0
- package/dist/tui/command-transcript.d.ts +1 -1
- package/dist/tui/command-transcript.js +17 -21
- package/dist/tui/commands.js +4 -1
- package/dist/tui/compaction-status.d.ts +7 -0
- package/dist/tui/compaction-status.js +39 -0
- package/dist/tui/config-recovery.d.ts +12 -0
- package/dist/tui/config-recovery.js +54 -0
- package/dist/tui/context-meter.d.ts +3 -0
- package/dist/tui/context-meter.js +35 -8
- package/dist/tui/diff.d.ts +9 -0
- package/dist/tui/diff.js +43 -19
- package/dist/tui/fusion-menu.d.ts +5 -0
- package/dist/tui/fusion-menu.js +89 -0
- package/dist/tui/i18n-catalog.js +312 -1
- package/dist/tui/live-command.d.ts +1 -0
- package/dist/tui/live-command.js +9 -0
- package/dist/tui/live-output.d.ts +1 -0
- package/dist/tui/live-output.js +11 -0
- package/dist/tui/model-details.js +13 -12
- package/dist/tui/model-picker.js +17 -7
- package/dist/tui/model-selection.js +19 -10
- package/dist/tui/native-scrollback.js +5 -0
- package/dist/tui/prompt-queue.d.ts +3 -0
- package/dist/tui/prompt-queue.js +19 -1
- package/dist/tui/provider-label.d.ts +2 -0
- package/dist/tui/provider-label.js +9 -0
- package/dist/tui/provider-menu.js +25 -24
- package/dist/tui/queue-screen.js +2 -1
- package/dist/tui/remote-command.d.ts +7 -0
- package/dist/tui/remote-command.js +79 -0
- package/dist/tui/repl.d.ts +8 -2
- package/dist/tui/repl.js +355 -311
- package/dist/tui/review-store.d.ts +1 -0
- package/dist/tui/review-store.js +28 -7
- package/dist/tui/review-team.js +4 -3
- package/dist/tui/runtime-error.d.ts +2 -0
- package/dist/tui/runtime-error.js +26 -0
- package/dist/tui/selector-mouse.d.ts +1 -0
- package/dist/tui/selector-mouse.js +1 -1
- package/dist/tui/selector.d.ts +7 -0
- package/dist/tui/selector.js +19 -2
- package/dist/tui/session-controls.d.ts +26 -0
- package/dist/tui/session-controls.js +65 -0
- package/dist/tui/session-naming-notice.js +5 -3
- package/dist/tui/session-replay.d.ts +1 -0
- package/dist/tui/session-replay.js +101 -32
- package/dist/tui/session-transcript-layout.d.ts +9 -0
- package/dist/tui/session-transcript-layout.js +41 -0
- package/dist/tui/startup.js +63 -33
- package/dist/tui/status-line.d.ts +1 -1
- package/dist/tui/status-line.js +20 -12
- package/dist/tui/talos-art-data.d.ts +1 -0
- package/dist/tui/talos-art-data.js +2 -0
- package/dist/tui/talos-art.d.ts +1 -0
- package/dist/tui/talos-art.js +9 -0
- package/dist/tui/task-labels.d.ts +1 -0
- package/dist/tui/task-labels.js +8 -0
- package/dist/tui/terminal-text.d.ts +2 -1
- package/dist/tui/terminal-text.js +28 -2
- package/dist/tui/theme.js +2 -2
- package/dist/tui/tool-presentation.d.ts +3 -0
- package/dist/tui/tool-presentation.js +98 -0
- package/dist/tui/tool-style.js +4 -0
- package/dist/tui/transcript-store.d.ts +25 -0
- package/dist/tui/transcript-store.js +151 -0
- package/dist/tui/usage-footer.d.ts +1 -1
- package/dist/tui/usage-footer.js +3 -1
- package/dist/tui/vascend-id.d.ts +49 -0
- package/dist/tui/vascend-id.js +321 -0
- package/dist/tui/web-login-screen.js +4 -3
- package/dist/tui/web-login.d.ts +7 -15
- package/dist/tui/web-login.js +38 -78
- package/dist/tui/worker-transcripts.d.ts +31 -0
- package/dist/tui/worker-transcripts.js +91 -0
- package/dist/vascend/candidate-collector.d.ts +6 -5
- package/dist/vascend/candidate-collector.js +44 -43
- package/dist/vascend/context-lint.js +32 -2
- package/dist/vascend/context-market-selection.js +12 -5
- package/dist/vascend/context-pack-render.js +10 -4
- package/dist/vascend/context-packet-mirrors.d.ts +8 -0
- package/dist/vascend/context-packet-mirrors.js +24 -0
- package/dist/vascend/context-plan-events.d.ts +4 -0
- package/dist/vascend/context-plan-events.js +44 -0
- package/dist/vascend/context-snapshot.js +4 -2
- package/dist/vascend/context-store-reader.d.ts +20 -0
- package/dist/vascend/context-store-reader.js +46 -0
- package/dist/vascend/file-lock.d.ts +1 -0
- package/dist/vascend/file-lock.js +26 -0
- package/dist/vascend/granular-kingdom.d.ts +1 -0
- package/dist/vascend/granular-kingdom.js +22 -15
- package/dist/vascend/kingdom-bus.d.ts +1 -0
- package/dist/vascend/kingdom-bus.js +3 -0
- package/dist/vascend/kingdom-channel.d.ts +2 -0
- package/dist/vascend/kingdom-channel.js +3 -0
- package/dist/vascend/kingdom-live.d.ts +32 -4
- package/dist/vascend/kingdom-live.js +149 -13
- package/dist/vascend/kingdom-run.d.ts +5 -1
- package/dist/vascend/kingdom-run.js +6 -2
- package/dist/vascend/kingdom.d.ts +3 -0
- package/dist/vascend/kingdom.js +20 -13
- package/dist/vascend/memory-packet.d.ts +2 -1
- package/dist/vascend/memory-packet.js +31 -44
- package/dist/vascend/plan-store.d.ts +3 -1
- package/dist/vascend/plan-store.js +6 -4
- package/dist/vascend/room-memory.d.ts +10 -1
- package/dist/vascend/room-memory.js +42 -2
- package/dist/vascend/runtime.d.ts +3 -11
- package/dist/vascend/runtime.js +11 -123
- package/dist/vascend/session-search.js +2 -0
- package/dist/vascend/team-session.js +3 -2
- package/dist/vascend/vision.d.ts +3 -1
- package/dist/vascend/vision.js +6 -4
- package/dist/vascend/worker-session.d.ts +20 -0
- package/dist/vascend/worker-session.js +83 -0
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export declare const VASCEND_ID_ISSUER = "https://id.vascend.it";
|
|
2
|
+
export declare const VASCEND_ID_CLIENT = "talos";
|
|
3
|
+
export declare const AGENT_RESOURCE = "https://api.vascend.it/agent";
|
|
4
|
+
export declare const RELAY_RESOURCE = "https://relay.vascend.it";
|
|
5
|
+
export declare const LOGIN_RESOURCES: readonly ["https://api.vascend.it/agent", "https://relay.vascend.it"];
|
|
6
|
+
export declare const VASCEND_ID_SCOPE = "openid email offline_access agent sessions";
|
|
7
|
+
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
8
|
+
export interface IdDeps {
|
|
9
|
+
readonly fetchImpl?: FetchLike;
|
|
10
|
+
readonly now?: () => number;
|
|
11
|
+
}
|
|
12
|
+
export interface TokenRisorsa {
|
|
13
|
+
readonly token: string;
|
|
14
|
+
readonly scade: number;
|
|
15
|
+
}
|
|
16
|
+
export interface SessioneId {
|
|
17
|
+
readonly accessToken: string;
|
|
18
|
+
readonly refreshToken: string;
|
|
19
|
+
readonly scade: number;
|
|
20
|
+
readonly email: string;
|
|
21
|
+
readonly risorse?: Readonly<Record<string, TokenRisorsa>>;
|
|
22
|
+
}
|
|
23
|
+
export declare class VascendIdError extends Error {
|
|
24
|
+
readonly code: string;
|
|
25
|
+
constructor(code: string);
|
|
26
|
+
}
|
|
27
|
+
export declare function issuerOf(env: NodeJS.ProcessEnv): string;
|
|
28
|
+
export declare function tokenPlausibile(token: unknown, max?: number): token is string;
|
|
29
|
+
export declare function claimNonVerificati(token: string): Record<string, unknown>;
|
|
30
|
+
export declare function chiamaId(env: NodeJS.ProcessEnv, path: string, campi: Record<string, string | readonly string[]>, deps?: IdDeps, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
31
|
+
export declare function sessioneDaRisposta(data: Record<string, unknown>, now: number): SessioneId;
|
|
32
|
+
export declare function sessionePath(env: NodeJS.ProcessEnv): string | null;
|
|
33
|
+
export declare function leggiSessione(env: NodeJS.ProcessEnv): SessioneId | null;
|
|
34
|
+
export declare function salvaSessione(env: NodeJS.ProcessEnv, sessione: SessioneId): boolean;
|
|
35
|
+
export declare function rimuoviSessione(env: NodeJS.ProcessEnv): void;
|
|
36
|
+
export interface EsitoToken {
|
|
37
|
+
readonly token: string;
|
|
38
|
+
readonly email?: string;
|
|
39
|
+
readonly errore?: "assente" | "respinta" | "rete";
|
|
40
|
+
}
|
|
41
|
+
export declare function tokenAgente(env: NodeJS.ProcessEnv, deps?: IdDeps): Promise<EsitoToken>;
|
|
42
|
+
export interface EsitoTokenRisorsa {
|
|
43
|
+
readonly token: string;
|
|
44
|
+
readonly email?: string;
|
|
45
|
+
readonly errore?: "assente" | "respinta" | "rete" | "risorsa";
|
|
46
|
+
}
|
|
47
|
+
export declare function tokenRisorsa(env: NodeJS.ProcessEnv, risorsa: string, deps?: IdDeps): Promise<EsitoTokenRisorsa>;
|
|
48
|
+
export declare function revocaSessioneId(env: NodeJS.ProcessEnv, deps?: IdDeps): Promise<void>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// Sessione Vascend ID (https://id.vascend.it), il login unico dell'ecosistema.
|
|
2
|
+
//
|
|
3
|
+
// Talos non riceve piu' un token dal backend agentico: si collega a Vascend ID
|
|
4
|
+
// col device flow (RFC 8628, vedi web-login.ts) e ottiene
|
|
5
|
+
// - un access token per l'agente (aud https://api.vascend.it/agent, 10 min)
|
|
6
|
+
// - un refresh token, che RUOTA a ogni uso: il riuso di uno vecchio revoca
|
|
7
|
+
// l'intera sessione su tutti i dispositivi collegati con quel login.
|
|
8
|
+
// Il login chiede anche la risorsa del relay (controllo remoto, scope
|
|
9
|
+
// `sessions`): col refresh token si ottiene un token per l'una o per l'altra
|
|
10
|
+
// (RFC 8707), sempre sotto lo stesso lock, perche' ogni rinnovo lo ruota.
|
|
11
|
+
//
|
|
12
|
+
// Per la rotazione, il rinnovo va fatto da UN processo alla volta: due Talos
|
|
13
|
+
// aperti che rinnovano insieme presenterebbero lo stesso refresh token e il
|
|
14
|
+
// provider li butterebbe fuori entrambi. Per questo lettura, rinnovo e
|
|
15
|
+
// scrittura stanno sotto lo stesso lock di file.
|
|
16
|
+
//
|
|
17
|
+
// La sessione vive in ~/.talos/vascend-id.json (0600), separata dal config dei
|
|
18
|
+
// provider: il refresh token vale settimane e non deve comparire accanto a
|
|
19
|
+
// chiavi che l'utente incolla, esporta o condivide.
|
|
20
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { readConfigObject, updateConfigObject } from "../core/config-storage.js";
|
|
23
|
+
import { talosConfigDir } from "../core/env-paths.js";
|
|
24
|
+
import { withLock } from "../vascend/file-lock.js";
|
|
25
|
+
export const VASCEND_ID_ISSUER = "https://id.vascend.it";
|
|
26
|
+
export const VASCEND_ID_CLIENT = "talos";
|
|
27
|
+
export const AGENT_RESOURCE = "https://api.vascend.it/agent";
|
|
28
|
+
export const RELAY_RESOURCE = "https://relay.vascend.it";
|
|
29
|
+
// Le risorse chieste al login: una risorsa non chiesta qui non si ottiene piu'
|
|
30
|
+
// col refresh token, serve un nuovo login.
|
|
31
|
+
export const LOGIN_RESOURCES = [AGENT_RESOURCE, RELAY_RESOURCE];
|
|
32
|
+
export const VASCEND_ID_SCOPE = "openid email offline_access agent sessions";
|
|
33
|
+
const TIMEOUT_MS = 12_000;
|
|
34
|
+
// Si rinnova un minuto prima della scadenza: una richiesta partita con un token
|
|
35
|
+
// valido non deve arrivare al server con un token scaduto.
|
|
36
|
+
const MARGINE_S = 60;
|
|
37
|
+
export class VascendIdError extends Error {
|
|
38
|
+
code;
|
|
39
|
+
constructor(code) {
|
|
40
|
+
super(code);
|
|
41
|
+
this.code = code;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function issuerValido(url) {
|
|
45
|
+
try {
|
|
46
|
+
const u = new URL(url);
|
|
47
|
+
if (u.username || u.password || u.search || u.hash)
|
|
48
|
+
return false;
|
|
49
|
+
if (u.protocol === "https:")
|
|
50
|
+
return true;
|
|
51
|
+
return u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Override solo per sviluppo/collaudo; uno malformato o in chiaro verso un host
|
|
58
|
+
// remoto non degrada il login: si torna al provider di produzione.
|
|
59
|
+
export function issuerOf(env) {
|
|
60
|
+
const override = String(env["TALOS_VASCEND_ID_URL"] ?? "").trim();
|
|
61
|
+
return (override && issuerValido(override) ? override : VASCEND_ID_ISSUER).replace(/\/+$/, "");
|
|
62
|
+
}
|
|
63
|
+
export function tokenPlausibile(token, max = 8192) {
|
|
64
|
+
return typeof token === "string" && token.length >= 20 && token.length <= max && /^[A-Za-z0-9._~-]+$/.test(token);
|
|
65
|
+
}
|
|
66
|
+
// Legge i claim SENZA verificarli: servono solo a mostrare l'email e a sapere
|
|
67
|
+
// quando rinnovare. La verifica vera la fa il server che riceve il token.
|
|
68
|
+
export function claimNonVerificati(token) {
|
|
69
|
+
const parte = token.split(".")[1];
|
|
70
|
+
if (!parte)
|
|
71
|
+
return {};
|
|
72
|
+
try {
|
|
73
|
+
const valore = JSON.parse(Buffer.from(parte, "base64url").toString("utf8"));
|
|
74
|
+
return valore && typeof valore === "object" && !Array.isArray(valore) ? valore : {};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function emailPulita(valore) {
|
|
81
|
+
const email = typeof valore === "string" ? valore.trim().toLowerCase() : "";
|
|
82
|
+
return email.length <= 254 && email.includes("@") && !/[\x00-\x20\x7f-\x9f]/.test(email) ? email : "";
|
|
83
|
+
}
|
|
84
|
+
// POST application/x-www-form-urlencoded verso il provider, con le stesse
|
|
85
|
+
// difese di prima: niente redirect, timeout, risposta limitata e validata.
|
|
86
|
+
export async function chiamaId(env, path, campi, deps = {}, signal) {
|
|
87
|
+
const f = deps.fetchImpl ?? fetch;
|
|
88
|
+
// Un array diventa un parametro ripetuto (resource=...&resource=...).
|
|
89
|
+
const corpo = new URLSearchParams();
|
|
90
|
+
for (const [chiave, valore] of Object.entries(campi)) {
|
|
91
|
+
for (const v of typeof valore === "string" ? [valore] : valore)
|
|
92
|
+
corpo.append(chiave, v);
|
|
93
|
+
}
|
|
94
|
+
let response;
|
|
95
|
+
let raw;
|
|
96
|
+
try {
|
|
97
|
+
response = await f(issuerOf(env) + path, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
100
|
+
body: corpo.toString(),
|
|
101
|
+
redirect: "error",
|
|
102
|
+
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT_MS)]) : AbortSignal.timeout(TIMEOUT_MS),
|
|
103
|
+
});
|
|
104
|
+
raw = await response.text();
|
|
105
|
+
if (signal?.aborted)
|
|
106
|
+
throw new VascendIdError("cancelled");
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
if (signal?.aborted)
|
|
110
|
+
throw new VascendIdError("cancelled");
|
|
111
|
+
throw new VascendIdError("temporarily_unavailable");
|
|
112
|
+
}
|
|
113
|
+
if (raw.length > 32_768)
|
|
114
|
+
throw new VascendIdError("invalid_response");
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = JSON.parse(raw);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new VascendIdError("invalid_response");
|
|
121
|
+
}
|
|
122
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
123
|
+
throw new VascendIdError("invalid_response");
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const noti = ["authorization_pending", "slow_down", "expired_token", "access_denied", "invalid_grant",
|
|
126
|
+
"temporarily_unavailable", "invalid_client", "invalid_target"];
|
|
127
|
+
throw new VascendIdError(noti.includes(String(data["error"])) ? String(data["error"]) : "authorization_failed");
|
|
128
|
+
}
|
|
129
|
+
return data;
|
|
130
|
+
}
|
|
131
|
+
// Da una risposta di /token a una sessione, validando tutto quello che arriva.
|
|
132
|
+
export function sessioneDaRisposta(data, now) {
|
|
133
|
+
const accessToken = data["access_token"], refreshToken = data["refresh_token"], expiresIn = data["expires_in"];
|
|
134
|
+
if (!tokenPlausibile(accessToken) || !tokenPlausibile(refreshToken, 2048) ||
|
|
135
|
+
typeof expiresIn !== "number" || !Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 86_400) {
|
|
136
|
+
throw new VascendIdError("invalid_response");
|
|
137
|
+
}
|
|
138
|
+
const email = emailPulita(claimNonVerificati(accessToken)["email"]);
|
|
139
|
+
if (!email)
|
|
140
|
+
throw new VascendIdError("invalid_response");
|
|
141
|
+
return { accessToken, refreshToken, scade: Math.floor(now / 1000) + expiresIn, email };
|
|
142
|
+
}
|
|
143
|
+
// --- Persistenza -------------------------------------------------------------
|
|
144
|
+
export function sessionePath(env) {
|
|
145
|
+
if (env["TALOS_NO_GLOBAL_CONFIG"] === "1")
|
|
146
|
+
return null;
|
|
147
|
+
return join(talosConfigDir(env), "vascend-id.json");
|
|
148
|
+
}
|
|
149
|
+
function leggiDaFile(path) {
|
|
150
|
+
if (!existsSync(path))
|
|
151
|
+
return null;
|
|
152
|
+
let raw;
|
|
153
|
+
try {
|
|
154
|
+
raw = readConfigObject(path);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const scade = Number(raw["scade"]);
|
|
160
|
+
const email = emailPulita(raw["email"]);
|
|
161
|
+
if (!tokenPlausibile(raw["accessToken"]) || !tokenPlausibile(raw["refreshToken"], 2048) ||
|
|
162
|
+
!Number.isFinite(scade) || !email)
|
|
163
|
+
return null;
|
|
164
|
+
return { accessToken: raw["accessToken"], refreshToken: raw["refreshToken"], scade, email, risorse: risorseDa(raw["risorse"]) };
|
|
165
|
+
}
|
|
166
|
+
// Solo le voci ben formate: una voce rovinata costa un rinnovo, non la sessione.
|
|
167
|
+
function risorseDa(valore) {
|
|
168
|
+
if (!valore || typeof valore !== "object" || Array.isArray(valore))
|
|
169
|
+
return undefined;
|
|
170
|
+
const voci = {};
|
|
171
|
+
for (const [risorsa, v] of Object.entries(valore)) {
|
|
172
|
+
const t = v && typeof v === "object" ? v : {};
|
|
173
|
+
const scade = Number(t["scade"]);
|
|
174
|
+
if (risorsa.startsWith("https://") && tokenPlausibile(t["token"]) && Number.isFinite(scade)) {
|
|
175
|
+
voci[risorsa] = { token: t["token"], scade };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return Object.keys(voci).length ? voci : undefined;
|
|
179
|
+
}
|
|
180
|
+
export function leggiSessione(env) {
|
|
181
|
+
const path = sessionePath(env);
|
|
182
|
+
return path ? leggiDaFile(path) : null;
|
|
183
|
+
}
|
|
184
|
+
export function salvaSessione(env, sessione) {
|
|
185
|
+
const path = sessionePath(env);
|
|
186
|
+
if (!path)
|
|
187
|
+
return false;
|
|
188
|
+
try {
|
|
189
|
+
mkdirSync(talosConfigDir(env), { recursive: true });
|
|
190
|
+
updateConfigObject(path, () => ({ ...sessione }));
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
export function rimuoviSessione(env) {
|
|
198
|
+
const path = sessionePath(env);
|
|
199
|
+
if (!path || !existsSync(path))
|
|
200
|
+
return;
|
|
201
|
+
try {
|
|
202
|
+
updateConfigObject(path, () => ({}));
|
|
203
|
+
}
|
|
204
|
+
catch { /* best-effort */ }
|
|
205
|
+
}
|
|
206
|
+
// Token per l'agente, rinnovato se sta per scadere. Mai due rinnovi in
|
|
207
|
+
// parallelo: il secondo processo trova gia' la sessione nuova e la usa.
|
|
208
|
+
export async function tokenAgente(env, deps = {}) {
|
|
209
|
+
const path = sessionePath(env);
|
|
210
|
+
if (!path)
|
|
211
|
+
return { token: "", errore: "assente" };
|
|
212
|
+
const now = deps.now ?? Date.now;
|
|
213
|
+
const valida = (s) => s.scade - MARGINE_S > Math.floor(now() / 1000);
|
|
214
|
+
const prima = leggiDaFile(path);
|
|
215
|
+
if (!prima)
|
|
216
|
+
return { token: "", errore: "assente" };
|
|
217
|
+
if (valida(prima))
|
|
218
|
+
return { token: prima.accessToken, email: prima.email };
|
|
219
|
+
mkdirSync(talosConfigDir(env), { recursive: true });
|
|
220
|
+
// Lock dedicato al rinnovo: la scrittura del file prende gia' il suo (non
|
|
221
|
+
// rientrante), quindi qui ne serve uno diverso che copra lettura+rinnovo.
|
|
222
|
+
return withLock(`${path}.rinnovo`, async () => {
|
|
223
|
+
const attuale = leggiDaFile(path);
|
|
224
|
+
if (!attuale)
|
|
225
|
+
return { token: "", errore: "assente" };
|
|
226
|
+
if (valida(attuale))
|
|
227
|
+
return { token: attuale.accessToken, email: attuale.email };
|
|
228
|
+
try {
|
|
229
|
+
const data = await chiamaId(env, "/token", {
|
|
230
|
+
grant_type: "refresh_token",
|
|
231
|
+
refresh_token: attuale.refreshToken,
|
|
232
|
+
client_id: VASCEND_ID_CLIENT,
|
|
233
|
+
resource: AGENT_RESOURCE,
|
|
234
|
+
}, deps);
|
|
235
|
+
const nuova = sessioneDaRisposta(data, now());
|
|
236
|
+
updateConfigObject(path, () => ({ ...nuova, risorse: attuale.risorse }));
|
|
237
|
+
return { token: nuova.accessToken, email: nuova.email };
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
const code = e instanceof VascendIdError ? e.code : "";
|
|
241
|
+
if (code === "temporarily_unavailable")
|
|
242
|
+
return { token: "", errore: "rete" };
|
|
243
|
+
// Refresh respinto (revocato, scaduto, riusato): la sessione non vale piu'.
|
|
244
|
+
updateConfigObject(path, () => ({}));
|
|
245
|
+
return { token: "", errore: "respinta" };
|
|
246
|
+
}
|
|
247
|
+
}, { timeoutMs: 20_000, staleMs: 30_000 });
|
|
248
|
+
}
|
|
249
|
+
// Token per una risorsa diversa dall'agente (il relay). Stesso lock di
|
|
250
|
+
// tokenAgente: ogni rinnovo ruota il refresh token, e due rinnovi in parallelo
|
|
251
|
+
// butterebbero fuori la sessione.
|
|
252
|
+
export async function tokenRisorsa(env, risorsa, deps = {}) {
|
|
253
|
+
const path = sessionePath(env);
|
|
254
|
+
if (!path)
|
|
255
|
+
return { token: "", errore: "assente" };
|
|
256
|
+
const now = deps.now ?? Date.now;
|
|
257
|
+
const valido = (t) => !!t && t.scade - MARGINE_S > Math.floor(now() / 1000);
|
|
258
|
+
const prima = leggiDaFile(path);
|
|
259
|
+
if (!prima)
|
|
260
|
+
return { token: "", errore: "assente" };
|
|
261
|
+
const giaPronto = prima.risorse?.[risorsa];
|
|
262
|
+
if (valido(giaPronto))
|
|
263
|
+
return { token: giaPronto.token, email: prima.email };
|
|
264
|
+
mkdirSync(talosConfigDir(env), { recursive: true });
|
|
265
|
+
return withLock(`${path}.rinnovo`, async () => {
|
|
266
|
+
const attuale = leggiDaFile(path);
|
|
267
|
+
if (!attuale)
|
|
268
|
+
return { token: "", errore: "assente" };
|
|
269
|
+
const pronto = attuale.risorse?.[risorsa];
|
|
270
|
+
if (valido(pronto))
|
|
271
|
+
return { token: pronto.token, email: attuale.email };
|
|
272
|
+
let data;
|
|
273
|
+
try {
|
|
274
|
+
data = await chiamaId(env, "/token", {
|
|
275
|
+
grant_type: "refresh_token",
|
|
276
|
+
refresh_token: attuale.refreshToken,
|
|
277
|
+
client_id: VASCEND_ID_CLIENT,
|
|
278
|
+
resource: risorsa,
|
|
279
|
+
}, deps);
|
|
280
|
+
}
|
|
281
|
+
catch (e) {
|
|
282
|
+
const code = e instanceof VascendIdError ? e.code : "";
|
|
283
|
+
// Risorsa fuori dal login: il refresh token non e' stato consumato, la
|
|
284
|
+
// sessione per l'agente resta valida.
|
|
285
|
+
if (code === "invalid_target")
|
|
286
|
+
return { token: "", errore: "risorsa" };
|
|
287
|
+
// Solo un refresh respinto chiude la sessione: una funzione facoltativa
|
|
288
|
+
// come il controllo remoto non deve scollegare l'utente per un errore
|
|
289
|
+
// temporaneo o una risposta strana.
|
|
290
|
+
if (code !== "invalid_grant")
|
|
291
|
+
return { token: "", errore: "rete" };
|
|
292
|
+
updateConfigObject(path, () => ({}));
|
|
293
|
+
return { token: "", errore: "respinta" };
|
|
294
|
+
}
|
|
295
|
+
const accessToken = data["access_token"], refreshToken = data["refresh_token"], expiresIn = data["expires_in"];
|
|
296
|
+
if (!tokenPlausibile(accessToken) || !tokenPlausibile(refreshToken, 2048) ||
|
|
297
|
+
typeof expiresIn !== "number" || !Number.isInteger(expiresIn) || expiresIn < 30 || expiresIn > 86_400) {
|
|
298
|
+
// Il refresh token e' gia' stato ruotato dal provider: quello vecchio non vale piu'.
|
|
299
|
+
updateConfigObject(path, () => ({}));
|
|
300
|
+
return { token: "", errore: "respinta" };
|
|
301
|
+
}
|
|
302
|
+
const voce = { token: accessToken, scade: Math.floor(now() / 1000) + expiresIn };
|
|
303
|
+
updateConfigObject(path, () => ({ ...attuale, refreshToken, risorse: { ...attuale.risorse, [risorsa]: voce } }));
|
|
304
|
+
return { token: voce.token, email: attuale.email };
|
|
305
|
+
}, { timeoutMs: 20_000, staleMs: 30_000 });
|
|
306
|
+
}
|
|
307
|
+
// Logout: revoca il refresh token sul provider (best-effort), poi il chiamante
|
|
308
|
+
// cancella la sessione locale.
|
|
309
|
+
export async function revocaSessioneId(env, deps = {}) {
|
|
310
|
+
const sessione = leggiSessione(env);
|
|
311
|
+
if (!sessione)
|
|
312
|
+
return;
|
|
313
|
+
try {
|
|
314
|
+
await chiamaId(env, "/token/revocation", {
|
|
315
|
+
token: sessione.refreshToken,
|
|
316
|
+
token_type_hint: "refresh_token",
|
|
317
|
+
client_id: VASCEND_ID_CLIENT,
|
|
318
|
+
}, deps);
|
|
319
|
+
}
|
|
320
|
+
catch { /* best-effort: il logout locale prosegue */ }
|
|
321
|
+
}
|
|
@@ -22,10 +22,11 @@ export function renderWebLoginScreen(grant, options = {}) {
|
|
|
22
22
|
add(tr("Collega il tuo account e inizia.", "Connect your account and get started."), "muted");
|
|
23
23
|
lines.push("");
|
|
24
24
|
add(tr("1. Accedi nel browser", "1. Sign in in your browser"));
|
|
25
|
-
add(tr("Si aprirà vascend.it per continuare.", "vascend.it will open to continue."), "muted", 5);
|
|
25
|
+
add(tr("Si aprirà id.vascend.it per continuare.", "id.vascend.it will open to continue."), "muted", 5);
|
|
26
26
|
lines.push("");
|
|
27
27
|
add(tr("2. Confronta il codice e conferma", "2. Match the code and approve"));
|
|
28
|
-
|
|
28
|
+
// Vascend ID manda il codice gia' diviso (XXXX-XXXX): lo si mostra com'e'.
|
|
29
|
+
const code = plainText(grant.userCode);
|
|
29
30
|
const edge = options.unicode === false ? ["+", "-", "+", "|", "+", "+"] : ["╭", "─", "╮", "│", "╰", "╯"];
|
|
30
31
|
const codeWidth = Math.min(25, width - 5);
|
|
31
32
|
const padding = Math.max(0, Math.floor((codeWidth - code.length) / 2));
|
|
@@ -57,7 +58,7 @@ export function showWebLoginScreen(grant, abort, options = {}) {
|
|
|
57
58
|
const output = options.output ?? stdout;
|
|
58
59
|
const env = options.env ?? process.env;
|
|
59
60
|
const now = options.now ?? Date.now;
|
|
60
|
-
const url = verificationUrl(grant.url, grant.userCode);
|
|
61
|
+
const url = verificationUrl(grant.url, grant.userCode, env);
|
|
61
62
|
const animated = Boolean(output.isTTY) && env.TERM !== "dumb";
|
|
62
63
|
const appearance = () => ({ columns: output.columns, color: animated && env.NO_COLOR === undefined,
|
|
63
64
|
unicode: env.TERM !== "dumb", theme: options.theme });
|
package/dist/tui/web-login.d.ts
CHANGED
|
@@ -1,28 +1,20 @@
|
|
|
1
|
-
import { type LoginDeps } from "./vascend-login.js";
|
|
2
1
|
import type { Lingua } from "./onboarding.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
constructor(code: string);
|
|
6
|
-
}
|
|
2
|
+
import { VascendIdError, type IdDeps, type SessioneId } from "./vascend-id.js";
|
|
3
|
+
export { VascendIdError as WebLoginError };
|
|
7
4
|
export interface WebGrant {
|
|
8
5
|
readonly deviceCode: string;
|
|
9
|
-
readonly verifier: string;
|
|
10
6
|
readonly userCode: string;
|
|
11
7
|
readonly url: string;
|
|
12
8
|
readonly expiresIn: number;
|
|
13
9
|
readonly interval: number;
|
|
14
10
|
}
|
|
15
|
-
export interface WebSession {
|
|
11
|
+
export interface WebSession extends SessioneId {
|
|
16
12
|
readonly token: string;
|
|
17
|
-
readonly email: string;
|
|
18
13
|
readonly language: Lingua;
|
|
19
|
-
readonly tier?: string;
|
|
20
14
|
}
|
|
21
|
-
type Deps =
|
|
15
|
+
type Deps = IdDeps & {
|
|
22
16
|
readonly wait?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
23
|
-
readonly now?: () => number;
|
|
24
17
|
};
|
|
25
|
-
export declare function verificationUrl(value: unknown, code: string): string;
|
|
26
|
-
export declare function startWebLogin(env: NodeJS.ProcessEnv,
|
|
27
|
-
export declare function waitForWebLogin(env: NodeJS.ProcessEnv, grant: WebGrant, deps?: Deps, signal?: AbortSignal): Promise<WebSession>;
|
|
28
|
-
export {};
|
|
18
|
+
export declare function verificationUrl(value: unknown, code: string, env?: NodeJS.ProcessEnv): string;
|
|
19
|
+
export declare function startWebLogin(env: NodeJS.ProcessEnv, _language: Lingua, deps?: Deps, signal?: AbortSignal): Promise<WebGrant>;
|
|
20
|
+
export declare function waitForWebLogin(env: NodeJS.ProcessEnv, grant: WebGrant, language: Lingua, deps?: Deps, signal?: AbortSignal): Promise<WebSession>;
|
package/dist/tui/web-login.js
CHANGED
|
@@ -1,85 +1,45 @@
|
|
|
1
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
2
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
async function post(env, path, body, deps, signal) {
|
|
12
|
-
let response;
|
|
13
|
-
let raw;
|
|
14
|
-
try {
|
|
15
|
-
response = await (deps.fetchImpl ?? fetch)(baseOf(deps, env) + "/auth/device/" + path, {
|
|
16
|
-
method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
|
|
17
|
-
redirect: "error", signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(12_000)]) : AbortSignal.timeout(12_000),
|
|
18
|
-
});
|
|
19
|
-
raw = await response.text();
|
|
20
|
-
if (signal?.aborted)
|
|
21
|
-
throw new WebLoginError("cancelled");
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
if (signal?.aborted)
|
|
25
|
-
throw new WebLoginError("cancelled");
|
|
26
|
-
throw new WebLoginError("temporarily_unavailable");
|
|
27
|
-
}
|
|
28
|
-
if (raw.length > 16_384)
|
|
29
|
-
throw new WebLoginError("invalid_response");
|
|
30
|
-
let data;
|
|
31
|
-
try {
|
|
32
|
-
data = JSON.parse(raw);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
throw new WebLoginError("invalid_response");
|
|
36
|
-
}
|
|
37
|
-
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
38
|
-
throw new WebLoginError("invalid_response");
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
const known = ["authorization_pending", "slow_down", "expired_token", "access_denied", "invalid_grant", "temporarily_unavailable"];
|
|
41
|
-
throw new WebLoginError(known.includes(String(data["error"])) ? String(data["error"]) : "authorization_failed");
|
|
42
|
-
}
|
|
43
|
-
return data;
|
|
44
|
-
}
|
|
45
|
-
export function verificationUrl(value, code) {
|
|
2
|
+
import { AGENT_RESOURCE, LOGIN_RESOURCES, VASCEND_ID_CLIENT, VASCEND_ID_SCOPE, VascendIdError, chiamaId, issuerOf, sessioneDaRisposta, } from "./vascend-id.js";
|
|
3
|
+
// Collegamento di Talos a Vascend ID col device flow (RFC 8628): Talos mostra
|
|
4
|
+
// un codice, l'utente lo conferma su id.vascend.it (login col codice via email
|
|
5
|
+
// e, se attivo, il secondo fattore), Talos riceve i token.
|
|
6
|
+
export { VascendIdError as WebLoginError };
|
|
7
|
+
// Alfabeto base-20 di Vascend ID (niente vocali ne' lettere ambigue), a gruppi di 4.
|
|
8
|
+
const USER_CODE = /^[BCDFGHJKLMNPQRSTVWXZ]{4}-[BCDFGHJKLMNPQRSTVWXZ]{4}$/;
|
|
9
|
+
export function verificationUrl(value, code, env = {}) {
|
|
46
10
|
if (typeof value !== "string" || value.length > 512)
|
|
47
|
-
throw new
|
|
11
|
+
throw new VascendIdError("invalid_response");
|
|
48
12
|
let u;
|
|
49
13
|
try {
|
|
50
14
|
u = new URL(value);
|
|
51
15
|
}
|
|
52
16
|
catch {
|
|
53
|
-
throw new
|
|
17
|
+
throw new VascendIdError("invalid_response");
|
|
54
18
|
}
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
if (u.origin !==
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
throw new
|
|
19
|
+
// Link fornito dal server, controllato in modo indipendente prima di aprire
|
|
20
|
+
// il browser locale: una risposta manomessa non deve aprire altro.
|
|
21
|
+
if (u.origin !== new URL(issuerOf(env)).origin || u.username || u.password || u.hash ||
|
|
22
|
+
u.pathname !== "/device" || u.searchParams.get("user_code") !== code ||
|
|
23
|
+
[...u.searchParams.keys()].join(",") !== "user_code") {
|
|
24
|
+
throw new VascendIdError("invalid_response");
|
|
61
25
|
}
|
|
62
26
|
return u.href;
|
|
63
27
|
}
|
|
64
|
-
export async function startWebLogin(env,
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
code_challenge: createHash("sha256").update(verifier).digest("base64url"),
|
|
68
|
-
// The website currently exposes Italian and English routes, as in the
|
|
69
|
-
// desktop host. Its fallback must not overwrite the local UI preference.
|
|
70
|
-
code_challenge_method: "S256", language: language === "it" ? "it" : "en",
|
|
28
|
+
export async function startWebLogin(env, _language, deps = {}, signal) {
|
|
29
|
+
const data = await chiamaId(env, "/device/auth", {
|
|
30
|
+
client_id: VASCEND_ID_CLIENT, scope: VASCEND_ID_SCOPE, resource: LOGIN_RESOURCES,
|
|
71
31
|
}, deps, signal);
|
|
72
32
|
const deviceCode = data["device_code"], userCode = data["user_code"];
|
|
73
|
-
const expiresIn = data["expires_in"], interval = data["interval"];
|
|
74
|
-
if (typeof deviceCode !== "string" || !/^[A-Za-z0-9_-]{
|
|
75
|
-
typeof userCode !== "string" ||
|
|
76
|
-
typeof expiresIn !== "number" || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn >
|
|
33
|
+
const expiresIn = data["expires_in"], interval = data["interval"] ?? 5;
|
|
34
|
+
if (typeof deviceCode !== "string" || !/^[A-Za-z0-9_-]{20,128}$/.test(deviceCode) ||
|
|
35
|
+
typeof userCode !== "string" || !USER_CODE.test(userCode) ||
|
|
36
|
+
typeof expiresIn !== "number" || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 1800 ||
|
|
77
37
|
typeof interval !== "number" || !Number.isInteger(interval) || interval < 1 || interval > 30) {
|
|
78
|
-
throw new
|
|
38
|
+
throw new VascendIdError("invalid_response");
|
|
79
39
|
}
|
|
80
|
-
return {
|
|
40
|
+
return { deviceCode, userCode, expiresIn, interval, url: verificationUrl(data["verification_uri_complete"], userCode, env) };
|
|
81
41
|
}
|
|
82
|
-
export async function waitForWebLogin(env, grant, deps = {}, signal) {
|
|
42
|
+
export async function waitForWebLogin(env, grant, language, deps = {}, signal) {
|
|
83
43
|
const now = deps.now ?? Date.now;
|
|
84
44
|
const deadline = now() + grant.expiresIn * 1000;
|
|
85
45
|
const wait = deps.wait ?? (async (ms, signal) => { await delay(ms, undefined, { signal }); });
|
|
@@ -89,24 +49,24 @@ export async function waitForWebLogin(env, grant, deps = {}, signal) {
|
|
|
89
49
|
await wait(Math.min(interval * 1000, deadline - now()), signal);
|
|
90
50
|
}
|
|
91
51
|
catch {
|
|
92
|
-
throw new
|
|
52
|
+
throw new VascendIdError("cancelled");
|
|
93
53
|
}
|
|
94
54
|
if (signal?.aborted)
|
|
95
|
-
throw new
|
|
55
|
+
throw new VascendIdError("cancelled");
|
|
96
56
|
if (now() >= deadline)
|
|
97
57
|
break;
|
|
98
58
|
try {
|
|
99
|
-
const data = await
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
59
|
+
const data = await chiamaId(env, "/token", {
|
|
60
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
61
|
+
device_code: grant.deviceCode,
|
|
62
|
+
client_id: VASCEND_ID_CLIENT,
|
|
63
|
+
resource: AGENT_RESOURCE,
|
|
64
|
+
}, deps, signal);
|
|
65
|
+
const sessione = sessioneDaRisposta(data, now());
|
|
66
|
+
return { ...sessione, token: sessione.accessToken, language };
|
|
107
67
|
}
|
|
108
68
|
catch (e) {
|
|
109
|
-
if (!(e instanceof
|
|
69
|
+
if (!(e instanceof VascendIdError))
|
|
110
70
|
throw e;
|
|
111
71
|
if (e.code === "slow_down" || e.code === "temporarily_unavailable")
|
|
112
72
|
interval = Math.min(30, interval + 5);
|
|
@@ -114,5 +74,5 @@ export async function waitForWebLogin(env, grant, deps = {}, signal) {
|
|
|
114
74
|
throw e;
|
|
115
75
|
}
|
|
116
76
|
}
|
|
117
|
-
throw new
|
|
77
|
+
throw new VascendIdError("expired_token");
|
|
118
78
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AgentEvent } from "../core/types.js";
|
|
2
|
+
export interface TranscriptBuffer {
|
|
3
|
+
write(text: string): void;
|
|
4
|
+
logLength(): number;
|
|
5
|
+
setLogLine(index: number, text: string): void;
|
|
6
|
+
getLogLine(index: number): string | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface TranscriptSink {
|
|
9
|
+
(event: AgentEvent): void;
|
|
10
|
+
preview?(): string;
|
|
11
|
+
}
|
|
12
|
+
export type WorkerSinkFactory = (buffer: TranscriptBuffer, width: number) => TranscriptSink;
|
|
13
|
+
export interface TranscriptLayout {
|
|
14
|
+
readonly length: number;
|
|
15
|
+
slice(start: number, end: number): readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare class WorkerTranscripts {
|
|
18
|
+
private readonly events;
|
|
19
|
+
private readonly rendered;
|
|
20
|
+
private factory?;
|
|
21
|
+
private revision?;
|
|
22
|
+
private renderedRevision?;
|
|
23
|
+
invalidate(): void;
|
|
24
|
+
configure(factory: WorkerSinkFactory, revision?: () => unknown): void;
|
|
25
|
+
reset(): void;
|
|
26
|
+
delete(id: string): void;
|
|
27
|
+
record(id: string, event: AgentEvent): void;
|
|
28
|
+
layout(id: string, width: number): TranscriptLayout | undefined;
|
|
29
|
+
lines(id: string, width: number): readonly string[] | undefined;
|
|
30
|
+
private render;
|
|
31
|
+
}
|