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,221 @@
|
|
|
1
|
+
/** Canonical, provider-neutral contract for user-authored forged tools. */
|
|
2
|
+
const MAX_MANIFEST_BYTES = 65_536;
|
|
3
|
+
const MAX_NODES = 64;
|
|
4
|
+
const MAX_TRANSITIONS = 256;
|
|
5
|
+
const SAFE_SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
6
|
+
const FORBIDDEN_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__']);
|
|
7
|
+
const READ_ONLY_ROOTS = new Set(['input', 'runtime']);
|
|
8
|
+
const NODE_TYPES = ['capability', 'if', 'return', 'fail'];
|
|
9
|
+
const CAPABILITIES = Object.freeze({
|
|
10
|
+
'tasks.list': { actions: ['read'], risk: 'R1' },
|
|
11
|
+
'tasks.create': { actions: ['write'], risk: 'R2' },
|
|
12
|
+
'tasks.setStatus': { actions: ['write'], risk: 'R2' },
|
|
13
|
+
'notes.list': { actions: ['read'], risk: 'R1' },
|
|
14
|
+
'notes.create': { actions: ['write'], risk: 'R2' },
|
|
15
|
+
'notes.update': { actions: ['write'], risk: 'R2' },
|
|
16
|
+
'memory.search': { actions: ['read'], risk: 'R2' },
|
|
17
|
+
'memory.create': { actions: ['write'], risk: 'R3' },
|
|
18
|
+
});
|
|
19
|
+
const RISK_ORDER = { R1: 1, R2: 2, R3: 3 };
|
|
20
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9_-]{2,63}$/;
|
|
21
|
+
|
|
22
|
+
function rawSegments(path) {
|
|
23
|
+
const normalized = path.startsWith('$.') ? path.slice(2) : path === '$' ? '' : path;
|
|
24
|
+
return normalized.split('.').filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function forgePathViolation(path, { writable = false } = {}) {
|
|
28
|
+
if (typeof path !== 'string') return 'must be a string';
|
|
29
|
+
const parts = rawSegments(path);
|
|
30
|
+
if (parts.length > 16) return 'path exceeds 16 segments';
|
|
31
|
+
for (const part of parts) {
|
|
32
|
+
if (part.length > 64) return `segment "${part}" exceeds 64 characters`;
|
|
33
|
+
if (!SAFE_SEGMENT.test(part)) return `segment "${part}" must be alphanumeric, "_" or "-" only`;
|
|
34
|
+
if (FORBIDDEN_SEGMENTS.has(part)) return `segment "${part}" is forbidden`;
|
|
35
|
+
}
|
|
36
|
+
if (writable && parts[0] && READ_ONLY_ROOTS.has(parts[0])) return `"${parts[0]}" is read-only; write to "state" instead`;
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isReference(value) {
|
|
41
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value) && Object.keys(value).length === 1 && typeof value.$ref === 'string';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function collectReferences(value, result = []) {
|
|
45
|
+
if (isReference(value)) { result.push(value.$ref); return result; }
|
|
46
|
+
if (Array.isArray(value)) { for (const item of value) collectReferences(item, result); return result; }
|
|
47
|
+
if (value && typeof value === 'object') { for (const item of Object.values(value)) collectReferences(item, result); }
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function addReferenceDiagnostics(value, path, add) {
|
|
52
|
+
for (const reference of collectReferences(value)) {
|
|
53
|
+
const violation = forgePathViolation(reference);
|
|
54
|
+
if (violation) add(path, `unsafe $ref "${reference}": ${violation}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validates the versioned forge manifest without any filesystem or process
|
|
60
|
+
* access. It returns diagnostics instead of throwing so the caller can show
|
|
61
|
+
* a useful explanation and avoid writing an unsafe manifest.
|
|
62
|
+
*/
|
|
63
|
+
export function validaManifestForgeLocale(manifest) {
|
|
64
|
+
const diagnostics = [];
|
|
65
|
+
const add = (path, message) => diagnostics.push(`${path}: ${message}`);
|
|
66
|
+
if (!manifest || typeof manifest !== 'object') return { ok: false, diagnostica: ['manifest: must be an object'] };
|
|
67
|
+
let bytes;
|
|
68
|
+
try { bytes = new TextEncoder().encode(JSON.stringify(manifest)).length; } catch { bytes = Number.POSITIVE_INFINITY; }
|
|
69
|
+
if (bytes > MAX_MANIFEST_BYTES) add('manifest', `exceeds ${MAX_MANIFEST_BYTES} bytes`);
|
|
70
|
+
if (typeof manifest.id !== 'string' || !ID_PATTERN.test(manifest.id)) add('id', 'must be a lowercase slug, 3-64 chars, a-z0-9_- only');
|
|
71
|
+
if (typeof manifest.title !== 'string' || manifest.title.length < 1 || manifest.title.length > 80) add('title', 'must be 1-80 characters');
|
|
72
|
+
if (typeof manifest.description !== 'string' || manifest.description.length < 1 || manifest.description.length > 400) add('description', 'must be 1-400 characters');
|
|
73
|
+
const flow = manifest.flow;
|
|
74
|
+
if (!flow || typeof flow !== 'object') { add('flow', 'is required'); return { ok: false, diagnostica: diagnostics }; }
|
|
75
|
+
if (typeof flow.entry !== 'string' || flow.entry.length === 0) add('flow.entry', 'is required');
|
|
76
|
+
if (!Array.isArray(flow.nodes) || flow.nodes.length === 0) add('flow.nodes', 'must be a non-empty array');
|
|
77
|
+
if (Array.isArray(flow.nodes) && flow.nodes.length > MAX_NODES) add('flow.nodes', `exceeds ${MAX_NODES} nodes`);
|
|
78
|
+
const maxTransitions = Number(flow.maxTransitions);
|
|
79
|
+
if (!Number.isFinite(maxTransitions) || maxTransitions < 1 || maxTransitions > MAX_TRANSITIONS) add('flow.maxTransitions', `must be 1-${MAX_TRANSITIONS}`);
|
|
80
|
+
if (!Array.isArray(flow.nodes)) return { ok: false, diagnostica: diagnostics };
|
|
81
|
+
const ids = new Set();
|
|
82
|
+
const capabilities = new Set();
|
|
83
|
+
for (const [index, node] of flow.nodes.entries()) {
|
|
84
|
+
const path = `flow.nodes[${index}]`;
|
|
85
|
+
if (!node || typeof node !== 'object' || typeof node.id !== 'string' || node.id.length === 0) { add(path, 'must have a non-empty "id"'); continue; }
|
|
86
|
+
if (ids.has(node.id)) add(path, `duplicate node id "${node.id}"`);
|
|
87
|
+
ids.add(node.id);
|
|
88
|
+
if (!NODE_TYPES.includes(node.type)) { add(`${path}.type`, `must be one of ${NODE_TYPES.join(', ')}`); continue; }
|
|
89
|
+
if (node.type === 'capability') {
|
|
90
|
+
if (typeof node.capability !== 'string' || !CAPABILITIES[node.capability]) add(`${path}.capability`, `must be one of ${Object.keys(CAPABILITIES).join(', ')}`);
|
|
91
|
+
else capabilities.add(node.capability);
|
|
92
|
+
if (typeof node.next !== 'string' || node.next.length === 0) add(`${path}.next`, 'is required');
|
|
93
|
+
addReferenceDiagnostics(node.input, `${path}.input`, add);
|
|
94
|
+
if (node.target !== undefined) {
|
|
95
|
+
const violation = typeof node.target !== 'string' ? 'must be a string path' : forgePathViolation(node.target, { writable: true });
|
|
96
|
+
if (violation) add(`${path}.target`, violation);
|
|
97
|
+
}
|
|
98
|
+
} else if (node.type === 'if') {
|
|
99
|
+
if (!node.condition || typeof node.condition !== 'object') add(`${path}.condition`, 'is required');
|
|
100
|
+
else { addReferenceDiagnostics(node.condition.left, `${path}.condition`, add); addReferenceDiagnostics(node.condition.right, `${path}.condition`, add); }
|
|
101
|
+
if (typeof node.then !== 'string' || node.then.length === 0) add(`${path}.then`, 'is required');
|
|
102
|
+
if (typeof node.else !== 'string' || node.else.length === 0) add(`${path}.else`, 'is required');
|
|
103
|
+
} else if (node.type === 'return') {
|
|
104
|
+
addReferenceDiagnostics(node.value, `${path}.value`, add);
|
|
105
|
+
} else if (node.type === 'fail') {
|
|
106
|
+
if (typeof node.code !== 'string' || node.code.length === 0) add(`${path}.code`, 'is required');
|
|
107
|
+
if (typeof node.message !== 'string' || node.message.length === 0) add(`${path}.message`, 'is required');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (diagnostics.length > 0) return { ok: false, diagnostica: diagnostics };
|
|
111
|
+
for (const node of flow.nodes) {
|
|
112
|
+
for (const field of ['next', 'then', 'else']) {
|
|
113
|
+
if (typeof node[field] === 'string' && !ids.has(node[field])) return { ok: false, diagnostica: [`flow: "${node[field]}" (referenced by node "${node.id}".${field}) is not a node id`] };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!ids.has(flow.entry)) return { ok: false, diagnostica: [`flow.entry: "${flow.entry}" is not a node id`] };
|
|
117
|
+
const actions = new Set();
|
|
118
|
+
let risk = 'R1';
|
|
119
|
+
for (const capability of capabilities) {
|
|
120
|
+
const descriptor = CAPABILITIES[capability];
|
|
121
|
+
for (const action of descriptor.actions) actions.add(action);
|
|
122
|
+
if (RISK_ORDER[descriptor.risk] > RISK_ORDER[risk]) risk = descriptor.risk;
|
|
123
|
+
}
|
|
124
|
+
return { ok: true, diagnostica: [], capacita: [...capabilities], azioni: [...actions], rischio: risk };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export const FORGE_PREFISSO_NOME_TOOL = 'forge_';
|
|
128
|
+
|
|
129
|
+
export function forgeReadPath(root, path) {
|
|
130
|
+
if (path === '$') return root;
|
|
131
|
+
let current = root;
|
|
132
|
+
for (const segment of rawSegments(path)) {
|
|
133
|
+
if (current === null || typeof current !== 'object') return undefined;
|
|
134
|
+
if (Array.isArray(current) && /^\d+$/.test(segment)) { current = current[Number(segment)]; continue; }
|
|
135
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) return undefined;
|
|
136
|
+
current = current[segment];
|
|
137
|
+
}
|
|
138
|
+
return current;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function forgeWritePath(root, path, value) {
|
|
142
|
+
const violation = forgePathViolation(path, { writable: true });
|
|
143
|
+
if (violation) throw new Error(`TALOS_FORGE_PATH_UNSAFE:${violation}`);
|
|
144
|
+
const parts = rawSegments(path);
|
|
145
|
+
if (parts.length === 0) throw new Error('TALOS_FORGE_SET_ROOT_FORBIDDEN');
|
|
146
|
+
let current = root;
|
|
147
|
+
for (const part of parts.slice(0, -1)) {
|
|
148
|
+
const existing = current[part];
|
|
149
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) current[part] = Object.create(null);
|
|
150
|
+
current = current[part];
|
|
151
|
+
}
|
|
152
|
+
current[parts.at(-1)] = value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function forgeResolveExpression(expression, vars) {
|
|
156
|
+
if (isReference(expression)) return forgeReadPath(vars, expression.$ref);
|
|
157
|
+
if (Array.isArray(expression)) return expression.map((value) => forgeResolveExpression(value, vars));
|
|
158
|
+
if (expression && typeof expression === 'object') return Object.fromEntries(Object.entries(expression).map(([key, value]) => [key, forgeResolveExpression(value, vars)]));
|
|
159
|
+
return expression;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function forgeEvaluateCondition(condition, vars) {
|
|
163
|
+
const left = forgeResolveExpression(condition.left, vars);
|
|
164
|
+
const right = forgeResolveExpression(condition.right, vars);
|
|
165
|
+
switch (condition.op) {
|
|
166
|
+
case 'eq': return Object.is(left, right);
|
|
167
|
+
case 'neq': return !Object.is(left, right);
|
|
168
|
+
case 'truthy': return Boolean(left);
|
|
169
|
+
case 'exists': return left !== undefined && left !== null;
|
|
170
|
+
case 'contains': return typeof left === 'string' ? left.includes(String(right ?? '')) : Array.isArray(left) ? left.some((value) => Object.is(value, right)) : false;
|
|
171
|
+
case 'gt': return typeof left === 'number' && typeof right === 'number' && left > right;
|
|
172
|
+
case 'gte': return typeof left === 'number' && typeof right === 'number' && left >= right;
|
|
173
|
+
case 'lt': return typeof left === 'number' && typeof right === 'number' && left < right;
|
|
174
|
+
case 'lte': return typeof left === 'number' && typeof right === 'number' && left <= right;
|
|
175
|
+
default: return false;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function eseguiFlowForgeLocale(manifest, input, { capacitaFn } = {}) {
|
|
180
|
+
if (typeof capacitaFn !== 'function') throw new Error('TALOS_FORGE_CAPABILITY_UNAVAILABLE');
|
|
181
|
+
const vars = Object.create(null);
|
|
182
|
+
vars.input = input;
|
|
183
|
+
vars.state = Object.create(null);
|
|
184
|
+
const trace = [];
|
|
185
|
+
const nodes = new Map(manifest.flow.nodes.map((node) => [node.id, node]));
|
|
186
|
+
let currentId = manifest.flow.entry;
|
|
187
|
+
let transitions = 0;
|
|
188
|
+
const limit = Number.isFinite(manifest.flow.maxTransitions) ? Math.min(manifest.flow.maxTransitions, MAX_TRANSITIONS) : MAX_TRANSITIONS;
|
|
189
|
+
const pathFailure = (error) => ({ status: 'failed', error: { code: 'TALOS_FORGE_PATH_UNSAFE', message: error instanceof Error ? error.message : String(error) }, trace });
|
|
190
|
+
while (true) {
|
|
191
|
+
transitions += 1;
|
|
192
|
+
if (transitions > limit) return { status: 'failed', error: { code: 'TALOS_FORGE_MAX_TRANSITIONS', message: 'The tool ran too many steps without finishing.' }, trace };
|
|
193
|
+
const node = nodes.get(currentId);
|
|
194
|
+
if (!node) return { status: 'failed', error: { code: 'TALOS_FORGE_NODE_MISSING', message: `Node "${currentId}" does not exist.` }, trace };
|
|
195
|
+
if (node.type === 'capability') {
|
|
196
|
+
let resolved;
|
|
197
|
+
try { resolved = forgeResolveExpression(node.input, vars); } catch (error) { return pathFailure(error); }
|
|
198
|
+
let result;
|
|
199
|
+
try { result = await capacitaFn(node.capability, resolved); } catch (error) { return { status: 'failed', error: { code: 'TALOS_FORGE_CAPABILITY_FAILED', message: `Capability "${node.capability}" failed: ${error instanceof Error ? error.message : String(error)}` }, trace }; }
|
|
200
|
+
trace.push({ node: node.id, type: 'capability', capability: node.capability });
|
|
201
|
+
if (node.target) { try { forgeWritePath(vars, node.target, result); } catch (error) { return pathFailure(error); } }
|
|
202
|
+
currentId = node.next;
|
|
203
|
+
} else if (node.type === 'if') {
|
|
204
|
+
let outcome;
|
|
205
|
+
try { outcome = forgeEvaluateCondition(node.condition, vars); } catch (error) { return pathFailure(error); }
|
|
206
|
+
trace.push({ node: node.id, type: 'if', result: outcome });
|
|
207
|
+
currentId = outcome ? node.then : node.else;
|
|
208
|
+
} else if (node.type === 'return') {
|
|
209
|
+
let output;
|
|
210
|
+
try { output = forgeResolveExpression(node.value, vars); } catch (error) { return pathFailure(error); }
|
|
211
|
+
trace.push({ node: node.id, type: 'return' });
|
|
212
|
+
return { status: 'succeeded', output, trace };
|
|
213
|
+
} else if (node.type === 'fail') {
|
|
214
|
+
trace.push({ node: node.id, type: 'fail' });
|
|
215
|
+
return { status: 'failed', error: { code: node.code, message: node.message }, trace };
|
|
216
|
+
} else {
|
|
217
|
+
return { status: 'failed', error: { code: 'TALOS_FORGE_NODE_TYPE_UNKNOWN', message: `Unknown node type "${node.type}".` }, trace };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* frequent-dirs.mjs — owner, coda del piano `elegant-spinning-dongarra.md`:
|
|
3
|
+
* "nella lista file quando si crea una sessione, bisogna mettere directory
|
|
4
|
+
* più usate (tipo desktop downloads etc)". Solo LETTURA, solo suggerimenti:
|
|
5
|
+
* questo file non decide se una cartella è AMMESSA — quello resta
|
|
6
|
+
* `custom-task.mjs`/`avviaLibero` (permesso "Full access", vedi la sua doc
|
|
7
|
+
* sul perché niente denylist), qui si limita a proporre percorsi VERI e
|
|
8
|
+
* VERIFICATI, mai una scorciatoia verso il nulla.
|
|
9
|
+
*
|
|
10
|
+
* ⛔⛔⛔ 30/8, CORRETTO — owner dal vivo: "come mai non ho le cartelle più
|
|
11
|
+
* usate?" La prima versione (28/8) calcolava SOLO dalle cartelle utente
|
|
12
|
+
* standard di Windows (`os.homedir()`) — sempre le stesse tre, MAI la
|
|
13
|
+
* cronologia reale, quindi mai davvero "più usate". Ora la fonte PRIMARIA
|
|
14
|
+
* è `sessionRegistry.cartellePiuUsate()` (cronologia reale delle
|
|
15
|
+
* sessioni, aggregata in `session-registry.mjs`); le tre cartelle
|
|
16
|
+
* Windows standard restano SOLO il ripiego onesto per l'avvio a freddo
|
|
17
|
+
* (zero sessioni mai partite) — vedi `cartelleFrequenti()` sotto.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ Deliberatamente SEPARATO da `config.mjs#parseCartelleProgetto`: quella
|
|
20
|
+
* è l'allowlist fissa dell'amministratore, validata fail-closed all'AVVIO
|
|
21
|
+
* del server. Questa è un elenco DINAMICO, calcolato a ogni richiesta —
|
|
22
|
+
* le due liste non si mescolano MAI: le cartelle frequenti sono solo
|
|
23
|
+
* suggerimenti per il campo "Full access" (percorso a piacere), non una
|
|
24
|
+
* seconda allowlist che aggirerebbe il permesso.
|
|
25
|
+
*/
|
|
26
|
+
import { homedir } from 'node:os';
|
|
27
|
+
import { join, resolve } from 'node:path';
|
|
28
|
+
import { statSync } from 'node:fs';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* ⭐ Le stesse TRE cartelle che ogni file manager Windows mostra per
|
|
32
|
+
* default nella barra laterale ("Quick access") — non inventate, sono i
|
|
33
|
+
* nomi di cartella standard sotto il profilo utente su ogni installazione
|
|
34
|
+
* Windows moderna. Il ripiego onesto per l'avvio a freddo (nessuna
|
|
35
|
+
* cronologia reale ancora), MAI mescolate con essa — vedi doc di testa.
|
|
36
|
+
*/
|
|
37
|
+
const CANDIDATE = Object.freeze([
|
|
38
|
+
{ etichetta: 'Desktop', sottocartella: 'Desktop' },
|
|
39
|
+
{ etichetta: 'Download', sottocartella: 'Downloads' },
|
|
40
|
+
{ etichetta: 'Documenti', sottocartella: 'Documents' },
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* ⭐⭐⭐ 30/8 — owner dal vivo: "come mai non ho le cartelle più usate?"
|
|
45
|
+
* Le tre cartelle Windows standard sopra NON erano mai state "più usate"
|
|
46
|
+
* per davvero — erano SEMPRE le stesse, indipendentemente da cosa
|
|
47
|
+
* l'owner avesse effettivamente aperto. Ora la fonte VERA
|
|
48
|
+
* (`sessionRegistry.cartellePiuUsate()`, aggregata dalla cronologia reale
|
|
49
|
+
* delle sessioni) ha priorità; le cartelle Windows standard restano
|
|
50
|
+
* SOLO il ripiego onesto per l'avvio a freddo (zero sessioni mai
|
|
51
|
+
* partite) — mai sparire del tutto, mai fingere una cronologia che non
|
|
52
|
+
* c'è.
|
|
53
|
+
*
|
|
54
|
+
* @param {number} massimoRisultati — quante suggerirne al massimo (il campo è solo un aiuto, non un elenco esaustivo).
|
|
55
|
+
* @returns {Array<{percorso:string, conteggio:number, ultimaVolta:string}>}
|
|
56
|
+
*/
|
|
57
|
+
function candidatiDaCronologia(sessionRegistry, massimoRisultati) {
|
|
58
|
+
if (!sessionRegistry || typeof sessionRegistry.cartellePiuUsate !== 'function') return [];
|
|
59
|
+
return sessionRegistry.cartellePiuUsate().slice(0, massimoRisultati);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function cartellaReale(percorso, statSyncFn) {
|
|
63
|
+
try {
|
|
64
|
+
return statSyncFn(percorso).isDirectory();
|
|
65
|
+
} catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function cartelleStandard({ homedirFn, statSyncFn }) {
|
|
71
|
+
const home = homedirFn();
|
|
72
|
+
return CANDIDATE.flatMap(({ etichetta, sottocartella }) => {
|
|
73
|
+
const percorso = join(home, sottocartella);
|
|
74
|
+
return cartellaReale(percorso, statSyncFn) ? [{ etichetta, percorso }] : [];
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function chiavePercorso(percorso) {
|
|
79
|
+
return resolve(percorso).replace(/[\\/]+$/, '').toLocaleLowerCase('en-US');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {{homedirFn?: typeof homedir, statSyncFn?: typeof statSync, sessionRegistry?: {cartellePiuUsate: () => Array<{percorso:string, conteggio:number, ultimaVolta:string}>}}} [deps] — `sessionRegistry` opzionale: assente nei test che provano solo il ripiego Windows, sempre presente dal server reale (vedi http-app.mjs).
|
|
84
|
+
* @returns {Array<{etichetta:string, percorso:string}>} — SOLO le cartelle che esistono
|
|
85
|
+
* davvero sul disco in questo momento, mai una candidata a occhio: una
|
|
86
|
+
* cartella cancellata dall'owner (Download standard, o una copia
|
|
87
|
+
* usa-e-getta del corpus benchmark ormai ripulita) non deve comparire
|
|
88
|
+
* come scorciatoia verso il nulla.
|
|
89
|
+
*/
|
|
90
|
+
export function cartelleFrequenti({ homedirFn = homedir, statSyncFn = statSync, sessionRegistry } = {}) {
|
|
91
|
+
const MASSIMO = 6;
|
|
92
|
+
const daCronologia = candidatiDaCronologia(sessionRegistry, MASSIMO);
|
|
93
|
+
const trovate = [];
|
|
94
|
+
for (const { percorso } of daCronologia) {
|
|
95
|
+
if (!cartellaReale(percorso, statSyncFn)) continue;
|
|
96
|
+
// ⭐ etichetta = nome dell'ultima cartella nel percorso ("qa-visiva-harness-2026-08-30" da ...\projects\qa-visiva-harness-2026-08-30), stesso principio di nomeCartella lato client (app.js, split su [\\/]).
|
|
97
|
+
const etichetta = percorso.replace(/[/\\]+$/, '').split(/[/\\]/).pop() || percorso;
|
|
98
|
+
trovate.push({ etichetta, percorso });
|
|
99
|
+
}
|
|
100
|
+
if (trovate.length > 0) return trovate; // ⛔ cronologia reale disponibile: MAI mescolarla con Desktop/Downloads/Documenti sotto — quelle tre non sono "più usate", solo il ripiego a freddo.
|
|
101
|
+
return cartelleStandard({ homedirFn, statSyncFn });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Scelte rapide della workbench "Nuova sessione". A differenza del contratto
|
|
106
|
+
* storico `cartelleFrequenti()` (che resta identico), qui l'owner ha bisogno
|
|
107
|
+
* sia della cronologia vera sia delle cartelle standard realmente presenti.
|
|
108
|
+
* L'unione è solo presentazione: non crea una allowlist e non modifica i
|
|
109
|
+
* permessi della sessione.
|
|
110
|
+
*
|
|
111
|
+
* @returns {Array<{etichetta:string, percorso:string, tipo:'recent'|'known'}>}
|
|
112
|
+
*/
|
|
113
|
+
export function cartelleConsigliate({ homedirFn = homedir, statSyncFn = statSync, sessionRegistry } = {}) {
|
|
114
|
+
const trovate = [];
|
|
115
|
+
const viste = new Set();
|
|
116
|
+
for (const { percorso } of candidatiDaCronologia(sessionRegistry, 6)) {
|
|
117
|
+
if (!cartellaReale(percorso, statSyncFn)) continue;
|
|
118
|
+
const chiave = chiavePercorso(percorso);
|
|
119
|
+
if (viste.has(chiave)) continue;
|
|
120
|
+
viste.add(chiave);
|
|
121
|
+
trovate.push({
|
|
122
|
+
etichetta: percorso.replace(/[/\\]+$/, '').split(/[/\\]/).pop() || percorso,
|
|
123
|
+
percorso,
|
|
124
|
+
tipo: 'recent',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
for (const { etichetta, percorso } of cartelleStandard({ homedirFn, statSyncFn })) {
|
|
128
|
+
const chiave = chiavePercorso(percorso);
|
|
129
|
+
if (viste.has(chiave)) continue;
|
|
130
|
+
viste.add(chiave);
|
|
131
|
+
trovate.push({ etichetta, percorso, tipo: 'known' });
|
|
132
|
+
}
|
|
133
|
+
return trovate;
|
|
134
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, join, resolve } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Durable local store for images produced by an agent run.
|
|
7
|
+
*
|
|
8
|
+
* The workspace copy remains the user-facing Library item. This store is the
|
|
9
|
+
* recovery copy: it is written atomically, carries a small JSON sidecar, and
|
|
10
|
+
* can be reopened by a fresh process after a server restart. No remote URL is
|
|
11
|
+
* retained as the image itself.
|
|
12
|
+
*/
|
|
13
|
+
export const GENERATED_IMAGE_MIME_TYPES = Object.freeze({
|
|
14
|
+
'image/png': 'png',
|
|
15
|
+
'image/jpeg': 'jpg',
|
|
16
|
+
'image/webp': 'webp',
|
|
17
|
+
'image/gif': 'gif',
|
|
18
|
+
'image/avif': 'avif',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const ID = /^[a-f0-9]{64}$/u;
|
|
22
|
+
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
23
|
+
|
|
24
|
+
export class GeneratedImageStoreError extends Error {
|
|
25
|
+
constructor(message, code = 'TALOS_IMAGE_PERSIST_FAILED') {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'GeneratedImageStoreError';
|
|
28
|
+
this.code = code;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function asBytes(value) {
|
|
33
|
+
if (Buffer.isBuffer(value)) return value;
|
|
34
|
+
if (value instanceof Uint8Array) return Buffer.from(value);
|
|
35
|
+
throw new GeneratedImageStoreError('image bytes are missing or invalid', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validatePromptHash(value) {
|
|
39
|
+
if (value == null || value === '') return null;
|
|
40
|
+
if (typeof value !== 'string' || !SHA256.test(value)) throw new GeneratedImageStoreError('image prompt hash is invalid', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateSource(value) {
|
|
45
|
+
if (typeof value !== 'string' || value.trim() === '' || value.length > 512 || /[\0\r\n]/u.test(value)) {
|
|
46
|
+
throw new GeneratedImageStoreError('image source is invalid', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
47
|
+
}
|
|
48
|
+
return value.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function safeId({ bytes, source, promptHash }) {
|
|
52
|
+
const digest = createHash('sha256');
|
|
53
|
+
digest.update(bytes);
|
|
54
|
+
digest.update('\0');
|
|
55
|
+
digest.update(source);
|
|
56
|
+
digest.update('\0');
|
|
57
|
+
digest.update(promptHash ?? '');
|
|
58
|
+
return digest.digest('hex');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseMetadata(value, id) {
|
|
62
|
+
if (!value || typeof value !== 'object' || value.id !== id || typeof value.mimeType !== 'string' || !Object.hasOwn(GENERATED_IMAGE_MIME_TYPES, value.mimeType) || typeof value.source !== 'string' || (value.promptHash !== null && !SHA256.test(value.promptHash)) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || !SHA256.test(value.sha256) || typeof value.createdAt !== 'string' || !Number.isFinite(Date.parse(value.createdAt))) {
|
|
63
|
+
throw new GeneratedImageStoreError('stored image metadata is invalid', 'TALOS_IMAGE_PERSIST_CORRUPT');
|
|
64
|
+
}
|
|
65
|
+
return structuredClone(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createGeneratedImageStore({ rootDir, fsImpl = {}, now = () => new Date() } = {}) {
|
|
69
|
+
if (typeof rootDir !== 'string' || !rootDir.trim() || !resolve(rootDir)) throw new GeneratedImageStoreError('generated image store root is invalid', 'TALOS_IMAGE_PERSIST_CONFIG_INVALID');
|
|
70
|
+
const fs = { mkdir, readFile, rename, rm, stat, writeFile, ...fsImpl };
|
|
71
|
+
const imageDir = resolve(rootDir);
|
|
72
|
+
|
|
73
|
+
const paths = (id, mimeType) => {
|
|
74
|
+
if (typeof id !== 'string' || !ID.test(id)) throw new GeneratedImageStoreError('stored image id is invalid', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
75
|
+
const extension = GENERATED_IMAGE_MIME_TYPES[mimeType];
|
|
76
|
+
return {
|
|
77
|
+
bytes: join(imageDir, `${id}.${extension}`),
|
|
78
|
+
metadata: join(imageDir, `${id}.json`),
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
async function persistGeneratedImage({ bytes: rawBytes, mimeType, source, promptHash = null } = {}) {
|
|
83
|
+
const bytes = asBytes(rawBytes);
|
|
84
|
+
if (bytes.byteLength === 0) throw new GeneratedImageStoreError('empty image cannot be saved', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
85
|
+
if (typeof mimeType !== 'string' || !Object.hasOwn(GENERATED_IMAGE_MIME_TYPES, mimeType)) throw new GeneratedImageStoreError('image format is not supported', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
86
|
+
const normalizedSource = validateSource(source);
|
|
87
|
+
const normalizedPromptHash = validatePromptHash(promptHash);
|
|
88
|
+
const id = safeId({ bytes, source: normalizedSource, promptHash: normalizedPromptHash });
|
|
89
|
+
const destination = paths(id, mimeType);
|
|
90
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
91
|
+
const metadata = {
|
|
92
|
+
schema: 'talos.generated-image.v1', id, mimeType, source: normalizedSource,
|
|
93
|
+
promptHash: normalizedPromptHash, bytes: bytes.byteLength, sha256, createdAt: now().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
await fs.mkdir(imageDir, { recursive: true });
|
|
96
|
+
const temporaryBytes = `${destination.bytes}.tmp-${process.pid}-${randomUUID()}`;
|
|
97
|
+
const temporaryMetadata = `${destination.metadata}.tmp-${process.pid}-${randomUUID()}`;
|
|
98
|
+
try {
|
|
99
|
+
await fs.writeFile(temporaryBytes, bytes, { flag: 'wx' });
|
|
100
|
+
await fs.writeFile(temporaryMetadata, `${JSON.stringify(metadata)}\n`, { encoding: 'utf8', flag: 'wx' });
|
|
101
|
+
await fs.rename(temporaryBytes, destination.bytes);
|
|
102
|
+
await fs.rename(temporaryMetadata, destination.metadata);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
await fs.rm(temporaryBytes, { force: true }).catch(() => {});
|
|
105
|
+
await fs.rm(temporaryMetadata, { force: true }).catch(() => {});
|
|
106
|
+
// A deterministic id means a retry of the same response is safe: if a
|
|
107
|
+
// complete pair is already present, reopen and return it instead of
|
|
108
|
+
// pretending that the second write was a new image.
|
|
109
|
+
if (error?.code === 'EEXIST') {
|
|
110
|
+
const existing = await readGeneratedImage(id).catch(() => null);
|
|
111
|
+
if (existing?.sha256 === sha256 && existing.mimeType === mimeType) return existing;
|
|
112
|
+
}
|
|
113
|
+
throw new GeneratedImageStoreError(`image could not be saved safely: ${error?.message ?? String(error)}`);
|
|
114
|
+
}
|
|
115
|
+
return { ...metadata, path: destination.bytes };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function readGeneratedImage(id) {
|
|
119
|
+
if (typeof id !== 'string' || !ID.test(id)) throw new GeneratedImageStoreError('stored image id is invalid', 'TALOS_IMAGE_PERSIST_INVALID');
|
|
120
|
+
let metadata;
|
|
121
|
+
try { metadata = parseMetadata(JSON.parse(await fs.readFile(join(imageDir, `${id}.json`), 'utf8')), id); }
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof GeneratedImageStoreError) throw error;
|
|
124
|
+
if (error?.code === 'ENOENT') throw new GeneratedImageStoreError('stored image was not found', 'TALOS_IMAGE_PERSIST_NOT_FOUND');
|
|
125
|
+
throw new GeneratedImageStoreError(`stored image metadata could not be read: ${error?.message ?? String(error)}`, 'TALOS_IMAGE_PERSIST_CORRUPT');
|
|
126
|
+
}
|
|
127
|
+
const destination = paths(id, metadata.mimeType);
|
|
128
|
+
let bytes;
|
|
129
|
+
try { bytes = await fs.readFile(destination.bytes); }
|
|
130
|
+
catch (error) { throw new GeneratedImageStoreError(`stored image bytes could not be read: ${error?.message ?? String(error)}`, 'TALOS_IMAGE_PERSIST_CORRUPT'); }
|
|
131
|
+
if (bytes.byteLength !== metadata.bytes || createHash('sha256').update(bytes).digest('hex') !== metadata.sha256) throw new GeneratedImageStoreError('stored image checksum does not match', 'TALOS_IMAGE_PERSIST_CORRUPT');
|
|
132
|
+
return { ...metadata, bytes: Buffer.from(bytes), path: destination.bytes };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function removeGeneratedImage(id) {
|
|
136
|
+
const metadataPath = join(imageDir, `${id}.json`);
|
|
137
|
+
let metadata;
|
|
138
|
+
try { metadata = parseMetadata(JSON.parse(await fs.readFile(metadataPath, 'utf8')), id); }
|
|
139
|
+
catch (error) { if (error?.code === 'ENOENT') return false; throw error; }
|
|
140
|
+
await fs.rm(paths(id, metadata.mimeType).bytes, { force: true });
|
|
141
|
+
await fs.rm(metadataPath, { force: true });
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return Object.freeze({ persistGeneratedImage, readGeneratedImage, removeGeneratedImage });
|
|
146
|
+
}
|
|
147
|
+
|