@bastani/atomic 0.8.28-alpha.1 → 0.8.28-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +120 -118
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +26 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/builtin/open-claude-design.ts +150 -13
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +5 -2
- package/dist/builtin/workflows/src/extension/dispatcher.ts +2 -0
- package/dist/builtin/workflows/src/extension/index.ts +8 -0
- package/dist/builtin/workflows/src/extension/render-result.ts +5 -2
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +18 -0
- package/dist/builtin/workflows/src/runs/background/status.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +1251 -110
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +34 -10
- package/dist/builtin/workflows/src/shared/expanded-workflow-graph.ts +10 -2
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +28 -9
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +9 -3
- package/dist/builtin/workflows/src/shared/store-types.ts +10 -3
- package/dist/builtin/workflows/src/shared/store.ts +29 -7
- package/dist/builtin/workflows/src/shared/types.ts +12 -10
- package/dist/builtin/workflows/src/tui/chat-surface.ts +32 -33
- package/dist/builtin/workflows/src/tui/run-detail.ts +23 -4
- package/dist/builtin/workflows/src/tui/status-helpers.ts +4 -0
- package/dist/builtin/workflows/src/tui/status-list.ts +47 -3
- package/dist/builtin/workflows/src/tui/store-widget-installer.ts +1 -1
- package/dist/builtin/workflows/src/tui/widget.ts +12 -3
- package/dist/builtin/workflows/src/workflows/define-workflow.ts +3 -3
- package/dist/cli/args.d.ts +4 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +35 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/project-trust.d.ts +10 -0
- package/dist/cli/project-trust.d.ts.map +1 -0
- package/dist/cli/project-trust.js +36 -0
- package/dist/cli/project-trust.js.map +1 -0
- package/dist/cli/startup-ui.d.ts +7 -0
- package/dist/cli/startup-ui.d.ts.map +1 -0
- package/dist/cli/startup-ui.js +57 -0
- package/dist/cli/startup-ui.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +24 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +3 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +1 -0
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +3 -2
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +9 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +70 -21
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +4 -3
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +3 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +9 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +18 -24
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/utils.d.ts +1 -1
- package/dist/core/compaction/utils.d.ts.map +1 -1
- package/dist/core/compaction/utils.js +1 -1
- package/dist/core/compaction/utils.js.map +1 -1
- package/dist/core/experimental.d.ts +2 -0
- package/dist/core/experimental.d.ts.map +1 -0
- package/dist/core/experimental.js +5 -0
- package/dist/core/experimental.js.map +1 -0
- package/dist/core/export-html/template.js +19 -6
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +6 -4
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +11 -4
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +53 -3
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +34 -4
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +2 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +27 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +64 -7
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/output-guard.d.ts +1 -0
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +52 -22
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +20 -8
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/project-trust.d.ts +15 -0
- package/dist/core/project-trust.d.ts.map +1 -0
- package/dist/core/project-trust.js +58 -0
- package/dist/core/project-trust.js.map +1 -0
- package/dist/core/prompt-templates.d.ts +5 -4
- package/dist/core/prompt-templates.d.ts.map +1 -1
- package/dist/core/prompt-templates.js +30 -29
- package/dist/core/prompt-templates.js.map +1 -1
- package/dist/core/provider-attribution.d.ts +4 -0
- package/dist/core/provider-attribution.d.ts.map +1 -0
- package/dist/core/provider-attribution.js +73 -0
- package/dist/core/provider-attribution.js.map +1 -0
- package/dist/core/provider-display-names.d.ts.map +1 -1
- package/dist/core/provider-display-names.js +3 -0
- package/dist/core/provider-display-names.js.map +1 -1
- package/dist/core/resolve-config-value.d.ts +9 -1
- package/dist/core/resolve-config-value.d.ts.map +1 -1
- package/dist/core/resolve-config-value.js +134 -11
- package/dist/core/resolve-config-value.js.map +1 -1
- package/dist/core/resource-loader.d.ts +12 -2
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +108 -18
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +13 -42
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +6 -7
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +99 -35
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +15 -2
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +69 -10
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -0
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/inline-input.d.ts +28 -0
- package/dist/core/tools/ask-user-question/state/inline-input.d.ts.map +1 -0
- package/dist/core/tools/ask-user-question/state/inline-input.js +56 -0
- package/dist/core/tools/ask-user-question/state/inline-input.js.map +1 -0
- package/dist/core/tools/ask-user-question/state/key-router.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/key-router.js +30 -4
- package/dist/core/tools/ask-user-question/state/key-router.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js +9 -8
- package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.d.ts +3 -2
- package/dist/core/tools/ask-user-question/state/row-intent.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.js +1 -1
- package/dist/core/tools/ask-user-question/state/row-intent.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +2 -0
- package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/selectors/projections.js +2 -0
- package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state-reducer.js +36 -24
- package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.d.ts +8 -0
- package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +6 -0
- package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.js +19 -1
- package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +3 -2
- package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/response-envelope.js +15 -3
- package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
- package/dist/core/tools/ask-user-question/tool/types.d.ts +2 -1
- package/dist/core/tools/ask-user-question/tool/types.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/tool/types.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts +5 -2
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.js +2 -0
- package/dist/core/tools/ask-user-question/view/components/chat-row-view.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts +1 -0
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +2 -1
- package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts +3 -3
- package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
- package/dist/core/tools/ask-user-question/view/props-adapter.js +11 -4
- package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
- package/dist/core/tools/bash-policy.d.ts +62 -0
- package/dist/core/tools/bash-policy.d.ts.map +1 -0
- package/dist/core/tools/bash-policy.js +1069 -0
- package/dist/core/tools/bash-policy.js.map +1 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +9 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +7 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +1 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/oversized-tool-result.d.ts +53 -0
- package/dist/core/tools/oversized-tool-result.d.ts.map +1 -0
- package/dist/core/tools/oversized-tool-result.js +206 -0
- package/dist/core/tools/oversized-tool-result.js.map +1 -0
- package/dist/core/tools/read.d.ts +12 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +99 -34
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +6 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +17 -1
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts +6 -0
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +2 -0
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/core/tools/tool-limits.d.ts +25 -0
- package/dist/core/tools/tool-limits.d.ts.map +1 -0
- package/dist/core/tools/tool-limits.js +25 -0
- package/dist/core/tools/tool-limits.js.map +1 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +1 -1
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts +31 -0
- package/dist/core/trust-manager.d.ts.map +1 -0
- package/dist/core/trust-manager.js +196 -0
- package/dist/core/trust-manager.js.map +1 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +142 -30
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts +3 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +325 -7
- package/dist/migrations.js.map +1 -1
- package/dist/modes/index.d.ts +1 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +2 -2
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +6 -0
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +9 -16
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +20 -0
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +22 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
- package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
- package/dist/modes/interactive/components/trust-selector.js +85 -0
- package/dist/modes/interactive/components/trust-selector.js.map +1 -0
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +1 -1
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +130 -9
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +1 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +3 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +50 -6
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +23 -4
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +6 -2
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +104 -10
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/changelog.d.ts +1 -0
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +72 -0
- package/dist/utils/changelog.js.map +1 -1
- package/dist/utils/deprecation.d.ts +4 -0
- package/dist/utils/deprecation.d.ts.map +1 -0
- package/dist/utils/deprecation.js +13 -0
- package/dist/utils/deprecation.js.map +1 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +54 -22
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/json.d.ts +3 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +7 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/open-browser.d.ts +9 -0
- package/dist/utils/open-browser.d.ts.map +1 -0
- package/dist/utils/open-browser.js +22 -0
- package/dist/utils/open-browser.js.map +1 -0
- package/docs/containerization.md +111 -0
- package/docs/custom-provider.md +9 -9
- package/docs/development.md +1 -1
- package/docs/docs.json +2 -0
- package/docs/extensions.md +40 -4
- package/docs/index.md +2 -0
- package/docs/models.md +10 -10
- package/docs/packages.md +1 -1
- package/docs/prompt-templates.md +9 -2
- package/docs/providers.md +18 -5
- package/docs/quickstart.md +1 -0
- package/docs/rpc.md +3 -2
- package/docs/sdk.md +47 -0
- package/docs/security.md +58 -0
- package/docs/session-format.md +2 -2
- package/docs/sessions.md +8 -0
- package/docs/settings.md +21 -4
- package/docs/skills.md +1 -1
- package/docs/terminal-setup.md +44 -2
- package/docs/themes.md +1 -1
- package/docs/tmux.md +4 -2
- package/docs/tui.md +14 -5
- package/docs/usage.md +17 -3
- package/docs/workflows.md +127 -15
- package/examples/README.md +1 -1
- package/examples/extensions/README.md +8 -5
- package/examples/extensions/bash-spawn-hook.ts +1 -1
- package/examples/extensions/built-in-tool-renderer.ts +1 -1
- package/examples/extensions/claude-rules.ts +1 -1
- package/examples/extensions/commands.ts +1 -1
- package/examples/extensions/custom-header.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +3 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
- package/examples/extensions/custom-provider-anthropic/package.json +6 -6
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +55 -4
- package/examples/extensions/custom-provider-gitlab-duo/package.json +3 -3
- package/examples/extensions/doom-overlay/README.md +1 -1
- package/examples/extensions/doom-overlay/index.ts +2 -2
- package/examples/extensions/git-merge-and-resolve.ts +115 -0
- package/examples/extensions/gondolin/index.ts +523 -0
- package/examples/extensions/gondolin/package-lock.json +185 -0
- package/examples/extensions/gondolin/package.json +19 -0
- package/examples/extensions/handoff.ts +1 -1
- package/examples/extensions/hidden-thinking-label.ts +1 -1
- package/examples/extensions/inline-bash.ts +2 -2
- package/examples/extensions/input-transform-streaming.ts +39 -0
- package/examples/extensions/input-transform.ts +3 -3
- package/examples/extensions/interactive-shell.ts +2 -2
- package/examples/extensions/mac-system-theme.ts +2 -2
- package/examples/extensions/minimal-mode.ts +1 -1
- package/examples/extensions/modal-editor.ts +1 -1
- package/examples/extensions/model-status.ts +1 -1
- package/examples/extensions/overlay-qa-tests.ts +198 -179
- package/examples/extensions/overlay-test.ts +1 -1
- package/examples/extensions/pirate.ts +1 -1
- package/examples/extensions/preset.ts +14 -12
- package/examples/extensions/project-trust.ts +64 -0
- package/examples/extensions/prompt-customizer.ts +1 -1
- package/examples/extensions/qna.ts +1 -1
- package/examples/extensions/question.ts +1 -1
- package/examples/extensions/questionnaire.ts +1 -1
- package/examples/extensions/rainbow-editor.ts +1 -1
- package/examples/extensions/sandbox/index.ts +16 -14
- package/examples/extensions/sandbox/package-lock.json +90 -90
- package/examples/extensions/sandbox/package.json +17 -17
- package/examples/extensions/snake.ts +1 -1
- package/examples/extensions/space-invaders.ts +1 -1
- package/examples/extensions/ssh.ts +2 -2
- package/examples/extensions/subagent/README.md +13 -13
- package/examples/extensions/subagent/agents.ts +4 -2
- package/examples/extensions/subagent/index.ts +6 -6
- package/examples/extensions/summarize.ts +1 -1
- package/examples/extensions/tic-tac-toe.ts +1 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/extensions/tool-override.ts +1 -1
- package/examples/extensions/tools.ts +6 -1
- package/examples/extensions/with-deps/package-lock.json +4 -4
- package/examples/extensions/with-deps/package.json +7 -7
- package/examples/extensions/working-indicator.ts +4 -4
- package/examples/extensions/working-message-test.ts +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/06-extensions.ts +2 -2
- package/examples/sdk/08-prompt-templates.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +2 -2
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nconst DEFAULT_PROMPT_TOOLS = [\n \"read\",\n \"bash\",\n \"edit\",\n \"write\",\n \"ask_user_question\",\n \"todo\",\n] as const;\n\nexport interface SystemPromptModel {\n /** Provider identifier for the selected model. */\n provider: string;\n /** Stable provider-specific model identifier. */\n id: string;\n /** Human-readable model name, when available. */\n name?: string;\n}\n\nexport interface BuildSystemPromptOptions {\n /** Custom system prompt (replaces default). */\n customPrompt?: string;\n /** Tools to include in prompt. Default: [read, bash, edit, write, ask_user_question, todo] */\n selectedTools?: string[];\n /** Tool names explicitly excluded by the caller and omitted from generated guidance. */\n excludedTools?: string[];\n /** Optional one-line tool snippets keyed by tool name. */\n toolSnippets?: Record<string, string>;\n /** Additional guideline bullets appended to the default system prompt guidelines. */\n promptGuidelines?: string[];\n /** Text to append to system prompt. */\n appendSystemPrompt?: string;\n /** Working directory. */\n cwd: string;\n /** Currently selected model, used for model-aware prompt metadata. */\n selectedModel?: SystemPromptModel;\n /** Current reasoning/thinking level for the selected model. */\n selectedThinkingLevel?: string;\n /** Pre-loaded context files. */\n contextFiles?: Array<{ path: string; content: string }>;\n /** Pre-loaded skills. */\n skills?: Skill[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n const {\n customPrompt,\n selectedTools,\n excludedTools,\n toolSnippets,\n promptGuidelines,\n appendSystemPrompt,\n cwd,\n selectedModel,\n selectedThinkingLevel,\n contextFiles: providedContextFiles,\n skills: providedSkills,\n } = options;\n const resolvedCwd = cwd;\n const promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n const day = String(now.getDate()).padStart(2, \"0\");\n const date = `${year}-${month}-${day}`;\n\n const appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n const modelName =\n selectedModel?.name?.trim() || selectedModel?.id || \"unknown\";\n const modelReasoningLevel = selectedThinkingLevel?.trim() || \"off\";\n\n const contextFiles = providedContextFiles ?? [];\n const skills = providedSkills ?? [];\n const explicitlyExcludedTools = new Set(excludedTools ?? []);\n const isPromptToolAvailable = (name: string): boolean =>\n (!selectedTools || selectedTools.includes(name)) &&\n !explicitlyExcludedTools.has(name);\n\n if (customPrompt) {\n let prompt = customPrompt;\n\n if (appendSection) {\n prompt += appendSection;\n }\n\n // Append project context files\n if (contextFiles.length > 0) {\n prompt += \"\\n\\n# Project Context\\n\\n\";\n prompt += \"Project-specific instructions and guidelines:\\n\\n\";\n for (const { path: filePath, content } of contextFiles) {\n prompt += `<context_file path=\\\"${filePath}\\\">\\n${content}\\n</context_file>\\n\\n`;\n }\n }\n\n // Append skills section (only if read tool is available)\n if (isPromptToolAvailable(\"read\") && skills.length > 0) {\n prompt += formatSkillsForPrompt(skills);\n }\n\n // Add model metadata, date, and working directory last\n prompt += `\\nModel name (used for commit attribution): ${modelName}`;\n prompt += `\\nModel reasoning level: ${modelReasoningLevel}`;\n prompt += `\\nCurrent date: ${date}`;\n prompt += `\\nCurrent working directory: ${promptCwd}`;\n\n return prompt;\n }\n\n // Get absolute paths to documentation and examples\n const readmePath = getReadmePath();\n const docsPath = getDocsPath();\n const examplesPath = getExamplesPath();\n\n // Build tools list based on selected tools.\n // A tool appears in Available tools only when the caller provides a one-line snippet.\n const tools = (selectedTools ?? DEFAULT_PROMPT_TOOLS).filter(\n (name) => !explicitlyExcludedTools.has(name),\n );\n const visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n const toolsList =\n visibleTools.length > 0\n ? visibleTools\n .map((name) => `- ${name}: ${toolSnippets![name]}`)\n .join(\"\\n\")\n : \"(none)\";\n\n // Build guidelines based on which tools are actually available\n const guidelinesList: string[] = [];\n const guidelinesSet = new Set<string>();\n const addGuideline = (guideline: string): void => {\n if (guidelinesSet.has(guideline)) {\n return;\n }\n guidelinesSet.add(guideline);\n guidelinesList.push(guideline);\n };\n\n const hasBash = tools.includes(\"bash\");\n const hasGrep = tools.includes(\"grep\");\n const hasFind = tools.includes(\"find\");\n const hasLs = tools.includes(\"ls\");\n const hasRead = tools.includes(\"read\");\n\n // File exploration guidelines\n if (hasBash && !hasGrep && !hasFind && !hasLs) {\n addGuideline(\"Use bash for file operations like ls, rg, find\");\n } else if (hasBash && (hasGrep || hasFind || hasLs)) {\n addGuideline(\n \"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\",\n );\n }\n\n for (const guideline of promptGuidelines ?? []) {\n const normalized = guideline.trim();\n if (normalized.length > 0) {\n addGuideline(normalized);\n }\n }\n\n // Always include these\n addGuideline(\"Be concise in your responses\");\n addGuideline(\"Show file paths clearly when working with files\");\n\n const guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n const askUserQuestionGuidance = explicitlyExcludedTools.has(\n \"ask_user_question\",\n )\n ? \"\"\n : \"- Always ask clarifying questions if the user's request is ambiguous or lacks necessary details. NEVER make assumptions about what the user wants. If you find yourself circling in thought and asking what the user \\\"really\\\" wants, stop and ask the user for clarification using the ask_user_question tool if available. It's better to clarify intent rather than to guess.\\n- **Asking the user is a strict requirement**: Whenever you need to ask the user anything — a clarification, a decision, a choice between options, a confirmation, or any yes/no question — you MUST ask it by calling the `ask_user_question` tool. Never pose a question to the user as plain assistant text. Every question you direct to the user goes through `ask_user_question`; writing the question in prose instead of calling the tool is not allowed.\";\n const todoGuidance = explicitlyExcludedTools.has(\"todo\")\n ? \"\"\n : \"- **To-do management**: If the user has a complex task that can be broken down into actionable steps, use the `todo` tool to create a task list before proceeding. This ensures clarity and alignment with the user's goals and that you have a way to track your work and ensure you are meeting the user's expectations.\";\n\n const subagentGuidance = explicitlyExcludedTools.has(\"subagent\")\n ? \"\"\n : `- **Subagent Orchestration**:\n - To avoid draining your context window, prefer to use subagents for complex tasks all non-trivial operations should be delegated to subagents.\n - You should delegate running bash commands (particularly ones that are likely to produce lots of output) such as investigating with the \\`aws\\` CLI, using the \\`gh\\` CLI, digging through logs to \\`bash\\` subagents.\n - You should use separate subagents for separate tasks, and you may launch them in parallel, but do not delegate multiple tasks that are likely to have significant overlap to separate subagents.\n - Sometimes subagents will take a long time. DO NOT attempt to do the job yourself while waiting for the subagent to respond Instead, use the time to plan out your next steps.\n - **Debugging**: When a user asks about debugging, spawn a debugger subagent first.\n - Do not attempt to debug or analyze code yourself without first consulting the debugger subagent.\n - Explain the debugger's insights to the user clearly and concisely.\n - Once the user confirms, implement the necessary code changes based on those insights.\n - If the user has follow-up questions, spawn additional debugger and research subagents as needed.`;\n\n const workflowGuidance = explicitlyExcludedTools.has(\"workflow\")\n ? \"\"\n : `- **Workflows**: Use the \\`workflow\\` tool for existing named workflows and for repeatable, inspectable, resumable, or multi-stage processes; use direct \\`task\\`, \\`tasks\\`, or \\`chain\\` workflow calls for one-off tracked work when that is useful.\n - For unfamiliar named workflows, discover with \\`action: \"list\"\\`, inspect with \\`action: \"get\"\\` or \\`action: \"inputs\"\\`, and run with \\`action: \"run\"\\`, \\`workflow\\`, and validated \\`inputs\\`; do not invent workflow names or input keys.\n - When designing or editing workflows, read docs/workflows.md and reference its Workflow Starter Patterns: Classify-and-act, Fan-out-and-synthesize, Adversarial verification, Generate-and-filter, Tournament, and Loop until done. Choose or combine these patterns before inventing a custom stage graph, and reflect the selected pattern in the spec and Mermaid diagram when using the create-spec skill.\n - Once you run a workflow with the workflow tool, end your current turn and wait for the next user input or lifecycle notice.\n - You will automatically be alerted of key lifecycle events like start, finish, failure; do not micro-manage the run with sleep/status polling loops or read its logs/stages unless the user asks you to or you need information for the next step.\n - If the user needs information from the workflow run, use targeted \\`status\\`/\\`stages\\`/\\`stage\\` checks instead of trying to read everything.\n - Offer to help the user on another task instead of anxiously polling or help the user run another workflow if they need.\n - Use run-control and messaging actions (\\`send\\`, \\`pause\\`, \\`resume\\`, \\`interrupt\\`, \\`kill\\`) only when needed to answer prompts, steer a stage, resume or interrupt paused work, or respond to user requests/control signals.\n - For transcripts, avoid reading whole session transcripts at once. Use \\`stages\\` or \\`stage\\` to get \\`sessionFile\\`/\\`transcriptPath\\`, quote the exact path without rewriting separators (preserve Windows backslashes), search it with \\`rg\\`/\\`grep\\`, and read small relevant ranges; use \\`transcript\\` with explicit \\`tail\\` or \\`limit\\` only for quick recent-context checks.\n - If a user asks to create or edit a workflow, use the create-spec skill when available and ask detailed clarifying questions until you understand its purpose, inputs, stages, handoffs, validation, success criteria, and selected starter pattern. Then read the workflow docs/examples and implement the workflow from the created spec directly as a TypeScript definition. After you implement the workflow, reload it to access it and run it with test inputs to validate it works as intended before presenting it to the user.\n - Tip: when designing workflows, implement it in a way that you pass information from stage to stage by writing it to a file or artifact (either deterministic or model-driven), pass the path with \\`reads\\`, and explicitly prompt the downstream agent with wording like \\`Read the file at <path>...\\`; do not inject large \\`previous\\` payloads or session history into the next prompt unless explicitly requested to.\n - If you run \\`ralph\\` or \\`goal\\` workflow, define an objective that includes tight scope, concrete and verifiable done criteria, and validation steps; then monitor progress as above instead of doing parallel implementation yourself.`;\n\n let prompt = `You are an expert coding assistant operating named Atomic, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n${askUserQuestionGuidance}\n${todoGuidance}\n${subagentGuidance}\n${workflowGuidance}\n\nAtomic documentation (read only when the user asks about customizing Atomic itself, its SDK, creating workflows, packages, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- Docs/examples references above must be resolved against these absolute roots; e.g. docs/foo.md means ${docsPath}/foo.md and examples/bar means ${examplesPath}/bar.\n- When asked about: atomic workflows (docs/workflows.md), extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), atomic packages (docs/packages.md)\n- When working on Atomic topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read Atomic .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n if (appendSection) {\n prompt += appendSection;\n }\n\n // Append project context files\n if (contextFiles.length > 0) {\n prompt += \"\\n\\n# Project Context\\n\\n\";\n prompt += \"Project-specific instructions and guidelines:\\n\\n\";\n for (const { path: filePath, content } of contextFiles) {\n prompt += `<context_file path=\\\"${filePath}\\\">\\n${content}\\n</context_file>\\n\\n`;\n }\n }\n\n // Append skills section (only if read tool is available)\n if (hasRead && skills.length > 0) {\n prompt += formatSkillsForPrompt(skills);\n }\n\n // Add model metadata, date, and working directory last\n prompt += `\\nModel name (used for commit attribution): ${modelName}`;\n prompt += `\\nModel reasoning level: ${modelReasoningLevel}`;\n prompt += `\\nCurrent date: ${date}`;\n prompt += `\\nCurrent working directory: ${promptCwd}`;\n\n return prompt;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,qBAAqB,EAAc,MAAM,aAAa,CAAC;AAEhE,MAAM,oBAAoB,GAAG;IAC3B,MAAM;IACN,MAAM;IACN,MAAM;IACN,OAAO;IACP,mBAAmB;IACnB,MAAM;CACE,CAAC;AAoCX,kEAAkE;AAClE,MAAM,UAAU,iBAAiB,CAAC,OAAiC;IACjE,MAAM,EACJ,YAAY,EACZ,aAAa,EACb,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,GAAG,EACH,aAAa,EACb,qBAAqB,EACrB,YAAY,EAAE,oBAAoB,EAClC,MAAM,EAAE,cAAc,GACvB,GAAG,OAAO,CAAC;IACZ,MAAM,WAAW,GAAG,GAAG,CAAC;IACxB,MAAM,SAAS,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAElD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;IAEvC,MAAM,aAAa,GAAG,kBAAkB,CAAC,CAAC,CAAC,OAAO,kBAAkB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,MAAM,SAAS,GACb,aAAa,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,aAAa,EAAE,EAAE,IAAI,SAAS,CAAC;IAChE,MAAM,mBAAmB,GAAG,qBAAqB,EAAE,IAAI,EAAE,IAAI,KAAK,CAAC;IAEnE,MAAM,YAAY,GAAG,oBAAoB,IAAI,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,cAAc,IAAI,EAAE,CAAC;IACpC,MAAM,uBAAuB,GAAG,IAAI,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAW,EAAE,CACtD,CAAC,CAAC,aAAa,IAAI,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAChD,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAErC,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,MAAM,GAAG,YAAY,CAAC;QAE1B,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,IAAI,aAAa,CAAC;QAC1B,CAAC;QAED,+BAA+B;QAC/B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,2BAA2B,CAAC;YACtC,MAAM,IAAI,mDAAmD,CAAC;YAC9D,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,YAAY,EAAE,CAAC;gBACvD,MAAM,IAAI,wBAAwB,QAAQ,QAAQ,OAAO,uBAAuB,CAAC;YACnF,CAAC;QACH,CAAC;QAED,yDAAyD;QACzD,IAAI,qBAAqB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC1C,CAAC;QAED,uDAAuD;QACvD,MAAM,IAAI,+CAA+C,SAAS,EAAE,CAAC;QACrE,MAAM,IAAI,4BAA4B,mBAAmB,EAAE,CAAC;QAC5D,MAAM,IAAI,mBAAmB,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,gCAAgC,SAAS,EAAE,CAAC;QAEtD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,mDAAmD;IACnD,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;IAEvC,4CAA4C;IAC5C,sFAAsF;IACtF,MAAM,KAAK,GAAG,CAAC,aAAa,IAAI,oBAAoB,CAAC,CAAC,MAAM,CAC1D,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,uBAAuB,CAAC,GAAG,CAAC,IAAI,CAAC,CAC7C,CAAC;IACF,MAAM,YAAY,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IACpE,MAAM,SAAS,GACb,YAAY,CAAC,MAAM,GAAG,CAAC;QACrB,CAAC,CAAC,YAAY;aACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,KAAK,YAAa,CAAC,IAAI,CAAC,EAAE,CAAC;aAClD,IAAI,CAAC,IAAI,CAAC;QACf,CAAC,CAAC,QAAQ,CAAC;IAEf,+DAA+D;IAC/D,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAQ,EAAE;QAC/C,IAAI,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACjC,OAAO;QACT,CAAC;QACD,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7B,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAEvC,8BAA8B;IAC9B,IAAI,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9C,YAAY,CAAC,gDAAgD,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,gBAAgB,IAAI,EAAE,EAAE,CAAC;QAC/C,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;QACpC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,YAAY,CAAC,UAAU,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,YAAY,CAAC,8BAA8B,CAAC,CAAC;IAC7C,YAAY,CAAC,iDAAiD,CAAC,CAAC;IAEhE,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAElE,MAAM,uBAAuB,GAAG,uBAAuB,CAAC,GAAG,CACzD,mBAAmB,CACpB;QACC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,szBAAszB,CAAC;IAC3zB,MAAM,YAAY,GAAG,uBAAuB,CAAC,GAAG,CAAC,MAAM,CAAC;QACtD,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,4TAA4T,CAAC;IAEjU,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,GAAG,CAAC,UAAU,CAAC;QAC9D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;;;;;;;;;uGASiG,CAAC;IAEtG,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,GAAG,CAAC,UAAU,CAAC;QAC9D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;;;;;;;;;;;6OAWuO,CAAC;IAE5O,IAAI,MAAM,GAAG;;;EAGb,SAAS;;;;;EAKT,UAAU;EACV,uBAAuB;EACvB,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;;;wBAGM,UAAU;qBACb,QAAQ;cACf,YAAY;yGAC+E,QAAQ,kCAAkC,YAAY;;;8GAGjD,CAAC;IAE7G,IAAI,aAAa,EAAE,CAAC;QAClB,MAAM,IAAI,aAAa,CAAC;IAC1B,CAAC;IAED,+BAA+B;IAC/B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,2BAA2B,CAAC;QACtC,MAAM,IAAI,mDAAmD,CAAC;QAC9D,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,YAAY,EAAE,CAAC;YACvD,MAAM,IAAI,wBAAwB,QAAQ,QAAQ,OAAO,uBAAuB,CAAC;QACnF,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,IAAI,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC;IAED,uDAAuD;IACvD,MAAM,IAAI,+CAA+C,SAAS,EAAE,CAAC;IACrE,MAAM,IAAI,4BAA4B,mBAAmB,EAAE,CAAC;IAC5D,MAAM,IAAI,mBAAmB,IAAI,EAAE,CAAC;IACpC,MAAM,IAAI,gCAAgC,SAAS,EAAE,CAAC;IAEtD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport { getDocsPath, getExamplesPath, getReadmePath } from \"../config.ts\";\nimport { formatSkillsForPrompt, type Skill } from \"./skills.ts\";\n\nconst DEFAULT_PROMPT_TOOLS = [\n \"read\",\n \"bash\",\n \"edit\",\n \"write\",\n \"ask_user_question\",\n \"todo\",\n] as const;\n\nexport interface SystemPromptModel {\n /** Provider identifier for the selected model. */\n provider: string;\n /** Stable provider-specific model identifier. */\n id: string;\n /** Human-readable model name, when available. */\n name?: string;\n}\n\nexport interface BuildSystemPromptOptions {\n /** Custom system prompt (replaces default). */\n customPrompt?: string;\n /** Tools to include in prompt. Default: [read, bash, edit, write, ask_user_question, todo] */\n selectedTools?: string[];\n /** Tool names explicitly excluded by the caller and omitted from generated guidance. */\n excludedTools?: string[];\n /** Optional one-line tool snippets keyed by tool name. */\n toolSnippets?: Record<string, string>;\n /** Additional guideline bullets appended to the default system prompt guidelines. */\n promptGuidelines?: string[];\n /** Text to append to system prompt. */\n appendSystemPrompt?: string;\n /** Working directory. */\n cwd: string;\n /** Currently selected model, used for model-aware prompt metadata. */\n selectedModel?: SystemPromptModel;\n /** Current reasoning/thinking level for the selected model. */\n selectedThinkingLevel?: string;\n /** Pre-loaded context files. */\n contextFiles?: Array<{ path: string; content: string }>;\n /** Pre-loaded skills. */\n skills?: Skill[];\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(options: BuildSystemPromptOptions): string {\n const {\n customPrompt,\n selectedTools,\n excludedTools,\n toolSnippets,\n promptGuidelines,\n appendSystemPrompt,\n cwd,\n selectedModel,\n selectedThinkingLevel,\n contextFiles: providedContextFiles,\n skills: providedSkills,\n } = options;\n const resolvedCwd = cwd;\n const promptCwd = resolvedCwd.replace(/\\\\/g, \"/\");\n\n const now = new Date();\n const year = now.getFullYear();\n const month = String(now.getMonth() + 1).padStart(2, \"0\");\n const day = String(now.getDate()).padStart(2, \"0\");\n const date = `${year}-${month}-${day}`;\n\n const appendSection = appendSystemPrompt ? `\\n\\n${appendSystemPrompt}` : \"\";\n const modelName =\n selectedModel?.name?.trim() || selectedModel?.id || \"unknown\";\n const modelReasoningLevel = selectedThinkingLevel?.trim() || \"off\";\n\n const contextFiles = providedContextFiles ?? [];\n const skills = providedSkills ?? [];\n const explicitlyExcludedTools = new Set(excludedTools ?? []);\n const isPromptToolAvailable = (name: string): boolean =>\n (!selectedTools || selectedTools.includes(name)) &&\n !explicitlyExcludedTools.has(name);\n\n if (customPrompt) {\n let prompt = customPrompt;\n\n if (appendSection) {\n prompt += appendSection;\n }\n\n // Append project context files\n if (contextFiles.length > 0) {\n prompt += \"\\n\\n# Project Context\\n\\n\";\n prompt += \"Project-specific instructions and guidelines:\\n\\n\";\n for (const { path: filePath, content } of contextFiles) {\n prompt += `<context_file path=\\\"${filePath}\\\">\\n${content}\\n</context_file>\\n\\n`;\n }\n }\n\n // Append skills section (only if read tool is available)\n if (isPromptToolAvailable(\"read\") && skills.length > 0) {\n prompt += formatSkillsForPrompt(skills);\n }\n\n // Add model metadata, date, and working directory last\n prompt += `\\nModel name (used for commit attribution): ${modelName}`;\n prompt += `\\nModel reasoning level: ${modelReasoningLevel}`;\n prompt += `\\nCurrent date: ${date}`;\n prompt += `\\nCurrent working directory: ${promptCwd}`;\n\n return prompt;\n }\n\n // Get absolute paths to documentation and examples\n const readmePath = getReadmePath();\n const docsPath = getDocsPath();\n const examplesPath = getExamplesPath();\n\n // Build tools list based on selected tools.\n // A tool appears in Available tools only when the caller provides a one-line snippet.\n const tools = (selectedTools ?? DEFAULT_PROMPT_TOOLS).filter(\n (name) => !explicitlyExcludedTools.has(name),\n );\n const visibleTools = tools.filter((name) => !!toolSnippets?.[name]);\n const toolsList =\n visibleTools.length > 0\n ? visibleTools\n .map((name) => `- ${name}: ${toolSnippets![name]}`)\n .join(\"\\n\")\n : \"(none)\";\n\n // Build guidelines based on which tools are actually available\n const guidelinesList: string[] = [];\n const guidelinesSet = new Set<string>();\n const addGuideline = (guideline: string): void => {\n if (guidelinesSet.has(guideline)) {\n return;\n }\n guidelinesSet.add(guideline);\n guidelinesList.push(guideline);\n };\n\n const hasBash = tools.includes(\"bash\");\n const hasGrep = tools.includes(\"grep\");\n const hasFind = tools.includes(\"find\");\n const hasLs = tools.includes(\"ls\");\n const hasRead = tools.includes(\"read\");\n\n // File exploration guidelines\n if (hasBash && !hasGrep && !hasFind && !hasLs) {\n addGuideline(\"Use bash for file operations like ls, rg, find\");\n }\n\n for (const guideline of promptGuidelines ?? []) {\n const normalized = guideline.trim();\n if (normalized.length > 0) {\n addGuideline(normalized);\n }\n }\n\n // Always include these\n addGuideline(\"Be concise in your responses\");\n addGuideline(\"Show file paths clearly when working with files\");\n\n const guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n const askUserQuestionGuidance = explicitlyExcludedTools.has(\n \"ask_user_question\",\n )\n ? \"\"\n : \"- Always ask clarifying questions if the user's request is ambiguous or lacks necessary details. NEVER make assumptions about what the user wants. If you find yourself circling in thought and asking what the user \\\"really\\\" wants, stop and ask the user for clarification using the ask_user_question tool if available. It's better to clarify intent rather than to guess.\\n- **Asking the user is a strict requirement**: Whenever you need to ask the user anything — a clarification, a decision, a choice between options, a confirmation, or any yes/no question — you MUST ask it by calling the `ask_user_question` tool. Never pose a question to the user as plain assistant text. Every question you direct to the user goes through `ask_user_question`; writing the question in prose instead of calling the tool is not allowed.\";\n const todoGuidance = explicitlyExcludedTools.has(\"todo\")\n ? \"\"\n : \"- **To-do management**: If the user has a complex task that can be broken down into actionable steps, use the `todo` tool to create a task list before proceeding. This ensures clarity and alignment with the user's goals and that you have a way to track your work and ensure you are meeting the user's expectations.\";\n\n const subagentGuidance = explicitlyExcludedTools.has(\"subagent\")\n ? \"\"\n : `- **Subagent Orchestration**:\n - To avoid draining your context window, prefer to use subagents for complex tasks all non-trivial operations should be delegated to subagents.\n - You should delegate running bash commands (particularly ones that are likely to produce lots of output) such as investigating with the \\`aws\\` CLI, using the \\`gh\\` CLI, digging through logs to \\`bash\\` subagents.\n - You should use separate subagents for separate tasks, and you may launch them in parallel, but do not delegate multiple tasks that are likely to have significant overlap to separate subagents.\n - Sometimes subagents will take a long time. DO NOT attempt to do the job yourself while waiting for the subagent to respond Instead, use the time to plan out your next steps.\n - **Debugging**: When a user asks about debugging, spawn a debugger subagent first.\n - Do not attempt to debug or analyze code yourself without first consulting the debugger subagent.\n - Explain the debugger's insights to the user clearly and concisely.\n - Once the user confirms, implement the necessary code changes based on those insights.\n - If the user has follow-up questions, spawn additional debugger and research subagents as needed.`;\n\n const workflowGuidance = explicitlyExcludedTools.has(\"workflow\")\n ? \"\"\n : `- **Workflows**: Use the \\`workflow\\` tool for existing named workflows and for repeatable, inspectable, resumable, or multi-stage processes; use direct \\`task\\`, \\`tasks\\`, or \\`chain\\` workflow calls for one-off tracked work when that is useful.\n - For unfamiliar named workflows, discover with \\`action: \"list\"\\`, inspect with \\`action: \"get\"\\` or \\`action: \"inputs\"\\`, and run with \\`action: \"run\"\\`, \\`workflow\\`, and validated \\`inputs\\`; do not invent workflow names or input keys.\n - When designing or editing workflows, read docs/workflows.md and reference its Workflow Starter Patterns: Classify-and-act, Fan-out-and-synthesize, Adversarial verification, Generate-and-filter, Tournament, and Loop until done. Choose or combine these patterns before inventing a custom stage graph, and reflect the selected pattern in the spec and Mermaid diagram when using the create-spec skill.\n - Once you run a workflow with the workflow tool, end your current turn and wait for the next user input or lifecycle notice.\n - You will automatically be alerted of key lifecycle events like start, finish, failure; do not micro-manage the run with sleep/status polling loops or read its logs/stages unless the user asks you to or you need information for the next step.\n - If the user needs information from the workflow run, use targeted \\`status\\`/\\`stages\\`/\\`stage\\` checks instead of trying to read everything.\n - Offer to help the user on another task instead of anxiously polling or help the user run another workflow if they need.\n - Use run-control and messaging actions (\\`send\\`, \\`pause\\`, \\`resume\\`, \\`interrupt\\`, \\`kill\\`) only when needed to answer prompts, steer a stage, resume or interrupt paused work, or respond to user requests/control signals.\n - For transcripts, avoid reading whole session transcripts at once. Use \\`stages\\` or \\`stage\\` to get \\`sessionFile\\`/\\`transcriptPath\\`, quote the exact path without rewriting separators (preserve Windows backslashes), search it with \\`rg\\`/\\`grep\\`, and read small relevant ranges; use \\`transcript\\` with explicit \\`tail\\` or \\`limit\\` only for quick recent-context checks.\n - If a user asks to create or edit a workflow, use the create-spec skill when available and ask detailed clarifying questions until you understand its purpose, inputs, stages, handoffs, validation, success criteria, and selected starter pattern. Then read the workflow docs/examples and implement the workflow from the created spec directly as a TypeScript definition. After you implement the workflow, reload it to access it and run it with test inputs to validate it works as intended before presenting it to the user.\n - Tip: when designing workflows, implement it in a way that you pass information from stage to stage by writing it to a file or artifact (either deterministic or model-driven), pass the path with \\`reads\\`, and explicitly prompt the downstream agent with wording like \\`Read the file at <path>...\\`; do not inject large \\`previous\\` payloads or session history into the next prompt unless explicitly requested to.\n - If you run \\`ralph\\` or \\`goal\\` workflow, define an objective that includes tight scope, concrete and verifiable done criteria, and validation steps; then monitor progress as above instead of doing parallel implementation yourself.`;\n\n let prompt = `You are an expert coding assistant operating named Atomic, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nIn addition to the tools above, you may have access to other custom tools depending on the project.\n\nGuidelines:\n${guidelines}\n${askUserQuestionGuidance}\n${todoGuidance}\n${subagentGuidance}\n${workflowGuidance}\n\nAtomic documentation (read only when the user asks about customizing Atomic itself, its SDK, creating workflows, packages, extensions, themes, skills, or TUI):\n- Main documentation: ${readmePath}\n- Additional docs: ${docsPath}\n- Examples: ${examplesPath} (extensions, custom tools, SDK)\n- Docs/examples references above must be resolved against these absolute roots; e.g. docs/foo.md means ${docsPath}/foo.md and examples/bar means ${examplesPath}/bar.\n- When asked about: atomic workflows (docs/workflows.md), extensions (docs/extensions.md, examples/extensions/), themes (docs/themes.md), skills (docs/skills.md), prompt templates (docs/prompt-templates.md), TUI components (docs/tui.md), keybindings (docs/keybindings.md), SDK integrations (docs/sdk.md), custom providers (docs/custom-provider.md), adding models (docs/models.md), atomic packages (docs/packages.md)\n- When working on Atomic topics, read the docs and examples, and follow .md cross-references before implementing\n- Always read Atomic .md files completely and follow links to related docs (e.g., tui.md for TUI API details)`;\n\n if (appendSection) {\n prompt += appendSection;\n }\n\n // Append project context files\n if (contextFiles.length > 0) {\n prompt += \"\\n\\n# Project Context\\n\\n\";\n prompt += \"Project-specific instructions and guidelines:\\n\\n\";\n for (const { path: filePath, content } of contextFiles) {\n prompt += `<context_file path=\\\"${filePath}\\\">\\n${content}\\n</context_file>\\n\\n`;\n }\n }\n\n // Append skills section (only if read tool is available)\n if (hasRead && skills.length > 0) {\n prompt += formatSkillsForPrompt(skills);\n }\n\n // Add model metadata, date, and working directory last\n prompt += `\\nModel name (used for commit attribution): ${modelName}`;\n prompt += `\\nModel reasoning level: ${modelReasoningLevel}`;\n prompt += `\\nCurrent date: ${date}`;\n prompt += `\\nCurrent working directory: ${promptCwd}`;\n\n return prompt;\n}\n"]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { InlineInputOwner, QuestionnaireState } from "./state.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Shared owner-branching accessors/mutator for the two per-owner inline-input
|
|
4
|
+
* map pairs on `QuestionnaireState`:
|
|
5
|
+
* - `"chat"` → `chatDraftByTab` / `chatCaretByTab`
|
|
6
|
+
* - `"other"` → `customDraftByTab` / `customCaretByTab`
|
|
7
|
+
*
|
|
8
|
+
* Both the pure reducer (`state-reducer.ts`) and the runtime
|
|
9
|
+
* (`questionnaire-session.ts`) read and write these maps. Hoisting the helpers
|
|
10
|
+
* here keeps the `owner === "chat"` branch in exactly one place instead of
|
|
11
|
+
* copying it into each caller (previously `withInlineDraft` was duplicated
|
|
12
|
+
* verbatim and the accessor pairs were near-duplicates split across the two
|
|
13
|
+
* files).
|
|
14
|
+
*/
|
|
15
|
+
/** Raw draft text persisted for `owner` at the current tab, or undefined when none is stored. */
|
|
16
|
+
export declare function readInlineDraft(state: QuestionnaireState, owner: InlineInputOwner): string | undefined;
|
|
17
|
+
/** Raw caret offset persisted for `owner` at the current tab, or undefined when none is stored. */
|
|
18
|
+
export declare function readInlineCaret(state: QuestionnaireState, owner: InlineInputOwner): number | undefined;
|
|
19
|
+
/** Immutably persist `value`/`caret` into `owner`'s draft+caret maps for the current tab. */
|
|
20
|
+
export declare function withInlineDraft(state: QuestionnaireState, owner: InlineInputOwner, value: string, caret: number): QuestionnaireState;
|
|
21
|
+
/**
|
|
22
|
+
* Draft text used to hydrate the inline editor when (re)focusing `owner`: the
|
|
23
|
+
* in-flight draft when present, otherwise the prior committed answer for that
|
|
24
|
+
* owner. The `"chat"` owner excludes the reserved sentinel label so a prior
|
|
25
|
+
* signal-only chat (`"Chat about this"`) does not re-hydrate as editable text.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveInlineDraftValue(state: QuestionnaireState, owner: InlineInputOwner): string;
|
|
28
|
+
//# sourceMappingURL=inline-input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-input.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/inline-input.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEvE;;;;;;;;;;;;GAYG;AAEH,iGAAiG;AACjG,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAEtG;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAEtG;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAC9B,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,gBAAgB,EACvB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,GACX,kBAAkB,CAapB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,EAAE,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAUlG","sourcesContent":["import { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { InlineInputOwner, QuestionnaireState } from \"./state.ts\";\n\n/**\n * Shared owner-branching accessors/mutator for the two per-owner inline-input\n * map pairs on `QuestionnaireState`:\n * - `\"chat\"` → `chatDraftByTab` / `chatCaretByTab`\n * - `\"other\"` → `customDraftByTab` / `customCaretByTab`\n *\n * Both the pure reducer (`state-reducer.ts`) and the runtime\n * (`questionnaire-session.ts`) read and write these maps. Hoisting the helpers\n * here keeps the `owner === \"chat\"` branch in exactly one place instead of\n * copying it into each caller (previously `withInlineDraft` was duplicated\n * verbatim and the accessor pairs were near-duplicates split across the two\n * files).\n */\n\n/** Raw draft text persisted for `owner` at the current tab, or undefined when none is stored. */\nexport function readInlineDraft(state: QuestionnaireState, owner: InlineInputOwner): string | undefined {\n\treturn owner === \"chat\" ? state.chatDraftByTab.get(state.currentTab) : state.customDraftByTab.get(state.currentTab);\n}\n\n/** Raw caret offset persisted for `owner` at the current tab, or undefined when none is stored. */\nexport function readInlineCaret(state: QuestionnaireState, owner: InlineInputOwner): number | undefined {\n\treturn owner === \"chat\" ? state.chatCaretByTab.get(state.currentTab) : state.customCaretByTab.get(state.currentTab);\n}\n\n/** Immutably persist `value`/`caret` into `owner`'s draft+caret maps for the current tab. */\nexport function withInlineDraft(\n\tstate: QuestionnaireState,\n\towner: InlineInputOwner,\n\tvalue: string,\n\tcaret: number,\n): QuestionnaireState {\n\tif (owner === \"chat\") {\n\t\tconst chatDraftByTab = new Map(state.chatDraftByTab);\n\t\tconst chatCaretByTab = new Map(state.chatCaretByTab);\n\t\tchatDraftByTab.set(state.currentTab, value);\n\t\tchatCaretByTab.set(state.currentTab, caret);\n\t\treturn { ...state, chatDraftByTab, chatCaretByTab };\n\t}\n\tconst customDraftByTab = new Map(state.customDraftByTab);\n\tconst customCaretByTab = new Map(state.customCaretByTab);\n\tcustomDraftByTab.set(state.currentTab, value);\n\tcustomCaretByTab.set(state.currentTab, caret);\n\treturn { ...state, customDraftByTab, customCaretByTab };\n}\n\n/**\n * Draft text used to hydrate the inline editor when (re)focusing `owner`: the\n * in-flight draft when present, otherwise the prior committed answer for that\n * owner. The `\"chat\"` owner excludes the reserved sentinel label so a prior\n * signal-only chat (`\"Chat about this\"`) does not re-hydrate as editable text.\n */\nexport function resolveInlineDraftValue(state: QuestionnaireState, owner: InlineInputOwner): string {\n\tconst draft = readInlineDraft(state, owner);\n\tif (draft !== undefined) return draft;\n\tconst prior = state.answers.get(state.currentTab);\n\tif (owner === \"other\") {\n\t\treturn prior?.kind === \"custom\" && typeof prior.answer === \"string\" ? prior.answer : \"\";\n\t}\n\treturn prior?.kind === \"chat\" && typeof prior.answer === \"string\" && prior.answer !== ROW_INTENT_META.chat.label\n\t\t? prior.answer\n\t\t: \"\";\n}\n"]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ROW_INTENT_META } from "./row-intent.js";
|
|
2
|
+
/**
|
|
3
|
+
* Shared owner-branching accessors/mutator for the two per-owner inline-input
|
|
4
|
+
* map pairs on `QuestionnaireState`:
|
|
5
|
+
* - `"chat"` → `chatDraftByTab` / `chatCaretByTab`
|
|
6
|
+
* - `"other"` → `customDraftByTab` / `customCaretByTab`
|
|
7
|
+
*
|
|
8
|
+
* Both the pure reducer (`state-reducer.ts`) and the runtime
|
|
9
|
+
* (`questionnaire-session.ts`) read and write these maps. Hoisting the helpers
|
|
10
|
+
* here keeps the `owner === "chat"` branch in exactly one place instead of
|
|
11
|
+
* copying it into each caller (previously `withInlineDraft` was duplicated
|
|
12
|
+
* verbatim and the accessor pairs were near-duplicates split across the two
|
|
13
|
+
* files).
|
|
14
|
+
*/
|
|
15
|
+
/** Raw draft text persisted for `owner` at the current tab, or undefined when none is stored. */
|
|
16
|
+
export function readInlineDraft(state, owner) {
|
|
17
|
+
return owner === "chat" ? state.chatDraftByTab.get(state.currentTab) : state.customDraftByTab.get(state.currentTab);
|
|
18
|
+
}
|
|
19
|
+
/** Raw caret offset persisted for `owner` at the current tab, or undefined when none is stored. */
|
|
20
|
+
export function readInlineCaret(state, owner) {
|
|
21
|
+
return owner === "chat" ? state.chatCaretByTab.get(state.currentTab) : state.customCaretByTab.get(state.currentTab);
|
|
22
|
+
}
|
|
23
|
+
/** Immutably persist `value`/`caret` into `owner`'s draft+caret maps for the current tab. */
|
|
24
|
+
export function withInlineDraft(state, owner, value, caret) {
|
|
25
|
+
if (owner === "chat") {
|
|
26
|
+
const chatDraftByTab = new Map(state.chatDraftByTab);
|
|
27
|
+
const chatCaretByTab = new Map(state.chatCaretByTab);
|
|
28
|
+
chatDraftByTab.set(state.currentTab, value);
|
|
29
|
+
chatCaretByTab.set(state.currentTab, caret);
|
|
30
|
+
return { ...state, chatDraftByTab, chatCaretByTab };
|
|
31
|
+
}
|
|
32
|
+
const customDraftByTab = new Map(state.customDraftByTab);
|
|
33
|
+
const customCaretByTab = new Map(state.customCaretByTab);
|
|
34
|
+
customDraftByTab.set(state.currentTab, value);
|
|
35
|
+
customCaretByTab.set(state.currentTab, caret);
|
|
36
|
+
return { ...state, customDraftByTab, customCaretByTab };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Draft text used to hydrate the inline editor when (re)focusing `owner`: the
|
|
40
|
+
* in-flight draft when present, otherwise the prior committed answer for that
|
|
41
|
+
* owner. The `"chat"` owner excludes the reserved sentinel label so a prior
|
|
42
|
+
* signal-only chat (`"Chat about this"`) does not re-hydrate as editable text.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveInlineDraftValue(state, owner) {
|
|
45
|
+
const draft = readInlineDraft(state, owner);
|
|
46
|
+
if (draft !== undefined)
|
|
47
|
+
return draft;
|
|
48
|
+
const prior = state.answers.get(state.currentTab);
|
|
49
|
+
if (owner === "other") {
|
|
50
|
+
return prior?.kind === "custom" && typeof prior.answer === "string" ? prior.answer : "";
|
|
51
|
+
}
|
|
52
|
+
return prior?.kind === "chat" && typeof prior.answer === "string" && prior.answer !== ROW_INTENT_META.chat.label
|
|
53
|
+
? prior.answer
|
|
54
|
+
: "";
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=inline-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inline-input.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/inline-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD;;;;;;;;;;;;GAYG;AAEH,iGAAiG;AACjG,MAAM,UAAU,eAAe,CAAC,KAAyB,EAAE,KAAuB;IACjF,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AACrH,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,eAAe,CAAC,KAAyB,EAAE,KAAuB;IACjF,OAAO,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;AACrH,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,eAAe,CAC9B,KAAyB,EACzB,KAAuB,EACvB,KAAa,EACb,KAAa;IAEb,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;QACtB,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QACrD,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC5C,cAAc,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC5C,OAAO,EAAE,GAAG,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACzD,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACzD,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC9C,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC9C,OAAO,EAAE,GAAG,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC;AACzD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,KAAyB,EAAE,KAAuB;IACzF,MAAM,KAAK,GAAG,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACzF,CAAC;IACD,OAAO,KAAK,EAAE,IAAI,KAAK,MAAM,IAAI,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,eAAe,CAAC,IAAI,CAAC,KAAK;QAC/G,CAAC,CAAC,KAAK,CAAC,MAAM;QACd,CAAC,CAAC,EAAE,CAAC;AACP,CAAC","sourcesContent":["import { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { InlineInputOwner, QuestionnaireState } from \"./state.ts\";\n\n/**\n * Shared owner-branching accessors/mutator for the two per-owner inline-input\n * map pairs on `QuestionnaireState`:\n * - `\"chat\"` → `chatDraftByTab` / `chatCaretByTab`\n * - `\"other\"` → `customDraftByTab` / `customCaretByTab`\n *\n * Both the pure reducer (`state-reducer.ts`) and the runtime\n * (`questionnaire-session.ts`) read and write these maps. Hoisting the helpers\n * here keeps the `owner === \"chat\"` branch in exactly one place instead of\n * copying it into each caller (previously `withInlineDraft` was duplicated\n * verbatim and the accessor pairs were near-duplicates split across the two\n * files).\n */\n\n/** Raw draft text persisted for `owner` at the current tab, or undefined when none is stored. */\nexport function readInlineDraft(state: QuestionnaireState, owner: InlineInputOwner): string | undefined {\n\treturn owner === \"chat\" ? state.chatDraftByTab.get(state.currentTab) : state.customDraftByTab.get(state.currentTab);\n}\n\n/** Raw caret offset persisted for `owner` at the current tab, or undefined when none is stored. */\nexport function readInlineCaret(state: QuestionnaireState, owner: InlineInputOwner): number | undefined {\n\treturn owner === \"chat\" ? state.chatCaretByTab.get(state.currentTab) : state.customCaretByTab.get(state.currentTab);\n}\n\n/** Immutably persist `value`/`caret` into `owner`'s draft+caret maps for the current tab. */\nexport function withInlineDraft(\n\tstate: QuestionnaireState,\n\towner: InlineInputOwner,\n\tvalue: string,\n\tcaret: number,\n): QuestionnaireState {\n\tif (owner === \"chat\") {\n\t\tconst chatDraftByTab = new Map(state.chatDraftByTab);\n\t\tconst chatCaretByTab = new Map(state.chatCaretByTab);\n\t\tchatDraftByTab.set(state.currentTab, value);\n\t\tchatCaretByTab.set(state.currentTab, caret);\n\t\treturn { ...state, chatDraftByTab, chatCaretByTab };\n\t}\n\tconst customDraftByTab = new Map(state.customDraftByTab);\n\tconst customCaretByTab = new Map(state.customCaretByTab);\n\tcustomDraftByTab.set(state.currentTab, value);\n\tcustomCaretByTab.set(state.currentTab, caret);\n\treturn { ...state, customDraftByTab, customCaretByTab };\n}\n\n/**\n * Draft text used to hydrate the inline editor when (re)focusing `owner`: the\n * in-flight draft when present, otherwise the prior committed answer for that\n * owner. The `\"chat\"` owner excludes the reserved sentinel label so a prior\n * signal-only chat (`\"Chat about this\"`) does not re-hydrate as editable text.\n */\nexport function resolveInlineDraftValue(state: QuestionnaireState, owner: InlineInputOwner): string {\n\tconst draft = readInlineDraft(state, owner);\n\tif (draft !== undefined) return draft;\n\tconst prior = state.answers.get(state.currentTab);\n\tif (owner === \"other\") {\n\t\treturn prior?.kind === \"custom\" && typeof prior.answer === \"string\" ? prior.answer : \"\";\n\t}\n\treturn prior?.kind === \"chat\" && typeof prior.answer === \"string\" && prior.answer !== ROW_INTENT_META.chat.label\n\t\t? prior.answer\n\t\t: \"\";\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"key-router.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAU3E,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE;AACxB;;;GAGG;GACD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEtB,MAAM,WAAW,wBAAwB;IACxC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAM7F;AAkGD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAwHpH","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even if inputMode is still true (e.g. user\n\t// navigated from \"Type something.\" and DOWN focused the chat row).\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: item.label,\n\t\t};\n\t}\n\n\tif (state.inputMode) {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"key-router.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAU3E,MAAM,MAAM,mBAAmB,GAC5B;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE;AACxB;;;GAGG;GACD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AAEtB,MAAM,WAAW,wBAAwB;IACxC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7C;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAM7F;AAgHD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAsIpH","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\n/**\n * Resolve the chat answer text at confirm time. Reads the LIVE `runtime.inputBuffer`\n * rather than a persisted draft because confirm only reaches here while chat is the\n * focused row and therefore owns the live inline editor (`inlineInputOwner === \"chat\"`),\n * so the in-flight buffer is authoritative. The blank-vs-typed decision uses `trim()`,\n * but the returned text is left UNTRIMMED to preserve the user's literal message; a\n * blank/whitespace buffer falls back to the reserved sentinel label (signal-only chat).\n */\nfunction chatAnswerValue(state: QuestionnaireState, runtime: QuestionnaireRuntime, sentinelLabel: string): string {\n\tif (state.inlineInputOwner !== \"chat\") return sentinelLabel;\n\treturn runtime.inputBuffer.trim().length > 0 ? runtime.inputBuffer : sentinelLabel;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even though inputMode is true for the shared\n\t// inline editor. Non-blank chat drafts become the chat answer; blank drafts preserve the\n\t// legacy sentinel-only \"Chat about this\" answer.\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: chatAnswerValue(state, runtime, item.label),\n\t\t};\n\t}\n\n\tif (state.inputMode && state.inlineInputOwner === \"other\") {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\t// Left/Right guard is intentionally asymmetric — it exists for the chat owner but\n\t\t// NOT the \"other\" inline editor. The chatFocused branch falls through to\n\t\t// `tabSwitchAction` below, which in multi-question mode consumes Left/Right for tab\n\t\t// switching; swallowing them here keeps Left/Right as in-editor caret movement for\n\t\t// the chat input. The \"other\" inline editor flows through the separate\n\t\t// `state.inputMode` branch (further down), which never calls `tabSwitchAction`, so it\n\t\t// needs no equivalent guard. Do not \"simplify\" by unifying the two paths.\n\t\tif (\n\t\t\tstate.inputMode &&\n\t\t\tstate.inlineInputOwner === \"chat\" &&\n\t\t\t(matchesKey(data, Key.left) || matchesKey(data, Key.right))\n\t\t) {\n\t\t\treturn { kind: \"ignore\" };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
|
|
@@ -30,23 +30,37 @@ function computeAutoAdvanceTab(state, runtime) {
|
|
|
30
30
|
return state.currentTab + 1;
|
|
31
31
|
return runtime.questions.length;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the chat answer text at confirm time. Reads the LIVE `runtime.inputBuffer`
|
|
35
|
+
* rather than a persisted draft because confirm only reaches here while chat is the
|
|
36
|
+
* focused row and therefore owns the live inline editor (`inlineInputOwner === "chat"`),
|
|
37
|
+
* so the in-flight buffer is authoritative. The blank-vs-typed decision uses `trim()`,
|
|
38
|
+
* but the returned text is left UNTRIMMED to preserve the user's literal message; a
|
|
39
|
+
* blank/whitespace buffer falls back to the reserved sentinel label (signal-only chat).
|
|
40
|
+
*/
|
|
41
|
+
function chatAnswerValue(state, runtime, sentinelLabel) {
|
|
42
|
+
if (state.inlineInputOwner !== "chat")
|
|
43
|
+
return sentinelLabel;
|
|
44
|
+
return runtime.inputBuffer.trim().length > 0 ? runtime.inputBuffer : sentinelLabel;
|
|
45
|
+
}
|
|
33
46
|
function buildSingleSelectAnswer(state, runtime) {
|
|
34
47
|
const q = runtime.questions[state.currentTab];
|
|
35
48
|
if (!q)
|
|
36
49
|
return null;
|
|
37
50
|
// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides
|
|
38
|
-
// currentItem() to return the chat sentinel even
|
|
39
|
-
//
|
|
51
|
+
// currentItem() to return the chat sentinel even though inputMode is true for the shared
|
|
52
|
+
// inline editor. Non-blank chat drafts become the chat answer; blank drafts preserve the
|
|
53
|
+
// legacy sentinel-only "Chat about this" answer.
|
|
40
54
|
const item = runtime.currentItem;
|
|
41
55
|
if (item?.kind === "chat") {
|
|
42
56
|
return {
|
|
43
57
|
questionIndex: state.currentTab,
|
|
44
58
|
question: q.question,
|
|
45
59
|
kind: "chat",
|
|
46
|
-
answer: item.label,
|
|
60
|
+
answer: chatAnswerValue(state, runtime, item.label),
|
|
47
61
|
};
|
|
48
62
|
}
|
|
49
|
-
if (state.inputMode) {
|
|
63
|
+
if (state.inputMode && state.inlineInputOwner === "other") {
|
|
50
64
|
const label = runtime.inputBuffer;
|
|
51
65
|
return {
|
|
52
66
|
questionIndex: state.currentTab,
|
|
@@ -139,6 +153,18 @@ export function routeKey(data, state, runtime) {
|
|
|
139
153
|
if (kb.matches(data, KEYBIND_DOWN)) {
|
|
140
154
|
return { kind: "focus_options", optionIndex: 0 };
|
|
141
155
|
}
|
|
156
|
+
// Left/Right guard is intentionally asymmetric — it exists for the chat owner but
|
|
157
|
+
// NOT the "other" inline editor. The chatFocused branch falls through to
|
|
158
|
+
// `tabSwitchAction` below, which in multi-question mode consumes Left/Right for tab
|
|
159
|
+
// switching; swallowing them here keeps Left/Right as in-editor caret movement for
|
|
160
|
+
// the chat input. The "other" inline editor flows through the separate
|
|
161
|
+
// `state.inputMode` branch (further down), which never calls `tabSwitchAction`, so it
|
|
162
|
+
// needs no equivalent guard. Do not "simplify" by unifying the two paths.
|
|
163
|
+
if (state.inputMode &&
|
|
164
|
+
state.inlineInputOwner === "chat" &&
|
|
165
|
+
(matchesKey(data, Key.left) || matchesKey(data, Key.right))) {
|
|
166
|
+
return { kind: "ignore" };
|
|
167
|
+
}
|
|
142
168
|
const tab = tabSwitchAction(data, state, runtime);
|
|
143
169
|
if (tab)
|
|
144
170
|
return tab;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"key-router.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even if inputMode is still true (e.g. user\n\t// navigated from \"Type something.\" and DOWN focused the chat row).\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: item.label,\n\t\t};\n\t}\n\n\tif (state.inputMode) {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"key-router.js","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/key-router.ts"],"names":[],"mappings":"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","sourcesContent":["import { Key, matchesKey } from \"@earendil-works/pi-tui\";\nimport type { QuestionAnswer } from \"../tool/types.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\n\nconst KEYBIND_UP = \"tui.select.up\";\nconst KEYBIND_DOWN = \"tui.select.down\";\nconst KEYBIND_CONFIRM = \"tui.select.confirm\";\nconst KEYBIND_CANCEL = \"tui.select.cancel\";\n\nconst NOTES_ACTIVATE_KEY = \"n\";\nconst SPACE_KEY = \" \";\n\nexport type QuestionnaireAction =\n\t| { kind: \"nav\"; nextIndex: number }\n\t| { kind: \"tab_switch\"; nextTab: number }\n\t| { kind: \"confirm\"; answer: QuestionAnswer; autoAdvanceTab?: number }\n\t| { kind: \"toggle\"; index: number }\n\t| { kind: \"multi_confirm\"; selected: string[]; autoAdvanceTab?: number }\n\t| { kind: \"cancel\" }\n\t| { kind: \"notes_enter\" }\n\t| { kind: \"notes_exit\" }\n\t| { kind: \"submit\" }\n\t| { kind: \"submit_nav\"; nextIndex: 0 | 1 }\n\t| { kind: \"focus_chat\" }\n\t/**\n\t * Carries the target index so UP/DOWN form a continuous cycle through\n\t * `[chat, option0, …, optionLast]`.\n\t */\n\t| { kind: \"focus_options\"; optionIndex: number }\n\t| { kind: \"notes_forward\"; data: string }\n\t| { kind: \"ignore\" };\n\nexport interface QuestionnaireKeybindings {\n\tmatches(data: string, name: string): boolean;\n}\n\nexport function wrapTab(index: number, total: number): number {\n\tif (total <= 0) return 0;\n\treturn ((index % total) + total) % total;\n}\n\nexport function allAnswered(state: QuestionnaireState, runtime: QuestionnaireRuntime): boolean {\n\tif (runtime.questions.length === 0) return false;\n\tfor (let i = 0; i < runtime.questions.length; i++) {\n\t\tif (!state.answers.has(i)) return false;\n\t}\n\treturn true;\n}\n\nfunction totalTabs(runtime: QuestionnaireRuntime): number {\n\treturn runtime.isMulti ? runtime.questions.length + 1 : 1;\n}\n\nfunction computeAutoAdvanceTab(state: QuestionnaireState, runtime: QuestionnaireRuntime): number | undefined {\n\tif (!runtime.isMulti) return undefined;\n\tif (state.currentTab < runtime.questions.length - 1) return state.currentTab + 1;\n\treturn runtime.questions.length;\n}\n\n/**\n * Resolve the chat answer text at confirm time. Reads the LIVE `runtime.inputBuffer`\n * rather than a persisted draft because confirm only reaches here while chat is the\n * focused row and therefore owns the live inline editor (`inlineInputOwner === \"chat\"`),\n * so the in-flight buffer is authoritative. The blank-vs-typed decision uses `trim()`,\n * but the returned text is left UNTRIMMED to preserve the user's literal message; a\n * blank/whitespace buffer falls back to the reserved sentinel label (signal-only chat).\n */\nfunction chatAnswerValue(state: QuestionnaireState, runtime: QuestionnaireRuntime, sentinelLabel: string): string {\n\tif (state.inlineInputOwner !== \"chat\") return sentinelLabel;\n\treturn runtime.inputBuffer.trim().length > 0 ? runtime.inputBuffer : sentinelLabel;\n}\n\nfunction buildSingleSelectAnswer(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionAnswer | null {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return null;\n\n\t// Chat sentinel takes priority over inputMode: when chatFocused=true, the host overrides\n\t// currentItem() to return the chat sentinel even though inputMode is true for the shared\n\t// inline editor. Non-blank chat drafts become the chat answer; blank drafts preserve the\n\t// legacy sentinel-only \"Chat about this\" answer.\n\tconst item = runtime.currentItem;\n\tif (item?.kind === \"chat\") {\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"chat\",\n\t\t\tanswer: chatAnswerValue(state, runtime, item.label),\n\t\t};\n\t}\n\n\tif (state.inputMode && state.inlineInputOwner === \"other\") {\n\t\tconst label = runtime.inputBuffer;\n\t\treturn {\n\t\t\tquestionIndex: state.currentTab,\n\t\t\tquestion: q.question,\n\t\t\tkind: \"custom\",\n\t\t\tanswer: label.length > 0 ? label : null,\n\t\t};\n\t}\n\tif (!item) return null;\n\tif (item.kind === \"other\") {\n\t\treturn null;\n\t}\n\tif (item.kind === \"next\") {\n\t\treturn null;\n\t}\n\treturn {\n\t\tquestionIndex: state.currentTab,\n\t\tquestion: q.question,\n\t\tkind: \"option\",\n\t\tanswer: item.label,\n\t};\n}\n\nfunction buildMultiSelected(state: QuestionnaireState, runtime: QuestionnaireRuntime): string[] {\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return [];\n\tconst out: string[] = [];\n\tfor (let i = 0; i < q.options.length; i++) {\n\t\tif (state.multiSelectChecked.has(i)) {\n\t\t\tconst label = q.options[i]?.label;\n\t\t\tif (typeof label === \"string\") out.push(label);\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction tabSwitchAction(\n\tdata: string,\n\tstate: QuestionnaireState,\n\truntime: QuestionnaireRuntime,\n): QuestionnaireAction | null {\n\tif (!runtime.isMulti) return null;\n\tconst total = totalTabs(runtime);\n\tif (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab + 1, total) };\n\t}\n\tif (matchesKey(data, Key.shift(\"tab\")) || matchesKey(data, Key.left)) {\n\t\treturn { kind: \"tab_switch\", nextTab: wrapTab(state.currentTab - 1, total) };\n\t}\n\treturn null;\n}\n\n// DOWN at the last item emits focus_chat so the cycle [chat, option0, …, optionLast] wraps.\nfunction nextNavOnDown(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === runtime.items.length - 1) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex + 1, Math.max(1, runtime.items.length)) };\n}\n\n// UP at the top item emits focus_chat (symmetric with nextNavOnDown).\nfunction prevNavOnUp(state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tif (runtime.items.length > 0 && state.optionIndex === 0) {\n\t\treturn { kind: \"focus_chat\" };\n\t}\n\treturn { kind: \"nav\", nextIndex: wrapTab(state.optionIndex - 1, Math.max(1, runtime.items.length)) };\n}\n\nexport function routeKey(data: string, state: QuestionnaireState, runtime: QuestionnaireRuntime): QuestionnaireAction {\n\tconst kb = runtime.keybindings;\n\n\tif (state.notesVisible) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"notes_exit\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) return { kind: \"notes_exit\" };\n\t\treturn { kind: \"notes_forward\", data };\n\t}\n\n\tif (state.chatFocused) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\t// Continuous cycle: UP from chat → bottom of options (last navigable row), DOWN from\n\t\t// chat → top of options (option 0). Symmetric with UP-at-top → focus_chat and\n\t\t// DOWN-at-bottom → focus_chat below; together they form one wrapping cycle through\n\t\t// `[chat, option0, …, optionLast]`.\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\tconst last = Math.max(0, runtime.items.length - 1);\n\t\t\treturn { kind: \"focus_options\", optionIndex: last };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn { kind: \"focus_options\", optionIndex: 0 };\n\t\t}\n\t\t// Left/Right guard is intentionally asymmetric — it exists for the chat owner but\n\t\t// NOT the \"other\" inline editor. The chatFocused branch falls through to\n\t\t// `tabSwitchAction` below, which in multi-question mode consumes Left/Right for tab\n\t\t// switching; swallowing them here keeps Left/Right as in-editor caret movement for\n\t\t// the chat input. The \"other\" inline editor flows through the separate\n\t\t// `state.inputMode` branch (further down), which never calls `tabSwitchAction`, so it\n\t\t// needs no equivalent guard. Do not \"simplify\" by unifying the two paths.\n\t\tif (\n\t\t\tstate.inputMode &&\n\t\t\tstate.inlineInputOwner === \"chat\" &&\n\t\t\t(matchesKey(data, Key.left) || matchesKey(data, Key.right))\n\t\t) {\n\t\t\treturn { kind: \"ignore\" };\n\t\t}\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (state.inputMode) {\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\t\tif (!answer) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tif (kb.matches(data, KEYBIND_UP)) {\n\t\t\treturn prevNavOnUp(state, runtime);\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\t\treturn nextNavOnDown(state, runtime);\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (runtime.isMulti && state.currentTab === runtime.questions.length) {\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\tconst tab = tabSwitchAction(data, state, runtime);\n\t\tif (tab) return tab;\n\t\tif (kb.matches(data, KEYBIND_UP) || kb.matches(data, KEYBIND_DOWN)) {\n\t\t\tconst delta = kb.matches(data, KEYBIND_DOWN) ? 1 : -1;\n\t\t\tconst next = wrapTab(state.submitChoiceIndex + delta, 2);\n\t\t\treturn { kind: \"submit_nav\", nextIndex: (next === 1 ? 1 : 0) as 0 | 1 };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// D1 (revised): Submit always submits; Cancel always cancels. The warning header\n\t\t\t// is informational only — `allAnswered(state)` no longer gates submission. Partial\n\t\t\t// answers flow through `orderedAnswers()` in the host.\n\t\t\treturn state.submitChoiceIndex === 1 ? { kind: \"cancel\" } : { kind: \"submit\" };\n\t\t}\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tconst tab = tabSwitchAction(data, state, runtime);\n\tif (tab) return tab;\n\n\tconst q = runtime.questions[state.currentTab];\n\tif (!q) return { kind: \"ignore\" };\n\n\tif (data === NOTES_ACTIVATE_KEY && !q.multiSelect && state.focusedOptionHasPreview) {\n\t\treturn { kind: \"notes_enter\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_UP)) {\n\t\treturn prevNavOnUp(state, runtime);\n\t}\n\tif (kb.matches(data, KEYBIND_DOWN)) {\n\t\treturn nextNavOnDown(state, runtime);\n\t}\n\n\tif (q.multiSelect) {\n\t\tconst focusedKind = runtime.currentItem?.kind;\n\t\tconst focusedMeta = focusedKind ? ROW_INTENT_META[focusedKind] : undefined;\n\t\t// Space toggles the focused row's checkbox. Suppressed on rows whose META declares\n\t\t// `blocksMultiToggle` (the Next sentinel) — Next is not a real option and has no\n\t\t// checked/unchecked state.\n\t\tif (data === SPACE_KEY) {\n\t\t\tif (focusedMeta?.blocksMultiToggle) return { kind: \"ignore\" };\n\t\t\treturn { kind: \"toggle\", index: state.optionIndex };\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\t\t// Enter on a regular row toggles (matching Space) — committing the question is now\n\t\t\t// gated behind explicit focus on a row whose META declares `autoSubmitsInMulti`\n\t\t\t// (the Next sentinel), so Enter on options is a no-cost way to flip checkboxes\n\t\t\t// without leaving the keyboard home row.\n\t\t\tif (!focusedMeta?.autoSubmitsInMulti) return { kind: \"toggle\", index: state.optionIndex };\n\t\t\t// Enter on Next: carry autoAdvanceTab so the host can advance to the next tab in\n\t\t\t// multi-question mode, OR submit the dialog in single-question mode\n\t\t\t// (autoAdvanceTab === undefined when !isMulti). Without this, a single multi-select\n\t\t\t// question would have no way to commit at all.\n\t\t\treturn {\n\t\t\t\tkind: \"multi_confirm\",\n\t\t\t\tselected: buildMultiSelected(state, runtime),\n\t\t\t\tautoAdvanceTab: computeAutoAdvanceTab(state, runtime),\n\t\t\t};\n\t\t}\n\t\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\t\treturn { kind: \"ignore\" };\n\t}\n\n\tif (kb.matches(data, KEYBIND_CONFIRM)) {\n\t\tconst answer = buildSingleSelectAnswer(state, runtime);\n\t\tif (!answer) return { kind: \"ignore\" };\n\t\treturn { kind: \"confirm\", answer, autoAdvanceTab: computeAutoAdvanceTab(state, runtime) };\n\t}\n\tif (kb.matches(data, KEYBIND_CANCEL)) return { kind: \"cancel\" };\n\treturn { kind: \"ignore\" };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questionnaire-session.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,KAAK,EAAgB,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;
|
|
1
|
+
{"version":3,"file":"questionnaire-session.d.ts","sourceRoot":"","sources":["../../../../../src/core/tools/ask-user-question/state/questionnaire-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8CAA8C,CAAC;AAE1E,OAAO,KAAK,EAAgB,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAuBhF,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE;QAAE,QAAQ,EAAE;YAAE,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,aAAa,IAAI,IAAI,CAAA;KAAE,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC;IACnC,IAAI,EAAE,CAAC,MAAM,EAAE,mBAAmB,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,6BAA6B;IAC7C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAChC,UAAU,IAAI,IAAI,CAAC;IACnB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAuBD;;;;;GAKG;AACH,qBAAa,oBAAoB;IAChC,OAAO,CAAC,KAAK,CAAsC;IAEnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0B;IACpD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyB;IAEpD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA4B;IAExD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoC;IACxD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqC;IAE1D,QAAQ,CAAC,SAAS,EAAE,6BAA6B,CAAC;IAElD,YAAY,MAAM,EAAE,0BAA0B,EA8B7C;IAED,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO3B;IAED,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,sBAAsB;IAW9B,OAAO,CAAC,SAAS;IAqBjB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;CAMnB","sourcesContent":["import type { Theme } from \"../../../../modes/interactive/theme/theme.ts\";\nimport { getKeybindings, type Input } from \"@earendil-works/pi-tui\";\nimport type { QuestionData, QuestionnaireResult, QuestionParams } from \"../tool/types.ts\";\nimport type { WrappingSelectItem } from \"../view/components/wrapping-select.ts\";\nimport type { QuestionnairePropsAdapter } from \"../view/props-adapter.ts\";\nimport { buildQuestionnaire } from \"./build-questionnaire.ts\";\nimport { readInlineCaret, readInlineDraft, withInlineDraft } from \"./inline-input.ts\";\nimport { ROW_INTENT_META } from \"./row-intent.ts\";\nimport { type QuestionnaireAction, routeKey } from \"./key-router.ts\";\nimport { computeFocusedOptionHasPreview } from \"./selectors/derivations.ts\";\nimport type { QuestionnaireRuntime, QuestionnaireState } from \"./state.ts\";\nimport { type ApplyContext, type Effect, reduce } from \"./state-reducer.ts\";\n\nfunction readInputCursor(input: Input): number {\n\tconst value = input.getValue();\n\tconst raw = Reflect.get(input, \"cursor\");\n\treturn typeof raw === \"number\" && Number.isFinite(raw)\n\t\t? Math.max(0, Math.min(raw, value.length))\n\t\t: value.length;\n}\n\nfunction writeInputCursor(input: Input, cursor: number): void {\n\tconst value = input.getValue();\n\tReflect.set(input, \"cursor\", Math.max(0, Math.min(cursor, value.length)));\n}\n\nexport interface QuestionnaireSessionConfig {\n\ttui: { terminal: { columns: number }; requestRender(): void };\n\ttheme: Theme;\n\tparams: QuestionParams;\n\titemsByTab: WrappingSelectItem[][];\n\tdone: (result: QuestionnaireResult) => void;\n}\n\nexport interface QuestionnaireSessionComponent {\n\trender(width: number): string[];\n\tinvalidate(): void;\n\thandleInput(data: string): void;\n}\n\nfunction initialState(): QuestionnaireState {\n\treturn {\n\t\tcurrentTab: 0,\n\t\toptionIndex: 0,\n\t\tinputMode: false,\n\t\tinlineInputOwner: null,\n\t\tnotesVisible: false,\n\t\tchatFocused: false,\n\t\tanswers: new Map(),\n\t\tmultiSelectChecked: new Set(),\n\t\tnotesByTab: new Map(),\n\t\tfocusedOptionHasPreview: false,\n\t\tsubmitChoiceIndex: 0,\n\t\tnotesDraft: \"\",\n\t\tcustomDraftByTab: new Map(),\n\t\tcustomCaretByTab: new Map(),\n\t\tchatDraftByTab: new Map(),\n\t\tchatCaretByTab: new Map(),\n\t};\n}\n\n/**\n * Slim runtime: owns the canonical state cell, the input-buffer cell, the\n * two-pass `notesVisible` dispatch loop, and the effect runner. State\n * transitions go through the pure `reduce` reducer; UI fan-out goes through\n * the `QuestionnairePropsAdapter` produced by `buildQuestionnaire`.\n */\nexport class QuestionnaireSession {\n\tprivate state: QuestionnaireState = initialState();\n\n\tprivate readonly questions: readonly QuestionData[];\n\tprivate readonly isMulti: boolean;\n\tprivate readonly itemsByTab: WrappingSelectItem[][];\n\n\tprivate readonly notesInput: Input;\n\tprivate readonly inlineInput: Input;\n\tprivate readonly viewAdapter: QuestionnairePropsAdapter;\n\n\tprivate readonly tui: QuestionnaireSessionConfig[\"tui\"];\n\tprivate readonly done: QuestionnaireSessionConfig[\"done\"];\n\n\treadonly component: QuestionnaireSessionComponent;\n\n\tconstructor(config: QuestionnaireSessionConfig) {\n\t\tthis.tui = config.tui;\n\t\tthis.done = config.done;\n\t\tthis.questions = config.params.questions;\n\t\tthis.isMulti = this.questions.length > 1;\n\t\tthis.itemsByTab = config.itemsByTab;\n\t\t// Seed from the focused option at start; the reducer keeps it in sync via withFocusedOptionHasPreview.\n\t\tthis.state = { ...this.state, focusedOptionHasPreview: computeFocusedOptionHasPreview(this.questions, 0, 0) };\n\n\t\tconst built = buildQuestionnaire({\n\t\t\ttui: this.tui,\n\t\t\ttheme: config.theme,\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t\tisMulti: this.isMulti,\n\t\t\tinitialState: this.state,\n\t\t\tgetCurrentTab: () => this.state.currentTab,\n\t\t});\n\n\t\tthis.notesInput = built.notesInput;\n\t\tthis.inlineInput = built.inlineInput;\n\t\tthis.viewAdapter = built.adapter;\n\n\t\tthis.component = {\n\t\t\trender: built.render,\n\t\t\tinvalidate: built.invalidate,\n\t\t\thandleInput: (data) => this.dispatch(data),\n\t\t};\n\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tdispatch(data: string): void {\n\t\tconst action = routeKey(data, this.state, this.runtime());\n\t\tif (action.kind === \"ignore\") {\n\t\t\tthis.handleIgnoreInline(data);\n\t\t\treturn;\n\t\t}\n\t\tthis.commit(action);\n\t}\n\n\tprivate commit(action: QuestionnaireAction): void {\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tconst result = reduce(this.state, action, this.applyContext());\n\t\tthis.state = result.state;\n\t\tfor (const effect of result.effects) this.runEffect(effect);\n\t\tthis.state = this.mirrorNotesDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate mirrorNotesDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tconst draft = this.notesInput.getValue();\n\t\treturn s.notesDraft === draft ? s : { ...s, notesDraft: draft };\n\t}\n\n\tprivate mirrorInlineInputDraft(s: QuestionnaireState): QuestionnaireState {\n\t\tif (!s.inputMode || !s.inlineInputOwner) return s;\n\t\tconst owner = s.inlineInputOwner;\n\t\tconst value = this.inlineInput.getValue();\n\t\tconst caret = readInputCursor(this.inlineInput);\n\t\tif (readInlineDraft(s, owner) === value && readInlineCaret(s, owner) === caret) {\n\t\t\treturn s;\n\t\t}\n\t\treturn withInlineDraft(s, owner, value, caret);\n\t}\n\n\tprivate runEffect(effect: Effect): void {\n\t\tswitch (effect.kind) {\n\t\t\tcase \"set_input_buffer\":\n\t\t\t\tthis.inlineInput.setValue(effect.value);\n\t\t\t\twriteInputCursor(this.inlineInput, effect.caret);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_value\":\n\t\t\t\tthis.notesInput.setValue(effect.value);\n\t\t\t\treturn;\n\t\t\tcase \"set_notes_focused\":\n\t\t\t\tthis.notesInput.focused = effect.focused;\n\t\t\t\treturn;\n\t\t\tcase \"forward_notes_keystroke\":\n\t\t\t\tthis.notesInput.handleInput(effect.data);\n\t\t\t\treturn;\n\t\t\tcase \"done\":\n\t\t\t\tthis.done(effect.result);\n\t\t\t\treturn;\n\t\t}\n\t}\n\n\t/**\n\t * Per-keystroke `ignore` fast path: delegates to the headless `inlineInput`\n\t * Input so bracketed-paste accumulator (`input.js:33-63`) and Kitty CSI-u\n\t * decode (`input.js:155-163`) take effect. Cursor is NOT force-reset here —\n\t * doing so would corrupt split-chunk pastes (a `\\x05` byte mid-paste lands\n\t * verbatim in `pasteBuffer` and survives `handlePaste`'s narrow strip).\n\t * Cursor advances naturally via `insertCharacter` on typing/paste; cursor-\n\t * movement keys (Left/Right/Home/End/word-jumps) are now functional, and\n\t * the live buffer/caret are mirrored into canonical state so rendering can\n\t * draw the same thick cursor at the editing caret. `viewAdapter.apply` is\n\t * called directly without a reducer round-trip — preserves the D3 fast-path\n\t * latency profile from Phase 11.\n\t */\n\tprivate handleIgnoreInline(data: string): void {\n\t\tif (!this.state.inputMode || !this.state.inlineInputOwner) return;\n\t\tthis.inlineInput.handleInput(data);\n\t\tthis.state = this.mirrorInlineInputDraft(this.state);\n\t\tthis.viewAdapter.apply(this.state);\n\t}\n\n\tprivate runtime(): QuestionnaireRuntime {\n\t\treturn {\n\t\t\tkeybindings: getKeybindings(),\n\t\t\tinputBuffer: this.inlineInput.getValue(),\n\t\t\tquestions: this.questions,\n\t\t\tisMulti: this.isMulti,\n\t\t\tcurrentItem: this.currentItem(),\n\t\t\titems: this.itemsByTab[this.state.currentTab] ?? [],\n\t\t};\n\t}\n\n\tprivate applyContext(): ApplyContext {\n\t\treturn {\n\t\t\tquestions: this.questions,\n\t\t\titemsByTab: this.itemsByTab,\n\t\t};\n\t}\n\n\tprivate currentItem(): WrappingSelectItem | undefined {\n\t\tif (this.state.chatFocused) return { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t\tconst arr = this.itemsByTab[this.state.currentTab] ?? [];\n\t\tif (this.state.optionIndex < arr.length) return arr[this.state.optionIndex];\n\t\treturn { kind: \"chat\", label: ROW_INTENT_META.chat.label };\n\t}\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getKeybindings } from "@earendil-works/pi-tui";
|
|
2
2
|
import { buildQuestionnaire } from "./build-questionnaire.js";
|
|
3
|
+
import { readInlineCaret, readInlineDraft, withInlineDraft } from "./inline-input.js";
|
|
3
4
|
import { ROW_INTENT_META } from "./row-intent.js";
|
|
4
5
|
import { routeKey } from "./key-router.js";
|
|
5
6
|
import { computeFocusedOptionHasPreview } from "./selectors/derivations.js";
|
|
@@ -20,6 +21,7 @@ function initialState() {
|
|
|
20
21
|
currentTab: 0,
|
|
21
22
|
optionIndex: 0,
|
|
22
23
|
inputMode: false,
|
|
24
|
+
inlineInputOwner: null,
|
|
23
25
|
notesVisible: false,
|
|
24
26
|
chatFocused: false,
|
|
25
27
|
answers: new Map(),
|
|
@@ -30,6 +32,8 @@ function initialState() {
|
|
|
30
32
|
notesDraft: "",
|
|
31
33
|
customDraftByTab: new Map(),
|
|
32
34
|
customCaretByTab: new Map(),
|
|
35
|
+
chatDraftByTab: new Map(),
|
|
36
|
+
chatCaretByTab: new Map(),
|
|
33
37
|
};
|
|
34
38
|
}
|
|
35
39
|
/**
|
|
@@ -89,18 +93,15 @@ export class QuestionnaireSession {
|
|
|
89
93
|
return s.notesDraft === draft ? s : { ...s, notesDraft: draft };
|
|
90
94
|
}
|
|
91
95
|
mirrorInlineInputDraft(s) {
|
|
92
|
-
if (!s.inputMode)
|
|
96
|
+
if (!s.inputMode || !s.inlineInputOwner)
|
|
93
97
|
return s;
|
|
98
|
+
const owner = s.inlineInputOwner;
|
|
94
99
|
const value = this.inlineInput.getValue();
|
|
95
100
|
const caret = readInputCursor(this.inlineInput);
|
|
96
|
-
if (
|
|
101
|
+
if (readInlineDraft(s, owner) === value && readInlineCaret(s, owner) === caret) {
|
|
97
102
|
return s;
|
|
98
103
|
}
|
|
99
|
-
|
|
100
|
-
const customCaretByTab = new Map(s.customCaretByTab);
|
|
101
|
-
customDraftByTab.set(s.currentTab, value);
|
|
102
|
-
customCaretByTab.set(s.currentTab, caret);
|
|
103
|
-
return { ...s, customDraftByTab, customCaretByTab };
|
|
104
|
+
return withInlineDraft(s, owner, value, caret);
|
|
104
105
|
}
|
|
105
106
|
runEffect(effect) {
|
|
106
107
|
switch (effect.kind) {
|
|
@@ -136,7 +137,7 @@ export class QuestionnaireSession {
|
|
|
136
137
|
* latency profile from Phase 11.
|
|
137
138
|
*/
|
|
138
139
|
handleIgnoreInline(data) {
|
|
139
|
-
if (!this.state.inputMode)
|
|
140
|
+
if (!this.state.inputMode || !this.state.inlineInputOwner)
|
|
140
141
|
return;
|
|
141
142
|
this.inlineInput.handleInput(data);
|
|
142
143
|
this.state = this.mirrorInlineInputDraft(this.state);
|