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,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-registry.mjs — FASE E del piano `elegant-spinning-dongarra.md`,
|
|
3
|
+
* seconda fetta (29/8): dove vive la configurazione dei server MCP
|
|
4
|
+
* per-workspace, e il trust che decide se un server dichiarato può
|
|
5
|
+
* davvero connettersi. `hook-registry.mjs` letto per intero come
|
|
6
|
+
* precedente diretto prima di scrivere questo file — stesso design,
|
|
7
|
+
* per lo stesso motivo: un comando esterno arbitrario (qui: un intero
|
|
8
|
+
* server a lunga vita, con i SUOI tool, non solo un controllo rapido)
|
|
9
|
+
* non deve MAI partire senza un consenso esplicito dell'owner, e quel
|
|
10
|
+
* consenso resta legato al CONTENUTO della dichiarazione (hash), non
|
|
11
|
+
* al nome.
|
|
12
|
+
*
|
|
13
|
+
* ⛔⛔ Due cartelle DIVERSE, stesso motivo di hook-registry.mjs:
|
|
14
|
+
* - `.harness-ui-mcp.json` vive DENTRO il workspace (come hooks.json,
|
|
15
|
+
* AGENTS.md) — "quali server MCP servono questo progetto" è una
|
|
16
|
+
* proprietà del progetto, non della macchina.
|
|
17
|
+
* - il trust vive FUORI, accanto a server.mjs (stesso pattern di
|
|
18
|
+
* `.automations/` e del trust degli hook).
|
|
19
|
+
*
|
|
20
|
+
* ⛔⛔⛔ L'`allowlist` per server è OBBLIGATORIA nello schema, non
|
|
21
|
+
* opzionale — a differenza di `filtraToolMcp` in mcp-client.mjs (che
|
|
22
|
+
* accetta qualunque array, anche vuoto, perché non è compito suo
|
|
23
|
+
* imporre una policy). Qui SÌ: un server dichiarato senza allowlist è
|
|
24
|
+
* un file MALFORMATO, rifiutato prima ancora di poter connettersi —
|
|
25
|
+
* "mai un tool MCP remoto esposto senza filtro" (piano madre, §2.2)
|
|
26
|
+
* è applicato al punto più a monte
|
|
27
|
+
* possibile, non lasciato alla disciplina di chi integra dopo.
|
|
28
|
+
*
|
|
29
|
+
* Il PROTOCOLLO (connettersi, elencare, chiamare) resta interamente
|
|
30
|
+
* in mcp-client.mjs — questo file non lo importa e non lo usa: solo
|
|
31
|
+
* configurazione e trust, stesso confine "qui solo X, mai Y" già
|
|
32
|
+
* documentato nella testa di mcp-client.mjs stesso.
|
|
33
|
+
*/
|
|
34
|
+
import { createHash } from 'node:crypto';
|
|
35
|
+
import { promises as fsp } from 'node:fs';
|
|
36
|
+
import { join } from 'node:path';
|
|
37
|
+
|
|
38
|
+
export class McpRegistryError extends Error {
|
|
39
|
+
constructor(message, code = 'MCP_CONFIG_INVALID') {
|
|
40
|
+
super(message);
|
|
41
|
+
this.name = 'McpRegistryError';
|
|
42
|
+
this.code = code;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const NOME_FILE_MCP = '.harness-ui-mcp.json';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Legge `<cartella>/.harness-ui-mcp.json`. Un progetto senza server
|
|
50
|
+
* dichiarati è uno stato valido — mai un errore, torna `{ server: [] }`.
|
|
51
|
+
* Uno schema malformato invece È un errore dichiarato (mai un server
|
|
52
|
+
* fantasma silenziosamente ignorato, stesso principio di caricaHooks).
|
|
53
|
+
*/
|
|
54
|
+
export async function caricaServerMcp({ cartella }, deps = {}) {
|
|
55
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
56
|
+
const percorso = join(cartella, NOME_FILE_MCP);
|
|
57
|
+
let testo;
|
|
58
|
+
try {
|
|
59
|
+
testo = await readFileFn(percorso, 'utf8');
|
|
60
|
+
} catch (errore) {
|
|
61
|
+
if (errore?.code === 'ENOENT') return { server: [] };
|
|
62
|
+
throw new McpRegistryError(`Impossibile leggere ${NOME_FILE_MCP}: ${errore.message}`, 'MCP_CONFIG_READ_FAILED');
|
|
63
|
+
}
|
|
64
|
+
let dati;
|
|
65
|
+
try {
|
|
66
|
+
dati = JSON.parse(testo);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new McpRegistryError(`${NOME_FILE_MCP} non è un JSON valido`, 'MCP_CONFIG_MALFORMED');
|
|
69
|
+
}
|
|
70
|
+
if (!dati || !Array.isArray(dati.server)) {
|
|
71
|
+
throw new McpRegistryError(`${NOME_FILE_MCP} deve avere un campo "server" (array)`, 'MCP_CONFIG_MALFORMED');
|
|
72
|
+
}
|
|
73
|
+
const server = dati.server.map((voce, indice) => {
|
|
74
|
+
if (typeof voce?.id !== 'string' || voce.id.length === 0) {
|
|
75
|
+
throw new McpRegistryError(`server[${indice}] manca di "id" (stringa non vuota)`, 'MCP_CONFIG_MALFORMED');
|
|
76
|
+
}
|
|
77
|
+
if (typeof voce.comando !== 'string' || voce.comando.length === 0) {
|
|
78
|
+
throw new McpRegistryError(`server[${indice}] ("${voce.id}") manca di "comando" (stringa non vuota)`, 'MCP_CONFIG_MALFORMED');
|
|
79
|
+
}
|
|
80
|
+
if (voce.argomenti !== undefined && (!Array.isArray(voce.argomenti) || !voce.argomenti.every((a) => typeof a === 'string'))) {
|
|
81
|
+
throw new McpRegistryError(`server[${indice}] ("${voce.id}") ha "argomenti" non valido — atteso un array di stringhe`, 'MCP_CONFIG_MALFORMED');
|
|
82
|
+
}
|
|
83
|
+
if (!Array.isArray(voce.allowlist) || voce.allowlist.length === 0 || !voce.allowlist.every((n) => typeof n === 'string' && n.length > 0)) {
|
|
84
|
+
throw new McpRegistryError(`server[${indice}] ("${voce.id}") manca di "allowlist" (array non vuoto di nomi tool) — un server MCP non si dichiara senza dire quali tool sono ammessi`, 'MCP_CONFIG_MALFORMED');
|
|
85
|
+
}
|
|
86
|
+
const argomenti = voce.argomenti ?? [];
|
|
87
|
+
const hash = createHash('sha256').update(JSON.stringify({ comando: voce.comando, argomenti, allowlist: voce.allowlist })).digest('hex');
|
|
88
|
+
return { id: voce.id, comando: voce.comando, argomenti, allowlist: voce.allowlist, hash };
|
|
89
|
+
});
|
|
90
|
+
return { server };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function percorsoTrust(cartellaTrust, serverId) {
|
|
94
|
+
// ⛔ serverId arriva da un file scritto dall'owner (.harness-ui-mcp.json),
|
|
95
|
+
// ma è comunque un input esterno: mai costruire un percorso da una
|
|
96
|
+
// stringa non validata come nome file — stesso principio già in uso
|
|
97
|
+
// in hook-registry.mjs/workspace-files.mjs.
|
|
98
|
+
if (typeof serverId !== 'string' || serverId.length === 0 || /[\\/]|\.\./.test(serverId)) {
|
|
99
|
+
throw new McpRegistryError('serverId non valido — un nome, non un percorso', 'MCP_SERVER_ID_INVALID');
|
|
100
|
+
}
|
|
101
|
+
return join(cartellaTrust, `${serverId}.json`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Un server è fidato SOLO se il registro persistito porta lo STESSO
|
|
106
|
+
* hash della dichiarazione attuale (comando+argomenti+allowlist) — un
|
|
107
|
+
* server il cui contenuto è cambiato torna automaticamente "non
|
|
108
|
+
* fidato", senza bisogno di un'azione esplicita di sfiducia: il trust
|
|
109
|
+
* è legato al CONTENUTO, non al nome. Stesso comportamento di
|
|
110
|
+
* verificaTrust in hook-registry.mjs.
|
|
111
|
+
*/
|
|
112
|
+
export async function verificaTrustMcp({ cartellaTrust, serverId, hash }, deps = {}) {
|
|
113
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
114
|
+
let testo;
|
|
115
|
+
try {
|
|
116
|
+
testo = await readFileFn(percorsoTrust(cartellaTrust, serverId), 'utf8');
|
|
117
|
+
} catch {
|
|
118
|
+
return false; // mai fidato per default: assente = non fidato, non un errore
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const dati = JSON.parse(testo);
|
|
122
|
+
return dati?.hash === hash;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Registra il trust — chiamata SOLO da un'azione owner esplicita (la
|
|
130
|
+
* rotta HTTP dedicata, mai automatica dal ciclo dell'agente o
|
|
131
|
+
* dall'avvio di una sessione).
|
|
132
|
+
*/
|
|
133
|
+
export async function fidaServerMcp({ cartellaTrust, serverId, hash }, deps = {}) {
|
|
134
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
135
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
136
|
+
await mkdirFn(cartellaTrust, { recursive: true });
|
|
137
|
+
await writeFileFn(percorsoTrust(cartellaTrust, serverId), JSON.stringify({ hash, fidatoIl: new Date().toISOString() }), 'utf8');
|
|
138
|
+
return { fidato: true };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Il punto d'ingresso per chi avvia una sessione (session-registry.mjs):
|
|
143
|
+
* carica i server dichiarati, tiene SOLO quelli fidati (stesso
|
|
144
|
+
* fail-closed di hook-registry.mjs — un server non fidato semplicemente
|
|
145
|
+
* non compare, mai un errore che blocca l'avvio della sessione: la
|
|
146
|
+
* sessione parte comunque, solo senza quel server), e torna l'elenco
|
|
147
|
+
* pronto per essere connesso da mcp-client.mjs.
|
|
148
|
+
*/
|
|
149
|
+
export async function serverMcpFidati({ cartella, cartellaTrust }, deps = {}) {
|
|
150
|
+
const { server } = await caricaServerMcp({ cartella }, deps);
|
|
151
|
+
const fidati = [];
|
|
152
|
+
for (const s of server) {
|
|
153
|
+
const fidato = await verificaTrustMcp({ cartellaTrust, serverId: s.id, hash: s.hash }, deps);
|
|
154
|
+
if (fidato) fidati.push(s);
|
|
155
|
+
}
|
|
156
|
+
return { fidati, nonFidati: server.filter((s) => !fidati.includes(s)) };
|
|
157
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcp-session.mjs — FASE E del piano `elegant-spinning-dongarra.md`,
|
|
3
|
+
* terza fetta (29/8): il punto che UNISCE `mcp-registry.mjs` (quali
|
|
4
|
+
* server sono dichiarati e fidati) e `mcp-client.mjs` (il protocollo)
|
|
5
|
+
* in quello che una sessione REALE offre al kernel — `toolMcp` +
|
|
6
|
+
* `chiamaToolMcpFn`, esattamente la forma che `talosLavora` accetta
|
|
7
|
+
* (vedi `AVM-harness/.../talosHarness.mjs`, commit `a902af7`).
|
|
8
|
+
*
|
|
9
|
+
* ⛔ A differenza degli hook (`costruisciHookFn`, letto per intero
|
|
10
|
+
* prima di scrivere questo file): un hook si legge PIGRAMENTE, alla
|
|
11
|
+
* prima tool-call, perché serve solo quando un evento capita. I tool
|
|
12
|
+
* MCP invece devono essere NOTI PRIMA della prima chiamata al modello
|
|
13
|
+
* (finiscono nello schema `tools` della richiesta) — questo file
|
|
14
|
+
* connette tutti i server fidati EAGERMENTE, una volta, prima che
|
|
15
|
+
* `talosLavora` parta.
|
|
16
|
+
*
|
|
17
|
+
* ⛔⛔⛔ Piu' server possono esporre un tool con lo STESSO nome (`read_file`
|
|
18
|
+
* su due server diversi non e' un caso raro — sono processi
|
|
19
|
+
* indipendenti, non sanno l'uno dell'altro). Il nome esposto al
|
|
20
|
+
* modello e' quindi SEMPRE prefissato `mcp__<serverId>__<toolName>`
|
|
21
|
+
* (stessa convenzione dell'ecosistema MCP, non inventata qui) — un
|
|
22
|
+
* dispatcher interno lo demux nel client/nome originali giusti,
|
|
23
|
+
* mai un'ambiguita' silenziosa fra due server.
|
|
24
|
+
*
|
|
25
|
+
* Un server che non parte (comando sbagliato, dipendenza mancante)
|
|
26
|
+
* non impedisce MAI alla sessione di avviarsi — stesso principio
|
|
27
|
+
* "mai un blocco silenzioso, degrada" gia' in uso per gli hook: viene
|
|
28
|
+
* saltato, e il suo fallimento e' registrato per chi chiama (mai
|
|
29
|
+
* inghiottito senza traccia).
|
|
30
|
+
*/
|
|
31
|
+
import {
|
|
32
|
+
caricaServerMcp as caricaServerMcpReale,
|
|
33
|
+
serverMcpFidati as serverMcpFidatiReale,
|
|
34
|
+
} from './mcp-registry.mjs';
|
|
35
|
+
import { schemaIngressoAttrezzo } from './tool-schema-normalize.mjs';
|
|
36
|
+
import {
|
|
37
|
+
chiamaToolMcp as chiamaToolMcpReale,
|
|
38
|
+
connettiServerMcp as connettiServerMcpReale,
|
|
39
|
+
elencaToolMcp as elencaToolMcpReale,
|
|
40
|
+
filtraToolMcp as filtraToolMcpReale,
|
|
41
|
+
} from './mcp-client.mjs';
|
|
42
|
+
|
|
43
|
+
const SEPARATORE = '__';
|
|
44
|
+
const PREFISSO = `mcp${SEPARATORE}`;
|
|
45
|
+
|
|
46
|
+
/** Pura: costruisce il nome esposto al modello. Un test la esercita senza connettere nulla. */
|
|
47
|
+
export function nomeEspostoMcp(serverId, nomeTool) {
|
|
48
|
+
return `${PREFISSO}${serverId}${SEPARATORE}${nomeTool}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Tetto della partenza in parallelo: oltre, si aprirebbero troppi processi figli in un colpo all'avvio di una sessione. */
|
|
52
|
+
export const CONCORRENZA_AVVIO_MCP_MASSIMA = 8;
|
|
53
|
+
|
|
54
|
+
/*
|
|
55
|
+
* ⭐ 14/09 (F07 della review) — la scoperta dei tool era SERIALE: N server fidati costavano la SOMMA dei loro avvii,
|
|
56
|
+
* e ogni avvio è un processo figlio che fa handshake, cioè attesa, non calcolo. Misurato su quattro server d'eco:
|
|
57
|
+
* mediana **121 ms** in fila contro **46 ms** a quattro per volta.
|
|
58
|
+
* ⛔ Resta SPENTA di serie (`1` = il comportamento di prima, byte per byte): un server MCP di terzi può avere un
|
|
59
|
+
* avvio pesante, e far partire più processi insieme sulla macchina di qualcun altro non è una scelta che prendo io.
|
|
60
|
+
* Chi la vuole la accende con `TALOS_MCP_STARTUP_CONCURRENCY`.
|
|
61
|
+
* ⛔ Qualunque valore storto — vuoto, «due», 0, -3, 2.5 — torna **1**, mai un errore: una variabile d'ambiente scritta
|
|
62
|
+
* male non deve impedire a una sessione di partire.
|
|
63
|
+
*/
|
|
64
|
+
export function concorrenzaAvvioMcp(env = process.env) {
|
|
65
|
+
const grezzo = env?.TALOS_MCP_STARTUP_CONCURRENCY;
|
|
66
|
+
if (typeof grezzo !== 'string' && typeof grezzo !== 'number') return 1;
|
|
67
|
+
const numero = Number(String(grezzo).trim());
|
|
68
|
+
if (!Number.isInteger(numero) || numero < 1) return 1;
|
|
69
|
+
return Math.min(numero, CONCORRENZA_AVVIO_MCP_MASSIMA);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Connette OGNI server fidato del workspace, scopre e filtra i suoi
|
|
74
|
+
* tool con la sua allowlist (mai "tutto ciò che offre" — la stessa
|
|
75
|
+
* garanzia che `filtraToolMcp` già dà, applicata qui a ogni server
|
|
76
|
+
* dichiarato), e torna `{toolMcp, chiamaToolMcpFn, falliti, chiudiTutti}`
|
|
77
|
+
* pronto per `talosLavora`.
|
|
78
|
+
*
|
|
79
|
+
* `falliti`: `{serverId, errore}[]` — i server che NON sono partiti,
|
|
80
|
+
* mai inghiottiti in silenzio (chi chiama decide se avvisare l'owner).
|
|
81
|
+
* `chiudiTutti()`: chiude OGNI connessione aperta — va chiamato quando
|
|
82
|
+
* la sessione finisce, altrimenti il processo figlio del server resta
|
|
83
|
+
* vivo (stesso rischio già noto per ogni sottoprocesso spawnato).
|
|
84
|
+
*/
|
|
85
|
+
export async function preparaToolMcpPerSessione({ cartella, cartellaTrust }, deps = {}) {
|
|
86
|
+
const serverMcpFidatiFn = deps.serverMcpFidatiFn ?? serverMcpFidatiReale;
|
|
87
|
+
const connettiServerMcpFn = deps.connettiServerMcpFn ?? connettiServerMcpReale;
|
|
88
|
+
const elencaToolMcpFn = deps.elencaToolMcpFn ?? elencaToolMcpReale;
|
|
89
|
+
const filtraToolMcpFn = deps.filtraToolMcpFn ?? filtraToolMcpReale;
|
|
90
|
+
const chiamaToolMcpFnReale = deps.chiamaToolMcpFn ?? chiamaToolMcpReale;
|
|
91
|
+
|
|
92
|
+
let fidati = [];
|
|
93
|
+
try {
|
|
94
|
+
({ fidati } = await serverMcpFidatiFn({ cartella, cartellaTrust }));
|
|
95
|
+
} catch {
|
|
96
|
+
// ⭐ stesso principio degli hook: un registro che non si legge non blocca la sessione, degrada a "nessun server".
|
|
97
|
+
return { toolMcp: [], chiamaToolMcpFn: null, falliti: [], chiudiTutti: async () => {} };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const toolMcp = [];
|
|
101
|
+
const instradamento = new Map(); // nomeEsposto -> { client, nomeOriginale }
|
|
102
|
+
const connessioni = []; // { client, chiudi } — per chiudiTutti
|
|
103
|
+
const falliti = [];
|
|
104
|
+
|
|
105
|
+
/** L'avvio di UN server: non lancia mai — un fallimento è un esito come un altro, da comporre in ordine più sotto. */
|
|
106
|
+
async function avviaUnServer(server) {
|
|
107
|
+
let client;
|
|
108
|
+
let chiudi;
|
|
109
|
+
try {
|
|
110
|
+
({ client, chiudi } = await connettiServerMcpFn({ comando: server.comando, argomenti: server.argomenti, nome: `talos-harness-${server.id}` }));
|
|
111
|
+
} catch (errore) {
|
|
112
|
+
// un server che non parte non ferma gli altri, ne' la sessione
|
|
113
|
+
return { server, errore: errore instanceof Error ? errore.message : String(errore) };
|
|
114
|
+
}
|
|
115
|
+
let tuttiITool;
|
|
116
|
+
try {
|
|
117
|
+
tuttiITool = await elencaToolMcpFn({ client });
|
|
118
|
+
} catch (errore) {
|
|
119
|
+
// la connessione resta aperta (chiusa comunque da chiudiTutti), ma zero tool da questo server
|
|
120
|
+
return { server, client, chiudi, errore: `elenco tool fallito: ${errore instanceof Error ? errore.message : String(errore)}` };
|
|
121
|
+
}
|
|
122
|
+
return { server, client, chiudi, tool: filtraToolMcpFn(tuttiITool, server.allowlist) };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/*
|
|
126
|
+
* ⛔ L'ORDINE del risultato non dipende da chi finisce prima: gli esiti si depositano nella casella del loro server e
|
|
127
|
+
* si compongono dopo, in fila. Senza, `toolMcp` cambierebbe ordine a ogni avvio a seconda di quale processo figlio
|
|
128
|
+
* è stato più svelto — e con due server che espongono lo stesso attrezzo cambierebbe anche quale vince a schermo.
|
|
129
|
+
*/
|
|
130
|
+
const concorrenza = Math.max(1, Math.min(deps.concorrenza ?? concorrenzaAvvioMcp(deps.env), CONCORRENZA_AVVIO_MCP_MASSIMA));
|
|
131
|
+
const esiti = new Array(fidati.length).fill(null);
|
|
132
|
+
let prossimo = 0;
|
|
133
|
+
async function unLavoratore() {
|
|
134
|
+
while (prossimo < fidati.length) {
|
|
135
|
+
const indice = prossimo;
|
|
136
|
+
prossimo += 1;
|
|
137
|
+
esiti[indice] = await avviaUnServer(fidati[indice]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await Promise.all(Array.from({ length: Math.min(concorrenza, fidati.length) }, () => unLavoratore()));
|
|
141
|
+
|
|
142
|
+
for (const esito of esiti) {
|
|
143
|
+
if (!esito) continue;
|
|
144
|
+
if (esito.chiudi) connessioni.push({ client: esito.client, chiudi: esito.chiudi });
|
|
145
|
+
if (esito.errore) {
|
|
146
|
+
falliti.push({ serverId: esito.server.id, errore: esito.errore });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
for (const t of esito.tool) {
|
|
150
|
+
const nomeEsposto = nomeEspostoMcp(esito.server.id, t.name);
|
|
151
|
+
/*
|
|
152
|
+
* ⛔ Stessa cura degli attrezzi Forge, e qui serve ancora di più: questo
|
|
153
|
+
* schema arriva da un server MCP di TERZI, che non abbiamo scritto noi e
|
|
154
|
+
* non possiamo correggere. Passarlo intatto a un motore che ne costruisce
|
|
155
|
+
* una grammatica fa fallire l'intera richiesta — non solo quell'attrezzo.
|
|
156
|
+
*/
|
|
157
|
+
toolMcp.push({ name: nomeEsposto, description: t.description, inputSchema: schemaIngressoAttrezzo(t.inputSchema) });
|
|
158
|
+
instradamento.set(nomeEsposto, { client: esito.client, nomeOriginale: t.name });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const chiamaToolMcpFn = toolMcp.length > 0
|
|
163
|
+
? async (nomeEsposto, argomenti) => {
|
|
164
|
+
const voce = instradamento.get(nomeEsposto);
|
|
165
|
+
if (!voce) throw new Error(`tool MCP "${nomeEsposto}" non è fra quelli connessi in questa sessione`);
|
|
166
|
+
return chiamaToolMcpFnReale({ client: voce.client }, voce.nomeOriginale, argomenti);
|
|
167
|
+
}
|
|
168
|
+
: null;
|
|
169
|
+
|
|
170
|
+
const chiudiTutti = async () => {
|
|
171
|
+
for (const { chiudi } of connessioni) {
|
|
172
|
+
try { await chiudi(); } catch { /* un server che non chiude pulito non deve bloccare la chiusura degli altri */ }
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
return { toolMcp, chiamaToolMcpFn, falliti, chiudiTutti };
|
|
177
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memory-store.mjs — FASE N, sesto sistema (30/8), piano
|
|
3
|
+
* `elegant-spinning-dongarra.md`. Owner: "TUTTI i 5 sistemi rimasti, uno
|
|
4
|
+
* dopo l'altro".
|
|
5
|
+
*
|
|
6
|
+
* ⛔⛔⛔ GLOBALE, non per-progetto — stesso principio di notes-store.mjs/
|
|
7
|
+
* tasks-store.mjs. Letto alla fonte: `mobile/src/lib/tools/memoryWriteTools.ts`
|
|
8
|
+
* + `readTools.ts` + `chatDatabaseSchema.ts` (`talos_memories`) +
|
|
9
|
+
* `stationFacades.ts`/`chatController.ts` (l'implementazione VERA dietro
|
|
10
|
+
* l'interfaccia del tool, non presunta). Lo schema mobile ha uno
|
|
11
|
+
* `scope_type` a tre livelli (global/project/session), ma il CHIAMANTE
|
|
12
|
+
* del tool lo fissa a `'global'` SEMPRE, verificato in
|
|
13
|
+
* `chatController.ts`: "«ricordati che preferisco le risposte brevi»
|
|
14
|
+
* non vale solo in questa conversazione" — questo porto riflette solo
|
|
15
|
+
* ciò che il tool espone davvero, non l'intero schema del deposito.
|
|
16
|
+
*
|
|
17
|
+
* ⛔ `status` (active/disabled/quarantined/rejected nello schema mobile)
|
|
18
|
+
* NON portato: quella macchina a stati è per la STAZIONE mobile
|
|
19
|
+
* (moderazione manuale) — verificato in `stationFacades.ts`,
|
|
20
|
+
* `TalosMemoryWriteSources` non ha un `setStatus`. `memory_delete`
|
|
21
|
+
* desktop fa una cancellazione VERA (mobile stesso chiama
|
|
22
|
+
* `deleteMemory`, un hard delete — confermato alla fonte, non un
|
|
23
|
+
* cambio di stato), stesso schema di notes_delete/tasks_delete.
|
|
24
|
+
*
|
|
25
|
+
* ⭐⭐⭐ L'UNICA logica genuinamente nuova rispetto a Notes/Tasks: la
|
|
26
|
+
* DEDUPLICAZIONE per titolo. Owner mobile, 2026-08-07: "«no, ricordati
|
|
27
|
+
* invece che...» produceva una seconda memoria accanto alla prima" — la
|
|
28
|
+
* memoria è l'unica superficie in cui un duplicato non è disordine, è
|
|
29
|
+
* una contraddizione che si autoalimenta a ogni conversazione futura.
|
|
30
|
+
* `creaMemoria` cerca PRIMA per titolo (case-insensitive, spazi
|
|
31
|
+
* ignorati) e torna la voce ESISTENTE con `duplicato:true` invece di
|
|
32
|
+
* scriverne una seconda.
|
|
33
|
+
*
|
|
34
|
+
* ⛔ Diverso da mobile in UN dettaglio, dichiarato: mobile `create()`
|
|
35
|
+
* NON torna un id ("l'ha detto il typecheck, non io" — un vincolo
|
|
36
|
+
* accidentale della sua implementazione, non una scelta di design,
|
|
37
|
+
* verificato leggendo il commento originale). Questo store non ha
|
|
38
|
+
* quel vincolo: `creaMemoria` torna sempre l'id, per coerenza con
|
|
39
|
+
* notes-store.mjs/tasks-store.mjs (il messaggio di successo desktop
|
|
40
|
+
* include l'id, come "Saved the note «X» (id Y)." — mobile non lo fa
|
|
41
|
+
* per un accidente della sua repository, non per principio).
|
|
42
|
+
*
|
|
43
|
+
* ⛔ Stessa CONVENZIONE DI CHIAMATA di notes-store.mjs/tasks-store.mjs
|
|
44
|
+
* (`funzione({cartella, ...}, deps={})`). Un file JSON per memoria
|
|
45
|
+
* (stessa classe di bug — riscrittura invece di accodamento — già
|
|
46
|
+
* evitata altrove, session-store.mjs).
|
|
47
|
+
*/
|
|
48
|
+
import { randomUUID } from 'node:crypto';
|
|
49
|
+
import { promises as fsp } from 'node:fs';
|
|
50
|
+
import { join } from 'node:path';
|
|
51
|
+
import { idArchivioValido } from './id-archivio.mjs';
|
|
52
|
+
|
|
53
|
+
const TITOLO_MASSIMO = 80;
|
|
54
|
+
const CONTENUTO_MASSIMO = 600;
|
|
55
|
+
const GENERI = Object.freeze(['preference', 'project_fact', 'procedure', 'policy_note']);
|
|
56
|
+
|
|
57
|
+
/** ⭐ Stesso pattern REALE di `.automations/`/`.notes-store/`/`.tasks-store/`: il nome che `server.mjs` userà per il percorso di default. */
|
|
58
|
+
export const CARTELLA_MEMORIA = '.memory-store';
|
|
59
|
+
|
|
60
|
+
export class MemoryStoreError extends Error {
|
|
61
|
+
constructor(message, code = 'MEMORY_INVALID') {
|
|
62
|
+
super(message);
|
|
63
|
+
this.name = 'MemoryStoreError';
|
|
64
|
+
this.code = code;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function percorsoDi(cartella, id) {
|
|
69
|
+
// ⛔ 14/09: l'id arriva da fuori (indirizzo HTTP, attrezzo del modello) — un id fuori grammatica non nomina nessun file e
|
|
70
|
+
// torna `null`, mai un `join` che con `..` uscirebbe dalla cartella. Vedi id-archivio.mjs.
|
|
71
|
+
if (!idArchivioValido(id)) return null;
|
|
72
|
+
return join(cartella, `${id}.json`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function normalizzaTitoloPerConfronto(titolo) {
|
|
76
|
+
return titolo.trim().toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validaTitolo(title) {
|
|
80
|
+
if (typeof title !== 'string' || title.trim().length === 0 || title.length > TITOLO_MASSIMO) {
|
|
81
|
+
throw new MemoryStoreError(`title deve avere 1-${TITOLO_MASSIMO} caratteri`, 'MEMORY_INVALID');
|
|
82
|
+
}
|
|
83
|
+
return title.trim();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function validaContenuto(content) {
|
|
87
|
+
if (typeof content !== 'string' || content.trim().length === 0 || content.length > CONTENUTO_MASSIMO) {
|
|
88
|
+
throw new MemoryStoreError(`content deve avere 1-${CONTENUTO_MASSIMO} caratteri`, 'MEMORY_INVALID');
|
|
89
|
+
}
|
|
90
|
+
return content.trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validaGenere(kind) {
|
|
94
|
+
if (!GENERI.includes(kind)) {
|
|
95
|
+
throw new MemoryStoreError(`kind deve essere uno fra ${GENERI.join('/')}`, 'MEMORY_INVALID');
|
|
96
|
+
}
|
|
97
|
+
return kind;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ⛔ 11/09 — CHI ha scritto questa memoria: stesso principio e stesso default di notes-store.mjs/tasks-store.mjs. */
|
|
101
|
+
const ORIGINI = Object.freeze(['persona', 'modello']);
|
|
102
|
+
|
|
103
|
+
function validaOrigine(origine) {
|
|
104
|
+
if (!ORIGINI.includes(origine)) {
|
|
105
|
+
throw new MemoryStoreError(`origine deve essere una fra ${ORIGINI.join('/')}`, 'MEMORY_INVALID');
|
|
106
|
+
}
|
|
107
|
+
return origine;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** La forma PUBBLICA di una memoria — l'unica che esce da questo prodotto, uguale per ogni superficie che la mostra. */
|
|
111
|
+
export function formaPubblicaMemoria(voce) {
|
|
112
|
+
return {
|
|
113
|
+
id: voce.id,
|
|
114
|
+
titolo: voce.titolo,
|
|
115
|
+
contenuto: voce.contenuto,
|
|
116
|
+
genere: voce.genere,
|
|
117
|
+
creataAlle: voce.creataAlle ?? null,
|
|
118
|
+
aggiornataAlle: voce.aggiornataAlle ?? null,
|
|
119
|
+
origine: ORIGINI.includes(voce.origine) ? voce.origine : 'modello',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Più recentemente aggiornate per prime. Cartella assente ⇒ `[]`, mai un errore. */
|
|
124
|
+
export async function elencaMemorie({ cartella }, deps = {}) {
|
|
125
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
126
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
127
|
+
let nomi;
|
|
128
|
+
try {
|
|
129
|
+
nomi = await readdirFn(cartella);
|
|
130
|
+
} catch {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const voci = [];
|
|
134
|
+
for (const nome of nomi) {
|
|
135
|
+
if (!nome.endsWith('.json')) continue;
|
|
136
|
+
try {
|
|
137
|
+
voci.push(JSON.parse(await readFileFn(join(cartella, nome), 'utf8')));
|
|
138
|
+
} catch { /* una voce corrotta non deve nascondere le altre */ }
|
|
139
|
+
}
|
|
140
|
+
return voci.sort((a, b) => b.aggiornataAlle.localeCompare(a.aggiornataAlle) || a.id.localeCompare(b.id));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Un id assente torna `null`, mai un'eccezione. */
|
|
144
|
+
export async function leggiMemoria({ cartella, id }, deps = {}) {
|
|
145
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
146
|
+
const percorso = percorsoDi(cartella, id);
|
|
147
|
+
if (!percorso) return null; // ⛔ 14/09: id fuori grammatica = «non c'è», mai una lettura fuori dalla cartella
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(await readFileFn(percorso, 'utf8'));
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Cerca per titolo esatto (case/spazi-insensitive) — usata dalla deduplicazione, mai per il modello direttamente. */
|
|
156
|
+
export async function trovaMemoriaPerTitolo({ cartella, title }, deps = {}) {
|
|
157
|
+
const cercato = normalizzaTitoloPerConfronto(title);
|
|
158
|
+
const tutte = await elencaMemorie({ cartella }, deps);
|
|
159
|
+
return tutte.find((m) => normalizzaTitoloPerConfronto(m.titolo) === cercato) ?? null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* PURA — stesso confine di `cercaVoci` in library-store.mjs: chi
|
|
164
|
+
* chiama carica l'elenco (I/O), questa funzione filtra (nessun I/O,
|
|
165
|
+
* un test la esercita con un array letterale). Sottostringa
|
|
166
|
+
* case-insensitive su titolo O contenuto — `memory_search` mobile non
|
|
167
|
+
* documenta un algoritmo più sofisticato di questo (nessuna fonte
|
|
168
|
+
* trovata per un ranking/fuzzy match), quindi non se ne inventa uno.
|
|
169
|
+
*/
|
|
170
|
+
export function cercaMemorie(memorie, { query, limit = 5 } = {}) {
|
|
171
|
+
const cercato = String(query ?? '').trim().toLowerCase();
|
|
172
|
+
const trovate = cercato === ''
|
|
173
|
+
? []
|
|
174
|
+
: memorie.filter((m) => m.titolo.toLowerCase().includes(cercato) || m.contenuto.toLowerCase().includes(cercato));
|
|
175
|
+
return { memorie: trovate.slice(0, limit), totale: trovate.length };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @returns `{voce, duplicato}` — `duplicato:true` quando esiste già una
|
|
180
|
+
* memoria con lo STESSO titolo: torna quella esistente, MAI ne scrive
|
|
181
|
+
* una seconda (la dedup è la ragion d'essere di questa funzione, non
|
|
182
|
+
* un dettaglio).
|
|
183
|
+
* @throws {MemoryStoreError} MEMORY_INVALID PRIMA di ogni I/O.
|
|
184
|
+
*/
|
|
185
|
+
export async function creaMemoria({ cartella, title, content, kind = 'preference', origine = 'modello' }, deps = {}) {
|
|
186
|
+
const titolo = validaTitolo(title);
|
|
187
|
+
const contenuto = validaContenuto(content);
|
|
188
|
+
const genere = validaGenere(kind);
|
|
189
|
+
const daChi = validaOrigine(origine);
|
|
190
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
191
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
192
|
+
const gemello = await trovaMemoriaPerTitolo({ cartella, title: titolo }, { readdirFn, readFileFn });
|
|
193
|
+
if (gemello) return { voce: gemello, duplicato: true };
|
|
194
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
195
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
196
|
+
const clockFn = deps.clockFn ?? (() => new Date());
|
|
197
|
+
const ora = clockFn().toISOString();
|
|
198
|
+
const voce = { id: randomUUID(), titolo, contenuto, genere, origine: daChi, creataAlle: ora, aggiornataAlle: ora };
|
|
199
|
+
await mkdirFn(cartella, { recursive: true });
|
|
200
|
+
await writeFileFn(percorsoDi(cartella, voce.id), JSON.stringify(voce, null, 2), 'utf8');
|
|
201
|
+
return { voce, duplicato: false };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Solo i campi passati cambiano. @throws {MemoryStoreError} MEMORY_NOT_FOUND se l'id non esiste. */
|
|
205
|
+
export async function aggiornaMemoria({ cartella, id, title, content, kind }, deps = {}) {
|
|
206
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
207
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
208
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
209
|
+
const clockFn = deps.clockFn ?? (() => new Date());
|
|
210
|
+
const voce = await leggiMemoria({ cartella, id }, { readFileFn });
|
|
211
|
+
if (!voce) throw new MemoryStoreError(`nessuna memoria con id ${id}`, 'MEMORY_NOT_FOUND');
|
|
212
|
+
if (title !== undefined) voce.titolo = validaTitolo(title);
|
|
213
|
+
if (content !== undefined) voce.contenuto = validaContenuto(content);
|
|
214
|
+
if (kind !== undefined) voce.genere = validaGenere(kind);
|
|
215
|
+
voce.aggiornataAlle = clockFn().toISOString();
|
|
216
|
+
await mkdirFn(cartella, { recursive: true });
|
|
217
|
+
await writeFileFn(percorsoDi(cartella, id), JSON.stringify(voce, null, 2), 'utf8');
|
|
218
|
+
return voce;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Cancellazione VERA (mobile stesso: `deleteMemory`, un hard delete) — idempotente, un id già assente non è un errore. */
|
|
222
|
+
export async function eliminaMemoria({ cartella, id }, deps = {}) {
|
|
223
|
+
const rmFn = deps.rmFn ?? fsp.rm;
|
|
224
|
+
const percorso = percorsoDi(cartella, id);
|
|
225
|
+
if (!percorso) return; // ⛔ 14/09: un id che non può nominare un file è già «assente» — no-op idempotente
|
|
226
|
+
await rmFn(percorso, { force: true });
|
|
227
|
+
}
|