talos-code 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -0
- package/LICENSE +661 -0
- package/README.md +116 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +100 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +244 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +46 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +92 -0
- package/dist/i18n/en/errors.js +267 -0
- package/dist/i18n/en/firstrun.js +101 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +774 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +331 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/openrouter-login.js +172 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-archive.js +69 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +265 -0
- package/dist/runtime/talos-composition.js +1011 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/agent-view.js +51 -0
- package/dist/tui/app.js +4098 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +560 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +38 -0
- package/dist/tui/components/terminal-shell.js +214 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/descendant-approvals.js +104 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +148 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +56 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +43 -0
- package/dist/tui/overlays/approval-dialog.js +642 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +77 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +171 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +289 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +791 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/setup-wizard.js +46 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +82 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +27 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +64 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pty-terminal.mjs — Terminale REALE, ledger in
|
|
3
|
+
* `.claude/LEDGER-TERMINALE-REALE.md`. Owner, 28/8: *"deve essere un
|
|
4
|
+
* terminale vero e proprio bash [...] che non ha limiti [...] usabile
|
|
5
|
+
* dall'utente con le sue dita umane"* — non il log a righe dei comandi
|
|
6
|
+
* dell'agente (quello resta, invariato, nel tool-call della chat).
|
|
7
|
+
*
|
|
8
|
+
* Ricerca fatta prima di scrivere: su Windows (dove non esiste
|
|
9
|
+
* un primitivo PTY POSIX nativo), lo stato dell'arte monta Git Bash dentro
|
|
10
|
+
* una PTY vera — la stessa strategia adottata da altri strumenti dello
|
|
11
|
+
* stesso tipo.
|
|
12
|
+
* Verificato empiricamente su QUESTA macchina (non presunto): `node-pty`
|
|
13
|
+
* include il prebuild `win32-x64` DENTRO il pacchetto npm (zero
|
|
14
|
+
* compilazione), Git Bash è installato
|
|
15
|
+
* (`C:\Program Files\Git\bin\bash.exe`, lo stesso binario che questa
|
|
16
|
+
* sessione usa per il proprio tool Bash), e una sonda reale
|
|
17
|
+
* (`pty.spawn` + un comando scritto come keystroke) ha prodotto un
|
|
18
|
+
* prompt MINGW64 vero con sequenze ANSI vere.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ Questo modulo NON impone timeout, non tronca l'output, non ha una
|
|
21
|
+
* allowlist di comandi — a differenza di `eseguiComandoSandboxato`
|
|
22
|
+
* (l'attrezzo `shell` dato in mano a un MODELLO): qui la shell è in
|
|
23
|
+
* mano al proprietario della macchina, "non ha limiti" per richiesta
|
|
24
|
+
* esplicita.
|
|
25
|
+
*/
|
|
26
|
+
import { spawn as spawnPty } from 'node-pty';
|
|
27
|
+
import { existsSync } from 'node:fs';
|
|
28
|
+
|
|
29
|
+
import { ambienteSenzaVariabiliDelServer } from './ambiente-solo-server.mjs';
|
|
30
|
+
|
|
31
|
+
/** ⛔ Solo win32: `existsFn` iniettabile per i test, mai una vera ricerca su disco lì. */
|
|
32
|
+
const PERCORSI_GIT_BASH_WINDOWS = [
|
|
33
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
34
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Tetto del backlog **PER SCHEDA** — un replay alla riconnessione, non uno
|
|
39
|
+
* storico infinito.
|
|
40
|
+
*
|
|
41
|
+
* ⛔⛔ W1-01 (05/9): verificato che il tetto è per voce del registro e non
|
|
42
|
+
* globale (`voce.byteBacklog` vive dentro la singola voce), perché con più
|
|
43
|
+
* schede per sessione un tetto globale farebbe sparire il backlog di una
|
|
44
|
+
* scheda quando ne parla un'altra. Il numero è **200.000 byte per scheda**:
|
|
45
|
+
* dichiarato, non infinito. Stessa disciplina di VS Code, che limita lo
|
|
46
|
+
* scrollback ripristinato di una sessione persistente con
|
|
47
|
+
* `terminal.integrated.persistentSessionScrollback` (default **100 righe**,
|
|
48
|
+
* code.visualstudio.com/docs/terminal/advanced, letto il 05/09/2026) invece di
|
|
49
|
+
* rigiocare tutto. ⛔ E il backlog DEVE stare qui: `node-pty` non tiene
|
|
50
|
+
* scrollback proprio — lo scrollback vive in xterm, lato client, e un F5 lo
|
|
51
|
+
* azzera (ricerca 05/09/2026).
|
|
52
|
+
*/
|
|
53
|
+
export const BACKLOG_MASSIMO_BYTE = 200_000;
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* ⛔ 14/09 (F04 della review, riprodotto): il tetto NON si rispettava con un solo pezzo — il vecchio ciclo si fermava a
|
|
57
|
+
* `backlog.length > 1`, quindi un `cat` di un file grosso lasciava in memoria un elemento da megabyte PER SCHEDA, e
|
|
58
|
+
* il numero dichiarato qui sopra («200.000 byte per scheda, dichiarato, non infinito») era falso proprio nel caso che
|
|
59
|
+
* conta. Adesso l'ultimo pezzo si TAGLIA, e si tiene la CODA: è quella che la scheda deve rivedere al rientro.
|
|
60
|
+
* ⛔ Il taglio è sui byte UTF-8, mai sui caratteri: si entra da destra e si scavalcano i byte di continuazione
|
|
61
|
+
* (`10xxxxxx`), perché una sequenza multi-byte spezzata arriverebbe a xterm come `�` — cioè il backlog
|
|
62
|
+
* consegnerebbe caratteri che la shell non ha mai scritto.
|
|
63
|
+
*/
|
|
64
|
+
function limitaBacklog(voce) {
|
|
65
|
+
while (voce.byteBacklog > BACKLOG_MASSIMO_BYTE && voce.backlog.length > 1) {
|
|
66
|
+
voce.byteBacklog -= Buffer.byteLength(voce.backlog.shift(), 'utf8');
|
|
67
|
+
}
|
|
68
|
+
if (voce.byteBacklog <= BACKLOG_MASSIMO_BYTE || voce.backlog.length === 0) return;
|
|
69
|
+
const ultimo = Buffer.from(voce.backlog[0], 'utf8');
|
|
70
|
+
let taglio = ultimo.length - BACKLOG_MASSIMO_BYTE;
|
|
71
|
+
while (taglio < ultimo.length && (ultimo[taglio] & 0xc0) === 0x80) taglio += 1;
|
|
72
|
+
const coda = ultimo.subarray(taglio).toString('utf8');
|
|
73
|
+
voce.backlog[0] = coda;
|
|
74
|
+
voce.byteBacklog = Buffer.byteLength(coda, 'utf8');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Una PTY disconnessa da più di così viene chiusa dal reaper — pulizia di schede mai più tornate, non un limite sulla shell viva. */
|
|
78
|
+
export const MINUTI_PRIMA_DI_CHIUDERE_PTY_ORFANA = 10;
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* ⛔⛔⛔ CLI-REQ-04 (17/09/2026) — L'AMBIENTE DELLA SHELL DELLA PERSONA.
|
|
82
|
+
*
|
|
83
|
+
* Fino a oggi `apri` passava `env: process.env`, cioè l'ambiente INTERO del server. Dentro ci
|
|
84
|
+
* sono il token di loopback a 64 esadecimali che protegge TUTTA la nostra API locale e la chiave
|
|
85
|
+
* privata che firma le ricevute (`desktop/runtime.mjs:47-60`, `src/config.mjs:601` e `:625-627`).
|
|
86
|
+
* Un `npm install` con i suoi script di installazione, o un attrezzo scaricato, lanciato dalla
|
|
87
|
+
* persona in questa scheda poteva leggerli e usarli.
|
|
88
|
+
*
|
|
89
|
+
* ⛔ Ricerca prima di scrivere, 17/09/2026: la documentazione di `node:child_process`
|
|
90
|
+
* (nodejs.org/api/child_process.html) dichiara `env` con default `process.env`, cioè l'eredità
|
|
91
|
+
* INTERA è il comportamento predefinito e va disfatta a mano; lo stato dell'arte sui segreti in
|
|
92
|
+
* Node (nodejs-security.com/blog/do-not-use-secrets-in-environment-variables-and-here-is-how-to
|
|
93
|
+
* -do-it-better) chiama l'eredità automatica una violazione del minimo privilegio.
|
|
94
|
+
*
|
|
95
|
+
* ⛔ L'elenco e la funzione NON vivono più qui: stanno in `ambiente-solo-server.mjs`, perché dal
|
|
96
|
+
* secondo giro il browser di sistema (`browser-vivo.mjs`) ha lo stesso bisogno e due copie
|
|
97
|
+
* divergerebbero al primo segreto nuovo. Lì stanno anche il perché dell'elenco CHIUSO e la
|
|
98
|
+
* misura che mostra che D-10E NON copre già questi nomi.
|
|
99
|
+
*
|
|
100
|
+
* ⛔ L'ELENCO DEI PUNTI CHE AVVIANO PROCESSI CON L'AMBIENTE INTERO — cioè chi altri ha questo
|
|
101
|
+
* stesso difetto, quanto è coperto e quanto no — sta in
|
|
102
|
+
* `.claude/ELENCO-SPAWN-AMBIENTE-2026-09-17.md`, misurato il 17/09/2026. Chi tocca questa riga
|
|
103
|
+
* lo rilegga: una cura su una sola strada non chiude la classe.
|
|
104
|
+
*/
|
|
105
|
+
export { VARIABILI_DEL_SERVER_DICHIARATE_INNOCUE, VARIABILI_SOLO_DEL_SERVER } from './ambiente-solo-server.mjs';
|
|
106
|
+
|
|
107
|
+
/** Il nome con cui questo modulo ha sempre esposto la funzione: una sola implementazione, due nomi. */
|
|
108
|
+
export const ambienteDelTerminale = ambienteSenzaVariabiliDelServer;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sceglie la shell reale da lanciare. Su Windows: Git Bash se esiste
|
|
112
|
+
* — altrimenti `cmd.exe`, ma
|
|
113
|
+
* DICHIARATO (`enforcement:'cmd-fallback'`), mai un fallback silenzioso
|
|
114
|
+
* spacciato per bash (stesso principio "onestà sull'enforcement" già
|
|
115
|
+
* in uso per l'attrezzo `shell` dell'agente). Su POSIX: `$SHELL` o
|
|
116
|
+
* `/bin/bash`.
|
|
117
|
+
*/
|
|
118
|
+
export function sceltaShell(deps = {}) {
|
|
119
|
+
const platform = deps.platform ?? process.platform;
|
|
120
|
+
const existsFn = deps.existsFn ?? existsSync;
|
|
121
|
+
const env = deps.env ?? process.env;
|
|
122
|
+
if (platform === 'win32') {
|
|
123
|
+
const percorsi = deps.percorsiGitBash ?? PERCORSI_GIT_BASH_WINDOWS;
|
|
124
|
+
const gitBash = percorsi.find((percorso) => existsFn(percorso));
|
|
125
|
+
if (gitBash) return { comando: gitBash, argomenti: ['--login', '-i'], enforcement: 'git-bash' };
|
|
126
|
+
return { comando: 'cmd.exe', argomenti: [], enforcement: 'cmd-fallback' };
|
|
127
|
+
}
|
|
128
|
+
return { comando: env.SHELL || '/bin/bash', argomenti: ['-l'], enforcement: 'posix-shell' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Codifica/decodifica del framing binario sulla WebSocket — un byte di
|
|
133
|
+
* comando in testa, mai lo stream incapsulato in JSON (corromperebbe le
|
|
134
|
+
* sequenze ANSI/di controllo — guardia raccolta in ricerca:
|
|
135
|
+
* xtermjs.org/docs/guides/flowcontrol). `0` = dati grezzi (tastiera
|
|
136
|
+
* umana → PTY, o PTY → schermo). `1` = controllo JSON (resize, o
|
|
137
|
+
* l'evento di uscita della shell).
|
|
138
|
+
*/
|
|
139
|
+
export const TIPO_FRAME_DATI = 0;
|
|
140
|
+
export const TIPO_FRAME_CONTROLLO = 1;
|
|
141
|
+
|
|
142
|
+
export function codificaFrame(tipo, payload) {
|
|
143
|
+
const corpo = typeof payload === 'string' ? Buffer.from(payload, 'utf8') : payload;
|
|
144
|
+
return Buffer.concat([Buffer.from([tipo]), corpo]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** AL CONTRARIO — un frame vuoto o con un tipo ignoto torna `null`, mai un crash. */
|
|
148
|
+
export function decodificaFrame(dati) {
|
|
149
|
+
const buf = Buffer.isBuffer(dati) ? dati : Buffer.from(dati);
|
|
150
|
+
if (buf.length === 0) return null;
|
|
151
|
+
const tipo = buf[0];
|
|
152
|
+
if (tipo !== TIPO_FRAME_DATI && tipo !== TIPO_FRAME_CONTROLLO) return null;
|
|
153
|
+
return { tipo, payload: buf.subarray(1) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Registro delle PTY vive, per id. Una riconnessione sullo STESSO id
|
|
158
|
+
* riaggancia la PTY viva invece di aprirne una seconda (un F5 non perde la
|
|
159
|
+
* shell) — stesso principio del `Last-Event-ID` già in uso per SSE, qui
|
|
160
|
+
* applicato a una PTY invece che a un run dell'agente.
|
|
161
|
+
*
|
|
162
|
+
* ⛔⛔ W1-01 (05/9) — l'id NON è più il `sessionId`: è il **`terminalId`**, e
|
|
163
|
+
* una sessione può averne più d'uno. Chi decide quali id esistono, a quale
|
|
164
|
+
* sessione appartengono e in quale cartella si aprono è
|
|
165
|
+
* `terminal-registry.mjs`; questo file non autorizza niente, tiene solo in
|
|
166
|
+
* vita le PTY. ⇒ Ogni id ha la sua voce, quindi la sua PTY, il suo backlog e
|
|
167
|
+
* i suoi ascoltatori: due schede della stessa sessione **non condividono
|
|
168
|
+
* I/O**, ed è il criterio della riga (provato in `pty-terminal.test.mjs`).
|
|
169
|
+
*/
|
|
170
|
+
export function creaRegistroTerminali(deps = {}) {
|
|
171
|
+
const spawnPtyFn = deps.spawnPtyFn ?? spawnPty;
|
|
172
|
+
const sceltaShellFn = deps.sceltaShellFn ?? sceltaShell;
|
|
173
|
+
const ambienteFn = deps.ambienteFn ?? ambienteDelTerminale; // ⛔ CLI-REQ-04: iniettabile per i test.
|
|
174
|
+
const clock = deps.clock ?? (() => Date.now());
|
|
175
|
+
/** @type {Map<string, any>} */
|
|
176
|
+
const terminali = new Map();
|
|
177
|
+
|
|
178
|
+
function apri({ id, cartella, cols = 80, rows = 24 }) {
|
|
179
|
+
const esistente = terminali.get(id);
|
|
180
|
+
if (esistente && !esistente.chiusa) {
|
|
181
|
+
esistente.ultimaDisconnessioneMs = null;
|
|
182
|
+
return esistente;
|
|
183
|
+
}
|
|
184
|
+
const scelta = sceltaShellFn();
|
|
185
|
+
const handle = spawnPtyFn(scelta.comando, scelta.argomenti, {
|
|
186
|
+
name: 'xterm-256color',
|
|
187
|
+
cols,
|
|
188
|
+
rows,
|
|
189
|
+
cwd: cartella,
|
|
190
|
+
env: ambienteFn(), // ⛔ CLI-REQ-04 (17/09/2026): mai `process.env` intero — vedi VARIABILI_SOLO_DEL_SERVER.
|
|
191
|
+
});
|
|
192
|
+
const voce = {
|
|
193
|
+
id,
|
|
194
|
+
handle,
|
|
195
|
+
enforcement: scelta.enforcement,
|
|
196
|
+
comando: scelta.comando, // 06/9 B1: il nome della shell arriva alla scheda
|
|
197
|
+
backlog: [],
|
|
198
|
+
byteBacklog: 0,
|
|
199
|
+
ultimaDisconnessioneMs: null,
|
|
200
|
+
chiusa: false,
|
|
201
|
+
ascoltatori: new Set(),
|
|
202
|
+
};
|
|
203
|
+
handle.onData((dati) => {
|
|
204
|
+
voce.backlog.push(dati);
|
|
205
|
+
voce.byteBacklog += Buffer.byteLength(dati, 'utf8');
|
|
206
|
+
limitaBacklog(voce);
|
|
207
|
+
for (const ascolta of voce.ascoltatori) ascolta({ tipo: 'dati', dati });
|
|
208
|
+
});
|
|
209
|
+
handle.onExit(({ exitCode, signal }) => {
|
|
210
|
+
voce.chiusa = true;
|
|
211
|
+
for (const ascolta of voce.ascoltatori) ascolta({ tipo: 'uscita', exitCode, signal });
|
|
212
|
+
});
|
|
213
|
+
terminali.set(id, voce);
|
|
214
|
+
return voce;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function scrivi(id, dati) {
|
|
218
|
+
terminali.get(id)?.handle.write(dati);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function ridimensiona(id, cols, rows) {
|
|
222
|
+
const voce = terminali.get(id);
|
|
223
|
+
if (voce && !voce.chiusa && cols > 0 && rows > 0) voce.handle.resize(cols, rows);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/*
|
|
227
|
+
* ⛔ 14/09 (F05 della review, riprodotto): una scheda che si stacca NON rende orfana una PTY che un'ALTRA finestra sta
|
|
228
|
+
* ancora guardando. Prima il timbro si metteva comunque, e dieci minuti dopo il reaper uccideva un terminale vivo
|
|
229
|
+
* sotto gli occhi di chi lo stava usando — difetto che il lavoro a due finestre di oggi rende tutt'altro che teorico.
|
|
230
|
+
* Il timbro lo mette solo l'ULTIMO che se ne va; se resta qualcuno, si azzera.
|
|
231
|
+
*/
|
|
232
|
+
function segnaDisconnesso(id) {
|
|
233
|
+
const voce = terminali.get(id);
|
|
234
|
+
if (voce) voce.ultimaDisconnessioneMs = voce.ascoltatori.size === 0 ? clock() : null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Chiusura esplicita (mai implicita): l'owner chiude la scheda del terminale dalla UI. */
|
|
238
|
+
function chiudiForzato(id) {
|
|
239
|
+
const voce = terminali.get(id);
|
|
240
|
+
if (!voce) return;
|
|
241
|
+
try { voce.handle.kill(); } catch { /* già morta, nulla da fare */ }
|
|
242
|
+
terminali.delete(id);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Chiude solo le PTY orfane da più di `MINUTI_PRIMA_DI_CHIUDERE_PTY_ORFANA`
|
|
247
|
+
* — mai quelle ancora attaccate a un client (`ultimaDisconnessioneMs`
|
|
248
|
+
* nullo finché una WS resta aperta), mai per un tetto di tempo sulla
|
|
249
|
+
* shell VIVA.
|
|
250
|
+
*/
|
|
251
|
+
function reap() {
|
|
252
|
+
const ora = clock();
|
|
253
|
+
for (const [id, voce] of terminali) {
|
|
254
|
+
// ⛔ 14/09 (F05): «orfana» vuol dire che NESSUNO la guarda — il timbro da solo non basta, perché un'altra finestra
|
|
255
|
+
// può essersi riagganciata nel frattempo. Due condizioni, non una.
|
|
256
|
+
const orfanaScaduta = voce.ascoltatori.size === 0
|
|
257
|
+
&& voce.ultimaDisconnessioneMs !== null
|
|
258
|
+
&& ora - voce.ultimaDisconnessioneMs > MINUTI_PRIMA_DI_CHIUDERE_PTY_ORFANA * 60_000;
|
|
259
|
+
if (voce.chiusa || orfanaScaduta) {
|
|
260
|
+
try { voce.handle.kill(); } catch { /* già morta */ }
|
|
261
|
+
terminali.delete(id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* ⭐ W1-01 (05/9) — lo stato di UNA PTY, senza esporre la voce mutabile del
|
|
268
|
+
* registro fuori da qui (stesso principio di `cartellaDi` in
|
|
269
|
+
* `session-registry.mjs`). Serve all'elenco delle schede per dire «shell
|
|
270
|
+
* viva» / «shell già uscita»: `null` significa «nessuna PTY per questo id»,
|
|
271
|
+
* che è un terzo fatto e non si confonde con `{viva:false}`.
|
|
272
|
+
*/
|
|
273
|
+
function stato(id) {
|
|
274
|
+
const voce = terminali.get(id);
|
|
275
|
+
if (!voce) return null;
|
|
276
|
+
return { viva: !voce.chiusa, enforcement: voce.enforcement, byteBacklog: voce.byteBacklog };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* ⭐⭐⭐ Come `apri`, ma DICE se ha ripreso una PTY viva o ne ha aperta una
|
|
281
|
+
* nuova.
|
|
282
|
+
*
|
|
283
|
+
* ⛔ Serve perché da fuori le due cose sono indistinguibili: il ponte
|
|
284
|
+
* rigioca il backlog in entrambi i casi, e una PTY appena creata ha backlog
|
|
285
|
+
* vuoto — che è anche l'aspetto di una shell viva che non ha ancora
|
|
286
|
+
* stampato niente. Dedurlo dall'assenza di backlog è un indovinello, non una
|
|
287
|
+
* misura, e l'interfaccia finiva per dire «riconnesso» senza poter sapere se
|
|
288
|
+
* la shell della persona fosse sopravvissuta. Lo stato dell'arte separa
|
|
289
|
+
* l'identità della CONNESSIONE da quella della SESSIONE e fa dichiarare al
|
|
290
|
+
* server se ha davvero ripreso (un pattern noto come "connection state
|
|
291
|
+
* recovery", ricerca del 05/09/2026: websocket.org/guides/reconnection/).
|
|
292
|
+
*
|
|
293
|
+
* ⛔ La risposta viene dallo STESSO controllo che fa `apri` («la voce esiste
|
|
294
|
+
* e non è chiusa»), letto un attimo prima: non è una seconda verità che può
|
|
295
|
+
* divergere dalla prima. Fra la lettura e la chiamata non c'è `await`.
|
|
296
|
+
*
|
|
297
|
+
* @returns {{voce:object, ripresa:boolean}} `ripresa:false` significa shell
|
|
298
|
+
* NUOVA — la prima volta in assoluto, oppure dopo che il reaper ha chiuso
|
|
299
|
+
* quella di prima.
|
|
300
|
+
*/
|
|
301
|
+
function apriDichiarando({ id, cartella, cols, rows }) {
|
|
302
|
+
const esistente = terminali.get(id);
|
|
303
|
+
const ripresa = Boolean(esistente && !esistente.chiusa);
|
|
304
|
+
return { voce: apri({ id, cartella, cols, rows }), ripresa };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
apri, apriDichiarando, scrivi, ridimensiona, segnaDisconnesso, chiudiForzato, reap, stato,
|
|
309
|
+
/** ⛔ Solo per i test — mai usato dal codice di produzione. */
|
|
310
|
+
_terminali: terminali,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const diagnostics = new Map();
|
|
4
|
+
const MESSAGES = Object.freeze({
|
|
5
|
+
CONFIG_INVALID: { title: 'Configurazione non pronta', explanation: 'Manca una parte della configurazione necessaria per completare questa operazione.', action: 'Apri Doctor e segui i controlli indicati, poi riprova.' },
|
|
6
|
+
TASK_CATALOG_UNAVAILABLE: { title: 'Elenco attività non disponibile', explanation: 'Il servizio che fornisce le attività non è ancora collegato.', action: 'Controlla Doctor o configura il servizio delle attività, poi riprova.' },
|
|
7
|
+
RUNTIME_NOT_AVAILABLE: { title: 'Servizio locale non disponibile', explanation: 'Non è stato possibile trovare un servizio locale pronto.', action: 'Apri Doctor per vedere cosa manca oppure scegli un servizio online.' },
|
|
8
|
+
RUNTIME_UNREACHABLE: { title: 'Servizio locale non raggiungibile', explanation: 'Il servizio locale non ha risposto.', action: 'Controlla che sia avviato in Doctor e riprova.' },
|
|
9
|
+
PATH_NOT_ALLOWED: { title: 'Percorso non consentito', explanation: 'Il percorso scelto è fuori dall’area autorizzata.', action: 'Scegli una cartella dentro il progetto aperto.' },
|
|
10
|
+
PROCESS_POLICY_REJECTED: { title: 'Operazione bloccata per sicurezza', explanation: 'Il comando richiesto non rientra nelle autorizzazioni correnti.', action: 'Controlla il permesso della sessione e riprova solo se riconosci il comando.' },
|
|
11
|
+
/*
|
|
12
|
+
* ⛔ 07/9, O-49 — senza una voce qui la busta portava la copia di INTERNAL_ERROR:
|
|
13
|
+
* «Si è verificato un problema imprevisto» e «Apri Doctor». Falso due volte: non è
|
|
14
|
+
* imprevisto, ed è l’unica cosa che Doctor non può spiegare. La scheda del permesso
|
|
15
|
+
* è semplicemente vecchia — si ricarica la sessione e si guarda cosa chiede adesso.
|
|
16
|
+
*/
|
|
17
|
+
APPROVAL_NOT_PENDING: { title: 'Richiesta di permesso scaduta', explanation: 'La sessione è andata avanti: quella domanda non aspetta più una risposta.', action: 'Ricarica la sessione e rispondi alla richiesta che vedi adesso, se ce n’è una.' },
|
|
18
|
+
/*
|
|
19
|
+
* ⛔⛔ 07/9, owner bloccato: «la sessione e ancora bloccata, non riesco a inviare messaggi e
|
|
20
|
+
* spunta errore toast». `SESSION_NOT_READY` NON era in questa mappa, quindi cadeva su
|
|
21
|
+
* INTERNAL_ERROR e a schermo arrivava «Si e verificato un problema imprevisto» — mentre il
|
|
22
|
+
* registro aveva gia scritto la frase giusta e AZIONABILE: «Questa sessione e stata interrotta
|
|
23
|
+
* da un riavvio del server e non puo essere ripresa: avvia una sessione nuova».
|
|
24
|
+
* ⇒ Il codice piu utile della mappa e quello che dice COSA FARE. Qui la spiegazione generica
|
|
25
|
+
* resta come rete, ma il motivo VERO del registro passa in `explanation` (vedi `problemFor`):
|
|
26
|
+
* una porta chiusa senza indicazione di dove sia quella aperta e il modo migliore per bloccare
|
|
27
|
+
* una persona su una schermata.
|
|
28
|
+
*/
|
|
29
|
+
SESSION_NOT_READY: { title: 'Sessione non pronta', explanation: 'Questa sessione non puo accettare l’azione richiesta nello stato in cui si trova.', action: 'Se e stata interrotta da un riavvio, avvia una sessione nuova: la conversazione resta leggibile qui.' },
|
|
30
|
+
/*
|
|
31
|
+
* ⛔⛔ 12/09, L5 — LA STESSA VORAGINE DI O-49, e per questo sono qui il giorno stesso in cui
|
|
32
|
+
* nascono i codici. Senza una voce in questa mappa una risposta cade sulla copia di
|
|
33
|
+
* INTERNAL_ERROR, e a schermo una ricerca cancellata dieci secondi fa diventa «Si è verificato
|
|
34
|
+
* un problema imprevisto · Apri Doctor, copia il riferimento»: falso due volte — non è
|
|
35
|
+
* imprevisto, ed è l'unica cosa che Doctor non può spiegare. Misurato interrogando la rotta
|
|
36
|
+
* vera prima di scrivere queste righe, non dedotto.
|
|
37
|
+
* ⛔ Nessuna di queste è un guasto: sono tre no diversi, e ognuna dice COSA FARE.
|
|
38
|
+
*/
|
|
39
|
+
RESEARCH_NOT_FOUND: { title: 'Ricerca non trovata', explanation: 'Questa ricerca approfondita non è più nel progetto: può essere stata eliminata.', action: 'Torna all’elenco delle ricerche: mostra quelle che ci sono adesso.' },
|
|
40
|
+
RESEARCH_CONFLICT: { title: 'Azione non possibile adesso', explanation: 'Questa ricerca non è nello stato che l’azione richiede — per esempio è già ferma, o è già finita.', action: 'Riapri la scheda della ricerca: dice come sta in questo momento.' },
|
|
41
|
+
RESEARCH_RECHECK_UNAVAILABLE: { title: 'Controllo delle fonti non possibile', explanation: 'Per ricontrollare le fonti serve un rapporto con i passaggi citati, e questa ricerca non ne ha.', action: 'Le ricerche nuove lo portano: questa si può rifare, oppure lasciarla com’è.' },
|
|
42
|
+
RESEARCH_INVALID: { title: 'Richiesta non valida', explanation: 'L’identificativo della ricerca non ha una forma ammessa.', action: 'Apri la ricerca dall’elenco invece di comporre l’indirizzo a mano.' },
|
|
43
|
+
INTERNAL_ERROR: { title: 'Operazione non riuscita', explanation: 'Si è verificato un problema imprevisto durante l’operazione.', action: 'Apri Doctor, copia il riferimento e riprova.' },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function safeCode(error) {
|
|
47
|
+
return typeof error?.code === 'string' && /^[A-Z0-9_]+$/u.test(error.code) ? error.code : 'INTERNAL_ERROR';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function referenceFor(code, requestId, operation) {
|
|
51
|
+
return `doctor-${createHash('sha256').update(`${code}|${requestId || ''}|${operation || ''}`).digest('hex').slice(0, 12)}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function safeDiagnosticDetail(error) {
|
|
55
|
+
if (typeof error?.message !== 'string') return '';
|
|
56
|
+
return error.message
|
|
57
|
+
.replace(/\b(?:sk|pk)-[A-Za-z0-9_-]+\b/giu, '[redacted]')
|
|
58
|
+
.replace(/\bsecret\b/giu, '[redacted]')
|
|
59
|
+
.replace(/\bTALOS_[A-Z0-9_]+\b/gu, '[setting]')
|
|
60
|
+
.replace(/\b[A-Za-z]:\\[^\r\n;]+/gu, '[path]')
|
|
61
|
+
.replace(/\/(?:Users|home|tmp|var|data)\/[^\r\n; ]+/giu, '[path]')
|
|
62
|
+
.replace(/\s+/gu, ' ')
|
|
63
|
+
.trim()
|
|
64
|
+
.slice(0, 240);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
* ⛔⛔ 07/9 — I codici il cui `message` e GIA scritto per una persona, e non per un log.
|
|
69
|
+
* Il registro, per una sessione che non puo ripartire, dice: «Questa sessione e stata interrotta da
|
|
70
|
+
* un riavvio del server e non puo essere ripresa: avvia una sessione nuova» — una frase che dice
|
|
71
|
+
* cosa e successo E cosa fare. Quella frase veniva BUTTATA e sostituita con «Si e verificato un
|
|
72
|
+
* problema imprevisto», e l'owner e rimasto bloccato su una schermata senza sapere dove fosse
|
|
73
|
+
* l'uscita.
|
|
74
|
+
* ⛔ La sostituzione generica esiste per una ragione buona — un messaggio d'errore grezzo puo
|
|
75
|
+
* portare percorsi, id, dettagli interni — e resta il comportamento predefinito. Qui si dichiara
|
|
76
|
+
* la sola eccezione: i codici dove chi ha scritto il messaggio lo ha scritto PER lo schermo.
|
|
77
|
+
* Aggiungerne uno vuol dire prendersi la responsabilita che quel testo sia leggibile e privo di
|
|
78
|
+
* dettagli interni: si guarda ogni `erroreAvvio` di quel codice prima di metterlo qui.
|
|
79
|
+
*/
|
|
80
|
+
const MESSAGGIO_GIA_PER_LA_PERSONA = new Set(['SESSION_NOT_READY']);
|
|
81
|
+
|
|
82
|
+
/** Un testo del registro e pubblicabile solo se e corto e non porta percorsi o identificatori. */
|
|
83
|
+
function messaggioPubblicabile(testo) {
|
|
84
|
+
const t = String(testo || '').trim();
|
|
85
|
+
if (t.length < 12 || t.length > 240) return '';
|
|
86
|
+
if (/[\/]{1}[\w.-]+[\/]|[0-9a-f]{8}-[0-9a-f]{4}/i.test(t)) return ''; // percorsi o id: restano nel log
|
|
87
|
+
return t;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function toPublicProblem(error, { requestId = '', operation = '' } = {}) {
|
|
91
|
+
const code = safeCode(error);
|
|
92
|
+
const copy = MESSAGES[code] ?? MESSAGES.INTERNAL_ERROR;
|
|
93
|
+
const doctorReference = referenceFor(code, requestId, operation);
|
|
94
|
+
diagnostics.set(doctorReference, Object.freeze({ code, operation: String(operation || 'operation'), requestId: String(requestId || ''), detail: safeDiagnosticDetail(error) }));
|
|
95
|
+
const vero = MESSAGGIO_GIA_PER_LA_PERSONA.has(code) ? messaggioPubblicabile(error?.message) : '';
|
|
96
|
+
return { title: copy.title, explanation: vero || copy.explanation, action: copy.action, doctorReference };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function logDiagnosticProblem(error, { requestId = '', operation = '', logger = console } = {}) {
|
|
100
|
+
const publicProblem = toPublicProblem(error, { requestId, operation });
|
|
101
|
+
const code = safeCode(error);
|
|
102
|
+
try { logger?.warn?.(`[${publicProblem.doctorReference}] ${operation || 'operation'} (${code})`); } catch { /* un logger guasto non cambia l’esito dell’operazione */ }
|
|
103
|
+
return publicProblem.doctorReference;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getDiagnosticProblem(reference) {
|
|
107
|
+
const detail = diagnostics.get(reference);
|
|
108
|
+
return detail ? { ...detail } : null;
|
|
109
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { talosResearchIsResting, talosResearchIsTerminal, talosResearchProgressOf } from './run.mjs'
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* PORTO FEDELE di AVM/mobile/src/lib/research/researchCard.ts (211 righe, 11/09/2026).
|
|
5
|
+
*
|
|
6
|
+
* ⛔ `run.mjs` è il porto di `researchRun.ts` e lo cura un'altra sessione, in
|
|
7
|
+
* parallelo: qui se ne importa solo il nome concordato.
|
|
8
|
+
*
|
|
9
|
+
* Che aspetto ha una ricerca da fuori, prima di aprirla.
|
|
10
|
+
*
|
|
11
|
+
* La stazione era un modulo con sotto un elenco di righe espandibili, e le
|
|
12
|
+
* righe dicevano `3/3 · done`. Quella è la vista della macchina. Questa è
|
|
13
|
+
* quella del lettore: che cosa ho chiesto, quando, com'è finita e — quando è
|
|
14
|
+
* finita — **quanto regge**.
|
|
15
|
+
*
|
|
16
|
+
* L'ultima parte è tutto l'argomento. La ricerca sui concorrenti (2026-08-03)
|
|
17
|
+
* ha trovato che tutti e cinque i prodotti guidano col volume — «56 siti»,
|
|
18
|
+
* «hundreds of sources» — e nessuno dice se le affermazioni hanno retto. La sua
|
|
19
|
+
* conclusione è che la vittoria non sono più citazioni, è un resoconto migliore
|
|
20
|
+
* della relazione fra affermazione e prova. Quindi una scheda qui guida col
|
|
21
|
+
* bilancio e mai col conteggio delle fonti.
|
|
22
|
+
*
|
|
23
|
+
* Pura apposta: una scheda è un riassunto, e un riassunto che ha bisogno di un
|
|
24
|
+
* database è un riassunto che non si può provare.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @import { TalosResearchRun, TalosResearchStatus } from './run.mjs'
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* I secchi che il filtro offre. Una corsa sta in esattamente uno.
|
|
33
|
+
* @typedef {'running' | 'paused' | 'unfinished' | 'cancelled' | 'done' | 'failed'} TalosResearchBucket
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {object} TalosResearchStanding
|
|
38
|
+
* @property {number} total
|
|
39
|
+
* @property {number} supported
|
|
40
|
+
* @property {number} partial
|
|
41
|
+
* @property {number} unsupported
|
|
42
|
+
* @property {number} unchecked
|
|
43
|
+
* @property {number} [contested] ⛔ CONTESA-01 — le affermazioni su cui le fonti
|
|
44
|
+
* non concordano. Opzionale, e non per pigrizia: i rapporti scritti prima del
|
|
45
|
+
* 2026-08-20 non hanno questo conto, e leggerlo come zero su un rapporto
|
|
46
|
+
* vecchio direbbe «nessun disaccordo» dove la verità è «non guardato». Chi lo
|
|
47
|
+
* mostra usa `?? 0` sapendo cosa sta facendo.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {object} TalosResearchCard
|
|
52
|
+
* @property {string} id
|
|
53
|
+
* @property {string} question Quello che l'elenco MOSTRA: l'etichetta scelta, o la domanda quando non ce n'è.
|
|
54
|
+
* @property {string} originalQuestion La domanda come è stata posta, tenuta accanto all'etichetta perché la provenienza sopravviva a una rinomina.
|
|
55
|
+
* @property {boolean} renamed Vero quando qualcuno ha dato un nome suo a questa ricerca.
|
|
56
|
+
* @property {string} startedAt
|
|
57
|
+
* @property {string} updatedAt
|
|
58
|
+
* @property {TalosResearchBucket} bucket
|
|
59
|
+
* @property {TalosResearchStatus} status La parola del giornale, che il secchio apposta sfuma.
|
|
60
|
+
* @property {number} done
|
|
61
|
+
* @property {number} total
|
|
62
|
+
* @property {number} failedSteps Rami falliti, così una scheda può ammettere un risultato parziale invece di nasconderlo.
|
|
63
|
+
* @property {TalosResearchStanding | null} standing Presente solo una volta letto il rapporto; l'elenco non lo aspetta.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* In quale secchio sta una corsa.
|
|
68
|
+
*
|
|
69
|
+
* `running` lo decide il registro VIVO, non il giornale: una corsa che il
|
|
70
|
+
* giornale chiama incompiuta può essere in volo proprio adesso, e chiamarla
|
|
71
|
+
* interrotta mentre lavora è esattamente la bugia che questo rifacimento è
|
|
72
|
+
* nato per togliere. Il giornale risponde a «che cosa è stato scritto», mai a
|
|
73
|
+
* «che cosa sta succedendo».
|
|
74
|
+
*
|
|
75
|
+
* @param {TalosResearchRun} run
|
|
76
|
+
* @param {boolean} isRunning
|
|
77
|
+
* @returns {TalosResearchBucket}
|
|
78
|
+
*/
|
|
79
|
+
export function talosResearchBucketOf(run, isRunning) {
|
|
80
|
+
if (isRunning) return 'running'
|
|
81
|
+
if (run.status === 'failed') return 'failed'
|
|
82
|
+
if (run.status === 'done') return 'done'
|
|
83
|
+
// Annullata è una DECISIONE, come la pausa, e per la stessa ragione non sta
|
|
84
|
+
// con le corse che il telefono ha ucciso. Peggio che disordinato: quelle
|
|
85
|
+
// sono archiviate come «interrotte», il che promette che si possano
|
|
86
|
+
// riprendere — e una ricerca annullata è l'unica cosa qui che non riparte mai.
|
|
87
|
+
if (run.status === 'cancelled') return 'cancelled'
|
|
88
|
+
// In pausa ha un secchio SUO, ed è il punto stesso della pausa: la persona
|
|
89
|
+
// ha fermato apposta e conta di tornarci. Archiviarla accanto alle corse che
|
|
90
|
+
// il telefono ha ucciso le direbbe che la sua decisione è stata un incidente.
|
|
91
|
+
// `pause_requested` sta qui anche lui — è una pausa che non ha finito di
|
|
92
|
+
// atterrare, non una situazione diversa per chi legge.
|
|
93
|
+
if (talosResearchIsResting(run.status)) return 'paused'
|
|
94
|
+
// Tutto il resto — planning, collecting, synthesising, verifying — è lavoro
|
|
95
|
+
// che si è fermato senza finire. Un secchio solo, perché dal lato del
|
|
96
|
+
// lettore sono la stessa situazione: deve qualcosa e non sta succedendo niente.
|
|
97
|
+
return 'unfinished'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {TalosResearchRun} run
|
|
102
|
+
* @param {{ isRunning: boolean, standing?: TalosResearchStanding | null }} options
|
|
103
|
+
* @returns {TalosResearchCard}
|
|
104
|
+
*/
|
|
105
|
+
export function talosResearchCardOf(run, options) {
|
|
106
|
+
const progress = talosResearchProgressOf(run)
|
|
107
|
+
return {
|
|
108
|
+
id: run.id,
|
|
109
|
+
question: run.title ?? run.question,
|
|
110
|
+
originalQuestion: run.question,
|
|
111
|
+
renamed: run.title !== null,
|
|
112
|
+
startedAt: run.startedAt,
|
|
113
|
+
updatedAt: run.updatedAt,
|
|
114
|
+
bucket: talosResearchBucketOf(run, options.isRunning),
|
|
115
|
+
status: run.status,
|
|
116
|
+
done: progress.done,
|
|
117
|
+
total: progress.total,
|
|
118
|
+
failedSteps: run.steps.filter((step) => step.state === 'failed').length,
|
|
119
|
+
standing: options.standing ?? null,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Quanto regge un rapporto finito, come un numero solo fra 0 e 1.
|
|
125
|
+
*
|
|
126
|
+
* Il sostegno parziale conta metà. Non è una furbizia: un'affermazione che la
|
|
127
|
+
* fonte sostiene in parte sta davvero in mezzo, e arrotondarla a «sostenuta» è
|
|
128
|
+
* il modo in cui un rapporto finisce per sembrare più solido di quanto sia —
|
|
129
|
+
* che è il guasto che tutta la verifica esiste per impedire. Il non verificato
|
|
130
|
+
* conta zero per la stessa ragione: «non siamo riusciti a controllare» non è
|
|
131
|
+
* una promozione.
|
|
132
|
+
*
|
|
133
|
+
* ⛔ Una CONTESA vale zero, come una smentita, e la ragione è la stessa che
|
|
134
|
+
* regge tutta questa funzione: se le fonti si contraddicono, quell'affermazione
|
|
135
|
+
* non è una prova. Contarla anche solo per metà rifarebbe esattamente il
|
|
136
|
+
* difetto che la verifica esiste per impedire — un rapporto che sembra più
|
|
137
|
+
* solido di quanto sia.
|
|
138
|
+
*
|
|
139
|
+
* @param {TalosResearchStanding | null} standing
|
|
140
|
+
* @returns {number | null}
|
|
141
|
+
*/
|
|
142
|
+
export function talosResearchSolidity(standing) {
|
|
143
|
+
if (!standing || standing.total === 0) return null
|
|
144
|
+
return (standing.supported + (standing.partial * 0.5)) / standing.total
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Se un rapporto finito merita un secondo sguardo.
|
|
149
|
+
*
|
|
150
|
+
* Qualsiasi cosa non sostenuta, o una solidità sotto due terzi. La soglia è un
|
|
151
|
+
* giudizio, ed è scritta qui invece che sparsa in un modello di pagina, così la
|
|
152
|
+
* si può contestare in un posto solo.
|
|
153
|
+
*
|
|
154
|
+
* @param {TalosResearchCard} card
|
|
155
|
+
* @returns {boolean}
|
|
156
|
+
*/
|
|
157
|
+
export function talosResearchNeedsAttention(card) {
|
|
158
|
+
if (card.failedSteps > 0) return true
|
|
159
|
+
if (!card.standing) return false
|
|
160
|
+
if (card.standing.unsupported > 0) return true
|
|
161
|
+
// ⛔ Anche UNA contesa merita un secondo sguardo: vuol dire che su quel
|
|
162
|
+
// punto il mondo non concorda, ed è precisamente il caso in cui una
|
|
163
|
+
// persona vuole leggere le fonti invece di fidarsi del riassunto.
|
|
164
|
+
if ((card.standing.contested ?? 0) > 0) return true
|
|
165
|
+
const solidity = talosResearchSolidity(card.standing)
|
|
166
|
+
return solidity !== null && solidity < 2 / 3
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Il file del rapporto che la sintesi ha scritto, quando ce n'è uno da leggere.
|
|
171
|
+
*
|
|
172
|
+
* Vive qui invece che in ogni schermata: l'elenco, la pagina del rapporto, la
|
|
173
|
+
* pagina dell'affermazione e quella della fonte ne hanno tutte bisogno, e
|
|
174
|
+
* quattro copie dello stesso `find` è il modo in cui cominciano a non essere
|
|
175
|
+
* più d'accordo su che cosa conti come finito.
|
|
176
|
+
*
|
|
177
|
+
* @param {TalosResearchRun} run
|
|
178
|
+
* @returns {string | null}
|
|
179
|
+
*/
|
|
180
|
+
export function talosResearchReportRefOf(run) {
|
|
181
|
+
const synthesis = run.steps.find((step) => step.kind === 'synthesise' && step.state === 'done')
|
|
182
|
+
return synthesis?.resultRef ?? null
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Il filtro, applicato. `all` è un secchio che la UI offre e che i dati non hanno mai.
|
|
187
|
+
*
|
|
188
|
+
* @param {readonly TalosResearchCard[]} cards
|
|
189
|
+
* @param {TalosResearchBucket | 'all'} bucket
|
|
190
|
+
* @param {string} query
|
|
191
|
+
* @returns {readonly TalosResearchCard[]}
|
|
192
|
+
*/
|
|
193
|
+
export function talosResearchFilterCards(cards, bucket, query) {
|
|
194
|
+
const needle = query.trim().toLowerCase()
|
|
195
|
+
return cards.filter((card) => {
|
|
196
|
+
if (bucket !== 'all' && card.bucket !== bucket) return false
|
|
197
|
+
if (needle.length === 0) return true
|
|
198
|
+
return card.question.toLowerCase().includes(needle)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Che cosa si può fare a questa ricerca, adesso.
|
|
204
|
+
*
|
|
205
|
+
* Un elenco invece di un insieme di voci disabilitate: quali azioni esistono è
|
|
206
|
+
* un fatto sulla cosa, e un menu di opzioni morte fa lavorare il lettore per
|
|
207
|
+
* capire perché siano morte. Una ricerca viva offre Pausa e Annulla; una in
|
|
208
|
+
* pausa offre Riprendi; una annullata nessuna delle due, perché il motore si
|
|
209
|
+
* rifiuta di guidare una corsa finita e far finta di sì sarebbe un pulsante che
|
|
210
|
+
* mente.
|
|
211
|
+
*
|
|
212
|
+
* Elimina manca mentre gira, e non è schizzinosità: togliere il giornale da
|
|
213
|
+
* sotto all'unico scrittore distruggerebbe l'unica prova che un passo già
|
|
214
|
+
* mandato a un fornitore era stato pagato. Prima si ferma.
|
|
215
|
+
*
|
|
216
|
+
* @typedef {'open' | 'rename' | 'pause' | 'resume' | 'cancel' | 'delete'} TalosResearchAction
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {TalosResearchCard} card
|
|
221
|
+
* @returns {readonly TalosResearchAction[]}
|
|
222
|
+
*/
|
|
223
|
+
export function talosResearchActionsFor(card) {
|
|
224
|
+
/** @type {TalosResearchAction[]} */
|
|
225
|
+
const actions = ['open', 'rename']
|
|
226
|
+
if (card.bucket === 'running') {
|
|
227
|
+
actions.push('pause', 'cancel')
|
|
228
|
+
// Niente elimina: vedi sopra.
|
|
229
|
+
return actions
|
|
230
|
+
}
|
|
231
|
+
// In riposo o solo interrotta — entrambe devono ancora lavoro ed entrambe riprendono.
|
|
232
|
+
if (!talosResearchIsTerminal(card.status)) actions.push('resume', 'cancel')
|
|
233
|
+
actions.push('delete')
|
|
234
|
+
return actions
|
|
235
|
+
}
|