talos-code 0.0.1 → 0.1.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/LICENSE +661 -0
- package/README.md +111 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +97 -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 +185 -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 +43 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +82 -0
- package/dist/i18n/en/errors.js +246 -0
- package/dist/i18n/en/firstrun.js +89 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +736 -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 +330 -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/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-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 +262 -0
- package/dist/runtime/talos-composition.js +1006 -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/app.js +3761 -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 +559 -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 +32 -0
- package/dist/tui/components/terminal-shell.js +205 -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/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 +139 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +45 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +39 -0
- package/dist/tui/overlays/approval-dialog.js +640 -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 +72 -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 +145 -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 +287 -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 +778 -0
- package/dist/tui/session-export.js +54 -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 +80 -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 +25 -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 +63 -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,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⛔⛔⛔ P0 · PUNTO 7 — IL FAILSAFE DELLA GENERAZIONE, IN UNA PORTA SOLA (16/09/2026).
|
|
3
|
+
*
|
|
4
|
+
* ## Il difetto che questo file chiude
|
|
5
|
+
*
|
|
6
|
+
* TALOS aveva TRE tetti sulla DURATA di una risposta del modello, tutti e tre deadline TOTALI —
|
|
7
|
+
* cioè misurate dall'inizio della chiamata, indifferenti al fatto che il modello stesse parlando:
|
|
8
|
+
*
|
|
9
|
+
* 1. `talosHarness.mjs` · `AbortSignal.timeout(180_000)` su ogni giro, streaming compreso;
|
|
10
|
+
* 2. `runtime-owner-adapter.mjs` · `AbortSignal.timeout(timeoutSeconds * 1000)`, default 60 s;
|
|
11
|
+
* 3. `undici` sotto `fetch` · `headersTimeout` e `bodyTimeout` a 300 s DI SERIE, mai dichiarati.
|
|
12
|
+
*
|
|
13
|
+
* Misurato il 16/09/2026 con un fornitore finto non-OpenRouter che emette un token ogni 2 s per
|
|
14
|
+
* 90 s, col default di 60 s: la risposta veniva **tagliata a 60 s mentre i token arrivavano**, e
|
|
15
|
+
* l'errore diceva «Il fornitore ha superato il tempo massimo» — una bugia, il fornitore stava
|
|
16
|
+
* rispondendo benissimo.
|
|
17
|
+
*
|
|
18
|
+
* ⛔ È la stessa forma del tetto sui GIRI, tolto l'11/09/2026 (`giri-senza-tetto.test.mjs`): *un
|
|
19
|
+
* tetto sulla durata non è una guardia contro il guasto — è una guardia contro il TEMPO, e il
|
|
20
|
+
* primo a incontrarla è il compito lungo ma SANO*.
|
|
21
|
+
*
|
|
22
|
+
* ## Perché un'INATTIVITÀ e non un numero più grande
|
|
23
|
+
*
|
|
24
|
+
* Alzare 60 a 600 sposta il muro, non lo toglie: il giorno del prompt da 200k token su CPU si
|
|
25
|
+
* ripresenta identico. La domanda giusta non è «quanto può durare» ma «da quanto tempo è MORTO».
|
|
26
|
+
*
|
|
27
|
+
* Ricerca fatta PRIMA di scrivere, 16/09/2026:
|
|
28
|
+
* · **OpenAI Codex CLI** — `DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300_000`
|
|
29
|
+
* (`codex-rs/model-provider-info/src/lib.rs`): un'inattività dello stream, non un tetto totale.
|
|
30
|
+
* · **openai/codex#39771**, «High-reasoning Responses WebSocket idle timeout is a false
|
|
31
|
+
* dead-stream (model resumed after 8.5 min of no text frames)»: con ragionamento alto perfino
|
|
32
|
+
* CINQUE minuti di silenzio sono normali, e l'issue conclude che «lowering the stream idle
|
|
33
|
+
* timeout globally is not a safe fix because healthy reasoning can produce no response frames
|
|
34
|
+
* for that length of time».
|
|
35
|
+
* · **openai/codex#23807** e **#31376**: stalli di ESATTAMENTE 300 s fra un risultato di attrezzo
|
|
36
|
+
* e la richiesta successiva — la firma inconfondibile di un tetto di trasporto che nessuno ha
|
|
37
|
+
* dichiarato (i nostri 300 s di undici).
|
|
38
|
+
* · **ggml-org/llama.cpp#22997** e `tools/server/README.md`: durante il *prefill* di un prompt
|
|
39
|
+
* lungo llama-server non manda **nessun byte** per minuti; da lì `sse_ping_interval`, i commenti
|
|
40
|
+
* SSE emessi apposta per rendere osservabile un canale vivo che tace.
|
|
41
|
+
* · **OpenRouter · Streaming**: durante l'elaborazione il canale porta commenti SSE
|
|
42
|
+
* (`: OPENROUTER PROCESSING`) come keep-alive.
|
|
43
|
+
* · **Anthropic** (anthropics/anthropic-sdk-python#1698): sopra i ~10 minuti lo streaming diventa
|
|
44
|
+
* obbligatorio — cioè il primo fornitore al mondo mette la sua soglia di «operazione lunga ma
|
|
45
|
+
* sana» a dieci minuti, non a uno.
|
|
46
|
+
*
|
|
47
|
+
* ⇒ **30 minuti.** Il numero non è tondo per caso: è ~6× l'inattività di Codex e ~3× la soglia
|
|
48
|
+
* Anthropic, cioè ampiamente oltre il peggior silenzio SANO documentato (8,5 minuti misurati
|
|
49
|
+
* nell'issue #39771), e comunque un ordine di grandezza sotto «per sempre». Chi ha un caso
|
|
50
|
+
* peggiore lo dice con `TALOS_GENERATION_IDLE_MS`.
|
|
51
|
+
*
|
|
52
|
+
* ## Cosa NON è questo failsafe
|
|
53
|
+
*
|
|
54
|
+
* ⛔ Non è un timeout di generazione, e l'errore che produce non deve dirlo: quando scatta, il
|
|
55
|
+
* canale è **morto** — nessun byte, nessun commento, per mezz'ora. La classe è `rete`
|
|
56
|
+
* («la connessione con il fornitore del modello è caduta»), ed è TRANSITORIA: una ricerca caduta
|
|
57
|
+
* così si può riprendere. Chiamarlo «timeout» manderebbe a studiare il modello invece del cavo.
|
|
58
|
+
*
|
|
59
|
+
* ⛔ Non sostituisce lo STOP della persona, che resta la prima e più veloce via d'uscita: qui il
|
|
60
|
+
* `userSignal` è sempre onorato per primo, e la prova `P0-D-08`/`P0-D-09` misura che chiude in
|
|
61
|
+
* meno di un secondo anche su un fornitore che tace.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Trenta minuti. Vedi la motivazione in testa al file: è oltre il peggior silenzio SANO
|
|
66
|
+
* documentato (8,5 min, openai/codex#39771), non un numero scelto perché suona grande.
|
|
67
|
+
*/
|
|
68
|
+
export const INATTIVITA_GENERAZIONE_MS_PREDEFINITA = 1_800_000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* ⛔ Sotto il minuto non è un failsafe, è il tetto di prima con un altro nome: un prefill lungo su
|
|
72
|
+
* CPU tace molto più a lungo. Chi scrive un valore più basso lo sta usando per il verso sbagliato.
|
|
73
|
+
*/
|
|
74
|
+
export const INATTIVITA_GENERAZIONE_MS_MINIMA = 60_000;
|
|
75
|
+
|
|
76
|
+
/** Il nome della variabile, in un posto solo: lo cita il README e lo leggono i test. */
|
|
77
|
+
export const VARIABILE_INATTIVITA_GENERAZIONE = 'TALOS_GENERATION_IDLE_MS';
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Il silenzio del fornitore: nessun byte E nessun commento SSE per tutto il limite.
|
|
81
|
+
*
|
|
82
|
+
* ⛔ `classe: 'rete'` e non `'timeout-fornitore'`: `classificaGuasto` in `runtime-owner-adapter.mjs`
|
|
83
|
+
* legge questo `code` ESPLICITAMENTE, invece di lasciare che la tabella dei segni indovini dal
|
|
84
|
+
* testo del messaggio — un filtro che riconosce la MENZIONE di una parola non riconosce la cosa.
|
|
85
|
+
*/
|
|
86
|
+
export class SilenzioDelFornitoreError extends Error {
|
|
87
|
+
constructor(limiteMs) {
|
|
88
|
+
const minuti = Math.max(1, Math.round(limiteMs / 60_000));
|
|
89
|
+
super(`Connessione con il fornitore interrotta: nessun dato per ${minuti} minuti.`);
|
|
90
|
+
this.name = 'SilenzioDelFornitoreError';
|
|
91
|
+
this.code = 'PROVIDER_SILENCE';
|
|
92
|
+
this.classe = 'rete';
|
|
93
|
+
this.transitorio = true;
|
|
94
|
+
this.limiteMs = limiteMs;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Il limite di inattività, letto dall'ambiente.
|
|
100
|
+
*
|
|
101
|
+
* ⛔ Un valore illeggibile NON spegne la guardia in silenzio: si torna al default e si va avanti.
|
|
102
|
+
* Lo ZERO invece la spegne davvero, ed è l'unico modo DICHIARATO per farlo — chi lo scrive sa
|
|
103
|
+
* cosa sta facendo, e la differenza fra «non ho capito il tuo valore» e «hai chiesto niente
|
|
104
|
+
* guardia» deve restare leggibile.
|
|
105
|
+
* ⛔ Sotto il minimo si SALE al minimo invece di obbedire: vedi `INATTIVITA_GENERAZIONE_MS_MINIMA`.
|
|
106
|
+
*
|
|
107
|
+
* @param {Record<string, string|undefined>} [env]
|
|
108
|
+
* @returns {number} millisecondi; `0` significa nessun failsafe
|
|
109
|
+
*/
|
|
110
|
+
export function leggiInattivitaGenerazioneMs(env = process.env) {
|
|
111
|
+
const grezzo = env?.[VARIABILE_INATTIVITA_GENERAZIONE];
|
|
112
|
+
if (grezzo === undefined || grezzo === null || String(grezzo).trim() === '') return INATTIVITA_GENERAZIONE_MS_PREDEFINITA;
|
|
113
|
+
const valore = Number(grezzo);
|
|
114
|
+
if (!Number.isFinite(valore) || valore < 0) return INATTIVITA_GENERAZIONE_MS_PREDEFINITA;
|
|
115
|
+
if (valore === 0) return 0;
|
|
116
|
+
return Math.max(INATTIVITA_GENERAZIONE_MS_MINIMA, Math.round(valore));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Il guardiano dell'inattività, in una funzione sola.
|
|
121
|
+
*
|
|
122
|
+
* Aspetta `promessa`; se non si conclude entro `limiteMs` aborta il `controller` e rifiuta con
|
|
123
|
+
* l'errore che `creaErrore` costruisce. Lo `userSignal` vince sempre e subito.
|
|
124
|
+
*
|
|
125
|
+
* ⛔ Nessun timer nuovo «per far quadrare una corsa»: questo È il timer del failsafe, uno solo, e
|
|
126
|
+
* viene CANCELLATO da ogni via d'uscita (`pulisci`). Il listener sullo stop è `{once:true}` e si
|
|
127
|
+
* toglie comunque: un ascoltatore lasciato su un segnale che vive quanto la sessione è una
|
|
128
|
+
* perdita che cresce a ogni giro.
|
|
129
|
+
* ⭐ `programmaTimer`/`annullaTimer` sono iniettabili perché una prova non deve aspettare mezz'ora
|
|
130
|
+
* per provare la mezz'ora.
|
|
131
|
+
*
|
|
132
|
+
* @template T
|
|
133
|
+
* @param {Promise<T>|T} promessa
|
|
134
|
+
* @param {{limiteMs:number, controller:AbortController, userSignal?:AbortSignal|null,
|
|
135
|
+
* creaErrore?:(ms:number)=>Error, programmaTimer?:Function, annullaTimer?:Function}} opzioni
|
|
136
|
+
* @returns {Promise<T>}
|
|
137
|
+
*/
|
|
138
|
+
export function sorvegliaInattivita(promessa, {
|
|
139
|
+
limiteMs,
|
|
140
|
+
controller,
|
|
141
|
+
userSignal = null,
|
|
142
|
+
creaErrore = (ms) => new SilenzioDelFornitoreError(ms),
|
|
143
|
+
programmaTimer = setTimeout,
|
|
144
|
+
annullaTimer = clearTimeout,
|
|
145
|
+
} = {}) {
|
|
146
|
+
return new Promise((risolvi, rifiuta) => {
|
|
147
|
+
let conclusa = false;
|
|
148
|
+
let timer = null;
|
|
149
|
+
const pulisci = () => {
|
|
150
|
+
if (timer !== null) annullaTimer(timer);
|
|
151
|
+
userSignal?.removeEventListener?.('abort', fermaUtente);
|
|
152
|
+
};
|
|
153
|
+
const chiudi = (azione, valore) => {
|
|
154
|
+
if (conclusa) return;
|
|
155
|
+
conclusa = true;
|
|
156
|
+
pulisci();
|
|
157
|
+
azione(valore);
|
|
158
|
+
};
|
|
159
|
+
function fermaUtente() {
|
|
160
|
+
const motivo = userSignal.reason ?? new DOMException('Fermato dall’utente', 'AbortError');
|
|
161
|
+
controller.abort(motivo);
|
|
162
|
+
chiudi(rifiuta, motivo);
|
|
163
|
+
}
|
|
164
|
+
/*
|
|
165
|
+
* ⛔⛔⛔ 16/09/2026 — IL GUINZAGLIO SULLA PROMESSA SI ATTACCA PER PRIMO, E SEMPRE.
|
|
166
|
+
*
|
|
167
|
+
* Stava in fondo, dopo un `return` anticipato: se lo Stop era GIÀ arrivato quando questa
|
|
168
|
+
* funzione viene chiamata, si usciva subito e `lettore.read()` restava **senza un solo
|
|
169
|
+
* gestore**. Quando poi il corpo veniva demolito, quella promessa rifiutava nel vuoto: un
|
|
170
|
+
* `unhandledRejection` che esplode dopo, addosso a chi non c'entra.
|
|
171
|
+
* ⛔ Misurato: il difetto è comparso solo quando il guardiano è stato DAVVERO agganciato —
|
|
172
|
+
* `PH-FALLBACK-20` («stop DOPO che la richiesta è partita») ha cominciato a segnalare
|
|
173
|
+
* «asynchronous activity after the test ended ... AbortError: This operation was aborted»
|
|
174
|
+
* pur restando verde come singola prova, e a fallire era il FILE. Finché il guardiano era
|
|
175
|
+
* inerte non si vedeva niente, perché non c'era nessuna lettura da orfanare.
|
|
176
|
+
* ⭐ `chiudi` è idempotente: chi arriva primo decide, e chi arriva dopo è un no-op. Quindi
|
|
177
|
+
* attaccare prima non cambia nessun esito — cambia solo che nessuna rejection resta sola.
|
|
178
|
+
*/
|
|
179
|
+
Promise.resolve(promessa).then((v) => chiudi(risolvi, v), (e) => chiudi(rifiuta, e));
|
|
180
|
+
if (userSignal?.aborted) { fermaUtente(); return; }
|
|
181
|
+
userSignal?.addEventListener?.('abort', fermaUtente, { once: true });
|
|
182
|
+
/* ⛔ `limiteMs` a zero (o non numerico) = nessun failsafe: si aspetta, e basta. */
|
|
183
|
+
if (Number.isFinite(limiteMs) && limiteMs > 0) {
|
|
184
|
+
timer = programmaTimer(() => {
|
|
185
|
+
const errore = creaErrore(limiteMs);
|
|
186
|
+
controller.abort(errore);
|
|
187
|
+
chiudi(rifiuta, errore);
|
|
188
|
+
}, limiteMs);
|
|
189
|
+
timer?.unref?.();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Lo stesso guardiano, messo addosso al CORPO di una risposta già arrivata.
|
|
196
|
+
*
|
|
197
|
+
* ⛔ Il conteggio si azzera a ogni **byte**, non a ogni `data:` — quindi i commenti SSE
|
|
198
|
+
* (`: OPENROUTER PROCESSING`, i ping di llama-server) contano come VITA. Un failsafe che
|
|
199
|
+
* guardasse solo i pacchetti con contenuto scarterebbe muto proprio ciò che il fornitore manda
|
|
200
|
+
* apposta per dire «sono vivo», e ucciderebbe la connessione più sana che c'è.
|
|
201
|
+
*
|
|
202
|
+
* ⭐ Perché qui e non solo nel dispatcher: `bodyTimeout` di undici copre il trasporto HTTP, ma non
|
|
203
|
+
* il motore locale servito da un ponte, né gli SDK nativi, né l'agente ACP. Questo confine li
|
|
204
|
+
* vede tutti, perché parla di `Response`, e produce un errore NOSTRO e classificato invece di un
|
|
205
|
+
* `UND_ERR_BODY_TIMEOUT` che arriverebbe in chat come «terminated».
|
|
206
|
+
*
|
|
207
|
+
* @param {Response} risposta
|
|
208
|
+
* @param {{limiteMs:number, userSignal?:AbortSignal|null, programmaTimer?:Function, annullaTimer?:Function}} opzioni
|
|
209
|
+
* @returns {Response} la stessa risposta, col corpo sorvegliato (o intatta se non c'è corpo)
|
|
210
|
+
*/
|
|
211
|
+
export function sorvegliaCorpoDiGenerazione(risposta, {
|
|
212
|
+
limiteMs,
|
|
213
|
+
userSignal = null,
|
|
214
|
+
programmaTimer = setTimeout,
|
|
215
|
+
annullaTimer = clearTimeout,
|
|
216
|
+
} = {}) {
|
|
217
|
+
/*
|
|
218
|
+
* ⛔⛔⛔ 16/09/2026 — QUESTA GUARDIA È NATA DA UN DIFETTO CHE È VISSUTO INVISIBILE PER UN GIRO.
|
|
219
|
+
*
|
|
220
|
+
* L'aggancio scriveva `sorveglia(conCacheDichiarata(await fetch(...)))` e `conCacheDichiarata` è
|
|
221
|
+
* `async`: qui arrivava una **Promise**, non una `Response`. Una Promise non ha `.body`, quindi
|
|
222
|
+
* la riga sotto la restituiva **intatta** — e il failsafe risultava attaccato a niente su
|
|
223
|
+
* deepseek, z.ai, openai e su tutto il percorso cloud, in streaming e non. Nessun errore, nessun
|
|
224
|
+
* avviso: il chiamante riceveva esattamente ciò che aveva passato, e sembrava che funzionasse.
|
|
225
|
+
* A fermare i silenzi era il `bodyTimeout` del dispatcher, 1,2× più tardi e con un altro nome.
|
|
226
|
+
*
|
|
227
|
+
* ⇒ Un contratto violato si DICE. È la stessa lezione di [[il-catch-giusto-nasconde-il-bug-sbagliato]]:
|
|
228
|
+
* chi degrada in silenzio deve dichiarare quale guasto copre e rilanciare gli errori di
|
|
229
|
+
* contratto. Qui «non ho un corpo da sorvegliare» è un caso legittimo (una 204, una risposta
|
|
230
|
+
* già materializzata); «mi hai dato una Promise» non lo è mai.
|
|
231
|
+
*/
|
|
232
|
+
if (typeof risposta?.then === 'function') {
|
|
233
|
+
throw new TypeError('sorvegliaCorpoDiGenerazione vuole una Response già risolta, non una Promise: senza `await` il failsafe resterebbe attaccato a niente.');
|
|
234
|
+
}
|
|
235
|
+
if (!Number.isFinite(limiteMs) || limiteMs <= 0) return risposta;
|
|
236
|
+
const corpo = risposta?.body;
|
|
237
|
+
if (!corpo || typeof corpo.getReader !== 'function') return risposta;
|
|
238
|
+
|
|
239
|
+
const lettore = corpo.getReader();
|
|
240
|
+
const controller = new AbortController();
|
|
241
|
+
const sorvegliato = new ReadableStream({
|
|
242
|
+
async pull(uscita) {
|
|
243
|
+
try {
|
|
244
|
+
const { done, value } = await sorvegliaInattivita(lettore.read(), {
|
|
245
|
+
limiteMs, controller, userSignal, programmaTimer, annullaTimer,
|
|
246
|
+
});
|
|
247
|
+
if (done) { uscita.close(); return; }
|
|
248
|
+
uscita.enqueue(value);
|
|
249
|
+
} catch (errore) {
|
|
250
|
+
await lettore.cancel(errore).catch(() => {});
|
|
251
|
+
uscita.error(userSignal?.aborted ? (userSignal.reason ?? errore) : errore);
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
cancel(motivo) { return lettore.cancel(motivo); },
|
|
255
|
+
});
|
|
256
|
+
return new Response(sorvegliato, {
|
|
257
|
+
status: risposta.status,
|
|
258
|
+
statusText: risposta.statusText,
|
|
259
|
+
headers: risposta.headers,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/*
|
|
264
|
+
* ⛔⛔ IL DISPATCHER — e perché NON è `setGlobalDispatcher`.
|
|
265
|
+
*
|
|
266
|
+
* Sotto `fetch` c'è undici, con `headersTimeout` e `bodyTimeout` a **300 s di serie**: se non li
|
|
267
|
+
* si tocca, il tetto che si è appena tolto dal codice resta là sotto, tacito. È il caso di
|
|
268
|
+
* openai/codex#23807 (stalli di esattamente 300 s) e del post di raphael.badia.cc
|
|
269
|
+
* («Fixing Headers Timeout Error with Vercel AI SDK», letto 16/09/2026).
|
|
270
|
+
*
|
|
271
|
+
* La ricetta che circola è `setGlobalDispatcher(new Agent({...}))` dal pacchetto npm `undici`.
|
|
272
|
+
* Qui NON si può e NON si deve:
|
|
273
|
+
*
|
|
274
|
+
* · `undici` **non è una dipendenza dichiarata** di harness-ui (misurato: non compare in
|
|
275
|
+
* `package.json`; sul disco c'è solo come dipendenza transitiva di qualcun altro). Aggiungerla
|
|
276
|
+
* è una dipendenza nuova, e non serve.
|
|
277
|
+
* · È **globale**: cambierebbe i tetti di OGNI fetch dell'applicazione — ricerca web, hub dei
|
|
278
|
+
* modelli, proxy delle immagini, MCP. Quelle chiamate devono restare impazienti. Il punto 7
|
|
279
|
+
* parla della durata del RAGIONAMENTO, non della pazienza di tutto il prodotto.
|
|
280
|
+
* · Ed è **fragile in modo silenzioso**: misurato il 16/09/2026 su Node v24.18.0, il `fetch`
|
|
281
|
+
* globale legge `Symbol.for('undici.globalDispatcher.1')`; scrivere solo su `.2` non ha alcun
|
|
282
|
+
* effetto (prova: 5003 ms con un tetto da 1200 ms, cioè il tetto non ha morso). Una cura che
|
|
283
|
+
* arriva inerte e non protesta è peggio di nessuna cura.
|
|
284
|
+
*
|
|
285
|
+
* ⇒ Si costruisce un dispatcher **per richiesta**, dalla classe che Node stesso usa già — misurato
|
|
286
|
+
* funzionante: `fetch(url, { dispatcher })` con tetto 1200 ms su header a 5000 ms fallisce con
|
|
287
|
+
* `UND_ERR_HEADERS_TIMEOUT` in 1498 ms. Nessuna dipendenza nuova, nessun effetto fuori dalle
|
|
288
|
+
* chiamate ai fornitori.
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
/** Costruito una volta sola per limite: un Agent tiene un pool di socket, non se ne fa uno a giro. */
|
|
292
|
+
const dispatcherPerLimite = new Map();
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Il dispatcher undici per le chiamate ai fornitori, con i tetti ALLINEATI al failsafe.
|
|
296
|
+
*
|
|
297
|
+
* ⛔ I tetti del trasporto stanno un po' SOPRA il failsafe (× `MARGINE`), mai sotto: così a
|
|
298
|
+
* scadere per primo è sempre la NOSTRA guardia, che produce un errore classificato e leggibile,
|
|
299
|
+
* e mai `UND_ERR_BODY_TIMEOUT`, che in chat arriverebbe come «terminated». Il trasporto è la
|
|
300
|
+
* rete di sicurezza della rete di sicurezza.
|
|
301
|
+
* ⛔ `limiteMs` a 0 (failsafe spento) ⇒ tetti a 0, che per undici significa **disabilitato**
|
|
302
|
+
* (docs undici, `Client`/`Dispatcher`): chi spegne la guardia spegne anche i 300 s taciti,
|
|
303
|
+
* altrimenti spegnerebbe solo quella che si vede.
|
|
304
|
+
*
|
|
305
|
+
* @param {{limiteMs:number, dispatcherGlobale?:any}} opzioni
|
|
306
|
+
* @returns {any|null} il dispatcher, o `null` se questo Node non permette di costruirlo (e allora
|
|
307
|
+
* restano i 300 s di serie: il chiamante non deve fingere che vada bene, deve dirlo)
|
|
308
|
+
*/
|
|
309
|
+
export function dispatcherDiGenerazione({ limiteMs, dispatcherGlobale = undefined } = {}) {
|
|
310
|
+
const MARGINE = 1.2;
|
|
311
|
+
const tetto = Number.isFinite(limiteMs) && limiteMs > 0 ? Math.round(limiteMs * MARGINE) : 0;
|
|
312
|
+
if (dispatcherPerLimite.has(tetto)) return dispatcherPerLimite.get(tetto);
|
|
313
|
+
/*
|
|
314
|
+
* La classe arriva dal dispatcher che Node ha già costruito per sé: è lo stesso `Agent` del suo
|
|
315
|
+
* undici interno, senza importarne una seconda copia. Se l'ambiente ha un proxy, quella classe è
|
|
316
|
+
* un `EnvHttpProxyAgent` e accetta le stesse opzioni: si eredita la forma giusta invece di
|
|
317
|
+
* imporne una.
|
|
318
|
+
*/
|
|
319
|
+
const attuale = dispatcherGlobale ?? globalThis[Symbol.for('undici.globalDispatcher.1')];
|
|
320
|
+
const Costruttore = attuale?.constructor;
|
|
321
|
+
if (typeof Costruttore !== 'function') { dispatcherPerLimite.set(tetto, null); return null; }
|
|
322
|
+
let costruito = null;
|
|
323
|
+
try {
|
|
324
|
+
costruito = new Costruttore({ headersTimeout: tetto, bodyTimeout: tetto });
|
|
325
|
+
/* ⛔ Una prova che l'oggetto sia davvero un dispatcher: senza `dispatch` non lo è. */
|
|
326
|
+
if (typeof costruito?.dispatch !== 'function') costruito = null;
|
|
327
|
+
} catch {
|
|
328
|
+
costruito = null;
|
|
329
|
+
}
|
|
330
|
+
dispatcherPerLimite.set(tetto, costruito);
|
|
331
|
+
return costruito;
|
|
332
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { open } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Fase 5, punto 4 del piano — parser minimo del formato binario GGUF.
|
|
5
|
+
* Spec ufficiale letta fresca prima di scrivere (regola vincolante del
|
|
6
|
+
* progetto), verificata byte per byte contro un file REALE già presente
|
|
7
|
+
* in `.local-models/` (Qwen3-0.6B.Q2_K.gguf — magic/version/tensor_count/
|
|
8
|
+
* metadata_kv_count/prima chiave "general.architecture" tutti confermati
|
|
9
|
+
* a mano con un hex dump prima di scrivere una riga di codice):
|
|
10
|
+
* https://github.com/ggml-org/ggml/blob/master/docs/gguf.md
|
|
11
|
+
*
|
|
12
|
+
* Layout: uint32 magic ("GGUF") · uint32 version · uint64 tensor_count ·
|
|
13
|
+
* uint64 metadata_kv_count · metadata_kv_count coppie (chiave stringa
|
|
14
|
+
* length-prefixed, uint32 tipo, valore tipato). Tutto little-endian (il
|
|
15
|
+
* caso reale osservato — la spec permette anche big-endian dalla v3 in
|
|
16
|
+
* poi, non supportato qui: un parser "minimo" dichiarato tale, non un
|
|
17
|
+
* mockup che finge di coprire tutto).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export class GgufHeaderError extends Error {
|
|
21
|
+
constructor(message, code = 'GGUF_HEADER_INVALID') { super(message); this.name = 'GgufHeaderError'; this.code = code; }
|
|
22
|
+
}
|
|
23
|
+
function fail(message, code = 'GGUF_HEADER_INVALID') { throw new GgufHeaderError(message, code); }
|
|
24
|
+
|
|
25
|
+
// La metadata reale osservata (inclusi vocabolari tokenizer di centinaia di
|
|
26
|
+
// migliaia di voci) sta ben sotto questa soglia — i TENSORI, che seguono la
|
|
27
|
+
// metadata e pesano i GB veri del file, restano fuori: non li leggiamo mai.
|
|
28
|
+
const MAX_PREFIX_BYTES = 32 * 1024 * 1024;
|
|
29
|
+
|
|
30
|
+
const VALUE_TYPE = Object.freeze({
|
|
31
|
+
UINT8: 0, INT8: 1, UINT16: 2, INT16: 3, UINT32: 4, INT32: 5, FLOAT32: 6,
|
|
32
|
+
BOOL: 7, STRING: 8, ARRAY: 9, UINT64: 10, INT64: 11, FLOAT64: 12,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
class Cursor {
|
|
36
|
+
constructor(buffer) { this.buffer = buffer; this.offset = 0; }
|
|
37
|
+
need(bytes) { if (this.offset + bytes > this.buffer.length) fail('metadata truncated, or exceeds the read prefix', 'GGUF_HEADER_TRUNCATED'); }
|
|
38
|
+
bytes(count) { this.need(count); const v = this.buffer.subarray(this.offset, this.offset + count); this.offset += count; return v; }
|
|
39
|
+
u8() { this.need(1); const v = this.buffer.readUInt8(this.offset); this.offset += 1; return v; }
|
|
40
|
+
i8() { this.need(1); const v = this.buffer.readInt8(this.offset); this.offset += 1; return v; }
|
|
41
|
+
u16() { this.need(2); const v = this.buffer.readUInt16LE(this.offset); this.offset += 2; return v; }
|
|
42
|
+
i16() { this.need(2); const v = this.buffer.readInt16LE(this.offset); this.offset += 2; return v; }
|
|
43
|
+
u32() { this.need(4); const v = this.buffer.readUInt32LE(this.offset); this.offset += 4; return v; }
|
|
44
|
+
i32() { this.need(4); const v = this.buffer.readInt32LE(this.offset); this.offset += 4; return v; }
|
|
45
|
+
f32() { this.need(4); const v = this.buffer.readFloatLE(this.offset); this.offset += 4; return v; }
|
|
46
|
+
f64() { this.need(8); const v = this.buffer.readDoubleLE(this.offset); this.offset += 8; return v; }
|
|
47
|
+
u64() { this.need(8); const v = this.buffer.readBigUInt64LE(this.offset); this.offset += 8; return v; }
|
|
48
|
+
i64() { this.need(8); const v = this.buffer.readBigInt64LE(this.offset); this.offset += 8; return v; }
|
|
49
|
+
string() {
|
|
50
|
+
const len = this.u64();
|
|
51
|
+
if (len > BigInt(this.buffer.length)) fail('metadata string length exceeds the read prefix', 'GGUF_HEADER_TRUNCATED');
|
|
52
|
+
return this.bytes(Number(len)).toString('utf8');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readTypedValue(cursor, type) {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case VALUE_TYPE.UINT8: return cursor.u8();
|
|
59
|
+
case VALUE_TYPE.INT8: return cursor.i8();
|
|
60
|
+
case VALUE_TYPE.UINT16: return cursor.u16();
|
|
61
|
+
case VALUE_TYPE.INT16: return cursor.i16();
|
|
62
|
+
case VALUE_TYPE.UINT32: return cursor.u32();
|
|
63
|
+
case VALUE_TYPE.INT32: return cursor.i32();
|
|
64
|
+
case VALUE_TYPE.FLOAT32: return cursor.f32();
|
|
65
|
+
case VALUE_TYPE.BOOL: return cursor.u8() !== 0;
|
|
66
|
+
case VALUE_TYPE.STRING: return cursor.string();
|
|
67
|
+
case VALUE_TYPE.UINT64: return cursor.u64();
|
|
68
|
+
case VALUE_TYPE.INT64: return cursor.i64();
|
|
69
|
+
case VALUE_TYPE.FLOAT64: return cursor.f64();
|
|
70
|
+
case VALUE_TYPE.ARRAY: {
|
|
71
|
+
const elementType = cursor.u32();
|
|
72
|
+
const count = cursor.u64();
|
|
73
|
+
if (count > BigInt(cursor.buffer.length)) fail('metadata array length exceeds the read prefix', 'GGUF_HEADER_TRUNCATED');
|
|
74
|
+
const values = new Array(Number(count));
|
|
75
|
+
for (let i = 0; i < values.length; i += 1) values[i] = readTypedValue(cursor, elementType);
|
|
76
|
+
return values;
|
|
77
|
+
}
|
|
78
|
+
default: fail(`unknown GGUF metadata value type ${type}`, 'GGUF_HEADER_UNKNOWN_TYPE');
|
|
79
|
+
}
|
|
80
|
+
return undefined; // istruzione morta, solo per lint: fail() lancia sempre
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function positiveSafeInteger(value, label) {
|
|
84
|
+
const n = typeof value === 'bigint' ? Number(value) : value;
|
|
85
|
+
if (!Number.isSafeInteger(n) || n <= 0) fail(`${label} is missing or not a positive integer`, 'GGUF_HEADER_METADATA_MISSING');
|
|
86
|
+
return n;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/*
|
|
90
|
+
* Stima dichiarata, non misurata (coerente con lo stato 'declared' che
|
|
91
|
+
* local-runtime-probe.mjs assegna a estimatedWorkingBytes): pesi caricati
|
|
92
|
+
* (≈ dimensione del file) + KV-cache stimata con la formula standard
|
|
93
|
+
* (2 per K+V · strati · contesto · embedding · 2 byte per valore fp16).
|
|
94
|
+
* ⛔ Semplificazione dichiarata: usa embedding_length INTERO anche per
|
|
95
|
+
* architetture con grouped-query attention (meno teste KV della
|
|
96
|
+
* dimensione piena) — sovrastima invece di sottostimare, appropriato per
|
|
97
|
+
* un controllo "ci sta?" prima di caricare: meglio dire "forse non ci
|
|
98
|
+
* sta" che far girare un modello che poi va in OOM davvero.
|
|
99
|
+
*/
|
|
100
|
+
/**
|
|
101
|
+
* ⛔⛔⛔ 02/9 (sera) — la versione precedente moltiplicava per
|
|
102
|
+
* `embeddingLength` INTERO, cioè come se ogni testa di attenzione avesse
|
|
103
|
+
* la sua coppia K/V. Sulle architetture moderne non è così: la
|
|
104
|
+
* **grouped-query attention** condivide le teste KV fra più teste di
|
|
105
|
+
* query, e la cache si riduce dello stesso rapporto. Sul Qwen3 27B
|
|
106
|
+
* dell'owner la stima usciva **340 GB** contro 15,6 GB liberi — un
|
|
107
|
+
* verdetto "non compatibile" che non dice niente di utile, perché
|
|
108
|
+
* sbagliato di un ordine di grandezza.
|
|
109
|
+
*
|
|
110
|
+
* ⭐ Ricerca 02/9 — la formula standard è
|
|
111
|
+
* `2 × strati × teste_KV × head_dim × token × byte_per_valore`
|
|
112
|
+
* (github.com/ggml-org/llama.cpp discussion #7949,
|
|
113
|
+
* omrimallis.com/posts/techniques-for-kv-cache-optimization): il `2` sono
|
|
114
|
+
* K e V, e le teste sono quelle **KV**, non quelle di query. Esempio
|
|
115
|
+
* citato: Llama 3 ha 8 teste KV contro 64 di query — **8× di cache in
|
|
116
|
+
* meno**.
|
|
117
|
+
*
|
|
118
|
+
* Qui `head_dim = embedding_length / head_count` e la dimensione KV è
|
|
119
|
+
* `head_count_kv × head_dim`. ⛔ Se il file non dichiara i conteggi delle
|
|
120
|
+
* teste si ricade sull'`embedding_length` intero: resta una
|
|
121
|
+
* sovrastima, ma dichiarata — meglio "forse non ci sta" che un OOM vero.
|
|
122
|
+
*/
|
|
123
|
+
function kvBytesPerToken({ blockCount, embeddingLength, headCount, headCountKv }) {
|
|
124
|
+
const dimensioneKv = (Number.isSafeInteger(headCount) && headCount > 0 && Number.isSafeInteger(headCountKv) && headCountKv > 0)
|
|
125
|
+
? (embeddingLength / headCount) * headCountKv
|
|
126
|
+
: embeddingLength;
|
|
127
|
+
/*
|
|
128
|
+
* ⛔ 02/9 — `Math.ceil` non è cosmetico: su un modello reale (il Qwen3 27B
|
|
129
|
+
* dell'owner) `embedding_length / head_count` NON è esatto e il prodotto
|
|
130
|
+
* usciva frazionario — 221.866,67 byte per token. `validateHeader` in
|
|
131
|
+
* `local-runtime-probe.mjs` pretende interi positivi, quindi il file
|
|
132
|
+
* veniva rifiutato come `MODEL_HEADER_INVALID`: un modello leggibile
|
|
133
|
+
* dichiarato illeggibile. Si arrotonda PER ECCESSO, nella stessa
|
|
134
|
+
* direzione conservativa del resto della stima.
|
|
135
|
+
*/
|
|
136
|
+
return Math.ceil(2 * blockCount * dimensioneKv * 2); // K+V, e 2 byte per valore (fp16)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Stima dichiarata, non misurata: pesi caricati (≈ dimensione del file) +
|
|
141
|
+
* KV-cache per il numero di token indicato.
|
|
142
|
+
* ⛔ `contextLength` è quello per cui si vuole la stima: chi chiama passa
|
|
143
|
+
* il contesto RICHIESTO dal profilo, non quello addestrato. La versione
|
|
144
|
+
* precedente usava sempre quello addestrato — sul 27B, 262.144 token
|
|
145
|
+
* invece dei 65.536 richiesti: un fattore 4 di sovrastima sopra a quello
|
|
146
|
+
* della GQA.
|
|
147
|
+
*/
|
|
148
|
+
function estimateWorkingBytes({ fileBytes, blockCount, contextLength, embeddingLength, headCount, headCountKv }) {
|
|
149
|
+
return fileBytes + kvBytesPerToken({ blockCount, embeddingLength, headCount, headCountKv }) * contextLength;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
export async function readGgufHeader(path) {
|
|
154
|
+
if (typeof path !== 'string' || !path) fail('path is required', 'GGUF_HEADER_MISCONFIGURED');
|
|
155
|
+
const handle = await open(path, 'r');
|
|
156
|
+
try {
|
|
157
|
+
const { size: fileBytes } = await handle.stat();
|
|
158
|
+
const prefixSize = Math.min(fileBytes, MAX_PREFIX_BYTES);
|
|
159
|
+
const buffer = Buffer.alloc(prefixSize);
|
|
160
|
+
const { bytesRead } = await handle.read(buffer, 0, prefixSize, 0);
|
|
161
|
+
const cursor = new Cursor(buffer.subarray(0, bytesRead));
|
|
162
|
+
|
|
163
|
+
const magic = cursor.bytes(4).toString('ascii');
|
|
164
|
+
const version = cursor.u32();
|
|
165
|
+
if (magic !== 'GGUF') fail('not a GGUF file (magic mismatch)', 'GGUF_HEADER_MAGIC_MISMATCH');
|
|
166
|
+
if (version !== 3) fail(`unsupported GGUF version ${version} (only 3 is supported)`, 'GGUF_HEADER_VERSION_UNSUPPORTED');
|
|
167
|
+
cursor.u64(); // tensor_count — non serve per questa stima, letto solo per avanzare correttamente
|
|
168
|
+
const metadataCount = cursor.u64();
|
|
169
|
+
if (metadataCount > 1_000_000n) fail('metadata_kv_count is implausibly large', 'GGUF_HEADER_INVALID');
|
|
170
|
+
|
|
171
|
+
const metadata = new Map();
|
|
172
|
+
for (let i = 0n; i < metadataCount; i += 1n) {
|
|
173
|
+
const key = cursor.string();
|
|
174
|
+
const type = cursor.u32();
|
|
175
|
+
metadata.set(key, readTypedValue(cursor, type));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const architecture = metadata.get('general.architecture');
|
|
179
|
+
if (typeof architecture !== 'string' || !architecture) fail('general.architecture metadata key is missing', 'GGUF_HEADER_ARCHITECTURE_MISSING');
|
|
180
|
+
const contextLength = positiveSafeInteger(metadata.get(`${architecture}.context_length`), `${architecture}.context_length`);
|
|
181
|
+
const embeddingLength = positiveSafeInteger(metadata.get(`${architecture}.embedding_length`), `${architecture}.embedding_length`);
|
|
182
|
+
const blockCount = positiveSafeInteger(metadata.get(`${architecture}.block_count`), `${architecture}.block_count`);
|
|
183
|
+
/*
|
|
184
|
+
* ⛔ I conteggi delle teste sono OPZIONALI, a differenza dei tre
|
|
185
|
+
* sopra: un file che non li dichiara resta leggibile e si stima con
|
|
186
|
+
* l'embedding intero (sovrastima dichiarata, vedi kvBytesPerToken).
|
|
187
|
+
* Per questo NON passano da `positiveSafeInteger`, che lancerebbe.
|
|
188
|
+
*/
|
|
189
|
+
const numeroInteroOpzionale = (valore) => {
|
|
190
|
+
const n = typeof valore === 'bigint' ? Number(valore) : valore;
|
|
191
|
+
return Number.isSafeInteger(n) && n > 0 ? n : undefined;
|
|
192
|
+
};
|
|
193
|
+
const headCount = numeroInteroOpzionale(metadata.get(`${architecture}.attention.head_count`));
|
|
194
|
+
const headCountKv = numeroInteroOpzionale(metadata.get(`${architecture}.attention.head_count_kv`));
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
magic,
|
|
198
|
+
version,
|
|
199
|
+
trainedContext: contextLength,
|
|
200
|
+
/** ⭐ 02/9 — il costo per TOKEN, così chi chiama può stimare sul contesto che gli serve invece che su quello addestrato. */
|
|
201
|
+
kvCacheBytesPerToken: kvBytesPerToken({ blockCount, embeddingLength, headCount, headCountKv }),
|
|
202
|
+
estimatedWorkingBytes: estimateWorkingBytes({ fileBytes, blockCount, contextLength, embeddingLength, headCount, headCountKv }),
|
|
203
|
+
};
|
|
204
|
+
} finally {
|
|
205
|
+
await handle.close();
|
|
206
|
+
}
|
|
207
|
+
}
|