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,629 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { createServer } from 'node:net';
|
|
5
|
+
import { basename, dirname, isAbsolute, join } from 'node:path';
|
|
6
|
+
import { createProcessPolicy } from './process-policy.mjs';
|
|
7
|
+
import { statSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
const LOOPBACK = '127.0.0.1';
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
+
/*
|
|
12
|
+
* ⛔⛔⛔ 03/9 — «PERCHE' CAZZO NE DEVI USARE UNO DA 600 MILIONI?».
|
|
13
|
+
*
|
|
14
|
+
* L'owner aveva scaricato un 27B da 15,7 GB e io usavo un 0.6B, perche' il
|
|
15
|
+
* 27B «non partiva»: la rotta di caricamento rispondeva INTERNAL_ERROR dopo
|
|
16
|
+
* 15 secondi netti. Avevo creduto alla mia stima di memoria e non ho chiesto
|
|
17
|
+
* il motivo vero.
|
|
18
|
+
*
|
|
19
|
+
* Il motivo vero, misurato lanciando llama-server a mano: il modello STA
|
|
20
|
+
* CARICANDO — oltre due minuti per leggere 15,7 GB dal disco — e siamo noi ad
|
|
21
|
+
* abbandonarlo dopo 15 secondi. Non era «troppo grande»: era un'attesa
|
|
22
|
+
* tarata su modelli piccoli.
|
|
23
|
+
*
|
|
24
|
+
* ⇒ L'attesa si commisura ai BYTE del modello: quindici secondi di base piu'
|
|
25
|
+
* quindici per gigabyte. Sul 27B fanno ~4 minuti, sul 0.6B restano i 15
|
|
26
|
+
* secondi di sempre. ⛔ Non una costante piu' grande per tutti: un modello
|
|
27
|
+
* piccolo che non parte deve continuare a dirlo subito, non far aspettare
|
|
28
|
+
* quattro minuti per scoprire che il binario e' rotto.
|
|
29
|
+
*/
|
|
30
|
+
const ATTESA_PER_GIGABYTE_MS = 15_000;
|
|
31
|
+
export function attesaSaluteMs(byteModello, base = DEFAULT_TIMEOUT_MS) {
|
|
32
|
+
const gb = Number.isFinite(byteModello) && byteModello > 0 ? byteModello / 1_000_000_000 : 0;
|
|
33
|
+
return Math.round(base + gb * ATTESA_PER_GIGABYTE_MS);
|
|
34
|
+
}
|
|
35
|
+
const DEFAULT_POLL_MS = 100;
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* ⭐⭐⭐ BC-13, 12/09/2026 — LE LEVE DI VELOCITÀ NON SI SCRIVONO A MANO: SI
|
|
39
|
+
* CHIEDONO AL BINARIO.
|
|
40
|
+
*
|
|
41
|
+
* Owner 12/09: «bisogna fare una ricerca delle ultime tecnologie e metodi
|
|
42
|
+
* all'avanguardia, dobbiamo rendere il motore di llm locale estremamente
|
|
43
|
+
* rapido e meglio dei competitor». E vincolo dell'11/09, che vale su tutto
|
|
44
|
+
* quello che segue: «NESSUN MODELLO PREDEFINITO: motore ottimizzato a
|
|
45
|
+
* livello UNIVERSALE, non forziamo nulla, sarà l'utente a decidere».
|
|
46
|
+
*
|
|
47
|
+
* ⇒ Nessuna delle due leve qui sotto nomina un modello, una scheda o un
|
|
48
|
+
* numero magico: ognuna fa una DOMANDA al binario che l'utente ha, e usa la
|
|
49
|
+
* risposta. Se il binario non sa rispondere, si resta esattamente al
|
|
50
|
+
* comportamento di prima.
|
|
51
|
+
*/
|
|
52
|
+
const TIMEOUT_SONDA_MS = 30_000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Il fitter ufficiale di llama.cpp (`llama-fit-params`, lo stesso codice che
|
|
56
|
+
* il server usa con `--fit on`) stampa su stdout gli argomenti che ENTRANO
|
|
57
|
+
* nella memoria del dispositivo, per esempio `-c 16384 -ngl -1` oppure
|
|
58
|
+
* `-c 16384 -ngl 56`.
|
|
59
|
+
*
|
|
60
|
+
* ⛔ `-1` (o `all`) è l'unico esito che dice «ci sta TUTTO». Un numero è già
|
|
61
|
+
* una resa parziale: qualche livello resterebbe fuori dalla scheda.
|
|
62
|
+
*/
|
|
63
|
+
export function leggiNglDalFitter(stdout) {
|
|
64
|
+
if (typeof stdout !== 'string') return null;
|
|
65
|
+
const trovato = /-ngl\s+(-?\d+|all|auto)/u.exec(stdout);
|
|
66
|
+
if (!trovato) return null;
|
|
67
|
+
const valore = trovato[1];
|
|
68
|
+
if (valore === 'all' || valore === '-1') return 'tutto';
|
|
69
|
+
if (valore === 'auto') return null;
|
|
70
|
+
const numero = Number(valore);
|
|
71
|
+
return Number.isInteger(numero) ? numero : null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* ⭐⭐⭐ MISURATO 12/09/2026 sul banco (Qwen3-4B-Q4_K_M, RX 9070 XT Vulkan,
|
|
76
|
+
* preambolo di 11.065 token, 3 ripetizioni, mediane):
|
|
77
|
+
*
|
|
78
|
+
* KV cache q8_0 (quello che passavamo SEMPRE): prompt 3.964 ms · gen 127,6 t/s
|
|
79
|
+
* KV cache f16 (il predefinito del binario): prompt 2.849 ms · gen 117,5 t/s
|
|
80
|
+
*
|
|
81
|
+
* Cioè: quantizzare la KV cache costa **+39% sul tempo al primo token** e
|
|
82
|
+
* regala +8,6% in generazione. Su un preambolo d'agente da 11.000 token il
|
|
83
|
+
* primo token è quello che la persona aspetta — 1,1 secondi in più, ogni
|
|
84
|
+
* volta che la cache non prende. Sul compito «ricopia» la forbice è ancora
|
|
85
|
+
* più larga: 4.212 ms contro 2.969 (−29,5%), e il turno successivo 289 ms
|
|
86
|
+
* contro 179 (−38,1%).
|
|
87
|
+
*
|
|
88
|
+
* ⛔ La misura del 03/09 che aveva introdotto q8_0 («+408% in elaborazione
|
|
89
|
+
* del prompt») era stata presa sul modello giocattolo da 0,6B Q2_K, dove la
|
|
90
|
+
* KV cache è minuscola: su un modello vero il verso si ROVESCIA. Non è che
|
|
91
|
+
* quella misura fosse sbagliata — è che non parlava di questo caso.
|
|
92
|
+
*
|
|
93
|
+
* ⛔ E q8_0 NON è inutile: dimezza la cache. Sullo stesso 4B a 131.072 token
|
|
94
|
+
* di contesto il fitter risponde `-ngl 24` con f16 (24 livelli su GPU, il
|
|
95
|
+
* resto sul processore = disastro) e `-ngl -1` con q8_0 (tutto sulla
|
|
96
|
+
* scheda). ⇒ La scelta NON è una preferenza, è una MISURA che cambia da
|
|
97
|
+
* modello a modello e da contesto a contesto.
|
|
98
|
+
*
|
|
99
|
+
* ⇒ Regola universale: f16 se con f16 ci sta tutto; altrimenti q8_0.
|
|
100
|
+
* Se non si riesce a chiedere, q8_0 — cioè il comportamento di ieri.
|
|
101
|
+
*
|
|
102
|
+
* @returns {{tipo: 'f16'|'q8_0', perche: string}}
|
|
103
|
+
*/
|
|
104
|
+
export function decidiTipoKvCache({ nglConF16, nglConQ8 } = {}) {
|
|
105
|
+
if (nglConF16 === 'tutto') {
|
|
106
|
+
return { tipo: 'f16', perche: 'con KV f16 il fitter del binario dichiara che TUTTI i livelli entrano nel dispositivo' };
|
|
107
|
+
}
|
|
108
|
+
if (nglConF16 === null || nglConF16 === undefined) {
|
|
109
|
+
return { tipo: 'q8_0', perche: 'il fitter del binario non ha risposto: si resta sulla KV quantizzata di prima' };
|
|
110
|
+
}
|
|
111
|
+
if (nglConQ8 === 'tutto') {
|
|
112
|
+
return { tipo: 'q8_0', perche: `con KV f16 entrerebbero solo ${nglConF16} livelli, con q8_0 entrano tutti` };
|
|
113
|
+
}
|
|
114
|
+
return { tipo: 'q8_0', perche: `con KV f16 entrerebbero solo ${nglConF16} livelli: la cache dimezzata ne fa entrare di più` };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* ⭐⭐⭐ MISURATO 12/09/2026, stesso banco, compito «ricopia alla lettera un
|
|
119
|
+
* passaggio del contesto» — cioè il lavoro vero di un agente: rimettere
|
|
120
|
+
* fuori un risultato d'attrezzo, riscrivere un file che ha appena letto.
|
|
121
|
+
* Generazione, mediane su 3 ripetizioni:
|
|
122
|
+
*
|
|
123
|
+
* senza 123,5 token/s
|
|
124
|
+
* --spec-type ngram-simple 158,4 token/s (+28%)
|
|
125
|
+
* --spec-type ngram-cache 202,3 token/s (+64%)
|
|
126
|
+
* --spec-type ngram-mod 353,2 token/s (+186%, cioè 2,9×)
|
|
127
|
+
* ngram-mod + KV f16 445,5 token/s (+261% sulla riga di oggi)
|
|
128
|
+
*
|
|
129
|
+
* ⭐ Perché conta più di quanto sembri: la decodifica speculativa classica
|
|
130
|
+
* vuole un SECONDO modello (il «draft»). LM Studio la offre solo così — la
|
|
131
|
+
* sua documentazione dice che «relies on the collaboration of two models»
|
|
132
|
+
* (lmstudio.ai/docs/app/advanced/speculative-decoding, letta il 12/09/2026);
|
|
133
|
+
* Ollama non la offre affatto (docs.ollama.com/faq, 12/09/2026). Le varianti
|
|
134
|
+
* `ngram-*` di llama.cpp NON vogliono nessun secondo modello: pescano i
|
|
135
|
+
* candidati dal contesto già presente. ⇒ È l'unica forma compatibile col
|
|
136
|
+
* vincolo «nessun modello predefinito», e vale per un GGUF qualunque.
|
|
137
|
+
*
|
|
138
|
+
* ⛔ IL VERSO CONTRARIO, misurato e non supposto: quando nel contesto non
|
|
139
|
+
* c'è niente da pescare la leva COSTA. Prima chiamata di un server appena
|
|
140
|
+
* acceso, caso peggiore osservato 110,7 token/s contro 123,7 (−10,5%); sul
|
|
141
|
+
* compito «riassumi» (testo nuovo) prima chiamata 124,7 contro 127,6
|
|
142
|
+
* (−2,3%). Il costo si paga una volta e si ripaga dalla seconda chiamata in
|
|
143
|
+
* poi: nella stessa sessione la mediana sale a 353. Un harness fa decine di
|
|
144
|
+
* chiamate per giro, non una.
|
|
145
|
+
*
|
|
146
|
+
* ⛔ Si accende SOLO se questo binario la offre davvero: `--spec-type` è
|
|
147
|
+
* comparso da poco e un binario più vecchio morirebbe all'avvio con
|
|
148
|
+
* «unknown argument». Non si suppone: si legge il suo `--help`.
|
|
149
|
+
*/
|
|
150
|
+
export function supportaSpeculativaNgram(testoAiuto) {
|
|
151
|
+
if (typeof testoAiuto !== 'string' || testoAiuto === '') return false;
|
|
152
|
+
const riga = /--spec-type[^\n]*/u.exec(testoAiuto);
|
|
153
|
+
return Boolean(riga && riga[0].includes('ngram-mod'));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Sonda sincrona e volutamente povera: un eseguibile ACCANTO al binario del
|
|
158
|
+
* server, nessuna shell, un tetto di tempo, e un `catch` che non nasconde
|
|
159
|
+
* niente perché chi chiama tratta `null` come «non lo so».
|
|
160
|
+
*/
|
|
161
|
+
function creaSondaBinario(spawnSyncImpl = spawnSync) {
|
|
162
|
+
return function sonda(eseguibile, argomenti, timeoutMs = TIMEOUT_SONDA_MS) {
|
|
163
|
+
try {
|
|
164
|
+
const esito = spawnSyncImpl(eseguibile, argomenti, {
|
|
165
|
+
shell: false, windowsHide: true, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024,
|
|
166
|
+
});
|
|
167
|
+
if (!esito || esito.error) return null;
|
|
168
|
+
return `${esito.stdout ?? ''}\n${esito.stderr ?? ''}`;
|
|
169
|
+
} catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** `llama-fit-params` sta nella stessa cartella di `llama-server`, con la stessa estensione. */
|
|
176
|
+
export function percorsoFitter(binaryPath) {
|
|
177
|
+
if (typeof binaryPath !== 'string' || binaryPath.trim() === '') return null;
|
|
178
|
+
const nome = basename(binaryPath);
|
|
179
|
+
const sostituito = nome.replace(/llama-server/iu, 'llama-fit-params');
|
|
180
|
+
if (sostituito === nome) return null;
|
|
181
|
+
return join(dirname(binaryPath), sostituito);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export class LlamaServerSupervisorError extends Error {
|
|
185
|
+
constructor(message, code = 'RUNTIME_FAILED') {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = 'LlamaServerSupervisorError';
|
|
188
|
+
this.code = code;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function invalid(message) {
|
|
193
|
+
return new LlamaServerSupervisorError(message, 'RUNTIME_INVALID');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function allocatePort() {
|
|
197
|
+
const server = createServer();
|
|
198
|
+
await new Promise((resolve, reject) => { server.once('error', reject); server.listen(0, LOOPBACK, resolve); });
|
|
199
|
+
const port = server.address().port;
|
|
200
|
+
await new Promise((resolve) => server.close(resolve));
|
|
201
|
+
return port;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function wait(ms) {
|
|
205
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function createLlamaServerSupervisor({
|
|
209
|
+
binaryPath,
|
|
210
|
+
// R-03: il binario CPU di riserva e la descrizione del motore scelto dal guscio.
|
|
211
|
+
fallbackBinaryPath = null,
|
|
212
|
+
motore: motoreDichiarato = null,
|
|
213
|
+
modelStore = null,
|
|
214
|
+
spawnImpl,
|
|
215
|
+
fetchImpl = fetch,
|
|
216
|
+
portAllocator = allocatePort,
|
|
217
|
+
now = () => new Date(),
|
|
218
|
+
healthTimeoutMs = DEFAULT_TIMEOUT_MS,
|
|
219
|
+
/**
|
|
220
|
+
* Quanti livelli mandare sulla GPU. ⛔ `0` (predefinito) = nessuno, cioè il
|
|
221
|
+
* comportamento di sempre: una build senza backend accetterebbe `-ngl` e lo
|
|
222
|
+
* ignorerebbe in silenzio, facendoci credere di usare una scheda che non
|
|
223
|
+
* stiamo toccando.
|
|
224
|
+
*/
|
|
225
|
+
gpuLayers = 0,
|
|
226
|
+
pollIntervalMs = DEFAULT_POLL_MS,
|
|
227
|
+
/**
|
|
228
|
+
* ⭐ BC-13 — come si INTERROGA il binario (il suo `--help`, il suo fitter).
|
|
229
|
+
* Iniettabile perché nessun test debba avviare un processo vero, e perché
|
|
230
|
+
* la prova al verso contrario («il binario NON offre la leva») si possa
|
|
231
|
+
* scrivere senza procurarsi un binario vecchio.
|
|
232
|
+
*/
|
|
233
|
+
sondaBinario = creaSondaBinario(),
|
|
234
|
+
/**
|
|
235
|
+
* ⛔ L'interruttore della speculativa, e il motivo per cui esiste: il
|
|
236
|
+
* difetto aperto ggml-org/llama.cpp#25819 — «server : add stuck-loop escape
|
|
237
|
+
* for ngram-mod (WIP)», aperto il 17/07/2026 e ancora aperto al 12/09/2026
|
|
238
|
+
* — descrive un ciclo che non esce quando la verifica dei candidati
|
|
239
|
+
* fallisce ripetutamente. Sul banco del 12/09 non si è mai presentato (3
|
|
240
|
+
* ripetizioni × 2 compiti × 2 modelli di cache, uscite identiche byte per
|
|
241
|
+
* byte alla riga senza speculativa su 3 domande su 3), ma un difetto
|
|
242
|
+
* aperto a monte si spegne da UN posto, non riscrivendo il codice.
|
|
243
|
+
* `'auto'` = si accende se il binario la offre; `'off'` = mai.
|
|
244
|
+
*/
|
|
245
|
+
speculativaNgram = 'auto',
|
|
246
|
+
} = {}) {
|
|
247
|
+
if (typeof binaryPath !== 'string' || binaryPath.trim() === '') throw new LlamaServerSupervisorError('binaryPath is required', 'RUNTIME_MISCONFIGURED');
|
|
248
|
+
if (fallbackBinaryPath !== null && (typeof fallbackBinaryPath !== 'string' || fallbackBinaryPath.trim() === '')) throw new LlamaServerSupervisorError('fallbackBinaryPath must be a non-empty string', 'RUNTIME_MISCONFIGURED');
|
|
249
|
+
const processPolicy = createProcessPolicy({ allowedExecutables: [binaryPath, ...(fallbackBinaryPath ? [fallbackBinaryPath] : [])], spawnFn: spawnImpl });
|
|
250
|
+
let current = null;
|
|
251
|
+
let state = 'unavailable';
|
|
252
|
+
const listeners = new Set();
|
|
253
|
+
/* Le risposte del binario non cambiano fra un avvio e l'altro: si pagano
|
|
254
|
+
* una volta sola. Il fitter dipende anche dal modello e dal contesto.
|
|
255
|
+
* R-03: si ricordano PER BINARIO — la riserva CPU non offre le stesse cose. */
|
|
256
|
+
const aiutoPerBinario = new Map();
|
|
257
|
+
const leveMemorizzate = new Map();
|
|
258
|
+
const motoreIniziale = Object.freeze({
|
|
259
|
+
variante: motoreDichiarato?.variante ?? (/vulkan/iu.test(basename(binaryPath)) ? 'vulkan' : 'cpu'),
|
|
260
|
+
dispositivi: Array.isArray(motoreDichiarato?.dispositivi) ? [...motoreDichiarato.dispositivi] : [],
|
|
261
|
+
});
|
|
262
|
+
let motore = { variante: motoreIniziale.variante, dispositivi: [...motoreIniziale.dispositivi], ripiego: null, proposta: null };
|
|
263
|
+
|
|
264
|
+
function speculativaDisponibile(binario = binaryPath) {
|
|
265
|
+
if (speculativaNgram === 'off') return false;
|
|
266
|
+
if (!aiutoPerBinario.has(binario)) aiutoPerBinario.set(binario, sondaBinario(binario, ['--help'], 10_000));
|
|
267
|
+
return supportaSpeculativaNgram(aiutoPerBinario.get(binario) ?? '');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* ⛔ Si chiede al fitter SOLO quando stiamo davvero offloadando: su una
|
|
272
|
+
* build senza backend la domanda «ci sta nella scheda?» non ha oggetto, e
|
|
273
|
+
* nessuno dei due argomenti verrebbe passato comunque.
|
|
274
|
+
*/
|
|
275
|
+
function leveVelocita(binario, modelPath, contextLength) {
|
|
276
|
+
const chiave = `${binario}|${modelPath}|${contextLength ?? ''}`;
|
|
277
|
+
if (leveMemorizzate.has(chiave)) return leveMemorizzate.get(chiave);
|
|
278
|
+
const fitter = percorsoFitter(binario);
|
|
279
|
+
const contesto = Number.isInteger(contextLength) && contextLength > 0 ? ['-c', String(contextLength)] : [];
|
|
280
|
+
let kv = { tipo: 'q8_0', perche: 'il fitter del binario non è stato trovato: si resta sulla KV quantizzata di prima' };
|
|
281
|
+
if (fitter) {
|
|
282
|
+
const conF16 = leggiNglDalFitter(sondaBinario(fitter, ['-m', modelPath, ...contesto, '-fa', '1']) ?? '');
|
|
283
|
+
const conQ8 = conF16 === 'tutto'
|
|
284
|
+
? null
|
|
285
|
+
: leggiNglDalFitter(sondaBinario(fitter, ['-m', modelPath, ...contesto, '-fa', '1', '-ctk', 'q8_0', '-ctv', 'q8_0']) ?? '');
|
|
286
|
+
kv = decidiTipoKvCache({ nglConF16: conF16, nglConQ8: conQ8 });
|
|
287
|
+
}
|
|
288
|
+
const leve = { kv, speculativa: speculativaDisponibile(binario) };
|
|
289
|
+
leveMemorizzate.set(chiave, leve);
|
|
290
|
+
return leve;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function status() {
|
|
294
|
+
// R-03: `motore` esce sempre (anche a riposo o dopo un guasto): variante, dispositivi,
|
|
295
|
+
// l'eventuale ripiego avvenuto in questo caricamento e la proposta per la persona.
|
|
296
|
+
const fotoMotore = { variante: motore.variante, dispositivi: [...motore.dispositivi], ripiego: motore.ripiego ? { ...motore.ripiego } : null, proposta: motore.proposta ? { ...motore.proposta } : null };
|
|
297
|
+
if (!current) return { state, runtimeId: 'llama.cpp', motore: fotoMotore, observedAt: now().toISOString() };
|
|
298
|
+
return {
|
|
299
|
+
state: current.state,
|
|
300
|
+
runtimeId: 'llama.cpp',
|
|
301
|
+
motore: fotoMotore,
|
|
302
|
+
port: current.port,
|
|
303
|
+
baseUrl: current.baseUrl,
|
|
304
|
+
/*
|
|
305
|
+
* ⛔ 02/9 — QUALE modello è caricato, non solo CHE ce n'è uno.
|
|
306
|
+
* `current.modelId` era già tracciato dall'avvio (è lo stesso valore
|
|
307
|
+
* che finisce in `--alias`), ma non usciva da qui: chi chiedeva lo
|
|
308
|
+
* stato sapeva che un runtime era pronto e non di chi fosse. Da lì
|
|
309
|
+
* nasceva il difetto curato in `local-runtime-probe.mjs` — l'`n_ctx`
|
|
310
|
+
* del modello caricato attribuito a QUALUNQUE modello ispezionato.
|
|
311
|
+
*/
|
|
312
|
+
modelId: current.modelId ?? null,
|
|
313
|
+
observedAt: now().toISOString(),
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function emitLog(stream, chunk) {
|
|
318
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
319
|
+
for (const listener of listeners) {
|
|
320
|
+
try { listener({ stream, text }); } catch { /* observer failure must not affect the process */ }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function attachProcess(entry) {
|
|
325
|
+
const onClose = () => {
|
|
326
|
+
// R-03: l'ultima riga senza a-capo (il motore muore a metà frase) si tiene lo stesso.
|
|
327
|
+
if (entry.residuoStderr && entry.residuoStderr.trim() !== '') {
|
|
328
|
+
entry.ultimeRighe.push(entry.residuoStderr.trim());
|
|
329
|
+
if (entry.ultimeRighe.length > 12) entry.ultimeRighe.shift();
|
|
330
|
+
}
|
|
331
|
+
entry.residuoStderr = '';
|
|
332
|
+
entry.closed = true;
|
|
333
|
+
if (current === entry && entry.state !== 'stopping') {
|
|
334
|
+
entry.state = 'failed';
|
|
335
|
+
state = 'failed';
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const onError = (error) => {
|
|
339
|
+
entry.failure = error;
|
|
340
|
+
entry.state = 'failed';
|
|
341
|
+
state = 'failed';
|
|
342
|
+
};
|
|
343
|
+
entry.child.once('close', onClose);
|
|
344
|
+
entry.child.once('error', onError);
|
|
345
|
+
entry.child.stdout?.on('data', (chunk) => emitLog('stdout', chunk));
|
|
346
|
+
entry.child.stderr?.on('data', (chunk) => {
|
|
347
|
+
/*
|
|
348
|
+
* ⛔ BC-13 — le ultime righe si TENGONO, non solo si trasmettono. Sono
|
|
349
|
+
* l'unica cosa che spiega perché un motore non è partito, e finora
|
|
350
|
+
* uscivano solo verso chi si era iscritto ai log: chi riceveva
|
|
351
|
+
* l'eccezione leggeva «timeout» e andava a cercare un guasto che non
|
|
352
|
+
* c'era.
|
|
353
|
+
*/
|
|
354
|
+
const testo = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
355
|
+
/*
|
|
356
|
+
* ⛔ R-03, 13/09 — i pezzi arrivano SPEZZATI («No d» + «evices found.»): una
|
|
357
|
+
* riga si chiude solo all'a-capo, altrimenti la firma del guasto non si
|
|
358
|
+
* riconosce. Il residuo senza a-capo si tiene fino al prossimo pezzo o alla
|
|
359
|
+
* chiusura del processo (vedi onClose).
|
|
360
|
+
*/
|
|
361
|
+
entry.residuoStderr = (entry.residuoStderr ?? '') + testo;
|
|
362
|
+
const parti = entry.residuoStderr.split('\n');
|
|
363
|
+
entry.residuoStderr = parti.pop();
|
|
364
|
+
for (const riga of parti) {
|
|
365
|
+
if (riga.trim() === '') continue;
|
|
366
|
+
entry.ultimeRighe.push(riga.trim());
|
|
367
|
+
if (entry.ultimeRighe.length > 12) entry.ultimeRighe.shift();
|
|
368
|
+
}
|
|
369
|
+
emitLog('stderr', chunk);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function health() {
|
|
374
|
+
if (!current) return { ok: false, status: 0, code: 'RUNTIME_UNREACHABLE' };
|
|
375
|
+
try {
|
|
376
|
+
const response = await fetchImpl(`${current.baseUrl}/health`, {
|
|
377
|
+
headers: { Accept: 'application/json', Authorization: `Bearer ${current.apiKey}` },
|
|
378
|
+
});
|
|
379
|
+
return { ok: response.ok, status: response.status };
|
|
380
|
+
} catch {
|
|
381
|
+
return { ok: false, status: 0, code: 'RUNTIME_UNREACHABLE' };
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Internal authenticated transport. The bearer token never leaves this
|
|
386
|
+
// module through status() or logs; runtimes receive only this capability.
|
|
387
|
+
async function request(path, options = {}) {
|
|
388
|
+
if (typeof path !== 'string' || !path.startsWith('/') || path.includes('://')) {
|
|
389
|
+
throw invalid('runtime request path must be relative');
|
|
390
|
+
}
|
|
391
|
+
if (!current || current.state !== 'ready') throw new LlamaServerSupervisorError('runtime is not ready', 'RUNTIME_NOT_READY');
|
|
392
|
+
const headers = new Headers(options.headers ?? {});
|
|
393
|
+
if (!headers.has('Accept')) headers.set('Accept', 'application/json');
|
|
394
|
+
headers.set('Authorization', `Bearer ${current.apiKey}`);
|
|
395
|
+
return fetchImpl(`${current.baseUrl}${path}`, { ...options, headers });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* @param {{modelId?: string, modelPath: string, port?: number, contextLength?: number}} opzioni
|
|
400
|
+
* `contextLength` — ⛔ QUANTI TOKEN chiedere al motore. Vedi il commento
|
|
401
|
+
* sopra `-c` più sotto: senza, llama.cpp prova ad allocare il contesto
|
|
402
|
+
* ADDESTRATO, e su un modello grande non ci sta in nessuna macchina.
|
|
403
|
+
*/
|
|
404
|
+
/*
|
|
405
|
+
* ⭐⭐⭐ R-03, 13/09/2026 — IL MOTORE SI SCEGLIE DALLA MACCHINA, E SE LA SCHEDA
|
|
406
|
+
* NON C'È SI RIPIEGA SUL PROCESSORE.
|
|
407
|
+
*
|
|
408
|
+
* Il guscio sceglie la build Vulkan solo se `--list-devices` elenca un
|
|
409
|
+
* dispositivo (`desktop/runtime.mjs`), ma una scheda può mancare o sparire
|
|
410
|
+
* DOPO: driver assente («ggml_vulkan: No devices found», «ErrorIncompatibleDriver»)
|
|
411
|
+
* o dispositivo perso («vk::DeviceLostError», «ErrorDeviceLost»). In quei due
|
|
412
|
+
* casi, e solo in quelli, il caricamento riparte UNA volta sul binario CPU di
|
|
413
|
+
* riserva (`fallbackBinaryPath`, `-ngl 0 --device none`, senza KV quantizzata né
|
|
414
|
+
* speculativa se quel binario non la offre), e lo stato lo dice:
|
|
415
|
+
* `motore.ripiego = { da, a, motivo }`.
|
|
416
|
+
*
|
|
417
|
+
* ⛔ NON si ripiega su «ErrorOutOfDeviceMemory»: la scheda c'è, è il modello che
|
|
418
|
+
* non entra (llama.cpp #15054, #5848, #9271, letti il 13/09/2026); sul
|
|
419
|
+
* processore girerebbe ma lentissimo, e la scelta spetta alla persona:
|
|
420
|
+
* `motore.proposta = { a: 'cpu', motivo }`. Né su un errore generico (file GGUF
|
|
421
|
+
* rotto, argomento sconosciuto): cambiare binario non lo curerebbe.
|
|
422
|
+
* ⛔ Un processo morto DOPO essere diventato pronto non riparte da solo (come
|
|
423
|
+
* prima); il prossimo `start()` ritenta sempre dal binario scelto dal guscio.
|
|
424
|
+
* Firme prese da ggml-vulkan.cpp al pin b10517 (elenco chiuso: un regex largo
|
|
425
|
+
* scambierebbe un avviso per un guasto). Fonti nel rapporto R-03.
|
|
426
|
+
*/
|
|
427
|
+
const FIRME_RIPIEGO = Object.freeze([
|
|
428
|
+
{ classe: 'driver', motivo: 'la scheda grafica non è disponibile (driver Vulkan assente o incompatibile)', firme: ['ggml_vulkan: No devices found', 'ErrorIncompatibleDriver', 'ErrorInitializationFailed', 'ErrorLayerNotPresent'] },
|
|
429
|
+
{ classe: 'perso', motivo: 'la scheda grafica non risponde più (dispositivo perso)', firme: ['DeviceLostError', 'ErrorDeviceLost', 'device lost'] },
|
|
430
|
+
]);
|
|
431
|
+
const FIRME_MEMORIA = Object.freeze(['ErrorOutOfDeviceMemory', 'ErrorOutOfHostMemory']);
|
|
432
|
+
|
|
433
|
+
function classificaGuastoVulkan(righe) {
|
|
434
|
+
const testo = righe.join('\n');
|
|
435
|
+
for (const f of FIRME_RIPIEGO) if (f.firme.some(s => testo.includes(s))) return { classe: f.classe, motivo: f.motivo };
|
|
436
|
+
if (FIRME_MEMORIA.some(s => testo.includes(s))) return { classe: 'memoria', motivo: 'la memoria della scheda grafica non basta per questo modello con questo contesto' };
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function nuovoMotore(variante) {
|
|
441
|
+
return { variante, dispositivi: variante === motoreIniziale.variante ? [...motoreIniziale.dispositivi] : [], ripiego: null, proposta: null };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function lanciaProcesso({ modelId, modelPath, selectedPort, contextLength, binario, ngl, apiKey }) {
|
|
445
|
+
const entry = {
|
|
446
|
+
child: null,
|
|
447
|
+
apiKey,
|
|
448
|
+
modelId: modelId ?? null,
|
|
449
|
+
modelPath,
|
|
450
|
+
port: selectedPort,
|
|
451
|
+
baseUrl: `http://${LOOPBACK}:${selectedPort}`,
|
|
452
|
+
state: 'loading',
|
|
453
|
+
startedAt: now().toISOString(),
|
|
454
|
+
failure: null,
|
|
455
|
+
closed: false,
|
|
456
|
+
ultimeRighe: [],
|
|
457
|
+
residuoStderr: '',
|
|
458
|
+
binario,
|
|
459
|
+
};
|
|
460
|
+
current = entry;
|
|
461
|
+
state = 'loading';
|
|
462
|
+
const conOffload = Number.isInteger(ngl) && ngl > 0;
|
|
463
|
+
/*
|
|
464
|
+
* ⛔ Le domande al binario si fanno PRIMA di accenderlo, e il loro esito
|
|
465
|
+
* si dice ad alta voce: una leva che si accende in silenzio è una leva
|
|
466
|
+
* che nessuno può smentire. Misurato 12/09: il fitter risponde in 0,34 s
|
|
467
|
+
* su un modello da 2,3 GB e in 3,9 s su uno da 15,3 GB; `--help` in
|
|
468
|
+
* meno di 0,3 s; e si pagano una volta sola per (binario, modello, contesto).
|
|
469
|
+
*/
|
|
470
|
+
const leve = conOffload
|
|
471
|
+
? leveVelocita(binario, modelPath, contextLength)
|
|
472
|
+
: { kv: { tipo: 'q8_0', perche: 'nessun offload sul dispositivo: la KV cache non entra nella scelta' }, speculativa: speculativaDisponibile(binario) };
|
|
473
|
+
emitLog('stderr', `[talos] motore ${motore.variante}${motore.ripiego ? ` (ripiego da ${motore.ripiego.da})` : ''}: ${binario}\n`);
|
|
474
|
+
emitLog('stderr', `[talos] KV cache ${leve.kv.tipo} — ${leve.kv.perche}\n`);
|
|
475
|
+
emitLog('stderr', `[talos] decodifica speculativa a n-grammi: ${leve.speculativa ? 'accesa (--spec-type ngram-mod)' : 'non offerta da questo binario'}\n`);
|
|
476
|
+
entry.child = processPolicy.spawn(binario, [
|
|
477
|
+
'-m', modelPath,
|
|
478
|
+
...(modelId ? ['--alias', modelId] : []),
|
|
479
|
+
'--host', LOOPBACK,
|
|
480
|
+
'--port', String(selectedPort),
|
|
481
|
+
'--api-key', apiKey,
|
|
482
|
+
/*
|
|
483
|
+
* ⛔⛔⛔ 03/9 — QUI NON PASSAVAMO MAI `-c`, e il 27B dell'owner non
|
|
484
|
+
* partiva: senza `-c` llama.cpp alloca il contesto ADDESTRATO del modello
|
|
485
|
+
* (262.144 token per quel file). Misurato: lo stesso file con `-c 2048`
|
|
486
|
+
* dice «model loaded / listening» in 13 secondi. Storia intera nel
|
|
487
|
+
* commit c89dc763 e seguenti.
|
|
488
|
+
*/
|
|
489
|
+
...(Number.isInteger(contextLength) && contextLength > 0 ? ['-c', String(contextLength)] : []),
|
|
490
|
+
/*
|
|
491
|
+
* ⭐⭐⭐ 03/9 — LA GPU: `-ngl 99` esplicito (misurato meglio di `auto` su
|
|
492
|
+
* RX 9070 XT, banco A/B del 03/9: 13,28 contro 11,12 tok/s). Solo se il
|
|
493
|
+
* binario ha davvero un backend. R-03: sul binario di RISERVA si dichiara
|
|
494
|
+
* `-ngl 0 --device none` (llama.cpp b10517, common/arg.cpp: «none = don't
|
|
495
|
+
* use»), così un ripiego non tenta mai un offload silenzioso.
|
|
496
|
+
*/
|
|
497
|
+
...(conOffload ? ['-ngl', String(ngl)] : (motore.ripiego ? ['-ngl', '0', '--device', 'none'] : [])),
|
|
498
|
+
/*
|
|
499
|
+
* ⭐⭐⭐ 3/9 + BC-13 12/09 — KV cache quantizzata SIMMETRICA con `-fa 1`
|
|
500
|
+
* solo con offload (+15 % generazione, +408 % prompt, misurati); il tipo
|
|
501
|
+
* lo decide il fitter del binario (vedi `decidiTipoKvCache`).
|
|
502
|
+
*/
|
|
503
|
+
...(conOffload
|
|
504
|
+
? ['-fa', '1', '--cache-type-k', leve.kv.tipo, '--cache-type-v', leve.kv.tipo]
|
|
505
|
+
: []),
|
|
506
|
+
/*
|
|
507
|
+
* ⭐⭐⭐ BC-13 — decodifica speculativa a n-grammi, SENZA modello draft,
|
|
508
|
+
* solo se QUESTO binario la offre (un binario più vecchio morirebbe con
|
|
509
|
+
* «unknown argument»). Misure nel commento di `supportaSpeculativaNgram`.
|
|
510
|
+
*/
|
|
511
|
+
...(leve.speculativa ? ['--spec-type', 'ngram-mod'] : []),
|
|
512
|
+
'--jinja',
|
|
513
|
+
'--metrics',
|
|
514
|
+
'--props',
|
|
515
|
+
], { cwd: isAbsolute(binario) ? dirname(binario) : process.cwd(), shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
516
|
+
if (!entry.child || typeof entry.child.once !== 'function') throw new LlamaServerSupervisorError('spawn did not return a child process', 'RUNTIME_PROCESS_FAILED');
|
|
517
|
+
attachProcess(entry);
|
|
518
|
+
/*
|
|
519
|
+
* ⛔ L'attesa la decide la DIMENSIONE del file, letta adesso dal disco:
|
|
520
|
+
* un modello che il sistema deve ancora leggere non e' un modello che
|
|
521
|
+
* non parte. Se la misura non riesce si resta sull'attesa di base.
|
|
522
|
+
*/
|
|
523
|
+
let byteModello = 0;
|
|
524
|
+
try { byteModello = statSync(modelPath).size; } catch { byteModello = 0; }
|
|
525
|
+
const attesa = attesaSaluteMs(byteModello, healthTimeoutMs);
|
|
526
|
+
const deadline = Date.now() + attesa;
|
|
527
|
+
while (Date.now() < deadline) {
|
|
528
|
+
if (entry.failure) throw new LlamaServerSupervisorError(`llama-server failed: ${entry.failure.message}`, 'RUNTIME_PROCESS_FAILED');
|
|
529
|
+
/*
|
|
530
|
+
* ⛔⛔⛔ BC-13, 12/09/2026 — UN PROCESSO GIÀ MORTO NON DIVENTA PRONTO:
|
|
531
|
+
* il 27B dell'owner muore in 4,9 s con «ErrorOutOfDeviceMemory» e prima
|
|
532
|
+
* si aspettava 245 s bussando a una porta chiusa. Si guarda `closed`, e
|
|
533
|
+
* l'errore PORTA le ultime righe del motore.
|
|
534
|
+
*/
|
|
535
|
+
if (entry.closed) {
|
|
536
|
+
const detto = entry.ultimeRighe.slice(-4).join(' | ');
|
|
537
|
+
const errore = new LlamaServerSupervisorError(
|
|
538
|
+
`llama-server si è chiuso dopo ${Math.round((Date.now() - (deadline - attesa)) / 1000)} s senza mai diventare pronto${detto ? `: ${detto}` : ''}`,
|
|
539
|
+
'RUNTIME_PROCESS_FAILED',
|
|
540
|
+
);
|
|
541
|
+
errore.righeMotore = [...entry.ultimeRighe];
|
|
542
|
+
throw errore;
|
|
543
|
+
}
|
|
544
|
+
const result = await health();
|
|
545
|
+
if (result.ok) {
|
|
546
|
+
entry.state = 'ready';
|
|
547
|
+
state = 'ready';
|
|
548
|
+
return status();
|
|
549
|
+
}
|
|
550
|
+
await wait(pollIntervalMs);
|
|
551
|
+
}
|
|
552
|
+
entry.state = 'failed';
|
|
553
|
+
state = 'failed';
|
|
554
|
+
// ⛔ Il messaggio dice QUANTO si e' aspettato e quanto pesa il modello:
|
|
555
|
+
// «timeout» da solo manda a cercare un guasto che non c'e'.
|
|
556
|
+
throw new LlamaServerSupervisorError(`llama-server non è diventato pronto entro ${Math.round(attesa / 1000)} s (modello di ${(byteModello / 1_000_000_000).toFixed(1)} GB)`, 'RUNTIME_HEALTH_TIMEOUT');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
async function start({ modelId, modelPath, port, contextLength } = {}) {
|
|
560
|
+
if (current && ['loading', 'ready', 'stopping'].includes(current.state)) throw new LlamaServerSupervisorError('runtime is already active', 'RUNTIME_ALREADY_RUNNING');
|
|
561
|
+
if (typeof modelPath !== 'string' || !isAbsolute(modelPath)) throw invalid('modelPath must be absolute');
|
|
562
|
+
const selectedPort = port ?? await portAllocator();
|
|
563
|
+
if (!Number.isInteger(selectedPort) || selectedPort < 1024 || selectedPort > 65535) throw invalid('port is invalid');
|
|
564
|
+
let locked = false;
|
|
565
|
+
if (modelStore && modelId) {
|
|
566
|
+
await modelStore.lock(modelId);
|
|
567
|
+
locked = true;
|
|
568
|
+
}
|
|
569
|
+
const apiKey = randomBytes(32).toString('hex');
|
|
570
|
+
// Ogni caricamento riparte dal binario scelto dal guscio: il ripiego vale per un giro solo.
|
|
571
|
+
motore = nuovoMotore(motoreIniziale.variante);
|
|
572
|
+
const chiudi = (entry) => { if (entry?.child && !entry.closed) entry.child.kill('SIGTERM'); };
|
|
573
|
+
try {
|
|
574
|
+
try {
|
|
575
|
+
return await lanciaProcesso({ modelId, modelPath, selectedPort, contextLength, binario: binaryPath, ngl: gpuLayers, apiKey });
|
|
576
|
+
} catch (primo) {
|
|
577
|
+
const guasto = motore.variante === 'vulkan' && primo.code === 'RUNTIME_PROCESS_FAILED' ? classificaGuastoVulkan(primo.righeMotore ?? []) : null;
|
|
578
|
+
if (guasto?.classe === 'memoria') {
|
|
579
|
+
motore.proposta = { a: 'cpu', motivo: `${guasto.motivo}; sul processore il modello può girare, più lento: la scelta è della persona` };
|
|
580
|
+
throw primo;
|
|
581
|
+
}
|
|
582
|
+
if (!guasto || !fallbackBinaryPath) throw primo;
|
|
583
|
+
const primaEntry = current;
|
|
584
|
+
chiudi(primaEntry);
|
|
585
|
+
if (current === primaEntry) current = null;
|
|
586
|
+
const ripiego = { da: motore.variante, a: 'cpu', motivo: guasto.motivo, classe: guasto.classe, righe: (primo.righeMotore ?? []).slice(-4) };
|
|
587
|
+
motore = { ...nuovoMotore('cpu'), ripiego };
|
|
588
|
+
for (const listener of listeners) {
|
|
589
|
+
try { listener({ stream: 'stderr', text: `[talos] ${guasto.motivo}: il modello viene caricato sul processore.\n`, ripiego }); } catch { /* observer failure must not affect the process */ }
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
return await lanciaProcesso({ modelId, modelPath, selectedPort, contextLength, binario: fallbackBinaryPath, ngl: 0, apiKey });
|
|
593
|
+
} catch (secondo) {
|
|
594
|
+
// ⛔ Le DUE code restano leggibili: chi legge deve sapere che sono morti entrambi, e come.
|
|
595
|
+
const codaGpu = ripiego.righe.join(' | ');
|
|
596
|
+
const errore = new LlamaServerSupervisorError(`${secondo.message}${codaGpu ? ` [prima, sulla scheda grafica: ${codaGpu}]` : ''}`, secondo.code === 'RUNTIME_HEALTH_TIMEOUT' ? 'RUNTIME_HEALTH_TIMEOUT' : 'RUNTIME_PROCESS_FAILED');
|
|
597
|
+
errore.righeMotore = secondo.righeMotore;
|
|
598
|
+
throw errore;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
} catch (error) {
|
|
602
|
+
chiudi(current);
|
|
603
|
+
current = null;
|
|
604
|
+
state = 'failed';
|
|
605
|
+
if (locked) await modelStore.unlock(modelId).catch(() => {});
|
|
606
|
+
throw error instanceof LlamaServerSupervisorError ? error : new LlamaServerSupervisorError(error.message, 'RUNTIME_PROCESS_FAILED');
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
async function stop() {
|
|
611
|
+
const entry = current;
|
|
612
|
+
if (!entry) { state = 'unavailable'; return status(); }
|
|
613
|
+
entry.state = 'stopping';
|
|
614
|
+
state = 'stopping';
|
|
615
|
+
if (entry.child && !entry.closed) entry.child.kill('SIGTERM');
|
|
616
|
+
current = null;
|
|
617
|
+
state = 'unavailable';
|
|
618
|
+
if (modelStore && entry.modelId) await modelStore.unlock(entry.modelId).catch(() => {});
|
|
619
|
+
return status();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function subscribeLogs(listener) {
|
|
623
|
+
if (typeof listener !== 'function') throw invalid('log listener must be a function');
|
|
624
|
+
listeners.add(listener);
|
|
625
|
+
return () => listeners.delete(listener);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
return Object.freeze({ start, health, request, stop, status, subscribeLogs });
|
|
629
|
+
}
|