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,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* library-store.mjs — FASE N del piano `elegant-spinning-dongarra.md`
|
|
3
|
+
* (29/8): port dal mobile, owner testuale: "iniziamo da library,
|
|
4
|
+
* ricordati che sul mobile esistono già da tempo tutte queste
|
|
5
|
+
* funzioni... è tutto già fatto". Stesso CONTRATTO (schema/semantica)
|
|
6
|
+
* degli 8 tool mobile — letti alla fonte il 29/8, non presunti:
|
|
7
|
+
* `mobile/src/lib/tools/readTools.ts` righe 320-650 (i 4 tool di
|
|
8
|
+
* lettura) e `mobile/src/lib/vaultLibrary.ts` (il modello dati) —
|
|
9
|
+
* storage DIVERSO: file su disco per-progetto (decisione già presa
|
|
10
|
+
* nel ledger FASE N: "storage per-progetto dentro il workspace"), non
|
|
11
|
+
* SQLCipher cross-chat come sul mobile.
|
|
12
|
+
*
|
|
13
|
+
* Prima fetta: SOLO i 4 tool di lettura (list/search/read/file_origin)
|
|
14
|
+
* — come sul mobile, dove sono arrivati per primi (domanda 2 del
|
|
15
|
+
* ledger FASE N, risolta con la convenzione già stabilita per
|
|
16
|
+
* E/F/G/H: registry+tool prima, mutazioni/UI dopo). `salvaVoce` esiste
|
|
17
|
+
* già qui — serve per seminare/testare questa stessa fetta dal vivo
|
|
18
|
+
* (senza scrittura non c'è nulla da leggere) ed è la base pronta per
|
|
19
|
+
* la seconda fetta — ma non è ancora esposta come tool al modello.
|
|
20
|
+
*
|
|
21
|
+
* ⛔⛔⛔ Deliberatamente SENZA il concetto mobile di `contentOrigin`/A8
|
|
22
|
+
* (la provenienza dinamica "peggiore fra N risultati", usata sul
|
|
23
|
+
* mobile per la difesa da prompt injection): verificato il 29/8
|
|
24
|
+
* leggendo `talosHarness.mjs` per intero — non ha NESSUN equivalente,
|
|
25
|
+
* in nessun nome. La sua trifecta (`SICUREZZA_PER_ATTREZZO`) è un
|
|
26
|
+
* vocabolario DIVERSO — booleano, STATICO per nome di attrezzo, e
|
|
27
|
+
* limitato ai soli 5 "ATTREZZI_CON_RICEVUTA" (scrivi/prova/shell/
|
|
28
|
+
* document_create/generate_image); perfino `naviga` (che legge
|
|
29
|
+
* contenuto web non fidato) ne è fuori oggi. I 4 tool di lettura qui
|
|
30
|
+
* sotto ricevono lo STESSO trattamento non censito di
|
|
31
|
+
* elenca/cerca/leggi/naviga: nessuna voce in SICUREZZA_PER_ATTREZZO,
|
|
32
|
+
* il default `{readsPrivateData:false, readsUntrustedContent:false,
|
|
33
|
+
* canTransmit:false}` si applica — coerente col precedente stabilito,
|
|
34
|
+
* non un buco nuovo introdotto da questa fase.
|
|
35
|
+
*
|
|
36
|
+
* Stile DI: stesso pattern di `skill-registry.mjs`/`frequent-dirs.mjs`
|
|
37
|
+
* (letti come precedenti diretti) — funzioni async con `deps` opzionali
|
|
38
|
+
* per i test, mai un vero filesystem mockato altrove.
|
|
39
|
+
*/
|
|
40
|
+
import { promises as fsp } from 'node:fs';
|
|
41
|
+
import { join } from 'node:path';
|
|
42
|
+
import { randomUUID } from 'node:crypto';
|
|
43
|
+
|
|
44
|
+
// ⛔ Il tetto dello scarico è UNO SOLO per tutto il prodotto: vedi `leggiBytesVoce` in fondo.
|
|
45
|
+
import { DIMENSIONE_MASSIMA_SCARICO } from './workspace-files.mjs';
|
|
46
|
+
|
|
47
|
+
export class LibraryStoreError extends Error {
|
|
48
|
+
constructor(message, code = 'LIBRARY_INVALID') {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = 'LibraryStoreError';
|
|
51
|
+
this.code = code;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const CARTELLA_LIBRERIA = '.harness-ui-library';
|
|
56
|
+
const NOME_FILE_META = 'meta.json';
|
|
57
|
+
const NOME_FILE_CONTENUTO = 'contenuto';
|
|
58
|
+
const MAX_VOCI_PAGINA = 20;
|
|
59
|
+
const MAX_RISULTATI_RICERCA = 20;
|
|
60
|
+
// Stesso ordine di grandezza di MAX_LIBRARY_LIST_CURSORS mobile (readTools.ts riga 247, valore 128).
|
|
61
|
+
const MAX_CURSORI_VIVI = 128;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @returns {'image'|'document'} — derivato dal mediaType, come
|
|
65
|
+
* `talosLibraryFileType` mobile (vaultLibrary.ts righe 66-71). Niente
|
|
66
|
+
* `'link'`: l'archiviazione di pagine web nella Libreria è una
|
|
67
|
+
* capacità mobile (ricerca approfondita che salva le fonti) che il
|
|
68
|
+
* desktop non ha ancora — il valore resta nello schema del filtro
|
|
69
|
+
* (per parità di contratto) ma non produce mai risultati, onestamente.
|
|
70
|
+
*/
|
|
71
|
+
export function tipoFileLibreria(mediaType) {
|
|
72
|
+
return String(mediaType || '').startsWith('image/') ? 'image' : 'document';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Una Map nuova per sessione — stessa vita di `libraryListCursors`
|
|
77
|
+
* mobile (un'istanza per chat, chiusa nella factory di `defineTalosTool`).
|
|
78
|
+
* Qui il chiamante (agent-service.mjs, una volta per sessione) la crea
|
|
79
|
+
* e la passa a `impaginaVoci` ad ogni chiamata di `library_list`.
|
|
80
|
+
*/
|
|
81
|
+
export function creaCursoriLibreria() {
|
|
82
|
+
return new Map();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* ⛔⛔⛔ 10/09/2026 — L'ID DI UNA VOCE NON È PIÙ SOLO UNA COSA DEL MODELLO.
|
|
87
|
+
*
|
|
88
|
+
* Fino a oggi ogni `id` arrivava da `impaginaVoci`/`cercaVoci`, cioè da un elenco che questo
|
|
89
|
+
* file aveva appena letto dal disco. Da oggi arriva anche da un SEGMENTO DI INDIRIZZO HTTP
|
|
90
|
+
* (`/api/v1/sessions/:id/library/:voceId/...`), cioè da fuori: un id `..%2f..` diventerebbe
|
|
91
|
+
* `join(cartella, '.harness-ui-library', '..', '..')` e uscirebbe dalla cartella del progetto,
|
|
92
|
+
* e `leggiVoce`/`rinominaVoce`/`eliminaVoce` lo avrebbero seguito senza fiatare. La stessa porta
|
|
93
|
+
* era già aperta dalla parte del modello (`agent-service.mjs` passa `argomenti?.id ?? ''` così
|
|
94
|
+
* com'è): la difesa sta QUI, nel magazzino, così vale per tutti e due i chiamanti e non c'è una
|
|
95
|
+
* seconda copia che un giorno diverge.
|
|
96
|
+
*
|
|
97
|
+
* Ricerca 10/09/2026, prima di scrivere (Microsoft Learn, «Naming Files, Paths, and Namespaces»;
|
|
98
|
+
* npm `sanitize-filename` e la sua tabella di casi): un nome di file sicuro non è «una stringa
|
|
99
|
+
* senza `..`» — sono vietati anche i separatori, i caratteri di controllo, i punti e gli spazi
|
|
100
|
+
* in coda, e i nomi riservati di Windows (CON, PRN, AUX, NUL, COM1-9, LPT1-9) perfino con
|
|
101
|
+
* un'estensione appiccicata dietro. Qui il tetto è più stretto di quell'elenco, e per questo non
|
|
102
|
+
* serve ricopiarlo: un id di Libreria lo genera SEMPRE `salvaVoce` nella forma `lib-<uuid>` (mai
|
|
103
|
+
* il chiamante), e `randomUUID` produce solo cifre esadecimali e trattini ⇒ un'allowlist di
|
|
104
|
+
* lettere, cifre, punto, trattino e trattino basso accetta tutto ciò che esiste davvero sul disco
|
|
105
|
+
* e rifiuta tutto il resto.
|
|
106
|
+
*
|
|
107
|
+
* ⛔ Un id fuori grammatica torna `null` — «questa voce non c'è», non un'eccezione: è vero (un id
|
|
108
|
+
* con una barra dentro non può nominare nessuna voce) e non cambia il contratto delle funzioni
|
|
109
|
+
* qui sotto, che per un id sconosciuto rispondono già `null`. Un'eccezione nuova, invece,
|
|
110
|
+
* sarebbe arrivata in faccia al modello per una chiamata che oggi si chiude con un «non trovato».
|
|
111
|
+
*/
|
|
112
|
+
export function idVoceLibreriaValido(id) {
|
|
113
|
+
return typeof id === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Il percorso RELATIVO (dalla cartella del progetto) del file di una voce — la sola forma in cui
|
|
118
|
+
* la disposizione interna della Libreria esce da questo file. `null` se l'id non è valido.
|
|
119
|
+
* Serve a «rivela in Esplora file», che vuole un percorso dentro il workspace: farlo comporre al
|
|
120
|
+
* chiamante vorrebbe dire scrivere `.harness-ui-library/<id>/contenuto` in due posti, cioè tenere
|
|
121
|
+
* due mappe della stessa cartella — e quando la Libreria cambierà forma se ne aggiornerà una sola.
|
|
122
|
+
*/
|
|
123
|
+
export function percorsoContenutoVoce(id) {
|
|
124
|
+
if (!idVoceLibreriaValido(id)) return null;
|
|
125
|
+
return `${CARTELLA_LIBRERIA}/${id}/${NOME_FILE_CONTENUTO}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* ⭐⭐⭐ BC-38 (12/09/2026), owner: «mettere il percorso dei file nella Libreria… nel dettaglio
|
|
130
|
+
* anche da chi sono stati creati e da quale sessione».
|
|
131
|
+
*
|
|
132
|
+
* Il percorso ASSOLUTO del file di contenuto — la sola forma che si può mettere davanti a una
|
|
133
|
+
* persona, perché è quella che si incolla in Esplora file o in un terminale. Gemello di
|
|
134
|
+
* `percorsoContenutoVoce` (che resta relativo perché è ciò che vuole `rivelaInEsploraFile`), e
|
|
135
|
+
* come lui vive QUI: la disposizione della cartella di Libreria ha un solo proprietario.
|
|
136
|
+
* `null` se l'id non è in grammatica — «questa voce non c'è», mai un percorso inventato.
|
|
137
|
+
*/
|
|
138
|
+
export function percorsoAssolutoVoce(cartella, id) {
|
|
139
|
+
const relativo = percorsoContenutoVoce(id);
|
|
140
|
+
if (!relativo || typeof cartella !== 'string' || cartella.length === 0) return null;
|
|
141
|
+
return join(cartella, relativo);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* CHI ha prodotto una voce, letto dal `meta.json` e mai dedotto dal NOME del file.
|
|
146
|
+
*
|
|
147
|
+
* ⛔ Due valori, non tre: `persona` (un file caricato) e `modello` (un file generato). La terza
|
|
148
|
+
* categoria che si potrebbe volere — «l'ha scritto una ricerca approfondita» — NON è
|
|
149
|
+
* distinguibile oggi: `research-orchestrator.mjs` chiama `salvaVoce` con lo stesso
|
|
150
|
+
* `origine:'generated'` di un artefatto o di un `document_create`, e l'unico segno che resta
|
|
151
|
+
* sul disco sarebbe il prefisso «Research - » nel nome. Un prefisso nel nome NON è un dato:
|
|
152
|
+
* è una stringa che chiunque rinomina. ⇒ si dice «il modello», che è vero, invece di indovinare.
|
|
153
|
+
* ⛔ `modello`/`provider` restano `null` per tutte le voci scritte prima di oggi: nessuna li
|
|
154
|
+
* portava (misurato il 12/09/2026 sulla Libreria del Desktop dell owner: 14 voci su 14
|
|
155
|
+
* senza `modello`). Un `null` qui vuol dire «non registrato», e a schermo si legge così.
|
|
156
|
+
*/
|
|
157
|
+
function creatoDaDiMeta(meta, origineEffettiva) {
|
|
158
|
+
if (origineEffettiva !== 'generated') return { tipo: 'persona' };
|
|
159
|
+
return {
|
|
160
|
+
tipo: 'modello',
|
|
161
|
+
modello: typeof meta.modello === 'string' && meta.modello.trim() ? meta.modello.trim() : null,
|
|
162
|
+
provider: typeof meta.provider === 'string' && meta.provider.trim() ? meta.provider.trim() : null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* DA QUALE sessione arriva una voce. `null` quando il `meta.json` non lo dice — ed è il caso di
|
|
168
|
+
* ogni voce scritta prima del 12/09/2026, perché `salvaVoce` non lo salvava.
|
|
169
|
+
* ⛔ `null` NON si riempie con un ripiego: «sessione non registrata» è la verità, un id inventato
|
|
170
|
+
* o l'id del progetto al posto suo sarebbero una bugia che porta a cliccare su una chat che non
|
|
171
|
+
* esiste. Il `nome` è quello congelato al momento della scrittura: può essere `null` da solo
|
|
172
|
+
* (l'id c'è, il nome no) e chi disegna lo risolve con l'elenco vivo delle sessioni.
|
|
173
|
+
*/
|
|
174
|
+
function sessioneDiMeta(meta) {
|
|
175
|
+
const id = typeof meta.sessionId === 'string' && meta.sessionId.trim() ? meta.sessionId.trim() : null;
|
|
176
|
+
if (!id) return null;
|
|
177
|
+
const nome = typeof meta.sessionNome === 'string' && meta.sessionNome.trim() ? meta.sessionNome.trim() : null;
|
|
178
|
+
return { id, nome };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** La cartella di UNA voce, o `null` se l'id non è un nome piatto (vedi `idVoceLibreriaValido`). */
|
|
182
|
+
function cartellaDellaVoce(cartella, id) {
|
|
183
|
+
if (!idVoceLibreriaValido(id)) return null;
|
|
184
|
+
return join(cartella, CARTELLA_LIBRERIA, id);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function meta2voce(id, meta) {
|
|
188
|
+
const creatoIl = typeof meta.creatoIl === 'string' ? meta.creatoIl : new Date(0).toISOString();
|
|
189
|
+
return {
|
|
190
|
+
id,
|
|
191
|
+
nome: meta.nome,
|
|
192
|
+
mediaType: typeof meta.mediaType === 'string' ? meta.mediaType : 'application/octet-stream',
|
|
193
|
+
fileType: tipoFileLibreria(meta.mediaType),
|
|
194
|
+
// Fail-closed come `parseVaultOrigin` mobile: qualunque cosa non sia esplicitamente 'generated' è trattata come un upload.
|
|
195
|
+
origine: meta.origine === 'generated' ? 'generated' : 'uploaded',
|
|
196
|
+
creatoIl,
|
|
197
|
+
aggiornatoIl: typeof meta.aggiornatoIl === 'string' ? meta.aggiornatoIl : creatoIl,
|
|
198
|
+
modello: typeof meta.modello === 'string' ? meta.modello : null,
|
|
199
|
+
provider: typeof meta.provider === 'string' ? meta.provider : null,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function leggiMeta(percorsoMeta, readFileFn, id) {
|
|
204
|
+
let testo;
|
|
205
|
+
try {
|
|
206
|
+
testo = await readFileFn(percorsoMeta, 'utf8');
|
|
207
|
+
} catch (errore) {
|
|
208
|
+
if (errore?.code === 'ENOENT') return undefined; // assente: non un errore, un "non c'è" onesto
|
|
209
|
+
throw new LibraryStoreError(`Impossibile leggere ${id}/${NOME_FILE_META}: ${errore.message}`, 'LIBRARY_READ_FAILED');
|
|
210
|
+
}
|
|
211
|
+
let meta;
|
|
212
|
+
try {
|
|
213
|
+
meta = JSON.parse(testo);
|
|
214
|
+
} catch {
|
|
215
|
+
throw new LibraryStoreError(`${id}/${NOME_FILE_META} non è JSON valido`, 'LIBRARY_MALFORMED');
|
|
216
|
+
}
|
|
217
|
+
if (typeof meta.nome !== 'string' || meta.nome.length === 0) {
|
|
218
|
+
throw new LibraryStoreError(`${id}/${NOME_FILE_META} manca di "nome"`, 'LIBRARY_MALFORMED');
|
|
219
|
+
}
|
|
220
|
+
return meta;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Legge OGNI voce (solo metadata, MAI il contenuto) sotto
|
|
225
|
+
* `<cartella>/.harness-ui-library/`. Cartella assente ⇒ `[]`, mai un
|
|
226
|
+
* errore — un progetto senza Libreria è uno stato valido, stesso
|
|
227
|
+
* principio "gli stati sono tre" di caricaHooks/caricaSkill. Una
|
|
228
|
+
* sottocartella senza `meta.json` è saltata (non è una voce, non è un
|
|
229
|
+
* errore); un `meta.json` malformato FERMA il caricamento con un
|
|
230
|
+
* errore dichiarato — mai una voce fantasma letta a metà.
|
|
231
|
+
*/
|
|
232
|
+
/*
|
|
233
|
+
* ⭐ BC-38 — `conProvenienza` è SPENTO di default, e non è una cautela generica: `elencaVoci` è
|
|
234
|
+
* anche l'attrezzo `library_list` del MODELLO (`agent-service.mjs:1398` → `impaginaVoci`, che
|
|
235
|
+
* restituisce le voci intere). Accendere sempre i quattro campi nuovi vorrebbe dire mettere un
|
|
236
|
+
* percorso assoluto e una provenienza dentro il contesto di OGNI giro, cioè cambiare in silenzio
|
|
237
|
+
* il contratto verso il modello e il conto dei token. ⇒ li chiede solo chi disegna per la
|
|
238
|
+
* PERSONA (`session-registry.elencaLibreria`); per il modello questa funzione resta identica
|
|
239
|
+
* bit per bit.
|
|
240
|
+
*/
|
|
241
|
+
export async function elencaVoci({ cartella, conProvenienza = false }, deps = {}) {
|
|
242
|
+
const readdirFn = deps.readdirFn ?? fsp.readdir;
|
|
243
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
244
|
+
const cartellaLibreria = join(cartella, CARTELLA_LIBRERIA);
|
|
245
|
+
let voci;
|
|
246
|
+
try {
|
|
247
|
+
voci = await readdirFn(cartellaLibreria, { withFileTypes: true });
|
|
248
|
+
} catch (errore) {
|
|
249
|
+
if (errore?.code === 'ENOENT') return [];
|
|
250
|
+
throw new LibraryStoreError(`Impossibile leggere ${CARTELLA_LIBRERIA}: ${errore.message}`, 'LIBRARY_READ_FAILED');
|
|
251
|
+
}
|
|
252
|
+
const entries = [];
|
|
253
|
+
for (const voce of voci) {
|
|
254
|
+
if (!voce.isDirectory()) continue; // un file sciolto dentro .harness-ui-library/ non è una voce
|
|
255
|
+
const meta = await leggiMeta(join(cartellaLibreria, voce.name, NOME_FILE_META), readFileFn, voce.name);
|
|
256
|
+
if (meta === undefined) continue;
|
|
257
|
+
const base = meta2voce(voce.name, meta);
|
|
258
|
+
entries.push(conProvenienza
|
|
259
|
+
? {
|
|
260
|
+
...base,
|
|
261
|
+
cartella,
|
|
262
|
+
percorso: percorsoAssolutoVoce(cartella, voce.name),
|
|
263
|
+
creatoDa: creatoDaDiMeta(meta, base.origine),
|
|
264
|
+
sessione: sessioneDiMeta(meta),
|
|
265
|
+
}
|
|
266
|
+
: base);
|
|
267
|
+
}
|
|
268
|
+
return entries;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Come `elencaVoci`, ma legge ANCHE il testo estratto di ogni voce
|
|
273
|
+
* non-immagine — stessa separazione deliberata di mobile
|
|
274
|
+
* (`listLibraryEntries` "metadata-only", `listLibraryDocs` "complete
|
|
275
|
+
* extracted text, loaded only by an explicit search call", doc in
|
|
276
|
+
* readTools.ts riga 85-88): un `library_list` non paga il costo I/O
|
|
277
|
+
* di leggere ogni contenuto, solo `library_search` lo fa.
|
|
278
|
+
*/
|
|
279
|
+
export async function elencaVociConTesto({ cartella }, deps = {}) {
|
|
280
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
281
|
+
const entries = await elencaVoci({ cartella }, deps);
|
|
282
|
+
const conTesto = [];
|
|
283
|
+
for (const entry of entries) {
|
|
284
|
+
if (entry.fileType === 'image') {
|
|
285
|
+
conTesto.push({ ...entry, testoEstratto: '' });
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
let testoEstratto = '';
|
|
289
|
+
try {
|
|
290
|
+
testoEstratto = await readFileFn(join(cartella, CARTELLA_LIBRERIA, entry.id, NOME_FILE_CONTENUTO), 'utf8');
|
|
291
|
+
} catch {
|
|
292
|
+
// ⛔ contenuto mancante/illeggibile: la voce resta cercabile per nome, mai un errore che blocca l'intera ricerca per una voce sola.
|
|
293
|
+
}
|
|
294
|
+
conTesto.push({ ...entry, testoEstratto });
|
|
295
|
+
}
|
|
296
|
+
return conTesto;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function confrontaVoci(a, b) {
|
|
300
|
+
// Più recente per aggiornamento, poi creazione, poi id — stesso ordine di `compareLibraryListEntries` mobile (readTools.ts righe 250-257).
|
|
301
|
+
return b.aggiornatoIl.localeCompare(a.aggiornatoIl)
|
|
302
|
+
|| b.creatoIl.localeCompare(a.creatoIl)
|
|
303
|
+
|| b.id.localeCompare(a.id);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function primaDelCursore(voce, dopo) {
|
|
307
|
+
if (voce.aggiornatoIl !== dopo.aggiornatoIl) return voce.aggiornatoIl < dopo.aggiornatoIl;
|
|
308
|
+
if (voce.creatoIl !== dopo.creatoIl) return voce.creatoIl < dopo.creatoIl;
|
|
309
|
+
return voce.id < dopo.id;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Pagina l'elenco — PURA (nessun I/O): filtro origine/tipo, ordine
|
|
314
|
+
* più-recente-prima, cursore opaco (token → stato, nella Map passata
|
|
315
|
+
* dal chiamante — vedi `creaCursoriLibreria`). Un `pageToken`
|
|
316
|
+
* sconosciuto/scaduto torna `{errore:'CURSOR_INVALID'}`; un
|
|
317
|
+
* `pageToken` con filtri diversi da quelli della pagina precedente
|
|
318
|
+
* torna `{errore:'FILTER_DRIFT'}` — stessi due esiti onesti di
|
|
319
|
+
* `library_list` mobile (readTools.ts righe 339-365), mai un salto
|
|
320
|
+
* silenzioso a una pagina qualunque.
|
|
321
|
+
*/
|
|
322
|
+
export function impaginaVoci(voci, { origine = 'all', fileType = 'all', pageSize = 10, pageToken } = {}, cursori) {
|
|
323
|
+
const taglia = Math.max(1, Math.min(MAX_VOCI_PAGINA, pageSize));
|
|
324
|
+
let cursore;
|
|
325
|
+
if (pageToken) {
|
|
326
|
+
cursore = cursori.get(pageToken);
|
|
327
|
+
if (!cursore) return { errore: 'CURSOR_INVALID' };
|
|
328
|
+
if (cursore.origine !== origine || cursore.fileType !== fileType) return { errore: 'FILTER_DRIFT' };
|
|
329
|
+
}
|
|
330
|
+
const filtrate = voci
|
|
331
|
+
.filter((v) => origine === 'all' || v.origine === origine)
|
|
332
|
+
.filter((v) => fileType === 'all' || v.fileType === fileType)
|
|
333
|
+
.slice()
|
|
334
|
+
.sort(confrontaVoci);
|
|
335
|
+
const candidate = cursore ? filtrate.filter((v) => primaDelCursore(v, cursore.dopo)) : filtrate;
|
|
336
|
+
const pagina = candidate.slice(0, taglia);
|
|
337
|
+
const vistiPrima = cursore?.visti ?? 0;
|
|
338
|
+
const vistiDopo = vistiPrima + pagina.length;
|
|
339
|
+
const altre = candidate.length > pagina.length;
|
|
340
|
+
const ultima = pagina.at(-1);
|
|
341
|
+
let nextPageToken = null;
|
|
342
|
+
if (altre && ultima) {
|
|
343
|
+
while (cursori.size >= MAX_CURSORI_VIVI) {
|
|
344
|
+
const piuVecchio = cursori.keys().next().value;
|
|
345
|
+
if (piuVecchio === undefined) break;
|
|
346
|
+
cursori.delete(piuVecchio); // FIFO — stesso schema di eviction di issueLibraryListCursor mobile (readTools.ts righe 308-318)
|
|
347
|
+
}
|
|
348
|
+
nextPageToken = randomUUID();
|
|
349
|
+
cursori.set(nextPageToken, {
|
|
350
|
+
origine, fileType, visti: vistiDopo,
|
|
351
|
+
dopo: { aggiornatoIl: ultima.aggiornatoIl, creatoIl: ultima.creatoIl, id: ultima.id },
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return { pagina, totale: filtrate.length, vistiPrima, vistiDopo, nextPageToken };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function punteggioRicerca(voce, query) {
|
|
358
|
+
const parole = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
359
|
+
if (parole.length === 0) return 0;
|
|
360
|
+
const nome = voce.nome.toLowerCase();
|
|
361
|
+
const corpo = (voce.testoEstratto || '').toLowerCase();
|
|
362
|
+
let punti = 0;
|
|
363
|
+
for (const parola of parole) {
|
|
364
|
+
if (nome.includes(parola)) punti += 3;
|
|
365
|
+
if (corpo.includes(parola)) punti += 1;
|
|
366
|
+
}
|
|
367
|
+
return punti;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Cerca fra le voci (richiede `testoEstratto`, cioè il risultato di
|
|
372
|
+
* `elencaVociConTesto` — mai `elencaVoci`, che non lo porta). PURA.
|
|
373
|
+
*
|
|
374
|
+
* ⛔ Punteggio semplice per la prima fetta — non porta l'euristica
|
|
375
|
+
* esatta di `rankLibraryDocs` mobile: fuori scala per un porting
|
|
376
|
+
* "stesso contratto, storage diverso", dove ciò che conta per la
|
|
377
|
+
* parità è lo SCHEMA (solo punteggio>0 è un match, offset paginato),
|
|
378
|
+
* non l'algoritmo di ranking interno. Nome pesa più del corpo, parole
|
|
379
|
+
* della query, case-insensitive.
|
|
380
|
+
*/
|
|
381
|
+
export function cercaVoci(vociConTesto, { query, limit = 5, offset = 0 } = {}) {
|
|
382
|
+
const taglia = Math.max(1, Math.min(MAX_RISULTATI_RICERCA, limit));
|
|
383
|
+
const puntati = vociConTesto
|
|
384
|
+
.map((v) => ({ voce: v, punteggio: punteggioRicerca(v, query) }))
|
|
385
|
+
// Un punteggio zero non è entrato: dire il contrario contaminerebbe la ricerca con voci a caso — stesso principio di `matching.filter(({score}) => score > 0)` mobile.
|
|
386
|
+
.filter(({ punteggio }) => punteggio > 0)
|
|
387
|
+
.sort((a, b) => b.punteggio - a.punteggio || b.voce.aggiornatoIl.localeCompare(a.voce.aggiornatoIl));
|
|
388
|
+
const pagina = puntati.slice(offset, offset + taglia);
|
|
389
|
+
const nextOffset = offset + pagina.length < puntati.length ? offset + pagina.length : null;
|
|
390
|
+
return { pagina: pagina.map((r) => r.voce), totale: puntati.length, nextOffset };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Legge UNA voce per intero (metadata + contenuto). Mai un id
|
|
395
|
+
* indovinato dal chiamante: sempre ottenuto da `impaginaVoci`/
|
|
396
|
+
* `cercaVoci` prima — stesso principio di `library_read` mobile.
|
|
397
|
+
* `null` se l'id non esiste (mai un'eccezione per un id sbagliato:
|
|
398
|
+
* è un esito onesto, non un guasto).
|
|
399
|
+
*/
|
|
400
|
+
export async function leggiVoce({ cartella, id }, deps = {}) {
|
|
401
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
402
|
+
const cartellaVoce = cartellaDellaVoce(cartella, id);
|
|
403
|
+
if (!cartellaVoce) return null;
|
|
404
|
+
const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
|
|
405
|
+
if (meta === undefined) return null;
|
|
406
|
+
const voce = meta2voce(id, meta);
|
|
407
|
+
const percorsoContenuto = join(cartellaVoce, NOME_FILE_CONTENUTO);
|
|
408
|
+
if (voce.fileType === 'image') {
|
|
409
|
+
let bytes;
|
|
410
|
+
try {
|
|
411
|
+
bytes = await readFileFn(percorsoContenuto);
|
|
412
|
+
} catch (errore) {
|
|
413
|
+
throw new LibraryStoreError(`${id}: metadata presente ma contenuto illeggibile: ${errore.message}`, 'LIBRARY_READ_FAILED');
|
|
414
|
+
}
|
|
415
|
+
return { nome: voce.nome, mediaType: voce.mediaType, origine: voce.origine, immagineBase64: bytes.toString('base64') };
|
|
416
|
+
}
|
|
417
|
+
let testo;
|
|
418
|
+
try {
|
|
419
|
+
testo = await readFileFn(percorsoContenuto, 'utf8');
|
|
420
|
+
} catch (errore) {
|
|
421
|
+
throw new LibraryStoreError(`${id}: metadata presente ma contenuto illeggibile: ${errore.message}`, 'LIBRARY_READ_FAILED');
|
|
422
|
+
}
|
|
423
|
+
return { nome: voce.nome, mediaType: voce.mediaType, origine: voce.origine, testo };
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* I BYTE di una voce, per essere SCARICATA — sorella di `leggiVoce` qui sopra, con una differenza
|
|
429
|
+
* che non è un dettaglio: `leggiVoce` legge in `utf8` tutto ciò che non è un'immagine, e un `.docx`
|
|
430
|
+
* (che è uno zip) o un `.pdf` passati da lì tornano CORROTTI in modo irreversibile — ogni byte non
|
|
431
|
+
* valido diventa U+FFFD e non torna più indietro. Quella porta serve al modello, che vuole del
|
|
432
|
+
* testo; questa serve alla persona che clicca «scarica», e qui i byte restano byte.
|
|
433
|
+
*
|
|
434
|
+
* ⛔ Il tetto dei 64 MB non è un numero nuovo: è `DIMENSIONE_MASSIMA_SCARICO`, lo stesso identico
|
|
435
|
+
* dello scarico di un file del workspace (`workspace-files.mjs`) — importato, non ricopiato,
|
|
436
|
+
* perché due tetti scritti in due posti sono due tetti che un giorno diranno numeri diversi.
|
|
437
|
+
* Serve davvero: qui i byte finiscono TUTTI in memoria prima di partire.
|
|
438
|
+
* @returns {Promise<{bytes:Buffer, dimensione:number, nome:string, mediaType:string}|null>} `null`
|
|
439
|
+
* se l'id non nomina nessuna voce (id sconosciuto o fuori grammatica).
|
|
440
|
+
*/
|
|
441
|
+
export async function leggiBytesVoce({ cartella, id }, deps = {}) {
|
|
442
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
443
|
+
const statFn = deps.statFn ?? fsp.stat;
|
|
444
|
+
const cartellaVoce = cartellaDellaVoce(cartella, id);
|
|
445
|
+
if (!cartellaVoce) return null;
|
|
446
|
+
const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
|
|
447
|
+
if (meta === undefined) return null;
|
|
448
|
+
const voce = meta2voce(id, meta);
|
|
449
|
+
const percorsoContenuto = join(cartellaVoce, NOME_FILE_CONTENUTO);
|
|
450
|
+
let stat;
|
|
451
|
+
try {
|
|
452
|
+
stat = await statFn(percorsoContenuto);
|
|
453
|
+
} catch (errore) {
|
|
454
|
+
// ⛔ La scheda c'è e il file no: uno stato ROTTO, non un «non trovato» — dirlo è il solo modo perché qualcuno lo ripari.
|
|
455
|
+
throw new LibraryStoreError(`${id}: la scheda c'è, il file no (${errore.message})`, 'LIBRARY_READ_FAILED');
|
|
456
|
+
}
|
|
457
|
+
if (stat.size > DIMENSIONE_MASSIMA_SCARICO) {
|
|
458
|
+
throw new LibraryStoreError(
|
|
459
|
+
`File troppo grande da scaricare (${Math.round(stat.size / 1024 / 1024)} MB, tetto ${DIMENSIONE_MASSIMA_SCARICO / 1024 / 1024} MB)`,
|
|
460
|
+
'LIBRARY_TOO_LARGE',
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
const bytes = await readFileFn(percorsoContenuto);
|
|
464
|
+
return { bytes, dimensione: stat.size, nome: voce.nome, mediaType: voce.mediaType };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Solo la provenienza — MAI il contenuto: la stessa "seconda porta"
|
|
469
|
+
* di `library_file_origin` mobile (owner: "ogni funzione ha DUE
|
|
470
|
+
* porte, la stazione e l'attrezzo" — qui l'attrezzo che risponde "chi
|
|
471
|
+
* ha fatto questo file" senza dover leggerlo). `null` se l'id non
|
|
472
|
+
* esiste.
|
|
473
|
+
*/
|
|
474
|
+
export async function origineVoce({ cartella, id }, deps = {}) {
|
|
475
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
476
|
+
const cartellaVoce = cartellaDellaVoce(cartella, id);
|
|
477
|
+
if (!cartellaVoce) return null;
|
|
478
|
+
const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
|
|
479
|
+
if (meta === undefined) return null;
|
|
480
|
+
const voce = meta2voce(id, meta);
|
|
481
|
+
return { nome: voce.nome, origine: voce.origine, modello: voce.modello, provider: voce.provider, creatoIl: voce.creatoIl };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Un nome di Libreria non è testo libero: è un'etichetta. Porto diretto
|
|
486
|
+
* di `talosSafeLibraryName` mobile (`libraryWriteTools.ts` righe 48-58)
|
|
487
|
+
* — tolti separatori di percorso e caratteri di controllo, perché
|
|
488
|
+
* questo nome finisce in un file VERO su disco (`library_export`,
|
|
489
|
+
* seconda fetta) dove una barra smetterebbe di essere una lettera e
|
|
490
|
+
* diventerebbe una cartella. ⛔ Non porta `talosStripPromptEnvelope`
|
|
491
|
+
* (mobile la applica prima: rimuove un involucro di prompt-injection
|
|
492
|
+
* specifico del suo formato di messaggio) — il desktop non ha quel
|
|
493
|
+
* concetto, e sanificare qui i soli caratteri strutturalmente
|
|
494
|
+
* pericolosi resta sufficiente per un nome file.
|
|
495
|
+
*/
|
|
496
|
+
export function sanificaNomeLibreria(valore) {
|
|
497
|
+
return String(valore ?? '')
|
|
498
|
+
.normalize('NFKC')
|
|
499
|
+
.replace(/[--]/g, ' ')
|
|
500
|
+
.replace(/[/\\:*?"<>|]/g, ' ')
|
|
501
|
+
.replace(/\s+/g, ' ')
|
|
502
|
+
.trim()
|
|
503
|
+
.replace(/^\.+\s*/, '')
|
|
504
|
+
.trim();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Risolve un riferimento del modello (id ESATTO o nome visibile
|
|
509
|
+
* ESATTO) a UNA voce — porto diretto della logica di `library_export`
|
|
510
|
+
* mobile (`libraryExportTools.ts` righe 62-79): mai un fuzzy-match sul
|
|
511
|
+
* nome (case/spazi normalizzati sì, un sinonimo o un troncamento no).
|
|
512
|
+
* Più di un nome uguale ⇒ `{ambiguo:true}` (mai una scelta a caso: il
|
|
513
|
+
* modello deve chiedere); nessuna corrispondenza ⇒ `null`.
|
|
514
|
+
*/
|
|
515
|
+
export function trovaVoce(voci, reference) {
|
|
516
|
+
const perId = voci.find((v) => v.id === reference);
|
|
517
|
+
if (perId) return perId;
|
|
518
|
+
const cercato = String(reference ?? '').normalize('NFKC').trim().toLowerCase();
|
|
519
|
+
const perNome = voci.filter((v) => v.nome.normalize('NFKC').trim().toLowerCase() === cercato);
|
|
520
|
+
if (perNome.length > 1) return { ambiguo: true };
|
|
521
|
+
return perNome[0] ?? null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Rinomina una voce — SOLO i metadata, mai il contenuto. `null` se
|
|
526
|
+
* l'id non esiste; il nome nuovo è sanificato QUI (non fidato dal
|
|
527
|
+
* chiamante), e un nome vuoto DOPO la sanificazione è un errore
|
|
528
|
+
* dichiarato — mai un file rinominato al vuoto.
|
|
529
|
+
*/
|
|
530
|
+
export async function rinominaVoce({ cartella, id, nome }, deps = {}) {
|
|
531
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
532
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
533
|
+
const nomeSicuro = sanificaNomeLibreria(nome);
|
|
534
|
+
if (!nomeSicuro) {
|
|
535
|
+
throw new LibraryStoreError('Il nome è vuoto una volta tolti i caratteri di percorso — scegline uno semplice.', 'LIBRARY_NAME_EMPTY');
|
|
536
|
+
}
|
|
537
|
+
const cartellaVoce = cartellaDellaVoce(cartella, id);
|
|
538
|
+
if (!cartellaVoce) return null;
|
|
539
|
+
const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
|
|
540
|
+
if (meta === undefined) return null;
|
|
541
|
+
const nomePrima = meta.nome;
|
|
542
|
+
const aggiornato = { ...meta, nome: nomeSicuro, aggiornatoIl: new Date().toISOString() };
|
|
543
|
+
await writeFileFn(join(cartellaVoce, NOME_FILE_META), JSON.stringify(aggiornato, null, 2), 'utf8');
|
|
544
|
+
return { id, nomePrima, nomeDopo: nomeSicuro };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Elimina una voce PER INTERO (metadata + contenuto) — la cartella
|
|
549
|
+
* `<id>/` sparisce dal disco. `null` se l'id non esiste (mai
|
|
550
|
+
* un'eccezione per un id già sparito: è un esito onesto, "già andato",
|
|
551
|
+
* non un guasto — stesso principio di `library_delete` mobile,
|
|
552
|
+
* `libraryWriteTools.ts` riga 191 "It may already be gone").
|
|
553
|
+
*/
|
|
554
|
+
export async function eliminaVoce({ cartella, id }, deps = {}) {
|
|
555
|
+
const readFileFn = deps.readFileFn ?? fsp.readFile;
|
|
556
|
+
const rmFn = deps.rmFn ?? fsp.rm;
|
|
557
|
+
const cartellaVoce = cartellaDellaVoce(cartella, id);
|
|
558
|
+
if (!cartellaVoce) return null;
|
|
559
|
+
const meta = await leggiMeta(join(cartellaVoce, NOME_FILE_META), readFileFn, id);
|
|
560
|
+
if (meta === undefined) return null;
|
|
561
|
+
await rmFn(cartellaVoce, { recursive: true, force: true });
|
|
562
|
+
return { id, nome: meta.nome };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/*
|
|
566
|
+
* ⭐ (16/09/2026) — ELIMINAZIONE A LOTTI (richiesta owner): una sola chiamata
|
|
567
|
+
* `library_delete` con `ids` ripulisce N voci senza N giri del modello. L'iterazione
|
|
568
|
+
* riusa `eliminaVoce` COSÌ COM'È (mai una copia della sua logica: un credito futuro
|
|
569
|
+
* qui deve valere anche lì) e distingue l'eliminato dall'assente — un id già sparito
|
|
570
|
+
* NON è un guasto (stesso principio di `eliminaVoce`), finisce in `assenti` e l'esito
|
|
571
|
+
* resta onesto. Limiti spessi: mai un lotto che svuota la Libreria per sbaglio
|
|
572
|
+
* (tetto 100, una chiamata extra quando serve di più). `eliminaVoce` NON cambia.
|
|
573
|
+
*/
|
|
574
|
+
export async function eliminaVoci({ cartella, ids }, deps = {}) {
|
|
575
|
+
if (!Array.isArray(ids) || ids.length === 0) {
|
|
576
|
+
throw new LibraryStoreError('Per eliminare a lotti serve un array `ids` con almeno un id.', 'LIBRARY_INVALID');
|
|
577
|
+
}
|
|
578
|
+
if (ids.length > 100) {
|
|
579
|
+
throw new LibraryStoreError('Massimo 100 id per chiamata: spegni la richiesta in più lotti.', 'LIBRARY_INVALID');
|
|
580
|
+
}
|
|
581
|
+
for (const id of ids) {
|
|
582
|
+
if (typeof id !== 'string' || !id.trim()) {
|
|
583
|
+
throw new LibraryStoreError('Ogni elemento di `ids` deve essere un id non vuoto (stringa).', 'LIBRARY_INVALID');
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
const eliminate = [];
|
|
587
|
+
const assenti = [];
|
|
588
|
+
for (const id of ids) {
|
|
589
|
+
const risultato = await eliminaVoce({ cartella, id }, deps);
|
|
590
|
+
if (risultato) eliminate.push(risultato);
|
|
591
|
+
else assenti.push(id);
|
|
592
|
+
}
|
|
593
|
+
return { eliminate, assenti };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* ⛔ NON un tool del modello — primitivo interno per seminare/testare
|
|
598
|
+
* (e per la futura auto-archiviazione di document_create/generate_image
|
|
599
|
+
* nella Libreria, non ancora decisa). Un id non si passa mai dal
|
|
600
|
+
* chiamante: è sempre generato qui, stesso principio di ogni id in
|
|
601
|
+
* questo progetto.
|
|
602
|
+
*/
|
|
603
|
+
/*
|
|
604
|
+
* ⭐⭐⭐ BC-38 (12/09/2026) — `sessionId`/`sessionNome`: DA QUALE conversazione nasce un file.
|
|
605
|
+
*
|
|
606
|
+
* ⛔ Si SCRIVONO, non si deducono. Fino a stamattina il `meta.json` non portava nessun legame con
|
|
607
|
+
* la sessione, e non c'era modo di ricostruirlo dopo: la Libreria è per CARTELLA di progetto,
|
|
608
|
+
* quindi la stessa voce compare in TUTTE le sessioni che lavorano lì (verificato il 12/09/2026:
|
|
609
|
+
* `c8e9b07b` ed `efc9559d` mostrano le stesse 14 voci) — l'elenco in cui una voce compare non
|
|
610
|
+
* dice chi l'ha prodotta.
|
|
611
|
+
* ⛔ Il NOME viaggia congelato accanto all'id e non al posto suo: una sessione si rinomina, e una
|
|
612
|
+
* voce scritta ieri deve continuare a nominare la sessione giusta anche se oggi si chiama in un
|
|
613
|
+
* altro modo. Chi disegna preferisce il nome VIVO quando l'elenco delle sessioni ce l'ha.
|
|
614
|
+
* ⛔ Assenti ⇒ il `meta.json` esce identico a ieri, campo per campo: nessun chiamante che non li
|
|
615
|
+
* passa cambia di un byte, e le voci vecchie restano leggibili con `sessione: null`.
|
|
616
|
+
*/
|
|
617
|
+
export async function salvaVoce({ cartella, nome, mediaType, origine = 'uploaded', testo, base64, modello, provider, sessionId, sessionNome }, deps = {}) {
|
|
618
|
+
const mkdirFn = deps.mkdirFn ?? fsp.mkdir;
|
|
619
|
+
const writeFileFn = deps.writeFileFn ?? fsp.writeFile;
|
|
620
|
+
const randomUUIDFn = deps.randomUUIDFn ?? randomUUID;
|
|
621
|
+
if (typeof nome !== 'string' || nome.trim().length === 0) {
|
|
622
|
+
throw new LibraryStoreError('Un file di Libreria vuole un nome', 'LIBRARY_INVALID');
|
|
623
|
+
}
|
|
624
|
+
if (typeof testo !== 'string' && typeof base64 !== 'string') {
|
|
625
|
+
throw new LibraryStoreError('Un file di Libreria vuole un contenuto (testo o base64)', 'LIBRARY_INVALID');
|
|
626
|
+
}
|
|
627
|
+
const id = `lib-${randomUUIDFn()}`;
|
|
628
|
+
const cartellaVoce = join(cartella, CARTELLA_LIBRERIA, id);
|
|
629
|
+
await mkdirFn(cartellaVoce, { recursive: true });
|
|
630
|
+
const adesso = new Date().toISOString();
|
|
631
|
+
const origineEffettiva = origine === 'generated' ? 'generated' : 'uploaded';
|
|
632
|
+
const meta = {
|
|
633
|
+
nome: nome.trim(),
|
|
634
|
+
mediaType: mediaType || 'text/plain',
|
|
635
|
+
origine: origineEffettiva,
|
|
636
|
+
creatoIl: adesso,
|
|
637
|
+
aggiornatoIl: adesso,
|
|
638
|
+
...(origineEffettiva === 'generated' && modello ? { modello } : {}),
|
|
639
|
+
...(origineEffettiva === 'generated' && provider ? { provider } : {}),
|
|
640
|
+
...(typeof sessionId === 'string' && sessionId.trim() ? { sessionId: sessionId.trim() } : {}),
|
|
641
|
+
...(typeof sessionId === 'string' && sessionId.trim() && typeof sessionNome === 'string' && sessionNome.trim()
|
|
642
|
+
? { sessionNome: sessionNome.trim() }
|
|
643
|
+
: {}),
|
|
644
|
+
};
|
|
645
|
+
await writeFileFn(join(cartellaVoce, NOME_FILE_META), JSON.stringify(meta, null, 2), 'utf8');
|
|
646
|
+
if (typeof base64 === 'string') {
|
|
647
|
+
await writeFileFn(join(cartellaVoce, NOME_FILE_CONTENUTO), Buffer.from(base64, 'base64'));
|
|
648
|
+
} else {
|
|
649
|
+
await writeFileFn(join(cartellaVoce, NOME_FILE_CONTENUTO), testo, 'utf8');
|
|
650
|
+
}
|
|
651
|
+
return id;
|
|
652
|
+
}
|