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,791 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { buildAgentTree } from "../runtime/agent-tree.js";
|
|
3
|
+
import { createPermissionEngine } from "../security/permission-engine.js";
|
|
4
|
+
import { createApprovalCoordinator, decisionFromChoice, decisionFromSource } from "./approval.js";
|
|
5
|
+
import { createTuiEventAdapter, denialFromEngine, isolationOf } from "./event-adapter.js";
|
|
6
|
+
import { createDescendantApprovals } from "./descendant-approvals.js";
|
|
7
|
+
import { simulatePermission } from "../security/permission-explanation.js";
|
|
8
|
+
import { KERNEL_RUN_TEXT } from "../i18n/kernel-map.js";
|
|
9
|
+
import { permissionActionFromApproval } from "../security/from-approval.js";
|
|
10
|
+
import { persistPermissionRule } from "../security/persist.js";
|
|
11
|
+
import { EXECUTION_TEXT } from "../runtime/brokered-executor.js";
|
|
12
|
+
import { secretValuesFromEnvironment } from "../diagnostics/redact.js";
|
|
13
|
+
import { normalizeSessions } from "./overlays/session-picker.js";
|
|
14
|
+
import { explainApproval } from "./overlays/approval-dialog.js";
|
|
15
|
+
import { resolveProjectReferences } from "./project-references.js";
|
|
16
|
+
import { createQueueStore } from "./queue-store.js";
|
|
17
|
+
import { createCheckpointStore } from "../workspace/checkpoint-store.js";
|
|
18
|
+
import { developmentLog, developmentLogError, developmentTextEvidence } from "../diagnostics/development-log.js";
|
|
19
|
+
export class TuiNotReadyError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
provider;
|
|
22
|
+
constructor(code, provider, message) { super(message); this.name = 'TuiNotReadyError'; this.code = code; this.provider = provider; }
|
|
23
|
+
}
|
|
24
|
+
const storedEntry = (entry) => entry.kind !== 'slash';
|
|
25
|
+
export function createTuiSessionController({ runtime, projectRoot, model, mode, rules, paths, autoClassifier, readiness, onEvent, onApproval, onError = () => { }, onQueueChange, onQueueRestored, onQueuedDispatch, onQueuedSlash, onSteerState }) {
|
|
26
|
+
let currentMode = mode;
|
|
27
|
+
let currentModel = model;
|
|
28
|
+
const engine = createPermissionEngine({ projectRoot, rules, mode, ...(autoClassifier ? { classifier: autoClassifier } : {}), ...(paths ? { persistRule: async (effect, action) => persistPermissionRule({ paths, projectRoot, effect, action }) } : {}) });
|
|
29
|
+
/* R5a wiring (Q-R5-17, gate finding 15): the adapter learns who refused a tool BEFORE the kernel hears the "no" — the engine
|
|
30
|
+
alone (a deny rule, the policy, the mode: `denialFromEngine`) or the person (`resolveApproval`) — so the row never words
|
|
31
|
+
a refusal nobody asked for as the person's. */
|
|
32
|
+
const denialTargets = new Map();
|
|
33
|
+
const coordinator = createApprovalCoordinator({ engine, answer: (sid, rid, approved) => runtime.answerApproval(sid, rid, approved), interactive: true, mode: () => currentMode,
|
|
34
|
+
beforeDeny: (rid, decision) => { const target = denialTargets.get(rid); if (target)
|
|
35
|
+
adapter.noteDenial(target.toolCallId, denialFromEngine(decision, { rule: () => simulatePermission({ rules, action: target.action, projectRoot }).explanation.rule ?? null })); } });
|
|
36
|
+
const adapter = createTuiEventAdapter(secretValuesFromEnvironment(process.env));
|
|
37
|
+
/* P13: the approvals the current run's sub-agents ask for (descendant-approvals.ts), watched while that run is open. */
|
|
38
|
+
const descendants = createDescendantApprovals({ runtime, secretValues: secretValuesFromEnvironment(process.env),
|
|
39
|
+
onRequired: (origin, event) => requestApproval(origin.sessionId, event, origin),
|
|
40
|
+
onResolved: (_origin, requestId) => approvalResolved(requestId),
|
|
41
|
+
onError: error => developmentLogError('descendants.failure', error, { sessionId: current }, 'tui-controller') });
|
|
42
|
+
let unsubscribe = () => { };
|
|
43
|
+
let closed = false;
|
|
44
|
+
let current = null;
|
|
45
|
+
/*
|
|
46
|
+
* ⭐⭐⭐ DECISIONE OWNER 16/09/2026 — UN COMANDO DIGITATO MENTRE UN GIRO E' IN CORSO SI ACCODA.
|
|
47
|
+
*
|
|
48
|
+
* Misurato sui due concorrenti: Claude Code accoda SEMPRE mentre un turno e' attivo, Hermes
|
|
49
|
+
* esegue subito e lo documenta come regola. Sono opposti, quindi nessuna posizione di parita'
|
|
50
|
+
* esisteva e l'owner ha scelto la prima.
|
|
51
|
+
* ⇒ Conseguenza che TOGLIE lavoro: non serve nessun lucchetto sull'albero di lavoro, e un'intera
|
|
52
|
+
* classe di difetti da scrittura concorrente non puo' verificarsi per costruzione.
|
|
53
|
+
*/
|
|
54
|
+
const queuedEntries = [];
|
|
55
|
+
const queueStore = paths?.queueRoot ? createQueueStore({ rootDir: paths.queueRoot }) : null;
|
|
56
|
+
let queuePausedState = false, drainingQueue = false, boundaryWhileDraining = false, queueEpoch = 0;
|
|
57
|
+
let queueHydration = Promise.resolve();
|
|
58
|
+
let pendingSteer = null;
|
|
59
|
+
let steerInvocation = null;
|
|
60
|
+
let cancelling = false;
|
|
61
|
+
const checkpointStore = paths?.checkpointsRoot ? createCheckpointStore({ rootDir: paths.checkpointsRoot, projectRoot }) : null;
|
|
62
|
+
const shellAwaitingStart = [];
|
|
63
|
+
const shellByCommandId = new Map();
|
|
64
|
+
const shellCommandForCheckpoint = new Map();
|
|
65
|
+
/* R0 (2026-09-23): "this checkpoint received its ComandoUtenteIniziato". Unlike the map above it is NOT cleared by
|
|
66
|
+
ComandoUtenteFinito, so a command that finishes before `runtime.shell` returns is not misread as never correlated
|
|
67
|
+
(which raised CHECKPOINT_SHELL_CORRELATION_FAILED and blocked every later mutation). */
|
|
68
|
+
const shellCorrelated = new WeakSet();
|
|
69
|
+
let checkpointBlockedError = null;
|
|
70
|
+
function checkpointFailure(code, error) {
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
const blocked = Object.assign(new Error(`${code}: ${message}`), { code, cause: error });
|
|
73
|
+
checkpointBlockedError = blocked;
|
|
74
|
+
queuePausedState = true;
|
|
75
|
+
developmentLogError('checkpoint.failure', blocked, { code }, 'tui-controller');
|
|
76
|
+
if (!closed)
|
|
77
|
+
onError(blocked);
|
|
78
|
+
return blocked;
|
|
79
|
+
}
|
|
80
|
+
function assertCheckpointReady() { if (checkpointBlockedError)
|
|
81
|
+
throw checkpointBlockedError; }
|
|
82
|
+
async function runModelMutation(operation, sessionId, invoke) {
|
|
83
|
+
const invokeStarted = performance.now();
|
|
84
|
+
developmentLog('runtime.invoke.begin', { operation, sessionId }, 'debug', 'tui-controller');
|
|
85
|
+
try {
|
|
86
|
+
const id = await invoke();
|
|
87
|
+
developmentLog('runtime.invoke.accepted', { operation, sessionId: id, durationMs: performance.now() - invokeStarted }, 'info', 'tui-controller');
|
|
88
|
+
return id;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
developmentLogError('runtime.invoke.failure', error, { operation, sessionId, durationMs: performance.now() - invokeStarted }, 'tui-controller');
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function runShellMutation(sessionId, command) {
|
|
96
|
+
assertCheckpointReady();
|
|
97
|
+
if (!checkpointStore) {
|
|
98
|
+
await runtime.shell(sessionId, command);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const checkpoint = await checkpointStore.begin({ operation: 'shell', sessionId });
|
|
102
|
+
shellAwaitingStart.push(checkpoint);
|
|
103
|
+
try {
|
|
104
|
+
await runtime.shell(sessionId, command);
|
|
105
|
+
if (!shellCorrelated.has(checkpoint)) {
|
|
106
|
+
const index = shellAwaitingStart.indexOf(checkpoint);
|
|
107
|
+
if (index >= 0)
|
|
108
|
+
shellAwaitingStart.splice(index, 1);
|
|
109
|
+
throw checkpointFailure('CHECKPOINT_SHELL_CORRELATION_FAILED', new Error('Direct shell returned without its synchronous ComandoUtenteIniziato correlation event.'));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
if (!shellCorrelated.has(checkpoint)) {
|
|
114
|
+
const index = shellAwaitingStart.indexOf(checkpoint);
|
|
115
|
+
if (index >= 0)
|
|
116
|
+
shellAwaitingStart.splice(index, 1);
|
|
117
|
+
try {
|
|
118
|
+
await checkpoint.finalize();
|
|
119
|
+
}
|
|
120
|
+
catch (finalization) {
|
|
121
|
+
checkpointFailure('CHECKPOINT_FINALIZE_FAILED', finalization);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function processRawEvent(id, raw, allowDrain = true) {
|
|
128
|
+
if (closed)
|
|
129
|
+
return;
|
|
130
|
+
developmentLog('runtime.raw_event', { sessionId: id, raw }, 'debug', 'tui-controller');
|
|
131
|
+
handleRawSteerEvent(raw);
|
|
132
|
+
const events = adapter.translateAll(raw);
|
|
133
|
+
const person = personCommandEvents(raw, events[0] ?? null);
|
|
134
|
+
if (!events.length) {
|
|
135
|
+
for (const extra of person)
|
|
136
|
+
onEvent(extra);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const [index, translated] of events.entries()) {
|
|
140
|
+
processEvent(id, translated, allowDrain);
|
|
141
|
+
if (index === 0)
|
|
142
|
+
for (const extra of person)
|
|
143
|
+
onEvent(extra);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function processEvent(id, translated, allowDrain) {
|
|
147
|
+
let event = personStopVerdict(id, translated);
|
|
148
|
+
if (event.type === 'run.started') {
|
|
149
|
+
openRun = event.runId ? { runId: event.runId } : {};
|
|
150
|
+
if (replayingHistory)
|
|
151
|
+
event = { ...event, history: true };
|
|
152
|
+
else
|
|
153
|
+
descendants.start(id);
|
|
154
|
+
}
|
|
155
|
+
else if (event.type === 'run.completed' || event.type === 'run.failed' || event.type === 'run.cancelled') {
|
|
156
|
+
openRun = null;
|
|
157
|
+
runEpoch++;
|
|
158
|
+
descendants.stop();
|
|
159
|
+
}
|
|
160
|
+
onEvent(event);
|
|
161
|
+
bypassWhy(event);
|
|
162
|
+
if ((event.type === 'run.completed' || event.type === 'run.failed' || event.type === 'run.cancelled') && interruptTerminalSession === id) {
|
|
163
|
+
const resolve = interruptTerminalResolve;
|
|
164
|
+
interruptTerminalResolve = null;
|
|
165
|
+
resolve?.();
|
|
166
|
+
}
|
|
167
|
+
if (event.type === 'approval.required') {
|
|
168
|
+
if (replayingHistory)
|
|
169
|
+
replayedApprovals.set(event.requestId, event);
|
|
170
|
+
else
|
|
171
|
+
requestApproval(id, event);
|
|
172
|
+
}
|
|
173
|
+
else if (event.type === 'approval.resolved')
|
|
174
|
+
approvalResolved(event.requestId);
|
|
175
|
+
else if (allowDrain && (event.type === 'run.completed' || event.type === 'run.failed') && !pendingSteer && !cancelling)
|
|
176
|
+
requestQueuedDrain(id);
|
|
177
|
+
}
|
|
178
|
+
/*
|
|
179
|
+
* R3 gate E12, owner decision Q-R3-15. The engine answers asynchronously, and a replay is synchronous: a replayed
|
|
180
|
+
* approval used to open its dialog AFTER the replayed answer had closed it (a stale dialog for a run long over). A
|
|
181
|
+
* replayed request is only remembered; once the replay is over it is asked only if its run is still waiting in the
|
|
182
|
+
* kernel (settleReplayedRun). A live request answered, or whose run ended, before the engine replied never opens.
|
|
183
|
+
*/
|
|
184
|
+
let runEpoch = 0;
|
|
185
|
+
/*
|
|
186
|
+
* Q-R3-13 (owner, R3 follow-up): `Full access` is the why of every row that RAN in bypass mode, the tools the kernel ran
|
|
187
|
+
* without asking included. The mode is taken when the tool starts (live only: a replay does not know the old mode); at
|
|
188
|
+
* its result, a tool with no why yet that was not refused gets `full-access`. A row the person answered, and the
|
|
189
|
+
* person's own `!` command, keep their why; a why is sent once per tool.
|
|
190
|
+
*/
|
|
191
|
+
const decidedTools = new Set();
|
|
192
|
+
const toolModes = new Map();
|
|
193
|
+
function emitDecision(toolCallId, decision) {
|
|
194
|
+
if (decision.kind === 'full-access' && decidedTools.has(toolCallId))
|
|
195
|
+
return;
|
|
196
|
+
decidedTools.add(toolCallId);
|
|
197
|
+
onEvent({ type: 'tool.decision', toolCallId, decision });
|
|
198
|
+
}
|
|
199
|
+
function bypassWhy(event) {
|
|
200
|
+
if (event.type === 'tool.started') {
|
|
201
|
+
if (!replayingHistory)
|
|
202
|
+
toolModes.set(event.toolCallId, currentMode);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (event.type === 'tool.completed') {
|
|
206
|
+
const mode = toolModes.get(event.toolCallId);
|
|
207
|
+
toolModes.delete(event.toolCallId);
|
|
208
|
+
if (mode === 'bypassPermissions' && !decidedTools.has(event.toolCallId) && event.outcome?.allowed !== false)
|
|
209
|
+
emitDecision(event.toolCallId, { kind: 'full-access' });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (event.type === 'run.completed' || event.type === 'run.failed' || event.type === 'run.cancelled') {
|
|
213
|
+
decidedTools.clear();
|
|
214
|
+
toolModes.clear();
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const replayedApprovals = new Map();
|
|
218
|
+
const answeredApprovals = new Set();
|
|
219
|
+
const approvalTools = new Map();
|
|
220
|
+
function approvalResolved(requestId) {
|
|
221
|
+
replayedApprovals.delete(requestId);
|
|
222
|
+
approvalTools.delete(requestId);
|
|
223
|
+
answeredApprovals.add(requestId);
|
|
224
|
+
if (answeredApprovals.size > 256)
|
|
225
|
+
answeredApprovals.delete(answeredApprovals.values().next().value);
|
|
226
|
+
onApproval?.(null);
|
|
227
|
+
}
|
|
228
|
+
function requestApproval(id, event, origin) {
|
|
229
|
+
const action = permissionActionFromApproval(event.payload);
|
|
230
|
+
const epoch = runEpoch;
|
|
231
|
+
answeredApprovals.delete(event.requestId);
|
|
232
|
+
if (event.toolCallId)
|
|
233
|
+
denialTargets.set(event.requestId, { toolCallId: event.toolCallId, action });
|
|
234
|
+
void coordinator.request(id, event.requestId, action).finally(() => denialTargets.delete(event.requestId)).then(result => {
|
|
235
|
+
if (result.pending) {
|
|
236
|
+
if (answeredApprovals.has(event.requestId) || epoch !== runEpoch || (origin ? !descendants.watching().includes(id) : current !== id))
|
|
237
|
+
return;
|
|
238
|
+
if (event.toolCallId)
|
|
239
|
+
approvalTools.set(event.requestId, event.toolCallId);
|
|
240
|
+
const runtimeHint = event.payload.trifecta === true ? 'trifecta' : undefined;
|
|
241
|
+
onApproval?.({ requestId: event.requestId, sessionId: id, ...(origin ? { origin: { kind: 'agent', task: origin.task } } : {}), action: result.action, rawPayload: event.payload, reason: result.reason, ...(result.rule ? { rule: result.rule } : {}), ...(runtimeHint ? { runtimeHint } : {}), ...explainApproval({ rules, action: result.action, projectRoot, persists: Boolean(paths) }) });
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
/* R3 gate E4 (Q-R3-3): why the engine let it run, on the tool's row. */
|
|
245
|
+
const decision = result.allowed ? decisionFromSource(result.source) : null;
|
|
246
|
+
if (decision && event.toolCallId)
|
|
247
|
+
emitDecision(event.toolCallId, decision);
|
|
248
|
+
onApproval?.(null);
|
|
249
|
+
}
|
|
250
|
+
}).catch(onError);
|
|
251
|
+
}
|
|
252
|
+
/*
|
|
253
|
+
* R3 gate E4 (Q-R3-1/3). The person's `!` command: the kernel announces it as `ComandoUtenteIniziato{comandoId}`
|
|
254
|
+
* immediately followed by its `ToolCallStart{shell}` (agent-service.mjs:2124-2125) and ends it with
|
|
255
|
+
* `ComandoUtenteFinito{comandoId,codice,enforcement}` (:2168); the v2 bridge drops both. That tool call's row is the
|
|
256
|
+
* person's (`typed by you`), with the exit code and isolation of the finish event. Its duration is measured here, live
|
|
257
|
+
* only: a replay delivers every event at once, and a duration measured on the observer of a replay is not a duration
|
|
258
|
+
* (agui-events.mjs OSS-1), so a replayed command states none.
|
|
259
|
+
*/
|
|
260
|
+
let personCommandNext = null;
|
|
261
|
+
const personCommands = new Map();
|
|
262
|
+
function personCommandEvents(raw, event) {
|
|
263
|
+
const row = raw && typeof raw === 'object' ? raw : null;
|
|
264
|
+
const announced = personCommandNext;
|
|
265
|
+
personCommandNext = null;
|
|
266
|
+
if (row?.type === 'ComandoUtenteIniziato') {
|
|
267
|
+
personCommandNext = typeof row.comandoId === 'string' && row.comandoId ? row.comandoId : null;
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
if (announced && event?.type === 'tool.started' && event.toolName === 'shell') {
|
|
271
|
+
personCommands.set(announced, { toolCallId: event.toolCallId, startedAt: replayingHistory ? null : performance.now() });
|
|
272
|
+
decidedTools.add(event.toolCallId);
|
|
273
|
+
return [{ type: 'tool.origin', toolCallId: event.toolCallId, origin: 'person' }, { type: 'tool.decision', toolCallId: event.toolCallId, decision: { kind: 'typed' } }];
|
|
274
|
+
}
|
|
275
|
+
if (row?.type === 'ComandoUtenteFinito' && typeof row.comandoId === 'string') {
|
|
276
|
+
const command = personCommands.get(row.comandoId);
|
|
277
|
+
if (!command)
|
|
278
|
+
return [];
|
|
279
|
+
personCommands.delete(row.comandoId);
|
|
280
|
+
const isolation = isolationOf(row.enforcement);
|
|
281
|
+
const facts = {
|
|
282
|
+
...(typeof row.codice === 'number' && Number.isInteger(row.codice) ? { exitCode: row.codice } : {}),
|
|
283
|
+
...(command.startedAt !== null && !replayingHistory ? { durationMs: Math.max(0, Math.round(performance.now() - command.startedAt)) } : {}),
|
|
284
|
+
...(isolation ? { isolation } : {}),
|
|
285
|
+
};
|
|
286
|
+
return Object.keys(facts).length ? [{ type: 'tool.facts', toolCallId: command.toolCallId, facts }] : [];
|
|
287
|
+
}
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
function handleCheckpointRaw(id, raw) {
|
|
291
|
+
if (!raw || typeof raw !== 'object')
|
|
292
|
+
return false;
|
|
293
|
+
const row = raw;
|
|
294
|
+
if (row.type === 'ComandoUtenteIniziato') {
|
|
295
|
+
const checkpoint = shellAwaitingStart.shift();
|
|
296
|
+
if (checkpoint && typeof row.comandoId === 'string' && row.comandoId) {
|
|
297
|
+
checkpoint.bindCommand(row.comandoId);
|
|
298
|
+
shellByCommandId.set(row.comandoId, checkpoint);
|
|
299
|
+
shellCommandForCheckpoint.set(checkpoint, row.comandoId);
|
|
300
|
+
shellCorrelated.add(checkpoint);
|
|
301
|
+
}
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
if (row.type === 'ComandoUtenteFinito') {
|
|
305
|
+
let commandId = typeof row.comandoId === 'string' && row.comandoId ? row.comandoId : null;
|
|
306
|
+
let checkpoint = commandId ? shellByCommandId.get(commandId) ?? null : null;
|
|
307
|
+
if (!checkpoint && commandId === null && shellByCommandId.size === 1) {
|
|
308
|
+
const only = [...shellByCommandId.entries()][0];
|
|
309
|
+
commandId = only[0];
|
|
310
|
+
checkpoint = only[1];
|
|
311
|
+
}
|
|
312
|
+
if (checkpoint && commandId) {
|
|
313
|
+
shellByCommandId.delete(commandId);
|
|
314
|
+
shellCommandForCheckpoint.delete(checkpoint);
|
|
315
|
+
void checkpoint.finalize().catch(error => checkpointFailure('CHECKPOINT_FINALIZE_FAILED', error));
|
|
316
|
+
}
|
|
317
|
+
else if (commandId === null && shellByCommandId.size > 1)
|
|
318
|
+
checkpointFailure('CHECKPOINT_SHELL_CORRELATION_FAILED', new Error('Multiple direct shell checkpoints are active and the terminal event has no command id.'));
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
function steerState(state) { onSteerState?.(state); }
|
|
324
|
+
function pendingSteerNow() { return pendingSteer; }
|
|
325
|
+
function rejectSteer(code, message) { const outcome = { status: 'rejected', code, message }; steerState(outcome); return outcome; }
|
|
326
|
+
function handleRawSteerEvent(raw) {
|
|
327
|
+
if (!raw || typeof raw !== 'object')
|
|
328
|
+
return;
|
|
329
|
+
const row = raw;
|
|
330
|
+
const type = typeof row.type === 'string' ? row.type : '', redirectId = typeof row.redirectId === 'string' ? row.redirectId : '';
|
|
331
|
+
if (type === 'RunRedirectRequested') {
|
|
332
|
+
if (!steerInvocation || pendingSteer || !redirectId)
|
|
333
|
+
return;
|
|
334
|
+
pendingSteer = { redirectId, text: steerInvocation.text };
|
|
335
|
+
steerState({ status: 'requested', redirectId });
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (!pendingSteer || pendingSteer.redirectId !== redirectId)
|
|
339
|
+
return;
|
|
340
|
+
if (type === 'RunRedirectApplied') {
|
|
341
|
+
steerState({ status: 'applied', redirectId });
|
|
342
|
+
pendingSteer = null;
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
if (type === 'RunRedirectCancelled') {
|
|
346
|
+
steerState({ status: 'cancelled', redirectId });
|
|
347
|
+
pendingSteer = null;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (type === 'RunRedirectFailed') {
|
|
351
|
+
const code = typeof row.code === 'string' ? row.code : 'SESSION_STEER_FAILED', message = typeof row.message === 'string' ? row.message : 'Steering failed.';
|
|
352
|
+
steerState({ status: 'failed', redirectId, code, message });
|
|
353
|
+
pendingSteer = null;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function actionOf(entry) { return { kind: entry.kind, text: entry.text, ...(entry.attachments ? { attachments: entry.attachments.map(row => ({ ...row })) } : {}) }; }
|
|
357
|
+
function publicEntry(entry) { return { id: entry.id, kind: entry.kind, text: entry.text, status: entry.status === 'dispatching' ? 'uncertain' : 'pending', ...(entry.attachments ? { attachments: entry.attachments.map(row => ({ ...row })) } : {}) }; }
|
|
358
|
+
function queueSnapshot() { return queuedEntries.map(actionOf); }
|
|
359
|
+
function queueEntriesSnapshot() { return queuedEntries.map(publicEntry); }
|
|
360
|
+
function notifyQueue() { onQueueChange?.(queueSnapshot()); }
|
|
361
|
+
async function persistFor(sessionId, entries) { if (queueStore)
|
|
362
|
+
await queueStore.save(sessionId, entries.filter(storedEntry)); }
|
|
363
|
+
async function replaceQueue(entries, sessionId = current ?? '') {
|
|
364
|
+
if (!sessionId)
|
|
365
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
366
|
+
await persistFor(sessionId, entries);
|
|
367
|
+
if (current !== sessionId)
|
|
368
|
+
throw new Error('QUEUE_SESSION_CHANGED');
|
|
369
|
+
queuedEntries.splice(0, queuedEntries.length, ...entries.map(entry => ({ ...entry, ...(entry.attachments ? { attachments: entry.attachments.map(row => ({ ...row })) } : {}) })));
|
|
370
|
+
notifyQueue();
|
|
371
|
+
}
|
|
372
|
+
async function hydrateQueue(id, epoch) {
|
|
373
|
+
queuedEntries.length = 0;
|
|
374
|
+
queuePausedState = false;
|
|
375
|
+
notifyQueue();
|
|
376
|
+
if (!queueStore)
|
|
377
|
+
return;
|
|
378
|
+
try {
|
|
379
|
+
const loaded = await queueStore.load(id);
|
|
380
|
+
if (current !== id || epoch !== queueEpoch)
|
|
381
|
+
return;
|
|
382
|
+
queuedEntries.splice(0, queuedEntries.length, ...loaded);
|
|
383
|
+
queuePausedState = loaded.length > 0;
|
|
384
|
+
notifyQueue();
|
|
385
|
+
if (loaded.length)
|
|
386
|
+
onQueueRestored?.(queueEntriesSnapshot());
|
|
387
|
+
}
|
|
388
|
+
catch (error) {
|
|
389
|
+
if (current !== id || epoch !== queueEpoch)
|
|
390
|
+
return;
|
|
391
|
+
queuePausedState = true;
|
|
392
|
+
onError(error);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
async function enqueue(action) {
|
|
396
|
+
await queueHydration;
|
|
397
|
+
if (!current)
|
|
398
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
399
|
+
const entry = { id: randomUUID(), kind: action.kind, text: action.text, status: 'pending', ...(action.attachments?.length ? { attachments: action.attachments.map(row => ({ ...row })) } : {}) };
|
|
400
|
+
await replaceQueue([...queuedEntries, entry], current);
|
|
401
|
+
return entry;
|
|
402
|
+
}
|
|
403
|
+
function queuePosition(entry) { const index = queuedEntries.findIndex(row => row.id === entry.id); return index < 0 ? 0 : index + 1; }
|
|
404
|
+
async function dispatchQueuedActions(id) {
|
|
405
|
+
await queueHydration;
|
|
406
|
+
if (current !== id || queuePausedState || drainingQueue)
|
|
407
|
+
return;
|
|
408
|
+
drainingQueue = true;
|
|
409
|
+
let followed = null;
|
|
410
|
+
try {
|
|
411
|
+
while (current === id && queuedEntries.length) {
|
|
412
|
+
const next = queuedEntries[0];
|
|
413
|
+
if (next.kind === 'prompt')
|
|
414
|
+
await assertReady();
|
|
415
|
+
const marked = { ...next, status: 'dispatching', ...(next.attachments ? { attachments: next.attachments.map(row => ({ ...row })) } : {}) }, tail = queuedEntries.slice(1);
|
|
416
|
+
if (marked.kind === 'slash') {
|
|
417
|
+
/* R5a Q-R5-8: out of the queue first, then run; a command that moved the session (/resume, /fork) takes what was
|
|
418
|
+
typed after it along, since that was typed for the session it opens. */
|
|
419
|
+
await replaceQueue(tail, id);
|
|
420
|
+
try {
|
|
421
|
+
await onQueuedSlash?.(marked.text);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
onError(error);
|
|
425
|
+
}
|
|
426
|
+
if (current !== id) {
|
|
427
|
+
const moved = tail.filter(entry => !queuedEntries.some(row => row.id === entry.id));
|
|
428
|
+
if (current && moved.length) {
|
|
429
|
+
await queueHydration;
|
|
430
|
+
await persistFor(id, []);
|
|
431
|
+
await replaceQueue([...queuedEntries, ...moved.map(entry => ({ ...entry, status: 'pending' }))], current);
|
|
432
|
+
followed = current;
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
await replaceQueue([marked, ...tail], id);
|
|
439
|
+
/* R5a S5 (E8, gate finding 8): a queued `!` command's own line is shown BEFORE it runs, so its row lands after it. */
|
|
440
|
+
if (marked.kind === 'command')
|
|
441
|
+
onQueuedDispatch?.(actionOf(marked));
|
|
442
|
+
try {
|
|
443
|
+
if (marked.kind === 'command')
|
|
444
|
+
await runShellMutation(id, marked.text);
|
|
445
|
+
else
|
|
446
|
+
await runModelMutation('resume', id, () => runtime.resume(id, marked.text, marked.attachments));
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
if (current === id)
|
|
450
|
+
await replaceQueue([{ ...marked, status: 'pending' }, ...tail], id).catch(onError);
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
await replaceQueue(tail, id);
|
|
454
|
+
if (marked.kind !== 'command')
|
|
455
|
+
onQueuedDispatch?.(actionOf(marked));
|
|
456
|
+
if (marked.kind === 'prompt')
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
drainingQueue = false;
|
|
462
|
+
if (boundaryWhileDraining) {
|
|
463
|
+
boundaryWhileDraining = false;
|
|
464
|
+
requestQueuedDrain(id);
|
|
465
|
+
}
|
|
466
|
+
if (followed)
|
|
467
|
+
requestQueuedDrain(followed);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
/* R5a Q-R5-8: a session-changing slash command typed during a turn waits in the queue (app.ts decides which ones). */
|
|
471
|
+
async function deferSlash(text, { running }) {
|
|
472
|
+
await queueHydration;
|
|
473
|
+
const value = String(text ?? '').trim();
|
|
474
|
+
if (!current)
|
|
475
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
476
|
+
if (!value)
|
|
477
|
+
return 0;
|
|
478
|
+
const entry = await enqueue({ kind: 'slash', text: value });
|
|
479
|
+
if (!running && !queuePausedState)
|
|
480
|
+
await dispatchQueuedActions(current);
|
|
481
|
+
return queuePosition(entry);
|
|
482
|
+
}
|
|
483
|
+
function requestQueuedDrain(id) {
|
|
484
|
+
if (queuePausedState || pendingSteer || cancelling)
|
|
485
|
+
return;
|
|
486
|
+
if (drainingQueue) {
|
|
487
|
+
boundaryWhileDraining = true;
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
void queueHydration.then(() => { if (current === id && !queuePausedState && !pendingSteer && !cancelling)
|
|
491
|
+
return dispatchQueuedActions(id); }).catch(onError);
|
|
492
|
+
}
|
|
493
|
+
async function assertReady() { if (!readiness)
|
|
494
|
+
return; const answer = await readiness(currentModel); if (!answer.ready)
|
|
495
|
+
throw new TuiNotReadyError(answer.code, answer.provider, answer.message); }
|
|
496
|
+
async function resolveId(id) { if (id !== 'last')
|
|
497
|
+
return id; const rows = await runtime.listSessions(); const row = rows.at(-1); if (!row?.sessionId)
|
|
498
|
+
throw new Error('SESSION_NOT_FOUND'); return String(row.sessionId); }
|
|
499
|
+
/*
|
|
500
|
+
* R1 lane R (D-04, D-08; gate E9). Re-attaching to an EXISTING session (resume, fork) replays its history synchronously inside
|
|
501
|
+
* `runtime.subscribe` (registry `iscriviti`, session-registry.mjs:5825-5831): those RunStarted events are marked `history`, so
|
|
502
|
+
* the transcript never lets an old message absorb a new send's echo. A session this controller has just started is not
|
|
503
|
+
* history — the prompt given on the command line is echoed by app.ts only after `initialize` returns.
|
|
504
|
+
* `openRun` follows the run lifecycle of the attached session; the kernel appends no terminal event to a run cut off by
|
|
505
|
+
* closing TALOS (measured: RunStarted, TextMessageStart, TextMessageContent, then nothing) but its session list says so.
|
|
506
|
+
*/
|
|
507
|
+
let replayingHistory = false;
|
|
508
|
+
/*
|
|
509
|
+
* R3 gate E8, owner decision Q-R3-8. The kernel ends every stopped run with `RunError{code:'fermato'}`
|
|
510
|
+
* (harness-ui/src/agent-service.mjs:185); the CLI is the only one that knows the person asked for the stop, so it keeps
|
|
511
|
+
* that request until the run's terminal event and turns that event into the interrupted path the transcript already
|
|
512
|
+
* has (`run.cancelled{interrupted:true}`), with `reason:'person'` (a replayed cut-off run has no reason). A `fermato`
|
|
513
|
+
* nobody asked for (the model went silent), or any other code racing the stop, stays a failure. The request belongs to
|
|
514
|
+
* the run that was open when it was made: a new `run.started` drops it.
|
|
515
|
+
*/
|
|
516
|
+
let stopRequested = null;
|
|
517
|
+
function personStopVerdict(id, event) {
|
|
518
|
+
if (event.type === 'run.started') {
|
|
519
|
+
stopRequested = null;
|
|
520
|
+
return event;
|
|
521
|
+
}
|
|
522
|
+
if (event.type !== 'run.failed' && event.type !== 'run.cancelled' && event.type !== 'run.completed')
|
|
523
|
+
return event;
|
|
524
|
+
const asked = stopRequested === id;
|
|
525
|
+
if (asked)
|
|
526
|
+
stopRequested = null;
|
|
527
|
+
/* A replayed `fermato` is the person's stop unless it is the kernel's exact "stopped without an answer" sentence. */
|
|
528
|
+
if (event.type !== 'run.failed' || event.error?.envelope?.code !== 'fermato')
|
|
529
|
+
return event;
|
|
530
|
+
const replayedStop = replayingHistory && (event.error.raw ?? event.error.message ?? '').trim() !== KERNEL_RUN_TEXT.stoppedWithoutAnswer;
|
|
531
|
+
if (!asked && !replayedStop)
|
|
532
|
+
return event;
|
|
533
|
+
return { type: 'run.cancelled', ...(event.runId ? { runId: event.runId } : {}), interrupted: true, reason: 'person', ...(event.eventId ? { eventId: event.eventId } : {}) };
|
|
534
|
+
}
|
|
535
|
+
let openRun = null;
|
|
536
|
+
function attach(id, from = 0, history = false) {
|
|
537
|
+
unsubscribe();
|
|
538
|
+
descendants.stop();
|
|
539
|
+
adapter.bindSession(id);
|
|
540
|
+
current = id;
|
|
541
|
+
openRun = null;
|
|
542
|
+
replayedApprovals.clear();
|
|
543
|
+
personCommandNext = null;
|
|
544
|
+
decidedTools.clear();
|
|
545
|
+
toolModes.clear();
|
|
546
|
+
const epoch = ++queueEpoch;
|
|
547
|
+
queueHydration = hydrateQueue(id, epoch);
|
|
548
|
+
replayingHistory = history;
|
|
549
|
+
try {
|
|
550
|
+
unsubscribe = runtime.subscribe(id, (raw) => { if (handleCheckpointRaw(id, raw))
|
|
551
|
+
return; processRawEvent(id, raw); }, from);
|
|
552
|
+
}
|
|
553
|
+
finally {
|
|
554
|
+
replayingHistory = false;
|
|
555
|
+
}
|
|
556
|
+
return id;
|
|
557
|
+
}
|
|
558
|
+
/* After a history replay, a run still open in the events but not running in the kernel was cut off: it is closed as
|
|
559
|
+
interrupted through one terminal event, so the transcript row and the status line (state.ts `run.cancelled`) agree with
|
|
560
|
+
the footer. Settled BEFORE any new prompt of the same resume is sent, and skipped if the session moved on meanwhile. */
|
|
561
|
+
async function settleReplayedRun(id) {
|
|
562
|
+
/* R3 E12 (Q-R3-15): the approvals the replay left unanswered are asked only when their run is still waiting. */
|
|
563
|
+
const waiting = [...replayedApprovals.values()];
|
|
564
|
+
replayedApprovals.clear();
|
|
565
|
+
const open = openRun;
|
|
566
|
+
if (!open || current !== id)
|
|
567
|
+
return;
|
|
568
|
+
let outcome = null;
|
|
569
|
+
try {
|
|
570
|
+
outcome = (await runtime.listSessions()).find((row) => row?.sessionId === id)?.outcome ?? null;
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
developmentLogError('replay.settle.failure', error, { sessionId: id }, 'tui-controller');
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (current === id && openRun === open && outcome === 'running') {
|
|
577
|
+
for (const event of waiting)
|
|
578
|
+
requestApproval(id, event);
|
|
579
|
+
descendants.start(id);
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
if (current !== id || openRun !== open || typeof outcome !== 'string' || outcome === 'running' || outcome === 'unknown')
|
|
583
|
+
return;
|
|
584
|
+
openRun = null;
|
|
585
|
+
onEvent({ type: 'run.cancelled', ...(open.runId ? { runId: open.runId } : {}), interrupted: true });
|
|
586
|
+
}
|
|
587
|
+
async function listSessions() { return normalizeSessions(await runtime.listSessions()); }
|
|
588
|
+
async function agentTree() { if (!current)
|
|
589
|
+
return null; return buildAgentTree(await runtime.listSessions(), current); }
|
|
590
|
+
/* B1 slice 18, fix round: a prompt makes resume and fork a send, so readiness is asked FIRST — before the session list, the
|
|
591
|
+
subscription or the runtime call — and a not-ready answer reaches no runtime method. Without a prompt nothing is sent. */
|
|
592
|
+
async function referencesFor(text) { return resolveProjectReferences({ projectRoot, prompt: text }); }
|
|
593
|
+
function promptAction(text, attachments) { return attachments.length ? { kind: 'prompt', text, attachments } : { kind: 'prompt', text }; }
|
|
594
|
+
async function resumeSession(id, prompt) { const text = prompt?.trim(); if (text)
|
|
595
|
+
await assertReady(); const attachments = text ? await referencesFor(text) : []; const resolved = await resolveId(id); attach(resolved, 0, true); await queueHydration; await settleReplayedRun(resolved); if (text)
|
|
596
|
+
await runModelMutation('resume', resolved, () => runtime.resume(resolved, text, attachments)); return resolved; }
|
|
597
|
+
async function forkSession(id, prompt) { const text = prompt?.trim(); if (text)
|
|
598
|
+
await assertReady(); const attachments = text ? await referencesFor(text) : []; const resolved = await resolveId(id); const child = await runModelMutation('fork', null, () => runtime.fork(resolved, text, attachments)); attach(child, 0, true); await queueHydration; await settleReplayedRun(child); return child; }
|
|
599
|
+
async function initialize({ resume, fork, initialPrompt } = {}) { const text = initialPrompt?.trim(); if (text)
|
|
600
|
+
await assertReady(); const attachments = text ? await referencesFor(text) : []; if (resume || fork)
|
|
601
|
+
await runtime.restore(); if (resume) {
|
|
602
|
+
const id = await resolveId(resume);
|
|
603
|
+
attach(id, 0, true);
|
|
604
|
+
await queueHydration;
|
|
605
|
+
await settleReplayedRun(id);
|
|
606
|
+
if (text)
|
|
607
|
+
await runModelMutation('resume', id, () => runtime.resume(id, text, attachments));
|
|
608
|
+
return id;
|
|
609
|
+
} if (fork) {
|
|
610
|
+
const parent = await resolveId(fork);
|
|
611
|
+
const id = await runModelMutation('fork', null, () => runtime.fork(parent, text, attachments));
|
|
612
|
+
attach(id, 0, true);
|
|
613
|
+
await queueHydration;
|
|
614
|
+
await settleReplayedRun(id);
|
|
615
|
+
return id;
|
|
616
|
+
} if (text) {
|
|
617
|
+
const id = await runModelMutation('start', null, () => runtime.start({ projectRoot, prompt: text, model: currentModel, permissionMode: currentMode, ...(attachments.length ? { attachments } : {}) }));
|
|
618
|
+
attach(id, 0);
|
|
619
|
+
await queueHydration;
|
|
620
|
+
return id;
|
|
621
|
+
} return null; }
|
|
622
|
+
/*
|
|
623
|
+
* ⛔ IL PREFISSO `!` VA AL COMANDO, NON AL MODELLO. Nessun `start`/`resume` parte da questo ramo:
|
|
624
|
+
* il testo non diventa mai un messaggio per il modello.
|
|
625
|
+
* ⛔ E senza sessione si RIFIUTA a voce alta: far sparire un comando digitato e' peggio che negarlo.
|
|
626
|
+
*/
|
|
627
|
+
async function send(prompt, { running }) {
|
|
628
|
+
developmentLog('controller.send', { prompt: developmentTextEvidence(prompt), running, currentSessionId: current, model: currentModel, mode: currentMode, queueCount: queuedEntries.length }, 'debug', 'tui-controller');
|
|
629
|
+
await queueHydration;
|
|
630
|
+
const text = prompt.trim();
|
|
631
|
+
if (!text)
|
|
632
|
+
return { sessionId: current, kind: 'none', queued: false, position: 0 };
|
|
633
|
+
if (text.startsWith('!')) {
|
|
634
|
+
const command = text.slice(1).trim();
|
|
635
|
+
if (!command)
|
|
636
|
+
return { sessionId: current, kind: 'none', queued: false, position: 0 };
|
|
637
|
+
if (!current)
|
|
638
|
+
throw new Error(EXECUTION_TEXT.shellSessionRequired);
|
|
639
|
+
if (running || queuedEntries.length) {
|
|
640
|
+
const action = await enqueue({ kind: 'command', text: command });
|
|
641
|
+
if (!running && !queuePausedState)
|
|
642
|
+
await dispatchQueuedActions(current);
|
|
643
|
+
const position = queuePosition(action);
|
|
644
|
+
return { sessionId: current, kind: 'command', queued: position > 0, position };
|
|
645
|
+
}
|
|
646
|
+
await runShellMutation(current, command);
|
|
647
|
+
return { sessionId: current, kind: 'command', queued: false, position: 0 };
|
|
648
|
+
}
|
|
649
|
+
await assertReady();
|
|
650
|
+
const attachments = await referencesFor(text);
|
|
651
|
+
if (running || queuedEntries.length) {
|
|
652
|
+
if (!current)
|
|
653
|
+
throw new Error('SESSION_NOT_FOUND');
|
|
654
|
+
const action = await enqueue(promptAction(text, attachments));
|
|
655
|
+
if (!running && !queuePausedState)
|
|
656
|
+
await dispatchQueuedActions(current);
|
|
657
|
+
const position = queuePosition(action);
|
|
658
|
+
return { sessionId: current, kind: 'prompt', queued: position > 0, position };
|
|
659
|
+
}
|
|
660
|
+
if (current) {
|
|
661
|
+
await runModelMutation('resume', current, () => runtime.resume(current, text, attachments));
|
|
662
|
+
return { sessionId: current, kind: 'prompt', queued: false, position: 0 };
|
|
663
|
+
}
|
|
664
|
+
const id = await runModelMutation('start', null, () => runtime.start({ projectRoot, prompt: text, model: currentModel, permissionMode: currentMode, ...(attachments.length ? { attachments } : {}) }));
|
|
665
|
+
attach(id, 0);
|
|
666
|
+
return { sessionId: id, kind: 'prompt', queued: false, position: 0 };
|
|
667
|
+
}
|
|
668
|
+
async function steer(prompt, { running }) {
|
|
669
|
+
await queueHydration;
|
|
670
|
+
const text = String(prompt ?? '').trim();
|
|
671
|
+
if (!text)
|
|
672
|
+
return rejectSteer('STEER_TEXT_REQUIRED', 'Steering text is required.');
|
|
673
|
+
if (text.startsWith('!'))
|
|
674
|
+
return rejectSteer('STEER_COMMAND_UNSUPPORTED', 'Shell commands cannot be used as steering input.');
|
|
675
|
+
if (!current)
|
|
676
|
+
return rejectSteer('SESSION_NOT_FOUND', 'There is no active session to steer.');
|
|
677
|
+
if (!running)
|
|
678
|
+
return rejectSteer('STEER_NOT_RUNNING', 'Steering requires an active run.');
|
|
679
|
+
if (pendingSteer || steerInvocation)
|
|
680
|
+
return rejectSteer('STEER_ALREADY_PENDING', 'A steering request is already waiting for a safe boundary.');
|
|
681
|
+
try {
|
|
682
|
+
await assertReady();
|
|
683
|
+
const attachments = await referencesFor(text);
|
|
684
|
+
steerInvocation = { text };
|
|
685
|
+
const accepted = await runtime.steer(current, text, attachments);
|
|
686
|
+
const correlated = pendingSteerNow();
|
|
687
|
+
if (correlated && correlated.redirectId !== accepted.redirectId) {
|
|
688
|
+
const stale = correlated;
|
|
689
|
+
pendingSteer = null;
|
|
690
|
+
steerState({ status: 'failed', redirectId: stale.redirectId, code: 'STEER_CORRELATION_FAILED', message: 'The runtime returned a different steering correlation id.' });
|
|
691
|
+
return rejectSteer('STEER_CORRELATION_FAILED', 'The runtime returned a different steering correlation id.');
|
|
692
|
+
}
|
|
693
|
+
if (!pendingSteerNow()) {
|
|
694
|
+
pendingSteer = { redirectId: accepted.redirectId, text };
|
|
695
|
+
steerState({ status: 'requested', redirectId: accepted.redirectId });
|
|
696
|
+
}
|
|
697
|
+
return { status: 'requested', redirectId: accepted.redirectId };
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
pendingSteer = null;
|
|
701
|
+
const code = typeof error?.code === 'string' ? error.code : 'SESSION_STEER_REJECTED', message = error instanceof Error ? error.message : String(error);
|
|
702
|
+
return rejectSteer(code, message);
|
|
703
|
+
}
|
|
704
|
+
finally {
|
|
705
|
+
steerInvocation = null;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
async function editQueue(id, text) { await queueHydration; if (!current)
|
|
709
|
+
throw new Error('SESSION_NOT_FOUND'); const index = queuedEntries.findIndex(row => row.id === id); if (index < 0)
|
|
710
|
+
throw new Error('QUEUE_ENTRY_NOT_FOUND'); const value = text.trim(); if (!value)
|
|
711
|
+
throw new Error('QUEUE_TEXT_REQUIRED'); const previous = queuedEntries[index], attachments = previous.kind === 'prompt' ? await referencesFor(value) : []; const edited = { id: previous.id, kind: previous.kind, text: value, status: 'pending', ...(attachments.length ? { attachments } : {}) }; const next = [...queuedEntries]; next[index] = edited; await replaceQueue(next, current); }
|
|
712
|
+
async function moveQueue(id, direction) { await queueHydration; if (!current)
|
|
713
|
+
throw new Error('SESSION_NOT_FOUND'); const index = queuedEntries.findIndex(row => row.id === id); if (index < 0)
|
|
714
|
+
throw new Error('QUEUE_ENTRY_NOT_FOUND'); const target = Math.max(0, Math.min(queuedEntries.length - 1, index + direction)); if (target === index)
|
|
715
|
+
return; const next = [...queuedEntries]; const [entry] = next.splice(index, 1); next.splice(target, 0, entry); await replaceQueue(next, current); }
|
|
716
|
+
async function deleteQueue(id) { await queueHydration; if (!current)
|
|
717
|
+
throw new Error('SESSION_NOT_FOUND'); const index = queuedEntries.findIndex(row => row.id === id); if (index < 0)
|
|
718
|
+
return null; const next = [...queuedEntries]; const [removed] = next.splice(index, 1); await replaceQueue(next, current); if (next.length === 0)
|
|
719
|
+
queuePausedState = false; return removed ? publicEntry(removed) : null; }
|
|
720
|
+
async function clearQueue() { await queueHydration; const dropped = queueSnapshot(); if (current)
|
|
721
|
+
await replaceQueue([], current);
|
|
722
|
+
else {
|
|
723
|
+
queuedEntries.length = 0;
|
|
724
|
+
notifyQueue();
|
|
725
|
+
} queuePausedState = false; return dropped; }
|
|
726
|
+
async function dispatchQueue() { await queueHydration; if (!current)
|
|
727
|
+
throw new Error('SESSION_NOT_FOUND'); if (!queuedEntries.length) {
|
|
728
|
+
queuePausedState = false;
|
|
729
|
+
return 0;
|
|
730
|
+
} const normalized = queuedEntries.map(row => row.status === 'dispatching' ? { ...row, status: 'pending' } : row); await replaceQueue(normalized, current); queuePausedState = false; await dispatchQueuedActions(current); return queuedEntries.length; }
|
|
731
|
+
let interruptPromise = null;
|
|
732
|
+
let interruptTerminalSession = null;
|
|
733
|
+
let interruptTerminalResolve = null;
|
|
734
|
+
async function interrupt() {
|
|
735
|
+
await queueHydration;
|
|
736
|
+
if (!current)
|
|
737
|
+
return queuedEntries.length;
|
|
738
|
+
if (interruptPromise)
|
|
739
|
+
return interruptPromise;
|
|
740
|
+
const sessionId = current;
|
|
741
|
+
cancelling = true;
|
|
742
|
+
interruptTerminalSession = sessionId;
|
|
743
|
+
if (openRun)
|
|
744
|
+
stopRequested = sessionId;
|
|
745
|
+
const terminal = new Promise(resolve => { interruptTerminalResolve = resolve; });
|
|
746
|
+
interruptPromise = (async () => {
|
|
747
|
+
try {
|
|
748
|
+
await runtime.cancel(sessionId);
|
|
749
|
+
await terminal;
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
queuePausedState = true;
|
|
753
|
+
onError(error);
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
finally {
|
|
757
|
+
cancelling = false;
|
|
758
|
+
interruptTerminalSession = null;
|
|
759
|
+
interruptTerminalResolve = null;
|
|
760
|
+
}
|
|
761
|
+
if (current !== sessionId || queuedEntries.length === 0)
|
|
762
|
+
return queuedEntries.length;
|
|
763
|
+
const normalized = queuedEntries.map(row => row.status === 'dispatching' ? { ...row, status: 'pending' } : row);
|
|
764
|
+
await replaceQueue(normalized, sessionId);
|
|
765
|
+
queuePausedState = false;
|
|
766
|
+
await dispatchQueuedActions(sessionId);
|
|
767
|
+
return queuedEntries.length;
|
|
768
|
+
})().finally(() => { interruptPromise = null; });
|
|
769
|
+
return interruptPromise;
|
|
770
|
+
}
|
|
771
|
+
return { initialize, attach, send, deferSlash, steer, listSessions, agentTree, resumeSession, forkSession, pendingCommands: () => queuedEntries.filter(action => action.kind === 'command').length, pendingPrompts: () => queuedEntries.filter(action => action.kind === 'prompt').length, queue: queueSnapshot, queueEntries: queueEntriesSnapshot, queuePaused: () => queuePausedState, queueReady: () => queueHydration, editQueue, moveQueue, deleteQueue, clearQueue, dispatchQueue, interrupt, current: () => current, mode: () => currentMode, setMode(next) { currentMode = next; }, model: () => currentModel, setModel(next) { if (typeof next !== 'string' || !next.trim())
|
|
772
|
+
throw new Error('MODEL_REQUIRED'); currentModel = next.trim(); }, async compact() { if (!current)
|
|
773
|
+
throw new Error('SESSION_NOT_FOUND'); return runtime.compact(current); }, async resolveApproval(requestId, choice) { /* R3 R3-ROW: the link is taken BEFORE the answer: the kernel's ApprovalResolved arrives during the await and drops it. */ const toolCallId = approvalTools.get(requestId); approvalTools.delete(requestId); if (toolCallId && !choice.startsWith('allow-'))
|
|
774
|
+
adapter.noteDenial(toolCallId, { by: 'person' }); await coordinator.resolve(requestId, choice); const decision = decisionFromChoice(choice); if (toolCallId && decision)
|
|
775
|
+
emitDecision(toolCallId, decision); onApproval?.(null); }, async cancel() { if (!current)
|
|
776
|
+
return []; const dropped = queueSnapshot(); if (openRun)
|
|
777
|
+
stopRequested = current; onEvent({ type: 'run.cancelled' }); cancelling = true; try {
|
|
778
|
+
await runtime.cancel(current);
|
|
779
|
+
await clearQueue();
|
|
780
|
+
return dropped;
|
|
781
|
+
}
|
|
782
|
+
catch (error) {
|
|
783
|
+
queuePausedState = true;
|
|
784
|
+
onError(error);
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
finally {
|
|
788
|
+
cancelling = false;
|
|
789
|
+
} }, close() { if (closed)
|
|
790
|
+
return; closed = true; developmentLog('controller.close', { sessionId: current, queueCount: queuedEntries.length }, 'info', 'tui-controller'); descendants.stop(); unsubscribe(); } };
|
|
791
|
+
}
|