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,189 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Cosa c'è davvero dentro la cartella che stai per dare a un agente.
|
|
3
|
+
*
|
|
4
|
+
* Decisioni del 04/09 sulla modale «Nuova sessione»:
|
|
5
|
+
* - F9 avvisa se la cartella scelta è una radice (il disco intero, la scrivania, la home);
|
|
6
|
+
* - F10 dice QUANTI file ha, prima di partire, non dopo;
|
|
7
|
+
* - F19 il ramo git corrente · F20 le modifiche non salvate · F21 i repo annidati.
|
|
8
|
+
* Nell'audit del 06/09 erano tutte e cinque ❌: la modale non diceva niente di tutto questo, e
|
|
9
|
+
* l'unico modo di scoprirlo era avviare una sessione e guardarla annaspare.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ Contare i file di una cartella enorme è ESSO STESSO il problema: VS Code, aprendo una cartella
|
|
12
|
+
* con milioni di voci, arriva a piantarsi, e la sua stessa guida dice di avvisare invece di
|
|
13
|
+
* espandere (microsoft/vscode #237394, #75004, letti 06/09/2026). Quindi qui si conta CON UN TETTO:
|
|
14
|
+
* si smette a `TETTO_FILE` e si risponde «più di N», che è esattamente l'informazione che serve —
|
|
15
|
+
* nessuno ha bisogno del numero esatto quando il numero è «troppi».
|
|
16
|
+
* ⛔ E si saltano le cartelle che non sono lavoro (`node_modules`, `.git`, cache): contarle darebbe
|
|
17
|
+
* un numero vero e inutile, e costerebbe la parte più lenta della scansione.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ I repo annidati non si scoprono da soli: VS Code li cerca esplicitamente e con un limite di
|
|
20
|
+
* profondità (microsoft/vscode #87888, #133577). Stessa scelta qui: profondità 3, che copre
|
|
21
|
+
* `mobile/`, `packages/*` e i vendor, senza camminare l'albero intero.
|
|
22
|
+
*/
|
|
23
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
24
|
+
import { join, parse, sep } from 'node:path';
|
|
25
|
+
import { spawn } from 'node:child_process';
|
|
26
|
+
|
|
27
|
+
export const TETTO_FILE = 20_000;
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
* I numeri si scrivono all'italiana con una funzione nostra, non con `toLocaleString`: la
|
|
31
|
+
* formattazione locale dipende dai dati ICU compilati in Node, e su un ambiente senza ICU completo
|
|
32
|
+
* «1234» resta «1234». Un separatore che a volte c'è e a volte no è peggio di nessun separatore.
|
|
33
|
+
*/
|
|
34
|
+
export function numeroItaliano(n) {
|
|
35
|
+
const v = Number(n);
|
|
36
|
+
if (!Number.isFinite(v)) return '0';
|
|
37
|
+
return String(Math.trunc(Math.abs(v))).replace(/\B(?=(\d{3})+(?!\d))/g, '.').replace(/^/, v < 0 ? '-' : '');
|
|
38
|
+
}
|
|
39
|
+
export const PROFONDITA_REPO_ANNIDATI = 3;
|
|
40
|
+
const SALTA = new Set(['node_modules', '.git', '.hg', '.svn', 'dist', 'build', 'out', '.next', '.cache', '.venv', 'venv', '__pycache__', '.gradle', 'target', 'vendor', '.turbo', '.parcel-cache']);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Una radice è una cartella che non si dà a un agente a cuor leggero: il disco intero, la home,
|
|
44
|
+
* la scrivania, i documenti. Non si vieta — si avvisa (F9).
|
|
45
|
+
*/
|
|
46
|
+
export function eUnaRadice(percorso) {
|
|
47
|
+
const grezzo = String(percorso || '');
|
|
48
|
+
if (!grezzo) return false;
|
|
49
|
+
const p = grezzo.replace(/[\/]+$/, '');
|
|
50
|
+
/*
|
|
51
|
+
* ⛔ misurato dalla prova: togliendo la barra finale «C:\» diventa «C:» e «/» diventa la stringa
|
|
52
|
+
* vuota, e nessuna delle due somigliava più a una radice. Le due forme si riconoscono PRIMA del resto.
|
|
53
|
+
*/
|
|
54
|
+
if (p === '' || /^[a-z]:$/i.test(p)) return true;
|
|
55
|
+
const analisi = parse(p);
|
|
56
|
+
if (analisi.root && (p + sep) === analisi.root) return true;
|
|
57
|
+
if (p === analisi.root) return true;
|
|
58
|
+
const ultimo = p.split(/[\\/]/).pop()?.toLowerCase() || '';
|
|
59
|
+
const nomiRadice = new Set(['desktop', 'scrivania', 'documents', 'documenti', 'downloads', 'download', 'users', 'home', 'utenti']);
|
|
60
|
+
if (nomiRadice.has(ultimo)) return true;
|
|
61
|
+
// la home dell'utente: due segmenti sotto la radice, sotto Users/home
|
|
62
|
+
const pezzi = p.split(/[\\/]/).filter(Boolean);
|
|
63
|
+
if (pezzi.length === 2 && /^(users|home|utenti)$/i.test(pezzi[0])) return true;
|
|
64
|
+
if (pezzi.length === 3 && /^[a-z]:$/i.test(pezzi[0]) && /^(users|utenti)$/i.test(pezzi[1])) return true;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Conta i file veri fino al tetto, saltando ciò che non è lavoro. Torna `{ file, cartelle, oltre }`. */
|
|
69
|
+
export async function contaFile(percorso, { tetto = TETTO_FILE, salta = SALTA } = {}) {
|
|
70
|
+
let file = 0; let cartelle = 0; let oltre = false;
|
|
71
|
+
const coda = [percorso];
|
|
72
|
+
while (coda.length > 0) {
|
|
73
|
+
const corrente = coda.shift();
|
|
74
|
+
let voci;
|
|
75
|
+
try { voci = await readdir(corrente, { withFileTypes: true }); } catch { continue; }
|
|
76
|
+
for (const voce of voci) {
|
|
77
|
+
if (voce.isDirectory()) {
|
|
78
|
+
if (salta.has(voce.name)) continue;
|
|
79
|
+
cartelle += 1;
|
|
80
|
+
coda.push(join(corrente, voce.name));
|
|
81
|
+
} else if (voce.isFile()) {
|
|
82
|
+
file += 1;
|
|
83
|
+
if (file >= tetto) { oltre = true; return { file, cartelle, oltre }; }
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { file, cartelle, oltre };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function git(argomenti, cwd) {
|
|
91
|
+
return new Promise((risolvi) => {
|
|
92
|
+
let uscita = '';
|
|
93
|
+
let processo;
|
|
94
|
+
try {
|
|
95
|
+
processo = spawn('git', argomenti, { cwd, windowsHide: true });
|
|
96
|
+
} catch { risolvi(null); return; }
|
|
97
|
+
const timer = setTimeout(() => { try { processo.kill(); } catch { /* già morto */ } risolvi(null); }, 4000);
|
|
98
|
+
processo.stdout?.on('data', (pezzo) => { uscita += String(pezzo); });
|
|
99
|
+
processo.on('error', () => { clearTimeout(timer); risolvi(null); });
|
|
100
|
+
processo.on('close', (codice) => { clearTimeout(timer); risolvi(codice === 0 ? uscita : null); });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Ramo, modifiche non salvate, repo annidati (F19-F21). Se non è un repo, torna `null` senza rumore. */
|
|
105
|
+
export async function statoGit(percorso, { profondita = PROFONDITA_REPO_ANNIDATI, eseguiGit = git } = {}) {
|
|
106
|
+
const dentro = await eseguiGit(['rev-parse', '--is-inside-work-tree'], percorso);
|
|
107
|
+
if (!dentro || dentro.trim() !== 'true') return null;
|
|
108
|
+
const ramo = (await eseguiGit(['rev-parse', '--abbrev-ref', 'HEAD'], percorso) || '').trim() || null;
|
|
109
|
+
const stato = await eseguiGit(['status', '--porcelain'], percorso);
|
|
110
|
+
const nonSalvate = stato === null ? null : stato.split('\n').filter((r) => r.trim()).length;
|
|
111
|
+
const annidati = await repoAnnidati(percorso, profondita);
|
|
112
|
+
return { ramo, nonSalvate, repoAnnidati: annidati };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Le cartelle che hanno un `.git` proprio, sotto quella scelta, fino alla profondità data. */
|
|
116
|
+
export async function repoAnnidati(percorso, profondita = PROFONDITA_REPO_ANNIDATI, livello = 1) {
|
|
117
|
+
if (livello > profondita) return [];
|
|
118
|
+
let voci;
|
|
119
|
+
try { voci = await readdir(percorso, { withFileTypes: true }); } catch { return []; }
|
|
120
|
+
const trovati = [];
|
|
121
|
+
for (const voce of voci) {
|
|
122
|
+
if (!voce.isDirectory() || SALTA.has(voce.name)) continue;
|
|
123
|
+
const figlio = join(percorso, voce.name);
|
|
124
|
+
let suo = false;
|
|
125
|
+
try { await stat(join(figlio, '.git')); suo = true; } catch { /* non è un repo */ }
|
|
126
|
+
if (suo) { trovati.push(figlio); continue; } // dentro un repo annidato non si scende oltre
|
|
127
|
+
trovati.push(...await repoAnnidati(figlio, profondita, livello + 1));
|
|
128
|
+
}
|
|
129
|
+
return trovati;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Le istruzioni dell'agente già presenti nella cartella: dicono che il progetto è già «abitato». */
|
|
133
|
+
export async function istruzioniPresenti(percorso) {
|
|
134
|
+
const nomi = ['CLAUDE.md', 'AGENTS.md', 'TALOS.md', '.talos'];
|
|
135
|
+
const trovate = [];
|
|
136
|
+
for (const nome of nomi) {
|
|
137
|
+
try { await stat(join(percorso, nome)); trovate.push(nome); } catch { /* non c'è */ }
|
|
138
|
+
}
|
|
139
|
+
return trovate;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Il ritratto completo della cartella, quello che la modale scrive prima di avviare.
|
|
144
|
+
* Non lancia mai: una cartella illeggibile torna `leggibile: false` invece di rompere la modale.
|
|
145
|
+
*/
|
|
146
|
+
export async function ritrattoCartella(percorso, opzioni = {}) {
|
|
147
|
+
const p = String(percorso || '');
|
|
148
|
+
if (!p) return { percorso: p, leggibile: false };
|
|
149
|
+
let esiste = false;
|
|
150
|
+
try { const s = await stat(p); esiste = s.isDirectory(); } catch { esiste = false; }
|
|
151
|
+
if (!esiste) return { percorso: p, leggibile: false };
|
|
152
|
+
const [conteggio, gitInfo, istruzioni] = await Promise.all([
|
|
153
|
+
contaFile(p, opzioni),
|
|
154
|
+
statoGit(p, opzioni).catch(() => null),
|
|
155
|
+
istruzioniPresenti(p).catch(() => []),
|
|
156
|
+
]);
|
|
157
|
+
return {
|
|
158
|
+
percorso: p,
|
|
159
|
+
leggibile: true,
|
|
160
|
+
radice: eUnaRadice(p),
|
|
161
|
+
file: conteggio.file,
|
|
162
|
+
cartelle: conteggio.cartelle,
|
|
163
|
+
oltreIlTetto: conteggio.oltre,
|
|
164
|
+
tetto: opzioni.tetto ?? TETTO_FILE,
|
|
165
|
+
git: gitInfo,
|
|
166
|
+
istruzioni,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** La frase che la modale mostra: una riga, in italiano, coi numeri veri. */
|
|
171
|
+
export function frasiRitratto(ritratto) {
|
|
172
|
+
if (!ritratto || !ritratto.leggibile) return 'Non riesco a leggere questa cartella.';
|
|
173
|
+
const pezzi = [];
|
|
174
|
+
pezzi.push(ritratto.oltreIlTetto ? `più di ${numeroItaliano(ritratto.tetto)} file` : `${numeroItaliano(ritratto.file)} file`);
|
|
175
|
+
if (ritratto.cartelle > 0) pezzi.push(`${numeroItaliano(ritratto.cartelle)} cartelle`);
|
|
176
|
+
if (ritratto.git?.ramo) pezzi.push(`ramo ${ritratto.git.ramo}`);
|
|
177
|
+
if (Number.isFinite(ritratto.git?.nonSalvate)) pezzi.push(ritratto.git.nonSalvate === 0 ? 'niente da salvare' : `${ritratto.git.nonSalvate} modifiche non salvate`);
|
|
178
|
+
if (ritratto.git?.repoAnnidati?.length) pezzi.push(`${ritratto.git.repoAnnidati.length} repo annidati`);
|
|
179
|
+
if (ritratto.istruzioni?.length) pezzi.push(`istruzioni: ${ritratto.istruzioni.join(', ')}`);
|
|
180
|
+
return pezzi.join(' · ');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** L'avviso, quando serve: una frase che dice cosa succede se parti così. */
|
|
184
|
+
export function avvisoRitratto(ritratto) {
|
|
185
|
+
if (!ritratto || !ritratto.leggibile) return '';
|
|
186
|
+
if (ritratto.radice) return 'Questa è una cartella radice: l’agente vedrebbe tutto quello che c’è sotto. Scegli il progetto, non il disco.';
|
|
187
|
+
if (ritratto.oltreIlTetto) return `Qui ci sono più di ${numeroItaliano(ritratto.tetto)} file: l’albero pesa a ogni giro. Se puoi, scegli una sottocartella.`;
|
|
188
|
+
return '';
|
|
189
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import {
|
|
2
|
+
accessSync,
|
|
3
|
+
constants,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
realpathSync,
|
|
7
|
+
statSync,
|
|
8
|
+
writeFileSync,
|
|
9
|
+
} from 'node:fs';
|
|
10
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
11
|
+
import { basename, dirname, isAbsolute } from 'node:path';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TTL_MS = 15 * 60_000;
|
|
14
|
+
const TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
15
|
+
const INTENT_PATTERN = /^[A-Za-z0-9_-]{32}$/;
|
|
16
|
+
const MAX_PATH_BYTES = 4096;
|
|
17
|
+
|
|
18
|
+
export class WorkspaceLaunchError extends Error {
|
|
19
|
+
constructor(message, code) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'WorkspaceLaunchError';
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function nowMs(clock) {
|
|
27
|
+
const value = clock();
|
|
28
|
+
return value instanceof Date ? value.getTime() : Number(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ensureCredential({ credentialFile, randomBytesFn }) {
|
|
32
|
+
let value;
|
|
33
|
+
try {
|
|
34
|
+
value = readFileSync(credentialFile, 'utf8').trim();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error?.code !== 'ENOENT') {
|
|
37
|
+
throw new WorkspaceLaunchError('La credenziale del launcher non è leggibile', 'WORKSPACE_LAUNCH_CONFIG_INVALID');
|
|
38
|
+
}
|
|
39
|
+
mkdirSync(dirname(credentialFile), { recursive: true });
|
|
40
|
+
const generated = randomBytesFn(32).toString('hex');
|
|
41
|
+
try {
|
|
42
|
+
writeFileSync(credentialFile, `${generated}\n`, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
43
|
+
value = generated;
|
|
44
|
+
} catch (writeError) {
|
|
45
|
+
if (writeError?.code !== 'EEXIST') {
|
|
46
|
+
throw new WorkspaceLaunchError('La credenziale del launcher non può essere salvata', 'WORKSPACE_LAUNCH_CONFIG_INVALID');
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
value = readFileSync(credentialFile, 'utf8').trim();
|
|
50
|
+
} catch {
|
|
51
|
+
throw new WorkspaceLaunchError('La credenziale del launcher non è leggibile', 'WORKSPACE_LAUNCH_CONFIG_INVALID');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!TOKEN_PATTERN.test(value)) {
|
|
56
|
+
throw new WorkspaceLaunchError('La credenziale del launcher è danneggiata', 'WORKSPACE_LAUNCH_CONFIG_INVALID');
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function authenticated(expected, candidate) {
|
|
62
|
+
if (typeof candidate !== 'string' || !TOKEN_PATTERN.test(candidate)) return false;
|
|
63
|
+
const expectedBytes = Buffer.from(expected, 'hex');
|
|
64
|
+
const candidateBytes = Buffer.from(candidate, 'hex');
|
|
65
|
+
return expectedBytes.length === candidateBytes.length && timingSafeEqual(expectedBytes, candidateBytes);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function resolveWorkspace(percorso) {
|
|
69
|
+
if (typeof percorso !== 'string' || percorso.trim() === '' || Buffer.byteLength(percorso, 'utf8') > MAX_PATH_BYTES || !isAbsolute(percorso)) {
|
|
70
|
+
throw new WorkspaceLaunchError('Scegli una cartella valida e riprova', 'WORKSPACE_NOT_AVAILABLE');
|
|
71
|
+
}
|
|
72
|
+
let reale;
|
|
73
|
+
try {
|
|
74
|
+
reale = realpathSync(percorso);
|
|
75
|
+
if (!statSync(reale).isDirectory()) throw new Error('not-directory');
|
|
76
|
+
accessSync(reale, constants.R_OK | constants.W_OK);
|
|
77
|
+
} catch {
|
|
78
|
+
throw new WorkspaceLaunchError('La cartella non è disponibile o non consente di lavorarci', 'WORKSPACE_NOT_AVAILABLE');
|
|
79
|
+
}
|
|
80
|
+
return reale;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Intenzioni brevi e opache fra il launcher Windows e la nuova sessione.
|
|
85
|
+
* Il percorso assoluto non attraversa mai URL, localStorage o risposta HTTP.
|
|
86
|
+
*/
|
|
87
|
+
export function createWorkspaceLaunchStore({
|
|
88
|
+
credentialFile,
|
|
89
|
+
ttlMs = DEFAULT_TTL_MS,
|
|
90
|
+
clock = Date.now,
|
|
91
|
+
randomBytesFn = randomBytes,
|
|
92
|
+
} = {}) {
|
|
93
|
+
if (typeof credentialFile !== 'string' || credentialFile.trim() === '' || !Number.isSafeInteger(ttlMs) || ttlMs < 1_000) {
|
|
94
|
+
throw new WorkspaceLaunchError('Configurazione launcher non valida', 'WORKSPACE_LAUNCH_CONFIG_INVALID');
|
|
95
|
+
}
|
|
96
|
+
const credential = ensureCredential({ credentialFile, randomBytesFn });
|
|
97
|
+
const intents = new Map();
|
|
98
|
+
|
|
99
|
+
function publicView(intent) {
|
|
100
|
+
return { id: intent.id, nome: intent.nome, scadeAlle: new Date(intent.expiresAt).toISOString() };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function cleanup() {
|
|
104
|
+
const current = nowMs(clock);
|
|
105
|
+
for (const [id, intent] of intents) {
|
|
106
|
+
if (intent.expiresAt <= current) intents.delete(id);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function get(id) {
|
|
111
|
+
cleanup();
|
|
112
|
+
if (typeof id !== 'string' || !INTENT_PATTERN.test(id) || !intents.has(id)) {
|
|
113
|
+
throw new WorkspaceLaunchError('Il collegamento non è più disponibile. Usa di nuovo “Apri cartella con TALOS”.', 'WORKSPACE_LAUNCH_NOT_AVAILABLE');
|
|
114
|
+
}
|
|
115
|
+
return intents.get(id);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
create({ percorso, credential: candidate } = {}) {
|
|
120
|
+
if (!authenticated(credential, candidate)) {
|
|
121
|
+
throw new WorkspaceLaunchError('Il comando locale non è autorizzato', 'WORKSPACE_LAUNCH_UNAUTHORIZED');
|
|
122
|
+
}
|
|
123
|
+
const reale = resolveWorkspace(percorso);
|
|
124
|
+
cleanup();
|
|
125
|
+
let id;
|
|
126
|
+
do { id = randomBytesFn(24).toString('base64url'); } while (intents.has(id));
|
|
127
|
+
const intent = Object.freeze({
|
|
128
|
+
id,
|
|
129
|
+
nome: basename(reale) || reale,
|
|
130
|
+
percorso: reale,
|
|
131
|
+
expiresAt: nowMs(clock) + ttlMs,
|
|
132
|
+
});
|
|
133
|
+
intents.set(id, intent);
|
|
134
|
+
return publicView(intent);
|
|
135
|
+
},
|
|
136
|
+
inspect(id) {
|
|
137
|
+
return publicView(get(id));
|
|
138
|
+
},
|
|
139
|
+
resolve(id) {
|
|
140
|
+
const intent = get(id);
|
|
141
|
+
return { ...publicView(intent), percorso: intent.percorso };
|
|
142
|
+
},
|
|
143
|
+
consume(id) {
|
|
144
|
+
cleanup();
|
|
145
|
+
if (typeof id !== 'string' || !INTENT_PATTERN.test(id)) return false;
|
|
146
|
+
return intents.delete(id);
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace-tree.mjs — l'albero file REALE di una sessione (piano
|
|
3
|
+
* `elegant-spinning-dongarra.md`, FASE 1, §1.3, riga "Contesto workspace").
|
|
4
|
+
*
|
|
5
|
+
* L'accesso al disco passa dall'adapter locale `workspace-disk.mjs`: nessun
|
|
6
|
+
* import da repository esterni e una sola policy di elenco, provata sul disco
|
|
7
|
+
* reale. Il contratto `{nome, cartella}` resta compatibile con il vecchio
|
|
8
|
+
* `discoNode` così i chiamanti e i test non cambiano.
|
|
9
|
+
*
|
|
10
|
+
* ⭐ UN livello alla volta, mai un dump ricorsivo intero: la UI espande le
|
|
11
|
+
* cartelle a richiesta (`percorso` sale un pezzo per volta). Stessa lezione
|
|
12
|
+
* di `talos-non-vede-i-file-del-corpus-storia` in memoria — un elenco
|
|
13
|
+
* piatto esplode in byte prima di essere utile a chi guarda.
|
|
14
|
+
*
|
|
15
|
+
* ⛔⛔ `percorso` arriva da una query string HTTP, non da un modello
|
|
16
|
+
* cooperativo dentro un tool call: `discoNode.elenca()` stesso NON sanifica
|
|
17
|
+
* `..` o percorsi assoluti (verificato leggendo dist/kernelPerIlBanco.js —
|
|
18
|
+
* usa `path.join` puro, che non contiene la risalita). Qui il controllo è
|
|
19
|
+
* ESPLICITO — stesso `isPathInside` già in path-policy.mjs, importato, non
|
|
20
|
+
* duplicato — perché un endpoint HTTP è raggiungibile da chiunque tocchi il
|
|
21
|
+
* loopback, non solo da un modello che gioca secondo le regole.
|
|
22
|
+
*/
|
|
23
|
+
import { realpathSync } from 'node:fs';
|
|
24
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
25
|
+
|
|
26
|
+
import { isPathInside } from './path-policy.mjs';
|
|
27
|
+
import { createWorkspaceDisk } from './workspace-disk.mjs';
|
|
28
|
+
|
|
29
|
+
export class WorkspaceTreeError extends Error {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'WorkspaceTreeError';
|
|
33
|
+
this.code = 'QUERY_INVALID';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} input
|
|
39
|
+
* @param {string} input.cartella — radice della sessione (già allowlisted a monte da task-catalog.preparaEsecuzione)
|
|
40
|
+
* @param {string} [input.percorso] — sottocartella relativa da elencare; '' = radice
|
|
41
|
+
* @param {object} [deps]
|
|
42
|
+
* @param {Function} [deps.discoNodeFn] — SOLO per compatibilità/test; il default è l'adapter locale
|
|
43
|
+
* @returns {Promise<Array<{nome:string, cartella:boolean}>>} cartelle prima, poi file, ciascuno alfabetico
|
|
44
|
+
*/
|
|
45
|
+
export async function leggiAlberoWorkspace({ cartella, percorso = '' }, { discoNodeFn } = {}) {
|
|
46
|
+
if (typeof percorso !== 'string' || percorso.includes('\0') || isAbsolute(percorso)) {
|
|
47
|
+
throw new WorkspaceTreeError('Percorso non valido');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let voci;
|
|
51
|
+
try {
|
|
52
|
+
const radiceReale = realpathSync(cartella);
|
|
53
|
+
const candidatoReale = percorso === '' ? radiceReale : realpathSync(resolve(cartella, percorso));
|
|
54
|
+
if (!isPathInside(radiceReale, candidatoReale)) {
|
|
55
|
+
throw new WorkspaceTreeError('Percorso fuori dalla cartella della sessione');
|
|
56
|
+
}
|
|
57
|
+
const disco = (discoNodeFn ?? (({ radice }) => createWorkspaceDisk({ rootDir: radice })) )({ radice: cartella });
|
|
58
|
+
voci = await disco.elenca(percorso);
|
|
59
|
+
} catch (errore) {
|
|
60
|
+
if (errore instanceof WorkspaceTreeError) throw errore;
|
|
61
|
+
throw new WorkspaceTreeError('Percorso non leggibile');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return voci
|
|
65
|
+
.map((v) => ({ nome: v.nome, cartella: Boolean(v.cartella) }))
|
|
66
|
+
.sort((a, b) => (a.cartella === b.cartella ? a.nome.localeCompare(b.nome, 'en') : a.cartella ? -1 : 1));
|
|
67
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workspace-watcher.mjs — segnala quando i file di un workspace
|
|
3
|
+
* cambiano FUORI dall'app (Explorer, un editor, git...). Piano
|
|
4
|
+
* `elegant-spinning-dongarra.md`, owner 27/8: "se muovo i file il
|
|
5
|
+
* work tree non si aggiorna automaticamente".
|
|
6
|
+
*
|
|
7
|
+
* ⛔ Ricerca fatta PRIMA di scrivere (28/8, REGOLA ZERO): `fs.watch`
|
|
8
|
+
* nativo di Node ha difetti reali anche su Windows (più eventi per
|
|
9
|
+
* salvataggio e `filename` non sempre presente), quindi debounce e
|
|
10
|
+
* normalizzazione restano qui. Il profilo reale del 1/9 ha però
|
|
11
|
+
* invalidato l'uso di Chokidar per QUESTO percorso ricorsivo: oltre
|
|
12
|
+
* 255 mila handle e un core occupato a riposo dopo l'attivazione su un
|
|
13
|
+
* workspace grande. Node 24 usa `ReadDirectoryChangesW` per la
|
|
14
|
+
* ricorsione Windows con una registrazione nativa e supporta `ignore`,
|
|
15
|
+
* `AbortSignal` e `persistent:false`: il contratto upstream viene
|
|
16
|
+
* adottato direttamente dietro questo adapter TALOS.
|
|
17
|
+
*
|
|
18
|
+
* ⭐ Un watcher per CARTELLA, non per sessione: due sessioni sulla
|
|
19
|
+
* stessa cartella (un fork, un resume) condividono un solo watcher —
|
|
20
|
+
* conteggio dei sottoscrittori, chiuso quando l'ultimo si disiscrive.
|
|
21
|
+
* Mai duplicare un handle di sistema operativo per la stessa cartella.
|
|
22
|
+
*
|
|
23
|
+
* ⭐ `.git`/`node_modules` esclusi dal WATCH (non dalla vista
|
|
24
|
+
* dell'albero, che li mostra già se aperti a mano): sono le due
|
|
25
|
+
* cartelle a più alto churn di qualunque progetto reale — un `git
|
|
26
|
+
* status`/`npm install` rifarebbe scattare l'albero decine di volte
|
|
27
|
+
* per un cambiamento che l'utente non è mai lì a guardare.
|
|
28
|
+
*
|
|
29
|
+
* ⭐ Debounce IBRIDO (ricerca 28/8): il timer si resetta ad ogni nuovo
|
|
30
|
+
* evento (raffiche di scritture diventano UN solo segnale), ma un
|
|
31
|
+
* tetto massimo forza comunque un invio anche durante una raffica
|
|
32
|
+
* lunghissima — mai un'attesa indefinita.
|
|
33
|
+
*/
|
|
34
|
+
import { watch as watchReale } from 'node:fs';
|
|
35
|
+
import { dirname, isAbsolute, parse, relative, resolve, sep } from 'node:path';
|
|
36
|
+
|
|
37
|
+
const DEBOUNCE_MS = 400;
|
|
38
|
+
const TETTO_MASSIMO_MS = 2_000;
|
|
39
|
+
/*
|
|
40
|
+
* Stato interno del server: viene scritto mentre una sessione è in corso.
|
|
41
|
+
* Se il workspace scelto è la cartella Harness (o la radice del worktree),
|
|
42
|
+
* osservarlo produrrebbe un feedback continuo: l'evento aggiorna la sessione,
|
|
43
|
+
* la sessione scrive il proprio registro e il registro riattiva il watcher.
|
|
44
|
+
* Queste directory non fanno parte dell'albero del progetto dell'owner e
|
|
45
|
+
* restano leggibili tramite le loro API dedicate, quindi sono escluse dal
|
|
46
|
+
* refresh automatico allo stesso modo di `.git` e `node_modules`.
|
|
47
|
+
*/
|
|
48
|
+
const IGNORATI = [
|
|
49
|
+
/(^|[/\\])\.git([/\\]|$)/,
|
|
50
|
+
/(^|[/\\])node_modules([/\\]|$)/,
|
|
51
|
+
/(^|[/\\])\.(?:sessions-store|automations|generated-images|local-models|hooks-trust|mcp-trust|plugin-trust|notes-store|tasks-store|memory-store|tool-forge-store)([/\\]|$)/,
|
|
52
|
+
/(^|[/\\])\.provider-runtime\.json$/,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function eRadiceVolume(cartella) {
|
|
56
|
+
const assoluta = resolve(cartella);
|
|
57
|
+
const radice = parse(assoluta).root;
|
|
58
|
+
return assoluta === radice || dirname(assoluta) === assoluta;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const guardati = new Map(); // cartella -> { watcher, controller, sottoscrittori: Set<fn>, timerDebounce, primoEventoRaffica, percorsiInSospeso }
|
|
62
|
+
|
|
63
|
+
function percorsoRelativoEvento(cartella, filename) {
|
|
64
|
+
if (filename === null || filename === undefined) return '.';
|
|
65
|
+
const grezzo = Buffer.isBuffer(filename) ? filename.toString('utf8') : String(filename);
|
|
66
|
+
if (grezzo.length === 0) return '.';
|
|
67
|
+
const assoluto = isAbsolute(grezzo) ? resolve(grezzo) : resolve(cartella, grezzo);
|
|
68
|
+
const relativo = relative(resolve(cartella), assoluto);
|
|
69
|
+
if (relativo === '' || relativo === '..' || relativo.startsWith(`..${sep}`)) return '.';
|
|
70
|
+
return relativo.split(/[/\\]+/).join('/');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function percorsoIgnorato(percorso) {
|
|
74
|
+
return percorso !== '.' && IGNORATI.some((rx) => rx.test(percorso));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function creaGestoreWorkspaceWatcher({ watchFn = watchReale } = {}) {
|
|
78
|
+
/**
|
|
79
|
+
* Sottoscrive `onCambiamento(percorsiRelativi)` ai cambiamenti REALI
|
|
80
|
+
* (mai quelli della scansione iniziale — `ready` li separa) di
|
|
81
|
+
* `cartella`. Torna una funzione di disiscrizione — MAI un throw:
|
|
82
|
+
* un watcher che fallisce ad avviarsi (cartella inesistente,
|
|
83
|
+
* permessi) degrada a "nessun refresh automatico", non rompe la
|
|
84
|
+
* sessione che lo ha chiesto.
|
|
85
|
+
*/
|
|
86
|
+
function guardaWorkspace(cartella, onCambiamento) {
|
|
87
|
+
/*
|
|
88
|
+
* Full access può indicare una radice come `C:\\`. Anche il watcher nativo
|
|
89
|
+
* ricorsivo non deve trasformare l’intero volume in una sorgente di eventi
|
|
90
|
+
* permanente prima che il modello riceva il primo messaggio. La radice resta
|
|
91
|
+
* leggibile a richiesta tramite workspace-tree, ma non ha refresh automatico:
|
|
92
|
+
* stato onesto e fail-closed, coerente con il lazy tree.
|
|
93
|
+
*/
|
|
94
|
+
if (eRadiceVolume(cartella)) return () => {};
|
|
95
|
+
let voce = guardati.get(cartella);
|
|
96
|
+
if (!voce) {
|
|
97
|
+
voce = { watcher: null, controller: null, sottoscrittori: new Set(), timerDebounce: null, primoEventoRaffica: 0, percorsiInSospeso: new Set() };
|
|
98
|
+
guardati.set(cartella, voce);
|
|
99
|
+
try {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const watcher = watchFn(cartella, {
|
|
102
|
+
recursive: true,
|
|
103
|
+
persistent: false,
|
|
104
|
+
encoding: 'utf8',
|
|
105
|
+
ignore: IGNORATI,
|
|
106
|
+
signal: controller.signal,
|
|
107
|
+
}, (_evento, filename) => {
|
|
108
|
+
const relativo = percorsoRelativoEvento(cartella, filename);
|
|
109
|
+
// Il filtro nativo riduce gli eventi a monte; questo secondo guard
|
|
110
|
+
// difende adapter finti, backend OS e versioni Node non omogenei.
|
|
111
|
+
if (percorsoIgnorato(relativo)) return;
|
|
112
|
+
voce.percorsiInSospeso.add(relativo);
|
|
113
|
+
const adesso = Date.now();
|
|
114
|
+
if (!voce.timerDebounce) voce.primoEventoRaffica = adesso;
|
|
115
|
+
clearTimeout(voce.timerDebounce);
|
|
116
|
+
const restaSulTetto = TETTO_MASSIMO_MS - (adesso - voce.primoEventoRaffica);
|
|
117
|
+
const attesa = Math.max(0, Math.min(DEBOUNCE_MS, restaSulTetto));
|
|
118
|
+
voce.timerDebounce = setTimeout(() => {
|
|
119
|
+
const percorsi = [...voce.percorsiInSospeso];
|
|
120
|
+
voce.percorsiInSospeso.clear();
|
|
121
|
+
voce.timerDebounce = null;
|
|
122
|
+
/*
|
|
123
|
+
* ⛔⛔⛔ 28/8, trovato dal vivo: un `for...of` senza try/catch
|
|
124
|
+
* ABORTISCE l'intero giro al primo sottoscrittore che lancia —
|
|
125
|
+
* i sottoscrittori DOPO quello (in ordine di iscrizione, quindi
|
|
126
|
+
* le sessioni create PIÙ DI RECENTE sulla stessa cartella) non
|
|
127
|
+
* venivano mai notificati. Ogni sottoscrittore è isolato dagli
|
|
128
|
+
* altri, come broadcast() già fa per i propri ascoltatori.
|
|
129
|
+
*/
|
|
130
|
+
for (const s of voce.sottoscrittori) {
|
|
131
|
+
try { s(percorsi); } catch { /* un sottoscrittore rotto non deve mai bloccare gli altri */ }
|
|
132
|
+
}
|
|
133
|
+
}, attesa);
|
|
134
|
+
});
|
|
135
|
+
watcher.on?.('error', () => { /* ⛔ degrado silenzioso per costruzione — vedi doc sopra: mai un throw che romperebbe la sessione */ });
|
|
136
|
+
watcher.unref?.();
|
|
137
|
+
voce.watcher = watcher;
|
|
138
|
+
voce.controller = controller;
|
|
139
|
+
} catch { /* stesso principio: nessun refresh automatico, non un guasto */ }
|
|
140
|
+
}
|
|
141
|
+
voce.sottoscrittori.add(onCambiamento);
|
|
142
|
+
return () => {
|
|
143
|
+
voce.sottoscrittori.delete(onCambiamento);
|
|
144
|
+
if (voce.sottoscrittori.size === 0) {
|
|
145
|
+
clearTimeout(voce.timerDebounce);
|
|
146
|
+
voce.controller?.abort();
|
|
147
|
+
try { voce.watcher?.close(); } catch { /* la chiusura via signal può averlo già chiuso */ }
|
|
148
|
+
guardati.delete(cartella);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Solo per i test — mai chiamata da codice di produzione. */
|
|
154
|
+
function quantiWatcherAttiviPerTest() {
|
|
155
|
+
return guardati.size;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { guardaWorkspace, quantiWatcherAttiviPerTest };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const { guardaWorkspace } = creaGestoreWorkspaceWatcher();
|