@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
package/dist/tui/repl.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { reviewContinuation } from "../core/continuation-review.js";
|
|
2
|
+
import { skillList, mcpList } from "./capability-lists.js";
|
|
2
3
|
import * as readline from "node:readline";
|
|
3
4
|
import { spawn } from "node:child_process";
|
|
4
5
|
import { watch, existsSync, readFileSync } from "node:fs";
|
|
@@ -6,17 +7,23 @@ import { stdin, stdout } from "node:process";
|
|
|
6
7
|
import { repoRoot, currentBranch } from "../vascend/worktree.js";
|
|
7
8
|
import { runTurn, compactNow } from "../core/agent.js";
|
|
8
9
|
import { createUserInbox, pendingText } from "../core/user-inbox.js";
|
|
9
|
-
import {
|
|
10
|
+
import { renderDiffParts } from "./diff.js";
|
|
10
11
|
import { renderMarkdown, wrapPlain } from "./markdown.js";
|
|
11
12
|
import { SessionUsageState } from "./session-usage.js";
|
|
13
|
+
import { readSessionUsage } from "../core/session-usage.js";
|
|
14
|
+
import { storageHealth } from "../core/storage-health.js";
|
|
15
|
+
import { storageWarning } from "./storage-warning.js";
|
|
12
16
|
import { ContextMeter, contextDetails } from "./context-meter.js";
|
|
13
|
-
import { buildUsageFooter, subscriptionDetails,
|
|
17
|
+
import { buildUsageFooter, subscriptionDetails, sessionUsageExplanation } from "./usage-footer.js";
|
|
14
18
|
import { copyText, lastAssistantText } from "./clipboard-text.js";
|
|
19
|
+
import { sendWorker, stopWorker, deleteWorker, workerFooter, bindWorkerInbox, wakeWorker } from "./session-controls.js";
|
|
15
20
|
import { withCommandFeedback, notifyStatus } from "./command-feedback.js";
|
|
16
21
|
import { loadModelsDev, cachedModelCatalog } from "../providers/models-dev.js";
|
|
17
22
|
import { modelEfforts } from "../providers/model-effort.js";
|
|
18
23
|
import { modelDetail } from "./model-details.js";
|
|
24
|
+
import { providerLabel } from "./provider-label.js";
|
|
19
25
|
import { applyModelChoice, pickEffort } from "./model-selection.js";
|
|
26
|
+
import { pickFusion } from "./fusion-menu.js";
|
|
20
27
|
import { PROVIDER_CATALOG } from "../providers/catalog.js";
|
|
21
28
|
import { fetchOpenRouterModels, openRouterDetail } from "../providers/openrouter.js";
|
|
22
29
|
import { providerRows, actionsFor, rowStatusLabel, providerKeyHint, saveProviderKey, saveProviderBaseUrl, removeProvider, } from "./provider-menu.js";
|
|
@@ -29,6 +36,7 @@ import { frostPaint, makeTheme, renderAssistantSegment, renderAssistantTurnEnd,
|
|
|
29
36
|
import { renderVascendTerminalLogo } from "./logo.js";
|
|
30
37
|
import { enterAccount } from "./account-entry.js";
|
|
31
38
|
import { tr } from "./i18n.js";
|
|
39
|
+
import { runtimeError, isUserInterruption } from "./runtime-error.js";
|
|
32
40
|
import { LANGUAGES, normalizePreference } from "./locales.js";
|
|
33
41
|
import { languagePreference, saveLanguagePreference } from "./language-preference.js";
|
|
34
42
|
import { leggiOnboarding } from "./onboarding.js";
|
|
@@ -45,7 +53,7 @@ import { restoreQueuedInput } from "./restore-queued-input.js";
|
|
|
45
53
|
import { ReviewStore } from "./review-store.js";
|
|
46
54
|
import { ReviewScreen, reviewText } from "./review-screen.js";
|
|
47
55
|
import { fitLine, oneLine, terminalWidth, wrapText } from "./terminal-text.js";
|
|
48
|
-
import { livePreview, liveResultSummary } from "./live-output.js";
|
|
56
|
+
import { livePreview, liveResultSummary, backgroundResultSummary } from "./live-output.js";
|
|
49
57
|
import { commandSource, commandPreview, commandHeader, commandOutput, commandResultState } from "./command-transcript.js";
|
|
50
58
|
import { mcpName, mcpHeader, mcpPreview, mcpOutput } from "./mcp-transcript.js";
|
|
51
59
|
import { loadedSkill, skillHeader, skillPreview } from "./skill-transcript.js";
|
|
@@ -58,6 +66,7 @@ import { readClipboardImage } from "./clipboard-image.js";
|
|
|
58
66
|
import { queryBackgroundMode } from "./term-bg.js";
|
|
59
67
|
import { runKingdomWarm, warmConcurrency, decomposeKingdom, looksBigGoal, collectConflicts, formatConflictList } from "../vascend/kingdom.js";
|
|
60
68
|
import { worktreeMode } from "../vascend/worktree.js";
|
|
69
|
+
import { kingdomLive } from "../vascend/kingdom-live.js";
|
|
61
70
|
import { runKingdomForHost } from "../vascend/kingdom-run.js";
|
|
62
71
|
import { kingdomBus } from "../vascend/kingdom-bus.js";
|
|
63
72
|
import { resolveKingdomBus } from "../vascend/kingdom-channel.js";
|
|
@@ -76,16 +85,22 @@ import { VascendPlanStore } from "../vascend/plan-store.js";
|
|
|
76
85
|
import { reviewOptions, shouldReview, buildRoomMetricsDigest, buildReviewerPrompt } from "../vascend/reviewer.js";
|
|
77
86
|
import { Session, listSessions, sessionFilePath } from "../core/session.js";
|
|
78
87
|
import { sessionTitle } from "../core/session-title.js";
|
|
88
|
+
import { nameSession } from "../core/session-naming.js";
|
|
79
89
|
import { ToolRegistry } from "../tools/registry.js";
|
|
80
90
|
import { saveGlobalSettings } from "../core/settings.js";
|
|
81
91
|
import { autoApproves, nextPermMode, permModeLabel } from "./perm-mode.js";
|
|
82
92
|
import { backgroundManager } from "../core/background.js";
|
|
83
93
|
import { BUILTIN_COMMANDS, visibleBuiltinNames, commandsByCategory, suggestCommands } from "./commands.js";
|
|
84
|
-
import { TASK_STATUS_LABELS } from "./task-labels.js";
|
|
85
|
-
import {
|
|
86
|
-
import {
|
|
94
|
+
import { TASK_STATUS_LABELS, taskActivityLabel } from "./task-labels.js";
|
|
95
|
+
import { liveCommandAllowed } from "./live-command.js";
|
|
96
|
+
import { rimuoviLicenzaLocale, rimuoviTokenVascend, revocaSessione, } from "./vascend-login.js";
|
|
97
|
+
import { leggiSessione, revocaSessioneId, rimuoviSessione, tokenAgente } from "./vascend-id.js";
|
|
98
|
+
import { paintToolName, pulseDot } from "./tool-style.js";
|
|
99
|
+
import { toolArg, toolLabel } from "./tool-presentation.js";
|
|
87
100
|
import { listVibeModes, parseVibeMode } from "../core/vibe-modes.js";
|
|
88
101
|
import { contextProjectKey, loadContextFrame, renderContextFrame, resetContextFrame, updateContextPins, } from "../vascend/context-fabric.js";
|
|
102
|
+
import { collegaMani, fermaRemoto, messaggioUtente, osservaEvento, permessoConGara, sessioneRemota } from "../remote/index.js";
|
|
103
|
+
import { comandoRemoto } from "./remote-command.js";
|
|
89
104
|
// (BUILTIN_COMMANDS spostato in ./commands.ts con categorie: vedi import in testa.)
|
|
90
105
|
// Sorgente dinamica dei nomi comando (builtin + estensioni + template + skill).
|
|
91
106
|
function allCommandNames(harness, templates) {
|
|
@@ -323,7 +338,10 @@ export async function startRepl(deps) {
|
|
|
323
338
|
const stats = new SessionUsageState(costTracker);
|
|
324
339
|
stats.restore(harness.config.sessionDir, harness.session.id, harness.session.tree().find(n => n.message.role === "assistant")?.ts);
|
|
325
340
|
const review = new ReviewStore();
|
|
341
|
+
review.sessions.conversations.configure((buffer, width) => makeSink(theme, () => width, harness.config.cwd, env, { ...buffer, setStatus: () => { }, setTokens: () => { },
|
|
342
|
+
refresh: () => { } }, undefined, true), () => theme);
|
|
326
343
|
review.reset(sessionConversation(harness.session));
|
|
344
|
+
review.sessions.restoreMain(readSessionUsage(harness.config.sessionDir, harness.session.id));
|
|
327
345
|
const reviewContext = () => {
|
|
328
346
|
const cost = costTracker.snapshot(harness.config.model);
|
|
329
347
|
const tasks = vascend.state().kingdom.castles.flatMap(c => c.tasks);
|
|
@@ -348,7 +366,7 @@ export async function startRepl(deps) {
|
|
|
348
366
|
? last.cacheReadTokens / Math.max(1, last.inputTokens + last.cacheReadTokens)
|
|
349
367
|
: undefined;
|
|
350
368
|
return {
|
|
351
|
-
provider: current ? (current.provider
|
|
369
|
+
provider: current ? providerLabel(current.provider) : tr("non configurato", "not configured"),
|
|
352
370
|
sessionId: harness.session.id,
|
|
353
371
|
sessionNodes: stima.nodi,
|
|
354
372
|
headId: harness.session.head,
|
|
@@ -411,7 +429,7 @@ export async function startRepl(deps) {
|
|
|
411
429
|
const startupMeta = () => {
|
|
412
430
|
const resolved = harness.providers.resolve(harness.config.model);
|
|
413
431
|
return {
|
|
414
|
-
provider: resolved ? (resolved.provider
|
|
432
|
+
provider: resolved ? providerLabel(resolved.provider) : tr("non configurato", "not configured"),
|
|
415
433
|
sessionId: harness.session.id,
|
|
416
434
|
sessionNodes: harness.session.tree().length,
|
|
417
435
|
headId: harness.session.head,
|
|
@@ -445,8 +463,10 @@ export async function startRepl(deps) {
|
|
|
445
463
|
// questa sessione soltanto: dopo /new, `txt #1` sarebbe un altro testo.
|
|
446
464
|
const pasteRegistry = new PasteRegistry();
|
|
447
465
|
const resetSessionUi = (ctl) => {
|
|
466
|
+
kingdomLive.clearAll();
|
|
448
467
|
ctl.clearConversation?.();
|
|
449
468
|
review.reset(sessionConversation(harness.session));
|
|
469
|
+
review.sessions.restoreMain(readSessionUsage(harness.config.sessionDir, harness.session.id));
|
|
450
470
|
imageRegistry.clear();
|
|
451
471
|
pasteRegistry.reset();
|
|
452
472
|
ctl.restoreQueue?.();
|
|
@@ -501,6 +521,7 @@ export async function startRepl(deps) {
|
|
|
501
521
|
try {
|
|
502
522
|
if (!line.startsWith("/")) {
|
|
503
523
|
ctl.write(renderUserMessage(line, theme, terminalColumns(), fromQueue ? tr("Dalla coda · consegnato", "From queue · delivered") : undefined));
|
|
524
|
+
messaggioUtente(line);
|
|
504
525
|
const imageParts = queued ? queued.parts.filter(part => part.type === "image") : imageRegistry.imagePartsFor(line);
|
|
505
526
|
// In chat resta il riferimento compatto (`txt #1`); al modello va il
|
|
506
527
|
// testo per intero. L'espansione avviene qui, dopo l'echo e prima del
|
|
@@ -518,6 +539,10 @@ export async function startRepl(deps) {
|
|
|
518
539
|
ctl.write(reviewText(review, reviewContext(), theme, terminalColumns(), section));
|
|
519
540
|
return;
|
|
520
541
|
}
|
|
542
|
+
if (/^\/(?:agents|agenti)\s*$/i.test(line)) {
|
|
543
|
+
ctl.showAgents?.();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
521
546
|
// /img <path>: allega esplicitamente un'immagine. La registra come "img #N"
|
|
522
547
|
// e ne mostra l'anteprima inline; non avvia un turno (e' solo l'allegato).
|
|
523
548
|
const imgCmd = /^\/img(?:\s+(.+))?$/i.exec(line);
|
|
@@ -628,7 +653,7 @@ export async function startRepl(deps) {
|
|
|
628
653
|
? { done: all.filter((t) => t.status === "completed").length, total: all.length, castles: castles.length }
|
|
629
654
|
: null;
|
|
630
655
|
return {
|
|
631
|
-
model: harness.config.model,
|
|
656
|
+
model: harness.config.fusion ? `Fusion: Jev + ${harness.config.model}` : harness.config.model,
|
|
632
657
|
provider: m.provider,
|
|
633
658
|
cwd: harness.config.cwd,
|
|
634
659
|
effort: harness.config.effort || "default",
|
|
@@ -650,15 +675,25 @@ export async function startRepl(deps) {
|
|
|
650
675
|
};
|
|
651
676
|
const input = new BottomInput({
|
|
652
677
|
review,
|
|
678
|
+
onSessionSubmit: sendWorker,
|
|
679
|
+
bindSessionInbox: bindWorkerInbox,
|
|
680
|
+
wakeSession: wakeWorker,
|
|
681
|
+
onSessionStop: stopWorker,
|
|
682
|
+
onSessionDelete: deleteWorker,
|
|
683
|
+
onSessionBackground: session => ({ ok: !!backgroundManager.requestBackground(session.id) }),
|
|
684
|
+
sessionFooter: (session, width) => workerFooter(session, width, theme, stats, model => harness.providers.resolve(model)?.provider.id ?? "").slice(0, 1),
|
|
685
|
+
sessionBelowInput: (session, width) => workerFooter(session, width, theme, stats, model => harness.providers.resolve(model)?.provider.id ?? "").slice(1),
|
|
653
686
|
createReviewScreen: (section) => new ReviewScreen(review, () => theme, reviewContext, section),
|
|
654
687
|
nativeScroll: env["TALOS_NATIVE_SCROLL"] !== "0",
|
|
655
688
|
headerLines: () => buildTerminalHeader(statusData(), theme),
|
|
656
689
|
startupLines: () => buildTerminalStartup(statusData(), theme),
|
|
657
690
|
promptText: () => theme.accent(harness.config.ui.prompt),
|
|
658
|
-
placeholder: () => theme.muted(tr("
|
|
691
|
+
placeholder: () => theme.muted(tr("Descrivi un'attività o digita / per i comandi", "Describe a task or type / for commands")),
|
|
659
692
|
composerPadding: () => !harness.session.head,
|
|
660
693
|
composerStyle: (s) => theme.composerText?.(s) ?? theme.chatText(s),
|
|
661
694
|
attachmentStyle: (s) => theme.attachmentRef?.(s) ?? s,
|
|
695
|
+
currentModel: () => harness.config.model,
|
|
696
|
+
limitWarningStyle: s => theme.yellow(s),
|
|
662
697
|
belowInputLines: () => {
|
|
663
698
|
if (!harness.session.head)
|
|
664
699
|
return [buildStartupRuntime(statusData(), theme)];
|
|
@@ -671,6 +706,7 @@ export async function startRepl(deps) {
|
|
|
671
706
|
const providerId = harness.providers.resolve(harness.config.model)?.provider.id ?? "";
|
|
672
707
|
return buildUsageFooter(statusData(), theme, stats, providerId, statusOpts()).slice(0, 1);
|
|
673
708
|
},
|
|
709
|
+
warningLines: () => storageWarning(storageHealth.snapshot(harness.config.sessionDir), terminalColumns(), theme.yellow),
|
|
674
710
|
completion,
|
|
675
711
|
hint: slashHint,
|
|
676
712
|
statusStyle: (s) => theme.dark(s),
|
|
@@ -692,14 +728,14 @@ export async function startRepl(deps) {
|
|
|
692
728
|
// Ctrl+B: manda in background il comando shell in corso; se non ce n'e'
|
|
693
729
|
// uno, mostra la lista dei task in background.
|
|
694
730
|
onBackground: () => {
|
|
695
|
-
const task = backgroundManager.requestBackground();
|
|
731
|
+
const task = backgroundManager.requestBackground(harness.session.id);
|
|
696
732
|
if (task)
|
|
697
733
|
input.write(theme.dim(`\n ⤓ in background [${task.id}]: ${truncate(task.command, 60)}\n`));
|
|
698
734
|
else
|
|
699
735
|
void showBackgroundPanel(input, theme, terminalColumns);
|
|
700
736
|
},
|
|
701
737
|
// Suffisso spinner: suggerisce Ctrl+B solo quando c'e' un comando in corso.
|
|
702
|
-
runningHint: () => backgroundManager.hasForeground() ? "Ctrl+B sfondo" : null,
|
|
738
|
+
runningHint: () => backgroundManager.hasForeground(harness.session.id) ? "Ctrl+B sfondo" : null,
|
|
703
739
|
prepareQueuedInput: text => [{ type: "text", text: pasteRegistry.expand(text) }, ...imageRegistry.imagePartsFor(text)],
|
|
704
740
|
queuePersistence: () => new PromptQueueFile(harness.config.sessionDir, harness.session.id),
|
|
705
741
|
restoreQueuedInput: entry => restoreQueuedInput(entry, imageRegistry),
|
|
@@ -709,6 +745,18 @@ export async function startRepl(deps) {
|
|
|
709
745
|
if (result === "quit")
|
|
710
746
|
input.requestQuit();
|
|
711
747
|
},
|
|
748
|
+
onCommandDuringRun: async (line, signal) => {
|
|
749
|
+
const ctl = new Proxy(input, {
|
|
750
|
+
get(target, prop, receiver) {
|
|
751
|
+
if (prop === "busyCommands")
|
|
752
|
+
return true;
|
|
753
|
+
const value = Reflect.get(target, prop, receiver);
|
|
754
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
755
|
+
},
|
|
756
|
+
});
|
|
757
|
+
// Bypass processLine: its finally closes the active turn's inbox.
|
|
758
|
+
await handleSlash(line === "/" ? "/commands" : line, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, () => { });
|
|
759
|
+
},
|
|
712
760
|
// Incollo: un path/data-URI immagine diventa "img #N", un testo lungo
|
|
713
761
|
// diventa "txt #N", il resto si inserisce come testo normale.
|
|
714
762
|
onPaste: onPasteInput,
|
|
@@ -721,23 +769,28 @@ export async function startRepl(deps) {
|
|
|
721
769
|
return span ? { start: span.start, end: span.end } : null;
|
|
722
770
|
},
|
|
723
771
|
onReady: () => {
|
|
724
|
-
input.write(buildTerminalWelcome(terminalColumns(), theme));
|
|
725
|
-
if (accessoNota)
|
|
726
|
-
input.write(wrapText(accessoNota, Math.max(2, terminalColumns() - 3), true).map((line) => theme.muted(" " + line.trim())).join("\n") + "\n");
|
|
727
|
-
input.write("\n");
|
|
772
|
+
input.write(buildTerminalWelcome(terminalColumns(), theme, accessoNota));
|
|
728
773
|
if (harness.session.head)
|
|
729
774
|
replaySession(harness.session, input, theme, terminalColumns());
|
|
730
775
|
if (deps.resumePicker)
|
|
731
776
|
void processLine("/resume", input, new AbortController().signal);
|
|
732
777
|
},
|
|
733
778
|
onQuit: () => {
|
|
779
|
+
kingdomLive.clearAll();
|
|
780
|
+
review.sessions.conversations.reset();
|
|
734
781
|
void harness.host.emit("session_shutdown", {});
|
|
735
|
-
stdout.write("\n");
|
|
736
|
-
|
|
782
|
+
const esci = () => { stdout.write("\n"); process.exit(0); };
|
|
783
|
+
// Il telefono deve sapere che la sessione e' finita, senza trattenere l'uscita.
|
|
784
|
+
if (sessioneRemota())
|
|
785
|
+
void Promise.race([fermaRemoto(), new Promise(r => setTimeout(r, 2_000))]).finally(esci);
|
|
786
|
+
else
|
|
787
|
+
esci();
|
|
737
788
|
},
|
|
738
789
|
});
|
|
739
790
|
// Le notify delle estensioni vanno nella regione viva (no corruzione).
|
|
740
791
|
commit = (t) => input.write(t);
|
|
792
|
+
// Controllo remoto: il telefono scrive e ferma attraverso la barra di input.
|
|
793
|
+
collegaMani({ invia: (testo, subito) => input.submitExternal(testo, subito), ferma: () => input.requestStop() });
|
|
741
794
|
// UI reale per le estensioni: confirm/select/input passano dalla TUI (cosi'
|
|
742
795
|
// un'estensione puo' fare domande in chat come il tool builtin ask_user).
|
|
743
796
|
harness.host.bind({
|
|
@@ -778,11 +831,21 @@ export async function startRepl(deps) {
|
|
|
778
831
|
backgroundManager.onFinish((t) => {
|
|
779
832
|
if (!t.detached)
|
|
780
833
|
return; // i comandi in primo piano mostrano gia' il risultato inline
|
|
781
|
-
|
|
834
|
+
if (t.status === "done") {
|
|
835
|
+
notifyStatus(input, tr("Background {id} completato · /bg {id} per l'output", "Background {id} completed · /bg {id} for output", { id: t.id }));
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const mark = theme.red("✗");
|
|
782
839
|
input.write(`\n ${mark} background ${t.id} ` +
|
|
783
840
|
theme.dim(`[${t.status}${t.exitCode != null ? ` · uscita ${t.exitCode}` : ""}] ${truncate(t.command, 50)} · /bg ${t.id} per l'output\n`));
|
|
784
841
|
});
|
|
785
|
-
|
|
842
|
+
const unsubscribeStorage = storageHealth.subscribe(() => input.refresh());
|
|
843
|
+
try {
|
|
844
|
+
await input.start();
|
|
845
|
+
}
|
|
846
|
+
finally {
|
|
847
|
+
unsubscribeStorage();
|
|
848
|
+
}
|
|
786
849
|
return;
|
|
787
850
|
}
|
|
788
851
|
// Fallback (non-TTY o TALOS_CLASSIC_INPUT=1): loop readline classico inline.
|
|
@@ -790,6 +853,13 @@ export async function startRepl(deps) {
|
|
|
790
853
|
if (harness.session.head)
|
|
791
854
|
replaySession(harness.session, { write: text => stdout.write(text) }, theme, terminalColumns());
|
|
792
855
|
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
856
|
+
const reportStorage = () => {
|
|
857
|
+
const rows = storageWarning(storageHealth.snapshot(harness.config.sessionDir), terminalColumns(), theme.yellow);
|
|
858
|
+
if (rows.length)
|
|
859
|
+
stdout.write("\n" + rows.join("\n") + "\n");
|
|
860
|
+
};
|
|
861
|
+
const unsubscribeStorage = storageHealth.subscribe(reportStorage);
|
|
862
|
+
reportStorage();
|
|
793
863
|
const ctl = {
|
|
794
864
|
review,
|
|
795
865
|
pause: () => rl.pause(),
|
|
@@ -826,7 +896,7 @@ export async function startRepl(deps) {
|
|
|
826
896
|
processLine: (raw, signal) => processLine(raw, ctl, signal),
|
|
827
897
|
prompt,
|
|
828
898
|
requestClose: () => rl.close(),
|
|
829
|
-
onError: (error) => notifyStatus(ctl, theme.red(`\n
|
|
899
|
+
onError: (error) => notifyStatus(ctl, theme.red(`\n ${tr("Errore", "Error")}: ${runtimeError(error.message)}\n`)),
|
|
830
900
|
installAbort: (abort) => {
|
|
831
901
|
const onSigint = () => abort();
|
|
832
902
|
process.once("SIGINT", onSigint);
|
|
@@ -848,6 +918,7 @@ export async function startRepl(deps) {
|
|
|
848
918
|
void lineQueue.enqueue(raw);
|
|
849
919
|
});
|
|
850
920
|
rl.on("close", () => {
|
|
921
|
+
unsubscribeStorage();
|
|
851
922
|
lineQueue.close();
|
|
852
923
|
harness.dispose?.();
|
|
853
924
|
void harness.host.emit("session_shutdown", {});
|
|
@@ -868,15 +939,26 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
868
939
|
// Gate permessi: selezione Sì/No dalla UI con le frecce (stile Claude Code),
|
|
869
940
|
// niente piu' digitazione di y/n. Le modalita' "accetta modifiche"/"auto"
|
|
870
941
|
// (Shift+Tab) saltano il prompt per le azioni che approvano.
|
|
871
|
-
const confirm = async (action, detail) => {
|
|
942
|
+
const confirm = async (action, detail, requestSignal = signal) => {
|
|
872
943
|
if (autoApproves(getPermMode(), action))
|
|
873
944
|
return true;
|
|
945
|
+
// Con il controllo remoto acceso risponde chi arriva prima, TUI o telefono.
|
|
946
|
+
return permessoConGara(action, detail, requestSignal, (gara) => confirmInTui(action, detail, gara));
|
|
947
|
+
};
|
|
948
|
+
const confirmInTui = async (action, detail, requestSignal) => {
|
|
874
949
|
const choice = await selectOption({
|
|
875
|
-
|
|
876
|
-
|
|
950
|
+
signal: requestSignal,
|
|
951
|
+
title: tr("consenti: {p0}", "Allow: {p0}", { p0: action }),
|
|
952
|
+
hint: tr("{p0}\nShift+Tab: cambia permessi anche durante la conferma", "{p0}\nShift+Tab: change permissions during confirmation", { p0: detail }),
|
|
953
|
+
onCycleMode: ctl.cyclePermissionMode ? () => {
|
|
954
|
+
ctl.cyclePermissionMode();
|
|
955
|
+
return autoApproves(getPermMode(), action)
|
|
956
|
+
? { label: tr("Consentito dalla modalità corrente", "Allowed by the current mode"), detail: "", value: true }
|
|
957
|
+
: undefined;
|
|
958
|
+
} : undefined,
|
|
877
959
|
options: [
|
|
878
|
-
{ key: "yes", label: "Sì, esegui", detail: "consenti questa azione", value: true },
|
|
879
|
-
{ key: "no", label: "No, annulla", detail: "blocca l'azione", value: false },
|
|
960
|
+
{ key: "yes", label: tr("Sì, esegui", "Yes, run it"), detail: tr("consenti questa azione", "allow this action"), value: true },
|
|
961
|
+
{ key: "no", label: tr("No, annulla", "No, cancel"), detail: tr("blocca l'azione", "block the action"), value: false },
|
|
880
962
|
],
|
|
881
963
|
currentKey: "yes",
|
|
882
964
|
ctl,
|
|
@@ -886,11 +968,12 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
886
968
|
return choice?.value === true;
|
|
887
969
|
};
|
|
888
970
|
// Domanda all'utente in chat (tool ask_user): opzioni -> picker; libera -> input.
|
|
889
|
-
const ask = async (question, options) => {
|
|
971
|
+
const ask = async (question, options, requestSignal = signal) => {
|
|
890
972
|
if (ctl.queueQuestion)
|
|
891
|
-
return ctl.queueQuestion(question, options,
|
|
973
|
+
return ctl.queueQuestion(question, options, requestSignal);
|
|
892
974
|
if (options && options.length) {
|
|
893
975
|
const choice = await selectOption({
|
|
976
|
+
signal: requestSignal,
|
|
894
977
|
title: question,
|
|
895
978
|
question: true,
|
|
896
979
|
options: options.map((o) => ({ label: o, detail: "", value: o })),
|
|
@@ -901,15 +984,20 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
901
984
|
return choice?.value ?? null;
|
|
902
985
|
}
|
|
903
986
|
ctl.write(renderQuestion(question, theme, terminalColumns()));
|
|
904
|
-
return ctl.promptInput("rispondi qui (Invio conferma · Esc annulla)");
|
|
987
|
+
return ctl.promptInput(tr("rispondi qui (Invio conferma · Esc annulla)", "Reply here (Enter confirm · Esc cancel)"));
|
|
905
988
|
};
|
|
906
989
|
// Kingdoms Mode invocabile dall'agente (tool kingdoms_warm): esegue i castelli
|
|
907
990
|
// in parallelo (vassalli) e ritorna i report. Il consenso e' gia' stato dato
|
|
908
991
|
// al gate del tool (ctx.confirm); qui i vassalli eseguono i tool in blocco.
|
|
992
|
+
const onWorkerEvent = (actor, event) => {
|
|
993
|
+
if (event.type !== "kingdom" && event.type !== "usage")
|
|
994
|
+
ctl.review?.recordActor(actor, event);
|
|
995
|
+
};
|
|
909
996
|
const spawnKingdom = async (castles, kopts) => {
|
|
910
997
|
if (kopts?.detach)
|
|
911
998
|
return runKingdomForHost({
|
|
912
|
-
harness: deps.harness, castles, signal, sink: wrappedSink, confirm, ask, env, detach: true,
|
|
999
|
+
harness: deps.harness, castles, signal, sink: wrappedSink, confirm, ask, env, detach: true, retainSessions: "queued",
|
|
1000
|
+
onWorkerEvent,
|
|
913
1001
|
...(kopts.baseBranch ? { baseBranch: kopts.baseBranch } : {}),
|
|
914
1002
|
});
|
|
915
1003
|
// Obiettivo complessivo del regno: esplicito > goal grezzo del turno.
|
|
@@ -941,6 +1029,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
941
1029
|
concurrency: conc,
|
|
942
1030
|
ask,
|
|
943
1031
|
onEvent: live.onEvent,
|
|
1032
|
+
retainSessions: "queued",
|
|
944
1033
|
worktree: wt,
|
|
945
1034
|
...(kopts?.baseBranch ? { baseBranch: kopts.baseBranch } : {}),
|
|
946
1035
|
onNotice: (text) => notifyStatus(ctl, theme.accent(" ⎇ ") + theme.dim(text) + "\n"),
|
|
@@ -1007,6 +1096,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1007
1096
|
ctl.setStatus(tr("avvio", "starting"));
|
|
1008
1097
|
let turnFailed = false;
|
|
1009
1098
|
const wrappedSink = (event) => {
|
|
1099
|
+
osservaEvento(event);
|
|
1010
1100
|
if (event.type === "error" || (event.type === "turn_end" && event.stopReason === "error"))
|
|
1011
1101
|
turnFailed = true;
|
|
1012
1102
|
ctl.review?.record(event);
|
|
@@ -1057,7 +1147,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1057
1147
|
const turnInput = imageParts && imageParts.length
|
|
1058
1148
|
? [{ type: "text", text: input }, ...imageParts]
|
|
1059
1149
|
: input;
|
|
1060
|
-
await runTurn({ ...deps.harness.deps(confirm), ask, spawnKingdom, inbox }, turnInput, wrappedSink, signal);
|
|
1150
|
+
await runTurn({ ...deps.harness.deps(confirm), env: { ...env, TALOS_AUTO_TITLE: "1" }, ask, spawnKingdom, onWorkerEvent, inbox }, turnInput, wrappedSink, signal);
|
|
1061
1151
|
if (turnFailed || signal.aborted)
|
|
1062
1152
|
return;
|
|
1063
1153
|
// REVISORE di team: per i task complessi (molte stanze) con stanze fatte e
|
|
@@ -1122,8 +1212,14 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1122
1212
|
}
|
|
1123
1213
|
}
|
|
1124
1214
|
catch (err) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1215
|
+
if (signal.aborted) {
|
|
1216
|
+
ctl.setRoomStatus?.(null);
|
|
1217
|
+
ctl.write(theme.yellow(`\n ${tr("Interrotto · puoi inviare un nuovo messaggio per continuare.", "Interrupted · send a new message to continue.")}\n`));
|
|
1218
|
+
}
|
|
1219
|
+
else {
|
|
1220
|
+
ctl.review?.record({ type: "error", message: err.message });
|
|
1221
|
+
notifyStatus(ctl, theme.red(`\n ${tr("Errore", "Error")}: ${runtimeError(err.message)}\n`));
|
|
1222
|
+
}
|
|
1127
1223
|
}
|
|
1128
1224
|
finally {
|
|
1129
1225
|
ctl.review?.finish(signal.aborted);
|
|
@@ -1209,7 +1305,7 @@ async function runTeamReviewer(deps, vascend, sdir, sid, ctl, theme, terminalCol
|
|
|
1209
1305
|
}
|
|
1210
1306
|
vascend.loadPlan(sdir, sid);
|
|
1211
1307
|
}
|
|
1212
|
-
export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
1308
|
+
export function makeSink(theme, terminalColumns, cwd, env, ctl, plan, replay = false) {
|
|
1213
1309
|
let textBuffer = "";
|
|
1214
1310
|
let assistantSegments = 0;
|
|
1215
1311
|
let assistantStarted = false;
|
|
@@ -1217,6 +1313,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1217
1313
|
const writeNotice = (text, failed = false) => {
|
|
1218
1314
|
notifyStatus(ctl, text, failed);
|
|
1219
1315
|
};
|
|
1316
|
+
const writeWarning = (text) => {
|
|
1317
|
+
ctl.setRoomStatus?.(null);
|
|
1318
|
+
const rows = wrapText(text, Math.max(8, terminalColumns() - 4));
|
|
1319
|
+
ctl.write("\n" + rows.map(row => theme.yellow(` ${row}`)).join("\n") + "\n");
|
|
1320
|
+
};
|
|
1220
1321
|
// Correlazione call->risultato e contenuto PRE-scrittura, per gli avvisi del
|
|
1221
1322
|
// regno e per il diff dei tool di scrittura.
|
|
1222
1323
|
const calls = new Map();
|
|
@@ -1226,6 +1327,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1226
1327
|
const mcpCards = new Map();
|
|
1227
1328
|
const toolCards = new Map();
|
|
1228
1329
|
const skillCards = new Map();
|
|
1330
|
+
const backgroundPolls = new Map();
|
|
1229
1331
|
let newInputTok = 0; // input non in cache del run (cumulativo)
|
|
1230
1332
|
let cacheTok = 0; // cache letta/scritta nel run (cumulativo)
|
|
1231
1333
|
let outTok = 0; // token generati nel run (cumulativi)
|
|
@@ -1281,21 +1383,21 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1281
1383
|
}
|
|
1282
1384
|
// Riposa il bullet allo stato statico (accent) come il resto dello scrollback.
|
|
1283
1385
|
if (settle && liveDot && ctl.setLogLine) {
|
|
1284
|
-
ctl.setLogLine(liveDot.index, fitLine(
|
|
1386
|
+
ctl.setLogLine(liveDot.index, fitLine(" " + theme.muted("◇ ") + liveDot.tail, terminalColumns() - 1));
|
|
1285
1387
|
}
|
|
1286
1388
|
liveDot = null;
|
|
1287
1389
|
};
|
|
1288
1390
|
const startDot = (index, name, tail) => {
|
|
1289
1391
|
stopDot(false); // chiudi l'eventuale precedente senza ridisegnare (verra' sovrascritto)
|
|
1290
|
-
if (!ctl.setLogLine)
|
|
1291
|
-
return; //
|
|
1392
|
+
if (!ctl.setLogLine || replay)
|
|
1393
|
+
return; // Replay has no timers or terminal side effects.
|
|
1292
1394
|
liveDot = { index, name, tail };
|
|
1293
1395
|
liveFrame = 0;
|
|
1294
1396
|
dotTimer = setInterval(() => {
|
|
1295
1397
|
if (!liveDot || !ctl.setLogLine)
|
|
1296
1398
|
return;
|
|
1297
1399
|
liveFrame += 1;
|
|
1298
|
-
ctl.setLogLine(liveDot.index, fitLine(
|
|
1400
|
+
ctl.setLogLine(liveDot.index, fitLine(" " + pulseDot(liveDot.name, liveFrame) + " " + liveDot.tail, terminalColumns() - 1));
|
|
1299
1401
|
}, 140);
|
|
1300
1402
|
};
|
|
1301
1403
|
const beginCommand = () => {
|
|
@@ -1304,7 +1406,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1304
1406
|
stopDot();
|
|
1305
1407
|
ctl.write("\n");
|
|
1306
1408
|
};
|
|
1307
|
-
|
|
1409
|
+
const sink = (event) => {
|
|
1308
1410
|
switch (event.type) {
|
|
1309
1411
|
case "context_window":
|
|
1310
1412
|
ctl.refresh?.();
|
|
@@ -1335,7 +1437,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1335
1437
|
// come evento status separato.
|
|
1336
1438
|
lastTruncated = event.stopReason === "max_tokens";
|
|
1337
1439
|
if (event.stopReason === "max_tokens") {
|
|
1338
|
-
notifyStatus(ctl, theme.dim(
|
|
1440
|
+
notifyStatus(ctl, theme.dim(`\n ${tr("Output al limite del modello: continuo automaticamente…", "Model output limit reached: continuing automatically…")}\n`));
|
|
1339
1441
|
}
|
|
1340
1442
|
break;
|
|
1341
1443
|
case "status":
|
|
@@ -1344,6 +1446,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1344
1446
|
break;
|
|
1345
1447
|
// Attivita' del modello senza testo (es. streaming di un input grande).
|
|
1346
1448
|
ctl.setStatus(event.label);
|
|
1449
|
+
{
|
|
1450
|
+
const limit = event.label.match(/^Limite sessione di 5 ore raggiunto · reset alle (.+?) · riprendo automaticamente al reset · Esc per annullare$/);
|
|
1451
|
+
if (limit) {
|
|
1452
|
+
flushText("final");
|
|
1453
|
+
const time = limit[1];
|
|
1454
|
+
writeWarning(tr("Limite della sessione raggiunto · reset alle {time}", "You've hit your session limit · resets {time}", { time }));
|
|
1455
|
+
writeWarning(tr("Ripresa automatica alle {time} · Esc per annullare", "Continuing automatically at {time} · esc to cancel", { time: time.replace(/\s*\([^)]*\)$/, "") }));
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1347
1458
|
break;
|
|
1348
1459
|
case "text":
|
|
1349
1460
|
ctl.setStatus(tr("risposta in corso", "responding"));
|
|
@@ -1358,6 +1469,10 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1358
1469
|
parent.child = true;
|
|
1359
1470
|
}
|
|
1360
1471
|
calls.set(event.call.id, event.call);
|
|
1472
|
+
if (event.call.name === "bash_output") {
|
|
1473
|
+
ctl.setStatus(tr("leggo output background", "reading background output"));
|
|
1474
|
+
break;
|
|
1475
|
+
}
|
|
1361
1476
|
// Compact protocol wrappers emit real child calls. Show those commands
|
|
1362
1477
|
// once, without a duplicate execute(args) envelope in the transcript.
|
|
1363
1478
|
if (event.call.name === "execute" || event.call.name === "tool_invoke" || event.call.name === "run_program") {
|
|
@@ -1404,7 +1519,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1404
1519
|
ctl.write(preview.lines.join("\n") + "\n");
|
|
1405
1520
|
commandCards.set(event.call.id, { index: ctl.logLength() - preview.lines.length,
|
|
1406
1521
|
firstRow: preview.firstRow, columns, end: ctl.logLength(), header: preview.lines[0] });
|
|
1407
|
-
ctl.setStatus(tr("
|
|
1522
|
+
ctl.setStatus(tr("Esecuzione comando", "Running command"));
|
|
1408
1523
|
ctl.setStatusTool?.(event.call.name);
|
|
1409
1524
|
break;
|
|
1410
1525
|
}
|
|
@@ -1421,11 +1536,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1421
1536
|
const st = String(event.call.input["status"] ?? "completed");
|
|
1422
1537
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1423
1538
|
if (st === "running")
|
|
1424
|
-
ctl.setRoomStatus?.(
|
|
1539
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "running"), "running");
|
|
1425
1540
|
else if (st === "failed")
|
|
1426
|
-
ctl.setRoomStatus?.(
|
|
1541
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "failed"), "failed");
|
|
1427
1542
|
else
|
|
1428
|
-
ctl.setRoomStatus?.(
|
|
1543
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "completed"), "done");
|
|
1429
1544
|
break;
|
|
1430
1545
|
}
|
|
1431
1546
|
// Each operation owns a small separate block. Full data stays in review.
|
|
@@ -1438,7 +1553,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1438
1553
|
calls.set(event.call.id, event.call);
|
|
1439
1554
|
// Cattura il contenuto PRIMA della scrittura, per il diff (il file non
|
|
1440
1555
|
// e' ancora stato toccato a questo punto del loop).
|
|
1441
|
-
if (event.call.name === "write_file") {
|
|
1556
|
+
if (!replay && event.call.name === "write_file") {
|
|
1442
1557
|
const p = event.call.input["path"];
|
|
1443
1558
|
if (typeof p === "string" && p) {
|
|
1444
1559
|
const abs = isAbsolute(p) ? p : resolve(cwd, p);
|
|
@@ -1454,18 +1569,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1454
1569
|
const argRendered = typeof pathArg === "string" && pathArg && argText === pathArg
|
|
1455
1570
|
? fileLink(isAbsolute(pathArg) ? pathArg : resolve(cwd, pathArg), theme.dim(pathArg))
|
|
1456
1571
|
: theme.dim(argText);
|
|
1457
|
-
//
|
|
1458
|
-
|
|
1459
|
-
// shell (e' un comando bash).
|
|
1460
|
-
const nameRendered = gitSum
|
|
1461
|
-
? paintCategoryName("shell", "Git")
|
|
1462
|
-
: paintToolName(event.call.name, toolLabel(event.call.name));
|
|
1572
|
+
// Quiet action headers leave the emphasis to results and changed code.
|
|
1573
|
+
const nameRendered = theme.muted(label);
|
|
1463
1574
|
// Coda statica della riga (tutto cio' che segue il bullet): la riusiamo
|
|
1464
1575
|
// sia per la scrittura iniziale sia per l'animazione del bullet.
|
|
1465
|
-
const tail = nameRendered +
|
|
1466
|
-
const header = fitLine(
|
|
1576
|
+
const tail = nameRendered + " " + argRendered;
|
|
1577
|
+
const header = fitLine(" " + theme.muted("◇ ") + tail, terminalColumns() - 1);
|
|
1578
|
+
const index = ctl.logLength();
|
|
1467
1579
|
ctl.write(header + "\n");
|
|
1468
|
-
toolCards.set(event.call.id, { end: ctl.logLength(), header });
|
|
1580
|
+
toolCards.set(event.call.id, { index, end: ctl.logLength(), header });
|
|
1469
1581
|
if (event.call.name === "ask_user")
|
|
1470
1582
|
startDot(ctl.logLength() - 1, event.call.name, tail);
|
|
1471
1583
|
break;
|
|
@@ -1474,6 +1586,9 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1474
1586
|
ctl.setStatus(tr("elaboro", "working"));
|
|
1475
1587
|
stopDot(); // ferma l'animazione del bullet: il tool e' finito
|
|
1476
1588
|
const call = calls.get(event.result.callId);
|
|
1589
|
+
const before = preContent.get(event.result.callId);
|
|
1590
|
+
calls.delete(event.result.callId);
|
|
1591
|
+
preContent.delete(event.result.callId);
|
|
1477
1592
|
const toolCard = toolCards.get(event.result.callId);
|
|
1478
1593
|
toolCards.delete(event.result.callId);
|
|
1479
1594
|
if (event.parentId) {
|
|
@@ -1483,6 +1598,20 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1483
1598
|
parent.isError = event.result.isError;
|
|
1484
1599
|
}
|
|
1485
1600
|
}
|
|
1601
|
+
if (call?.name === "bash_output") {
|
|
1602
|
+
const id = String(call.input["id"] ?? call.id);
|
|
1603
|
+
const snapshot = JSON.stringify([event.result.isError ?? false, event.result.output, event.result.media]);
|
|
1604
|
+
if (backgroundPolls.get(id) === snapshot)
|
|
1605
|
+
break;
|
|
1606
|
+
backgroundPolls.set(id, snapshot);
|
|
1607
|
+
flushText();
|
|
1608
|
+
ensureAssistantStarted();
|
|
1609
|
+
const summary = backgroundResultSummary(event.result.output, terminalColumns());
|
|
1610
|
+
ctl.write("\n" + fitLine(" " + (event.result.isError ? theme.red("× ") : theme.muted("◇ "))
|
|
1611
|
+
+ theme.muted(toolLabel("bash_output")), terminalColumns() - 1) + "\n"
|
|
1612
|
+
+ theme.dark(" └ ") + (event.result.isError ? theme.red(summary) : theme.dim(summary)) + "\n");
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1486
1615
|
const skillCard = skillCards.get(event.result.callId);
|
|
1487
1616
|
if (skillCard) {
|
|
1488
1617
|
skillCards.delete(event.result.callId);
|
|
@@ -1534,7 +1663,8 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1534
1663
|
const card = commandCards.get(event.result.callId);
|
|
1535
1664
|
if (card && call) {
|
|
1536
1665
|
const state = commandResultState(event.result.output, event.result.isError);
|
|
1537
|
-
const retained = !ctl.getLogLine || ctl.getLogLine(card.index) === card.header
|
|
1666
|
+
const retained = !ctl.getLogLine || (ctl.getLogLine(card.index) === card.header
|
|
1667
|
+
&& ctl.getLogLine(card.index + 1) === commandPreview(commandSource(call), theme, card.columns).lines[1]);
|
|
1538
1668
|
if (retained)
|
|
1539
1669
|
ctl.setLogLine?.(card.index, commandHeader(card.firstRow, state, theme, card.columns));
|
|
1540
1670
|
// Parallel calls can complete in a different order. Repeat the
|
|
@@ -1548,7 +1678,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1548
1678
|
break;
|
|
1549
1679
|
}
|
|
1550
1680
|
if (call && !event.result.isError && call.name === "write_file") {
|
|
1551
|
-
ctl.review?.change(call.id, { before: call.input["append"] === true ? "" :
|
|
1681
|
+
ctl.review?.change(call.id, { before: call.input["append"] === true ? "" : before,
|
|
1552
1682
|
after: String(call.input["content"] ?? ""), scope: call.input["append"] === true ? "append" : "file" });
|
|
1553
1683
|
}
|
|
1554
1684
|
// Token per stanza/castello (solo con vascend attivo). Snapshot a
|
|
@@ -1564,11 +1694,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1564
1694
|
const st = String(call.input["status"] ?? "completed");
|
|
1565
1695
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1566
1696
|
if (st === "running")
|
|
1567
|
-
ctl.setRoomStatus?.(
|
|
1697
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "running"), "running");
|
|
1568
1698
|
else if (st === "failed")
|
|
1569
|
-
ctl.setRoomStatus?.(
|
|
1699
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "failed"), "failed");
|
|
1570
1700
|
else
|
|
1571
|
-
ctl.setRoomStatus?.(
|
|
1701
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "completed"), "done");
|
|
1572
1702
|
break;
|
|
1573
1703
|
}
|
|
1574
1704
|
// Detailed plans are available in the task panel.
|
|
@@ -1579,6 +1709,23 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1579
1709
|
notifyStatus(ctl, notice);
|
|
1580
1710
|
break;
|
|
1581
1711
|
}
|
|
1712
|
+
const diff = call && !event.result.isError
|
|
1713
|
+
? diffForCall(call, before, event.result.output, theme, terminalColumns())
|
|
1714
|
+
: undefined;
|
|
1715
|
+
if (diff) {
|
|
1716
|
+
const retained = Boolean(toolCard && (!ctl.getLogLine || ctl.getLogLine(toolCard.index) === toolCard.header));
|
|
1717
|
+
// Con un risultato sequenziale trasformiamo Update(path) direttamente
|
|
1718
|
+
// nell'intestazione finale. Nessun secondo riepilogo "modificato" e
|
|
1719
|
+
// nessuna intestazione duplicata sopra la diff.
|
|
1720
|
+
if (toolCard && retained && ctl.setLogLine && toolCard.end === ctl.logLength()) {
|
|
1721
|
+
ctl.setLogLine(toolCard.index, fitLine(diff.header, terminalColumns() - 1));
|
|
1722
|
+
}
|
|
1723
|
+
else {
|
|
1724
|
+
ctl.write("\n" + fitLine(diff.header, terminalColumns() - 1) + "\n");
|
|
1725
|
+
}
|
|
1726
|
+
ctl.write(previewDiff(diff.body, theme, terminalColumns() - 1, diff.hiddenRows));
|
|
1727
|
+
break;
|
|
1728
|
+
}
|
|
1582
1729
|
// Associate out-of-order results with their own operation rather than
|
|
1583
1730
|
// appending several anonymous result lines below the newest call.
|
|
1584
1731
|
if (toolCard && toolCard.end !== ctl.logLength())
|
|
@@ -1588,18 +1735,22 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1588
1735
|
// muted (piu' leggibile del dim slavato, ma chiaramente secondario).
|
|
1589
1736
|
{
|
|
1590
1737
|
const summary = liveResultSummary(event.result.output, event.result.isError ? null : resultSummary(call, event.result.output), terminalColumns());
|
|
1591
|
-
ctl.write(theme.dark("
|
|
1738
|
+
ctl.write(theme.dark(" └ ") + (event.result.isError ? theme.red("× ") : "") +
|
|
1592
1739
|
(event.result.isError ? theme.red(summary) : theme.dim(summary)) + "\n");
|
|
1593
1740
|
}
|
|
1594
|
-
// Keep the change preview alongside its operation.
|
|
1595
|
-
if (call && !event.result.isError) {
|
|
1596
|
-
const diff = diffForCall(call, preContent.get(event.result.callId), event.result.output, theme, terminalColumns());
|
|
1597
|
-
if (diff) {
|
|
1598
|
-
ctl.write(previewDiff(diff, theme, terminalColumns() - 1));
|
|
1599
|
-
}
|
|
1600
|
-
}
|
|
1601
1741
|
break;
|
|
1602
1742
|
}
|
|
1743
|
+
case "session_naming":
|
|
1744
|
+
ctl.setSessionNaming?.(event.phase, event.title);
|
|
1745
|
+
break;
|
|
1746
|
+
case "compacting":
|
|
1747
|
+
stopDot();
|
|
1748
|
+
flushText();
|
|
1749
|
+
ctl.setCompacting?.(true);
|
|
1750
|
+
break;
|
|
1751
|
+
case "compaction_end":
|
|
1752
|
+
ctl.setCompacting?.(false);
|
|
1753
|
+
break;
|
|
1603
1754
|
case "compacted":
|
|
1604
1755
|
stopDot();
|
|
1605
1756
|
flushText();
|
|
@@ -1635,7 +1786,12 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1635
1786
|
ctl.setLogLine?.(card.index, commandHeader(card.firstRow, "stopped", theme, card.columns));
|
|
1636
1787
|
}
|
|
1637
1788
|
commandCards.clear();
|
|
1638
|
-
|
|
1789
|
+
if (isUserInterruption(event.message)) {
|
|
1790
|
+
writeWarning(tr("Interrotto · puoi inviare un nuovo messaggio per continuare.", "Interrupted · send a new message to continue."));
|
|
1791
|
+
}
|
|
1792
|
+
else {
|
|
1793
|
+
writeNotice(`${tr("Errore", "Error")}: ${runtimeError(event.message)}`, true);
|
|
1794
|
+
}
|
|
1639
1795
|
break;
|
|
1640
1796
|
case "done":
|
|
1641
1797
|
for (const card of mcpCards.values()) {
|
|
@@ -1654,11 +1810,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1654
1810
|
// Auto-continuazione esaurita: il run finisce ancora troncato. Avvisa di
|
|
1655
1811
|
// spezzare il compito (o alzare TALOS_MAX_AUTOCONTINUE).
|
|
1656
1812
|
if (lastTruncated) {
|
|
1657
|
-
notifyStatus(ctl, theme.yellow(
|
|
1813
|
+
notifyStatus(ctl, theme.yellow(`\n ${tr("Output ancora al limite dopo le continuazioni automatiche: suddividi il compito o aumenta TALOS_MAX_AUTOCONTINUE.", "Output still at the limit after automatic continuations: split the task or increase TALOS_MAX_AUTOCONTINUE.")}\n`));
|
|
1658
1814
|
}
|
|
1659
1815
|
break;
|
|
1660
1816
|
}
|
|
1661
1817
|
};
|
|
1818
|
+
return Object.assign(sink, { preview: () => {
|
|
1819
|
+
const text = stripToolMarkup(textBuffer).trimEnd();
|
|
1820
|
+
return text ? renderAssistantSegment(text, theme, terminalColumns(), { cwd, env }, assistantStarted ? "progress" : "start", assistantSegments + 1) : "";
|
|
1821
|
+
} });
|
|
1662
1822
|
}
|
|
1663
1823
|
// Attribuzione token alle stanze + riepilogo per castello (metodo Danilov).
|
|
1664
1824
|
// All'avvio della stanza (running) salva lo snapshot di outTok; a stanza
|
|
@@ -1847,10 +2007,10 @@ function renderVascendToolNotice(call, output, theme, columns) {
|
|
|
1847
2007
|
const st = String(call.input["status"] ?? "completed");
|
|
1848
2008
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1849
2009
|
if (st === "running")
|
|
1850
|
-
return renderVascendNotice("start",
|
|
2010
|
+
return renderVascendNotice("start", taskActivityLabel(`${where}${task}`, "running"), theme);
|
|
1851
2011
|
if (st === "failed")
|
|
1852
|
-
return renderVascendNotice("fail",
|
|
1853
|
-
return renderVascendNotice("done",
|
|
2012
|
+
return renderVascendNotice("fail", taskActivityLabel(`${where}${task}`, "failed"), theme);
|
|
2013
|
+
return renderVascendNotice("done", taskActivityLabel(`${where}${task}`, "completed"), theme);
|
|
1854
2014
|
}
|
|
1855
2015
|
return null;
|
|
1856
2016
|
}
|
|
@@ -1862,26 +2022,26 @@ function diffForCall(call, pre, output, theme, columns) {
|
|
|
1862
2022
|
const append = call.input["append"] === true;
|
|
1863
2023
|
const next = String(call.input["content"] ?? "");
|
|
1864
2024
|
const existed = (pre ?? "") !== "";
|
|
1865
|
-
const verb = append ? "aggiunto" : existed ? "modificato" : "creato";
|
|
2025
|
+
const verb = append ? tr("aggiunto", "appended") : existed ? tr("modificato", "modified") : tr("creato", "created");
|
|
1866
2026
|
// In append mostriamo solo l'aggiunta (base vuota); altrimenti il delta vs file.
|
|
1867
|
-
return
|
|
2027
|
+
return renderDiffParts(append ? "" : (pre ?? ""), next, theme, { path, verb, columns: columns - 1, wrapLines: true, maxRows: 100 }) ?? undefined;
|
|
1868
2028
|
}
|
|
1869
2029
|
if (call.name === "edit_file" && output.startsWith("modificato:")) {
|
|
1870
2030
|
const path = typeof call.input["path"] === "string" ? call.input["path"] : undefined;
|
|
1871
2031
|
const oldS = String(call.input["old_string"] ?? "");
|
|
1872
2032
|
const newS = String(call.input["new_string"] ?? "");
|
|
1873
|
-
return
|
|
2033
|
+
return renderDiffParts(oldS, newS, theme, { path, verb: tr("modificato", "modified"), context: 2, columns: columns - 1, wrapLines: true, maxRows: 100 }) ?? undefined;
|
|
1874
2034
|
}
|
|
1875
|
-
return
|
|
2035
|
+
return undefined;
|
|
1876
2036
|
}
|
|
1877
2037
|
// Soglia riferita alle righe del confronto, escluse intestazione e separatore.
|
|
1878
|
-
function previewDiff(full, theme, width) {
|
|
2038
|
+
function previewDiff(full, theme, width, hiddenRows = 0) {
|
|
1879
2039
|
const lines = full.replace(/\n$/, "").split("\n");
|
|
1880
|
-
const limit = 100
|
|
1881
|
-
if (lines.length <= limit)
|
|
2040
|
+
const limit = 100;
|
|
2041
|
+
if (lines.length <= limit && hiddenRows === 0)
|
|
1882
2042
|
return full;
|
|
1883
2043
|
const head = lines.slice(0, limit);
|
|
1884
|
-
const hidden = lines.length - limit;
|
|
2044
|
+
const hidden = Math.max(0, lines.length - limit) + hiddenRows;
|
|
1885
2045
|
const hint = ` … altre ${hidden} righe · Ctrl+O scegli file`;
|
|
1886
2046
|
head.push(theme.dim(fitLine(terminalWidth(hint) <= width ? hint : ` Ctrl+O · +${hidden} righe`, width)));
|
|
1887
2047
|
return head.join("\n") + "\n";
|
|
@@ -2061,6 +2221,7 @@ function sessionOptions(list) {
|
|
|
2061
2221
|
label: sessionTitle(s.name ?? "") || s.preview || s.id,
|
|
2062
2222
|
detail: `${fmtAge(s.updatedAt)} · ${s.messages} msg · ${s.id}`,
|
|
2063
2223
|
value: s.id,
|
|
2224
|
+
age: fmtAge(s.updatedAt),
|
|
2064
2225
|
}));
|
|
2065
2226
|
}
|
|
2066
2227
|
// Riprende una sessione: la carica nell'harness (le closure vedono il nuovo
|
|
@@ -2083,115 +2244,6 @@ function resumeInto(harness, vascend, id, ctl, theme, tcols, resetSessionUi) {
|
|
|
2083
2244
|
replaySession(loaded, ctl, theme, tcols());
|
|
2084
2245
|
return true;
|
|
2085
2246
|
}
|
|
2086
|
-
// Etichetta leggibile del tool (stile Claude Code: Read/Write/Update/Bash...).
|
|
2087
|
-
function toolLabel(name) {
|
|
2088
|
-
const map = {
|
|
2089
|
-
read_file: "Read",
|
|
2090
|
-
write_file: "Write",
|
|
2091
|
-
edit_file: "Update",
|
|
2092
|
-
list_dir: "List",
|
|
2093
|
-
bash: "Bash",
|
|
2094
|
-
bash_output: "BashOutput",
|
|
2095
|
-
web_fetch: "Fetch",
|
|
2096
|
-
ask_user: "Ask",
|
|
2097
|
-
wait: "Wait",
|
|
2098
|
-
tool_search: "ToolSearch",
|
|
2099
|
-
tool_invoke: "Invoke",
|
|
2100
|
-
skill_search: "SkillSearch",
|
|
2101
|
-
skill_load: "SkillLoad",
|
|
2102
|
-
output_page: "Page",
|
|
2103
|
-
kingdoms_warm: tr("Collaboratori", "Collaborators"),
|
|
2104
|
-
kingdom_send: "Send",
|
|
2105
|
-
kingdom_inbox: "Inbox",
|
|
2106
|
-
kingdom_wait: "WaitMsg",
|
|
2107
|
-
glob: "Glob",
|
|
2108
|
-
grep: "Grep",
|
|
2109
|
-
recall: "Recall",
|
|
2110
|
-
image_generate: "Image",
|
|
2111
|
-
vascend_plan: tr("Lista attività", "Task list"),
|
|
2112
|
-
vascend_mark: tr("Stato attività", "Task status"),
|
|
2113
|
-
vascend_note: "Note",
|
|
2114
|
-
vascend_skill: "Skill",
|
|
2115
|
-
vascend_addtasks: tr("Aggiungi attività", "Add tasks"),
|
|
2116
|
-
vascend_addrooms: tr("Aggiungi attività", "Add tasks"),
|
|
2117
|
-
vascend_status: tr("Lista attività", "Task list"),
|
|
2118
|
-
};
|
|
2119
|
-
return map[name] ?? name;
|
|
2120
|
-
}
|
|
2121
|
-
// Argomento mostrato accanto al tool: il path se c'e', il comando per bash,
|
|
2122
|
-
// altrimenti gli argomenti compatti.
|
|
2123
|
-
function toolArg(call) {
|
|
2124
|
-
const p = call.input["path"];
|
|
2125
|
-
if (call.name === "run_program") {
|
|
2126
|
-
const code = String(call.input["code"] ?? "");
|
|
2127
|
-
return tr("JavaScript · {count} righe · Ctrl+R dettagli", "JavaScript · {count} lines · Ctrl+R details", { count: code.split("\n").length });
|
|
2128
|
-
}
|
|
2129
|
-
if (call.name === "tool_invoke") {
|
|
2130
|
-
const inner = String(call.input["name"] ?? "");
|
|
2131
|
-
const a = call.input["args"] && typeof call.input["args"] === "object" ? compactArgs(call.input["args"]) : "";
|
|
2132
|
-
return truncate(`${inner}${a && a !== "{}" ? ` ${a}` : ""}`, 56);
|
|
2133
|
-
}
|
|
2134
|
-
if (call.name === "tool_search" || call.name === "skill_search")
|
|
2135
|
-
return truncate(String(call.input["query"] ?? ""), 50);
|
|
2136
|
-
if (call.name === "skill_load")
|
|
2137
|
-
return String(call.input["name"] ?? "");
|
|
2138
|
-
if (call.name === "grep" || call.name === "glob")
|
|
2139
|
-
return truncate(String(call.input["pattern"] ?? ""), 50);
|
|
2140
|
-
if (call.name === "wait") {
|
|
2141
|
-
const until = call.input["until"];
|
|
2142
|
-
return typeof until === "string" && until ? `until ${truncate(until, 44)}` : `${call.input["seconds"] ?? 5}s`;
|
|
2143
|
-
}
|
|
2144
|
-
// Tool del metodo Danilov: argomento LEGGIBILE invece del JSON grezzo
|
|
2145
|
-
// (es. Mark(T01 → running) al posto di Mark({"task":"T01","status":"running"})).
|
|
2146
|
-
const danilov = vascendArg(call);
|
|
2147
|
-
if (danilov !== null)
|
|
2148
|
-
return danilov;
|
|
2149
|
-
if (typeof p === "string" && p)
|
|
2150
|
-
return p;
|
|
2151
|
-
if (call.name === "bash")
|
|
2152
|
-
return truncate(oneLine(String(call.input["command"] ?? "")), 60);
|
|
2153
|
-
return compactArgs(call.input);
|
|
2154
|
-
}
|
|
2155
|
-
// Argomento LEGGIBILE per i tool del metodo Danilov: trasforma l'input grezzo
|
|
2156
|
-
// in una sintesi human-friendly. Ritorna null se non e' un tool Danilov noto
|
|
2157
|
-
// (cosi' toolArg prosegue con la sua logica standard).
|
|
2158
|
-
function vascendArg(call) {
|
|
2159
|
-
const i = call.input;
|
|
2160
|
-
const castle = typeof i["castle"] === "string" && i["castle"] && i["castle"] !== "master" ? ` @${i["castle"]}` : "";
|
|
2161
|
-
switch (call.name) {
|
|
2162
|
-
case "vascend_mark": {
|
|
2163
|
-
const task = String(i["task"] ?? "");
|
|
2164
|
-
const status = String(i["status"] ?? "completed");
|
|
2165
|
-
return truncate(`${task} → ${status}${castle}`, 56);
|
|
2166
|
-
}
|
|
2167
|
-
case "vascend_plan": {
|
|
2168
|
-
const tasks = Array.isArray(i["tasks"]) ? i["tasks"].length : 0;
|
|
2169
|
-
const title = typeof i["title"] === "string" ? i["title"] : "";
|
|
2170
|
-
const n = tr("{count} attività", "{count} tasks", { count: tasks });
|
|
2171
|
-
return truncate(title ? `${title} · ${n}${castle}` : `${n}${castle}`, 60);
|
|
2172
|
-
}
|
|
2173
|
-
case "vascend_addtasks": {
|
|
2174
|
-
const tasks = Array.isArray(i["tasks"]) ? i["tasks"].length : 0;
|
|
2175
|
-
return truncate("+" + tr("{count} attività", "{count} tasks", { count: tasks }) + castle, 56);
|
|
2176
|
-
}
|
|
2177
|
-
case "vascend_note": {
|
|
2178
|
-
const task = typeof i["task"] === "string" && i["task"] ? `${i["task"]}: ` : "";
|
|
2179
|
-
const text = String(i["text"] ?? i["note"] ?? "");
|
|
2180
|
-
return truncate(`${task}${text}`, 56);
|
|
2181
|
-
}
|
|
2182
|
-
case "vascend_skill": {
|
|
2183
|
-
const task = typeof i["task"] === "string" && i["task"] ? `${i["task"]}: ` : "";
|
|
2184
|
-
const skills = Array.isArray(i["skills"]) ? i["skills"].join(", ") : String(i["skills"] ?? i["skill"] ?? "");
|
|
2185
|
-
return truncate(`${task}${skills}`, 56);
|
|
2186
|
-
}
|
|
2187
|
-
default:
|
|
2188
|
-
return null;
|
|
2189
|
-
}
|
|
2190
|
-
}
|
|
2191
|
-
function compactArgs(input) {
|
|
2192
|
-
const s = JSON.stringify(input);
|
|
2193
|
-
return s.length > 80 ? s.slice(0, 77) + "..." : s;
|
|
2194
|
-
}
|
|
2195
2247
|
function firstLine(s) {
|
|
2196
2248
|
const line = s.split("\n")[0] ?? "";
|
|
2197
2249
|
return line.length > 100 ? line.slice(0, 97) + "..." : line;
|
|
@@ -2287,6 +2339,10 @@ function controlCollapsible(ctl, channel, action) {
|
|
|
2287
2339
|
// Ritorna: "quit" per uscire, input da inviare al modello, o null se il
|
|
2288
2340
|
// comando e' stato gestito internamente.
|
|
2289
2341
|
async function handleSlash(line, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi) {
|
|
2342
|
+
if (ctl.busyCommands && !liveCommandAllowed(line)) {
|
|
2343
|
+
notifyStatus(ctl, tr("{command}: disponibile a fine turno. Non inviato al modello; attendi o premi Esc per interrompere.", "{command}: available after the turn. Not sent to the model; wait or press Esc to interrupt.", { command: line.split(/\s+/)[0] }));
|
|
2344
|
+
return null;
|
|
2345
|
+
}
|
|
2290
2346
|
return withCommandFeedback(ctl, commandCtl => handleSlashCommand(line, deps, commandCtl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi), lines => showReadonlyOverlay({ title: line.split(/\s+/)[0], lines, ctl, theme, terminalColumns }));
|
|
2291
2347
|
}
|
|
2292
2348
|
async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi) {
|
|
@@ -2294,6 +2350,12 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2294
2350
|
const args = rest.join(" ");
|
|
2295
2351
|
const { harness } = deps;
|
|
2296
2352
|
switch (cmd) {
|
|
2353
|
+
case "skills":
|
|
2354
|
+
case "mcp": {
|
|
2355
|
+
const inventory = cmd === "skills" ? skillList(harness.skills, harness.skillCommandsEnabled) : mcpList(harness.tools);
|
|
2356
|
+
await showReadonlyOverlay({ ...inventory, ctl, theme, terminalColumns });
|
|
2357
|
+
return null;
|
|
2358
|
+
}
|
|
2297
2359
|
case "review":
|
|
2298
2360
|
case "revisione": {
|
|
2299
2361
|
if (ctl.showReview)
|
|
@@ -2318,8 +2380,45 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2318
2380
|
return null;
|
|
2319
2381
|
}
|
|
2320
2382
|
case "rename": {
|
|
2321
|
-
|
|
2322
|
-
if (!title
|
|
2383
|
+
let title = args.trim();
|
|
2384
|
+
if (!title) {
|
|
2385
|
+
const choice = await selectOption({
|
|
2386
|
+
title: tr("Rinomina chat", "Rename chat"),
|
|
2387
|
+
question: true,
|
|
2388
|
+
options: [
|
|
2389
|
+
{ key: "generate", label: tr("Rigenera dall'ultimo prompt", "Regenerate from the latest prompt"),
|
|
2390
|
+
detail: tr("Usa il modello selezionato per scegliere il titolo.", "Use the selected model to choose the title."), value: "generate" },
|
|
2391
|
+
{ key: "manual", label: tr("Imposta manualmente", "Set manually"),
|
|
2392
|
+
detail: tr("Scrivi il nuovo nome della chat.", "Type the new chat name."), value: "manual" },
|
|
2393
|
+
],
|
|
2394
|
+
ctl, theme, terminalColumns,
|
|
2395
|
+
});
|
|
2396
|
+
if (!choice || signal.aborted)
|
|
2397
|
+
return null;
|
|
2398
|
+
if (choice.value === "manual") {
|
|
2399
|
+
const entered = await ctl.promptInput(tr("Nome della chat (Invio conferma · Esc annulla)", "Chat name (Enter confirms · Esc cancels)"));
|
|
2400
|
+
if (!entered?.trim() || signal.aborted)
|
|
2401
|
+
return null;
|
|
2402
|
+
title = entered.trim();
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
if (!title) {
|
|
2406
|
+
const last = [...harness.session.history()].reverse().find(message => message.role === "user");
|
|
2407
|
+
const prompt = last?.content.filter(part => part.type === "text").map(part => part.text).join(" ") || "";
|
|
2408
|
+
if (!prompt) {
|
|
2409
|
+
notifyStatus(ctl, "Invia prima un messaggio per generare il titolo.");
|
|
2410
|
+
return null;
|
|
2411
|
+
}
|
|
2412
|
+
await nameSession(harness.deps(async () => false), prompt, event => {
|
|
2413
|
+
if (event.type === "usage")
|
|
2414
|
+
stats.observe?.(event);
|
|
2415
|
+
if (event.type === "session_naming")
|
|
2416
|
+
ctl.setSessionNaming?.(event.phase, event.title);
|
|
2417
|
+
}, signal, true);
|
|
2418
|
+
ctl.refresh?.();
|
|
2419
|
+
return null;
|
|
2420
|
+
}
|
|
2421
|
+
if (title.length > 120) {
|
|
2323
2422
|
ctl.write(theme.dim("\n Usa /rename Titolo della chat (da 1 a 120 caratteri).\n"));
|
|
2324
2423
|
return null;
|
|
2325
2424
|
}
|
|
@@ -2403,15 +2502,29 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2403
2502
|
// Esegue/riapre il comando scelto come se digitato.
|
|
2404
2503
|
return handleSlash(`/${selected.value}`, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi);
|
|
2405
2504
|
}
|
|
2505
|
+
case "remote":
|
|
2506
|
+
case "remoto": {
|
|
2507
|
+
await comandoRemoto(args, ctl, theme, harness.env, harness.config.cwd, (testo) => {
|
|
2508
|
+
if (ctl.setRoomStatus)
|
|
2509
|
+
ctl.setRoomStatus(testo, "done");
|
|
2510
|
+
});
|
|
2511
|
+
return null;
|
|
2512
|
+
}
|
|
2406
2513
|
case "logout": {
|
|
2407
2514
|
// Esce dall'account Vascend: revoca best-effort sul backend, poi rimuove
|
|
2408
2515
|
// il token locale. Al prossimo avvio il cancello chiedera' l'accesso.
|
|
2409
|
-
|
|
2410
|
-
|
|
2516
|
+
// Il controllo remoto si chiude prima: usa la stessa sessione.
|
|
2517
|
+
await fermaRemoto();
|
|
2518
|
+
if (!leggiSessione(harness.env)) {
|
|
2519
|
+
rimuoviTokenVascend(harness.env);
|
|
2411
2520
|
ctl.write(theme.dim("\n nessun accesso Vascend attivo\n"));
|
|
2412
2521
|
return null;
|
|
2413
2522
|
}
|
|
2414
|
-
await
|
|
2523
|
+
const { token } = await tokenAgente(harness.env);
|
|
2524
|
+
if (token)
|
|
2525
|
+
await revocaSessione(harness.env, token);
|
|
2526
|
+
await revocaSessioneId(harness.env);
|
|
2527
|
+
rimuoviSessione(harness.env);
|
|
2415
2528
|
rimuoviTokenVascend(harness.env);
|
|
2416
2529
|
// Via anche la prova offline: un logout che la lasciasse in giro
|
|
2417
2530
|
// regalerebbe una finestra offline a chi e' appena uscito.
|
|
@@ -2455,7 +2568,10 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2455
2568
|
await pickModel(harness, ctl, theme, terminalColumns);
|
|
2456
2569
|
return null;
|
|
2457
2570
|
}
|
|
2458
|
-
|
|
2571
|
+
if (args.trim().toLowerCase() === "fusion")
|
|
2572
|
+
await pickFusion(harness, ctl, theme, terminalColumns);
|
|
2573
|
+
else
|
|
2574
|
+
await applyModelChoice(args, harness, ctl, theme, terminalColumns);
|
|
2459
2575
|
return null;
|
|
2460
2576
|
}
|
|
2461
2577
|
case "models": {
|
|
@@ -2466,22 +2582,22 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2466
2582
|
case "themes": {
|
|
2467
2583
|
if (args) {
|
|
2468
2584
|
if (themeCtl.apply(args)) {
|
|
2469
|
-
ctl.write(makeTheme(deps.harness.config.ui, themeCtl.spec()).green(
|
|
2585
|
+
ctl.write(makeTheme(deps.harness.config.ui, themeCtl.spec()).green(("\n " + tr("tema impostato: {p0}", "Theme set: {p0}", { p0: args }) + "\n")));
|
|
2470
2586
|
}
|
|
2471
2587
|
else {
|
|
2472
|
-
ctl.write(theme.yellow(
|
|
2473
|
-
theme.dim(
|
|
2588
|
+
ctl.write(theme.yellow(("\n " + tr("tema sconosciuto: {p0}", "Unknown theme: {p0}", { p0: args }))) +
|
|
2589
|
+
theme.dim((" " + tr("disponibili: {p0}", "available: {p0}", { p0: themeCtl.available.map((t) => t.name).join(", ") }) + "\n")));
|
|
2474
2590
|
}
|
|
2475
2591
|
return null;
|
|
2476
2592
|
}
|
|
2477
2593
|
const options = themeCtl.available.map((t) => ({
|
|
2478
2594
|
key: t.name,
|
|
2479
2595
|
label: t.name,
|
|
2480
|
-
detail: t.mode === "light" ? "chiaro" : "scuro",
|
|
2596
|
+
detail: t.mode === "light" ? tr("chiaro", "light") : tr("scuro", "dark"),
|
|
2481
2597
|
value: t.name,
|
|
2482
2598
|
}));
|
|
2483
2599
|
const selected = await selectOption({
|
|
2484
|
-
title: "Temi",
|
|
2600
|
+
title: tr("Temi", "Themes"),
|
|
2485
2601
|
options,
|
|
2486
2602
|
currentKey: themeCtl.activeName(),
|
|
2487
2603
|
ctl,
|
|
@@ -2489,7 +2605,7 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2489
2605
|
terminalColumns,
|
|
2490
2606
|
});
|
|
2491
2607
|
if (selected && themeCtl.apply(selected.value)) {
|
|
2492
|
-
ctl.write(makeTheme(deps.harness.config.ui, themeCtl.spec()).green(
|
|
2608
|
+
ctl.write(makeTheme(deps.harness.config.ui, themeCtl.spec()).green(("\n " + tr("tema impostato: {p0}", "Theme set: {p0}", { p0: selected.value }) + "\n")));
|
|
2493
2609
|
}
|
|
2494
2610
|
return null;
|
|
2495
2611
|
}
|
|
@@ -2503,39 +2619,39 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2503
2619
|
const snapshot = stats.cost.snapshot(harness.config.model);
|
|
2504
2620
|
const last = stats.last;
|
|
2505
2621
|
const context = (stats.context ?? new ContextMeter()).read(harness);
|
|
2506
|
-
const lines = [theme.bold("\n Consumi della sessione") + theme.dim(` · ${harness.session.name || harness.session.id}`)];
|
|
2507
|
-
lines.push(theme.dim(` ${
|
|
2622
|
+
const lines = [theme.bold(("\n " + tr("Consumi della sessione", "Session usage"))) + theme.dim(` · ${harness.session.name || harness.session.id}`)];
|
|
2623
|
+
lines.push(theme.dim(` ${sessionUsageExplanation()}`));
|
|
2508
2624
|
if (stats.incomplete)
|
|
2509
|
-
lines.push(theme.yellow(" Storico parziale: le chiamate precedenti alla registrazione non sono ricostruibili."));
|
|
2625
|
+
lines.push(theme.yellow((" " + tr("Storico parziale: le chiamate precedenti alla registrazione non sono ricostruibili.", "Partial history: calls made before recording cannot be reconstructed."))));
|
|
2510
2626
|
if (stats.missing)
|
|
2511
|
-
lines.push(theme.yellow(
|
|
2627
|
+
lines.push(theme.yellow((" " + tr("{p0} chiamate senza conteggio token restituito dal provider.", "{p0} calls without token counts reported by the provider.", { p0: stats.missing }))));
|
|
2512
2628
|
if (stats.unsaved)
|
|
2513
|
-
lines.push(theme.yellow(" Alcuni consumi non sono stati salvati su disco."));
|
|
2629
|
+
lines.push(theme.yellow((" " + tr("Alcuni consumi non sono stati salvati su disco.", "Some usage records were not saved to disk."))));
|
|
2514
2630
|
if (snapshot.totalTurns === 0) {
|
|
2515
|
-
lines.push(theme.dim(" Nessun conteggio token ricevuto dal provider per questa sessione."));
|
|
2631
|
+
lines.push(theme.dim((" " + tr("Nessun conteggio token ricevuto dal provider per questa sessione.", "No token counts received from the provider for this session."))));
|
|
2516
2632
|
}
|
|
2517
2633
|
else {
|
|
2518
|
-
lines.push(theme.silver(
|
|
2519
|
-
lines.push(theme.silver(
|
|
2520
|
-
lines.push(theme.dim(
|
|
2521
|
-
lines.push(theme.dim(
|
|
2522
|
-
lines.push(theme.dim(
|
|
2634
|
+
lines.push(theme.silver((" " + tr("chiamate modello: {p0}", "model calls: {p0}", { p0: formatTokenCount(snapshot.totalTurns) }))));
|
|
2635
|
+
lines.push(theme.silver((" " + tr("input totale: {p0}", "total input: {p0}", { p0: formatTokenCount(snapshot.totalPromptTokens) }))));
|
|
2636
|
+
lines.push(theme.dim((" " + tr("nuovo: {p0}", "new: {p0}", { p0: formatTokenCount(snapshot.totalInputTokens) }))));
|
|
2637
|
+
lines.push(theme.dim((" " + tr("cache letta: {p0}", "cache read: {p0}", { p0: formatTokenCount(snapshot.totalCacheReadTokens) }))));
|
|
2638
|
+
lines.push(theme.dim((" " + tr("cache scritta: {p0}", "cache written: {p0}", { p0: formatTokenCount(snapshot.totalCacheWriteTokens) }))));
|
|
2523
2639
|
lines.push(theme.silver(` output: ${formatTokenCount(snapshot.totalOutputTokens)}`));
|
|
2524
|
-
lines.push(theme.silver(
|
|
2640
|
+
lines.push(theme.silver((" " + tr("totale provider: {p0}", "provider total: {p0}", { p0: formatTokenCount(snapshot.totalProviderTokens) }))));
|
|
2525
2641
|
}
|
|
2526
2642
|
if (last) {
|
|
2527
|
-
lines.push(theme.bold("\n ultima chiamata"));
|
|
2528
|
-
lines.push(theme.dim(
|
|
2643
|
+
lines.push(theme.bold(("\n " + tr("ultima chiamata", "last call"))));
|
|
2644
|
+
lines.push(theme.dim((" " + tr("input {p0} = nuovo {p1} + cache letta {p2} + cache scritta {p3}", "input {p0} = new {p1} + cache read {p2} + cache written {p3}", { p0: formatTokenCount(promptTokens(last)), p1: formatTokenCount(last.inputTokens), p2: formatTokenCount(last.cacheReadTokens ?? 0), p3: formatTokenCount(last.cacheWriteTokens ?? 0) }))));
|
|
2529
2645
|
lines.push(theme.dim(` output ${formatTokenCount(last.outputTokens)}`));
|
|
2530
2646
|
}
|
|
2531
2647
|
if (snapshot.perModel.length) {
|
|
2532
|
-
lines.push(theme.bold("\n Per modello (collaboratori inclusi quando collegati alla chat)"));
|
|
2648
|
+
lines.push(theme.bold(("\n " + tr("Per modello (collaboratori inclusi quando collegati alla chat)", "Per model (including collaborators linked to the chat)"))));
|
|
2533
2649
|
for (const row of snapshot.perModel)
|
|
2534
|
-
lines.push(theme.dim(
|
|
2650
|
+
lines.push(theme.dim((" " + tr("{p0}: {p1} token · {p2} chiamate", "{p0}: {p1} tokens · {p2} calls", { p0: row.modelId, p1: formatTokenCount(promptTokens(row) + row.outputTokens), p2: row.turns }))));
|
|
2535
2651
|
}
|
|
2536
2652
|
const providerId = harness.providers.resolve(harness.config.model)?.provider.id ?? "";
|
|
2537
2653
|
lines.push(...subscriptionDetails(providerId, stats.limits?.get(providerId), theme));
|
|
2538
|
-
lines.push(theme.bold("\n contesto vivo stimato") + theme.dim(" · non e' consumo API"));
|
|
2654
|
+
lines.push(theme.bold(("\n " + tr("contesto vivo stimato", "estimated active context"))) + theme.dim((" " + tr("· non e' consumo API", "· not API usage"))));
|
|
2539
2655
|
lines.push(...contextDetails(context).map(line => theme.dim(` ${line}`)));
|
|
2540
2656
|
ctl.write(lines.join("\n") + "\n");
|
|
2541
2657
|
return null;
|
|
@@ -2585,10 +2701,10 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2585
2701
|
const csub = args.trim();
|
|
2586
2702
|
if (csub === "close" || csub === "open" || csub === "toggle") {
|
|
2587
2703
|
if (!controlCollapsible(ctl, "config", csub) && csub === "close") {
|
|
2588
|
-
ctl.write(theme.dim("\n nessuna config da chiudere: usa /config\n\n"));
|
|
2704
|
+
ctl.write(theme.dim(("\n " + tr("nessuna config da chiudere: usa /config", "No configuration view to close: use /config") + "\n\n")));
|
|
2589
2705
|
}
|
|
2590
2706
|
else if (csub === "close") {
|
|
2591
|
-
ctl.write(theme.dim("\n config chiusa (output rimosso dalla chat)\n\n"));
|
|
2707
|
+
ctl.write(theme.dim(("\n " + tr("config chiusa (output rimosso dalla chat)", "Configuration closed (output removed from chat)") + "\n\n")));
|
|
2592
2708
|
}
|
|
2593
2709
|
return null;
|
|
2594
2710
|
}
|
|
@@ -2596,21 +2712,21 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2596
2712
|
const cfg = harness.config;
|
|
2597
2713
|
const resolved = harness.providers.resolve(cfg.model);
|
|
2598
2714
|
const provider = resolved
|
|
2599
|
-
? (resolved.provider
|
|
2600
|
-
: "non configurato";
|
|
2715
|
+
? providerLabel(resolved.provider)
|
|
2716
|
+
: tr("non configurato", "not configured");
|
|
2601
2717
|
const settings = [
|
|
2602
|
-
{ key: "model", label: "Modello", detail: cfg.model, value: "model" },
|
|
2603
|
-
{ key: "provider", label: "Provider e accessi", detail: provider, value: "provider" },
|
|
2604
|
-
{ key: "vibe", label: "Profilo operativo", detail: cfg.vibeMode, value: "vibe" },
|
|
2605
|
-
{ key: "theme", label: "Tema", detail: themeCtl.activeName(), value: "theme" },
|
|
2718
|
+
{ key: "model", label: tr("Modello", "Model"), detail: cfg.model, value: "model" },
|
|
2719
|
+
{ key: "provider", label: tr("Provider e accessi", "Providers and sign-ins"), detail: provider, value: "provider" },
|
|
2720
|
+
{ key: "vibe", label: tr("Profilo operativo", "Work profile"), detail: cfg.vibeMode, value: "vibe" },
|
|
2721
|
+
{ key: "theme", label: tr("Tema", "Theme"), detail: themeCtl.activeName(), value: "theme" },
|
|
2606
2722
|
{ key: "cwd", label: "Workspace", detail: cfg.cwd, value: "cwd" },
|
|
2607
|
-
{ key: "sessions", label: "Sessioni", detail: harness.session.id, value: "sessions" },
|
|
2608
|
-
{ key: "extensions", label: "Estensioni", detail:
|
|
2609
|
-
{ key: "usage", label: "Utilizzo e contesto", detail: "token provider, cache e finestra viva", value: "usage" },
|
|
2723
|
+
{ key: "sessions", label: tr("Sessioni", "Sessions"), detail: harness.session.id, value: "sessions" },
|
|
2724
|
+
{ key: "extensions", label: tr("Estensioni", "Extensions"), detail: tr("{p0} caricate", "{p0} loaded", { p0: harness.host.inventory().filter((item) => item.loaded).length }), value: "extensions" },
|
|
2725
|
+
{ key: "usage", label: tr("Utilizzo e contesto", "Usage and context"), detail: tr("token provider, cache e finestra viva", "provider tokens, cache and active context"), value: "usage" },
|
|
2610
2726
|
];
|
|
2611
2727
|
const selected = await selectOption({
|
|
2612
|
-
title: "Impostazioni",
|
|
2613
|
-
hint: "/config details mostra i file di configurazione",
|
|
2728
|
+
title: tr("Impostazioni", "Settings"),
|
|
2729
|
+
hint: tr("/config details mostra i file di configurazione", "/config details shows configuration files"),
|
|
2614
2730
|
options: settings,
|
|
2615
2731
|
fullHeight: true,
|
|
2616
2732
|
wide: true,
|
|
@@ -2625,22 +2741,22 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2625
2741
|
const cfg = harness.config;
|
|
2626
2742
|
const files = cfg.loadedConfigFiles.length
|
|
2627
2743
|
? cfg.loadedConfigFiles.map((file) => ` ${file}`).join("\n")
|
|
2628
|
-
: " nessun file caricato";
|
|
2629
|
-
const body = theme.dim(
|
|
2630
|
-
theme.dim(` maxTokens: ${cfg.maxTokens > 0 ? cfg.maxTokens : "auto (max del modello)"}\n`) +
|
|
2744
|
+
: (" " + tr("nessun file caricato", "no files loaded"));
|
|
2745
|
+
const body = theme.dim((" " + tr("modello: {p0}", "model: {p0}", { p0: cfg.model }) + "\n")) +
|
|
2746
|
+
theme.dim(` maxTokens: ${cfg.maxTokens > 0 ? cfg.maxTokens : tr("auto (max del modello)", "auto (model maximum)")}\n`) +
|
|
2631
2747
|
theme.dim(` vibeMode: ${cfg.vibeMode}\n`) +
|
|
2632
2748
|
theme.dim(` sessionDir: ${cfg.sessionDir}\n`) +
|
|
2633
2749
|
theme.dim(` contextFiles: ${cfg.contextFiles.join(", ")}\n`) +
|
|
2634
|
-
theme.dim(` skill: ${harness.skills.length}${harness.skillCommandsEnabled ? "" : " (comandi off)"}${harness.skills.length ? ` (${harness.skills.map((s) => s.name).join(", ")})` : ""}\n`) +
|
|
2635
|
-
theme.dim(
|
|
2636
|
-
theme.bold("\n tema\n") +
|
|
2637
|
-
theme.dim(
|
|
2638
|
-
theme.dim(
|
|
2750
|
+
theme.dim(` skill: ${harness.skills.length}${harness.skillCommandsEnabled ? "" : (" " + tr("(comandi off)", "(commands off)"))}${harness.skills.length ? ` (${harness.skills.map((s) => s.name).join(", ")})` : ""}\n`) +
|
|
2751
|
+
theme.dim((" " + tr("progetto fidato: {p0}", "trusted project: {p0}", { p0: harness.projectTrusted ? tr("si", "yes") : tr("no (risorse locali non caricate)", "no (local resources not loaded)") }) + "\n")) +
|
|
2752
|
+
theme.bold(("\n " + tr("tema", "theme") + "\n")) +
|
|
2753
|
+
theme.dim((" " + tr("attivo: {p0}{p1}", "active: {p0}{p1}", { p0: themeCtl.activeName(), p1: themeCtl.spec() ? ` (${themeCtl.spec()?.mode === "light" ? tr("chiaro", "light") : tr("scuro", "dark")})` : "" }) + "\n")) +
|
|
2754
|
+
theme.dim((" " + tr("disponibili: {p0}", "available: {p0}", { p0: themeCtl.available.map((t) => t.name).join(", ") }) + "\n")) +
|
|
2639
2755
|
theme.dim(` banner: ${cfg.ui.banner}\n`) +
|
|
2640
2756
|
theme.dim(` prompt: ${JSON.stringify(cfg.ui.prompt)}\n`) +
|
|
2641
2757
|
theme.bold("\n file\n") +
|
|
2642
2758
|
theme.dim(`${files}`);
|
|
2643
|
-
showCollapsible(ctl, "config", theme.bold("\n configurazione") + theme.dim(" · /config close per nascondere"), body);
|
|
2759
|
+
showCollapsible(ctl, "config", theme.bold(("\n " + tr("configurazione", "configuration"))) + theme.dim((" " + tr("· /config close per nascondere", "· /config close to hide"))), body);
|
|
2644
2760
|
return null;
|
|
2645
2761
|
}
|
|
2646
2762
|
case "vibe":
|
|
@@ -3001,50 +3117,53 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
3001
3117
|
ctl.write(theme.green(`\n ${result.message}\n`));
|
|
3002
3118
|
return null;
|
|
3003
3119
|
}
|
|
3004
|
-
//
|
|
3005
|
-
//
|
|
3006
|
-
// .vascend-compact.md per la ripresa a freddo. /vascend-compact e' un alias.
|
|
3007
|
-
case "vascend-compact":
|
|
3120
|
+
// Compatta direttamente la cronologia JSON della sessione. Piano e stato
|
|
3121
|
+
// operativo hanno gia i rispettivi store JSON: nessun checkpoint parallelo.
|
|
3008
3122
|
case "compact": {
|
|
3009
3123
|
const hasModel = Boolean(harness.providers.resolve(harness.config.model));
|
|
3124
|
+
let feedback = "Nessun modello configurato; contesto conservato.";
|
|
3125
|
+
let failed = false;
|
|
3010
3126
|
// 1) Compattazione LLM del contesto della sessione.
|
|
3011
3127
|
if (hasModel) {
|
|
3012
3128
|
const meter = stats.context ?? new ContextMeter();
|
|
3013
3129
|
const before = meter.read(harness).tokens;
|
|
3014
|
-
ctl.write(theme.dim(`\n compatto ~${formatTokenCount(before)} token attivi; preservo richiesta, attivita aperte e lavoro recente...\n`));
|
|
3015
3130
|
const controller = new AbortController();
|
|
3016
3131
|
const onSigint = () => controller.abort();
|
|
3132
|
+
if (signal.aborted)
|
|
3133
|
+
controller.abort();
|
|
3134
|
+
signal.addEventListener("abort", onSigint, { once: true });
|
|
3017
3135
|
process.once("SIGINT", onSigint);
|
|
3018
3136
|
try {
|
|
3019
3137
|
const done = await compactNow(harness.deps(async () => false), controller.signal, (e) => {
|
|
3020
3138
|
if (e.type === "usage")
|
|
3021
3139
|
stats.observe?.(e);
|
|
3140
|
+
if (e.type === "compacting")
|
|
3141
|
+
ctl.setCompacting?.(true);
|
|
3142
|
+
if (e.type === "compaction_end")
|
|
3143
|
+
ctl.setCompacting?.(false);
|
|
3022
3144
|
}, args || undefined);
|
|
3023
3145
|
if (done) {
|
|
3024
3146
|
const after = meter.read(harness);
|
|
3025
|
-
|
|
3026
|
-
|
|
3147
|
+
feedback = tr("Contesto compattato: ~{before} -> ~{after} token", "Context compacted: ~{before} -> ~{after} tokens", { before: formatTokenCount(before), after: formatTokenCount(after.tokens) });
|
|
3148
|
+
}
|
|
3149
|
+
else {
|
|
3150
|
+
feedback = controller.signal.aborted
|
|
3151
|
+
? tr("Compattazione interrotta; contesto conservato.", "Compaction interrupted; context preserved.")
|
|
3152
|
+
: tr("Nessuna compattazione necessaria; contesto conservato.", "No compaction needed; context preserved.");
|
|
3027
3153
|
}
|
|
3028
|
-
if (!done)
|
|
3029
|
-
ctl.write(theme.dim(controller.signal.aborted
|
|
3030
|
-
? " compattazione interrotta; contesto conservato.\n"
|
|
3031
|
-
: " nessuna compattazione applicata; contesto conservato.\n"));
|
|
3032
3154
|
}
|
|
3033
3155
|
catch (err) {
|
|
3034
|
-
|
|
3156
|
+
feedback = tr("Compattazione non riuscita: {detail}", "Compaction failed: {detail}", { detail: runtimeError(err.message) });
|
|
3157
|
+
failed = true;
|
|
3035
3158
|
}
|
|
3036
3159
|
finally {
|
|
3037
3160
|
process.removeListener("SIGINT", onSigint);
|
|
3161
|
+
signal.removeEventListener("abort", onSigint);
|
|
3162
|
+
ctl.setCompacting?.(false);
|
|
3163
|
+
ctl.setStatus(null);
|
|
3038
3164
|
}
|
|
3039
3165
|
}
|
|
3040
|
-
|
|
3041
|
-
ctl.write(theme.dim("\n nessun modello configurato: salvo solo il checkpoint Vascend.\n"));
|
|
3042
|
-
}
|
|
3043
|
-
// 2) Checkpoint Vascend/Danilov sullo stato (gia' compattato) della sessione.
|
|
3044
|
-
vascend.loadPlan(harness.config.sessionDir, harness.config.planStoreId ?? harness.config.roomStoreId ?? harness.session.id);
|
|
3045
|
-
const checkpoint = vascend.compact(harness.session.history());
|
|
3046
|
-
ctl.write(theme.green(" ✓ checkpoint Vascend salvato in .vascend-compact.md\n"));
|
|
3047
|
-
ctl.write(theme.dim(` piano e contesto pronti per la ripresa (${checkpoint.text.split("\n").length} righe)\n`));
|
|
3166
|
+
notifyStatus(ctl, feedback, failed);
|
|
3048
3167
|
return null;
|
|
3049
3168
|
}
|
|
3050
3169
|
case "tree": {
|
|
@@ -3200,9 +3319,11 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
3200
3319
|
return null;
|
|
3201
3320
|
}
|
|
3202
3321
|
const selected = await selectOption({
|
|
3203
|
-
title:
|
|
3322
|
+
title: "Resume a previous session",
|
|
3204
3323
|
hint: tr("Scrivi per cercare · Invio apre · Esc torna alla chat corrente", "Type to search · Enter open · Esc return to the current chat"),
|
|
3205
3324
|
filterable: true,
|
|
3325
|
+
sessionList: true,
|
|
3326
|
+
fullHeight: true,
|
|
3206
3327
|
options: sessionOptions(list).map((option) => ({ ...option,
|
|
3207
3328
|
detail: option.detail + (option.value === harness.session.id ? " · " + tr("corrente", "current") : ""),
|
|
3208
3329
|
})),
|
|
@@ -3400,7 +3521,7 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3400
3521
|
const rows = loginRows(harness.env, harness.providers);
|
|
3401
3522
|
const picked = await selectOption({
|
|
3402
3523
|
title: tr("accesso ai modelli", "model sign-in"),
|
|
3403
|
-
hint: "Enter gestisci · digita per cercare · Esc chiudi",
|
|
3524
|
+
hint: tr("Enter gestisci · digita per cercare · Esc chiudi", "Enter manage · type to search · Esc close"),
|
|
3404
3525
|
options: rows.map((row) => ({
|
|
3405
3526
|
key: row.id,
|
|
3406
3527
|
label: row.label,
|
|
@@ -3428,7 +3549,7 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3428
3549
|
}
|
|
3429
3550
|
const action = await selectOption({
|
|
3430
3551
|
title: `${row.label} · ${loginStatusLabel(row)}`,
|
|
3431
|
-
hint: row.command ?
|
|
3552
|
+
hint: row.command ? tr("comando: {p0}", "command: {p0}", { p0: row.command.join(" ") }) : "",
|
|
3432
3553
|
options: [
|
|
3433
3554
|
{
|
|
3434
3555
|
key: "login",
|
|
@@ -3462,10 +3583,10 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3462
3583
|
harness.reloadProviders();
|
|
3463
3584
|
const refreshed = loginRows(harness.env, harness.providers).find((item) => item.id === row.id);
|
|
3464
3585
|
if (completed && refreshed?.configured && !refreshed.needsRefresh) {
|
|
3465
|
-
ctl.write(theme.green(
|
|
3586
|
+
ctl.write(theme.green(("\n " + tr("{p0}: accesso rilevato e modelli aggiornati.", "{p0}: sign-in detected and models updated.", { p0: row.label }) + "\n")));
|
|
3466
3587
|
}
|
|
3467
3588
|
else if (completed) {
|
|
3468
|
-
ctl.write(theme.yellow(
|
|
3589
|
+
ctl.write(theme.yellow(("\n " + tr("{p0}: il comando e' terminato, ma non trovo ancora una sessione valida.", "{p0}: the command finished, but no valid session was found.", { p0: row.label }) + "\n")));
|
|
3469
3590
|
}
|
|
3470
3591
|
}
|
|
3471
3592
|
}
|
|
@@ -3474,11 +3595,11 @@ async function runInteractiveLogin(command, harness, ctl, theme) {
|
|
|
3474
3595
|
if (!executable)
|
|
3475
3596
|
return false;
|
|
3476
3597
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
3477
|
-
ctl.write(theme.dim(
|
|
3598
|
+
ctl.write(theme.dim(("\n " + tr("esegui in un terminale: {p0}", "Run in a terminal: {p0}", { p0: command.join(" ") }) + "\n")));
|
|
3478
3599
|
return false;
|
|
3479
3600
|
}
|
|
3480
|
-
ctl.write(theme.bold(
|
|
3481
|
-
ctl.write(theme.dim(" completa l'accesso; al termine tornerai automaticamente a Talos
|
|
3601
|
+
ctl.write(theme.bold(("\n " + tr("avvio {p0}", "Starting {p0}", { p0: command.join(" ") }) + "\n")));
|
|
3602
|
+
ctl.write(theme.dim((" " + tr("completa l'accesso; al termine tornerai automaticamente a Talos.", "Complete sign-in; you will return to Talos automatically.") + "\n\n")));
|
|
3482
3603
|
ctl.pause();
|
|
3483
3604
|
ctl.setOverlay(null);
|
|
3484
3605
|
ctl.setStatus(null);
|
|
@@ -3497,7 +3618,7 @@ async function runInteractiveLogin(command, harness, ctl, theme) {
|
|
|
3497
3618
|
child.once("exit", (value) => resolve(value ?? -1));
|
|
3498
3619
|
});
|
|
3499
3620
|
if (code !== 0) {
|
|
3500
|
-
ctl.write(theme.yellow(
|
|
3621
|
+
ctl.write(theme.yellow(("\n " + tr("login non completato (uscita {p0}). Verifica che `{p1}` sia installato.", "Sign-in incomplete (exit {p0}). Check that `{p1}` is installed.", { p0: code, p1: executable }) + "\n")));
|
|
3501
3622
|
return false;
|
|
3502
3623
|
}
|
|
3503
3624
|
return true;
|
|
@@ -3521,13 +3642,13 @@ async function providerMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3521
3642
|
const options = rows.map((row) => ({
|
|
3522
3643
|
key: row.id,
|
|
3523
3644
|
label: `${row.label}${row.active ? " ←" : ""}`,
|
|
3524
|
-
detail: `${rowStatusLabel(row)}${row.modelCount ?
|
|
3645
|
+
detail: `${rowStatusLabel(row)}${row.modelCount ? (" " + tr("· {p0} modelli", "· {p0} models", { p0: row.modelCount })) : ""}`,
|
|
3525
3646
|
value: row,
|
|
3526
3647
|
}));
|
|
3527
3648
|
const configured = rows.filter((r) => r.configured).length;
|
|
3528
3649
|
const picked = await selectOption({
|
|
3529
|
-
title:
|
|
3530
|
-
hint: "↑/↓ scegli · Enter apri · digita per cercare · Esc chiudi",
|
|
3650
|
+
title: tr("provider — {p0} configurati su {p1}", "providers — {p0} of {p1} configured", { p0: configured, p1: rows.length }),
|
|
3651
|
+
hint: tr("↑/↓ scegli · Enter apri · digita per cercare · Esc chiudi", "↑/↓ select · Enter open · type to search · Esc close"),
|
|
3531
3652
|
options,
|
|
3532
3653
|
ctl,
|
|
3533
3654
|
theme,
|
|
@@ -3563,7 +3684,7 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3563
3684
|
case "set-key": {
|
|
3564
3685
|
const ok = await setKeyFlow(fresh, harness, ctl, theme);
|
|
3565
3686
|
if (ok)
|
|
3566
|
-
ctl.write(theme.green(
|
|
3687
|
+
ctl.write(theme.green(("\n " + tr("{p0}: chiave salvata e provider attivato.", "{p0}: key saved and provider activated.", { p0: fresh.label }) + "\n")));
|
|
3567
3688
|
break;
|
|
3568
3689
|
}
|
|
3569
3690
|
case "set-endpoint": {
|
|
@@ -3582,11 +3703,11 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3582
3703
|
}
|
|
3583
3704
|
case "remove": {
|
|
3584
3705
|
const confirmed = await selectOption({
|
|
3585
|
-
title:
|
|
3586
|
-
hint: "la chiave salvata verra' eliminata dal config",
|
|
3706
|
+
title: tr("rimuovere la credenziale di {p0}?", "Remove the credential for {p0}?", { p0: fresh.label }),
|
|
3707
|
+
hint: tr("la chiave salvata verra' eliminata dal config", "The saved key will be deleted from configuration"),
|
|
3587
3708
|
options: [
|
|
3588
|
-
{ key: "no", label: "No, mantieni", detail: "", value: false },
|
|
3589
|
-
{ key: "yes", label: "Sì, rimuovi", detail: "", value: true },
|
|
3709
|
+
{ key: "no", label: tr("No, mantieni", "No, keep it"), detail: "", value: false },
|
|
3710
|
+
{ key: "yes", label: tr("Sì, rimuovi", "Yes, remove it"), detail: "", value: true },
|
|
3590
3711
|
],
|
|
3591
3712
|
currentKey: "no",
|
|
3592
3713
|
ctl,
|
|
@@ -3597,10 +3718,10 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3597
3718
|
const res = removeProvider(harness.env, fresh.id);
|
|
3598
3719
|
if (res.ok) {
|
|
3599
3720
|
harness.reloadProviders();
|
|
3600
|
-
ctl.write(theme.green(
|
|
3721
|
+
ctl.write(theme.green(("\n " + tr("{p0}: credenziale rimossa.", "{p0}: credential removed.", { p0: fresh.label }) + "\n")));
|
|
3601
3722
|
}
|
|
3602
3723
|
else {
|
|
3603
|
-
ctl.write(theme.yellow(`\n ${res.error ?? "rimozione non riuscita"}\n`));
|
|
3724
|
+
ctl.write(theme.yellow(`\n ${res.error ?? tr("rimozione non riuscita", "removal failed")}\n`));
|
|
3604
3725
|
}
|
|
3605
3726
|
return "back";
|
|
3606
3727
|
}
|
|
@@ -3613,28 +3734,28 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3613
3734
|
// rete), la salva e ricostruisce il registro. Ritorna true se salvata.
|
|
3614
3735
|
async function setKeyFlow(row, harness, ctl, theme) {
|
|
3615
3736
|
ctl.write(theme.dim(`\n ${providerKeyHint(row.spec)}\n`));
|
|
3616
|
-
const key = await ctl.promptInput(
|
|
3737
|
+
const key = await ctl.promptInput(tr("API key per {p0} (Invio conferma · Esc annulla)", "API key for {p0} (Enter confirm · Esc cancel)", { p0: row.label }));
|
|
3617
3738
|
if (!key)
|
|
3618
3739
|
return false;
|
|
3619
3740
|
const trimmed = key.trim();
|
|
3620
3741
|
// Validazione di forma per OpenRouter (sk-or-...). Avviso non bloccante.
|
|
3621
3742
|
if (row.id === "openrouter" && !looksLikeApiKey(trimmed)) {
|
|
3622
|
-
ctl.write(theme.yellow("\n attenzione: la chiave non sembra una OpenRouter key (sk-or-...). Provo comunque
|
|
3743
|
+
ctl.write(theme.yellow(("\n " + tr("attenzione: la chiave non sembra una OpenRouter key (sk-or-...). Provo comunque.", "Warning: the key does not look like an OpenRouter key (sk-or-...). Trying anyway.") + "\n")));
|
|
3623
3744
|
}
|
|
3624
3745
|
// Validazione di rete (oggi solo OpenRouter espone /key). Best effort.
|
|
3625
3746
|
if (row.id === "openrouter") {
|
|
3626
|
-
ctl.setStatus(theme.dim("verifico la chiave su OpenRouter…"));
|
|
3747
|
+
ctl.setStatus(theme.dim(tr("verifico la chiave su OpenRouter…", "Checking the key on OpenRouter…")));
|
|
3627
3748
|
try {
|
|
3628
3749
|
const v = await validateApiKey(trimmed);
|
|
3629
3750
|
ctl.setStatus(null);
|
|
3630
3751
|
if (!v.ok) {
|
|
3631
|
-
ctl.write(theme.yellow(
|
|
3752
|
+
ctl.write(theme.yellow(("\n " + tr("chiave non valida: {p0}. Non salvata.", "Invalid key: {p0}. Not saved.", { p0: v.error ?? tr("verifica fallita", "verification failed") }) + "\n")));
|
|
3632
3753
|
return false;
|
|
3633
3754
|
}
|
|
3634
3755
|
const credit = v.limitRemaining === null || v.limitRemaining === undefined
|
|
3635
|
-
? "credito illimitato"
|
|
3636
|
-
:
|
|
3637
|
-
ctl.write(theme.green(
|
|
3756
|
+
? tr("credito illimitato", "unlimited credit")
|
|
3757
|
+
: tr("credito residuo {p0}", "remaining credit {p0}", { p0: v.limitRemaining.toFixed(2) });
|
|
3758
|
+
ctl.write(theme.green(("\n " + tr("chiave valida{p0} · {p1}", "valid key{p0} · {p1}", { p0: v.label ? ` (${v.label})` : "", p1: credit }) + "\n")));
|
|
3638
3759
|
}
|
|
3639
3760
|
catch {
|
|
3640
3761
|
ctl.setStatus(null);
|
|
@@ -3642,7 +3763,7 @@ async function setKeyFlow(row, harness, ctl, theme) {
|
|
|
3642
3763
|
}
|
|
3643
3764
|
const res = saveProviderKey(harness.env, row.id, trimmed);
|
|
3644
3765
|
if (!res.ok) {
|
|
3645
|
-
ctl.write(theme.yellow(`\n ${res.error ?? "salvataggio non riuscito"}\n`));
|
|
3766
|
+
ctl.write(theme.yellow(`\n ${res.error ?? tr("salvataggio non riuscito", "save failed")}\n`));
|
|
3646
3767
|
return false;
|
|
3647
3768
|
}
|
|
3648
3769
|
// La env scoped serve a reloadProviders (che la rilegge dal disco), ma la
|
|
@@ -3654,13 +3775,13 @@ async function setKeyFlow(row, harness, ctl, theme) {
|
|
|
3654
3775
|
}
|
|
3655
3776
|
// Flusso "imposta endpoint": chiede una baseUrl (vuota = ripristina default).
|
|
3656
3777
|
async function setEndpointFlow(row, harness, ctl, theme) {
|
|
3657
|
-
ctl.write(theme.dim(
|
|
3658
|
-
const url = await ctl.promptInput(
|
|
3778
|
+
ctl.write(theme.dim(("\n " + tr("endpoint attuale: {p0} · vuoto = ripristina default", "Current endpoint: {p0} · empty = restore default", { p0: row.baseUrl ?? tr("(default del provider)", "(provider default)") }) + "\n")));
|
|
3779
|
+
const url = await ctl.promptInput(tr("baseUrl per {p0} (Invio conferma · Esc annulla)", "baseUrl for {p0} (Enter confirm · Esc cancel)", { p0: row.label }));
|
|
3659
3780
|
if (url === null)
|
|
3660
3781
|
return; // Esc
|
|
3661
3782
|
const res = saveProviderBaseUrl(harness.env, row.id, url);
|
|
3662
3783
|
if (!res.ok) {
|
|
3663
|
-
ctl.write(theme.yellow(`\n ${res.error ?? "salvataggio non riuscito"}\n`));
|
|
3784
|
+
ctl.write(theme.yellow(`\n ${res.error ?? tr("salvataggio non riuscito", "save failed")}\n`));
|
|
3664
3785
|
return;
|
|
3665
3786
|
}
|
|
3666
3787
|
const norm = row.id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
@@ -3669,26 +3790,26 @@ async function setEndpointFlow(row, harness, ctl, theme) {
|
|
|
3669
3790
|
else
|
|
3670
3791
|
delete harness.env[`TALOS_${norm}_BASE_URL`];
|
|
3671
3792
|
harness.reloadProviders();
|
|
3672
|
-
ctl.write(theme.green(
|
|
3793
|
+
ctl.write(theme.green(("\n " + tr("{p0}: endpoint {p1}.", "{p0}: endpoint {p1}.", { p0: row.label, p1: url.trim() ? tr("aggiornato", "updated") : tr("ripristinato al default", "restored to default") }) + "\n")));
|
|
3673
3794
|
}
|
|
3674
3795
|
// Flusso OAuth (oggi: OpenRouter PKCE). Mostra l'URL da aprire nel browser e
|
|
3675
3796
|
// raccoglie il code dal callback, poi scambia e salva la chiave.
|
|
3676
3797
|
async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
3677
3798
|
if (row.id !== "openrouter") {
|
|
3678
|
-
transientNotice(ctl, "OAuth non disponibile per questo provider.", "failed", theme);
|
|
3799
|
+
transientNotice(ctl, tr("OAuth non disponibile per questo provider.", "OAuth is not available for this provider."), "failed", theme);
|
|
3679
3800
|
return;
|
|
3680
3801
|
}
|
|
3681
3802
|
// Il login e' una vista operativa temporanea: non deve diventare parte della
|
|
3682
3803
|
// conversazione. L'overlay viene aggiornato in place e rimosso al termine.
|
|
3683
3804
|
let authUrl = "";
|
|
3684
|
-
let authState = "avvio il server locale…";
|
|
3805
|
+
let authState = tr("avvio il server locale…", "Starting the local server…");
|
|
3685
3806
|
const drawOAuth = () => {
|
|
3686
3807
|
const lines = [authState];
|
|
3687
3808
|
if (authUrl)
|
|
3688
|
-
lines.push("", "Se il browser non si apre, copia questo link:", authUrl);
|
|
3689
|
-
lines.push("", "Questa schermata si chiudera' automaticamente.");
|
|
3809
|
+
lines.push("", tr("Se il browser non si apre, copia questo link:", "If the browser does not open, copy this link:"), authUrl);
|
|
3810
|
+
lines.push("", tr("Questa schermata si chiudera' automaticamente.", "This screen will close automatically."));
|
|
3690
3811
|
ctl.setOverlay([
|
|
3691
|
-
theme.bold(" OpenRouter · accesso via browser"),
|
|
3812
|
+
theme.bold((" " + tr("OpenRouter · accesso via browser", "OpenRouter · browser sign-in"))),
|
|
3692
3813
|
theme.dark(` ${"─".repeat(Math.max(12, terminalColumns() - 4))}`),
|
|
3693
3814
|
...lines.flatMap((line) => wrapPlain(line || " ", Math.max(20, terminalColumns() - 4)))
|
|
3694
3815
|
.map((line) => ` ${theme.silver(line)}`),
|
|
@@ -3699,19 +3820,19 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3699
3820
|
switch (ev.phase) {
|
|
3700
3821
|
case "url_built":
|
|
3701
3822
|
authUrl = ev.detail ?? authUrl;
|
|
3702
|
-
authState = "apro il browser per autorizzare…";
|
|
3823
|
+
authState = tr("apro il browser per autorizzare…", "Opening the browser for authorization…");
|
|
3703
3824
|
break;
|
|
3704
3825
|
case "browser_opened":
|
|
3705
|
-
authState = "browser aperto · in attesa dell'autorizzazione…";
|
|
3826
|
+
authState = tr("browser aperto · in attesa dell'autorizzazione…", "Browser opened · waiting for authorization…");
|
|
3706
3827
|
break;
|
|
3707
3828
|
case "browser_failed":
|
|
3708
|
-
authState = "apri il link manualmente · in attesa dell'autorizzazione…";
|
|
3829
|
+
authState = tr("apri il link manualmente · in attesa dell'autorizzazione…", "Open the link manually · waiting for authorization…");
|
|
3709
3830
|
break;
|
|
3710
3831
|
case "awaiting_code":
|
|
3711
|
-
authState = "in attesa dell'autorizzazione nel browser…";
|
|
3832
|
+
authState = tr("in attesa dell'autorizzazione nel browser…", "Waiting for authorization in the browser…");
|
|
3712
3833
|
break;
|
|
3713
3834
|
case "exchanging":
|
|
3714
|
-
authState = "autorizzazione ricevuta · attivo la API key…";
|
|
3835
|
+
authState = tr("autorizzazione ricevuta · attivo la API key…", "Authorization received · activating the API key…");
|
|
3715
3836
|
break;
|
|
3716
3837
|
default:
|
|
3717
3838
|
break;
|
|
@@ -3742,20 +3863,20 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3742
3863
|
saveProviderKey(harness.env, row.id, result.apiKey);
|
|
3743
3864
|
harness.env["TALOS_OPENROUTER_API_KEY"] = result.apiKey;
|
|
3744
3865
|
harness.reloadProviders();
|
|
3745
|
-
transientNotice(ctl, "OpenRouter collegato e attivato", "done", theme);
|
|
3866
|
+
transientNotice(ctl, tr("OpenRouter collegato e attivato", "OpenRouter connected and activated"), "done", theme);
|
|
3746
3867
|
return;
|
|
3747
3868
|
}
|
|
3748
3869
|
// Fallimento del flusso automatico (browser/timeout/callback/server):
|
|
3749
3870
|
// ricadiamo sul percorso manuale con un flow nuovo e coerente (PKCE
|
|
3750
3871
|
// proprio + authUrl mostrato all'utente, che incolla il code).
|
|
3751
|
-
const reason = result.error ?? "callback automatico non riuscito";
|
|
3872
|
+
const reason = result.error ?? tr("callback automatico non riuscito", "automatic callback failed");
|
|
3752
3873
|
const manualFlow = startOAuthFlow(harness.env, callbackUrlForPort());
|
|
3753
3874
|
const manualLines = [];
|
|
3754
3875
|
const manual = await runManualCodeFallback({
|
|
3755
3876
|
print: (line) => { manualLines.push(line); },
|
|
3756
3877
|
prompt: async (label) => {
|
|
3757
3878
|
await showReadonlyOverlay({
|
|
3758
|
-
title: "OpenRouter · autorizzazione manuale",
|
|
3879
|
+
title: tr("OpenRouter · autorizzazione manuale", "OpenRouter · manual authorization"),
|
|
3759
3880
|
lines: manualLines,
|
|
3760
3881
|
ctl,
|
|
3761
3882
|
theme,
|
|
@@ -3763,25 +3884,25 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3763
3884
|
});
|
|
3764
3885
|
return ctl.promptInput(label);
|
|
3765
3886
|
},
|
|
3766
|
-
}, manualFlow, { reason });
|
|
3887
|
+
}, manualFlow, { reason, translate: tr });
|
|
3767
3888
|
if (!manual.ok || !manual.apiKey) {
|
|
3768
3889
|
if (manual.error === "cancelled") {
|
|
3769
|
-
transientNotice(ctl, "OAuth annullato", "done", theme);
|
|
3890
|
+
transientNotice(ctl, tr("OAuth annullato", "OAuth cancelled"), "done", theme);
|
|
3770
3891
|
}
|
|
3771
3892
|
else {
|
|
3772
|
-
transientNotice(ctl,
|
|
3893
|
+
transientNotice(ctl, tr("OAuth non riuscito: {p0}", "OAuth failed: {p0}", { p0: manual.error ?? tr("scambio fallito", "exchange failed") }), "failed", theme);
|
|
3773
3894
|
}
|
|
3774
3895
|
return;
|
|
3775
3896
|
}
|
|
3776
3897
|
saveProviderKey(harness.env, row.id, manual.apiKey);
|
|
3777
3898
|
harness.env["TALOS_OPENROUTER_API_KEY"] = manual.apiKey;
|
|
3778
3899
|
harness.reloadProviders();
|
|
3779
|
-
transientNotice(ctl, "OpenRouter collegato e attivato", "done", theme);
|
|
3900
|
+
transientNotice(ctl, tr("OpenRouter collegato e attivato", "OpenRouter connected and activated"), "done", theme);
|
|
3780
3901
|
}
|
|
3781
3902
|
catch (err) {
|
|
3782
3903
|
ctl.setStatus(null);
|
|
3783
3904
|
ctl.setOverlay(null);
|
|
3784
|
-
transientNotice(ctl,
|
|
3905
|
+
transientNotice(ctl, tr("errore OAuth: {p0}", "OAuth error: {p0}", { p0: err.message }), "failed", theme);
|
|
3785
3906
|
}
|
|
3786
3907
|
}
|
|
3787
3908
|
function transientNotice(ctl, label, state, theme) {
|
|
@@ -3794,18 +3915,22 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3794
3915
|
.filter((row) => !row.configured || row.needsRefresh)
|
|
3795
3916
|
.map((row) => ({
|
|
3796
3917
|
key: `access:${row.id}`,
|
|
3797
|
-
label: `${row.needsRefresh ? "Rinnova" : "Collega"} ${row.label}`,
|
|
3918
|
+
label: `${row.needsRefresh ? tr("Rinnova", "Renew") : tr("Collega", "Connect")} ${row.label}`,
|
|
3798
3919
|
detail: `${loginStatusLabel(row)} · ${row.detail}`,
|
|
3799
3920
|
value: { providerId: `access:${row.id}`, modelId: "", fullModel: `access:${row.id}` },
|
|
3800
3921
|
}));
|
|
3801
|
-
const options = [
|
|
3922
|
+
const options = [{ key: "fusion", label: "Fusion",
|
|
3923
|
+
detail: tr("Jev decide (modello, focus, permessi), l'agente esegue · piani a pagamento", "Jev decides (model, focus, permissions), the agent executes · paid plans"),
|
|
3924
|
+
value: { providerId: "fusion", modelId: "", fullModel: "fusion" } }, ...accessOptions, ...modelOptions(providers, dynamic)];
|
|
3802
3925
|
if (!options.length) {
|
|
3803
|
-
ctl.write(theme.yellow("\n nessun provider configurato. Usa /login per collegare un abbonamento o una API key
|
|
3926
|
+
ctl.write(theme.yellow(("\n " + tr("nessun provider configurato. Usa /login per collegare un abbonamento o una API key.", "No provider configured. Use /login to connect a subscription or an API key.") + "\n")));
|
|
3804
3927
|
return false;
|
|
3805
3928
|
}
|
|
3806
3929
|
const current = canonicalModel(harness.config.model);
|
|
3807
3930
|
const selected = await selectOption({
|
|
3808
|
-
title: "Modelli",
|
|
3931
|
+
title: tr("Modelli", "Models"),
|
|
3932
|
+
modelList: true,
|
|
3933
|
+
fullHeight: true,
|
|
3809
3934
|
options,
|
|
3810
3935
|
currentKey: current?.fullModel,
|
|
3811
3936
|
ctl,
|
|
@@ -3815,6 +3940,8 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3815
3940
|
});
|
|
3816
3941
|
if (!selected)
|
|
3817
3942
|
return false;
|
|
3943
|
+
if (selected.value.providerId === "fusion")
|
|
3944
|
+
return pickFusion(harness, ctl, theme, terminalColumns);
|
|
3818
3945
|
if (selected.value.providerId.startsWith("access:")) {
|
|
3819
3946
|
await loginMenu(harness, ctl, theme, terminalColumns);
|
|
3820
3947
|
return false;
|
|
@@ -3827,15 +3954,15 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3827
3954
|
async function pickAuthMethod(choice, harness, ctl, theme, terminalColumns) {
|
|
3828
3955
|
const options = authOptions(choice, harness.providers.list());
|
|
3829
3956
|
if (!options.length) {
|
|
3830
|
-
ctl.write(theme.yellow(
|
|
3957
|
+
ctl.write(theme.yellow(("\n " + tr("nessun metodo di accesso configurato per {p0}.", "No sign-in method configured for {p0}.", { p0: choice.fullModel }) + "\n")));
|
|
3831
3958
|
ctl.write(theme.dim(authHint(choice.providerId) + "\n"));
|
|
3832
3959
|
return null;
|
|
3833
3960
|
}
|
|
3834
3961
|
if (!requiresAuthChoice(choice.providerId))
|
|
3835
3962
|
return options[0] ?? null;
|
|
3836
3963
|
return selectOption({
|
|
3837
|
-
title: "metodo accesso",
|
|
3838
|
-
hint: "↑/↓ muovi Enter conferma Esc/q annulla",
|
|
3964
|
+
title: tr("metodo accesso", "sign-in method"),
|
|
3965
|
+
hint: tr("↑/↓ muovi Enter conferma Esc/q annulla", "↑/↓ move Enter confirm Esc/q cancel"),
|
|
3839
3966
|
options,
|
|
3840
3967
|
currentKey: harness.config.model,
|
|
3841
3968
|
ctl,
|
|
@@ -3862,7 +3989,9 @@ function modelOptions(providers, dynamic) {
|
|
|
3862
3989
|
const detail = dynById.get(model)?.detail;
|
|
3863
3990
|
out.push({
|
|
3864
3991
|
key: full,
|
|
3865
|
-
label:
|
|
3992
|
+
label: model,
|
|
3993
|
+
group: spec.label,
|
|
3994
|
+
modelInfo: dynById.get(model)?.modelInfo,
|
|
3866
3995
|
detail: `${authSummary(spec.id, authProviders)}${detail ? ` · ${detail}` : ""}`,
|
|
3867
3996
|
value: { providerId: spec.id, modelId: model, fullModel: full },
|
|
3868
3997
|
});
|
|
@@ -3881,10 +4010,11 @@ async function loadDynamicModels(providers, ctl, theme, env) {
|
|
|
3881
4010
|
continue;
|
|
3882
4011
|
map.set(spec.id, catalog.list(spec.id)
|
|
3883
4012
|
.filter(m => m.status !== "deprecated" && m.outputModalities.includes("text") && m.toolCall !== false)
|
|
3884
|
-
.map(m => ({ id: m.id, detail: modelDetail(m)
|
|
4013
|
+
.map(m => ({ id: m.id, detail: modelDetail(m), modelInfo: { context: m.context,
|
|
4014
|
+
reasoning: m.reasoning, cost: m.cost } })));
|
|
3885
4015
|
}
|
|
3886
4016
|
if (providers.some((p) => p.id === "openrouter")) {
|
|
3887
|
-
ctl.setStatus(theme.dim("carico catalogo OpenRouter…"));
|
|
4017
|
+
ctl.setStatus(theme.dim(tr("carico catalogo OpenRouter…", "Loading the OpenRouter catalog…")));
|
|
3888
4018
|
try {
|
|
3889
4019
|
const models = await fetchOpenRouterModels(env);
|
|
3890
4020
|
if (models.length) {
|
|
@@ -3910,7 +4040,7 @@ function authOptions(choice, providers) {
|
|
|
3910
4040
|
.map((provider) => ({
|
|
3911
4041
|
key: `${provider.id}/${choice.modelId}`,
|
|
3912
4042
|
label: authLabel(choice.providerId, provider.id),
|
|
3913
|
-
detail: provider
|
|
4043
|
+
detail: providerLabel(provider),
|
|
3914
4044
|
value: `${provider.id}/${choice.modelId}`,
|
|
3915
4045
|
}));
|
|
3916
4046
|
}
|
|
@@ -3926,14 +4056,14 @@ function authProviderIds(providerId) {
|
|
|
3926
4056
|
}
|
|
3927
4057
|
function authLabel(canonicalProviderId, actualProviderId) {
|
|
3928
4058
|
if (canonicalProviderId === "anthropic" && actualProviderId === "claude-code")
|
|
3929
|
-
return "OAuth abbonamento";
|
|
4059
|
+
return tr("OAuth abbonamento", "OAuth subscription");
|
|
3930
4060
|
if (canonicalProviderId === "anthropic" && actualProviderId === "anthropic")
|
|
3931
4061
|
return "API key";
|
|
3932
4062
|
if (canonicalProviderId === "openai" && actualProviderId === "chatgpt")
|
|
3933
|
-
return "OAuth abbonamento";
|
|
4063
|
+
return tr("OAuth abbonamento", "OAuth subscription");
|
|
3934
4064
|
if (canonicalProviderId === "openai" && actualProviderId === "openai")
|
|
3935
4065
|
return "API key";
|
|
3936
|
-
return actualProviderId === "ollama" ? "locale" : "API key";
|
|
4066
|
+
return actualProviderId === "ollama" ? tr("locale", "local") : "API key";
|
|
3937
4067
|
}
|
|
3938
4068
|
function authSummary(providerId, providers) {
|
|
3939
4069
|
return providers.map((provider) => authLabel(providerId, provider.id)).join(" / ");
|
|
@@ -3965,11 +4095,11 @@ function canonicalProviderId(providerId) {
|
|
|
3965
4095
|
}
|
|
3966
4096
|
function authHint(providerId) {
|
|
3967
4097
|
if (providerId === "anthropic")
|
|
3968
|
-
return " Configura CLAUDE_CODE OAuth con `claude` oppure ANTHROPIC_API_KEY.";
|
|
4098
|
+
return (" " + tr("Configura CLAUDE_CODE OAuth con `claude` oppure ANTHROPIC_API_KEY.", "Configure CLAUDE_CODE OAuth with `claude` or ANTHROPIC_API_KEY."));
|
|
3969
4099
|
if (providerId === "openai")
|
|
3970
|
-
return " Configura ChatGPT OAuth con `codex` oppure OPENAI_API_KEY.";
|
|
4100
|
+
return (" " + tr("Configura ChatGPT OAuth con `codex` oppure OPENAI_API_KEY.", "Configure ChatGPT OAuth with `codex` or OPENAI_API_KEY."));
|
|
3971
4101
|
const spec = PROVIDER_CATALOG.find((item) => item.id === providerId);
|
|
3972
|
-
return spec?.envKey ?
|
|
4102
|
+
return spec?.envKey ? (" " + tr("Configura {p0}.", "Configure {p0}.", { p0: spec.envKey })) : (" " + tr("Verifica la configurazione del provider.", "Check the provider configuration."));
|
|
3973
4103
|
}
|
|
3974
4104
|
function unique(values) {
|
|
3975
4105
|
return [...new Set(values)];
|
|
@@ -3997,7 +4127,7 @@ async function showReadonlyOverlay(opts) {
|
|
|
3997
4127
|
opts.theme.bold(` ${opts.title}`),
|
|
3998
4128
|
opts.theme.dark(` ${"─".repeat(Math.max(8, width - 4))}`),
|
|
3999
4129
|
...body.map((line) => ` ${opts.theme.silver(truncate(line, Math.max(12, width - 4)))}`),
|
|
4000
|
-
opts.theme.dark(` ${allLines.length > pageSize() ?
|
|
4130
|
+
opts.theme.dark(` ${allLines.length > pageSize() ? `${tr("↑/↓ scorri", "↑/↓ scroll")} · ${offset + 1}-${Math.min(offset + pageSize(), allLines.length)}/${allLines.length} · ` : ""}${tr("Esc/Invio/q chiude", "Esc/Enter/q closes")}`),
|
|
4001
4131
|
];
|
|
4002
4132
|
opts.ctl.setOverlay(rows);
|
|
4003
4133
|
};
|