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,339 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const LOCAL_MODEL_STATES = ['incomplete', 'ready', 'failed'];
|
|
6
|
+
|
|
7
|
+
export class LocalModelStoreError extends Error {
|
|
8
|
+
constructor(message, code = 'MODEL_STORE_FAILED') {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'LocalModelStoreError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const MANIFEST_KEYS = ['id', 'repo', 'revision', 'files', 'bytes', 'sha256', 'license', 'path', 'state', 'updatedAt'];
|
|
16
|
+
const FILE_KEYS = ['path', 'bytes', 'sha256'];
|
|
17
|
+
const SHA256 = /^[a-f0-9]{64}$/i;
|
|
18
|
+
const REVISION = /^[a-f0-9]{40,64}$/i;
|
|
19
|
+
const ID = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
20
|
+
|
|
21
|
+
function invalid(message) {
|
|
22
|
+
return new LocalModelStoreError(message, 'MODEL_INVALID');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function exactKeys(value, keys) {
|
|
26
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateRelativePath(value, label) {
|
|
30
|
+
if (typeof value !== 'string' || value.trim() === '' || isAbsolute(value)) throw invalid(`${label} must be relative`);
|
|
31
|
+
const segments = value.replaceAll('\\', '/').split('/');
|
|
32
|
+
if (segments.some((segment) => segment === '' || segment === '.' || segment === '..')) throw invalid(`${label} contains traversal`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function validateManifest(value, rootDir) {
|
|
36
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || !exactKeys(value, MANIFEST_KEYS)) throw invalid('manifest shape is invalid');
|
|
37
|
+
if (typeof value.id !== 'string' || !ID.test(value.id)) throw invalid('id is invalid');
|
|
38
|
+
if (typeof value.repo !== 'string' || value.repo.trim() === '') throw invalid('repo is required');
|
|
39
|
+
if (typeof value.revision !== 'string' || !REVISION.test(value.revision)) throw invalid('revision must be a full commit hash');
|
|
40
|
+
if (!Array.isArray(value.files) || value.files.length === 0) throw invalid('files are required');
|
|
41
|
+
let sum = 0;
|
|
42
|
+
for (const [index, file] of value.files.entries()) {
|
|
43
|
+
if (!file || typeof file !== 'object' || Array.isArray(file) || !exactKeys(file, FILE_KEYS)) throw invalid(`files[${index}] is invalid`);
|
|
44
|
+
validateRelativePath(file.path, `files[${index}].path`);
|
|
45
|
+
if (!Number.isSafeInteger(file.bytes) || file.bytes <= 0) throw invalid(`files[${index}].bytes is invalid`);
|
|
46
|
+
if (typeof file.sha256 !== 'string' || !SHA256.test(file.sha256)) throw invalid(`files[${index}].sha256 is invalid`);
|
|
47
|
+
sum += file.bytes;
|
|
48
|
+
}
|
|
49
|
+
if (!Number.isSafeInteger(value.bytes) || value.bytes <= 0 || value.bytes !== sum) throw invalid('bytes do not match file sizes');
|
|
50
|
+
if (typeof value.sha256 !== 'string' || !SHA256.test(value.sha256)) throw invalid('sha256 is invalid');
|
|
51
|
+
if (typeof value.license !== 'string' || value.license.trim() === '') throw invalid('license is required');
|
|
52
|
+
validateRelativePath(value.path, 'path');
|
|
53
|
+
const absoluteModelPath = resolve(rootDir, value.path);
|
|
54
|
+
const difference = relative(resolve(rootDir), absoluteModelPath);
|
|
55
|
+
if (difference === '..' || difference.startsWith(`..${'\\'}`) || isAbsolute(difference)) throw invalid('path escapes model root');
|
|
56
|
+
if (!LOCAL_MODEL_STATES.includes(value.state)) throw invalid('state is invalid');
|
|
57
|
+
if (typeof value.updatedAt !== 'string' || !Number.isFinite(Date.parse(value.updatedAt))) throw invalid('updatedAt is invalid');
|
|
58
|
+
return structuredClone(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* ⭐⭐⭐ BC-13 (11/09/2026) — quante letture in volo insieme quando l'elenco è FREDDO.
|
|
63
|
+
*
|
|
64
|
+
* MISURATO su questa macchina (scratchpad/misura-concorrenza.mjs, 10 giri per riga,
|
|
65
|
+
* mediane), leggendo manifest + eventuale `.name.json`:
|
|
66
|
+
*
|
|
67
|
+
* 200 manifest → 1: 119,02 ms · 8: 24,20 · 16: 22,72 · **32: 22,38** · 64: 22,78 · ∞: 25,04
|
|
68
|
+
* 1000 manifest → 1: 626,73 ms · 8: 134,52 · 16: 129,29 · **32: 126,65** · 64: 122,62 · ∞: 126,67
|
|
69
|
+
*
|
|
70
|
+
* ⇒ L'altopiano arriva già a 16: oltre non si guadagna più niente, e `Infinity` è
|
|
71
|
+
* perfino PEGGIO di 32 (25,04 contro 22,38 su 200) perché il disco non va più
|
|
72
|
+
* veloce e la coda interna di libuv si allunga soltanto.
|
|
73
|
+
* ⛔ E `Promise.all` senza limite su una cartella grande è il modo classico di
|
|
74
|
+
* arrivare a `EMFILE: too many open files` — il consiglio unanime è una coda a
|
|
75
|
+
* concorrenza limitata invece di alzare il tetto dei descrittori
|
|
76
|
+
* (oneuptime.com «How to Fix Error: EMFILE: too many open files in Node.js»,
|
|
77
|
+
* 22/01/2026, e latchkey.dev «Node.js EMFILE — Fix the File-Descriptor Limit»,
|
|
78
|
+
* letti l'11/09/2026). Qui i file sono due per modello: 32 in volo = 32
|
|
79
|
+
* descrittori, un tetto che nessuna libreria esterna deve garantire.
|
|
80
|
+
*/
|
|
81
|
+
const LETTURE_IN_PARALLELO = 32;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Una pozza di `limite` operai che si passano la lista: nessuna dipendenza
|
|
85
|
+
* esterna (`p-limit` farebbe esattamente questo), ordine del risultato
|
|
86
|
+
* conservato perché ogni operaio scrive nella SUA casella, non in coda.
|
|
87
|
+
* ⛔ Un `catch` qui non ci va: `list()` deve continuare a fallire come prima se
|
|
88
|
+
* un manifest è corrotto — la concorrenza cambia il COME si legge, mai il COSA
|
|
89
|
+
* si risponde.
|
|
90
|
+
*/
|
|
91
|
+
async function mappaConLimite(elementi, limite, fn) {
|
|
92
|
+
const esiti = new Array(elementi.length);
|
|
93
|
+
let prossimo = 0;
|
|
94
|
+
const operai = Array.from({ length: Math.min(limite, elementi.length) }, async () => {
|
|
95
|
+
while (prossimo < elementi.length) {
|
|
96
|
+
const indice = prossimo;
|
|
97
|
+
prossimo += 1;
|
|
98
|
+
esiti[indice] = await fn(elementi[indice]);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
await Promise.all(operai);
|
|
102
|
+
return esiti;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function createLocalModelStore({ rootDir, fsImpl = {}, now = () => new Date() } = {}) {
|
|
106
|
+
if (typeof rootDir !== 'string' || !isAbsolute(rootDir)) throw new LocalModelStoreError('rootDir must be absolute', 'MODEL_STORE_MISCONFIGURED');
|
|
107
|
+
const fs = { mkdir, readFile, readdir, rename, rm, stat, writeFile, ...fsImpl };
|
|
108
|
+
const manifestDir = join(rootDir, 'manifests');
|
|
109
|
+
const locks = new Set();
|
|
110
|
+
const manifestPath = (id) => join(manifestDir, `${id}.json`);
|
|
111
|
+
const namePath = (id) => join(manifestDir, `${id}.name.json`);
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* ⭐⭐⭐ BC-13 — «i modelli locali devono comparire ISTANTANEI», owner 11/09/2026.
|
|
115
|
+
*
|
|
116
|
+
* ## Da dove viene questa forma: il mobile, che ci ha lavorato oggi
|
|
117
|
+
*
|
|
118
|
+
* `AVM/mobile/src/lib/models/localCatalogueSignal.ts:1-89` dice la regola per
|
|
119
|
+
* esteso: «un elenco che non si aggiorna da solo è un elenco che mente finché
|
|
120
|
+
* qualcuno non lo interroga», e «il disco è l'unica fonte che non può essere in
|
|
121
|
+
* ritardo». ⇒ Là chi CAMBIA il disco lo annuncia (`transfer-finished`,
|
|
122
|
+
* `imported`, `deleted`) e chi mostra l'elenco rilegge — senza pulsante.
|
|
123
|
+
*
|
|
124
|
+
* Qui il desktop non ha bisogno di un bus: chi cambia il disco e chi lo legge
|
|
125
|
+
* sono lo STESSO oggetto. Quindi l'annuncio è un contatore interno — `revisione`
|
|
126
|
+
* — che ogni scrittura di questo store alza. È la stessa idea, con una riga
|
|
127
|
+
* invece di un modulo, perché il confine fra i due lati qui non esiste.
|
|
128
|
+
*
|
|
129
|
+
* ## Perché serve ANCHE il mtime della cartella, e non basta il contatore
|
|
130
|
+
*
|
|
131
|
+
* Il contatore vede solo ciò che passa da qui. I manifest possono cambiare da
|
|
132
|
+
* fuori (un'altra istanza, l'owner che cancella una cartella a mano). Il mtime
|
|
133
|
+
* della cartella copre quel caso.
|
|
134
|
+
*
|
|
135
|
+
* ⛔ MISURATO l'11/09/2026 su questo NTFS (scratchpad/misura-varianti.mjs), non
|
|
136
|
+
* dedotto dalla documentazione — Microsoft Learn «File Times» descrive i tempi
|
|
137
|
+
* del FILE, non quelli della cartella che lo contiene:
|
|
138
|
+
* · aggiunta di una voce → il mtime della cartella CAMBIA ✔
|
|
139
|
+
* · rename di una voce dentro → CAMBIA ✔
|
|
140
|
+
* · riscrittura IN LOCO → NON cambia ✘
|
|
141
|
+
* Il terzo caso non ci riguarda: `persist()` e `renameModel()` scrivono SEMPRE
|
|
142
|
+
* un temporaneo e poi `rename` (è la scrittura atomica che questo file già
|
|
143
|
+
* faceva), quindi ogni scrittura vera passa dal caso che si vede. E se un
|
|
144
|
+
* giorno qualcuno riscrivesse in loco da dentro, il contatore lo prende
|
|
145
|
+
* comunque: le due guardie coprono buchi diversi, per questo ci sono entrambe.
|
|
146
|
+
*
|
|
147
|
+
* ## Cosa costa la guardia, e cosa risparmia (misurato, mediane)
|
|
148
|
+
*
|
|
149
|
+
* `stat` della sola cartella .............. 0,03 ms
|
|
150
|
+
* `list()` in serie, come prima ........... 2 modelli 1,50 ms · 50: 32,04 · 200: 129,30
|
|
151
|
+
* `list()` in parallelo (32) .............. 2 modelli 1,10 ms · 50: 6,75 · 200: 22,38
|
|
152
|
+
* `structuredClone` dell'elenco in cache .. 50: 0,077 ms · 200: 0,297 · 1000: 1,678
|
|
153
|
+
* ⇒ a caldo su 200 modelli: 0,03 + 0,30 ≈ 0,33 ms contro 129,30 → ~390×.
|
|
154
|
+
*
|
|
155
|
+
* ⛔ Si restituisce un `structuredClone`, non l'array in cache: prima di oggi
|
|
156
|
+
* ogni `list()` consegnava oggetti freschi, e chi chiama ha il diritto di
|
|
157
|
+
* maneggiarli. Congelarli sarebbe costato meno (0,3 ms su 200 non sono
|
|
158
|
+
* niente) ma avrebbe cambiato un contratto in silenzio, che è esattamente il
|
|
159
|
+
* genere di cura che poi rompe qualcun altro.
|
|
160
|
+
*/
|
|
161
|
+
let revisione = 0;
|
|
162
|
+
/** @type {{revisione: number, firma: number, modelli: Array}|null} */
|
|
163
|
+
let elencoInCache = null;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* La firma della cartella, o `null` se non si può leggere. `null` NON è una
|
|
167
|
+
* firma: non si confronta mai con niente, quindi una cartella che non esiste
|
|
168
|
+
* (o un `fsImpl` di prova senza `stat`) fa semplicemente decadere la cache
|
|
169
|
+
* invece di congelare una risposta sbagliata.
|
|
170
|
+
*/
|
|
171
|
+
async function firmaDellaCartella() {
|
|
172
|
+
try {
|
|
173
|
+
const informazioni = await fs.stat(manifestDir);
|
|
174
|
+
const valore = Number(informazioni?.mtimeMs);
|
|
175
|
+
return Number.isFinite(valore) ? valore : null;
|
|
176
|
+
} catch {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function persist(manifest) {
|
|
182
|
+
await fs.mkdir(manifestDir, { recursive: true });
|
|
183
|
+
const target = manifestPath(manifest.id);
|
|
184
|
+
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
185
|
+
try {
|
|
186
|
+
await fs.writeFile(temporary, `${JSON.stringify(manifest, null, 2)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
187
|
+
await fs.rename(temporary, target);
|
|
188
|
+
revisione += 1; // BC-13: l'annuncio del mobile, ridotto a un contatore perché qui chi scrive e chi legge sono lo stesso oggetto
|
|
189
|
+
} catch (error) {
|
|
190
|
+
await fs.rm(temporary, { force: true }).catch(() => {});
|
|
191
|
+
if (error?.code === 'EEXIST') throw new LocalModelStoreError(`model ${manifest.id} already exists`, 'MODEL_EXISTS');
|
|
192
|
+
throw new LocalModelStoreError(`cannot persist model ${manifest.id}: ${error.message}`, 'MODEL_WRITE_FAILED');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function inspect(id) {
|
|
197
|
+
if (typeof id !== 'string' || !ID.test(id)) throw invalid('id is invalid');
|
|
198
|
+
let contents;
|
|
199
|
+
try {
|
|
200
|
+
contents = await fs.readFile(manifestPath(id), 'utf8');
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error?.code === 'ENOENT') return null;
|
|
203
|
+
throw new LocalModelStoreError(`cannot read model ${id}: ${error.message}`, 'MODEL_READ_FAILED');
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const manifest = validateManifest(JSON.parse(contents), rootDir);
|
|
207
|
+
try {
|
|
208
|
+
const name = await fs.readFile(namePath(id), 'utf8');
|
|
209
|
+
if (typeof name === 'string' && name.trim()) manifest.name = name.trim();
|
|
210
|
+
} catch (error) { if (error?.code !== 'ENOENT') throw new LocalModelStoreError(`cannot read model ${id} name: ${error.message}`, 'MODEL_READ_FAILED'); }
|
|
211
|
+
return manifest;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
if (error instanceof LocalModelStoreError) throw new LocalModelStoreError(`manifest ${id} is corrupt: ${error.message}`, 'MODEL_CORRUPT');
|
|
214
|
+
throw new LocalModelStoreError(`manifest ${id} is not JSON`, 'MODEL_CORRUPT');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function register(value) {
|
|
219
|
+
const manifest = validateManifest({ ...value, updatedAt: value?.updatedAt ?? now().toISOString() }, rootDir);
|
|
220
|
+
if (await inspect(manifest.id)) throw new LocalModelStoreError(`model ${manifest.id} already exists`, 'MODEL_EXISTS');
|
|
221
|
+
await persist(manifest);
|
|
222
|
+
return manifest;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function setState(id, state) {
|
|
226
|
+
if (!LOCAL_MODEL_STATES.includes(state)) throw invalid('state is invalid');
|
|
227
|
+
const current = await inspect(id);
|
|
228
|
+
if (!current) throw new LocalModelStoreError(`model ${id} not found`, 'MODEL_NOT_FOUND');
|
|
229
|
+
const manifest = validateManifest({ ...current, state, updatedAt: now().toISOString() }, rootDir);
|
|
230
|
+
await persist(manifest);
|
|
231
|
+
return manifest;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* BC-13 — `{ fresco: true }` salta la cache e rilegge il disco.
|
|
236
|
+
*
|
|
237
|
+
* ⛔ Non è un ripiego «in caso non funzioni»: è la porta per chi ha una ragione
|
|
238
|
+
* di sospettare il disco che questo store non può conoscere (una verifica
|
|
239
|
+
* dell'owner, una diagnosi). Chi non ne ha una, non la usa — altrimenti la
|
|
240
|
+
* cura vale zero.
|
|
241
|
+
*/
|
|
242
|
+
async function list({ fresco = false } = {}) {
|
|
243
|
+
const firma = await firmaDellaCartella();
|
|
244
|
+
if (!fresco && elencoInCache && firma !== null
|
|
245
|
+
&& elencoInCache.firma === firma && elencoInCache.revisione === revisione) {
|
|
246
|
+
return structuredClone(elencoInCache.modelli);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let entries;
|
|
250
|
+
try { entries = await fs.readdir(manifestDir, { withFileTypes: true }); }
|
|
251
|
+
catch (error) { if (error?.code === 'ENOENT') return []; throw new LocalModelStoreError(`cannot list models: ${error.message}`, 'MODEL_READ_FAILED'); }
|
|
252
|
+
const identificativi = [];
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
const name = typeof entry === 'string' ? entry : entry.name;
|
|
255
|
+
if (!name.endsWith('.json') || name.endsWith('.name.json')) continue;
|
|
256
|
+
identificativi.push(name.slice(0, -5));
|
|
257
|
+
}
|
|
258
|
+
/*
|
|
259
|
+
* La revisione si legge PRIMA delle letture: se una scrittura arriva mentre
|
|
260
|
+
* stiamo leggendo, il confronto qui sotto fallisce e l'elenco non finisce in
|
|
261
|
+
* cache. Meglio una cache che non si popola che una cache che congela una
|
|
262
|
+
* fotografia scattata a metà di una scrittura.
|
|
263
|
+
*/
|
|
264
|
+
const revisionePrimaDiLeggere = revisione;
|
|
265
|
+
const letti = await mappaConLimite(identificativi, LETTURE_IN_PARALLELO, (id) => inspect(id));
|
|
266
|
+
const models = letti.filter((model) => model).sort((a, b) => a.id.localeCompare(b.id));
|
|
267
|
+
|
|
268
|
+
const firmaDopo = await firmaDellaCartella();
|
|
269
|
+
if (firmaDopo !== null && firmaDopo === firma && revisione === revisionePrimaDiLeggere) {
|
|
270
|
+
elencoInCache = { revisione, firma: firmaDopo, modelli: structuredClone(models) };
|
|
271
|
+
} else {
|
|
272
|
+
elencoInCache = null;
|
|
273
|
+
}
|
|
274
|
+
return models;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function lock(id) {
|
|
278
|
+
if (!await inspect(id)) throw new LocalModelStoreError(`model ${id} not found`, 'MODEL_NOT_FOUND');
|
|
279
|
+
if (locks.has(id)) throw new LocalModelStoreError(`model ${id} is already locked`, 'MODEL_LOCKED');
|
|
280
|
+
locks.add(id);
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function unlock(id) {
|
|
285
|
+
return locks.delete(id);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 06/09 (richiesta INST-DELETE-FILE): «Elimina» toglieva solo i manifest e
|
|
290
|
+
* lasciava i pesi sul disco. Ora cancella anche la cartella del modello, ma SOLO se sta dentro
|
|
291
|
+
* `rootDir` (il manifest lo garantisce già; qui si riverifica prima di un `rm` ricorsivo:
|
|
292
|
+
* risolvi, poi `relative()` che non inizi con `..` e non sia assoluto — openreplay «Preventing
|
|
293
|
+
* Path Traversal in Node.js», googleapis/nodejs-storage #2654, letti il 06/09/2026). Mai la
|
|
294
|
+
* radice stessa, mai un percorso fuori: in quei casi restano i manifest via e i pesi intatti.
|
|
295
|
+
*/
|
|
296
|
+
async function remove(id) {
|
|
297
|
+
if (locks.has(id)) throw new LocalModelStoreError(`model ${id} is locked`, 'MODEL_LOCKED');
|
|
298
|
+
const current = await inspect(id).catch(() => null);
|
|
299
|
+
if (current?.path) {
|
|
300
|
+
// `path` è la CARTELLA del modello (download HF: `org-model/`) o il FILE principale
|
|
301
|
+
// (import locale: `<id>/<peso>.gguf`): si guarda sul disco, non si indovina.
|
|
302
|
+
try {
|
|
303
|
+
const radice = resolve(rootDir);
|
|
304
|
+
const dentro = (assoluto) => { const d = relative(radice, assoluto); return d !== '' && d !== '..' && !d.startsWith(`..${sep}`) && !d.startsWith('../') && !isAbsolute(d); };
|
|
305
|
+
const bersaglio = resolve(radice, current.path);
|
|
306
|
+
const eCartella = await fs.stat(bersaglio).then((s) => s.isDirectory()).catch(() => false);
|
|
307
|
+
const cartella = eCartella ? bersaglio : dirname(bersaglio);
|
|
308
|
+
if (!eCartella) for (const b of [bersaglio, ...(current.files || []).map((f) => resolve(cartella, f.path))]) if (dentro(b)) await fs.rm(b, { force: true });
|
|
309
|
+
// la cartella del modello è sua per costruzione: via anche quella, mai la radice
|
|
310
|
+
if (cartella !== radice && dentro(cartella)) await fs.rm(cartella, { recursive: true, force: true });
|
|
311
|
+
} catch {
|
|
312
|
+
// i pesi non si sono lasciati cancellare: i manifest vanno via lo stesso (comportamento di prima)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
await fs.rm(manifestPath(id), { force: true });
|
|
316
|
+
await fs.rm(namePath(id), { force: true });
|
|
317
|
+
revisione += 1; // BC-13: un modello in meno è una notizia per l'elenco, esattamente come uno in più
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function renameModel(id, name) {
|
|
322
|
+
if (typeof name !== 'string' || name.trim().length === 0 || name.trim().length > 160) throw invalid('name is invalid');
|
|
323
|
+
const current = await inspect(id);
|
|
324
|
+
if (!current) throw new LocalModelStoreError(`model ${id} not found`, 'MODEL_NOT_FOUND');
|
|
325
|
+
const temporary = `${namePath(id)}.tmp-${process.pid}-${randomUUID()}`;
|
|
326
|
+
try {
|
|
327
|
+
await fs.mkdir(manifestDir, { recursive: true });
|
|
328
|
+
await fs.writeFile(temporary, `${name.trim()}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
329
|
+
await fs.rename(temporary, namePath(id));
|
|
330
|
+
revisione += 1; // BC-13: `list()` porta anche il `name`, quindi un nome nuovo è un elenco nuovo
|
|
331
|
+
} catch (error) {
|
|
332
|
+
await fs.rm(temporary, { force: true }).catch(() => {});
|
|
333
|
+
throw new LocalModelStoreError(`cannot rename model ${id}: ${error.message}`, 'MODEL_WRITE_FAILED');
|
|
334
|
+
}
|
|
335
|
+
return { ...current, name: name.trim() };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return Object.freeze({ inspect, list, register, setState, lock, unlock, remove, rename: renameModel });
|
|
339
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const LOCAL_RUNTIME_STATES = ['unavailable', 'detected', 'loading', 'ready', 'stopping', 'failed'];
|
|
2
|
+
const EVENT_TYPES = ['text', 'reasoning', 'tool_call', 'status', 'error', 'done'];
|
|
3
|
+
|
|
4
|
+
function invalid(message) {
|
|
5
|
+
const error = new Error(message);
|
|
6
|
+
error.code = 'LOCAL_RUNTIME_INVALID';
|
|
7
|
+
return error;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function exactKeys(value, keys) {
|
|
11
|
+
return Object.keys(value).length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function nonEmptyString(value, name) {
|
|
15
|
+
if (typeof value !== 'string' || value.trim() === '') throw invalid(`${name} is required`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function validDate(value) {
|
|
19
|
+
return typeof value === 'string' && Number.isFinite(Date.parse(value));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function safeModelId(value) {
|
|
23
|
+
return typeof value === 'string' && value.trim() !== '' && !/^(?:[a-z]:[\\/]|[\\/]{2}|[\\/])/i.test(value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseLocalRuntimeSnapshot(value) {
|
|
27
|
+
const keys = ['runtimeId', 'version', 'backend', 'model', 'contextTokens', 'capabilities', 'state', 'observedAt'];
|
|
28
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || !exactKeys(value, keys)) throw invalid('runtime snapshot shape is invalid');
|
|
29
|
+
nonEmptyString(value.runtimeId, 'runtimeId');
|
|
30
|
+
nonEmptyString(value.version, 'version');
|
|
31
|
+
if (!value.backend || typeof value.backend !== 'object' || Array.isArray(value.backend) || !exactKeys(value.backend, ['id', 'device'])) throw invalid('backend is invalid');
|
|
32
|
+
nonEmptyString(value.backend.id, 'backend.id');
|
|
33
|
+
if (value.backend.device !== null) nonEmptyString(value.backend.device, 'backend.device');
|
|
34
|
+
if (value.model !== null) {
|
|
35
|
+
if (typeof value.model !== 'object' || Array.isArray(value.model) || !exactKeys(value.model, ['id', 'label']) || !safeModelId(value.model.id)) throw invalid('model is invalid');
|
|
36
|
+
if (value.model.label !== null) nonEmptyString(value.model.label, 'model.label');
|
|
37
|
+
}
|
|
38
|
+
if (value.contextTokens !== null && (!Number.isInteger(value.contextTokens) || value.contextTokens <= 0)) throw invalid('contextTokens is invalid');
|
|
39
|
+
const capabilityKeys = ['streaming', 'reasoning', 'toolCalls', 'multimodal'];
|
|
40
|
+
if (!value.capabilities || typeof value.capabilities !== 'object' || Array.isArray(value.capabilities) || !exactKeys(value.capabilities, capabilityKeys) || capabilityKeys.some((key) => typeof value.capabilities[key] !== 'boolean')) throw invalid('capabilities are invalid');
|
|
41
|
+
if (!LOCAL_RUNTIME_STATES.includes(value.state) || !validDate(value.observedAt)) throw invalid('state or observedAt is invalid');
|
|
42
|
+
if (value.state === 'ready' && (!value.model || value.contextTokens === null)) throw invalid('ready runtime must have a model and context');
|
|
43
|
+
return structuredClone(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseLocalRuntimeEvent(value) {
|
|
47
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.type !== 'string' || !EVENT_TYPES.includes(value.type)) throw invalid('runtime event type is invalid');
|
|
48
|
+
if (!Number.isInteger(value.seq) || value.seq < 0) throw invalid('event sequence is invalid');
|
|
49
|
+
const common = ['type', 'seq'];
|
|
50
|
+
if (value.type === 'text' || value.type === 'reasoning') {
|
|
51
|
+
if (!exactKeys(value, [...common, 'value']) || typeof value.value !== 'string') throw invalid(`${value.type} event is invalid`);
|
|
52
|
+
} else if (value.type === 'tool_call') {
|
|
53
|
+
if (!exactKeys(value, [...common, 'id', 'name', 'arguments'])) throw invalid('tool_call event shape is invalid');
|
|
54
|
+
nonEmptyString(value.id, 'tool_call.id');
|
|
55
|
+
nonEmptyString(value.name, 'tool_call.name');
|
|
56
|
+
if (typeof value.arguments !== 'string') throw invalid('tool_call.arguments is invalid');
|
|
57
|
+
try { JSON.parse(value.arguments); } catch { throw invalid('tool_call.arguments must be JSON'); }
|
|
58
|
+
} else if (value.type === 'status') {
|
|
59
|
+
if (!exactKeys(value, [...common, 'state']) || !LOCAL_RUNTIME_STATES.includes(value.state)) throw invalid('status event is invalid');
|
|
60
|
+
} else if (value.type === 'error') {
|
|
61
|
+
if (!exactKeys(value, [...common, 'code', 'message', 'retryable']) || typeof value.code !== 'string' || typeof value.message !== 'string' || typeof value.retryable !== 'boolean') throw invalid('error event is invalid');
|
|
62
|
+
} else if (!exactKeys(value, common)) {
|
|
63
|
+
throw invalid('done event is invalid');
|
|
64
|
+
}
|
|
65
|
+
return value.type;
|
|
66
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parseLocalRuntimeEvent } from './local-runtime-contract.mjs';
|
|
2
|
+
|
|
3
|
+
const ENVELOPE_KEYS = ['runId', 'turnId', 'runtimeId', 'seq', 'at', 'type'];
|
|
4
|
+
|
|
5
|
+
function invalid(message) {
|
|
6
|
+
const error = new Error(message);
|
|
7
|
+
error.code = 'LOCAL_RUNTIME_INVALID';
|
|
8
|
+
return error;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function nonEmptyString(value) {
|
|
12
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function parseRuntimeEventEnvelope(value) {
|
|
16
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw invalid('event envelope shape is invalid');
|
|
17
|
+
if (!ENVELOPE_KEYS.every((key) => Object.hasOwn(value, key)) || Object.keys(value).some((key) => !ENVELOPE_KEYS.includes(key) && !['value', 'id', 'name', 'arguments', 'state', 'code', 'message', 'retryable'].includes(key))) throw invalid('event envelope fields are invalid');
|
|
18
|
+
if (!nonEmptyString(value.runId) || !nonEmptyString(value.turnId) || !nonEmptyString(value.runtimeId) || !nonEmptyString(value.at)) throw invalid('event envelope identity is invalid');
|
|
19
|
+
if (!Number.isInteger(value.seq) || value.seq < 0) throw invalid('event envelope sequence is invalid');
|
|
20
|
+
const payload = { ...value };
|
|
21
|
+
delete payload.runId; delete payload.turnId; delete payload.runtimeId; delete payload.at;
|
|
22
|
+
parseLocalRuntimeEvent(payload);
|
|
23
|
+
if (!Number.isFinite(Date.parse(value.at))) throw invalid('event envelope timestamp is invalid');
|
|
24
|
+
return structuredClone(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function createRuntimeEventLedger() {
|
|
28
|
+
const events = new Map();
|
|
29
|
+
return {
|
|
30
|
+
push(value) {
|
|
31
|
+
const event = parseRuntimeEventEnvelope(value);
|
|
32
|
+
const current = events.get(event.seq);
|
|
33
|
+
if (current) {
|
|
34
|
+
if (JSON.stringify(current) !== JSON.stringify(event)) throw invalid('event sequence conflict');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
events.set(event.seq, event);
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
40
|
+
replay() {
|
|
41
|
+
return [...events.values()].sort((a, b) => a.seq - b.seq).map((event) => structuredClone(event));
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|