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,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-catalog.mjs — quali task sono LANCIABILI da Harness UI, e come
|
|
3
|
+
* ottenere una cartella vera per farli girare. Piano
|
|
4
|
+
* `elegant-spinning-dongarra.md`, FASE 1, §1.5 (Opzione A).
|
|
5
|
+
*
|
|
6
|
+
* ⛔⛔ ALLOWLIST STRETTA, stesso principio di `path-policy.mjs`: un id che non
|
|
7
|
+
* è testualmente uno dei task del corpus non produce MAI una cartella. Prima
|
|
8
|
+
* di questo file, Harness UI era read-only per costruzione (`http-app.mjs`
|
|
9
|
+
* rifiuta ogni metodo che non sia GET/HEAD) — questo è il PRIMO punto dove
|
|
10
|
+
* un browser può far succedere qualcosa di reale sul disco, quindi l'elenco
|
|
11
|
+
* ammesso non è "qualunque cartella passi", è SOLO ciò che è già nel corpus.
|
|
12
|
+
*
|
|
13
|
+
* Il catalogo non possiede più un corpus implicito: riceve un provider
|
|
14
|
+
* esplicito dal runtime configurato. Senza provider il risultato è
|
|
15
|
+
* `TASK_CATALOG_UNAVAILABLE`, non una fixture o un import da un checkout
|
|
16
|
+
* fratello. Questo mantiene la promessa di portabilità del desktop.
|
|
17
|
+
*/
|
|
18
|
+
export class TaskCatalogError extends Error {
|
|
19
|
+
constructor(message, code = 'TASK_NOT_ALLOWED') {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'TaskCatalogError';
|
|
22
|
+
this.code = code;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function providerRequired(provider) {
|
|
27
|
+
if (!provider || typeof provider.list !== 'function' || typeof provider.prepare !== 'function') {
|
|
28
|
+
throw new TaskCatalogError('Il catalogo task non è disponibile in questa installazione. Configura il runtime proprietario.', 'TASK_CATALOG_UNAVAILABLE');
|
|
29
|
+
}
|
|
30
|
+
return provider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Un elenco LEGGERO — mai il corpo intero della consegna, che può essere
|
|
35
|
+
* lungo (i task storia, quando arriveranno, elencano casi di test interi).
|
|
36
|
+
* Chi avvia una sessione riceve la consegna vera da `preparaEsecuzione`, non
|
|
37
|
+
* da qui: questa funzione è per un menu, non per lanciare niente.
|
|
38
|
+
*/
|
|
39
|
+
export function listaTaskDisponibili(provider) {
|
|
40
|
+
return providerRequired(provider).list().map((task) => ({
|
|
41
|
+
id: task.id,
|
|
42
|
+
progetto: task.progetto,
|
|
43
|
+
difficolta: task.difficolta,
|
|
44
|
+
consegnaCorta: task.consegnaCorta,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Da un id ammesso a una cartella VERA e usa-e-getta, pronta per
|
|
50
|
+
* `talosLavora`. Lancia `TaskCatalogError` su qualunque id che non sia
|
|
51
|
+
* ESATTAMENTE uno dei task del corpus — nessuna normalizzazione, nessun
|
|
52
|
+
* fallback: lo stesso stile di `assertSafeSegment` in `path-policy.mjs`.
|
|
53
|
+
*
|
|
54
|
+
* @returns {{cartella:string, comandoProva:string, task:object, pulisci:()=>void}}
|
|
55
|
+
*/
|
|
56
|
+
export function preparaEsecuzione(taskId, provider) {
|
|
57
|
+
if (typeof taskId !== 'string' || taskId.length === 0) {
|
|
58
|
+
throw new TaskCatalogError('Id task non valido', 'QUERY_INVALID');
|
|
59
|
+
}
|
|
60
|
+
const risultato = providerRequired(provider).prepare(taskId);
|
|
61
|
+
if (!risultato || typeof risultato !== 'object' || typeof risultato.cartella !== 'string' || !risultato.task) {
|
|
62
|
+
throw new TaskCatalogError('Il catalogo task ha restituito una sessione non valida.', 'TASK_CATALOG_INVALID');
|
|
63
|
+
}
|
|
64
|
+
return risultato;
|
|
65
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tasks-store.mjs — FASE N, quinto 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
|
+
* (letto alla fonte: `mobile/src/lib/tools/tasksWriteTools.ts`+
|
|
8
|
+
* `readTools.ts`, `mobile/src/persistence/chatDatabaseSchema.ts`).
|
|
9
|
+
* "Ricordami di chiamare l'idraulico" non riguarda nessun progetto di
|
|
10
|
+
* codice — un desktop che rendesse le attività per-progetto le
|
|
11
|
+
* renderebbe invisibili da qualunque altra sessione. Storage accanto a
|
|
12
|
+
* `server.mjs`, stesso pattern REALE di `.automations/`/`.notes-store/`.
|
|
13
|
+
*
|
|
14
|
+
* ⛔ `schedule_json`/`instruction`/`last_run_at` (schema mobile
|
|
15
|
+
* `talos_tasks`) NON portati: colonne presenti nello schema mobile ma
|
|
16
|
+
* NON esposte da NESSUN tool mobile oggi ("pianificare aggiungerà un
|
|
17
|
+
* campo, non un'altra entità" — mai arrivato, verificato leggendo
|
|
18
|
+
* `TalosTasksWriteSources` per intero, zero riferimento a schedule).
|
|
19
|
+
* Harness Desktop ha già un proprio sistema di scheduling (Automazioni,
|
|
20
|
+
* `automation-store.mjs`, chiuso 27/8) — quando/se un task deve
|
|
21
|
+
* ripartire da solo, quella è la casa giusta, non una seconda qui.
|
|
22
|
+
*
|
|
23
|
+
* ⛔ Stessa CONVENZIONE DI CHIAMATA di `notes-store.mjs`/`library-store.mjs`
|
|
24
|
+
* (`funzione({cartella, ...}, deps={})`), per lo stesso motivo: va
|
|
25
|
+
* agganciato negli stessi punti di contatto I/O iniettabili di
|
|
26
|
+
* `agent-service.mjs`.
|
|
27
|
+
*
|
|
28
|
+
* ⛔ Un file JSON per attività (non un unico array) — stessa classe di
|
|
29
|
+
* bug (riscrittura invece di accodamento) già evitata altrove in
|
|
30
|
+
* questo progetto (session-store.mjs).
|
|
31
|
+
*/
|
|
32
|
+
import { randomUUID } from 'node:crypto';
|
|
33
|
+
import { promises as fsp } from 'node:fs';
|
|
34
|
+
import { join } from 'node:path';
|
|
35
|
+
import { idArchivioValido } from './id-archivio.mjs';
|
|
36
|
+
|
|
37
|
+
const TITOLO_MASSIMO = 200;
|
|
38
|
+
const DESCRIZIONE_MASSIMA = 2_000;
|
|
39
|
+
const PRIORITA = Object.freeze(['low', 'normal', 'high']);
|
|
40
|
+
const STATI = Object.freeze(['todo', 'doing', 'done']);
|
|
41
|
+
|
|
42
|
+
/** ⭐ Stesso pattern REALE di `.automations/`/`.notes-store/`: il nome che `server.mjs` userà per il percorso di default, esportato per non duplicarlo lì. */
|
|
43
|
+
export const CARTELLA_ATTIVITA = '.tasks-store';
|
|
44
|
+
|
|
45
|
+
export class TaskStoreError extends Error {
|
|
46
|
+
constructor(message, code = 'TASK_INVALID') {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = 'TaskStoreError';
|
|
49
|
+
this.code = code;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function percorsoDi(cartella, id) {
|
|
54
|
+
// ⛔ 14/09: l'id arriva da fuori (indirizzo HTTP, attrezzo del modello) — un id fuori grammatica non nomina nessun file e
|
|
55
|
+
// torna `null`, mai un `join` che con `..` uscirebbe dalla cartella. Vedi id-archivio.mjs.
|
|
56
|
+
if (!idArchivioValido(id)) return null;
|
|
57
|
+
return join(cartella, `${id}.json`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validaTitolo(title) {
|
|
61
|
+
if (typeof title !== 'string' || title.trim().length === 0 || title.length > TITOLO_MASSIMO) {
|
|
62
|
+
throw new TaskStoreError(`title deve avere 1-${TITOLO_MASSIMO} caratteri`, 'TASK_INVALID');
|
|
63
|
+
}
|
|
64
|
+
return title.trim();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** `null`/assente sono la stessa cosa (nessun dettaglio) — una descrizione fatta di soli spazi diventa `null`, stesso principio del tool mobile. */
|
|
68
|
+
function validaDescrizione(description) {
|
|
69
|
+
if (description === undefined || description === null) return null;
|
|
70
|
+
if (typeof description !== 'string' || description.length > DESCRIZIONE_MASSIMA) {
|
|
71
|
+
throw new TaskStoreError(`description deve avere al massimo ${DESCRIZIONE_MASSIMA} caratteri`, 'TASK_INVALID');
|
|
72
|
+
}
|
|
73
|
+
return description.trim() || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validaPriorita(priority) {
|
|
77
|
+
if (!PRIORITA.includes(priority)) {
|
|
78
|
+
throw new TaskStoreError(`priority deve essere una fra ${PRIORITA.join('/')}`, 'TASK_INVALID');
|
|
79
|
+
}
|
|
80
|
+
return priority;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validaStato(status) {
|
|
84
|
+
if (!STATI.includes(status)) {
|
|
85
|
+
throw new TaskStoreError(`status deve essere uno fra ${STATI.join('/')}`, 'TASK_INVALID');
|
|
86
|
+
}
|
|
87
|
+
return status;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* ⛔ 11/09 — CHI ha scritto questa attività, stesso identico principio (e stesso default) di
|
|
92
|
+
* `ORIGINI` in notes-store.mjs: due porte sole — gli attrezzi `tasks_*` del modello e le
|
|
93
|
+
* rotte HTTP della persona — e ciò che è già sul disco viene tutto dal modello, perché fino
|
|
94
|
+
* a oggi la persona non aveva una porta per scrivere.
|
|
95
|
+
*/
|
|
96
|
+
const ORIGINI = Object.freeze(['persona', 'modello']);
|
|
97
|
+
|
|
98
|
+
function validaOrigine(origine) {
|
|
99
|
+
if (!ORIGINI.includes(origine)) {
|
|
100
|
+
throw new TaskStoreError(`origine deve essere una fra ${ORIGINI.join('/')}`, 'TASK_INVALID');
|
|
101
|
+
}
|
|
102
|
+
return origine;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* La forma PUBBLICA di un'attività — l'unica che esce da questo prodotto.
|
|
107
|
+
* ⛔ `fatta` è CALCOLATA da `stato`, mai scritta sul disco: due campi che dicono la stessa cosa
|
|
108
|
+
* e possono divergere sono un difetto, non una comodità. Esce dalla porta perché un pannello
|
|
109
|
+
* ha bisogno di una casella da spuntare, ma la verità resta una sola (`stato`).
|
|
110
|
+
* ⛔ Nessuna `scadenza`: non esiste in questo magazzino e non esiste in nessun attrezzo del
|
|
111
|
+
* modello (vedi l'intestazione di questo file sulle colonne mobile NON portate). Inventarla
|
|
112
|
+
* qui vorrebbe dire un campo che solo la persona può scrivere e che il modello non vedrà mai
|
|
113
|
+
* — la asimmetria che questo porto esiste per evitare.
|
|
114
|
+
*/
|
|
115
|
+
export function formaPubblicaAttivita(voce) {
|
|
116
|
+
return {
|
|
117
|
+
id: voce.id,
|
|
118
|
+
titolo: voce.titolo,
|
|
119
|
+
descrizione: voce.descrizione ?? null,
|
|
120
|
+
stato: voce.stato,
|
|
121
|
+
fatta: voce.stato === 'done',
|
|
122
|
+
priorita: voce.priorita,
|
|
123
|
+
creataAlle: voce.creataAlle ?? null,
|
|
124
|
+
aggiornataAlle: voce.aggiornataAlle ?? null,
|
|
125
|
+
origine: ORIGINI.includes(voce.origine) ? voce.origine : 'modello',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Più recentemente aggiornate per prime — stesso ordine di `tasks_list` mobile. Cartella assente ⇒ `[]`, mai un errore. */
|
|
130
|
+
export async function elencaAttivita({ cartella }, deps = {}) {
|
|
131
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
132
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
133
|
+
let nomi;
|
|
134
|
+
try {
|
|
135
|
+
nomi = await readdirFn(cartella);
|
|
136
|
+
} catch {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
const voci = [];
|
|
140
|
+
for (const nome of nomi) {
|
|
141
|
+
if (!nome.endsWith('.json')) continue;
|
|
142
|
+
try {
|
|
143
|
+
voci.push(JSON.parse(await readFileFn(join(cartella, nome), 'utf8')));
|
|
144
|
+
} catch { /* una voce corrotta non deve nascondere le altre */ }
|
|
145
|
+
}
|
|
146
|
+
return voci.sort((a, b) => b.aggiornataAlle.localeCompare(a.aggiornataAlle) || a.id.localeCompare(b.id));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Un id assente torna `null`, mai un'eccezione. */
|
|
150
|
+
export async function leggiAttivita({ cartella, id }, deps = {}) {
|
|
151
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
152
|
+
const percorso = percorsoDi(cartella, id);
|
|
153
|
+
if (!percorso) return null; // ⛔ 14/09: id fuori grammatica = «non c'è», mai una lettura fuori dalla cartella
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(await readFileFn(percorso, 'utf8'));
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** @returns l'attività creata: `{id, titolo, descrizione, priorita, stato:'todo', creataAlle, aggiornataAlle}`. @throws {TaskStoreError} TASK_INVALID PRIMA di ogni I/O. */
|
|
162
|
+
export async function creaAttivita({ cartella, title, description, priority = 'normal', origine = 'modello' }, deps = {}) {
|
|
163
|
+
const titolo = validaTitolo(title);
|
|
164
|
+
const descrizione = validaDescrizione(description);
|
|
165
|
+
const priorita = validaPriorita(priority);
|
|
166
|
+
const daChi = validaOrigine(origine);
|
|
167
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
168
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
169
|
+
const clockFn = deps.clockFn ?? (() => new Date());
|
|
170
|
+
const ora = clockFn().toISOString();
|
|
171
|
+
const voce = { id: randomUUID(), titolo, descrizione, priorita, stato: 'todo', origine: daChi, creataAlle: ora, aggiornataAlle: ora };
|
|
172
|
+
await mkdirFn(cartella, { recursive: true });
|
|
173
|
+
await writeFileFn(percorsoDi(cartella, voce.id), JSON.stringify(voce, null, 2), 'utf8');
|
|
174
|
+
return voce;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Solo title/description/priority — MAI lo stato (quello è
|
|
179
|
+
* `completaAttivita`, stesso confine del tool mobile: "Do NOT use this
|
|
180
|
+
* to mark something done"). Solo i campi passati cambiano.
|
|
181
|
+
* @throws {TaskStoreError} TASK_NOT_FOUND se l'id non esiste.
|
|
182
|
+
*/
|
|
183
|
+
export async function aggiornaAttivita({ cartella, id, title, description, priority }, deps = {}) {
|
|
184
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
185
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
186
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
187
|
+
const clockFn = deps.clockFn ?? (() => new Date());
|
|
188
|
+
const voce = await leggiAttivita({ cartella, id }, { readFileFn });
|
|
189
|
+
if (!voce) throw new TaskStoreError(`nessuna attività con id ${id}`, 'TASK_NOT_FOUND');
|
|
190
|
+
if (title !== undefined) voce.titolo = validaTitolo(title);
|
|
191
|
+
if (description !== undefined) voce.descrizione = validaDescrizione(description);
|
|
192
|
+
if (priority !== undefined) voce.priorita = validaPriorita(priority);
|
|
193
|
+
voce.aggiornataAlle = clockFn().toISOString();
|
|
194
|
+
await mkdirFn(cartella, { recursive: true });
|
|
195
|
+
await writeFileFn(percorsoDi(cartella, id), JSON.stringify(voce, null, 2), 'utf8');
|
|
196
|
+
return voce;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Cambia SOLO lo stato — tool a parte sul mobile perché "è la cosa che si fa più spesso, e di gran lunga". @throws {TaskStoreError} TASK_NOT_FOUND se l'id non esiste. */
|
|
200
|
+
export async function completaAttivita({ cartella, id, status = 'done' }, deps = {}) {
|
|
201
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
202
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
203
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
204
|
+
const clockFn = deps.clockFn ?? (() => new Date());
|
|
205
|
+
const voce = await leggiAttivita({ cartella, id }, { readFileFn });
|
|
206
|
+
if (!voce) throw new TaskStoreError(`nessuna attività con id ${id}`, 'TASK_NOT_FOUND');
|
|
207
|
+
voce.stato = validaStato(status);
|
|
208
|
+
voce.aggiornataAlle = clockFn().toISOString();
|
|
209
|
+
await mkdirFn(cartella, { recursive: true });
|
|
210
|
+
await writeFileFn(percorsoDi(cartella, id), JSON.stringify(voce, null, 2), 'utf8');
|
|
211
|
+
return voce;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Idempotente — un id già assente non è un errore, è l'esito voluto ottenuto da qualcun altro. */
|
|
215
|
+
export async function eliminaAttivita({ cartella, id }, deps = {}) {
|
|
216
|
+
const rmFn = deps.rmFn ?? fsp.rm;
|
|
217
|
+
const percorso = percorsoDi(cartella, id);
|
|
218
|
+
if (!percorso) return; // ⛔ 14/09: un id che non può nominare un file è già «assente» — no-op idempotente
|
|
219
|
+
await rmFn(percorso, { force: true });
|
|
220
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* terminal-registry.mjs — W1-01 (05/09). Il registro che decide **CHI può
|
|
3
|
+
* attaccarsi a quale PTY**. Ledger: `.claude/LEDGER-W1-01-SCHEDE-TERMINALE-2026-09-05.md`.
|
|
4
|
+
*
|
|
5
|
+
* ⛔⛔⛔ Perché è un modulo A PARTE da `pty-terminal.mjs`: quello è il ciclo
|
|
6
|
+
* di vita di una PTY (spawn, backlog, reap); questo è **autorizzazione**.
|
|
7
|
+
* Fino al 04/9 le due cose erano la stessa cosa perché l'id della PTY *era*
|
|
8
|
+
* il `sessionId`, quindi non c'era niente da autorizzare. Nel momento in cui
|
|
9
|
+
* una sessione può avere PIÙ schede, il `terminalId` diventa un identificativo
|
|
10
|
+
* scelto dal client — ed è esattamente la forma di difetto che ha prodotto
|
|
11
|
+
* **CVE-2026-59224** (Open WebUI, 2026 —
|
|
12
|
+
* github.com/advisories/GHSA-j657-m4c4-24jq, letto il 05/09/2026): il proxy
|
|
13
|
+
* WebSocket del terminale costruiva l'URL a monte concatenando il `session_id`
|
|
14
|
+
* ricevuto dal client senza validarlo, e con un id vivo ci si **agganciava
|
|
15
|
+
* alla PTY di un'altra persona**. L'identità inoltrata era una pretesa
|
|
16
|
+
* «bearer», senza legame di integrità.
|
|
17
|
+
*
|
|
18
|
+
* ⇒ Il principio NON NEGOZIABILE di questo file: **il `terminalId` non decide
|
|
19
|
+
* MAI la cartella**. La cartella si risolve UNA volta, alla creazione, dalla
|
|
20
|
+
* sessione vera (`cartellaDiSessione`), e da lì resta congelata nella voce.
|
|
21
|
+
* Il client può solo NOMINARE un terminale che il server gli ha già dato.
|
|
22
|
+
* È la cura standard per IDOR/BOLA — OWASP API1:2023 Broken Object Level
|
|
23
|
+
* Authorization, oggi #1: «l'API si fida di un identificativo fornito dal
|
|
24
|
+
* client senza verificare che il chiamante possa accedere a QUELL'oggetto»
|
|
25
|
+
* (ricerca 05/09/2026).
|
|
26
|
+
*
|
|
27
|
+
* ⛔ Il difetto misurato che questo file chiude, `server.mjs:589` prima di oggi:
|
|
28
|
+
*
|
|
29
|
+
* risolviCartella: (id) => sessionRegistry.cartellaDi(id)
|
|
30
|
+
* ?? config.cartelleProgetto[0]?.percorso ?? process.cwd()
|
|
31
|
+
*
|
|
32
|
+
* Un id **sconosciuto** non veniva rifiutato: cadeva sul primo progetto
|
|
33
|
+
* configurato. Con l'id legato al `sessionId` il danno era contenuto; con un
|
|
34
|
+
* `terminalId` libero **qualunque stringa apriva una shell**. Qui quel `??`
|
|
35
|
+
* non esiste: `risolviPerConnessione()` torna `null`, e chi lo chiama rifiuta.
|
|
36
|
+
*
|
|
37
|
+
* ⛔ Il tetto per sessione NON è burocrazia: su Windows ogni PTY porta con sé
|
|
38
|
+
* un processo `conhost` (node-pty#471, ConPTY — letto il 05/09/2026), e
|
|
39
|
+
* `conhost` è noto per sopravvivere alla morte del processo figlio. Schede
|
|
40
|
+
* illimitate = processi di sistema illimitati.
|
|
41
|
+
*/
|
|
42
|
+
import { randomUUID } from 'node:crypto';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Tetto di schede per sessione. Otto: abbastanza per il lavoro vero (build,
|
|
46
|
+
* test, log, un paio di scratch), abbastanza poco da non lasciare in giro
|
|
47
|
+
* decine di `conhost` su Windows. ⛔ Dichiarato, non indovinato a runtime.
|
|
48
|
+
*/
|
|
49
|
+
export const SCHEDE_MASSIME_PER_SESSIONE = 8;
|
|
50
|
+
|
|
51
|
+
/*
|
|
52
|
+
* ⛔⛔ Quanto vive una scheda DIMENTICATA — 24 ore dall'ultimo aggancio.
|
|
53
|
+
*
|
|
54
|
+
* Il registro delle PTY ha gia' il suo reaper, ma chiude la SHELL, non la
|
|
55
|
+
* scheda: e' voluto, perche' un F5 dieci minuti dopo deve riaprire nella
|
|
56
|
+
* cartella giusta invece di prendere un 403. Il prezzo era che le schede non
|
|
57
|
+
* si dimenticavano MAI: su un server acceso per settimane e' crescita lenta,
|
|
58
|
+
* e non era misurata da nessuno.
|
|
59
|
+
*
|
|
60
|
+
* ⛔ Il tetto e' molto piu' lungo della grazia delle PTY, di proposito: dopo
|
|
61
|
+
* la scadenza una riconnessione riceve 403, quindi il numero non e' un
|
|
62
|
+
* dettaglio di implementazione ma una PROMESSA — «una scheda che non tocchi
|
|
63
|
+
* per un giorno non c'e' piu'». Lo stato dell'arte dice esattamente questo:
|
|
64
|
+
* la scadenza si applica lato SERVER, con una politica di durata dichiarata,
|
|
65
|
+
* e la pulizia si programma invece di affidarla al caso (una GC probabilistica
|
|
66
|
+
* puo' non passare mai). Ricerca del 05/09/2026:
|
|
67
|
+
* cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html ·
|
|
68
|
+
* modelcontextprotocol.io/seps/2567-sessionless-mcp
|
|
69
|
+
*
|
|
70
|
+
* ⛔ E una scheda tolta in SILENZIO sarebbe il difetto peggiore di quello che
|
|
71
|
+
* cura: `reap()` torna chi ha tolto e perche', e `misura()` dice quante ne
|
|
72
|
+
* esistono adesso — un problema di dimensionamento che nessuno puo' vedere
|
|
73
|
+
* non e' un problema risolto.
|
|
74
|
+
*/
|
|
75
|
+
export const ORE_PRIMA_DI_DIMENTICARE_UNA_SCHEDA = 24;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Registro delle schede terminale.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} deps
|
|
81
|
+
* @param {(sessionId:string)=>string|null} deps.cartellaDiSessione L'UNICA autorità sulla cartella. `null` ⇒ la sessione non esiste ⇒ niente terminale.
|
|
82
|
+
* @param {(terminalId:string)=>void} [deps.chiudiPtyFn] Chiude la PTY vera quando una scheda viene chiusa esplicitamente.
|
|
83
|
+
* @param {(terminalId:string)=>{viva:boolean}|null} [deps.statoPtyFn] Per dire in elenco se la shell è ancora viva, senza esporre la voce interna del registro PTY.
|
|
84
|
+
* @param {string|null} [deps.cartellaStandaloneLegacy] ⛔ DEBITO DICHIARATO, vedi `risolviPerConnessione`. `null` (default) = registro STRETTO: un id ignoto si rifiuta e basta.
|
|
85
|
+
*/
|
|
86
|
+
export function creaRegistroSchedeTerminale({
|
|
87
|
+
cartellaDiSessione,
|
|
88
|
+
chiudiPtyFn = () => {},
|
|
89
|
+
statoPtyFn = () => null,
|
|
90
|
+
cartellaStandaloneLegacy = null,
|
|
91
|
+
schedeMassimePerSessione = SCHEDE_MASSIME_PER_SESSIONE,
|
|
92
|
+
orePrimaDiDimenticare = ORE_PRIMA_DI_DIMENTICARE_UNA_SCHEDA,
|
|
93
|
+
generaId = randomUUID,
|
|
94
|
+
clock = () => new Date(),
|
|
95
|
+
} = {}) {
|
|
96
|
+
/** @type {Map<string, {terminalId:string, sessionId:string|null, cartella:string, creatoAlle:string, origine:string}>} */
|
|
97
|
+
const schede = new Map();
|
|
98
|
+
|
|
99
|
+
function schedeDi(sessionId) {
|
|
100
|
+
return [...schede.values()].filter((voce) => voce.sessionId === sessionId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function pubblica(voce) {
|
|
104
|
+
return {
|
|
105
|
+
terminalId: voce.terminalId,
|
|
106
|
+
sessionId: voce.sessionId,
|
|
107
|
+
cartella: voce.cartella,
|
|
108
|
+
creatoAlle: voce.creatoAlle,
|
|
109
|
+
origine: voce.origine,
|
|
110
|
+
/* ⛔ Tre stati, non due: `attiva:false` significa "scheda registrata, shell non ancora aperta o già uscita" — mai confuso con "scheda inesistente" (quella non compare proprio). */
|
|
111
|
+
attiva: statoPtyFn(voce.terminalId)?.viva === true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Registra la PRIMA scheda di una sessione con `terminalId === sessionId`.
|
|
117
|
+
*
|
|
118
|
+
* ⭐ Compatibilità all'indietro OBBLIGATORIA: `public/harness-ui/app.js` è
|
|
119
|
+
* congelato da un contratto (`frontend/tests/contract/legacy-contract-snapshot.test.mjs`
|
|
120
|
+
* blocca byte-count e sha256) e si collega a `?id=<sessionId>`. Tenendo la
|
|
121
|
+
* prima scheda uguale al `sessionId`, il monolite continua a funzionare
|
|
122
|
+
* **senza una riga di modifica**.
|
|
123
|
+
*
|
|
124
|
+
* ⛔ Non è un fallback travestito: la cartella la dà comunque
|
|
125
|
+
* `cartellaDiSessione()`, cioè il registro delle sessioni. Se la sessione
|
|
126
|
+
* non esiste, qui non nasce niente.
|
|
127
|
+
*/
|
|
128
|
+
function registraPrimaScheda(sessionId, cartella) {
|
|
129
|
+
const voce = {
|
|
130
|
+
terminalId: sessionId,
|
|
131
|
+
sessionId,
|
|
132
|
+
cartella,
|
|
133
|
+
creatoAlle: clock().toISOString(),
|
|
134
|
+
ultimoAggancioMs: clock().getTime(),
|
|
135
|
+
origine: 'prima-scheda',
|
|
136
|
+
};
|
|
137
|
+
schede.set(sessionId, voce);
|
|
138
|
+
return voce;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
/**
|
|
143
|
+
* Crea una scheda per una sessione VERA. È l'unica porta da cui nasce un
|
|
144
|
+
* `terminalId` nuovo, e l'id lo sceglie il SERVER (`randomUUID`), mai il
|
|
145
|
+
* client — un id imprevedibile è metà della cura raccomandata da OWASP
|
|
146
|
+
* per le WebSocket (il browser non applica la same-origin policy alle WS
|
|
147
|
+
* come la applica a `fetch`: serve il controllo Origin esplicito **più**
|
|
148
|
+
* un valore che non si indovina — WebSocket Security Cheat Sheet, letto
|
|
149
|
+
* il 05/09/2026).
|
|
150
|
+
*/
|
|
151
|
+
crea({ sessionId } = {}) {
|
|
152
|
+
if (typeof sessionId !== 'string' || sessionId === '') {
|
|
153
|
+
return { erroreAvvio: 'Sessione non valida', code: 'QUERY_INVALID' };
|
|
154
|
+
}
|
|
155
|
+
const cartella = cartellaDiSessione(sessionId);
|
|
156
|
+
if (typeof cartella !== 'string' || cartella === '') {
|
|
157
|
+
/* ⛔ Qui viveva il `??` di server.mjs:589. Una sessione che non esiste non prende una cartella di ripiego: non prende NIENTE. */
|
|
158
|
+
return { erroreAvvio: 'Sessione non trovata', code: 'NOT_FOUND' };
|
|
159
|
+
}
|
|
160
|
+
const gia = schedeDi(sessionId);
|
|
161
|
+
if (gia.length >= schedeMassimePerSessione) {
|
|
162
|
+
return { erroreAvvio: `Massimo ${schedeMassimePerSessione} terminali per sessione`, code: 'TERMINAL_LIMIT_REACHED' };
|
|
163
|
+
}
|
|
164
|
+
if (gia.length === 0) return pubblica(registraPrimaScheda(sessionId, cartella));
|
|
165
|
+
let terminalId = generaId();
|
|
166
|
+
/* ⛔ AL CONTRARIO — un `generaId` che collide (o un test che ne inietta uno fisso) non deve MAI sovrascrivere una scheda viva di qualcun altro. */
|
|
167
|
+
while (schede.has(terminalId)) terminalId = `${terminalId}-${schede.size}`;
|
|
168
|
+
const voce = {
|
|
169
|
+
terminalId,
|
|
170
|
+
sessionId,
|
|
171
|
+
cartella,
|
|
172
|
+
creatoAlle: clock().toISOString(),
|
|
173
|
+
ultimoAggancioMs: clock().getTime(),
|
|
174
|
+
origine: 'rotta',
|
|
175
|
+
};
|
|
176
|
+
schede.set(terminalId, voce);
|
|
177
|
+
return pubblica(voce);
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/** Elenco delle schede di UNA sessione — mai di tutte: una sessione non vede i terminali delle altre. */
|
|
181
|
+
elenca(sessionId) {
|
|
182
|
+
if (typeof sessionId !== 'string' || sessionId === '') return { erroreAvvio: 'Sessione non valida', code: 'QUERY_INVALID' };
|
|
183
|
+
if (cartellaDiSessione(sessionId) === null) return { erroreAvvio: 'Sessione non trovata', code: 'NOT_FOUND' };
|
|
184
|
+
return { items: schedeDi(sessionId).map(pubblica) };
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Chiusura ESPLICITA di una scheda: chiude anche la PTY vera.
|
|
189
|
+
*
|
|
190
|
+
* ⛔⛔ La proprietà si verifica, non si presume: chiudere un terminale che
|
|
191
|
+
* appartiene a un'ALTRA sessione risponde `NOT_FOUND` — lo stesso codice di
|
|
192
|
+
* un terminale inesistente, di proposito. Distinguere «non esiste» da «non
|
|
193
|
+
* è tuo» regalerebbe a chi prova id a caso una sonda per scoprire quali
|
|
194
|
+
* esistono (OWASP API1:2023, ricerca 05/09/2026).
|
|
195
|
+
*/
|
|
196
|
+
chiudi({ sessionId, terminalId } = {}) {
|
|
197
|
+
if (typeof sessionId !== 'string' || sessionId === '' || typeof terminalId !== 'string' || terminalId === '') {
|
|
198
|
+
return { erroreAvvio: 'Terminale non valido', code: 'QUERY_INVALID' };
|
|
199
|
+
}
|
|
200
|
+
const voce = schede.get(terminalId);
|
|
201
|
+
if (!voce || voce.sessionId !== sessionId) {
|
|
202
|
+
return { erroreAvvio: 'Terminale non trovato', code: 'NOT_FOUND' };
|
|
203
|
+
}
|
|
204
|
+
chiudiPtyFn(terminalId);
|
|
205
|
+
schede.delete(terminalId);
|
|
206
|
+
return { ok: true, terminalId, sessionId };
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* L'UNICA domanda che `terminal-ws.mjs` ha il diritto di fare: «questo id
|
|
211
|
+
* può attaccarsi, e a quale cartella?». Tre esiti, in quest'ordine:
|
|
212
|
+
*
|
|
213
|
+
* 1. **scheda già registrata** → la sua voce, con la cartella congelata
|
|
214
|
+
* alla creazione (⛔ mai ri-risolta a ogni connessione: è così che si
|
|
215
|
+
* evita che un cambio di stato altrove sposti una shell già aperta);
|
|
216
|
+
* 2. **id che è un `sessionId` VIVO** → prima scheda di quella sessione,
|
|
217
|
+
* registrata al volo. È la porta di compatibilità del monolite
|
|
218
|
+
* congelato, e l'autorità resta il registro delle sessioni;
|
|
219
|
+
* 3. **tutto il resto** → `null`, cioè **rifiuto**. Nessuna PTY nasce.
|
|
220
|
+
*
|
|
221
|
+
* ⛔ DEBITO DICHIARATO — `cartellaStandaloneLegacy`. Il monolite congelato
|
|
222
|
+
* ha una TERZA strada, misurata il 05/09 leggendo `public/app.js` (non
|
|
223
|
+
* presunta): `idTerminaleCorrente()` fa
|
|
224
|
+
* `if (state.realSession.id) return state.realSession.id;` e **altrimenti
|
|
225
|
+
* inventa un `crypto.randomUUID()` lato client** — un terminale
|
|
226
|
+
* "standalone" senza nessuna sessione, raggiungibile aprendo il tab
|
|
227
|
+
* Terminale prima di avviare qualsiasi cosa. Quel caso è esattamente ciò
|
|
228
|
+
* che il `??` di server.mjs teneva in piedi. Qui il default è `null`, cioè
|
|
229
|
+
* **rifiuto**; chi vuole tenere viva quella funzione del monolite deve
|
|
230
|
+
* passare una cartella ESPLICITA a costruzione — una riga sola, visibile,
|
|
231
|
+
* con un tetto suo — invece di una catena di `??` valutata a ogni
|
|
232
|
+
* connessione. ⛔ Si spegne rimettendo `null` in `server.mjs` il giorno in
|
|
233
|
+
* cui il frontend nuovo sostituisce il monolite.
|
|
234
|
+
*/
|
|
235
|
+
risolviPerConnessione(id) {
|
|
236
|
+
if (typeof id !== 'string' || id === '') return null;
|
|
237
|
+
const registrata = schede.get(id);
|
|
238
|
+
if (registrata) {
|
|
239
|
+
// ⛔ «Ultimo aggancio», non «ultima creazione»: e' l'uso che tiene viva
|
|
240
|
+
// una scheda, ed e' l'unico momento in cui qualcuno la nomina davvero.
|
|
241
|
+
registrata.ultimoAggancioMs = clock().getTime();
|
|
242
|
+
return registrata;
|
|
243
|
+
}
|
|
244
|
+
const cartellaSessione = cartellaDiSessione(id);
|
|
245
|
+
if (typeof cartellaSessione === 'string' && cartellaSessione !== '') {
|
|
246
|
+
return registraPrimaScheda(id, cartellaSessione);
|
|
247
|
+
}
|
|
248
|
+
if (typeof cartellaStandaloneLegacy === 'string' && cartellaStandaloneLegacy !== '') {
|
|
249
|
+
const standalone = [...schede.values()].filter((voce) => voce.sessionId === null);
|
|
250
|
+
/* ⛔ Tetto anche qui: senza, «qualunque stringa apre una shell» resterebbe vero per la porta legacy. */
|
|
251
|
+
if (standalone.length >= schedeMassimePerSessione) return null;
|
|
252
|
+
const voce = {
|
|
253
|
+
terminalId: id,
|
|
254
|
+
sessionId: null,
|
|
255
|
+
cartella: cartellaStandaloneLegacy,
|
|
256
|
+
creatoAlle: clock().toISOString(),
|
|
257
|
+
ultimoAggancioMs: clock().getTime(),
|
|
258
|
+
origine: 'standalone-legacy',
|
|
259
|
+
};
|
|
260
|
+
schede.set(id, voce);
|
|
261
|
+
return voce;
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* ⭐⭐⭐ Dimentica le schede che nessuno tocca da `orePrimaDiDimenticare`.
|
|
268
|
+
*
|
|
269
|
+
* ⛔ Una scheda si toglie SOLO se la sua shell e' gia' morta: finche' una
|
|
270
|
+
* PTY e' viva la scheda serve, per lunga che sia l'attesa — buttarla
|
|
271
|
+
* lascerebbe un processo vivo senza piu' nessuno che possa raggiungerlo,
|
|
272
|
+
* che e' esattamente la perdita che questa funzione dovrebbe curare.
|
|
273
|
+
*
|
|
274
|
+
* ⛔ Torna CHI ha tolto e PERCHE': una scheda sparita in silenzio sarebbe
|
|
275
|
+
* peggio del problema, perche' la prossima riconnessione riceve 403 e
|
|
276
|
+
* nessuno saprebbe dire da dove viene.
|
|
277
|
+
*/
|
|
278
|
+
dimenticaLeVecchie({ adesso = clock().getTime() } = {}) {
|
|
279
|
+
const limite = orePrimaDiDimenticare * 60 * 60 * 1000;
|
|
280
|
+
const tolte = [];
|
|
281
|
+
for (const [terminalId, voce] of [...schede]) {
|
|
282
|
+
const ferma = adesso - (voce.ultimoAggancioMs ?? 0);
|
|
283
|
+
if (ferma < limite) continue;
|
|
284
|
+
const pty = statoPtyFn(terminalId);
|
|
285
|
+
if (pty && pty.viva) continue;
|
|
286
|
+
schede.delete(terminalId);
|
|
287
|
+
tolte.push({ terminalId, sessionId: voce.sessionId, origine: voce.origine, fermaDaMs: ferma });
|
|
288
|
+
}
|
|
289
|
+
return { tolte, restano: schede.size };
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Quante schede esistono adesso, e da quanto tace la piu' vecchia.
|
|
294
|
+
* ⛔ Serve a vedere un problema di dimensionamento PRIMA che diventi una
|
|
295
|
+
* perdita: un numero che nessuno puo' leggere non e' una garanzia.
|
|
296
|
+
*/
|
|
297
|
+
misura({ adesso = clock().getTime() } = {}) {
|
|
298
|
+
let piuVecchiaMs = null;
|
|
299
|
+
let conShellViva = 0;
|
|
300
|
+
for (const [terminalId, voce] of schede) {
|
|
301
|
+
const ferma = adesso - (voce.ultimoAggancioMs ?? 0);
|
|
302
|
+
if (piuVecchiaMs === null || ferma > piuVecchiaMs) piuVecchiaMs = ferma;
|
|
303
|
+
const pty = statoPtyFn(terminalId);
|
|
304
|
+
if (pty && pty.viva) conShellViva += 1;
|
|
305
|
+
}
|
|
306
|
+
return { schede: schede.size, conShellViva, piuVecchiaFermaDaMs: piuVecchiaMs, orePrimaDiDimenticare };
|
|
307
|
+
},
|
|
308
|
+
|
|
309
|
+
/** ⛔ Solo per i test e per lo shutdown — mai per decidere un permesso. */
|
|
310
|
+
_schede: schede,
|
|
311
|
+
};
|
|
312
|
+
}
|