@vascend/talos 0.1.4 → 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/LICENSE +22 -0
- package/README.md +37 -12
- package/dist/core/agent.d.ts +16 -3
- package/dist/core/agent.js +170 -70
- 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.d.ts +3 -0
- package/dist/core/session-naming.js +47 -0
- package/dist/core/session-usage.js +4 -1
- package/dist/core/session.d.ts +5 -2
- package/dist/core/session.js +22 -7
- 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/storage-health.d.ts +24 -0
- package/dist/core/storage-health.js +72 -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 +26 -2
- 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 +35 -0
- package/dist/tui/agent-sessions-screen.js +178 -0
- package/dist/tui/agent-sessions.d.ts +53 -0
- package/dist/tui/agent-sessions.js +236 -0
- package/dist/tui/bottom-input.d.ts +57 -2
- package/dist/tui/bottom-input.js +449 -54
- 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 +6 -2
- 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 +351 -2
- 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/logo.js +3 -0
- package/dist/tui/model-details.js +13 -12
- package/dist/tui/model-picker.d.ts +2 -0
- package/dist/tui/model-picker.js +59 -0
- package/dist/tui/model-selection.js +19 -10
- package/dist/tui/native-scrollback.d.ts +2 -1
- package/dist/tui/native-scrollback.js +31 -6
- package/dist/tui/prompt-queue-store.d.ts +1 -0
- package/dist/tui/prompt-queue-store.js +4 -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/queued-questions.js +2 -4
- package/dist/tui/quick-help.d.ts +1 -1
- package/dist/tui/quick-help.js +3 -0
- package/dist/tui/remote-command.d.ts +7 -0
- package/dist/tui/remote-command.js +79 -0
- package/dist/tui/repl.d.ts +11 -2
- package/dist/tui/repl.js +457 -327
- package/dist/tui/review-store.d.ts +8 -1
- package/dist/tui/review-store.js +64 -11
- 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/scrollback-archive.d.ts +16 -0
- package/dist/tui/scrollback-archive.js +90 -0
- package/dist/tui/selector-mouse.d.ts +1 -0
- package/dist/tui/selector-mouse.js +1 -1
- package/dist/tui/selector.d.ts +11 -0
- package/dist/tui/selector.js +28 -4
- package/dist/tui/session-controls.d.ts +26 -0
- package/dist/tui/session-controls.js +65 -0
- package/dist/tui/session-naming-notice.d.ts +14 -0
- package/dist/tui/session-naming-notice.js +59 -0
- package/dist/tui/session-picker.d.ts +2 -0
- package/dist/tui/session-picker.js +32 -0
- 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/storage-warning.d.ts +2 -0
- package/dist/tui/storage-warning.js +35 -0
- 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-feedback-loop.js +2 -0
- 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.d.ts +1 -0
- package/dist/vascend/context-snapshot.js +8 -5
- package/dist/vascend/context-store-reader.d.ts +20 -0
- package/dist/vascend/context-store-reader.js +46 -0
- package/dist/vascend/context-window-compiler.d.ts +1 -0
- package/dist/vascend/context-window-compiler.js +1 -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 +52 -17
- 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 +22 -11
- package/dist/vascend/memory-packet.d.ts +2 -1
- package/dist/vascend/memory-packet.js +31 -44
- package/dist/vascend/memory-store.d.ts +1 -0
- package/dist/vascend/memory-store.js +12 -4
- package/dist/vascend/plan-store.d.ts +3 -1
- package/dist/vascend/plan-store.js +6 -4
- package/dist/vascend/room-memory.d.ts +13 -2
- package/dist/vascend/room-memory.js +48 -6
- 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 +2 -2
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type StatoRelay = "connessione" | "collegato" | "attesa" | "chiuso";
|
|
2
|
+
export type CodiceRelay = "piano" | "token" | "troppe_sessioni" | "occupato" | "rete" | "risposta" | "finita";
|
|
3
|
+
export declare class RelayError extends Error {
|
|
4
|
+
readonly code: CodiceRelay;
|
|
5
|
+
readonly status?: number | undefined;
|
|
6
|
+
constructor(code: CodiceRelay, status?: number | undefined);
|
|
7
|
+
}
|
|
8
|
+
export interface ComandoRelay {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly cid: string;
|
|
11
|
+
readonly k: string;
|
|
12
|
+
readonly b?: string;
|
|
13
|
+
}
|
|
14
|
+
type FetchLike = (url: string, init?: RequestInit) => Promise<Response>;
|
|
15
|
+
type SocketCtor = new (url: string) => WebSocket;
|
|
16
|
+
export interface RelayDeps {
|
|
17
|
+
readonly token: () => Promise<string>;
|
|
18
|
+
readonly fetchImpl?: FetchLike;
|
|
19
|
+
readonly WebSocketImpl?: SocketCtor;
|
|
20
|
+
readonly wait?: (ms: number) => Promise<void>;
|
|
21
|
+
readonly onStato?: (stato: StatoRelay, motivo?: string) => void;
|
|
22
|
+
readonly onComando?: (comando: ComandoRelay) => Promise<void>;
|
|
23
|
+
readonly onPresenza?: (viewer: number) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare const RELAY_URL = "https://relay.vascend.it";
|
|
26
|
+
export declare function relayOf(env: NodeJS.ProcessEnv): string;
|
|
27
|
+
export interface MetaSessione {
|
|
28
|
+
readonly title: string;
|
|
29
|
+
readonly agent: string;
|
|
30
|
+
readonly device: string;
|
|
31
|
+
}
|
|
32
|
+
export declare class RelayHost {
|
|
33
|
+
private readonly base;
|
|
34
|
+
readonly sessione: string;
|
|
35
|
+
private readonly deps;
|
|
36
|
+
private ws;
|
|
37
|
+
private stato;
|
|
38
|
+
private prossimo;
|
|
39
|
+
private readonly inAttesa;
|
|
40
|
+
private readonly eseguiti;
|
|
41
|
+
private catenaComandi;
|
|
42
|
+
private chiuso;
|
|
43
|
+
private tentativi;
|
|
44
|
+
private constructor();
|
|
45
|
+
static apri(base: string, meta: MetaSessione, deps: RelayDeps): Promise<RelayHost>;
|
|
46
|
+
statoAttuale(): StatoRelay;
|
|
47
|
+
invia(k: string, b?: string): boolean;
|
|
48
|
+
chiudi(attesaMs?: number): Promise<void>;
|
|
49
|
+
private cambiaStato;
|
|
50
|
+
private connetti;
|
|
51
|
+
private riconnetti;
|
|
52
|
+
private spedisci;
|
|
53
|
+
private daRelay;
|
|
54
|
+
private ciao;
|
|
55
|
+
private conferma;
|
|
56
|
+
private comando;
|
|
57
|
+
}
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Talos come host del relay delle sessioni (relay.vascend.it): possiede la
|
|
2
|
+
// sessione, trasmette gli eventi e riceve i comandi dei dispositivi abbinati.
|
|
3
|
+
// Protocollo: vascend-streaming/services/relay/src/protocollo.ts.
|
|
4
|
+
//
|
|
5
|
+
// - HTTP: POST /v1/sessions crea la sessione e da' un ticket da host;
|
|
6
|
+
// POST /v1/sessions/:id/tickets ne da' uno nuovo a ogni riconnessione.
|
|
7
|
+
// - WebSocket /v1/connect?ticket=...: il ticket vale 30 s e una volta sola,
|
|
8
|
+
// il token non finisce mai in un URL.
|
|
9
|
+
// - seq contigui da last_seq+1 (arriva nel hello), conferma cumulativa
|
|
10
|
+
// (ack). Gli eventi non confermati restano qui e si rimandano dopo una
|
|
11
|
+
// riconnessione; il relay scarta i doppioni.
|
|
12
|
+
// - i comandi si confermano (cmd_ack) dopo averli eseguiti: dopo una
|
|
13
|
+
// riconnessione il relay rimanda quelli non confermati, e qui si
|
|
14
|
+
// scartano per cid.
|
|
15
|
+
export class RelayError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
status;
|
|
18
|
+
constructor(code, status) {
|
|
19
|
+
super(code);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.status = status;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export const RELAY_URL = "https://relay.vascend.it";
|
|
25
|
+
const TIMEOUT_MS = 12_000;
|
|
26
|
+
const ID_SESSIONE = /^s_[0-9a-f]{32}$/;
|
|
27
|
+
const TICKET = /^[0-9a-f]{64}$/;
|
|
28
|
+
const ID_STREAM = /^\d{1,15}-\d{1,10}$/;
|
|
29
|
+
const CID = /^[A-Za-z0-9_-]{8,64}$/;
|
|
30
|
+
// Oltre questi eventi non confermati si smette di mandare i delta (effimeri);
|
|
31
|
+
// gli eventi durevoli restano tutti, e si scarta solo sopra il tetto massimo.
|
|
32
|
+
const TETTO_DELTA = 2_000;
|
|
33
|
+
const TETTO_EVENTI = 20_000;
|
|
34
|
+
// Codici di chiusura del relay (connessioni.ts) dopo cui riprovare non serve.
|
|
35
|
+
const CHIUSURE_DEFINITIVE = {
|
|
36
|
+
4001: "accesso non valido",
|
|
37
|
+
4003: "accesso negato",
|
|
38
|
+
4009: "sessione aperta da un altro dispositivo",
|
|
39
|
+
4400: "messaggi non validi",
|
|
40
|
+
4404: "sessione chiusa",
|
|
41
|
+
};
|
|
42
|
+
function urlValido(url) {
|
|
43
|
+
try {
|
|
44
|
+
const u = new URL(url);
|
|
45
|
+
if (u.username || u.password || u.search || u.hash)
|
|
46
|
+
return false;
|
|
47
|
+
if (u.protocol === "https:")
|
|
48
|
+
return true;
|
|
49
|
+
return u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Override solo per sviluppo/collaudo (stesse regole dell'issuer di Vascend ID).
|
|
56
|
+
export function relayOf(env) {
|
|
57
|
+
const override = String(env["TALOS_RELAY_URL"] ?? "").trim();
|
|
58
|
+
return (override && urlValido(override) ? override : RELAY_URL).replace(/\/+$/, "");
|
|
59
|
+
}
|
|
60
|
+
export class RelayHost {
|
|
61
|
+
base;
|
|
62
|
+
sessione;
|
|
63
|
+
deps;
|
|
64
|
+
ws = null;
|
|
65
|
+
stato = "connessione";
|
|
66
|
+
prossimo = 1;
|
|
67
|
+
inAttesa = [];
|
|
68
|
+
eseguiti = new Set();
|
|
69
|
+
catenaComandi = Promise.resolve();
|
|
70
|
+
chiuso = false;
|
|
71
|
+
tentativi = 0;
|
|
72
|
+
constructor(base, sessione, deps) {
|
|
73
|
+
this.base = base;
|
|
74
|
+
this.sessione = sessione;
|
|
75
|
+
this.deps = deps;
|
|
76
|
+
}
|
|
77
|
+
// Crea la sessione sul relay e apre il collegamento.
|
|
78
|
+
static async apri(base, meta, deps) {
|
|
79
|
+
const data = await richiesta(base, deps, "POST", "/v1/sessions", meta);
|
|
80
|
+
const sessione = data["session"]?.["id"];
|
|
81
|
+
const ticket = data["ticket"];
|
|
82
|
+
if (typeof sessione !== "string" || !ID_SESSIONE.test(sessione) || typeof ticket !== "string" || !TICKET.test(ticket)) {
|
|
83
|
+
throw new RelayError("risposta");
|
|
84
|
+
}
|
|
85
|
+
const host = new RelayHost(base, sessione, deps);
|
|
86
|
+
host.connetti(ticket);
|
|
87
|
+
return host;
|
|
88
|
+
}
|
|
89
|
+
statoAttuale() { return this.stato; }
|
|
90
|
+
// Evento da trasmettere; `b` e' gia' cifrato. Falso se non e' stato accodato.
|
|
91
|
+
invia(k, b) {
|
|
92
|
+
if (this.chiuso)
|
|
93
|
+
return false;
|
|
94
|
+
if (this.inAttesa.length >= TETTO_EVENTI || (k === "delta" && this.inAttesa.length >= TETTO_DELTA))
|
|
95
|
+
return false;
|
|
96
|
+
const voce = { seq: this.prossimo++, k, b };
|
|
97
|
+
this.inAttesa.push(voce);
|
|
98
|
+
if (this.stato === "collegato")
|
|
99
|
+
this.spedisci(voce);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
// Chiude per tutti: ultimi eventi (breve attesa della conferma), poi la
|
|
103
|
+
// sessione sul relay e il WebSocket.
|
|
104
|
+
async chiudi(attesaMs = 1_500) {
|
|
105
|
+
if (this.chiuso)
|
|
106
|
+
return;
|
|
107
|
+
const fine = Date.now() + attesaMs;
|
|
108
|
+
const wait = this.deps.wait ?? ((ms) => new Promise(r => setTimeout(r, ms)));
|
|
109
|
+
while (this.inAttesa.length > 0 && this.stato === "collegato" && Date.now() < fine)
|
|
110
|
+
await wait(50);
|
|
111
|
+
this.chiuso = true;
|
|
112
|
+
this.cambiaStato("chiuso");
|
|
113
|
+
try {
|
|
114
|
+
this.ws?.close(1000, "fine");
|
|
115
|
+
}
|
|
116
|
+
catch { /* gia' chiuso */ }
|
|
117
|
+
this.ws = null;
|
|
118
|
+
try {
|
|
119
|
+
await richiesta(this.base, this.deps, "DELETE", `/v1/sessions/${this.sessione}`);
|
|
120
|
+
}
|
|
121
|
+
catch { /* best-effort */ }
|
|
122
|
+
}
|
|
123
|
+
// --- collegamento -----------------------------------------------------------
|
|
124
|
+
cambiaStato(stato, motivo) {
|
|
125
|
+
if (this.stato === stato && !motivo)
|
|
126
|
+
return;
|
|
127
|
+
this.stato = stato;
|
|
128
|
+
this.deps.onStato?.(stato, motivo);
|
|
129
|
+
}
|
|
130
|
+
connetti(ticket) {
|
|
131
|
+
if (this.chiuso)
|
|
132
|
+
return;
|
|
133
|
+
const Ctor = this.deps.WebSocketImpl ?? globalThis.WebSocket;
|
|
134
|
+
if (!Ctor) {
|
|
135
|
+
this.chiuso = true;
|
|
136
|
+
this.cambiaStato("chiuso", "WebSocket non disponibile in questo runtime");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const ws = new Ctor(`${this.base.replace(/^http/, "ws")}/v1/connect?ticket=${ticket}`);
|
|
140
|
+
this.ws = ws;
|
|
141
|
+
ws.addEventListener("message", (ev) => {
|
|
142
|
+
if (this.ws === ws && typeof ev.data === "string")
|
|
143
|
+
this.daRelay(ev.data);
|
|
144
|
+
});
|
|
145
|
+
ws.addEventListener("close", (ev) => {
|
|
146
|
+
if (this.ws !== ws)
|
|
147
|
+
return;
|
|
148
|
+
this.ws = null;
|
|
149
|
+
if (this.chiuso)
|
|
150
|
+
return;
|
|
151
|
+
const definitiva = CHIUSURE_DEFINITIVE[ev.code];
|
|
152
|
+
if (definitiva) {
|
|
153
|
+
this.chiuso = true;
|
|
154
|
+
this.cambiaStato("chiuso", definitiva);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.cambiaStato("attesa");
|
|
158
|
+
void this.riconnetti();
|
|
159
|
+
});
|
|
160
|
+
ws.addEventListener("error", () => { });
|
|
161
|
+
}
|
|
162
|
+
async riconnetti() {
|
|
163
|
+
const wait = this.deps.wait ?? ((ms) => new Promise(r => setTimeout(r, ms)));
|
|
164
|
+
while (!this.chiuso) {
|
|
165
|
+
const attesa = Math.min(30_000, 1_000 * 2 ** Math.min(this.tentativi, 5));
|
|
166
|
+
this.tentativi++;
|
|
167
|
+
await wait(attesa);
|
|
168
|
+
if (this.chiuso)
|
|
169
|
+
return;
|
|
170
|
+
try {
|
|
171
|
+
const data = await richiesta(this.base, this.deps, "POST", `/v1/sessions/${this.sessione}/tickets`, { role: "host" });
|
|
172
|
+
const ticket = data["ticket"];
|
|
173
|
+
if (typeof ticket !== "string" || !TICKET.test(ticket))
|
|
174
|
+
throw new RelayError("risposta");
|
|
175
|
+
this.cambiaStato("connessione");
|
|
176
|
+
this.connetti(ticket);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
const code = e instanceof RelayError ? e.code : "rete";
|
|
181
|
+
// Sessione chiusa o piano scaduto: inutile riprovare.
|
|
182
|
+
if (code === "finita" || code === "piano" || code === "token") {
|
|
183
|
+
this.chiuso = true;
|
|
184
|
+
this.cambiaStato("chiuso", code);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
spedisci(voce) {
|
|
191
|
+
try {
|
|
192
|
+
this.ws?.send(JSON.stringify({ t: "ev", seq: voce.seq, k: voce.k, b: voce.b }));
|
|
193
|
+
}
|
|
194
|
+
catch { /* ripreso al prossimo hello */ }
|
|
195
|
+
}
|
|
196
|
+
daRelay(raw) {
|
|
197
|
+
let f;
|
|
198
|
+
try {
|
|
199
|
+
f = JSON.parse(raw);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!f || typeof f !== "object")
|
|
205
|
+
return;
|
|
206
|
+
switch (f["t"]) {
|
|
207
|
+
case "hello": return this.ciao(Number(f["last_seq"]));
|
|
208
|
+
case "ack": return this.conferma(Number(f["seq"]));
|
|
209
|
+
case "cmd": return this.comando(f);
|
|
210
|
+
case "presence":
|
|
211
|
+
this.deps.onPresenza?.(Number(f["viewers"]) || 0);
|
|
212
|
+
return;
|
|
213
|
+
default: return;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
ciao(ultimo) {
|
|
217
|
+
const salvato = Number.isSafeInteger(ultimo) && ultimo >= 0 ? ultimo : 0;
|
|
218
|
+
this.conferma(salvato);
|
|
219
|
+
// Il relay ha perso degli eventi (es. Redis svuotato): si rinumera da li'
|
|
220
|
+
// invece di restare fuori sequenza per sempre.
|
|
221
|
+
if (this.inAttesa.length > 0 && this.inAttesa[0].seq !== salvato + 1) {
|
|
222
|
+
this.inAttesa.forEach((v, i) => { v.seq = salvato + 1 + i; });
|
|
223
|
+
}
|
|
224
|
+
this.prossimo = Math.max(this.prossimo, salvato + 1 + this.inAttesa.length);
|
|
225
|
+
if (this.inAttesa.length === 0)
|
|
226
|
+
this.prossimo = Math.max(salvato + 1, this.prossimo);
|
|
227
|
+
this.tentativi = 0;
|
|
228
|
+
this.cambiaStato("collegato");
|
|
229
|
+
for (const v of this.inAttesa)
|
|
230
|
+
this.spedisci(v);
|
|
231
|
+
}
|
|
232
|
+
conferma(seq) {
|
|
233
|
+
if (!Number.isSafeInteger(seq))
|
|
234
|
+
return;
|
|
235
|
+
while (this.inAttesa.length > 0 && this.inAttesa[0].seq <= seq)
|
|
236
|
+
this.inAttesa.shift();
|
|
237
|
+
}
|
|
238
|
+
comando(f) {
|
|
239
|
+
const id = f["id"], cid = f["cid"], k = f["k"], b = f["b"];
|
|
240
|
+
if (typeof id !== "string" || !ID_STREAM.test(id) || typeof cid !== "string" || !CID.test(cid) || typeof k !== "string")
|
|
241
|
+
return;
|
|
242
|
+
const ws = this.ws;
|
|
243
|
+
const ack = () => { try {
|
|
244
|
+
if (this.ws === ws)
|
|
245
|
+
ws?.send(JSON.stringify({ t: "cmd_ack", id }));
|
|
246
|
+
}
|
|
247
|
+
catch { /* rimandato dal relay */ } };
|
|
248
|
+
if (this.eseguiti.has(cid)) {
|
|
249
|
+
ack();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
// In ordine, uno alla volta: un "stop" dopo un messaggio deve arrivare dopo.
|
|
253
|
+
this.catenaComandi = this.catenaComandi.then(async () => {
|
|
254
|
+
if (this.eseguiti.has(cid))
|
|
255
|
+
return ack();
|
|
256
|
+
this.eseguiti.add(cid);
|
|
257
|
+
if (this.eseguiti.size > 5_000)
|
|
258
|
+
this.eseguiti.delete(this.eseguiti.values().next().value);
|
|
259
|
+
try {
|
|
260
|
+
await this.deps.onComando?.({ id, cid, k, b: typeof b === "string" ? b : undefined });
|
|
261
|
+
}
|
|
262
|
+
catch { /* un comando rotto non ferma gli altri */ }
|
|
263
|
+
ack();
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// --- HTTP ---------------------------------------------------------------------
|
|
268
|
+
async function richiesta(base, deps, method, path, body) {
|
|
269
|
+
const token = await deps.token();
|
|
270
|
+
const f = deps.fetchImpl ?? fetch;
|
|
271
|
+
let response;
|
|
272
|
+
let raw;
|
|
273
|
+
try {
|
|
274
|
+
response = await f(base + path, {
|
|
275
|
+
method,
|
|
276
|
+
headers: {
|
|
277
|
+
Authorization: `Bearer ${token}`,
|
|
278
|
+
Accept: "application/json",
|
|
279
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
280
|
+
},
|
|
281
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
282
|
+
redirect: "error",
|
|
283
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
284
|
+
});
|
|
285
|
+
raw = await response.text();
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
throw new RelayError("rete");
|
|
289
|
+
}
|
|
290
|
+
if (raw.length > 65_536)
|
|
291
|
+
throw new RelayError("risposta", response.status);
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
const s = response.status;
|
|
294
|
+
if (s === 401)
|
|
295
|
+
throw new RelayError("token", s);
|
|
296
|
+
if (s === 402)
|
|
297
|
+
throw new RelayError("piano", s);
|
|
298
|
+
if (s === 404 || s === 409)
|
|
299
|
+
throw new RelayError("finita", s);
|
|
300
|
+
if (s === 429)
|
|
301
|
+
throw new RelayError("troppe_sessioni", s);
|
|
302
|
+
if (s === 503)
|
|
303
|
+
throw new RelayError("occupato", s);
|
|
304
|
+
throw new RelayError(s >= 500 ? "rete" : "risposta", s);
|
|
305
|
+
}
|
|
306
|
+
if (!raw)
|
|
307
|
+
return {};
|
|
308
|
+
try {
|
|
309
|
+
const data = JSON.parse(raw);
|
|
310
|
+
if (data && typeof data === "object" && !Array.isArray(data))
|
|
311
|
+
return data;
|
|
312
|
+
}
|
|
313
|
+
catch { /* sotto */ }
|
|
314
|
+
throw new RelayError("risposta", response.status);
|
|
315
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AgentEvent } from "../core/types.js";
|
|
2
|
+
import { type RelayDeps, type StatoRelay } from "./relay-client.js";
|
|
3
|
+
export declare const VIEWER_URL = "https://vascend.it/remoto";
|
|
4
|
+
export interface ManiRemote {
|
|
5
|
+
readonly invia: (testo: string, subito: boolean) => boolean;
|
|
6
|
+
readonly ferma: () => boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface AvvioRemoto {
|
|
9
|
+
readonly base: string;
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
readonly mani: ManiRemote;
|
|
12
|
+
readonly token: RelayDeps["token"];
|
|
13
|
+
readonly onStato?: (stato: StatoRelay, motivo?: string) => void;
|
|
14
|
+
readonly onPresenza?: (viewer: number) => void;
|
|
15
|
+
readonly relayDeps?: Omit<RelayDeps, "token" | "onStato" | "onComando" | "onPresenza">;
|
|
16
|
+
readonly now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
export declare class SessioneRemota {
|
|
19
|
+
private readonly relay;
|
|
20
|
+
private readonly chiave;
|
|
21
|
+
readonly link: string;
|
|
22
|
+
private readonly mani;
|
|
23
|
+
private readonly now;
|
|
24
|
+
private catena;
|
|
25
|
+
private delta;
|
|
26
|
+
private timerDelta;
|
|
27
|
+
private risposta;
|
|
28
|
+
private viewer;
|
|
29
|
+
private readonly permessi;
|
|
30
|
+
private constructor();
|
|
31
|
+
static avvia(opts: AvvioRemoto): Promise<SessioneRemota>;
|
|
32
|
+
get id(): string;
|
|
33
|
+
statoRelay(): StatoRelay;
|
|
34
|
+
dispositivi(): number;
|
|
35
|
+
osserva(event: AgentEvent): void;
|
|
36
|
+
testo(delta: string): void;
|
|
37
|
+
rispostaCompleta(): void;
|
|
38
|
+
chiamataTool(c: {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
readonly input: unknown;
|
|
42
|
+
readonly parent?: string;
|
|
43
|
+
}): void;
|
|
44
|
+
risultatoTool(r: {
|
|
45
|
+
readonly id: string;
|
|
46
|
+
readonly error: boolean;
|
|
47
|
+
readonly output: string;
|
|
48
|
+
readonly parent?: string;
|
|
49
|
+
}): void;
|
|
50
|
+
statoAgente(state: "thinking" | "working" | "waiting" | "idle" | "error", label?: string): void;
|
|
51
|
+
messaggioUtente(testo: string): void;
|
|
52
|
+
permessoConGara(azione: string, dettaglio: string, signal: AbortSignal, tui: (signal: AbortSignal) => Promise<boolean>): Promise<boolean>;
|
|
53
|
+
chiudi(): Promise<void>;
|
|
54
|
+
private comando;
|
|
55
|
+
private svuotaDelta;
|
|
56
|
+
private chiudiRisposta;
|
|
57
|
+
private stato;
|
|
58
|
+
private evento;
|
|
59
|
+
private accoda;
|
|
60
|
+
}
|