talos-code 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/LICENSE +661 -0
- package/README.md +116 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +100 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +244 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +46 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +92 -0
- package/dist/i18n/en/errors.js +267 -0
- package/dist/i18n/en/firstrun.js +101 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +774 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +331 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/openrouter-login.js +172 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-archive.js +69 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +265 -0
- package/dist/runtime/talos-composition.js +1011 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/agent-view.js +51 -0
- package/dist/tui/app.js +4098 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +560 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +38 -0
- package/dist/tui/components/terminal-shell.js +214 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/descendant-approvals.js +104 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +148 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +56 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +43 -0
- package/dist/tui/overlays/approval-dialog.js +642 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +77 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +171 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +289 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +791 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/setup-wizard.js +46 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +82 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +27 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +64 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-ws.mjs — il ponte WebSocket fra il registro di PTY vere
|
|
3
|
+
* (`pty-terminal.mjs`) e il browser. Ledger: `.claude/LEDGER-TERMINALE-REALE.md`.
|
|
4
|
+
*
|
|
5
|
+
* ⛔⛔ Nessuna porta nuova: l'upgrade avviene sullo STESSO `http.Server`
|
|
6
|
+
* già bindato solo su loopback (`config.host`, validato in
|
|
7
|
+
* `config.mjs`) — la WS eredita lo stesso perimetro di rete di ogni
|
|
8
|
+
* altra rotta di questo backend, zero superficie aggiunta.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ Validazione Origin ESPLICITA prima di completare l'upgrade — un
|
|
11
|
+
* browser NON applica la same-origin policy alle WebSocket come la
|
|
12
|
+
* applica a `fetch`/XHR (rischio "cross-site WebSocket hijacking",
|
|
13
|
+
* guardia raccolta in ricerca prima di scrivere). Qui la shell aperta
|
|
14
|
+
* "non ha limiti" (owner, 28/8): il confine giusto da tenere stretto è
|
|
15
|
+
* il TRASPORTO, non la shell stessa.
|
|
16
|
+
*/
|
|
17
|
+
import { WebSocketServer } from 'ws';
|
|
18
|
+
|
|
19
|
+
import { codificaFrame, decodificaFrame, TIPO_FRAME_CONTROLLO, TIPO_FRAME_DATI } from './pty-terminal.mjs';
|
|
20
|
+
|
|
21
|
+
const PERCORSO_WS = '/api/v1/terminal/ws';
|
|
22
|
+
|
|
23
|
+
function leggiCookieGrezzo(grezzo, nome) {
|
|
24
|
+
if (typeof grezzo !== 'string' || grezzo === '') return null;
|
|
25
|
+
for (const parte of grezzo.split(';')) {
|
|
26
|
+
const i = parte.indexOf('=');
|
|
27
|
+
if (i !== -1 && parte.slice(0, i).trim() === nome) return parte.slice(i + 1).trim();
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* ⭐⭐⭐ W1-01 (05/9) — `?id=` non è più il `sessionId`: è il **`terminalId`**,
|
|
34
|
+
* e una sessione può averne più d'uno.
|
|
35
|
+
*
|
|
36
|
+
* ⛔⛔⛔ Il parametro `risolviCartella` (che tornava SEMPRE una cartella, al
|
|
37
|
+
* peggio la prima configurata) è stato sostituito da `risolviScheda`, che ha
|
|
38
|
+
* il diritto di dire **no**: `null` ⇒ upgrade rifiutato, e nessuna PTY nasce.
|
|
39
|
+
* Prima di oggi un id sconosciuto non veniva respinto — cadeva sul primo
|
|
40
|
+
* progetto — quindi con un `terminalId` libero qualunque stringa avrebbe
|
|
41
|
+
* aperto una shell. È la forma esatta di **CVE-2026-59224** (Open WebUI,
|
|
42
|
+
* 2026): un id scelto dal client che decide a quale PTY ti attacchi. E il
|
|
43
|
+
* terminale è l'endpoint dove un buco non è un buco: **CVE-2026-39987**
|
|
44
|
+
* (Marimo, 2026) era `/terminal/ws`, l'unica WebSocket senza controllo
|
|
45
|
+
* d'autenticazione, cioè RCE pre-auth. Entrambe lette il 05/09/2026, fonti nel
|
|
46
|
+
* ledger `.claude/LEDGER-W1-01-SCHEDE-TERMINALE-2026-09-05.md`.
|
|
47
|
+
*/
|
|
48
|
+
export function creaGestoreTerminaleWs({ registro, originiConsentite, risolviScheda, token = null }, deps = {}) {
|
|
49
|
+
const WSS = deps.WebSocketServer ?? WebSocketServer;
|
|
50
|
+
const wss = new WSS({ noServer: true });
|
|
51
|
+
|
|
52
|
+
function gestisciUpgrade(req, socket, head) {
|
|
53
|
+
let url;
|
|
54
|
+
try {
|
|
55
|
+
url = new URL(req.url, 'http://localhost');
|
|
56
|
+
} catch {
|
|
57
|
+
socket.destroy();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (url.pathname !== PERCORSO_WS) {
|
|
61
|
+
socket.destroy();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const origin = req.headers.origin;
|
|
65
|
+
if (origin && originiConsentite && !originiConsentite.has(origin)) {
|
|
66
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
67
|
+
socket.destroy();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// ⭐ 04/9, W1-10 — stesso cancello a token di /api/*: senza il cookie talos_token l'upgrade è 401, la PTY non nasce nemmeno.
|
|
71
|
+
if (token && leggiCookieGrezzo(req.headers.cookie, 'talos_token') !== token) {
|
|
72
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
73
|
+
socket.destroy();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const id = url.searchParams.get('id');
|
|
77
|
+
if (typeof id !== 'string' || id.length === 0 || id.length > 200) {
|
|
78
|
+
socket.destroy();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
/*
|
|
82
|
+
* ⛔⛔⛔ 05/9, W1-01 — IL CANCELLO. Si chiede al registro delle schede
|
|
83
|
+
* PRIMA di completare l'upgrade: un `terminalId` che il server non ha
|
|
84
|
+
* creato (e che non è un `sessionId` vivo) riceve 403 e il socket muore
|
|
85
|
+
* QUI. ⛔ `registro.apri` non viene nemmeno sfiorato: la prova che conta
|
|
86
|
+
* non è lo status code, è che la PTY non nasca — ed è così che i test la
|
|
87
|
+
* misurano (contano gli spawn, non le risposte).
|
|
88
|
+
*/
|
|
89
|
+
const scheda = risolviScheda(id);
|
|
90
|
+
if (!scheda) {
|
|
91
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
92
|
+
socket.destroy();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
96
|
+
collega(ws, scheda);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function collega(ws, scheda) {
|
|
101
|
+
/* ⛔ L'id e la cartella vengono dalla SCHEDA del registro, mai dalla query: il client nomina, il server decide. */
|
|
102
|
+
const id = scheda.terminalId;
|
|
103
|
+
/*
|
|
104
|
+
* ⭐⭐⭐ Il segnale «ripreso», PRIMA di qualunque byte.
|
|
105
|
+
*
|
|
106
|
+
* ⛔ Senza, il client non poteva sapere se la sua shell fosse sopravvissuta:
|
|
107
|
+
* il ponte rigioca il backlog sia quando riaggancia una PTY viva sia
|
|
108
|
+
* quando il reaper l'ha chiusa e ne nasce una nuova (backlog vuoto — che è
|
|
109
|
+
* anche l'aspetto di una shell viva che non ha ancora stampato niente).
|
|
110
|
+
* L'interfaccia era costretta a dire «riconnesso, non sappiamo se la shell
|
|
111
|
+
* è ancora quella». Lo stato dell'arte separa l'identità della CONNESSIONE
|
|
112
|
+
* da quella della SESSIONE e fa dichiarare al server se ha ripreso davvero
|
|
113
|
+
* (un pattern noto come "connection state recovery"; ricerca del 05/09/2026,
|
|
114
|
+
* websocket.org/guides/reconnection/).
|
|
115
|
+
*
|
|
116
|
+
* ⛔ Va PRIMA del backlog: chi legge deve sapere di che shell sono i byte
|
|
117
|
+
* che sta per ricevere, non scoprirlo dopo averli scritti a schermo.
|
|
118
|
+
*
|
|
119
|
+
* ⛔ Sicuro per il monolite congelato (`public/app.js`), verificato alla
|
|
120
|
+
* fonte: alla riga 7702 tratta solo `evento === 'uscita'` e ignora in
|
|
121
|
+
* silenzio ogni altro evento di controllo — nessun `else`, nessun crash.
|
|
122
|
+
*/
|
|
123
|
+
const { voce, ripresa } = registro.apriDichiarando({ id, cartella: scheda.cartella });
|
|
124
|
+
/* 06/9 B1 — la scheda prende il nome della shell che il server ha scelto DAVVERO (`enforcement` di `sceltaShell`), non uno indovinato dal client. */
|
|
125
|
+
ws.send(codificaFrame(TIPO_FRAME_CONTROLLO, JSON.stringify({ evento: 'agganciato', ripreso: ripresa, shell: voce.enforcement ?? null, comando: voce.comando ?? null })));
|
|
126
|
+
|
|
127
|
+
// ⭐ Riconnessione (F5, o WS caduta): replay del backlog PRIMA di tornare live — stesso principio del Last-Event-ID già in uso per SSE, qui su una PTY invece che su un run agente.
|
|
128
|
+
for (const pezzo of voce.backlog) {
|
|
129
|
+
ws.send(codificaFrame(TIPO_FRAME_DATI, pezzo));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const ascolta = (evento) => {
|
|
133
|
+
if (ws.readyState !== ws.OPEN) return;
|
|
134
|
+
if (evento.tipo === 'dati') {
|
|
135
|
+
ws.send(codificaFrame(TIPO_FRAME_DATI, evento.dati));
|
|
136
|
+
} else if (evento.tipo === 'uscita') {
|
|
137
|
+
ws.send(codificaFrame(TIPO_FRAME_CONTROLLO, JSON.stringify({ evento: 'uscita', codice: evento.exitCode ?? null })));
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
voce.ascoltatori.add(ascolta);
|
|
141
|
+
|
|
142
|
+
ws.on('message', (dati) => {
|
|
143
|
+
const frame = decodificaFrame(dati);
|
|
144
|
+
if (!frame) return; // AL CONTRARIO: un frame vuoto/di tipo ignoto si scarta, mai un crash della connessione
|
|
145
|
+
if (frame.tipo === TIPO_FRAME_DATI) {
|
|
146
|
+
registro.scrivi(id, frame.payload.toString('utf8'));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
let comando;
|
|
150
|
+
try {
|
|
151
|
+
comando = JSON.parse(frame.payload.toString('utf8'));
|
|
152
|
+
} catch {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (comando?.tipo === 'resize' && Number.isInteger(comando.cols) && Number.isInteger(comando.rows)) {
|
|
156
|
+
registro.ridimensiona(id, comando.cols, comando.rows);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
ws.on('close', () => {
|
|
161
|
+
voce.ascoltatori.delete(ascolta);
|
|
162
|
+
registro.segnaDisconnesso(id);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ⛔ Un errore di trasporto non deve propagarsi come un'eccezione non gestita del processo server.
|
|
166
|
+
ws.on('error', () => { /* 'close' segue comunque, la pulizia avviene lì */ });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { gestisciUpgrade };
|
|
170
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tool-forge-store.mjs — FASE N, nono e ultimo sistema (30/8): Tool
|
|
3
|
+
* Forge, "fetta onesta" (owner, AskUserQuestion: "Fetta onesta
|
|
4
|
+
* (consigliato)"). Letto alla fonte PRIMA di disegnare: `mobile/src/
|
|
5
|
+
* lib/tools/dynamic/forgeRegistryRepository.ts` (269 righe, SQLite
|
|
6
|
+
* device-wide con versioning) — questo store è la traduzione onesta
|
|
7
|
+
* su file, come ogni altro store di questa fase: un file per tool
|
|
8
|
+
* installato, mai un database.
|
|
9
|
+
*
|
|
10
|
+
* ⭐⭐⭐ Tool Forge è GLOBALE, come Notes/Tasks/Memory — non PER-PROGETTO
|
|
11
|
+
* come Libreria/Deep Research. Verificato non presunto: le 8 capability
|
|
12
|
+
* che un tool forgiato può chiamare (tasks, notes, memory — vedi
|
|
13
|
+
* CAPACITA_FORGE nel kernel) operano TUTTE su store GLOBALI
|
|
14
|
+
* (.tasks-store, .notes-store, .memory-store) — un tool forgiato creato
|
|
15
|
+
* mentre si lavora sul progetto A deve restare disponibile e utile
|
|
16
|
+
* anche lavorando sul progetto B, esattamente come una nota o
|
|
17
|
+
* un'attività. Storage: `.tool-forge-store/<id>.json`, accanto a
|
|
18
|
+
* `.notes-store/`/`.tasks-store/`/`.memory-store/`.
|
|
19
|
+
*
|
|
20
|
+
* ⛔⛔⛔ `cartella` qui è SEMPRE la cartella FINALE, mai un genitore da
|
|
21
|
+
* suffissare — stesso contratto ESATTO di notes-store.mjs/
|
|
22
|
+
* tasks-store.mjs/memory-store.mjs (verificato leggendo notes-store.mjs
|
|
23
|
+
* riga per riga, non presunto: `elencaNote` fa `join(cartella, nome)`,
|
|
24
|
+
* MAI `join(cartella, CARTELLA_NOTE, nome)`). Il default REALE vive in
|
|
25
|
+
* session-registry.mjs (`cartellaForge = .../.tool-forge-store/`,
|
|
26
|
+
* GIÀ col suffisso) — un secondo `join(cartella, CARTELLA_FORGE)` qui
|
|
27
|
+
* dentro avrebbe prodotto un doppio annidamento
|
|
28
|
+
* (`.tool-forge-store/.tool-forge-store/`).
|
|
29
|
+
*
|
|
30
|
+
* ⛔⛔⛔ Bug reale trovato E corretto dal vivo (30/8), non da lettura: la
|
|
31
|
+
* prima versione di questo file FACEVA quel doppio join — un
|
|
32
|
+
* `tool_create` chiamato da un modello vero tornava "Created..." (il
|
|
33
|
+
* messaggio di successo, corretto) ma il file non appariva MAI in
|
|
34
|
+
* `.tool-forge-store/` (la cartella vera, controllata sul disco): la
|
|
35
|
+
* scrittura andava un livello più in profondità
|
|
36
|
+
* (`.tool-forge-store/.tool-forge-store/<id>.json`), mai controllata da
|
|
37
|
+
* nessun test perché ogni test di QUESTO file passava una `cartella`
|
|
38
|
+
* generica ("/tmp/x") che non rivelava la duplicazione — solo
|
|
39
|
+
* confrontando il contratto con notes-store.mjs (il precedente diretto)
|
|
40
|
+
* il difetto è emerso.
|
|
41
|
+
*
|
|
42
|
+
* ⛔⛔⛔ Ogni tool installato nasce SEMPRE `abilitato:false` — porto
|
|
43
|
+
* diretto del vincolo mobile ("installed but stays DISABLED until the
|
|
44
|
+
* user turns it on"). Abilitare/disabilitare NON è mai un tool del
|
|
45
|
+
* modello (verificato in toolControlCatalog.ts: tool_create è l'UNICO
|
|
46
|
+
* tool_* chat-facing su ENTRAMBE le piattaforme) — solo un'azione
|
|
47
|
+
* owner-facing dal pannello Capability hub, `abilitaToolForgiato` sotto.
|
|
48
|
+
*
|
|
49
|
+
* Stile DI: stesso pattern di notes-store.mjs — funzioni async con
|
|
50
|
+
* `deps` opzionali per i test, mai un vero filesystem mockato altrove.
|
|
51
|
+
*/
|
|
52
|
+
import { promises as fsp } from 'node:fs';
|
|
53
|
+
import { join } from 'node:path';
|
|
54
|
+
|
|
55
|
+
export class ToolForgeStoreError extends Error {
|
|
56
|
+
constructor(message, code = 'FORGE_INVALID') {
|
|
57
|
+
super(message);
|
|
58
|
+
this.name = 'ToolForgeStoreError';
|
|
59
|
+
this.code = code;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Nome canonico della cartella globale — MAI usato per un join interno qui (vedi la doc di testa): solo documentazione/riferimento esterno, come CARTELLA_NOTE in notes-store.mjs. */
|
|
64
|
+
export const CARTELLA_FORGE = '.tool-forge-store';
|
|
65
|
+
/** Censito da forgeRegistryRepository.ts mobile (MAX_INSTALLED_TOOLS = 64) — non inventato. */
|
|
66
|
+
export const MAX_TOOL_INSTALLATI = 64;
|
|
67
|
+
|
|
68
|
+
function percorsoVoce(cartella, id) {
|
|
69
|
+
return join(cartella, `${id}.json`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @returns {Promise<Array<object>>} — [] se la cartella non esiste
|
|
74
|
+
* ancora (mai un errore: nessun tool forgiato è uno stato onesto).
|
|
75
|
+
* Più recenti prime.
|
|
76
|
+
*/
|
|
77
|
+
export async function elencaToolForgiati({ cartella }, deps = {}) {
|
|
78
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
79
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
80
|
+
let file;
|
|
81
|
+
try {
|
|
82
|
+
file = await readdirFn(cartella);
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const strumenti = [];
|
|
87
|
+
for (const nomeFile of file) {
|
|
88
|
+
if (!nomeFile.endsWith('.json')) continue;
|
|
89
|
+
try {
|
|
90
|
+
strumenti.push(JSON.parse(await readFileFn(join(cartella, nomeFile), 'utf8')));
|
|
91
|
+
} catch {
|
|
92
|
+
// ⛔ una voce corrotta non impedisce di vedere le altre — stesso principio di leggiRegistro (session-store.mjs).
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
strumenti.sort((a, b) => String(b.installatoAlle || '').localeCompare(String(a.installatoAlle || '')));
|
|
96
|
+
return strumenti;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @returns {Promise<object|null>} — null se l'id non esiste. */
|
|
100
|
+
export async function leggiToolForgiato({ cartella, id }, deps = {}) {
|
|
101
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(await readFileFn(percorsoVoce(cartella, id), 'utf8'));
|
|
104
|
+
} catch (errore) {
|
|
105
|
+
if (errore?.code === 'ENOENT') return null;
|
|
106
|
+
throw new ToolForgeStoreError(`${id}: metadata presente ma illeggibile: ${errore.message}`, 'FORGE_READ_FAILED');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Installa un manifest GIÀ VALIDATO (dal chiamante, via
|
|
112
|
+
* validaManifestForge del kernel — questo store non rivalida, si
|
|
113
|
+
* fida del chiamante come library-store.mjs si fida di
|
|
114
|
+
* sanificaNomeLibreria). Rifiuta un id già esistente (porto diretto
|
|
115
|
+
* del messaggio mobile — v1 non ha un tool_update, un secondo
|
|
116
|
+
* tool_create sullo stesso id è sempre un conflitto, mai un
|
|
117
|
+
* aggiornamento silenzioso) e un registro pieno (porto diretto del
|
|
118
|
+
* messaggio mobile, MAX_TOOL_INSTALLATI censito non inventato).
|
|
119
|
+
*/
|
|
120
|
+
export async function installaToolForgiato({ cartella, manifest, capacita, azioni, rischio }, deps = {}) {
|
|
121
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
122
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
123
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
124
|
+
if (!manifest || typeof manifest.id !== 'string' || manifest.id.length === 0) {
|
|
125
|
+
throw new ToolForgeStoreError('a tool manifest needs an id', 'FORGE_INVALID');
|
|
126
|
+
}
|
|
127
|
+
let esistenti;
|
|
128
|
+
try {
|
|
129
|
+
esistenti = await readdirFn(cartella);
|
|
130
|
+
} catch {
|
|
131
|
+
esistenti = [];
|
|
132
|
+
}
|
|
133
|
+
if (esistenti.includes(`${manifest.id}.json`)) {
|
|
134
|
+
throw new ToolForgeStoreError(`a tool with id "${manifest.id}" already exists — pick a different id`, 'FORGE_VERSION_NOT_NEWER');
|
|
135
|
+
}
|
|
136
|
+
if (esistenti.filter((nome) => nome.endsWith('.json')).length >= MAX_TOOL_INSTALLATI) {
|
|
137
|
+
throw new ToolForgeStoreError('the tool registry on this device is full — remove an unused tool first', 'FORGE_REGISTRY_FULL');
|
|
138
|
+
}
|
|
139
|
+
await mkdirFn(cartella, { recursive: true });
|
|
140
|
+
const voce = {
|
|
141
|
+
id: manifest.id, manifest, capacita, azioni, rischio,
|
|
142
|
+
abilitato: false, installatoAlle: new Date().toISOString(),
|
|
143
|
+
};
|
|
144
|
+
await writeFileFn(percorsoVoce(cartella, manifest.id), JSON.stringify(voce, null, 2), 'utf8');
|
|
145
|
+
return voce;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* L'UNICA mutazione owner-facing di questa fase (vedi la doc di testa
|
|
150
|
+
* sul perché) — mai chiamata da un tool del modello. `null` se l'id
|
|
151
|
+
* non esiste.
|
|
152
|
+
*/
|
|
153
|
+
export async function abilitaToolForgiato({ cartella, id, abilitato }, deps = {}) {
|
|
154
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
155
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
156
|
+
let voce;
|
|
157
|
+
try {
|
|
158
|
+
voce = JSON.parse(await readFileFn(percorsoVoce(cartella, id), 'utf8'));
|
|
159
|
+
} catch (errore) {
|
|
160
|
+
if (errore?.code === 'ENOENT') return null;
|
|
161
|
+
throw new ToolForgeStoreError(`${id}: metadata presente ma illeggibile: ${errore.message}`, 'FORGE_READ_FAILED');
|
|
162
|
+
}
|
|
163
|
+
const aggiornata = { ...voce, abilitato: Boolean(abilitato) };
|
|
164
|
+
await writeFileFn(percorsoVoce(cartella, id), JSON.stringify(aggiornata, null, 2), 'utf8');
|
|
165
|
+
return aggiornata;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** `null` se l'id non esiste già (idempotente, mai un'eccezione — stesso principio di eliminaVoce/eliminaRicerca). */
|
|
169
|
+
export async function eliminaToolForgiato({ cartella, id }, deps = {}) {
|
|
170
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
171
|
+
const rmFn = deps.rmFn ?? fsp.rm;
|
|
172
|
+
try {
|
|
173
|
+
await readFileFn(percorsoVoce(cartella, id), 'utf8');
|
|
174
|
+
} catch (errore) {
|
|
175
|
+
if (errore?.code === 'ENOENT') return null;
|
|
176
|
+
throw new ToolForgeStoreError(`${id}: metadata presente ma illeggibile: ${errore.message}`, 'FORGE_READ_FAILED');
|
|
177
|
+
}
|
|
178
|
+
await rmFn(percorsoVoce(cartella, id), { force: true });
|
|
179
|
+
return { id };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
/* M10-C — owner-managed revision history. The active <id>.json remains the only runtime source. */
|
|
184
|
+
const OWNER_HISTORY_DIR = '.owner-versions';
|
|
185
|
+
const OWNER_HISTORY_LIMIT = 10;
|
|
186
|
+
const OWNER_AUDIT_LIMIT = 256;
|
|
187
|
+
const OWNER_ID = /^[a-z0-9][a-z0-9_-]{2,63}$/;
|
|
188
|
+
|
|
189
|
+
function ownerDir(cartella, id) { return join(cartella, OWNER_HISTORY_DIR, id); }
|
|
190
|
+
function ownerVersionsDir(cartella, id) { return join(ownerDir(cartella, id), 'versions'); }
|
|
191
|
+
function ownerMetaPath(cartella, id) { return join(ownerDir(cartella, id), 'meta.json'); }
|
|
192
|
+
function ownerAuditPath(cartella, id) { return join(ownerDir(cartella, id), 'audit.jsonl'); }
|
|
193
|
+
function ownerVersionPath(cartella, id, revision) { return join(ownerVersionsDir(cartella, id), revision + '.json'); }
|
|
194
|
+
function validOwnerInput(id, revision) {
|
|
195
|
+
if (typeof id !== 'string' || !OWNER_ID.test(id)) throw new ToolForgeStoreError('owner-managed tool id is invalid', 'FORGE_INVALID');
|
|
196
|
+
if (!Number.isSafeInteger(revision) || revision < 1) throw new ToolForgeStoreError('owner revision must be a positive integer', 'FORGE_INVALID');
|
|
197
|
+
}
|
|
198
|
+
async function readOwnerMeta(cartella, id, deps = {}) {
|
|
199
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
200
|
+
try {
|
|
201
|
+
const value = JSON.parse(await readFileFn(ownerMetaPath(cartella, id), 'utf8'));
|
|
202
|
+
return { maxRevision: Number.isSafeInteger(value?.maxRevision) && value.maxRevision > 0 ? value.maxRevision : 0 };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (error?.code === 'ENOENT') return { maxRevision: 0 };
|
|
205
|
+
throw new ToolForgeStoreError(`${id}: owner version metadata unreadable: ${error.message}`, 'FORGE_READ_FAILED');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function appendOwnerAudit(cartella, id, event, deps = {}) {
|
|
209
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
210
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
211
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
212
|
+
await mkdirFn(ownerDir(cartella, id), { recursive: true });
|
|
213
|
+
let rows = [];
|
|
214
|
+
try { rows = (await readFileFn(ownerAuditPath(cartella, id), 'utf8')).split('\n').filter(Boolean).map((line) => JSON.parse(line)); }
|
|
215
|
+
catch (error) { if (error?.code !== 'ENOENT') throw new ToolForgeStoreError(`${id}: owner audit unreadable: ${error.message}`, 'FORGE_READ_FAILED'); }
|
|
216
|
+
rows.push(event);
|
|
217
|
+
rows = rows.slice(-OWNER_AUDIT_LIMIT);
|
|
218
|
+
await writeFileFn(ownerAuditPath(cartella, id), rows.map((row) => JSON.stringify(row)).join('\n') + '\n', 'utf8');
|
|
219
|
+
}
|
|
220
|
+
async function pruneOwnerVersions(cartella, id, deps = {}) {
|
|
221
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
222
|
+
const rmFn = deps.rmFn ?? fsp.rm;
|
|
223
|
+
let names = [];
|
|
224
|
+
try { names = await readdirFn(ownerVersionsDir(cartella, id)); } catch { return; }
|
|
225
|
+
const revisions = names.map((name) => /^([1-9][0-9]*)\.json$/.exec(name)).filter(Boolean).map((match) => Number(match[1])).filter(Number.isSafeInteger).sort((a, b) => a - b);
|
|
226
|
+
for (const revision of revisions.slice(0, Math.max(0, revisions.length - OWNER_HISTORY_LIMIT))) await rmFn(ownerVersionPath(cartella, id, revision), { force: true });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Owner-only versioned install/update. This intentionally does NOT replace installaToolForgiato:
|
|
231
|
+
* model tool_create remains create-only and same-id conflicts forever.
|
|
232
|
+
*/
|
|
233
|
+
export async function installaVersioneToolForgiatoOwner({ cartella, revision, manifest, capacita, azioni, rischio, evidence }, deps = {}) {
|
|
234
|
+
validOwnerInput(manifest?.id, revision);
|
|
235
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
236
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
237
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
238
|
+
const active = await leggiToolForgiato({ cartella, id: manifest.id }, deps);
|
|
239
|
+
if (active && active.ownerManaged !== true) throw new ToolForgeStoreError(`tool "${manifest.id}" exists with legacy/model provenance and cannot be silently adopted`, 'FORGE_OWNER_ADOPTION_REQUIRED');
|
|
240
|
+
if (!active) {
|
|
241
|
+
let existing = []; try { existing = await readdirFn(cartella); } catch {}
|
|
242
|
+
if (existing.filter((name) => name.endsWith('.json')).length >= MAX_TOOL_INSTALLATI) throw new ToolForgeStoreError('the tool registry on this device is full — remove an unused tool first', 'FORGE_REGISTRY_FULL');
|
|
243
|
+
}
|
|
244
|
+
const meta = await readOwnerMeta(cartella, manifest.id, deps);
|
|
245
|
+
const maxEver = Math.max(meta.maxRevision, Number.isSafeInteger(active?.ownerRevision) ? active.ownerRevision : 0);
|
|
246
|
+
if (revision <= maxEver) throw new ToolForgeStoreError(`owner revision ${revision} is not newer than max-ever revision ${maxEver}`, 'FORGE_VERSION_NOT_NEWER');
|
|
247
|
+
await mkdirFn(ownerVersionsDir(cartella, manifest.id), { recursive: true });
|
|
248
|
+
const at = new Date().toISOString();
|
|
249
|
+
const record = {
|
|
250
|
+
id: manifest.id, manifest, capacita: Array.isArray(capacita) ? capacita : [], azioni: Array.isArray(azioni) ? azioni : [],
|
|
251
|
+
rischio: typeof rischio === 'string' ? rischio : 'R1', abilitato: false, installatoAlle: at,
|
|
252
|
+
ownerManaged: true, ownerRevision: revision, evidence: evidence ?? null,
|
|
253
|
+
};
|
|
254
|
+
try { await writeFileFn(ownerVersionPath(cartella, manifest.id, revision), JSON.stringify(record, null, 2), { encoding: 'utf8', flag: 'wx' }); }
|
|
255
|
+
catch (error) { if (error?.code === 'EEXIST') throw new ToolForgeStoreError(`owner revision ${revision} already exists`, 'FORGE_VERSION_NOT_NEWER'); throw error; }
|
|
256
|
+
await mkdirFn(cartella, { recursive: true });
|
|
257
|
+
await writeFileFn(percorsoVoce(cartella, manifest.id), JSON.stringify(record, null, 2), 'utf8');
|
|
258
|
+
await writeFileFn(ownerMetaPath(cartella, manifest.id), JSON.stringify({ maxRevision: revision }, null, 2), 'utf8');
|
|
259
|
+
await appendOwnerAudit(cartella, manifest.id, { kind: active ? 'update' : 'install', revision, at }, deps);
|
|
260
|
+
await pruneOwnerVersions(cartella, manifest.id, deps);
|
|
261
|
+
return record;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function elencaVersioniToolForgiatoOwner({ cartella, id }, deps = {}) {
|
|
265
|
+
if (typeof id !== 'string' || !OWNER_ID.test(id)) throw new ToolForgeStoreError('owner-managed tool id is invalid', 'FORGE_INVALID');
|
|
266
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
267
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
268
|
+
let names = []; try { names = await readdirFn(ownerVersionsDir(cartella, id)); } catch (error) { if (error?.code === 'ENOENT') return []; throw error; }
|
|
269
|
+
const revisions = names.map((name) => /^([1-9][0-9]*)\.json$/.exec(name)).filter(Boolean).map((match) => Number(match[1])).filter(Number.isSafeInteger).sort((a, b) => a - b);
|
|
270
|
+
const rows = [];
|
|
271
|
+
for (const revision of revisions) {
|
|
272
|
+
try { const value = JSON.parse(await readFileFn(ownerVersionPath(cartella, id, revision), 'utf8')); rows.push({ ...value, revision }); }
|
|
273
|
+
catch (error) { throw new ToolForgeStoreError(`${id} revision ${revision} unreadable: ${error.message}`, 'FORGE_READ_FAILED'); }
|
|
274
|
+
}
|
|
275
|
+
return rows;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function leggiAuditToolForgiatoOwner({ cartella, id }, deps = {}) {
|
|
279
|
+
if (typeof id !== 'string' || !OWNER_ID.test(id)) throw new ToolForgeStoreError('owner-managed tool id is invalid', 'FORGE_INVALID');
|
|
280
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
281
|
+
try { return (await readFileFn(ownerAuditPath(cartella, id), 'utf8')).split('\n').filter(Boolean).map((line) => JSON.parse(line)); }
|
|
282
|
+
catch (error) { if (error?.code === 'ENOENT') return []; throw new ToolForgeStoreError(`${id}: owner audit unreadable: ${error.message}`, 'FORGE_READ_FAILED'); }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export async function ripristinaVersioneToolForgiatoOwner({ cartella, id, revision }, deps = {}) {
|
|
286
|
+
validOwnerInput(id, revision);
|
|
287
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
288
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
289
|
+
const active = await leggiToolForgiato({ cartella, id }, deps);
|
|
290
|
+
if (!active) throw new ToolForgeStoreError(`tool "${id}" does not exist`, 'FORGE_NOT_FOUND');
|
|
291
|
+
if (active.ownerManaged !== true) throw new ToolForgeStoreError(`tool "${id}" is not owner-managed`, 'FORGE_OWNER_ADOPTION_REQUIRED');
|
|
292
|
+
let snapshot;
|
|
293
|
+
try { snapshot = JSON.parse(await readFileFn(ownerVersionPath(cartella, id, revision), 'utf8')); }
|
|
294
|
+
catch (error) { if (error?.code === 'ENOENT') throw new ToolForgeStoreError(`owner revision ${revision} is not retained`, 'FORGE_VERSION_NOT_FOUND'); throw new ToolForgeStoreError(`${id} revision ${revision} unreadable: ${error.message}`, 'FORGE_READ_FAILED'); }
|
|
295
|
+
const restored = { ...snapshot, abilitato: false };
|
|
296
|
+
await writeFileFn(percorsoVoce(cartella, id), JSON.stringify(restored, null, 2), 'utf8');
|
|
297
|
+
await appendOwnerAudit(cartella, id, { kind: 'rollback', revision, at: new Date().toISOString() }, deps);
|
|
298
|
+
return restored;
|
|
299
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendere JSON Schema uno schema che JSON Schema non è.
|
|
3
|
+
*
|
|
4
|
+
* ## Il guasto che l'ha fatto nascere, misurato
|
|
5
|
+
*
|
|
6
|
+
* Owner 03/9: «il modello locale dà errore, quindi non funziona». Il motore
|
|
7
|
+
* rispondeva:
|
|
8
|
+
*
|
|
9
|
+
* HTTP 400 — Unable to generate parser for this template.
|
|
10
|
+
* JSON schema conversion failed: Unrecognized schema: "string"
|
|
11
|
+
* Unrecognized schema: "string"
|
|
12
|
+
*
|
|
13
|
+
* Due volte. Ed erano esattamente due: un manifest Forge in
|
|
14
|
+
* `.tool-forge-store/promemoria-trattativa.json` dichiara
|
|
15
|
+
*
|
|
16
|
+
* { required: ['nome_contatto'],
|
|
17
|
+
* properties: { nome_contatto: 'string', dettagli_contatto: 'string' } }
|
|
18
|
+
*
|
|
19
|
+
* cioè le proprietà come STRINGHE NUDE invece di `{ type: 'string' }`, e senza
|
|
20
|
+
* `type: 'object'` in testa. È un'abbreviazione comoda, ed è nostra: la scrive
|
|
21
|
+
* chi forgia un attrezzo. Solo che non è JSON Schema.
|
|
22
|
+
*
|
|
23
|
+
* ## Perché non si era mai visto
|
|
24
|
+
*
|
|
25
|
+
* OpenRouter non costruisce grammatiche: riceve lo schema, lo passa al
|
|
26
|
+
* modello come testo e non protesta. llama.cpp invece deve trasformarlo in
|
|
27
|
+
* GBNF per vincolare l'output, quindi lo legge davvero — e si ferma.
|
|
28
|
+
* ⇒ Il difetto era lì da sempre, e a scoprirlo è stato il primo motore che
|
|
29
|
+
* quello schema lo legge sul serio. ⛔ Non è «un limite del locale»: è una
|
|
30
|
+
* cosa rotta nostra che solo il locale è abbastanza severo da vedere.
|
|
31
|
+
*
|
|
32
|
+
* ⭐ Ricerca 03/9 (llama.cpp PR #17572; docs LLGuidance): il convertitore
|
|
33
|
+
* JSON-Schema→GBNF di llama.cpp copre un SOTTOINSIEME e rifiuta il resto con
|
|
34
|
+
* questo messaggio; upstream sta cambiando il 500 in 400 proprio per non far
|
|
35
|
+
* ritentare a vuoto. LLGuidance coprirebbe di più ma vuole Rust.
|
|
36
|
+
* ⭐ E il mobile aveva già scelto il principio giusto per lo stesso problema
|
|
37
|
+
* (`localToolPromptProtocol.ts`): il trasporto si sceglie dalle capacità
|
|
38
|
+
* MISURATE, «mai da un nome di file o da una famiglia indovinata».
|
|
39
|
+
*
|
|
40
|
+
* ## Cosa fa questo modulo, e cosa NON fa
|
|
41
|
+
*
|
|
42
|
+
* Espande le abbreviazioni verso il JSON Schema vero. ⛔ Non inventa vincoli
|
|
43
|
+
* e non butta via proprietà che non capisce: uno schema sconosciuto resta com'è
|
|
44
|
+
* e sarà il motore a dire la sua. Togliere in silenzio un parametro
|
|
45
|
+
* cambierebbe cosa il modello può chiedere, ed è peggio di un errore visibile.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/** I nomi di tipo che JSON Schema conosce. Una stringa nuda che ne è uno è un'abbreviazione. */
|
|
49
|
+
const TIPI_JSON = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {unknown} schema
|
|
53
|
+
* @returns {unknown} lo stesso schema, con le abbreviazioni espanse
|
|
54
|
+
*/
|
|
55
|
+
export function normalizzaSchemaAttrezzo(schema) {
|
|
56
|
+
// `'string'` → `{ type: 'string' }`. È l'abbreviazione che ha rotto tutto.
|
|
57
|
+
if (typeof schema === 'string') return TIPI_JSON.has(schema) ? { type: schema } : schema;
|
|
58
|
+
if (!schema || typeof schema !== 'object') return schema;
|
|
59
|
+
if (Array.isArray(schema)) return schema.map(normalizzaSchemaAttrezzo);
|
|
60
|
+
|
|
61
|
+
const uscita = { ...schema };
|
|
62
|
+
if (uscita.properties && typeof uscita.properties === 'object' && !Array.isArray(uscita.properties)) {
|
|
63
|
+
uscita.properties = Object.fromEntries(
|
|
64
|
+
Object.entries(uscita.properties).map(([nome, valore]) => [nome, normalizzaSchemaAttrezzo(valore)]),
|
|
65
|
+
);
|
|
66
|
+
/*
|
|
67
|
+
* ⛔ Un oggetto con `properties` ma senza `type` è la seconda metà della
|
|
68
|
+
* stessa abbreviazione: chi scrive `{ properties: {...} }` intende un
|
|
69
|
+
* oggetto, e il convertitore di grammatica non tira a indovinare.
|
|
70
|
+
*/
|
|
71
|
+
if (uscita.type === undefined) uscita.type = 'object';
|
|
72
|
+
}
|
|
73
|
+
if (uscita.items !== undefined) uscita.items = normalizzaSchemaAttrezzo(uscita.items);
|
|
74
|
+
if (uscita.additionalProperties !== undefined && typeof uscita.additionalProperties !== 'boolean') {
|
|
75
|
+
uscita.additionalProperties = normalizzaSchemaAttrezzo(uscita.additionalProperties);
|
|
76
|
+
}
|
|
77
|
+
for (const combinatore of ['anyOf', 'oneOf', 'allOf']) {
|
|
78
|
+
if (Array.isArray(uscita[combinatore])) uscita[combinatore] = uscita[combinatore].map(normalizzaSchemaAttrezzo);
|
|
79
|
+
}
|
|
80
|
+
if (uscita.$defs && typeof uscita.$defs === 'object') {
|
|
81
|
+
uscita.$defs = Object.fromEntries(Object.entries(uscita.$defs).map(([k, v]) => [k, normalizzaSchemaAttrezzo(v)]));
|
|
82
|
+
}
|
|
83
|
+
if (uscita.definitions && typeof uscita.definitions === 'object') {
|
|
84
|
+
uscita.definitions = Object.fromEntries(Object.entries(uscita.definitions).map(([k, v]) => [k, normalizzaSchemaAttrezzo(v)]));
|
|
85
|
+
}
|
|
86
|
+
return uscita;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Uno schema di ingresso valido anche quando il manifest non ne dichiara uno.
|
|
91
|
+
* ⛔ `{}` vuoto è ammesso da JSON Schema ma non dice niente a una grammatica:
|
|
92
|
+
* meglio un oggetto senza proprietà, che è ciò che l'attrezzo intende.
|
|
93
|
+
*/
|
|
94
|
+
export function schemaIngressoAttrezzo(schema) {
|
|
95
|
+
if (schema === undefined || schema === null) return { type: 'object', properties: {} };
|
|
96
|
+
const normalizzato = normalizzaSchemaAttrezzo(schema);
|
|
97
|
+
if (!normalizzato || typeof normalizzato !== 'object' || Array.isArray(normalizzato)) return { type: 'object', properties: {} };
|
|
98
|
+
if (normalizzato.type === undefined && normalizzato.properties === undefined) return { ...normalizzato, type: 'object', properties: {} };
|
|
99
|
+
return normalizzato;
|
|
100
|
+
}
|