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,1011 @@
|
|
|
1
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
return path;
|
|
8
|
+
};
|
|
9
|
+
import { providerLabelText } from "../provider/provider-text.js";
|
|
10
|
+
/* R5a lane R: the runtime facts (S1-S7, owner decisions Q-R5-3…Q-R5-11). */
|
|
11
|
+
import { secretValuesFromEnvironment } from "../diagnostics/redact.js";
|
|
12
|
+
import { isTestProfile } from "../provider/store.js";
|
|
13
|
+
import { compactionEvent, createCliEventHub, createRunObserver, RETRY_AFTER_MAX_MS, summaryRequestedEvent } from "./provider-attempts.js";
|
|
14
|
+
import { createToolOutputStore, pruneToolOutput } from "./output-store.js";
|
|
15
|
+
import { withArchivableCapture } from "./context-archive.js";
|
|
16
|
+
import { CONTEXT_ENGINE_MIN_WINDOW, configureModelProfiles, createModelsDevLoader, modelProfile, modelProfileEvidence, ollamaWindow, rememberOllamaWindow, setModelOverrides } from "./model-profile.js";
|
|
17
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
import { rm, rmdir } from 'node:fs/promises';
|
|
20
|
+
import { dirname, join } from 'node:path';
|
|
21
|
+
import { pathToFileURL } from 'node:url';
|
|
22
|
+
import { createSessionFacade } from "./session-facade.js";
|
|
23
|
+
import { CliRuntimeError } from "./types.js";
|
|
24
|
+
import { openCredentialStore } from "../provider/store.js";
|
|
25
|
+
import { missingKeyText, useProviderRegistry } from "../provider/missing-key.js";
|
|
26
|
+
import { loadEffectiveConfig } from "../config/load.js";
|
|
27
|
+
import { configSet } from "../config/commands.js";
|
|
28
|
+
import { createTuiCatalogService, keyBench, unusableKeyRefusal } from "../tui/catalog-service.js";
|
|
29
|
+
import { createGuardedPluginTrustVerifier, pluginReviewFromSnapshot, scanPluginPackage } from "../security/plugin-guard.js";
|
|
30
|
+
import { createTrustAuthority } from "../security/trust-authority.js";
|
|
31
|
+
import { createBrokeredKernelExecutor } from "./brokered-executor.js";
|
|
32
|
+
import { createCheckpointStore } from "../workspace/checkpoint-store.js";
|
|
33
|
+
import { developmentLog, developmentLogError } from "../diagnostics/development-log.js";
|
|
34
|
+
import { attachKeyOrigin, createEnvironmentKeyConsent, describeKeyOrigin, providerOfModel } from "../provider/environment-keys.js";
|
|
35
|
+
import { createProviderControlPlane } from "../provider/control-plane.js";
|
|
36
|
+
function remapService(s) { return s.replace(/^talos-harness-/u, 'talos-cli-'); }
|
|
37
|
+
function contextProfileValue(value) { return Number.isSafeInteger(value) && Number(value) > 0 ? Number(value) : null; }
|
|
38
|
+
export function createCliContextBridge({ repoRoot, paths, registry, providerRegistry, providerStore, ownerRuntime, chatImageStore, separaFonteModello, prepareProfile, onSummaryRequest }) {
|
|
39
|
+
/*
|
|
40
|
+
* ⭐ R5a lane R, S2 (owner decision Q-R5-3): the profile — window and reserve — comes from `modelProfile` (model-profile.ts),
|
|
41
|
+
* which layers the person's override, a local Ollama's own window, the provider's rows, the registry's documented rows and
|
|
42
|
+
* the models.dev catalogue. Before R5 only a registry or store row carrying BOTH a window and an output limit made a profile
|
|
43
|
+
* (5 of the registry's 49 rows, all Z.ai), so for every other model the Context Engine stayed off: no 50 % rule, the kernel's
|
|
44
|
+
* every-8-calls compaction and its silent 8,000-character cut instead (gate finding 2). A model with a known window now gets
|
|
45
|
+
* the Context Engine. The risks of that switch, and what is done about each, are measured in r5a-r-context-engine.test.ts:
|
|
46
|
+
* the token counter's preflight (below), the reserve the kernel sends as `max_tokens`, and the switch mid-session when a
|
|
47
|
+
* window becomes known between two runs.
|
|
48
|
+
*/
|
|
49
|
+
const profileForSelection = (selected) => {
|
|
50
|
+
const provider = typeof selected?.provider === 'string' ? selected.provider : null, model = typeof selected?.model === 'string' ? selected.model : null;
|
|
51
|
+
if (!provider || !model)
|
|
52
|
+
return null;
|
|
53
|
+
if (contextProfileValue(selected.windowTokens) && contextProfileValue(selected.responseReserve) && selected.windowTokens > selected.responseReserve)
|
|
54
|
+
return selected.windowTokens >= CONTEXT_ENGINE_MIN_WINDOW ? selected : null;
|
|
55
|
+
const profile = modelProfile({ provider, model });
|
|
56
|
+
/* R5a owner decision: below 64k the Context Engine cannot hold TALOS's own instructions and tools; the simple mode stays. */
|
|
57
|
+
if (!profile || profile.windowTokens < CONTEXT_ENGINE_MIN_WINDOW || profile.windowTokens <= profile.responseReserve)
|
|
58
|
+
return null;
|
|
59
|
+
const evidence = modelProfileEvidence({ provider, model });
|
|
60
|
+
return { provider, model, windowTokens: profile.windowTokens, responseReserve: profile.responseReserve, profileEvidence: { source: evidence?.source ?? profile.source, date: evidence?.date ?? profile.date } };
|
|
61
|
+
};
|
|
62
|
+
const profileForSession = (sessionId, session) => {
|
|
63
|
+
session ??= registry.leggiSessioneContesto?.(sessionId) ?? null;
|
|
64
|
+
if (!session)
|
|
65
|
+
return null;
|
|
66
|
+
if (session.provider === 'local')
|
|
67
|
+
return null;
|
|
68
|
+
let selected = null;
|
|
69
|
+
try {
|
|
70
|
+
selected = typeof separaFonteModello === 'function' ? separaFonteModello(String(session.modello ?? '')) : null;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
selected = null;
|
|
74
|
+
}
|
|
75
|
+
if (!selected?.fonte || !selected?.modelloRemoto)
|
|
76
|
+
return null;
|
|
77
|
+
const profile = profileForSelection({ provider: selected.fonte, model: selected.modelloRemoto });
|
|
78
|
+
if (!profile)
|
|
79
|
+
return null;
|
|
80
|
+
return { ...profile, requestOptions: session.reasoning == null ? {} : { reasoning: structuredClone(session.reasoning) } };
|
|
81
|
+
};
|
|
82
|
+
/* R5a lane R: the asynchronous sources of a session's model (the override file, a local Ollama, models.dev) are brought up
|
|
83
|
+
to date before the profile decides, each bounded by its own timeout; a source that does not answer leaves the profile as
|
|
84
|
+
it was. */
|
|
85
|
+
const prepareForSession = async (sessionId) => {
|
|
86
|
+
if (!prepareProfile)
|
|
87
|
+
return;
|
|
88
|
+
const session = registry.leggiSessioneContesto?.(sessionId) ?? null;
|
|
89
|
+
if (!session || session.provider === 'local')
|
|
90
|
+
return;
|
|
91
|
+
let selected = null;
|
|
92
|
+
try {
|
|
93
|
+
selected = typeof separaFonteModello === 'function' ? separaFonteModello(String(session.modello ?? '')) : null;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
selected = null;
|
|
97
|
+
}
|
|
98
|
+
if (!selected?.fonte || !selected?.modelloRemoto)
|
|
99
|
+
return;
|
|
100
|
+
try {
|
|
101
|
+
await prepareProfile(selected.fonte, selected.modelloRemoto);
|
|
102
|
+
}
|
|
103
|
+
catch { /* a source that fails is no source */ }
|
|
104
|
+
};
|
|
105
|
+
let loaded = null;
|
|
106
|
+
const load = () => loaded ??= (async () => {
|
|
107
|
+
const contextRoot = join(repoRoot, 'context-engine', 'src'), harnessRoot = join(repoRoot, 'harness-ui', 'src');
|
|
108
|
+
const [engineModule, storeModule, adapterModule, serviceModule, counterModule] = await Promise.all([
|
|
109
|
+
import(__rewriteRelativeImportExtension(pathToFileURL(join(contextRoot, 'engine.mjs')).href)),
|
|
110
|
+
import(__rewriteRelativeImportExtension(pathToFileURL(join(contextRoot, 'node', 'sqlite-store.mjs')).href)),
|
|
111
|
+
import(__rewriteRelativeImportExtension(pathToFileURL(join(harnessRoot, 'context-provider-adapter.mjs')).href)),
|
|
112
|
+
import(__rewriteRelativeImportExtension(pathToFileURL(join(harnessRoot, 'context-desktop-service.mjs')).href)),
|
|
113
|
+
import(__rewriteRelativeImportExtension(pathToFileURL(join(harnessRoot, 'context-token-counters.mjs')).href)),
|
|
114
|
+
]);
|
|
115
|
+
const store = storeModule.createSqliteContextStore({ databasePath: join(paths.dataRoot, 'context', 'context.sqlite') });
|
|
116
|
+
const tokenCounter = counterModule.createContextTokenCounter({
|
|
117
|
+
resolveProfile: async (model) => {
|
|
118
|
+
let runtime = {};
|
|
119
|
+
try {
|
|
120
|
+
runtime = providerStore.getRuntime?.(model.provider) ?? {};
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
runtime = {};
|
|
124
|
+
}
|
|
125
|
+
let key = null;
|
|
126
|
+
try {
|
|
127
|
+
key = providerStore.getKey?.(model.provider) ?? null;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
key = null;
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
baseURL: runtime.endpoint ?? providerRegistry[model.provider]?.baseUrl,
|
|
134
|
+
apiKey: key,
|
|
135
|
+
nativeRequestBuilder: (request) => counterModule.buildPreparedDesktopContextRequest(request, chatImageStore ? { resolveImages: (messages) => chatImageStore.resolveMessages(messages) } : {}),
|
|
136
|
+
};
|
|
137
|
+
},
|
|
138
|
+
/*
|
|
139
|
+
* ⛔ R5a lane R, S2 risk 1 (measured, r5a-r-context-engine.test.ts): before each model request the counter POSTs the prepared
|
|
140
|
+
* request, with the key, to a count endpoint. Only three providers document one (OpenAI `/responses/input_tokens`,
|
|
141
|
+
* Anthropic `/messages/count_tokens`, Gemini `:countTokens`); for every other one the kernel guesses
|
|
142
|
+
* `/chat/completions/input_tokens`, which no provider in the registry documents — an extra request per turn that at
|
|
143
|
+
* best answers 404 (then the counter estimates), and at worst 400/401/403/429, which fails the counter
|
|
144
|
+
* (`CTX_TOKEN_HTTP`/`CTX_TOKEN_AUTH`, context-token-counters.mjs:99-101) and with it EVERY turn. So the guessed endpoint is
|
|
145
|
+
* never sent (the counter gets the 404 it already treats as "estimate", and the measurement says `heuristic`), and a
|
|
146
|
+
* documented one that fails, or cannot be reached, is read the same way: a count that could not be taken is an
|
|
147
|
+
* estimate, never a failed turn.
|
|
148
|
+
*/
|
|
149
|
+
fetchFn: async (url, options) => {
|
|
150
|
+
const notCounted = () => new Response(null, { status: 404 });
|
|
151
|
+
if (/\/chat\/completions\/input_tokens$/u.test(new URL(url).pathname))
|
|
152
|
+
return notCounted();
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(url, options);
|
|
155
|
+
if (response.ok)
|
|
156
|
+
return response;
|
|
157
|
+
try {
|
|
158
|
+
await response.body?.cancel();
|
|
159
|
+
}
|
|
160
|
+
catch { /* not read */ }
|
|
161
|
+
return notCounted();
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (options?.signal?.aborted)
|
|
165
|
+
throw error;
|
|
166
|
+
return notCounted();
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
const adapter = adapterModule.createContextModelAdapter({
|
|
171
|
+
resolveModel: ({ sessionModel, settings }) => {
|
|
172
|
+
const selected = settings?.model?.mode === 'explicit' ? settings.model : sessionModel;
|
|
173
|
+
const profile = profileForSelection(selected);
|
|
174
|
+
if (!profile)
|
|
175
|
+
throw Object.assign(new Error('Context profile unavailable.'), { code: 'CTX_MODEL_NOT_CONFIGURED' });
|
|
176
|
+
return profile;
|
|
177
|
+
},
|
|
178
|
+
/* R5b (R5a open 2): every summary the Context Engine asks for is counted, so a turn can say how many were billed. */
|
|
179
|
+
callModel: (request) => { try {
|
|
180
|
+
onSummaryRequest?.();
|
|
181
|
+
}
|
|
182
|
+
catch { /* a counter never stops a summary */ } return ownerRuntime.callContextModel(request); },
|
|
183
|
+
});
|
|
184
|
+
const engine = engineModule.createContextEngine({ store, model: adapter, tokenCounter });
|
|
185
|
+
const service = serviceModule.createDesktopContextService({
|
|
186
|
+
engine, store,
|
|
187
|
+
readSession: (sessionId) => registry.leggiSessioneContesto?.(sessionId) ?? null,
|
|
188
|
+
isSessionEnabled: (sessionId) => Boolean(profileForSession(sessionId)),
|
|
189
|
+
resolveSessionModel: ({ sessionId, session }) => {
|
|
190
|
+
const profile = profileForSession(sessionId, session);
|
|
191
|
+
if (!profile)
|
|
192
|
+
throw Object.assign(new Error('Context profile unavailable.'), { code: 'CTX_MODEL_NOT_CONFIGURED' });
|
|
193
|
+
return profile;
|
|
194
|
+
},
|
|
195
|
+
onEvent: (input) => registry.pubblicaEventoContesto(input),
|
|
196
|
+
});
|
|
197
|
+
await store.health();
|
|
198
|
+
return { store, service };
|
|
199
|
+
})();
|
|
200
|
+
const ensureSettings = async (service, sessionId) => {
|
|
201
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
202
|
+
const snapshot = await service.request({ sessionId, method: 'GET', path: '/' });
|
|
203
|
+
const currentTarget = typeof snapshot?.settings?.targetRatio === 'number' ? snapshot.settings.targetRatio : null;
|
|
204
|
+
const targetRatio = currentTarget !== null && currentTarget > 0 && currentTarget < 0.5 ? currentTarget : 0.49;
|
|
205
|
+
if (snapshot?.settings?.auto === true && snapshot?.settings?.triggerRatio === 0.5 && snapshot?.settings?.targetRatio === targetRatio)
|
|
206
|
+
return snapshot;
|
|
207
|
+
try {
|
|
208
|
+
await service.request({ sessionId, method: 'PATCH', path: '/settings', body: { expectedRevision: snapshot.revision, idempotencyKey: 'cli-m6e-' + randomUUID(), patch: { auto: true, triggerRatio: 0.5, targetRatio } } });
|
|
209
|
+
return service.request({ sessionId, method: 'GET', path: '/' });
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
if (error?.code !== 'CTX_STALE_REVISION' || attempt === 1)
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return service.request({ sessionId, method: 'GET', path: '/' });
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
/* P12: `capture` archives only whole tool exchanges (context-archive.ts), so an interrupted turn cannot leave an
|
|
220
|
+
unanswered call in the archive that the history then drops (CTX_HISTORY_DIVERGED on every later turn). */
|
|
221
|
+
async hooks(input) { await prepareForSession(input.sessionId); const profile = profileForSession(input.sessionId); if (!profile)
|
|
222
|
+
return undefined; const { service } = await load(); await ensureSettings(service, input.sessionId); return withArchivableCapture(await service.createKernelHooks(input)); },
|
|
223
|
+
async compact(input) { await prepareForSession(input.sessionId); const profile = profileForSession(input.sessionId); if (!profile)
|
|
224
|
+
return undefined; const { service } = await load(); await ensureSettings(service, input.sessionId); return service.compact(input); },
|
|
225
|
+
async status(sessionId) {
|
|
226
|
+
await prepareForSession(sessionId);
|
|
227
|
+
const profile = profileForSession(sessionId);
|
|
228
|
+
if (!profile)
|
|
229
|
+
return { unavailableReason: 'profile-unavailable' };
|
|
230
|
+
const { store } = await load();
|
|
231
|
+
const snapshot = await store.readContextSnapshot({ sessionId });
|
|
232
|
+
const currentTarget = typeof snapshot?.settings?.targetRatio === 'number' ? snapshot.settings.targetRatio : null;
|
|
233
|
+
const targetRatio = currentTarget !== null && currentTarget > 0 && currentTarget < 0.5 ? currentTarget : 0.49;
|
|
234
|
+
const effective = snapshot ?? { revision: 0, measurement: null, activeVersion: null };
|
|
235
|
+
return { ...effective, settings: { ...(snapshot?.settings ?? {}), auto: true, triggerRatio: 0.5, targetRatio }, cliProfileEvidence: profile.profileEvidence ?? null };
|
|
236
|
+
},
|
|
237
|
+
async close() { if (!loaded)
|
|
238
|
+
return; const { service, store } = await loaded; try {
|
|
239
|
+
await service.close();
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
await store.close();
|
|
243
|
+
} },
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export function scopeCliKeyring(raw) { if (!raw)
|
|
247
|
+
return null; return { get: (s, a) => raw.get(remapService(s), a), set: (s, a, v) => raw.set(remapService(s), a, v), remove: (s, a) => raw.remove(remapService(s), a) }; }
|
|
248
|
+
/** The kernel modules the product composes with. Exported so a test can observe one of them while every other stays the production one. */
|
|
249
|
+
export async function loadModules(repoRoot) { const src = join(repoRoot, 'harness-ui', 'src'); const [cred, owner, agent, sessions, plugins, search, duck, providerRegistry, probe, destination, readiness, chatImages] = await Promise.all([import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'provider-credential-store.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'runtime-owner-adapter.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'agent-service.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'session-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'plugin-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'search-source-store.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'duckduckgo-search.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'provider-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'provider-probe.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'model-destination.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'sessione-pronta.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'chat-image-attachments.mjs')).href))]); return { createProviderCredentialStore: cred.createProviderCredentialStore, createOwnerRuntimeAdapter: owner.createOwnerRuntimeAdapter, creaFetchMultiProvider: owner.creaFetchMultiProvider, compattaSessione: agent.compattaSessione, chiediAlModelloUnaVolta: agent.chiediAlModelloUnaVolta, avviaSessione: agent.avviaSessione, eseguiComandoDiretto: agent.eseguiComandoDiretto, createSessionRegistry: sessions.createSessionRegistry, verificaTrustPlugin: plugins.verificaTrustPlugin, createSearchSourceStore: search.createSearchSourceStore, creaTrasportoSenzaChiave: duck.creaTrasportoSenzaChiave, ENDPOINT_SENTINELLA_DUCKDUCKGO: duck.ENDPOINT_SENTINELLA_DUCKDUCKGO, REGISTRO_FORNITORI: providerRegistry.REGISTRO_FORNITORI, createProviderProbe: probe.createProviderProbe, separaFonteModello: destination.separaFonteModello, creaProntoFn: readiness.creaProntoFn, createChatImageStore: chatImages.createChatImageStore }; }
|
|
250
|
+
async function loadTrustSupport(repoRoot) {
|
|
251
|
+
const src = join(repoRoot, 'harness-ui', 'src');
|
|
252
|
+
const [plugins, hooks, mcp, mcpSession, pluginSession] = await Promise.all([import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'plugin-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'hook-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'mcp-registry.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'mcp-session.mjs')).href)), import(__rewriteRelativeImportExtension(pathToFileURL(join(src, 'plugin-session.mjs')).href))]);
|
|
253
|
+
return { caricaPlugin: plugins.caricaPlugin, fidaPlugin: plugins.fidaPlugin, caricaHooks: hooks.caricaHooks, fidaHook: hooks.fidaHook, eseguiHook: hooks.eseguiHook, caricaServerMcp: mcp.caricaServerMcp, fidaServerMcp: mcp.fidaServerMcp, preparaToolMcpPerSessione: mcpSession.preparaToolMcpPerSessione, nomeEspostoMcp: mcpSession.nomeEspostoMcp, preparaToolPluginPerSessione: pluginSession.preparaToolPluginPerSessione };
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* ⭐ B1 slice 18 — THE ENVIRONMENT THAT REACHES THE CREDENTIAL STORE.
|
|
257
|
+
*
|
|
258
|
+
* `environmentKeys:'use'` hands the store the whole environment, as before: a non-interactive run
|
|
259
|
+
* (`talos -p`, CI) uses a key it finds there and states where it came from.
|
|
260
|
+
* `environmentKeys:'consent'` hands it the environment WITHOUT any provider-key variable whose current
|
|
261
|
+
* value has not been approved in `/provider` (`provider/environment-keys.ts`). The default is
|
|
262
|
+
* `consent`: a caller that forgets to choose gets the closed behaviour, not the open one.
|
|
263
|
+
* ⛔ The store reads the environment once, when it is built, and the kernel keeps a reference to it.
|
|
264
|
+
* An answer given mid-session therefore rebuilds the store behind a forwarder whose identity never
|
|
265
|
+
* changes, so the kernel sees the approved key from the next request on.
|
|
266
|
+
*/
|
|
267
|
+
function forwardingStore(current, shape) { const out = {}; for (const name of Object.keys(shape ?? {})) {
|
|
268
|
+
if (typeof shape[name] === 'function')
|
|
269
|
+
out[name] = (...args) => current()[name](...args);
|
|
270
|
+
} return Object.freeze(out); }
|
|
271
|
+
export async function composeTalosRuntime({ repoRoot, projectRoot, paths, model, env = process.env, environmentKeys = 'consent', modules, keyring, brokerFactory }) {
|
|
272
|
+
const m = modules ?? await loadModules(repoRoot);
|
|
273
|
+
const trustM = modules ? m : { ...m, ...await loadTrustSupport(repoRoot) }; /*
|
|
274
|
+
* R2 (gate E13, Q-R2-5): the store comes from the ONE resolver (`provider/store.ts`), `system` or `memory`, chosen by
|
|
275
|
+
* TALOS_CREDENTIAL_STORE or `credentials.store`; a test profile that asks for `system` is refused here, before the native module
|
|
276
|
+
* is resolved. An injected `keyring` (tests, `create-runtime.ts` input) is used as given. A configuration that cannot be read
|
|
277
|
+
* leaves the choice to the variable and the default: the config layer reports its own error where the person looks.
|
|
278
|
+
*/
|
|
279
|
+
let storeConfig = null;
|
|
280
|
+
if (keyring === undefined) {
|
|
281
|
+
try {
|
|
282
|
+
storeConfig = (await loadEffectiveConfig({ paths, projectRoot })).value;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
storeConfig = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const opened = keyring === undefined ? openCredentialStore({ repoRoot, scope: paths.dataRoot, env: { ...process.env, ...env }, config: storeConfig }) : null;
|
|
289
|
+
const credentialStore = opened ? { kind: opened.kind, source: opened.source, testProfile: opened.testProfile } : null;
|
|
290
|
+
const raw = keyring === undefined ? opened.keyring : keyring;
|
|
291
|
+
const cliKeyring = scopeCliKeyring(raw);
|
|
292
|
+
const providerRegistry = m.REGISTRO_FORNITORI ?? {};
|
|
293
|
+
/* R2 (E8): the missing-key sentence reads the label and the first variable from this registry. */
|
|
294
|
+
useProviderRegistry(providerRegistry);
|
|
295
|
+
const consent = createEnvironmentKeyConsent({ dataRoot: paths.dataRoot, registry: providerRegistry, env });
|
|
296
|
+
const storeEnvironment = () => environmentKeys === 'use' ? env : consent.environmentForStore();
|
|
297
|
+
const buildStore = () => { const built = m.createProviderCredentialStore({ env: storeEnvironment(), keyring: cliKeyring, runtimeFile: join(paths.dataRoot, 'provider-runtime.json') }); built.loadFromKeyring?.(); return built; };
|
|
298
|
+
let currentStore = buildStore();
|
|
299
|
+
const providerStore = environmentKeys === 'use' ? currentStore : forwardingStore(() => currentStore, currentStore);
|
|
300
|
+
const answerEnvironmentKey = (id, answer) => { consent.answer(id, answer); currentStore = buildStore(); };
|
|
301
|
+
const kernel = join(repoRoot, 'harness-ui', 'src', 'kernel', 'talosHarness.mjs');
|
|
302
|
+
const destinazioneModelloDeps = { leggiChiave: (p) => { try {
|
|
303
|
+
return providerStore.getKey(p);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
} }, leggiRuntime: (p) => { try {
|
|
308
|
+
return providerStore.getRuntime(p);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return {};
|
|
312
|
+
} }, localePronto: () => false };
|
|
313
|
+
const chatImageStore = typeof m.createChatImageStore === 'function' ? m.createChatImageStore({ rootDir: join(paths.dataRoot, 'chat-images') }) : null;
|
|
314
|
+
const ownerRuntime = m.createOwnerRuntimeAdapter({ modulePath: kernel, providerStore, openRouterRuntimeFn: () => { try {
|
|
315
|
+
return providerStore.getRuntime('openrouter');
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
return { timeoutSeconds: 60 };
|
|
319
|
+
} }, destinazioneModelloDeps, ...(chatImageStore ? { resolveImagesFn: (messages) => chatImageStore.resolveMessages(messages) } : {}) });
|
|
320
|
+
let taskCatalogProvider = null;
|
|
321
|
+
try {
|
|
322
|
+
taskCatalogProvider = await ownerRuntime.taskCatalogProvider?.();
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
taskCatalogProvider = null;
|
|
326
|
+
}
|
|
327
|
+
let searchSourceStore = null;
|
|
328
|
+
let ricercaWebFn = undefined;
|
|
329
|
+
if (m.createSearchSourceStore) {
|
|
330
|
+
searchSourceStore = m.createSearchSourceStore({ env, keyring: cliKeyring, file: join(paths.dataRoot, 'search-source.json') });
|
|
331
|
+
const transport = m.creaTrasportoSenzaChiave?.();
|
|
332
|
+
ricercaWebFn = () => searchSourceStore.perKernel({ trasportoSenzaChiave: transport, sentinellaDuckDuckGo: m.ENDPOINT_SENTINELLA_DUCKDUCKGO });
|
|
333
|
+
}
|
|
334
|
+
const trustAuthority = createTrustAuthority({ projectRoot, trustRoot: paths.trust.projects });
|
|
335
|
+
const verificaTrustHookFn = async ({ hookId }) => trustAuthority.verifyScope({ kind: 'hook', id: hookId });
|
|
336
|
+
const verificaTrustMcpFn = async ({ serverId }) => trustAuthority.verifyScope({ kind: 'mcp', id: serverId });
|
|
337
|
+
const verificaTrustPluginFn = createGuardedPluginTrustVerifier({ projectRoot, inspectReview: () => trustAuthority.inspect(), verifyTrusted: async ({ pluginId }) => trustAuthority.verifyScope({ kind: 'plugin', id: pluginId }) });
|
|
338
|
+
async function loadedPlugin(pluginId) {
|
|
339
|
+
if (typeof trustM.caricaPlugin !== 'function')
|
|
340
|
+
throw Object.assign(new Error('PLUGIN_REGISTRY_UNAVAILABLE'), { code: 'PLUGIN_REGISTRY_UNAVAILABLE' });
|
|
341
|
+
const loaded = await trustM.caricaPlugin({ cartella: projectRoot });
|
|
342
|
+
const plugin = loaded.plugin?.find((row) => row.id === pluginId);
|
|
343
|
+
if (!plugin)
|
|
344
|
+
throw Object.assign(new Error('PLUGIN_NOT_FOUND'), { code: 'PLUGIN_NOT_FOUND' });
|
|
345
|
+
return plugin;
|
|
346
|
+
}
|
|
347
|
+
async function assertPluginAllowed(pluginId) {
|
|
348
|
+
const reviewed = await trustAuthority.inspect();
|
|
349
|
+
const plugin = await loadedPlugin(pluginId);
|
|
350
|
+
const expectedReview = pluginReviewFromSnapshot(reviewed, pluginId);
|
|
351
|
+
const guard = await scanPluginPackage({ projectRoot, plugin: { id: plugin.id, tools: plugin.tools ?? [], hooks: plugin.hooks ?? [] }, expectedReview });
|
|
352
|
+
if (guard.verdict === 'dangerous')
|
|
353
|
+
throw Object.assign(new Error('PLUGIN_DANGEROUS'), { code: 'PLUGIN_DANGEROUS', guard });
|
|
354
|
+
return { plugin, reviewed };
|
|
355
|
+
}
|
|
356
|
+
async function pluginIdForHook(hookId) {
|
|
357
|
+
if (typeof trustM.caricaPlugin !== 'function')
|
|
358
|
+
return null;
|
|
359
|
+
const loaded = await trustM.caricaPlugin({ cartella: projectRoot });
|
|
360
|
+
for (const plugin of loaded.plugin ?? [])
|
|
361
|
+
for (const hook of plugin.hooks ?? [])
|
|
362
|
+
if (`plugin:${plugin.id}:${hook.id}` === hookId)
|
|
363
|
+
return String(plugin.id);
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
async function executePluginHookWithAuthority(input) {
|
|
367
|
+
const hookId = String(input?.hook?.id ?? '');
|
|
368
|
+
const pluginId = await pluginIdForHook(hookId);
|
|
369
|
+
if (!pluginId)
|
|
370
|
+
return { consentito: false, motivo: 'TALOS cannot resolve the plugin owner for this hook, so it was not executed.' };
|
|
371
|
+
try {
|
|
372
|
+
await assertPluginAllowed(pluginId);
|
|
373
|
+
if (!await trustAuthority.verifyScope({ kind: 'plugin', id: pluginId }))
|
|
374
|
+
return { consentito: false, motivo: 'The plugin changed or is no longer trusted, so its hook was not executed.' };
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
return { consentito: false, motivo: 'TALOS could not verify this plugin package safely, so its hook was not executed.' };
|
|
378
|
+
}
|
|
379
|
+
if (typeof trustM.eseguiHook !== 'function')
|
|
380
|
+
return { consentito: false, motivo: 'The plugin hook executor is unavailable, so the hook was not executed.' };
|
|
381
|
+
return trustM.eseguiHook(input);
|
|
382
|
+
}
|
|
383
|
+
const statoTrustPluginFn = async ({ pluginId, hash }) => {
|
|
384
|
+
const fidato = await verificaTrustPluginFn({ cartellaTrust: 'authority', pluginId, hash });
|
|
385
|
+
if (fidato)
|
|
386
|
+
return { fidato: true, motivo: 'fidato', frase: null };
|
|
387
|
+
const snapshot = await trustAuthority.inspect();
|
|
388
|
+
const prefix = `.harness-ui-plugins/${pluginId}/`;
|
|
389
|
+
const rows = snapshot.resources.filter(row => row.kind === 'plugin' && row.relativePath.startsWith(prefix));
|
|
390
|
+
const changed = rows.some(row => row.state === 'changed' || row.state === 'removed');
|
|
391
|
+
return { fidato: false, motivo: changed ? 'contenuto-cambiato' : 'mai-approvato', frase: changed ? 'The plugin content no longer matches the TALOS project trust record.' : 'This plugin is not trusted by the TALOS project authority.' };
|
|
392
|
+
};
|
|
393
|
+
/* R4 Q-R4-17: a folder trusted for this session only keeps nothing on disk — no mirror outside a run's own folder. */
|
|
394
|
+
async function writeMcpMirror(serverId, hash) { if (await trustAuthority.sessionGrant())
|
|
395
|
+
return; const roots = await trustAuthority.compatibilityRoots(); await rm(join(roots.mcp, `${serverId}.json`), { force: true }); if (typeof trustM.fidaServerMcp === 'function')
|
|
396
|
+
await trustM.fidaServerMcp({ cartellaTrust: roots.mcp, serverId, hash }); }
|
|
397
|
+
async function writePluginMirror(pluginId, hash) { if (await trustAuthority.sessionGrant())
|
|
398
|
+
return; const roots = await trustAuthority.compatibilityRoots(); await rm(join(roots.plugins, `${pluginId}.json`), { force: true }); if (typeof trustM.fidaPlugin === 'function')
|
|
399
|
+
await trustM.fidaPlugin({ cartellaTrust: roots.plugins, pluginId, hash }); }
|
|
400
|
+
const fidaHookFn = async ({ hookId }) => { await trustAuthority.trustScope({ kind: 'hook', id: hookId }); return { fidato: true }; };
|
|
401
|
+
const fidaServerMcpFn = async ({ serverId, hash }) => { await trustAuthority.trustScope({ kind: 'mcp', id: serverId }); await writeMcpMirror(serverId, hash); return { fidato: true }; };
|
|
402
|
+
const fidaPluginFn = async ({ pluginId, hash }) => { const review = await assertPluginAllowed(pluginId); await trustAuthority.trustScope({ kind: 'plugin', id: pluginId }, { expected: review.reviewed }); await writePluginMirror(pluginId, hash); return { fidato: true }; };
|
|
403
|
+
async function sessionCompatibility() {
|
|
404
|
+
const base = await trustAuthority.compatibilityRoots();
|
|
405
|
+
const launchId = randomUUID();
|
|
406
|
+
const roots = { hooks: base.hooks, mcp: join(base.mcp, 'runs', launchId), plugins: join(base.plugins, 'runs', launchId) };
|
|
407
|
+
if (typeof trustM.caricaServerMcp === 'function' && typeof trustM.fidaServerMcp === 'function') {
|
|
408
|
+
const loaded = await trustM.caricaServerMcp({ cartella: projectRoot });
|
|
409
|
+
for (const server of loaded.server ?? [])
|
|
410
|
+
if (await trustAuthority.verifyScope({ kind: 'mcp', id: server.id }))
|
|
411
|
+
await trustM.fidaServerMcp({ cartellaTrust: roots.mcp, serverId: server.id, hash: server.hash });
|
|
412
|
+
}
|
|
413
|
+
if (typeof trustM.caricaPlugin === 'function' && typeof trustM.fidaPlugin === 'function') {
|
|
414
|
+
const reviewed = await trustAuthority.inspect();
|
|
415
|
+
const loaded = await trustM.caricaPlugin({ cartella: projectRoot });
|
|
416
|
+
for (const plugin of loaded.plugin ?? []) {
|
|
417
|
+
const expectedReview = pluginReviewFromSnapshot(reviewed, plugin.id);
|
|
418
|
+
const guard = await scanPluginPackage({ projectRoot, plugin: { id: plugin.id, tools: plugin.tools ?? [], hooks: plugin.hooks ?? [] }, expectedReview });
|
|
419
|
+
if (guard.verdict !== 'dangerous' && await trustAuthority.verifyScope({ kind: 'plugin', id: plugin.id }))
|
|
420
|
+
await trustM.fidaPlugin({ cartellaTrust: roots.plugins, pluginId: plugin.id, hash: plugin.hash });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/* After a session-only run the folders this run created go too, emptiest first; a folder another run still uses is not empty and stays. */
|
|
424
|
+
const leftovers = [join(base.mcp, 'runs'), join(base.plugins, 'runs'), base.mcp, base.plugins, base.hooks, dirname(base.hooks), dirname(dirname(base.hooks))];
|
|
425
|
+
const cleanup = async () => {
|
|
426
|
+
await Promise.all([rm(roots.mcp, { recursive: true, force: true }), rm(roots.plugins, { recursive: true, force: true })]);
|
|
427
|
+
if (await trustAuthority.sessionGrant())
|
|
428
|
+
for (const folder of leftovers)
|
|
429
|
+
await rmdir(folder).catch(() => { });
|
|
430
|
+
};
|
|
431
|
+
return { roots, cleanup };
|
|
432
|
+
}
|
|
433
|
+
/*
|
|
434
|
+
* ⭐⭐⭐ B1 slice 8 — IL COMANDO DIGITATO DALLA PERSONA PASSA DAL BROKER.
|
|
435
|
+
*
|
|
436
|
+
* `createSessionRegistry` accetta da sempre `eseguiComandoDirettoFn` (session-registry.mjs L1329) e
|
|
437
|
+
* `eseguiComandoDiretto` accetta da sempre `eseguiComandoSandboxatoFn` (agent-service.mjs L1975):
|
|
438
|
+
* qui si usano quei due seam gia' esistenti invece di scrivere un secondo esecutore. Cosi' il
|
|
439
|
+
* vocabolario di eventi, l'accorpamento dei pezzi e la cucitura in cronologia restano UNO SOLO.
|
|
440
|
+
*
|
|
441
|
+
* ⛔ `brokerFactory` resta FACOLTATIVO qui e il suo default resta `undefined`, cioe' «nessun
|
|
442
|
+
* broker consultato, comportamento di ripiego puro»: questa funzione e' PARAMETRICA e non
|
|
443
|
+
* sceglie.
|
|
444
|
+
* ⭐ B1 fetta 12 — CHI SCEGLIE E' L'INGRESSO. `createCliRuntime`/`createCliRuntimeContext`
|
|
445
|
+
* (`create-runtime.ts`) passano ora il broker VERO (`createCliExecutionBroker`) per default,
|
|
446
|
+
* costruito UNA volta per runtime; chi compone puo' ancora sovrascriverlo. Chi legge questo
|
|
447
|
+
* commento non deve dedurne che il CLI giri senza broker: senza `brokerFactory` e' questa
|
|
448
|
+
* COMPOSIZIONE a girare senza, non il prodotto.
|
|
449
|
+
* ⛔ Il kernel si carica PIGRAMENTE: nessun comando digitato, nessun costo all'avvio.
|
|
450
|
+
*/
|
|
451
|
+
/*
|
|
452
|
+
* ⭐ R5a lane R, S6 (owner decision Q-R5-10): a command's full output is kept in a private file of ITS session
|
|
453
|
+
* (output-store.ts). The session is known where the command starts — a model run (`talosLavoraConBroker`, below) or the
|
|
454
|
+
* person's `!` command (`shell`, below) — and reaches the executor through this async scope; a command outside any session
|
|
455
|
+
* keeps no file, and its marker says so. Folders of sessions that no longer exist are removed at start.
|
|
456
|
+
*/
|
|
457
|
+
const toolOutputScope = new AsyncLocalStorage();
|
|
458
|
+
const toolOutputStore = createToolOutputStore({ dataRoot: paths.dataRoot, secretValues: secretValuesFromEnvironment({ ...process.env, ...env }) });
|
|
459
|
+
void pruneToolOutput({ dataRoot: paths.dataRoot, sessionsRoot: paths.sessionsRoot }).catch(() => { });
|
|
460
|
+
/* R5a lane R, S3/S4: the CLI's own live facts per session (provider-attempts.ts), merged into `subscribe` below. */
|
|
461
|
+
const cliEvents = createCliEventHub();
|
|
462
|
+
const brokeredExecutor = createBrokeredKernelExecutor({
|
|
463
|
+
keepFullOutput: async (text) => { const sessionId = toolOutputScope.getStore()?.sessionId; return sessionId ? toolOutputStore.save({ sessionId, text }) : null; },
|
|
464
|
+
kernel: async () => (await import(__rewriteRelativeImportExtension(pathToFileURL(kernel).href))),
|
|
465
|
+
...(brokerFactory ? {
|
|
466
|
+
broker: brokerFactory({ paths: { cacheRoot: paths.cacheRoot }, platform: process.platform }),
|
|
467
|
+
/* F16 hardening, kept: the product asks for the verified AppContainer/BFS sandbox, never a weaker one, network denied.
|
|
468
|
+
R0 lane S, owner decision 1 (2026-09-23): when no verified sandbox exists the command runs on the host, DECLARED
|
|
469
|
+
(`⛶ host · not isolated` and why) instead of being refused; `true` here is the strict switch (gate E7). */
|
|
470
|
+
network: 'deny',
|
|
471
|
+
requiredEnforcement: 'windows-sandbox',
|
|
472
|
+
requireVerifiedIsolation: false,
|
|
473
|
+
} : {}),
|
|
474
|
+
});
|
|
475
|
+
/* M1-E: model-owned shell uses the same CLI broker as the direct-command path.
|
|
476
|
+
Broker assignment is last so runtime input cannot replace the product-selected executor. */
|
|
477
|
+
const modelCheckpointStore = paths.checkpointsRoot ? createCheckpointStore({ rootDir: paths.checkpointsRoot, projectRoot }) : null;
|
|
478
|
+
let modelCheckpointBlockedError = null;
|
|
479
|
+
const talosLavoraConBroker = async (runtimeInput) => {
|
|
480
|
+
const { checkpointSessionId, checkpointOperation, ...kernelInput } = runtimeInput ?? {};
|
|
481
|
+
const operation = checkpointOperation === 'resume' || checkpointOperation === 'fork' ? checkpointOperation : 'start';
|
|
482
|
+
const checkpointState = { promise: null };
|
|
483
|
+
const beforeMutation = async (azione) => {
|
|
484
|
+
if (modelCheckpointBlockedError)
|
|
485
|
+
throw modelCheckpointBlockedError;
|
|
486
|
+
if (!modelCheckpointStore)
|
|
487
|
+
return;
|
|
488
|
+
if (!checkpointState.promise) {
|
|
489
|
+
const started = performance.now();
|
|
490
|
+
developmentLog('checkpoint.lazy.begin', { operation, sessionId: checkpointSessionId ?? null, actionType: azione?.tipo ?? null }, 'debug', 'runtime-composition');
|
|
491
|
+
checkpointState.promise = modelCheckpointStore.begin({ operation, ...(typeof checkpointSessionId === 'string' && checkpointSessionId ? { sessionId: checkpointSessionId } : {}) })
|
|
492
|
+
.then(handle => { developmentLog('checkpoint.lazy.ready', { operation, sessionId: checkpointSessionId ?? null, checkpointId: handle.id, durationMs: performance.now() - started }, 'info', 'runtime-composition'); return handle; })
|
|
493
|
+
.catch(error => { modelCheckpointBlockedError = error; developmentLogError('checkpoint.lazy.failure', error, { operation, sessionId: checkpointSessionId ?? null, actionType: azione?.tipo ?? null, durationMs: performance.now() - started }, 'runtime-composition'); throw error; });
|
|
494
|
+
}
|
|
495
|
+
await checkpointState.promise;
|
|
496
|
+
};
|
|
497
|
+
let result;
|
|
498
|
+
let runError = null;
|
|
499
|
+
/*
|
|
500
|
+
* ⭐ R5a lane R, S3/S4/S7 (owner decisions Q-R5-4, Q-R5-6, Q-R5-11): the run's observer wraps the innermost transport the
|
|
501
|
+
* owner adapter uses (`input.fetchDiRete`, runtime-owner-adapter.mjs:1193, the global fetch until now) and receives the
|
|
502
|
+
* kernel's retry waits (the owner's amendment: `retryAfterMaxMs`, `onRetryWait`); the kernel marks the 8,000-character cut
|
|
503
|
+
* of a tool result (`toolResultCutMarker`). Assigned after the runtime input, like the executor, so input cannot remove them.
|
|
504
|
+
*/
|
|
505
|
+
const runSession = typeof checkpointSessionId === 'string' && checkpointSessionId ? checkpointSessionId : null;
|
|
506
|
+
const observer = runSession ? createRunObserver({ sessionId: runSession, hub: cliEvents, baseFetch: typeof kernelInput.fetchDiRete === 'function' ? kernelInput.fetchDiRete : (input, init) => fetch(input, init) }) : null;
|
|
507
|
+
try {
|
|
508
|
+
result = await toolOutputScope.run({ sessionId: runSession }, () => ownerRuntime.talosLavora({ ...kernelInput,
|
|
509
|
+
...(observer ? { fetchDiRete: observer.fetchDiRete, onRetryWait: observer.onRetryWait, retryAfterMsFn: observer.retryAfterMsFn } : {}), retryAfterMaxMs: RETRY_AFTER_MAX_MS, toolResultCutMarker: true,
|
|
510
|
+
primaDiMutazioneFn: beforeMutation, eseguiComandoSandboxatoFn: brokeredExecutor }));
|
|
511
|
+
}
|
|
512
|
+
catch (error) {
|
|
513
|
+
runError = error;
|
|
514
|
+
observer?.announceFailure(error);
|
|
515
|
+
}
|
|
516
|
+
const pendingCheckpoint = checkpointState.promise;
|
|
517
|
+
if (pendingCheckpoint) {
|
|
518
|
+
let handle = null;
|
|
519
|
+
try {
|
|
520
|
+
handle = await pendingCheckpoint;
|
|
521
|
+
}
|
|
522
|
+
catch { /* begin failure was already logged/refused at the action boundary */ }
|
|
523
|
+
if (handle) {
|
|
524
|
+
const started = performance.now();
|
|
525
|
+
try {
|
|
526
|
+
await handle.finalize();
|
|
527
|
+
developmentLog('checkpoint.lazy.finalized', { operation, sessionId: checkpointSessionId ?? null, checkpointId: handle.id, durationMs: performance.now() - started }, 'info', 'runtime-composition');
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
modelCheckpointBlockedError = error;
|
|
531
|
+
developmentLogError('checkpoint.lazy.finalize_failure', error, { operation, sessionId: checkpointSessionId ?? null, checkpointId: handle.id, durationMs: performance.now() - started }, 'runtime-composition');
|
|
532
|
+
if (!runError)
|
|
533
|
+
runError = error;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
if (runError)
|
|
538
|
+
throw runError;
|
|
539
|
+
return result;
|
|
540
|
+
};
|
|
541
|
+
/*
|
|
542
|
+
* ⭐ B1 slice 24 — COMPACTION AND THE RESEARCH JUDGE REACH THE PROVIDER OF THE MODEL THEY NAME.
|
|
543
|
+
*
|
|
544
|
+
* ⭐ B1 slice 25 — KEPT, BY MEASUREMENT, AFTER CLI-REQ-05 LANDED. The kernel at e7b3a1b6 (`harness-ui/` from ba420a95) now
|
|
545
|
+
* names the session's own model when it compacts (`session-registry.mjs:4502-4514`, `modelloDiSessionePerRete` at `:145`) and
|
|
546
|
+
* hands both calls `fetchDiRete: fetchModelloFn()` (`:4513`, and the judge through `creaChiediAlModelloGiudice`, `:224-229`).
|
|
547
|
+
* Slice 24's eleven tests (`test/runtime/auxiliary-provider-route.test.ts`), measured on both routes, hermetic:
|
|
548
|
+
* - these two functions injected, with `prontoFn` and `fetchModelloFn` wired: 11 of 11 pass;
|
|
549
|
+
* - the two functions and the compaction scope removed in favour of the kernel's route (`fetchModelloFn` = the guarded route
|
|
550
|
+
* below): 6 of 11. Failing: no key and an unapproved environment key end `{ok:true, compattato:false}` instead of
|
|
551
|
+
* `PROVIDER_KEY_MISSING` (the kernel swallows the failed call, `kernel/talosHarness.mjs:260-265`); the kernel is handed the
|
|
552
|
+
* OpenRouter key (`chiaveFn`) instead of the placeholder, so a request the route did not rewrite is not refused; a local
|
|
553
|
+
* session answers `compattato:false` instead of a refusal, and a compaction outside the session runtime is sent; two
|
|
554
|
+
* compactions overlap. ⇒ The injection stays. `fetchModelloFn` is wired to the same guarded route (`registry`, below), so
|
|
555
|
+
* no kernel path that asks for it reaches a bare `fetch`; the two functions here keep building their own route, because
|
|
556
|
+
* they report the code it raised before the network.
|
|
557
|
+
*
|
|
558
|
+
* Kernel lines relied on, read at 5e05de8e:
|
|
559
|
+
* - `session-registry.mjs:4217` `compatta()` calls `compattaSessioneFn({messaggiFinali, modello, chiave})`, and `:1712-1716` the
|
|
560
|
+
* research judge calls `chiediAlModelloUnaVoltaFn({modello, chiave, prompt})`; the defaults (`:1326`, `:1564`) are
|
|
561
|
+
* `agent-service.mjs:1905` `compattaSessione` and `:1946` `chiediAlModelloUnaVolta`, which hand `fetchDiRete` to `chiamaConRitenta`.
|
|
562
|
+
* - `kernel/talosHarness.mjs:1249-1253` `chiamaConRitenta` runs the call inside `fetchDiRete.eseguiConFallback` when it exists;
|
|
563
|
+
* otherwise `:1308` posts to the fixed `https://openrouter.ai/api/v1/chat/completions` with the key it was given.
|
|
564
|
+
* - `runtime-owner-adapter.mjs:654` `creaFetchMultiProvider`: only WITH a `providerStore` does it return `eseguiConFallback`
|
|
565
|
+
* (`:660`, and without it an OpenRouter model goes back to the kernel's own address, `:552`); `:669` a request whose JSON body
|
|
566
|
+
* has no string `model`, or whose URL is not `/chat/completions`, reaches the network untouched; `:673-675` a provider that
|
|
567
|
+
* requires a key and has none throws `PROVIDER_KEY_MISSING` before the network; `:710-712` OpenRouter too goes to its
|
|
568
|
+
* configured endpoint, and `:589` the destination's headers replace the kernel's; `:766-770` a failed call is rethrown as
|
|
569
|
+
* `PROVIDER_REQUEST_ERROR`, which drops the code raised before the network.
|
|
570
|
+
* - `model-destination.mjs:122-189` `risolviDestinazioneModello` takes endpoint and key from the model's prefix.
|
|
571
|
+
* - `kernel/talosHarness.mjs:247-256` `compattaConversazione` swallows a failed call and answers `compattato:false`.
|
|
572
|
+
* Measured with a probe on this tree before writing (recording fetch, no network, dummy keys): the routed DeepSeek judge and
|
|
573
|
+
* compaction reached `<configured endpoint>/chat/completions` with the DeepSeek key and model `deepseek-flash`, an OpenRouter
|
|
574
|
+
* model reached the configured OpenRouter endpoint with the OpenRouter key; with the DeepSeek key removed nothing was sent, the
|
|
575
|
+
* judge failed `PROVIDER_REQUEST_ERROR` and compaction answered `compattato:false`; a body without a model reached the network
|
|
576
|
+
* layer at the fixed OpenRouter address carrying the credential the kernel was given.
|
|
577
|
+
*
|
|
578
|
+
* Hence four rules:
|
|
579
|
+
* 1. The kernel never receives the OpenRouter key for these calls, only a per-runtime placeholder; the route replaces it with
|
|
580
|
+
* the destination provider's key, and the network layer REFUSES any request that still carries the placeholder, i.e. one
|
|
581
|
+
* the route did not rewrite. Nothing reaches the fixed address by falling through.
|
|
582
|
+
* 2. A missing route (a kernel without `creaFetchMultiProvider`, or one that returns no `eseguiConFallback`) is a refusal,
|
|
583
|
+
* never the registry's default.
|
|
584
|
+
* 3. When the call fails, the code the route raised before the network is the one reported (only for the codes below, whose
|
|
585
|
+
* messages the kernel or this file wrote: an upstream error keeps the kernel's public message, P-K).
|
|
586
|
+
* 4. An explicit compaction whose model call failed FAILS with that error, instead of reporting "already compact".
|
|
587
|
+
* ⛔ The environment-key consent of slice 18 holds by construction: keys come only from `providerStore`, the consent-filtered
|
|
588
|
+
* forwarder above, so an unapproved environment key is not visible to these calls either.
|
|
589
|
+
*/
|
|
590
|
+
const unroutedCredential = `talos-cli-unrouted-${randomUUID()}`;
|
|
591
|
+
const PRE_NETWORK_CODES = new Set(['PROVIDER_KEY_MISSING', 'PROVIDER_RUNTIME_INVALID', 'MODEL_DESTINATION_INVALID', 'LOCAL_RUNTIME_NOT_READY', 'AUXILIARY_CALL_NOT_ROUTED']);
|
|
592
|
+
const auxiliaryNetwork = (input, init = {}) => {
|
|
593
|
+
let unrouted = true;
|
|
594
|
+
try {
|
|
595
|
+
const bearer = `Bearer ${unroutedCredential}`;
|
|
596
|
+
unrouted = new Headers(init?.headers ?? undefined).get('authorization') === bearer || (typeof input?.headers?.get === 'function' && input.headers.get('authorization') === bearer);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
unrouted = true;
|
|
600
|
+
}
|
|
601
|
+
if (unrouted)
|
|
602
|
+
return Promise.reject(new CliRuntimeError('AUXILIARY_CALL_NOT_ROUTED', 'An auxiliary model request was not routed to its provider, so it was not sent.'));
|
|
603
|
+
return fetch(input, init);
|
|
604
|
+
};
|
|
605
|
+
const auxiliaryFailure = (error, modello) => {
|
|
606
|
+
if (error?.code !== 'PROVIDER_KEY_MISSING')
|
|
607
|
+
return error;
|
|
608
|
+
const prefix = providerOfModel(String(modello ?? ''));
|
|
609
|
+
const provider = prefix && Object.hasOwn(providerRegistry, prefix) ? prefix : 'openrouter';
|
|
610
|
+
/* R2 (E8 / Q-R2-7): the one missing-key sentence; the code PROVIDER_KEY_MISSING is what the screen maps. */
|
|
611
|
+
return new CliRuntimeError('PROVIDER_KEY_MISSING', missingKeyText(provider), { provider });
|
|
612
|
+
};
|
|
613
|
+
const auxiliaryRoute = () => {
|
|
614
|
+
const unavailable = () => new CliRuntimeError('AUXILIARY_ROUTE_UNAVAILABLE', 'This TALOS kernel cannot route compaction or the research judge to the session provider, so nothing was sent.');
|
|
615
|
+
if (typeof m.creaFetchMultiProvider !== 'function')
|
|
616
|
+
throw unavailable();
|
|
617
|
+
const route = m.creaFetchMultiProvider(auxiliaryNetwork, { dipendenze: destinazioneModelloDeps, providerStore });
|
|
618
|
+
if (typeof route?.eseguiConFallback !== 'function')
|
|
619
|
+
throw unavailable();
|
|
620
|
+
let failure = null;
|
|
621
|
+
const fetchDiRete = Object.assign((input, init) => route(input, init), {
|
|
622
|
+
eseguiConFallback: async (chiama, opzioni = {}) => {
|
|
623
|
+
let thrown = null;
|
|
624
|
+
try {
|
|
625
|
+
return await route.eseguiConFallback((aggiunte) => chiama({ ...aggiunte, fetchDiRete: async (input, init) => { thrown = null; try {
|
|
626
|
+
return await aggiunte.fetchDiRete(input, init);
|
|
627
|
+
}
|
|
628
|
+
catch (error) {
|
|
629
|
+
thrown = error;
|
|
630
|
+
throw error;
|
|
631
|
+
} } }), opzioni);
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
const stopped = error?.name === 'AbortError' || error?.fermatoSuRichiesta === true;
|
|
635
|
+
failure = stopped ? error : auxiliaryFailure(PRE_NETWORK_CODES.has(thrown?.code) ? thrown : error, opzioni?.modello);
|
|
636
|
+
throw failure;
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
});
|
|
640
|
+
return { fetchDiRete, failure: () => failure };
|
|
641
|
+
};
|
|
642
|
+
/*
|
|
643
|
+
* ⭐ B1 slice 24, condition C1 — COMPACTION NAMES THE SESSION'S MODEL, NOT THE REGISTRY'S.
|
|
644
|
+
*
|
|
645
|
+
* `session-registry.mjs:4217` (at 5e05de8e) handed `compattaSessioneFn` the model the registry was BUILT with (its closure `modello`,
|
|
646
|
+
* i.e. this composition's `model`), not the session's (`voce.modello`); since CLI-REQ-05 the kernel passes the session's own (see
|
|
647
|
+
* slice 25 above), and the scope below still decides. `main.ts` composes once with the launch model, and `/model`
|
|
648
|
+
* only changes the model new sessions start with. Measured on this tree before the cure: a DeepSeek session in a runtime
|
|
649
|
+
* composed with an OpenRouter model compacted on OpenRouter's configured endpoint with the OpenRouter key.
|
|
650
|
+
* ⇒ The session runtime's `compatta` (`sessionRegistry`, below) reads the session's own row from `registry.elenca()`
|
|
651
|
+
* (`session-registry.mjs:5520` `modello`, `:5525` `provider`; a restored session keeps both, `:3559`, `:3564`), runs ONE
|
|
652
|
+
* compaction at a time, and binds that session's model to the compaction's async context. `compattaSessioneFn` uses only
|
|
653
|
+
* that model and ignores the one the kernel passes.
|
|
654
|
+
* ⛔ Never a guess, never a fallback. Refused before anything is sent:
|
|
655
|
+
* - a compaction that did not come through the session runtime (no bound scope): the model is unknown;
|
|
656
|
+
* - a session whose provider is not `cloud` (the registry knows only `cloud` and `local`) or whose model is missing;
|
|
657
|
+
* - a `local` session: its stored model is a bare GGUF id, which the route would read as an OpenRouter id
|
|
658
|
+
* (`model-destination.mjs:65-75`). Compacting it waits for the kernel fix, CLI-REQ-05.
|
|
659
|
+
* The research judge is unaffected: it names its own model explicitly.
|
|
660
|
+
*/
|
|
661
|
+
const compactionScope = new AsyncLocalStorage();
|
|
662
|
+
const compattaSessioneFn = async ({ messaggiFinali }) => {
|
|
663
|
+
const scope = compactionScope.getStore();
|
|
664
|
+
if (!scope)
|
|
665
|
+
throw new CliRuntimeError('SESSION_MODEL_UNKNOWN', 'This compaction did not come through the session runtime, so the session model is unknown and nothing was sent.');
|
|
666
|
+
if (scope.refusal)
|
|
667
|
+
throw scope.refusal;
|
|
668
|
+
if (typeof m.compattaSessione !== 'function')
|
|
669
|
+
throw new CliRuntimeError('AUXILIARY_ROUTE_UNAVAILABLE', 'This TALOS kernel does not expose compaction, so nothing was sent.');
|
|
670
|
+
const route = auxiliaryRoute();
|
|
671
|
+
const result = await m.compattaSessione({ messaggiFinali, modello: scope.model, chiave: unroutedCredential, fetchDiRete: route.fetchDiRete });
|
|
672
|
+
const failure = route.failure();
|
|
673
|
+
if (result?.compattato !== true && failure)
|
|
674
|
+
throw failure;
|
|
675
|
+
/* R5a lane R, S3 (Q-R5-4): an explicit /compact of a session without the Context Engine is named like the automatic one: the
|
|
676
|
+
kernel keeps the system prompt and the task (talosHarness.mjs `compattaConversazione`) and summarises the rest. */
|
|
677
|
+
if (result?.compattato === true) {
|
|
678
|
+
const before = Array.isArray(messaggiFinali) ? messaggiFinali.length : null;
|
|
679
|
+
const tokens = Number(result?.usage?.prompt_tokens);
|
|
680
|
+
cliEvents.emit(scope.sessionId, compactionEvent({ engine: 'kernel', messagesSummarized: before === null ? null : Math.max(0, before - 2), tokensBefore: Number.isSafeInteger(tokens) && tokens >= 0 ? tokens : null, tokensAfter: null, trigger: 'manual' }));
|
|
681
|
+
}
|
|
682
|
+
return result;
|
|
683
|
+
};
|
|
684
|
+
const chiediAlModelloUnaVoltaFn = async ({ modello, prompt, segnaleStop }) => {
|
|
685
|
+
if (typeof m.chiediAlModelloUnaVolta !== 'function')
|
|
686
|
+
throw new CliRuntimeError('AUXILIARY_ROUTE_UNAVAILABLE', 'This TALOS kernel does not expose the research judge call, so nothing was sent.');
|
|
687
|
+
return m.chiediAlModelloUnaVolta({ modello, chiave: unroutedCredential, prompt, ...(segnaleStop ? { segnaleStop } : {}), fetchDiRete: auxiliaryRoute().fetchDiRete });
|
|
688
|
+
};
|
|
689
|
+
/*
|
|
690
|
+
* ⭐ B1 slice 25 — READINESS AT START: THE KERNEL DECIDES, THE CLI SAYS IT.
|
|
691
|
+
*
|
|
692
|
+
* The rule "ready means a key usable NOW, for every provider" lives in the kernel since the desktop lane's `3cbecf60`, which fixed
|
|
693
|
+
* the benched-key defect this slice found: `harness-ui/src/sessione-pronta.mjs`, `creaProntoFn`. Kernel lines relied on, read at
|
|
694
|
+
* 3cbecf60:
|
|
695
|
+
* - `sessione-pronta.mjs:48-72`: synchronous; an empty or unparsable model is not ready; a provider that needs a key is ready
|
|
696
|
+
* only if `providerStore.getKey` (which skips a benched key) returns one, or, for OpenRouter only, `chiaveApi`.
|
|
697
|
+
* - `session-registry.mjs:2859-2877`: a non-local session starts only on `pronto`; `codice`/`messaggio` become the refusal, and a
|
|
698
|
+
* throw becomes one too. Without `prontoFn` the registry keeps asking every session for an OpenRouter key.
|
|
699
|
+
* What the CLI adds around it, and why:
|
|
700
|
+
* - ⛔ `chiaveApi: null`. On the desktop server it is the startup `OPENROUTER_API_KEY`; here an environment key reaches the store
|
|
701
|
+
* only with the person's consent (slice 18), so no key outside the store may make a session ready.
|
|
702
|
+
* - A model whose provider cannot be read, or that the registry does not know, is refused before asking: the kernel answers
|
|
703
|
+
* "ready" for a provider it has no record of (`sessione-pronta.mjs:60`), and a guard that cannot evaluate denies.
|
|
704
|
+
* - The refusal is the CLI's own, in English, with the codes the screen gives (`unusableKeyRefusal`, `keyBench`), never the
|
|
705
|
+
* kernel's Italian sentence. An answer that cannot be read, or a throw, refuses and says TALOS could not tell.
|
|
706
|
+
*/
|
|
707
|
+
const kernelReady = typeof m.creaProntoFn === 'function' ? m.creaProntoFn({ providerStore, chiaveApi: null }) : null;
|
|
708
|
+
const prontoFn = (modello) => {
|
|
709
|
+
let fonte = null;
|
|
710
|
+
try {
|
|
711
|
+
fonte = typeof modello === 'string' && typeof m.separaFonteModello === 'function' ? m.separaFonteModello(modello).fonte : null;
|
|
712
|
+
}
|
|
713
|
+
catch {
|
|
714
|
+
fonte = null;
|
|
715
|
+
}
|
|
716
|
+
const record = fonte && Object.hasOwn(providerRegistry, fonte) ? providerRegistry[fonte] : null;
|
|
717
|
+
if (!fonte || !record)
|
|
718
|
+
return { pronto: false, codice: 'MODEL_DESTINATION_INVALID', messaggio: 'The model of this session cannot be read, so nothing was sent. Choose one with /model.' };
|
|
719
|
+
const fornitore = providerLabelText(fonte, String(record.etichetta ?? fonte));
|
|
720
|
+
let verdict = null;
|
|
721
|
+
try {
|
|
722
|
+
verdict = kernelReady ? kernelReady(modello) : null;
|
|
723
|
+
}
|
|
724
|
+
catch {
|
|
725
|
+
verdict = null;
|
|
726
|
+
}
|
|
727
|
+
if (verdict?.pronto === true)
|
|
728
|
+
return { pronto: true, fornitore };
|
|
729
|
+
if (verdict?.pronto !== false)
|
|
730
|
+
return { pronto: false, fornitore, codice: 'KERNEL_READINESS_UNREADABLE', messaggio: `TALOS could not tell whether ${fornitore} can be used now, so nothing was sent.` };
|
|
731
|
+
let row = null;
|
|
732
|
+
try {
|
|
733
|
+
row = (providerStore.listPublic?.() ?? []).find((entry) => entry?.id === fonte) ?? null;
|
|
734
|
+
}
|
|
735
|
+
catch {
|
|
736
|
+
row = null;
|
|
737
|
+
}
|
|
738
|
+
const refusal = unusableKeyRefusal(fonte, keyBench(row));
|
|
739
|
+
return { pronto: false, fornitore, codice: refusal.code, messaggio: refusal.message };
|
|
740
|
+
};
|
|
741
|
+
async function prepareMcpWithAuthority(input) {
|
|
742
|
+
if (typeof trustM.preparaToolMcpPerSessione !== 'function')
|
|
743
|
+
return { toolMcp: [], chiamaToolMcpFn: null, falliti: [], chiudiTutti: async () => { } };
|
|
744
|
+
const loaded = typeof trustM.caricaServerMcp === 'function' ? await trustM.caricaServerMcp({ cartella: projectRoot }) : null;
|
|
745
|
+
const prepared = await trustM.preparaToolMcpPerSessione(input);
|
|
746
|
+
if (typeof prepared?.chiamaToolMcpFn !== 'function' || !Array.isArray(prepared?.toolMcp) || !loaded)
|
|
747
|
+
return prepared;
|
|
748
|
+
const owners = new Map();
|
|
749
|
+
const exposed = (serverId, toolName) => typeof trustM.nomeEspostoMcp === 'function' ? trustM.nomeEspostoMcp(serverId, toolName) : `mcp__${serverId}__${toolName}`;
|
|
750
|
+
for (const server of loaded.server ?? []) {
|
|
751
|
+
for (const toolName of server.allowlist ?? []) {
|
|
752
|
+
const serverId = String(server.id);
|
|
753
|
+
const name = exposed(serverId, String(toolName));
|
|
754
|
+
const previous = owners.get(name);
|
|
755
|
+
owners.set(name, previous === undefined || previous === serverId ? serverId : null);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const routes = new Map();
|
|
759
|
+
for (const tool of prepared.toolMcp) {
|
|
760
|
+
const name = String(tool?.name ?? '');
|
|
761
|
+
const serverId = owners.get(name);
|
|
762
|
+
if (serverId)
|
|
763
|
+
routes.set(name, serverId);
|
|
764
|
+
}
|
|
765
|
+
const dispatch = prepared.chiamaToolMcpFn;
|
|
766
|
+
return { ...prepared, chiamaToolMcpFn: async (name, args) => {
|
|
767
|
+
const serverId = routes.get(name);
|
|
768
|
+
if (!serverId)
|
|
769
|
+
throw Object.assign(new Error('MCP_TOOL_OWNER_UNRESOLVED'), { code: 'MCP_TOOL_OWNER_UNRESOLVED' });
|
|
770
|
+
if (!await trustAuthority.verifyScope({ kind: 'mcp', id: serverId }))
|
|
771
|
+
throw Object.assign(new Error('MCP_TRUST_INVALIDATED'), { code: 'MCP_TRUST_INVALIDATED', serverId });
|
|
772
|
+
return dispatch(name, args);
|
|
773
|
+
} };
|
|
774
|
+
}
|
|
775
|
+
async function preparePluginWithAuthority(input) {
|
|
776
|
+
if (typeof trustM.preparaToolPluginPerSessione !== 'function')
|
|
777
|
+
return { toolPlugin: [], hookPlugin: [], eseguiToolPluginFn: null };
|
|
778
|
+
const prepared = await trustM.preparaToolPluginPerSessione(input);
|
|
779
|
+
if (typeof prepared?.eseguiToolPluginFn !== 'function' || typeof prepared?.pluginIdDiTool !== 'function')
|
|
780
|
+
return prepared;
|
|
781
|
+
const dispatch = prepared.eseguiToolPluginFn;
|
|
782
|
+
return { ...prepared, eseguiToolPluginFn: async (name, args) => {
|
|
783
|
+
const pluginId = prepared.pluginIdDiTool(name);
|
|
784
|
+
if (typeof pluginId !== 'string' || !pluginId)
|
|
785
|
+
throw Object.assign(new Error('PLUGIN_TOOL_OWNER_UNRESOLVED'), { code: 'PLUGIN_TOOL_OWNER_UNRESOLVED' });
|
|
786
|
+
if (!await trustAuthority.verifyScope({ kind: 'plugin', id: pluginId }))
|
|
787
|
+
throw Object.assign(new Error('PLUGIN_TRUST_INVALIDATED'), { code: 'PLUGIN_TRUST_INVALIDATED', pluginId });
|
|
788
|
+
return dispatch(name, args);
|
|
789
|
+
} };
|
|
790
|
+
}
|
|
791
|
+
const authoritySessionAdapters = typeof trustM.preparaToolMcpPerSessione === 'function' || typeof trustM.preparaToolPluginPerSessione === 'function';
|
|
792
|
+
let contextBridge = null;
|
|
793
|
+
const registry = m.createSessionRegistry({
|
|
794
|
+
prontoFn, fetchModelloFn: () => auxiliaryRoute().fetchDiRete, compattaSessioneFn, chiediAlModelloUnaVoltaFn,
|
|
795
|
+
contextHooksFn: (input) => contextBridge?.hooks(input), contextCompactFn: (input) => contextBridge?.compact(input),
|
|
796
|
+
avviaSessioneFn: async (input) => {
|
|
797
|
+
if (!authoritySessionAdapters)
|
|
798
|
+
return m.avviaSessione({ ...input, talosLavoraFn: talosLavoraConBroker });
|
|
799
|
+
const compatibility = await sessionCompatibility();
|
|
800
|
+
try {
|
|
801
|
+
return await m.avviaSessione({ ...input, cartellaTrustMcp: compatibility.roots.mcp, cartellaTrustPlugin: compatibility.roots.plugins, ...(trustM.preparaToolMcpPerSessione ? { preparaToolMcpPerSessioneFn: prepareMcpWithAuthority } : {}), ...(trustM.preparaToolPluginPerSessione ? { preparaToolPluginPerSessioneFn: preparePluginWithAuthority } : {}), ...(trustM.eseguiHook ? { eseguiHookFn: executePluginHookWithAuthority } : {}), talosLavoraFn: talosLavoraConBroker });
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
await compatibility.cleanup();
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
modello: model, chiave: providerStore.getKey?.('openrouter') ?? '', chiaveFn: () => providerStore.getKey?.('openrouter') ?? '', cartelleProgetto: [{ id: 'cli', nome: 'CLI', percorso: projectRoot }], taskCatalogProvider, ricercaWebFn, cartellaStore: paths.sessionsRoot,
|
|
808
|
+
cartellaTrustHook: paths.trust.hooks, cartellaTrustMcp: paths.trust.mcp, cartellaTrustPlugin: paths.trust.plugins,
|
|
809
|
+
verificaTrustFn: verificaTrustHookFn, verificaTrustMcpFn, fidaHookFn, fidaServerMcpFn, verificaTrustPluginFn, statoTrustPluginFn, fidaPluginFn,
|
|
810
|
+
cartellaNote: join(paths.dataRoot, 'notes'), cartellaAttivita: join(paths.dataRoot, 'tasks'), cartellaMemoria: join(paths.dataRoot, 'memory'), cartellaForge: join(paths.dataRoot, 'forge'), attrezziKernelFn: () => ownerRuntime.attrezziKernel(),
|
|
811
|
+
eseguiComandoDirettoFn: (input) => { if (typeof m.eseguiComandoDiretto !== 'function')
|
|
812
|
+
throw Object.assign(new Error('KERNEL_COMMAND_PATH_UNAVAILABLE'), { code: 'KERNEL_COMMAND_PATH_UNAVAILABLE' }); return m.eseguiComandoDiretto({ ...input, eseguiComandoSandboxatoFn: brokeredExecutor }); }
|
|
813
|
+
});
|
|
814
|
+
/*
|
|
815
|
+
* R5a lane R, S2: the profile's sources (model-profile.ts). The store's and the registry's rows are read live; the person's
|
|
816
|
+
* override is re-read before each decision (the TUI writes it with `writeConfigScope`); a local Ollama is asked for the
|
|
817
|
+
* model's real window; models.dev is loaded in the background (never in a test profile, gate E7) and waited for at most 2 s.
|
|
818
|
+
*/
|
|
819
|
+
configureModelProfiles({ registry: providerRegistry, rowsFor: (provider) => {
|
|
820
|
+
const record = providerRegistry[provider] ?? {};
|
|
821
|
+
let runtime = {};
|
|
822
|
+
try {
|
|
823
|
+
runtime = providerStore.getRuntime?.(provider) ?? {};
|
|
824
|
+
}
|
|
825
|
+
catch {
|
|
826
|
+
runtime = {};
|
|
827
|
+
}
|
|
828
|
+
return { runtime: Array.isArray(runtime.modelli) ? runtime.modelli : [], registry: [...(Array.isArray(record.modelliNoti) ? record.modelliNoti : []), ...(Array.isArray(record.modelliDiRiserva) ? record.modelliDiRiserva : [])] };
|
|
829
|
+
} });
|
|
830
|
+
let catalogConfig = null;
|
|
831
|
+
try {
|
|
832
|
+
catalogConfig = (await loadEffectiveConfig({ paths, projectRoot })).value;
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
catalogConfig = null;
|
|
836
|
+
}
|
|
837
|
+
const modelsDev = createModelsDevLoader({ repoRoot, dataRoot: paths.dataRoot, env: { ...process.env, ...env }, config: catalogConfig, fetchImpl: (url, init) => fetch(url, init), testProfile: isTestProfile({ ...process.env, ...env }) });
|
|
838
|
+
const refreshOverrides = async () => { try {
|
|
839
|
+
setModelOverrides((await loadEffectiveConfig({ paths, projectRoot })).value.models);
|
|
840
|
+
}
|
|
841
|
+
catch { /* an unreadable configuration is reported by the config layer */ } };
|
|
842
|
+
await refreshOverrides();
|
|
843
|
+
const launch = typeof m.separaFonteModello === 'function' ? (() => { try {
|
|
844
|
+
return m.separaFonteModello(model);
|
|
845
|
+
}
|
|
846
|
+
catch {
|
|
847
|
+
return null;
|
|
848
|
+
} })() : null;
|
|
849
|
+
if (launch?.fonte && modelsDev.enabled)
|
|
850
|
+
void modelsDev.load(launch.fonte);
|
|
851
|
+
const withinMs = (work, ms) => new Promise(resolve => { const timer = setTimeout(resolve, ms); timer.unref?.(); work.then(() => { clearTimeout(timer); resolve(); }, () => { clearTimeout(timer); resolve(); }); });
|
|
852
|
+
const prepareProfile = async (provider, modelId) => {
|
|
853
|
+
await refreshOverrides();
|
|
854
|
+
const record = providerRegistry[provider];
|
|
855
|
+
if (record?.catalogo?.fonte === 'runtime-locale' && record?.catalogo?.forma === 'ollama-tags') {
|
|
856
|
+
let runtime = {};
|
|
857
|
+
try {
|
|
858
|
+
runtime = providerStore.getRuntime?.(provider) ?? {};
|
|
859
|
+
}
|
|
860
|
+
catch {
|
|
861
|
+
runtime = {};
|
|
862
|
+
}
|
|
863
|
+
const root = String(runtime.endpoint ?? record.baseUrl ?? '');
|
|
864
|
+
rememberOllamaWindow(provider, modelId, await ollamaWindow({ root, model: modelId, fetchImpl: (url, init) => fetch(url, init) }));
|
|
865
|
+
}
|
|
866
|
+
if (modelsDev.enabled)
|
|
867
|
+
await withinMs(modelsDev.load(provider), 2_000);
|
|
868
|
+
};
|
|
869
|
+
contextBridge = createCliContextBridge({ repoRoot, paths, registry, providerRegistry, providerStore, ownerRuntime, chatImageStore, prepareProfile, onSummaryRequest: () => { const sessionId = toolOutputScope.getStore()?.sessionId; if (sessionId)
|
|
870
|
+
cliEvents.emit(sessionId, summaryRequestedEvent()); }, ...(m.separaFonteModello ? { separaFonteModello: m.separaFonteModello } : {}) });
|
|
871
|
+
/* C1 (see `compactionScope` above): the session's own row decides the model; one compaction at a time, in call order. */
|
|
872
|
+
const compactionOf = (sessionId) => {
|
|
873
|
+
let rows = null;
|
|
874
|
+
try {
|
|
875
|
+
rows = registry.elenca?.();
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
rows = null;
|
|
879
|
+
}
|
|
880
|
+
const row = Array.isArray(rows) ? rows.find((candidate) => candidate?.sessionId === sessionId) ?? null : null;
|
|
881
|
+
const unknown = (details) => ({ model: null, refusal: new CliRuntimeError('SESSION_MODEL_UNKNOWN', 'The model of this session cannot be determined, so it was not compacted and nothing was sent.', { sessionId, ...details }) });
|
|
882
|
+
if (!row)
|
|
883
|
+
return unknown({});
|
|
884
|
+
if (row.provider === 'local')
|
|
885
|
+
return { model: null, refusal: new CliRuntimeError('LOCAL_SESSION_COMPACTION_UNAVAILABLE', 'Compacting a local-model session waits for a kernel fix (CLI-REQ-05), so nothing was sent.', { sessionId, provider: 'local' }) };
|
|
886
|
+
if (row.provider !== 'cloud')
|
|
887
|
+
return unknown({ provider: typeof row.provider === 'string' ? row.provider : null });
|
|
888
|
+
if (typeof row.modello !== 'string' || row.modello.trim() === '')
|
|
889
|
+
return unknown({ provider: 'cloud' });
|
|
890
|
+
return { model: row.modello, refusal: null };
|
|
891
|
+
};
|
|
892
|
+
let compactionTail = Promise.resolve();
|
|
893
|
+
const compactSession = (sessionId) => {
|
|
894
|
+
const run = () => compactionScope.run({ sessionId, ...compactionOf(sessionId) }, () => registry.compatta(sessionId));
|
|
895
|
+
const result = compactionTail.then(run);
|
|
896
|
+
compactionTail = result.then(() => undefined, () => undefined);
|
|
897
|
+
return result;
|
|
898
|
+
};
|
|
899
|
+
/*
|
|
900
|
+
* R5a lane R: `iscriviti` also delivers the session's CLI events (provider attempts, a provider failure's facts, a kernel
|
|
901
|
+
* compaction), live only: they carry no `_sequenza`, so a replay never repeats them and the supervisor's cursor ignores them.
|
|
902
|
+
* `shell` runs the person's `!` command inside its session's output scope (S6).
|
|
903
|
+
*/
|
|
904
|
+
const subscribeWithCliEvents = (sessionId, listener, from = 0) => {
|
|
905
|
+
const offKernel = registry.iscriviti(sessionId, listener, from);
|
|
906
|
+
const offCli = cliEvents.subscribe(sessionId, listener);
|
|
907
|
+
return () => { offCli(); if (typeof offKernel === 'function')
|
|
908
|
+
offKernel(); };
|
|
909
|
+
};
|
|
910
|
+
const sessionRegistry = Object.create(registry, { ...(typeof registry?.compatta === 'function' ? { compatta: { value: compactSession, enumerable: true } } : {}), statoContesto: { value: (sessionId) => contextBridge.status(sessionId), enumerable: true }, chiudiContesto: { value: () => contextBridge.close(), enumerable: false },
|
|
911
|
+
...(typeof registry?.iscriviti === 'function' ? { iscriviti: { value: subscribeWithCliEvents, enumerable: true } } : {}),
|
|
912
|
+
...(typeof registry?.shell === 'function' ? { shell: { value: (sessionId, comando) => toolOutputScope.run({ sessionId }, () => registry.shell(sessionId, comando)), enumerable: true } } : {}) });
|
|
913
|
+
let tuiCatalog = null;
|
|
914
|
+
const modelImageCapability = async (runModel) => { const provider = providerOfModel(runModel); if (!provider || !tuiCatalog)
|
|
915
|
+
return null; try {
|
|
916
|
+
const rows = await tuiCatalog.listModels({ provider });
|
|
917
|
+
return rows.find((row) => row.id === runModel)?.images ?? null;
|
|
918
|
+
}
|
|
919
|
+
catch {
|
|
920
|
+
return null;
|
|
921
|
+
} };
|
|
922
|
+
const materializeImage = async (attachment) => { if (!chatImageStore)
|
|
923
|
+
throw new CliRuntimeError('IMAGE_ATTACHMENT_UNAVAILABLE', 'Image attachment storage is unavailable.'); const name = String(attachment.path).split('/').at(-1) || 'image'; return chatImageStore.upload({ nome: name, dataUrl: `data:${attachment.mimeType};base64,${attachment.dataBase64}` }); };
|
|
924
|
+
const runtime = createSessionFacade(sessionRegistry, { model, origin: 'talos-cli', modelImageCapability, materializeImage });
|
|
925
|
+
attachKeyOrigin(runtime, (runModel) => { const provider = providerOfModel(runModel); if (!provider)
|
|
926
|
+
return null; let publicRow = null; try {
|
|
927
|
+
publicRow = (providerStore.listPublic?.() ?? []).find((row) => row?.id === provider) ?? null;
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
publicRow = null;
|
|
931
|
+
} return describeKeyOrigin({ registry: providerRegistry, provider, publicRow, storeEnv: storeEnvironment(), mode: environmentKeys, store: credentialStore?.kind ?? null }); });
|
|
932
|
+
/* The probe over the saved keys. B1 slice 18: listing, probing and `talos provider test` all build it here, and none of them passes
|
|
933
|
+
`consentiGenerazione`; only the key test's own probe (`createKeyProbe`) may, for a provider that declares its minimal request. */
|
|
934
|
+
const storeProbe = (callerSignal) => { if (typeof m.createProviderProbe !== 'function')
|
|
935
|
+
return { prova: async () => { throw Object.assign(new Error('PROVIDER_PROBE_UNAVAILABLE'), { code: 'PROVIDER_PROBE_UNAVAILABLE' }); }, elencaModelli: async () => { throw Object.assign(new Error('PROVIDER_CATALOG_UNAVAILABLE'), { code: 'PROVIDER_CATALOG_UNAVAILABLE' }); } }; const fetchImpl = (input, init = {}) => { const internal = init?.signal; let signal = callerSignal ?? internal; if (callerSignal && internal)
|
|
936
|
+
signal = AbortSignal.any([internal, callerSignal]); return fetch(input, { ...init, ...(signal ? { signal } : {}) }); }; return m.createProviderProbe({ leggiChiave: (id) => { try {
|
|
937
|
+
return providerStore.getKey?.(id) ?? null;
|
|
938
|
+
}
|
|
939
|
+
catch {
|
|
940
|
+
return null;
|
|
941
|
+
} }, leggiRuntime: (id) => { try {
|
|
942
|
+
return providerStore.getRuntime?.(id) ?? {};
|
|
943
|
+
}
|
|
944
|
+
catch {
|
|
945
|
+
return {};
|
|
946
|
+
} }, fetchImpl, env }); };
|
|
947
|
+
const providerControlPlane = createProviderControlPlane({
|
|
948
|
+
publicProviders: () => { try {
|
|
949
|
+
return providerStore.listPublic?.() ?? [];
|
|
950
|
+
}
|
|
951
|
+
catch {
|
|
952
|
+
return [];
|
|
953
|
+
} }, registry: providerRegistry,
|
|
954
|
+
runtimeFor: (id) => { try {
|
|
955
|
+
return providerStore.getRuntime?.(id) ?? {};
|
|
956
|
+
}
|
|
957
|
+
catch {
|
|
958
|
+
return {};
|
|
959
|
+
} }, keyFor: (id) => { try {
|
|
960
|
+
return providerStore.getKey?.(id) ?? null;
|
|
961
|
+
}
|
|
962
|
+
catch {
|
|
963
|
+
return null;
|
|
964
|
+
} },
|
|
965
|
+
createProbe: storeProbe, environmentKey: (id) => environmentKeys === 'use' ? null : consent.stateFor(id),
|
|
966
|
+
});
|
|
967
|
+
tuiCatalog = createTuiCatalogService({
|
|
968
|
+
/* P21: OpenRouter sign-in with the kernel's PKCE module (the checkout's, or the package's vendor copy). */
|
|
969
|
+
openRouterLogin: async (input) => { const { loadOpenRouterOAuth, startOpenRouterLogin } = await import("../provider/openrouter-login.js"); return startOpenRouterLogin({ oauth: await loadOpenRouterOAuth(repoRoot), ...(input?.paste ? { paste: true } : {}) }); },
|
|
970
|
+
publicProviders: () => { try {
|
|
971
|
+
return providerStore.listPublic?.() ?? [];
|
|
972
|
+
}
|
|
973
|
+
catch {
|
|
974
|
+
return [];
|
|
975
|
+
} }, registry: m.REGISTRO_FORNITORI ?? {},
|
|
976
|
+
runtimeFor: (id) => { try {
|
|
977
|
+
return providerStore.getRuntime?.(id) ?? {};
|
|
978
|
+
}
|
|
979
|
+
catch {
|
|
980
|
+
return {};
|
|
981
|
+
} }, keyFor: (id) => { try {
|
|
982
|
+
return providerStore.getKey?.(id) ?? null;
|
|
983
|
+
}
|
|
984
|
+
catch {
|
|
985
|
+
return null;
|
|
986
|
+
} },
|
|
987
|
+
setKey: (id, secret) => { if (typeof providerStore.setKey !== 'function')
|
|
988
|
+
throw Object.assign(new Error('PROVIDER_STORE_UNAVAILABLE'), { code: 'PROVIDER_STORE_UNAVAILABLE' }); providerStore.setKey(id, secret); },
|
|
989
|
+
clearKey: (id) => { if (typeof providerStore.clearKey !== 'function')
|
|
990
|
+
throw Object.assign(new Error('PROVIDER_STORE_UNAVAILABLE'), { code: 'PROVIDER_STORE_UNAVAILABLE' }); providerStore.clearKey(id); },
|
|
991
|
+
createProbe: storeProbe,
|
|
992
|
+
persistModel: (id) => configSet({ paths, projectRoot, scope: 'project-user', path: 'model', value: id }),
|
|
993
|
+
/* ⛔ The candidate key is readable ONLY for the provider it is being tested against, and the probe builds
|
|
994
|
+
the request from that provider's own endpoint: the key cannot reach another provider through this door. */
|
|
995
|
+
createKeyProbe: (provider, secret, callerSignal) => { if (typeof m.createProviderProbe !== 'function')
|
|
996
|
+
return { prova: async () => ({ provider, esito: 'non-provabile', motivo: 'probe unavailable' }) }; const fetchImpl = (input, init = {}) => { const internal = init?.signal; let signal = callerSignal ?? internal; if (callerSignal && internal)
|
|
997
|
+
signal = AbortSignal.any([internal, callerSignal]); return fetch(input, { ...init, ...(signal ? { signal } : {}) }); }; return m.createProviderProbe({ leggiChiave: (id) => id === provider ? secret : null, leggiRuntime: (id) => { try {
|
|
998
|
+
return providerStore.getRuntime?.(id) ?? {};
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
return {};
|
|
1002
|
+
} }, fetchImpl, env }); },
|
|
1003
|
+
fetchImpl: (url, init) => fetch(url, init),
|
|
1004
|
+
environmentKey: (id) => environmentKeys === 'use' ? null : consent.stateFor(id),
|
|
1005
|
+
answerEnvironmentKey,
|
|
1006
|
+
chosenProvider: () => consent.chosenProvider(),
|
|
1007
|
+
chooseProvider: (id) => consent.chooseProvider(id),
|
|
1008
|
+
controlPlane: providerControlPlane,
|
|
1009
|
+
});
|
|
1010
|
+
return { runtime, registry: sessionRegistry, providerStore, ownerRuntime, searchSourceStore, tuiCatalog, providerProbe: storeProbe, providerControlPlane, credentialStore };
|
|
1011
|
+
}
|