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.
- package/README.md +218 -224
- package/dist/artifacts.js +109 -55
- package/dist/executors/{codex-local-executor.js → codex-executor.js} +6 -5
- package/dist/executors/configs/{codex-local-config.js → codex-config.js} +1 -1
- package/dist/executors/configs/jira-fetch-config.js +2 -0
- package/dist/executors/configs/telegram-notifier-config.js +3 -0
- package/dist/executors/fetch-gitlab-diff-executor.js +1 -1
- package/dist/executors/fetch-gitlab-review-executor.js +1 -1
- package/dist/executors/git-commit-executor.js +25 -0
- package/dist/executors/jira-fetch-executor.js +1 -0
- package/dist/executors/opencode-executor.js +22 -11
- package/dist/executors/process-executor.js +3 -0
- package/dist/executors/telegram-notifier-executor.js +54 -0
- package/dist/flow-state.js +46 -1
- package/dist/gitlab.js +13 -8
- package/dist/index.js +477 -514
- package/dist/interactive-ui.js +609 -88
- package/dist/jira.js +109 -5
- package/dist/pipeline/auto-flow.js +6 -6
- package/dist/pipeline/context.js +1 -0
- package/dist/pipeline/flow-catalog.js +34 -4
- package/dist/pipeline/flow-model-settings.js +77 -0
- package/dist/pipeline/flow-specs/auto-common.json +446 -0
- package/dist/pipeline/flow-specs/auto-golang.json +563 -0
- package/dist/pipeline/flow-specs/{bug-analyze.json → bugz/bug-analyze.json} +43 -25
- package/dist/pipeline/flow-specs/{bug-fix.json → bugz/bug-fix.json} +5 -4
- package/dist/pipeline/flow-specs/git-commit.json +196 -0
- package/dist/pipeline/flow-specs/{gitlab-diff-review.json → gitlab/gitlab-diff-review.json} +20 -50
- package/dist/pipeline/flow-specs/{gitlab-review.json → gitlab/gitlab-review.json} +65 -133
- package/dist/pipeline/flow-specs/{mr-description.json → gitlab/mr-description.json} +17 -10
- package/dist/pipeline/flow-specs/{run-go-linter-loop.json → go/run-go-linter-loop.json} +40 -14
- package/dist/pipeline/flow-specs/{run-go-tests-loop.json → go/run-go-tests-loop.json} +40 -14
- package/dist/pipeline/flow-specs/implement.json +5 -4
- package/dist/pipeline/flow-specs/plan.json +40 -148
- package/dist/pipeline/flow-specs/{review-fix.json → review/review-fix.json} +74 -13
- package/dist/pipeline/flow-specs/review/review-loop.json +282 -0
- package/dist/pipeline/flow-specs/review/review-project.json +87 -0
- package/dist/pipeline/flow-specs/review/review.json +126 -0
- package/dist/pipeline/flow-specs/task-describe.json +252 -11
- package/dist/pipeline/launch-profile-config.js +38 -0
- package/dist/pipeline/node-registry.js +75 -45
- package/dist/pipeline/nodes/build-failure-summary-node.js +16 -29
- package/dist/pipeline/nodes/build-review-fix-prompt-node.js +36 -0
- package/dist/pipeline/nodes/codex-prompt-node.js +41 -0
- package/dist/pipeline/nodes/commit-message-form-node.js +79 -0
- package/dist/pipeline/nodes/git-commit-form-node.js +138 -0
- package/dist/pipeline/nodes/git-commit-node.js +28 -0
- package/dist/pipeline/nodes/git-status-node.js +221 -0
- package/dist/pipeline/nodes/gitlab-review-artifacts-node.js +10 -6
- package/dist/pipeline/nodes/jira-context-node.js +10 -0
- package/dist/pipeline/nodes/jira-fetch-node.js +3 -0
- package/dist/pipeline/nodes/llm-prompt-node.js +62 -0
- package/dist/pipeline/nodes/plan-codex-node.js +1 -1
- package/dist/pipeline/nodes/read-file-node.js +11 -0
- package/dist/pipeline/nodes/review-findings-form-node.js +48 -14
- package/dist/pipeline/nodes/select-files-form-node.js +72 -0
- package/dist/pipeline/nodes/telegram-notifier-node.js +28 -0
- package/dist/pipeline/nodes/user-input-node.js +43 -8
- package/dist/pipeline/nodes/write-selection-file-node.js +46 -0
- package/dist/pipeline/prompt-registry.js +3 -4
- package/dist/pipeline/prompt-runtime.js +13 -3
- package/dist/pipeline/registry.js +6 -8
- package/dist/pipeline/spec-compiler.js +5 -0
- package/dist/pipeline/spec-types.js +9 -3
- package/dist/pipeline/spec-validator.js +4 -0
- package/dist/pipeline/types.js +1 -0
- package/dist/pipeline/value-resolver.js +50 -38
- package/dist/prompts.js +119 -110
- package/dist/runtime/agentweaver-home.js +8 -0
- package/dist/runtime/command-resolution.js +0 -38
- package/dist/runtime/env-loader.js +43 -0
- package/dist/runtime/process-runner.js +9 -3
- package/dist/structured-artifact-schema-registry.js +54 -0
- package/dist/structured-artifact-schemas.json +22 -20
- package/dist/structured-artifacts.js +3 -43
- package/dist/user-input.js +38 -3
- package/package.json +2 -6
- package/Dockerfile.codex +0 -56
- package/dist/executors/claude-executor.js +0 -46
- package/dist/executors/codex-docker-executor.js +0 -27
- package/dist/executors/configs/claude-config.js +0 -12
- package/dist/executors/configs/codex-docker-config.js +0 -10
- package/dist/executors/configs/verify-build-config.js +0 -7
- package/dist/executors/verify-build-executor.js +0 -123
- package/dist/pipeline/flow-specs/auto.json +0 -979
- package/dist/pipeline/flow-specs/opencode/auto-opencode.json +0 -1365
- package/dist/pipeline/flow-specs/opencode/bugz/bug-analyze-opencode.json +0 -382
- package/dist/pipeline/flow-specs/opencode/bugz/bug-fix-opencode.json +0 -56
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-diff-review-opencode.json +0 -308
- package/dist/pipeline/flow-specs/opencode/gitlab/gitlab-review-opencode.json +0 -437
- package/dist/pipeline/flow-specs/opencode/gitlab/mr-description-opencode.json +0 -117
- package/dist/pipeline/flow-specs/opencode/go/run-go-linter-loop-opencode.json +0 -321
- package/dist/pipeline/flow-specs/opencode/go/run-go-tests-loop-opencode.json +0 -321
- package/dist/pipeline/flow-specs/opencode/implement-opencode.json +0 -64
- package/dist/pipeline/flow-specs/opencode/plan-opencode.json +0 -603
- package/dist/pipeline/flow-specs/opencode/review/review-fix-opencode.json +0 -209
- package/dist/pipeline/flow-specs/opencode/review/review-opencode.json +0 -452
- package/dist/pipeline/flow-specs/opencode/task-describe-opencode.json +0 -148
- package/dist/pipeline/flow-specs/review-project.json +0 -243
- package/dist/pipeline/flow-specs/review.json +0 -312
- package/dist/pipeline/flows/preflight-flow.js +0 -19
- package/dist/pipeline/nodes/claude-prompt-node.js +0 -54
- package/dist/pipeline/nodes/codex-docker-prompt-node.js +0 -32
- package/dist/pipeline/nodes/codex-local-prompt-node.js +0 -32
- package/dist/pipeline/nodes/review-claude-node.js +0 -38
- package/dist/pipeline/nodes/review-reply-codex-node.js +0 -40
- package/dist/pipeline/nodes/verify-build-node.js +0 -15
- package/dist/runtime/docker-runtime.js +0 -51
- package/docker-compose.yml +0 -445
- package/verify_build.sh +0 -105
package/dist/interactive-ui.js
CHANGED
|
@@ -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
|
-
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
const
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
|
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
|
-
|
|
777
|
-
|
|
895
|
+
headerLines.push("");
|
|
896
|
+
headerLines.push(session.form.description.trim());
|
|
778
897
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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
|
-
|
|
902
|
+
headerLines.push(field.help.trim());
|
|
784
903
|
}
|
|
785
|
-
|
|
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
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
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.
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
-
|
|
812
|
-
if (option.description?.trim()) {
|
|
813
|
-
lines.push(` {gray-fg}${option.description.trim()}{/gray-fg}`);
|
|
814
|
-
}
|
|
974
|
+
return `${marker} ${option.label}`;
|
|
815
975
|
});
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
lines.push(
|
|
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
|
-
|
|
829
|
-
|
|
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.
|
|
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
|
-
|
|
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 (!
|
|
1200
|
+
if (!session || !field || field.type !== "text") {
|
|
844
1201
|
return;
|
|
845
1202
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
this.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
1492
|
+
this.confirmActiveFormField();
|
|
982
1493
|
}
|
|
983
1494
|
return;
|
|
984
1495
|
}
|
|
985
1496
|
if (key.name === "up") {
|
|
986
|
-
this.
|
|
1497
|
+
this.syncActiveSelectFieldValue();
|
|
1498
|
+
this.requestRender();
|
|
987
1499
|
return;
|
|
988
1500
|
}
|
|
989
1501
|
if (key.name === "down") {
|
|
990
|
-
this.
|
|
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
|
-
|
|
999
|
-
|
|
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
|
|
1968
|
+
values,
|
|
1449
1969
|
currentFieldIndex: 0,
|
|
1450
|
-
currentOptionIndex:
|
|
1970
|
+
currentOptionIndex: initialOptionIndex,
|
|
1971
|
+
currentTextCursorIndex: initialCursorIndex,
|
|
1451
1972
|
resolve,
|
|
1452
1973
|
reject,
|
|
1453
1974
|
};
|