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,244 @@
|
|
|
1
|
+
import { parseRuntimeEventEnvelope } from './local-runtime-events.mjs';
|
|
2
|
+
import { createStreamPartitioner } from './stream-partition.mjs';
|
|
3
|
+
|
|
4
|
+
export class LlamaServerRuntimeError extends Error {
|
|
5
|
+
constructor(message, code = 'RUNTIME_FAILED') {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'LlamaServerRuntimeError';
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function runtimeInvalid(message) {
|
|
13
|
+
return new LlamaServerRuntimeError(message, 'RUNTIME_INVALID');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function envelope({ runId, turnId, seq, type, runtimeId = 'llama.cpp', ...payload }, now) {
|
|
17
|
+
return parseRuntimeEventEnvelope({ runId, turnId, runtimeId, seq, at: now().toISOString(), type, ...payload });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function readText(response) {
|
|
21
|
+
return typeof response.text === 'function' ? response.text() : '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function* sseEvents(response) {
|
|
25
|
+
if (!response.body || typeof response.body.getReader !== 'function') throw new LlamaServerRuntimeError('SSE body is missing', 'RUNTIME_SSE_INVALID');
|
|
26
|
+
const reader = response.body.getReader();
|
|
27
|
+
const decoder = new TextDecoder();
|
|
28
|
+
let buffer = '';
|
|
29
|
+
let finished = false;
|
|
30
|
+
try {
|
|
31
|
+
while (!finished) {
|
|
32
|
+
const { value, done } = await reader.read();
|
|
33
|
+
buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done });
|
|
34
|
+
const lines = buffer.split(/\r?\n/u);
|
|
35
|
+
buffer = lines.pop() ?? '';
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (!line.startsWith('data:')) continue;
|
|
38
|
+
const data = line.slice(5).trim();
|
|
39
|
+
if (data === '') continue;
|
|
40
|
+
if (data === '[DONE]') { finished = true; break; }
|
|
41
|
+
try { yield JSON.parse(data); } catch { yield { __invalid: true }; }
|
|
42
|
+
}
|
|
43
|
+
if (done) break;
|
|
44
|
+
}
|
|
45
|
+
if (buffer.startsWith('data:')) {
|
|
46
|
+
const data = buffer.slice(5).trim();
|
|
47
|
+
if (data && data !== '[DONE]') {
|
|
48
|
+
try { yield JSON.parse(data); } catch { yield { __invalid: true }; }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} finally {
|
|
52
|
+
await reader.cancel().catch(() => {});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createLlamaServerRuntime({ supervisor, fetchImpl = fetch, now = () => new Date() } = {}) {
|
|
57
|
+
if (!supervisor || typeof supervisor.status !== 'function') throw new LlamaServerRuntimeError('supervisor is required', 'RUNTIME_MISCONFIGURED');
|
|
58
|
+
const activeRequests = new Map();
|
|
59
|
+
|
|
60
|
+
function baseUrl() {
|
|
61
|
+
const status = supervisor.status();
|
|
62
|
+
if (!status.baseUrl || status.state !== 'ready') throw new LlamaServerRuntimeError('runtime is not ready', 'RUNTIME_NOT_READY');
|
|
63
|
+
return status.baseUrl;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function jsonRequest(path) {
|
|
67
|
+
const response = typeof supervisor.request === 'function'
|
|
68
|
+
? await supervisor.request(path, { headers: { Accept: 'application/json' } })
|
|
69
|
+
: await fetchImpl(`${baseUrl()}${path}`, { headers: { Accept: 'application/json' } });
|
|
70
|
+
if (!response.ok) throw new LlamaServerRuntimeError(`runtime returned HTTP ${response.status}`, 'RUNTIME_HTTP_ERROR');
|
|
71
|
+
try { return await response.json(); } catch { throw new LlamaServerRuntimeError('runtime returned invalid JSON', 'RUNTIME_RESPONSE_INVALID'); }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function probe() {
|
|
75
|
+
return jsonRequest('/props');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* L'id del modello effettivamente caricato adesso, o `null` se non c'è
|
|
80
|
+
* nessun runtime pronto.
|
|
81
|
+
*
|
|
82
|
+
* ⛔ 02/9 — serve a `inspectModel()` per NON attribuire l'`n_ctx` osservato
|
|
83
|
+
* a un modello diverso da quello che lo ha prodotto. Si legge dal
|
|
84
|
+
* supervisore (codice nostro, lo stesso valore che finisce in `--alias`) e
|
|
85
|
+
* non da un campo indovinato dentro `/props`: llama.cpp ne espone di
|
|
86
|
+
* simili, ma un nome di campo dedotto invece che verificato è già costato
|
|
87
|
+
* tre difetti su questo stesso sottosistema il 02/9.
|
|
88
|
+
*/
|
|
89
|
+
function loadedModelId() {
|
|
90
|
+
try {
|
|
91
|
+
const stato = supervisor.status();
|
|
92
|
+
return stato?.state === 'ready' && typeof stato.modelId === 'string' && stato.modelId !== ''
|
|
93
|
+
? stato.modelId
|
|
94
|
+
: null;
|
|
95
|
+
} catch { return null; }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function listModels() {
|
|
99
|
+
const body = await jsonRequest('/v1/models');
|
|
100
|
+
if (!Array.isArray(body?.data)) throw new LlamaServerRuntimeError('runtime model list is invalid', 'RUNTIME_RESPONSE_INVALID');
|
|
101
|
+
return body.data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** ⛔ `options` viaggia intero, `contextLength` compreso: era qui che si perdeva. */
|
|
105
|
+
async function load(options) {
|
|
106
|
+
return supervisor.start(options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function unload() {
|
|
110
|
+
return supervisor.stop();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function health() {
|
|
114
|
+
return supervisor.health();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function metrics() {
|
|
118
|
+
const response = typeof supervisor.request === 'function'
|
|
119
|
+
? await supervisor.request('/metrics', { headers: { Accept: 'text/plain' } })
|
|
120
|
+
: await fetchImpl(`${baseUrl()}/metrics`, { headers: { Accept: 'text/plain' } });
|
|
121
|
+
if (!response.ok) throw new LlamaServerRuntimeError(`runtime returned HTTP ${response.status}`, 'RUNTIME_HTTP_ERROR');
|
|
122
|
+
return readText(response);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function* generateStream({ runId, turnId, modelId, messages, reasoning, reasoningFormat, parseToolCalls = false, maxTokens = 512, signal, requestId } = {}) {
|
|
126
|
+
if (typeof runId !== 'string' || runId.trim() === '' || typeof turnId !== 'string' || turnId.trim() === '' || typeof modelId !== 'string' || modelId.trim() === '' || !Array.isArray(messages)) throw runtimeInvalid('stream request is invalid');
|
|
127
|
+
const controller = new AbortController();
|
|
128
|
+
if (signal?.aborted) controller.abort(signal.reason);
|
|
129
|
+
const combinedSignal = signal && typeof AbortSignal.any === 'function' ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
130
|
+
if (signal && typeof AbortSignal.any !== 'function') signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
|
|
131
|
+
if (requestId) activeRequests.set(requestId, controller);
|
|
132
|
+
let response;
|
|
133
|
+
try {
|
|
134
|
+
combinedSignal.throwIfAborted();
|
|
135
|
+
const body = { model: modelId, messages, stream: true, max_tokens: Number.isInteger(maxTokens) && maxTokens > 0 ? maxTokens : 512 };
|
|
136
|
+
if (reasoning?.effort) body.reasoning_effort = reasoning.effort;
|
|
137
|
+
if (reasoningFormat) body.reasoning_format = reasoningFormat;
|
|
138
|
+
if (parseToolCalls) body.parse_tool_calls = true;
|
|
139
|
+
const requestOptions = {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
headers: { Accept: 'text/event-stream', 'Content-Type': 'application/json' },
|
|
142
|
+
body: JSON.stringify(body),
|
|
143
|
+
signal: combinedSignal,
|
|
144
|
+
};
|
|
145
|
+
response = typeof supervisor.request === 'function'
|
|
146
|
+
? await supervisor.request('/v1/chat/completions', requestOptions)
|
|
147
|
+
: await fetchImpl(`${baseUrl()}/v1/chat/completions`, requestOptions);
|
|
148
|
+
if (!response.ok) throw new LlamaServerRuntimeError(`runtime returned HTTP ${response.status}: ${await readText(response)}`, 'RUNTIME_HTTP_ERROR');
|
|
149
|
+
let seq = 0;
|
|
150
|
+
const pendingTools = new Set();
|
|
151
|
+
const toolsByIndex = new Map();
|
|
152
|
+
const toolsById = new Map();
|
|
153
|
+
// A valid JSON prefix is not a completed call (e.g. "1" then "2").
|
|
154
|
+
// Join and validate once, at finish_reason or ordinary stream EOF.
|
|
155
|
+
// Aborted/failed readers never reach the final flush.
|
|
156
|
+
const flushNativeTools = function* () {
|
|
157
|
+
for (const tool of pendingTools) {
|
|
158
|
+
combinedSignal.throwIfAborted();
|
|
159
|
+
let event;
|
|
160
|
+
try {
|
|
161
|
+
event = envelope({ runId, turnId, seq, type: 'tool_call', id: tool.id,
|
|
162
|
+
name: tool.names.join(''), arguments: tool.arguments.join('') }, now);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (error?.code !== 'LOCAL_RUNTIME_INVALID') throw error;
|
|
165
|
+
event = envelope({ runId, turnId, seq, type: 'error', code: 'TOOL_CALL_MALFORMED',
|
|
166
|
+
message: `malformed tool call ${tool.id}`, retryable: false }, now);
|
|
167
|
+
}
|
|
168
|
+
seq++;
|
|
169
|
+
yield event;
|
|
170
|
+
}
|
|
171
|
+
pendingTools.clear();
|
|
172
|
+
toolsByIndex.clear();
|
|
173
|
+
toolsById.clear();
|
|
174
|
+
};
|
|
175
|
+
let taggedEvents = [];
|
|
176
|
+
const taggedContent = createStreamPartitioner({
|
|
177
|
+
onText: (value) => taggedEvents.push({ type: 'text', value }),
|
|
178
|
+
onReasoning: (value) => taggedEvents.push({ type: 'reasoning', value }),
|
|
179
|
+
onToolCall: (value) => taggedEvents.push({ type: 'tool_call', ...value }),
|
|
180
|
+
});
|
|
181
|
+
const flushTaggedEvents = function* () {
|
|
182
|
+
const events = taggedEvents;
|
|
183
|
+
taggedEvents = [];
|
|
184
|
+
for (const event of events) {
|
|
185
|
+
if (event.type === 'tool_call') {
|
|
186
|
+
yield envelope({ runId, turnId, seq: seq++, type: 'tool_call', id: event.id || `tagged-tool-${seq}`, name: event.name, arguments: typeof event.arguments === 'string' ? event.arguments : JSON.stringify(event.arguments ?? {}) }, now);
|
|
187
|
+
} else {
|
|
188
|
+
yield envelope({ runId, turnId, seq: seq++, type: event.type, value: event.value }, now);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
for await (const chunk of sseEvents(response)) {
|
|
193
|
+
combinedSignal.throwIfAborted();
|
|
194
|
+
if (chunk.__invalid) {
|
|
195
|
+
yield envelope({ runId, turnId, seq: seq++, type: 'error', code: 'RUNTIME_SSE_INVALID', message: 'runtime emitted malformed SSE JSON', retryable: false }, now);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const choice = chunk.choices?.[0];
|
|
199
|
+
const delta = choice?.delta;
|
|
200
|
+
if (!delta) {
|
|
201
|
+
if (choice?.finish_reason != null) yield* flushNativeTools();
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const reasoningDelta = delta.reasoning_content ?? delta.reasoning;
|
|
205
|
+
if (typeof reasoningDelta === 'string' && reasoningDelta !== '') yield envelope({ runId, turnId, seq: seq++, type: 'reasoning', value: reasoningDelta }, now);
|
|
206
|
+
if (typeof delta.content === 'string' && delta.content !== '') {
|
|
207
|
+
taggedContent.push(delta.content);
|
|
208
|
+
yield* flushTaggedEvents();
|
|
209
|
+
}
|
|
210
|
+
for (const call of delta.tool_calls ?? []) {
|
|
211
|
+
// OpenAI-style deltas normally supply id/name only in the first
|
|
212
|
+
// fragment; index remains stable. Keep id-only legacy streams too.
|
|
213
|
+
const index = Number.isInteger(call.index) && call.index >= 0 ? call.index : null;
|
|
214
|
+
const hasId = call.id !== undefined && call.id !== null;
|
|
215
|
+
const tool = (index !== null ? toolsByIndex.get(index) : undefined)
|
|
216
|
+
?? (hasId ? toolsById.get(call.id) : undefined)
|
|
217
|
+
?? { id: call.id ?? String(index ?? pendingTools.size), names: [], arguments: [] };
|
|
218
|
+
pendingTools.add(tool);
|
|
219
|
+
if (index !== null) toolsByIndex.set(index, tool);
|
|
220
|
+
if (hasId) { tool.id = call.id; toolsById.set(call.id, tool); }
|
|
221
|
+
if (call.function?.name != null) tool.names.push(call.function.name);
|
|
222
|
+
if (call.function?.arguments != null) tool.arguments.push(call.function.arguments);
|
|
223
|
+
}
|
|
224
|
+
if (choice.finish_reason != null) yield* flushNativeTools();
|
|
225
|
+
}
|
|
226
|
+
combinedSignal.throwIfAborted();
|
|
227
|
+
taggedContent.finish();
|
|
228
|
+
yield* flushTaggedEvents();
|
|
229
|
+
yield* flushNativeTools();
|
|
230
|
+
yield envelope({ runId, turnId, seq: seq++, type: 'done' }, now);
|
|
231
|
+
} finally {
|
|
232
|
+
if (requestId) activeRequests.delete(requestId);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function cancel(requestId) {
|
|
237
|
+
const controller = activeRequests.get(requestId);
|
|
238
|
+
if (!controller) return false;
|
|
239
|
+
controller.abort();
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return Object.freeze({ probe, loadedModelId, listModels, load, unload, generateStream, cancel, health, metrics });
|
|
244
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unisce cartella e file del manifest. ⛔ Non usa `node:path.join`: i
|
|
3
|
+
* percorsi del manifest sono relativi e sempre con `/` (validati così da
|
|
4
|
+
* `local-model-store.mjs`), e su Windows `join` li riscriverebbe con `\`,
|
|
5
|
+
* cambiando una stringa che il chiamante potrebbe confrontare. Il
|
|
6
|
+
* chiamante ci antepone la radice assoluta con il join vero.
|
|
7
|
+
*/
|
|
8
|
+
function joinPosix(cartella, file) {
|
|
9
|
+
return `${String(cartella).replace(/[/\\]+$/u, '')}/${String(file).replace(/^[/\\]+/u, '')}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class LocalRuntimeProbeError extends Error {
|
|
13
|
+
constructor(message, code = 'LOCAL_RUNTIME_PROBE_FAILED') {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'LocalRuntimeProbeError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const unknown = () => ({ state: 'unknown', value: null });
|
|
21
|
+
const fact = (state, value) => ({ state, value });
|
|
22
|
+
const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
|
|
23
|
+
|
|
24
|
+
function observedString(value) {
|
|
25
|
+
return typeof value === 'string' && value.trim() !== '' ? fact('observed', value) : unknown();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function observedBoolean(value) {
|
|
29
|
+
return typeof value === 'boolean' ? fact('observed', value) : unknown();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function validateHeader(value) {
|
|
33
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
34
|
+
|| value.magic !== 'GGUF' || value.version !== 3
|
|
35
|
+
|| !positiveInteger(value.trainedContext)
|
|
36
|
+
|| !positiveInteger(value.estimatedWorkingBytes)) {
|
|
37
|
+
throw new LocalRuntimeProbeError('GGUF header is invalid', 'MODEL_HEADER_INVALID');
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseTokensPerSecond(value) {
|
|
43
|
+
if (typeof value !== 'string') return unknown();
|
|
44
|
+
for (const line of value.split(/\r?\n/u)) {
|
|
45
|
+
const match = line.match(/^llamacpp:predicted_tokens_seconds(?:\{[^}]*\})?\s+([^\s]+)$/u);
|
|
46
|
+
const number = match ? Number(match[1]) : NaN;
|
|
47
|
+
if (Number.isFinite(number) && number >= 0) return fact('observed', number);
|
|
48
|
+
}
|
|
49
|
+
return unknown();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createLocalRuntimeProbe({
|
|
53
|
+
runtime,
|
|
54
|
+
modelStore,
|
|
55
|
+
readHeader,
|
|
56
|
+
measureMachine,
|
|
57
|
+
now = () => new Date(),
|
|
58
|
+
clockMs = () => performance.now(),
|
|
59
|
+
} = {}) {
|
|
60
|
+
if (!runtime || typeof runtime.probe !== 'function' || typeof runtime.generateStream !== 'function'
|
|
61
|
+
|| !modelStore || typeof modelStore.inspect !== 'function'
|
|
62
|
+
|| typeof readHeader !== 'function' || typeof measureMachine !== 'function') {
|
|
63
|
+
throw new LocalRuntimeProbeError('probe dependencies are invalid', 'LOCAL_RUNTIME_PROBE_MISCONFIGURED');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function readRuntimeProps() {
|
|
67
|
+
try {
|
|
68
|
+
const props = await runtime.probe();
|
|
69
|
+
if (!props || typeof props !== 'object' || Array.isArray(props)) throw new Error('invalid response');
|
|
70
|
+
return props;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error instanceof LocalRuntimeProbeError) throw error;
|
|
73
|
+
throw new LocalRuntimeProbeError(`runtime probe failed: ${error?.message || 'unknown error'}`, 'RUNTIME_PROBE_FAILED');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} modelId
|
|
79
|
+
* @param {{contextTokens?: number}} [opzioni] `contextTokens` = il contesto
|
|
80
|
+
* per cui si vuole la stima di memoria. ⛔ Assente = si usa quello
|
|
81
|
+
* ADDESTRATO, che per un modello a contesto lungo è enormemente più
|
|
82
|
+
* grande di quello che si userà davvero (sul Qwen3 27B: 262.144 token
|
|
83
|
+
* invece dei 65.536 richiesti, un fattore 4 di sovrastima). `fit()` lo
|
|
84
|
+
* passa sempre; resta opzionale per non rompere chi chiama
|
|
85
|
+
* `inspectModel` da solo per sapere cosa dichiara il file.
|
|
86
|
+
*/
|
|
87
|
+
async function inspectModel(modelId, { contextTokens } = {}) {
|
|
88
|
+
const manifest = await modelStore.inspect(modelId);
|
|
89
|
+
if (!manifest) throw new LocalRuntimeProbeError(`model ${modelId} not found`, 'MODEL_NOT_FOUND');
|
|
90
|
+
if (manifest.state !== 'ready') throw new LocalRuntimeProbeError(`model ${modelId} is not ready`, 'MODEL_NOT_READY');
|
|
91
|
+
|
|
92
|
+
let header;
|
|
93
|
+
try {
|
|
94
|
+
/*
|
|
95
|
+
* ⛔⛔⛔ 02/9 (sera) — QUI si passava `manifest.path` da solo, e
|
|
96
|
+
* `manifest.path` è la CARTELLA del modello, non il file: il lettore
|
|
97
|
+
* riceveva una directory e falliva sempre con
|
|
98
|
+
* `MODEL_HEADER_UNREADABLE`. Il difetto è mio, del collegamento
|
|
99
|
+
* scritto stamattina, e non era emerso perché i test iniettano un
|
|
100
|
+
* `readHeader` finto (a cui il percorso non importa) e sulla macchina
|
|
101
|
+
* non c'era NESSUN modello registrato con cui provarlo dal vivo:
|
|
102
|
+
* è saltato fuori solo dopo aver reimportato due GGUF veri.
|
|
103
|
+
* ⇒ Il percorso si compone qui, dove si conosce la forma del
|
|
104
|
+
* manifest (cartella + primo file), esattamente come già faceva
|
|
105
|
+
* `load` in `server.mjs`; la radice assoluta la mette il chiamante,
|
|
106
|
+
* che è l'unico a conoscerla.
|
|
107
|
+
*/
|
|
108
|
+
const file = Array.isArray(manifest.files) ? manifest.files[0] : null;
|
|
109
|
+
if (!file || typeof file.path !== 'string') throw new LocalRuntimeProbeError(`model ${modelId} has no file to inspect`, 'MODEL_HEADER_UNREADABLE');
|
|
110
|
+
header = validateHeader(await readHeader(joinPosix(manifest.path, file.path)));
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof LocalRuntimeProbeError) throw error;
|
|
113
|
+
throw new LocalRuntimeProbeError(`cannot read model header: ${error?.message || 'unknown error'}`, 'MODEL_HEADER_UNREADABLE');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* ⛔⛔ 02/9 (sera) — `readRuntimeProps()` LANCIA se il runtime locale
|
|
118
|
+
* non risponde, e faceva abortire tutta `inspectModel`: `/fit`
|
|
119
|
+
* rispondeva `RUNTIME_PROBE_FAILED` anche quando aveva già letto
|
|
120
|
+
* l'header e poteva dire cose vere su spazio, memoria e contesto
|
|
121
|
+
* addestrato. Incoerente col disegno di questo stesso file: tutto ciò
|
|
122
|
+
* che segue è GIÀ scritto per degradare (`unknown()`,
|
|
123
|
+
* `observedBoolean(caps?.…)`), e `fit()` ha lo stato `unknown` con
|
|
124
|
+
* motivo `context` esattamente per questo caso.
|
|
125
|
+
* ⇒ Un runtime spento non è un errore della lettura: è un fatto NON
|
|
126
|
+
* OSSERVATO, e si dichiara come tale invece di rifiutare la risposta.
|
|
127
|
+
* ⛔ `qualify()` resta severo: chiede `fit()` compatibile e con
|
|
128
|
+
* `unknown` si ferma da solo — un giro di generazione vero senza
|
|
129
|
+
* runtime non deve neanche essere tentato.
|
|
130
|
+
*/
|
|
131
|
+
let props = {};
|
|
132
|
+
let raggiungibile = true;
|
|
133
|
+
try {
|
|
134
|
+
props = await readRuntimeProps();
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!(error instanceof LocalRuntimeProbeError) || error.code !== 'RUNTIME_PROBE_FAILED') throw error;
|
|
137
|
+
raggiungibile = false;
|
|
138
|
+
}
|
|
139
|
+
/*
|
|
140
|
+
* ⛔⛔⛔ L'`n_ctx` È DEL MODELLO CARICATO, NON DELLA MACCHINA — 02/9.
|
|
141
|
+
*
|
|
142
|
+
* Il difetto, trovato dal vivo e non leggendo: con un runtime acceso su
|
|
143
|
+
* un modello, la riga di OGNI ALTRO modello mostrava come «contesto
|
|
144
|
+
* disponibile» l'`n_ctx` di quello caricato. `/props` descrive la
|
|
145
|
+
* sessione in corso, non la macchina: quel numero è vero solo per il
|
|
146
|
+
* modello che l'ha prodotto. Un 262k caricato faceva sembrare enorme il
|
|
147
|
+
* contesto di un 4k, e un 4k caricato faceva bocciare per contesto un
|
|
148
|
+
* modello che ne ha 262k — cioè l'errore in ENTRAMBE le direzioni.
|
|
149
|
+
*
|
|
150
|
+
* ⭐ Ricerca 02/9: llama.cpp tiene i due numeri separati proprio perché
|
|
151
|
+
* sono cose diverse — `n_ctx` (quello caricato) contro `n_ctx_train`
|
|
152
|
+
* (quello del modello) — e stampa un avviso quando divergono
|
|
153
|
+
* («n_ctx_per_seq (4096) < n_ctx_train (8192)»). Noi ne usavamo uno solo
|
|
154
|
+
* per rispondere a due domande.
|
|
155
|
+
*
|
|
156
|
+
* ⇒ L'osservazione vale solo per il modello caricato. Per gli altri il
|
|
157
|
+
* runtime NON HA OSSERVATO NIENTE, e `unknown()` è la risposta onesta:
|
|
158
|
+
* quando quel modello verrà caricato avrà il contesto suo. ⛔ Non è un
|
|
159
|
+
* ripiego pessimista — è la differenza fra «non lo so» e «so che è
|
|
160
|
+
* questo», la stessa che regge tutto il resto di questo file.
|
|
161
|
+
*/
|
|
162
|
+
/*
|
|
163
|
+
* `null` = nessun runtime pronto (e allora `props` è vuoto, quindi si
|
|
164
|
+
* ricade su `unknown()` da sé) oppure un adattatore che non sa ancora
|
|
165
|
+
* rispondere. In entrambi i casi non c'è un modello ALTRO a cui
|
|
166
|
+
* attribuire il numero, che è la cosa da impedire.
|
|
167
|
+
*/
|
|
168
|
+
const caricato = typeof runtime.loadedModelId === 'function' ? runtime.loadedModelId() : null;
|
|
169
|
+
const parlaDiQuestoModello = caricato === null || caricato === manifest.id;
|
|
170
|
+
const runtimeTokens = props.default_generation_settings?.n_ctx;
|
|
171
|
+
const runtimeContext = parlaDiQuestoModello && positiveInteger(runtimeTokens)
|
|
172
|
+
? fact('observed', runtimeTokens)
|
|
173
|
+
: unknown();
|
|
174
|
+
const caps = props.chat_template_caps;
|
|
175
|
+
return {
|
|
176
|
+
modelId: manifest.id,
|
|
177
|
+
format: { state: 'observed', magic: 'GGUF', version: 3 },
|
|
178
|
+
storageBytes: { state: 'declared', value: manifest.bytes },
|
|
179
|
+
/*
|
|
180
|
+
* ⛔ 02/9 (sera) — si stima sul contesto RICHIESTO quando il lettore
|
|
181
|
+
* espone il costo per token; senza quel dato (header vecchio o file
|
|
182
|
+
* che non dichiara le teste) si ricade sulla stima del file, che usa
|
|
183
|
+
* il contesto addestrato. Il tetto è il contesto addestrato: chiedere
|
|
184
|
+
* più di quanto il modello sa fare non costa più memoria, semmai è il
|
|
185
|
+
* controllo sul contesto a bocciarlo, e con un motivo suo.
|
|
186
|
+
*/
|
|
187
|
+
workingMemoryBytes: {
|
|
188
|
+
state: 'declared',
|
|
189
|
+
// ⛔ Math.ceil come nel lettore: il consumatore a valle pretende interi.
|
|
190
|
+
value: (Number.isSafeInteger(header.kvCacheBytesPerToken) && Number.isSafeInteger(contextTokens) && contextTokens > 0)
|
|
191
|
+
? Math.ceil((header.estimatedWorkingBytes - header.kvCacheBytesPerToken * header.trainedContext)
|
|
192
|
+
+ header.kvCacheBytesPerToken * Math.min(contextTokens, header.trainedContext))
|
|
193
|
+
: header.estimatedWorkingBytes,
|
|
194
|
+
},
|
|
195
|
+
context: {
|
|
196
|
+
trainedTokens: fact('declared', header.trainedContext),
|
|
197
|
+
runtimeTokens: runtimeContext,
|
|
198
|
+
effectiveTokens: runtimeContext.state === 'observed'
|
|
199
|
+
? fact('observed', Math.min(header.trainedContext, runtimeContext.value))
|
|
200
|
+
: fact('declared', header.trainedContext),
|
|
201
|
+
},
|
|
202
|
+
/*
|
|
203
|
+
* ⛔⛔⛔ E NON È SOLO IL CONTESTO — scoperto allargando la cura, 02/9.
|
|
204
|
+
*
|
|
205
|
+
* `chat_template` e `chat_template_caps` arrivano dallo STESSO `/props`,
|
|
206
|
+
* e descrivono anch'essi la sessione in corso. Con un modello caricato,
|
|
207
|
+
* la riga di un altro modello dichiarava il suo template e il suo
|
|
208
|
+
* supporto agli attrezzi — cioè rispondeva «sì, chiama gli attrezzi»
|
|
209
|
+
* per un modello che potrebbe non saperlo fare. È il difetto più grave
|
|
210
|
+
* dei due, perché una capacità inventata si scopre solo a metà di una
|
|
211
|
+
* sessione agentica.
|
|
212
|
+
* ⇒ Stessa regola del contesto: se non è il modello caricato, il
|
|
213
|
+
* runtime non ha osservato NIENTE di lui.
|
|
214
|
+
*/
|
|
215
|
+
runtime: { reachable: raggiungibile, servingThisModel: parlaDiQuestoModello, servingModelId: caricato },
|
|
216
|
+
template: parlaDiQuestoModello && typeof props.chat_template === 'string' && props.chat_template !== ''
|
|
217
|
+
? fact('observed', true)
|
|
218
|
+
: unknown(),
|
|
219
|
+
capabilities: parlaDiQuestoModello ? {
|
|
220
|
+
tools: observedBoolean(caps?.supports_tools),
|
|
221
|
+
toolCalls: observedBoolean(caps?.supports_tool_calls),
|
|
222
|
+
systemRole: observedBoolean(caps?.supports_system_role),
|
|
223
|
+
} : { tools: unknown(), toolCalls: unknown(), systemRole: unknown() },
|
|
224
|
+
backend: observedString(props.backend),
|
|
225
|
+
build: observedString(props.build),
|
|
226
|
+
observedAt: now().toISOString(),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function measureBackend() {
|
|
231
|
+
const props = await readRuntimeProps();
|
|
232
|
+
return {
|
|
233
|
+
backend: observedString(props.backend),
|
|
234
|
+
thermal: unknown(),
|
|
235
|
+
observedAt: now().toISOString(),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function fit(modelId, { profile = 'agent', contextTokens = profile === 'agent' ? 65_536 : 4_096 } = {}) {
|
|
240
|
+
if (!['agent', 'chat'].includes(profile) || !positiveInteger(contextTokens)) {
|
|
241
|
+
throw new LocalRuntimeProbeError('fit request is invalid', 'FIT_INVALID');
|
|
242
|
+
}
|
|
243
|
+
// ⛔ Il contesto RICHIESTO arriva fin qui: è quello per cui la memoria va stimata.
|
|
244
|
+
const inspection = await inspectModel(modelId, { contextTokens });
|
|
245
|
+
let machine;
|
|
246
|
+
try {
|
|
247
|
+
machine = await measureMachine();
|
|
248
|
+
} catch {
|
|
249
|
+
return { modelId, profile, state: 'unknown', reason: 'measurement', inspection };
|
|
250
|
+
}
|
|
251
|
+
const storageAvailable = machine?.storage?.allocatableBytes;
|
|
252
|
+
const memoryAvailable = machine?.memory?.freeBytes;
|
|
253
|
+
const base = {
|
|
254
|
+
modelId,
|
|
255
|
+
profile,
|
|
256
|
+
context: { requestedTokens: contextTokens, availableTokens: inspection.context.effectiveTokens.value },
|
|
257
|
+
storage: { requiredBytes: inspection.storageBytes.value, availableBytes: storageAvailable ?? null },
|
|
258
|
+
memory: { requiredBytes: inspection.workingMemoryBytes.value, availableBytes: memoryAvailable ?? null },
|
|
259
|
+
inspection,
|
|
260
|
+
};
|
|
261
|
+
if (!Number.isSafeInteger(storageAvailable) || !Number.isSafeInteger(memoryAvailable)) return { ...base, state: 'unknown', reason: 'measurement' };
|
|
262
|
+
if (inspection.storageBytes.value > storageAvailable) return { ...base, state: 'blocked', reason: 'storage' };
|
|
263
|
+
if (inspection.workingMemoryBytes.value > memoryAvailable) return { ...base, state: 'blocked', reason: 'memory' };
|
|
264
|
+
/*
|
|
265
|
+
* ⛔ Il cancello del contesto guarda la RAGGIUNGIBILITÀ, non l'osservazione.
|
|
266
|
+
*
|
|
267
|
+
* Prima leggeva `runtimeTokens.state !== 'observed'`, e finché quel campo
|
|
268
|
+
* conteneva l'`n_ctx` di chiunque fosse caricato le due cose coincidevano.
|
|
269
|
+
* Da quando il numero è attribuito al modello giusto, non coincidono più:
|
|
270
|
+
* un modello NON caricato ha `runtimeTokens` sconosciuto per costruzione,
|
|
271
|
+
* e con la vecchia riga ogni riga della lista sarebbe diventata «non lo
|
|
272
|
+
* so» — cioè la lista avrebbe smesso di rispondere alla sola domanda per
|
|
273
|
+
* cui esiste (*ci sta, prima di caricarlo?*).
|
|
274
|
+
*
|
|
275
|
+
* ⇒ Il caso che quella riga voleva prendere resta preso, e per intero: se
|
|
276
|
+
* il runtime non risponde non si sa niente né del contesto né del
|
|
277
|
+
* template. Se invece risponde ma sta servendo un altro modello, il
|
|
278
|
+
* contesto ADDESTRATO lo dichiara l'header — che è un fatto del modello,
|
|
279
|
+
* non della sessione — e il profilo agente si ferma comunque poco sotto,
|
|
280
|
+
* sul cancello delle capacità, che ora è sconosciuto per lo stesso motivo.
|
|
281
|
+
*/
|
|
282
|
+
if (!inspection.runtime.reachable) return { ...base, state: 'unknown', reason: 'context' };
|
|
283
|
+
if (inspection.context.effectiveTokens.value < contextTokens) {
|
|
284
|
+
return { ...base, state: profile === 'agent' && inspection.context.effectiveTokens.value < 65_536 ? 'chat-only' : 'blocked', reason: 'context' };
|
|
285
|
+
}
|
|
286
|
+
if (profile === 'agent') {
|
|
287
|
+
const capabilityValues = Object.values(inspection.capabilities);
|
|
288
|
+
if (capabilityValues.some(({ state }) => state !== 'observed')) return { ...base, state: 'unknown', reason: 'capabilities' };
|
|
289
|
+
if (capabilityValues.some(({ value }) => value !== true)) return { ...base, state: 'chat-only', reason: 'template' };
|
|
290
|
+
}
|
|
291
|
+
return { ...base, state: 'compatible', reason: 'fits' };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* ⭐⭐⭐ LA STIMA PRIMA DELLO SCARICAMENTO — 03/9, richiesta dell'owner:
|
|
296
|
+
* «badge e pulsanti per misurare in tempo reale se quel modello e
|
|
297
|
+
* quantizzazione entrano e girano nel pc».
|
|
298
|
+
*
|
|
299
|
+
* ## Perché non è `fit()`
|
|
300
|
+
*
|
|
301
|
+
* `fit()` legge l'header GGUF dal disco: sa strati, teste e contesto
|
|
302
|
+
* addestrato, quindi calcola la cache del contesto al byte. Qui il file
|
|
303
|
+
* **non c'è ancora** — è una variante su Hugging Face — e l'unica cosa
|
|
304
|
+
* nota è quanto pesa. Fingere lo stesso verdetto sarebbe inventare.
|
|
305
|
+
*
|
|
306
|
+
* ## Cosa dichiara, e cosa NON dichiara
|
|
307
|
+
*
|
|
308
|
+
* ⛔ Questa è una **soglia inferiore**, non un totale: copre i PESI e
|
|
309
|
+
* basta. La cache del contesto si somma sopra e non è calcolabile senza
|
|
310
|
+
* l'header (serve `n_kv_heads` × `head_dim` × strati). Perciò:
|
|
311
|
+
* · se già i pesi non ci stanno, **non ci sta**, ed è un fatto;
|
|
312
|
+
* · se i pesi ci stanno, si è detto solo che i pesi ci stanno.
|
|
313
|
+
* Il campo `basis: 'weights-only'` viaggia con la risposta proprio perché
|
|
314
|
+
* chi la mostra non possa dimenticarsene.
|
|
315
|
+
*
|
|
316
|
+
* ⭐ Ricerca 03/9 (LM Studio, tech-insider/insiderllm): il concorrente
|
|
317
|
+
* etichetta ogni quantizzazione con la memoria stimata e una scala a
|
|
318
|
+
* badge (giallo = offload parziale, rosso = non entra), e ha
|
|
319
|
+
* `lms load --estimate-only`. La stima prima dello scaricamento è una
|
|
320
|
+
* capacità affermata, non un'invenzione nostra — ma nessuno dei due
|
|
321
|
+
* dichiara che è una soglia inferiore, e noi sì.
|
|
322
|
+
*
|
|
323
|
+
* ⛔ Stessa `measureMachine()` e stesso vocabolario di stati/motivi di
|
|
324
|
+
* `fit()`: due verdetti sulla stessa schermata scritti con parole diverse
|
|
325
|
+
* insegnano a non fidarsi di nessuno dei due.
|
|
326
|
+
*/
|
|
327
|
+
async function estimateFit({ bytes, contextTokens = null } = {}) {
|
|
328
|
+
if (!Number.isSafeInteger(bytes) || bytes <= 0) {
|
|
329
|
+
throw new LocalRuntimeProbeError('estimate request is invalid', 'FIT_INVALID');
|
|
330
|
+
}
|
|
331
|
+
if (contextTokens !== null && !positiveInteger(contextTokens)) {
|
|
332
|
+
throw new LocalRuntimeProbeError('estimate request is invalid', 'FIT_INVALID');
|
|
333
|
+
}
|
|
334
|
+
let machine;
|
|
335
|
+
try {
|
|
336
|
+
machine = await measureMachine();
|
|
337
|
+
} catch {
|
|
338
|
+
return { bytes, basis: 'weights-only', state: 'unknown', reason: 'measurement', storage: { requiredBytes: bytes, availableBytes: null }, memory: { requiredBytes: bytes, availableBytes: null }, observedAt: now().toISOString() };
|
|
339
|
+
}
|
|
340
|
+
const storageAvailable = machine?.storage?.allocatableBytes;
|
|
341
|
+
const memoryAvailable = machine?.memory?.freeBytes;
|
|
342
|
+
const base = {
|
|
343
|
+
bytes,
|
|
344
|
+
basis: 'weights-only',
|
|
345
|
+
contextTokens,
|
|
346
|
+
storage: { requiredBytes: bytes, availableBytes: Number.isSafeInteger(storageAvailable) ? storageAvailable : null },
|
|
347
|
+
memory: { requiredBytes: bytes, availableBytes: Number.isSafeInteger(memoryAvailable) ? memoryAvailable : null },
|
|
348
|
+
observedAt: now().toISOString(),
|
|
349
|
+
};
|
|
350
|
+
if (!Number.isSafeInteger(storageAvailable) || !Number.isSafeInteger(memoryAvailable)) return { ...base, state: 'unknown', reason: 'measurement' };
|
|
351
|
+
// ⛔ Il disco per primo, e con parole sue: lo spazio si libera, la memoria no.
|
|
352
|
+
if (bytes > storageAvailable) return { ...base, state: 'blocked', reason: 'storage' };
|
|
353
|
+
if (bytes > memoryAvailable) return { ...base, state: 'blocked', reason: 'memory' };
|
|
354
|
+
// Stessa soglia dichiarata di `fit()` lato UI: sopra il 90% del libero è «al limite».
|
|
355
|
+
if (bytes > memoryAvailable * 0.9) return { ...base, state: 'tight', reason: 'memory' };
|
|
356
|
+
return { ...base, state: 'compatible', reason: 'fits' };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function qualify({ modelId, consent, profile = 'agent', contextTokens = profile === 'agent' ? 65_536 : 4_096 } = {}) {
|
|
360
|
+
if (consent !== true) throw new LocalRuntimeProbeError('qualification requires explicit consent', 'PROBE_CONSENT_REQUIRED');
|
|
361
|
+
const fitResult = await fit(modelId, { profile, contextTokens });
|
|
362
|
+
if (fitResult.state !== 'compatible') throw new LocalRuntimeProbeError(`model fit is ${fitResult.state}`, 'MODEL_NOT_COMPATIBLE');
|
|
363
|
+
|
|
364
|
+
const startedAt = clockMs();
|
|
365
|
+
let firstTokenAt = null;
|
|
366
|
+
let completed = false;
|
|
367
|
+
for await (const event of runtime.generateStream({
|
|
368
|
+
runId: `qualification-${modelId}`,
|
|
369
|
+
turnId: 'qualification',
|
|
370
|
+
modelId,
|
|
371
|
+
messages: [{ role: 'user', content: 'Reply with OK.' }],
|
|
372
|
+
parseToolCalls: false,
|
|
373
|
+
})) {
|
|
374
|
+
if (firstTokenAt === null && (event.type === 'text' || event.type === 'reasoning')) firstTokenAt = clockMs();
|
|
375
|
+
if (event.type === 'error') throw new LocalRuntimeProbeError(event.message || 'qualification stream failed', 'PROBE_GENERATION_FAILED');
|
|
376
|
+
if (event.type === 'done') completed = true;
|
|
377
|
+
}
|
|
378
|
+
const endedAt = clockMs();
|
|
379
|
+
if (!completed || firstTokenAt === null) throw new LocalRuntimeProbeError('qualification produced no complete response', 'PROBE_GENERATION_INCOMPLETE');
|
|
380
|
+
|
|
381
|
+
let tokensPerSecond = unknown();
|
|
382
|
+
if (typeof runtime.metrics === 'function') {
|
|
383
|
+
try { tokensPerSecond = parseTokensPerSecond(await runtime.metrics()); } catch { /* metric remains unknown */ }
|
|
384
|
+
}
|
|
385
|
+
return {
|
|
386
|
+
modelId,
|
|
387
|
+
state: 'qualified',
|
|
388
|
+
backend: fitResult.inspection.backend,
|
|
389
|
+
build: fitResult.inspection.build,
|
|
390
|
+
contextTokens: fitResult.context.availableTokens,
|
|
391
|
+
performance: {
|
|
392
|
+
ttftMs: fact('observed', Math.max(0, firstTokenAt - startedAt)),
|
|
393
|
+
tokensPerSecond,
|
|
394
|
+
},
|
|
395
|
+
durationMs: Math.max(0, endedAt - startedAt),
|
|
396
|
+
measuredAt: now().toISOString(),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return Object.freeze({ inspectModel, measureBackend, fit, estimateFit, qualify });
|
|
401
|
+
}
|