talos-code 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +111 -3
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/archive/zip.js +73 -0
- package/dist/args.js +97 -0
- package/dist/automations/history-store.js +34 -0
- package/dist/automations/policy-store.js +43 -0
- package/dist/automations/runner.js +107 -0
- package/dist/automations/schedule.js +85 -0
- package/dist/automations/windows-task.js +53 -0
- package/dist/commands/advanced-cli.js +233 -0
- package/dist/commands/checkpoint-cli.js +45 -0
- package/dist/commands/config-cli.js +107 -0
- package/dist/commands/context.js +17 -0
- package/dist/commands/extensions-cli.js +207 -0
- package/dist/commands/project-cli.js +74 -0
- package/dist/commands/project-commands.js +143 -0
- package/dist/commands/provider-cli.js +185 -0
- package/dist/commands/services-cli.js +248 -0
- package/dist/commands/session-cli.js +171 -0
- package/dist/commands/system-cli.js +405 -0
- package/dist/config/commands.js +60 -0
- package/dist/config/load.js +382 -0
- package/dist/config/migrations.js +48 -0
- package/dist/config/types.js +11 -0
- package/dist/diagnostics/development-log.js +154 -0
- package/dist/diagnostics/doctor.js +104 -0
- package/dist/diagnostics/redact.js +80 -0
- package/dist/diagnostics/zip.js +52 -0
- package/dist/errors.js +156 -0
- package/dist/events/bridge.js +130 -0
- package/dist/extensions/installer.js +178 -0
- package/dist/extensions/package-schema.js +24 -0
- package/dist/headless/result.js +84 -0
- package/dist/headless/run.js +230 -0
- package/dist/i18n/en/approval.js +43 -0
- package/dist/i18n/en/common.js +9 -0
- package/dist/i18n/en/credentials.js +82 -0
- package/dist/i18n/en/errors.js +246 -0
- package/dist/i18n/en/firstrun.js +89 -0
- package/dist/i18n/en/providers.js +37 -0
- package/dist/i18n/en/screen.js +736 -0
- package/dist/i18n/en/tools.js +127 -0
- package/dist/i18n/en.js +14 -0
- package/dist/i18n/error-view.js +307 -0
- package/dist/i18n/index.js +19 -0
- package/dist/i18n/kernel-map.js +139 -0
- package/dist/io.js +45 -0
- package/dist/main.js +330 -0
- package/dist/paths.js +41 -0
- package/dist/protocol/v2/codec.js +9 -0
- package/dist/protocol/v2/events.js +555 -0
- package/dist/protocol/v2/index.js +4 -0
- package/dist/protocol/v2/replay.js +28 -0
- package/dist/protocol/v2/types.js +1 -0
- package/dist/provider/control-plane.js +135 -0
- package/dist/provider/environment-keys.js +275 -0
- package/dist/provider/health.js +110 -0
- package/dist/provider/missing-key.js +48 -0
- package/dist/provider/model-catalog.js +168 -0
- package/dist/provider/provider-text.js +13 -0
- package/dist/provider/store.js +95 -0
- package/dist/provider/system-keyring.js +214 -0
- package/dist/runtime/active-run.js +63 -0
- package/dist/runtime/agent-tree.js +74 -0
- package/dist/runtime/attachments.js +84 -0
- package/dist/runtime/brokered-executor.js +395 -0
- package/dist/runtime/context-status.js +76 -0
- package/dist/runtime/create-runtime.js +111 -0
- package/dist/runtime/model-profile.js +361 -0
- package/dist/runtime/output-store.js +137 -0
- package/dist/runtime/provider-attempts.js +185 -0
- package/dist/runtime/replay-buffer.js +153 -0
- package/dist/runtime/repo.js +95 -0
- package/dist/runtime/session-facade.js +135 -0
- package/dist/runtime/session-summary.js +125 -0
- package/dist/runtime/supervisor.js +262 -0
- package/dist/runtime/talos-composition.js +1006 -0
- package/dist/runtime/types.js +5 -0
- package/dist/runtime/usage-snapshot.js +82 -0
- package/dist/security/auto-classifier.js +38 -0
- package/dist/security/credential-free-environment.js +54 -0
- package/dist/security/evaluate.js +243 -0
- package/dist/security/execution-backends.js +236 -0
- package/dist/security/execution-broker.js +102 -0
- package/dist/security/forge-scan.js +74 -0
- package/dist/security/from-approval.js +39 -0
- package/dist/security/mxc-execution-backend.js +281 -0
- package/dist/security/permission-engine.js +138 -0
- package/dist/security/permission-explanation.js +54 -0
- package/dist/security/persist.js +179 -0
- package/dist/security/plugin-guard.js +230 -0
- package/dist/security/process-tree-evidence-store.js +74 -0
- package/dist/security/process-tree-probe.js +554 -0
- package/dist/security/project-resource-inventory.js +186 -0
- package/dist/security/project-trust-gate.js +38 -0
- package/dist/security/project-trust.js +367 -0
- package/dist/security/rule-parser.js +201 -0
- package/dist/security/shell-segmentation.js +68 -0
- package/dist/security/trust-authority.js +324 -0
- package/dist/security/types.js +1 -0
- package/dist/security/workspace-identity.js +102 -0
- package/dist/services/automation-facade.js +59 -0
- package/dist/services/forge-facade.js +77 -0
- package/dist/services/hook-facade.js +240 -0
- package/dist/services/index.js +16 -0
- package/dist/services/library-facade.js +174 -0
- package/dist/services/mcp-facade.js +348 -0
- package/dist/services/memory-facade.js +126 -0
- package/dist/services/notes-facade.js +157 -0
- package/dist/services/plugin-facade.js +308 -0
- package/dist/services/research-facade.js +110 -0
- package/dist/services/task-board-facade.js +236 -0
- package/dist/services/workflow-catalog.js +246 -0
- package/dist/sessions/export-format.js +98 -0
- package/dist/sessions/metadata-store.js +160 -0
- package/dist/sessions/share.js +122 -0
- package/dist/sessions/transfer.js +16 -0
- package/dist/subcommands.js +62 -0
- package/dist/tui/agent-roster.js +36 -0
- package/dist/tui/app.js +3761 -0
- package/dist/tui/approval.js +36 -0
- package/dist/tui/boot/boot-sequence.js +76 -0
- package/dist/tui/boot/cinematic.js +243 -0
- package/dist/tui/boot/desktop-logo.js +82 -0
- package/dist/tui/boot.js +3 -0
- package/dist/tui/busy-input.js +58 -0
- package/dist/tui/catalog-service.js +559 -0
- package/dist/tui/components/assistant-stream.js +1 -0
- package/dist/tui/components/command-menu.js +68 -0
- package/dist/tui/components/composer.js +219 -0
- package/dist/tui/components/diff.js +35 -0
- package/dist/tui/components/footer.js +48 -0
- package/dist/tui/components/header.js +6 -0
- package/dist/tui/components/markdown.js +305 -0
- package/dist/tui/components/status-indicator.js +32 -0
- package/dist/tui/components/terminal-shell.js +205 -0
- package/dist/tui/components/thinking-row.js +19 -0
- package/dist/tui/components/tool-row.js +97 -0
- package/dist/tui/components/transcript-virtualizer.js +165 -0
- package/dist/tui/components/transcript.js +43 -0
- package/dist/tui/diff-model.js +77 -0
- package/dist/tui/editor-history.js +21 -0
- package/dist/tui/editor.js +210 -0
- package/dist/tui/event-adapter.js +436 -0
- package/dist/tui/exit-output.js +58 -0
- package/dist/tui/external-editor.js +53 -0
- package/dist/tui/file-completion.js +89 -0
- package/dist/tui/focus-manager.js +5 -0
- package/dist/tui/highlight.js +30 -0
- package/dist/tui/input-router.js +18 -0
- package/dist/tui/interrupt.js +99 -0
- package/dist/tui/keybindings.js +194 -0
- package/dist/tui/keymap-resolver.js +91 -0
- package/dist/tui/launch-state.js +139 -0
- package/dist/tui/line-diff.js +57 -0
- package/dist/tui/live-activity.js +45 -0
- package/dist/tui/metrics.js +123 -0
- package/dist/tui/onboarding.js +36 -0
- package/dist/tui/overlays/agent-tree.js +39 -0
- package/dist/tui/overlays/approval-dialog.js +640 -0
- package/dist/tui/overlays/automation-center.js +51 -0
- package/dist/tui/overlays/checkpoint-picker.js +47 -0
- package/dist/tui/overlays/context-inspector.js +36 -0
- package/dist/tui/overlays/effort-line.js +110 -0
- package/dist/tui/overlays/forge-center.js +46 -0
- package/dist/tui/overlays/help-dialog.js +16 -0
- package/dist/tui/overlays/history-picker.js +37 -0
- package/dist/tui/overlays/hook-center.js +70 -0
- package/dist/tui/overlays/library-center.js +55 -0
- package/dist/tui/overlays/mcp-center.js +60 -0
- package/dist/tui/overlays/memory-center.js +63 -0
- package/dist/tui/overlays/model-picker.js +72 -0
- package/dist/tui/overlays/notes-center.js +53 -0
- package/dist/tui/overlays/overlay-host.js +8 -0
- package/dist/tui/overlays/plugin-center.js +76 -0
- package/dist/tui/overlays/provider-picker.js +145 -0
- package/dist/tui/overlays/queue-editor.js +32 -0
- package/dist/tui/overlays/research-center.js +59 -0
- package/dist/tui/overlays/scroll-window.js +234 -0
- package/dist/tui/overlays/session-picker.js +115 -0
- package/dist/tui/overlays/tasks-center.js +83 -0
- package/dist/tui/overlays/theme-picker.js +21 -0
- package/dist/tui/overlays/transcript-search.js +58 -0
- package/dist/tui/overlays/trust-center.js +29 -0
- package/dist/tui/overlays/workflow-center.js +46 -0
- package/dist/tui/project-file-index.js +214 -0
- package/dist/tui/project-references.js +85 -0
- package/dist/tui/project-trust-prompt.js +287 -0
- package/dist/tui/prompt-history-store.js +116 -0
- package/dist/tui/queue-store.js +110 -0
- package/dist/tui/regions/budget.js +59 -0
- package/dist/tui/regions/views.js +96 -0
- package/dist/tui/render-coordinator.js +148 -0
- package/dist/tui/render-scheduler.js +4 -0
- package/dist/tui/run.js +69 -0
- package/dist/tui/selection-list.js +29 -0
- package/dist/tui/session-controller.js +778 -0
- package/dist/tui/session-export.js +54 -0
- package/dist/tui/shell-input.js +35 -0
- package/dist/tui/shell-layout.js +48 -0
- package/dist/tui/shell-model.js +98 -0
- package/dist/tui/slash-commands.js +80 -0
- package/dist/tui/state.js +151 -0
- package/dist/tui/status-bar.js +125 -0
- package/dist/tui/status-view.js +40 -0
- package/dist/tui/terminal-capabilities.js +9 -0
- package/dist/tui/terminal-session.js +11 -0
- package/dist/tui/text-width.js +66 -0
- package/dist/tui/theme-catalog.js +25 -0
- package/dist/tui/theme-store.js +23 -0
- package/dist/tui/theme.js +23 -0
- package/dist/tui/tool-display.js +135 -0
- package/dist/tui/tool-facts.js +1 -0
- package/dist/tui/tools/bash-renderer.js +32 -0
- package/dist/tui/tools/change.js +61 -0
- package/dist/tui/tools/edit-renderer.js +17 -0
- package/dist/tui/tools/generic-renderer.js +13 -0
- package/dist/tui/tools/list-renderer.js +14 -0
- package/dist/tui/tools/read-renderer.js +13 -0
- package/dist/tui/tools/registry.js +20 -0
- package/dist/tui/tools/row-format.js +187 -0
- package/dist/tui/tools/search-renderer.js +38 -0
- package/dist/tui/tools/shared.js +97 -0
- package/dist/tui/tools/write-renderer.js +15 -0
- package/dist/tui/transcript-model.js +441 -0
- package/dist/tui/ui-preferences.js +79 -0
- package/dist/tui/usage-view.js +77 -0
- package/dist/tui/vim-mode.js +99 -0
- package/dist/update/check.js +15 -0
- package/dist/update/npm.js +51 -0
- package/dist/update/run.js +129 -0
- package/dist/version.js +2 -0
- package/dist/workspace/checkpoint-store.js +210 -0
- package/dist/workspace/checkpoint.js +413 -0
- package/dist/workspace/restore.js +232 -0
- package/package.json +63 -5
- package/vendor/context-engine/package.json +11 -0
- package/vendor/context-engine/src/compaction-planner.mjs +57 -0
- package/vendor/context-engine/src/contracts.mjs +48 -0
- package/vendor/context-engine/src/engine.mjs +445 -0
- package/vendor/context-engine/src/node/context-export.mjs +164 -0
- package/vendor/context-engine/src/node/legacy-import.mjs +92 -0
- package/vendor/context-engine/src/node/migrations/001-context.sql +126 -0
- package/vendor/context-engine/src/node/sqlite-store.mjs +85 -0
- package/vendor/context-engine/src/node/sqlite-worker.mjs +559 -0
- package/vendor/context-engine/src/profiles.mjs +10 -0
- package/vendor/context-engine/src/retrieval.mjs +104 -0
- package/vendor/context-engine/src/summary.mjs +97 -0
- package/vendor/context-engine/src/usage.mjs +34 -0
- package/vendor/harness-ui/package.json +38 -0
- package/vendor/harness-ui/src/acp-agent.mjs +350 -0
- package/vendor/harness-ui/src/agent-service.mjs +2188 -0
- package/vendor/harness-ui/src/agui-events.mjs +452 -0
- package/vendor/harness-ui/src/ambiente-solo-server.mjs +121 -0
- package/vendor/harness-ui/src/artifact-store.mjs +39 -0
- package/vendor/harness-ui/src/assistenza.mjs +123 -0
- package/vendor/harness-ui/src/automation-scheduler.mjs +69 -0
- package/vendor/harness-ui/src/automation-store.mjs +145 -0
- package/vendor/harness-ui/src/browser-annota.mjs +639 -0
- package/vendor/harness-ui/src/browser-frame.mjs +211 -0
- package/vendor/harness-ui/src/browser-proxy-universale.mjs +519 -0
- package/vendor/harness-ui/src/browser-proxy.mjs +87 -0
- package/vendor/harness-ui/src/browser-sessione-viva.mjs +329 -0
- package/vendor/harness-ui/src/browser-stream.mjs +445 -0
- package/vendor/harness-ui/src/browser-vivo.mjs +694 -0
- package/vendor/harness-ui/src/chat-image-attachments.mjs +72 -0
- package/vendor/harness-ui/src/config.mjs +697 -0
- package/vendor/harness-ui/src/contesto-del-progetto.mjs +385 -0
- package/vendor/harness-ui/src/context-asset-adapter.mjs +72 -0
- package/vendor/harness-ui/src/context-desktop-service.mjs +253 -0
- package/vendor/harness-ui/src/context-embedding-runtime.mjs +252 -0
- package/vendor/harness-ui/src/context-inference-scheduler.mjs +81 -0
- package/vendor/harness-ui/src/context-native-compaction.mjs +75 -0
- package/vendor/harness-ui/src/context-provider-adapter.mjs +141 -0
- package/vendor/harness-ui/src/context-runtime.mjs +118 -0
- package/vendor/harness-ui/src/context-token-counters.mjs +184 -0
- package/vendor/harness-ui/src/context-tool-catalog.mjs +86 -0
- package/vendor/harness-ui/src/context-tool-output.mjs +72 -0
- package/vendor/harness-ui/src/costo-elenco.mjs +252 -0
- package/vendor/harness-ui/src/custom-task.mjs +171 -0
- package/vendor/harness-ui/src/doctor.mjs +142 -0
- package/vendor/harness-ui/src/document-filename.mjs +97 -0
- package/vendor/harness-ui/src/document-generator.mjs +493 -0
- package/vendor/harness-ui/src/document-report.mjs +331 -0
- package/vendor/harness-ui/src/duckduckgo-search.mjs +155 -0
- package/vendor/harness-ui/src/elenco-profondo.mjs +337 -0
- package/vendor/harness-ui/src/favicon-proxy.mjs +113 -0
- package/vendor/harness-ui/src/forge-contract.mjs +221 -0
- package/vendor/harness-ui/src/frequent-dirs.mjs +134 -0
- package/vendor/harness-ui/src/generated-image-store.mjs +147 -0
- package/vendor/harness-ui/src/generation-idle.mjs +332 -0
- package/vendor/harness-ui/src/gguf-header.mjs +207 -0
- package/vendor/harness-ui/src/git-service.mjs +626 -0
- package/vendor/harness-ui/src/gitignore-elenco.mjs +604 -0
- package/vendor/harness-ui/src/harness-receipt-keypair.mjs +207 -0
- package/vendor/harness-ui/src/hf-direct-transfer.mjs +170 -0
- package/vendor/harness-ui/src/hf-hub-client.mjs +106 -0
- package/vendor/harness-ui/src/hf-image-proxy.mjs +105 -0
- package/vendor/harness-ui/src/hf-model-transfer.mjs +245 -0
- package/vendor/harness-ui/src/hook-registry.mjs +186 -0
- package/vendor/harness-ui/src/http-app.mjs +6259 -0
- package/vendor/harness-ui/src/http-lifecycle.mjs +132 -0
- package/vendor/harness-ui/src/id-archivio.mjs +27 -0
- package/vendor/harness-ui/src/image-generator.mjs +143 -0
- package/vendor/harness-ui/src/istruzioni-di-progetto.mjs +234 -0
- package/vendor/harness-ui/src/kernel/dist/kernelPerIlBanco.js +518 -0
- package/vendor/harness-ui/src/kernel/talosHarness.mjs +10437 -0
- package/vendor/harness-ui/src/library-policy-store.mjs +175 -0
- package/vendor/harness-ui/src/library-store.mjs +652 -0
- package/vendor/harness-ui/src/llama-server-supervisor.mjs +629 -0
- package/vendor/harness-ui/src/local-model-store.mjs +339 -0
- package/vendor/harness-ui/src/local-runtime-contract.mjs +66 -0
- package/vendor/harness-ui/src/local-runtime-events.mjs +44 -0
- package/vendor/harness-ui/src/local-runtime-llama-server.mjs +244 -0
- package/vendor/harness-ui/src/local-runtime-probe.mjs +401 -0
- package/vendor/harness-ui/src/machine-capacity.mjs +66 -0
- package/vendor/harness-ui/src/mappa-cartelle.mjs +491 -0
- package/vendor/harness-ui/src/mcp-client.mjs +98 -0
- package/vendor/harness-ui/src/mcp-registry.mjs +157 -0
- package/vendor/harness-ui/src/mcp-session.mjs +177 -0
- package/vendor/harness-ui/src/memory-store.mjs +227 -0
- package/vendor/harness-ui/src/model-catalog-models-dev.mjs +276 -0
- package/vendor/harness-ui/src/model-catalog.mjs +129 -0
- package/vendor/harness-ui/src/model-destination.mjs +189 -0
- package/vendor/harness-ui/src/modifica-ancorata.mjs +177 -0
- package/vendor/harness-ui/src/native-provider-adapter.mjs +205 -0
- package/vendor/harness-ui/src/notes-store.mjs +250 -0
- package/vendor/harness-ui/src/openai-compatible-runtime.mjs +428 -0
- package/vendor/harness-ui/src/openrouter-oauth.mjs +339 -0
- package/vendor/harness-ui/src/path-policy.mjs +442 -0
- package/vendor/harness-ui/src/plugin-registry.mjs +780 -0
- package/vendor/harness-ui/src/plugin-session.mjs +180 -0
- package/vendor/harness-ui/src/process-policy.mjs +345 -0
- package/vendor/harness-ui/src/prompt-enhancer-provider.mjs +94 -0
- package/vendor/harness-ui/src/provider-auth-cloud.mjs +95 -0
- package/vendor/harness-ui/src/provider-credential-store.mjs +541 -0
- package/vendor/harness-ui/src/provider-probe.mjs +582 -0
- package/vendor/harness-ui/src/provider-registry.mjs +1633 -0
- package/vendor/harness-ui/src/pty-terminal.mjs +312 -0
- package/vendor/harness-ui/src/public-problem.mjs +109 -0
- package/vendor/harness-ui/src/research/card.mjs +235 -0
- package/vendor/harness-ui/src/research/citations.mjs +142 -0
- package/vendor/harness-ui/src/research/collector.mjs +275 -0
- package/vendor/harness-ui/src/research/deposito-a-pezzi.mjs +139 -0
- package/vendor/harness-ui/src/research/dossier.mjs +114 -0
- package/vendor/harness-ui/src/research/esportazioni.mjs +560 -0
- package/vendor/harness-ui/src/research/fetch-cache.mjs +465 -0
- package/vendor/harness-ui/src/research/fidelity.mjs +122 -0
- package/vendor/harness-ui/src/research/independence.mjs +159 -0
- package/vendor/harness-ui/src/research/ledger.mjs +166 -0
- package/vendor/harness-ui/src/research/markdown-server.mjs +565 -0
- package/vendor/harness-ui/src/research/narration.mjs +181 -0
- package/vendor/harness-ui/src/research/open-cards.mjs +131 -0
- package/vendor/harness-ui/src/research/opposing.mjs +305 -0
- package/vendor/harness-ui/src/research/outline.mjs +111 -0
- package/vendor/harness-ui/src/research/page-budget.mjs +209 -0
- package/vendor/harness-ui/src/research/pdf.mjs +291 -0
- package/vendor/harness-ui/src/research/plan.mjs +301 -0
- package/vendor/harness-ui/src/research/raccolta-viva.mjs +452 -0
- package/vendor/harness-ui/src/research/recheck-document.mjs +69 -0
- package/vendor/harness-ui/src/research/recheck-history.mjs +192 -0
- package/vendor/harness-ui/src/research/recheck.mjs +194 -0
- package/vendor/harness-ui/src/research/report.mjs +203 -0
- package/vendor/harness-ui/src/research/run.mjs +527 -0
- package/vendor/harness-ui/src/research/synthesis.mjs +318 -0
- package/vendor/harness-ui/src/research/verification.mjs +572 -0
- package/vendor/harness-ui/src/research-orchestrator.mjs +2679 -0
- package/vendor/harness-ui/src/research-store.mjs +1133 -0
- package/vendor/harness-ui/src/runtime-build-manifest.mjs +26 -0
- package/vendor/harness-ui/src/runtime-contract.mjs +59 -0
- package/vendor/harness-ui/src/runtime-owner-adapter.mjs +1348 -0
- package/vendor/harness-ui/src/runtime-owner-contract.mjs +32 -0
- package/vendor/harness-ui/src/scheda-di-lavoro.mjs +249 -0
- package/vendor/harness-ui/src/search-source-store.mjs +172 -0
- package/vendor/harness-ui/src/session-registry.mjs +6095 -0
- package/vendor/harness-ui/src/session-store.mjs +220 -0
- package/vendor/harness-ui/src/sessione-pronta.mjs +73 -0
- package/vendor/harness-ui/src/setup-stato.mjs +31 -0
- package/vendor/harness-ui/src/sezioni-istruzioni.mjs +204 -0
- package/vendor/harness-ui/src/skill-registry.mjs +120 -0
- package/vendor/harness-ui/src/sse-replay-coalescente.mjs +0 -0
- package/vendor/harness-ui/src/static-files.mjs +96 -0
- package/vendor/harness-ui/src/stream-partition.mjs +123 -0
- package/vendor/harness-ui/src/subagent-orchestrator.mjs +453 -0
- package/vendor/harness-ui/src/task-catalog.mjs +65 -0
- package/vendor/harness-ui/src/tasks-store.mjs +220 -0
- package/vendor/harness-ui/src/terminal-registry.mjs +312 -0
- package/vendor/harness-ui/src/terminal-ws.mjs +170 -0
- package/vendor/harness-ui/src/tool-forge-store.mjs +299 -0
- package/vendor/harness-ui/src/tool-schema-normalize.mjs +100 -0
- package/vendor/harness-ui/src/usage-cache.mjs +315 -0
- package/vendor/harness-ui/src/workspace-browser.mjs +213 -0
- package/vendor/harness-ui/src/workspace-context.mjs +124 -0
- package/vendor/harness-ui/src/workspace-disk.mjs +62 -0
- package/vendor/harness-ui/src/workspace-files.mjs +589 -0
- package/vendor/harness-ui/src/workspace-info.mjs +189 -0
- package/vendor/harness-ui/src/workspace-launch-store.mjs +150 -0
- package/vendor/harness-ui/src/workspace-tree.mjs +67 -0
- package/vendor/harness-ui/src/workspace-watcher.mjs +161 -0
- package/vendor/manifest.json +170 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** Shared lifecycle primitives for HTTP requests, SSE streams and shutdown. */
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* ⛔⛔⛔ P0 · punto 7 (16/09/2026) — QUI C'ERA UNA MINA: `createRequestLifecycle`, RIMOSSA.
|
|
5
|
+
*
|
|
6
|
+
* Era una funzione che abortiva una richiesta dopo `timeoutMs = 30_000`, e **non la usava
|
|
7
|
+
* nessuno**: misurato con `grep -rn createRequestLifecycle harness-ui --include=*.mjs` — due sole
|
|
8
|
+
* occorrenze, la sua definizione e il suo test. Zero chiamanti in tre mesi.
|
|
9
|
+
*
|
|
10
|
+
* ⛔ Perché toglierla invece di lasciarla lì: era pronta per essere cablata dal primo che avesse
|
|
11
|
+
* cercato «lifecycle» per la rotta `/events`. E cablarla avrebbe ucciso OGNI sessione SSE dopo
|
|
12
|
+
* trenta secondi — cioè avrebbe rimesso, in un colpo solo, il tetto di durata che questo punto
|
|
13
|
+
* sta togliendo, dalla parte del server invece che da quella del fornitore. Un attrezzo che fa
|
|
14
|
+
* la cosa sbagliata e aspetta paziente è peggio di un attrezzo mancante: il secondo lo scrivi
|
|
15
|
+
* guardando il problema, il primo lo adotti perché c'era già.
|
|
16
|
+
* ⭐ Ciò che serviva davvero — chiudere quando il client se ne va — `createSseSession` lo fa già,
|
|
17
|
+
* qui sotto, senza nessun orologio: ascolta `close` sulla risposta e l'abort del segnale.
|
|
18
|
+
* ⛔ Il cancello che impedisce di rimetterla sta in `tests/timeout-generazione-p0.test.mjs`
|
|
19
|
+
* (P0-D-13), non nella memoria di qualcuno.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ⛔⛔ P0 · punto 7 (16/09/2026) — I TEMPI DEL SERVER, DICHIARATI INVECE CHE SUBITI.
|
|
24
|
+
*
|
|
25
|
+
* `server.mjs` faceva `createServer(app)` e `listen()` senza toccare nessuno dei quattro tempi:
|
|
26
|
+
* quelli attivi erano quelli che capitavano. Misurati su **Node v24.18.0** (script su file, non
|
|
27
|
+
* `node -e`), il 16/09/2026:
|
|
28
|
+
* server.timeout = 0 · headersTimeout = 60000 · keepAliveTimeout = 5000 · requestTimeout = 300000
|
|
29
|
+
* Sono i default di OGGI, e cambiano fra le versioni di Node — `server.timeout` valeva 120 s fino a
|
|
30
|
+
* Node 13. Una rotta SSE che deve reggere un ragionamento lungo non può dipendere da quale Node è
|
|
31
|
+
* installato sul computer di chi apre l'app.
|
|
32
|
+
*
|
|
33
|
+
* I quattro valori, e perché:
|
|
34
|
+
* · `timeout: 0` — nessun guardiano di inattività sul SOCKET. È il valore che rende possibile una
|
|
35
|
+
* risposta SSE lunga: qualunque numero qui ucciderebbe la chat mentre il modello pensa. Il
|
|
36
|
+
* canale non resta comunque muto, perché il battito scrive ogni 15 s.
|
|
37
|
+
* · `keepAliveTimeout: 72_000` — quanto si tiene aperta una connessione INUTILIZZATA fra due
|
|
38
|
+
* richieste. Il default di 5 s fa ricostruire il socket di continuo a un'interfaccia che
|
|
39
|
+
* chiacchiera; 72 s sono poco meno di cinque battiti, cioè comodamente oltre il ritmo con cui
|
|
40
|
+
* l'app parla col server.
|
|
41
|
+
* · `headersTimeout: 75_000` — **deve restare maggiore di `keepAliveTimeout`**: se fosse minore,
|
|
42
|
+
* Node può chiudere un socket riusato mentre la richiesta successiva è per strada, e il browser
|
|
43
|
+
* vede un `ECONNRESET` senza colpevole. Tre secondi di margine sopra il keep-alive.
|
|
44
|
+
* · `requestTimeout: 300_000` — tempo per ricevere la RICHIESTA completa (intestazioni + corpo),
|
|
45
|
+
* non la risposta: non tocca lo streaming in uscita, e a zero aprirebbe uno slowloris. Resta il
|
|
46
|
+
* valore di Node, ma scritto, così nessuno deve andare a cercarlo.
|
|
47
|
+
*
|
|
48
|
+
* ⛔ Stanno QUI e non dentro `server.mjs` perché la prova che il battito regge oltre il vecchio
|
|
49
|
+
* muro (`P0-D-12`, e la misura lunga in `tests/aiuto/`) deve usare gli STESSI numeri del
|
|
50
|
+
* prodotto: due copie divergerebbero, e la prova continuerebbe a essere verde su numeri che
|
|
51
|
+
* nessuno usa più.
|
|
52
|
+
*/
|
|
53
|
+
export const TEMPI_SERVER_HTTP = Object.freeze({
|
|
54
|
+
timeout: 0,
|
|
55
|
+
keepAliveTimeout: 72_000,
|
|
56
|
+
headersTimeout: 75_000,
|
|
57
|
+
requestTimeout: 300_000,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Applica i tempi dichiarati sopra a un server HTTP, e li restituisce come letti DAL server —
|
|
62
|
+
* non come li abbiamo chiesti.
|
|
63
|
+
*
|
|
64
|
+
* ⛔ Il ritorno è la lettura vera (`server.timeout`, …) perché un assegnamento che non attecchisse
|
|
65
|
+
* (una versione di Node che rifiuta un valore, un server finto) lascerebbe il chiamante convinto
|
|
66
|
+
* di aver deciso qualcosa. «L'ho scritto» e «è così» sono due misure diverse.
|
|
67
|
+
*/
|
|
68
|
+
export function applicaTempiDelServer(server, tempi = TEMPI_SERVER_HTTP) {
|
|
69
|
+
if (!server) return null;
|
|
70
|
+
for (const [nome, valore] of Object.entries(tempi)) server[nome] = valore;
|
|
71
|
+
return Object.freeze(Object.fromEntries(Object.keys(tempi).map((nome) => [nome, server[nome]])));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function createSseSession({ response, heartbeatMs = 15_000, signal = null, lastEventId = 0, headers = {}, setIntervalFn = setInterval, clearIntervalFn = clearInterval } = {}) {
|
|
75
|
+
let closed = false;
|
|
76
|
+
let heartbeat = null;
|
|
77
|
+
let onResponseClose;
|
|
78
|
+
let onAbort;
|
|
79
|
+
const send = (event) => {
|
|
80
|
+
if (closed || response?.writableEnded || response?.destroyed) return false;
|
|
81
|
+
if (typeof event?._sequenza === 'number' && event._sequenza <= lastEventId) return false;
|
|
82
|
+
if (typeof event?._sequenza === 'number') response.write(`id: ${event._sequenza}\n`);
|
|
83
|
+
response.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
const close = () => {
|
|
87
|
+
if (closed) return;
|
|
88
|
+
closed = true;
|
|
89
|
+
if (heartbeat !== null) clearIntervalFn(heartbeat);
|
|
90
|
+
response?.removeListener?.('close', onResponseClose);
|
|
91
|
+
signal?.removeEventListener?.('abort', onAbort);
|
|
92
|
+
};
|
|
93
|
+
const start = () => {
|
|
94
|
+
if (closed) return;
|
|
95
|
+
response.writeHead(200, {
|
|
96
|
+
...headers,
|
|
97
|
+
'Content-Type': 'text/event-stream; charset=utf-8',
|
|
98
|
+
'Cache-Control': 'no-store',
|
|
99
|
+
Connection: 'keep-alive',
|
|
100
|
+
});
|
|
101
|
+
response.socket?.setNoDelay?.(true);
|
|
102
|
+
response.write(':ok\n\n');
|
|
103
|
+
onResponseClose = close;
|
|
104
|
+
response.once?.('close', onResponseClose);
|
|
105
|
+
onAbort = close;
|
|
106
|
+
signal?.addEventListener?.('abort', onAbort, { once: true });
|
|
107
|
+
heartbeat = setIntervalFn(() => {
|
|
108
|
+
if (closed || response.writableEnded || response.destroyed) { close(); return; }
|
|
109
|
+
response.write(':battito\n\n');
|
|
110
|
+
}, heartbeatMs);
|
|
111
|
+
heartbeat?.unref?.();
|
|
112
|
+
};
|
|
113
|
+
return Object.freeze({ start, send, close, get closed() { return closed; } });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function closeRuntimeResources(reason, { resources = [], logger = null } = {}) {
|
|
117
|
+
const pending = [];
|
|
118
|
+
for (const resource of resources) {
|
|
119
|
+
try {
|
|
120
|
+
const result = typeof resource?.abort === 'function' ? resource.abort(reason)
|
|
121
|
+
: typeof resource?.stop === 'function' ? resource.stop(reason)
|
|
122
|
+
: typeof resource?.close === 'function' ? resource.close(reason)
|
|
123
|
+
: typeof resource?.destroy === 'function' ? resource.destroy(reason) : undefined;
|
|
124
|
+
if (result && typeof result.then === 'function') pending.push(Promise.resolve(result).catch((error) => {
|
|
125
|
+
try { logger?.warn?.(`[runtime-close] ${error?.code || 'cleanup-failed'}`); } catch { /* cleanup best effort */ }
|
|
126
|
+
}));
|
|
127
|
+
} catch (error) {
|
|
128
|
+
try { logger?.warn?.(`[runtime-close] ${error?.code || 'cleanup-failed'}`); } catch { /* cleanup best effort */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await Promise.all(pending);
|
|
132
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ⛔⛔⛔ 14/09/2026 — L'ID DI UN ARCHIVIO (Note/Attività/Memoria) ARRIVA DA FUORI, e finiva dritto nel percorso di un file.
|
|
3
|
+
*
|
|
4
|
+
* Trovato dalla review ingegneristica del 13/09 (F01) e CONFERMATO dal vivo su un banco a porta effimera: una
|
|
5
|
+
* `DELETE /api/v1/sessions/<id>/notes/..%5C<nome>` rispondeva 200 e cancellava un file FUORI da `.notes-store/`. La causa:
|
|
6
|
+
* `http-app.mjs` fa `decodeURIComponent` dell'id e lo passa a `percorsoDi(cartella, id) = join(cartella, id + '.json')`,
|
|
7
|
+
* senza nessun controllo — e lo stesso id arriva ANCHE dagli attrezzi del modello (`agent-service.mjs`, `argomenti?.id`),
|
|
8
|
+
* quindi la porta era aperta pure a un prompt-injection, non solo a una richiesta HTTP sul loopback.
|
|
9
|
+
*
|
|
10
|
+
* La Libreria aveva già chiuso lo stesso buco il 10/09 (`library-store.mjs#idVoceLibreriaValido`), con QUESTA identica
|
|
11
|
+
* grammatica. Le tre restanti no. La difesa sta nel magazzino, in un posto solo, così vale per tutti e due i chiamanti
|
|
12
|
+
* (la persona via HTTP e il modello via attrezzo) e non c'è una seconda copia che un giorno diverge.
|
|
13
|
+
*
|
|
14
|
+
* ⛔ Un id fuori grammatica NON è un'eccezione: `percorsoDi` torna `null` e chi lo chiama si comporta come per un id
|
|
15
|
+
* assente — «questa voce non c'è». È vero (un id con una barra o `..` dentro non può nominare nessuna voce reale),
|
|
16
|
+
* tiene l'idempotenza delle `elimina*` (un id già assente non è un errore) e non arriva in faccia al modello come un
|
|
17
|
+
* guasto nuovo per una chiamata che oggi si chiude con un «non trovato». È la stessa scelta della Libreria.
|
|
18
|
+
*
|
|
19
|
+
* ⛔ La grammatica è quella della Libreria, non quella (più stretta, coi nomi riservati di Windows) della patch della
|
|
20
|
+
* review: gli id di questi archivi li genera SEMPRE `randomUUID()` (solo esadecimali e trattini), il primo carattere
|
|
21
|
+
* dev'essere alfanumerico — quindi `..`, `a/b`, `a\b`, un id vuoto e ogni separatore o `..` sono già respinti, cioè
|
|
22
|
+
* TUTTO ciò che fa uscire dalla cartella. Restare sulla stessa grammatica della Libreria è la regola di casa: una
|
|
23
|
+
* difesa sola, non due che divergono.
|
|
24
|
+
*/
|
|
25
|
+
export function idArchivioValido(id) {
|
|
26
|
+
return typeof id === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(id);
|
|
27
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* image-generator.mjs — FASE H del piano `elegant-spinning-dongarra.md`
|
|
3
|
+
* (29/8): chiama la Image API di OpenRouter (lanciata fine giugno
|
|
4
|
+
* 2026), la STESSA chiave già in `config.mjs` — zero provider nuovo,
|
|
5
|
+
* il one-up dichiarato: niente attrezzo immagine verso un provider
|
|
6
|
+
* separato dal modello di chat.
|
|
7
|
+
*
|
|
8
|
+
* Codici d'errore `TALOS_IMAGE_*` — stessa famiglia di nomi già in
|
|
9
|
+
* uso nell'omologo mobile (`mobile/src/lib/images/imageTools.ts`:
|
|
10
|
+
* `TALOS_IMAGE_NO_PROVIDER`/`TALOS_IMAGE_SOURCE_NOT_FOUND`/...), non
|
|
11
|
+
* inventata qui — un modello che ha già visto quei codici (in
|
|
12
|
+
* un'altra conversazione, o nella sua formazione) li riconosce.
|
|
13
|
+
*
|
|
14
|
+
* Due percorsi CONFERMATI (29/8, tre fonti incluso il riferimento API
|
|
15
|
+
* ufficiale, non presunti — vedi il piano madre, FASE H):
|
|
16
|
+
* - **DEDICATO** (`nativo:false`): `POST /api/v1/images`, corpo
|
|
17
|
+
* `{model, prompt, aspect_ratio}`, risposta
|
|
18
|
+
* `data[0].{b64_json, media_type}`. Il catalogo di questi modelli è
|
|
19
|
+
* `GET /api/v1/images/models` — un endpoint SEPARATO da
|
|
20
|
+
* `/api/v1/models` (verificato dal vivo il 29/8: un modello dedicato
|
|
21
|
+
* reale, `bytedance-seed/seedream-4.5`, NON compare affatto in
|
|
22
|
+
* `/api/v1/models` — solo i modelli NATIVI ci compaiono, con
|
|
23
|
+
* `architecture.output_modalities` che include `"image"`).
|
|
24
|
+
* - **NATIVO** (`nativo:true`): `POST /api/v1/chat/completions`
|
|
25
|
+
* STANDARD (lo stesso endpoint che il kernel già chiama per il
|
|
26
|
+
* testo) con `modalities:["text","image"]` nel corpo — l'immagine
|
|
27
|
+
* arriva in `choices[0].message.images[0].image_url.url`, una data
|
|
28
|
+
* URL (`data:<mediaType>;base64,<dati>`), MAI in `data[].b64_json`
|
|
29
|
+
* (quello è solo del percorso dedicato).
|
|
30
|
+
*
|
|
31
|
+
* ⛔ Quale dei due usare NON si indovina qui — è una proprietà del
|
|
32
|
+
* MODELLO scelto (`config.mjs`, `immagine.nativo`), decisa a monte:
|
|
33
|
+
* questo modulo non chiama nessun catalogo per scoprirlo (un giro di
|
|
34
|
+
* rete in più per ogni immagine, per un fatto che il chiamante può
|
|
35
|
+
* già sapere). I due cataloghi VERI esistono e sono stati verificati
|
|
36
|
+
* dal vivo il 29/8 — restano per un futuro picker owner-facing
|
|
37
|
+
* (mirror del picker `modello` già in uso), non per questa decisione
|
|
38
|
+
* a runtime.
|
|
39
|
+
*
|
|
40
|
+
* ⛔ Aspect ratio sul percorso NATIVO: non inviato in questa fetta —
|
|
41
|
+
* un campo che alcune fonti secondarie citano (`image_config.aspect_ratio`)
|
|
42
|
+
* non è confermato nel riferimento API ufficiale consultato il 29/8;
|
|
43
|
+
* mai un campo non verificato nella chiamata vera. Il prompt può
|
|
44
|
+
* comunque descrivere l'inquadratura in linguaggio naturale.
|
|
45
|
+
*/
|
|
46
|
+
import { talosSafeFileStem } from './document-filename.mjs';
|
|
47
|
+
|
|
48
|
+
/*
|
|
49
|
+
* ⛔⛔⛔ DICHIARATO E NON CURATO — CLI-REQ-05, punto 3 del terzo giro (17/09/2026).
|
|
50
|
+
*
|
|
51
|
+
* Questi due indirizzi sono FISSI e la chiamata parte da una `fetch` nuda: in una sessione il cui
|
|
52
|
+
* fornitore NON è OpenRouter — una DeepSeek, una locale — il prompt dell'immagine va comunque a
|
|
53
|
+
* openrouter.ai. È la stessa classe del difetto curato per la compattazione e per il giudice
|
|
54
|
+
* (`session-registry.mjs`, `compatta()` e `creaChiediAlModelloGiudice`), che adesso passano dalla
|
|
55
|
+
* destinazione multi-fornitore dell'host col modello DELLA SESSIONE.
|
|
56
|
+
*
|
|
57
|
+
* ⇒ NON si cura qui, ed è una scelta dichiarata, non una dimenticanza: la generazione di immagini
|
|
58
|
+
* è nata su OpenRouter come unico fornitore («zero provider nuovo»), e spostarla vuol dire
|
|
59
|
+
* decidere quale fornitore genera le immagini per una sessione che non è di OpenRouter — una
|
|
60
|
+
* decisione dell'owner, non di questa riga.
|
|
61
|
+
* ⛔ Finché resta così: chi sceglie una sessione locale perché niente esca, e poi chiede
|
|
62
|
+
* un'immagine, manda comunque il suo prompt a openrouter.ai. Sta scritto qui perché la prossima
|
|
63
|
+
* persona che legge questo file lo sappia senza doverlo scoprire.
|
|
64
|
+
*/
|
|
65
|
+
const OPENROUTER_IMAGES_URL = 'https://openrouter.ai/api/v1/images';
|
|
66
|
+
const OPENROUTER_CHAT_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
|
67
|
+
|
|
68
|
+
/** ⭐ Le tre forme che il tool del kernel offre — stesso vocabolario di `mobile/src/lib/images/imageTools.ts` (`SHAPES`), non un terzo nuovo. */
|
|
69
|
+
export const ASPECT_RATIO_PER_FORMA = Object.freeze({ square: '1:1', portrait: '9:16', landscape: '16:9' });
|
|
70
|
+
|
|
71
|
+
function decodeDataUrl(dataUrl) {
|
|
72
|
+
const match = /^data:([^;]+);base64,(.+)$/s.exec(dataUrl ?? '');
|
|
73
|
+
if (!match) throw new Error('TALOS_IMAGE_BAD_RESPONSE: OpenRouter has returned an image in an unexpected format (not a base64 data URL).');
|
|
74
|
+
return { mediaType: match[1], bytes: Buffer.from(match[2], 'base64') };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function leggiCorpoJson(risposta, dove) {
|
|
78
|
+
try {
|
|
79
|
+
return await risposta.json();
|
|
80
|
+
} catch {
|
|
81
|
+
throw new Error(`TALOS_IMAGE_BAD_RESPONSE: ${dove} returned a response that is not valid JSON.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function chiamaImmaginiDedicato({ prompt, shape, modello, chiave }, fetchFn) {
|
|
86
|
+
let risposta;
|
|
87
|
+
try {
|
|
88
|
+
risposta = await fetchFn(OPENROUTER_IMAGES_URL, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { Authorization: `Bearer ${chiave}`, 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ model: modello, prompt, aspect_ratio: ASPECT_RATIO_PER_FORMA[shape] ?? '1:1' }),
|
|
92
|
+
});
|
|
93
|
+
} catch (errore) {
|
|
94
|
+
throw new Error(`TALOS_IMAGE_UNREACHABLE: the OpenRouter Image API could not be reached (${errore instanceof Error ? errore.message : String(errore)}).`);
|
|
95
|
+
}
|
|
96
|
+
const corpo = await leggiCorpoJson(risposta, 'the OpenRouter Image API');
|
|
97
|
+
if (!risposta.ok) {
|
|
98
|
+
throw new Error(`TALOS_IMAGE_UPSTREAM_ERROR: the OpenRouter Image API responded ${risposta.status} (${corpo?.error?.message ?? 'no detail'}).`);
|
|
99
|
+
}
|
|
100
|
+
const voce = corpo?.data?.[0];
|
|
101
|
+
if (!voce?.b64_json) throw new Error('TALOS_IMAGE_BAD_RESPONSE: the OpenRouter Image API response has no image data.');
|
|
102
|
+
return { mediaType: typeof voce.media_type === 'string' && voce.media_type ? voce.media_type : 'image/png', bytes: Buffer.from(voce.b64_json, 'base64') };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function chiamaImmaginiNativo({ prompt, modello, chiave }, fetchFn) {
|
|
106
|
+
let risposta;
|
|
107
|
+
try {
|
|
108
|
+
risposta = await fetchFn(OPENROUTER_CHAT_URL, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: { Authorization: `Bearer ${chiave}`, 'Content-Type': 'application/json' },
|
|
111
|
+
body: JSON.stringify({ model: modello, messages: [{ role: 'user', content: prompt }], modalities: ['text', 'image'] }),
|
|
112
|
+
});
|
|
113
|
+
} catch (errore) {
|
|
114
|
+
throw new Error(`TALOS_IMAGE_UNREACHABLE: OpenRouter could not be reached for native image generation (${errore instanceof Error ? errore.message : String(errore)}).`);
|
|
115
|
+
}
|
|
116
|
+
const corpo = await leggiCorpoJson(risposta, 'OpenRouter (native image generation)');
|
|
117
|
+
if (!risposta.ok) {
|
|
118
|
+
throw new Error(`TALOS_IMAGE_UPSTREAM_ERROR: OpenRouter responded ${risposta.status} for native image generation (${corpo?.error?.message ?? 'no detail'}).`);
|
|
119
|
+
}
|
|
120
|
+
const dataUrl = corpo?.choices?.[0]?.message?.images?.[0]?.image_url?.url;
|
|
121
|
+
if (!dataUrl) {
|
|
122
|
+
throw new Error('TALOS_IMAGE_NO_IMAGE: the model did not return an image — it may have answered in text only. Try a more explicit drawing prompt, or a dedicated image model.');
|
|
123
|
+
}
|
|
124
|
+
return decodeDataUrl(dataUrl);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* `spec.shape` è ignorato sul percorso nativo (vedi la doc sopra sul
|
|
129
|
+
* campo non confermato) — non un bug, un limite dichiarato.
|
|
130
|
+
* @param {{prompt:string, shape?:'square'|'portrait'|'landscape', modello:string, nativo:boolean, chiave:string}} spec
|
|
131
|
+
* @param {{fetchFn?: typeof fetch}} [deps]
|
|
132
|
+
* @returns {Promise<{mediaType:string, bytes:Buffer, fileStem:string}>}
|
|
133
|
+
*/
|
|
134
|
+
export async function generaImmagineOpenRouter(spec, deps = {}) {
|
|
135
|
+
const fetchFn = deps.fetchFn ?? fetch;
|
|
136
|
+
const { prompt, shape, modello, nativo, chiave } = spec;
|
|
137
|
+
const { mediaType, bytes } = nativo
|
|
138
|
+
? await chiamaImmaginiNativo({ prompt, modello, chiave }, fetchFn)
|
|
139
|
+
: await chiamaImmaginiDedicato({ prompt, shape, modello, chiave }, fetchFn);
|
|
140
|
+
// ⭐ un nome file leggibile dal prompt, mai "immagine-<uuid>" — stesso principio di talosSafeFileStem già in uso per document-generator.mjs.
|
|
141
|
+
const fileStem = talosSafeFileStem(prompt, 80, 'immagine');
|
|
142
|
+
return { mediaType, bytes, fileStem };
|
|
143
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BC-48 A (12/09/2026): indice della radice e dei file oltre 60 righe; sezioni sempre
|
|
3
|
+
* intere, commenti HTML esterni al codice rimossi. Il compositore omette file interi
|
|
4
|
+
* con avviso. Il resoconto storico sotto e `tagliaIstruzioni` descrivono il contratto
|
|
5
|
+
* precedente; la funzione esportata resta per compatibilità, fuori dal preambolo.
|
|
6
|
+
*
|
|
7
|
+
* istruzioni-di-progetto.mjs — BLOCCO 3 del preambolo: `AGENTS.md` / `CLAUDE.md`, con un tetto
|
|
8
|
+
* di byte DICHIARATO e un troncamento che dice dove riprendere.
|
|
9
|
+
*
|
|
10
|
+
* ── IL DIFETTO CHE CHIUDE ────────────────────────────────────────────────────────────────────
|
|
11
|
+
*
|
|
12
|
+
* Misurato l'11/09/2026 (`.claude/RAPPORTO-PREAMBOLO-CONCORRENTI-2026-09-11.md` §6.3): in
|
|
13
|
+
* `harness-ui/src/` i nomi `AGENTS.md` e `CLAUDE.md` comparivano **solo** in `path-policy.mjs:35`
|
|
14
|
+
* (permessi) e dentro i commenti. ⇒ **Al modello arrivavano 17.000 token di inventario dei file e
|
|
15
|
+
* ZERO byte di istruzioni del progetto** — l'esatto contrario di tutti e quattro i concorrenti
|
|
16
|
+
* letti nel codice lo stesso giorno:
|
|
17
|
+
* · Codex .......... `agents_md.rs`, catena dalla radice del progetto al cwd, tetto
|
|
18
|
+
* `AGENTS_MD_MAX_BYTES = 32 KiB`, e il commento dice *«Larger files are
|
|
19
|
+
* silently truncated»*;
|
|
20
|
+
* · Claude Code .... budget `max(40.000, 5% della finestra)`, taglio per file a 200 righe /
|
|
21
|
+
* 25.000 byte con una sezione `WARNING` che lo dichiara;
|
|
22
|
+
* · Hermes v0.21 ... `CONTEXT_FILE_MAX_CHARS = 20_000`, head 70% / tail 20%, e il marcatore dice
|
|
23
|
+
* **dove riprendere** (`read_file` sul percorso vero);
|
|
24
|
+
* · DeepSeek `dsh` . budget in byte **obbligatorio** in configurazione (65.536 di serie), e la
|
|
25
|
+
* politica: *«it drops whole broader files before truncating the most-specific
|
|
26
|
+
* file, and emits a visible `Workspace instruction budget …` notice naming the
|
|
27
|
+
* omitted and truncated paths»*.
|
|
28
|
+
*
|
|
29
|
+
* RICERCA WEB, letta l'11/09/2026 PRIMA di scrivere:
|
|
30
|
+
* 1. Specifica aperta AGENTS.md <https://agents.md/> — verbatim: *«Place another AGENTS.md inside
|
|
31
|
+
* each package. Agents automatically read the nearest file in the directory tree, so the
|
|
32
|
+
* closest one takes precedence.»* e *«used by over 60k open-source projects»*. ⛔ La specifica
|
|
33
|
+
* **non dichiara nessun tetto di byte**: il tetto è una scelta di ogni harness, e ogni harness
|
|
34
|
+
* che abbiamo letto ne ha uno. Chi non ce l'ha, prima o poi manda in contesto un file da 200 KB.
|
|
35
|
+
* 2. Claude Platform Docs, «Prompt caching» (letto 11/09/2026,
|
|
36
|
+
* <https://platform.claude.com/docs/en/docs/build-with-claude/prompt-caching>) — *«Cache hits
|
|
37
|
+
* require 100% identical prompt segments»*: il testo prodotto qui sta nel PREFISSO, quindi
|
|
38
|
+
* dev'essere una funzione pura del contenuto dei file, senza orari, senza percorsi assoluti,
|
|
39
|
+
* senza niente che cambi da una chiamata all'altra.
|
|
40
|
+
*
|
|
41
|
+
* ── LE QUATTRO DECISIONI ─────────────────────────────────────────────────────────────────────
|
|
42
|
+
*
|
|
43
|
+
* (1) LA CATENA, dalla radice del progetto al cwd, il più SPECIFICO per ULTIMO. È la forma di
|
|
44
|
+
* Codex (`agents_md.rs`: *«Collect every AGENTS.md found from the project root down to the
|
|
45
|
+
* current working directory (inclusive) and concatenate their contents in that order»*) e la
|
|
46
|
+
* precedenza che la specifica dichiara. L'ultimo letto vince perché è l'ultimo letto: non
|
|
47
|
+
* serve nessuna regola in più, e il testo lo dice al modello a parole.
|
|
48
|
+
* ⛔ Non si risale MAI oltre la radice del progetto: un `AGENTS.md` nella home dell'utente
|
|
49
|
+
* riguarda un altro lavoro, e trascinarlo dentro sarebbe rifare il difetto del `.gitignore`
|
|
50
|
+
* che ereditava regole da fuori (curato in `gitignore-elenco.mjs`).
|
|
51
|
+
*
|
|
52
|
+
* (2) UN SOLO FILE PER CARTELLA, nell'ordine `AGENTS.md` poi `CLAUDE.md` — i candidati di serie di
|
|
53
|
+
* `dsh` (`packages/context/agent-instructions/src/config.ts:11-15`). Prenderli entrambi
|
|
54
|
+
* significherebbe pagare due volte lo stesso contenuto in un repo che li tiene allineati, ed è
|
|
55
|
+
* esattamente il caso di questo repo.
|
|
56
|
+
*
|
|
57
|
+
* (3) IL TETTO SI DICHIARA, e il troncamento è ONESTO. Due livelli, come `dsh`:
|
|
58
|
+
* · se la catena sfora, si tolgono INTERI i file più GENERICI (i più lontani dal cwd) prima
|
|
59
|
+
* di tagliare il più specifico — quello vicino al lavoro è quello che serve;
|
|
60
|
+
* · il file che resta e sfora si taglia head 70% / tail 20% (Hermes), e il marcatore dice
|
|
61
|
+
* quanti caratteri mancano e **con quale attrezzo** leggere il file intero.
|
|
62
|
+
* ⛔ Un taglio silenzioso è il difetto che questo progetto ha già pagato sulla propria
|
|
63
|
+
* memoria: `MEMORY.md` perde righe oltre 200/25.000 senza un avviso in sessione, e nessuno
|
|
64
|
+
* se n'era accorto per settimane. Qui il taglio è una RIGA DI TESTO, non un effetto.
|
|
65
|
+
*
|
|
66
|
+
* (4) SE NON C'È NIENTE, IL BLOCCO NON ESISTE. Nessuna intestazione vuota, nessun «(nessuna
|
|
67
|
+
* istruzione trovata)»: zero byte. È il principio che Hermes scrive in chiaro
|
|
68
|
+
* (`system_prompt.py:713-716`): *«Emits a single line; emits NOTHING when the environment is
|
|
69
|
+
* clean (no token cost)»*.
|
|
70
|
+
*/
|
|
71
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
72
|
+
import { dirname, join, parse, relative, sep } from 'node:path';
|
|
73
|
+
import { rendiFileIstruzioni } from './sezioni-istruzioni.mjs';
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Il tetto, in BYTE, dell'intero blocco 3. 24.000 sta fra Hermes (20.000 caratteri) e Codex
|
|
77
|
+
* (32 KiB), e sotto `dsh` (65.536). Perché non di più: a 3,5 byte/token (la taratura misurata il
|
|
78
|
+
* 09/09 su `z-ai/glm-5.3-flash`, `costo-elenco.mjs`) sono ~6.900 token, cioè già il 40% di tutto
|
|
79
|
+
* ciò che l'elenco dei file costava su `AVM/mobile` prima di questa cura (17.149 token misurati).
|
|
80
|
+
* Un tetto più alto renderebbe il preambolo nuovo non più leggero di quello vecchio nel caso
|
|
81
|
+
* peggiore, e il caso peggiore è l'unico che conta.
|
|
82
|
+
* ⛔ È un ARGOMENTO, non una legge: chi ha un progetto con istruzioni enormi lo alza sapendo cosa
|
|
83
|
+
* paga. Ciò che non è negoziabile è che sia DICHIARATO nel testo quando morde.
|
|
84
|
+
*/
|
|
85
|
+
export const TETTO_BYTE_PREDEFINITO = 24_000;
|
|
86
|
+
|
|
87
|
+
/** I candidati, nell'ordine in cui si guardano dentro una cartella. Vedi decisione (2). */
|
|
88
|
+
export const NOMI_CANDIDATI = Object.freeze(['AGENTS.md', 'CLAUDE.md']);
|
|
89
|
+
|
|
90
|
+
/** Quanti livelli si risale al massimo cercando la radice del progetto. */
|
|
91
|
+
export const RISALITA_MASSIMA = 40;
|
|
92
|
+
|
|
93
|
+
/** Quote del taglio per file: testa 70%, coda 20%, il 10% di mezzo è il marcatore e il margine. */
|
|
94
|
+
export const QUOTA_TESTA = 0.7;
|
|
95
|
+
export const QUOTA_CODA = 0.2;
|
|
96
|
+
|
|
97
|
+
const normalizza = (p) => String(p).replace(/\\/g, '/');
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* La radice del progetto: la prima cartella, risalendo, che ha un `.git`.
|
|
101
|
+
* ⛔ `.git` si cerca con `stat` e non si distingue DIRECTORY da FILE: in un worktree è un file, ed
|
|
102
|
+
* è il caso di oggi su questo repo. È la stessa scelta di ripgrep (`Ignore::add_parents`, che
|
|
103
|
+
* usa `exists()`) già documentata in `gitignore-elenco.mjs`.
|
|
104
|
+
*/
|
|
105
|
+
export async function trovaRadiceProgetto(cartella, { fs, risalitaMassima = RISALITA_MASSIMA } = {}) {
|
|
106
|
+
const disco = { stat, ...(fs ?? {}) };
|
|
107
|
+
let corrente = String(cartella ?? '');
|
|
108
|
+
for (let i = 0; i < risalitaMassima && corrente; i += 1) {
|
|
109
|
+
try {
|
|
110
|
+
await disco.stat(join(corrente, '.git'));
|
|
111
|
+
return corrente;
|
|
112
|
+
} catch { /* non è la radice: si sale */ }
|
|
113
|
+
const sopra = dirname(corrente);
|
|
114
|
+
if (!sopra || sopra === corrente || sopra === parse(corrente).root) break;
|
|
115
|
+
corrente = sopra;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Trova i file di istruzioni della catena, dalla radice del progetto al cwd.
|
|
122
|
+
* @returns {Promise<{percorso:string, etichetta:string, lettura:string, contenuto:string, byte:number}[]>}
|
|
123
|
+
* in ordine dal più GENERICO al più SPECIFICO (l'ultimo vince).
|
|
124
|
+
*/
|
|
125
|
+
export async function trovaIstruzioniDiProgetto(cartella, { fs, nomi = NOMI_CANDIDATI, risalitaMassima = RISALITA_MASSIMA } = {}) {
|
|
126
|
+
const disco = { readFile, stat, ...(fs ?? {}) };
|
|
127
|
+
const cwd = String(cartella ?? '');
|
|
128
|
+
if (!cwd) return [];
|
|
129
|
+
const radice = await trovaRadiceProgetto(cwd, { fs, risalitaMassima }) ?? cwd;
|
|
130
|
+
|
|
131
|
+
/* Le cartelle da guardare: dalla radice del progetto giù fino al cwd, incluse entrambe. */
|
|
132
|
+
const tratto = relative(radice, cwd);
|
|
133
|
+
const pezzi = tratto && tratto !== '.' ? tratto.split(sep).filter(Boolean) : [];
|
|
134
|
+
const cartelle = [radice];
|
|
135
|
+
let corrente = radice;
|
|
136
|
+
for (const pezzo of pezzi) {
|
|
137
|
+
corrente = join(corrente, pezzo);
|
|
138
|
+
cartelle.push(corrente);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const trovati = [];
|
|
142
|
+
for (const dove of cartelle) {
|
|
143
|
+
for (const nome of nomi) {
|
|
144
|
+
let contenuto;
|
|
145
|
+
try {
|
|
146
|
+
contenuto = await disco.readFile(join(dove, nome), 'utf8');
|
|
147
|
+
} catch { continue; } // non c'è, o non si legge: si passa al candidato dopo
|
|
148
|
+
if (typeof contenuto !== 'string' || contenuto.trim() === '') continue;
|
|
149
|
+
/*
|
|
150
|
+
* ⛔ L'ETICHETTA È RELATIVA ALLA RADICE DEL PROGETTO, MAI ASSOLUTA. Un percorso assoluto
|
|
151
|
+
* contiene il nome della persona e questo testo esce dalla macchina dentro un prompt —
|
|
152
|
+
* lezione [[cancello-4-non-guardava-tutto-mobile]]. E in più: un percorso assoluto
|
|
153
|
+
* cambia da macchina a macchina, quindi romperebbe il prefisso della cache fra due
|
|
154
|
+
* installazioni dello stesso progetto.
|
|
155
|
+
*/
|
|
156
|
+
const etichetta = normalizza(relative(radice, join(dove, nome))) || nome;
|
|
157
|
+
trovati.push({ percorso: join(dove, nome), etichetta, lettura: normalizza(relative(cwd, join(dove, nome))), contenuto, byte: Buffer.byteLength(contenuto, 'utf8') });
|
|
158
|
+
break; // decisione (2): un solo file per cartella
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return trovati;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Taglia un contenuto al tetto dato, testa 70% / coda 20%, e DICE dove riprendere.
|
|
166
|
+
* ⛔ Si taglia sui BYTE (non sui caratteri) perché il tetto è in byte e un carattere accentato ne
|
|
167
|
+
* vale 2: contare caratteri farebbe sforare il tetto proprio sui testi italiani, cioè i nostri.
|
|
168
|
+
* E si ritaglia all'a-capo più vicino, per non spezzare una riga a metà parola.
|
|
169
|
+
*/
|
|
170
|
+
export function tagliaIstruzioni(contenuto, tetto, etichetta) {
|
|
171
|
+
const byte = Buffer.byteLength(contenuto, 'utf8');
|
|
172
|
+
if (byte <= tetto) return { testo: contenuto, tagliato: false, byteTenuti: byte, byteTotali: byte };
|
|
173
|
+
|
|
174
|
+
const buf = Buffer.from(contenuto, 'utf8');
|
|
175
|
+
const byteTesta = Math.max(0, Math.floor(tetto * QUOTA_TESTA));
|
|
176
|
+
const byteCoda = Math.max(0, Math.floor(tetto * QUOTA_CODA));
|
|
177
|
+
/* `toString` su un taglio a metà di una sequenza UTF-8 produce U+FFFD: si accetta e si ripulisce
|
|
178
|
+
all'a-capo, invece di fingere che il taglio sui byte sia sempre su un confine di carattere. */
|
|
179
|
+
let testa = buf.subarray(0, byteTesta).toString('utf8');
|
|
180
|
+
const aCapoTesta = testa.lastIndexOf('\n');
|
|
181
|
+
if (aCapoTesta > byteTesta * 0.5) testa = testa.slice(0, aCapoTesta);
|
|
182
|
+
let coda = buf.subarray(buf.length - byteCoda).toString('utf8');
|
|
183
|
+
const aCapoCoda = coda.indexOf('\n');
|
|
184
|
+
if (aCapoCoda >= 0 && aCapoCoda < byteCoda * 0.5) coda = coda.slice(aCapoCoda + 1);
|
|
185
|
+
|
|
186
|
+
const mancanti = byte - Buffer.byteLength(testa, 'utf8') - Buffer.byteLength(coda, 'utf8');
|
|
187
|
+
const marcatore = `\n\n[...«${etichetta}» è stato TAGLIATO: di ${byte} byte ne vedi i primi ~${Buffer.byteLength(testa, 'utf8')} e gli ultimi ~${Buffer.byteLength(coda, 'utf8')}; ne mancano ${mancanti} nel mezzo. Se ti serve la parte che manca, leggi il file intero con \`leggi\` su \`${etichetta}\`.]\n\n`;
|
|
188
|
+
const testo = testa + marcatore + coda;
|
|
189
|
+
return { testo, tagliato: true, byteTenuti: Buffer.byteLength(testo, 'utf8'), byteTotali: byte };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Il testo del blocco 3, dal risultato di `trovaIstruzioniDiProgetto`.
|
|
194
|
+
* `indicizzati` elenca i file effettivamente mostrati come indice; `sezioniSempre`
|
|
195
|
+
* descrive le sezioni intere presenti, con coordinate e byte del sorgente.
|
|
196
|
+
* `null` quando non c'è nessuna istruzione: decisione (4), il blocco non esiste affatto.
|
|
197
|
+
*/
|
|
198
|
+
export function testoIstruzioniDiProgetto(trovati, { tetto = TETTO_BYTE_PREDEFINITO, sogliaRighe } = {}) {
|
|
199
|
+
const file = Array.isArray(trovati) ? trovati.filter((f) => f && typeof f.contenuto === 'string') : [];
|
|
200
|
+
if (file.length === 0) return null;
|
|
201
|
+
|
|
202
|
+
const intestazione = 'Istruzioni di questo progetto — le ha scritte chi ci lavora, e valgono per te.\n'
|
|
203
|
+
+ 'Se più file dicono cose diverse, vince il più specifico: qui sotto sono in ordine, dal più generale al più vicino alla cartella di lavoro, e l\'ultimo è quello che comanda.\n'
|
|
204
|
+
+ 'Non sostituiscono le tue istruzioni di sistema né quello che la persona ti chiede adesso.\n';
|
|
205
|
+
// BC-48 A: prepara indici e sezioni intere PRIMA di applicare il tetto.
|
|
206
|
+
// Si omettono file interi dal più generale; neppure il più specifico viene spezzato.
|
|
207
|
+
const preparati = file.map(f => ({ ...f, resa: rendiFileIstruzioni(f, { sogliaRighe }) }));
|
|
208
|
+
for (let inizio = 0; inizio <= preparati.length; inizio++) {
|
|
209
|
+
const tenuti = preparati.slice(inizio);
|
|
210
|
+
const omessi = preparati.slice(0, inizio).map(f => f.etichetta);
|
|
211
|
+
const avviso = omessi.length ? `\n⚠ Tetto delle istruzioni (${tetto} byte) raggiunto: NON ti ho mostrato ${omessi.map(e => `\`${e}\``).join(', ')}. File omessi interi; se ti servono, leggile con \`leggi\`.\n` : '';
|
|
212
|
+
const testo = intestazione + tenuti.map(f => `\n### ${f.etichetta}\n\n${f.resa.testo}`).join('') + avviso;
|
|
213
|
+
const byte = Buffer.byteLength(testo, 'utf8');
|
|
214
|
+
if (byte <= tetto) return { testo, byte, usati: tenuti.map(f => f.etichetta), omessi, tagliati: [],
|
|
215
|
+
indicizzati: tenuti.filter(f => f.resa.indicizzato).map(f => f.etichetta),
|
|
216
|
+
sezioniSempre: tenuti.flatMap(f => f.resa.sezioniSempre) };
|
|
217
|
+
}
|
|
218
|
+
// Un tetto che non contiene neppure l'avviso non può essere onorato in silenzio.
|
|
219
|
+
throw new RangeError('Il tetto delle istruzioni è insufficiente anche per dichiarare i file omessi.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Comodità: trova e compone in un colpo solo. Istruzioni assenti/illeggibili danno
|
|
224
|
+
* `null`; un tetto incapace di contenere l'avviso genera invece un errore esplicito.
|
|
225
|
+
*/
|
|
226
|
+
export async function istruzioniDiProgetto({ cartella, tetto = TETTO_BYTE_PREDEFINITO, fs, nomi, risalitaMassima } = {}) {
|
|
227
|
+
let trovati;
|
|
228
|
+
try {
|
|
229
|
+
trovati = await trovaIstruzioniDiProgetto(cartella, { fs, nomi, risalitaMassima });
|
|
230
|
+
} catch {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
return testoIstruzioniDiProgetto(trovati, { tetto });
|
|
234
|
+
}
|