agentweaver 0.1.10 → 0.1.12

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 (110) hide show
  1. package/README.md +218 -224
  2. package/dist/artifacts.js +109 -55
  3. package/dist/executors/{codex-local-executor.js → codex-executor.js} +6 -5
  4. package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
  5. package/dist/executors/configs/jira-fetch-config.js +2 -0
  6. package/dist/executors/configs/telegram-notifier-config.js +3 -0
  7. package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
  8. package/dist/executors/fetch-gitlab-review-executor.js +1 -1
  9. package/dist/executors/git-commit-executor.js +25 -0
  10. package/dist/executors/jira-fetch-executor.js +1 -0
  11. package/dist/executors/opencode-executor.js +22 -11
  12. package/dist/executors/process-executor.js +3 -0
  13. package/dist/executors/telegram-notifier-executor.js +54 -0
  14. package/dist/flow-state.js +46 -1
  15. package/dist/gitlab.js +13 -8
  16. package/dist/index.js +477 -514
  17. package/dist/interactive-ui.js +609 -88
  18. package/dist/jira.js +109 -5
  19. package/dist/pipeline/auto-flow.js +6 -6
  20. package/dist/pipeline/context.js +1 -0
  21. package/dist/pipeline/flow-catalog.js +34 -4
  22. package/dist/pipeline/flow-model-settings.js +77 -0
  23. package/dist/pipeline/flow-specs/auto-common.json +446 -0
  24. package/dist/pipeline/flow-specs/auto-golang.json +563 -0
  25. package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
  26. package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
  27. package/dist/pipeline/flow-specs/git-commit.json +196 -0
  28. package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
  29. package/dist/pipeline/flow-specs/{gitlab-review.json → gitlab/gitlab-review.json} +65 -133
  30. package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
  31. package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
  32. package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
  33. package/dist/pipeline/flow-specs/implement.json +5 -4
  34. package/dist/pipeline/flow-specs/plan.json +40 -148
  35. package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +74 -13
  36. package/dist/pipeline/flow-specs/review/review-loop.json +282 -0
  37. package/dist/pipeline/flow-specs/review/review-project.json +87 -0
  38. package/dist/pipeline/flow-specs/review/review.json +126 -0
  39. package/dist/pipeline/flow-specs/task-describe.json +252 -11
  40. package/dist/pipeline/launch-profile-config.js +38 -0
  41. package/dist/pipeline/node-registry.js +75 -45
  42. package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
  43. package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
  44. package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
  45. package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
  46. package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
  47. package/dist/pipeline/nodes/git-commit-node.js +28 -0
  48. package/dist/pipeline/nodes/git-status-node.js +221 -0
  49. package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
  50. package/dist/pipeline/nodes/jira-context-node.js +10 -0
  51. package/dist/pipeline/nodes/jira-fetch-node.js +3 -0
  52. package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
  53. package/dist/pipeline/nodes/plan-codex-node.js +1 -1
  54. package/dist/pipeline/nodes/read-file-node.js +11 -0
  55. package/dist/pipeline/nodes/review-findings-form-node.js +48 -14
  56. package/dist/pipeline/nodes/select-files-form-node.js +72 -0
  57. package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
  58. package/dist/pipeline/nodes/user-input-node.js +43 -8
  59. package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
  60. package/dist/pipeline/prompt-registry.js +3 -4
  61. package/dist/pipeline/prompt-runtime.js +13 -3
  62. package/dist/pipeline/registry.js +6 -8
  63. package/dist/pipeline/spec-compiler.js +5 -0
  64. package/dist/pipeline/spec-types.js +9 -3
  65. package/dist/pipeline/spec-validator.js +4 -0
  66. package/dist/pipeline/types.js +1 -0
  67. package/dist/pipeline/value-resolver.js +50 -38
  68. package/dist/prompts.js +119 -110
  69. package/dist/runtime/agentweaver-home.js +8 -0
  70. package/dist/runtime/command-resolution.js +0 -38
  71. package/dist/runtime/env-loader.js +43 -0
  72. package/dist/runtime/process-runner.js +9 -3
  73. package/dist/structured-artifact-schema-registry.js +54 -0
  74. package/dist/structured-artifact-schemas.json +22 -20
  75. package/dist/structured-artifacts.js +3 -43
  76. package/dist/user-input.js +38 -3
  77. package/package.json +2 -6
  78. package/Dockerfile.codex +0 -56
  79. package/dist/executors/claude-executor.js +0 -46
  80. package/dist/executors/codex-docker-executor.js +0 -27
  81. package/dist/executors/configs/claude-config.js +0 -12
  82. package/dist/executors/configs/codex-docker-config.js +0 -10
  83. package/dist/executors/configs/verify-build-config.js +0 -7
  84. package/dist/executors/verify-build-executor.js +0 -123
  85. package/dist/pipeline/flow-specs/auto.json +0 -979
  86. package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
  87. package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
  88. package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
  89. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
  90. package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
  91. package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
  92. package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
  93. package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
  94. package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
  95. package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
  96. package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
  97. package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
  98. package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
  99. package/dist/pipeline/flow-specs/review-project.json +0 -243
  100. package/dist/pipeline/flow-specs/review.json +0 -312
  101. package/dist/pipeline/flows/preflight-flow.js +0 -19
  102. package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
  103. package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
  104. package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
  105. package/dist/pipeline/nodes/review-claude-node.js +0 -38
  106. package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
  107. package/dist/pipeline/nodes/verify-build-node.js +0 -15
  108. package/dist/runtime/docker-runtime.js +0 -51
  109. package/docker-compose.yml +0 -445
  110. package/verify_build.sh +0 -105
@@ -15,6 +15,28 @@ function makeFolderKey(pathSegments) {
15
15
  function makeFlowKey(flowId) {
16
16
  return `flow:${flowId}`;
17
17
  }
18
+ function escapeBlessedTags(text) {
19
+ return text.replace(/[{}]/g, (ch) => (ch === "{" ? "{open}" : "{close}"));
20
+ }
21
+ function textIndexToLineColumn(value, index) {
22
+ const boundedIndex = Math.max(0, Math.min(value.length, index));
23
+ const beforeCursor = value.slice(0, boundedIndex);
24
+ const lines = beforeCursor.split("\n");
25
+ return {
26
+ line: Math.max(0, lines.length - 1),
27
+ column: (lines[lines.length - 1] ?? "").length,
28
+ };
29
+ }
30
+ function textLineColumnToIndex(value, line, column) {
31
+ const lines = value.split("\n");
32
+ const boundedLine = Math.max(0, Math.min(lines.length - 1, line));
33
+ let index = 0;
34
+ for (let currentLine = 0; currentLine < boundedLine; currentLine += 1) {
35
+ index += (lines[currentLine] ?? "").length + 1;
36
+ }
37
+ const targetLine = lines[boundedLine] ?? "";
38
+ return index + Math.max(0, Math.min(targetLine.length, column));
39
+ }
18
40
  function buildFlowTree(flows) {
19
41
  const roots = new Map();
20
42
  const ensureFolder = (pathSegments) => {
@@ -114,6 +136,11 @@ export class InteractiveUi {
114
136
  help;
115
137
  confirm;
116
138
  formModal;
139
+ formLineInput;
140
+ formTextInput;
141
+ formBooleanInput;
142
+ formSelectInput;
143
+ formHint;
117
144
  flowMap;
118
145
  flowTree;
119
146
  expandedFlowFolders = new Set();
@@ -142,6 +169,7 @@ export class InteractiveUi {
142
169
  scopeKey;
143
170
  jiraIssueKey;
144
171
  summaryVisible;
172
+ version;
145
173
  constructor(options) {
146
174
  this.options = options;
147
175
  if (options.flows.length === 0) {
@@ -149,12 +177,13 @@ export class InteractiveUi {
149
177
  }
150
178
  this.flowMap = new Map(options.flows.map((flow) => [flow.id, flow]));
151
179
  this.flowTree = buildFlowTree(options.flows);
152
- this.selectedFlowId = options.flows[0]?.id ?? "auto";
180
+ this.selectedFlowId = options.flows[0]?.id ?? "auto-golang";
153
181
  this.visibleFlowItems = this.computeVisibleFlowItems();
154
182
  this.selectedFlowItemKey = this.visibleFlowItems[0]?.key ?? makeFlowKey(this.selectedFlowId);
155
183
  this.scopeKey = options.scopeKey;
156
184
  this.jiraIssueKey = options.jiraIssueKey ?? null;
157
185
  this.summaryVisible = options.summaryText.trim().length > 0;
186
+ this.version = options.version ?? "";
158
187
  this.screen = blessed.screen({
159
188
  smartCSR: true,
160
189
  fullUnicode: true,
@@ -281,6 +310,86 @@ export class InteractiveUi {
281
310
  fg: "white",
282
311
  },
283
312
  });
313
+ this.formLineInput = blessed.box({
314
+ parent: this.formModal,
315
+ top: 0,
316
+ left: 0,
317
+ width: "100%-2",
318
+ height: 3,
319
+ hidden: true,
320
+ tags: true,
321
+ border: "line",
322
+ style: {
323
+ border: { fg: "cyan" },
324
+ fg: "white",
325
+ },
326
+ });
327
+ this.formTextInput = blessed.box({
328
+ parent: this.formModal,
329
+ top: 0,
330
+ left: 0,
331
+ width: "100%-2",
332
+ height: 3,
333
+ hidden: true,
334
+ tags: true,
335
+ mouse: true,
336
+ border: "line",
337
+ style: {
338
+ border: { fg: "cyan" },
339
+ fg: "white",
340
+ },
341
+ });
342
+ this.formBooleanInput = blessed.checkbox({
343
+ parent: this.formModal,
344
+ top: 0,
345
+ left: 0,
346
+ width: "100%-2",
347
+ height: 3,
348
+ hidden: true,
349
+ mouse: true,
350
+ keys: true,
351
+ vi: true,
352
+ border: "line",
353
+ style: {
354
+ border: { fg: "cyan" },
355
+ fg: "white",
356
+ },
357
+ });
358
+ this.formSelectInput = blessed.list({
359
+ parent: this.formModal,
360
+ top: 0,
361
+ left: 0,
362
+ width: "100%-2",
363
+ height: 8,
364
+ hidden: true,
365
+ tags: true,
366
+ mouse: true,
367
+ keys: true,
368
+ vi: true,
369
+ border: "line",
370
+ scrollable: true,
371
+ alwaysScroll: true,
372
+ style: {
373
+ border: { fg: "cyan" },
374
+ fg: "white",
375
+ selected: {
376
+ fg: "black",
377
+ bg: "green",
378
+ },
379
+ },
380
+ });
381
+ this.formHint = blessed.box({
382
+ parent: this.formModal,
383
+ top: 0,
384
+ left: 1,
385
+ width: "100%-4",
386
+ height: 2,
387
+ hidden: true,
388
+ tags: true,
389
+ style: {
390
+ fg: "gray",
391
+ },
392
+ });
284
393
  this.description = blessed.box({
285
394
  parent: this.screen,
286
395
  bottom: 6,
@@ -721,10 +830,12 @@ export class InteractiveUi {
721
830
  const branchLabel = this.options.gitBranchName ? this.options.gitBranchName : "detached-head";
722
831
  const flowLabel = `${current}${this.busy ? " {yellow-fg}[running]{/yellow-fg}" : ""}`;
723
832
  const divider = " {gray-fg}│{/gray-fg} ";
833
+ const versionLabel = this.version ? `${divider}{bold}Version{/bold} {white-fg}${this.version}{/white-fg}` : "";
724
834
  this.header.setContent([
725
835
  "{bold}AgentWeaver{/bold}",
726
836
  divider,
727
837
  `{bold}Scope{/bold} {green-fg}${this.scopeKey}{/green-fg}`,
838
+ versionLabel,
728
839
  this.jiraIssueKey ? `${divider}{bold}Jira{/bold} {yellow-fg}${this.jiraIssueKey}{/yellow-fg}` : "",
729
840
  divider,
730
841
  `{bold}Flow{/bold} ${flowLabel}`,
@@ -745,23 +856,30 @@ export class InteractiveUi {
745
856
  }
746
857
  return this.activeFormSession.form.fields[this.activeFormSession.currentFieldIndex] ?? null;
747
858
  }
748
- renderTextInputValue(value, placeholder) {
749
- const rawText = value || placeholder || "Введите текст";
750
- const frameWidth = Math.max(36, rawText.length + 6);
751
- const innerWidth = Math.max(32, frameWidth - 4);
752
- const visibleText = rawText.length > innerWidth - 2 ? `${rawText.slice(0, innerWidth - 5)}...` : rawText;
753
- const padded = value
754
- ? `{white-fg}${visibleText.padEnd(innerWidth - 2, " ")}{/white-fg}`
755
- : `{gray-fg}${visibleText.padEnd(innerWidth - 2, " ")}{/gray-fg}`;
756
- return [
757
- `{cyan-fg}┌${"".repeat(frameWidth - 2)}┐{/cyan-fg}`,
758
- `{cyan-fg}│{/cyan-fg}{black-bg} {green-fg}>{/green-fg} ${padded} {/black-bg}{cyan-fg}│{/cyan-fg}`,
759
- `{cyan-fg}└${"─".repeat(frameWidth - 2)}┘{/cyan-fg}`,
760
- ];
859
+ formModalInnerHeight() {
860
+ const rawHeight = typeof this.formModal.height === "number" ? this.formModal.height : this.formModal?.lpos?.yi
861
+ ? this.formModal.lpos.yl - this.formModal.lpos.yi + 1
862
+ : 0;
863
+ const paddingTop = Number(this.formModal.padding?.top ?? 0);
864
+ const paddingBottom = Number(this.formModal.padding?.bottom ?? 0);
865
+ return Math.max(6, rawHeight - 2 - paddingTop - paddingBottom);
866
+ }
867
+ formModalInnerWidth() {
868
+ const rawWidth = typeof this.formModal.width === "number" ? this.formModal.width : this.formModal?.lpos?.xi
869
+ ? this.formModal.lpos.xl - this.formModal.lpos.xi + 1
870
+ : 0;
871
+ const paddingLeft = Number(this.formModal.padding?.left ?? 0);
872
+ const paddingRight = Number(this.formModal.padding?.right ?? 0);
873
+ return Math.max(24, rawWidth - 2 - paddingLeft - paddingRight);
761
874
  }
762
875
  renderActiveForm() {
763
876
  if (!this.activeFormSession) {
764
877
  this.formModal.hide();
878
+ this.hideFormLineInput();
879
+ this.hideFormTextInput();
880
+ this.hideFormBooleanInput();
881
+ this.hideFormSelectInput();
882
+ this.hideFormHint();
765
883
  this.footer.setContent(" Up/Down: select | Left/Right: fold | Enter: toggle/run | Esc: close/interrupt | h: help | Tab: switch pane | q: exit ");
766
884
  this.requestRender();
767
885
  return;
@@ -771,81 +889,334 @@ export class InteractiveUi {
771
889
  if (!field) {
772
890
  return;
773
891
  }
774
- const lines = [`{bold}${session.form.title}{/bold}`];
892
+ const isLastField = session.currentFieldIndex >= session.form.fields.length - 1;
893
+ const headerLines = [`{bold}${session.form.title}{/bold}`];
775
894
  if (session.form.description?.trim()) {
776
- lines.push("");
777
- lines.push(session.form.description.trim());
895
+ headerLines.push("");
896
+ headerLines.push(session.form.description.trim());
778
897
  }
779
- lines.push("");
780
- lines.push(`Field ${session.currentFieldIndex + 1}/${session.form.fields.length}`);
781
- lines.push(`{yellow-fg}${field.label}{/yellow-fg}`);
898
+ headerLines.push("");
899
+ headerLines.push(`Field ${session.currentFieldIndex + 1}/${session.form.fields.length}`);
900
+ headerLines.push(`{yellow-fg}${field.label}{/yellow-fg}`);
782
901
  if (field.help?.trim()) {
783
- lines.push(field.help.trim());
902
+ headerLines.push(field.help.trim());
784
903
  }
785
- lines.push("");
904
+ headerLines.push("");
905
+ const helperLines = [];
906
+ const lines = [...headerLines];
907
+ let footerHint = ` Form: Enter ${isLastField ? "submit" : "confirm"} | Tab next | Shift+Tab prev | Esc cancel `;
908
+ let hintLines = [];
786
909
  if (field.type === "boolean") {
910
+ this.hideFormLineInput();
911
+ this.hideFormTextInput();
912
+ this.hideFormSelectInput();
913
+ helperLines.push("{cyan-fg}Use the checkbox below.{/cyan-fg}");
914
+ this.ensureFormBooleanInputVisible(headerLines.length + helperLines.length, 0);
787
915
  const current = session.values[field.id] === true;
788
- lines.push(`${current ? "[x]" : "[ ]"} ${field.label}`);
789
- lines.push("");
790
- lines.push("Space: toggle");
791
- lines.push("Enter/Tab: next field");
916
+ this.formBooleanInput.setText(field.label);
917
+ if (current) {
918
+ this.formBooleanInput.check();
919
+ }
920
+ else {
921
+ this.formBooleanInput.uncheck();
922
+ }
923
+ hintLines = [
924
+ `Space: toggle | Enter: ${isLastField ? "submit" : "confirm and next"}`,
925
+ "Tab/Shift+Tab: switch field | Esc: cancel",
926
+ ];
927
+ footerHint = ` Form: Space toggle | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `;
792
928
  }
793
929
  else if (field.type === "text") {
930
+ this.hideFormBooleanInput();
931
+ this.hideFormSelectInput();
932
+ helperLines.push("{cyan-fg}Use the standard editor below.{/cyan-fg}");
794
933
  const current = String(session.values[field.id] ?? "");
795
- lines.push(...this.renderTextInputValue(current, field.placeholder));
796
- lines.push("");
797
- lines.push("Type text, Backspace: delete");
798
- lines.push("Enter/Tab: next field");
934
+ if (field.multiline) {
935
+ this.hideFormLineInput();
936
+ this.ensureFormTextInputVisible(field, headerLines.length + helperLines.length, 0);
937
+ session.currentTextCursorIndex = Math.max(0, Math.min(current.length, session.currentTextCursorIndex));
938
+ this.renderFormMultilineInput(current || field.placeholder || "", current.length === 0 && Boolean(field.placeholder));
939
+ hintLines = [
940
+ "Enter: newline | Tab/Shift+Tab: switch field",
941
+ `Ctrl+S: submit | Esc: cancel`,
942
+ ];
943
+ footerHint = " Form: Enter newline | Tab switch | Ctrl+S submit | Esc cancel ";
944
+ }
945
+ else {
946
+ this.hideFormTextInput();
947
+ this.ensureFormLineInputVisible(headerLines.length + helperLines.length, 0);
948
+ session.currentTextCursorIndex = Math.max(0, Math.min(current.length, session.currentTextCursorIndex));
949
+ this.renderFormLineInput(current);
950
+ hintLines = [
951
+ `Left/Right/Home/End: move | Enter: ${isLastField ? "submit" : "confirm and next"}`,
952
+ "Tab/Shift+Tab: switch field | Esc: cancel",
953
+ ];
954
+ footerHint = ` Form: Type text | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `;
955
+ }
799
956
  }
800
957
  else {
801
- const currentOptionIndex = Math.min(session.currentOptionIndex, Math.max(0, field.options.length - 1));
958
+ this.hideFormLineInput();
959
+ this.hideFormTextInput();
960
+ this.hideFormBooleanInput();
961
+ helperLines.push("{cyan-fg}Use the list below.{/cyan-fg}");
962
+ this.ensureFormSelectInputVisible(headerLines.length + helperLines.length, 0);
963
+ const preferredOptionIndex = field.type === "single-select"
964
+ ? this.selectedOptionIndexForField(field)
965
+ : session.currentOptionIndex;
966
+ const currentOptionIndex = Math.min(preferredOptionIndex, Math.max(0, field.options.length - 1));
802
967
  session.currentOptionIndex = currentOptionIndex;
803
- field.options.forEach((option, index) => {
804
- const isCursor = index === currentOptionIndex;
805
- const value = session.values[field.id];
806
- const isSelected = field.type === "single-select"
807
- ? value === option.value
808
- : Array.isArray(value) && value.includes(option.value);
809
- const cursor = isCursor ? "{cyan-fg}>{/cyan-fg}" : " ";
968
+ const selectedValues = field.type === "single-select"
969
+ ? [String(session.values[field.id] ?? "")]
970
+ : Array.isArray(session.values[field.id]) ? session.values[field.id] : [];
971
+ const items = field.options.map((option) => {
972
+ const isSelected = selectedValues.includes(option.value);
810
973
  const marker = isSelected ? "[x]" : "[ ]";
811
- lines.push(`${cursor} ${marker} ${option.label}`);
812
- if (option.description?.trim()) {
813
- lines.push(` {gray-fg}${option.description.trim()}{/gray-fg}`);
814
- }
974
+ return `${marker} ${option.label}`;
815
975
  });
816
- lines.push("");
817
- lines.push("Up/Down: move");
818
- lines.push("Space: select/toggle");
819
- lines.push("Enter/Tab: next field");
820
- }
821
- lines.push("");
822
- lines.push("{green-fg}Ctrl+S{/green-fg}: submit");
823
- lines.push("{magenta-fg}Shift+Tab{/magenta-fg}: previous field");
824
- lines.push("{red-fg}Esc{/red-fg}: cancel");
976
+ this.formSelectInput.setItems(items);
977
+ this.formSelectInput.select(currentOptionIndex);
978
+ hintLines = [
979
+ `Up/Down: move | Space: ${field.type === "single-select" ? "pick" : "toggle"} | Enter: ${isLastField ? "submit" : "confirm and next"}`,
980
+ "Tab/Shift+Tab: switch field | Esc: cancel",
981
+ ];
982
+ footerHint = ` Form: Up/Down move | Enter ${isLastField ? "submit" : "next"} | Tab switch | Esc cancel `;
983
+ }
984
+ lines.push(...helperLines);
825
985
  this.formModal.setContent(lines.join("\n"));
986
+ this.formModal.setScroll(0);
826
987
  this.formModal.show();
827
988
  this.formModal.setFront();
828
- this.formModal.focus();
829
- this.footer.setContent(" Form: Space select | Tab next | Shift+Tab prev | Ctrl+S submit | Esc cancel ");
989
+ if (field.type === "text") {
990
+ if (field.multiline) {
991
+ this.formTextInput.focus();
992
+ }
993
+ else {
994
+ this.formLineInput.focus();
995
+ }
996
+ }
997
+ else if (field.type === "boolean") {
998
+ this.formBooleanInput.focus();
999
+ }
1000
+ else {
1001
+ this.formSelectInput.focus();
1002
+ }
1003
+ this.showFormHint(hintLines);
1004
+ this.footer.setContent(footerHint);
830
1005
  this.requestRender();
831
1006
  }
832
1007
  moveActiveFormField(delta) {
833
1008
  if (!this.activeFormSession) {
834
1009
  return;
835
1010
  }
1011
+ this.syncActiveTextFieldValue();
1012
+ this.syncActiveBooleanFieldValue();
1013
+ this.syncActiveSelectFieldValue();
836
1014
  const nextIndex = Math.min(this.activeFormSession.form.fields.length - 1, Math.max(0, this.activeFormSession.currentFieldIndex + delta));
837
1015
  this.activeFormSession.currentFieldIndex = nextIndex;
838
- this.activeFormSession.currentOptionIndex = 0;
1016
+ const nextField = this.activeFormSession.form.fields[nextIndex];
1017
+ if (nextField?.type === "text") {
1018
+ const current = String(this.activeFormSession.values[nextField.id] ?? "");
1019
+ this.activeFormSession.currentTextCursorIndex = current.length;
1020
+ this.activeFormSession.currentOptionIndex = 0;
1021
+ }
1022
+ else if (nextField?.type === "single-select" || nextField?.type === "multi-select") {
1023
+ this.activeFormSession.currentTextCursorIndex = 0;
1024
+ this.activeFormSession.currentOptionIndex = this.selectedOptionIndexForField(nextField);
1025
+ }
1026
+ else {
1027
+ this.activeFormSession.currentTextCursorIndex = 0;
1028
+ this.activeFormSession.currentOptionIndex = 0;
1029
+ }
839
1030
  this.renderActiveForm();
840
1031
  }
841
- moveActiveFormOption(delta) {
1032
+ selectedOptionIndexForField(field) {
1033
+ const session = this.activeFormSession;
1034
+ if (!session || field.options.length === 0) {
1035
+ return 0;
1036
+ }
1037
+ if (field.type === "single-select") {
1038
+ const selectedValue = String(session.values[field.id] ?? "");
1039
+ const selectedIndex = field.options.findIndex((option) => option.value === selectedValue);
1040
+ return selectedIndex >= 0 ? selectedIndex : 0;
1041
+ }
1042
+ const selectedValues = Array.isArray(session.values[field.id]) ? session.values[field.id] : [];
1043
+ const selectedIndex = field.options.findIndex((option) => selectedValues.includes(option.value));
1044
+ return selectedIndex >= 0 ? selectedIndex : 0;
1045
+ }
1046
+ confirmActiveFormField() {
1047
+ const session = this.activeFormSession;
1048
+ if (!session) {
1049
+ return;
1050
+ }
1051
+ this.syncActiveTextFieldValue();
1052
+ this.syncActiveBooleanFieldValue();
1053
+ this.syncActiveSelectFieldValue();
1054
+ if (session.currentFieldIndex >= session.form.fields.length - 1) {
1055
+ this.submitActiveForm();
1056
+ return;
1057
+ }
1058
+ this.moveActiveFormField(1);
1059
+ }
1060
+ ensureFormLineInputVisible(headerLineCount, footerLineCount) {
1061
+ const reservedTop = Math.max(1, headerLineCount + 2);
1062
+ const availableHeight = Math.max(5, this.formModalInnerHeight() - reservedTop - footerLineCount - 2);
1063
+ this.formLineInput.top = reservedTop;
1064
+ this.formLineInput.left = 1;
1065
+ this.formLineInput.width = Math.max(24, this.formModalInnerWidth() - 4);
1066
+ this.formLineInput.height = Math.min(3, availableHeight);
1067
+ this.formLineInput.show();
1068
+ this.formLineInput.setFront();
1069
+ this.formLineInput.focus();
1070
+ this.positionFormHint(this.formLineInput.top + this.formLineInput.height);
1071
+ }
1072
+ ensureFormTextInputVisible(field, headerLineCount, footerLineCount) {
1073
+ const reservedTop = Math.max(1, headerLineCount + 2);
1074
+ const availableHeight = Math.max(5, this.formModalInnerHeight() - reservedTop - footerLineCount - 2);
1075
+ const desiredRows = field.multiline ? Math.max(3, field.rows ?? 3) : 1;
1076
+ const inputHeight = Math.min(Math.max(3, availableHeight - 2), desiredRows + 2);
1077
+ this.formTextInput.top = reservedTop;
1078
+ this.formTextInput.left = 1;
1079
+ this.formTextInput.width = Math.max(24, this.formModalInnerWidth() - 4);
1080
+ this.formTextInput.height = inputHeight;
1081
+ this.formTextInput.show();
1082
+ this.formTextInput.setFront();
1083
+ this.formTextInput.focus();
1084
+ this.positionFormHint(this.formTextInput.top + this.formTextInput.height);
1085
+ }
1086
+ renderFormLineInput(value) {
1087
+ const session = this.activeFormSession;
1088
+ if (!session) {
1089
+ return;
1090
+ }
1091
+ const totalWidth = typeof this.formLineInput.width === "number" ? this.formLineInput.width : this.formModalInnerWidth();
1092
+ const innerWidth = Math.max(1, totalWidth - 2);
1093
+ const cursorIndex = Math.max(0, Math.min(value.length, session.currentTextCursorIndex));
1094
+ session.currentTextCursorIndex = cursorIndex;
1095
+ const visibleStart = Math.max(0, cursorIndex - innerWidth + 1);
1096
+ const visibleValue = value.slice(visibleStart, visibleStart + innerWidth);
1097
+ const cursorOffset = cursorIndex - visibleStart;
1098
+ const beforeCursor = escapeBlessedTags(visibleValue.slice(0, Math.max(0, cursorOffset)));
1099
+ const cursorChar = visibleValue[cursorOffset] ?? " ";
1100
+ const afterCursor = escapeBlessedTags(visibleValue.slice(Math.min(visibleValue.length, cursorOffset + 1)));
1101
+ this.formLineInput.setContent(`${beforeCursor}{inverse}${escapeBlessedTags(cursorChar)}{/inverse}${afterCursor}`);
1102
+ }
1103
+ renderFormMultilineInput(value, dimmed = false) {
1104
+ const session = this.activeFormSession;
1105
+ if (!session) {
1106
+ return;
1107
+ }
1108
+ const totalWidth = typeof this.formTextInput.width === "number" ? this.formTextInput.width : this.formModalInnerWidth();
1109
+ const totalHeight = typeof this.formTextInput.height === "number" ? this.formTextInput.height : 3;
1110
+ const innerWidth = Math.max(1, totalWidth - 2);
1111
+ const innerHeight = Math.max(1, totalHeight - 2);
1112
+ const cursorIndex = Math.max(0, Math.min(value.length, session.currentTextCursorIndex));
1113
+ session.currentTextCursorIndex = cursorIndex;
1114
+ const cursor = textIndexToLineColumn(value, cursorIndex);
1115
+ const sourceLines = value.split("\n");
1116
+ const visibleStartLine = Math.max(0, cursor.line - innerHeight + 1);
1117
+ const visibleLines = sourceLines.slice(visibleStartLine, visibleStartLine + innerHeight);
1118
+ const renderedLines = visibleLines.map((lineText, visibleLineIndex) => {
1119
+ const absoluteLineIndex = visibleStartLine + visibleLineIndex;
1120
+ const isCursorLine = absoluteLineIndex === cursor.line;
1121
+ const startColumn = isCursorLine && cursor.column >= innerWidth ? cursor.column - innerWidth + 1 : 0;
1122
+ const visibleText = lineText.slice(startColumn, startColumn + innerWidth);
1123
+ if (!isCursorLine) {
1124
+ return escapeBlessedTags(visibleText);
1125
+ }
1126
+ const cursorOffset = cursor.column - startColumn;
1127
+ const beforeCursor = escapeBlessedTags(visibleText.slice(0, Math.max(0, cursorOffset)));
1128
+ const cursorChar = visibleText[cursorOffset] ?? " ";
1129
+ const afterCursor = escapeBlessedTags(visibleText.slice(Math.min(visibleText.length, cursorOffset + 1)));
1130
+ return `${beforeCursor}{inverse}${escapeBlessedTags(cursorChar)}{/inverse}${afterCursor}`;
1131
+ });
1132
+ const rendered = renderedLines.join("\n");
1133
+ this.formTextInput.setContent(dimmed ? `{gray-fg}${rendered}{/gray-fg}` : rendered);
1134
+ }
1135
+ ensureFormBooleanInputVisible(headerLineCount, footerLineCount) {
1136
+ const reservedTop = Math.max(1, headerLineCount + 2);
1137
+ const availableHeight = Math.max(5, this.formModalInnerHeight() - reservedTop - footerLineCount - 2);
1138
+ this.formBooleanInput.top = reservedTop;
1139
+ this.formBooleanInput.left = 1;
1140
+ this.formBooleanInput.width = Math.max(24, this.formModalInnerWidth() - 4);
1141
+ this.formBooleanInput.height = Math.min(4, availableHeight);
1142
+ this.formBooleanInput.show();
1143
+ this.formBooleanInput.setFront();
1144
+ this.formBooleanInput.focus();
1145
+ this.positionFormHint(this.formBooleanInput.top + this.formBooleanInput.height);
1146
+ }
1147
+ ensureFormSelectInputVisible(headerLineCount, footerLineCount) {
1148
+ const reservedTop = Math.max(1, headerLineCount + 2);
1149
+ const availableHeight = Math.max(6, this.formModalInnerHeight() - reservedTop - footerLineCount - 2);
1150
+ this.formSelectInput.top = reservedTop;
1151
+ this.formSelectInput.left = 1;
1152
+ this.formSelectInput.width = Math.max(24, this.formModalInnerWidth() - 4);
1153
+ this.formSelectInput.height = Math.max(4, availableHeight - 2);
1154
+ this.formSelectInput.show();
1155
+ this.formSelectInput.setFront();
1156
+ this.formSelectInput.focus();
1157
+ this.positionFormHint(this.formSelectInput.top + this.formSelectInput.height);
1158
+ }
1159
+ positionFormHint(top) {
1160
+ this.formHint.top = top;
1161
+ this.formHint.left = 1;
1162
+ this.formHint.width = Math.max(24, this.formModalInnerWidth() - 4);
1163
+ this.formHint.height = 2;
1164
+ }
1165
+ showFormHint(lines) {
1166
+ this.formHint.setContent(lines.join("\n"));
1167
+ this.formHint.show();
1168
+ this.formHint.setFront();
1169
+ }
1170
+ hideFormLineInput() {
1171
+ this.formLineInput.hide();
1172
+ if (typeof this.formLineInput.blur === "function") {
1173
+ this.formLineInput.blur();
1174
+ }
1175
+ }
1176
+ hideFormTextInput() {
1177
+ this.formTextInput.hide();
1178
+ if (typeof this.formTextInput.blur === "function") {
1179
+ this.formTextInput.blur();
1180
+ }
1181
+ }
1182
+ hideFormBooleanInput() {
1183
+ this.formBooleanInput.hide();
1184
+ if (typeof this.formBooleanInput.blur === "function") {
1185
+ this.formBooleanInput.blur();
1186
+ }
1187
+ }
1188
+ hideFormSelectInput() {
1189
+ this.formSelectInput.hide();
1190
+ if (typeof this.formSelectInput.blur === "function") {
1191
+ this.formSelectInput.blur();
1192
+ }
1193
+ }
1194
+ hideFormHint() {
1195
+ this.formHint.hide();
1196
+ }
1197
+ syncActiveTextFieldValue() {
1198
+ const session = this.activeFormSession;
842
1199
  const field = this.currentFormField();
843
- if (!this.activeFormSession || !field || (field.type !== "single-select" && field.type !== "multi-select")) {
1200
+ if (!session || !field || field.type !== "text") {
844
1201
  return;
845
1202
  }
846
- const nextIndex = Math.min(field.options.length - 1, Math.max(0, this.activeFormSession.currentOptionIndex + delta));
847
- this.activeFormSession.currentOptionIndex = nextIndex;
848
- this.renderActiveForm();
1203
+ }
1204
+ syncActiveBooleanFieldValue() {
1205
+ const session = this.activeFormSession;
1206
+ const field = this.currentFormField();
1207
+ if (!session || !field || field.type !== "boolean") {
1208
+ return;
1209
+ }
1210
+ session.values[field.id] = this.formBooleanInput.checked === true;
1211
+ }
1212
+ syncActiveSelectFieldValue() {
1213
+ const session = this.activeFormSession;
1214
+ const field = this.currentFormField();
1215
+ if (!session || !field || (field.type !== "single-select" && field.type !== "multi-select")) {
1216
+ return;
1217
+ }
1218
+ const selectedIndex = this.formSelectInput.selected ?? session.currentOptionIndex;
1219
+ session.currentOptionIndex = Math.max(0, Math.min(field.options.length - 1, selectedIndex));
849
1220
  }
850
1221
  toggleActiveFormValue() {
851
1222
  const session = this.activeFormSession;
@@ -858,6 +1229,7 @@ export class InteractiveUi {
858
1229
  this.renderActiveForm();
859
1230
  return;
860
1231
  }
1232
+ this.syncActiveSelectFieldValue();
861
1233
  if (field.type === "single-select") {
862
1234
  const option = field.options[session.currentOptionIndex];
863
1235
  if (!option) {
@@ -880,28 +1252,13 @@ export class InteractiveUi {
880
1252
  this.renderActiveForm();
881
1253
  }
882
1254
  }
883
- appendActiveFormText(ch, key) {
884
- const session = this.activeFormSession;
885
- const field = this.currentFormField();
886
- if (!session || !field || field.type !== "text") {
887
- return;
888
- }
889
- const current = String(session.values[field.id] ?? "");
890
- if (key.name === "backspace") {
891
- session.values[field.id] = current.slice(0, -1);
892
- this.renderActiveForm();
893
- return;
894
- }
895
- if (key.ctrl || key.meta || !ch || ch === "\r" || ch === "\n" || ch === "\t") {
896
- return;
897
- }
898
- session.values[field.id] = `${current}${ch}`;
899
- this.renderActiveForm();
900
- }
901
1255
  submitActiveForm() {
902
1256
  if (!this.activeFormSession) {
903
1257
  return;
904
1258
  }
1259
+ this.syncActiveTextFieldValue();
1260
+ this.syncActiveBooleanFieldValue();
1261
+ this.syncActiveSelectFieldValue();
905
1262
  const session = this.activeFormSession;
906
1263
  try {
907
1264
  validateUserInputValues(session.form, session.values);
@@ -912,6 +1269,10 @@ export class InteractiveUi {
912
1269
  };
913
1270
  this.activeFormSession = null;
914
1271
  this.formModal.hide();
1272
+ this.hideFormLineInput();
1273
+ this.hideFormTextInput();
1274
+ this.hideFormBooleanInput();
1275
+ this.hideFormSelectInput();
915
1276
  this.focusPane("flows");
916
1277
  session.resolve(result);
917
1278
  this.renderActiveForm();
@@ -928,6 +1289,10 @@ export class InteractiveUi {
928
1289
  const session = this.activeFormSession;
929
1290
  this.activeFormSession = null;
930
1291
  this.formModal.hide();
1292
+ this.hideFormLineInput();
1293
+ this.hideFormTextInput();
1294
+ this.hideFormBooleanInput();
1295
+ this.hideFormSelectInput();
931
1296
  this.focusPane("flows");
932
1297
  session.reject(new TaskRunnerError(`User cancelled form '${session.form.formId}'.`));
933
1298
  this.renderActiveForm();
@@ -939,6 +1304,10 @@ export class InteractiveUi {
939
1304
  const session = this.activeFormSession;
940
1305
  this.activeFormSession = null;
941
1306
  this.formModal.hide();
1307
+ this.hideFormLineInput();
1308
+ this.hideFormTextInput();
1309
+ this.hideFormBooleanInput();
1310
+ this.hideFormSelectInput();
942
1311
  this.focusPane("flows");
943
1312
  session.reject(new FlowInterruptedError(message));
944
1313
  this.renderActiveForm();
@@ -965,11 +1334,153 @@ export class InteractiveUi {
965
1334
  return;
966
1335
  }
967
1336
  if (field.type === "text") {
1337
+ if (!field.multiline) {
1338
+ const session = this.activeFormSession;
1339
+ if (!session) {
1340
+ return;
1341
+ }
1342
+ const current = String(session.values[field.id] ?? "");
1343
+ if (key.name === "enter") {
1344
+ this.confirmActiveFormField();
1345
+ return;
1346
+ }
1347
+ if (key.name === "left") {
1348
+ session.currentTextCursorIndex = Math.max(0, session.currentTextCursorIndex - 1);
1349
+ this.renderFormLineInput(current);
1350
+ this.requestRender();
1351
+ return;
1352
+ }
1353
+ if (key.name === "right") {
1354
+ session.currentTextCursorIndex = Math.min(current.length, session.currentTextCursorIndex + 1);
1355
+ this.renderFormLineInput(current);
1356
+ this.requestRender();
1357
+ return;
1358
+ }
1359
+ if (key.name === "home") {
1360
+ session.currentTextCursorIndex = 0;
1361
+ this.renderFormLineInput(current);
1362
+ this.requestRender();
1363
+ return;
1364
+ }
1365
+ if (key.name === "end") {
1366
+ session.currentTextCursorIndex = current.length;
1367
+ this.renderFormLineInput(current);
1368
+ this.requestRender();
1369
+ return;
1370
+ }
1371
+ if (key.name === "backspace") {
1372
+ if (session.currentTextCursorIndex > 0) {
1373
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex - 1)}${current.slice(session.currentTextCursorIndex)}`;
1374
+ session.currentTextCursorIndex -= 1;
1375
+ session.values[field.id] = nextValue;
1376
+ this.renderFormLineInput(nextValue);
1377
+ this.requestRender();
1378
+ }
1379
+ return;
1380
+ }
1381
+ if (key.name === "delete") {
1382
+ if (session.currentTextCursorIndex < current.length) {
1383
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex)}${current.slice(session.currentTextCursorIndex + 1)}`;
1384
+ session.values[field.id] = nextValue;
1385
+ this.renderFormLineInput(nextValue);
1386
+ this.requestRender();
1387
+ }
1388
+ return;
1389
+ }
1390
+ if (ch && !key.ctrl && !key.meta && !/^[\x00-\x1f\x7f]$/.test(ch)) {
1391
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex)}${ch}${current.slice(session.currentTextCursorIndex)}`;
1392
+ session.currentTextCursorIndex += ch.length;
1393
+ session.values[field.id] = nextValue;
1394
+ this.renderFormLineInput(nextValue);
1395
+ this.requestRender();
1396
+ }
1397
+ return;
1398
+ }
968
1399
  if (key.name === "enter") {
969
- this.moveActiveFormField(1);
1400
+ const session = this.activeFormSession;
1401
+ if (!session) {
1402
+ return;
1403
+ }
1404
+ const current = String(session.values[field.id] ?? "");
1405
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex)}\n${current.slice(session.currentTextCursorIndex)}`;
1406
+ session.currentTextCursorIndex += 1;
1407
+ session.values[field.id] = nextValue;
1408
+ this.renderFormMultilineInput(nextValue);
1409
+ this.requestRender();
1410
+ return;
1411
+ }
1412
+ const session = this.activeFormSession;
1413
+ if (!session) {
1414
+ return;
1415
+ }
1416
+ const current = String(session.values[field.id] ?? "");
1417
+ if (key.name === "left") {
1418
+ session.currentTextCursorIndex = Math.max(0, session.currentTextCursorIndex - 1);
1419
+ this.renderFormMultilineInput(current);
1420
+ this.requestRender();
1421
+ return;
1422
+ }
1423
+ if (key.name === "right") {
1424
+ session.currentTextCursorIndex = Math.min(current.length, session.currentTextCursorIndex + 1);
1425
+ this.renderFormMultilineInput(current);
1426
+ this.requestRender();
1427
+ return;
1428
+ }
1429
+ if (key.name === "home") {
1430
+ const { line } = textIndexToLineColumn(current, session.currentTextCursorIndex);
1431
+ session.currentTextCursorIndex = textLineColumnToIndex(current, line, 0);
1432
+ this.renderFormMultilineInput(current);
1433
+ this.requestRender();
1434
+ return;
1435
+ }
1436
+ if (key.name === "end") {
1437
+ const { line } = textIndexToLineColumn(current, session.currentTextCursorIndex);
1438
+ const lineText = current.split("\n")[line] ?? "";
1439
+ session.currentTextCursorIndex = textLineColumnToIndex(current, line, lineText.length);
1440
+ this.renderFormMultilineInput(current);
1441
+ this.requestRender();
1442
+ return;
1443
+ }
1444
+ if (key.name === "up") {
1445
+ const { line, column } = textIndexToLineColumn(current, session.currentTextCursorIndex);
1446
+ session.currentTextCursorIndex = textLineColumnToIndex(current, Math.max(0, line - 1), column);
1447
+ this.renderFormMultilineInput(current);
1448
+ this.requestRender();
1449
+ return;
1450
+ }
1451
+ if (key.name === "down") {
1452
+ const { line, column } = textIndexToLineColumn(current, session.currentTextCursorIndex);
1453
+ session.currentTextCursorIndex = textLineColumnToIndex(current, line + 1, column);
1454
+ this.renderFormMultilineInput(current);
1455
+ this.requestRender();
970
1456
  return;
971
1457
  }
972
- this.appendActiveFormText(ch, key);
1458
+ if (key.name === "backspace") {
1459
+ if (session.currentTextCursorIndex > 0) {
1460
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex - 1)}${current.slice(session.currentTextCursorIndex)}`;
1461
+ session.currentTextCursorIndex -= 1;
1462
+ session.values[field.id] = nextValue;
1463
+ this.renderFormMultilineInput(nextValue);
1464
+ this.requestRender();
1465
+ }
1466
+ return;
1467
+ }
1468
+ if (key.name === "delete") {
1469
+ if (session.currentTextCursorIndex < current.length) {
1470
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex)}${current.slice(session.currentTextCursorIndex + 1)}`;
1471
+ session.values[field.id] = nextValue;
1472
+ this.renderFormMultilineInput(nextValue);
1473
+ this.requestRender();
1474
+ }
1475
+ return;
1476
+ }
1477
+ if (ch && !key.ctrl && !key.meta && !/^[\x00-\x1f\x7f]$/.test(ch)) {
1478
+ const nextValue = `${current.slice(0, session.currentTextCursorIndex)}${ch}${current.slice(session.currentTextCursorIndex)}`;
1479
+ session.currentTextCursorIndex += ch.length;
1480
+ session.values[field.id] = nextValue;
1481
+ this.renderFormMultilineInput(nextValue);
1482
+ this.requestRender();
1483
+ }
973
1484
  return;
974
1485
  }
975
1486
  if (field.type === "boolean") {
@@ -978,27 +1489,28 @@ export class InteractiveUi {
978
1489
  return;
979
1490
  }
980
1491
  if (key.name === "enter") {
981
- this.moveActiveFormField(1);
1492
+ this.confirmActiveFormField();
982
1493
  }
983
1494
  return;
984
1495
  }
985
1496
  if (key.name === "up") {
986
- this.moveActiveFormOption(-1);
1497
+ this.syncActiveSelectFieldValue();
1498
+ this.requestRender();
987
1499
  return;
988
1500
  }
989
1501
  if (key.name === "down") {
990
- this.moveActiveFormOption(1);
1502
+ this.syncActiveSelectFieldValue();
1503
+ this.requestRender();
991
1504
  return;
992
1505
  }
993
1506
  if (key.name === "space") {
1507
+ this.syncActiveSelectFieldValue();
994
1508
  this.toggleActiveFormValue();
995
1509
  return;
996
1510
  }
997
1511
  if (key.name === "enter") {
998
- if (field.type === "single-select") {
999
- this.toggleActiveFormValue();
1000
- }
1001
- this.moveActiveFormField(1);
1512
+ this.syncActiveSelectFieldValue();
1513
+ this.confirmActiveFormField();
1002
1514
  }
1003
1515
  }
1004
1516
  renderDescription() {
@@ -1443,11 +1955,20 @@ export class InteractiveUi {
1443
1955
  });
1444
1956
  }
1445
1957
  return new Promise((resolve, reject) => {
1958
+ const values = buildInitialUserInputValues(form.fields);
1959
+ const firstField = form.fields[0];
1960
+ const initialCursorIndex = firstField?.type === "text" ? String(values[firstField.id] ?? "").length : 0;
1961
+ const initialOptionIndex = firstField?.type === "single-select"
1962
+ ? Math.max(0, firstField.options.findIndex((option) => option.value === String(values[firstField.id] ?? "")))
1963
+ : firstField?.type === "multi-select"
1964
+ ? Math.max(0, firstField.options.findIndex((option) => Array.isArray(values[firstField.id]) && values[firstField.id].includes(option.value)))
1965
+ : 0;
1446
1966
  this.activeFormSession = {
1447
1967
  form,
1448
- values: buildInitialUserInputValues(form.fields),
1968
+ values,
1449
1969
  currentFieldIndex: 0,
1450
- currentOptionIndex: 0,
1970
+ currentOptionIndex: initialOptionIndex,
1971
+ currentTextCursorIndex: initialCursorIndex,
1451
1972
  resolve,
1452
1973
  reject,
1453
1974
  };