@selesai/code 0.5.26 → 0.5.28
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.
|
@@ -1144,6 +1144,42 @@ test("terminal split keyboard scroll supports Pi page aliases and preserves app
|
|
|
1144
1144
|
compositor.dispose();
|
|
1145
1145
|
});
|
|
1146
1146
|
|
|
1147
|
+
test("terminal split leaves PgUp/PgDn to a focused custom view", () => {
|
|
1148
|
+
const terminal = new FakeTerminal();
|
|
1149
|
+
let ownsKeyboardScroll = true;
|
|
1150
|
+
let inputListener: ((data: string) => { consume?: boolean; data?: string } | undefined) | null = null;
|
|
1151
|
+
const tui = {
|
|
1152
|
+
terminal,
|
|
1153
|
+
addInputListener(listener: (data: string) => { consume?: boolean; data?: string } | undefined) {
|
|
1154
|
+
inputListener = listener;
|
|
1155
|
+
return () => {
|
|
1156
|
+
inputListener = null;
|
|
1157
|
+
};
|
|
1158
|
+
},
|
|
1159
|
+
requestRender() {},
|
|
1160
|
+
render() {
|
|
1161
|
+
return Array.from({ length: 30 }, (_, index) => `line-${index}`);
|
|
1162
|
+
},
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
const compositor = new TerminalSplitCompositor({
|
|
1166
|
+
tui,
|
|
1167
|
+
terminal,
|
|
1168
|
+
shouldCaptureKeyboardScroll: () => ownsKeyboardScroll,
|
|
1169
|
+
renderCluster: () => ({ lines: ["cluster-a", "cluster-b"], cursor: null }),
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
compositor.install();
|
|
1173
|
+
tui.render();
|
|
1174
|
+
|
|
1175
|
+
assert.deepEqual(inputListener?.("\x1b[5~"), { consume: true });
|
|
1176
|
+
ownsKeyboardScroll = false;
|
|
1177
|
+
assert.equal(inputListener?.("\x1b[5~"), undefined);
|
|
1178
|
+
assert.equal(inputListener?.("\x1b[6~"), undefined);
|
|
1179
|
+
|
|
1180
|
+
compositor.dispose();
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1147
1183
|
test("terminal split keyboard scroll accepts configured shortcuts", () => {
|
|
1148
1184
|
const terminal = new FakeTerminal();
|
|
1149
1185
|
const renderRequests: Array<boolean | undefined> = [];
|
|
@@ -92,6 +92,7 @@ class QuestionComponent extends Container implements Component {
|
|
|
92
92
|
private theme: Theme,
|
|
93
93
|
private keybindings: KeybindingsManager,
|
|
94
94
|
private onCancel: () => void,
|
|
95
|
+
private onCommit: () => void,
|
|
95
96
|
initialResponse?: QuestionResponse,
|
|
96
97
|
) {
|
|
97
98
|
super();
|
|
@@ -177,6 +178,7 @@ class QuestionComponent extends Container implements Component {
|
|
|
177
178
|
if (!multi) this.otherDraft = "";
|
|
178
179
|
this.updateAnswerText();
|
|
179
180
|
this.tui.requestRender();
|
|
181
|
+
if (this.getResponse()) this.onCommit();
|
|
180
182
|
};
|
|
181
183
|
list.onEnterFreeform = () => this.showOther();
|
|
182
184
|
list.onCancel = this.onCancel;
|
|
@@ -195,6 +197,7 @@ class QuestionComponent extends Container implements Component {
|
|
|
195
197
|
editor.setText(text.trim());
|
|
196
198
|
this.updateAnswerText();
|
|
197
199
|
this.tui.requestRender();
|
|
200
|
+
if (this.getResponse()) this.onCommit();
|
|
198
201
|
};
|
|
199
202
|
this.editor = editor;
|
|
200
203
|
return editor;
|
|
@@ -430,7 +433,7 @@ class BatchQuestionComponent extends Container implements Component {
|
|
|
430
433
|
} else {
|
|
431
434
|
const question = this.questions[this.currentPage]!;
|
|
432
435
|
const state = this.states.get(question.id);
|
|
433
|
-
this.questionComponent = new QuestionComponent(question, this.tui, this.theme, this.keybindings, this.cancel, state?.response);
|
|
436
|
+
this.questionComponent = new QuestionComponent(question, this.tui, this.theme, this.keybindings, this.cancel, this.commitCurrent, state?.response);
|
|
434
437
|
this.questionComponent.focused = this.focusedState;
|
|
435
438
|
this.content.addChild(this.questionComponent);
|
|
436
439
|
}
|
|
@@ -452,6 +455,16 @@ class BatchQuestionComponent extends Container implements Component {
|
|
|
452
455
|
this.showPage();
|
|
453
456
|
}
|
|
454
457
|
|
|
458
|
+
private commitCurrent = (): void => {
|
|
459
|
+
this.saveCurrent(true);
|
|
460
|
+
if (this.questions.length === 1) {
|
|
461
|
+
this.onDone({ status: "submitted", answers: buildQuestionAnswers(this.questions, this.states) });
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (this.currentPage < this.questions.length) this.currentPage++;
|
|
465
|
+
this.showPage();
|
|
466
|
+
};
|
|
467
|
+
|
|
455
468
|
private goPrevious(): void {
|
|
456
469
|
this.saveCurrent(false);
|
|
457
470
|
if (this.currentPage > 0) this.currentPage--;
|
|
@@ -52,15 +52,13 @@ test("wizard submits typed selections and text without review comments", async (
|
|
|
52
52
|
{ id: "note", type: "text", question: "Explain" },
|
|
53
53
|
],
|
|
54
54
|
});
|
|
55
|
-
component().handleInput(ENTER);
|
|
56
|
-
component().handleInput(TAB);
|
|
55
|
+
component().handleInput(ENTER); // choice commits and advances
|
|
57
56
|
component().handleInput(" ");
|
|
58
57
|
component().handleInput(DOWN);
|
|
59
58
|
component().handleInput(" ");
|
|
60
|
-
component().handleInput(
|
|
59
|
+
component().handleInput(ENTER); // multi-select commits and advances
|
|
61
60
|
type(component(), "details");
|
|
62
|
-
component().handleInput(
|
|
63
|
-
component().handleInput(TAB);
|
|
61
|
+
component().handleInput(ENTER); // text commits and advances to review
|
|
64
62
|
assert.doesNotMatch(component().render(120).join("\n"), /Optional comment/);
|
|
65
63
|
component().handleInput(ENTER);
|
|
66
64
|
|
|
@@ -85,14 +83,19 @@ test("wizard preserves skipped, Other, and cancelled outcomes", async () => {
|
|
|
85
83
|
other.component().handleInput(DOWN);
|
|
86
84
|
other.component().handleInput(ENTER);
|
|
87
85
|
type(other.component(), "custom");
|
|
88
|
-
other.component().handleInput(
|
|
89
|
-
other.component().handleInput(TAB);
|
|
90
|
-
other.component().handleInput(ENTER);
|
|
86
|
+
other.component().handleInput(ENTER); // one answered question submits immediately
|
|
91
87
|
assert.deepEqual((await other.execution).details, {
|
|
92
88
|
status: "submitted",
|
|
93
89
|
answers: [{ id: "q1", status: "answered", response: { kind: "selection", values: [], otherText: "custom" } }],
|
|
94
90
|
});
|
|
95
91
|
|
|
92
|
+
const selected = await runWizard({ questions: [{ type: "select", question: "Select", options: [{ value: "x", label: "X" }] }] });
|
|
93
|
+
selected.component().handleInput(ENTER);
|
|
94
|
+
assert.deepEqual((await selected.execution).details, {
|
|
95
|
+
status: "submitted",
|
|
96
|
+
answers: [{ id: "q1", status: "answered", response: { kind: "selection", values: ["x"] } }],
|
|
97
|
+
});
|
|
98
|
+
|
|
96
99
|
const cancelled = await runWizard({ questions: [{ type: "select", question: "Cancel", options: [{ value: "x", label: "X" }] }] });
|
|
97
100
|
cancelled.component().handleInput(ESC);
|
|
98
101
|
assert.deepEqual((await cancelled.execution).details, { status: "cancelled", reason: "user" });
|
|
@@ -8,7 +8,9 @@ Interview the user relentlessly until you reach a shared understanding. Map this
|
|
|
8
8
|
|
|
9
9
|
Work the tree in **rounds**. The **frontier** is every decision whose prerequisites are already settled — the questions you can ask *now* without guessing at answers you haven't heard yet.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Before asking a frontier, normalize it by intent. Remove semantic duplicates and merge items that ask for the same decision into one concise parent question; when compatible, combine their choices into one `select` or `multiselect`. Different wording, examples, or context do not make a new decision. Keep questions separate only when the user could reasonably answer them differently and each answer must be recorded independently.
|
|
12
|
+
|
|
13
|
+
For every normalized, non-empty frontier, call the `question` tool **exactly once** with its `questions` array; never print frontier questions as plain chat text. Give every item a stable `id`, short `label`, one-sentence `question`, and `context` containing the recommended answer and why in at most two short sentences. Do not restate the question or option labels in `context`. Provide options when known and set `allowOther: true` when the user may need another answer. The batch UI lets the user revisit pages before pressing Enter on its final review page to submit all answers. Wait for that tool result, map returned `answers` by `id`, then recompute the next frontier.
|
|
12
14
|
|
|
13
15
|
Each round the user answers reshapes the tree — settled decisions push the frontier outward and unblock questions that depended on them. Recompute the frontier and ask the next round. A question whose answer depends on another question still open in this round belongs to a *later* round, not this one.
|
|
14
16
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@selesai/code",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.28",
|
|
4
4
|
"description": "Maintained, extension-first Pi coding agent with built-in workflows, subagents, web research, questions, skills, and an enhanced terminal UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|