@bastani/atomic 0.9.13 → 0.9.14-alpha.1

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.
Files changed (96) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/builtin/i-have-adhd/package.json +1 -1
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +12 -0
  8. package/dist/builtin/workflows/README.md +9 -0
  9. package/dist/builtin/workflows/builtin/adversarial-verification.ts +4 -0
  10. package/dist/builtin/workflows/builtin/classify-and-act.ts +4 -0
  11. package/dist/builtin/workflows/builtin/fan-out-and-synthesize.ts +4 -0
  12. package/dist/builtin/workflows/builtin/generate-and-filter.ts +4 -0
  13. package/dist/builtin/workflows/builtin/goal.ts +4 -0
  14. package/dist/builtin/workflows/builtin/loop-until-done.ts +4 -0
  15. package/dist/builtin/workflows/builtin/open-claude-design.ts +4 -0
  16. package/dist/builtin/workflows/builtin/ralph.ts +4 -0
  17. package/dist/builtin/workflows/builtin/tournament.ts +4 -0
  18. package/dist/builtin/workflows/package.json +1 -1
  19. package/dist/builtin/workflows/src/authoring/workflow.ts +9 -0
  20. package/dist/builtin/workflows/src/authoring.d.ts +3 -0
  21. package/dist/builtin/workflows/src/durable/completed-catalog.ts +13 -2
  22. package/dist/builtin/workflows/src/durable/workflow-heartbeat-anchor.ts +153 -0
  23. package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +28 -0
  24. package/dist/builtin/workflows/src/engine/run.ts +1 -0
  25. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +138 -0
  26. package/dist/builtin/workflows/src/extension/index.bundle.mjs +766 -4
  27. package/dist/builtin/workflows/src/extension/workflow-heartbeat-delivery.ts +197 -0
  28. package/dist/builtin/workflows/src/extension/workflow-heartbeat-notice.ts +115 -0
  29. package/dist/builtin/workflows/src/extension/workflow-heartbeat-scheduler.ts +962 -0
  30. package/dist/builtin/workflows/src/sdk-surface.ts +9 -0
  31. package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +1 -0
  32. package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +1 -0
  33. package/dist/builtin/workflows/src/shared/persistence-restore.ts +12 -0
  34. package/dist/builtin/workflows/src/shared/workflow-authoring-types.d.ts +1 -0
  35. package/dist/builtin/workflows/src/shared/workflow-authoring-types.ts +1 -0
  36. package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.d.ts +21 -0
  37. package/dist/builtin/workflows/src/shared/workflow-heartbeat-contract.ts +25 -0
  38. package/dist/core/extensions/index.d.ts +1 -0
  39. package/dist/core/extensions/index.d.ts.map +1 -1
  40. package/dist/core/extensions/index.js +1 -0
  41. package/dist/core/extensions/index.js.map +1 -1
  42. package/dist/core/extensions/ui-types.d.ts +26 -0
  43. package/dist/core/extensions/ui-types.d.ts.map +1 -1
  44. package/dist/core/extensions/ui-types.js +17 -1
  45. package/dist/core/extensions/ui-types.js.map +1 -1
  46. package/dist/core/tools/ask-user-question/ask-user-question.d.ts +21 -0
  47. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  48. package/dist/core/tools/ask-user-question/ask-user-question.js +34 -9
  49. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  50. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
  51. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js +5 -2
  52. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
  53. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
  54. package/dist/core/tools/ask-user-question/view/components/submit-picker.js +3 -1
  55. package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
  56. package/dist/core/tools/ask-user-question/view/components/wrapping-select.d.ts.map +1 -1
  57. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js +8 -1
  58. package/dist/core/tools/ask-user-question/view/components/wrapping-select.js.map +1 -1
  59. package/dist/index-extensions.d.ts +1 -1
  60. package/dist/index-extensions.d.ts.map +1 -1
  61. package/dist/index-extensions.js +1 -1
  62. package/dist/index-extensions.js.map +1 -1
  63. package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts +95 -0
  64. package/dist/modes/interactive/components/reserved-bottom-overlay.d.ts.map +1 -0
  65. package/dist/modes/interactive/components/reserved-bottom-overlay.js +234 -0
  66. package/dist/modes/interactive/components/reserved-bottom-overlay.js.map +1 -0
  67. package/dist/modes/interactive/interactive-extension-custom-ui.js +94 -6
  68. package/dist/modes/interactive/interactive-extension-custom-ui.js.map +1 -1
  69. package/dist/modes/interactive/interactive-mode-base.d.ts +3 -0
  70. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  71. package/dist/modes/interactive/interactive-mode-base.js +5 -1
  72. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  73. package/dist/modes/interactive/interactive-mode-surface.d.ts +2 -0
  74. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  76. package/dist/modes/interactive/interactive-tui.d.ts +3 -0
  77. package/dist/modes/interactive/interactive-tui.d.ts.map +1 -1
  78. package/dist/modes/interactive/interactive-tui.js +14 -0
  79. package/dist/modes/interactive/interactive-tui.js.map +1 -1
  80. package/dist/modes/interactive-engine/engine-custom-ui.d.ts +2 -0
  81. package/dist/modes/interactive-engine/engine-custom-ui.d.ts.map +1 -1
  82. package/dist/modes/interactive-engine/engine-custom-ui.js +1 -0
  83. package/dist/modes/interactive-engine/engine-custom-ui.js.map +1 -1
  84. package/dist/modes/interactive-engine/protocol.d.ts +1 -0
  85. package/dist/modes/interactive-engine/protocol.d.ts.map +1 -1
  86. package/dist/modes/interactive-engine/protocol.js +1 -0
  87. package/dist/modes/interactive-engine/protocol.js.map +1 -1
  88. package/dist/modes/interactive-engine/remote-component.d.ts +3 -1
  89. package/dist/modes/interactive-engine/remote-component.d.ts.map +1 -1
  90. package/dist/modes/interactive-engine/remote-component.js +3 -2
  91. package/dist/modes/interactive-engine/remote-component.js.map +1 -1
  92. package/docs/extensions.md +27 -0
  93. package/docs/keybindings.md +1 -0
  94. package/docs/workflows.md +34 -0
  95. package/npm-shrinkwrap.json +29 -29
  96. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"multi-select-view.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/multi-select-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../../../../modes/interactive/theme/theme.ts\";\nimport { ROW_INTENT_META } from \"../../state/row-intent.ts\";\nimport type { QuestionData } from \"../../tool/types.ts\";\nimport type { StatefulView } from \"../stateful-view.ts\";\n\nconst ACTIVE_POINTER = \"❯ \";\nconst INACTIVE_POINTER = \" \";\nconst CHECKED = \"[✔]\";\nconst UNCHECKED = \"[ ]\";\nconst NUMBER_SEPARATOR = \". \";\nconst BOX_LABEL_GAP = \" \";\n// CC parity: description continuation indents to col 2 (past the pointer slot), NOT to the\n// full prefix column. Wrap width still uses prefixVisibleWidth so naturalHeight matches render.\nconst CONTINUATION_INDENT = \" \";\n\nexport const MULTI_SUBMIT_LABEL = \"Submit\";\n\nexport interface MultiSelectViewProps {\n\trows: ReadonlyArray<{ checked: boolean; active: boolean }>;\n\tnextActive: boolean;\n\tnextLabel: string;\n}\n\n/**\n * Renders the multi-select option list (one row per option — pointer + checkbox + label —\n * plus zero or more wrapped continuation lines per description).\n *\n * `naturalHeight(width)` is state-INDEPENDENT (depends only on theme glyph widths,\n * question.options, and width) so the host can compute a stable globalContentHeight\n * without rendering. `naturalHeight(w) === render(w).length` for every props.\n *\n * `setProps(props)` is a pure field reassignment — no render, no invalidate side effects.\n */\nexport class MultiSelectView implements StatefulView<MultiSelectViewProps> {\n\tprivate props: MultiSelectViewProps;\n\n\tprivate declare readonly theme: Theme;\n\tprivate declare readonly question: QuestionData;\n\n\tconstructor(theme: Theme, question: QuestionData) {\n\t\tthis.theme = theme;\n\t\tthis.question = question;\n\t\tthis.props = { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\t}\n\n\tsetProps(props: MultiSelectViewProps): void {\n\t\tthis.props = props;\n\t}\n\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst prefixWidth = this.prefixVisibleWidth();\n\t\tconst contentWidth = Math.max(1, width - prefixWidth);\n\t\tconst numberWidth = String(Math.max(1, this.question.options.length)).length;\n\t\tfor (let i = 0; i < this.question.options.length; i++) {\n\t\t\tconst opt = this.question.options[i];\n\t\t\tconst row = this.props.rows[i];\n\t\t\tif (!opt || !row) continue;\n\t\t\tconst pointer = row.active ? this.theme.fg(\"accent\", ACTIVE_POINTER) : INACTIVE_POINTER;\n\t\t\t// Checked uses the same `accent` hue as the active-row label so checked rows read\n\t\t\t// as \"selected\" rather than \"success\" — matches the visual rhythm of the rest of\n\t\t\t// the dialog (active pointer, label, picker rows are all accent).\n\t\t\tconst box = row.checked ? this.theme.fg(\"accent\", CHECKED) : this.theme.fg(\"muted\", UNCHECKED);\n\t\t\tconst label = truncateToWidth(opt.label, contentWidth, \"…\");\n\t\t\tconst styledLabel = row.active ? this.theme.fg(\"accent\", this.theme.bold(label)) : label;\n\t\t\tconst num = String(i + 1).padStart(numberWidth, \" \");\n\t\t\tconst line = `${pointer}${num}${NUMBER_SEPARATOR}${box}${BOX_LABEL_GAP}${styledLabel}`;\n\t\t\tlines.push(truncateToWidth(line, width, \"\"));\n\t\t\tif (opt.description) {\n\t\t\t\tconst wrapped = wrapTextWithAnsi(opt.description, contentWidth);\n\t\t\t\tfor (const segment of wrapped) {\n\t\t\t\t\tlines.push(CONTINUATION_INDENT + this.theme.fg(\"muted\", segment));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst nextPointer = this.props.nextActive ? this.theme.fg(\"accent\", ACTIVE_POINTER) : INACTIVE_POINTER;\n\t\tconst nextLabel = this.props.nextActive\n\t\t\t? this.theme.fg(\"accent\", this.theme.bold(this.props.nextLabel))\n\t\t\t: this.props.nextLabel;\n\t\tlines.push(truncateToWidth(`${nextPointer}${nextLabel}`, width, \"\"));\n\t\treturn lines;\n\t}\n\n\tnaturalHeight(width: number): number {\n\t\tconst contentWidth = Math.max(1, width - this.prefixVisibleWidth());\n\t\tlet total = 0;\n\t\tfor (const opt of this.question.options) {\n\t\t\tif (!opt) continue;\n\t\t\ttotal += 1; // row line\n\t\t\tif (opt.description) {\n\t\t\t\ttotal += wrapTextWithAnsi(opt.description, contentWidth).length;\n\t\t\t}\n\t\t}\n\t\treturn total + 1; // Next sentinel row (no description; never wraps).\n\t}\n\n\tprivate prefixVisibleWidth(): number {\n\t\t// Canonical prefix for OPTION rows: INACTIVE_POINTER + numberWidth digits + NUMBER_SEPARATOR\n\t\t// + UNCHECKED + BOX_LABEL_GAP. State-independent because ACTIVE/INACTIVE pointer share\n\t\t// visibleWidth, CHECKED/UNCHECKED share visibleWidth, and numberWidth is constant per question.\n\t\t// The Next sentinel uses a bare `pointer + \"Next\"` shape — its width never exceeds this prefix\n\t\t// at any reasonable terminal width, so it's safe to leave it out of the canonical computation.\n\t\tconst numberWidth = String(Math.max(1, this.question.options.length)).length;\n\t\treturn (\n\t\t\tvisibleWidth(INACTIVE_POINTER) + numberWidth + visibleWidth(`${NUMBER_SEPARATOR}${UNCHECKED}${BOX_LABEL_GAP}`)\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"multi-select-view.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/multi-select-view.ts"],"names":[],"mappings":"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","sourcesContent":["import { truncateToWidth, visibleWidth, wrapTextWithAnsi } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../../../../modes/interactive/theme/theme.ts\";\nimport { OVERLAY_ACTIVE_ROW_MARKER } from \"../../../../extensions/ui-types.ts\";\nimport { ROW_INTENT_META } from \"../../state/row-intent.ts\";\nimport type { QuestionData } from \"../../tool/types.ts\";\nimport type { StatefulView } from \"../stateful-view.ts\";\n\nconst ACTIVE_POINTER = \"❯ \";\nconst INACTIVE_POINTER = \" \";\nconst CHECKED = \"[✔]\";\nconst UNCHECKED = \"[ ]\";\nconst NUMBER_SEPARATOR = \". \";\nconst BOX_LABEL_GAP = \" \";\n// CC parity: description continuation indents to col 2 (past the pointer slot), NOT to the\n// full prefix column. Wrap width still uses prefixVisibleWidth so naturalHeight matches render.\nconst CONTINUATION_INDENT = \" \";\n\nexport const MULTI_SUBMIT_LABEL = \"Submit\";\n\nexport interface MultiSelectViewProps {\n\trows: ReadonlyArray<{ checked: boolean; active: boolean }>;\n\tnextActive: boolean;\n\tnextLabel: string;\n}\n\n/**\n * Renders the multi-select option list (one row per option — pointer + checkbox + label —\n * plus zero or more wrapped continuation lines per description).\n *\n * `naturalHeight(width)` is state-INDEPENDENT (depends only on theme glyph widths,\n * question.options, and width) so the host can compute a stable globalContentHeight\n * without rendering. `naturalHeight(w) === render(w).length` for every props.\n *\n * `setProps(props)` is a pure field reassignment — no render, no invalidate side effects.\n */\nexport class MultiSelectView implements StatefulView<MultiSelectViewProps> {\n\tprivate props: MultiSelectViewProps;\n\n\tprivate declare readonly theme: Theme;\n\tprivate declare readonly question: QuestionData;\n\n\tconstructor(theme: Theme, question: QuestionData) {\n\t\tthis.theme = theme;\n\t\tthis.question = question;\n\t\tthis.props = { rows: [], nextActive: false, nextLabel: ROW_INTENT_META.next.label };\n\t}\n\n\tsetProps(props: MultiSelectViewProps): void {\n\t\tthis.props = props;\n\t}\n\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tconst prefixWidth = this.prefixVisibleWidth();\n\t\tconst contentWidth = Math.max(1, width - prefixWidth);\n\t\tconst numberWidth = String(Math.max(1, this.question.options.length)).length;\n\t\tfor (let i = 0; i < this.question.options.length; i++) {\n\t\t\tconst opt = this.question.options[i];\n\t\t\tconst row = this.props.rows[i];\n\t\t\tif (!opt || !row) continue;\n\t\t\tconst pointer = row.active ? this.theme.fg(\"accent\", ACTIVE_POINTER) : INACTIVE_POINTER;\n\t\t\t// Checked uses the same `accent` hue as the active-row label so checked rows read\n\t\t\t// as \"selected\" rather than \"success\" — matches the visual rhythm of the rest of\n\t\t\t// the dialog (active pointer, label, picker rows are all accent).\n\t\t\tconst box = row.checked ? this.theme.fg(\"accent\", CHECKED) : this.theme.fg(\"muted\", UNCHECKED);\n\t\t\tconst label = truncateToWidth(opt.label, contentWidth, \"…\");\n\t\t\tconst styledLabel = row.active ? this.theme.fg(\"accent\", this.theme.bold(label)) : label;\n\t\t\tconst num = String(i + 1).padStart(numberWidth, \" \");\n\t\t\tconst line = `${pointer}${num}${NUMBER_SEPARATOR}${box}${BOX_LABEL_GAP}${styledLabel}`;\n\t\t\tconst rendered = truncateToWidth(line, width, \"\");\n\t\t\tlines.push(`${rendered}${row.active ? OVERLAY_ACTIVE_ROW_MARKER : \"\"}`);\n\t\t\tif (opt.description) {\n\t\t\t\tconst wrapped = wrapTextWithAnsi(opt.description, contentWidth);\n\t\t\t\tfor (const segment of wrapped) {\n\t\t\t\t\tlines.push(CONTINUATION_INDENT + this.theme.fg(\"muted\", segment));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tconst nextPointer = this.props.nextActive ? this.theme.fg(\"accent\", ACTIVE_POINTER) : INACTIVE_POINTER;\n\t\tconst nextLabel = this.props.nextActive\n\t\t\t? this.theme.fg(\"accent\", this.theme.bold(this.props.nextLabel))\n\t\t\t: this.props.nextLabel;\n\t\tconst rendered = truncateToWidth(`${nextPointer}${nextLabel}`, width, \"\");\n\t\tlines.push(`${rendered}${this.props.nextActive ? OVERLAY_ACTIVE_ROW_MARKER : \"\"}`);\n\t\treturn lines;\n\t}\n\n\tnaturalHeight(width: number): number {\n\t\tconst contentWidth = Math.max(1, width - this.prefixVisibleWidth());\n\t\tlet total = 0;\n\t\tfor (const opt of this.question.options) {\n\t\t\tif (!opt) continue;\n\t\t\ttotal += 1; // row line\n\t\t\tif (opt.description) {\n\t\t\t\ttotal += wrapTextWithAnsi(opt.description, contentWidth).length;\n\t\t\t}\n\t\t}\n\t\treturn total + 1; // Next sentinel row (no description; never wraps).\n\t}\n\n\tprivate prefixVisibleWidth(): number {\n\t\t// Canonical prefix for OPTION rows: INACTIVE_POINTER + numberWidth digits + NUMBER_SEPARATOR\n\t\t// + UNCHECKED + BOX_LABEL_GAP. State-independent because ACTIVE/INACTIVE pointer share\n\t\t// visibleWidth, CHECKED/UNCHECKED share visibleWidth, and numberWidth is constant per question.\n\t\t// The Next sentinel uses a bare `pointer + \"Next\"` shape — its width never exceeds this prefix\n\t\t// at any reasonable terminal width, so it's safe to leave it out of the canonical computation.\n\t\tconst numberWidth = String(Math.max(1, this.question.options.length)).length;\n\t\treturn (\n\t\t\tvisibleWidth(INACTIVE_POINTER) + numberWidth + visibleWidth(`${NUMBER_SEPARATOR}${UNCHECKED}${BOX_LABEL_GAP}`)\n\t\t);\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"submit-picker.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/submit-picker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iDAAiD,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAMxD,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAC7C,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,4EAA4E;IAC5E,IAAI,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAa,YAAW,YAAY,CAAC,iBAAiB,CAAC;IACnE,OAAO,CAAC,KAAK,CAAoB;IAEjC,iBAAyB,KAAK,CAAQ;IAEtC,YAAY,KAAK,EAAE,KAAK,EAGvB;IAED,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEvC;IAED,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAG;IAEnC,UAAU,IAAI,IAAI,CAAG;IAErB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAW9B;CACD"}
1
+ {"version":3,"file":"submit-picker.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/submit-picker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,iDAAiD,CAAC;AAE7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAMxD,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAC7C,eAAO,MAAM,YAAY,WAAW,CAAC;AAErC;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,4EAA4E;IAC5E,IAAI,EAAE,aAAa,CAAC;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,YAAa,YAAW,YAAY,CAAC,iBAAiB,CAAC;IACnE,OAAO,CAAC,KAAK,CAAoB;IAEjC,iBAAyB,KAAK,CAAQ;IAEtC,YAAY,KAAK,EAAE,KAAK,EAGvB;IAED,QAAQ,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAEvC;IAED,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAG;IAEnC,UAAU,IAAI,IAAI,CAAG;IAErB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpC;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAY9B;CACD"}
@@ -1,4 +1,5 @@
1
1
  import { truncateToWidth } from "@earendil-works/pi-tui";
2
+ import { OVERLAY_ACTIVE_ROW_MARKER } from "../../../../extensions/ui-types.js";
2
3
  const ACTIVE_POINTER = "❯ ";
3
4
  const INACTIVE_POINTER = " ";
4
5
  const NUMBER_SEPARATOR = ". ";
@@ -36,7 +37,8 @@ export class SubmitPicker {
36
37
  const pointer = active ? ACTIVE_POINTER : INACTIVE_POINTER;
37
38
  const number = `${i + 1}${NUMBER_SEPARATOR}`;
38
39
  const label = active ? this.theme.fg("accent", this.theme.bold(text)) : this.theme.fg("text", text);
39
- lines.push(truncateToWidth(`${pointer}${number}${label}`, width, ""));
40
+ const rendered = truncateToWidth(`${pointer}${number}${label}`, width, "");
41
+ lines.push(`${rendered}${active ? OVERLAY_ACTIVE_ROW_MARKER : ""}`);
40
42
  }
41
43
  return lines;
42
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"submit-picker.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/submit-picker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAIzD,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC7C,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAYrC;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IAKxB,YAAY,KAAY;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC/D,CAAC;IAED,QAAQ,CAAC,KAAwB;QAChC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,WAAW,CAAC,KAAa,IAAS,CAAC;IAEnC,UAAU,KAAU,CAAC;IAErB,aAAa,CAAC,MAAc;QAC3B,OAAO,CAAC,CAAC;IACV,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC;YACnD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC;YAC3D,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,gBAAgB,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACpG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;CACD","sourcesContent":["import { truncateToWidth } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../../../../modes/interactive/theme/theme.ts\";\nimport type { StatefulView } from \"../stateful-view.ts\";\n\nconst ACTIVE_POINTER = \"❯ \";\nconst INACTIVE_POINTER = \" \";\nconst NUMBER_SEPARATOR = \". \";\n\nexport const SUBMIT_LABEL = \"Submit answers\";\nexport const CANCEL_LABEL = \"Cancel\";\n\n/**\n * Per-tick projection of SubmitPicker state. The picker is a fixed 2-row\n * structure (Submit / Cancel) — labels are static, only the active marker\n * varies per tick. `selectSubmitPickerProps` precomputes `active` per row.\n */\nexport interface SubmitPickerProps {\n\t/** Per-row active flag. Length always 2: row 0 = Submit, row 1 = Cancel. */\n\trows: ReadonlyArray<{ active: boolean }>;\n}\n\n/**\n * Static 2-row picker rendered on the Submit Tab. Row 0 = \"Submit answers\", Row 1 = \"Cancel\".\n *\n * - Active pointer (❯) follows `props.rows[i].active` per tick.\n * - Both rows render in normal style at all times — D1 (revised) allows partial submission,\n * so Submit is never dimmed or visually marked as unselectable. The warning header in\n * `buildSubmitContainer` is the sole signal of incompleteness.\n * - `naturalHeight(width)` is state-INDEPENDENT and returns a constant 2, so the\n * chrome-mirror layout in `buildSubmitContainer` can subtract a fixed 2 lines without\n * re-rendering.\n */\nexport class SubmitPicker implements StatefulView<SubmitPickerProps> {\n\tprivate props: SubmitPickerProps;\n\n\tprivate declare readonly theme: Theme;\n\n\tconstructor(theme: Theme) {\n\t\tthis.theme = theme;\n\t\tthis.props = { rows: [{ active: false }, { active: false }] };\n\t}\n\n\tsetProps(props: SubmitPickerProps): void {\n\t\tthis.props = props;\n\t}\n\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\tnaturalHeight(_width: number): number {\n\t\treturn 2;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (let i = 0; i < 2; i++) {\n\t\t\tconst text = i === 0 ? SUBMIT_LABEL : CANCEL_LABEL;\n\t\t\tconst active = this.props.rows[i]?.active ?? false;\n\t\t\tconst pointer = active ? ACTIVE_POINTER : INACTIVE_POINTER;\n\t\t\tconst number = `${i + 1}${NUMBER_SEPARATOR}`;\n\t\t\tconst label = active ? this.theme.fg(\"accent\", this.theme.bold(text)) : this.theme.fg(\"text\", text);\n\t\t\tlines.push(truncateToWidth(`${pointer}${number}${label}`, width, \"\"));\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
1
+ {"version":3,"file":"submit-picker.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/submit-picker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AAG/E,MAAM,cAAc,GAAG,IAAI,CAAC;AAC5B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,MAAM,CAAC,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC7C,MAAM,CAAC,MAAM,YAAY,GAAG,QAAQ,CAAC;AAYrC;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IAKxB,YAAY,KAAY;QACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;IAC/D,CAAC;IAED,QAAQ,CAAC,KAAwB;QAChC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,WAAW,CAAC,KAAa,IAAS,CAAC;IAEnC,UAAU,KAAU,CAAC;IAErB,aAAa,CAAC,MAAc;QAC3B,OAAO,CAAC,CAAC;IACV,CAAC;IAED,MAAM,CAAC,KAAa;QACnB,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC;YACnD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,IAAI,KAAK,CAAC;YACnD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,gBAAgB,CAAC;YAC3D,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,gBAAgB,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACpG,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YAC3E,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,yBAAyB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC;CACD","sourcesContent":["import { truncateToWidth } from \"@earendil-works/pi-tui\";\nimport type { Theme } from \"../../../../../modes/interactive/theme/theme.ts\";\nimport { OVERLAY_ACTIVE_ROW_MARKER } from \"../../../../extensions/ui-types.ts\";\nimport type { StatefulView } from \"../stateful-view.ts\";\n\nconst ACTIVE_POINTER = \"❯ \";\nconst INACTIVE_POINTER = \" \";\nconst NUMBER_SEPARATOR = \". \";\n\nexport const SUBMIT_LABEL = \"Submit answers\";\nexport const CANCEL_LABEL = \"Cancel\";\n\n/**\n * Per-tick projection of SubmitPicker state. The picker is a fixed 2-row\n * structure (Submit / Cancel) — labels are static, only the active marker\n * varies per tick. `selectSubmitPickerProps` precomputes `active` per row.\n */\nexport interface SubmitPickerProps {\n\t/** Per-row active flag. Length always 2: row 0 = Submit, row 1 = Cancel. */\n\trows: ReadonlyArray<{ active: boolean }>;\n}\n\n/**\n * Static 2-row picker rendered on the Submit Tab. Row 0 = \"Submit answers\", Row 1 = \"Cancel\".\n *\n * - Active pointer (❯) follows `props.rows[i].active` per tick.\n * - Both rows render in normal style at all times — D1 (revised) allows partial submission,\n * so Submit is never dimmed or visually marked as unselectable. The warning header in\n * `buildSubmitContainer` is the sole signal of incompleteness.\n * - `naturalHeight(width)` is state-INDEPENDENT and returns a constant 2, so the\n * chrome-mirror layout in `buildSubmitContainer` can subtract a fixed 2 lines without\n * re-rendering.\n */\nexport class SubmitPicker implements StatefulView<SubmitPickerProps> {\n\tprivate props: SubmitPickerProps;\n\n\tprivate declare readonly theme: Theme;\n\n\tconstructor(theme: Theme) {\n\t\tthis.theme = theme;\n\t\tthis.props = { rows: [{ active: false }, { active: false }] };\n\t}\n\n\tsetProps(props: SubmitPickerProps): void {\n\t\tthis.props = props;\n\t}\n\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\tnaturalHeight(_width: number): number {\n\t\treturn 2;\n\t}\n\n\trender(width: number): string[] {\n\t\tconst lines: string[] = [];\n\t\tfor (let i = 0; i < 2; i++) {\n\t\t\tconst text = i === 0 ? SUBMIT_LABEL : CANCEL_LABEL;\n\t\t\tconst active = this.props.rows[i]?.active ?? false;\n\t\t\tconst pointer = active ? ACTIVE_POINTER : INACTIVE_POINTER;\n\t\t\tconst number = `${i + 1}${NUMBER_SEPARATOR}`;\n\t\t\tconst label = active ? this.theme.fg(\"accent\", this.theme.bold(text)) : this.theme.fg(\"text\", text);\n\t\t\tconst rendered = truncateToWidth(`${pointer}${number}${label}`, width, \"\");\n\t\t\tlines.push(`${rendered}${active ? OVERLAY_ACTIVE_ROW_MARKER : \"\"}`);\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"wrapping-select.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/wrapping-select.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wrapping-select.d.ts","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/wrapping-select.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
+ import { OVERLAY_ACTIVE_ROW_MARKER } from "../../../../extensions/ui-types.js";
2
3
  import { ROW_INTENT_META } from "../../state/row-intent.js";
3
4
  export class WrappingSelect {
4
5
  static { this.ACTIVE_POINTER = "❯ "; }
@@ -84,7 +85,13 @@ export class WrappingSelect {
84
85
  if (!item)
85
86
  continue;
86
87
  const isActive = i === this.selectedIndex && this.focused;
87
- lines.push(...this.renderItem(item, i, isActive, width, numberWidth));
88
+ const itemLines = this.renderItem(item, i, isActive, width, numberWidth);
89
+ // Tell a bounding host which row it must not crop away (#2378). The mark
90
+ // is zero-width and the host strips it; see OVERLAY_ACTIVE_ROW_MARKER.
91
+ if (isActive && itemLines.length > 0) {
92
+ itemLines[0] = `${itemLines[0] ?? ""}${OVERLAY_ACTIVE_ROW_MARKER}`;
93
+ }
94
+ lines.push(...itemLines);
88
95
  }
89
96
  if (this.hasItemsOutsideWindow(startIndex, endIndex)) {
90
97
  lines.push(this.theme.scrollInfo(` (${this.selectedIndex + 1}/${this.items.length})`));
@@ -1 +1 @@
1
- {"version":3,"file":"wrapping-select.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/wrapping-select.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"@earendil-works/pi-tui\";\nimport { visibleWidth, wrapTextWithAnsi } from \"@earendil-works/pi-tui\";\nimport { ROW_INTENT_META } from \"../../state/row-intent.ts\";\n\n/**\n * Row-intent discriminated union. `kind` is the single discriminator —\n * pre-1.0.3 boolean flags have been removed (see `banned-flags.test.ts`).\n * Modeled after `QuestionnaireAction` (`key-router.ts:13-32`) and `Effect`\n * (`state-reducer.ts:26-32`) — pure literal-tagged variants, no shared base,\n * exhaustive-`switch` enforcement via non-`void` returns.\n *\n * Variant semantics:\n * - `option`: a regular author-defined option row.\n * - `other`: the inline free-text input row appended to single-select questions\n * (label is \"Type something.\"). Renders as inline `Input` when active.\n * - `chat`: the abandon-questionnaire escape-hatch row (label is \"Chat about this\").\n * Renders as inline `Input` when active.\n * - `next`: the explicit commit-and-advance row appended to multi-select questions\n * (label is \"Next\"). Renders without a number / checkbox.\n */\nexport type WrappingSelectItem =\n\t| { kind: \"option\"; label: string; description?: string }\n\t| { kind: \"other\"; label: string; description?: string }\n\t| { kind: \"chat\"; label: string; description?: string }\n\t| { kind: \"next\"; label: string; description?: string };\n\nexport interface WrappingSelectTheme {\n\tselectedText: (text: string) => string;\n\tdescription: (text: string) => string;\n\tscrollInfo: (text: string) => string;\n}\n\n/**\n * Numbering controls.\n *\n * Use `numberStartOffset` + `totalItemsForNumbering` when a list is logically a slice of a\n * larger numbered sequence — e.g. the chat row lives in its own WrappingSelect but should\n * render as `(N+1).` where N is the previous list's item count, with the column padded as\n * if both lists were one continuous numbered sequence.\n */\nexport interface WrappingSelectOptions {\n\t/** Start numbering at this offset + 1 (default 0 → rows labeled 1, 2, 3 …). */\n\tnumberStartOffset?: number;\n\t/** Override the total used to pad the number column (useful when items span multiple lists). */\n\ttotalItemsForNumbering?: number;\n}\n\nexport class WrappingSelect implements Component {\n\tprivate static readonly ACTIVE_POINTER = \"❯ \";\n\tprivate static readonly INACTIVE_POINTER = \" \";\n\tprivate static readonly NUMBER_SEPARATOR = \". \";\n\tprivate static readonly CURSOR_INVERSE_START = \"\\x1b[7m\";\n\tprivate static readonly CURSOR_INVERSE_END = \"\\x1b[0m\";\n\tprivate static readonly CONFIRMED_MARK = \" ✔\";\n\tprivate static readonly MIN_CONTENT_WIDTH = 1;\n\n\tprivate readonly items: readonly WrappingSelectItem[];\n\tprivate readonly maxVisible: number;\n\tprivate readonly theme: WrappingSelectTheme;\n\tprivate numberStartOffset: number;\n\tprivate totalItemsForNumbering: number;\n\n\tprivate selectedIndex = 0;\n\tprivate focused = true;\n\tprivate inputBuffer = \"\";\n\tprivate inputCursor: number | undefined = undefined;\n\t/**\n\t * Index of the row that was previously confirmed for this list (e.g. the user's prior\n\t * answer when re-entering a multi-question tab). Renders `<label> ✔` in the active-row\n\t * styling but WITHOUT the `❯` pointer — pointer is reserved for the live cursor. When\n\t * `selectedIndex === confirmedIndex && focused`, the active rendering wins (no double-mark).\n\t */\n\tprivate confirmedIndex: number | undefined = undefined;\n\t/**\n\t * When set together with `confirmedIndex`, replaces the row's static label at render time.\n\t * Used for the `kind: \"other\"` sentinel — its label is \"Type something.\" but if the user's\n\t * prior answer was custom text, we render that text instead (e.g. `4. Hello ✔`).\n\t */\n\tprivate confirmedLabelOverride: string | undefined = undefined;\n\n\tconstructor(\n\t\titems: readonly WrappingSelectItem[],\n\t\tmaxVisible: number,\n\t\ttheme: WrappingSelectTheme,\n\t\toptions: WrappingSelectOptions = {},\n\t) {\n\t\tthis.items = items;\n\t\tthis.maxVisible = Math.max(1, maxVisible);\n\t\tthis.theme = theme;\n\t\tthis.numberStartOffset = options.numberStartOffset ?? 0;\n\t\tthis.totalItemsForNumbering = options.totalItemsForNumbering ?? items.length;\n\t}\n\n\t/**\n\t * Update the numbering offset + total padding width without rebuilding the component.\n\t * Used by the host to keep the chat-row WrappingSelect's number aligned with the active tab's\n\t * options list when the user switches tabs (each tab can have a different items count).\n\t */\n\tsetNumbering(numberStartOffset: number, totalItemsForNumbering: number): void {\n\t\tthis.numberStartOffset = numberStartOffset;\n\t\tthis.totalItemsForNumbering = Math.max(1, totalItemsForNumbering);\n\t}\n\n\tsetSelectedIndex(index: number): void {\n\t\tthis.selectedIndex = Math.max(0, Math.min(index, this.items.length - 1));\n\t}\n\n\tsetFocused(focused: boolean): void {\n\t\tthis.focused = focused;\n\t}\n\n\t/**\n\t * Mark a previously-confirmed row. Pass `undefined` to clear. `labelOverride` replaces\n\t * the row's static `item.label` at render time — used for the `kind: \"other\"` sentinel so\n\t * the row reads `Hello ✔` instead of `Type something. ✔` when the prior answer was custom\n\t * text.\n\t */\n\tsetConfirmedIndex(index: number | undefined, labelOverride?: string): void {\n\t\tif (index === undefined) {\n\t\t\tthis.confirmedIndex = undefined;\n\t\t\tthis.confirmedLabelOverride = undefined;\n\t\t\treturn;\n\t\t}\n\t\tthis.confirmedIndex = Math.max(0, Math.min(index, this.items.length - 1));\n\t\tthis.confirmedLabelOverride = labelOverride;\n\t}\n\n\tsetInputBuffer(text: string): void {\n\t\tthis.inputBuffer = text;\n\t\tif (this.inputCursor !== undefined) this.inputCursor = this.clampInputCursor(this.inputCursor);\n\t}\n\n\tsetInputCursor(cursor: number): void {\n\t\tthis.inputCursor = this.clampInputCursor(cursor);\n\t}\n\n\t/** Intentionally empty — input is routed at the container level. */\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tif (this.items.length === 0) return [];\n\n\t\tconst { startIndex, endIndex } = this.computeVisibleWindow();\n\t\tconst numberWidth = String(Math.max(1, this.totalItemsForNumbering)).length;\n\t\tconst lines: string[] = [];\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.items[i];\n\t\t\tif (!item) continue;\n\t\t\tconst isActive = i === this.selectedIndex && this.focused;\n\t\t\tlines.push(...this.renderItem(item, i, isActive, width, numberWidth));\n\t\t}\n\n\t\tif (this.hasItemsOutsideWindow(startIndex, endIndex)) {\n\t\t\tlines.push(this.theme.scrollInfo(` (${this.selectedIndex + 1}/${this.items.length})`));\n\t\t}\n\t\treturn lines;\n\t}\n\n\tprivate computeVisibleWindow(): { startIndex: number; endIndex: number } {\n\t\tconst half = Math.floor(this.maxVisible / 2);\n\t\tconst startIndex = Math.max(0, Math.min(this.selectedIndex - half, this.items.length - this.maxVisible));\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.items.length);\n\t\treturn { startIndex, endIndex };\n\t}\n\n\tprivate hasItemsOutsideWindow(startIndex: number, endIndex: number): boolean {\n\t\treturn startIndex > 0 || endIndex < this.items.length;\n\t}\n\n\tprivate renderItem(\n\t\titem: WrappingSelectItem,\n\t\tindex: number,\n\t\tisActive: boolean,\n\t\twidth: number,\n\t\tnumberWidth: number,\n\t): string[] {\n\t\tconst rowPrefix = this.buildRowPrefix(index, isActive, numberWidth);\n\t\tconst continuationPrefix = \" \".repeat(visibleWidth(rowPrefix));\n\t\tconst contentWidth = Math.max(WrappingSelect.MIN_CONTENT_WIDTH, width - visibleWidth(rowPrefix));\n\n\t\tif (this.shouldRenderAsInlineInput(item, isActive)) {\n\t\t\treturn this.renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth);\n\t\t}\n\n\t\t// Confirmed row gets a trailing ` ✔` and accent+bold styling; pointer is independent\n\t\t// (still ❯ when active). When `index === confirmedIndex` AND `isActive`, both `❯` and\n\t\t// `✔` appear on the same row — load-bearing for the case where the prior answer was\n\t\t// row 0 (cursor resets to 0 on tab-back, so the confirmed row IS the active row).\n\t\t// Optional `confirmedLabelOverride` replaces the static label (used for `kind: \"other\"`\n\t\t// + `kind: \"custom\"` answer); the inline-input branch above still wins for `kind: \"other\" + isActive`.\n\t\tconst isConfirmed = index === this.confirmedIndex;\n\t\tconst label = isConfirmed\n\t\t\t? `${this.confirmedLabelOverride ?? item.label}${WrappingSelect.CONFIRMED_MARK}`\n\t\t\t: item.label;\n\t\tconst applySelectedStyle = isActive || isConfirmed;\n\n\t\treturn [\n\t\t\t...this.renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle),\n\t\t\t...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth),\n\t\t];\n\t}\n\n\tprivate buildRowPrefix(index: number, isActive: boolean, numberWidth: number): string {\n\t\tconst pointer = isActive ? WrappingSelect.ACTIVE_POINTER : WrappingSelect.INACTIVE_POINTER;\n\t\tconst displayNumber = this.numberStartOffset + index + 1;\n\t\tconst paddedNumber = String(displayNumber).padStart(numberWidth, \" \");\n\t\treturn `${pointer}${paddedNumber}${WrappingSelect.NUMBER_SEPARATOR}`;\n\t}\n\n\tprivate shouldRenderAsInlineInput(item: WrappingSelectItem, isActive: boolean): boolean {\n\t\treturn isActive && ROW_INTENT_META[item.kind].activatesInputMode;\n\t}\n\n\t/**\n\t * Render the inline input row across one or more lines, wrapping at `contentWidth`\n\t * so long input doesn't run off the right edge or trip the parent renderer's\n\t * width invariant. Mirrors `renderLabelBlock`'s contract: first line carries\n\t * `rowPrefix`, continuation lines carry `continuationPrefix` (spaces), and every\n\t * emitted line passes through `theme.selectedText`. The cursor mirrors the\n\t * main chat editor: inverse-video over the character at the caret, or an\n\t * inverse-video space at end-of-line.\n\t */\n\tprivate renderInlineInputRow(rowPrefix: string, continuationPrefix: string, contentWidth: number): string[] {\n\t\tconst raw = this.inputTextWithCursor();\n\t\tconst wrapped = wrapTextWithAnsi(raw, contentWidth);\n\t\treturn wrapped.map((segment, index) => {\n\t\t\tconst prefix = index === 0 ? rowPrefix : continuationPrefix;\n\t\t\treturn this.theme.selectedText(`${prefix}${segment}`);\n\t\t});\n\t}\n\n\tprivate inputTextWithCursor(): string {\n\t\tconst cursor = this.clampInputCursor(this.inputCursor ?? this.inputBuffer.length);\n\t\tconst before = this.inputBuffer.slice(0, cursor);\n\t\tconst after = this.inputBuffer.slice(cursor);\n\t\tconst [at = \"\"] = Array.from(after);\n\t\tif (at === \"\") {\n\t\t\treturn `${before}${WrappingSelect.CURSOR_INVERSE_START} ${WrappingSelect.CURSOR_INVERSE_END}`;\n\t\t}\n\t\tconst rest = after.slice(at.length);\n\t\treturn `${before}${WrappingSelect.CURSOR_INVERSE_START}${at}${WrappingSelect.CURSOR_INVERSE_END}${rest}`;\n\t}\n\n\tprivate clampInputCursor(cursor: number): number {\n\t\tif (!Number.isFinite(cursor)) return this.inputBuffer.length;\n\t\treturn Math.max(0, Math.min(cursor, this.inputBuffer.length));\n\t}\n\n\tprivate renderLabelBlock(\n\t\tlabel: string,\n\t\trowPrefix: string,\n\t\tcontinuationPrefix: string,\n\t\tcontentWidth: number,\n\t\tapplySelectedStyle: boolean,\n\t): string[] {\n\t\tconst wrapped = wrapTextWithAnsi(label, contentWidth);\n\t\treturn wrapped.map((segment, index) => {\n\t\t\tconst prefix = index === 0 ? rowPrefix : continuationPrefix;\n\t\t\tconst line = `${prefix}${segment}`;\n\t\t\treturn applySelectedStyle ? this.theme.selectedText(line) : line;\n\t\t});\n\t}\n\n\tprivate renderDescriptionBlock(\n\t\tdescription: string | undefined,\n\t\tcontinuationPrefix: string,\n\t\tcontentWidth: number,\n\t): string[] {\n\t\tif (!description) return [];\n\t\tconst wrapped = wrapTextWithAnsi(description, contentWidth);\n\t\treturn wrapped.map((segment) => `${continuationPrefix}${this.theme.description(segment)}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"wrapping-select.js","sourceRoot":"","sources":["../../../../../../src/core/tools/ask-user-question/view/components/wrapping-select.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"@earendil-works/pi-tui\";\nimport { visibleWidth, wrapTextWithAnsi } from \"@earendil-works/pi-tui\";\nimport { OVERLAY_ACTIVE_ROW_MARKER } from \"../../../../extensions/ui-types.ts\";\nimport { ROW_INTENT_META } from \"../../state/row-intent.ts\";\n\n/**\n * Row-intent discriminated union. `kind` is the single discriminator —\n * pre-1.0.3 boolean flags have been removed (see `banned-flags.test.ts`).\n * Modeled after `QuestionnaireAction` (`key-router.ts:13-32`) and `Effect`\n * (`state-reducer.ts:26-32`) — pure literal-tagged variants, no shared base,\n * exhaustive-`switch` enforcement via non-`void` returns.\n *\n * Variant semantics:\n * - `option`: a regular author-defined option row.\n * - `other`: the inline free-text input row appended to single-select questions\n * (label is \"Type something.\"). Renders as inline `Input` when active.\n * - `chat`: the abandon-questionnaire escape-hatch row (label is \"Chat about this\").\n * Renders as inline `Input` when active.\n * - `next`: the explicit commit-and-advance row appended to multi-select questions\n * (label is \"Next\"). Renders without a number / checkbox.\n */\nexport type WrappingSelectItem =\n\t| { kind: \"option\"; label: string; description?: string }\n\t| { kind: \"other\"; label: string; description?: string }\n\t| { kind: \"chat\"; label: string; description?: string }\n\t| { kind: \"next\"; label: string; description?: string };\n\nexport interface WrappingSelectTheme {\n\tselectedText: (text: string) => string;\n\tdescription: (text: string) => string;\n\tscrollInfo: (text: string) => string;\n}\n\n/**\n * Numbering controls.\n *\n * Use `numberStartOffset` + `totalItemsForNumbering` when a list is logically a slice of a\n * larger numbered sequence — e.g. the chat row lives in its own WrappingSelect but should\n * render as `(N+1).` where N is the previous list's item count, with the column padded as\n * if both lists were one continuous numbered sequence.\n */\nexport interface WrappingSelectOptions {\n\t/** Start numbering at this offset + 1 (default 0 → rows labeled 1, 2, 3 …). */\n\tnumberStartOffset?: number;\n\t/** Override the total used to pad the number column (useful when items span multiple lists). */\n\ttotalItemsForNumbering?: number;\n}\n\nexport class WrappingSelect implements Component {\n\tprivate static readonly ACTIVE_POINTER = \"❯ \";\n\tprivate static readonly INACTIVE_POINTER = \" \";\n\tprivate static readonly NUMBER_SEPARATOR = \". \";\n\tprivate static readonly CURSOR_INVERSE_START = \"\\x1b[7m\";\n\tprivate static readonly CURSOR_INVERSE_END = \"\\x1b[0m\";\n\tprivate static readonly CONFIRMED_MARK = \" ✔\";\n\tprivate static readonly MIN_CONTENT_WIDTH = 1;\n\n\tprivate readonly items: readonly WrappingSelectItem[];\n\tprivate readonly maxVisible: number;\n\tprivate readonly theme: WrappingSelectTheme;\n\tprivate numberStartOffset: number;\n\tprivate totalItemsForNumbering: number;\n\n\tprivate selectedIndex = 0;\n\tprivate focused = true;\n\tprivate inputBuffer = \"\";\n\tprivate inputCursor: number | undefined = undefined;\n\t/**\n\t * Index of the row that was previously confirmed for this list (e.g. the user's prior\n\t * answer when re-entering a multi-question tab). Renders `<label> ✔` in the active-row\n\t * styling but WITHOUT the `❯` pointer — pointer is reserved for the live cursor. When\n\t * `selectedIndex === confirmedIndex && focused`, the active rendering wins (no double-mark).\n\t */\n\tprivate confirmedIndex: number | undefined = undefined;\n\t/**\n\t * When set together with `confirmedIndex`, replaces the row's static label at render time.\n\t * Used for the `kind: \"other\"` sentinel — its label is \"Type something.\" but if the user's\n\t * prior answer was custom text, we render that text instead (e.g. `4. Hello ✔`).\n\t */\n\tprivate confirmedLabelOverride: string | undefined = undefined;\n\n\tconstructor(\n\t\titems: readonly WrappingSelectItem[],\n\t\tmaxVisible: number,\n\t\ttheme: WrappingSelectTheme,\n\t\toptions: WrappingSelectOptions = {},\n\t) {\n\t\tthis.items = items;\n\t\tthis.maxVisible = Math.max(1, maxVisible);\n\t\tthis.theme = theme;\n\t\tthis.numberStartOffset = options.numberStartOffset ?? 0;\n\t\tthis.totalItemsForNumbering = options.totalItemsForNumbering ?? items.length;\n\t}\n\n\t/**\n\t * Update the numbering offset + total padding width without rebuilding the component.\n\t * Used by the host to keep the chat-row WrappingSelect's number aligned with the active tab's\n\t * options list when the user switches tabs (each tab can have a different items count).\n\t */\n\tsetNumbering(numberStartOffset: number, totalItemsForNumbering: number): void {\n\t\tthis.numberStartOffset = numberStartOffset;\n\t\tthis.totalItemsForNumbering = Math.max(1, totalItemsForNumbering);\n\t}\n\n\tsetSelectedIndex(index: number): void {\n\t\tthis.selectedIndex = Math.max(0, Math.min(index, this.items.length - 1));\n\t}\n\n\tsetFocused(focused: boolean): void {\n\t\tthis.focused = focused;\n\t}\n\n\t/**\n\t * Mark a previously-confirmed row. Pass `undefined` to clear. `labelOverride` replaces\n\t * the row's static `item.label` at render time — used for the `kind: \"other\"` sentinel so\n\t * the row reads `Hello ✔` instead of `Type something. ✔` when the prior answer was custom\n\t * text.\n\t */\n\tsetConfirmedIndex(index: number | undefined, labelOverride?: string): void {\n\t\tif (index === undefined) {\n\t\t\tthis.confirmedIndex = undefined;\n\t\t\tthis.confirmedLabelOverride = undefined;\n\t\t\treturn;\n\t\t}\n\t\tthis.confirmedIndex = Math.max(0, Math.min(index, this.items.length - 1));\n\t\tthis.confirmedLabelOverride = labelOverride;\n\t}\n\n\tsetInputBuffer(text: string): void {\n\t\tthis.inputBuffer = text;\n\t\tif (this.inputCursor !== undefined) this.inputCursor = this.clampInputCursor(this.inputCursor);\n\t}\n\n\tsetInputCursor(cursor: number): void {\n\t\tthis.inputCursor = this.clampInputCursor(cursor);\n\t}\n\n\t/** Intentionally empty — input is routed at the container level. */\n\thandleInput(_data: string): void {}\n\n\tinvalidate(): void {}\n\n\trender(width: number): string[] {\n\t\tif (this.items.length === 0) return [];\n\n\t\tconst { startIndex, endIndex } = this.computeVisibleWindow();\n\t\tconst numberWidth = String(Math.max(1, this.totalItemsForNumbering)).length;\n\t\tconst lines: string[] = [];\n\n\t\tfor (let i = startIndex; i < endIndex; i++) {\n\t\t\tconst item = this.items[i];\n\t\t\tif (!item) continue;\n\t\t\tconst isActive = i === this.selectedIndex && this.focused;\n\t\t\tconst itemLines = this.renderItem(item, i, isActive, width, numberWidth);\n\t\t\t// Tell a bounding host which row it must not crop away (#2378). The mark\n\t\t\t// is zero-width and the host strips it; see OVERLAY_ACTIVE_ROW_MARKER.\n\t\t\tif (isActive && itemLines.length > 0) {\n\t\t\t\titemLines[0] = `${itemLines[0] ?? \"\"}${OVERLAY_ACTIVE_ROW_MARKER}`;\n\t\t\t}\n\t\t\tlines.push(...itemLines);\n\t\t}\n\n\t\tif (this.hasItemsOutsideWindow(startIndex, endIndex)) {\n\t\t\tlines.push(this.theme.scrollInfo(` (${this.selectedIndex + 1}/${this.items.length})`));\n\t\t}\n\t\treturn lines;\n\t}\n\n\tprivate computeVisibleWindow(): { startIndex: number; endIndex: number } {\n\t\tconst half = Math.floor(this.maxVisible / 2);\n\t\tconst startIndex = Math.max(0, Math.min(this.selectedIndex - half, this.items.length - this.maxVisible));\n\t\tconst endIndex = Math.min(startIndex + this.maxVisible, this.items.length);\n\t\treturn { startIndex, endIndex };\n\t}\n\n\tprivate hasItemsOutsideWindow(startIndex: number, endIndex: number): boolean {\n\t\treturn startIndex > 0 || endIndex < this.items.length;\n\t}\n\n\tprivate renderItem(\n\t\titem: WrappingSelectItem,\n\t\tindex: number,\n\t\tisActive: boolean,\n\t\twidth: number,\n\t\tnumberWidth: number,\n\t): string[] {\n\t\tconst rowPrefix = this.buildRowPrefix(index, isActive, numberWidth);\n\t\tconst continuationPrefix = \" \".repeat(visibleWidth(rowPrefix));\n\t\tconst contentWidth = Math.max(WrappingSelect.MIN_CONTENT_WIDTH, width - visibleWidth(rowPrefix));\n\n\t\tif (this.shouldRenderAsInlineInput(item, isActive)) {\n\t\t\treturn this.renderInlineInputRow(rowPrefix, continuationPrefix, contentWidth);\n\t\t}\n\n\t\t// Confirmed row gets a trailing ` ✔` and accent+bold styling; pointer is independent\n\t\t// (still ❯ when active). When `index === confirmedIndex` AND `isActive`, both `❯` and\n\t\t// `✔` appear on the same row — load-bearing for the case where the prior answer was\n\t\t// row 0 (cursor resets to 0 on tab-back, so the confirmed row IS the active row).\n\t\t// Optional `confirmedLabelOverride` replaces the static label (used for `kind: \"other\"`\n\t\t// + `kind: \"custom\"` answer); the inline-input branch above still wins for `kind: \"other\" + isActive`.\n\t\tconst isConfirmed = index === this.confirmedIndex;\n\t\tconst label = isConfirmed\n\t\t\t? `${this.confirmedLabelOverride ?? item.label}${WrappingSelect.CONFIRMED_MARK}`\n\t\t\t: item.label;\n\t\tconst applySelectedStyle = isActive || isConfirmed;\n\n\t\treturn [\n\t\t\t...this.renderLabelBlock(label, rowPrefix, continuationPrefix, contentWidth, applySelectedStyle),\n\t\t\t...this.renderDescriptionBlock(item.description, continuationPrefix, contentWidth),\n\t\t];\n\t}\n\n\tprivate buildRowPrefix(index: number, isActive: boolean, numberWidth: number): string {\n\t\tconst pointer = isActive ? WrappingSelect.ACTIVE_POINTER : WrappingSelect.INACTIVE_POINTER;\n\t\tconst displayNumber = this.numberStartOffset + index + 1;\n\t\tconst paddedNumber = String(displayNumber).padStart(numberWidth, \" \");\n\t\treturn `${pointer}${paddedNumber}${WrappingSelect.NUMBER_SEPARATOR}`;\n\t}\n\n\tprivate shouldRenderAsInlineInput(item: WrappingSelectItem, isActive: boolean): boolean {\n\t\treturn isActive && ROW_INTENT_META[item.kind].activatesInputMode;\n\t}\n\n\t/**\n\t * Render the inline input row across one or more lines, wrapping at `contentWidth`\n\t * so long input doesn't run off the right edge or trip the parent renderer's\n\t * width invariant. Mirrors `renderLabelBlock`'s contract: first line carries\n\t * `rowPrefix`, continuation lines carry `continuationPrefix` (spaces), and every\n\t * emitted line passes through `theme.selectedText`. The cursor mirrors the\n\t * main chat editor: inverse-video over the character at the caret, or an\n\t * inverse-video space at end-of-line.\n\t */\n\tprivate renderInlineInputRow(rowPrefix: string, continuationPrefix: string, contentWidth: number): string[] {\n\t\tconst raw = this.inputTextWithCursor();\n\t\tconst wrapped = wrapTextWithAnsi(raw, contentWidth);\n\t\treturn wrapped.map((segment, index) => {\n\t\t\tconst prefix = index === 0 ? rowPrefix : continuationPrefix;\n\t\t\treturn this.theme.selectedText(`${prefix}${segment}`);\n\t\t});\n\t}\n\n\tprivate inputTextWithCursor(): string {\n\t\tconst cursor = this.clampInputCursor(this.inputCursor ?? this.inputBuffer.length);\n\t\tconst before = this.inputBuffer.slice(0, cursor);\n\t\tconst after = this.inputBuffer.slice(cursor);\n\t\tconst [at = \"\"] = Array.from(after);\n\t\tif (at === \"\") {\n\t\t\treturn `${before}${WrappingSelect.CURSOR_INVERSE_START} ${WrappingSelect.CURSOR_INVERSE_END}`;\n\t\t}\n\t\tconst rest = after.slice(at.length);\n\t\treturn `${before}${WrappingSelect.CURSOR_INVERSE_START}${at}${WrappingSelect.CURSOR_INVERSE_END}${rest}`;\n\t}\n\n\tprivate clampInputCursor(cursor: number): number {\n\t\tif (!Number.isFinite(cursor)) return this.inputBuffer.length;\n\t\treturn Math.max(0, Math.min(cursor, this.inputBuffer.length));\n\t}\n\n\tprivate renderLabelBlock(\n\t\tlabel: string,\n\t\trowPrefix: string,\n\t\tcontinuationPrefix: string,\n\t\tcontentWidth: number,\n\t\tapplySelectedStyle: boolean,\n\t): string[] {\n\t\tconst wrapped = wrapTextWithAnsi(label, contentWidth);\n\t\treturn wrapped.map((segment, index) => {\n\t\t\tconst prefix = index === 0 ? rowPrefix : continuationPrefix;\n\t\t\tconst line = `${prefix}${segment}`;\n\t\t\treturn applySelectedStyle ? this.theme.selectedText(line) : line;\n\t\t});\n\t}\n\n\tprivate renderDescriptionBlock(\n\t\tdescription: string | undefined,\n\t\tcontinuationPrefix: string,\n\t\tcontentWidth: number,\n\t): string[] {\n\t\tif (!description) return [];\n\t\tconst wrapped = wrapTextWithAnsi(description, contentWidth);\n\t\treturn wrapped.map((segment) => `${continuationPrefix}${this.theme.description(segment)}`);\n\t}\n}\n"]}
@@ -1,3 +1,3 @@
1
1
  export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AtomicProviderCompat, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ConstrainedSamplingConfig, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionCustomComponent, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionScopedModels, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, HostInputFormField, HostInputFormFieldType, HostInputFormRequest, HostSessionPickerHandle, HostSessionPickerRequest, HostSessionPickerRow, InlineExtension, InputEvent, InputEventResult, InputSource, InstallReactiveWidgetOptions, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MarkdownTransformContext, MarkdownTransformer, MessageRenderer, MessageRenderOptions, ModelSelectEvent, OrchestrationContext, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReactiveWidgetAction, ReactiveWidgetComponent, ReactiveWidgetController, ReactiveWidgetFactory, ReactiveWidgetRefreshReason, ReactiveWidgetRenderContext, ReactiveWidgetRenderState, ReactiveWidgetScheduler, ReactiveWidgetTimerApi, ReactiveWidgetTimerHandle, ReactiveWidgetUi, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, ScopedModel, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, SubagentChildPolicy, SubagentIntercomIdentity, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkflowStageOrchestrationContext, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
2
- export { createExtensionRuntime, decideReactiveWidgetAction, defineTool, discoverAndLoadExtensions, ExtensionRunner, installReactiveWidget, isBashToolResult, isEditToolResult, isFindToolResult, isLsToolResult, isReadToolResult, isStaleExtensionContextError, isToolCallEventType, isWriteToolResult, STALE_EXTENSION_CONTEXT_MARKER, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
2
+ export { createExtensionRuntime, decideReactiveWidgetAction, defineTool, discoverAndLoadExtensions, ExtensionRunner, installReactiveWidget, isBashToolResult, isEditToolResult, isFindToolResult, isLsToolResult, isReadToolResult, isStaleExtensionContextError, isToolCallEventType, isWriteToolResult, OVERLAY_ACTIVE_ROW_MARKER, STALE_EXTENSION_CONTEXT_MARKER, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
3
3
  //# sourceMappingURL=index-extensions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-extensions.d.ts","sourceRoot":"","sources":["../src/index-extensions.ts"],"names":[],"mappings":"AACA,YAAY,EACX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,EAChC,wBAAwB,EACxB,cAAc,EACd,yBAAyB,EACzB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,EACvB,8BAA8B,EAC9B,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,QAAQ,EACR,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,iCAAiC,EACjC,uBAAuB,EACvB,kBAAkB,GAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACN,sBAAsB,EACtB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,4BAA4B,CAAC"}
1
+ {"version":3,"file":"index-extensions.d.ts","sourceRoot":"","sources":["../src/index-extensions.ts"],"names":[],"mappings":"AACA,YAAY,EACX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,uBAAuB,EACvB,aAAa,EACb,oBAAoB,EACpB,2BAA2B,EAC3B,iBAAiB,EACjB,qBAAqB,EACrB,2BAA2B,EAC3B,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,EAChC,wBAAwB,EACxB,cAAc,EACd,yBAAyB,EACzB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,uBAAuB,EACvB,8BAA8B,EAC9B,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACxB,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,sBAAsB,EACtB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,oBAAoB,EACpB,eAAe,EACf,UAAU,EACV,gBAAgB,EAChB,WAAW,EACX,4BAA4B,EAC5B,kBAAkB,EAClB,oBAAoB,EACpB,eAAe,EACf,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,EACrB,2BAA2B,EAC3B,2BAA2B,EAC3B,yBAAyB,EACzB,uBAAuB,EACvB,sBAAsB,EACtB,yBAAyB,EACzB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,WAAW,EACX,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,sBAAsB,EACtB,mBAAmB,EACnB,uBAAuB,EACvB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACnB,wBAAwB,EACxB,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,iBAAiB,EACjB,uBAAuB,EACvB,QAAQ,EACR,uBAAuB,EACvB,eAAe,EACf,YAAY,EACZ,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,eAAe,EACf,iCAAiC,EACjC,uBAAuB,EACvB,kBAAkB,GAClB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACN,sBAAsB,EACtB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,8BAA8B,EAC9B,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,4BAA4B,CAAC"}
@@ -1,2 +1,2 @@
1
- export { createExtensionRuntime, decideReactiveWidgetAction, defineTool, discoverAndLoadExtensions, ExtensionRunner, installReactiveWidget, isBashToolResult, isEditToolResult, isFindToolResult, isLsToolResult, isReadToolResult, isStaleExtensionContextError, isToolCallEventType, isWriteToolResult, STALE_EXTENSION_CONTEXT_MARKER, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.js";
1
+ export { createExtensionRuntime, decideReactiveWidgetAction, defineTool, discoverAndLoadExtensions, ExtensionRunner, installReactiveWidget, isBashToolResult, isEditToolResult, isFindToolResult, isLsToolResult, isReadToolResult, isStaleExtensionContextError, isToolCallEventType, isWriteToolResult, OVERLAY_ACTIVE_ROW_MARKER, STALE_EXTENSION_CONTEXT_MARKER, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.js";
2
2
  //# sourceMappingURL=index-extensions.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-extensions.js","sourceRoot":"","sources":["../src/index-extensions.ts"],"names":[],"mappings":"AA2HA,OAAO,EACN,sBAAsB,EACtB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,8BAA8B,EAC9B,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,4BAA4B,CAAC","sourcesContent":["// Extension system\nexport type {\n\tAgentEndEvent,\n\tAgentSettledEvent,\n\tAgentStartEvent,\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tAppKeybinding,\n\tAtomicProviderCompat,\n\tAutocompleteProviderFactory,\n\tBashToolCallEvent,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tBeforeProviderHeadersEvent,\n\tBeforeProviderRequestEvent,\n\tBeforeProviderRequestEventResult,\n\tBuildSystemPromptOptions,\n\tCompactOptions,\n\tConstrainedSamplingConfig,\n\tContextEvent,\n\tContextUsage,\n\tCustomToolCallEvent,\n\tEditToolCallEvent,\n\tEntryRenderer,\n\tEntryRenderOptions,\n\tExecOptions,\n\tExecResult,\n\tExtension,\n\tExtensionActions,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionCommandContextActions,\n\tExtensionContext,\n\tExtensionContextActions,\n\tExtensionCustomComponent,\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionHandler,\n\tExtensionRuntime,\n\tExtensionScopedModels,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n\tFindToolCallEvent,\n\tHostInputFormField,\n\tHostInputFormFieldType,\n\tHostInputFormRequest,\n\tHostSessionPickerHandle,\n\tHostSessionPickerRequest,\n\tHostSessionPickerRow,\n\tInlineExtension,\n\tInputEvent,\n\tInputEventResult,\n\tInputSource,\n\tInstallReactiveWidgetOptions,\n\tKeybindingsManager,\n\tLoadExtensionsResult,\n\tLsToolCallEvent,\n\tMarkdownTransformContext,\n\tMarkdownTransformer,\n\tMessageRenderer,\n\tMessageRenderOptions,\n\tModelSelectEvent,\n\tOrchestrationContext,\n\tProjectTrustContext,\n\tProjectTrustEvent,\n\tProjectTrustEventDecision,\n\tProjectTrustEventResult,\n\tProjectTrustHandler,\n\tProviderConfig,\n\tProviderModelConfig,\n\tReactiveWidgetAction,\n\tReactiveWidgetComponent,\n\tReactiveWidgetController,\n\tReactiveWidgetFactory,\n\tReactiveWidgetRefreshReason,\n\tReactiveWidgetRenderContext,\n\tReactiveWidgetRenderState,\n\tReactiveWidgetScheduler,\n\tReactiveWidgetTimerApi,\n\tReactiveWidgetTimerHandle,\n\tReactiveWidgetUi,\n\tReadToolCallEvent,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tResolvedCommand,\n\tScopedModel,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeForkEvent,\n\tSessionBeforeSwitchEvent,\n\tSessionBeforeTreeEvent,\n\tSessionCompactEvent,\n\tSessionInfoChangedEvent,\n\tSessionShutdownEvent,\n\tSessionStartEvent,\n\tSessionTreeEvent,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tSourceInfo,\n\tSubagentChildPolicy,\n\tSubagentIntercomIdentity,\n\tTerminalInputHandler,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolDefinition,\n\tToolExecutionEndEvent,\n\tToolExecutionMode,\n\tToolExecutionStartEvent,\n\tToolInfo,\n\tToolRenderResultOptions,\n\tToolResultEvent,\n\tTurnEndEvent,\n\tTurnStartEvent,\n\tUserBashEvent,\n\tUserBashEventResult,\n\tWidgetPlacement,\n\tWorkflowStageOrchestrationContext,\n\tWorkingIndicatorOptions,\n\tWriteToolCallEvent,\n} from \"./core/extensions/index.ts\";\nexport {\n\tcreateExtensionRuntime,\n\tdecideReactiveWidgetAction,\n\tdefineTool,\n\tdiscoverAndLoadExtensions,\n\tExtensionRunner,\n\tinstallReactiveWidget,\n\tisBashToolResult,\n\tisEditToolResult,\n\tisFindToolResult,\n\tisLsToolResult,\n\tisReadToolResult,\n\tisStaleExtensionContextError,\n\tisToolCallEventType,\n\tisWriteToolResult,\n\tSTALE_EXTENSION_CONTEXT_MARKER,\n\twrapRegisteredTool,\n\twrapRegisteredTools,\n} from \"./core/extensions/index.ts\";\n"]}
1
+ {"version":3,"file":"index-extensions.js","sourceRoot":"","sources":["../src/index-extensions.ts"],"names":[],"mappings":"AA2HA,OAAO,EACN,sBAAsB,EACtB,0BAA0B,EAC1B,UAAU,EACV,yBAAyB,EACzB,eAAe,EACf,qBAAqB,EACrB,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,EACzB,8BAA8B,EAC9B,kBAAkB,EAClB,mBAAmB,GACnB,MAAM,4BAA4B,CAAC","sourcesContent":["// Extension system\nexport type {\n\tAgentEndEvent,\n\tAgentSettledEvent,\n\tAgentStartEvent,\n\tAgentToolResult,\n\tAgentToolUpdateCallback,\n\tAppKeybinding,\n\tAtomicProviderCompat,\n\tAutocompleteProviderFactory,\n\tBashToolCallEvent,\n\tBeforeAgentStartEvent,\n\tBeforeAgentStartEventResult,\n\tBeforeProviderHeadersEvent,\n\tBeforeProviderRequestEvent,\n\tBeforeProviderRequestEventResult,\n\tBuildSystemPromptOptions,\n\tCompactOptions,\n\tConstrainedSamplingConfig,\n\tContextEvent,\n\tContextUsage,\n\tCustomToolCallEvent,\n\tEditToolCallEvent,\n\tEntryRenderer,\n\tEntryRenderOptions,\n\tExecOptions,\n\tExecResult,\n\tExtension,\n\tExtensionActions,\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionCommandContextActions,\n\tExtensionContext,\n\tExtensionContextActions,\n\tExtensionCustomComponent,\n\tExtensionError,\n\tExtensionEvent,\n\tExtensionFactory,\n\tExtensionFlag,\n\tExtensionHandler,\n\tExtensionRuntime,\n\tExtensionScopedModels,\n\tExtensionShortcut,\n\tExtensionUIContext,\n\tExtensionUIDialogOptions,\n\tExtensionWidgetOptions,\n\tFindToolCallEvent,\n\tHostInputFormField,\n\tHostInputFormFieldType,\n\tHostInputFormRequest,\n\tHostSessionPickerHandle,\n\tHostSessionPickerRequest,\n\tHostSessionPickerRow,\n\tInlineExtension,\n\tInputEvent,\n\tInputEventResult,\n\tInputSource,\n\tInstallReactiveWidgetOptions,\n\tKeybindingsManager,\n\tLoadExtensionsResult,\n\tLsToolCallEvent,\n\tMarkdownTransformContext,\n\tMarkdownTransformer,\n\tMessageRenderer,\n\tMessageRenderOptions,\n\tModelSelectEvent,\n\tOrchestrationContext,\n\tProjectTrustContext,\n\tProjectTrustEvent,\n\tProjectTrustEventDecision,\n\tProjectTrustEventResult,\n\tProjectTrustHandler,\n\tProviderConfig,\n\tProviderModelConfig,\n\tReactiveWidgetAction,\n\tReactiveWidgetComponent,\n\tReactiveWidgetController,\n\tReactiveWidgetFactory,\n\tReactiveWidgetRefreshReason,\n\tReactiveWidgetRenderContext,\n\tReactiveWidgetRenderState,\n\tReactiveWidgetScheduler,\n\tReactiveWidgetTimerApi,\n\tReactiveWidgetTimerHandle,\n\tReactiveWidgetUi,\n\tReadToolCallEvent,\n\tRegisteredCommand,\n\tRegisteredTool,\n\tResolvedCommand,\n\tScopedModel,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeForkEvent,\n\tSessionBeforeSwitchEvent,\n\tSessionBeforeTreeEvent,\n\tSessionCompactEvent,\n\tSessionInfoChangedEvent,\n\tSessionShutdownEvent,\n\tSessionStartEvent,\n\tSessionTreeEvent,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tSourceInfo,\n\tSubagentChildPolicy,\n\tSubagentIntercomIdentity,\n\tTerminalInputHandler,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolDefinition,\n\tToolExecutionEndEvent,\n\tToolExecutionMode,\n\tToolExecutionStartEvent,\n\tToolInfo,\n\tToolRenderResultOptions,\n\tToolResultEvent,\n\tTurnEndEvent,\n\tTurnStartEvent,\n\tUserBashEvent,\n\tUserBashEventResult,\n\tWidgetPlacement,\n\tWorkflowStageOrchestrationContext,\n\tWorkingIndicatorOptions,\n\tWriteToolCallEvent,\n} from \"./core/extensions/index.ts\";\nexport {\n\tcreateExtensionRuntime,\n\tdecideReactiveWidgetAction,\n\tdefineTool,\n\tdiscoverAndLoadExtensions,\n\tExtensionRunner,\n\tinstallReactiveWidget,\n\tisBashToolResult,\n\tisEditToolResult,\n\tisFindToolResult,\n\tisLsToolResult,\n\tisReadToolResult,\n\tisStaleExtensionContextError,\n\tisToolCallEventType,\n\tisWriteToolResult,\n\tOVERLAY_ACTIVE_ROW_MARKER,\n\tSTALE_EXTENSION_CONTEXT_MARKER,\n\twrapRegisteredTool,\n\twrapRegisteredTools,\n} from \"./core/extensions/index.ts\";\n"]}
@@ -0,0 +1,95 @@
1
+ import { type Component, type OverlayMargin, type SizeValue } from "@earendil-works/pi-tui";
2
+ import { type ExtensionCustomComponent } from "../../../core/extensions/ui-types.ts";
3
+ /**
4
+ * What {@link ReservedBottomOverlay} needs from the component it wraps: the
5
+ * documented `ctx.ui.custom()` contract, plus the internal-UI-action flag the
6
+ * host reads off whichever component holds focus.
7
+ */
8
+ export type WrappedOverlayComponent = ExtensionCustomComponent & {
9
+ handlesInternalUiAction?: boolean;
10
+ };
11
+ /**
12
+ * Transcript rows that must stay visible above a reserving bottom overlay.
13
+ *
14
+ * A bottom-anchored overlay is composited over the transcript rather than
15
+ * measured into the layout, so nothing stops it covering the whole screen. Six
16
+ * rows is the floor at which the transcript is still legible while paging, and
17
+ * it is what makes the reserve arithmetic below total-occlusion-proof:
18
+ * `visibleStrip === terminalRows - overlayHeight`, so bounding the overlay to
19
+ * `terminalRows - MIN_TRANSCRIPT_STRIP_ROWS` guarantees the strip.
20
+ */
21
+ export declare const MIN_TRANSCRIPT_STRIP_ROWS = 6;
22
+ /** Resolve pi-tui's terminal-relative max-height rule for valid SizeValue input. */
23
+ export declare function resolveOverlayMaxHeight(maxHeight: SizeValue | undefined, terminalRows: number, margin?: OverlayMargin | number): number | undefined;
24
+ /**
25
+ * Fit `lines` into `budget` rows.
26
+ *
27
+ * Trailing blank rows go first — a bottom-anchored overlay pays for them in
28
+ * occlusion and they carry nothing. The last {@link OVERLAY_TAIL_KEEP_ROWS} rows
29
+ * are always kept, because the closing border and the key hints live there.
30
+ *
31
+ * What survives in between is a single window of the frame, and it is placed so
32
+ * that it still contains the row the component marked as active. Keeping a fixed
33
+ * head instead — which is what this did first — drops the selected option on a
34
+ * short terminal: at 80 columns the marker sits at row `6 + 2 * optionIndex`, so
35
+ * option 4 disappeared below 24 rows and option 1 below 18, and the dialog looked
36
+ * frozen under the arrow keys. The window is the earliest one that still holds
37
+ * the active row, so a frame whose active row is already near the top crops
38
+ * exactly as it did before.
39
+ */
40
+ export declare function boundOverlayLines(lines: readonly string[], budget: number): string[];
41
+ /**
42
+ * A blocking overlay that leaves room for the transcript.
43
+ *
44
+ * Wraps the component an extension mounted with `reserveTranscriptRows`, bounds
45
+ * its height so it can never cover the whole screen, and records the height it
46
+ * actually painted so {@link TranscriptOverlayReserve} can extend the
47
+ * transcript's scroll extent by exactly the rows this overlay hides.
48
+ */
49
+ export declare class ReservedBottomOverlay implements Component {
50
+ readonly wantsKeyRelease: boolean;
51
+ readonly handlesInternalUiAction: boolean;
52
+ private readonly inner;
53
+ private readonly getTerminalRows;
54
+ private readonly margin;
55
+ private readonly maxHeight;
56
+ private readonly shouldPassViewportInput;
57
+ private readonly onHeightChange;
58
+ private height;
59
+ constructor(inner: WrappedOverlayComponent, getTerminalRows: () => number, margin?: OverlayMargin | number, maxHeight?: SizeValue, shouldPassViewportInput?: (data: string) => boolean, onHeightChange?: () => void);
60
+ /** Rows painted by the last `render`. Zero before the first frame. */
61
+ get renderedHeight(): number;
62
+ render(width: number): string[];
63
+ handleInput(data: string): boolean | undefined | Promise<boolean | undefined>;
64
+ invalidate(): void;
65
+ dispose(): void;
66
+ }
67
+ export interface TranscriptOverlayIntersection {
68
+ top: number;
69
+ bottom: number;
70
+ }
71
+ /** Return the terminal-row interval where a bottom overlay covers the transcript. */
72
+ export declare function transcriptOverlayIntersection(overlayHeight: number, terminalRows: number, transcriptViewportHeight: number, margin?: OverlayMargin | number): TranscriptOverlayIntersection | undefined;
73
+ /**
74
+ * One host-owned blank-row coordinator appended to the transcript document.
75
+ *
76
+ * Each visible bottom overlay supplies its real transcript intersection. The
77
+ * blank tail equals only the connected covered suffix at the transcript bottom:
78
+ * floating overlays need no tail, while overlapping intervals are unioned once.
79
+ */
80
+ export declare class TranscriptOverlayReserve implements Component {
81
+ private readonly getTranscriptViewportHeight;
82
+ private readonly reservations;
83
+ constructor(getTranscriptViewportHeight: () => number);
84
+ get empty(): boolean;
85
+ register(getIntersection: () => TranscriptOverlayIntersection | undefined): () => void;
86
+ handleInput(_data: string): void;
87
+ invalidate(): void;
88
+ render(_width: number): string[];
89
+ }
90
+ /**
91
+ * Zero-margin compatibility helper for callers that only need the covered
92
+ * transcript-bottom suffix length.
93
+ */
94
+ export declare function occludedTranscriptRows(overlayHeight: number, terminalRows: number, transcriptViewportHeight: number): number;
95
+ //# sourceMappingURL=reserved-bottom-overlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reserved-bottom-overlay.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/reserved-bottom-overlay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,234 @@
1
+ import { CURSOR_MARKER } from "@earendil-works/pi-tui";
2
+ import { OVERLAY_ACTIVE_ROW_MARKER } from "../../../core/extensions/ui-types.js";
3
+ import { theme } from "../theme/theme.js";
4
+ /**
5
+ * Transcript rows that must stay visible above a reserving bottom overlay.
6
+ *
7
+ * A bottom-anchored overlay is composited over the transcript rather than
8
+ * measured into the layout, so nothing stops it covering the whole screen. Six
9
+ * rows is the floor at which the transcript is still legible while paging, and
10
+ * it is what makes the reserve arithmetic below total-occlusion-proof:
11
+ * `visibleStrip === terminalRows - overlayHeight`, so bounding the overlay to
12
+ * `terminalRows - MIN_TRANSCRIPT_STRIP_ROWS` guarantees the strip.
13
+ */
14
+ export const MIN_TRANSCRIPT_STRIP_ROWS = 6;
15
+ function verticalOverlayMargin(margin) {
16
+ if (typeof margin === "number") {
17
+ const value = Math.max(0, margin);
18
+ return { top: value, bottom: value };
19
+ }
20
+ return {
21
+ top: Math.max(0, margin?.top ?? 0),
22
+ bottom: Math.max(0, margin?.bottom ?? 0),
23
+ };
24
+ }
25
+ /** Resolve pi-tui's terminal-relative max-height rule for valid SizeValue input. */
26
+ export function resolveOverlayMaxHeight(maxHeight, terminalRows, margin) {
27
+ if (maxHeight === undefined)
28
+ return undefined;
29
+ let parsed;
30
+ if (typeof maxHeight === "number") {
31
+ parsed = maxHeight;
32
+ }
33
+ else {
34
+ const match = /^(\d+(?:\.\d+)?)%$/.exec(maxHeight);
35
+ if (match?.[1] !== undefined)
36
+ parsed = Math.floor((terminalRows * Number.parseFloat(match[1])) / 100);
37
+ }
38
+ if (parsed === undefined)
39
+ return undefined;
40
+ const normalized = verticalOverlayMargin(margin);
41
+ const availableHeight = Math.max(1, terminalRows - normalized.top - normalized.bottom);
42
+ return Math.trunc(Math.max(1, Math.min(parsed, availableHeight)));
43
+ }
44
+ /**
45
+ * Rows kept from the bottom of an over-tall overlay. The dialog's closing
46
+ * border and its key hints live there, and they are the interaction
47
+ * affordances; a plain bottom slice would drop exactly those.
48
+ */
49
+ const OVERLAY_TAIL_KEEP_ROWS = 3;
50
+ function trimTrailingBlankRows(lines) {
51
+ let end = lines.length;
52
+ while (end > 0 && (lines[end - 1] ?? "").trim() === "")
53
+ end -= 1;
54
+ return end === lines.length ? lines : lines.slice(0, end);
55
+ }
56
+ /**
57
+ * Pull {@link OVERLAY_ACTIVE_ROW_MARKER} out of the rendered frame, reporting the
58
+ * row that carried it. Stripping is unconditional so the mark never reaches the
59
+ * terminal, whether or not the frame ends up cropped.
60
+ */
61
+ function takeActiveRow(lines) {
62
+ let activeRow;
63
+ const rows = lines.map((line, index) => {
64
+ const at = line.indexOf(OVERLAY_ACTIVE_ROW_MARKER);
65
+ if (activeRow === undefined && (at !== -1 || line.includes(CURSOR_MARKER)))
66
+ activeRow = index;
67
+ if (at === -1)
68
+ return line;
69
+ return line.slice(0, at) + line.slice(at + OVERLAY_ACTIVE_ROW_MARKER.length);
70
+ });
71
+ return { rows, activeRow };
72
+ }
73
+ /**
74
+ * Fit `lines` into `budget` rows.
75
+ *
76
+ * Trailing blank rows go first — a bottom-anchored overlay pays for them in
77
+ * occlusion and they carry nothing. The last {@link OVERLAY_TAIL_KEEP_ROWS} rows
78
+ * are always kept, because the closing border and the key hints live there.
79
+ *
80
+ * What survives in between is a single window of the frame, and it is placed so
81
+ * that it still contains the row the component marked as active. Keeping a fixed
82
+ * head instead — which is what this did first — drops the selected option on a
83
+ * short terminal: at 80 columns the marker sits at row `6 + 2 * optionIndex`, so
84
+ * option 4 disappeared below 24 rows and option 1 below 18, and the dialog looked
85
+ * frozen under the arrow keys. The window is the earliest one that still holds
86
+ * the active row, so a frame whose active row is already near the top crops
87
+ * exactly as it did before.
88
+ */
89
+ export function boundOverlayLines(lines, budget) {
90
+ const { rows, activeRow } = takeActiveRow(lines);
91
+ const trimmed = trimTrailingBlankRows(rows);
92
+ if (budget <= 0)
93
+ return [];
94
+ if (trimmed.length <= budget)
95
+ return [...trimmed];
96
+ if (activeRow !== undefined && budget === 1)
97
+ return [trimmed[activeRow] ?? trimmed[0] ?? ""];
98
+ const tail = Math.min(OVERLAY_TAIL_KEEP_ROWS, Math.max(0, budget - (activeRow === undefined ? 1 : 2)));
99
+ const window = budget - tail - 1;
100
+ if (window <= 0)
101
+ return [...trimmed.slice(0, budget)];
102
+ const tailStart = trimmed.length - tail;
103
+ const start = activeRow !== undefined && activeRow >= window && activeRow < tailStart
104
+ ? Math.min(activeRow - window + 1, tailStart - window)
105
+ : 0;
106
+ const kept = trimmed.slice(start, start + window);
107
+ const hidden = trimmed.length - kept.length - tail;
108
+ const notice = theme.fg("muted", `⋮ ${hidden} more rows hidden — answer or cancel to see them`);
109
+ const tailRows = trimmed.slice(tailStart);
110
+ return start === 0 ? [...kept, notice, ...tailRows] : [notice, ...kept, ...tailRows];
111
+ }
112
+ /**
113
+ * A blocking overlay that leaves room for the transcript.
114
+ *
115
+ * Wraps the component an extension mounted with `reserveTranscriptRows`, bounds
116
+ * its height so it can never cover the whole screen, and records the height it
117
+ * actually painted so {@link TranscriptOverlayReserve} can extend the
118
+ * transcript's scroll extent by exactly the rows this overlay hides.
119
+ */
120
+ export class ReservedBottomOverlay {
121
+ constructor(inner, getTerminalRows, margin, maxHeight, shouldPassViewportInput = () => false, onHeightChange = () => { }) {
122
+ this.height = 0;
123
+ this.inner = inner;
124
+ this.getTerminalRows = getTerminalRows;
125
+ this.margin = margin;
126
+ this.maxHeight = maxHeight;
127
+ this.shouldPassViewportInput = shouldPassViewportInput;
128
+ this.onHeightChange = onHeightChange;
129
+ this.wantsKeyRelease = inner.wantsKeyRelease === true;
130
+ this.handlesInternalUiAction = inner.handlesInternalUiAction === true;
131
+ }
132
+ /** Rows painted by the last `render`. Zero before the first frame. */
133
+ get renderedHeight() {
134
+ return this.height;
135
+ }
136
+ render(width) {
137
+ const terminalRows = this.getTerminalRows();
138
+ const { top, bottom } = verticalOverlayMargin(this.margin);
139
+ const availableHeight = Math.max(1, Math.floor(terminalRows - top - bottom));
140
+ const transcriptStripBudget = Math.max(1, Math.floor(terminalRows - bottom - MIN_TRANSCRIPT_STRIP_ROWS));
141
+ const resolvedMaxHeight = resolveOverlayMaxHeight(this.maxHeight, terminalRows, this.margin);
142
+ const budget = Math.min(availableHeight, transcriptStripBudget, resolvedMaxHeight ?? Number.POSITIVE_INFINITY);
143
+ const lines = boundOverlayLines(this.inner.render(width), budget);
144
+ const previousHeight = this.height;
145
+ this.height = lines.length;
146
+ if (this.height !== previousHeight)
147
+ this.onHeightChange();
148
+ return lines;
149
+ }
150
+ handleInput(data) {
151
+ if (this.shouldPassViewportInput(data))
152
+ return false;
153
+ const handler = this.inner.handleInput;
154
+ if (typeof handler !== "function")
155
+ return undefined;
156
+ return handler.call(this.inner, data);
157
+ }
158
+ invalidate() {
159
+ this.inner.invalidate();
160
+ }
161
+ dispose() {
162
+ this.inner.dispose?.();
163
+ }
164
+ }
165
+ /** Return the terminal-row interval where a bottom overlay covers the transcript. */
166
+ export function transcriptOverlayIntersection(overlayHeight, terminalRows, transcriptViewportHeight, margin) {
167
+ if (overlayHeight <= 0 || terminalRows <= 0 || transcriptViewportHeight <= 0)
168
+ return undefined;
169
+ const normalized = verticalOverlayMargin(margin);
170
+ const overlayTop = Math.max(normalized.top, terminalRows - normalized.bottom - overlayHeight);
171
+ const overlayBottom = Math.min(terminalRows, overlayTop + overlayHeight);
172
+ const top = Math.max(0, Math.min(transcriptViewportHeight, overlayTop));
173
+ const bottom = Math.max(0, Math.min(transcriptViewportHeight, overlayBottom));
174
+ return bottom > top ? { top, bottom } : undefined;
175
+ }
176
+ /**
177
+ * One host-owned blank-row coordinator appended to the transcript document.
178
+ *
179
+ * Each visible bottom overlay supplies its real transcript intersection. The
180
+ * blank tail equals only the connected covered suffix at the transcript bottom:
181
+ * floating overlays need no tail, while overlapping intervals are unioned once.
182
+ */
183
+ export class TranscriptOverlayReserve {
184
+ constructor(getTranscriptViewportHeight) {
185
+ this.reservations = new Map();
186
+ this.getTranscriptViewportHeight = getTranscriptViewportHeight;
187
+ }
188
+ get empty() {
189
+ return this.reservations.size === 0;
190
+ }
191
+ register(getIntersection) {
192
+ const token = Symbol();
193
+ this.reservations.set(token, getIntersection);
194
+ let released = false;
195
+ return () => {
196
+ if (released)
197
+ return;
198
+ released = true;
199
+ this.reservations.delete(token);
200
+ };
201
+ }
202
+ handleInput(_data) { }
203
+ invalidate() { }
204
+ render(_width) {
205
+ const viewportHeight = Math.max(0, Math.trunc(this.getTranscriptViewportHeight()));
206
+ const intersections = [...this.reservations.values()]
207
+ .map((getIntersection) => getIntersection())
208
+ .filter((intersection) => intersection !== undefined);
209
+ let coveredFrom = viewportHeight;
210
+ let extended = true;
211
+ while (extended) {
212
+ extended = false;
213
+ for (const intersection of intersections) {
214
+ const top = Math.max(0, Math.min(viewportHeight, intersection.top));
215
+ const bottom = Math.max(0, Math.min(viewportHeight, intersection.bottom));
216
+ if (top < coveredFrom && bottom >= coveredFrom) {
217
+ coveredFrom = top;
218
+ extended = true;
219
+ }
220
+ }
221
+ }
222
+ const rows = Math.max(0, Math.trunc(viewportHeight - coveredFrom));
223
+ return Array(rows).fill("");
224
+ }
225
+ }
226
+ /**
227
+ * Zero-margin compatibility helper for callers that only need the covered
228
+ * transcript-bottom suffix length.
229
+ */
230
+ export function occludedTranscriptRows(overlayHeight, terminalRows, transcriptViewportHeight) {
231
+ const intersection = transcriptOverlayIntersection(overlayHeight, terminalRows, transcriptViewportHeight);
232
+ return intersection?.bottom === transcriptViewportHeight ? transcriptViewportHeight - intersection.top : 0;
233
+ }
234
+ //# sourceMappingURL=reserved-bottom-overlay.js.map