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,82 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* R5 CONTRACT (owner: lane R; stub written by the coordinator so both lanes compile). Owner decisions Q-R5-2/4/5: the one
|
|
3
|
+
* status row shows `model · ↑in ↓out · ctx used/window % · ~$cost · cmp N`. usageSnapshot turns the turn's reported usage,
|
|
4
|
+
* the context status and the model profile into those facts. Unknown stays unknown (`null`, never 0 or a guess, gate E1);
|
|
5
|
+
* `estimated` marks a figure that was estimated rather than reported; `costUsd` only with a named price source
|
|
6
|
+
* (`costSource`), `local` engines have no price. Lane S only CALLS it and draws the row (new `tui/status-bar.ts`); lane R owns
|
|
7
|
+
* the body and may not change the signature.
|
|
8
|
+
*
|
|
9
|
+
* R5a lane R — the body, built on the shapes MEASURED through the real composition (r5a-r-usage-shapes.test.ts, 2026-09-24):
|
|
10
|
+
* - `usage.updated` carries the RUN's running total, `{prompt_tokens, completion_tokens, cached_tokens, turns}`
|
|
11
|
+
* (talosHarness.mjs `conto`, agent-service.mjs `eventoPerUsage`); it restarts at every run. That is `↑in ↓out`.
|
|
12
|
+
* - each model request's own usage arrives as the kernel's `CUSTOM consumo-fornitore` (runtime-owner-adapter.mjs:978, with the
|
|
13
|
+
* provider, the model, the provider's `usage` and, when the provider declares one, its cost); the event adapter folds the
|
|
14
|
+
* last one into the usage value as `lastRequest`, and the run's declared costs as `declaredCostUsd`.
|
|
15
|
+
* - the context in use is what the last request carried plus what it answered (Pi `calculateContextTokens`: `totalTokens`, or
|
|
16
|
+
* input + output, packages/coding-agent/docs/compaction.md, 2026-09-24): reported, not estimated. When the Context Engine
|
|
17
|
+
* has measured the prepared request (`contextStatus.measurement`), that measurement is used instead, `estimated` when its
|
|
18
|
+
* method is the heuristic.
|
|
19
|
+
* - `percent` exists only with a window (a profile, or the engine's measurement). No window, no percent: the screen says `ctx ?`.
|
|
20
|
+
* - cost: a cost the provider declared for this run's requests is used as reported (`costSource: 'provider-reported'`);
|
|
21
|
+
* otherwise the list price of a NAMED source (`modelPrice`: the registry's dated rows, a live model list, models.dev with
|
|
22
|
+
* its date), cached input at the cache-read price when one is listed — an ESTIMATE, which the screen marks `~` (Q-R5-5);
|
|
23
|
+
* otherwise null. A local engine (`isLocalProvider`) has no price: `local:true`, cost null.
|
|
24
|
+
* - `compactions`: the count lane S keeps from `context.compacted` events, passed as `state.compactions`; 0 when none.
|
|
25
|
+
* Optional additions to the frozen shape (stated in the hand-back): the input's `model?` and `state.compactions?`, and the
|
|
26
|
+
* output's `contextTokens?` (the used figure the row prints before the window).
|
|
27
|
+
*/
|
|
28
|
+
import { isLocalProvider, modelPrice, splitModelId } from "./model-profile.js";
|
|
29
|
+
function objectValue(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : null; }
|
|
30
|
+
function count(value) { return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null; }
|
|
31
|
+
function first(...values) { for (const value of values) {
|
|
32
|
+
const n = count(value);
|
|
33
|
+
if (n !== null)
|
|
34
|
+
return n;
|
|
35
|
+
} return null; }
|
|
36
|
+
export function usageSnapshot(input) {
|
|
37
|
+
const usage = objectValue(input?.state?.usage);
|
|
38
|
+
const last = objectValue(usage?.lastRequest);
|
|
39
|
+
const inputTokens = first(usage?.prompt_tokens, usage?.promptTokens, usage?.input_tokens, usage?.inputTokens);
|
|
40
|
+
const outputTokens = first(usage?.completion_tokens, usage?.completionTokens, usage?.output_tokens, usage?.outputTokens);
|
|
41
|
+
const cachedTokens = first(usage?.cached_tokens, usage?.cachedTokens, usage?.prompt_tokens_details?.cached_tokens);
|
|
42
|
+
const turns = first(usage?.turns, usage?.giri);
|
|
43
|
+
const status = objectValue(input?.contextStatus);
|
|
44
|
+
const measurement = status?.available === true ? objectValue(status.measurement) : null;
|
|
45
|
+
const measured = measurement && measurement.stale !== true && count(measurement.inputTokens) !== null ? measurement : null;
|
|
46
|
+
let contextTokens = null;
|
|
47
|
+
let estimated = false;
|
|
48
|
+
if (measured) {
|
|
49
|
+
contextTokens = count(measured.inputTokens);
|
|
50
|
+
estimated = measured.exact !== true || measured.method === 'heuristic';
|
|
51
|
+
}
|
|
52
|
+
else if (last && count(last.inputTokens) !== null) {
|
|
53
|
+
contextTokens = count(last.inputTokens) + (count(last.outputTokens) ?? 0);
|
|
54
|
+
}
|
|
55
|
+
else if (turns === 1 && inputTokens !== null) {
|
|
56
|
+
contextTokens = inputTokens + (outputTokens ?? 0);
|
|
57
|
+
}
|
|
58
|
+
const windowTokens = count(input?.profile?.windowTokens) ?? (measured ? count(measured.windowTokens) : null);
|
|
59
|
+
const percent = contextTokens !== null && windowTokens ? Math.round((contextTokens / windowTokens) * 1000) / 10 : null;
|
|
60
|
+
const named = typeof input?.model === 'string' && input.model ? splitModelId(input.model) : null;
|
|
61
|
+
const provider = typeof last?.provider === 'string' && last.provider ? last.provider : named?.provider ?? null;
|
|
62
|
+
const model = typeof last?.model === 'string' && last.model ? last.model : named?.model ?? null;
|
|
63
|
+
const local = isLocalProvider(provider);
|
|
64
|
+
let costUsd = null;
|
|
65
|
+
let costSource = null;
|
|
66
|
+
const declared = count(usage?.declaredCostUsd);
|
|
67
|
+
if (!local && declared !== null) {
|
|
68
|
+
costUsd = declared;
|
|
69
|
+
costSource = 'provider-reported';
|
|
70
|
+
}
|
|
71
|
+
else if (!local && provider && model && inputTokens !== null && outputTokens !== null) {
|
|
72
|
+
const price = modelPrice({ provider, model });
|
|
73
|
+
if (price) {
|
|
74
|
+
const cached = Math.min(cachedTokens ?? 0, inputTokens);
|
|
75
|
+
const cacheRate = price.cacheReadPerMTok ?? price.inputPerMTok;
|
|
76
|
+
costUsd = ((inputTokens - cached) * price.inputPerMTok + cached * cacheRate + outputTokens * price.outputPerMTok) / 1_000_000;
|
|
77
|
+
costSource = price.date ? `${price.source} ${price.date}` : price.source;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const compactions = Math.max(0, Math.round(count(input?.state?.compactions) ?? 0));
|
|
81
|
+
return { inputTokens, outputTokens, cachedTokens, windowTokens, percent, estimated, costUsd, costSource, local, compactions, contextTokens };
|
|
82
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { tokenizeCommand } from "./rule-parser.js";
|
|
2
|
+
const SAFE_EXACT = [['git', 'status'], ['git', 'diff'], ['git', 'log'], ['npm', 'test'], ['npm', 'run', 'test'], ['npm', 'run', 'lint'], ['npm', 'run', 'typecheck'], ['npx', 'tsc', '--noEmit'], ['rg'], ['grep'], ['ls'], ['dir']];
|
|
3
|
+
/*
|
|
4
|
+
* ⭐⭐⭐ R0 lane S — owner decision 2 (2026-09-23): TALOS `auto` on a host whose shell isolation is not verified ASKS
|
|
5
|
+
* the person for every command; the classifier never auto-allows one. It is Codex's rule ("If the command is flagged
|
|
6
|
+
* as dangerous or we have no sandbox protection, we should never allow it to run without approval",
|
|
7
|
+
* codex-rs/core/src/exec_policy.rs, 2026-09-01), chosen over Claude Code's "the classifier evaluates the underlying
|
|
8
|
+
* command" (gate §0.3 D1): SAFE_EXACT includes `npm test` and `npm run *`, which run the project's own code.
|
|
9
|
+
* ⛔ Edits and reads keep the mode's semantics (amendment 3, Claude Code rule D2): only Bash changes.
|
|
10
|
+
* ⛔ Explicit allow rules and session grants are the person's own answer and are decided BEFORE the classifier
|
|
11
|
+
* (permission-engine.ts); this rule does not touch them.
|
|
12
|
+
* ⛔ Fail-safe by construction: with no isolation source the classifier asks. Whoever builds it must pass the
|
|
13
|
+
* session's real isolation state for SAFE_EXACT to be auto-allowed on a verified host.
|
|
14
|
+
*/
|
|
15
|
+
async function shellIsVerified(source) {
|
|
16
|
+
if (!source)
|
|
17
|
+
return false;
|
|
18
|
+
try {
|
|
19
|
+
const state = await source();
|
|
20
|
+
return typeof state === 'object' && state !== null && state.verified === true;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function createLocalAutoClassifier(options = {}) { return async ({ action }) => { if (action.tool === 'Read')
|
|
27
|
+
return 'allow'; if (action.tool === 'Edit' || action.tool === 'Write')
|
|
28
|
+
return 'allow'; if (action.tool === 'Bash') {
|
|
29
|
+
const t = tokenizeCommand(action.command ?? '');
|
|
30
|
+
if (!t.length)
|
|
31
|
+
return 'ask';
|
|
32
|
+
for (const p of SAFE_EXACT) {
|
|
33
|
+
if (p.every((v, i) => t[i] === v))
|
|
34
|
+
return await shellIsVerified(options.shellIsolation) ? 'allow' : 'ask';
|
|
35
|
+
}
|
|
36
|
+
return 'ask';
|
|
37
|
+
} if (action.tool === 'TalosService' && /^(?:list|read|search|status)$/iu.test(action.operation ?? ''))
|
|
38
|
+
return 'allow'; return 'ask'; }; }
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ⭐⭐⭐ B1 slice 19 — THE KERNEL'S CREDENTIAL DENYLIST, COPIED INTO THE CLI AND PINNED TO THE KERNEL.
|
|
3
|
+
*
|
|
4
|
+
* ⛔⛔ NOT FOR ANYTHING THAT REACHES A COMMAND LINE. Round 1 of this slice used this rule to filter the
|
|
5
|
+
* environment the MXC SDK serialises as `--config-base64` on the `wxc-exec.exe` command line, and
|
|
6
|
+
* adversarial review measured 24 of 29 realistic secret-bearing names that carry no credential shape
|
|
7
|
+
* (DATABASE_URL, SENTRY_DSN, GH_PAT, MYSQL_PWD, HTTPS_PROXY with credentials, …) decoded out of it. A
|
|
8
|
+
* name-shape denylist cannot know what a VALUE holds. The probe now carries an allowlist
|
|
9
|
+
* (`PROBE_ENVIRONMENT_NAMES`, `execution-backends.ts`) and a brokered command carries no environment.
|
|
10
|
+
* Today this module has no production consumer; it stays, with its parity test, as the one CLI copy of
|
|
11
|
+
* the kernel's rule for a child whose environment never touches a command line.
|
|
12
|
+
*
|
|
13
|
+
* ⛔ THIS IS THE KERNEL'S RULE, COPIED, NOT A SECOND OPINION. `harness-ui/src/kernel/talosHarness.mjs`
|
|
14
|
+
* already scrubs every child it starts with `ambienteSenzaCredenziali()`: a denylist by SHAPE
|
|
15
|
+
* (a closed list covered one key in five) with its non-secrets declared one by one. The kernel does
|
|
16
|
+
* not export the shape or the exceptions, so they are copied letter for letter, and
|
|
17
|
+
* `test/security/credential-free-environment.test.ts` reads the kernel source and judges a table of
|
|
18
|
+
* names with the kernel's own `eUnaCredenziale`: a drift on either side turns it red.
|
|
19
|
+
* ⛔ A denylist and not an allowlist, for the kernel's reason: a typed `npm test` needs dozens of
|
|
20
|
+
* variables nobody can list in advance.
|
|
21
|
+
*/
|
|
22
|
+
/** Named explicitly by the kernel as well as matched by shape (`CHIAVI_CREDENZIALI_DA_NASCONDERE`). */
|
|
23
|
+
export const CREDENTIAL_NAMES_ALWAYS_HIDDEN = Object.freeze(['OPENROUTER_API_KEY']);
|
|
24
|
+
/** The kernel's `FORMA_DI_CREDENZIALE`, byte for byte. Deliberately wide: what is not a secret is listed below. */
|
|
25
|
+
export const CREDENTIAL_NAME_SHAPE = /TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|_KEY$|^KEY_|APIKEY|API_KEY|ACCESS_KEY|AUTH/i;
|
|
26
|
+
/**
|
|
27
|
+
* The kernel's `NON_SONO_SEGRETI`: names the shape matches by accident and that real commands need.
|
|
28
|
+
* `SSH_AUTH_SOCK` is the agent socket path (without it `git push` inside a command stops working),
|
|
29
|
+
* `GPG_AGENT_INFO` the same for signatures, `KEYBOARD_LAYOUT` and `AUTHORITY` match by chance.
|
|
30
|
+
*/
|
|
31
|
+
export const NOT_CREDENTIALS = new Set(['SSH_AUTH_SOCK', 'GPG_AGENT_INFO', 'KEYBOARD_LAYOUT', 'AUTHORITY']);
|
|
32
|
+
/** True when a variable of this name must not enter a child process. The same verdict as the kernel's `eUnaCredenziale`. */
|
|
33
|
+
export function isCredentialName(name) {
|
|
34
|
+
const key = String(name ?? '');
|
|
35
|
+
if (NOT_CREDENTIALS.has(key))
|
|
36
|
+
return false;
|
|
37
|
+
if (CREDENTIAL_NAMES_ALWAYS_HIDDEN.includes(key))
|
|
38
|
+
return true;
|
|
39
|
+
return CREDENTIAL_NAME_SHAPE.test(key);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A fresh object with every variable of `source` whose name is not a credential. Never the live
|
|
43
|
+
* `process.env`: a caller that later mutates the result cannot touch this process's environment, and
|
|
44
|
+
* an `undefined` value is left out rather than turned into the string "undefined".
|
|
45
|
+
*/
|
|
46
|
+
export function credentialFreeEnvironment(source = process.env) {
|
|
47
|
+
const clean = {};
|
|
48
|
+
for (const [name, value] of Object.entries(source)) {
|
|
49
|
+
if (value === undefined || isCredentialName(name))
|
|
50
|
+
continue;
|
|
51
|
+
clean[name] = value;
|
|
52
|
+
}
|
|
53
|
+
return clean;
|
|
54
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { commandOperands, expandHomePrefix, isReadOnlyShellCommand, isUnverifiableArgument, isWindowsStyleSwitch, parseRule, ruleProblem, tokenizeCommand } from "./rule-parser.js";
|
|
4
|
+
import { segmentShellCommand } from "./shell-segmentation.js";
|
|
5
|
+
// Every rule that can be read is compiled and every one that cannot is reported, in deny, ask, allow
|
|
6
|
+
// order. ⛔ Nothing here decides to go on without a rule: that decision belongs to the caller, and the
|
|
7
|
+
// engine's is to refuse — a skipped deny rule would silently allow what it denied.
|
|
8
|
+
export function compileRuleSet(input) {
|
|
9
|
+
const rules = { deny: [], ask: [], allow: [] };
|
|
10
|
+
const invalid = [];
|
|
11
|
+
const source = (input ?? {});
|
|
12
|
+
for (const effect of ['deny', 'ask', 'allow']) {
|
|
13
|
+
const list = source[effect];
|
|
14
|
+
if (!Array.isArray(list)) {
|
|
15
|
+
invalid.push({ effect, index: null, value: list, problem: 'is not a list of rules' });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
list.forEach((value, index) => { const problem = ruleProblem(value); if (problem !== null)
|
|
19
|
+
invalid.push({ effect, index, value, problem });
|
|
20
|
+
else
|
|
21
|
+
rules[effect].push(parseRule(value, effect)); });
|
|
22
|
+
}
|
|
23
|
+
return { rules, invalid };
|
|
24
|
+
}
|
|
25
|
+
export function compileRules(input) { const { rules, invalid } = compileRuleSet(input); const first = invalid[0]; if (first)
|
|
26
|
+
throw new Error(`PERMISSION_RULE_INVALID:${String(first.value)}`); return rules; }
|
|
27
|
+
function inside(root, candidate) { const rel = path.relative(path.resolve(root), path.resolve(candidate)); return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel)); }
|
|
28
|
+
function wildcard(text, pattern) { if (pattern === '*' || pattern === '**')
|
|
29
|
+
return true; const escaped = pattern.replace(/[.+^${}()|[\]\\]/gu, '\\$&').replace(/\*\*/gu, '.*').replace(/\*/gu, '[^\\s]*'); return new RegExp(`^${escaped}$`, 'u').test(text); }
|
|
30
|
+
// B1 slice 11. In a Bash pattern the verb is separated from its wildcard by a trailing `:*`, and
|
|
31
|
+
// only by that. The previous form split on the FIRST colon, which a Windows absolute path supplies
|
|
32
|
+
// itself: `Bash(cat C:\…\id_ed25519)` became the verb `cat C` plus the leftover `\…\id_ed25519`,
|
|
33
|
+
// so a rule naming an exact path matched nothing — measured on this host, in the allow direction
|
|
34
|
+
// AND in the deny direction — while `Bash(cat:*)` still read as "any argument". Anchoring the
|
|
35
|
+
// separator at the end also stops a colon inside a command from being eaten: `Bash(npm run
|
|
36
|
+
// test:unit)` now means that script, where before it meant the bare `npm run test`.
|
|
37
|
+
const BASH_VERB_WILDCARD = /:\*$/u;
|
|
38
|
+
// A pattern that names a command and not a path. These are the rules that must not be able to lift
|
|
39
|
+
// the location boundary: the person said which command, never which file.
|
|
40
|
+
export function patternNamesOnlyAVerb(pattern) { return pattern === '*' || pattern === '**' || BASH_VERB_WILDCARD.test(pattern); }
|
|
41
|
+
function matches(rule, action, projectRoot) {
|
|
42
|
+
if (rule.tool !== action.tool)
|
|
43
|
+
return false;
|
|
44
|
+
const p = rule.pattern;
|
|
45
|
+
if (action.tool === 'Bash') {
|
|
46
|
+
const tokens = tokenizeCommand(action.command ?? '');
|
|
47
|
+
if (!tokens.length)
|
|
48
|
+
return false;
|
|
49
|
+
if (p === '*')
|
|
50
|
+
return true;
|
|
51
|
+
const head = BASH_VERB_WILDCARD.test(p) ? p.slice(0, -2) : p;
|
|
52
|
+
const expected = tokenizeCommand(head);
|
|
53
|
+
if (expected.length === 0)
|
|
54
|
+
return false;
|
|
55
|
+
if (expected.some((v, i) => tokens[i] !== v))
|
|
56
|
+
return false;
|
|
57
|
+
if (BASH_VERB_WILDCARD.test(p))
|
|
58
|
+
return true;
|
|
59
|
+
return expected.length === tokens.length;
|
|
60
|
+
}
|
|
61
|
+
if (['Read', 'Write', 'Edit'].includes(action.tool)) {
|
|
62
|
+
if (!action.resource)
|
|
63
|
+
return false;
|
|
64
|
+
const candidate = path.isAbsolute(action.resource) ? path.resolve(action.resource) : path.resolve(projectRoot, action.resource);
|
|
65
|
+
if (!inside(projectRoot, candidate) && p.startsWith('.'))
|
|
66
|
+
return false;
|
|
67
|
+
if (p === '*')
|
|
68
|
+
return true;
|
|
69
|
+
if (p === './**')
|
|
70
|
+
return inside(projectRoot, candidate);
|
|
71
|
+
const rel = path.relative(projectRoot, candidate).split(path.sep).join('/');
|
|
72
|
+
return wildcard(rel, p.replace(/^\.\//u, ''));
|
|
73
|
+
}
|
|
74
|
+
if (action.tool === 'Mcp') {
|
|
75
|
+
const target = `${action.serverId ?? ''}:${action.operation ?? ''}`;
|
|
76
|
+
return wildcard(target, p);
|
|
77
|
+
}
|
|
78
|
+
if (action.tool === 'TalosService') {
|
|
79
|
+
return wildcard(action.operation ?? '', p) || wildcard(`${action.resource ?? ''}:${action.operation ?? ''}`, p);
|
|
80
|
+
}
|
|
81
|
+
return wildcard(action.operation ?? action.resource ?? '', p);
|
|
82
|
+
}
|
|
83
|
+
// B1 slice 21. The writing path asks this before persisting a rule: a rule that does not match the very
|
|
84
|
+
// request it was written for would be stored and do nothing, which is how a compound command's
|
|
85
|
+
// `Bash(<whole command>)` never took effect.
|
|
86
|
+
export function ruleMatchesAction(rule, action, projectRoot) { return matches(rule, action, projectRoot); }
|
|
87
|
+
// B1 slice 10. The read-only shell allowance is location-bounded: it survives only while every
|
|
88
|
+
// path-shaped argument of the segment resolves inside the project. This removes a silent
|
|
89
|
+
// auto-approval. It is not containment and it is not credential protection — it constrains the
|
|
90
|
+
// invocations TALOS's own tools produce, not a subprocess that opens files by itself, a path
|
|
91
|
+
// computed at runtime, or an interpreter handed code on its command line.
|
|
92
|
+
export const PATH_BOUNDARY_RULE = 'built-in:path-outside-project';
|
|
93
|
+
// Resolve the real path, expanding links, Windows 8.3 short names and the `\\?\` prefix. The
|
|
94
|
+
// JavaScript realpath expands neither (measured 2026-09-16: realpathSync('C:\\PROGRA~1') returns it
|
|
95
|
+
// unchanged and throws EISDIR on a `\\?\` path, while realpathSync.native returns 'C:\Program
|
|
96
|
+
// Files'). A path that does not exist yet is resolved through its longest existing ancestor, so a
|
|
97
|
+
// link in the middle of it is still followed, and anything that cannot be resolved at all throws.
|
|
98
|
+
function realPath(target) {
|
|
99
|
+
let current = path.resolve(target);
|
|
100
|
+
const tail = [];
|
|
101
|
+
for (let guard = 0; guard < 4096; guard += 1) {
|
|
102
|
+
try {
|
|
103
|
+
const real = fs.realpathSync.native(current);
|
|
104
|
+
return tail.length ? path.join(real, ...tail) : real;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const code = error.code;
|
|
108
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR')
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
const parent = path.dirname(current);
|
|
112
|
+
if (parent === current)
|
|
113
|
+
throw new Error('PATH_UNRESOLVABLE');
|
|
114
|
+
tail.unshift(path.basename(current));
|
|
115
|
+
current = parent;
|
|
116
|
+
}
|
|
117
|
+
throw new Error('PATH_UNRESOLVABLE');
|
|
118
|
+
}
|
|
119
|
+
// Fail closed, the way the shared kernel's own control-path check does: a path that cannot be
|
|
120
|
+
// resolved, a command that cannot be tokenized and a comparison that throws all ask, never allow.
|
|
121
|
+
function readOnlyCommandStaysInside(command, projectRoot) {
|
|
122
|
+
const operands = commandOperands(command);
|
|
123
|
+
if (!operands)
|
|
124
|
+
return { ok: false, reason: 'read-only shell command could not be reduced to verifiable arguments' };
|
|
125
|
+
// The root is canonicalized once. Without this, a project opened through a link or under a short
|
|
126
|
+
// 8.3 name is a spelling no argument can ever be under, and every in-project read prompts.
|
|
127
|
+
let realRoot;
|
|
128
|
+
try {
|
|
129
|
+
realRoot = realPath(projectRoot);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { ok: false, reason: 'project root could not be resolved' };
|
|
133
|
+
}
|
|
134
|
+
const lexicalRoot = path.resolve(projectRoot);
|
|
135
|
+
const outside = (operand, resolved) => ({ ok: false, reason: `read-only shell command would read outside the project: "${operand}" resolves to ${resolved}` });
|
|
136
|
+
for (const operand of operands) {
|
|
137
|
+
if (isUnverifiableArgument(operand))
|
|
138
|
+
return { ok: false, reason: `read-only shell command has an argument that cannot be statically verified: ${operand}` };
|
|
139
|
+
const lexical = path.resolve(projectRoot, expandHomePrefix(operand));
|
|
140
|
+
// An argument already outside the project lexically is answered without touching the
|
|
141
|
+
// filesystem. Only a path that looks inside needs the real one, because a link is the only way
|
|
142
|
+
// it can still escape — and this is what keeps an unreachable network path from stalling the
|
|
143
|
+
// decision for 21 seconds per operand while the OS waits on a host that will never answer.
|
|
144
|
+
if (!inside(lexicalRoot, lexical) && !inside(realRoot, lexical)) {
|
|
145
|
+
if (isWindowsStyleSwitch(operand) && !existsQuietly(lexical))
|
|
146
|
+
continue;
|
|
147
|
+
return outside(operand, lexical);
|
|
148
|
+
}
|
|
149
|
+
try {
|
|
150
|
+
const real = realPath(lexical);
|
|
151
|
+
if (!inside(realRoot, real))
|
|
152
|
+
return outside(operand, real);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return { ok: false, reason: `read-only shell command names a path that cannot be resolved: ${operand}` };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { ok: true };
|
|
159
|
+
}
|
|
160
|
+
// A Windows switch is only treated as a switch while it names nothing. `dir /s` must not prompt and
|
|
161
|
+
// must not be described as a path; `dir /Users` names a real directory and is judged as one.
|
|
162
|
+
function existsQuietly(target) { try {
|
|
163
|
+
return fs.existsSync(target);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return true;
|
|
167
|
+
} }
|
|
168
|
+
function hardPathDeny(action, projectRoot) { if (!['Read', 'Write', 'Edit'].includes(action.tool) || !action.resource)
|
|
169
|
+
return null; const candidate = path.isAbsolute(action.resource) ? path.resolve(action.resource) : path.resolve(projectRoot, action.resource); if (!inside(projectRoot, candidate) && action.tool !== 'Read')
|
|
170
|
+
return { effect: 'deny', reason: 'write path is outside the authorized project' }; return null; }
|
|
171
|
+
// An allow rule that names only a verb is downgraded to `ask` when the command reads outside the
|
|
172
|
+
// project. It is NOT a denial: the person can still say yes in the moment. A rule that names the
|
|
173
|
+
// path is obeyed, which is the whole distinction — "I meant this file" against "I meant this
|
|
174
|
+
// command". Only `allow` is narrowed here; `deny` and `ask` rules pass through untouched.
|
|
175
|
+
function verbOnlyAllowDowngrade(rule, action, projectRoot) {
|
|
176
|
+
if (rule.tool !== 'Bash' || action.tool !== 'Bash')
|
|
177
|
+
return null;
|
|
178
|
+
if (!patternNamesOnlyAVerb(rule.pattern))
|
|
179
|
+
return null;
|
|
180
|
+
if (!isReadOnlyShellCommand(action.command ?? ''))
|
|
181
|
+
return null;
|
|
182
|
+
const boundary = readOnlyCommandStaysInside(action.command ?? '', projectRoot);
|
|
183
|
+
if (boundary.ok)
|
|
184
|
+
return null;
|
|
185
|
+
return { effect: 'ask', rule: rule.raw, reason: `allow rule ${rule.raw} names a command and not a path, and ${boundary.reason}` };
|
|
186
|
+
}
|
|
187
|
+
function evaluateSingle(rules, action, projectRoot) {
|
|
188
|
+
const hard = hardPathDeny(action, projectRoot);
|
|
189
|
+
if (hard)
|
|
190
|
+
return hard;
|
|
191
|
+
for (const effect of ['deny', 'ask'])
|
|
192
|
+
for (const r of rules[effect])
|
|
193
|
+
if (matches(r, action, projectRoot))
|
|
194
|
+
return { effect, rule: r.raw, reason: `matched ${effect} rule` };
|
|
195
|
+
// ⛔ A downgrade is not a final answer. The natural shape of a configuration is a permissive base
|
|
196
|
+
// plus specific grants appended after it, so the rule that names the path is usually the LAST one
|
|
197
|
+
// — and if the first verb-only match ended the search, the very rule this slice tells a person to
|
|
198
|
+
// add would do nothing. The search continues; a rule that names the path still wins, and the
|
|
199
|
+
// first downgrade only speaks when no such rule exists. deny and ask keep first-match-wins.
|
|
200
|
+
let downgraded = null;
|
|
201
|
+
for (const r of rules.allow) {
|
|
202
|
+
if (!matches(r, action, projectRoot))
|
|
203
|
+
continue;
|
|
204
|
+
const downgrade = verbOnlyAllowDowngrade(r, action, projectRoot);
|
|
205
|
+
if (!downgrade)
|
|
206
|
+
return { effect: 'allow', rule: r.raw, reason: 'matched allow rule' };
|
|
207
|
+
downgraded ??= downgrade;
|
|
208
|
+
}
|
|
209
|
+
if (downgraded)
|
|
210
|
+
return downgraded;
|
|
211
|
+
if (action.tool === 'Read' && action.resource) {
|
|
212
|
+
const candidate = path.isAbsolute(action.resource) ? path.resolve(action.resource) : path.resolve(projectRoot, action.resource);
|
|
213
|
+
if (inside(projectRoot, candidate))
|
|
214
|
+
return { effect: 'allow', reason: 'read-only action inside project' };
|
|
215
|
+
}
|
|
216
|
+
if (action.tool === 'Bash' && isReadOnlyShellCommand(action.command ?? '')) {
|
|
217
|
+
const boundary = readOnlyCommandStaysInside(action.command ?? '', projectRoot);
|
|
218
|
+
if (boundary.ok)
|
|
219
|
+
return { effect: 'allow', reason: 'documented read-only shell command' };
|
|
220
|
+
return { effect: 'ask', rule: PATH_BOUNDARY_RULE, reason: boundary.reason };
|
|
221
|
+
}
|
|
222
|
+
return { effect: 'ask', reason: 'no rule matched' };
|
|
223
|
+
}
|
|
224
|
+
export function evaluateRules(rules, action, { projectRoot }) {
|
|
225
|
+
if (action.tool !== 'Bash')
|
|
226
|
+
return evaluateSingle(rules, action, projectRoot);
|
|
227
|
+
let segments;
|
|
228
|
+
try {
|
|
229
|
+
segments = segmentShellCommand(action.command ?? '');
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return { effect: 'deny', reason: 'unsupported shell composition' };
|
|
233
|
+
}
|
|
234
|
+
const decisions = segments.map(segment => evaluateSingle(rules, { ...action, command: segment.command }, projectRoot));
|
|
235
|
+
for (const effect of ['deny', 'ask', 'allow']) {
|
|
236
|
+
const index = decisions.findIndex(decision => decision.effect === effect);
|
|
237
|
+
if (index >= 0) {
|
|
238
|
+
const decision = decisions[index];
|
|
239
|
+
return { ...decision, reason: segments.length > 1 ? `segment ${index + 1}: ${decision.reason}` : decision.reason };
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { effect: 'deny', reason: 'unsupported shell composition' };
|
|
243
|
+
}
|