@selesai/code 0.5.20 → 0.5.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/core/agent-session-skill-block.test.d.ts +2 -0
- package/dist/core/agent-session-skill-block.test.d.ts.map +1 -0
- package/dist/core/agent-session-skill-block.test.js +20 -0
- package/dist/core/agent-session-skill-block.test.js.map +1 -0
- package/dist/core/agent-session.d.ts +7 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +22 -12
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +1 -1
- package/dist/core/skills.js.map +1 -1
- package/dist/extensions/handoff-new.test.ts +8 -9
- package/dist/extensions/handoff-new.ts +6 -8
- package/dist/extensions/inline-skills.test.ts +96 -0
- package/dist/extensions/inline-skills.ts +92 -0
- package/dist/extensions/package.json +1 -0
- package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +39 -0
- package/dist/extensions/pi-powerline-footer/tps.ts +172 -113
- package/dist/extensions/pi-subagents/CHANGELOG.md +1367 -0
- package/dist/extensions/pi-subagents/README.md +602 -220
- package/dist/extensions/pi-subagents/banner.png +0 -0
- package/dist/extensions/pi-subagents/index.ts +1 -0
- package/dist/extensions/pi-subagents/install.mjs +3 -3
- package/dist/extensions/pi-subagents/package-lock.json +3405 -0
- package/dist/extensions/pi-subagents/package.json +26 -14
- package/dist/extensions/pi-subagents/prompts/gather-context-and-clarify.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-cleanup.md +9 -9
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +3 -3
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +7 -7
- package/dist/extensions/pi-subagents/prompts/parallel-research.md +6 -6
- package/dist/extensions/pi-subagents/prompts/parallel-review.md +8 -8
- package/dist/extensions/pi-subagents/prompts/review-loop.md +13 -11
- package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +267 -176
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +89 -16
- package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +19 -0
- package/dist/extensions/pi-subagents/src/agents/agents.ts +218 -120
- package/dist/extensions/pi-subagents/src/agents/frontmatter.ts +67 -13
- package/dist/extensions/pi-subagents/src/agents/proactive-skills.ts +2 -2
- package/dist/extensions/pi-subagents/src/agents/skills.ts +25 -12
- package/dist/extensions/pi-subagents/src/api/background-work.ts +197 -0
- package/dist/extensions/pi-subagents/src/api/capability-ceiling.ts +17 -0
- package/dist/extensions/pi-subagents/src/api/delegation.ts +285 -0
- package/dist/extensions/pi-subagents/src/api/preflight.ts +399 -0
- package/dist/extensions/pi-subagents/src/extension/chain-validation.ts +165 -0
- package/dist/extensions/pi-subagents/src/extension/config.ts +7 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +15 -0
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +3 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +113 -157
- package/dist/extensions/pi-subagents/src/extension/rpc.ts +41 -4
- package/dist/extensions/pi-subagents/src/extension/schemas.ts +45 -15
- package/dist/extensions/pi-subagents/src/extension/steering-notices.ts +35 -0
- package/dist/extensions/pi-subagents/src/extension/tool-description.ts +28 -15
- package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -3
- package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +225 -29
- package/dist/extensions/pi-subagents/src/intercom/result-intercom.ts +11 -0
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +8 -10
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +452 -62
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +60 -9
- package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +198 -52
- package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +119 -21
- package/dist/extensions/pi-subagents/src/runs/background/auto-drain.ts +67 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +2 -0
- package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +16 -8
- package/dist/extensions/pi-subagents/src/runs/background/completion-batcher.ts +6 -4
- package/dist/extensions/pi-subagents/src/runs/background/completion-dedupe.ts +2 -11
- package/dist/extensions/pi-subagents/src/runs/background/control-channel.ts +260 -13
- package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +32 -6
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +171 -90
- package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +280 -0
- package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +149 -88
- package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +14 -2
- package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +33 -17
- package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +9 -1
- package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +38 -10
- package/dist/extensions/pi-subagents/src/runs/background/steering.ts +237 -0
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +1297 -307
- package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +610 -0
- package/dist/extensions/pi-subagents/src/runs/background/top-level-async.ts +2 -1
- package/dist/extensions/pi-subagents/src/runs/background/wait-config.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +26 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/async-steering-action.ts +230 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +22 -6
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +228 -140
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +452 -138
- package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +90 -0
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +1001 -416
- package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +443 -132
- package/dist/extensions/pi-subagents/src/runs/shared/agent-contract.ts +38 -0
- package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +178 -0
- package/dist/extensions/pi-subagents/src/runs/shared/child-protocol.ts +121 -0
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +41 -93
- package/dist/extensions/pi-subagents/src/runs/shared/context-mode.ts +44 -0
- package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +11 -9
- package/dist/extensions/pi-subagents/src/runs/shared/long-running-guard.ts +4 -0
- package/dist/extensions/pi-subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +12 -6
- package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +36 -0
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +44 -7
- package/dist/extensions/pi-subagents/src/runs/shared/nested-render.ts +4 -1
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +154 -0
- package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +18 -0
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +174 -54
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +38 -31
- package/dist/extensions/pi-subagents/src/runs/shared/run-history.ts +90 -5
- package/dist/extensions/pi-subagents/src/runs/shared/session-lease.ts +299 -0
- package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +61 -6
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-budget.ts +128 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +112 -7
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +14 -6
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +149 -36
- package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +181 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-availability.ts +83 -0
- package/dist/extensions/pi-subagents/src/runs/shared/tool-budget.ts +11 -5
- package/dist/extensions/pi-subagents/src/runs/shared/turn-budget.ts +50 -4
- package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +63 -14
- package/dist/extensions/pi-subagents/src/shared/accessible-dir.ts +25 -0
- package/dist/extensions/pi-subagents/src/shared/artifacts.ts +37 -7
- package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +18 -43
- package/dist/extensions/pi-subagents/src/shared/child-transcript.ts +52 -0
- package/dist/extensions/pi-subagents/src/shared/env.ts +16 -0
- package/dist/extensions/pi-subagents/src/shared/file-system-retry.ts +47 -0
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +28 -3
- package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +123 -0
- package/dist/extensions/pi-subagents/src/shared/model-info.ts +8 -5
- package/dist/extensions/pi-subagents/src/shared/settings.ts +9 -1
- package/dist/extensions/pi-subagents/src/shared/status-format.ts +7 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +522 -51
- package/dist/extensions/pi-subagents/src/shared/utils.ts +59 -53
- package/dist/extensions/pi-subagents/src/slash/delegation-adapters.ts +604 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-json.ts +108 -0
- package/dist/extensions/pi-subagents/src/slash/delegation-request.ts +249 -0
- package/dist/extensions/pi-subagents/src/slash/prompt-template-bridge.ts +353 -345
- package/dist/extensions/pi-subagents/src/slash/prompt-workflows.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/selector.ts +147 -0
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +265 -23
- package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +2 -2
- package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +428 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +362 -0
- package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +472 -0
- package/dist/extensions/pi-subagents/src/tui/fleet.ts +664 -0
- package/dist/extensions/pi-subagents/src/tui/render.ts +115 -31
- package/dist/extensions/pi-subagents/src/watchdog/change-signature.ts +220 -0
- package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +205 -0
- package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +123 -0
- package/dist/extensions/pi-subagents/src/watchdog/lsp-diagnostics.ts +532 -0
- package/dist/extensions/pi-subagents/src/watchdog/model-selection.ts +167 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +117 -0
- package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +433 -0
- package/dist/extensions/pi-subagents/src/watchdog/render.ts +54 -0
- package/dist/extensions/pi-subagents/src/watchdog/review.ts +298 -0
- package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +714 -0
- package/dist/extensions/pi-subagents/src/watchdog/settings.ts +528 -0
- package/dist/extensions/pi-subagents/src/watchdog/tool-actions.ts +155 -0
- package/dist/extensions/pi-subagents/src/watchdog/turn-delta.ts +161 -0
- package/dist/extensions/pi-subagents/src/watchdog/types.ts +188 -0
- package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +73 -0
- package/dist/extensions/pi-subagents/test/e2e/real-session-subagent.test.ts +130 -2
- package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +439 -0
- package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +1632 -61
- package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +54 -1
- package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +112 -0
- package/dist/extensions/pi-subagents/test/integration/chain-clarify.test.ts +98 -1
- package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +179 -9
- package/dist/extensions/pi-subagents/test/integration/detect-error.test.ts +16 -9
- package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +6 -5
- package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +589 -9
- package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +528 -21
- package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +227 -3
- package/dist/extensions/pi-subagents/test/integration/render-fork-badge.test.ts +235 -7
- package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +172 -2
- package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +1502 -18
- package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +993 -4
- package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +27 -0
- package/dist/extensions/pi-subagents/test/integration/top-level-async.test.ts +7 -1
- package/dist/extensions/pi-subagents/test/support/helpers.ts +32 -0
- package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +59 -2
- package/dist/extensions/pi-subagents/test/support/mock-pi.ts +32 -6
- package/dist/extensions/pi-subagents/test/support/real-session-child-cli.mjs +25 -24
- package/dist/extensions/pi-subagents/test/support/real-session-runner.ts +59 -35
- package/dist/extensions/pi-subagents/test/support/session-lease-child.mjs +42 -0
- package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +703 -69
- package/dist/extensions/pi-subagents/test/unit/accessible-dir.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/agent-disabled.test.ts +15 -15
- package/dist/extensions/pi-subagents/test/unit/agent-eject-disable.test.ts +66 -66
- package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +478 -19
- package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +228 -7
- package/dist/extensions/pi-subagents/test/unit/agent-memory.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +198 -78
- package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +15 -1
- package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +102 -7
- package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +439 -6
- package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +41 -1
- package/dist/extensions/pi-subagents/test/unit/auto-drain.test.ts +79 -0
- package/dist/extensions/pi-subagents/test/unit/background-work.test.ts +285 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling-pi-args.test.ts +84 -0
- package/dist/extensions/pi-subagents/test/unit/capability-ceiling.test.ts +68 -0
- package/dist/extensions/pi-subagents/test/unit/chain-validation.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/child-protocol.test.ts +82 -0
- package/dist/extensions/pi-subagents/test/unit/child-transcript.test.ts +46 -4
- package/dist/extensions/pi-subagents/test/unit/completion-batcher.test.ts +3 -1
- package/dist/extensions/pi-subagents/test/unit/completion-dedupe.test.ts +9 -17
- package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +175 -3
- package/dist/extensions/pi-subagents/test/unit/config-dir-runtime.test.ts +10 -5
- package/dist/extensions/pi-subagents/test/unit/control-channel.test.ts +126 -6
- package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +219 -0
- package/dist/extensions/pi-subagents/test/unit/delegation-api.test.ts +913 -0
- package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +13 -2
- package/dist/extensions/pi-subagents/test/unit/dynamic-fanout.test.ts +3 -3
- package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +351 -0
- package/dist/extensions/pi-subagents/test/unit/fleet-transcript.test.ts +280 -0
- package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +575 -0
- package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +65 -0
- package/dist/extensions/pi-subagents/test/unit/fork-context.test.ts +94 -1
- package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +23 -0
- package/dist/extensions/pi-subagents/test/unit/host-peer-runtime-imports.test.ts +157 -0
- package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +367 -5
- package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +11 -0
- package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +36 -0
- package/dist/extensions/pi-subagents/test/unit/model-info.test.ts +25 -3
- package/dist/extensions/pi-subagents/test/unit/model-scope.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +341 -10
- package/dist/extensions/pi-subagents/test/unit/nested-control.test.ts +16 -0
- package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +52 -0
- package/dist/extensions/pi-subagents/test/unit/notify.test.ts +168 -7
- package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +85 -7
- package/dist/extensions/pi-subagents/test/unit/parallel-handoff.test.ts +175 -0
- package/dist/extensions/pi-subagents/test/unit/path-resolution.test.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +243 -16
- package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +79 -36
- package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +174 -56
- package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +322 -0
- package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +204 -0
- package/dist/extensions/pi-subagents/test/unit/recursion-guard.test.ts +20 -12
- package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +117 -4
- package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +40 -7
- package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +48 -1
- package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +21 -3
- package/dist/extensions/pi-subagents/test/unit/selector.test.ts +57 -0
- package/dist/extensions/pi-subagents/test/unit/session-lease.test.ts +326 -0
- package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +101 -2
- package/dist/extensions/pi-subagents/test/unit/skills-fallback.test.ts +111 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-budget.test.ts +124 -0
- package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +6 -4
- package/dist/extensions/pi-subagents/test/unit/status-format.test.ts +7 -0
- package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +397 -0
- package/dist/extensions/pi-subagents/test/unit/steering-notices.test.ts +58 -0
- package/dist/extensions/pi-subagents/test/unit/steering.test.ts +173 -0
- package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +25 -4
- package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +390 -25
- package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +837 -0
- package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +86 -0
- package/dist/extensions/pi-subagents/test/unit/tool-budget.test.ts +15 -0
- package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +47 -1
- package/dist/extensions/pi-subagents/test/unit/turn-budget.test.ts +56 -10
- package/dist/extensions/pi-subagents/test/unit/watchdog-change-signature.test.ts +387 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +110 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-emission-guard.test.ts +62 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-lsp-diagnostics.test.ts +144 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-model-selection.test.ts +121 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +76 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +327 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +863 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +290 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-tool-actions.test.ts +91 -0
- package/dist/extensions/pi-subagents/test/unit/watchdog-turn-delta.test.ts +130 -0
- package/dist/extensions/pi-subagents/test/unit/widget-nested-render.test.ts +32 -4
- package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +5 -1
- package/dist/extensions/pi-subagents/test/unit/writer-budget-guidance.test.ts +30 -0
- package/dist/extensions/question/batch.ts +103 -0
- package/dist/extensions/question/constants.ts +1 -1
- package/dist/extensions/question/dialog-adapter.ts +23 -1
- package/dist/extensions/question/helpers.ts +34 -78
- package/dist/extensions/question/index.ts +412 -419
- package/dist/extensions/question/package.json +2 -1
- package/dist/extensions/question/question-list.ts +21 -14
- package/dist/extensions/question/row-layout.ts +2 -2
- package/dist/extensions/question/schemas.ts +38 -39
- package/dist/extensions/question/selection-mode.ts +5 -7
- package/dist/extensions/question/tests/batch.test.ts +75 -0
- package/dist/extensions/question/tests/helpers.test.ts +31 -156
- package/dist/extensions/question/tests/question-list.test.ts +17 -3
- package/dist/extensions/question/tests/row-layout.test.ts +4 -4
- package/dist/extensions/question/tests/schemas.test.ts +30 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +23 -3
- package/dist/extensions/question/tests/wizard.test.ts +99 -0
- package/dist/extensions/question/tui-adapter.ts +2 -3
- package/dist/extensions/question/types.ts +75 -39
- package/dist/extensions/question/ui-protocol.ts +3 -3
- package/dist/extensions/workflow/adapter.ts +4 -76
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +10 -10
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/skills/batch-grill-me/SKILL.md +3 -1
- package/dist/skills/workflow-creation/SKILL.md +2 -2
- package/docs/skills.md +8 -0
- package/docs/workflows.md +4 -4
- package/package.json +1 -1
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
11
11
|
"@selesai/code": "*",
|
|
12
|
+
"@earendil-works/pi-ai": "*",
|
|
12
13
|
"@earendil-works/pi-tui": "*",
|
|
13
14
|
"typebox": "*"
|
|
14
15
|
},
|
|
15
16
|
"scripts": {
|
|
16
|
-
"test": "
|
|
17
|
+
"test": "tsx --test tests/**/*.test.ts"
|
|
17
18
|
}
|
|
18
19
|
}
|
|
@@ -7,14 +7,12 @@ import {
|
|
|
7
7
|
type Component,
|
|
8
8
|
fuzzyFilter,
|
|
9
9
|
Key,
|
|
10
|
-
Markdown,
|
|
11
10
|
matchesKey,
|
|
12
11
|
truncateToWidth,
|
|
13
12
|
wrapTextWithAnsi,
|
|
14
13
|
} from "@earendil-works/pi-tui";
|
|
15
14
|
|
|
16
15
|
import { COMMENT_TOGGLE_LABEL, SPLIT_PANE_LEFT_MIN_WIDTH, SPLIT_PANE_MIN_WIDTH, SPLIT_PANE_RIGHT_MIN_WIDTH, SPLIT_PANE_SEPARATOR } from "./constants.ts";
|
|
17
|
-
import { safeMarkdownTheme } from "./helpers.ts";
|
|
18
16
|
import { matchesDown, matchesUp } from "./navigation.ts";
|
|
19
17
|
import { renderSingleSelectRows } from "./row-layout.ts";
|
|
20
18
|
import type { QuestionOption, ResolvedShortcut } from "./types.ts";
|
|
@@ -67,6 +65,22 @@ export class QuestionList implements Component {
|
|
|
67
65
|
return this.commentEnabled;
|
|
68
66
|
}
|
|
69
67
|
|
|
68
|
+
restoreSelection(values: readonly string[], comment?: string): void {
|
|
69
|
+
const indexes = values
|
|
70
|
+
.map((value) => this.options.findIndex((option) => option.value === value))
|
|
71
|
+
.filter((index) => index >= 0);
|
|
72
|
+
this.checked = new Set(indexes);
|
|
73
|
+
this.selectedIndex = indexes[0] ?? 0;
|
|
74
|
+
this.commentEnabled = Boolean(comment);
|
|
75
|
+
this.searchQuery = "";
|
|
76
|
+
this.invalidate();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getCheckedValues(): string[] {
|
|
80
|
+
if (!this.selectionMode.multi) return [];
|
|
81
|
+
return this.selectionMode.buildResult({ selectedIndex: this.selectedIndex, checked: this.checked, options: this.options });
|
|
82
|
+
}
|
|
83
|
+
|
|
70
84
|
setMaxVisibleRows(rows: number): void {
|
|
71
85
|
if (this.selectionMode.multi) return; // ponytail: multi-select uses fixed max 10, no-op here
|
|
72
86
|
const next = Math.max(1, Math.floor(rows));
|
|
@@ -201,7 +215,6 @@ export class QuestionList implements Component {
|
|
|
201
215
|
|
|
202
216
|
private buildPreviewLines(width: number, filtered: QuestionOption[], maxLines: number): string[] {
|
|
203
217
|
if (maxLines <= 0) return [];
|
|
204
|
-
const mdTheme = safeMarkdownTheme();
|
|
205
218
|
let md = "";
|
|
206
219
|
|
|
207
220
|
if (this.isCommentToggleRow(this.selectedIndex, filtered)) {
|
|
@@ -223,14 +236,9 @@ export class QuestionList implements Component {
|
|
|
223
236
|
}
|
|
224
237
|
}
|
|
225
238
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
lines
|
|
229
|
-
} else {
|
|
230
|
-
lines = [];
|
|
231
|
-
for (const line of wrapTextWithAnsi(md.trim(), Math.max(10, width))) {
|
|
232
|
-
lines.push(truncateToWidth(line, width, ""));
|
|
233
|
-
}
|
|
239
|
+
const lines: string[] = [];
|
|
240
|
+
for (const line of wrapTextWithAnsi(md.trim(), Math.max(10, width))) {
|
|
241
|
+
lines.push(truncateToWidth(line, width, ""));
|
|
234
242
|
}
|
|
235
243
|
while (lines.length > 0 && lines[lines.length - 1]?.trim() === "") lines.pop();
|
|
236
244
|
if (lines.length <= maxLines) return lines;
|
|
@@ -306,7 +314,7 @@ export class QuestionList implements Component {
|
|
|
306
314
|
this.onEnterFreeform?.();
|
|
307
315
|
return;
|
|
308
316
|
}
|
|
309
|
-
const result = filtered[this.selectedIndex]?.
|
|
317
|
+
const result = filtered[this.selectedIndex]?.value;
|
|
310
318
|
if (result) this.onSubmit?.([result], this.commentEnabled);
|
|
311
319
|
else this.onCancel?.();
|
|
312
320
|
return;
|
|
@@ -393,8 +401,7 @@ export class QuestionList implements Component {
|
|
|
393
401
|
checked: this.checked,
|
|
394
402
|
options: this.options,
|
|
395
403
|
});
|
|
396
|
-
|
|
397
|
-
else this.onCancel?.();
|
|
404
|
+
this.onSubmit?.(result, this.commentEnabled);
|
|
398
405
|
}
|
|
399
406
|
}
|
|
400
407
|
|
|
@@ -60,11 +60,11 @@ export function buildItemBlocks(
|
|
|
60
60
|
if (allowComment) {
|
|
61
61
|
allItems.push({
|
|
62
62
|
type: "comment-toggle",
|
|
63
|
-
option: { label: `${commentEnabled ? "[✓]" : "[ ]"} ${COMMENT_TOGGLE_LABEL}` },
|
|
63
|
+
option: { value: "__comment__", label: `${commentEnabled ? "[✓]" : "[ ]"} ${COMMENT_TOGGLE_LABEL}` },
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
if (allowFreeform) {
|
|
67
|
-
allItems.push({ type: "freeform", option: { label: FREEFORM_LABEL } });
|
|
67
|
+
allItems.push({ type: "freeform", option: { value: "__other__", label: FREEFORM_LABEL } });
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
return allItems.map((item, itemIndex) => {
|
|
@@ -1,44 +1,43 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Strict, batch-first TypeBox schema for the question tool.
|
|
2
2
|
|
|
3
|
+
import { StringEnum } from "@earendil-works/pi-ai";
|
|
3
4
|
import { Type } from "typebox";
|
|
4
5
|
|
|
5
|
-
const OptionSchema = Type.
|
|
6
|
-
Type.String({ description: "
|
|
7
|
-
Type.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const OptionSchema = Type.Object({
|
|
7
|
+
value: Type.String({ description: "Stable machine value returned when selected" }),
|
|
8
|
+
label: Type.String({ description: "Human-readable option label" }),
|
|
9
|
+
description: Type.Optional(Type.String({ description: "Optional help text shown under the option" })),
|
|
10
|
+
}, { additionalProperties: false });
|
|
11
|
+
|
|
12
|
+
const QuestionBase = {
|
|
13
|
+
id: Type.Optional(Type.String({ description: "Unique answer id. Missing ids become q1, q2, and so on." })),
|
|
14
|
+
label: Type.Optional(Type.String({ description: "Short page label. Defaults to Q1, Q2, and so on." })),
|
|
15
|
+
question: Type.String({ description: "Question shown to the user" }),
|
|
16
|
+
context: Type.Optional(Type.String({ description: "Optional plain-text context shown before the question" })),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SelectQuestionSchema = Type.Object({
|
|
20
|
+
...QuestionBase,
|
|
21
|
+
type: StringEnum(["select"] as const),
|
|
22
|
+
options: Type.Array(OptionSchema, { minItems: 1, description: "Available choices" }),
|
|
23
|
+
allowOther: Type.Optional(Type.Boolean({ description: "Allow a custom Other text answer" })),
|
|
24
|
+
}, { additionalProperties: false });
|
|
25
|
+
|
|
26
|
+
const MultiSelectQuestionSchema = Type.Object({
|
|
27
|
+
...QuestionBase,
|
|
28
|
+
type: StringEnum(["multiselect"] as const),
|
|
29
|
+
options: Type.Array(OptionSchema, { minItems: 1, description: "Available choices" }),
|
|
30
|
+
allowOther: Type.Optional(Type.Boolean({ description: "Allow custom Other text alongside selected values" })),
|
|
31
|
+
}, { additionalProperties: false });
|
|
32
|
+
|
|
33
|
+
const TextQuestionSchema = Type.Object({
|
|
34
|
+
...QuestionBase,
|
|
35
|
+
type: StringEnum(["text"] as const),
|
|
36
|
+
}, { additionalProperties: false });
|
|
12
37
|
|
|
13
38
|
export const QuestionParamsSchema = Type.Object({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
options: Type.Optional(
|
|
20
|
-
Type.Array(OptionSchema, {
|
|
21
|
-
description: "Optional multiple-choice options. Each option may be a string or { label, description }.",
|
|
22
|
-
}),
|
|
23
|
-
),
|
|
24
|
-
allowMultiple: Type.Optional(
|
|
25
|
-
Type.Boolean({ description: "Allow selecting multiple options. Default: false" }),
|
|
26
|
-
),
|
|
27
|
-
allowFreeform: Type.Optional(
|
|
28
|
-
Type.Boolean({
|
|
29
|
-
description: "Allow a custom freeform answer. Defaults to true when no options are provided, false when options exist.",
|
|
30
|
-
}),
|
|
31
|
-
),
|
|
32
|
-
allowComment: Type.Optional(
|
|
33
|
-
Type.Boolean({ description: "Collect an optional comment after selecting one or more options. Default: false" }),
|
|
34
|
-
),
|
|
35
|
-
commentToggleKey: Type.Optional(
|
|
36
|
-
Type.String({
|
|
37
|
-
description:
|
|
38
|
-
"Shortcut for toggling the optional comment/extra-context row when allowComment is true, e.g. 'ctrl+g'. Pass 'off' to disable. Default: PI_QUESTION_COMMENT_TOGGLE_KEY env var if set, otherwise 'ctrl+g'.",
|
|
39
|
-
}),
|
|
40
|
-
),
|
|
41
|
-
timeout: Type.Optional(
|
|
42
|
-
Type.Number({ description: "Auto-dismiss after N milliseconds. Returns null (cancelled) when expired." }),
|
|
43
|
-
),
|
|
44
|
-
});
|
|
39
|
+
questions: Type.Array(Type.Union([SelectQuestionSchema, MultiSelectQuestionSchema, TextQuestionSchema]), {
|
|
40
|
+
minItems: 1,
|
|
41
|
+
description: "One or more typed questions shown as a paged wizard followed by atomic review and submission.",
|
|
42
|
+
}),
|
|
43
|
+
}, { additionalProperties: false });
|
|
@@ -20,8 +20,8 @@ export const SingleSelect: SelectionMode = {
|
|
|
20
20
|
multi: false,
|
|
21
21
|
toggle() {},
|
|
22
22
|
buildResult(state) {
|
|
23
|
-
const
|
|
24
|
-
return
|
|
23
|
+
const value = state.options[state.selectedIndex]?.value;
|
|
24
|
+
return value ? [value] : [];
|
|
25
25
|
},
|
|
26
26
|
handleNumberKey(idx, count) {
|
|
27
27
|
return { toggle: false, selectIndex: Math.min(idx, count - 1) };
|
|
@@ -36,12 +36,10 @@ export const MultiSelect: SelectionMode = {
|
|
|
36
36
|
else checked.add(index);
|
|
37
37
|
},
|
|
38
38
|
buildResult(state) {
|
|
39
|
-
|
|
39
|
+
return Array.from(state.checked)
|
|
40
40
|
.sort((a, b) => a - b)
|
|
41
|
-
.map((i) => state.options[i]?.
|
|
42
|
-
.filter((
|
|
43
|
-
const fallback = state.options[state.selectedIndex]?.label;
|
|
44
|
-
return selectedTitles.length > 0 ? selectedTitles : fallback ? [fallback] : [];
|
|
41
|
+
.map((i) => state.options[i]?.value)
|
|
42
|
+
.filter((value): value is string => !!value);
|
|
45
43
|
},
|
|
46
44
|
handleNumberKey(idx, count) {
|
|
47
45
|
return { toggle: true, selectIndex: Math.min(idx, count - 1) };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { buildQuestionAnswers, prepareQuestions, saveDraftQuestion } from "../batch.ts";
|
|
5
|
+
import type { DraftQuestionState } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
test("prepareQuestions: normalizes typed questions and generated ids", () => {
|
|
8
|
+
const result = prepareQuestions([
|
|
9
|
+
{ type: "select", question: "Pick a color", options: [{ value: "red", label: "Red" }, { value: "blue", label: "Blue" }] },
|
|
10
|
+
{ type: "text", id: "notes", label: "Notes", question: "Anything else?" },
|
|
11
|
+
]);
|
|
12
|
+
assert.ok("questions" in result);
|
|
13
|
+
if (!("questions" in result)) return;
|
|
14
|
+
assert.deepEqual(result.questions, [
|
|
15
|
+
{
|
|
16
|
+
id: "q1",
|
|
17
|
+
label: "Q1",
|
|
18
|
+
question: "Pick a color",
|
|
19
|
+
context: undefined,
|
|
20
|
+
type: "select",
|
|
21
|
+
options: [
|
|
22
|
+
{ value: "red", label: "Red", description: undefined },
|
|
23
|
+
{ value: "blue", label: "Blue", description: undefined },
|
|
24
|
+
],
|
|
25
|
+
allowOther: false,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "notes",
|
|
29
|
+
label: "Notes",
|
|
30
|
+
question: "Anything else?",
|
|
31
|
+
context: undefined,
|
|
32
|
+
type: "text",
|
|
33
|
+
options: [],
|
|
34
|
+
allowOther: false,
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("prepareQuestions: rejects empty batches, generated-id collisions, and duplicate option values", () => {
|
|
40
|
+
assert.deepEqual(prepareQuestions([]), { error: "questions must contain at least one question." });
|
|
41
|
+
assert.match((prepareQuestions([
|
|
42
|
+
{ type: "text", id: "q2", question: "One" },
|
|
43
|
+
{ type: "text", question: "Two" },
|
|
44
|
+
]) as { error: string }).error, /duplicate id: q2/);
|
|
45
|
+
assert.match((prepareQuestions([{
|
|
46
|
+
type: "select",
|
|
47
|
+
question: "Choose",
|
|
48
|
+
options: [{ value: "same", label: "A" }, { value: "same", label: "B" }],
|
|
49
|
+
}]) as { error: string }).error, /duplicate value: same/);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("saveDraftQuestion: clearing an answer while navigating back leaves it unanswered", () => {
|
|
53
|
+
const states = new Map<string, DraftQuestionState>([["q1", { status: "answered", response: { kind: "text", text: "old" } }]]);
|
|
54
|
+
saveDraftQuestion(states, "q1", null, false);
|
|
55
|
+
assert.equal(states.has("q1"), false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("buildQuestionAnswers: preserves answered, skipped, and unvisited states", () => {
|
|
59
|
+
const prepared = prepareQuestions([
|
|
60
|
+
{ type: "text", id: "a", question: "A" },
|
|
61
|
+
{ type: "text", id: "b", question: "B" },
|
|
62
|
+
{ type: "text", id: "c", question: "C" },
|
|
63
|
+
]);
|
|
64
|
+
assert.ok("questions" in prepared);
|
|
65
|
+
if (!("questions" in prepared)) return;
|
|
66
|
+
const states = new Map<string, DraftQuestionState>([
|
|
67
|
+
["a", { status: "answered", response: { kind: "text", text: "answer" } }],
|
|
68
|
+
["b", { status: "skipped" }],
|
|
69
|
+
]);
|
|
70
|
+
assert.deepEqual(buildQuestionAnswers(prepared.questions, states), [
|
|
71
|
+
{ id: "a", status: "answered", response: { kind: "text", text: "answer" } },
|
|
72
|
+
{ id: "b", status: "skipped" },
|
|
73
|
+
{ id: "c", status: "unanswered" },
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
@@ -2,177 +2,52 @@ import test from "node:test";
|
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
normalizeOptions,
|
|
6
|
-
getOptionsFormatError,
|
|
7
|
-
createFreeformResponse,
|
|
8
5
|
createSelectionResponse,
|
|
6
|
+
createTextResponse,
|
|
7
|
+
formatQuestionResponse,
|
|
9
8
|
formatResponseSummary,
|
|
10
|
-
formatOptionsForMessage,
|
|
11
|
-
parseDialogSelections,
|
|
12
|
-
isCancelled,
|
|
13
9
|
oneLine,
|
|
10
|
+
previewText,
|
|
14
11
|
} from "../helpers.ts";
|
|
12
|
+
import type { PreparedQuestion } from "../types.ts";
|
|
15
13
|
|
|
16
|
-
test("
|
|
17
|
-
assert.
|
|
18
|
-
|
|
19
|
-
{ label: "b" },
|
|
20
|
-
]);
|
|
14
|
+
test("createTextResponse trims edges and rejects blank text", () => {
|
|
15
|
+
assert.equal(createTextResponse(" "), null);
|
|
16
|
+
assert.deepEqual(createTextResponse(" hello\nworld "), { kind: "text", text: "hello\nworld" });
|
|
21
17
|
});
|
|
22
18
|
|
|
23
|
-
test("
|
|
24
|
-
assert.
|
|
25
|
-
|
|
26
|
-
[{ label: "a", description: "desc" }],
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("normalizeOptions: mixed string and object", () => {
|
|
31
|
-
assert.deepEqual(normalizeOptions(["a", { label: "b", description: "d" }]), [
|
|
32
|
-
{ label: "a" },
|
|
33
|
-
{ label: "b", description: "d" },
|
|
34
|
-
]);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test("normalizeOptions: empty/undefined", () => {
|
|
38
|
-
assert.deepEqual(normalizeOptions(undefined), []);
|
|
39
|
-
assert.deepEqual(normalizeOptions([]), []);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test("normalizeOptions: whitespace-only labels filtered", () => {
|
|
43
|
-
assert.deepEqual(normalizeOptions([" ", "a", ""]), [{ label: "a" }]);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("getOptionsFormatError: rejects JSON-encoded option arrays", () => {
|
|
47
|
-
assert.match(getOptionsFormatError(['[{"label":"Yes","description":"desc"}]']) ?? "", /JSON-encoded string/);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("getOptionsFormatError: allows normal string labels", () => {
|
|
51
|
-
assert.equal(getOptionsFormatError(["Yes", "No"]), null);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("createFreeformResponse: null/undefined/empty → null", () => {
|
|
55
|
-
assert.equal(createFreeformResponse(null), null);
|
|
56
|
-
assert.equal(createFreeformResponse(undefined), null);
|
|
57
|
-
assert.equal(createFreeformResponse(""), null);
|
|
58
|
-
assert.equal(createFreeformResponse(" "), null);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("createFreeformResponse: non-empty → { kind: freeform }", () => {
|
|
62
|
-
assert.deepEqual(createFreeformResponse("hello"), { kind: "freeform", text: "hello" });
|
|
63
|
-
assert.deepEqual(createFreeformResponse(" trim "), { kind: "freeform", text: "trim" });
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("createSelectionResponse: empty → null", () => {
|
|
67
|
-
assert.equal(createSelectionResponse([]), null);
|
|
68
|
-
assert.equal(createSelectionResponse(["", " "]), null);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test("createSelectionResponse: single", () => {
|
|
72
|
-
assert.deepEqual(createSelectionResponse(["a"]), { kind: "selection", selections: ["a"] });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("createSelectionResponse: multiple with trim", () => {
|
|
76
|
-
assert.deepEqual(createSelectionResponse([" a ", "b"]), { kind: "selection", selections: ["a", "b"] });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("createSelectionResponse: with comment", () => {
|
|
80
|
-
assert.deepEqual(createSelectionResponse(["a"], "note"), {
|
|
19
|
+
test("createSelectionResponse supports values plus Other and rejects empty", () => {
|
|
20
|
+
assert.equal(createSelectionResponse([], " "), null);
|
|
21
|
+
assert.deepEqual(createSelectionResponse([" a ", "b"], " other "), {
|
|
81
22
|
kind: "selection",
|
|
82
|
-
|
|
83
|
-
|
|
23
|
+
values: ["a", "b"],
|
|
24
|
+
otherText: "other",
|
|
84
25
|
});
|
|
85
26
|
});
|
|
86
27
|
|
|
87
|
-
test("
|
|
88
|
-
assert.
|
|
89
|
-
|
|
90
|
-
selections: ["a"],
|
|
91
|
-
comment: "note",
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("createSelectionResponse: empty comment → no comment field", () => {
|
|
96
|
-
assert.deepEqual(createSelectionResponse(["a"], " "), { kind: "selection", selections: ["a"] });
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("formatResponseSummary: freeform → text", () => {
|
|
100
|
-
assert.equal(formatResponseSummary({ kind: "freeform", text: "hello" }), "hello");
|
|
28
|
+
test("formatResponseSummary returns machine values", () => {
|
|
29
|
+
assert.equal(formatResponseSummary({ kind: "selection", values: ["a", "b"], otherText: "c" }), "a, b, c");
|
|
30
|
+
assert.equal(formatResponseSummary({ kind: "text", text: "hello" }), "hello");
|
|
101
31
|
});
|
|
102
32
|
|
|
103
|
-
test("
|
|
104
|
-
|
|
33
|
+
test("formatQuestionResponse maps stable values to labels", () => {
|
|
34
|
+
const question: PreparedQuestion = {
|
|
35
|
+
id: "q1",
|
|
36
|
+
label: "Q1",
|
|
37
|
+
question: "Pick",
|
|
38
|
+
type: "multiselect",
|
|
39
|
+
options: [{ value: "a", label: "Alpha" }, { value: "b", label: "Beta" }],
|
|
40
|
+
allowOther: true,
|
|
41
|
+
};
|
|
42
|
+
assert.equal(formatQuestionResponse(question, { kind: "selection", values: ["b"], otherText: "Gamma" }), "Beta, Other: Gamma");
|
|
105
43
|
});
|
|
106
44
|
|
|
107
|
-
test("
|
|
108
|
-
assert.equal(
|
|
109
|
-
|
|
110
|
-
"a, b — note",
|
|
111
|
-
);
|
|
45
|
+
test("previewText bounds multiline summaries", () => {
|
|
46
|
+
assert.equal(previewText("one\ntwo\nthree", 160, 2), "one\ntwo…");
|
|
47
|
+
assert.equal(previewText("a".repeat(200), 10, 2), `${"a".repeat(9)}…`);
|
|
112
48
|
});
|
|
113
49
|
|
|
114
|
-
test("
|
|
115
|
-
assert.equal(formatOptionsForMessage([]), "");
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("formatOptionsForMessage: 1 option", () => {
|
|
119
|
-
assert.equal(formatOptionsForMessage([{ label: "a" }]), "1. a");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("formatOptionsForMessage: with description", () => {
|
|
123
|
-
assert.equal(formatOptionsForMessage([{ label: "a", description: "desc" }]), "1. a — desc");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("formatOptionsForMessage: multiple", () => {
|
|
127
|
-
assert.equal(
|
|
128
|
-
formatOptionsForMessage([{ label: "a" }, { label: "b", description: "d" }]),
|
|
129
|
-
"1. a\n2. b — d",
|
|
130
|
-
);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("parseDialogSelections: basic CSV", () => {
|
|
134
|
-
assert.deepEqual(parseDialogSelections("a,b,c"), ["a", "b", "c"]);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test("parseDialogSelections: whitespace trimmed", () => {
|
|
138
|
-
assert.deepEqual(parseDialogSelections(" a , b "), ["a", "b"]);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("parseDialogSelections: empty → []", () => {
|
|
142
|
-
assert.deepEqual(parseDialogSelections(""), []);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("parseDialogSelections: empty entries filtered", () => {
|
|
146
|
-
assert.deepEqual(parseDialogSelections("a,,b"), ["a", "b"]);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("isCancelled: null/undefined → true", () => {
|
|
150
|
-
assert.equal(isCancelled(null), true);
|
|
151
|
-
assert.equal(isCancelled(undefined), true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("isCancelled: falsy non-null → false", () => {
|
|
155
|
-
assert.equal(isCancelled(""), false);
|
|
156
|
-
assert.equal(isCancelled(0), false);
|
|
157
|
-
assert.equal(isCancelled(false), false);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("oneLine: undefined → empty string", () => {
|
|
161
|
-
assert.equal(oneLine(undefined), "");
|
|
162
|
-
assert.equal(oneLine(null), "");
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("oneLine: short → unchanged", () => {
|
|
166
|
-
assert.equal(oneLine("hello"), "hello");
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
test("oneLine: long → truncated with …", () => {
|
|
170
|
-
const long = "a".repeat(120);
|
|
171
|
-
const result = oneLine(long, 10);
|
|
172
|
-
assert.equal(result.length, 10);
|
|
173
|
-
assert.equal(result.endsWith("…"), true);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("oneLine: whitespace collapsed", () => {
|
|
50
|
+
test("oneLine collapses whitespace and truncates", () => {
|
|
177
51
|
assert.equal(oneLine(" hello world "), "hello world");
|
|
178
|
-
});
|
|
52
|
+
assert.equal(oneLine("a".repeat(20), 10), `${"a".repeat(9)}…`);
|
|
53
|
+
});
|
|
@@ -36,7 +36,7 @@ const disabledShortcut: ResolvedShortcut = {
|
|
|
36
36
|
matches: ((_data: string) => false) as (data: string) => false,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const opts = (labels: string[]) => labels.map((label) => ({ label }));
|
|
39
|
+
const opts = (labels: string[]) => labels.map((label) => ({ value: label, label }));
|
|
40
40
|
|
|
41
41
|
// Terminal data bytes
|
|
42
42
|
const ESC = "\x1b";
|
|
@@ -163,12 +163,26 @@ test("MultiSelect: confirm with checked items → onSubmit with sorted labels",
|
|
|
163
163
|
assert.deepEqual(submitted, [["a", "b"], false]); // sorted by index
|
|
164
164
|
});
|
|
165
165
|
|
|
166
|
-
test("
|
|
166
|
+
test("QuestionList: restores saved multi-select choices and comment state", () => {
|
|
167
|
+
const list = new QuestionList(opts(["a", "b", "c"]), MultiSelect, false, true, fakeTheme, fakeKeybindings(), disabledShortcut);
|
|
168
|
+
list.restoreSelection(["a", "c"], "reason");
|
|
169
|
+
assert.deepEqual([...((list as any).checked)], [0, 2]);
|
|
170
|
+
assert.equal((list as any).selectedIndex, 0);
|
|
171
|
+
assert.equal(list.isCommentEnabled(), true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("QuestionList: restores saved single-select cursor", () => {
|
|
175
|
+
const list = new QuestionList(opts(["a", "b", "c"]), SingleSelect, false, false, fakeTheme, fakeKeybindings(), disabledShortcut);
|
|
176
|
+
list.restoreSelection(["b"]);
|
|
177
|
+
assert.equal((list as any).selectedIndex, 1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("MultiSelect: confirm with nothing checked → empty selection", () => {
|
|
167
181
|
const list = new QuestionList(opts(["a", "b"]), MultiSelect, false, false, fakeTheme, fakeKeybindings(["tui.select.confirm"]), disabledShortcut);
|
|
168
182
|
let submitted: [string[], boolean] | null = null;
|
|
169
183
|
list.onSubmit = (selections, commentEnabled) => (submitted = [selections, commentEnabled]);
|
|
170
184
|
list.handleInput(ENTER);
|
|
171
|
-
assert.deepEqual(submitted, [[
|
|
185
|
+
assert.deepEqual(submitted, [[], false]);
|
|
172
186
|
});
|
|
173
187
|
|
|
174
188
|
test("MultiSelect: confirm with no options → onCancel", () => {
|
|
@@ -4,7 +4,7 @@ import assert from "node:assert/strict";
|
|
|
4
4
|
import { wrapPlain, buildItemBlocks, flattenBlocks, renderSingleSelectRows } from "../row-layout.ts";
|
|
5
5
|
import type { ItemBlock, QuestionOption } from "../types.ts";
|
|
6
6
|
|
|
7
|
-
const opts = (labels: string[]): QuestionOption[] => labels.map((label) => ({ label }));
|
|
7
|
+
const opts = (labels: string[]): QuestionOption[] => labels.map((label) => ({ value: label, label }));
|
|
8
8
|
|
|
9
9
|
test("wrapPlain: empty → ['']", () => {
|
|
10
10
|
assert.deepEqual(wrapPlain("", 20), [""]);
|
|
@@ -45,7 +45,7 @@ test("buildItemBlocks: selected=1 → arrow pointer on block 1", () => {
|
|
|
45
45
|
|
|
46
46
|
test("buildItemBlocks: with description → description lines included", () => {
|
|
47
47
|
const blocks = buildItemBlocks(
|
|
48
|
-
[{ label: "a", description: "some desc" }],
|
|
48
|
+
[{ value: "a", label: "a", description: "some desc" }],
|
|
49
49
|
80,
|
|
50
50
|
false,
|
|
51
51
|
false,
|
|
@@ -58,7 +58,7 @@ test("buildItemBlocks: with description → description lines included", () => {
|
|
|
58
58
|
|
|
59
59
|
test("buildItemBlocks: hideDescriptions=true → no description lines", () => {
|
|
60
60
|
const blocks = buildItemBlocks(
|
|
61
|
-
[{ label: "a", description: "some desc" }],
|
|
61
|
+
[{ value: "a", label: "a", description: "some desc" }],
|
|
62
62
|
80,
|
|
63
63
|
false,
|
|
64
64
|
false,
|
|
@@ -125,7 +125,7 @@ test("renderSingleSelectRows: maxRows truncation with indicator", () => {
|
|
|
125
125
|
|
|
126
126
|
test("renderSingleSelectRows: selected block larger than available → only selected + indicator", () => {
|
|
127
127
|
const rows = renderSingleSelectRows({
|
|
128
|
-
options: [{ label: "a".repeat(100) }],
|
|
128
|
+
options: [{ value: "a", label: "a".repeat(100) }],
|
|
129
129
|
selectedIndex: 0,
|
|
130
130
|
width: 10,
|
|
131
131
|
allowFreeform: false,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
|
|
4
|
+
import { Value } from "typebox/value";
|
|
5
|
+
|
|
6
|
+
import { QuestionParamsSchema } from "../schemas.ts";
|
|
7
|
+
|
|
8
|
+
const valid = {
|
|
9
|
+
questions: [{ type: "select", question: "Pick", options: [{ value: "yes", label: "Yes" }] }],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
test("QuestionParamsSchema accepts typed batch requests", () => {
|
|
13
|
+
assert.equal(Value.Check(QuestionParamsSchema, valid), true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("QuestionParamsSchema rejects legacy and mistyped fields", () => {
|
|
17
|
+
assert.equal(Value.Check(QuestionParamsSchema, { ...valid, timeout: 1000 }), false);
|
|
18
|
+
assert.equal(Value.Check(QuestionParamsSchema, {
|
|
19
|
+
questions: [{ ...valid.questions[0], allowMultiple: true }],
|
|
20
|
+
}), false);
|
|
21
|
+
assert.equal(Value.Check(QuestionParamsSchema, {
|
|
22
|
+
questions: [{ ...valid.questions[0], allowComment: true }],
|
|
23
|
+
}), false);
|
|
24
|
+
assert.equal(Value.Check(QuestionParamsSchema, {
|
|
25
|
+
questions: [{ type: "text", question: "Explain", options: [] }],
|
|
26
|
+
}), false);
|
|
27
|
+
assert.equal(Value.Check(QuestionParamsSchema, {
|
|
28
|
+
questions: [{ ...valid.questions[0], options: [{ value: "yes", label: "Yes", extra: true }] }],
|
|
29
|
+
}), false);
|
|
30
|
+
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
3
|
|
|
4
|
-
import { DialogFallback } from "../dialog-adapter.ts";
|
|
5
|
-
import type { QuestionResponse, ResolvedQuestionParams } from "../types.ts";
|
|
4
|
+
import { askBatchViaDialogs, DialogFallback } from "../dialog-adapter.ts";
|
|
5
|
+
import type { BatchedQuestion, QuestionResponse, ResolvedQuestionParams } from "../types.ts";
|
|
6
6
|
import type { UIProtocol } from "../ui-protocol.ts";
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
@@ -134,4 +134,24 @@ test("DialogFallback: freeform cancel (input returns undefined after selecting c
|
|
|
134
134
|
});
|
|
135
135
|
const result = await DialogFallback.ask(makeParams({ allowFreeform: true }), protocol);
|
|
136
136
|
assert.equal(result, null);
|
|
137
|
-
});
|
|
137
|
+
});
|
|
138
|
+
test("askBatchViaDialogs: returns identified answers and passes remaining timeout", async () => {
|
|
139
|
+
const timeouts: Array<number | undefined> = [];
|
|
140
|
+
const protocol = fakeProtocol({
|
|
141
|
+
select: async (_prompt, _options, opts) => {
|
|
142
|
+
timeouts.push(opts?.timeout);
|
|
143
|
+
return "Option A";
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
const questions: BatchedQuestion[] = [
|
|
147
|
+
{ ...makeParams(), id: "first", label: "First" },
|
|
148
|
+
{ ...makeParams({ question: "Pick again" }), id: "second", label: "Second" },
|
|
149
|
+
];
|
|
150
|
+
const result = await askBatchViaDialogs(questions, protocol, 1_000);
|
|
151
|
+
assert.deepEqual(result, [
|
|
152
|
+
{ id: "first", question: "Pick one", response: { kind: "selection", selections: ["Option A"] } },
|
|
153
|
+
{ id: "second", question: "Pick again", response: { kind: "selection", selections: ["Option A"] } },
|
|
154
|
+
]);
|
|
155
|
+
assert.equal(timeouts.length, 2);
|
|
156
|
+
assert.ok(timeouts.every((timeout) => timeout !== undefined && timeout > 0 && timeout <= 1_000));
|
|
157
|
+
});
|