talos-code 0.0.1 → 0.2.0
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/CHANGELOG.md +87 -0
- package/LICENSE +661 -0
- package/README.md +116 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +100 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +244 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +46 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +92 -0
- package/dist/i18n/en/errors.js +267 -0
- package/dist/i18n/en/firstrun.js +101 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +774 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +331 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/openrouter-login.js +172 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-archive.js +69 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +265 -0
- package/dist/runtime/talos-composition.js +1011 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/agent-view.js +51 -0
- package/dist/tui/app.js +4098 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +560 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +38 -0
- package/dist/tui/components/terminal-shell.js +214 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/descendant-approvals.js +104 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +148 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +56 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +43 -0
- package/dist/tui/overlays/approval-dialog.js +642 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +77 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +171 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +289 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +791 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/setup-wizard.js +46 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +82 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +27 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +64 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* B1 slice 18 — provider first, then model.
|
|
3
|
+
*
|
|
4
|
+
* `listProviders` says, for each chat destination, whether a key is usable and where it would come from;
|
|
5
|
+
* `providerModels` is the live list of ONE provider, or the list TALOS already knows marked not verified;
|
|
6
|
+
* `setProviderKey` tests a key against its own provider and saves it only when the test passes;
|
|
7
|
+
* `readiness` is what a send asks before anything leaves the machine.
|
|
8
|
+
*/
|
|
9
|
+
import { providerDescriptionText, providerLabelText } from "../provider/provider-text.js";
|
|
10
|
+
import { REASONING_EFFORTS } from "../config/types.js";
|
|
11
|
+
import { providerOfModel } from "../provider/environment-keys.js";
|
|
12
|
+
import { benchCauseText, benchedKeyRefusal } from "../provider/health.js";
|
|
13
|
+
import { missingKeyText, providerLabel } from "../provider/missing-key.js";
|
|
14
|
+
import { catalogSourceFromKernel, mergeModelRows, normalizeModelRows } from "../provider/model-catalog.js";
|
|
15
|
+
import { t, tn } from "../i18n/index.js";
|
|
16
|
+
import { kernelProbeDetail, looksItalian } from "../i18n/kernel-map.js";
|
|
17
|
+
/* R5a lane R, S2 (Q-R5-3): a live list that carries a model's window or output limit feeds its profile. */
|
|
18
|
+
import { rememberProviderModelRows } from "../runtime/model-profile.js";
|
|
19
|
+
function collectReasoningEfforts(...sources) {
|
|
20
|
+
let observed = false;
|
|
21
|
+
const out = [];
|
|
22
|
+
for (const source of sources) {
|
|
23
|
+
if (!Array.isArray(source))
|
|
24
|
+
continue;
|
|
25
|
+
observed = true;
|
|
26
|
+
for (const value of source) {
|
|
27
|
+
if (typeof value !== 'string' || !REASONING_EFFORTS.includes(value))
|
|
28
|
+
continue;
|
|
29
|
+
const effort = value;
|
|
30
|
+
if (!out.includes(effort))
|
|
31
|
+
out.push(effort);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return observed ? out : null;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeModels(rows, provider, available) {
|
|
37
|
+
return normalizeModelRows(rows, provider, available === 'remote' ? 'live' : available);
|
|
38
|
+
}
|
|
39
|
+
export function availabilityFromCatalog(source, successfulCatalogBoundary = false) {
|
|
40
|
+
return catalogSourceFromKernel(source, { successfulCatalogBoundary });
|
|
41
|
+
}
|
|
42
|
+
export function mergeModels(...groups) {
|
|
43
|
+
return mergeModelRows(...groups.flat());
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Providers whose kernel probe cannot verify a key, measured 2026-09-17 against the provider's own documentation.
|
|
47
|
+
* OpenRouter: `GET /api/v1/models` has no security block (public), so the kernel probe answers "connected" for any
|
|
48
|
+
* key; `GET /api/v1/models/user` requires the bearer token (401 without it). Source: openrouterteam/docs
|
|
49
|
+
* `openapi/openapi.yaml`, read through Context7 on 2026-09-17.
|
|
50
|
+
*/
|
|
51
|
+
const AUTHENTICATED_KEY_CHECK = Object.freeze({ openrouter: { path: '/models/user' } });
|
|
52
|
+
/*
|
|
53
|
+
* B1 slice 25 — slice 18's interim one-token table (`ONE_TOKEN_KEY_CHECK`) is gone. The kernel registry now declares the minimal
|
|
54
|
+
* request of DeepInfra, Novita, Ollama Cloud and Hugging Face as it did for Z.ai's Anthropic port (CLI-REQ-06, desktop lane
|
|
55
|
+
* `95716162`), and its probe sends it, reads the body under a 64 KiB cap, and states `credenzialeVerificata`. The key test asks the
|
|
56
|
+
* kernel for every provider that declares it (`testProviderKey` below).
|
|
57
|
+
*/
|
|
58
|
+
/** Live lists this CLI fetches itself, because the kernel's direct catalog does not cover them. No key is sent. */
|
|
59
|
+
const PUBLIC_MODEL_LIST = Object.freeze({ openrouter: { path: '/models' } });
|
|
60
|
+
const KEY_TEST_TIMEOUT_MS = 30_000;
|
|
61
|
+
/*
|
|
62
|
+
* R4 Q-R4-10 (gate E10): the models a LOCAL engine has installed, read by this CLI. The kernel's direct catalogue leaves local
|
|
63
|
+
* engines out, so before R4 the model picker for Ollama said "could not be reached" while Ollama was running. The registry
|
|
64
|
+
* declares where each engine lists its models (`catalogo: {fonte:'runtime-locale', forma, percorso}`); the rows are read the way
|
|
65
|
+
* the kernel's own local runtime reads them (`harness-ui/src/openai-compatible-runtime.mjs`, normalizeOllamaModel and
|
|
66
|
+
* normalizeLmModel). No key is sent, a redirect is refused, the answer is capped and the wait is short: it is this computer.
|
|
67
|
+
*/
|
|
68
|
+
const LOCAL_LIST_TIMEOUT_MS = 3_000;
|
|
69
|
+
const LOCAL_LIST_MAX_BYTES = 4 * 1024 * 1024;
|
|
70
|
+
function localCatalogOf(record) {
|
|
71
|
+
const catalog = record?.catalogo;
|
|
72
|
+
return catalog?.fonte === 'runtime-locale' && (catalog.forma === 'ollama-tags' || catalog.forma === 'lmstudio-models') && typeof catalog.percorso === 'string' && catalog.percorso.startsWith('/') ? { forma: catalog.forma, percorso: catalog.percorso } : null;
|
|
73
|
+
}
|
|
74
|
+
/* The engine's own root: an address saved for its OpenAI port (`…/v1`) lists models at the root, as the engines document. */
|
|
75
|
+
function engineRoot(endpoint) { return endpoint.trim().replace(/\/+$/u, '').replace(/\/v1$/u, ''); }
|
|
76
|
+
/* The address as the person reads it: scheme, host and path, never a user name or password. */
|
|
77
|
+
function shownAddress(root) { try {
|
|
78
|
+
const url = new URL(root);
|
|
79
|
+
return `${url.protocol}//${url.host}${url.pathname.replace(/\/+$/u, '')}`;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return root;
|
|
83
|
+
} }
|
|
84
|
+
function ollamaRows(body) {
|
|
85
|
+
if (!Array.isArray(body?.models))
|
|
86
|
+
return null;
|
|
87
|
+
return body.models.flatMap((raw) => {
|
|
88
|
+
const id = typeof raw?.name === 'string' && raw.name.trim() ? raw.name : '';
|
|
89
|
+
if (!id)
|
|
90
|
+
return [];
|
|
91
|
+
const details = raw.details ?? {};
|
|
92
|
+
const families = Array.isArray(details.families) ? details.families : null;
|
|
93
|
+
return [{ id, name: id,
|
|
94
|
+
...(Number.isSafeInteger(details.context_length) && details.context_length > 0 ? { contextLength: details.context_length } : {}),
|
|
95
|
+
...(families ? { inputModalities: families.some((family) => /(?:clip|vision|llava)/iu.test(String(family))) ? ['text', 'image'] : ['text'] } : {}) }];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function lmstudioRows(body) {
|
|
99
|
+
if (!Array.isArray(body?.models))
|
|
100
|
+
return null;
|
|
101
|
+
return body.models.flatMap((raw) => {
|
|
102
|
+
if (raw?.type !== 'llm' || typeof raw.key !== 'string' || raw.key.trim() === '')
|
|
103
|
+
return [];
|
|
104
|
+
const caps = raw.capabilities ?? {};
|
|
105
|
+
const allowed = Array.isArray(caps.reasoning?.allowed_options) ? caps.reasoning.allowed_options : [];
|
|
106
|
+
return [{ id: raw.key, name: typeof raw.display_name === 'string' && raw.display_name ? raw.display_name : raw.key,
|
|
107
|
+
...(Number.isSafeInteger(raw.max_context_length) && raw.max_context_length > 0 ? { contextLength: raw.max_context_length } : {}),
|
|
108
|
+
...(typeof caps.trained_for_tool_use === 'boolean' ? { toolCalling: caps.trained_for_tool_use } : {}),
|
|
109
|
+
...(typeof caps.vision === 'boolean' ? { inputModalities: caps.vision ? ['text', 'image'] : ['text'] } : {}),
|
|
110
|
+
reasoning: allowed.length > 0, ...(allowed.length ? { reasoningEfforts: allowed } : {}) }];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/* R4 Q-R4-16 (a): the probe's detail reaches the screen (the provider list falls back to it) in English: the kernel's sentence
|
|
114
|
+
by its pinned template, an English detail as it is, and an Italian sentence no template matches not at all. */
|
|
115
|
+
function probeDetailText(detail, label) {
|
|
116
|
+
const text = String(detail ?? '');
|
|
117
|
+
return kernelProbeDetail(text, { label }) ?? (looksItalian(text) ? '' : text);
|
|
118
|
+
}
|
|
119
|
+
/* Reads at most `cap` bytes of a body; null when it is longer (the rest is not read). */
|
|
120
|
+
async function readCapped(response, cap) {
|
|
121
|
+
if (!response.body)
|
|
122
|
+
return '';
|
|
123
|
+
const reader = response.body.getReader();
|
|
124
|
+
const chunks = [];
|
|
125
|
+
let size = 0;
|
|
126
|
+
try {
|
|
127
|
+
for (;;) {
|
|
128
|
+
const { value, done } = await reader.read();
|
|
129
|
+
if (done)
|
|
130
|
+
break;
|
|
131
|
+
size += value.byteLength;
|
|
132
|
+
if (size > cap)
|
|
133
|
+
return null;
|
|
134
|
+
chunks.push(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await reader.cancel().catch(() => { });
|
|
139
|
+
}
|
|
140
|
+
return new TextDecoder().decode(Buffer.concat(chunks));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* B1 slice 25 — what the kernel's public row says about a provider whose stored keys cannot be used now, or `null` when it stores
|
|
144
|
+
* none. Read from `listPublic`'s pool entries (`stato`, `inPanchinaFino`, `causa`). ⛔ Call it only when the key usable now
|
|
145
|
+
* (`getKey`) is absent: the pool is the description, `getKey` is the decision.
|
|
146
|
+
*/
|
|
147
|
+
export function keyBench(row) {
|
|
148
|
+
const pool = Array.isArray(row?.pool) ? row.pool : [];
|
|
149
|
+
const benched = pool.filter(entry => entry?.stato === 'in-panchina');
|
|
150
|
+
if (row?.keyConfigured !== true && benched.length === 0)
|
|
151
|
+
return null;
|
|
152
|
+
const timed = benched.filter(entry => Number.isFinite(entry?.inPanchinaFino)).sort((a, b) => a.inPanchinaFino - b.inPanchinaFino);
|
|
153
|
+
const first = timed[0] ?? benched[0] ?? null;
|
|
154
|
+
/* R2 (E1): the one bench-cause table, in the dictionary through `provider/health.ts`. */
|
|
155
|
+
const cause = benchCauseText(first?.causa);
|
|
156
|
+
return { cause, until: Number.isFinite(first?.inPanchinaFino) ? Number(first.inPanchinaFino) : null, keys: Math.max(1, pool.length) };
|
|
157
|
+
}
|
|
158
|
+
/* `benchClock` and `benchedKeyText` live in `provider/health.ts` since R0 (one benched-key sentence for every path); re-exported here. */
|
|
159
|
+
export { benchClock, benchedKeyText } from "../provider/health.js";
|
|
160
|
+
/**
|
|
161
|
+
* The refusal of a send whose provider has no key usable now: missing, or stored and benched.
|
|
162
|
+
* R2 (E8 / Q-R2-7): `provider` is the provider id (a label is still understood, for callers that only hold a row's label); a
|
|
163
|
+
* missing key is the one sentence of `missingKeyText`, the same as `-p`, `provider test` and `doctor`.
|
|
164
|
+
*/
|
|
165
|
+
export function unusableKeyRefusal(provider, bench, now = Date.now()) {
|
|
166
|
+
if (!bench)
|
|
167
|
+
return { code: 'PROVIDER_KEY_MISSING', message: missingKeyText(provider) };
|
|
168
|
+
return { code: 'PROVIDER_KEY_BENCHED', message: benchedKeyRefusal(providerLabel(provider), bench, now) };
|
|
169
|
+
}
|
|
170
|
+
/** ⛔ The only outcome that saves a key. */
|
|
171
|
+
const PASSED = new Set(['valid']);
|
|
172
|
+
/** A key as providers issue it, after trimming: printable ASCII 0x21-0x7E, no space, tab, line break, DEL or non-ASCII. */
|
|
173
|
+
const PRINTABLE_KEY = /^[\x21-\x7e]{1,4096}$/u;
|
|
174
|
+
function codeOf(error) { const code = error?.code; return typeof code === 'string' && /^[A-Z0-9_]{1,64}$/u.test(code) ? code : 'UNAVAILABLE'; }
|
|
175
|
+
function joinPath(endpoint, path) { return `${String(endpoint).replace(/\/+$/u, '')}${path}`; }
|
|
176
|
+
export function keyTestMessage(outcome, label, httpStatus) {
|
|
177
|
+
const status = httpStatus === null ? '' : ` (HTTP ${httpStatus})`;
|
|
178
|
+
const notVerified = 'The key was not verified.';
|
|
179
|
+
switch (outcome) {
|
|
180
|
+
case 'valid': return `${label} accepted the key${status}.`;
|
|
181
|
+
case 'invalid': return `${label} refused the key${status}.`;
|
|
182
|
+
case 'permission': return `${label} recognised the key but denied permission${status}.`;
|
|
183
|
+
case 'rate-limited': return `${label} answered HTTP ${httpStatus ?? 429}: rate-limited; try again shortly. ${notVerified}`;
|
|
184
|
+
case 'not-verified': return httpStatus !== null && httpStatus >= 200 && httpStatus < 300
|
|
185
|
+
? `${label} answered HTTP ${httpStatus} without a well-formed response. ${notVerified}`
|
|
186
|
+
: httpStatus !== null ? `${label} answered HTTP ${httpStatus}, which does not show that the key is valid. ${notVerified}`
|
|
187
|
+
: `${label} did not give an answer that shows the key is valid. ${notVerified}`;
|
|
188
|
+
case 'redirected': return `${label} answered with a redirect, and TALOS does not send a key on to another address. ${notVerified}`;
|
|
189
|
+
case 'unreachable': return `${label} could not be reached, so the key could not be tested.`;
|
|
190
|
+
case 'unverifiable': return `TALOS has no way to verify a ${label} key.`;
|
|
191
|
+
case 'malformed': return 'The key is empty or contains a character no provider issues (only printable ASCII without spaces is accepted). Nothing was sent.';
|
|
192
|
+
default: return httpStatus !== null && httpStatus >= 500 ? `${label} answered HTTP ${httpStatus}, a server error. ${notVerified}` : `${label} answered with an error${status}. ${notVerified}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/** An HTTP status that is not an affirmative acceptance. 2xx is decided by the caller, which knows whether the body was checked. */
|
|
196
|
+
function refusedStatus(httpStatus) {
|
|
197
|
+
if (httpStatus === 401)
|
|
198
|
+
return 'invalid';
|
|
199
|
+
if (httpStatus === 403)
|
|
200
|
+
return 'permission';
|
|
201
|
+
if (httpStatus === 429)
|
|
202
|
+
return 'rate-limited';
|
|
203
|
+
if (httpStatus >= 500)
|
|
204
|
+
return 'error';
|
|
205
|
+
return 'not-verified';
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The kernel probe's answer about a candidate key, read by its written contract (`provider-probe.mjs:225-235`):
|
|
209
|
+
* - `credenzialeVerificata === true` → `valid`, the only answer that saves;
|
|
210
|
+
* - `false` → judged, not confirmed: `non-autorizzato` is a refusal naming its status (401 `invalid`, 403 `permission`),
|
|
211
|
+
* `collegato` reached a public catalogue that says nothing of this key (`unverifiable`), any other is `not-verified`;
|
|
212
|
+
* - anything else (`null`, and off contract `undefined` or a non-boolean) → not judged, "could not tell": unreachable, no way to
|
|
213
|
+
* probe, or the status that answered without reading the key. ⛔ Never `invalid` or `permission`, whatever the status.
|
|
214
|
+
*/
|
|
215
|
+
export function classifyKeyTest(result) {
|
|
216
|
+
const httpStatus = Number.isInteger(result?.httpStatus) ? Number(result.httpStatus) : null;
|
|
217
|
+
const verdict = result?.credenzialeVerificata;
|
|
218
|
+
if (verdict === true)
|
|
219
|
+
return { outcome: result?.esito === 'collegato' ? 'valid' : 'not-verified', httpStatus };
|
|
220
|
+
if (verdict === false) {
|
|
221
|
+
if (result?.esito === 'non-autorizzato')
|
|
222
|
+
return { outcome: httpStatus === 403 ? 'permission' : 'invalid', httpStatus };
|
|
223
|
+
return { outcome: result?.esito === 'collegato' ? 'unverifiable' : 'not-verified', httpStatus };
|
|
224
|
+
}
|
|
225
|
+
if (result?.esito === 'irraggiungibile')
|
|
226
|
+
return { outcome: 'unreachable', httpStatus };
|
|
227
|
+
if (result?.esito === 'non-sondabile' || result?.esito === 'non-provabile')
|
|
228
|
+
return { outcome: 'unverifiable', httpStatus };
|
|
229
|
+
if (httpStatus === 429)
|
|
230
|
+
return { outcome: 'rate-limited', httpStatus };
|
|
231
|
+
if (httpStatus !== null && httpStatus >= 500)
|
|
232
|
+
return { outcome: 'error', httpStatus };
|
|
233
|
+
return { outcome: httpStatus !== null || result?.esito === 'collegato' ? 'not-verified' : 'error', httpStatus };
|
|
234
|
+
}
|
|
235
|
+
/** Node's fetch with `redirect:'error'` rejects with this cause (measured on Node 24.18: TypeError "fetch failed", cause "unexpected redirect"). */
|
|
236
|
+
function isRefusedRedirect(error) { return /unexpected redirect/u.test(String(error?.cause?.message ?? '')); }
|
|
237
|
+
export function createTuiCatalogService(deps) {
|
|
238
|
+
const labelOf = (id) => providerLabelText(id, String(deps.registry[id]?.etichetta ?? id));
|
|
239
|
+
const listProviders = async () => {
|
|
240
|
+
if (deps.controlPlane) {
|
|
241
|
+
return deps.controlPlane.list().map(profile => ({
|
|
242
|
+
id: profile.id, label: profile.label, description: profile.description,
|
|
243
|
+
configured: profile.auth.required ? profile.auth.usable : profile.id === 'esterno' ? profile.execution === 'configurato' : true,
|
|
244
|
+
requiresKey: profile.auth.required, authType: profile.auth.type, local: profile.local, cloud: profile.cloud,
|
|
245
|
+
endpointHost: profile.endpoint.host, supportsEndpoint: profile.endpoint.supportsCustom, supportsOAuth: profile.supportsOAuth,
|
|
246
|
+
execution: profile.execution, keySource: profile.auth.source, environmentVariable: profile.auth.environmentVariable,
|
|
247
|
+
benched: profile.cooldown.active ? { cause: profile.cooldown.cause, until: profile.cooldown.until, keys: Math.max(1, profile.cooldown.keys) } : null,
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
const publicRows = deps.publicProviders() ?? [];
|
|
251
|
+
const byId = new Map(publicRows.map(row => [String(row?.id ?? ''), row]));
|
|
252
|
+
return Object.values(deps.registry).filter((record) => record?.destinazioneChat === true).map((record) => {
|
|
253
|
+
const row = byId.get(record.id) ?? {};
|
|
254
|
+
const runtime = deps.runtimeFor(record.id) ?? {};
|
|
255
|
+
const endpoint = row.endpoint ?? runtime.endpoint ?? record.baseUrl ?? null;
|
|
256
|
+
let endpointHost = null;
|
|
257
|
+
try {
|
|
258
|
+
endpointHost = endpoint ? new URL(String(endpoint)).host : null;
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
endpointHost = null;
|
|
262
|
+
}
|
|
263
|
+
const requiresKey = typeof row.requiresKey === 'boolean' ? row.requiresKey : record.chiaveObbligatoria === true;
|
|
264
|
+
const execution = String(row.execution ?? record.esecuzione ?? 'unknown');
|
|
265
|
+
/* ⛔ B1 slice 25: usable means the key usable NOW (`getKey`). The kernel's `keyConfigured` is `hasKey`, which counts a benched
|
|
266
|
+
key too; it says only that a key is stored, which decides where the key comes from and whether it is benched. */
|
|
267
|
+
const usable = Boolean(deps.keyFor(record.id));
|
|
268
|
+
const stored = usable || row.keyConfigured === true;
|
|
269
|
+
const configured = record.id === 'esterno' ? execution === 'configurato' : requiresKey ? usable : true;
|
|
270
|
+
const benched = requiresKey && !usable ? keyBench(row) : null;
|
|
271
|
+
const local = record.wire === 'locale' || record.catalogo?.fonte === 'runtime-locale' || /locale/u.test(String(record.esecuzione ?? ''));
|
|
272
|
+
const environment = requiresKey ? deps.environmentKey?.(record.id) ?? null : null;
|
|
273
|
+
const keySource = !requiresKey ? 'not-required'
|
|
274
|
+
: row.origineChiave === 'custodia' ? 'saved'
|
|
275
|
+
: stored ? 'environment'
|
|
276
|
+
: environment?.state === 'unanswered' ? 'environment-unanswered'
|
|
277
|
+
: environment?.state === 'declined' ? 'environment-declined'
|
|
278
|
+
: 'missing';
|
|
279
|
+
return {
|
|
280
|
+
id: String(record.id), label: providerLabelText(String(record.id), String(row.label ?? record.etichetta ?? record.id)), description: providerDescriptionText(String(record.id), String(record.descrizione ?? '')), configured, requiresKey,
|
|
281
|
+
authType: String(record.auth?.tipo ?? (requiresKey ? 'key' : 'none')), local, cloud: Boolean(record.cloud), endpointHost,
|
|
282
|
+
supportsEndpoint: row.supportsEndpoint === true || record.indirizzoModificabile === true, supportsOAuth: row.supportsOAuth === true || Boolean(record.oauth), execution,
|
|
283
|
+
keySource, environmentVariable: environment?.variable ?? null, benched,
|
|
284
|
+
};
|
|
285
|
+
});
|
|
286
|
+
};
|
|
287
|
+
const enrichRegistryReasoning = (provider, row) => {
|
|
288
|
+
const bare = row.id.startsWith(`${provider}:`) ? row.id.slice(provider.length + 1) : row.id;
|
|
289
|
+
const record = deps.registry[provider] ?? {};
|
|
290
|
+
const compatible = record?.richiestaCompatibile?.modelli?.[bare];
|
|
291
|
+
const known = Array.isArray(record?.modelliNoti) ? record.modelliNoti.find((entry) => String(entry?.id ?? '') === bare) : null;
|
|
292
|
+
const observed = collectReasoningEfforts(compatible?.livelliRagionamento, known?.ragionamento?.livelli);
|
|
293
|
+
if (observed === null)
|
|
294
|
+
return row;
|
|
295
|
+
const evidenceUrl = typeof record?.richiestaCompatibile?.fonte === 'string' ? record.richiestaCompatibile.fonte : typeof known?.fonte === 'string' ? known.fonte : null;
|
|
296
|
+
const evidenceDate = typeof record?.richiestaCompatibile?.data === 'string' ? record.richiestaCompatibile.data : typeof known?.data === 'string' ? known.data : null;
|
|
297
|
+
const [documented] = normalizeModels([{
|
|
298
|
+
id: row.id, name: row.name, reasoning: true, reasoningEfforts: observed,
|
|
299
|
+
...(evidenceUrl ? { fonteMetadati: evidenceUrl } : {}), ...(evidenceDate ? { dataMetadati: evidenceDate } : {}),
|
|
300
|
+
}], provider, 'documented');
|
|
301
|
+
return documented ? mergeModels([row], [documented])[0] ?? row : row;
|
|
302
|
+
};
|
|
303
|
+
const knownModels = async (provider) => {
|
|
304
|
+
const publicRows = deps.publicProviders() ?? [];
|
|
305
|
+
const pub = publicRows.find(x => x?.id === provider);
|
|
306
|
+
const configured = deps.runtimeFor(provider)?.modelli ?? [];
|
|
307
|
+
const fallback = pub?.modelliDiRiserva ?? deps.registry[provider]?.modelliDiRiserva ?? [];
|
|
308
|
+
return mergeModels(normalizeModels(configured, provider, 'configured'), normalizeModels(fallback, provider, 'documented')).map(row => enrichRegistryReasoning(provider, row));
|
|
309
|
+
};
|
|
310
|
+
const endpointOf = (provider) => { const runtime = deps.runtimeFor(provider) ?? {}; return String(runtime.endpoint ?? deps.registry[provider]?.baseUrl ?? ''); };
|
|
311
|
+
const timeoutSignal = (signal) => signal ? AbortSignal.any([signal, AbortSignal.timeout(KEY_TEST_TIMEOUT_MS)]) : AbortSignal.timeout(KEY_TEST_TIMEOUT_MS);
|
|
312
|
+
async function localModels(provider, catalog, signal) {
|
|
313
|
+
const root = engineRoot(endpointOf(provider));
|
|
314
|
+
const address = shownAddress(root);
|
|
315
|
+
if (!/^https?:\/\//iu.test(root) || !deps.fetchImpl)
|
|
316
|
+
return { kind: 'down', address };
|
|
317
|
+
const timeout = AbortSignal.timeout(LOCAL_LIST_TIMEOUT_MS);
|
|
318
|
+
let response;
|
|
319
|
+
try {
|
|
320
|
+
response = await deps.fetchImpl(root + catalog.percorso, { method: 'GET', headers: { Accept: 'application/json' }, redirect: 'error', signal: signal ? AbortSignal.any([signal, timeout]) : timeout });
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
if (signal?.aborted)
|
|
324
|
+
throw error;
|
|
325
|
+
return { kind: isRefusedRedirect(error) ? 'unreadable' : 'down', address };
|
|
326
|
+
}
|
|
327
|
+
if (!response.ok) {
|
|
328
|
+
try {
|
|
329
|
+
await response.body?.cancel();
|
|
330
|
+
}
|
|
331
|
+
catch { /* not read */ }
|
|
332
|
+
return { kind: 'unreadable', address };
|
|
333
|
+
}
|
|
334
|
+
let text;
|
|
335
|
+
try {
|
|
336
|
+
text = await readCapped(response, LOCAL_LIST_MAX_BYTES);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (signal?.aborted)
|
|
340
|
+
throw error;
|
|
341
|
+
return { kind: 'unreadable', address };
|
|
342
|
+
}
|
|
343
|
+
let body = null;
|
|
344
|
+
try {
|
|
345
|
+
body = text === null ? null : JSON.parse(text);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
body = null;
|
|
349
|
+
}
|
|
350
|
+
const raw = catalog.forma === 'ollama-tags' ? ollamaRows(body) : lmstudioRows(body);
|
|
351
|
+
if (!raw)
|
|
352
|
+
return { kind: 'unreadable', address };
|
|
353
|
+
return { kind: 'ok', rows: normalizeModels(raw, provider, 'live') };
|
|
354
|
+
}
|
|
355
|
+
async function liveModels(provider, signal) {
|
|
356
|
+
const own = PUBLIC_MODEL_LIST[provider];
|
|
357
|
+
if (own) {
|
|
358
|
+
if (!deps.fetchImpl)
|
|
359
|
+
throw Object.assign(new Error('PROVIDER_CATALOG_UNAVAILABLE'), { code: 'PROVIDER_CATALOG_UNAVAILABLE' });
|
|
360
|
+
let response;
|
|
361
|
+
try {
|
|
362
|
+
response = await deps.fetchImpl(joinPath(endpointOf(provider), own.path), { method: 'GET', headers: { Accept: 'application/json' }, redirect: 'error', signal: timeoutSignal(signal) });
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
if (error?.name === 'AbortError' && signal?.aborted)
|
|
366
|
+
throw error;
|
|
367
|
+
throw Object.assign(new Error('CATALOG_UNREACHABLE'), { code: 'CATALOG_UNREACHABLE' });
|
|
368
|
+
}
|
|
369
|
+
if (!response.ok)
|
|
370
|
+
throw Object.assign(new Error('CATALOG_UPSTREAM_ERROR'), { code: 'CATALOG_UPSTREAM_ERROR', httpStatus: response.status });
|
|
371
|
+
const body = await response.json().catch(() => null);
|
|
372
|
+
if (!Array.isArray(body?.data))
|
|
373
|
+
throw Object.assign(new Error('CATALOG_UPSTREAM_ERROR'), { code: 'CATALOG_UPSTREAM_ERROR' });
|
|
374
|
+
rememberProviderModelRows(provider, body.data);
|
|
375
|
+
return normalizeModels(body.data, provider, 'live');
|
|
376
|
+
}
|
|
377
|
+
const remote = await deps.createProbe(signal).elencaModelli(provider);
|
|
378
|
+
const source = catalogSourceFromKernel(remote?.fonte, { successfulCatalogBoundary: true });
|
|
379
|
+
if (source === 'documented' || source === 'configured')
|
|
380
|
+
return null;
|
|
381
|
+
rememberProviderModelRows(provider, remote?.modelli ?? []);
|
|
382
|
+
return normalizeModels(remote?.modelli ?? [], provider, source);
|
|
383
|
+
}
|
|
384
|
+
/** The kernel declares a minimal generation for this provider: only the key test may ask the probe to send it. */
|
|
385
|
+
const declaresMinimalRequest = (id) => Boolean(deps.registry[id]?.sonda?.richiestaMinima);
|
|
386
|
+
function keyTestNotice(id) {
|
|
387
|
+
if (!deps.registry[id] || !declaresMinimalRequest(id))
|
|
388
|
+
return null;
|
|
389
|
+
return `One message with a one-token limit is sent to ${labelOf(id)} to test the key and may use a small amount of credit.`;
|
|
390
|
+
}
|
|
391
|
+
async function testProviderKey(id, secret, signal) {
|
|
392
|
+
const record = deps.registry[id];
|
|
393
|
+
if (!record)
|
|
394
|
+
throw Object.assign(new Error('PROVIDER_INVALID'), { code: 'PROVIDER_INVALID' });
|
|
395
|
+
const label = labelOf(id);
|
|
396
|
+
const candidate = typeof secret === 'string' ? secret.trim() : '';
|
|
397
|
+
const done = (outcome, httpStatus) => ({ provider: id, label, outcome, passed: PASSED.has(outcome), httpStatus, message: keyTestMessage(outcome, label, httpStatus) });
|
|
398
|
+
/* ⛔ Measured with Node's fetch: `€` or DEL make fetch throw before sending, while a tab, a non-breaking space, `é` or an inner
|
|
399
|
+
space are sent as they are. No provider issues such a key, so nothing is sent for it. */
|
|
400
|
+
if (!PRINTABLE_KEY.test(candidate))
|
|
401
|
+
return done('malformed', null);
|
|
402
|
+
const fetchFailure = (error) => {
|
|
403
|
+
if (error?.name === 'AbortError' && signal?.aborted)
|
|
404
|
+
throw error;
|
|
405
|
+
return done(isRefusedRedirect(error) ? 'redirected' : 'unreachable', null);
|
|
406
|
+
};
|
|
407
|
+
const own = AUTHENTICATED_KEY_CHECK[id];
|
|
408
|
+
if (own) {
|
|
409
|
+
if (!deps.fetchImpl)
|
|
410
|
+
return done('unverifiable', null);
|
|
411
|
+
let response;
|
|
412
|
+
try {
|
|
413
|
+
response = await deps.fetchImpl(joinPath(endpointOf(id), own.path), { method: 'GET', headers: { Accept: 'application/json', Authorization: `Bearer ${candidate}` }, redirect: 'error', signal: timeoutSignal(signal) });
|
|
414
|
+
}
|
|
415
|
+
catch (error) {
|
|
416
|
+
return fetchFailure(error);
|
|
417
|
+
}
|
|
418
|
+
try {
|
|
419
|
+
await response.body?.cancel();
|
|
420
|
+
}
|
|
421
|
+
catch { /* the body is never read: it could echo the request */ }
|
|
422
|
+
if (response.status >= 200 && response.status < 300)
|
|
423
|
+
return done('valid', response.status);
|
|
424
|
+
return done(refusedStatus(response.status), response.status);
|
|
425
|
+
}
|
|
426
|
+
if (!deps.createKeyProbe)
|
|
427
|
+
return done('unverifiable', null);
|
|
428
|
+
const probe = deps.createKeyProbe(id, candidate, signal);
|
|
429
|
+
/* ⛔ The ONLY call in this CLI that lets the kernel probe generate: the key test, for a provider that declares its minimal request. */
|
|
430
|
+
const result = declaresMinimalRequest(id) ? await probe.prova(id, { consentiGenerazione: true }) : await probe.prova(id);
|
|
431
|
+
/* ⛔ B1 slice 25: saved only on `credenzialeVerificata === true` (`classifyKeyTest` gives `valid` for nothing else). */
|
|
432
|
+
const classified = classifyKeyTest(result);
|
|
433
|
+
return done(classified.outcome, classified.httpStatus);
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
listProviders,
|
|
437
|
+
async probeProvider(id, signal) {
|
|
438
|
+
if (deps.controlPlane) {
|
|
439
|
+
const profile = await deps.controlPlane.probe(id, signal);
|
|
440
|
+
const health = profile.lastHealth;
|
|
441
|
+
const decision = deps.controlPlane.readiness(id);
|
|
442
|
+
const state = health.state === 'available' ? 'available' : health.state === 'unconfigured' ? 'unconfigured' : health.state === 'unknown' || health.state === 'unprobeable' ? 'unknown' : 'unavailable';
|
|
443
|
+
return { provider: id, state, detail: probeDetailText(health.detail, profile.label ?? labelOf(id)), latencyMs: health.latencyMs, remediation: profile.remediation, readinessCode: decision.ready ? null : decision.code };
|
|
444
|
+
}
|
|
445
|
+
const result = await deps.createProbe(signal).prova(id);
|
|
446
|
+
const state = result?.esito === 'collegato' ? 'available' : result?.codice === 'PROVIDER_KEY_MISSING' ? 'unconfigured' : result?.esito === 'non-provabile' || result?.esito === 'non-sondabile' ? 'unknown' : 'unavailable';
|
|
447
|
+
return { provider: id, state, detail: probeDetailText(result?.motivo ?? result?.esito, labelOf(id)), latencyMs: Number.isFinite(result?.millisecondi) ? Number(result.millisecondi) : null, remediation: null, readinessCode: null };
|
|
448
|
+
},
|
|
449
|
+
async listModels(input = {}) {
|
|
450
|
+
if (input.provider && input.refresh) {
|
|
451
|
+
const remote = await deps.createProbe(input.signal).elencaModelli(input.provider);
|
|
452
|
+
const source = availabilityFromCatalog(remote?.fonte, true);
|
|
453
|
+
return normalizeModels(remote?.modelli ?? [], input.provider, source).map(row => enrichRegistryReasoning(input.provider, row));
|
|
454
|
+
}
|
|
455
|
+
const providers = await listProviders();
|
|
456
|
+
return (await Promise.all(providers.filter(p => !input.provider || p.id === input.provider).map(p => knownModels(p.id)))).flat();
|
|
457
|
+
},
|
|
458
|
+
async providerModels(provider, signal) {
|
|
459
|
+
if (!deps.registry[provider])
|
|
460
|
+
throw Object.assign(new Error('PROVIDER_INVALID'), { code: 'PROVIDER_INVALID' });
|
|
461
|
+
const label = labelOf(provider);
|
|
462
|
+
const known = await knownModels(provider);
|
|
463
|
+
const local = localCatalogOf(deps.registry[provider]);
|
|
464
|
+
if (local) {
|
|
465
|
+
/* R4 Q-R4-10: what the engine has installed; the models configured for it are kept, marked when it does not have them. */
|
|
466
|
+
const result = await localModels(provider, local, signal);
|
|
467
|
+
if (result.kind !== 'ok') {
|
|
468
|
+
const said = t(result.kind === 'down' ? 'firstrun.models.down' : 'firstrun.models.unreadable', { label, address: result.address });
|
|
469
|
+
return { provider, label, rows: known, verified: false, notice: known.length ? `${said} ${t('firstrun.models.knownOnly')}` : said, engine: result.kind };
|
|
470
|
+
}
|
|
471
|
+
const rows = mergeModels(result.rows, known);
|
|
472
|
+
const installed = result.rows.length;
|
|
473
|
+
const missing = rows.filter(row => !row.availabilityVerified).length;
|
|
474
|
+
const said = installed ? tn('firstrun.models.live', installed, { label }) : t('firstrun.models.empty', { label });
|
|
475
|
+
return { provider, label, rows, verified: missing === 0, notice: missing ? `${said} ${tn('firstrun.models.notInstalled', missing, { label })}` : said };
|
|
476
|
+
}
|
|
477
|
+
try {
|
|
478
|
+
const live = await liveModels(provider, signal);
|
|
479
|
+
if (live && live.length) {
|
|
480
|
+
const rows = mergeModels(live, known);
|
|
481
|
+
const liveVerified = rows.filter(row => row.availabilityVerified).length;
|
|
482
|
+
const unverified = rows.length - liveVerified;
|
|
483
|
+
const verified = unverified === 0 && rows.length > 0;
|
|
484
|
+
const notice = verified
|
|
485
|
+
? `Live list from ${label}: ${rows.length} models.`
|
|
486
|
+
: `Catalog from ${label}: ${liveVerified} live-verified; ${unverified} configured/documented/unknown model${unverified === 1 ? ' is' : 's are'} not verified as currently available.`;
|
|
487
|
+
return { provider, label, rows, verified, notice };
|
|
488
|
+
}
|
|
489
|
+
return { provider, label, rows: known, verified: false, notice: `${label} did not return a live list; these are the models TALOS already knows. Not verified.` };
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
if (error?.name === 'AbortError' && signal?.aborted)
|
|
493
|
+
throw error;
|
|
494
|
+
return { provider, label, rows: known, verified: false, notice: `${label} could not be reached (${codeOf(error)}); these are the models TALOS already knows. Not verified.` };
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
async selectModel(id, scope) { if (scope !== 'project-user')
|
|
498
|
+
throw Object.assign(new Error('CONFIG_SCOPE_INVALID'), { code: 'CONFIG_SCOPE_INVALID' }); await deps.persistModel(id); },
|
|
499
|
+
testProviderKey,
|
|
500
|
+
keyTestNotice,
|
|
501
|
+
...(deps.openRouterLogin ? { startOpenRouterLogin: (input) => deps.openRouterLogin(input) } : {}),
|
|
502
|
+
async setProviderKey(id, secret, signal) {
|
|
503
|
+
const test = await testProviderKey(id, secret, signal);
|
|
504
|
+
if (!test.passed)
|
|
505
|
+
return { ...test, saved: false };
|
|
506
|
+
/* ⛔ A key that passed but could not be stored says so, by code only: store errors are never echoed as text. */
|
|
507
|
+
try {
|
|
508
|
+
deps.setKey(id, String(secret).trim());
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
return { ...test, saved: false, message: `${test.message} It could not be saved (${codeOf(error)}).` };
|
|
512
|
+
}
|
|
513
|
+
deps.controlPlane?.invalidate(id);
|
|
514
|
+
return { ...test, saved: true };
|
|
515
|
+
},
|
|
516
|
+
async clearProviderKey(id) { deps.clearKey(id); deps.controlPlane?.invalidate(id); },
|
|
517
|
+
async answerEnvironmentKey(id, answer) {
|
|
518
|
+
if (!deps.answerEnvironmentKey)
|
|
519
|
+
throw Object.assign(new Error('PROVIDER_CONSENT_UNAVAILABLE'), { code: 'PROVIDER_CONSENT_UNAVAILABLE' });
|
|
520
|
+
deps.answerEnvironmentKey(id, answer);
|
|
521
|
+
deps.controlPlane?.invalidate(id);
|
|
522
|
+
},
|
|
523
|
+
async chosenProvider() { return deps.chosenProvider?.() ?? null; },
|
|
524
|
+
async chooseProvider(id) {
|
|
525
|
+
if (!deps.registry[id] || deps.registry[id]?.destinazioneChat !== true)
|
|
526
|
+
throw Object.assign(new Error('PROVIDER_INVALID'), { code: 'PROVIDER_INVALID' });
|
|
527
|
+
if (!deps.chooseProvider)
|
|
528
|
+
throw Object.assign(new Error('PROVIDER_CONSENT_UNAVAILABLE'), { code: 'PROVIDER_CONSENT_UNAVAILABLE' });
|
|
529
|
+
deps.chooseProvider(id);
|
|
530
|
+
},
|
|
531
|
+
async readiness(model) {
|
|
532
|
+
const chosen = deps.chosenProvider?.() ?? null;
|
|
533
|
+
if (!chosen)
|
|
534
|
+
return { ready: false, code: 'PROVIDER_NOT_CHOSEN', provider: null, message: 'No provider has been chosen yet, so nothing was sent. Choose one with /provider.' };
|
|
535
|
+
const provider = providerOfModel(model);
|
|
536
|
+
if (provider !== chosen)
|
|
537
|
+
return { ready: false, code: 'MODEL_NOT_CHOSEN', provider: chosen, message: `No ${labelOf(chosen)} model has been chosen yet, so nothing was sent. Choose one with /model.` };
|
|
538
|
+
const row = (await listProviders()).find(entry => entry.id === provider);
|
|
539
|
+
if (!row)
|
|
540
|
+
return { ready: false, code: 'MODEL_NOT_CHOSEN', provider, message: `${provider} is not a provider TALOS can send to. Choose one with /provider.` };
|
|
541
|
+
if (deps.controlPlane) {
|
|
542
|
+
const decision = deps.controlPlane.readiness(provider);
|
|
543
|
+
if (!decision.ready)
|
|
544
|
+
return { ready: false, code: decision.code, provider, message: decision.message };
|
|
545
|
+
return { ready: true, provider, model };
|
|
546
|
+
}
|
|
547
|
+
/*
|
|
548
|
+
* ⛔ Same predicate as `listProviders` (the key usable now), so the screen never calls ready what the runtime will refuse.
|
|
549
|
+
* B1 slice 25: slice 18's `RUNTIME_OPENROUTER_KEY_REQUIRED` step is gone. The session registry no longer asks every session
|
|
550
|
+
* for an OpenRouter key: it asks the composition whether the session's own model is usable (`prontoFn`, CLI-REQ-05,
|
|
551
|
+
* `harness-ui/src/session-registry.mjs:2859-2876`).
|
|
552
|
+
*/
|
|
553
|
+
if (row.requiresKey && !row.configured) {
|
|
554
|
+
const refusal = unusableKeyRefusal(row.id, row.benched);
|
|
555
|
+
return { ready: false, code: refusal.code, provider, message: refusal.message };
|
|
556
|
+
}
|
|
557
|
+
return { ready: true, provider, model };
|
|
558
|
+
},
|
|
559
|
+
};
|
|
560
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function assistantStreamView(message) { return message ? { label: 'TALOS', id: message.id, text: message.text } : null; }
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
2
|
+
import { SLASH_COMMANDS } from "../slash-commands.js";
|
|
3
|
+
import { filterItems, moveSelection } from "../selection-list.js";
|
|
4
|
+
import { pickerSection } from "../overlays/scroll-window.js";
|
|
5
|
+
function commandQuery(input) { return input.trim().replace(/^\//u, ''); }
|
|
6
|
+
export function commandMenuItems(input) {
|
|
7
|
+
const query = commandQuery(input);
|
|
8
|
+
const items = SLASH_COMMANDS.map(command => ({ id: command.name, label: command.name, searchText: `${command.name} ${command.description}`, value: command }));
|
|
9
|
+
return filterItems(items, query).map((item, index) => ({ command: item.value, score: items.length - index }));
|
|
10
|
+
}
|
|
11
|
+
export function completeCommandSelection(command) { return `/${command.name} `; }
|
|
12
|
+
/*
|
|
13
|
+
* R4 Q-R4-5 / gate E6: which row Enter runs, or -1 for none. The top row is highlighted while the typed text is the start
|
|
14
|
+
* of its name (`/mo` → /model; `/` alone → the first row); a row the person moved to stays highlighted. After a typo (a
|
|
15
|
+
* fuzzy match only) or once arguments are typed nothing is highlighted, so Enter sends the text as typed and the existing
|
|
16
|
+
* "Did you mean" answer appears (Claude Code "Commands" :171-173, 2.1.269 → 2026-09-11, gate B.1).
|
|
17
|
+
*/
|
|
18
|
+
export function commandMenuHighlight(input, selection) {
|
|
19
|
+
const rows = commandMenuItems(input);
|
|
20
|
+
if (!rows.length)
|
|
21
|
+
return -1;
|
|
22
|
+
if (selection.moved)
|
|
23
|
+
return Math.max(0, Math.min(rows.length - 1, Math.trunc(selection.selected) || 0));
|
|
24
|
+
const query = commandQuery(input);
|
|
25
|
+
return rows[0].command.name.startsWith(query) ? 0 : -1;
|
|
26
|
+
}
|
|
27
|
+
const initialCommandMenuSelection = () => ({ query: '', selected: 0, pageSize: 10, moved: false });
|
|
28
|
+
export function createCommandMenuSelectionStore() {
|
|
29
|
+
let snapshot = initialCommandMenuSelection();
|
|
30
|
+
const listeners = new Set();
|
|
31
|
+
const publish = (next) => {
|
|
32
|
+
if (next.query === snapshot.query && next.selected === snapshot.selected && next.pageSize === snapshot.pageSize && Boolean(next.moved) === Boolean(snapshot.moved))
|
|
33
|
+
return snapshot;
|
|
34
|
+
snapshot = next;
|
|
35
|
+
for (const listener of [...listeners])
|
|
36
|
+
listener();
|
|
37
|
+
return snapshot;
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
getSnapshot: () => snapshot,
|
|
41
|
+
subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); },
|
|
42
|
+
reset() { return publish(initialCommandMenuSelection()); },
|
|
43
|
+
move(count, action) { return publish({ ...moveSelection(snapshot, count, action), moved: true }); },
|
|
44
|
+
highlight(index) { return publish({ ...snapshot, selected: Math.max(0, Math.trunc(index) || 0), moved: true }); },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function commandMenuFastTextInput(input) {
|
|
48
|
+
if (input.bootPhase !== 'ready' || input.focus !== 'command-menu' || input.modalOwner)
|
|
49
|
+
return null;
|
|
50
|
+
if (input.key.ctrl || input.key.meta)
|
|
51
|
+
return null;
|
|
52
|
+
if (input.routed?.kind !== 'text' || typeof input.routed.text !== 'string' || input.routed.text.length === 0)
|
|
53
|
+
return null;
|
|
54
|
+
return input.routed.text;
|
|
55
|
+
}
|
|
56
|
+
// R1 Q-R1-3: the slash-command menu as app.ts draws it (single border), capped to lane L's `maxRows` and scrolled
|
|
57
|
+
// around the selection by overlays/scroll-window.ts. R4: `selected` is the highlight (-1: none, see commandMenuHighlight);
|
|
58
|
+
// an empty menu says so and what Enter will do (Q-R4-13; Claude Code `No commands match "/name"`, gate §5.2).
|
|
59
|
+
export function commandMenuOverlay({ text, selected }) {
|
|
60
|
+
const rows = commandMenuItems(text);
|
|
61
|
+
return {
|
|
62
|
+
head: [{ text: t('screen.help.commands'), tone: 'strong' }],
|
|
63
|
+
sections: [rows.length
|
|
64
|
+
? pickerSection(rows, selected, row => `/${row.command.name} · ${row.command.description}`)
|
|
65
|
+
: { lines: [{ text: t('screen.commands.none', { query: commandQuery(text) }), tone: 'dim' }] }],
|
|
66
|
+
borderStyle: 'single',
|
|
67
|
+
};
|
|
68
|
+
}
|