@vascend/talos 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/agent.d.ts +16 -3
- package/dist/core/agent.js +114 -34
- package/dist/core/background.d.ts +10 -3
- package/dist/core/background.js +55 -9
- package/dist/core/bootstrap.js +1 -0
- package/dist/core/compaction.js +1 -1
- package/dist/core/config-storage.d.ts +5 -0
- package/dist/core/config-storage.js +83 -0
- package/dist/core/config.d.ts +11 -2
- package/dist/core/config.js +24 -8
- package/dist/core/context-ledger.js +2 -1
- package/dist/core/context-policy.d.ts +1 -1
- package/dist/core/context-policy.js +10 -7
- package/dist/core/credentials.js +29 -30
- package/dist/core/effort.d.ts +1 -1
- package/dist/core/effort.js +5 -1
- package/dist/core/execution-guidance.d.ts +1 -1
- package/dist/core/execution-guidance.js +2 -0
- package/dist/core/file-coordination.d.ts +3 -0
- package/dist/core/file-coordination.js +28 -0
- package/dist/core/file-lease-store.d.ts +3 -0
- package/dist/core/file-lease-store.js +28 -12
- package/dist/core/fusion.d.ts +24 -0
- package/dist/core/fusion.js +80 -0
- package/dist/core/model-limits.d.ts +1 -0
- package/dist/core/model-limits.js +5 -1
- package/dist/core/permission-queue.js +15 -15
- package/dist/core/request-plan.js +4 -3
- package/dist/core/session-naming.js +4 -0
- package/dist/core/session.d.ts +4 -2
- package/dist/core/session.js +13 -4
- package/dist/core/settings.d.ts +2 -0
- package/dist/core/settings.js +5 -16
- package/dist/core/step-policy.d.ts +18 -0
- package/dist/core/step-policy.js +6 -0
- package/dist/core/tool-artifacts.d.ts +2 -0
- package/dist/core/tool-artifacts.js +21 -3
- package/dist/core/tool-result-index.js +29 -33
- package/dist/core/tool-result-store.js +6 -5
- package/dist/core/types.d.ts +10 -3
- package/dist/core/user-inbox.d.ts +9 -0
- package/dist/core/user-inbox.js +22 -3
- package/dist/core/user-input.js +1 -1
- package/dist/fusion/autopilot-fast.d.ts +59 -0
- package/dist/fusion/autopilot-fast.js +213 -0
- package/dist/fusion/autopilot.d.ts +75 -0
- package/dist/fusion/autopilot.js +308 -0
- package/dist/fusion/focus.d.ts +30 -0
- package/dist/fusion/focus.js +122 -0
- package/dist/fusion/gate.d.ts +30 -0
- package/dist/fusion/gate.js +95 -0
- package/dist/fusion/jev.d.ts +71 -0
- package/dist/fusion/jev.js +164 -0
- package/dist/fusion/receipts.d.ts +25 -0
- package/dist/fusion/receipts.js +34 -0
- package/dist/fusion/route.d.ts +25 -0
- package/dist/fusion/route.js +84 -0
- package/dist/fusion/run.d.ts +4 -0
- package/dist/fusion/run.js +186 -0
- package/dist/horizon/engine.d.ts +20 -1
- package/dist/horizon/engine.js +57 -0
- package/dist/horizon/index.d.ts +2 -2
- package/dist/horizon/index.js +1 -1
- package/dist/horizon/types.d.ts +29 -0
- package/dist/index.js +12 -0
- package/dist/providers/anthropic-oauth.d.ts +8 -3
- package/dist/providers/anthropic-oauth.js +13 -10
- package/dist/providers/anthropic.d.ts +9 -2
- package/dist/providers/anthropic.js +64 -21
- package/dist/providers/cli-models.d.ts +30 -0
- package/dist/providers/cli-models.js +194 -0
- package/dist/providers/model-effort.js +1 -1
- package/dist/providers/openai-oauth.d.ts +8 -3
- package/dist/providers/openai-oauth.js +9 -3
- package/dist/providers/openrouter-manual-fallback.d.ts +3 -1
- package/dist/providers/openrouter-manual-fallback.js +20 -20
- package/dist/providers/registry.js +3 -2
- package/dist/providers/retry.d.ts +3 -0
- package/dist/providers/retry.js +44 -1
- package/dist/remote/cifratura.d.ts +12 -0
- package/dist/remote/cifratura.js +66 -0
- package/dist/remote/index.d.ts +15 -0
- package/dist/remote/index.js +65 -0
- package/dist/remote/relay-client.d.ts +58 -0
- package/dist/remote/relay-client.js +315 -0
- package/dist/remote/sessione-remota.d.ts +60 -0
- package/dist/remote/sessione-remota.js +266 -0
- package/dist/sdk.d.ts +7 -2
- package/dist/sdk.js +3 -1
- package/dist/tools/builtin.js +41 -20
- package/dist/tools/capabilities.d.ts +3 -1
- package/dist/tools/capabilities.js +5 -2
- package/dist/tools/computer-use.js +2 -1
- package/dist/tools/pagination.js +1 -1
- package/dist/tools/program.js +34 -21
- package/dist/tools/team-tools.js +11 -6
- package/dist/tui/account-entry.js +14 -9
- package/dist/tui/agent-label.d.ts +2 -0
- package/dist/tui/agent-label.js +5 -0
- package/dist/tui/agent-sessions-screen.d.ts +12 -2
- package/dist/tui/agent-sessions-screen.js +62 -16
- package/dist/tui/agent-sessions.d.ts +14 -0
- package/dist/tui/agent-sessions.js +67 -6
- package/dist/tui/bottom-input.d.ts +46 -2
- package/dist/tui/bottom-input.js +358 -59
- package/dist/tui/branding.js +8 -4
- package/dist/tui/capability-lists.d.ts +10 -0
- package/dist/tui/capability-lists.js +41 -0
- package/dist/tui/command-output-registry.js +4 -0
- package/dist/tui/command-transcript.d.ts +1 -1
- package/dist/tui/command-transcript.js +17 -21
- package/dist/tui/commands.js +4 -1
- package/dist/tui/compaction-status.d.ts +7 -0
- package/dist/tui/compaction-status.js +39 -0
- package/dist/tui/config-recovery.d.ts +12 -0
- package/dist/tui/config-recovery.js +54 -0
- package/dist/tui/context-meter.d.ts +3 -0
- package/dist/tui/context-meter.js +35 -8
- package/dist/tui/diff.d.ts +9 -0
- package/dist/tui/diff.js +43 -19
- package/dist/tui/fusion-menu.d.ts +5 -0
- package/dist/tui/fusion-menu.js +89 -0
- package/dist/tui/i18n-catalog.js +312 -1
- package/dist/tui/live-command.d.ts +1 -0
- package/dist/tui/live-command.js +9 -0
- package/dist/tui/live-output.d.ts +1 -0
- package/dist/tui/live-output.js +11 -0
- package/dist/tui/model-details.js +13 -12
- package/dist/tui/model-picker.js +17 -7
- package/dist/tui/model-selection.js +19 -10
- package/dist/tui/native-scrollback.js +5 -0
- package/dist/tui/prompt-queue.d.ts +3 -0
- package/dist/tui/prompt-queue.js +19 -1
- package/dist/tui/provider-label.d.ts +2 -0
- package/dist/tui/provider-label.js +9 -0
- package/dist/tui/provider-menu.js +25 -24
- package/dist/tui/queue-screen.js +2 -1
- package/dist/tui/remote-command.d.ts +7 -0
- package/dist/tui/remote-command.js +79 -0
- package/dist/tui/repl.d.ts +8 -2
- package/dist/tui/repl.js +355 -311
- package/dist/tui/review-store.d.ts +1 -0
- package/dist/tui/review-store.js +28 -7
- package/dist/tui/review-team.js +4 -3
- package/dist/tui/runtime-error.d.ts +2 -0
- package/dist/tui/runtime-error.js +26 -0
- package/dist/tui/selector-mouse.d.ts +1 -0
- package/dist/tui/selector-mouse.js +1 -1
- package/dist/tui/selector.d.ts +7 -0
- package/dist/tui/selector.js +19 -2
- package/dist/tui/session-controls.d.ts +26 -0
- package/dist/tui/session-controls.js +65 -0
- package/dist/tui/session-naming-notice.js +5 -3
- package/dist/tui/session-replay.d.ts +1 -0
- package/dist/tui/session-replay.js +101 -32
- package/dist/tui/session-transcript-layout.d.ts +9 -0
- package/dist/tui/session-transcript-layout.js +41 -0
- package/dist/tui/startup.js +63 -33
- package/dist/tui/status-line.d.ts +1 -1
- package/dist/tui/status-line.js +20 -12
- package/dist/tui/talos-art-data.d.ts +1 -0
- package/dist/tui/talos-art-data.js +2 -0
- package/dist/tui/talos-art.d.ts +1 -0
- package/dist/tui/talos-art.js +9 -0
- package/dist/tui/task-labels.d.ts +1 -0
- package/dist/tui/task-labels.js +8 -0
- package/dist/tui/terminal-text.d.ts +2 -1
- package/dist/tui/terminal-text.js +28 -2
- package/dist/tui/theme.js +2 -2
- package/dist/tui/tool-presentation.d.ts +3 -0
- package/dist/tui/tool-presentation.js +98 -0
- package/dist/tui/tool-style.js +4 -0
- package/dist/tui/transcript-store.d.ts +25 -0
- package/dist/tui/transcript-store.js +151 -0
- package/dist/tui/usage-footer.d.ts +1 -1
- package/dist/tui/usage-footer.js +3 -1
- package/dist/tui/vascend-id.d.ts +49 -0
- package/dist/tui/vascend-id.js +321 -0
- package/dist/tui/web-login-screen.js +4 -3
- package/dist/tui/web-login.d.ts +7 -15
- package/dist/tui/web-login.js +38 -78
- package/dist/tui/worker-transcripts.d.ts +31 -0
- package/dist/tui/worker-transcripts.js +91 -0
- package/dist/vascend/candidate-collector.d.ts +6 -5
- package/dist/vascend/candidate-collector.js +44 -43
- package/dist/vascend/context-lint.js +32 -2
- package/dist/vascend/context-market-selection.js +12 -5
- package/dist/vascend/context-pack-render.js +10 -4
- package/dist/vascend/context-packet-mirrors.d.ts +8 -0
- package/dist/vascend/context-packet-mirrors.js +24 -0
- package/dist/vascend/context-plan-events.d.ts +4 -0
- package/dist/vascend/context-plan-events.js +44 -0
- package/dist/vascend/context-snapshot.js +4 -2
- package/dist/vascend/context-store-reader.d.ts +20 -0
- package/dist/vascend/context-store-reader.js +46 -0
- package/dist/vascend/file-lock.d.ts +1 -0
- package/dist/vascend/file-lock.js +26 -0
- package/dist/vascend/granular-kingdom.d.ts +1 -0
- package/dist/vascend/granular-kingdom.js +22 -15
- package/dist/vascend/kingdom-bus.d.ts +1 -0
- package/dist/vascend/kingdom-bus.js +3 -0
- package/dist/vascend/kingdom-channel.d.ts +2 -0
- package/dist/vascend/kingdom-channel.js +3 -0
- package/dist/vascend/kingdom-live.d.ts +32 -4
- package/dist/vascend/kingdom-live.js +149 -13
- package/dist/vascend/kingdom-run.d.ts +5 -1
- package/dist/vascend/kingdom-run.js +6 -2
- package/dist/vascend/kingdom.d.ts +3 -0
- package/dist/vascend/kingdom.js +20 -13
- package/dist/vascend/memory-packet.d.ts +2 -1
- package/dist/vascend/memory-packet.js +31 -44
- package/dist/vascend/plan-store.d.ts +3 -1
- package/dist/vascend/plan-store.js +6 -4
- package/dist/vascend/room-memory.d.ts +10 -1
- package/dist/vascend/room-memory.js +42 -2
- package/dist/vascend/runtime.d.ts +3 -11
- package/dist/vascend/runtime.js +11 -123
- package/dist/vascend/session-search.js +2 -0
- package/dist/vascend/team-session.js +3 -2
- package/dist/vascend/vision.d.ts +3 -1
- package/dist/vascend/vision.js +6 -4
- package/dist/vascend/worker-session.d.ts +20 -0
- package/dist/vascend/worker-session.js +83 -0
- package/package.json +1 -1
package/dist/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,20 +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";
|
|
12
13
|
import { readSessionUsage } from "../core/session-usage.js";
|
|
13
14
|
import { storageHealth } from "../core/storage-health.js";
|
|
14
15
|
import { storageWarning } from "./storage-warning.js";
|
|
15
16
|
import { ContextMeter, contextDetails } from "./context-meter.js";
|
|
16
|
-
import { buildUsageFooter, subscriptionDetails,
|
|
17
|
+
import { buildUsageFooter, subscriptionDetails, sessionUsageExplanation } from "./usage-footer.js";
|
|
17
18
|
import { copyText, lastAssistantText } from "./clipboard-text.js";
|
|
19
|
+
import { sendWorker, stopWorker, deleteWorker, workerFooter, bindWorkerInbox, wakeWorker } from "./session-controls.js";
|
|
18
20
|
import { withCommandFeedback, notifyStatus } from "./command-feedback.js";
|
|
19
21
|
import { loadModelsDev, cachedModelCatalog } from "../providers/models-dev.js";
|
|
20
22
|
import { modelEfforts } from "../providers/model-effort.js";
|
|
21
23
|
import { modelDetail } from "./model-details.js";
|
|
24
|
+
import { providerLabel } from "./provider-label.js";
|
|
22
25
|
import { applyModelChoice, pickEffort } from "./model-selection.js";
|
|
26
|
+
import { pickFusion } from "./fusion-menu.js";
|
|
23
27
|
import { PROVIDER_CATALOG } from "../providers/catalog.js";
|
|
24
28
|
import { fetchOpenRouterModels, openRouterDetail } from "../providers/openrouter.js";
|
|
25
29
|
import { providerRows, actionsFor, rowStatusLabel, providerKeyHint, saveProviderKey, saveProviderBaseUrl, removeProvider, } from "./provider-menu.js";
|
|
@@ -32,6 +36,7 @@ import { frostPaint, makeTheme, renderAssistantSegment, renderAssistantTurnEnd,
|
|
|
32
36
|
import { renderVascendTerminalLogo } from "./logo.js";
|
|
33
37
|
import { enterAccount } from "./account-entry.js";
|
|
34
38
|
import { tr } from "./i18n.js";
|
|
39
|
+
import { runtimeError, isUserInterruption } from "./runtime-error.js";
|
|
35
40
|
import { LANGUAGES, normalizePreference } from "./locales.js";
|
|
36
41
|
import { languagePreference, saveLanguagePreference } from "./language-preference.js";
|
|
37
42
|
import { leggiOnboarding } from "./onboarding.js";
|
|
@@ -48,7 +53,7 @@ import { restoreQueuedInput } from "./restore-queued-input.js";
|
|
|
48
53
|
import { ReviewStore } from "./review-store.js";
|
|
49
54
|
import { ReviewScreen, reviewText } from "./review-screen.js";
|
|
50
55
|
import { fitLine, oneLine, terminalWidth, wrapText } from "./terminal-text.js";
|
|
51
|
-
import { livePreview, liveResultSummary } from "./live-output.js";
|
|
56
|
+
import { livePreview, liveResultSummary, backgroundResultSummary } from "./live-output.js";
|
|
52
57
|
import { commandSource, commandPreview, commandHeader, commandOutput, commandResultState } from "./command-transcript.js";
|
|
53
58
|
import { mcpName, mcpHeader, mcpPreview, mcpOutput } from "./mcp-transcript.js";
|
|
54
59
|
import { loadedSkill, skillHeader, skillPreview } from "./skill-transcript.js";
|
|
@@ -61,6 +66,7 @@ import { readClipboardImage } from "./clipboard-image.js";
|
|
|
61
66
|
import { queryBackgroundMode } from "./term-bg.js";
|
|
62
67
|
import { runKingdomWarm, warmConcurrency, decomposeKingdom, looksBigGoal, collectConflicts, formatConflictList } from "../vascend/kingdom.js";
|
|
63
68
|
import { worktreeMode } from "../vascend/worktree.js";
|
|
69
|
+
import { kingdomLive } from "../vascend/kingdom-live.js";
|
|
64
70
|
import { runKingdomForHost } from "../vascend/kingdom-run.js";
|
|
65
71
|
import { kingdomBus } from "../vascend/kingdom-bus.js";
|
|
66
72
|
import { resolveKingdomBus } from "../vascend/kingdom-channel.js";
|
|
@@ -85,11 +91,16 @@ import { saveGlobalSettings } from "../core/settings.js";
|
|
|
85
91
|
import { autoApproves, nextPermMode, permModeLabel } from "./perm-mode.js";
|
|
86
92
|
import { backgroundManager } from "../core/background.js";
|
|
87
93
|
import { BUILTIN_COMMANDS, visibleBuiltinNames, commandsByCategory, suggestCommands } from "./commands.js";
|
|
88
|
-
import { TASK_STATUS_LABELS } from "./task-labels.js";
|
|
89
|
-
import {
|
|
90
|
-
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";
|
|
91
100
|
import { listVibeModes, parseVibeMode } from "../core/vibe-modes.js";
|
|
92
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";
|
|
93
104
|
// (BUILTIN_COMMANDS spostato in ./commands.ts con categorie: vedi import in testa.)
|
|
94
105
|
// Sorgente dinamica dei nomi comando (builtin + estensioni + template + skill).
|
|
95
106
|
function allCommandNames(harness, templates) {
|
|
@@ -327,6 +338,8 @@ export async function startRepl(deps) {
|
|
|
327
338
|
const stats = new SessionUsageState(costTracker);
|
|
328
339
|
stats.restore(harness.config.sessionDir, harness.session.id, harness.session.tree().find(n => n.message.role === "assistant")?.ts);
|
|
329
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);
|
|
330
343
|
review.reset(sessionConversation(harness.session));
|
|
331
344
|
review.sessions.restoreMain(readSessionUsage(harness.config.sessionDir, harness.session.id));
|
|
332
345
|
const reviewContext = () => {
|
|
@@ -353,7 +366,7 @@ export async function startRepl(deps) {
|
|
|
353
366
|
? last.cacheReadTokens / Math.max(1, last.inputTokens + last.cacheReadTokens)
|
|
354
367
|
: undefined;
|
|
355
368
|
return {
|
|
356
|
-
provider: current ? (current.provider
|
|
369
|
+
provider: current ? providerLabel(current.provider) : tr("non configurato", "not configured"),
|
|
357
370
|
sessionId: harness.session.id,
|
|
358
371
|
sessionNodes: stima.nodi,
|
|
359
372
|
headId: harness.session.head,
|
|
@@ -416,7 +429,7 @@ export async function startRepl(deps) {
|
|
|
416
429
|
const startupMeta = () => {
|
|
417
430
|
const resolved = harness.providers.resolve(harness.config.model);
|
|
418
431
|
return {
|
|
419
|
-
provider: resolved ? (resolved.provider
|
|
432
|
+
provider: resolved ? providerLabel(resolved.provider) : tr("non configurato", "not configured"),
|
|
420
433
|
sessionId: harness.session.id,
|
|
421
434
|
sessionNodes: harness.session.tree().length,
|
|
422
435
|
headId: harness.session.head,
|
|
@@ -450,6 +463,7 @@ export async function startRepl(deps) {
|
|
|
450
463
|
// questa sessione soltanto: dopo /new, `txt #1` sarebbe un altro testo.
|
|
451
464
|
const pasteRegistry = new PasteRegistry();
|
|
452
465
|
const resetSessionUi = (ctl) => {
|
|
466
|
+
kingdomLive.clearAll();
|
|
453
467
|
ctl.clearConversation?.();
|
|
454
468
|
review.reset(sessionConversation(harness.session));
|
|
455
469
|
review.sessions.restoreMain(readSessionUsage(harness.config.sessionDir, harness.session.id));
|
|
@@ -507,6 +521,7 @@ export async function startRepl(deps) {
|
|
|
507
521
|
try {
|
|
508
522
|
if (!line.startsWith("/")) {
|
|
509
523
|
ctl.write(renderUserMessage(line, theme, terminalColumns(), fromQueue ? tr("Dalla coda · consegnato", "From queue · delivered") : undefined));
|
|
524
|
+
messaggioUtente(line);
|
|
510
525
|
const imageParts = queued ? queued.parts.filter(part => part.type === "image") : imageRegistry.imagePartsFor(line);
|
|
511
526
|
// In chat resta il riferimento compatto (`txt #1`); al modello va il
|
|
512
527
|
// testo per intero. L'espansione avviene qui, dopo l'echo e prima del
|
|
@@ -524,6 +539,10 @@ export async function startRepl(deps) {
|
|
|
524
539
|
ctl.write(reviewText(review, reviewContext(), theme, terminalColumns(), section));
|
|
525
540
|
return;
|
|
526
541
|
}
|
|
542
|
+
if (/^\/(?:agents|agenti)\s*$/i.test(line)) {
|
|
543
|
+
ctl.showAgents?.();
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
527
546
|
// /img <path>: allega esplicitamente un'immagine. La registra come "img #N"
|
|
528
547
|
// e ne mostra l'anteprima inline; non avvia un turno (e' solo l'allegato).
|
|
529
548
|
const imgCmd = /^\/img(?:\s+(.+))?$/i.exec(line);
|
|
@@ -634,7 +653,7 @@ export async function startRepl(deps) {
|
|
|
634
653
|
? { done: all.filter((t) => t.status === "completed").length, total: all.length, castles: castles.length }
|
|
635
654
|
: null;
|
|
636
655
|
return {
|
|
637
|
-
model: harness.config.model,
|
|
656
|
+
model: harness.config.fusion ? `Fusion: Jev + ${harness.config.model}` : harness.config.model,
|
|
638
657
|
provider: m.provider,
|
|
639
658
|
cwd: harness.config.cwd,
|
|
640
659
|
effort: harness.config.effort || "default",
|
|
@@ -656,15 +675,25 @@ export async function startRepl(deps) {
|
|
|
656
675
|
};
|
|
657
676
|
const input = new BottomInput({
|
|
658
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),
|
|
659
686
|
createReviewScreen: (section) => new ReviewScreen(review, () => theme, reviewContext, section),
|
|
660
687
|
nativeScroll: env["TALOS_NATIVE_SCROLL"] !== "0",
|
|
661
688
|
headerLines: () => buildTerminalHeader(statusData(), theme),
|
|
662
689
|
startupLines: () => buildTerminalStartup(statusData(), theme),
|
|
663
690
|
promptText: () => theme.accent(harness.config.ui.prompt),
|
|
664
|
-
placeholder: () => theme.muted(tr("
|
|
691
|
+
placeholder: () => theme.muted(tr("Descrivi un'attività o digita / per i comandi", "Describe a task or type / for commands")),
|
|
665
692
|
composerPadding: () => !harness.session.head,
|
|
666
693
|
composerStyle: (s) => theme.composerText?.(s) ?? theme.chatText(s),
|
|
667
694
|
attachmentStyle: (s) => theme.attachmentRef?.(s) ?? s,
|
|
695
|
+
currentModel: () => harness.config.model,
|
|
696
|
+
limitWarningStyle: s => theme.yellow(s),
|
|
668
697
|
belowInputLines: () => {
|
|
669
698
|
if (!harness.session.head)
|
|
670
699
|
return [buildStartupRuntime(statusData(), theme)];
|
|
@@ -699,14 +728,14 @@ export async function startRepl(deps) {
|
|
|
699
728
|
// Ctrl+B: manda in background il comando shell in corso; se non ce n'e'
|
|
700
729
|
// uno, mostra la lista dei task in background.
|
|
701
730
|
onBackground: () => {
|
|
702
|
-
const task = backgroundManager.requestBackground();
|
|
731
|
+
const task = backgroundManager.requestBackground(harness.session.id);
|
|
703
732
|
if (task)
|
|
704
733
|
input.write(theme.dim(`\n ⤓ in background [${task.id}]: ${truncate(task.command, 60)}\n`));
|
|
705
734
|
else
|
|
706
735
|
void showBackgroundPanel(input, theme, terminalColumns);
|
|
707
736
|
},
|
|
708
737
|
// Suffisso spinner: suggerisce Ctrl+B solo quando c'e' un comando in corso.
|
|
709
|
-
runningHint: () => backgroundManager.hasForeground() ? "Ctrl+B sfondo" : null,
|
|
738
|
+
runningHint: () => backgroundManager.hasForeground(harness.session.id) ? "Ctrl+B sfondo" : null,
|
|
710
739
|
prepareQueuedInput: text => [{ type: "text", text: pasteRegistry.expand(text) }, ...imageRegistry.imagePartsFor(text)],
|
|
711
740
|
queuePersistence: () => new PromptQueueFile(harness.config.sessionDir, harness.session.id),
|
|
712
741
|
restoreQueuedInput: entry => restoreQueuedInput(entry, imageRegistry),
|
|
@@ -716,6 +745,18 @@ export async function startRepl(deps) {
|
|
|
716
745
|
if (result === "quit")
|
|
717
746
|
input.requestQuit();
|
|
718
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
|
+
},
|
|
719
760
|
// Incollo: un path/data-URI immagine diventa "img #N", un testo lungo
|
|
720
761
|
// diventa "txt #N", il resto si inserisce come testo normale.
|
|
721
762
|
onPaste: onPasteInput,
|
|
@@ -728,23 +769,28 @@ export async function startRepl(deps) {
|
|
|
728
769
|
return span ? { start: span.start, end: span.end } : null;
|
|
729
770
|
},
|
|
730
771
|
onReady: () => {
|
|
731
|
-
input.write(buildTerminalWelcome(terminalColumns(), theme));
|
|
732
|
-
if (accessoNota)
|
|
733
|
-
input.write(wrapText(accessoNota, Math.max(2, terminalColumns() - 3), true).map((line) => theme.muted(" " + line.trim())).join("\n") + "\n");
|
|
734
|
-
input.write("\n");
|
|
772
|
+
input.write(buildTerminalWelcome(terminalColumns(), theme, accessoNota));
|
|
735
773
|
if (harness.session.head)
|
|
736
774
|
replaySession(harness.session, input, theme, terminalColumns());
|
|
737
775
|
if (deps.resumePicker)
|
|
738
776
|
void processLine("/resume", input, new AbortController().signal);
|
|
739
777
|
},
|
|
740
778
|
onQuit: () => {
|
|
779
|
+
kingdomLive.clearAll();
|
|
780
|
+
review.sessions.conversations.reset();
|
|
741
781
|
void harness.host.emit("session_shutdown", {});
|
|
742
|
-
stdout.write("\n");
|
|
743
|
-
|
|
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();
|
|
744
788
|
},
|
|
745
789
|
});
|
|
746
790
|
// Le notify delle estensioni vanno nella regione viva (no corruzione).
|
|
747
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() });
|
|
748
794
|
// UI reale per le estensioni: confirm/select/input passano dalla TUI (cosi'
|
|
749
795
|
// un'estensione puo' fare domande in chat come il tool builtin ask_user).
|
|
750
796
|
harness.host.bind({
|
|
@@ -785,7 +831,11 @@ export async function startRepl(deps) {
|
|
|
785
831
|
backgroundManager.onFinish((t) => {
|
|
786
832
|
if (!t.detached)
|
|
787
833
|
return; // i comandi in primo piano mostrano gia' il risultato inline
|
|
788
|
-
|
|
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("✗");
|
|
789
839
|
input.write(`\n ${mark} background ${t.id} ` +
|
|
790
840
|
theme.dim(`[${t.status}${t.exitCode != null ? ` · uscita ${t.exitCode}` : ""}] ${truncate(t.command, 50)} · /bg ${t.id} per l'output\n`));
|
|
791
841
|
});
|
|
@@ -846,7 +896,7 @@ export async function startRepl(deps) {
|
|
|
846
896
|
processLine: (raw, signal) => processLine(raw, ctl, signal),
|
|
847
897
|
prompt,
|
|
848
898
|
requestClose: () => rl.close(),
|
|
849
|
-
onError: (error) => notifyStatus(ctl, theme.red(`\n
|
|
899
|
+
onError: (error) => notifyStatus(ctl, theme.red(`\n ${tr("Errore", "Error")}: ${runtimeError(error.message)}\n`)),
|
|
850
900
|
installAbort: (abort) => {
|
|
851
901
|
const onSigint = () => abort();
|
|
852
902
|
process.once("SIGINT", onSigint);
|
|
@@ -889,15 +939,26 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
889
939
|
// Gate permessi: selezione Sì/No dalla UI con le frecce (stile Claude Code),
|
|
890
940
|
// niente piu' digitazione di y/n. Le modalita' "accetta modifiche"/"auto"
|
|
891
941
|
// (Shift+Tab) saltano il prompt per le azioni che approvano.
|
|
892
|
-
const confirm = async (action, detail) => {
|
|
942
|
+
const confirm = async (action, detail, requestSignal = signal) => {
|
|
893
943
|
if (autoApproves(getPermMode(), action))
|
|
894
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) => {
|
|
895
949
|
const choice = await selectOption({
|
|
896
|
-
|
|
897
|
-
|
|
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,
|
|
898
959
|
options: [
|
|
899
|
-
{ key: "yes", label: "Sì, esegui", detail: "consenti questa azione", value: true },
|
|
900
|
-
{ 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 },
|
|
901
962
|
],
|
|
902
963
|
currentKey: "yes",
|
|
903
964
|
ctl,
|
|
@@ -907,11 +968,12 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
907
968
|
return choice?.value === true;
|
|
908
969
|
};
|
|
909
970
|
// Domanda all'utente in chat (tool ask_user): opzioni -> picker; libera -> input.
|
|
910
|
-
const ask = async (question, options) => {
|
|
971
|
+
const ask = async (question, options, requestSignal = signal) => {
|
|
911
972
|
if (ctl.queueQuestion)
|
|
912
|
-
return ctl.queueQuestion(question, options,
|
|
973
|
+
return ctl.queueQuestion(question, options, requestSignal);
|
|
913
974
|
if (options && options.length) {
|
|
914
975
|
const choice = await selectOption({
|
|
976
|
+
signal: requestSignal,
|
|
915
977
|
title: question,
|
|
916
978
|
question: true,
|
|
917
979
|
options: options.map((o) => ({ label: o, detail: "", value: o })),
|
|
@@ -922,15 +984,20 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
922
984
|
return choice?.value ?? null;
|
|
923
985
|
}
|
|
924
986
|
ctl.write(renderQuestion(question, theme, terminalColumns()));
|
|
925
|
-
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)"));
|
|
926
988
|
};
|
|
927
989
|
// Kingdoms Mode invocabile dall'agente (tool kingdoms_warm): esegue i castelli
|
|
928
990
|
// in parallelo (vassalli) e ritorna i report. Il consenso e' gia' stato dato
|
|
929
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
|
+
};
|
|
930
996
|
const spawnKingdom = async (castles, kopts) => {
|
|
931
997
|
if (kopts?.detach)
|
|
932
998
|
return runKingdomForHost({
|
|
933
|
-
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,
|
|
934
1001
|
...(kopts.baseBranch ? { baseBranch: kopts.baseBranch } : {}),
|
|
935
1002
|
});
|
|
936
1003
|
// Obiettivo complessivo del regno: esplicito > goal grezzo del turno.
|
|
@@ -962,6 +1029,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
962
1029
|
concurrency: conc,
|
|
963
1030
|
ask,
|
|
964
1031
|
onEvent: live.onEvent,
|
|
1032
|
+
retainSessions: "queued",
|
|
965
1033
|
worktree: wt,
|
|
966
1034
|
...(kopts?.baseBranch ? { baseBranch: kopts.baseBranch } : {}),
|
|
967
1035
|
onNotice: (text) => notifyStatus(ctl, theme.accent(" ⎇ ") + theme.dim(text) + "\n"),
|
|
@@ -1028,6 +1096,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1028
1096
|
ctl.setStatus(tr("avvio", "starting"));
|
|
1029
1097
|
let turnFailed = false;
|
|
1030
1098
|
const wrappedSink = (event) => {
|
|
1099
|
+
osservaEvento(event);
|
|
1031
1100
|
if (event.type === "error" || (event.type === "turn_end" && event.stopReason === "error"))
|
|
1032
1101
|
turnFailed = true;
|
|
1033
1102
|
ctl.review?.record(event);
|
|
@@ -1078,7 +1147,7 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1078
1147
|
const turnInput = imageParts && imageParts.length
|
|
1079
1148
|
? [{ type: "text", text: input }, ...imageParts]
|
|
1080
1149
|
: input;
|
|
1081
|
-
await runTurn({ ...deps.harness.deps(confirm), env: { ...env, TALOS_AUTO_TITLE: "1" }, 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);
|
|
1082
1151
|
if (turnFailed || signal.aborted)
|
|
1083
1152
|
return;
|
|
1084
1153
|
// REVISORE di team: per i task complessi (molte stanze) con stanze fatte e
|
|
@@ -1143,8 +1212,14 @@ async function drive(deps, input, ctl, theme, terminalColumns, stats, signal, ge
|
|
|
1143
1212
|
}
|
|
1144
1213
|
}
|
|
1145
1214
|
catch (err) {
|
|
1146
|
-
|
|
1147
|
-
|
|
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
|
+
}
|
|
1148
1223
|
}
|
|
1149
1224
|
finally {
|
|
1150
1225
|
ctl.review?.finish(signal.aborted);
|
|
@@ -1230,7 +1305,7 @@ async function runTeamReviewer(deps, vascend, sdir, sid, ctl, theme, terminalCol
|
|
|
1230
1305
|
}
|
|
1231
1306
|
vascend.loadPlan(sdir, sid);
|
|
1232
1307
|
}
|
|
1233
|
-
export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
1308
|
+
export function makeSink(theme, terminalColumns, cwd, env, ctl, plan, replay = false) {
|
|
1234
1309
|
let textBuffer = "";
|
|
1235
1310
|
let assistantSegments = 0;
|
|
1236
1311
|
let assistantStarted = false;
|
|
@@ -1238,6 +1313,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1238
1313
|
const writeNotice = (text, failed = false) => {
|
|
1239
1314
|
notifyStatus(ctl, text, failed);
|
|
1240
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
|
+
};
|
|
1241
1321
|
// Correlazione call->risultato e contenuto PRE-scrittura, per gli avvisi del
|
|
1242
1322
|
// regno e per il diff dei tool di scrittura.
|
|
1243
1323
|
const calls = new Map();
|
|
@@ -1247,6 +1327,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1247
1327
|
const mcpCards = new Map();
|
|
1248
1328
|
const toolCards = new Map();
|
|
1249
1329
|
const skillCards = new Map();
|
|
1330
|
+
const backgroundPolls = new Map();
|
|
1250
1331
|
let newInputTok = 0; // input non in cache del run (cumulativo)
|
|
1251
1332
|
let cacheTok = 0; // cache letta/scritta nel run (cumulativo)
|
|
1252
1333
|
let outTok = 0; // token generati nel run (cumulativi)
|
|
@@ -1302,21 +1383,21 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1302
1383
|
}
|
|
1303
1384
|
// Riposa il bullet allo stato statico (accent) come il resto dello scrollback.
|
|
1304
1385
|
if (settle && liveDot && ctl.setLogLine) {
|
|
1305
|
-
ctl.setLogLine(liveDot.index, fitLine(
|
|
1386
|
+
ctl.setLogLine(liveDot.index, fitLine(" " + theme.muted("◇ ") + liveDot.tail, terminalColumns() - 1));
|
|
1306
1387
|
}
|
|
1307
1388
|
liveDot = null;
|
|
1308
1389
|
};
|
|
1309
1390
|
const startDot = (index, name, tail) => {
|
|
1310
1391
|
stopDot(false); // chiudi l'eventuale precedente senza ridisegnare (verra' sovrascritto)
|
|
1311
|
-
if (!ctl.setLogLine)
|
|
1312
|
-
return; //
|
|
1392
|
+
if (!ctl.setLogLine || replay)
|
|
1393
|
+
return; // Replay has no timers or terminal side effects.
|
|
1313
1394
|
liveDot = { index, name, tail };
|
|
1314
1395
|
liveFrame = 0;
|
|
1315
1396
|
dotTimer = setInterval(() => {
|
|
1316
1397
|
if (!liveDot || !ctl.setLogLine)
|
|
1317
1398
|
return;
|
|
1318
1399
|
liveFrame += 1;
|
|
1319
|
-
ctl.setLogLine(liveDot.index, fitLine(
|
|
1400
|
+
ctl.setLogLine(liveDot.index, fitLine(" " + pulseDot(liveDot.name, liveFrame) + " " + liveDot.tail, terminalColumns() - 1));
|
|
1320
1401
|
}, 140);
|
|
1321
1402
|
};
|
|
1322
1403
|
const beginCommand = () => {
|
|
@@ -1325,7 +1406,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1325
1406
|
stopDot();
|
|
1326
1407
|
ctl.write("\n");
|
|
1327
1408
|
};
|
|
1328
|
-
|
|
1409
|
+
const sink = (event) => {
|
|
1329
1410
|
switch (event.type) {
|
|
1330
1411
|
case "context_window":
|
|
1331
1412
|
ctl.refresh?.();
|
|
@@ -1356,7 +1437,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1356
1437
|
// come evento status separato.
|
|
1357
1438
|
lastTruncated = event.stopReason === "max_tokens";
|
|
1358
1439
|
if (event.stopReason === "max_tokens") {
|
|
1359
|
-
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`));
|
|
1360
1441
|
}
|
|
1361
1442
|
break;
|
|
1362
1443
|
case "status":
|
|
@@ -1365,6 +1446,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1365
1446
|
break;
|
|
1366
1447
|
// Attivita' del modello senza testo (es. streaming di un input grande).
|
|
1367
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
|
+
}
|
|
1368
1458
|
break;
|
|
1369
1459
|
case "text":
|
|
1370
1460
|
ctl.setStatus(tr("risposta in corso", "responding"));
|
|
@@ -1379,6 +1469,10 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1379
1469
|
parent.child = true;
|
|
1380
1470
|
}
|
|
1381
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
|
+
}
|
|
1382
1476
|
// Compact protocol wrappers emit real child calls. Show those commands
|
|
1383
1477
|
// once, without a duplicate execute(args) envelope in the transcript.
|
|
1384
1478
|
if (event.call.name === "execute" || event.call.name === "tool_invoke" || event.call.name === "run_program") {
|
|
@@ -1442,11 +1536,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1442
1536
|
const st = String(event.call.input["status"] ?? "completed");
|
|
1443
1537
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1444
1538
|
if (st === "running")
|
|
1445
|
-
ctl.setRoomStatus?.(
|
|
1539
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "running"), "running");
|
|
1446
1540
|
else if (st === "failed")
|
|
1447
|
-
ctl.setRoomStatus?.(
|
|
1541
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "failed"), "failed");
|
|
1448
1542
|
else
|
|
1449
|
-
ctl.setRoomStatus?.(
|
|
1543
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "completed"), "done");
|
|
1450
1544
|
break;
|
|
1451
1545
|
}
|
|
1452
1546
|
// Each operation owns a small separate block. Full data stays in review.
|
|
@@ -1459,7 +1553,7 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1459
1553
|
calls.set(event.call.id, event.call);
|
|
1460
1554
|
// Cattura il contenuto PRIMA della scrittura, per il diff (il file non
|
|
1461
1555
|
// e' ancora stato toccato a questo punto del loop).
|
|
1462
|
-
if (event.call.name === "write_file") {
|
|
1556
|
+
if (!replay && event.call.name === "write_file") {
|
|
1463
1557
|
const p = event.call.input["path"];
|
|
1464
1558
|
if (typeof p === "string" && p) {
|
|
1465
1559
|
const abs = isAbsolute(p) ? p : resolve(cwd, p);
|
|
@@ -1475,18 +1569,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1475
1569
|
const argRendered = typeof pathArg === "string" && pathArg && argText === pathArg
|
|
1476
1570
|
? fileLink(isAbsolute(pathArg) ? pathArg : resolve(cwd, pathArg), theme.dim(pathArg))
|
|
1477
1571
|
: theme.dim(argText);
|
|
1478
|
-
//
|
|
1479
|
-
|
|
1480
|
-
// shell (e' un comando bash).
|
|
1481
|
-
const nameRendered = gitSum
|
|
1482
|
-
? paintCategoryName("shell", "Git")
|
|
1483
|
-
: 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);
|
|
1484
1574
|
// Coda statica della riga (tutto cio' che segue il bullet): la riusiamo
|
|
1485
1575
|
// sia per la scrittura iniziale sia per l'animazione del bullet.
|
|
1486
|
-
const tail = nameRendered +
|
|
1487
|
-
const header = fitLine(
|
|
1576
|
+
const tail = nameRendered + " " + argRendered;
|
|
1577
|
+
const header = fitLine(" " + theme.muted("◇ ") + tail, terminalColumns() - 1);
|
|
1578
|
+
const index = ctl.logLength();
|
|
1488
1579
|
ctl.write(header + "\n");
|
|
1489
|
-
toolCards.set(event.call.id, { end: ctl.logLength(), header });
|
|
1580
|
+
toolCards.set(event.call.id, { index, end: ctl.logLength(), header });
|
|
1490
1581
|
if (event.call.name === "ask_user")
|
|
1491
1582
|
startDot(ctl.logLength() - 1, event.call.name, tail);
|
|
1492
1583
|
break;
|
|
@@ -1507,6 +1598,20 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1507
1598
|
parent.isError = event.result.isError;
|
|
1508
1599
|
}
|
|
1509
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
|
+
}
|
|
1510
1615
|
const skillCard = skillCards.get(event.result.callId);
|
|
1511
1616
|
if (skillCard) {
|
|
1512
1617
|
skillCards.delete(event.result.callId);
|
|
@@ -1558,7 +1663,8 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1558
1663
|
const card = commandCards.get(event.result.callId);
|
|
1559
1664
|
if (card && call) {
|
|
1560
1665
|
const state = commandResultState(event.result.output, event.result.isError);
|
|
1561
|
-
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]);
|
|
1562
1668
|
if (retained)
|
|
1563
1669
|
ctl.setLogLine?.(card.index, commandHeader(card.firstRow, state, theme, card.columns));
|
|
1564
1670
|
// Parallel calls can complete in a different order. Repeat the
|
|
@@ -1588,11 +1694,11 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1588
1694
|
const st = String(call.input["status"] ?? "completed");
|
|
1589
1695
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1590
1696
|
if (st === "running")
|
|
1591
|
-
ctl.setRoomStatus?.(
|
|
1697
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "running"), "running");
|
|
1592
1698
|
else if (st === "failed")
|
|
1593
|
-
ctl.setRoomStatus?.(
|
|
1699
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "failed"), "failed");
|
|
1594
1700
|
else
|
|
1595
|
-
ctl.setRoomStatus?.(
|
|
1701
|
+
ctl.setRoomStatus?.(taskActivityLabel(`${where}${task}`, "completed"), "done");
|
|
1596
1702
|
break;
|
|
1597
1703
|
}
|
|
1598
1704
|
// Detailed plans are available in the task panel.
|
|
@@ -1603,6 +1709,23 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1603
1709
|
notifyStatus(ctl, notice);
|
|
1604
1710
|
break;
|
|
1605
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
|
+
}
|
|
1606
1729
|
// Associate out-of-order results with their own operation rather than
|
|
1607
1730
|
// appending several anonymous result lines below the newest call.
|
|
1608
1731
|
if (toolCard && toolCard.end !== ctl.logLength())
|
|
@@ -1612,16 +1735,9 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1612
1735
|
// muted (piu' leggibile del dim slavato, ma chiaramente secondario).
|
|
1613
1736
|
{
|
|
1614
1737
|
const summary = liveResultSummary(event.result.output, event.result.isError ? null : resultSummary(call, event.result.output), terminalColumns());
|
|
1615
|
-
ctl.write(theme.dark("
|
|
1738
|
+
ctl.write(theme.dark(" └ ") + (event.result.isError ? theme.red("× ") : "") +
|
|
1616
1739
|
(event.result.isError ? theme.red(summary) : theme.dim(summary)) + "\n");
|
|
1617
1740
|
}
|
|
1618
|
-
// Keep the change preview alongside its operation.
|
|
1619
|
-
if (call && !event.result.isError) {
|
|
1620
|
-
const diff = diffForCall(call, before, event.result.output, theme, terminalColumns());
|
|
1621
|
-
if (diff) {
|
|
1622
|
-
ctl.write(previewDiff(diff, theme, terminalColumns() - 1));
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
1741
|
break;
|
|
1626
1742
|
}
|
|
1627
1743
|
case "session_naming":
|
|
@@ -1670,7 +1786,12 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1670
1786
|
ctl.setLogLine?.(card.index, commandHeader(card.firstRow, "stopped", theme, card.columns));
|
|
1671
1787
|
}
|
|
1672
1788
|
commandCards.clear();
|
|
1673
|
-
|
|
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
|
+
}
|
|
1674
1795
|
break;
|
|
1675
1796
|
case "done":
|
|
1676
1797
|
for (const card of mcpCards.values()) {
|
|
@@ -1689,11 +1810,15 @@ export function makeSink(theme, terminalColumns, cwd, env, ctl, plan) {
|
|
|
1689
1810
|
// Auto-continuazione esaurita: il run finisce ancora troncato. Avvisa di
|
|
1690
1811
|
// spezzare il compito (o alzare TALOS_MAX_AUTOCONTINUE).
|
|
1691
1812
|
if (lastTruncated) {
|
|
1692
|
-
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`));
|
|
1693
1814
|
}
|
|
1694
1815
|
break;
|
|
1695
1816
|
}
|
|
1696
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
|
+
} });
|
|
1697
1822
|
}
|
|
1698
1823
|
// Attribuzione token alle stanze + riepilogo per castello (metodo Danilov).
|
|
1699
1824
|
// All'avvio della stanza (running) salva lo snapshot di outTok; a stanza
|
|
@@ -1882,10 +2007,10 @@ function renderVascendToolNotice(call, output, theme, columns) {
|
|
|
1882
2007
|
const st = String(call.input["status"] ?? "completed");
|
|
1883
2008
|
const where = slug === "master" ? "" : `${slug}/`;
|
|
1884
2009
|
if (st === "running")
|
|
1885
|
-
return renderVascendNotice("start",
|
|
2010
|
+
return renderVascendNotice("start", taskActivityLabel(`${where}${task}`, "running"), theme);
|
|
1886
2011
|
if (st === "failed")
|
|
1887
|
-
return renderVascendNotice("fail",
|
|
1888
|
-
return renderVascendNotice("done",
|
|
2012
|
+
return renderVascendNotice("fail", taskActivityLabel(`${where}${task}`, "failed"), theme);
|
|
2013
|
+
return renderVascendNotice("done", taskActivityLabel(`${where}${task}`, "completed"), theme);
|
|
1889
2014
|
}
|
|
1890
2015
|
return null;
|
|
1891
2016
|
}
|
|
@@ -1897,26 +2022,26 @@ function diffForCall(call, pre, output, theme, columns) {
|
|
|
1897
2022
|
const append = call.input["append"] === true;
|
|
1898
2023
|
const next = String(call.input["content"] ?? "");
|
|
1899
2024
|
const existed = (pre ?? "") !== "";
|
|
1900
|
-
const verb = append ? "aggiunto" : existed ? "modificato" : "creato";
|
|
2025
|
+
const verb = append ? tr("aggiunto", "appended") : existed ? tr("modificato", "modified") : tr("creato", "created");
|
|
1901
2026
|
// In append mostriamo solo l'aggiunta (base vuota); altrimenti il delta vs file.
|
|
1902
|
-
return
|
|
2027
|
+
return renderDiffParts(append ? "" : (pre ?? ""), next, theme, { path, verb, columns: columns - 1, wrapLines: true, maxRows: 100 }) ?? undefined;
|
|
1903
2028
|
}
|
|
1904
2029
|
if (call.name === "edit_file" && output.startsWith("modificato:")) {
|
|
1905
2030
|
const path = typeof call.input["path"] === "string" ? call.input["path"] : undefined;
|
|
1906
2031
|
const oldS = String(call.input["old_string"] ?? "");
|
|
1907
2032
|
const newS = String(call.input["new_string"] ?? "");
|
|
1908
|
-
return
|
|
2033
|
+
return renderDiffParts(oldS, newS, theme, { path, verb: tr("modificato", "modified"), context: 2, columns: columns - 1, wrapLines: true, maxRows: 100 }) ?? undefined;
|
|
1909
2034
|
}
|
|
1910
|
-
return
|
|
2035
|
+
return undefined;
|
|
1911
2036
|
}
|
|
1912
2037
|
// Soglia riferita alle righe del confronto, escluse intestazione e separatore.
|
|
1913
|
-
function previewDiff(full, theme, width) {
|
|
2038
|
+
function previewDiff(full, theme, width, hiddenRows = 0) {
|
|
1914
2039
|
const lines = full.replace(/\n$/, "").split("\n");
|
|
1915
|
-
const limit = 100
|
|
1916
|
-
if (lines.length <= limit)
|
|
2040
|
+
const limit = 100;
|
|
2041
|
+
if (lines.length <= limit && hiddenRows === 0)
|
|
1917
2042
|
return full;
|
|
1918
2043
|
const head = lines.slice(0, limit);
|
|
1919
|
-
const hidden = lines.length - limit;
|
|
2044
|
+
const hidden = Math.max(0, lines.length - limit) + hiddenRows;
|
|
1920
2045
|
const hint = ` … altre ${hidden} righe · Ctrl+O scegli file`;
|
|
1921
2046
|
head.push(theme.dim(fitLine(terminalWidth(hint) <= width ? hint : ` Ctrl+O · +${hidden} righe`, width)));
|
|
1922
2047
|
return head.join("\n") + "\n";
|
|
@@ -2119,115 +2244,6 @@ function resumeInto(harness, vascend, id, ctl, theme, tcols, resetSessionUi) {
|
|
|
2119
2244
|
replaySession(loaded, ctl, theme, tcols());
|
|
2120
2245
|
return true;
|
|
2121
2246
|
}
|
|
2122
|
-
// Etichetta leggibile del tool (stile Claude Code: Read/Write/Update/Bash...).
|
|
2123
|
-
function toolLabel(name) {
|
|
2124
|
-
const map = {
|
|
2125
|
-
read_file: "Read",
|
|
2126
|
-
write_file: "Write",
|
|
2127
|
-
edit_file: "Update",
|
|
2128
|
-
list_dir: "List",
|
|
2129
|
-
bash: "Bash",
|
|
2130
|
-
bash_output: "BashOutput",
|
|
2131
|
-
web_fetch: "Fetch",
|
|
2132
|
-
ask_user: "Ask",
|
|
2133
|
-
wait: "Wait",
|
|
2134
|
-
tool_search: "ToolSearch",
|
|
2135
|
-
tool_invoke: "Invoke",
|
|
2136
|
-
skill_search: "SkillSearch",
|
|
2137
|
-
skill_load: "SkillLoad",
|
|
2138
|
-
output_page: "Page",
|
|
2139
|
-
kingdoms_warm: tr("Collaboratori", "Collaborators"),
|
|
2140
|
-
kingdom_send: "Send",
|
|
2141
|
-
kingdom_inbox: "Inbox",
|
|
2142
|
-
kingdom_wait: "WaitMsg",
|
|
2143
|
-
glob: "Glob",
|
|
2144
|
-
grep: "Grep",
|
|
2145
|
-
recall: "Recall",
|
|
2146
|
-
image_generate: "Image",
|
|
2147
|
-
vascend_plan: tr("Lista attività", "Task list"),
|
|
2148
|
-
vascend_mark: tr("Stato attività", "Task status"),
|
|
2149
|
-
vascend_note: "Note",
|
|
2150
|
-
vascend_skill: "Skill",
|
|
2151
|
-
vascend_addtasks: tr("Aggiungi attività", "Add tasks"),
|
|
2152
|
-
vascend_addrooms: tr("Aggiungi attività", "Add tasks"),
|
|
2153
|
-
vascend_status: tr("Lista attività", "Task list"),
|
|
2154
|
-
};
|
|
2155
|
-
return map[name] ?? name;
|
|
2156
|
-
}
|
|
2157
|
-
// Argomento mostrato accanto al tool: il path se c'e', il comando per bash,
|
|
2158
|
-
// altrimenti gli argomenti compatti.
|
|
2159
|
-
function toolArg(call) {
|
|
2160
|
-
const p = call.input["path"];
|
|
2161
|
-
if (call.name === "run_program") {
|
|
2162
|
-
const code = String(call.input["code"] ?? "");
|
|
2163
|
-
return tr("JavaScript · {count} righe · Ctrl+R dettagli", "JavaScript · {count} lines · Ctrl+R details", { count: code.split("\n").length });
|
|
2164
|
-
}
|
|
2165
|
-
if (call.name === "tool_invoke") {
|
|
2166
|
-
const inner = String(call.input["name"] ?? "");
|
|
2167
|
-
const a = call.input["args"] && typeof call.input["args"] === "object" ? compactArgs(call.input["args"]) : "";
|
|
2168
|
-
return truncate(`${inner}${a && a !== "{}" ? ` ${a}` : ""}`, 56);
|
|
2169
|
-
}
|
|
2170
|
-
if (call.name === "tool_search" || call.name === "skill_search")
|
|
2171
|
-
return truncate(String(call.input["query"] ?? ""), 50);
|
|
2172
|
-
if (call.name === "skill_load")
|
|
2173
|
-
return String(call.input["name"] ?? "");
|
|
2174
|
-
if (call.name === "grep" || call.name === "glob")
|
|
2175
|
-
return truncate(String(call.input["pattern"] ?? ""), 50);
|
|
2176
|
-
if (call.name === "wait") {
|
|
2177
|
-
const until = call.input["until"];
|
|
2178
|
-
return typeof until === "string" && until ? `until ${truncate(until, 44)}` : `${call.input["seconds"] ?? 5}s`;
|
|
2179
|
-
}
|
|
2180
|
-
// Tool del metodo Danilov: argomento LEGGIBILE invece del JSON grezzo
|
|
2181
|
-
// (es. Mark(T01 → running) al posto di Mark({"task":"T01","status":"running"})).
|
|
2182
|
-
const danilov = vascendArg(call);
|
|
2183
|
-
if (danilov !== null)
|
|
2184
|
-
return danilov;
|
|
2185
|
-
if (typeof p === "string" && p)
|
|
2186
|
-
return p;
|
|
2187
|
-
if (call.name === "bash")
|
|
2188
|
-
return truncate(oneLine(String(call.input["command"] ?? "")), 60);
|
|
2189
|
-
return compactArgs(call.input);
|
|
2190
|
-
}
|
|
2191
|
-
// Argomento LEGGIBILE per i tool del metodo Danilov: trasforma l'input grezzo
|
|
2192
|
-
// in una sintesi human-friendly. Ritorna null se non e' un tool Danilov noto
|
|
2193
|
-
// (cosi' toolArg prosegue con la sua logica standard).
|
|
2194
|
-
function vascendArg(call) {
|
|
2195
|
-
const i = call.input;
|
|
2196
|
-
const castle = typeof i["castle"] === "string" && i["castle"] && i["castle"] !== "master" ? ` @${i["castle"]}` : "";
|
|
2197
|
-
switch (call.name) {
|
|
2198
|
-
case "vascend_mark": {
|
|
2199
|
-
const task = String(i["task"] ?? "");
|
|
2200
|
-
const status = String(i["status"] ?? "completed");
|
|
2201
|
-
return truncate(`${task} → ${status}${castle}`, 56);
|
|
2202
|
-
}
|
|
2203
|
-
case "vascend_plan": {
|
|
2204
|
-
const tasks = Array.isArray(i["tasks"]) ? i["tasks"].length : 0;
|
|
2205
|
-
const title = typeof i["title"] === "string" ? i["title"] : "";
|
|
2206
|
-
const n = tr("{count} attività", "{count} tasks", { count: tasks });
|
|
2207
|
-
return truncate(title ? `${title} · ${n}${castle}` : `${n}${castle}`, 60);
|
|
2208
|
-
}
|
|
2209
|
-
case "vascend_addtasks": {
|
|
2210
|
-
const tasks = Array.isArray(i["tasks"]) ? i["tasks"].length : 0;
|
|
2211
|
-
return truncate("+" + tr("{count} attività", "{count} tasks", { count: tasks }) + castle, 56);
|
|
2212
|
-
}
|
|
2213
|
-
case "vascend_note": {
|
|
2214
|
-
const task = typeof i["task"] === "string" && i["task"] ? `${i["task"]}: ` : "";
|
|
2215
|
-
const text = String(i["text"] ?? i["note"] ?? "");
|
|
2216
|
-
return truncate(`${task}${text}`, 56);
|
|
2217
|
-
}
|
|
2218
|
-
case "vascend_skill": {
|
|
2219
|
-
const task = typeof i["task"] === "string" && i["task"] ? `${i["task"]}: ` : "";
|
|
2220
|
-
const skills = Array.isArray(i["skills"]) ? i["skills"].join(", ") : String(i["skills"] ?? i["skill"] ?? "");
|
|
2221
|
-
return truncate(`${task}${skills}`, 56);
|
|
2222
|
-
}
|
|
2223
|
-
default:
|
|
2224
|
-
return null;
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
function compactArgs(input) {
|
|
2228
|
-
const s = JSON.stringify(input);
|
|
2229
|
-
return s.length > 80 ? s.slice(0, 77) + "..." : s;
|
|
2230
|
-
}
|
|
2231
2247
|
function firstLine(s) {
|
|
2232
2248
|
const line = s.split("\n")[0] ?? "";
|
|
2233
2249
|
return line.length > 100 ? line.slice(0, 97) + "..." : line;
|
|
@@ -2323,6 +2339,10 @@ function controlCollapsible(ctl, channel, action) {
|
|
|
2323
2339
|
// Ritorna: "quit" per uscire, input da inviare al modello, o null se il
|
|
2324
2340
|
// comando e' stato gestito internamente.
|
|
2325
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
|
+
}
|
|
2326
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 }));
|
|
2327
2347
|
}
|
|
2328
2348
|
async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi) {
|
|
@@ -2330,6 +2350,12 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2330
2350
|
const args = rest.join(" ");
|
|
2331
2351
|
const { harness } = deps;
|
|
2332
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
|
+
}
|
|
2333
2359
|
case "review":
|
|
2334
2360
|
case "revisione": {
|
|
2335
2361
|
if (ctl.showReview)
|
|
@@ -2476,15 +2502,29 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2476
2502
|
// Esegue/riapre il comando scelto come se digitato.
|
|
2477
2503
|
return handleSlash(`/${selected.value}`, deps, ctl, theme, terminalColumns, themeCtl, templates, vascend, stats, signal, resetSessionUi);
|
|
2478
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
|
+
}
|
|
2479
2513
|
case "logout": {
|
|
2480
2514
|
// Esce dall'account Vascend: revoca best-effort sul backend, poi rimuove
|
|
2481
2515
|
// il token locale. Al prossimo avvio il cancello chiedera' l'accesso.
|
|
2482
|
-
|
|
2483
|
-
|
|
2516
|
+
// Il controllo remoto si chiude prima: usa la stessa sessione.
|
|
2517
|
+
await fermaRemoto();
|
|
2518
|
+
if (!leggiSessione(harness.env)) {
|
|
2519
|
+
rimuoviTokenVascend(harness.env);
|
|
2484
2520
|
ctl.write(theme.dim("\n nessun accesso Vascend attivo\n"));
|
|
2485
2521
|
return null;
|
|
2486
2522
|
}
|
|
2487
|
-
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);
|
|
2488
2528
|
rimuoviTokenVascend(harness.env);
|
|
2489
2529
|
// Via anche la prova offline: un logout che la lasciasse in giro
|
|
2490
2530
|
// regalerebbe una finestra offline a chi e' appena uscito.
|
|
@@ -2528,7 +2568,10 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2528
2568
|
await pickModel(harness, ctl, theme, terminalColumns);
|
|
2529
2569
|
return null;
|
|
2530
2570
|
}
|
|
2531
|
-
|
|
2571
|
+
if (args.trim().toLowerCase() === "fusion")
|
|
2572
|
+
await pickFusion(harness, ctl, theme, terminalColumns);
|
|
2573
|
+
else
|
|
2574
|
+
await applyModelChoice(args, harness, ctl, theme, terminalColumns);
|
|
2532
2575
|
return null;
|
|
2533
2576
|
}
|
|
2534
2577
|
case "models": {
|
|
@@ -2539,22 +2582,22 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2539
2582
|
case "themes": {
|
|
2540
2583
|
if (args) {
|
|
2541
2584
|
if (themeCtl.apply(args)) {
|
|
2542
|
-
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")));
|
|
2543
2586
|
}
|
|
2544
2587
|
else {
|
|
2545
|
-
ctl.write(theme.yellow(
|
|
2546
|
-
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")));
|
|
2547
2590
|
}
|
|
2548
2591
|
return null;
|
|
2549
2592
|
}
|
|
2550
2593
|
const options = themeCtl.available.map((t) => ({
|
|
2551
2594
|
key: t.name,
|
|
2552
2595
|
label: t.name,
|
|
2553
|
-
detail: t.mode === "light" ? "chiaro" : "scuro",
|
|
2596
|
+
detail: t.mode === "light" ? tr("chiaro", "light") : tr("scuro", "dark"),
|
|
2554
2597
|
value: t.name,
|
|
2555
2598
|
}));
|
|
2556
2599
|
const selected = await selectOption({
|
|
2557
|
-
title: "Temi",
|
|
2600
|
+
title: tr("Temi", "Themes"),
|
|
2558
2601
|
options,
|
|
2559
2602
|
currentKey: themeCtl.activeName(),
|
|
2560
2603
|
ctl,
|
|
@@ -2562,7 +2605,7 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2562
2605
|
terminalColumns,
|
|
2563
2606
|
});
|
|
2564
2607
|
if (selected && themeCtl.apply(selected.value)) {
|
|
2565
|
-
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")));
|
|
2566
2609
|
}
|
|
2567
2610
|
return null;
|
|
2568
2611
|
}
|
|
@@ -2576,39 +2619,39 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2576
2619
|
const snapshot = stats.cost.snapshot(harness.config.model);
|
|
2577
2620
|
const last = stats.last;
|
|
2578
2621
|
const context = (stats.context ?? new ContextMeter()).read(harness);
|
|
2579
|
-
const lines = [theme.bold("\n Consumi della sessione") + theme.dim(` · ${harness.session.name || harness.session.id}`)];
|
|
2580
|
-
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()}`));
|
|
2581
2624
|
if (stats.incomplete)
|
|
2582
|
-
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."))));
|
|
2583
2626
|
if (stats.missing)
|
|
2584
|
-
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 }))));
|
|
2585
2628
|
if (stats.unsaved)
|
|
2586
|
-
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."))));
|
|
2587
2630
|
if (snapshot.totalTurns === 0) {
|
|
2588
|
-
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."))));
|
|
2589
2632
|
}
|
|
2590
2633
|
else {
|
|
2591
|
-
lines.push(theme.silver(
|
|
2592
|
-
lines.push(theme.silver(
|
|
2593
|
-
lines.push(theme.dim(
|
|
2594
|
-
lines.push(theme.dim(
|
|
2595
|
-
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) }))));
|
|
2596
2639
|
lines.push(theme.silver(` output: ${formatTokenCount(snapshot.totalOutputTokens)}`));
|
|
2597
|
-
lines.push(theme.silver(
|
|
2640
|
+
lines.push(theme.silver((" " + tr("totale provider: {p0}", "provider total: {p0}", { p0: formatTokenCount(snapshot.totalProviderTokens) }))));
|
|
2598
2641
|
}
|
|
2599
2642
|
if (last) {
|
|
2600
|
-
lines.push(theme.bold("\n ultima chiamata"));
|
|
2601
|
-
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) }))));
|
|
2602
2645
|
lines.push(theme.dim(` output ${formatTokenCount(last.outputTokens)}`));
|
|
2603
2646
|
}
|
|
2604
2647
|
if (snapshot.perModel.length) {
|
|
2605
|
-
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)"))));
|
|
2606
2649
|
for (const row of snapshot.perModel)
|
|
2607
|
-
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 }))));
|
|
2608
2651
|
}
|
|
2609
2652
|
const providerId = harness.providers.resolve(harness.config.model)?.provider.id ?? "";
|
|
2610
2653
|
lines.push(...subscriptionDetails(providerId, stats.limits?.get(providerId), theme));
|
|
2611
|
-
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"))));
|
|
2612
2655
|
lines.push(...contextDetails(context).map(line => theme.dim(` ${line}`)));
|
|
2613
2656
|
ctl.write(lines.join("\n") + "\n");
|
|
2614
2657
|
return null;
|
|
@@ -2658,10 +2701,10 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2658
2701
|
const csub = args.trim();
|
|
2659
2702
|
if (csub === "close" || csub === "open" || csub === "toggle") {
|
|
2660
2703
|
if (!controlCollapsible(ctl, "config", csub) && csub === "close") {
|
|
2661
|
-
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")));
|
|
2662
2705
|
}
|
|
2663
2706
|
else if (csub === "close") {
|
|
2664
|
-
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")));
|
|
2665
2708
|
}
|
|
2666
2709
|
return null;
|
|
2667
2710
|
}
|
|
@@ -2669,21 +2712,21 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2669
2712
|
const cfg = harness.config;
|
|
2670
2713
|
const resolved = harness.providers.resolve(cfg.model);
|
|
2671
2714
|
const provider = resolved
|
|
2672
|
-
? (resolved.provider
|
|
2673
|
-
: "non configurato";
|
|
2715
|
+
? providerLabel(resolved.provider)
|
|
2716
|
+
: tr("non configurato", "not configured");
|
|
2674
2717
|
const settings = [
|
|
2675
|
-
{ key: "model", label: "Modello", detail: cfg.model, value: "model" },
|
|
2676
|
-
{ key: "provider", label: "Provider e accessi", detail: provider, value: "provider" },
|
|
2677
|
-
{ key: "vibe", label: "Profilo operativo", detail: cfg.vibeMode, value: "vibe" },
|
|
2678
|
-
{ 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" },
|
|
2679
2722
|
{ key: "cwd", label: "Workspace", detail: cfg.cwd, value: "cwd" },
|
|
2680
|
-
{ key: "sessions", label: "Sessioni", detail: harness.session.id, value: "sessions" },
|
|
2681
|
-
{ key: "extensions", label: "Estensioni", detail:
|
|
2682
|
-
{ 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" },
|
|
2683
2726
|
];
|
|
2684
2727
|
const selected = await selectOption({
|
|
2685
|
-
title: "Impostazioni",
|
|
2686
|
-
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"),
|
|
2687
2730
|
options: settings,
|
|
2688
2731
|
fullHeight: true,
|
|
2689
2732
|
wide: true,
|
|
@@ -2698,22 +2741,22 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
2698
2741
|
const cfg = harness.config;
|
|
2699
2742
|
const files = cfg.loadedConfigFiles.length
|
|
2700
2743
|
? cfg.loadedConfigFiles.map((file) => ` ${file}`).join("\n")
|
|
2701
|
-
: " nessun file caricato";
|
|
2702
|
-
const body = theme.dim(
|
|
2703
|
-
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`) +
|
|
2704
2747
|
theme.dim(` vibeMode: ${cfg.vibeMode}\n`) +
|
|
2705
2748
|
theme.dim(` sessionDir: ${cfg.sessionDir}\n`) +
|
|
2706
2749
|
theme.dim(` contextFiles: ${cfg.contextFiles.join(", ")}\n`) +
|
|
2707
|
-
theme.dim(` skill: ${harness.skills.length}${harness.skillCommandsEnabled ? "" : " (comandi off)"}${harness.skills.length ? ` (${harness.skills.map((s) => s.name).join(", ")})` : ""}\n`) +
|
|
2708
|
-
theme.dim(
|
|
2709
|
-
theme.bold("\n tema\n") +
|
|
2710
|
-
theme.dim(
|
|
2711
|
-
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")) +
|
|
2712
2755
|
theme.dim(` banner: ${cfg.ui.banner}\n`) +
|
|
2713
2756
|
theme.dim(` prompt: ${JSON.stringify(cfg.ui.prompt)}\n`) +
|
|
2714
2757
|
theme.bold("\n file\n") +
|
|
2715
2758
|
theme.dim(`${files}`);
|
|
2716
|
-
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);
|
|
2717
2760
|
return null;
|
|
2718
2761
|
}
|
|
2719
2762
|
case "vibe":
|
|
@@ -3074,13 +3117,11 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
3074
3117
|
ctl.write(theme.green(`\n ${result.message}\n`));
|
|
3075
3118
|
return null;
|
|
3076
3119
|
}
|
|
3077
|
-
//
|
|
3078
|
-
//
|
|
3079
|
-
// .vascend-compact.md per la ripresa a freddo. /vascend-compact e' un alias.
|
|
3080
|
-
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.
|
|
3081
3122
|
case "compact": {
|
|
3082
3123
|
const hasModel = Boolean(harness.providers.resolve(harness.config.model));
|
|
3083
|
-
let feedback = "
|
|
3124
|
+
let feedback = "Nessun modello configurato; contesto conservato.";
|
|
3084
3125
|
let failed = false;
|
|
3085
3126
|
// 1) Compattazione LLM del contesto della sessione.
|
|
3086
3127
|
if (hasModel) {
|
|
@@ -3103,16 +3144,16 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
3103
3144
|
}, args || undefined);
|
|
3104
3145
|
if (done) {
|
|
3105
3146
|
const after = meter.read(harness);
|
|
3106
|
-
feedback =
|
|
3147
|
+
feedback = tr("Contesto compattato: ~{before} -> ~{after} token", "Context compacted: ~{before} -> ~{after} tokens", { before: formatTokenCount(before), after: formatTokenCount(after.tokens) });
|
|
3107
3148
|
}
|
|
3108
3149
|
else {
|
|
3109
3150
|
feedback = controller.signal.aborted
|
|
3110
|
-
? "Compattazione interrotta; contesto conservato."
|
|
3111
|
-
: "Nessuna compattazione necessaria; contesto conservato.";
|
|
3151
|
+
? tr("Compattazione interrotta; contesto conservato.", "Compaction interrupted; context preserved.")
|
|
3152
|
+
: tr("Nessuna compattazione necessaria; contesto conservato.", "No compaction needed; context preserved.");
|
|
3112
3153
|
}
|
|
3113
3154
|
}
|
|
3114
3155
|
catch (err) {
|
|
3115
|
-
feedback =
|
|
3156
|
+
feedback = tr("Compattazione non riuscita: {detail}", "Compaction failed: {detail}", { detail: runtimeError(err.message) });
|
|
3116
3157
|
failed = true;
|
|
3117
3158
|
}
|
|
3118
3159
|
finally {
|
|
@@ -3122,9 +3163,6 @@ async function handleSlashCommand(line, deps, ctl, theme, terminalColumns, theme
|
|
|
3122
3163
|
ctl.setStatus(null);
|
|
3123
3164
|
}
|
|
3124
3165
|
}
|
|
3125
|
-
// 2) Checkpoint Vascend/Danilov sullo stato (gia' compattato) della sessione.
|
|
3126
|
-
vascend.loadPlan(harness.config.sessionDir, harness.config.planStoreId ?? harness.config.roomStoreId ?? harness.session.id);
|
|
3127
|
-
vascend.compact(harness.session.history());
|
|
3128
3166
|
notifyStatus(ctl, feedback, failed);
|
|
3129
3167
|
return null;
|
|
3130
3168
|
}
|
|
@@ -3483,7 +3521,7 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3483
3521
|
const rows = loginRows(harness.env, harness.providers);
|
|
3484
3522
|
const picked = await selectOption({
|
|
3485
3523
|
title: tr("accesso ai modelli", "model sign-in"),
|
|
3486
|
-
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"),
|
|
3487
3525
|
options: rows.map((row) => ({
|
|
3488
3526
|
key: row.id,
|
|
3489
3527
|
label: row.label,
|
|
@@ -3511,7 +3549,7 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3511
3549
|
}
|
|
3512
3550
|
const action = await selectOption({
|
|
3513
3551
|
title: `${row.label} · ${loginStatusLabel(row)}`,
|
|
3514
|
-
hint: row.command ?
|
|
3552
|
+
hint: row.command ? tr("comando: {p0}", "command: {p0}", { p0: row.command.join(" ") }) : "",
|
|
3515
3553
|
options: [
|
|
3516
3554
|
{
|
|
3517
3555
|
key: "login",
|
|
@@ -3545,10 +3583,10 @@ async function loginMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3545
3583
|
harness.reloadProviders();
|
|
3546
3584
|
const refreshed = loginRows(harness.env, harness.providers).find((item) => item.id === row.id);
|
|
3547
3585
|
if (completed && refreshed?.configured && !refreshed.needsRefresh) {
|
|
3548
|
-
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")));
|
|
3549
3587
|
}
|
|
3550
3588
|
else if (completed) {
|
|
3551
|
-
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")));
|
|
3552
3590
|
}
|
|
3553
3591
|
}
|
|
3554
3592
|
}
|
|
@@ -3557,11 +3595,11 @@ async function runInteractiveLogin(command, harness, ctl, theme) {
|
|
|
3557
3595
|
if (!executable)
|
|
3558
3596
|
return false;
|
|
3559
3597
|
if (!stdin.isTTY || !stdout.isTTY) {
|
|
3560
|
-
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")));
|
|
3561
3599
|
return false;
|
|
3562
3600
|
}
|
|
3563
|
-
ctl.write(theme.bold(
|
|
3564
|
-
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")));
|
|
3565
3603
|
ctl.pause();
|
|
3566
3604
|
ctl.setOverlay(null);
|
|
3567
3605
|
ctl.setStatus(null);
|
|
@@ -3580,7 +3618,7 @@ async function runInteractiveLogin(command, harness, ctl, theme) {
|
|
|
3580
3618
|
child.once("exit", (value) => resolve(value ?? -1));
|
|
3581
3619
|
});
|
|
3582
3620
|
if (code !== 0) {
|
|
3583
|
-
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")));
|
|
3584
3622
|
return false;
|
|
3585
3623
|
}
|
|
3586
3624
|
return true;
|
|
@@ -3604,13 +3642,13 @@ async function providerMenu(harness, ctl, theme, terminalColumns) {
|
|
|
3604
3642
|
const options = rows.map((row) => ({
|
|
3605
3643
|
key: row.id,
|
|
3606
3644
|
label: `${row.label}${row.active ? " ←" : ""}`,
|
|
3607
|
-
detail: `${rowStatusLabel(row)}${row.modelCount ?
|
|
3645
|
+
detail: `${rowStatusLabel(row)}${row.modelCount ? (" " + tr("· {p0} modelli", "· {p0} models", { p0: row.modelCount })) : ""}`,
|
|
3608
3646
|
value: row,
|
|
3609
3647
|
}));
|
|
3610
3648
|
const configured = rows.filter((r) => r.configured).length;
|
|
3611
3649
|
const picked = await selectOption({
|
|
3612
|
-
title:
|
|
3613
|
-
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"),
|
|
3614
3652
|
options,
|
|
3615
3653
|
ctl,
|
|
3616
3654
|
theme,
|
|
@@ -3646,7 +3684,7 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3646
3684
|
case "set-key": {
|
|
3647
3685
|
const ok = await setKeyFlow(fresh, harness, ctl, theme);
|
|
3648
3686
|
if (ok)
|
|
3649
|
-
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")));
|
|
3650
3688
|
break;
|
|
3651
3689
|
}
|
|
3652
3690
|
case "set-endpoint": {
|
|
@@ -3665,11 +3703,11 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3665
3703
|
}
|
|
3666
3704
|
case "remove": {
|
|
3667
3705
|
const confirmed = await selectOption({
|
|
3668
|
-
title:
|
|
3669
|
-
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"),
|
|
3670
3708
|
options: [
|
|
3671
|
-
{ key: "no", label: "No, mantieni", detail: "", value: false },
|
|
3672
|
-
{ 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 },
|
|
3673
3711
|
],
|
|
3674
3712
|
currentKey: "no",
|
|
3675
3713
|
ctl,
|
|
@@ -3680,10 +3718,10 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3680
3718
|
const res = removeProvider(harness.env, fresh.id);
|
|
3681
3719
|
if (res.ok) {
|
|
3682
3720
|
harness.reloadProviders();
|
|
3683
|
-
ctl.write(theme.green(
|
|
3721
|
+
ctl.write(theme.green(("\n " + tr("{p0}: credenziale rimossa.", "{p0}: credential removed.", { p0: fresh.label }) + "\n")));
|
|
3684
3722
|
}
|
|
3685
3723
|
else {
|
|
3686
|
-
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`));
|
|
3687
3725
|
}
|
|
3688
3726
|
return "back";
|
|
3689
3727
|
}
|
|
@@ -3696,28 +3734,28 @@ async function providerActions(row, harness, ctl, theme, terminalColumns) {
|
|
|
3696
3734
|
// rete), la salva e ricostruisce il registro. Ritorna true se salvata.
|
|
3697
3735
|
async function setKeyFlow(row, harness, ctl, theme) {
|
|
3698
3736
|
ctl.write(theme.dim(`\n ${providerKeyHint(row.spec)}\n`));
|
|
3699
|
-
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 }));
|
|
3700
3738
|
if (!key)
|
|
3701
3739
|
return false;
|
|
3702
3740
|
const trimmed = key.trim();
|
|
3703
3741
|
// Validazione di forma per OpenRouter (sk-or-...). Avviso non bloccante.
|
|
3704
3742
|
if (row.id === "openrouter" && !looksLikeApiKey(trimmed)) {
|
|
3705
|
-
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")));
|
|
3706
3744
|
}
|
|
3707
3745
|
// Validazione di rete (oggi solo OpenRouter espone /key). Best effort.
|
|
3708
3746
|
if (row.id === "openrouter") {
|
|
3709
|
-
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…")));
|
|
3710
3748
|
try {
|
|
3711
3749
|
const v = await validateApiKey(trimmed);
|
|
3712
3750
|
ctl.setStatus(null);
|
|
3713
3751
|
if (!v.ok) {
|
|
3714
|
-
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")));
|
|
3715
3753
|
return false;
|
|
3716
3754
|
}
|
|
3717
3755
|
const credit = v.limitRemaining === null || v.limitRemaining === undefined
|
|
3718
|
-
? "credito illimitato"
|
|
3719
|
-
:
|
|
3720
|
-
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")));
|
|
3721
3759
|
}
|
|
3722
3760
|
catch {
|
|
3723
3761
|
ctl.setStatus(null);
|
|
@@ -3725,7 +3763,7 @@ async function setKeyFlow(row, harness, ctl, theme) {
|
|
|
3725
3763
|
}
|
|
3726
3764
|
const res = saveProviderKey(harness.env, row.id, trimmed);
|
|
3727
3765
|
if (!res.ok) {
|
|
3728
|
-
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`));
|
|
3729
3767
|
return false;
|
|
3730
3768
|
}
|
|
3731
3769
|
// La env scoped serve a reloadProviders (che la rilegge dal disco), ma la
|
|
@@ -3737,13 +3775,13 @@ async function setKeyFlow(row, harness, ctl, theme) {
|
|
|
3737
3775
|
}
|
|
3738
3776
|
// Flusso "imposta endpoint": chiede una baseUrl (vuota = ripristina default).
|
|
3739
3777
|
async function setEndpointFlow(row, harness, ctl, theme) {
|
|
3740
|
-
ctl.write(theme.dim(
|
|
3741
|
-
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 }));
|
|
3742
3780
|
if (url === null)
|
|
3743
3781
|
return; // Esc
|
|
3744
3782
|
const res = saveProviderBaseUrl(harness.env, row.id, url);
|
|
3745
3783
|
if (!res.ok) {
|
|
3746
|
-
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`));
|
|
3747
3785
|
return;
|
|
3748
3786
|
}
|
|
3749
3787
|
const norm = row.id.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
@@ -3752,26 +3790,26 @@ async function setEndpointFlow(row, harness, ctl, theme) {
|
|
|
3752
3790
|
else
|
|
3753
3791
|
delete harness.env[`TALOS_${norm}_BASE_URL`];
|
|
3754
3792
|
harness.reloadProviders();
|
|
3755
|
-
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")));
|
|
3756
3794
|
}
|
|
3757
3795
|
// Flusso OAuth (oggi: OpenRouter PKCE). Mostra l'URL da aprire nel browser e
|
|
3758
3796
|
// raccoglie il code dal callback, poi scambia e salva la chiave.
|
|
3759
3797
|
async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
3760
3798
|
if (row.id !== "openrouter") {
|
|
3761
|
-
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);
|
|
3762
3800
|
return;
|
|
3763
3801
|
}
|
|
3764
3802
|
// Il login e' una vista operativa temporanea: non deve diventare parte della
|
|
3765
3803
|
// conversazione. L'overlay viene aggiornato in place e rimosso al termine.
|
|
3766
3804
|
let authUrl = "";
|
|
3767
|
-
let authState = "avvio il server locale…";
|
|
3805
|
+
let authState = tr("avvio il server locale…", "Starting the local server…");
|
|
3768
3806
|
const drawOAuth = () => {
|
|
3769
3807
|
const lines = [authState];
|
|
3770
3808
|
if (authUrl)
|
|
3771
|
-
lines.push("", "Se il browser non si apre, copia questo link:", authUrl);
|
|
3772
|
-
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."));
|
|
3773
3811
|
ctl.setOverlay([
|
|
3774
|
-
theme.bold(" OpenRouter · accesso via browser"),
|
|
3812
|
+
theme.bold((" " + tr("OpenRouter · accesso via browser", "OpenRouter · browser sign-in"))),
|
|
3775
3813
|
theme.dark(` ${"─".repeat(Math.max(12, terminalColumns() - 4))}`),
|
|
3776
3814
|
...lines.flatMap((line) => wrapPlain(line || " ", Math.max(20, terminalColumns() - 4)))
|
|
3777
3815
|
.map((line) => ` ${theme.silver(line)}`),
|
|
@@ -3782,19 +3820,19 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3782
3820
|
switch (ev.phase) {
|
|
3783
3821
|
case "url_built":
|
|
3784
3822
|
authUrl = ev.detail ?? authUrl;
|
|
3785
|
-
authState = "apro il browser per autorizzare…";
|
|
3823
|
+
authState = tr("apro il browser per autorizzare…", "Opening the browser for authorization…");
|
|
3786
3824
|
break;
|
|
3787
3825
|
case "browser_opened":
|
|
3788
|
-
authState = "browser aperto · in attesa dell'autorizzazione…";
|
|
3826
|
+
authState = tr("browser aperto · in attesa dell'autorizzazione…", "Browser opened · waiting for authorization…");
|
|
3789
3827
|
break;
|
|
3790
3828
|
case "browser_failed":
|
|
3791
|
-
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…");
|
|
3792
3830
|
break;
|
|
3793
3831
|
case "awaiting_code":
|
|
3794
|
-
authState = "in attesa dell'autorizzazione nel browser…";
|
|
3832
|
+
authState = tr("in attesa dell'autorizzazione nel browser…", "Waiting for authorization in the browser…");
|
|
3795
3833
|
break;
|
|
3796
3834
|
case "exchanging":
|
|
3797
|
-
authState = "autorizzazione ricevuta · attivo la API key…";
|
|
3835
|
+
authState = tr("autorizzazione ricevuta · attivo la API key…", "Authorization received · activating the API key…");
|
|
3798
3836
|
break;
|
|
3799
3837
|
default:
|
|
3800
3838
|
break;
|
|
@@ -3825,20 +3863,20 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3825
3863
|
saveProviderKey(harness.env, row.id, result.apiKey);
|
|
3826
3864
|
harness.env["TALOS_OPENROUTER_API_KEY"] = result.apiKey;
|
|
3827
3865
|
harness.reloadProviders();
|
|
3828
|
-
transientNotice(ctl, "OpenRouter collegato e attivato", "done", theme);
|
|
3866
|
+
transientNotice(ctl, tr("OpenRouter collegato e attivato", "OpenRouter connected and activated"), "done", theme);
|
|
3829
3867
|
return;
|
|
3830
3868
|
}
|
|
3831
3869
|
// Fallimento del flusso automatico (browser/timeout/callback/server):
|
|
3832
3870
|
// ricadiamo sul percorso manuale con un flow nuovo e coerente (PKCE
|
|
3833
3871
|
// proprio + authUrl mostrato all'utente, che incolla il code).
|
|
3834
|
-
const reason = result.error ?? "callback automatico non riuscito";
|
|
3872
|
+
const reason = result.error ?? tr("callback automatico non riuscito", "automatic callback failed");
|
|
3835
3873
|
const manualFlow = startOAuthFlow(harness.env, callbackUrlForPort());
|
|
3836
3874
|
const manualLines = [];
|
|
3837
3875
|
const manual = await runManualCodeFallback({
|
|
3838
3876
|
print: (line) => { manualLines.push(line); },
|
|
3839
3877
|
prompt: async (label) => {
|
|
3840
3878
|
await showReadonlyOverlay({
|
|
3841
|
-
title: "OpenRouter · autorizzazione manuale",
|
|
3879
|
+
title: tr("OpenRouter · autorizzazione manuale", "OpenRouter · manual authorization"),
|
|
3842
3880
|
lines: manualLines,
|
|
3843
3881
|
ctl,
|
|
3844
3882
|
theme,
|
|
@@ -3846,25 +3884,25 @@ async function oauthFlow(row, harness, ctl, theme, terminalColumns) {
|
|
|
3846
3884
|
});
|
|
3847
3885
|
return ctl.promptInput(label);
|
|
3848
3886
|
},
|
|
3849
|
-
}, manualFlow, { reason });
|
|
3887
|
+
}, manualFlow, { reason, translate: tr });
|
|
3850
3888
|
if (!manual.ok || !manual.apiKey) {
|
|
3851
3889
|
if (manual.error === "cancelled") {
|
|
3852
|
-
transientNotice(ctl, "OAuth annullato", "done", theme);
|
|
3890
|
+
transientNotice(ctl, tr("OAuth annullato", "OAuth cancelled"), "done", theme);
|
|
3853
3891
|
}
|
|
3854
3892
|
else {
|
|
3855
|
-
transientNotice(ctl,
|
|
3893
|
+
transientNotice(ctl, tr("OAuth non riuscito: {p0}", "OAuth failed: {p0}", { p0: manual.error ?? tr("scambio fallito", "exchange failed") }), "failed", theme);
|
|
3856
3894
|
}
|
|
3857
3895
|
return;
|
|
3858
3896
|
}
|
|
3859
3897
|
saveProviderKey(harness.env, row.id, manual.apiKey);
|
|
3860
3898
|
harness.env["TALOS_OPENROUTER_API_KEY"] = manual.apiKey;
|
|
3861
3899
|
harness.reloadProviders();
|
|
3862
|
-
transientNotice(ctl, "OpenRouter collegato e attivato", "done", theme);
|
|
3900
|
+
transientNotice(ctl, tr("OpenRouter collegato e attivato", "OpenRouter connected and activated"), "done", theme);
|
|
3863
3901
|
}
|
|
3864
3902
|
catch (err) {
|
|
3865
3903
|
ctl.setStatus(null);
|
|
3866
3904
|
ctl.setOverlay(null);
|
|
3867
|
-
transientNotice(ctl,
|
|
3905
|
+
transientNotice(ctl, tr("errore OAuth: {p0}", "OAuth error: {p0}", { p0: err.message }), "failed", theme);
|
|
3868
3906
|
}
|
|
3869
3907
|
}
|
|
3870
3908
|
function transientNotice(ctl, label, state, theme) {
|
|
@@ -3877,13 +3915,15 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3877
3915
|
.filter((row) => !row.configured || row.needsRefresh)
|
|
3878
3916
|
.map((row) => ({
|
|
3879
3917
|
key: `access:${row.id}`,
|
|
3880
|
-
label: `${row.needsRefresh ? "Rinnova" : "Collega"} ${row.label}`,
|
|
3918
|
+
label: `${row.needsRefresh ? tr("Rinnova", "Renew") : tr("Collega", "Connect")} ${row.label}`,
|
|
3881
3919
|
detail: `${loginStatusLabel(row)} · ${row.detail}`,
|
|
3882
3920
|
value: { providerId: `access:${row.id}`, modelId: "", fullModel: `access:${row.id}` },
|
|
3883
3921
|
}));
|
|
3884
|
-
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)];
|
|
3885
3925
|
if (!options.length) {
|
|
3886
|
-
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")));
|
|
3887
3927
|
return false;
|
|
3888
3928
|
}
|
|
3889
3929
|
const current = canonicalModel(harness.config.model);
|
|
@@ -3900,6 +3940,8 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3900
3940
|
});
|
|
3901
3941
|
if (!selected)
|
|
3902
3942
|
return false;
|
|
3943
|
+
if (selected.value.providerId === "fusion")
|
|
3944
|
+
return pickFusion(harness, ctl, theme, terminalColumns);
|
|
3903
3945
|
if (selected.value.providerId.startsWith("access:")) {
|
|
3904
3946
|
await loginMenu(harness, ctl, theme, terminalColumns);
|
|
3905
3947
|
return false;
|
|
@@ -3912,15 +3954,15 @@ async function pickModel(harness, ctl, theme, terminalColumns) {
|
|
|
3912
3954
|
async function pickAuthMethod(choice, harness, ctl, theme, terminalColumns) {
|
|
3913
3955
|
const options = authOptions(choice, harness.providers.list());
|
|
3914
3956
|
if (!options.length) {
|
|
3915
|
-
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")));
|
|
3916
3958
|
ctl.write(theme.dim(authHint(choice.providerId) + "\n"));
|
|
3917
3959
|
return null;
|
|
3918
3960
|
}
|
|
3919
3961
|
if (!requiresAuthChoice(choice.providerId))
|
|
3920
3962
|
return options[0] ?? null;
|
|
3921
3963
|
return selectOption({
|
|
3922
|
-
title: "metodo accesso",
|
|
3923
|
-
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"),
|
|
3924
3966
|
options,
|
|
3925
3967
|
currentKey: harness.config.model,
|
|
3926
3968
|
ctl,
|
|
@@ -3949,6 +3991,7 @@ function modelOptions(providers, dynamic) {
|
|
|
3949
3991
|
key: full,
|
|
3950
3992
|
label: model,
|
|
3951
3993
|
group: spec.label,
|
|
3994
|
+
modelInfo: dynById.get(model)?.modelInfo,
|
|
3952
3995
|
detail: `${authSummary(spec.id, authProviders)}${detail ? ` · ${detail}` : ""}`,
|
|
3953
3996
|
value: { providerId: spec.id, modelId: model, fullModel: full },
|
|
3954
3997
|
});
|
|
@@ -3967,10 +4010,11 @@ async function loadDynamicModels(providers, ctl, theme, env) {
|
|
|
3967
4010
|
continue;
|
|
3968
4011
|
map.set(spec.id, catalog.list(spec.id)
|
|
3969
4012
|
.filter(m => m.status !== "deprecated" && m.outputModalities.includes("text") && m.toolCall !== false)
|
|
3970
|
-
.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 } })));
|
|
3971
4015
|
}
|
|
3972
4016
|
if (providers.some((p) => p.id === "openrouter")) {
|
|
3973
|
-
ctl.setStatus(theme.dim("carico catalogo OpenRouter…"));
|
|
4017
|
+
ctl.setStatus(theme.dim(tr("carico catalogo OpenRouter…", "Loading the OpenRouter catalog…")));
|
|
3974
4018
|
try {
|
|
3975
4019
|
const models = await fetchOpenRouterModels(env);
|
|
3976
4020
|
if (models.length) {
|
|
@@ -3996,7 +4040,7 @@ function authOptions(choice, providers) {
|
|
|
3996
4040
|
.map((provider) => ({
|
|
3997
4041
|
key: `${provider.id}/${choice.modelId}`,
|
|
3998
4042
|
label: authLabel(choice.providerId, provider.id),
|
|
3999
|
-
detail: provider
|
|
4043
|
+
detail: providerLabel(provider),
|
|
4000
4044
|
value: `${provider.id}/${choice.modelId}`,
|
|
4001
4045
|
}));
|
|
4002
4046
|
}
|
|
@@ -4012,14 +4056,14 @@ function authProviderIds(providerId) {
|
|
|
4012
4056
|
}
|
|
4013
4057
|
function authLabel(canonicalProviderId, actualProviderId) {
|
|
4014
4058
|
if (canonicalProviderId === "anthropic" && actualProviderId === "claude-code")
|
|
4015
|
-
return "OAuth abbonamento";
|
|
4059
|
+
return tr("OAuth abbonamento", "OAuth subscription");
|
|
4016
4060
|
if (canonicalProviderId === "anthropic" && actualProviderId === "anthropic")
|
|
4017
4061
|
return "API key";
|
|
4018
4062
|
if (canonicalProviderId === "openai" && actualProviderId === "chatgpt")
|
|
4019
|
-
return "OAuth abbonamento";
|
|
4063
|
+
return tr("OAuth abbonamento", "OAuth subscription");
|
|
4020
4064
|
if (canonicalProviderId === "openai" && actualProviderId === "openai")
|
|
4021
4065
|
return "API key";
|
|
4022
|
-
return actualProviderId === "ollama" ? "locale" : "API key";
|
|
4066
|
+
return actualProviderId === "ollama" ? tr("locale", "local") : "API key";
|
|
4023
4067
|
}
|
|
4024
4068
|
function authSummary(providerId, providers) {
|
|
4025
4069
|
return providers.map((provider) => authLabel(providerId, provider.id)).join(" / ");
|
|
@@ -4051,11 +4095,11 @@ function canonicalProviderId(providerId) {
|
|
|
4051
4095
|
}
|
|
4052
4096
|
function authHint(providerId) {
|
|
4053
4097
|
if (providerId === "anthropic")
|
|
4054
|
-
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."));
|
|
4055
4099
|
if (providerId === "openai")
|
|
4056
|
-
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."));
|
|
4057
4101
|
const spec = PROVIDER_CATALOG.find((item) => item.id === providerId);
|
|
4058
|
-
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."));
|
|
4059
4103
|
}
|
|
4060
4104
|
function unique(values) {
|
|
4061
4105
|
return [...new Set(values)];
|
|
@@ -4083,7 +4127,7 @@ async function showReadonlyOverlay(opts) {
|
|
|
4083
4127
|
opts.theme.bold(` ${opts.title}`),
|
|
4084
4128
|
opts.theme.dark(` ${"─".repeat(Math.max(8, width - 4))}`),
|
|
4085
4129
|
...body.map((line) => ` ${opts.theme.silver(truncate(line, Math.max(12, width - 4)))}`),
|
|
4086
|
-
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")}`),
|
|
4087
4131
|
];
|
|
4088
4132
|
opts.ctl.setOverlay(rows);
|
|
4089
4133
|
};
|