@timurproko/a1 0.2.6-dev.736 → 0.2.7-dev.1221
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/README.md +41 -3
- package/bin/automatic-update.js +7 -0
- package/bin/cli.js +4 -0
- package/bin/guardian.js +5 -0
- package/bin/tab-launch-context.d.ts +27 -0
- package/bin/tab-launch-context.js +94 -0
- package/bin/ui.js +107 -59
- package/dist/app/session-shell/clipboard-diagnostics.js +1 -1
- package/dist/app/session-shell/dialog-coordinator.d.ts +42 -0
- package/dist/app/session-shell/dialog-coordinator.js +133 -0
- package/dist/app/session-shell/dock-chrome.d.ts +34 -0
- package/dist/app/session-shell/dock-chrome.js +109 -0
- package/dist/app/session-shell/exit-notice.d.ts +1 -1
- package/dist/app/session-shell/exit-notice.js +1 -1
- package/dist/app/session-shell/extension-surface.d.ts +30 -0
- package/dist/app/session-shell/extension-surface.js +105 -0
- package/dist/app/session-shell/helper-pool.d.ts +3 -5
- package/dist/app/session-shell/helper-pool.js +3 -5
- package/dist/app/session-shell/input-surface-switch.d.ts +36 -0
- package/dist/app/session-shell/input-surface-switch.js +85 -0
- package/dist/app/session-shell/login-interaction.d.ts +27 -0
- package/dist/app/session-shell/login-interaction.js +91 -0
- package/dist/app/session-shell/prompt-submission.d.ts +48 -0
- package/dist/app/session-shell/prompt-submission.js +276 -0
- package/dist/app/session-shell/resident-tab-coordinator.d.ts +38 -0
- package/dist/app/session-shell/resident-tab-coordinator.js +77 -0
- package/dist/app/session-shell/response-copy-helper.js +21 -6
- package/dist/app/session-shell/response-copy-protocol.d.ts +1 -1
- package/dist/app/session-shell/response-copy-transport.d.ts +16 -6
- package/dist/app/session-shell/response-copy-transport.js +69 -17
- package/dist/app/session-shell/session-concern.d.ts +22 -0
- package/dist/app/session-shell/session-concern.js +29 -0
- package/dist/app/session-shell/session-lifecycle.d.ts +29 -0
- package/dist/app/session-shell/session-lifecycle.js +49 -0
- package/dist/app/session-shell/session-shell-root.d.ts +42 -6
- package/dist/app/session-shell/session-shell-root.js +196 -908
- package/dist/app/session-shell/session-shell.d.ts +3 -8
- package/dist/app/session-shell/session-shell.js +324 -835
- package/dist/app/session-shell/session-viewport-controller.d.ts +2 -2
- package/dist/app/session-shell/session-viewport-controller.js +29 -15
- package/dist/app/session-shell/settings-bindings.d.ts +17 -0
- package/dist/app/session-shell/settings-bindings.js +91 -0
- package/dist/app/session-shell/shell-editor-factory.d.ts +27 -0
- package/dist/app/session-shell/shell-editor-factory.js +102 -0
- package/dist/app/session-shell/shell-host.d.ts +6 -0
- package/dist/app/session-shell/shell-host.js +34 -7
- package/dist/app/session-shell/shell-layout.d.ts +64 -0
- package/dist/app/session-shell/shell-layout.js +18 -0
- package/dist/app/session-shell/suggestion-surface.d.ts +25 -0
- package/dist/app/session-shell/suggestion-surface.js +63 -0
- package/dist/app/session-shell/transcript-document.d.ts +78 -0
- package/dist/app/session-shell/transcript-document.js +365 -0
- package/dist/app/session-shell/viewport-layout.d.ts +31 -0
- package/dist/app/session-shell/viewport-layout.js +160 -0
- package/dist/app/session-shell/workflow-output.d.ts +33 -0
- package/dist/app/session-shell/workflow-output.js +174 -0
- package/dist/app/session-shell/workflow-router.d.ts +25 -0
- package/dist/app/session-shell/workflow-router.js +39 -0
- package/dist/cli/dispatch.d.ts +11 -1
- package/dist/cli/dispatch.js +79 -5
- package/dist/cli/tabs-request.d.ts +7 -0
- package/dist/cli/tabs-request.js +1 -0
- package/dist/cli/tabs.d.ts +14 -0
- package/dist/cli/tabs.js +151 -0
- package/dist/cli/version-stats.js +38 -3
- package/dist/composition/owned-settings.d.ts +12 -0
- package/dist/composition/owned-settings.js +36 -0
- package/dist/composition/owned-ui.d.ts +27 -28
- package/dist/composition/owned-ui.js +205 -153
- package/dist/composition/resident-tab-bridge.d.ts +25 -0
- package/dist/composition/resident-tab-bridge.js +180 -0
- package/dist/composition/session-backend.d.ts +19 -0
- package/dist/composition/session-backend.js +1 -0
- package/dist/composition/session-runtime-registry.d.ts +20 -0
- package/dist/composition/session-runtime-registry.js +85 -0
- package/dist/composition/settings-route-host.d.ts +16 -1
- package/dist/composition/settings-route-host.js +29 -10
- package/dist/composition/settings-search-input.d.ts +9 -0
- package/dist/composition/settings-search-input.js +58 -0
- package/dist/composition/startup-route.d.ts +32 -0
- package/dist/composition/startup-route.js +72 -0
- package/dist/composition/utility-tab.d.ts +28 -0
- package/dist/composition/utility-tab.js +242 -0
- package/dist/contracts/owned-ui/index.d.ts +1 -1
- package/dist/contracts/owned-ui/model.d.ts +3 -0
- package/dist/contracts/owned-ui/session-backend.d.ts +3 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +11 -4
- package/dist/contracts/presentation/index.d.ts +11 -0
- package/dist/contracts/presentation/index.js +12 -0
- package/dist/features/launch/development-launch.js +6 -0
- package/dist/features/launch/index.d.ts +1 -1
- package/dist/features/launch/intent.d.ts +3 -1
- package/dist/features/launch/intent.js +7 -4
- package/dist/features/launch/profile-paths.js +4 -0
- package/dist/features/owned-ui/plus-menu-dialog.d.ts +22 -0
- package/dist/features/owned-ui/plus-menu-dialog.js +76 -0
- package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
- package/dist/features/owned-ui/project-trust-dialog.js +6 -8
- package/dist/features/owned-ui/project-trust-prompt.d.ts +1 -0
- package/dist/features/owned-ui/project-trust-prompt.js +7 -3
- package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
- package/dist/features/owned-ui/reference-screen-app.js +12 -2
- package/dist/features/owned-ui/resources/release-notes.json +4 -0
- package/dist/features/owned-ui/settings-app.d.ts +12 -2
- package/dist/features/owned-ui/settings-app.js +119 -84
- package/dist/features/owned-ui/settings-search-input.d.ts +29 -0
- package/dist/features/owned-ui/settings-search-input.js +1 -0
- package/dist/features/prompt-history/index.d.ts +1 -1
- package/dist/features/prompt-history/paths.d.ts +2 -1
- package/dist/features/prompt-history/paths.js +10 -2
- package/dist/features/prompt-history/service.d.ts +5 -0
- package/dist/features/prompt-history/service.js +1 -1
- package/dist/features/resident-tab/auto-name.d.ts +11 -0
- package/dist/features/resident-tab/auto-name.js +44 -0
- package/dist/features/resident-tab/index.d.ts +5 -0
- package/dist/features/resident-tab/index.js +7 -0
- package/dist/features/resident-tab/shortcut-bindings-file.d.ts +8 -0
- package/dist/features/resident-tab/shortcut-bindings-file.js +40 -0
- package/dist/features/resident-tab/shortcut-declarations.d.ts +13 -0
- package/dist/features/resident-tab/shortcut-declarations.js +26 -0
- package/dist/features/resident-tab/shortcut-projection.d.ts +29 -0
- package/dist/features/resident-tab/shortcut-projection.js +64 -0
- package/dist/features/resident-tab/status-deriver.d.ts +10 -0
- package/dist/features/resident-tab/status-deriver.js +12 -0
- package/dist/foundation/launch-context/index.d.ts +12 -0
- package/dist/foundation/launch-context/index.js +26 -0
- package/dist/foundation/launch-guardian/exit-watcher.d.ts +14 -0
- package/dist/foundation/launch-guardian/exit-watcher.js +94 -0
- package/dist/foundation/launch-guardian/main.d.ts +7 -1
- package/dist/foundation/launch-guardian/main.js +60 -7
- package/dist/foundation/lifecycle/session-repository-context.js +16 -2
- package/dist/foundation/process-containment/{native-guardian-containment.d.ts → addon-containment.d.ts} +7 -5
- package/dist/foundation/process-containment/addon-containment.js +139 -0
- package/dist/foundation/process-containment/addon-process-inspector.d.ts +11 -0
- package/dist/foundation/process-containment/addon-process-inspector.js +36 -0
- package/dist/foundation/process-containment/addon.d.ts +42 -0
- package/dist/foundation/process-containment/addon.js +57 -0
- package/dist/foundation/process-containment/artifact.d.ts +1 -1
- package/dist/foundation/process-containment/artifact.js +6 -5
- package/dist/foundation/process-containment/contracts.d.ts +2 -0
- package/dist/foundation/process-containment/index.d.ts +4 -4
- package/dist/foundation/process-containment/index.js +3 -3
- package/dist/foundation/process-containment/native-process-inspector.d.ts +2 -1
- package/dist/foundation/process-containment/native-process-inspector.js +10 -10
- package/dist/foundation/release/automatic-update.d.ts +37 -0
- package/dist/foundation/release/automatic-update.js +265 -0
- package/dist/foundation/release/bootstrap.d.ts +7 -9
- package/dist/foundation/release/bootstrap.js +107 -24
- package/dist/foundation/release/cohort-state.d.ts +1 -1
- package/dist/foundation/release/cohort-state.js +6 -3
- package/dist/foundation/release/dependency-certification.js +54 -19
- package/dist/foundation/release/early-accent.d.ts +12 -0
- package/dist/foundation/release/early-accent.js +59 -0
- package/dist/foundation/release/index.d.ts +2 -2
- package/dist/foundation/release/index.js +2 -2
- package/dist/foundation/release/latest-release.d.ts +24 -2
- package/dist/foundation/release/latest-release.js +51 -1
- package/dist/foundation/release/progress-palette.generated.d.ts +137 -3
- package/dist/foundation/release/progress-palette.generated.js +137 -3
- package/dist/foundation/release/release-gc.d.ts +3 -0
- package/dist/foundation/release/release-gc.js +59 -8
- package/dist/foundation/release/release-store.d.ts +12 -0
- package/dist/foundation/release/release-store.js +124 -2
- package/dist/foundation/release/update-execution-lease.d.ts +48 -0
- package/dist/foundation/release/update-execution-lease.js +161 -0
- package/dist/foundation/release/update.d.ts +12 -7
- package/dist/foundation/release/update.js +281 -196
- package/dist/foundation/resident-tabs/bounded-host-command.d.ts +18 -0
- package/dist/foundation/resident-tabs/bounded-host-command.js +39 -0
- package/dist/foundation/resident-tabs/bridge-client.d.ts +70 -0
- package/dist/foundation/resident-tabs/bridge-client.js +321 -0
- package/dist/foundation/resident-tabs/doctor.d.ts +28 -0
- package/dist/foundation/resident-tabs/doctor.js +166 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.d.ts +27 -0
- package/dist/foundation/resident-tabs/draft-checkpoint.js +145 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.d.ts +36 -0
- package/dist/foundation/resident-tabs/ensure-terminal-host.js +151 -0
- package/dist/foundation/resident-tabs/fault-injection.d.ts +8 -0
- package/dist/foundation/resident-tabs/fault-injection.js +26 -0
- package/dist/foundation/resident-tabs/fixture-control.d.ts +7 -0
- package/dist/foundation/resident-tabs/fixture-control.js +35 -0
- package/dist/foundation/resident-tabs/host-entry.d.ts +1 -0
- package/dist/foundation/resident-tabs/host-entry.js +17 -0
- package/dist/foundation/resident-tabs/index.d.ts +12 -0
- package/dist/foundation/resident-tabs/index.js +6 -0
- package/dist/foundation/resident-tabs/maintenance.d.ts +39 -0
- package/dist/foundation/resident-tabs/maintenance.js +75 -0
- package/dist/foundation/resident-tabs/progress-observation-schema.d.ts +34 -0
- package/dist/foundation/resident-tabs/progress-observation-schema.js +1 -0
- package/dist/foundation/resident-tabs/progress-observation.d.ts +4 -0
- package/dist/foundation/resident-tabs/progress-observation.js +150 -0
- package/dist/foundation/resident-tabs/prompt-journal.d.ts +63 -0
- package/dist/foundation/resident-tabs/prompt-journal.js +213 -0
- package/dist/foundation/resident-tabs/protocol.d.ts +12 -0
- package/dist/foundation/resident-tabs/protocol.js +195 -0
- package/dist/foundation/resident-tabs/recovery.d.ts +26 -0
- package/dist/foundation/resident-tabs/recovery.js +205 -0
- package/dist/foundation/resident-tabs/release-holds.d.ts +28 -0
- package/dist/foundation/resident-tabs/release-holds.js +75 -0
- package/dist/foundation/resident-tabs/routes.d.ts +5 -0
- package/dist/foundation/resident-tabs/routes.js +5 -0
- package/dist/foundation/resident-tabs/routing.d.ts +11 -0
- package/dist/foundation/resident-tabs/routing.js +10 -0
- package/dist/foundation/resident-tabs/session-sync.d.ts +14 -0
- package/dist/foundation/resident-tabs/session-sync.js +37 -0
- package/dist/foundation/resident-tabs/settings.d.ts +12 -0
- package/dist/foundation/resident-tabs/settings.js +58 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.d.ts +14 -0
- package/dist/foundation/resident-tabs/terminal-host-artifact.js +78 -0
- package/dist/foundation/resident-tabs/zip-archive.d.ts +9 -0
- package/dist/foundation/resident-tabs/zip-archive.js +56 -0
- package/dist/foundation/session-writer-lock/index.d.ts +30 -0
- package/dist/foundation/session-writer-lock/index.js +203 -0
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/foundation/terminal-cleanup/index.d.ts +1 -1
- package/dist/foundation/terminal-cleanup/index.js +1 -1
- package/dist/foundation/terminal-cleanup/terminal-reset.d.ts +6 -0
- package/dist/foundation/terminal-cleanup/terminal-reset.js +6 -0
- package/dist/integrations/pi/components/editor-interaction.d.ts +2 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +17 -1
- package/dist/integrations/pi/components/modal-frame.js +48 -9
- package/dist/integrations/pi/components/models-dialog.js +4 -52
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +6 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +90 -6
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +4 -4
- package/dist/integrations/pi/components/resources/builtin-themes.js +4 -4
- package/dist/integrations/pi/components/share-operation-dialog.d.ts +2 -0
- package/dist/integrations/pi/components/share-operation-dialog.js +2 -1
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +58 -36
- package/dist/integrations/pi/components/shell-extension-ui.js +79 -60
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +21 -5
- package/dist/integrations/pi/components/shell-hotkey-sections.js +1 -1
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +5 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +18 -4
- package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -1
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +5 -1
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +12 -1
- package/dist/integrations/pi/components/skills-dialog.js +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +17 -4
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +8 -6
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +16 -9
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +4 -4
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +2 -2
- package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/session-footer.js +31 -25
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector.js +8 -7
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +3 -3
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +8 -7
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +10 -9
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +2 -6
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +103 -101
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +9 -9
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -21
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/history/editor-core.js +7 -5
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.d.ts +5 -0
- package/dist/integrations/pi/components/upstream/theme/theme.js +29 -2
- package/dist/integrations/pi/engine/adapter.d.ts +5 -77
- package/dist/integrations/pi/engine/adapter.js +95 -394
- package/dist/integrations/pi/engine/command-dispatch.d.ts +13 -0
- package/dist/integrations/pi/engine/command-dispatch.js +57 -0
- package/dist/integrations/pi/engine/host.js +2 -2
- package/dist/integrations/pi/engine/offline-settings-port.d.ts +23 -0
- package/dist/integrations/pi/engine/offline-settings-port.js +53 -0
- package/dist/integrations/pi/engine/pi-settings-metadata.json +14 -2
- package/dist/integrations/pi/engine/ports.d.ts +56 -0
- package/dist/integrations/pi/engine/ports.js +93 -0
- package/dist/integrations/pi/engine/prompt-suggestions.d.ts +1 -0
- package/dist/integrations/pi/engine/prompt-suggestions.js +40 -0
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/runtime-integration.js +102 -29
- package/dist/integrations/pi/engine/session-runtime.d.ts +0 -6
- package/dist/integrations/pi/engine/session-runtime.js +13 -5
- package/dist/integrations/pi/engine/session-selection.d.ts +3 -0
- package/dist/integrations/pi/engine/session-selection.js +45 -9
- package/dist/integrations/pi/engine/settings-bridge.d.ts +5 -0
- package/dist/integrations/pi/engine/settings-bridge.js +15 -0
- package/dist/integrations/pi/engine/settings-port.js +11 -15
- package/dist/integrations/pi/engine/view-state.d.ts +42 -0
- package/dist/integrations/pi/engine/view-state.js +126 -0
- package/dist/integrations/pi/engine/workflow-contexts.js +10 -1
- package/dist/integrations/pi/engine/workflow-runner.js +73 -4
- package/dist/integrations/pi/engine/workflow-support.d.ts +12 -0
- package/dist/integrations/pi/engine/workflow-support.js +36 -0
- package/dist/integrations/pi/engine/workflows.d.ts +3 -1
- package/dist/integrations/pi/engine/workflows.js +1 -0
- package/dist/integrations/pi/startup-public.js +257 -310
- package/dist/integrations/pi/startup-public.manifest.json +564 -440
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +32 -1
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +5 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +12 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +31 -2
- package/dist/native/darwin-arm64/manifest.json +10 -7
- package/dist/native/darwin-arm64/process-guardian.node +0 -0
- package/dist/native/darwin-arm64/terminal-host-THIRD-PARTY-NOTICES.txt +10500 -0
- package/dist/native/darwin-arm64/terminal-host.manifest.json +46 -0
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +23 -0
- package/dist/native/darwin-x64/process-guardian.node +0 -0
- package/dist/native/darwin-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10737 -0
- package/dist/native/darwin-x64/terminal-host.manifest.json +46 -0
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +10 -7
- package/dist/native/linux-x64/process-guardian.node +0 -0
- package/dist/native/linux-x64/terminal-host-THIRD-PARTY-NOTICES.txt +11226 -0
- package/dist/native/linux-x64/terminal-host.manifest.json +46 -0
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +10 -7
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host-THIRD-PARTY-NOTICES.txt +10757 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +46 -0
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/product-identity.d.ts +3 -3
- package/dist/product-identity.js +4 -4
- package/dist/product-identity.json +16 -2
- package/dist/runtime-payload-inventory.json +6330 -6677
- package/dist/ui/apps/host.d.ts +2 -0
- package/dist/ui/apps/host.js +7 -1
- package/dist/ui/components/dialog-panel.js +35 -30
- package/dist/ui/components/index.d.ts +2 -2
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/line-input.d.ts +4 -1
- package/dist/ui/components/line-input.js +3 -1
- package/dist/ui/components/prompt-input.d.ts +11 -8
- package/dist/ui/components/prompt-input.js +15 -13
- package/dist/ui/components/selection-copy.d.ts +2 -0
- package/dist/ui/components/selection-copy.js +13 -0
- package/dist/ui/components/spans.js +2 -2
- package/dist/ui/components/submitted-prompt.d.ts +9 -6
- package/dist/ui/components/submitted-prompt.js +20 -12
- package/dist/ui/components/surface.js +4 -2
- package/dist/ui/components/text.d.ts +3 -6
- package/dist/ui/components/text.js +6 -8
- package/dist/ui/components/transcript-viewport.d.ts +9 -2
- package/dist/ui/components/transcript-viewport.js +176 -55
- package/dist/ui/components/value-menu.d.ts +6 -3
- package/dist/ui/components/value-menu.js +6 -5
- package/dist/ui/settings/declarations.d.ts +90 -16
- package/dist/ui/settings/declarations.js +67 -14
- package/dist/ui/settings/early-accent.d.ts +81 -0
- package/dist/ui/settings/early-accent.js +50 -0
- package/dist/ui/settings/index.d.ts +4 -2
- package/dist/ui/settings/index.js +2 -1
- package/dist/ui/settings/manager.d.ts +16 -3
- package/dist/ui/settings/manager.js +122 -33
- package/dist/ui/settings/migrations.d.ts +6 -0
- package/dist/ui/settings/migrations.js +73 -0
- package/dist/ui/settings/profile-document.d.ts +26 -0
- package/dist/ui/settings/profile-document.js +164 -0
- package/dist/ui/settings/progress-palette.generated.d.ts +139 -0
- package/dist/ui/settings/progress-palette.generated.js +139 -0
- package/dist/ui/settings/sections.d.ts +4 -0
- package/dist/ui/settings/sections.js +9 -3
- package/docs/architecture/boundaries.md +25 -10
- package/docs/architecture/evidence/resident-tabs/linux-x64.md +37 -0
- package/docs/architecture/evidence/resident-tabs/macos-arm64.md +36 -0
- package/docs/architecture/evidence/resident-tabs/macos-x64.md +35 -0
- package/docs/architecture/evidence/resident-tabs/windows-x64.md +36 -0
- package/docs/architecture/evidence/terminal-host-spike/manual-one-pane-terminal-host.md +70 -0
- package/docs/architecture/evidence/terminal-host-spike/manual-two-by-two-basic-terminal-host.md +32 -0
- package/docs/architecture/evidence/terminal-host-spike/milestone-baseline.json +115 -0
- package/docs/architecture/evidence/terminal-host-spike/owned-ui-foundation-prerequisite.json +48 -0
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-provenance.json +125 -0
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-1.json +56 -0
- package/docs/architecture/evidence/terminal-host-spike/terminal-host-task-5-3.json +174 -0
- package/docs/architecture/evidence/terminal-host-spike/terminal-spike-acceptance-record.json +18 -0
- package/docs/architecture/internal-naming.md +3 -1
- package/docs/architecture/process-guardian-provenance.md +18 -10
- package/docs/architecture/project-structure.md +8 -0
- package/docs/architecture/resident-tabs-requirements.md +18 -0
- package/docs/architecture/resident-tabs.md +648 -0
- package/docs/architecture/response-copy-delivery.md +1 -1
- package/docs/architecture/terminal-host-proof-gate.md +3 -1
- package/docs/architecture/terminal-host-provenance.md +16 -4
- package/docs/architecture/terminal-host-spike-evidence.md +1 -1
- package/docs/architecture/tool-image-presentation.md +2 -1
- package/docs/architecture/toolchain.md +11 -9
- package/docs/ci-release-runbook.md +14 -7
- package/docs/features/launch-profiles.md +11 -8
- package/docs/features/prompt-history.md +41 -20
- package/docs/manual-resident-tabs-acceptance.md +51 -0
- package/docs/openspec-archive-automation.md +49 -22
- package/docs/releases/0.2.6.md +44 -0
- package/docs/validation.md +49 -9
- package/package.json +19 -6
- package/dist/foundation/process-containment/darwin-process-inspector.d.ts +0 -5
- package/dist/foundation/process-containment/darwin-process-inspector.js +0 -7
- package/dist/foundation/process-containment/native-guardian-containment.js +0 -152
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +0 -19
- package/dist/foundation/process-containment/windows-process-inspector.js +0 -56
- package/dist/native/darwin-arm64/process-guardian +0 -0
- package/dist/native/linux-x64/process-guardian +0 -0
- package/dist/native/win32-x64/process-guardian.exe +0 -0
|
@@ -1,27 +1,41 @@
|
|
|
1
1
|
import { ResponseCopyCoordinator } from "./response-copy-coordinator.js";
|
|
2
|
-
import { createResponseCopyExecutor,
|
|
2
|
+
import { createResponseCopyExecutor, responseCopyDestination } from "./response-copy-transport.js";
|
|
3
3
|
import { MAX_COPY_CONTROL_BYTES } from "./response-copy-protocol.js";
|
|
4
4
|
import { PromptHistoryController } from "./prompt-history-controller.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { SessionDialogCoordinator } from "./dialog-coordinator.js";
|
|
6
|
+
import { LoginInteraction } from "./login-interaction.js";
|
|
7
|
+
import { PromptSubmission } from "./prompt-submission.js";
|
|
8
|
+
import { ResidentTabCoordinator } from "./resident-tab-coordinator.js";
|
|
9
|
+
import { SessionConcerns } from "./session-concern.js";
|
|
10
|
+
import { composeExitText, SessionStopLatch } from "./session-lifecycle.js";
|
|
11
|
+
import { openWorkflowSelector } from "./workflow-router.js";
|
|
12
|
+
import { bindSessionSettings } from "./settings-bindings.js";
|
|
13
|
+
import { promptSuggestionSurface, routeResponseToSuggestions, suggestionModelKey, suggestionPresentationBlockReason, } from "./suggestion-surface.js";
|
|
14
|
+
import { CLEAR_EXIT_INTERVAL_MS, boundedCleanup } from "../../foundation/terminal-cleanup/terminal-reset.js";
|
|
15
|
+
import { OWNED_UI_HIDDEN_COMMAND_NAMES } from "../../contracts/owned-ui/index.js";
|
|
16
|
+
import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
|
|
9
17
|
import { ContextualPromptSuggestionController } from "./prompt-suggestion-controller.js";
|
|
10
18
|
import { workflowCommandNames } from "../../integrations/pi/engine/workflows.js";
|
|
11
19
|
import { createPiExtensionUiBridge } from "../../integrations/pi/components/shell-extension-ui.js";
|
|
12
20
|
import { ProgramStatusReporter } from "../../integrations/pi/components/upstream/program-status-reporter.js";
|
|
13
21
|
import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission, skillPrompt, skillsFromCommands, } from "../../integrations/pi/components/skills-command.js";
|
|
14
|
-
import { createPiShellAuthProviderSelector,
|
|
15
|
-
import {
|
|
16
|
-
import { OwnedUiTerminalHost, sessionTerminalHostOptions, } from "./shell-host.js";
|
|
22
|
+
import { createPiShellAuthProviderSelector, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellReloadBox, createPiShellSkillsSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
|
|
23
|
+
import { OwnedUiTerminalHost, componentRuntime, sessionTerminalHostOptions, } from "./shell-host.js";
|
|
17
24
|
import { runImageWorker } from "./image-preparation-client.js";
|
|
18
25
|
import { STREAM_PRESENTATION_INTERVAL_MS, StreamPresentationCoalescer, } from "./stream-presentation-coalescer.js";
|
|
19
26
|
import { writeSystemClipboardText, } from "./system-clipboard.js";
|
|
20
27
|
import { OwnedUiSessionShellRoot, shellResourceEntries, } from "./session-shell-root.js";
|
|
21
28
|
// Invariant: a disposed shell leaves the engine a host that holds no reference back to it.
|
|
22
29
|
const DETACHED_WORKFLOW_INTERACTION = { prompt: async () => null, notify() { } };
|
|
30
|
+
/** Resident Agents refuse the commands their tab bar serves instead, naming where they live. */
|
|
31
|
+
const RESIDENT_UTILITY_TAB_REFUSALS = Object.freeze({
|
|
32
|
+
settings: "Settings is available from the global Settings tab.",
|
|
33
|
+
changelog: "The changelog opens as its own tab from the command palette (Alt+/).",
|
|
34
|
+
name: "Rename the session by renaming its tab (F2).",
|
|
35
|
+
});
|
|
23
36
|
export { OwnedUiSessionShellRoot, } from "./session-shell-root.js";
|
|
24
37
|
export { OwnedUiTerminalHost, sessionTerminalHostOptions } from "./shell-host.js";
|
|
38
|
+
export { formatSessionResumeCommand, quoteCommandArgument } from "./session-lifecycle.js";
|
|
25
39
|
/**
|
|
26
40
|
* Presents one engine session: its transcript root, editor, controllers, submission, workflows, and dialogs.
|
|
27
41
|
* It reaches the terminal only through its terminal host's handle, and paints only while it is attached.
|
|
@@ -33,7 +47,10 @@ export class OwnedUiSessionPresenter {
|
|
|
33
47
|
root;
|
|
34
48
|
#host;
|
|
35
49
|
#terminal;
|
|
36
|
-
#
|
|
50
|
+
#resident;
|
|
51
|
+
#submission;
|
|
52
|
+
#login;
|
|
53
|
+
#concerns;
|
|
37
54
|
#listeners = new Set();
|
|
38
55
|
#unsubscribe;
|
|
39
56
|
#unsubscribePromptSuggestions;
|
|
@@ -44,18 +61,11 @@ export class OwnedUiSessionPresenter {
|
|
|
44
61
|
#unsubscribeSkills;
|
|
45
62
|
#installedCommandSignature = "";
|
|
46
63
|
#promptHistory = null;
|
|
47
|
-
#
|
|
64
|
+
#historyBinding;
|
|
48
65
|
#extensionBridge;
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#dialogId;
|
|
52
|
-
#dialogSource;
|
|
53
|
-
#routeHost;
|
|
54
|
-
#startupRoute;
|
|
55
|
-
#startupRouteTimer;
|
|
56
|
-
#dialogHandle;
|
|
66
|
+
#stop = new SessionStopLatch();
|
|
67
|
+
#dialogs;
|
|
57
68
|
#sequence = 0;
|
|
58
|
-
#editorRevision = 0;
|
|
59
69
|
#started = false;
|
|
60
70
|
#disposed = false;
|
|
61
71
|
#released;
|
|
@@ -77,18 +87,12 @@ export class OwnedUiSessionPresenter {
|
|
|
77
87
|
#streamPresentation;
|
|
78
88
|
#inputCoordination;
|
|
79
89
|
#unsubscribeSettings;
|
|
80
|
-
#
|
|
81
|
-
#unbindTerminalSettings;
|
|
82
|
-
#unbindShutdownSettings;
|
|
90
|
+
#unbindSessionSettings;
|
|
83
91
|
#terminalProgressEnabled = false;
|
|
84
92
|
#programStatus;
|
|
85
|
-
#showImages = true;
|
|
86
|
-
#imageWidthCells = 80;
|
|
87
93
|
#fullscreenExitOutput = "transcript";
|
|
88
|
-
#waitingImages = new Map();
|
|
89
94
|
#lastClearTime = 0;
|
|
90
95
|
#lastEscapeTime = 0;
|
|
91
|
-
#activeLoginDialog;
|
|
92
96
|
#sessionGeneration;
|
|
93
97
|
#sessionBindingGeneration;
|
|
94
98
|
#suggestionModelKey;
|
|
@@ -102,10 +106,7 @@ export class OwnedUiSessionPresenter {
|
|
|
102
106
|
this.#presenters = options.presenters;
|
|
103
107
|
this.#sessionGeneration = this.backend.identity.sessionGeneration;
|
|
104
108
|
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
105
|
-
this.#suggestionModelKey =
|
|
106
|
-
this.#cwd = cwd;
|
|
107
|
-
this.#routeHost = routeHost ?? null;
|
|
108
|
-
this.#startupRoute = options.engine.startupRoute;
|
|
109
|
+
this.#suggestionModelKey = suggestionModelKey(this.backend.session.view());
|
|
109
110
|
this.#customViewport = sessionLayout === "custom-viewport";
|
|
110
111
|
this.#inputCoordination = inputPresentation?.coordination !== false;
|
|
111
112
|
this.#host = host;
|
|
@@ -113,9 +114,6 @@ export class OwnedUiSessionPresenter {
|
|
|
113
114
|
// Invariant: the host's optional members follow the layout, so it is built only once the layout is known.
|
|
114
115
|
this.#dialogHost = this.#createDialogHost();
|
|
115
116
|
this.#lazySelectors = options.diagnostics?.lazySelectors;
|
|
116
|
-
this.#stopped = new Promise(resolve => {
|
|
117
|
-
this.#resolveStopped = resolve;
|
|
118
|
-
});
|
|
119
117
|
let promptSuggestionController = null;
|
|
120
118
|
// Invariant: owned prompt limits and collapsed skills are bare-A1 replacements; comparison profiles keep pinned behavior.
|
|
121
119
|
this.#promptImages = this.#customViewport ? options.promptImages ?? null : null;
|
|
@@ -124,31 +122,24 @@ export class OwnedUiSessionPresenter {
|
|
|
124
122
|
// throw releases them in reverse, so a shell that never finished constructing holds nothing.
|
|
125
123
|
const constructed = [() => host.disconnect(this)];
|
|
126
124
|
try {
|
|
127
|
-
const terminalCopy = terminal !== undefined || hasAsyncClipboardOutput();
|
|
128
125
|
this.#responseCopy = this.#customViewport ? new ResponseCopyCoordinator({
|
|
129
126
|
execute: responseCopy?.execute ?? (this.#copyExecutor = createResponseCopyExecutor({
|
|
130
127
|
...(sharedClipboard === undefined ? {} : { pool: sharedClipboard.copy }),
|
|
131
128
|
...(terminal === undefined && clipboard === undefined ? {} : { destination: "terminal" }),
|
|
132
129
|
...(clipboard?.writeText === undefined ? {} : { writeText: (text, signal) => clipboard.writeText(text, signal) }),
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
130
|
+
// Invariant: the owned host is the route contract. Resident and pseudoterminal children must not infer
|
|
131
|
+
// clipboard reachability from Node's view of stdout, and inactive presenters are fenced here.
|
|
132
|
+
terminal: { submit: async (control, signal) => {
|
|
133
|
+
if (signal.aborted || this.#disposed || !this.#terminal.running || !this.#terminal.attached)
|
|
134
|
+
throw new Error("Copy canceled");
|
|
135
|
+
// Performance: never grow the real terminal's pending buffer with another clipboard payload.
|
|
136
|
+
if (terminal === undefined && process.stdout.writableLength + control.length > MAX_COPY_CONTROL_BYTES) {
|
|
137
|
+
throw new Error("Clipboard terminal is busy");
|
|
138
|
+
}
|
|
139
|
+
this.#terminal.writeControl(control);
|
|
140
|
+
} },
|
|
142
141
|
})),
|
|
143
142
|
...(responseCopy?.onEvent === undefined ? {} : { onEvent: responseCopy.onEvent }),
|
|
144
|
-
onFailure: result => {
|
|
145
|
-
if (this.#disposed || !this.#terminal.running)
|
|
146
|
-
return;
|
|
147
|
-
this.root.appendWorkflowStatus(result.outcome === "timed-out" ? "Copy timed out; the clipboard did not respond."
|
|
148
|
-
: result.failure === "size" ? "Selection exceeds this clipboard route's size limit."
|
|
149
|
-
: "Could not copy the selection; the clipboard is unavailable.");
|
|
150
|
-
this.#terminal.requestRender();
|
|
151
|
-
},
|
|
152
143
|
}) : null;
|
|
153
144
|
constructed.push(() => { this.#responseCopy?.dispose(); this.#copyExecutor?.dispose(); });
|
|
154
145
|
const readClipboard = async (signal) => {
|
|
@@ -183,10 +174,12 @@ export class OwnedUiSessionPresenter {
|
|
|
183
174
|
replacementSurfaceActive: !this.root.usesDefaultInputSurface(),
|
|
184
175
|
}),
|
|
185
176
|
enableDockInputReuse: inputPresentation?.viewportReuse !== false,
|
|
177
|
+
footerSessionName: options.engine.residentTab === undefined,
|
|
178
|
+
hideUtilityTabCommands: options.engine.residentTab !== undefined,
|
|
186
179
|
persistentHistory: this.#customViewport && promptHistory !== undefined,
|
|
187
180
|
...(promptHistory === undefined ? {} : { historyEditor: promptHistory.editor }),
|
|
188
|
-
onSubmit: text => { void this.submit(text).catch(() => this.#
|
|
189
|
-
onPasteRejected: error => this.#
|
|
181
|
+
onSubmit: text => { void this.submit(text).catch(() => this.#submission.reportError()); },
|
|
182
|
+
onPasteRejected: error => this.#submission.reportError(error),
|
|
190
183
|
onInterrupt: () => { void this.interrupt(); },
|
|
191
184
|
onClear: () => { void this.clearOrExit(); },
|
|
192
185
|
onExit: () => { void this.shutdown(); },
|
|
@@ -198,10 +191,11 @@ export class OwnedUiSessionPresenter {
|
|
|
198
191
|
this.#terminal.requestRender();
|
|
199
192
|
},
|
|
200
193
|
onMessageCopy: () => { void this.runWorkflow({ command: "copy", argument: "" }); },
|
|
201
|
-
onFollowUp: () => { void this.queueFollowUp().catch(() => this.#
|
|
194
|
+
onFollowUp: () => { void this.queueFollowUp().catch(() => this.#submission.reportError()); },
|
|
202
195
|
onDequeue: () => this.restoreQueuedInput(),
|
|
203
196
|
onEditorChange: text => {
|
|
204
|
-
this.#
|
|
197
|
+
this.#submission.noteEditorChange();
|
|
198
|
+
this.#resident.checkpoint(text);
|
|
205
199
|
promptSuggestionController?.abortPending();
|
|
206
200
|
if (text.length === 0) {
|
|
207
201
|
// Concurrency: the editor synchronizes deletion-owned autocomplete after onChange.
|
|
@@ -218,9 +212,9 @@ export class OwnedUiSessionPresenter {
|
|
|
218
212
|
promptSuggestionController?.invalidate();
|
|
219
213
|
this.#promptHistory?.synchronize();
|
|
220
214
|
},
|
|
221
|
-
onCopyText: text => {
|
|
215
|
+
onCopyText: (text, cut) => {
|
|
222
216
|
if (this.#responseCopy !== null) {
|
|
223
|
-
|
|
217
|
+
this.#trackCopy(this.#responseCopy.submitText(text), cut ? undefined : text.length);
|
|
224
218
|
return;
|
|
225
219
|
}
|
|
226
220
|
const write = () => {
|
|
@@ -263,8 +257,27 @@ export class OwnedUiSessionPresenter {
|
|
|
263
257
|
// Invariant: the presenter owns its root (theme subscription and helper clients) and disposes it once the
|
|
264
258
|
// terminal no longer renders it.
|
|
265
259
|
constructed.push(() => this.root.dispose());
|
|
260
|
+
this.#resident = new ResidentTabCoordinator(options.engine.residentTab, { root: this.root });
|
|
261
|
+
constructed.push(() => this.#resident.dispose());
|
|
262
|
+
const startupRoute = options.engine.startupRoute;
|
|
263
|
+
this.#dialogs = new SessionDialogCoordinator({
|
|
264
|
+
terminal: this.#terminal,
|
|
265
|
+
root: this.root,
|
|
266
|
+
routeHost: routeHost ?? null,
|
|
267
|
+
disposed: () => this.#disposed,
|
|
268
|
+
exit: () => { void this.shutdown(); },
|
|
269
|
+
routeClosed: route => this.#resident.closeOwnedRoute(route),
|
|
270
|
+
...(startupRoute === undefined ? {} : { startupRoute }),
|
|
271
|
+
});
|
|
272
|
+
constructed.push(() => this.#dialogs.dispose());
|
|
266
273
|
const initialPiSettings = this.backend.settings.snapshot();
|
|
267
274
|
this.#programStatus = new ProgramStatusReporter(status => this.#terminal.setProgramStatus(status), () => this.view().status.footer?.sessionName ?? undefined);
|
|
275
|
+
this.#login = new LoginInteraction({
|
|
276
|
+
terminal: this.#terminal, root: this.root, programStatus: this.#programStatus,
|
|
277
|
+
syncProgress: () => this.#syncTerminalProgress(this.view()),
|
|
278
|
+
});
|
|
279
|
+
this.#submission = this.#createSubmission(cwd);
|
|
280
|
+
this.#resident.restoreInput(options.history?.imageSidecar);
|
|
268
281
|
this.#reloadPresentation = reloadPresentation;
|
|
269
282
|
const presentationInterval = streamPresentationOptions?.intervalMs ?? STREAM_PRESENTATION_INTERVAL_MS;
|
|
270
283
|
this.#streamPresentation = streamPresentationOptions?.scheduler === undefined
|
|
@@ -276,18 +289,7 @@ export class OwnedUiSessionPresenter {
|
|
|
276
289
|
generator: promptSuggestionOptions.generator,
|
|
277
290
|
enabled: promptSuggestionOptions.enabled(),
|
|
278
291
|
...(promptSuggestionOptions.diagnostics === undefined ? {} : { diagnostics: promptSuggestionOptions.diagnostics }),
|
|
279
|
-
surface:
|
|
280
|
-
canPresent: identity => this.#promptSuggestionPresentationBlockReason(identity) === null,
|
|
281
|
-
presentationBlockReason: identity => this.#promptSuggestionPresentationBlockReason(identity),
|
|
282
|
-
present: text => {
|
|
283
|
-
if (!this.root.canPresentPromptSuggestion())
|
|
284
|
-
return false;
|
|
285
|
-
this.root.setPromptSuggestion(text);
|
|
286
|
-
return true;
|
|
287
|
-
},
|
|
288
|
-
clear: () => this.root.setPromptSuggestion(null),
|
|
289
|
-
requestRender: () => this.#terminal.requestRender(),
|
|
290
|
-
},
|
|
292
|
+
surface: promptSuggestionSurface(this.root, identity => this.#promptSuggestionPresentationBlockReason(identity), () => this.#terminal.requestRender()),
|
|
291
293
|
});
|
|
292
294
|
this.#promptSuggestions = promptSuggestionController;
|
|
293
295
|
constructed.push(() => this.#promptSuggestions?.dispose());
|
|
@@ -310,35 +312,7 @@ export class OwnedUiSessionPresenter {
|
|
|
310
312
|
this.#terminal.requestRender();
|
|
311
313
|
});
|
|
312
314
|
constructed.push(this.#unsubscribeSkills);
|
|
313
|
-
this.#terminal.setHardwareCursor(initialPiSettings.showHardwareCursor);
|
|
314
|
-
this.#terminal.setClearOnShrink(initialPiSettings.clearOnShrink);
|
|
315
|
-
this.#terminalProgressEnabled = initialPiSettings.showTerminalProgress;
|
|
316
315
|
this.#fullscreenExitOutput = initialPiSettings.fullscreenExitOutput;
|
|
317
|
-
// Invariant: bare A1 prints only the resume hint at exit, so the pinned exit-output
|
|
318
|
-
// choice is hidden there and cannot be bound; the comparison profile binds and honors it.
|
|
319
|
-
this.#unbindShutdownSettings = this.backend.settings.productMode === "bare" ? () => { } : this.backend.settings.bindOwner("shutdown", {
|
|
320
|
-
fullscreenExitOutput: { apply() { } },
|
|
321
|
-
});
|
|
322
|
-
constructed.push(this.#unbindShutdownSettings);
|
|
323
|
-
this.#unbindTerminalSettings = this.backend.settings.bindOwner("terminal", {
|
|
324
|
-
showHardwareCursor: { apply: value => {
|
|
325
|
-
if (typeof value !== "boolean")
|
|
326
|
-
throw new TypeError("Hardware cursor setting is invalid");
|
|
327
|
-
this.#terminal.setHardwareCursor(value);
|
|
328
|
-
} },
|
|
329
|
-
clearOnShrink: { apply: value => {
|
|
330
|
-
if (typeof value !== "boolean")
|
|
331
|
-
throw new TypeError("Clear-on-shrink setting is invalid");
|
|
332
|
-
this.#terminal.setClearOnShrink(value);
|
|
333
|
-
} },
|
|
334
|
-
showTerminalProgress: { apply: value => {
|
|
335
|
-
if (typeof value !== "boolean")
|
|
336
|
-
throw new TypeError("Terminal progress setting is invalid");
|
|
337
|
-
this.#terminalProgressEnabled = value;
|
|
338
|
-
this.#syncTerminalProgress(this.view());
|
|
339
|
-
} },
|
|
340
|
-
});
|
|
341
|
-
constructed.push(this.#unbindTerminalSettings);
|
|
342
316
|
const applyViewportSettings = () => {
|
|
343
317
|
const snapshot = viewportSettings?.snapshot();
|
|
344
318
|
this.root.setViewportConfig(snapshot ?? {
|
|
@@ -348,74 +322,23 @@ export class OwnedUiSessionPresenter {
|
|
|
348
322
|
});
|
|
349
323
|
};
|
|
350
324
|
applyViewportSettings();
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
325
|
+
const unsubscribeViewport = this.#customViewport && viewportSettings ? viewportSettings.onChange(settings => this.root.setViewportConfig(settings)) : () => { };
|
|
326
|
+
const unsubscribeHistoryScope = this.#customViewport && promptHistory?.onScopeChange?.(scope => this.#rebindTranscript(scope));
|
|
327
|
+
this.#unsubscribeSettings = () => { unsubscribeViewport(); if (unsubscribeHistoryScope)
|
|
328
|
+
unsubscribeHistoryScope(); };
|
|
354
329
|
constructed.push(this.#unsubscribeSettings);
|
|
355
|
-
this.
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
this.#
|
|
365
|
-
editorPaddingX: { apply: value => {
|
|
366
|
-
if (typeof value !== "number")
|
|
367
|
-
throw new TypeError("Editor padding is invalid");
|
|
368
|
-
this.root.setEditorPaddingX(value);
|
|
369
|
-
} },
|
|
370
|
-
autocompleteMaxVisible: { apply: value => {
|
|
371
|
-
if (typeof value !== "number")
|
|
372
|
-
throw new TypeError("Autocomplete maximum is invalid");
|
|
373
|
-
this.root.setAutocompleteMaxVisible(value);
|
|
374
|
-
} },
|
|
375
|
-
outputPad: { apply: value => {
|
|
376
|
-
if (value !== 0 && value !== 1)
|
|
377
|
-
throw new TypeError("Output padding is invalid");
|
|
378
|
-
this.root.setOutputPad(value);
|
|
379
|
-
} },
|
|
380
|
-
hideThinkingBlock: { apply: value => {
|
|
381
|
-
if (typeof value !== "boolean")
|
|
382
|
-
throw new TypeError("Thinking-block visibility is invalid");
|
|
383
|
-
this.root.setHideThinkingBlock(value);
|
|
384
|
-
} },
|
|
385
|
-
mermaidRenderingMode: { apply: value => {
|
|
386
|
-
if (value !== "off" && value !== "final" && value !== "streaming")
|
|
387
|
-
throw new TypeError("Mermaid mode is invalid");
|
|
388
|
-
this.root.setMermaidRenderingMode(value);
|
|
389
|
-
} },
|
|
390
|
-
showImages: { apply: value => {
|
|
391
|
-
if (typeof value !== "boolean")
|
|
392
|
-
throw new TypeError("Image visibility is invalid");
|
|
393
|
-
this.#showImages = value;
|
|
394
|
-
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
395
|
-
} },
|
|
396
|
-
imageWidthCells: { apply: value => {
|
|
397
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1)
|
|
398
|
-
throw new TypeError("Image width is invalid");
|
|
399
|
-
this.#imageWidthCells = value;
|
|
400
|
-
this.root.setImagePresentation(this.#showImages, this.#imageWidthCells);
|
|
401
|
-
} },
|
|
402
|
-
...(this.#customViewport ? { fullscreenCopyOnSelect: { apply: (value) => {
|
|
403
|
-
if (typeof value !== "boolean")
|
|
404
|
-
throw new TypeError("Fullscreen copy-on-select setting is invalid");
|
|
405
|
-
this.root.setFullscreenCopyOnSelect(value);
|
|
406
|
-
} } } : {
|
|
407
|
-
fullscreenWheelScrollLines: { apply: (value) => {
|
|
408
|
-
if (value !== "auto" && (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 100)) {
|
|
409
|
-
throw new TypeError("Fullscreen wheel-scroll distance is invalid");
|
|
410
|
-
}
|
|
411
|
-
this.#terminal.setWheelScrollLines(value);
|
|
412
|
-
} },
|
|
413
|
-
}),
|
|
414
|
-
});
|
|
415
|
-
constructed.push(this.#unbindPiSettings);
|
|
330
|
+
this.#unbindSessionSettings = bindSessionSettings(this.backend.settings, initialPiSettings, {
|
|
331
|
+
terminal: this.#terminal,
|
|
332
|
+
root: this.root,
|
|
333
|
+
setTerminalProgress: enabled => {
|
|
334
|
+
this.#terminalProgressEnabled = enabled;
|
|
335
|
+
if (this.#terminal.running)
|
|
336
|
+
this.#syncTerminalProgress(this.view());
|
|
337
|
+
},
|
|
338
|
+
}, { customViewport: this.#customViewport });
|
|
339
|
+
constructed.push(this.#unbindSessionSettings);
|
|
416
340
|
if (this.#customViewport && promptHistory !== undefined) {
|
|
417
|
-
this.#
|
|
418
|
-
const sidecar = this.#promptHistoryImageSidecar;
|
|
341
|
+
this.#historyBinding = { options: promptHistory, scope: promptHistory.scope ?? "unique", sessionId: promptHistory.sessionId, imageSidecar: promptHistory.imageSidecar };
|
|
419
342
|
this.#promptHistory = new PromptHistoryController({
|
|
420
343
|
editor: this.root.editor,
|
|
421
344
|
store: promptHistory.store,
|
|
@@ -423,57 +346,33 @@ export class OwnedUiSessionPresenter {
|
|
|
423
346
|
fallback: this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []),
|
|
424
347
|
active: () => this.root.usesDefaultInputSurface(),
|
|
425
348
|
render: () => this.#terminal.requestRender(),
|
|
426
|
-
rehydrate: value => this.root.rehydrateHistoryText(value, id =>
|
|
349
|
+
rehydrate: value => this.root.rehydrateHistoryText(value, id => this.#historyBinding?.imageSidecar?.readAttachment(id) ?? null),
|
|
427
350
|
});
|
|
428
351
|
constructed.push(() => { void this.#promptHistory?.close().catch(() => false); });
|
|
429
352
|
}
|
|
430
|
-
this.#extensionBridge =
|
|
431
|
-
runtime: {
|
|
432
|
-
getColumns: () => this.#terminal.viewport().columns,
|
|
433
|
-
getRows: () => this.#terminal.viewport().rows,
|
|
434
|
-
requestRender: () => this.#terminal.requestRender(),
|
|
435
|
-
},
|
|
436
|
-
agentDir: this.backend.identity.agentDir,
|
|
437
|
-
setInputSurface: component => this.root.setInputSurface(component, true, "opaque"),
|
|
438
|
-
setProgramStatusBlocked: status => this.#programStatus.setBlocked("extension-dialog", status),
|
|
439
|
-
showOverlay: (component, overlayOptions) => this.#terminal.showOverlay(component, overlayOptions),
|
|
440
|
-
listenInput: handler => this.#terminal.addInputListener(handler),
|
|
441
|
-
replaceWidget: (key, component, placement) => this.root.setExtensionWidget(key, component, placement),
|
|
442
|
-
replaceHeader: component => this.root.setExtensionHeader(component),
|
|
443
|
-
replaceFooter: component => this.root.setExtensionFooter(component),
|
|
444
|
-
setStatus: (key, text) => this.root.setExtensionStatus(key, text),
|
|
445
|
-
setWorking: (message, visible) => this.root.setExtensionWorking(message, visible),
|
|
446
|
-
notify: (message, type) => this.root.addExtensionNotification(message, type),
|
|
447
|
-
setTitle: title => this.#terminal.setTitle(title),
|
|
448
|
-
getEditorText: () => this.root.editor.getText(),
|
|
449
|
-
setEditorText: text => this.root.editor.setText(text),
|
|
450
|
-
pasteToEditor: text => this.root.editor.insertText(text),
|
|
451
|
-
addAutocompleteProvider: factory => this.root.editor.addAutocompleteProvider(factory),
|
|
452
|
-
setCustomEditor: component => this.root.setInputSurface(component, true, "opaque"),
|
|
453
|
-
getFooterData: () => this.root.extensionFooterData(),
|
|
454
|
-
getToolsExpanded: () => this.root.toolsExpanded,
|
|
455
|
-
setToolsExpanded: expanded => this.root.setToolsExpanded(expanded),
|
|
456
|
-
});
|
|
353
|
+
this.#extensionBridge = this.#createExtensionBridge();
|
|
457
354
|
constructed.push(() => this.#extensionBridge.dispose());
|
|
355
|
+
this.#concerns = this.#createConcerns();
|
|
458
356
|
constructed.push(() => this.backend.workflows.setWorkflowInteractionHost(DETACHED_WORKFLOW_INTERACTION));
|
|
459
357
|
this.backend.workflows.setWorkflowInteractionHost({
|
|
460
|
-
startLogin: request => this.#
|
|
461
|
-
prompt: request => this.#
|
|
462
|
-
notify: event => this.#
|
|
358
|
+
startLogin: request => this.#login.start(request),
|
|
359
|
+
prompt: request => this.#login.requestInput(request),
|
|
360
|
+
notify: event => this.#login.notify(event),
|
|
463
361
|
publish: message => {
|
|
464
362
|
this.root.appendWorkflowMessage(message);
|
|
465
363
|
this.#terminal.requestRender();
|
|
466
364
|
},
|
|
467
|
-
finishLogin: () => this.#
|
|
365
|
+
finishLogin: () => this.#login.finish(),
|
|
468
366
|
});
|
|
469
367
|
this.#installAutocompleteCommands();
|
|
470
368
|
this.#unsubscribe = this.backend.session.onEvent(event => {
|
|
471
369
|
// Invariant: once disposal begins no event touches the view; only the stop still settles the shell.
|
|
472
370
|
if (this.#disposed) {
|
|
473
371
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
474
|
-
this.#
|
|
372
|
+
this.#stop.settleAfter(() => this.dispose());
|
|
475
373
|
return;
|
|
476
374
|
}
|
|
375
|
+
this.#resident.observeLifecycle(event);
|
|
477
376
|
// Performance: a streamed chunk names one block, and touching only that block is what keeps the
|
|
478
377
|
// cost of a chunk the same in a long session as in a new one. Everything else
|
|
479
378
|
// resynchronizes the view, which is cheap next to re-reading the transcript.
|
|
@@ -489,28 +388,14 @@ export class OwnedUiSessionPresenter {
|
|
|
489
388
|
}
|
|
490
389
|
if (event.type === "assistant-message-completed") {
|
|
491
390
|
this.root.noteCompletedAssistantMessage();
|
|
492
|
-
|
|
493
|
-
sessionId: event.sessionId,
|
|
494
|
-
sessionGeneration: event.sessionGeneration,
|
|
495
|
-
runSequence: event.runSequence,
|
|
496
|
-
responseSequence: event.responseSequence,
|
|
497
|
-
model: event.model,
|
|
498
|
-
};
|
|
499
|
-
if (event.model === null)
|
|
500
|
-
this.#promptSuggestions?.skip(identity, "no-model");
|
|
501
|
-
else
|
|
502
|
-
this.#promptSuggestions?.consider({ ...identity, model: event.model }, !event.successful ? "failed-response"
|
|
503
|
-
: event.toolContinuation || event.stopReason === "toolUse" ? "tool-continuation"
|
|
504
|
-
: event.stopReason !== "stop" ? "incomplete-response"
|
|
505
|
-
: event.assistantMessageCount < 2 ? "early-conversation"
|
|
506
|
-
: this.root.promptSuggestionPrepareBlockReason());
|
|
391
|
+
routeResponseToSuggestions(this.#promptSuggestions, event, () => this.root.promptSuggestionPrepareBlockReason());
|
|
507
392
|
}
|
|
508
393
|
const semanticOnly = event.type === "agent-run-started" || event.type === "assistant-message-completed";
|
|
509
394
|
const view = event.type === "transcript-block" && this.#sessionGeneration === this.backend.identity.sessionGeneration
|
|
510
395
|
? this.#syncBlock(event.block)
|
|
511
396
|
: semanticOnly ? this.view() : this.#syncView();
|
|
512
397
|
this.#syncTerminalProgress(view);
|
|
513
|
-
const currentModelKey =
|
|
398
|
+
const currentModelKey = suggestionModelKey(view);
|
|
514
399
|
if (this.backend.identity.sessionGeneration !== this.#sessionGeneration || currentModelKey !== this.#suggestionModelKey) {
|
|
515
400
|
this.#promptSuggestions?.invalidate();
|
|
516
401
|
this.#suggestionModelKey = currentModelKey;
|
|
@@ -519,24 +404,10 @@ export class OwnedUiSessionPresenter {
|
|
|
519
404
|
this.#promptSuggestions?.invalidate();
|
|
520
405
|
}
|
|
521
406
|
if (event.type === "agent-run-settled") {
|
|
522
|
-
|
|
523
|
-
sessionId: event.sessionId,
|
|
524
|
-
sessionGeneration: event.sessionGeneration,
|
|
525
|
-
runSequence: event.runSequence,
|
|
526
|
-
responseSequence: event.responseSequence,
|
|
527
|
-
model: event.model,
|
|
528
|
-
};
|
|
529
|
-
if (event.model === null)
|
|
530
|
-
this.#promptSuggestions?.skip(identity, "no-model");
|
|
531
|
-
else
|
|
532
|
-
this.#promptSuggestions?.settle({ ...identity, model: event.model }, !event.successful ? "failed-response"
|
|
533
|
-
: event.toolContinuation || event.stopReason === "toolUse" ? "tool-continuation"
|
|
534
|
-
: event.stopReason !== "stop" ? "incomplete-response"
|
|
535
|
-
: event.assistantMessageCount < 2 ? "early-conversation"
|
|
536
|
-
: this.root.promptSuggestionPrepareBlockReason());
|
|
407
|
+
routeResponseToSuggestions(this.#promptSuggestions, event, () => this.root.promptSuggestionPrepareBlockReason());
|
|
537
408
|
}
|
|
538
409
|
if (event.type === "session-lifecycle" && event.lifecycle === "stopped")
|
|
539
|
-
this.#
|
|
410
|
+
this.#stop.settleAfter(() => this.dispose());
|
|
540
411
|
});
|
|
541
412
|
constructed.push(this.#unsubscribe);
|
|
542
413
|
this.#unbindClipboardWriter = this.#responseCopy === null ? () => { } : this.backend.extensions.bindClipboardWriter(async (text) => {
|
|
@@ -549,7 +420,7 @@ export class OwnedUiSessionPresenter {
|
|
|
549
420
|
});
|
|
550
421
|
constructed.push(this.#unbindClipboardWriter);
|
|
551
422
|
if (this.backend.session.view().lifecycle === "stopped")
|
|
552
|
-
this.#
|
|
423
|
+
this.#stop.resolve();
|
|
553
424
|
}
|
|
554
425
|
catch (error) {
|
|
555
426
|
for (const release of constructed.reverse()) {
|
|
@@ -626,29 +497,23 @@ export class OwnedUiSessionPresenter {
|
|
|
626
497
|
const routed = this.root.handleViewportPreInput(data, true, Date.now(), this.root.usesDefaultInputSurface() && !this.#terminal.hasFocusedOverlay());
|
|
627
498
|
if (routed.copySelection !== undefined) {
|
|
628
499
|
const snapshot = routed.copySelection;
|
|
629
|
-
|
|
630
|
-
const intent = ++this.#copyIntentSequence;
|
|
631
|
-
void this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite).then(result => {
|
|
632
|
-
if (this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
633
|
-
|| result.outcome !== "delivered" && result.outcome !== "submitted-unverified")
|
|
634
|
-
return;
|
|
635
|
-
if (acknowledgement.hasNonWhitespace) {
|
|
636
|
-
this.root.showCopyAcknowledgement(`copied ${acknowledgement.characters} chars to clipboard`);
|
|
637
|
-
}
|
|
638
|
-
});
|
|
500
|
+
this.#trackCopy(this.#responseCopy?.submit(snapshot, this.#pendingClipboardWrite), copyCharacters(snapshot));
|
|
639
501
|
}
|
|
640
502
|
if (!routed.consumed)
|
|
641
503
|
return routed.data === data ? undefined : { data: routed.data };
|
|
642
504
|
return routed.data.length === 0 ? { consume: true } : { data: routed.data };
|
|
643
505
|
}
|
|
506
|
+
#trackCopy(delivery, count) {
|
|
507
|
+
const intent = ++this.#copyIntentSequence;
|
|
508
|
+
void delivery?.then(({ outcome }) => {
|
|
509
|
+
if (count === undefined || this.#disposed || !this.#terminal.running || intent !== this.#copyIntentSequence
|
|
510
|
+
|| outcome !== "delivered" && outcome !== "submitted-unverified")
|
|
511
|
+
return;
|
|
512
|
+
this.root.showCopyAcknowledgement(`copied ${count} chars to clipboard`);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
644
515
|
#promptSuggestionPresentationBlockReason(identity) {
|
|
645
|
-
|
|
646
|
-
if (this.#disposed)
|
|
647
|
-
return "disposed";
|
|
648
|
-
if (identity.sessionId !== view.sessionId || identity.sessionGeneration !== this.backend.identity.sessionGeneration
|
|
649
|
-
|| modelKey(view) !== `${identity.model.providerId}/${identity.model.modelId}`)
|
|
650
|
-
return "stale-identity";
|
|
651
|
-
return this.root.promptSuggestionPresentationBlockReason();
|
|
516
|
+
return suggestionPresentationBlockReason(identity, { disposed: this.#disposed, view: this.view(), sessionGeneration: this.backend.identity.sessionGeneration }, () => this.root.promptSuggestionPresentationBlockReason());
|
|
652
517
|
}
|
|
653
518
|
start() {
|
|
654
519
|
if (this.#started)
|
|
@@ -663,7 +528,7 @@ export class OwnedUiSessionPresenter {
|
|
|
663
528
|
setImmediate(() => {
|
|
664
529
|
if (this.#disposed || !this.#customViewport)
|
|
665
530
|
return;
|
|
666
|
-
this.#presentStartupRoute();
|
|
531
|
+
this.#dialogs.presentStartupRoute();
|
|
667
532
|
void this.#loadLazySelectors().then(selectors => selectors.prepare()).catch(() => { });
|
|
668
533
|
this.root.warmPastePreparation();
|
|
669
534
|
this.#copyExecutor?.warm();
|
|
@@ -675,127 +540,14 @@ export class OwnedUiSessionPresenter {
|
|
|
675
540
|
return () => this.#listeners.delete(listener);
|
|
676
541
|
}
|
|
677
542
|
waitUntilStopped() {
|
|
678
|
-
return this.#stopped;
|
|
543
|
+
return this.#stop.stopped;
|
|
679
544
|
}
|
|
680
545
|
async submit(text) {
|
|
681
|
-
return this.#
|
|
682
|
-
}
|
|
683
|
-
#submitWhenReady(draft, action) {
|
|
684
|
-
const previous = this.#waitingImages.get(draft);
|
|
685
|
-
if (previous !== undefined)
|
|
686
|
-
return previous.result;
|
|
687
|
-
if (!this.root.hasPendingPastes(draft))
|
|
688
|
-
return this.#guardSubmission(draft, action);
|
|
689
|
-
const controller = new AbortController();
|
|
690
|
-
const generation = this.backend.identity.sessionGeneration;
|
|
691
|
-
const result = this.#guardSubmission(draft, async () => {
|
|
692
|
-
await this.root.waitForPromptPastes(draft, controller.signal);
|
|
693
|
-
if (controller.signal.aborted || this.#disposed || this.backend.identity.sessionGeneration !== generation)
|
|
694
|
-
return rejected("image submission canceled");
|
|
695
|
-
return action();
|
|
696
|
-
}).finally(() => {
|
|
697
|
-
this.#waitingImages.delete(draft);
|
|
698
|
-
if (!this.#disposed)
|
|
699
|
-
this.#showWaitingImages();
|
|
700
|
-
});
|
|
701
|
-
this.#waitingImages.set(draft, { controller, result });
|
|
702
|
-
if (this.root.editor.getText() === draft)
|
|
703
|
-
this.root.editor.setText("");
|
|
704
|
-
this.#showWaitingImages();
|
|
705
|
-
return result;
|
|
706
|
-
}
|
|
707
|
-
#showWaitingImages() {
|
|
708
|
-
const count = this.#waitingImages.size;
|
|
709
|
-
this.root.setExtensionWidget("owned-image-preparation", count === 0 ? null : {
|
|
710
|
-
render: width => [...renderPiShellStatusText(`Waiting for images (${count} submission${count === 1 ? "" : "s"}) — Esc cancels; dequeue restores`, width)],
|
|
711
|
-
invalidate: () => { },
|
|
712
|
-
}, "aboveEditor");
|
|
713
|
-
this.#terminal.requestRender();
|
|
714
|
-
}
|
|
715
|
-
#cancelWaitingImages() {
|
|
716
|
-
for (const item of this.#waitingImages.values())
|
|
717
|
-
item.controller.abort();
|
|
718
|
-
}
|
|
719
|
-
#rememberInput(text, kind) {
|
|
720
|
-
this.root.editor.addToHistory(text);
|
|
721
|
-
if (this.#promptHistory !== null) {
|
|
722
|
-
const reusable = this.root.prepareHistoryText(text);
|
|
723
|
-
if (reusable.length === 0)
|
|
724
|
-
this.#promptHistory.rememberRecovery(text);
|
|
725
|
-
else {
|
|
726
|
-
this.#persistImageSidecars(text);
|
|
727
|
-
this.#promptHistory.capture(reusable, kind, this.#cwd, this.backend.identity.sessionId);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
// Rationale: write the image chip payloads referenced by an editor draft to the sidecar
|
|
732
|
-
// directory before their history row commits. Missing sidecar dependency = silent skip; the
|
|
733
|
-
// recall path treats an absent sidecar the same as a corrupt one.
|
|
734
|
-
#persistImageSidecars(text) {
|
|
735
|
-
const sidecar = this.#promptHistoryImageSidecar;
|
|
736
|
-
if (sidecar === undefined)
|
|
737
|
-
return;
|
|
738
|
-
for (const record of this.root.imageChipAttachmentsFromEditor(text)) {
|
|
739
|
-
sidecar.write(record.id, { tag: record.tag, data: record.image.data, mimeType: record.image.mimeType, savedAt: new Date().toISOString() });
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
async #submit(text) {
|
|
743
|
-
this.#promptSuggestions?.invalidate();
|
|
744
|
-
const displayInput = text.trim();
|
|
745
|
-
if (!displayInput)
|
|
746
|
-
return { outcome: "completed", diagnostic: null };
|
|
747
|
-
if (displayInput.startsWith("/"))
|
|
748
|
-
return this.#slashCommand(displayInput);
|
|
749
|
-
const prepared = this.root.preparePromptSubmission(displayInput);
|
|
750
|
-
assertPromptImages(prepared.images);
|
|
751
|
-
const input = prepared.text.trim();
|
|
752
|
-
if (input.startsWith("!")) {
|
|
753
|
-
const excludeFromContext = input.startsWith("!!");
|
|
754
|
-
const command = input.slice(excludeFromContext ? 2 : 1).trim();
|
|
755
|
-
if (command) {
|
|
756
|
-
this.#rememberInput(displayInput, "bash");
|
|
757
|
-
try {
|
|
758
|
-
const result = await this.backend.workflows.executeBashWorkflow(command, excludeFromContext);
|
|
759
|
-
const workflow = {
|
|
760
|
-
command: "debug",
|
|
761
|
-
outcome: result.cancelled ? "cancelled" : result.exitCode === 0 || result.exitCode === undefined ? "completed" : "failed",
|
|
762
|
-
message: result.cancelled ? "Bash command cancelled" : `Bash exited ${result.exitCode ?? 0}: ${command}`,
|
|
763
|
-
...(result.output ? { detail: result.output } : {}),
|
|
764
|
-
};
|
|
765
|
-
this.root.appendWorkflowResult(workflow);
|
|
766
|
-
this.#terminal.requestRender();
|
|
767
|
-
return workflow.outcome === "failed" ? rejected(workflow.message) : { outcome: "completed", diagnostic: null };
|
|
768
|
-
}
|
|
769
|
-
catch (error) {
|
|
770
|
-
const message = `Bash command failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
771
|
-
this.root.appendWorkflowResult({ command: "debug", outcome: "failed", message });
|
|
772
|
-
this.#terminal.requestRender();
|
|
773
|
-
return rejected(message);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
// Compatibility: match interactive Pi: input during compaction is queued steering; the engine
|
|
778
|
-
// shows it in the pending rows and delivers it when compaction ends.
|
|
779
|
-
const type = this.view().lifecycle === "busy" ? "steer" : "prompt";
|
|
780
|
-
this.#rememberInput(this.root.omitUnsentPromptImages(displayInput).trim(), type);
|
|
781
|
-
this.root.resumeViewportFollowing();
|
|
782
|
-
const finishSending = prepared.images.length === 0 ? () => { } : this.root.beginImageSubmissionStatus();
|
|
783
|
-
try {
|
|
784
|
-
return await this.#execute({
|
|
785
|
-
type,
|
|
786
|
-
correlationId: this.#correlation(type),
|
|
787
|
-
sessionId: this.backend.identity.sessionId,
|
|
788
|
-
text: input,
|
|
789
|
-
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
790
|
-
}, displayInput);
|
|
791
|
-
}
|
|
792
|
-
finally {
|
|
793
|
-
finishSending();
|
|
794
|
-
}
|
|
546
|
+
return this.#submission.submit(text);
|
|
795
547
|
}
|
|
796
548
|
async clearOrExit(now = Date.now()) {
|
|
797
549
|
this.#promptSuggestions?.abortPending();
|
|
798
|
-
if (now - this.#lastClearTime <
|
|
550
|
+
if (now - this.#lastClearTime < CLEAR_EXIT_INTERVAL_MS)
|
|
799
551
|
return this.shutdown();
|
|
800
552
|
this.root.editor.setText("");
|
|
801
553
|
this.#lastClearTime = now;
|
|
@@ -804,8 +556,8 @@ export class OwnedUiSessionPresenter {
|
|
|
804
556
|
}
|
|
805
557
|
async interrupt(now = Date.now()) {
|
|
806
558
|
this.#promptSuggestions?.invalidate();
|
|
807
|
-
if (this.#
|
|
808
|
-
this.#
|
|
559
|
+
if (this.#submission.waiting) {
|
|
560
|
+
this.#submission.cancelWaiting();
|
|
809
561
|
return { outcome: "completed", diagnostic: null };
|
|
810
562
|
}
|
|
811
563
|
if (this.view().lifecycle === "busy")
|
|
@@ -832,23 +584,23 @@ export class OwnedUiSessionPresenter {
|
|
|
832
584
|
return { outcome: "completed", diagnostic: null };
|
|
833
585
|
}
|
|
834
586
|
async abort() {
|
|
835
|
-
return this.#execute(this.#simple("abort"));
|
|
587
|
+
return this.#submission.execute(this.#simple("abort"));
|
|
836
588
|
}
|
|
837
589
|
async retry() {
|
|
838
590
|
this.#promptSuggestions?.invalidate();
|
|
839
|
-
return this.#execute(this.#simple("retry"));
|
|
591
|
+
return this.#submission.execute(this.#simple("retry"));
|
|
840
592
|
}
|
|
841
593
|
async compact() {
|
|
842
594
|
this.#promptSuggestions?.invalidate();
|
|
843
|
-
return this.#execute(this.#simple("compact"));
|
|
595
|
+
return this.#submission.execute(this.#simple("compact"));
|
|
844
596
|
}
|
|
845
597
|
async newSession() {
|
|
846
598
|
this.#promptSuggestions?.invalidate();
|
|
847
|
-
return this.#execute(this.#simple("new-session"));
|
|
599
|
+
return this.#submission.execute(this.#simple("new-session"));
|
|
848
600
|
}
|
|
849
601
|
async resumeSession(sessionPath) {
|
|
850
602
|
this.#promptSuggestions?.invalidate();
|
|
851
|
-
return this.#execute({
|
|
603
|
+
return this.#submission.execute({
|
|
852
604
|
type: "resume-session",
|
|
853
605
|
correlationId: this.#correlation("resume"),
|
|
854
606
|
sessionId: this.backend.identity.sessionId,
|
|
@@ -857,7 +609,7 @@ export class OwnedUiSessionPresenter {
|
|
|
857
609
|
}
|
|
858
610
|
async setModel(providerId, modelId) {
|
|
859
611
|
this.#promptSuggestions?.invalidate();
|
|
860
|
-
return this.#execute({
|
|
612
|
+
return this.#submission.execute({
|
|
861
613
|
type: "set-model",
|
|
862
614
|
correlationId: this.#correlation("model"),
|
|
863
615
|
sessionId: this.backend.identity.sessionId,
|
|
@@ -865,7 +617,7 @@ export class OwnedUiSessionPresenter {
|
|
|
865
617
|
});
|
|
866
618
|
}
|
|
867
619
|
async setThinkingLevel(thinkingLevel) {
|
|
868
|
-
return this.#execute({
|
|
620
|
+
return this.#submission.execute({
|
|
869
621
|
type: "set-thinking-level",
|
|
870
622
|
correlationId: this.#correlation("thinking"),
|
|
871
623
|
sessionId: this.backend.identity.sessionId,
|
|
@@ -884,64 +636,13 @@ export class OwnedUiSessionPresenter {
|
|
|
884
636
|
return workflowAdapterResult(result);
|
|
885
637
|
}
|
|
886
638
|
async queueFollowUp() {
|
|
887
|
-
|
|
888
|
-
return this.#submitWhenReady(draft, () => this.#queueFollowUp(draft));
|
|
889
|
-
}
|
|
890
|
-
async #queueFollowUp(draft) {
|
|
891
|
-
const displayInput = draft.trim();
|
|
892
|
-
if (!displayInput)
|
|
893
|
-
return rejected("nothing to queue");
|
|
894
|
-
const prepared = this.root.preparePromptSubmission(displayInput);
|
|
895
|
-
assertPromptImages(prepared.images);
|
|
896
|
-
const text = prepared.text.trim();
|
|
897
|
-
this.#rememberInput(displayInput, "follow-up");
|
|
898
|
-
if (this.root.editor.getText() === draft)
|
|
899
|
-
this.root.editor.setText("");
|
|
900
|
-
this.root.resumeViewportFollowing();
|
|
901
|
-
return this.#execute({
|
|
902
|
-
type: "follow-up",
|
|
903
|
-
correlationId: this.#correlation("follow-up"),
|
|
904
|
-
sessionId: this.backend.identity.sessionId,
|
|
905
|
-
text,
|
|
906
|
-
...(prepared.images.length === 0 ? {} : { images: prepared.images }),
|
|
907
|
-
}, displayInput);
|
|
639
|
+
return this.#submission.queueFollowUp(this.root.editor.getText());
|
|
908
640
|
}
|
|
909
641
|
restoreQueuedInput() {
|
|
910
|
-
|
|
911
|
-
this.#cancelWaitingImages();
|
|
912
|
-
if (queued.length === 0)
|
|
913
|
-
return;
|
|
914
|
-
this.root.editor.setText(queued.join("\n"));
|
|
915
|
-
this.#terminal.requestRender();
|
|
642
|
+
this.#submission.restoreQueued();
|
|
916
643
|
}
|
|
917
644
|
showSelector(title, options, onSelect, onCancel) {
|
|
918
|
-
this.#
|
|
919
|
-
this.#dialogSource = "local";
|
|
920
|
-
this.#dialogId = undefined;
|
|
921
|
-
const component = createPiShellSelector({
|
|
922
|
-
title,
|
|
923
|
-
options,
|
|
924
|
-
onSelect: id => {
|
|
925
|
-
onSelect(id);
|
|
926
|
-
this.#dialogHandle?.hide();
|
|
927
|
-
this.#dialogHandle = undefined;
|
|
928
|
-
this.#dialogSource = undefined;
|
|
929
|
-
},
|
|
930
|
-
onCancel: () => {
|
|
931
|
-
this.#dialogHandle?.hide();
|
|
932
|
-
this.#dialogHandle = undefined;
|
|
933
|
-
this.#dialogSource = undefined;
|
|
934
|
-
onCancel?.();
|
|
935
|
-
},
|
|
936
|
-
});
|
|
937
|
-
const handle = this.#terminal.showOverlay(component, {
|
|
938
|
-
width: "70%",
|
|
939
|
-
maxHeight: "80%",
|
|
940
|
-
anchor: "center",
|
|
941
|
-
inputCoordination: "owned",
|
|
942
|
-
});
|
|
943
|
-
this.#dialogHandle = handle;
|
|
944
|
-
return handle;
|
|
645
|
+
return this.#dialogs.showSelector(title, options, onSelect, onCancel);
|
|
945
646
|
}
|
|
946
647
|
// Rationale: presenters see the shell only as a dialog host; selectors open in the input dock and run choices as workflows.
|
|
947
648
|
#createDialogHost() {
|
|
@@ -1009,15 +710,12 @@ export class OwnedUiSessionPresenter {
|
|
|
1009
710
|
}
|
|
1010
711
|
/** The Skills dialog: a replacement input like the model selector, applying the chosen skill through the prompt path. */
|
|
1011
712
|
showSkillsSelector(skills = this.#skillSummaries()) {
|
|
1012
|
-
const close =
|
|
1013
|
-
this.root.setInputSurface(null);
|
|
1014
|
-
this.#terminal.requestRender();
|
|
1015
|
-
};
|
|
713
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1016
714
|
const component = createPiShellSkillsSelector({
|
|
1017
715
|
skills,
|
|
1018
716
|
onSelect: name => {
|
|
1019
717
|
close();
|
|
1020
|
-
void this.#
|
|
718
|
+
void this.#submission.sendSlashPrompt(skillPrompt(name), skillPrompt(name)).catch(() => this.#submission.reportError());
|
|
1021
719
|
},
|
|
1022
720
|
onCancel: close,
|
|
1023
721
|
});
|
|
@@ -1034,10 +732,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1034
732
|
this.#terminal.requestRender();
|
|
1035
733
|
return;
|
|
1036
734
|
}
|
|
1037
|
-
const close =
|
|
1038
|
-
this.root.setInputSurface(null);
|
|
1039
|
-
this.#terminal.requestRender();
|
|
1040
|
-
};
|
|
735
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1041
736
|
const component = createPiShellUserMessageSelector(options, selection => {
|
|
1042
737
|
close();
|
|
1043
738
|
void this.runWorkflow({ command: "fork", argument: "", selection });
|
|
@@ -1064,10 +759,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1064
759
|
this.#terminal.requestRender();
|
|
1065
760
|
return;
|
|
1066
761
|
}
|
|
1067
|
-
const close =
|
|
1068
|
-
this.root.setInputSurface(null);
|
|
1069
|
-
this.#terminal.requestRender();
|
|
1070
|
-
};
|
|
762
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1071
763
|
const component = createPiShellAuthProviderSelector("logout", options, selection => {
|
|
1072
764
|
close();
|
|
1073
765
|
void this.runWorkflow({ command: "logout", argument: "", selection });
|
|
@@ -1087,10 +779,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1087
779
|
void this.runWorkflow({ command: "login", argument: "", selection });
|
|
1088
780
|
return;
|
|
1089
781
|
}
|
|
1090
|
-
const close =
|
|
1091
|
-
this.root.setInputSurface(null);
|
|
1092
|
-
this.#terminal.requestRender();
|
|
1093
|
-
};
|
|
782
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1094
783
|
const labels = method.options.map(option => option.label);
|
|
1095
784
|
const component = createPiShellExtensionSelector(method.title, labels, label => {
|
|
1096
785
|
const selection = method.options.find(option => option.label === label)?.id;
|
|
@@ -1108,10 +797,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1108
797
|
}, initialSelectedId);
|
|
1109
798
|
}
|
|
1110
799
|
showLoginAuthTypeSelector() {
|
|
1111
|
-
const close =
|
|
1112
|
-
this.root.setInputSurface(null);
|
|
1113
|
-
this.#terminal.requestRender();
|
|
1114
|
-
};
|
|
800
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1115
801
|
const labels = ["Sign in with an account", "Sign in with an API key"];
|
|
1116
802
|
const component = createPiShellExtensionSelector("Select authentication method:", labels, label => {
|
|
1117
803
|
close();
|
|
@@ -1127,10 +813,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1127
813
|
this.#terminal.requestRender();
|
|
1128
814
|
return;
|
|
1129
815
|
}
|
|
1130
|
-
const close =
|
|
1131
|
-
this.root.setInputSurface(null);
|
|
1132
|
-
this.#terminal.requestRender();
|
|
1133
|
-
};
|
|
816
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1134
817
|
const component = createPiShellAuthProviderSelector("login", options, id => {
|
|
1135
818
|
close();
|
|
1136
819
|
void this.runWorkflow({ command: "login", argument: "", selection: id });
|
|
@@ -1149,10 +832,22 @@ export class OwnedUiSessionPresenter {
|
|
|
1149
832
|
this.#presenters.openSettingsSelector(this.#dialogHost);
|
|
1150
833
|
}
|
|
1151
834
|
shutdown() {
|
|
1152
|
-
this.#shutdownPromise ??= this.#shutdown()
|
|
835
|
+
this.#shutdownPromise ??= this.#shutdown().finally(() => {
|
|
836
|
+
// Invariant: a rejected resident detach is retryable; ordinary shutdown has disposed the shell already.
|
|
837
|
+
if (!this.#disposed)
|
|
838
|
+
this.#shutdownPromise = undefined;
|
|
839
|
+
});
|
|
1153
840
|
return this.#shutdownPromise;
|
|
1154
841
|
}
|
|
1155
842
|
async #shutdown() {
|
|
843
|
+
const detached = await this.#resident.detach();
|
|
844
|
+
if (detached === true)
|
|
845
|
+
return { outcome: "completed", diagnostic: null };
|
|
846
|
+
if (detached !== undefined) {
|
|
847
|
+
this.root.appendWorkflowStatus(detached);
|
|
848
|
+
this.#terminal.requestRender();
|
|
849
|
+
return rejected("resident tab detach is unavailable");
|
|
850
|
+
}
|
|
1156
851
|
try {
|
|
1157
852
|
const quit = this.backend.workflows.executeWorkflow({ command: "quit", argument: "" });
|
|
1158
853
|
// Invariant: a slow or hung engine quit never holds the terminal past the cleanup deadline. The
|
|
@@ -1165,9 +860,16 @@ export class OwnedUiSessionPresenter {
|
|
|
1165
860
|
return workflowAdapterResult(result);
|
|
1166
861
|
}
|
|
1167
862
|
finally {
|
|
1168
|
-
this.#
|
|
863
|
+
this.#stop.resolve();
|
|
1169
864
|
}
|
|
1170
865
|
}
|
|
866
|
+
#currentSessionFile() {
|
|
867
|
+
const direct = this.backend.identity.currentSessionFile?.();
|
|
868
|
+
if (direct !== undefined && direct !== null)
|
|
869
|
+
return direct;
|
|
870
|
+
const resume = this.backend.identity.currentSessionResumeMetadata();
|
|
871
|
+
return resume === null ? null : `${resume.sessionDir}/${resume.sessionId}`;
|
|
872
|
+
}
|
|
1171
873
|
async runWorkflow(request) {
|
|
1172
874
|
if (request.command === "quit")
|
|
1173
875
|
return this.shutdown();
|
|
@@ -1188,68 +890,17 @@ export class OwnedUiSessionPresenter {
|
|
|
1188
890
|
if (request.command === "login" && request.selection !== undefined) {
|
|
1189
891
|
const setup = this.backend.catalog.pinnedAmbientAuthentication(request.selection);
|
|
1190
892
|
if (setup) {
|
|
1191
|
-
const close =
|
|
1192
|
-
|
|
1193
|
-
this.#terminal.requestRender();
|
|
1194
|
-
};
|
|
1195
|
-
const dialog = createPiShellLoginDialog({
|
|
1196
|
-
getColumns: () => this.#terminal.viewport().columns,
|
|
1197
|
-
getRows: () => this.#terminal.viewport().rows,
|
|
1198
|
-
requestRender: () => this.#terminal.requestRender(),
|
|
1199
|
-
}, setup.providerId, close, setup.providerName, setup.title);
|
|
893
|
+
const close = this.#dialogs.closeInputSurface;
|
|
894
|
+
const dialog = createPiShellLoginDialog(componentRuntime(this.#terminal), setup.providerId, close, setup.providerName, setup.title);
|
|
1200
895
|
dialog.showInfo(setup.message, [], true);
|
|
1201
896
|
this.root.setInputSurface(dialog);
|
|
1202
897
|
this.#terminal.requestRender();
|
|
1203
898
|
return { outcome: "completed", diagnostic: null };
|
|
1204
899
|
}
|
|
1205
900
|
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
return
|
|
1209
|
-
}
|
|
1210
|
-
if (request.command === "models" && request.selection === undefined) {
|
|
1211
|
-
this.showModelsDialog(request.argument.trim() || undefined);
|
|
1212
|
-
return { outcome: "completed", diagnostic: null };
|
|
1213
|
-
}
|
|
1214
|
-
if (request.command === "model" && request.selection === undefined && request.confirmed === undefined && request.argument.trim().length === 0) {
|
|
1215
|
-
this.showModelSelector();
|
|
1216
|
-
return { outcome: "completed", diagnostic: null };
|
|
1217
|
-
}
|
|
1218
|
-
if (request.command === "fork" && request.selection === undefined && request.confirmed === undefined) {
|
|
1219
|
-
this.showForkSelector();
|
|
1220
|
-
return { outcome: "completed", diagnostic: null };
|
|
1221
|
-
}
|
|
1222
|
-
if (request.command === "thinking" && request.selection === undefined && request.argument.trim().length === 0) {
|
|
1223
|
-
await this.showThinkingSelector();
|
|
1224
|
-
return { outcome: "completed", diagnostic: null };
|
|
1225
|
-
}
|
|
1226
|
-
if (request.command === "trust" && request.selection === undefined && request.confirmed === undefined) {
|
|
1227
|
-
this.showTrustSelector();
|
|
1228
|
-
return { outcome: "completed", diagnostic: null };
|
|
1229
|
-
}
|
|
1230
|
-
if (request.command === "settings" && request.selection === undefined && request.confirmed === undefined) {
|
|
1231
|
-
this.showSettingsSelector();
|
|
1232
|
-
return { outcome: "completed", diagnostic: null };
|
|
1233
|
-
}
|
|
1234
|
-
if (request.command === "resume" && request.selection === undefined && request.confirmed === undefined && request.argument.trim().length === 0) {
|
|
1235
|
-
await this.showSessionSelector();
|
|
1236
|
-
return { outcome: "completed", diagnostic: null };
|
|
1237
|
-
}
|
|
1238
|
-
if (request.command === "login" && request.selection === undefined && request.confirmed === undefined) {
|
|
1239
|
-
if (request.argument.trim().length === 0)
|
|
1240
|
-
this.showLoginAuthTypeSelector();
|
|
1241
|
-
else
|
|
1242
|
-
this.showLoginMethodSelector(request.argument);
|
|
1243
|
-
return { outcome: "completed", diagnostic: null };
|
|
1244
|
-
}
|
|
1245
|
-
if (request.command === "logout" && request.selection === undefined && request.confirmed === undefined) {
|
|
1246
|
-
await this.showLogoutSelector();
|
|
1247
|
-
return { outcome: "completed", diagnostic: null };
|
|
1248
|
-
}
|
|
1249
|
-
if (request.command === "tree" && request.selection === undefined && request.confirmed === undefined && request.argument.trim().length === 0) {
|
|
1250
|
-
await this.showTreeSelector();
|
|
1251
|
-
return { outcome: "completed", diagnostic: null };
|
|
1252
|
-
}
|
|
901
|
+
const selected = openWorkflowSelector(request, this, { residentTab: this.#resident.active });
|
|
902
|
+
if (selected !== undefined)
|
|
903
|
+
return selected;
|
|
1253
904
|
if (request.command === "reload") {
|
|
1254
905
|
const blocked = this.backend.workflows.reloadBlockedResult();
|
|
1255
906
|
if (blocked) {
|
|
@@ -1263,11 +914,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1263
914
|
? await import("./share-operation.js")
|
|
1264
915
|
: undefined;
|
|
1265
916
|
const shareSurface = shareDialog === undefined ? undefined
|
|
1266
|
-
: (this.#customViewport ? shareDialog.createPiShellShareOperationDialog : shareDialog.createPiShellOperationLoader)(
|
|
1267
|
-
getColumns: () => this.#terminal.viewport().columns,
|
|
1268
|
-
getRows: () => this.#terminal.viewport().rows,
|
|
1269
|
-
requestRender: () => this.#terminal.requestRender(),
|
|
1270
|
-
}, "Creating gist…");
|
|
917
|
+
: (this.#customViewport ? shareDialog.createPiShellShareOperationDialog : shareDialog.createPiShellOperationLoader)(componentRuntime(this.#terminal), "Creating gist…");
|
|
1271
918
|
const operationSurface = shareSurface ?? (request.command === "reload" ? createPiShellReloadBox() : undefined);
|
|
1272
919
|
const now = this.#reloadPresentation?.now ?? Date.now;
|
|
1273
920
|
const shownAt = now();
|
|
@@ -1277,7 +924,11 @@ export class OwnedUiSessionPresenter {
|
|
|
1277
924
|
}
|
|
1278
925
|
let result;
|
|
1279
926
|
try {
|
|
1280
|
-
result = await this.backend.workflows.executeWorkflow(shareSurface === undefined ? request : {
|
|
927
|
+
result = await this.backend.workflows.executeWorkflow(shareSurface === undefined ? request : {
|
|
928
|
+
...request,
|
|
929
|
+
signal: shareSurface.signal,
|
|
930
|
+
onProgress: message => { shareSurface.setMessage?.(message); this.#terminal.requestRender(); },
|
|
931
|
+
});
|
|
1281
932
|
}
|
|
1282
933
|
finally {
|
|
1283
934
|
if (operationSurface) {
|
|
@@ -1347,10 +998,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1347
998
|
}
|
|
1348
999
|
showTrustSelector() {
|
|
1349
1000
|
const context = this.backend.catalog.pinnedProjectTrustContext();
|
|
1350
|
-
const close =
|
|
1351
|
-
this.root.setInputSurface(null);
|
|
1352
|
-
this.#terminal.requestRender();
|
|
1353
|
-
};
|
|
1001
|
+
const close = this.#dialogs.closeInputSurface;
|
|
1354
1002
|
const component = createPiShellTrustSelector({
|
|
1355
1003
|
...context,
|
|
1356
1004
|
onSelect: selection => {
|
|
@@ -1414,18 +1062,13 @@ export class OwnedUiSessionPresenter {
|
|
|
1414
1062
|
this.#copyIntentSequence += 1;
|
|
1415
1063
|
this.#responseCopy?.dispose();
|
|
1416
1064
|
this.#copyExecutor?.dispose();
|
|
1417
|
-
this.#
|
|
1065
|
+
this.#submission.cancelWaiting();
|
|
1418
1066
|
let pasteCleanup = Promise.resolve();
|
|
1419
1067
|
let historyCleanup = Promise.resolve(true);
|
|
1420
1068
|
attempt(() => { historyCleanup = this.#promptHistory?.close() ?? Promise.resolve(true); });
|
|
1421
1069
|
attempt(() => { pasteCleanup = this.root.disposePendingPastes(); });
|
|
1422
1070
|
attempt(() => this.root.clearViewportPointerState());
|
|
1423
1071
|
attempt(() => this.#streamPresentation.dispose());
|
|
1424
|
-
attempt(() => {
|
|
1425
|
-
if (this.#startupRouteTimer !== undefined)
|
|
1426
|
-
clearTimeout(this.#startupRouteTimer);
|
|
1427
|
-
this.#startupRouteTimer = undefined;
|
|
1428
|
-
});
|
|
1429
1072
|
attempt(() => this.#promptSuggestions?.dispose());
|
|
1430
1073
|
attempt(() => this.#unsubscribePromptSuggestions());
|
|
1431
1074
|
attempt(() => this.#unsubscribePromptImages());
|
|
@@ -1433,21 +1076,18 @@ export class OwnedUiSessionPresenter {
|
|
|
1433
1076
|
attempt(() => this.#unsubscribeSettings());
|
|
1434
1077
|
let exitText = "";
|
|
1435
1078
|
attempt(() => {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
: [this.root.exitTranscript(terminal.columns), resumeHint].filter(Boolean).join("\n\n");
|
|
1079
|
+
exitText = composeExitText({
|
|
1080
|
+
terminalMode: terminal.mode,
|
|
1081
|
+
customViewport: this.#customViewport,
|
|
1082
|
+
exitMode: this.backend.identity.disposed ? this.#fullscreenExitOutput : this.backend.settings.snapshot().fullscreenExitOutput,
|
|
1083
|
+
resume: this.backend.identity.currentSessionResumeMetadata(),
|
|
1084
|
+
transcript: () => this.root.exitTranscript(terminal.columns),
|
|
1085
|
+
});
|
|
1444
1086
|
});
|
|
1445
1087
|
attempt(() => this.#unbindClipboardWriter());
|
|
1446
|
-
attempt(() => this.#
|
|
1447
|
-
attempt(() => this.#unbindTerminalSettings());
|
|
1448
|
-
attempt(() => this.#unbindShutdownSettings());
|
|
1088
|
+
attempt(() => this.#unbindSessionSettings());
|
|
1449
1089
|
attempt(() => this.backend.workflows.setWorkflowInteractionHost(DETACHED_WORKFLOW_INTERACTION));
|
|
1450
|
-
|
|
1090
|
+
this.#concerns.dispose(attempt);
|
|
1451
1091
|
attempt(() => this.#extensionBridge.dispose());
|
|
1452
1092
|
attempt(() => this.#programStatus.clear());
|
|
1453
1093
|
// Invariant: the host settles a presenter only once the terminal is restored, so these never stall it.
|
|
@@ -1461,20 +1101,14 @@ export class OwnedUiSessionPresenter {
|
|
|
1461
1101
|
}
|
|
1462
1102
|
await historyCleanup.catch(() => false); // Security: background durability outcomes never enter terminal output.
|
|
1463
1103
|
await boundedCleanup(() => pasteCleanup).catch(error => settled.push(error));
|
|
1104
|
+
await boundedCleanup(() => this.#resident.gracefulStop(this.#currentSessionFile()))
|
|
1105
|
+
.catch(error => settled.push(error));
|
|
1464
1106
|
await boundedCleanup(() => this.backend.extensions.unbindExtensionUi()).catch(error => settled.push(error));
|
|
1465
1107
|
return settled;
|
|
1466
1108
|
};
|
|
1467
1109
|
this.#released = { exitText, failures, settle };
|
|
1468
1110
|
return this.#released;
|
|
1469
1111
|
}
|
|
1470
|
-
#settleStoppedLifecycle() {
|
|
1471
|
-
void this.dispose().then(() => this.#resolveStoppedLifecycle(), () => this.#resolveStoppedLifecycle());
|
|
1472
|
-
}
|
|
1473
|
-
#resolveStoppedLifecycle() {
|
|
1474
|
-
const resolve = this.#resolveStopped;
|
|
1475
|
-
this.#resolveStopped = undefined;
|
|
1476
|
-
resolve?.();
|
|
1477
|
-
}
|
|
1478
1112
|
// Invariant: incremental block updates notify listeners with the same complete view contract.
|
|
1479
1113
|
#syncBlock(block) {
|
|
1480
1114
|
this.root.applyTranscriptBlock(block);
|
|
@@ -1492,6 +1126,121 @@ export class OwnedUiSessionPresenter {
|
|
|
1492
1126
|
listener(view);
|
|
1493
1127
|
return view;
|
|
1494
1128
|
}
|
|
1129
|
+
// Rationale: extensions reach the shell only through this bridge; every surface they draw lands in the root.
|
|
1130
|
+
#createExtensionBridge() {
|
|
1131
|
+
return createPiExtensionUiBridge({
|
|
1132
|
+
runtime: componentRuntime(this.#terminal),
|
|
1133
|
+
agentDir: this.backend.identity.agentDir,
|
|
1134
|
+
setInputSurface: component => this.root.setInputSurface(component, true, "opaque"),
|
|
1135
|
+
setProgramStatusBlocked: status => this.#programStatus.setBlocked("extension-dialog", status),
|
|
1136
|
+
showOverlay: (component, overlayOptions) => this.#terminal.showOverlay(component, overlayOptions),
|
|
1137
|
+
listenInput: handler => this.#terminal.addInputListener(handler),
|
|
1138
|
+
replaceWidget: (key, component, placement) => this.root.setExtensionWidget(key, component, placement),
|
|
1139
|
+
replaceHeader: component => this.root.setExtensionHeader(component),
|
|
1140
|
+
replaceFooter: component => this.root.setExtensionFooter(component),
|
|
1141
|
+
setStatus: (key, text) => this.root.setExtensionStatus(key, text),
|
|
1142
|
+
setWorking: (message, visible) => this.root.setExtensionWorking(message, visible),
|
|
1143
|
+
notify: (message, type) => this.root.addExtensionNotification(message, type),
|
|
1144
|
+
setTitle: title => this.#terminal.setTitle(title),
|
|
1145
|
+
getEditorText: () => this.root.editor.getText(),
|
|
1146
|
+
setEditorText: text => this.root.editor.setText(text),
|
|
1147
|
+
pasteToEditor: text => this.root.editor.insertText(text),
|
|
1148
|
+
addAutocompleteProvider: factory => this.root.editor.addAutocompleteProvider(factory),
|
|
1149
|
+
setCustomEditor: component => this.root.setInputSurface(component, true, "opaque"),
|
|
1150
|
+
getFooterData: () => this.root.extensionFooterData(),
|
|
1151
|
+
getToolsExpanded: () => this.root.toolsExpanded,
|
|
1152
|
+
setToolsExpanded: expanded => this.root.setToolsExpanded(expanded),
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
#createSubmission(cwd) {
|
|
1156
|
+
return new PromptSubmission({
|
|
1157
|
+
backend: this.backend,
|
|
1158
|
+
root: this.root,
|
|
1159
|
+
terminal: this.#terminal,
|
|
1160
|
+
residentTab: this.#resident,
|
|
1161
|
+
cwd,
|
|
1162
|
+
promptHistory: () => this.#promptHistory,
|
|
1163
|
+
imageSidecar: () => this.#historyBinding?.imageSidecar,
|
|
1164
|
+
disposed: () => this.#disposed,
|
|
1165
|
+
correlation: prefix => this.#correlation(prefix),
|
|
1166
|
+
lifecycle: () => this.view().lifecycle,
|
|
1167
|
+
currentSessionFile: () => this.#currentSessionFile(),
|
|
1168
|
+
slashCommand: text => this.#slashCommand(text),
|
|
1169
|
+
invalidateSuggestions: () => this.#promptSuggestions?.invalidate(),
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
// Invariant: the order is the reset order on session replacement; copy intent before pastes before the input surface.
|
|
1173
|
+
#createConcerns() {
|
|
1174
|
+
return new SessionConcerns([
|
|
1175
|
+
{ onSessionReplaced: () => { this.#copyIntentSequence += 1; this.#responseCopy?.reset(); } },
|
|
1176
|
+
this.#submission,
|
|
1177
|
+
{ onSessionReplaced: () => this.root.resetPendingPastes() },
|
|
1178
|
+
{ onSessionReplaced: () => this.#promptSuggestions?.invalidate() },
|
|
1179
|
+
{ onSessionReplaced: () => { this.#sessionGeneration = this.backend.identity.sessionGeneration; this.#programStatus.reset(); } },
|
|
1180
|
+
{ onSessionReplaced: () => this.#rebindTranscript() },
|
|
1181
|
+
this.#login,
|
|
1182
|
+
{ onSessionReplaced: () => this.#extensionBridge.reset() },
|
|
1183
|
+
// Invariant: a replaced session takes its transient viewport and owned-route state with it.
|
|
1184
|
+
{ onSessionReplaced: () => this.root.resetViewport() },
|
|
1185
|
+
this.#dialogs,
|
|
1186
|
+
{ onSessionReplaced: () => this.root.setInputSurface(null) },
|
|
1187
|
+
{ onSessionReplaced: () => this.root.resetExtensionUi() },
|
|
1188
|
+
{ onSessionReplaced: () => this.root.resetWorkflowPresentation() },
|
|
1189
|
+
this.#resident,
|
|
1190
|
+
]);
|
|
1191
|
+
}
|
|
1192
|
+
// Invariant: delivery recovery does not replace a store; conversation or live scope changes can.
|
|
1193
|
+
#rebindTranscript(scope) {
|
|
1194
|
+
const replaced = this.#sessionBindingGeneration !== this.backend.identity.sessionBindingGeneration;
|
|
1195
|
+
if (!replaced && scope === undefined)
|
|
1196
|
+
return;
|
|
1197
|
+
if (replaced) {
|
|
1198
|
+
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
1199
|
+
this.root.resetTranscript();
|
|
1200
|
+
}
|
|
1201
|
+
const binding = this.#historyBinding;
|
|
1202
|
+
if (binding?.options.forSession !== undefined || binding?.options.forScope !== undefined) {
|
|
1203
|
+
const sessionId = this.backend.identity.currentHistorySessionId();
|
|
1204
|
+
const selectedScope = scope ?? binding.scope;
|
|
1205
|
+
if (selectedScope !== binding.scope || sessionId !== binding.sessionId) {
|
|
1206
|
+
if (!replaced)
|
|
1207
|
+
this.root.editor.recall?.restoreDraft?.();
|
|
1208
|
+
const previous = this.#promptHistory;
|
|
1209
|
+
this.#promptHistory = null;
|
|
1210
|
+
binding.imageSidecar = undefined;
|
|
1211
|
+
binding.scope = selectedScope;
|
|
1212
|
+
binding.sessionId = sessionId ?? undefined;
|
|
1213
|
+
void previous?.close().catch(() => false);
|
|
1214
|
+
const destination = binding.options.forScope !== undefined
|
|
1215
|
+
? binding.options.forScope(selectedScope, sessionId)
|
|
1216
|
+
: sessionId === null ? null : binding.options.forSession(sessionId);
|
|
1217
|
+
if (destination !== null) {
|
|
1218
|
+
binding.imageSidecar = destination.imageSidecar;
|
|
1219
|
+
const controller = new PromptHistoryController({
|
|
1220
|
+
editor: this.root.editor,
|
|
1221
|
+
store: destination.store,
|
|
1222
|
+
limit: binding.options.limit,
|
|
1223
|
+
fallback: replaced ? this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []) : [],
|
|
1224
|
+
active: () => this.root.usesDefaultInputSurface(),
|
|
1225
|
+
render: () => this.#terminal.requestRender(),
|
|
1226
|
+
rehydrate: value => this.root.rehydrateHistoryText(value, id => this.#historyBinding?.imageSidecar?.readAttachment(id) ?? null),
|
|
1227
|
+
});
|
|
1228
|
+
this.#promptHistory = controller;
|
|
1229
|
+
if (this.#started)
|
|
1230
|
+
controller.start();
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
// Security: missing identity must not reopen Shared or retain another session's recall.
|
|
1234
|
+
const fallback = replaced ? this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []).reverse() : [];
|
|
1235
|
+
this.root.editor.recall?.reset();
|
|
1236
|
+
this.root.editor.recall?.replace(fallback);
|
|
1237
|
+
}
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
if (replaced)
|
|
1242
|
+
this.#promptHistory?.reset(this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
|
|
1243
|
+
}
|
|
1495
1244
|
#syncTerminalProgress(view) {
|
|
1496
1245
|
if (!this.#terminal.running)
|
|
1497
1246
|
return;
|
|
@@ -1511,34 +1260,10 @@ export class OwnedUiSessionPresenter {
|
|
|
1511
1260
|
#syncView() {
|
|
1512
1261
|
this.#streamPresentation.noteImmediatePresentation();
|
|
1513
1262
|
const view = this.view();
|
|
1514
|
-
if (this.backend.identity.sessionGeneration !== this.#sessionGeneration)
|
|
1515
|
-
this.#
|
|
1516
|
-
this.#responseCopy?.reset();
|
|
1517
|
-
this.#cancelWaitingImages();
|
|
1518
|
-
this.root.resetPendingPastes();
|
|
1519
|
-
this.#promptSuggestions?.invalidate();
|
|
1520
|
-
this.#sessionGeneration = this.backend.identity.sessionGeneration;
|
|
1521
|
-
this.#programStatus.reset();
|
|
1522
|
-
// Invariant: delivery recovery invalidates callbacks, not same-session local recall or its draft.
|
|
1523
|
-
if (this.#sessionBindingGeneration !== this.backend.identity.sessionBindingGeneration) {
|
|
1524
|
-
this.#sessionBindingGeneration = this.backend.identity.sessionBindingGeneration;
|
|
1525
|
-
this.root.resetTranscript();
|
|
1526
|
-
this.#promptHistory?.reset(view.transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
|
|
1527
|
-
}
|
|
1528
|
-
this.#activeLoginDialog = undefined;
|
|
1529
|
-
this.#extensionBridge.reset();
|
|
1530
|
-
// Invariant: a replaced session takes its transient viewport and owned-route state with it.
|
|
1531
|
-
this.root.resetViewport();
|
|
1532
|
-
this.#dialogHandle?.hide();
|
|
1533
|
-
this.#dialogHandle = undefined;
|
|
1534
|
-
this.#dialogId = undefined;
|
|
1535
|
-
this.#dialogSource = undefined;
|
|
1536
|
-
this.root.setInputSurface(null);
|
|
1537
|
-
this.root.resetExtensionUi();
|
|
1538
|
-
this.root.resetWorkflowPresentation();
|
|
1539
|
-
}
|
|
1263
|
+
if (this.backend.identity.sessionGeneration !== this.#sessionGeneration)
|
|
1264
|
+
this.#concerns.replaceSession();
|
|
1540
1265
|
this.root.update(view);
|
|
1541
|
-
this.#
|
|
1266
|
+
this.#dialogs.syncBackend(view.dialog);
|
|
1542
1267
|
this.#presentStartupTrustWarning(view);
|
|
1543
1268
|
this.#terminal.requestRender();
|
|
1544
1269
|
for (const listener of this.#listeners)
|
|
@@ -1556,100 +1281,34 @@ export class OwnedUiSessionPresenter {
|
|
|
1556
1281
|
this.#startupTrustHandled = true;
|
|
1557
1282
|
this.root.appendWorkflowMessage({ kind: "warning", message: diagnostic.message });
|
|
1558
1283
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (pending === undefined || this.#disposed || !this.#terminal.running)
|
|
1284
|
+
openOwnedRoute(route) {
|
|
1285
|
+
if (this.#disposed)
|
|
1562
1286
|
return;
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
}, 25);
|
|
1568
|
-
this.#startupRouteTimer.unref?.();
|
|
1287
|
+
// Invariant: a route requested before the runtime runs (the resident Settings bridge route arrives
|
|
1288
|
+
// before start()) is retained, latest wins, and presented through the startup-route path once started.
|
|
1289
|
+
if (!this.#terminal.running) {
|
|
1290
|
+
this.#dialogs.retainStartupRoute(route);
|
|
1569
1291
|
return;
|
|
1570
1292
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
#openOwnedRoute(route, input, onClosed) {
|
|
1575
|
-
const surface = this.#routeHost?.open(route, input) ?? null;
|
|
1576
|
-
if (surface === null)
|
|
1577
|
-
return { outcome: "failed", diagnostic: `route is unavailable: ${route}` };
|
|
1578
|
-
if (!this.#terminal.running)
|
|
1579
|
-
return { outcome: "failed", diagnostic: "runtime is not active" };
|
|
1580
|
-
this.root.dismissNotice();
|
|
1581
|
-
this.#dialogHandle?.hide();
|
|
1582
|
-
this.#dialogSource = "route";
|
|
1583
|
-
const handle = this.#terminal.openRoute(surface, {
|
|
1584
|
-
exit: () => { void this.shutdown(); },
|
|
1585
|
-
closed: rendered => {
|
|
1586
|
-
if (this.#dialogHandle === handle) {
|
|
1587
|
-
this.#dialogHandle = undefined;
|
|
1588
|
-
this.#dialogId = undefined;
|
|
1589
|
-
this.#dialogSource = undefined;
|
|
1590
|
-
}
|
|
1591
|
-
if (rendered) {
|
|
1592
|
-
try {
|
|
1593
|
-
void onClosed?.()?.catch(() => undefined);
|
|
1594
|
-
}
|
|
1595
|
-
catch { /* Rationale: a failed acknowledgement stays pending. */ }
|
|
1596
|
-
}
|
|
1597
|
-
},
|
|
1598
|
-
});
|
|
1599
|
-
this.#dialogHandle = handle;
|
|
1600
|
-
this.#dialogId = surface.id;
|
|
1601
|
-
return { outcome: "completed", diagnostic: null };
|
|
1602
|
-
}
|
|
1603
|
-
#syncDialog(dialog) {
|
|
1604
|
-
if (!this.#terminal.running)
|
|
1605
|
-
return;
|
|
1606
|
-
if (dialog === null) {
|
|
1607
|
-
// Invariant: locally owned routes (notably /settings) are independent of backend
|
|
1608
|
-
// lifecycle/status events and remain open while an agent is working.
|
|
1609
|
-
if (this.#dialogSource !== "backend")
|
|
1610
|
-
return;
|
|
1611
|
-
this.#dialogHandle?.hide();
|
|
1612
|
-
this.#dialogHandle = undefined;
|
|
1613
|
-
this.#dialogId = undefined;
|
|
1614
|
-
this.#dialogSource = undefined;
|
|
1615
|
-
return;
|
|
1293
|
+
const result = this.#dialogs.openRoute(route);
|
|
1294
|
+
if (result.outcome !== "completed" && result.diagnostic !== null) {
|
|
1295
|
+
this.root.appendWorkflowMessage({ kind: "warning", message: result.diagnostic });
|
|
1616
1296
|
}
|
|
1617
|
-
if (this.#dialogSource === "backend" && this.#dialogId === dialog.id)
|
|
1618
|
-
return;
|
|
1619
|
-
this.#dialogHandle?.hide();
|
|
1620
|
-
this.#dialogSource = "backend";
|
|
1621
|
-
const component = createPiShellDialog(dialog, {
|
|
1622
|
-
onSelect: () => {
|
|
1623
|
-
this.#dialogHandle?.hide();
|
|
1624
|
-
this.#dialogHandle = undefined;
|
|
1625
|
-
this.#dialogId = undefined;
|
|
1626
|
-
this.#dialogSource = undefined;
|
|
1627
|
-
},
|
|
1628
|
-
onCancel: () => {
|
|
1629
|
-
this.#dialogHandle?.hide();
|
|
1630
|
-
this.#dialogHandle = undefined;
|
|
1631
|
-
this.#dialogId = undefined;
|
|
1632
|
-
this.#dialogSource = undefined;
|
|
1633
|
-
},
|
|
1634
|
-
});
|
|
1635
|
-
this.#dialogHandle = this.#terminal.showOverlay(component, {
|
|
1636
|
-
width: "70%",
|
|
1637
|
-
maxHeight: "80%",
|
|
1638
|
-
anchor: "center",
|
|
1639
|
-
inputCoordination: "owned",
|
|
1640
|
-
});
|
|
1641
|
-
this.#dialogId = dialog.id;
|
|
1642
1297
|
}
|
|
1643
1298
|
hotkeysPresentation() {
|
|
1644
|
-
|
|
1299
|
+
const presentation = this.root.hotkeysPresentation();
|
|
1300
|
+
return this.#resident.active ? { ...presentation, residentTabs: true } : presentation;
|
|
1645
1301
|
}
|
|
1646
1302
|
async #slashCommand(text) {
|
|
1647
1303
|
const body = text.slice(1).trim();
|
|
1648
1304
|
const separator = body.search(/\s/);
|
|
1649
1305
|
const name = separator < 0 ? body : body.slice(0, separator);
|
|
1650
1306
|
const argument = separator < 0 ? "" : body.slice(separator + 1).trimStart();
|
|
1651
|
-
|
|
1652
|
-
|
|
1307
|
+
const utilityTab = this.#resident.active ? RESIDENT_UTILITY_TAB_REFUSALS[name] : undefined;
|
|
1308
|
+
if (utilityTab !== undefined)
|
|
1309
|
+
return rejected(utilityTab);
|
|
1310
|
+
if (this.#dialogs.claimsRoute(name))
|
|
1311
|
+
return this.#dialogs.openRoute(name);
|
|
1653
1312
|
if (this.#isWorkflowRoute(name))
|
|
1654
1313
|
return this.runWorkflow({ command: name, argument });
|
|
1655
1314
|
if (this.#skillsCollapsed()) {
|
|
@@ -1657,17 +1316,10 @@ export class OwnedUiSessionPresenter {
|
|
|
1657
1316
|
return this.#runSkillsCommand(text, argument);
|
|
1658
1317
|
const rewritten = rewriteSkillsTunnelSubmission(text);
|
|
1659
1318
|
if (rewritten !== text)
|
|
1660
|
-
return this.#
|
|
1319
|
+
return this.#submission.sendSlashPrompt(rewritten, text);
|
|
1661
1320
|
}
|
|
1662
1321
|
// Compatibility: unknown slash input, prompt templates, skills, and extension commands remain Pi prompt input.
|
|
1663
|
-
this.#
|
|
1664
|
-
this.root.resumeViewportFollowing();
|
|
1665
|
-
return this.#execute({
|
|
1666
|
-
type: this.view().lifecycle === "busy" ? "steer" : "prompt",
|
|
1667
|
-
correlationId: this.#correlation("prompt-command"),
|
|
1668
|
-
sessionId: this.backend.identity.sessionId,
|
|
1669
|
-
text,
|
|
1670
|
-
});
|
|
1322
|
+
return this.#submission.sendSlashPrompt(text);
|
|
1671
1323
|
}
|
|
1672
1324
|
#installAutocompleteCommands() {
|
|
1673
1325
|
this.root.editor.setAutocompleteCommands(this.backend.workflows.workflowAutocompleteCommands());
|
|
@@ -1701,18 +1353,7 @@ export class OwnedUiSessionPresenter {
|
|
|
1701
1353
|
this.#terminal.requestRender();
|
|
1702
1354
|
return { outcome: "failed", diagnostic: message };
|
|
1703
1355
|
}
|
|
1704
|
-
return this.#
|
|
1705
|
-
}
|
|
1706
|
-
// Rationale: engine expands skill arguments; history retains typed input for recall.
|
|
1707
|
-
#submitSkillPrompt(prompt, typed) {
|
|
1708
|
-
this.#rememberInput(typed, "slash");
|
|
1709
|
-
this.root.resumeViewportFollowing();
|
|
1710
|
-
return this.#execute({
|
|
1711
|
-
type: this.view().lifecycle === "busy" ? "steer" : "prompt",
|
|
1712
|
-
correlationId: this.#correlation("prompt-command"),
|
|
1713
|
-
sessionId: this.backend.identity.sessionId,
|
|
1714
|
-
text: prompt,
|
|
1715
|
-
});
|
|
1356
|
+
return this.#submission.sendSlashPrompt(skillPrompt(skill.name, separator < 0 ? "" : trimmed.slice(separator + 1)), text);
|
|
1716
1357
|
}
|
|
1717
1358
|
#treeSummary() {
|
|
1718
1359
|
return new Promise(resolve => {
|
|
@@ -1753,139 +1394,6 @@ export class OwnedUiSessionPresenter {
|
|
|
1753
1394
|
if (result.diagnostic === "Branch summarization cancelled")
|
|
1754
1395
|
await this.showTreeSelector(entryId);
|
|
1755
1396
|
}
|
|
1756
|
-
#startWorkflowLogin(request) {
|
|
1757
|
-
this.#finishWorkflowLogin();
|
|
1758
|
-
const dialog = createPiShellLoginDialog({
|
|
1759
|
-
getColumns: () => this.#terminal.viewport().columns,
|
|
1760
|
-
getRows: () => this.#terminal.viewport().rows,
|
|
1761
|
-
requestRender: () => this.#terminal.requestRender(),
|
|
1762
|
-
}, request.providerId, success => {
|
|
1763
|
-
if (!success)
|
|
1764
|
-
this.#finishWorkflowLogin();
|
|
1765
|
-
}, request.providerName);
|
|
1766
|
-
this.#activeLoginDialog = dialog;
|
|
1767
|
-
this.root.setInputSurface(dialog);
|
|
1768
|
-
this.#programStatus.setBlocked("authentication", {
|
|
1769
|
-
kind: "auth",
|
|
1770
|
-
message: `Log in to ${request.providerName}`,
|
|
1771
|
-
});
|
|
1772
|
-
this.#syncTerminalProgress(this.view());
|
|
1773
|
-
this.#terminal.requestRender();
|
|
1774
|
-
}
|
|
1775
|
-
#requestWorkflowInput(request) {
|
|
1776
|
-
const dialog = this.#activeLoginDialog;
|
|
1777
|
-
if (!dialog)
|
|
1778
|
-
return Promise.resolve(null);
|
|
1779
|
-
if (request.type === "select") {
|
|
1780
|
-
return new Promise(resolve => {
|
|
1781
|
-
const options = request.options ?? [];
|
|
1782
|
-
const labels = options.map(option => option.label);
|
|
1783
|
-
const restoreDialog = () => {
|
|
1784
|
-
if (this.#activeLoginDialog === dialog)
|
|
1785
|
-
this.root.setInputSurface(dialog);
|
|
1786
|
-
this.#terminal.requestRender();
|
|
1787
|
-
};
|
|
1788
|
-
const selector = createPiShellExtensionSelector(request.message, labels, label => {
|
|
1789
|
-
const id = options.find(option => option.label === label)?.id;
|
|
1790
|
-
restoreDialog();
|
|
1791
|
-
resolve(id ?? null);
|
|
1792
|
-
}, () => {
|
|
1793
|
-
restoreDialog();
|
|
1794
|
-
resolve(null);
|
|
1795
|
-
});
|
|
1796
|
-
this.root.setInputSurface(selector, false);
|
|
1797
|
-
this.#terminal.requestRender();
|
|
1798
|
-
});
|
|
1799
|
-
}
|
|
1800
|
-
const response = request.type === "manual-code"
|
|
1801
|
-
? dialog.showManualInput(request.message)
|
|
1802
|
-
: dialog.showPrompt(request.message, request.placeholder);
|
|
1803
|
-
this.#terminal.requestRender();
|
|
1804
|
-
return response.then(value => value, () => null);
|
|
1805
|
-
}
|
|
1806
|
-
#notifyWorkflowLogin(event) {
|
|
1807
|
-
const dialog = this.#activeLoginDialog;
|
|
1808
|
-
if (!dialog)
|
|
1809
|
-
return;
|
|
1810
|
-
if (event.type === "auth_url")
|
|
1811
|
-
dialog.showAuth(event.url, event.instructions);
|
|
1812
|
-
else if (event.type === "device_code") {
|
|
1813
|
-
dialog.showDeviceCode(event);
|
|
1814
|
-
dialog.showWaiting("Waiting for authentication...");
|
|
1815
|
-
}
|
|
1816
|
-
else if (event.type === "info")
|
|
1817
|
-
dialog.showInfo(event.message, event.links);
|
|
1818
|
-
else if (event.type === "waiting")
|
|
1819
|
-
dialog.showWaiting(event.message);
|
|
1820
|
-
else
|
|
1821
|
-
dialog.showProgress(event.message);
|
|
1822
|
-
this.#terminal.requestRender();
|
|
1823
|
-
}
|
|
1824
|
-
#finishWorkflowLogin() {
|
|
1825
|
-
if (!this.#activeLoginDialog)
|
|
1826
|
-
return;
|
|
1827
|
-
this.#activeLoginDialog = undefined;
|
|
1828
|
-
this.root.setInputSurface(null);
|
|
1829
|
-
this.#programStatus.setBlocked("authentication", undefined);
|
|
1830
|
-
this.#syncTerminalProgress(this.view());
|
|
1831
|
-
this.#terminal.requestRender();
|
|
1832
|
-
}
|
|
1833
|
-
async #execute(command, draft) {
|
|
1834
|
-
if (draft === undefined)
|
|
1835
|
-
return this.backend.session.execute(command);
|
|
1836
|
-
const revision = this.#editorRevision;
|
|
1837
|
-
try {
|
|
1838
|
-
assertOwnedUiCommand(command);
|
|
1839
|
-
}
|
|
1840
|
-
catch (error) {
|
|
1841
|
-
return this.#recoverSubmission(draft, revision, error);
|
|
1842
|
-
}
|
|
1843
|
-
try {
|
|
1844
|
-
const result = await this.backend.session.execute(command);
|
|
1845
|
-
if (result.outcome === "rejected")
|
|
1846
|
-
return this.#recoverSubmission(draft, revision);
|
|
1847
|
-
return result;
|
|
1848
|
-
}
|
|
1849
|
-
catch {
|
|
1850
|
-
// Security: dispatch might already have reached the provider. Never retry automatically.
|
|
1851
|
-
this.root.editor.addToHistory(draft);
|
|
1852
|
-
this.#promptHistory?.rememberRecovery(draft);
|
|
1853
|
-
this.#reportSubmissionError(undefined, "Submission failed; delivery is uncertain. Check the conversation before retrying. Press Up to recover the draft.");
|
|
1854
|
-
return { outcome: "failed", diagnostic: "submission delivery is uncertain" };
|
|
1855
|
-
}
|
|
1856
|
-
}
|
|
1857
|
-
async #guardSubmission(draft, action) {
|
|
1858
|
-
const revision = this.#editorRevision;
|
|
1859
|
-
try {
|
|
1860
|
-
return await action();
|
|
1861
|
-
}
|
|
1862
|
-
catch (error) {
|
|
1863
|
-
if (error instanceof ImageAttachmentError && error.code === "image-canceled")
|
|
1864
|
-
return rejected("image submission canceled");
|
|
1865
|
-
return this.#recoverSubmission(draft, revision, error);
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
#recoverSubmission(draft, revision, error) {
|
|
1869
|
-
this.root.editor.addToHistory(draft);
|
|
1870
|
-
this.#promptHistory?.rememberRecovery(draft);
|
|
1871
|
-
// Concurrency: never overwrite input typed (even typed and cleared) after this submission.
|
|
1872
|
-
if (revision === this.#editorRevision && this.root.editor.getText().length === 0)
|
|
1873
|
-
this.root.editor.setText(draft);
|
|
1874
|
-
const message = error instanceof ImageAttachmentError ? error.message : "Submission rejected. Check the prompt and attachments.";
|
|
1875
|
-
this.#reportSubmissionError(error, error instanceof ImageAttachmentError && error.code === "image-count"
|
|
1876
|
-
? message
|
|
1877
|
-
: `${message} Press Up to recover the draft.`);
|
|
1878
|
-
return rejected(message);
|
|
1879
|
-
}
|
|
1880
|
-
#reportSubmissionError(error, message) {
|
|
1881
|
-
// Security: arbitrary provider/extension error messages can contain the entire request.
|
|
1882
|
-
try {
|
|
1883
|
-
this.root.appendWorkflowResult({ command: "debug", outcome: "failed", message: message
|
|
1884
|
-
?? (error instanceof ImageAttachmentError ? error.message : "Submission failed. Check the prompt and try again.") }, error instanceof ImageAttachmentError ? error.code : undefined);
|
|
1885
|
-
this.#terminal.requestRender();
|
|
1886
|
-
}
|
|
1887
|
-
catch { /* Security: error presentation cannot create another rejected submission callback. */ }
|
|
1888
|
-
}
|
|
1889
1397
|
#simple(type) {
|
|
1890
1398
|
return { type, correlationId: this.#correlation(type), sessionId: this.backend.identity.sessionId };
|
|
1891
1399
|
}
|
|
@@ -1920,38 +1428,19 @@ export class OwnedUiSessionShell extends OwnedUiSessionPresenter {
|
|
|
1920
1428
|
function isCoalescedStreamBlock(kind) {
|
|
1921
1429
|
return kind === "assistant" || kind === "thinking" || kind === "tool-call" || kind === "tool-result";
|
|
1922
1430
|
}
|
|
1923
|
-
function
|
|
1431
|
+
function copyCharacters(snapshot) {
|
|
1924
1432
|
if (snapshot.literal !== true)
|
|
1925
|
-
return
|
|
1926
|
-
let
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
return { characters, hasNonWhitespace };
|
|
1933
|
-
}
|
|
1934
|
-
export function formatSessionResumeCommand(metadata) {
|
|
1935
|
-
const args = [PRODUCT_TEXT.commandName];
|
|
1936
|
-
if (!metadata.usesDefaultSessionDir)
|
|
1937
|
-
args.push("--session-dir", quoteCommandArgument(metadata.sessionDir));
|
|
1938
|
-
args.push("--session", metadata.sessionId);
|
|
1939
|
-
return args.join(" ");
|
|
1940
|
-
}
|
|
1941
|
-
export function quoteCommandArgument(value) {
|
|
1942
|
-
if (value.length > 0 && !/[^a-zA-Z0-9_\-./~:@]/.test(value))
|
|
1943
|
-
return value;
|
|
1944
|
-
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
1945
|
-
}
|
|
1946
|
-
function dim(value) {
|
|
1947
|
-
return `\u001b[2m${value}\u001b[22m`;
|
|
1433
|
+
return snapshot.sourceUnits;
|
|
1434
|
+
let count = Math.max(0, snapshot.rows.length - 1), hasContent = false;
|
|
1435
|
+
for (const { text } of snapshot.rows) {
|
|
1436
|
+
count += text.length;
|
|
1437
|
+
hasContent ||= /\S/u.test(text);
|
|
1438
|
+
}
|
|
1439
|
+
return hasContent ? count : undefined;
|
|
1948
1440
|
}
|
|
1949
1441
|
function rejected(diagnostic) {
|
|
1950
1442
|
return { outcome: "rejected", diagnostic };
|
|
1951
1443
|
}
|
|
1952
|
-
function modelKey(view) {
|
|
1953
|
-
return view.activeModel === null ? "" : `${view.activeModel.providerId}/${view.activeModel.modelId}`;
|
|
1954
|
-
}
|
|
1955
1444
|
function workflowAdapterResult(result) {
|
|
1956
1445
|
if (result.outcome === "completed")
|
|
1957
1446
|
return { outcome: "completed", diagnostic: null };
|