@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
package/dist/tools/program.js
CHANGED
|
@@ -64,11 +64,12 @@ const BRIDGE_DENYLIST = new Set([
|
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
65
|
const KERNEL_SOURCE = `"use strict";
|
|
66
66
|
const util = require("node:util");
|
|
67
|
+
const { AsyncLocalStorage } = require("node:async_hooks");
|
|
68
|
+
const executionScope = new AsyncLocalStorage();
|
|
67
69
|
let buf = "";
|
|
68
70
|
let seq = 0;
|
|
69
71
|
const pending = new Map();
|
|
70
72
|
const state = {};
|
|
71
|
-
let inputData = {};
|
|
72
73
|
|
|
73
74
|
function send(msg) { process.stdout.write(JSON.stringify(msg) + "\\n"); }
|
|
74
75
|
|
|
@@ -80,10 +81,12 @@ function render(value) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
function callTool(name, args, structured = false) {
|
|
84
|
+
const scope = executionScope.getStore();
|
|
85
|
+
if (!scope?.active) return Promise.reject(new Error("programma gia' concluso"));
|
|
83
86
|
return new Promise((resolve, reject) => {
|
|
84
87
|
seq += 1;
|
|
85
88
|
pending.set(seq, { resolve, reject, structured });
|
|
86
|
-
send({ t: "call", seq, name, args: args === undefined ? {} : args });
|
|
89
|
+
send({ t: "call", id: scope.id, seq, name, args: args === undefined ? {} : args });
|
|
87
90
|
});
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -92,7 +95,7 @@ function callTool(name, args, structured = false) {
|
|
|
92
95
|
const tools = new Proxy({}, {
|
|
93
96
|
get(_t, prop) {
|
|
94
97
|
if (typeof prop !== "string" || prop === "then" || prop === "toJSON") return undefined;
|
|
95
|
-
if (prop === "$data") return
|
|
98
|
+
if (prop === "$data") return executionScope.getStore()?.data ?? {};
|
|
96
99
|
if (prop === "$result") return (name, args) => callTool(name, args, true);
|
|
97
100
|
return (args) => callTool(prop, args);
|
|
98
101
|
},
|
|
@@ -100,24 +103,31 @@ const tools = new Proxy({}, {
|
|
|
100
103
|
|
|
101
104
|
const fakeConsole = {};
|
|
102
105
|
for (const level of ["log", "info", "warn", "error", "debug", "trace"]) {
|
|
103
|
-
fakeConsole[level] = (...parts) =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
fakeConsole[level] = (...parts) => {
|
|
107
|
+
const scope = executionScope.getStore();
|
|
108
|
+
if (scope?.active) send({
|
|
109
|
+
t: "log", id: scope.id,
|
|
110
|
+
line: parts.map((p) => (typeof p === "string" ? p : render(p))).join(" "),
|
|
111
|
+
});
|
|
112
|
+
};
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
async function execute(msg) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
const scope = { id: msg.id, data: msg.data ?? {}, active: true };
|
|
117
|
+
return executionScope.run(scope, async () => {
|
|
118
|
+
const AsyncFunction = (async () => {}).constructor;
|
|
119
|
+
try {
|
|
120
|
+
// require/process oscurati: e' un invito a passare dai tool (che portano i
|
|
121
|
+
// permessi), non un confine di sicurezza — il confine e' la conferma utente.
|
|
122
|
+
const fn = new AsyncFunction("tools", "state", "console", "require", "process", msg.code);
|
|
123
|
+
const value = await fn(tools, state, fakeConsole, undefined, undefined);
|
|
124
|
+
send({ t: "done", id: msg.id, value: render(value) });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
send({ t: "done", id: msg.id, error: err instanceof Error ? (err.stack || err.message) : String(err) });
|
|
127
|
+
} finally {
|
|
128
|
+
scope.active = false;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
121
131
|
}
|
|
122
132
|
|
|
123
133
|
process.stdin.setEncoding("utf8");
|
|
@@ -255,7 +265,7 @@ class ProgramKernel {
|
|
|
255
265
|
}
|
|
256
266
|
async onMessage(msg) {
|
|
257
267
|
const cur = this.current;
|
|
258
|
-
if (!cur)
|
|
268
|
+
if (!cur || Number(msg["id"]) !== cur.id)
|
|
259
269
|
return;
|
|
260
270
|
if (msg["t"] === "log") {
|
|
261
271
|
this.appendLog(cur, String(msg["line"] ?? ""));
|
|
@@ -307,8 +317,11 @@ class ProgramKernel {
|
|
|
307
317
|
cur.logsTruncatedLines += 1;
|
|
308
318
|
return;
|
|
309
319
|
}
|
|
310
|
-
cur.
|
|
311
|
-
cur.
|
|
320
|
+
const remaining = Math.max(0, MAX_LOG_CHARS - cur.logChars - 1);
|
|
321
|
+
cur.logs.push(line.slice(0, remaining));
|
|
322
|
+
cur.logChars += Math.min(line.length, remaining) + 1;
|
|
323
|
+
if (line.length > remaining)
|
|
324
|
+
cur.logsTruncatedLines += 1;
|
|
312
325
|
}
|
|
313
326
|
send(msg) {
|
|
314
327
|
try {
|
package/dist/tools/team-tools.js
CHANGED
|
@@ -257,7 +257,7 @@ export const teamWaitTool = {
|
|
|
257
257
|
const combacia = (m) => (!from || addressKey(m.from) === from) && (!contains || m.text.toLowerCase().includes(contains));
|
|
258
258
|
const canale = bus(ctx);
|
|
259
259
|
for (;;) {
|
|
260
|
-
if (ctx.signal.aborted)
|
|
260
|
+
if (ctx.signal.aborted || ctx.waitInterrupted?.())
|
|
261
261
|
return "attesa interrotta";
|
|
262
262
|
const msgs = canale.inbox(id, me, combacia);
|
|
263
263
|
if (msgs.length)
|
|
@@ -323,20 +323,25 @@ export const teamReportTool = {
|
|
|
323
323
|
const richiesti = Number(input["timeout_s"]);
|
|
324
324
|
const secondi = Number.isFinite(richiesti) ? Math.min(WAIT_MAX_S, Math.max(1, richiesti)) : WAIT_DEFAULT_S;
|
|
325
325
|
const deadline = Date.now() + secondi * 1000;
|
|
326
|
+
// Keep the original target even if a newer team appears during the wait.
|
|
327
|
+
const selected = () => teamById(team.sessionId, team.id) ?? team;
|
|
326
328
|
while (Date.now() < deadline) {
|
|
327
|
-
if (ctx.signal.aborted)
|
|
328
|
-
return reportResult("attesa interrotta; il lavoro del team non viene cancellato",
|
|
329
|
-
const current =
|
|
329
|
+
if (ctx.signal.aborted || ctx.waitInterrupted?.())
|
|
330
|
+
return reportResult("attesa interrotta; il lavoro del team non viene cancellato", selected());
|
|
331
|
+
const current = selected();
|
|
330
332
|
if (current?.done)
|
|
331
333
|
return finished(current);
|
|
332
334
|
try {
|
|
333
335
|
await sleep(Math.min(250, Math.max(1, deadline - Date.now())), ctx.signal);
|
|
334
336
|
}
|
|
335
337
|
catch {
|
|
336
|
-
return reportResult("attesa interrotta; il lavoro del team non viene cancellato",
|
|
338
|
+
return reportResult("attesa interrotta; il lavoro del team non viene cancellato", selected());
|
|
337
339
|
}
|
|
338
340
|
}
|
|
339
|
-
|
|
341
|
+
const current = selected();
|
|
342
|
+
if (current.done)
|
|
343
|
+
return finished(current);
|
|
344
|
+
return reportResult(`non hanno ancora finito dopo ${secondi}s: ${pendingMembers(current).join(", ")}. Richiama team_report piu' tardi.`, current);
|
|
340
345
|
},
|
|
341
346
|
};
|
|
342
347
|
export const teamTools = [
|
|
@@ -3,7 +3,8 @@ import { stdin, stdout } from "node:process";
|
|
|
3
3
|
import { tr } from "./i18n.js";
|
|
4
4
|
import { initializeLanguage, onboardingLanguage } from "./language-preference.js";
|
|
5
5
|
import { leggiOnboarding, salvaOnboarding, serveTermini, TESTI, consensoAffermativo, TERMINI_VERSIONE, LINK_TERMINI, LINK_PRIVACY } from "./onboarding.js";
|
|
6
|
-
import { decisioneCancello,
|
|
6
|
+
import { decisioneCancello, statoSessione, valutaLicenzaLocale, rimuoviTokenVascend, rinnovaProvaOffline } from "./vascend-login.js";
|
|
7
|
+
import { rimuoviSessione, salvaSessione, tokenAgente } from "./vascend-id.js";
|
|
7
8
|
import { startWebLogin, waitForWebLogin, WebLoginError } from "./web-login.js";
|
|
8
9
|
import { showWebLoginScreen } from "./web-login-screen.js";
|
|
9
10
|
import { CliCancelled } from "../core/cli-cancelled.js";
|
|
@@ -14,12 +15,16 @@ export async function enterAccount(env, theme) {
|
|
|
14
15
|
// requires a verified session and current account permissions.
|
|
15
16
|
if (env["TALOS_NO_LOGIN"] === "1")
|
|
16
17
|
return "";
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
// Token per l'agente dalla sessione Vascend ID, rinnovato se serve. Il vecchio
|
|
19
|
+
// token del backend agentico non vale piu': chi lo aveva rifa' l'accesso.
|
|
20
|
+
const esito = await tokenAgente(env);
|
|
21
|
+
const saved = esito.token;
|
|
22
|
+
const session = saved ? await statoSessione(env, saved)
|
|
23
|
+
: esito.errore === "rete" ? { stato: "irraggiungibile" } : null;
|
|
19
24
|
if (!stdin.isTTY || !stdout.isTTY || env["TALOS_HOST_PROFILE"]?.toLowerCase() === "horizon")
|
|
20
25
|
return "";
|
|
21
26
|
const offline = session?.stato === "irraggiungibile" ? valutaLicenzaLocale(env) : null;
|
|
22
|
-
const decision = decisioneCancello(saved, session, offline);
|
|
27
|
+
const decision = decisioneCancello(saved || (esito.errore === "rete" ? "offline" : ""), session, offline);
|
|
23
28
|
let tier = decision.tier;
|
|
24
29
|
let token = saved;
|
|
25
30
|
const note = () => tr("Account connesso", "Account connected") +
|
|
@@ -38,18 +43,18 @@ export async function enterAccount(env, theme) {
|
|
|
38
43
|
const write = (text) => { stdout.write(` ${text}\n`); };
|
|
39
44
|
try {
|
|
40
45
|
if (decision.azione === "login") {
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
rimuoviSessione(env);
|
|
47
|
+
rimuoviTokenVascend(env);
|
|
43
48
|
stdout.write(`\n ${tr("Preparazione dell'accesso a Talos...", "Preparing Talos sign-in...")}`);
|
|
44
49
|
const grant = await startWebLogin(env, language, {}, abort.signal)
|
|
45
50
|
.finally(() => { stdout.write(env.TERM === "dumb" ? "\n" : "\r\x1b[2K"); });
|
|
46
51
|
const finish = showWebLoginScreen(grant, abort, { env, theme });
|
|
47
52
|
try {
|
|
48
|
-
const account = await waitForWebLogin(env, grant, {}, abort.signal);
|
|
49
|
-
if (!
|
|
53
|
+
const account = await waitForWebLogin(env, grant, language, {}, abort.signal);
|
|
54
|
+
if (!salvaSessione(env, account))
|
|
50
55
|
throw new Error(TESTI[language].tokenNonSalvabile);
|
|
51
56
|
token = account.token;
|
|
52
|
-
tier =
|
|
57
|
+
tier = undefined;
|
|
53
58
|
finish(true);
|
|
54
59
|
write(TESTI[language].benvenuto(account.email));
|
|
55
60
|
}
|
|
@@ -1,21 +1,31 @@
|
|
|
1
|
-
import type { AgentSessions } from "./agent-sessions.js";
|
|
1
|
+
import type { AgentSession, AgentSessions } from "./agent-sessions.js";
|
|
2
2
|
import type { QueueStyle } from "./queue-screen.js";
|
|
3
3
|
import type { ScreenKey, TerminalScreen } from "./terminal-screen.js";
|
|
4
4
|
export declare function sessionDuration(ms: number): string;
|
|
5
5
|
export declare class AgentSessionsScreen implements TerminalScreen {
|
|
6
6
|
private readonly store;
|
|
7
7
|
private readonly style;
|
|
8
|
+
private readonly onDelete?;
|
|
8
9
|
private selected;
|
|
9
10
|
private active;
|
|
10
11
|
private offset;
|
|
11
12
|
private pageSize;
|
|
12
13
|
private maxOffset;
|
|
13
14
|
private follow;
|
|
15
|
+
private readonly transcript;
|
|
14
16
|
focused: boolean;
|
|
15
|
-
|
|
17
|
+
private deleteConfirmation?;
|
|
18
|
+
constructor(store: AgentSessions, style?: QueueStyle, onDelete?: ((session: AgentSession) => {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}) | undefined);
|
|
22
|
+
private deleteSelected;
|
|
23
|
+
get session(): AgentSession;
|
|
24
|
+
conversation(width: number, height: number): readonly string[];
|
|
16
25
|
get selectedId(): string;
|
|
17
26
|
get activeId(): string;
|
|
18
27
|
get available(): boolean;
|
|
28
|
+
returnToMain(): void;
|
|
19
29
|
sync(): void;
|
|
20
30
|
private select;
|
|
21
31
|
key(_text: string, key: ScreenKey): "close" | void;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { tr } from "./i18n.js";
|
|
2
2
|
import { PLAIN_QUEUE_STYLE } from "./queue-screen.js";
|
|
3
|
-
import { fitLine, oneLine, terminalWidth
|
|
3
|
+
import { fitLine, oneLine, terminalWidth } from "./terminal-text.js";
|
|
4
|
+
import { SessionTranscriptLayout } from "./session-transcript-layout.js";
|
|
5
|
+
import { agentLabel } from "./agent-label.js";
|
|
4
6
|
export function sessionDuration(ms) {
|
|
5
7
|
const seconds = Math.floor(Math.max(0, ms) / 1000);
|
|
6
8
|
return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
@@ -17,32 +19,69 @@ function metrics(session) {
|
|
|
17
19
|
export class AgentSessionsScreen {
|
|
18
20
|
store;
|
|
19
21
|
style;
|
|
22
|
+
onDelete;
|
|
20
23
|
selected = "main";
|
|
21
24
|
active = "main";
|
|
22
25
|
offset = 0;
|
|
23
26
|
pageSize = 1;
|
|
24
27
|
maxOffset = 0;
|
|
25
28
|
follow = true;
|
|
29
|
+
transcript = new SessionTranscriptLayout();
|
|
26
30
|
focused = false;
|
|
27
|
-
|
|
31
|
+
deleteConfirmation;
|
|
32
|
+
constructor(store, style = PLAIN_QUEUE_STYLE, onDelete) {
|
|
28
33
|
this.store = store;
|
|
29
34
|
this.style = style;
|
|
35
|
+
this.onDelete = onDelete;
|
|
36
|
+
}
|
|
37
|
+
deleteSelected() {
|
|
38
|
+
const session = this.store.get(this.selectedId);
|
|
39
|
+
if (!session || session.id === "main" || !this.onDelete)
|
|
40
|
+
return;
|
|
41
|
+
if (session.running && (this.deleteConfirmation?.id !== session.id || this.deleteConfirmation.until < Date.now())) {
|
|
42
|
+
this.deleteConfirmation = { id: session.id, until: Date.now() + 3000 };
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.deleteConfirmation = undefined;
|
|
46
|
+
const result = this.onDelete(session);
|
|
47
|
+
if (result.ok) {
|
|
48
|
+
this.store.delete(session.id);
|
|
49
|
+
this.sync();
|
|
50
|
+
}
|
|
51
|
+
else
|
|
52
|
+
this.store.notify(session.id, result.error ?? tr("Eliminazione non riuscita", "Deletion failed"));
|
|
53
|
+
}
|
|
54
|
+
get session() { return this.store.get(this.activeId); }
|
|
55
|
+
conversation(width, height) {
|
|
56
|
+
const session = this.session;
|
|
57
|
+
const lines = this.store.conversations.layout(session.id, width)
|
|
58
|
+
?? this.transcript.lines(session.id, [session.goal, ...session.transcript], Math.max(2, width - 2));
|
|
59
|
+
this.pageSize = Math.max(1, height);
|
|
60
|
+
this.maxOffset = Math.max(0, lines.length - this.pageSize);
|
|
61
|
+
this.offset = this.follow ? this.maxOffset : Math.min(this.offset, this.maxOffset);
|
|
62
|
+
return lines.slice(this.offset, this.offset + this.pageSize);
|
|
30
63
|
}
|
|
31
64
|
get selectedId() { this.sync(); return this.selected; }
|
|
32
65
|
get activeId() { this.sync(); return this.active; }
|
|
33
66
|
get available() { return this.store.hasAgents; }
|
|
67
|
+
returnToMain() {
|
|
68
|
+
this.active = "main";
|
|
69
|
+
this.selected = "main";
|
|
70
|
+
this.focused = false;
|
|
71
|
+
this.follow = true;
|
|
72
|
+
this.offset = 0;
|
|
73
|
+
}
|
|
34
74
|
sync() {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.
|
|
38
|
-
this.focused = false;
|
|
39
|
-
this.follow = true;
|
|
40
|
-
this.offset = 0;
|
|
41
|
-
}
|
|
75
|
+
this.transcript.retain(new Set(this.store.list().map(s => s.id)));
|
|
76
|
+
if (!this.store.get(this.active)?.live)
|
|
77
|
+
this.returnToMain();
|
|
42
78
|
if (!this.store.get(this.selected)?.live)
|
|
43
79
|
this.selected = this.active;
|
|
44
80
|
if (!this.available)
|
|
45
81
|
this.focused = false;
|
|
82
|
+
if (this.deleteConfirmation && (!this.focused || this.deleteConfirmation.id !== this.selected || this.deleteConfirmation.until < Date.now())) {
|
|
83
|
+
this.deleteConfirmation = undefined;
|
|
84
|
+
}
|
|
46
85
|
}
|
|
47
86
|
select(delta) {
|
|
48
87
|
this.sync();
|
|
@@ -53,6 +92,11 @@ export class AgentSessionsScreen {
|
|
|
53
92
|
}
|
|
54
93
|
key(_text, key) {
|
|
55
94
|
this.sync();
|
|
95
|
+
if (key.name === "delete" && this.focused) {
|
|
96
|
+
this.deleteSelected();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.deleteConfirmation = undefined;
|
|
56
100
|
if (key.name === "up" || key.name === "down")
|
|
57
101
|
this.select(key.name === "up" ? -1 : 1);
|
|
58
102
|
else if (key.name === "return" || key.name === "enter") {
|
|
@@ -88,14 +132,17 @@ export class AgentSessionsScreen {
|
|
|
88
132
|
const capacity = Math.max(1, maxRows - 1);
|
|
89
133
|
const index = Math.max(0, sessions.findIndex(s => s.id === this.selected));
|
|
90
134
|
const start = Math.max(0, Math.min(index - Math.floor(capacity / 2), sessions.length - capacity));
|
|
135
|
+
const deletion = this.onDelete && this.selected !== "main"
|
|
136
|
+
? this.deleteConfirmation ? tr("[Canc: conferma stop ed elimina]", "[Del: confirm stop and delete]")
|
|
137
|
+
: tr("[Canc: elimina sessione]", "[Del: delete session]") : "";
|
|
91
138
|
const help = this.focused
|
|
92
|
-
? tr("↑↓ seleziona · Invio apri
|
|
139
|
+
? `${deletion ? deletion + " · " : ""}${tr("↑↓ seleziona · Invio apri", "↑↓ select · Enter open")}`
|
|
93
140
|
: tr("↓ sessioni · Alt+A seleziona", "↓ sessions · Alt+A select");
|
|
94
141
|
const range = sessions.length > capacity ? ` [${start + 1}–${Math.min(sessions.length, start + capacity)}/${sessions.length}]` : "";
|
|
95
142
|
const rows = [this.style.muted(fitLine(` ${help}${range}`, width - 1))];
|
|
96
143
|
for (const s of sessions.slice(start, start + capacity)) {
|
|
97
144
|
const indicator = this.focused && s.id === this.selected ? ">" : " ";
|
|
98
|
-
const name = ` ${indicator} ${s.id === this.active ? "*" : " "} ${oneLine(s.actor)}`;
|
|
145
|
+
const name = ` ${indicator} ${s.id === this.active ? "*" : " "} ${oneLine(agentLabel(s.actor))}`;
|
|
99
146
|
const tail = ` ${metrics(s)}`;
|
|
100
147
|
const room = Math.max(1, width - 1 - terminalWidth(tail));
|
|
101
148
|
const activity = s.activity ? ` ${oneLine(s.activity)}` : "";
|
|
@@ -110,16 +157,15 @@ export class AgentSessionsScreen {
|
|
|
110
157
|
this.sync();
|
|
111
158
|
const h = Math.max(1, height), w = Math.max(1, width - 2);
|
|
112
159
|
const session = this.store.get(this.active);
|
|
113
|
-
const heading = this.style.heading(fitLine(` ${tr("Sessione", "Session")} · ${oneLine(session.actor)} · ${metrics(session)}`, width - 1));
|
|
160
|
+
const heading = this.style.heading(fitLine(` ${tr("Sessione", "Session")} · ${oneLine(agentLabel(session.actor))} · ${metrics(session)}`, width - 1));
|
|
114
161
|
const list = this.preview(width, Math.min(6, Math.max(2, Math.floor(h / 3))));
|
|
115
|
-
const help = this.style.muted(fitLine(tr("↑↓ seleziona · Invio apri · seleziona
|
|
162
|
+
const help = this.style.muted(fitLine(tr("↑↓ seleziona · Invio apri · seleziona principale per tornare · PgSu/PgGiù output", "↑↓ select · Enter open · select main to return · PgUp/PgDn output"), width - 1));
|
|
116
163
|
const head = [heading, ...list, ""];
|
|
117
164
|
const usage = session.usageKnown
|
|
118
165
|
? `Input: ${tokenCount(session.inputTokens)} · Output: ${tokenCount(session.outputTokens)}`
|
|
119
166
|
: tr("Token non ancora comunicati dal provider.", "Tokens not yet reported by the provider.");
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
const lines = wrapText(text, w, true);
|
|
167
|
+
const blocks = [session.goal, usage, ...session.transcript].filter(Boolean);
|
|
168
|
+
const lines = this.transcript.lines(session.id, blocks.length ? blocks : [tr("In attesa di attività…", "Waiting for activity…")], w);
|
|
123
169
|
this.pageSize = Math.max(1, h - head.length - 1);
|
|
124
170
|
this.maxOffset = Math.max(0, lines.length - this.pageSize);
|
|
125
171
|
this.offset = this.follow ? this.maxOffset : Math.min(this.offset, this.maxOffset);
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import type { ContextLedger } from "../core/context-ledger.js";
|
|
2
|
+
import { WorkerTranscripts } from "./worker-transcripts.js";
|
|
1
3
|
import type { AgentEvent } from "../core/types.js";
|
|
2
4
|
import type { SessionUsageRecord } from "../core/session-usage.js";
|
|
3
5
|
export interface AgentSession {
|
|
6
|
+
readonly kingdomId?: string;
|
|
7
|
+
readonly context?: ContextLedger;
|
|
8
|
+
readonly compacting?: boolean;
|
|
9
|
+
readonly compactingAt?: number;
|
|
10
|
+
readonly notice?: string;
|
|
4
11
|
readonly id: string;
|
|
5
12
|
readonly actor: string;
|
|
6
13
|
readonly goal: string;
|
|
@@ -16,10 +23,14 @@ export interface AgentSession {
|
|
|
16
23
|
readonly transcript: readonly string[];
|
|
17
24
|
}
|
|
18
25
|
export declare class AgentSessions {
|
|
26
|
+
readonly conversations: WorkerTranscripts;
|
|
19
27
|
private sessions;
|
|
20
28
|
private actors;
|
|
21
29
|
private seenUsage;
|
|
30
|
+
private deleted;
|
|
22
31
|
private listeners;
|
|
32
|
+
private messageListeners;
|
|
33
|
+
onMessage(listener: (id: string, messageId: string) => void): () => void;
|
|
23
34
|
subscribe(listener: () => void): () => void;
|
|
24
35
|
private changed;
|
|
25
36
|
reset(): void;
|
|
@@ -31,6 +42,9 @@ export declare class AgentSessions {
|
|
|
31
42
|
get(id: string, now?: number): AgentSession | undefined;
|
|
32
43
|
get hasAgents(): boolean;
|
|
33
44
|
private snapshot;
|
|
45
|
+
/** Remove only the live UI session, never files or persisted team reports. */
|
|
46
|
+
delete(id: string): boolean;
|
|
47
|
+
notify(id: string, notice: string): void;
|
|
34
48
|
begin(goal: string, at?: number): void;
|
|
35
49
|
finish(at?: number): void;
|
|
36
50
|
private stop;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { WorkerTranscripts } from "./worker-transcripts.js";
|
|
1
2
|
import { tr } from "./i18n.js";
|
|
3
|
+
import { runtimeError } from "./runtime-error.js";
|
|
2
4
|
import { oneLine, plainText } from "./terminal-text.js";
|
|
3
5
|
const EMPTY = { input: 0, output: 0 };
|
|
4
6
|
const MAIN = "main";
|
|
@@ -14,10 +16,17 @@ function fresh(id, actor, goal = "") {
|
|
|
14
16
|
// Session IDs come from the worker lifecycle, not from a tool call or a label.
|
|
15
17
|
// A finished response does not imply that its worker has been despawned.
|
|
16
18
|
export class AgentSessions {
|
|
19
|
+
conversations = new WorkerTranscripts();
|
|
17
20
|
sessions = new Map([[MAIN, fresh(MAIN, MAIN)]]);
|
|
18
21
|
actors = new Map();
|
|
19
22
|
seenUsage = new Set();
|
|
23
|
+
deleted = new Set();
|
|
20
24
|
listeners = new Set();
|
|
25
|
+
messageListeners = new Set();
|
|
26
|
+
onMessage(listener) {
|
|
27
|
+
this.messageListeners.add(listener);
|
|
28
|
+
return () => { this.messageListeners.delete(listener); };
|
|
29
|
+
}
|
|
21
30
|
subscribe(listener) {
|
|
22
31
|
this.listeners.add(listener);
|
|
23
32
|
return () => { this.listeners.delete(listener); };
|
|
@@ -25,9 +34,11 @@ export class AgentSessions {
|
|
|
25
34
|
changed() { for (const listener of this.listeners)
|
|
26
35
|
listener(); }
|
|
27
36
|
reset() {
|
|
37
|
+
this.conversations.reset();
|
|
28
38
|
this.sessions = new Map([[MAIN, fresh(MAIN, MAIN)]]);
|
|
29
39
|
this.actors.clear();
|
|
30
40
|
this.seenUsage.clear();
|
|
41
|
+
this.deleted.clear();
|
|
31
42
|
this.changed();
|
|
32
43
|
}
|
|
33
44
|
restoreMain(history) {
|
|
@@ -49,11 +60,31 @@ export class AgentSessions {
|
|
|
49
60
|
get hasAgents() { return [...this.sessions.values()].some(s => s.id !== MAIN && s.live); }
|
|
50
61
|
snapshot(s, now) {
|
|
51
62
|
const usage = s.ledgerSeen ? s.ledger : s.fallback;
|
|
52
|
-
return { id: s.id, actor: s.actor, goal: s.goal, activity: s.activity, live: s.live, running: s.running,
|
|
63
|
+
return { kingdomId: s.kingdomId, context: s.context, compacting: s.compacting, compactingAt: s.compactingAt, notice: s.notice, id: s.id, actor: s.actor, goal: s.goal, activity: s.activity, live: s.live, running: s.running,
|
|
53
64
|
startedAt: s.startedAt, elapsedMs: s.elapsedMs + (s.startedAt === undefined ? 0 : Math.max(0, now - s.startedAt)),
|
|
54
65
|
inputTokens: usage.input, outputTokens: usage.output, usageKnown: s.usageKnown, usageMissing: s.usageMissing,
|
|
55
66
|
transcript: s.streaming ? [...s.transcript, s.streaming] : s.transcript };
|
|
56
67
|
}
|
|
68
|
+
/** Remove only the live UI session, never files or persisted team reports. */
|
|
69
|
+
delete(id) {
|
|
70
|
+
if (id === MAIN || !this.sessions.has(id))
|
|
71
|
+
return false;
|
|
72
|
+
this.deleted.add(id);
|
|
73
|
+
this.sessions.delete(id);
|
|
74
|
+
this.conversations.delete(id);
|
|
75
|
+
for (const [actor, sessionId] of this.actors)
|
|
76
|
+
if (sessionId === id)
|
|
77
|
+
this.actors.delete(actor);
|
|
78
|
+
this.changed();
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
notify(id, notice) {
|
|
82
|
+
const session = this.sessions.get(id);
|
|
83
|
+
if (session) {
|
|
84
|
+
this.sessions.set(id, { ...session, notice });
|
|
85
|
+
this.changed();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
57
88
|
begin(goal, at = Date.now()) {
|
|
58
89
|
const s = this.sessions.get(MAIN);
|
|
59
90
|
this.sessions.set(MAIN, { ...s, goal, startedAt: s.startedAt ?? at, running: true,
|
|
@@ -86,24 +117,34 @@ export class AgentSessions {
|
|
|
86
117
|
}
|
|
87
118
|
record(event, at = Date.now(), actor = "Talos") {
|
|
88
119
|
if (event.type === "kingdom" && event.phase === "session") {
|
|
120
|
+
if (this.deleted.has(event.sessionId))
|
|
121
|
+
return;
|
|
122
|
+
if (event.state === "despawned")
|
|
123
|
+
this.conversations.delete(event.sessionId);
|
|
89
124
|
if (event.state === "spawned") {
|
|
90
125
|
if (!this.sessions.has(event.sessionId)) {
|
|
91
126
|
this.sessions.set(event.sessionId, { ...fresh(event.sessionId, event.slug, event.goal),
|
|
92
|
-
startedAt: event.at, running: true, activity: tr("Avvio", "Starting") });
|
|
127
|
+
kingdomId: event.kingdomId, startedAt: event.at, running: true, activity: tr("Avvio", "Starting") });
|
|
93
128
|
}
|
|
94
129
|
this.actors.set(event.slug, event.sessionId);
|
|
130
|
+
const session = this.sessions.get(event.sessionId);
|
|
131
|
+
this.sessions.set(event.sessionId, { ...session, live: true, running: true, startedAt: session.startedAt ?? event.at });
|
|
95
132
|
}
|
|
96
133
|
else
|
|
97
|
-
this.stop(event.sessionId, event.at,
|
|
134
|
+
this.stop(event.sessionId, event.at, event.state === "despawned");
|
|
98
135
|
// Retain a small completed history, without dropping any live worker.
|
|
99
136
|
const completed = [...this.sessions.values()].filter(s => !s.live);
|
|
100
|
-
for (const s of completed.slice(0, Math.max(0, completed.length - 32)))
|
|
137
|
+
for (const s of completed.slice(0, Math.max(0, completed.length - 32))) {
|
|
101
138
|
this.sessions.delete(s.id);
|
|
139
|
+
this.conversations.delete(s.id);
|
|
140
|
+
}
|
|
102
141
|
this.changed();
|
|
103
142
|
return;
|
|
104
143
|
}
|
|
105
144
|
let id = actor === "Talos" ? MAIN : this.actors.get(actor);
|
|
106
145
|
if (event.type === "usage") {
|
|
146
|
+
if (this.deleted.has(event.record.sessionId))
|
|
147
|
+
return;
|
|
107
148
|
id = this.sessions.has(event.record.sessionId) ? event.record.sessionId
|
|
108
149
|
: event.record.actor ? this.actors.get(event.record.actor) : id;
|
|
109
150
|
}
|
|
@@ -131,9 +172,21 @@ export class AgentSessions {
|
|
|
131
172
|
s = { ...s, fallback: event.usage ? add(s.fallback, tokens(event.usage)) : s.fallback,
|
|
132
173
|
usageKnown: s.usageKnown || !!event.usage };
|
|
133
174
|
}
|
|
175
|
+
else if (event.type === "context_window")
|
|
176
|
+
s = { ...s, context: event.ledger };
|
|
177
|
+
else if (event.type === "compacting")
|
|
178
|
+
s = { ...s, compacting: true, compactingAt: at };
|
|
179
|
+
else if (event.type === "compaction_end")
|
|
180
|
+
s = { ...s, compacting: false };
|
|
181
|
+
else if (event.type === "compacted")
|
|
182
|
+
s = { ...s, compacting: false,
|
|
183
|
+
context: s.context ? { ...s.context, tokens: event.tokensAfter, fraction: event.tokensAfter / s.context.window, pressure: event.tokensAfter / s.context.compactAt } : undefined };
|
|
134
184
|
else if (event.type === "status")
|
|
135
185
|
s = { ...s, activity: oneLine(event.label) };
|
|
136
186
|
else if (event.type === "turn_start")
|
|
187
|
+
s = { ...s, running: true, live: true,
|
|
188
|
+
startedAt: s.startedAt ?? at, notice: undefined, activity: tr("Elaborazione", "Thinking") };
|
|
189
|
+
else if (event.type === "reasoning")
|
|
137
190
|
s = { ...s, activity: tr("Elaborazione", "Thinking") };
|
|
138
191
|
else if (event.type === "text")
|
|
139
192
|
s = { ...this.log(s, event.delta, true), activity: tr("Risposta in corso", "Writing") };
|
|
@@ -148,9 +201,9 @@ export class AgentSessions {
|
|
|
148
201
|
else if (event.type === "user_message")
|
|
149
202
|
s = this.log(s, event.text);
|
|
150
203
|
else if (event.type === "error")
|
|
151
|
-
s = { ...this.log(s, event.message), activity: oneLine(event.message) };
|
|
204
|
+
s = { ...this.log(s, runtimeError(event.message)), activity: oneLine(runtimeError(event.message)) };
|
|
152
205
|
else if (event.type === "done")
|
|
153
|
-
s = { ...s, activity: tr("Concluso", "Finished") };
|
|
206
|
+
s = { ...s, compacting: false, activity: tr("Concluso", "Finished") };
|
|
154
207
|
else if (event.type === "kingdom") {
|
|
155
208
|
if (event.phase === "usage")
|
|
156
209
|
s = { ...s, fallback: { input: count(event.inputTokens), output: count(event.outputTokens) }, usageKnown: true };
|
|
@@ -169,6 +222,14 @@ export class AgentSessions {
|
|
|
169
222
|
}
|
|
170
223
|
else
|
|
171
224
|
return;
|
|
225
|
+
if (id !== MAIN && s.live) {
|
|
226
|
+
this.conversations.record(id, event);
|
|
227
|
+
if (event.type === "user_message") {
|
|
228
|
+
s = { ...s, notice: tr("Messaggio preso in carico", "Message received") };
|
|
229
|
+
for (const listener of this.messageListeners)
|
|
230
|
+
listener(id, event.id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
172
233
|
this.sessions.set(id, s);
|
|
173
234
|
this.changed();
|
|
174
235
|
}
|