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,560 @@
|
|
|
1
|
+
import { talosSafeFileStem } from '../document-filename.mjs'
|
|
2
|
+
import { talosResearchBibtex, talosResearchRis } from './citations.mjs'
|
|
3
|
+
import { talosResearchParseReport } from './report.mjs'
|
|
4
|
+
import {
|
|
5
|
+
escapeHtml,
|
|
6
|
+
inlineInHtml,
|
|
7
|
+
markdownInBlocchiReport,
|
|
8
|
+
markdownInHtml,
|
|
9
|
+
} from './markdown-server.mjs'
|
|
10
|
+
import {
|
|
11
|
+
TALOS_RESEARCH_PDF_DEFAULT_TONE,
|
|
12
|
+
TALOS_RESEARCH_PDF_TONES,
|
|
13
|
+
talosResearchPdfSpec,
|
|
14
|
+
} from './pdf.mjs'
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* ════════════════════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
* ⭐⭐⭐⭐ 12/09/2026 — LE ESPORTAZIONI DELLA RICERCA APPROFONDITA, IN UN POSTO SOLO
|
|
19
|
+
* ════════════════════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
*
|
|
21
|
+
* Owner 12/09/2026: «la ricerca approfondita deve avere una suite di esportazioni COMPLETA».
|
|
22
|
+
*
|
|
23
|
+
* Cosa c'era: il menu della sezione sapeva fare **tre** cose, e tutte e tre **nel browser** —
|
|
24
|
+
* Markdown, BibTeX, RIS, costruiti in `frontend/src/components/ricerca-dettaglio.js` e salvati
|
|
25
|
+
* con un `<a download>`. Il mobile aveva in più il PDF a tre toni (`researchPdf.ts`), il
|
|
26
|
+
* desktop aveva già i generatori di documenti veri (`document-generator.mjs`: PDF, DOCX, XLSX,
|
|
27
|
+
* HTML) usati da `document_create` — e i due non si erano mai incontrati.
|
|
28
|
+
*
|
|
29
|
+
* ⛔⛔ PERCHÉ IL SERVER E NON IL BROWSER, visto che tre formati lì funzionavano già.
|
|
30
|
+
* 1. `docx` e `pdf` **non si possono** fare nel browser senza spedirci `docx`, `pdfmake`,
|
|
31
|
+
* `jszip` e i font: sono le sette dipendenze che l'owner ha autorizzato **solo** nel
|
|
32
|
+
* backend (`document-generator.mjs`, 28/8: «mai nel bundle frontend»).
|
|
33
|
+
* 2. Un file che nasce sul server ha un **indirizzo**: si può riscaricare, si può mandare a
|
|
34
|
+
* un'altra macchina, e domani lo può depositare in Libreria. Un `Blob` no.
|
|
35
|
+
* 3. Il record verificabile lo rilegge **un lettore solo** (`report.mjs`). Il browser che
|
|
36
|
+
* ri-parsa il blocco recintato è un secondo lettore, in un altro linguaggio, che diverge
|
|
37
|
+
* dal primo alla prima modifica del formato.
|
|
38
|
+
*
|
|
39
|
+
* ⛔ QUESTO MODULO NON CONOSCE HTTP. Prende la **scheda** che la rotta ha già letto da
|
|
40
|
+
* `sessionRegistry.leggiRicerca` (la stessa di `GET …/research/:id`) e torna
|
|
41
|
+
* `{formato, nomeFile, mediaType, bytes}`. La rotta è un passacarte: nessuna decisione sul
|
|
42
|
+
* contenuto vive lì, e nessuna decisione su stati e intestazioni vive qui.
|
|
43
|
+
*
|
|
44
|
+
* ⛔ NIENTE PERCORSI DA FUORI. Il nome del file nasce dalla **domanda della ricerca**, non da
|
|
45
|
+
* niente che il chiamante possa scrivere: l'unico testo che arriva dalla query sono
|
|
46
|
+
* `formato` e `tono`, due allowlist chiuse. Non si apre, non si scrive e non si legge nessun
|
|
47
|
+
* file su disco da qui.
|
|
48
|
+
*
|
|
49
|
+
* ── Ricerca web PRIMA di scrivere (fonte + data) ────────────────────────────────────────────
|
|
50
|
+
* · **RFC 6266** (`Use of the Content-Disposition Header Field in HTTP`), letta il 12/09/2026:
|
|
51
|
+
* «"filename" and "filename*" differ only in that "filename*" uses the encoding defined in
|
|
52
|
+
* RFC5987», e quando ci sono entrambi «recipients SHOULD pick "filename*" and ignore
|
|
53
|
+
* "filename"». ⛔ E il vincolo che ha cambiato QUESTO file: il destinatario deve «strip all
|
|
54
|
+
* but the last path segment» e «ignore or substitute names» con significato nel filesystem —
|
|
55
|
+
* «..», «~», i nomi di dispositivo. ⇒ `nomeSicuroDiEsportazione` non si fida di
|
|
56
|
+
* `talosSafeFileStem` da solo: quello toglie `/` `\` `:` ma **lascia passare `..`**, e una
|
|
57
|
+
* domanda che è letteralmente «..» darebbe `...md`. Provato nei due versi.
|
|
58
|
+
* · **MDN, `Content-Disposition`**, letta il 12/09/2026: «avoid percent escape sequences in
|
|
59
|
+
* `filename`, because they are handled inconsistently across browsers (Firefox and Chrome
|
|
60
|
+
* decode them, while Safari does not)» ⇒ il ripiego ASCII non deve contenere `%`. È già
|
|
61
|
+
* così in `nomiPerContentDisposition` (sostituisce con `_`, non percent-codifica): qui si
|
|
62
|
+
* conferma la scelta invece di riscriverne una seconda.
|
|
63
|
+
* · **DOI Citation Formatter** (citation.doi.org/docs.html, ex citation.crosscite.org), letta
|
|
64
|
+
* il 12/09/2026: i tipi di contenuto per la negoziazione sono **`application/x-bibtex`** e
|
|
65
|
+
* **`application/x-research-info-systems`**, supportati da Crossref, DataCite e mEDRA.
|
|
66
|
+
* · **IANA Media Types registry**, letto il 12/09/2026: né BibTeX né RIS sono **registrati**.
|
|
67
|
+
* ⇒ i due tipi qui sopra sono `x-` per forza, non per pigrizia — e sono gli stessi che il
|
|
68
|
+
* frontend usa già oggi nel suo `<a download>`: due risposte diverse per lo stesso file
|
|
69
|
+
* farebbero aprire lo stesso `.bib` a due programmi diversi.
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
/** Un guasto di esportazione che la rotta traduce in uno stato HTTP. */
|
|
73
|
+
export class EsportazioneRicercaError extends Error {
|
|
74
|
+
/** @param {string} messaggio @param {string} code */
|
|
75
|
+
constructor(messaggio, code) {
|
|
76
|
+
super(messaggio)
|
|
77
|
+
this.name = 'EsportazioneRicercaError'
|
|
78
|
+
this.code = code
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* ⛔ L'inventario dei formati, e il campo che decide TUTTO il resto: `vuoleIlRecord`.
|
|
84
|
+
*
|
|
85
|
+
* Una ricerca `senza-rapporto` ha depositato qualcosa che il cancello ha respinto: prosa vera,
|
|
86
|
+
* pagata, senza il record recintato. Da lì `json`, `bib`, `ris` e `fonti` **non si possono**
|
|
87
|
+
* fare — non «escono vuoti»: non esistono. Un `.bib` di zero voci consegnato senza dire niente
|
|
88
|
+
* è il segno di verifica falso che tutto il disegno esiste per togliere ⇒ 409 col motivo.
|
|
89
|
+
* `md`, `html` e `pdf` invece escono lo stesso, perché la prosa c'è: con la dicitura «senza
|
|
90
|
+
* verifiche» stampata sopra, così nessuno la scambia per un rapporto verificato.
|
|
91
|
+
*/
|
|
92
|
+
const FORMATI = Object.freeze({
|
|
93
|
+
md: { estensione: 'md', mediaType: 'text/markdown; charset=utf-8', vuoleIlRecord: false },
|
|
94
|
+
html: { estensione: 'html', mediaType: 'text/html; charset=utf-8', vuoleIlRecord: false },
|
|
95
|
+
pdf: { estensione: 'pdf', mediaType: 'application/pdf', vuoleIlRecord: false },
|
|
96
|
+
docx: {
|
|
97
|
+
estensione: 'docx',
|
|
98
|
+
mediaType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
99
|
+
vuoleIlRecord: false,
|
|
100
|
+
},
|
|
101
|
+
json: { estensione: 'json', mediaType: 'application/json; charset=utf-8', vuoleIlRecord: true },
|
|
102
|
+
bib: { estensione: 'bib', mediaType: 'application/x-bibtex; charset=utf-8', vuoleIlRecord: true },
|
|
103
|
+
ris: {
|
|
104
|
+
estensione: 'ris',
|
|
105
|
+
mediaType: 'application/x-research-info-systems; charset=utf-8',
|
|
106
|
+
vuoleIlRecord: true,
|
|
107
|
+
},
|
|
108
|
+
/* ⛔ `fonti` è un Markdown, e il suo nome lo dice: `<domanda>-fonti.md`. Un secondo `.md`
|
|
109
|
+
con lo stesso nome del rapporto finirebbe in «(1)» nella cartella dei download, e nessuno
|
|
110
|
+
saprebbe quale dei due è. */
|
|
111
|
+
fonti: { estensione: 'md', suffisso: '-fonti', mediaType: 'text/markdown; charset=utf-8', vuoleIlRecord: true },
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export const FORMATI_ESPORTAZIONE = Object.freeze(Object.keys(FORMATI))
|
|
115
|
+
export const TONI_ESPORTAZIONE = TALOS_RESEARCH_PDF_TONES
|
|
116
|
+
export const TONO_ESPORTAZIONE_PREDEFINITO = TALOS_RESEARCH_PDF_DEFAULT_TONE
|
|
117
|
+
|
|
118
|
+
/** ⛔ La frase che tiene separato «rapporto» da «prosa depositata». Una sola, in un posto solo. */
|
|
119
|
+
export const DICITURA_SENZA_VERIFICHE = 'Rapporto SENZA VERIFICHE: questa ricerca non porta il record verificabile, quindi nessuna affermazione è stata confrontata con la sua fonte.'
|
|
120
|
+
|
|
121
|
+
/** Il tetto del nome, in byte UTF-8: lo stesso che `document-generator.mjs` usa per i suoi file. */
|
|
122
|
+
const BYTE_MASSIMI_DEL_NOME = 60
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Il nome del file, dalla domanda della ricerca. Puro, e si prova senza un server.
|
|
126
|
+
*
|
|
127
|
+
* ⛔ Due difese e non una: `talosSafeFileStem` toglie i caratteri vietati dal filesystem
|
|
128
|
+
* (`/ \ : " * ? < > |`, i controlli, i marcatori di direzione) e taglia su un confine di
|
|
129
|
+
* parola; qui sopra si aggiunge quella che RFC 6266 chiede e che quella non fa — un nome che
|
|
130
|
+
* sia **solo punti e spazi** (`.`, `..`, `. .`) non è un nome, è un percorso.
|
|
131
|
+
*
|
|
132
|
+
* @param {string|null|undefined} domanda
|
|
133
|
+
* @param {string} formato
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
export function nomeSicuroDiEsportazione(domanda, formato) {
|
|
137
|
+
const forma = FORMATI[formato]
|
|
138
|
+
if (!forma) throw new EsportazioneRicercaError(`unknown export format: ${formato}`, 'RESEARCH_INVALID')
|
|
139
|
+
const grezzo = talosSafeFileStem(String(domanda ?? ''), BYTE_MASSIMI_DEL_NOME, 'ricerca')
|
|
140
|
+
/*
|
|
141
|
+
* ⛔ `..` sopravvive a `talosSafeFileStem` (il punto non è un carattere vietato su nessun
|
|
142
|
+
* filesystem): qui cade, e con lui ogni nome fatto di soli punti e spazi.
|
|
143
|
+
* ⛔ La classe è `[.\s]+` e non `\.+`, e la differenza l'ha trovata il test: `../../etc/passwd`
|
|
144
|
+
* diventa «.. .. etc passwd» (le barre sono già spazi), e togliere la sola PRIMA sequenza di
|
|
145
|
+
* punti lasciava un nome che comincia ancora per punto — cioè un file nascosto su ogni
|
|
146
|
+
* sistema Unix. Si toglie tutto il prefisso di punti E spazi, quante volte si ripeta.
|
|
147
|
+
*/
|
|
148
|
+
const stelo = /[^.\s]/u.test(grezzo) ? grezzo.replace(/^[.\s]+/u, '').trim() || 'ricerca' : 'ricerca'
|
|
149
|
+
return `${stelo}${forma.suffisso ?? ''}.${forma.estensione}`
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const CODIFICA = new TextEncoder()
|
|
153
|
+
|
|
154
|
+
/** La sola data, dall'ISO: è ciò che una citazione vuole, e l'ora non aggiunge niente. */
|
|
155
|
+
function soloLaData(iso) {
|
|
156
|
+
return /^\d{4}-\d{2}-\d{2}/.exec(String(iso ?? ''))?.[0] ?? ''
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Il testo che la ricerca ha prodotto, qualunque cancello abbia passato — o `null`.
|
|
161
|
+
* ⛔ `contenutoRespinto` non si butta: è il prodotto di una corsa pagata.
|
|
162
|
+
*/
|
|
163
|
+
function prosaDisponibile(ricerca) {
|
|
164
|
+
for (const campo of [ricerca?.contenutoRapporto, ricerca?.contenutoRespinto]) {
|
|
165
|
+
if (typeof campo === 'string' && campo.trim().length > 0) return campo
|
|
166
|
+
}
|
|
167
|
+
return null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Il Markdown senza il blocco recintato. Serve a `docx` e al ripiego del `pdf`: il record è
|
|
172
|
+
* fatto per una macchina, e dentro un documento impaginato sarebbe una pagina di JSON.
|
|
173
|
+
* ⛔ Il `md` invece lo TIENE — è ciò che rende quel file ri-verificabile.
|
|
174
|
+
*/
|
|
175
|
+
function senzaIlRecinto(testo) {
|
|
176
|
+
return String(testo ?? '').replace(/```talos-research-report[\s\S]*?```/g, '').trimEnd()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Il record recintato della scheda, o `null`. ⛔ Il lettore è UNO: `talosResearchParseReport`,
|
|
181
|
+
* lo stesso che scrive il rapporto. Qui non si ri-parsa niente a mano.
|
|
182
|
+
*/
|
|
183
|
+
export function recordDellaScheda(ricerca) {
|
|
184
|
+
const testo = ricerca?.contenutoRapporto
|
|
185
|
+
return typeof testo === 'string' ? talosResearchParseReport(testo) : null
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Le citazioni, quattro campi e nient'altro.
|
|
190
|
+
*
|
|
191
|
+
* ⛔ Cosa NON esce, ed è la decisione di privacy di `researchCitationExport.ts` che qui si
|
|
192
|
+
* rispetta: la domanda, il modello che ha giudicato, l'identificativo della ricerca, la chat.
|
|
193
|
+
* Un file di bibliografia finisce in una cartella condivisa, in un allegato, in un
|
|
194
|
+
* repository: è il posto meno controllato in cui un dato personale possa arrivare.
|
|
195
|
+
*/
|
|
196
|
+
function citazioniDaRecord(record, letteAlle) {
|
|
197
|
+
const quando = soloLaData(letteAlle)
|
|
198
|
+
return (record.sources ?? []).map((fonte) => ({
|
|
199
|
+
url: String(fonte?.url ?? ''),
|
|
200
|
+
title: String(fonte?.title ?? ''),
|
|
201
|
+
publishedAt: typeof fonte?.publishedAt === 'string' ? fonte.publishedAt : null,
|
|
202
|
+
accessedAt: quando,
|
|
203
|
+
}))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Quando l'abbiamo letta: la conclusione se c'è, altrimenti l'avvio. Mai «adesso». */
|
|
207
|
+
function letteAlle(ricerca) {
|
|
208
|
+
return ricerca?.conclusaAlle ?? ricerca?.avviataAlle ?? null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/* ─────────────────────────────── i costruttori, uno per formato ─────────────────────────── */
|
|
212
|
+
|
|
213
|
+
function testoMarkdown(ricerca, record) {
|
|
214
|
+
const prosa = prosaDisponibile(ricerca)
|
|
215
|
+
if (!prosa) return null
|
|
216
|
+
// Col record: il rapporto COM'È — recinto compreso, perché è quello che lo rende
|
|
217
|
+
// ri-verificabile da chi lo riceve.
|
|
218
|
+
if (record) return prosa
|
|
219
|
+
return `> ⛔ ${DICITURA_SENZA_VERIFICHE}\n\n${senzaIlRecinto(prosa)}\n`
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Il JSON verificabile: il record **e** la meta che il record non ha.
|
|
224
|
+
*
|
|
225
|
+
* ⛔ `bilancio` e `spesa` non si ricalcolano qui: sono quelli che la scheda ha già, cioè quelli
|
|
226
|
+
* che la sezione mostra a schermo. Un file che dicesse numeri diversi da quelli sotto gli
|
|
227
|
+
* occhi di chi lo scarica sarebbe la peggiore delle due verità.
|
|
228
|
+
*/
|
|
229
|
+
function testoJson(ricerca, record) {
|
|
230
|
+
return `${JSON.stringify({
|
|
231
|
+
schema: 'talos.research.export.v1',
|
|
232
|
+
ricerca: {
|
|
233
|
+
id: ricerca.id ?? null,
|
|
234
|
+
domanda: ricerca.domanda ?? null,
|
|
235
|
+
titolo: ricerca.titolo ?? null,
|
|
236
|
+
stato: ricerca.stato ?? null,
|
|
237
|
+
modello: ricerca.modello ?? null,
|
|
238
|
+
avviataAlle: ricerca.avviataAlle ?? null,
|
|
239
|
+
conclusaAlle: ricerca.conclusaAlle ?? null,
|
|
240
|
+
bilancio: ricerca.bilancio ?? null,
|
|
241
|
+
proveDistinte: ricerca.proveDistinte ?? 0,
|
|
242
|
+
spesa: ricerca.spesa ?? null,
|
|
243
|
+
giudice: ricerca.giudice ?? null,
|
|
244
|
+
},
|
|
245
|
+
record,
|
|
246
|
+
}, null, 2)}\n`
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** L'elenco delle fonti, in Markdown: titolo, indirizzo, data dichiarata, passaggi citati. */
|
|
250
|
+
function testoFonti(ricerca, record) {
|
|
251
|
+
const righe = [
|
|
252
|
+
`# Fonti — ${ricerca.domanda ?? 'ricerca'}`,
|
|
253
|
+
'',
|
|
254
|
+
`${record.sources.length} fonti, ${record.claims.length} affermazioni.`,
|
|
255
|
+
'',
|
|
256
|
+
]
|
|
257
|
+
record.sources.forEach((fonte, indice) => {
|
|
258
|
+
const numero = indice + 1
|
|
259
|
+
righe.push(`## ${numero}. ${fonte.title || dominioOIndirizzo(fonte.url)}`)
|
|
260
|
+
righe.push('')
|
|
261
|
+
righe.push(`- Indirizzo: ${fonte.url}`)
|
|
262
|
+
righe.push(`- Data dichiarata: ${fonte.publishedAt ?? 'non dichiarata'}`)
|
|
263
|
+
righe.push(`- Come è stata ottenuta: ${fonte.obtained === 'page' ? 'pagina letta' : 'solo estratto dal motore di ricerca'}`)
|
|
264
|
+
/* ⛔ `sourceIndex` è 1-BASED nel record (vedi `pdf.mjs`): confrontare con `indice`
|
|
265
|
+
attribuirebbe ogni passaggio alla fonte precedente. */
|
|
266
|
+
const passaggi = record.claims.filter((c) => Number(c?.sourceIndex) === numero)
|
|
267
|
+
righe.push('')
|
|
268
|
+
if (passaggi.length === 0) {
|
|
269
|
+
righe.push('Nessuna affermazione poggia su questa fonte.')
|
|
270
|
+
} else {
|
|
271
|
+
righe.push('Passaggi citati:')
|
|
272
|
+
righe.push('')
|
|
273
|
+
for (const claim of passaggi) {
|
|
274
|
+
const passaggio = String(claim.passage ?? '').trim()
|
|
275
|
+
righe.push(passaggio
|
|
276
|
+
? `- «${passaggio}» — ${claim.text}`
|
|
277
|
+
: `- (il passaggio citato non è stato ritrovato nel testo della fonte) — ${claim.text}`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
righe.push('')
|
|
281
|
+
})
|
|
282
|
+
return righe.join('\n')
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Il dominio, o l'indirizzo grezzo: un titolo mancante non diventa una riga vuota. */
|
|
286
|
+
function dominioOIndirizzo(url) {
|
|
287
|
+
try {
|
|
288
|
+
return new URL(url).hostname || String(url ?? 'fonte')
|
|
289
|
+
} catch {
|
|
290
|
+
return String(url ?? 'fonte')
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* La pagina autonoma: CSS incorporato, **nessuno script**, e tema chiaro e scuro.
|
|
296
|
+
*
|
|
297
|
+
* ⛔ Niente `<script>`, e non è prudenza generica: questo file esce dalla nostra porta e viene
|
|
298
|
+
* aperto da un browser qualunque con `file://`. Il testo dentro (domanda, affermazioni, url,
|
|
299
|
+
* passaggi) viene dal web, cioè da chi ha scritto quelle pagine ⇒ ogni valore passa da
|
|
300
|
+
* `escapeHtml`, e la risposta esce comunque con `Content-Disposition: attachment` e
|
|
301
|
+
* `X-Content-Type-Options: nosniff` (la rotta), così non può essere interpretata come una
|
|
302
|
+
* pagina della nostra origine.
|
|
303
|
+
* ⛔ Tema chiaro E scuro, come ogni superficie di questo prodotto (owner 11/09): chi la apre di
|
|
304
|
+
* notte non prende un lampo bianco.
|
|
305
|
+
*/
|
|
306
|
+
function testoHtml(ricerca, record) {
|
|
307
|
+
const prosa = prosaDisponibile(ricerca)
|
|
308
|
+
const domanda = ricerca.domanda ?? 'Ricerca approfondita'
|
|
309
|
+
const b = ricerca.bilancio
|
|
310
|
+
const corpo = []
|
|
311
|
+
|
|
312
|
+
if (!record) {
|
|
313
|
+
corpo.push(`<p class="avviso">⛔ ${escapeHtml(DICITURA_SENZA_VERIFICHE)}</p>`)
|
|
314
|
+
/*
|
|
315
|
+
* ⛔⛔⛔ 12/09, IL DIFETTO VISTO IN FOTO: qui c'era
|
|
316
|
+
* `senzaIlRecinto(prosa).split(/\n{2,}/).map(b => \`<p>${escapeHtml(b)}</p>\`)`.
|
|
317
|
+
* Cioe': il rapporto spezzato sulle righe VUOTE e ogni pezzo escapato dentro un `<p>`.
|
|
318
|
+
* A schermo uscivano `# Agentic Desktop Harness…`, `## Executive Summary`,
|
|
319
|
+
* `**Key Components:**` LETTERALI, e ogni elenco schiacciato in un paragrafo solo —
|
|
320
|
+
* perche' un elenco e' separato da UN a capo, non da due.
|
|
321
|
+
* ⛔ `livelloMinimo: 2`: la pagina ha gia' il suo `<h1>` (la domanda). Due `<h1>` in una
|
|
322
|
+
* pagina sono due titoli, cioe' nessuno.
|
|
323
|
+
*/
|
|
324
|
+
corpo.push(markdownInHtml(senzaIlRecinto(prosa ?? ''), { livelloMinimo: 2 }))
|
|
325
|
+
} else {
|
|
326
|
+
/* ⛔ Anche la SINTESI e' Markdown: e' scritta dallo stesso modello che scrive il rapporto. */
|
|
327
|
+
corpo.push(`<div class="sintesi">${markdownInHtml(record.summary, { livelloMinimo: 2 })}</div>`)
|
|
328
|
+
if (b) {
|
|
329
|
+
corpo.push('<ul class="bilancio">',
|
|
330
|
+
`<li><b>${b.totali}</b> affermazioni</li>`,
|
|
331
|
+
`<li><b>${b.sostenute}</b> sostenute</li>`,
|
|
332
|
+
`<li><b>${b.inParte}</b> in parte</li>`,
|
|
333
|
+
`<li><b>${b.nonSostenute}</b> non sostenute</li>`,
|
|
334
|
+
`<li><b>${b.contese}</b> contese</li>`,
|
|
335
|
+
`<li><b>${b.nonVerificate}</b> non verificate</li>`,
|
|
336
|
+
'</ul>')
|
|
337
|
+
}
|
|
338
|
+
corpo.push(`<p class="giudice">${record.judge
|
|
339
|
+
? `Verifica eseguita da: ${escapeHtml(record.judge)} — mai dal modello che ha scritto il rapporto.`
|
|
340
|
+
: 'Verifica non eseguita: nessun giudice indipendente era disponibile.'}</p>`)
|
|
341
|
+
corpo.push('<h2>Le affermazioni</h2>')
|
|
342
|
+
/* ⛔ I verdetti escono dalla SCHEDA (`affermazioni[].verdettoUmano`), cioè dalla stessa
|
|
343
|
+
`talosResearchSupportLabel` che scrive la prosa: due frasari sono due verdetti. */
|
|
344
|
+
for (const a of ricerca.affermazioni ?? []) {
|
|
345
|
+
corpo.push('<article>',
|
|
346
|
+
/* ⛔ Il TESTO dell'affermazione passa dall'inline (un `**` scritto dal modello e'
|
|
347
|
+
enfasi); il PASSAGGIO no — vedi sotto. */
|
|
348
|
+
`<h3>${a.numero}. ${inlineInHtml(a.testo)}</h3>`,
|
|
349
|
+
`<p class="verdetto v-${escapeHtml(a.verdetto)}">Esito: ${escapeHtml(a.verdettoUmano)}${a.motivoVerdetto ? ` — ${escapeHtml(a.motivoVerdetto)}` : ''}</p>`,
|
|
350
|
+
/* ⛔⛔ IL PASSAGGIO NON SI RENDE MAI: e' la PROVA, cioe' il testo com'e' nella
|
|
351
|
+
fonte. Un asterisco dentro una citazione e' un asterisco che c'era davvero, e
|
|
352
|
+
trasformarlo in corsivo vorrebbe dire modificare l'unica cosa che il rapporto
|
|
353
|
+
conserva perche' non sia modificabile. Escapato, mai reso. */
|
|
354
|
+
a.passaggio
|
|
355
|
+
? `<blockquote>${escapeHtml(a.passaggio)}</blockquote>`
|
|
356
|
+
: '<p class="assente">Il passaggio citato non è stato ritrovato nel testo della fonte.</p>',
|
|
357
|
+
`<p class="fonte">Fonte ${a.fonte ?? '—'}</p>`,
|
|
358
|
+
'</article>')
|
|
359
|
+
}
|
|
360
|
+
corpo.push(`<h2>Fonti (${record.sources.length})</h2>`, '<ol class="fonti">')
|
|
361
|
+
for (const f of record.sources) {
|
|
362
|
+
corpo.push(`<li><b>${escapeHtml(f.title || dominioOIndirizzo(f.url))}</b><br>`
|
|
363
|
+
+ `<span class="url">${escapeHtml(f.url)}</span><br>`
|
|
364
|
+
+ `<span class="quando">${f.publishedAt ? `data dichiarata: ${escapeHtml(f.publishedAt)}` : 'data non dichiarata'}`
|
|
365
|
+
+ ` · ${f.obtained === 'page' ? 'pagina letta' : 'solo estratto dal motore di ricerca'}</span></li>`)
|
|
366
|
+
}
|
|
367
|
+
corpo.push('</ol>')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return `<!doctype html>
|
|
371
|
+
<html lang="it"><head><meta charset="utf-8">
|
|
372
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
373
|
+
<title>${escapeHtml(domanda)}</title>
|
|
374
|
+
<style>
|
|
375
|
+
:root { color-scheme: light dark; --fondo:#fbf9f5; --inchiostro:#2c2320; --tenue:#8a7f79; --pannello:#f2ede4; --bordo:#e3dcd2; --accento:#7b9c7b; --allarme:#9c3b2e; }
|
|
376
|
+
@media (prefers-color-scheme: dark) { :root { --fondo:#16130f; --inchiostro:#ece5dc; --tenue:#9d938b; --pannello:#211c17; --bordo:#332c25; --accento:#8fb08f; --allarme:#d98a7c; } }
|
|
377
|
+
* { box-sizing: border-box; }
|
|
378
|
+
body { margin:0; padding:32px 20px 64px; background:var(--fondo); color:var(--inchiostro);
|
|
379
|
+
font:16px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,system-ui,sans-serif; }
|
|
380
|
+
main { max-width: 46rem; margin: 0 auto; }
|
|
381
|
+
h1 { font-size:1.7rem; line-height:1.25; margin:0 0 4px; }
|
|
382
|
+
h2 { font-size:1.15rem; margin:36px 0 10px; padding-bottom:6px; border-bottom:1px solid var(--bordo); }
|
|
383
|
+
h3 { font-size:1rem; margin:0 0 6px; }
|
|
384
|
+
/* ⛔ 12/09, difetto visto NELLA FOTO in entrambi i temi: col colore tenue e un corpo piu'
|
|
385
|
+
piccolo del testo, un h4 si leggeva come una didascalia ed era piu' DEBOLE di un grassetto
|
|
386
|
+
di paragrafo che stava sotto di lui. Gerarchia invertita: un titolo di sezione non puo'
|
|
387
|
+
pesare meno di una riga qualunque della sezione. */
|
|
388
|
+
h4, h5, h6 { font-size:1rem; font-weight:600; margin:22px 0 6px; color:var(--inchiostro); }
|
|
389
|
+
/* ⛔ 12/09: da qui in giu' sono gli elementi che il Markdown RESO puo' produrre e che prima non
|
|
390
|
+
potevano esistere, perche' il rapporto usciva come paragrafi di testo grezzo. */
|
|
391
|
+
ul, ol { padding-left:1.4rem; margin:10px 0; }
|
|
392
|
+
li { margin:4px 0; }
|
|
393
|
+
hr { border:0; border-top:1px solid var(--bordo); margin:24px 0; }
|
|
394
|
+
code { font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:.9em;
|
|
395
|
+
background:var(--pannello); border:1px solid var(--bordo); border-radius:4px; padding:1px 5px; }
|
|
396
|
+
pre { background:var(--pannello); border:1px solid var(--bordo); border-radius:8px;
|
|
397
|
+
padding:12px 14px; overflow-x:auto; }
|
|
398
|
+
pre code { background:none; border:0; padding:0; font-size:.85rem; }
|
|
399
|
+
a { color:inherit; text-decoration:underline; text-underline-offset:2px; }
|
|
400
|
+
.tabella { overflow-x:auto; margin:14px 0; }
|
|
401
|
+
table { border-collapse:collapse; width:100%; font-size:.9rem; }
|
|
402
|
+
th, td { border-bottom:1px solid var(--bordo); padding:7px 10px; }
|
|
403
|
+
th { border-bottom:2px solid var(--tenue); font-weight:600; }
|
|
404
|
+
.meta, .quando, .fonte, .url { color:var(--tenue); font-size:.85rem; }
|
|
405
|
+
.sintesi { font-size:1.05rem; }
|
|
406
|
+
.avviso { background:var(--pannello); border-left:4px solid var(--allarme); padding:12px 14px; border-radius:0 6px 6px 0; }
|
|
407
|
+
.bilancio { list-style:none; display:flex; flex-wrap:wrap; gap:8px 18px; padding:12px 14px; margin:18px 0;
|
|
408
|
+
background:var(--pannello); border:1px solid var(--bordo); border-radius:8px; font-size:.9rem; }
|
|
409
|
+
article { border:1px solid var(--bordo); border-radius:8px; padding:14px 16px; margin:0 0 14px; }
|
|
410
|
+
blockquote { margin:10px 0 0; padding:8px 12px; border-left:3px solid var(--accento);
|
|
411
|
+
background:var(--pannello); border-radius:0 6px 6px 0; font-size:.95rem; }
|
|
412
|
+
.verdetto { margin:0; font-size:.9rem; font-weight:600; }
|
|
413
|
+
.v-no, .assente { color:var(--allarme); }
|
|
414
|
+
.fonti { padding-left:1.4rem; }
|
|
415
|
+
.fonti li { margin-bottom:12px; }
|
|
416
|
+
.url { word-break:break-all; }
|
|
417
|
+
footer { margin-top:44px; color:var(--tenue); font-size:.8rem; border-top:1px solid var(--bordo); padding-top:12px; }
|
|
418
|
+
</style></head>
|
|
419
|
+
<body><main>
|
|
420
|
+
<h1>${escapeHtml(domanda)}</h1>
|
|
421
|
+
<p class="meta">Stato: ${escapeHtml(ricerca.stato ?? 'ignoto')}${ricerca.modello ? ` · modello: ${escapeHtml(ricerca.modello)}` : ''}${letteAlle(ricerca) ? ` · ${escapeHtml(soloLaData(letteAlle(ricerca)))}` : ''}</p>
|
|
422
|
+
${corpo.join('\n')}
|
|
423
|
+
<footer>TALOS · ricerca approfondita</footer>
|
|
424
|
+
</main></body></html>
|
|
425
|
+
`
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Lo spec del PDF, nel tono chiesto — o il ripiego onesto quando il record non c'è.
|
|
430
|
+
*
|
|
431
|
+
* ⛔ Il ripiego NON inventa un tono: senza affermazioni i tre toni collassano sullo stesso
|
|
432
|
+
* documento (lo dice già `talosResearchPdfSpec`), perché non c'è niente da cui differire. Ciò
|
|
433
|
+
* che si aggiunge è la riga che dice **perché** è così.
|
|
434
|
+
*/
|
|
435
|
+
function specPdf(ricerca, record, tono) {
|
|
436
|
+
const opzioni = {
|
|
437
|
+
date: soloLaData(letteAlle(ricerca)) || undefined,
|
|
438
|
+
title: ricerca.titolo && ricerca.titolo !== ricerca.domanda ? ricerca.titolo : null,
|
|
439
|
+
}
|
|
440
|
+
if (record) return talosResearchPdfSpec(record, tono, opzioni)
|
|
441
|
+
|
|
442
|
+
const sintetico = {
|
|
443
|
+
version: 1,
|
|
444
|
+
question: ricerca.domanda ?? 'Ricerca approfondita',
|
|
445
|
+
/* ⛔ La sintesi del record sintetico resta VUOTA: la prosa entra qui sotto come BLOCCHI
|
|
446
|
+
impaginati, non come un paragrafo unico lungo cinque pagine col `##` dentro. */
|
|
447
|
+
summary: '',
|
|
448
|
+
judge: null,
|
|
449
|
+
claims: [],
|
|
450
|
+
sources: [],
|
|
451
|
+
}
|
|
452
|
+
const spec = talosResearchPdfSpec(sintetico, tono, opzioni)
|
|
453
|
+
return {
|
|
454
|
+
...spec,
|
|
455
|
+
blocks: [
|
|
456
|
+
spec.blocks[0],
|
|
457
|
+
{ t: 'note', x: DICITURA_SENZA_VERIFICHE },
|
|
458
|
+
/* ⛔ `livelloMinimo: 2`: la copertina porta gia' il titolo della ricerca. */
|
|
459
|
+
...markdownInBlocchiReport(senzaIlRecinto(prosaDisponibile(ricerca) ?? ''), { livelloMinimo: 2 }),
|
|
460
|
+
],
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* ─────────────────────────────────────── la porta sola ──────────────────────────────────── */
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Il contenuto di un'esportazione, nel formato chiesto.
|
|
468
|
+
*
|
|
469
|
+
* @param {{ricerca:object, formato:string, tono?:string}} richiesta
|
|
470
|
+
* @param {{generaDocumentoFn?:Function}} [deps] — iniettabile SOLO per non caricare pdfmake in
|
|
471
|
+
* un test che non guarda il PDF. Il valore vero è `generateTalosDocument`, il generatore di
|
|
472
|
+
* `document_create`: un secondo generatore vorrebbe dire due `.docx` diversi dallo stesso TALOS.
|
|
473
|
+
* @returns {Promise<{formato:string, nomeFile:string, mediaType:string, bytes:Uint8Array}>}
|
|
474
|
+
*/
|
|
475
|
+
export async function costruisciEsportazione({ ricerca, formato, tono }, deps = {}) {
|
|
476
|
+
const forma = FORMATI[formato]
|
|
477
|
+
if (!forma) {
|
|
478
|
+
throw new EsportazioneRicercaError(
|
|
479
|
+
`unknown export format "${formato}" — expected one of: ${FORMATI_ESPORTAZIONE.join(', ')}`,
|
|
480
|
+
'RESEARCH_INVALID',
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
const tonoScelto = tono ?? TONO_ESPORTAZIONE_PREDEFINITO
|
|
484
|
+
if (!TONI_ESPORTAZIONE.includes(tonoScelto)) {
|
|
485
|
+
throw new EsportazioneRicercaError(
|
|
486
|
+
`unknown pdf tone "${tonoScelto}" — expected one of: ${TONI_ESPORTAZIONE.join(', ')}`,
|
|
487
|
+
'RESEARCH_INVALID',
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
/* ⛔ Il tono si dichiara solo dove significa qualcosa. Accettarlo in silenzio su un `.bib`
|
|
491
|
+
lascerebbe credere che esistano tre bibliografie diverse. */
|
|
492
|
+
if (tono !== undefined && formato !== 'pdf') {
|
|
493
|
+
throw new EsportazioneRicercaError(`the "tono" parameter belongs to format=pdf, not to "${formato}"`, 'RESEARCH_INVALID')
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const record = recordDellaScheda(ricerca)
|
|
497
|
+
if (forma.vuoleIlRecord && !record) {
|
|
498
|
+
throw new EsportazioneRicercaError(
|
|
499
|
+
`this research has no verifiable record: "${formato}" is built from claims, passages and sources, and there are none`
|
|
500
|
+
+ (prosaDisponibile(ricerca) ? ' — what it deposited can still be exported as md, html or pdf' : ''),
|
|
501
|
+
'RESEARCH_CONFLICT',
|
|
502
|
+
)
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const nomeFile = nomeSicuroDiEsportazione(ricerca.domanda, formato)
|
|
506
|
+
const comune = { formato, nomeFile, mediaType: forma.mediaType }
|
|
507
|
+
|
|
508
|
+
if (formato === 'md' || formato === 'html' || formato === 'pdf' || formato === 'docx') {
|
|
509
|
+
/* ⛔ Un `md`/`html`/`pdf` senza NIENTE (una ricerca ancora in corso, o fallita prima di
|
|
510
|
+
depositare) non è un file vuoto: è una richiesta che non si può soddisfare. Un PDF di
|
|
511
|
+
sole intestazioni consegnato in silenzio è una bugia più cara di un 409. */
|
|
512
|
+
if (!record && !prosaDisponibile(ricerca)) {
|
|
513
|
+
throw new EsportazioneRicercaError(
|
|
514
|
+
'this research has not produced anything yet: there is no report and no deposited text to export',
|
|
515
|
+
'RESEARCH_CONFLICT',
|
|
516
|
+
)
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
switch (formato) {
|
|
521
|
+
case 'md':
|
|
522
|
+
return { ...comune, bytes: CODIFICA.encode(testoMarkdown(ricerca, record)) }
|
|
523
|
+
case 'json':
|
|
524
|
+
return { ...comune, bytes: CODIFICA.encode(testoJson(ricerca, record)) }
|
|
525
|
+
case 'bib':
|
|
526
|
+
return { ...comune, bytes: CODIFICA.encode(`${talosResearchBibtex(citazioniDaRecord(record, letteAlle(ricerca)))}\n`) }
|
|
527
|
+
case 'ris':
|
|
528
|
+
return { ...comune, bytes: CODIFICA.encode(`${talosResearchRis(citazioniDaRecord(record, letteAlle(ricerca)))}\n`) }
|
|
529
|
+
case 'fonti':
|
|
530
|
+
return { ...comune, bytes: CODIFICA.encode(testoFonti(ricerca, record)) }
|
|
531
|
+
case 'html':
|
|
532
|
+
return { ...comune, bytes: CODIFICA.encode(testoHtml(ricerca, record)) }
|
|
533
|
+
case 'pdf': {
|
|
534
|
+
const genera = deps.generaDocumentoFn ?? (await import('../document-generator.mjs')).generateTalosDocument
|
|
535
|
+
const documento = await genera({
|
|
536
|
+
format: 'pdf',
|
|
537
|
+
title: ricerca.domanda ?? 'Ricerca approfondita',
|
|
538
|
+
report: specPdf(ricerca, record, tonoScelto),
|
|
539
|
+
})
|
|
540
|
+
// ⛔ Il nome lo decide QUESTO modulo, non il generatore: la sua politica è quella dei
|
|
541
|
+
// file del workspace, e qui il nome è parte del contratto della rotta.
|
|
542
|
+
return { ...comune, bytes: documento.bytes }
|
|
543
|
+
}
|
|
544
|
+
case 'docx': {
|
|
545
|
+
const genera = deps.generaDocumentoFn ?? (await import('../document-generator.mjs')).generateTalosDocument
|
|
546
|
+
// BC35: il generatore condiviso interpreta titoli e liste. Appiattire qui
|
|
547
|
+
// cancellerebbe la struttura prima che Word possa riceverla.
|
|
548
|
+
const prosa = senzaIlRecinto(prosaDisponibile(ricerca) ?? '')
|
|
549
|
+
const documento = await genera({
|
|
550
|
+
format: 'docx',
|
|
551
|
+
title: ricerca.domanda ?? 'Ricerca approfondita',
|
|
552
|
+
body: record ? prosa : `${DICITURA_SENZA_VERIFICHE}\n\n${prosa}`,
|
|
553
|
+
})
|
|
554
|
+
return { ...comune, bytes: documento.bytes }
|
|
555
|
+
}
|
|
556
|
+
/* c8 ignore next 2 — irraggiungibile: l'allowlist in cima ha già respinto ogni altro. */
|
|
557
|
+
default:
|
|
558
|
+
throw new EsportazioneRicercaError(`unknown export format: ${formato}`, 'RESEARCH_INVALID')
|
|
559
|
+
}
|
|
560
|
+
}
|