@agents-ensemble/cli 0.12.0 → 0.14.0
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 +3 -0
- package/dist/format-session-summary.d.ts.map +1 -1
- package/dist/format-session-summary.js +74 -7
- package/dist/format-session-summary.js.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/tui/format-operator-context.d.ts +2 -3
- package/dist/tui/format-operator-context.d.ts.map +1 -1
- package/dist/tui/format-operator-context.js +6 -12
- package/dist/tui/format-operator-context.js.map +1 -1
- package/dist/tui/ink-test-keys.d.ts +1 -0
- package/dist/tui/ink-test-keys.d.ts.map +1 -1
- package/dist/tui/ink-test-keys.js +1 -0
- package/dist/tui/ink-test-keys.js.map +1 -1
- package/dist/tui/issue-session-tui-stream-cursor.test.js +58 -11
- package/dist/tui/issue-session-tui-stream-cursor.test.js.map +1 -1
- package/dist/tui/issue-session-tui-stream.d.ts.map +1 -1
- package/dist/tui/issue-session-tui-stream.js +86 -7
- package/dist/tui/issue-session-tui-stream.js.map +1 -1
- package/dist/tui/issue-session-tui-stream.test.js +147 -21
- package/dist/tui/issue-session-tui-stream.test.js.map +1 -1
- package/dist/tui/issue-session-tui.d.ts.map +1 -1
- package/dist/tui/issue-session-tui.js +90 -9
- package/dist/tui/issue-session-tui.js.map +1 -1
- package/dist/tui/issue-session-tui.test.js +280 -54
- package/dist/tui/issue-session-tui.test.js.map +1 -1
- package/dist/tui/open-question-answer-suggestions.d.ts +19 -0
- package/dist/tui/open-question-answer-suggestions.d.ts.map +1 -0
- package/dist/tui/open-question-answer-suggestions.js +62 -0
- package/dist/tui/open-question-answer-suggestions.js.map +1 -0
- package/dist/tui/operator-command-suggestions.d.ts +13 -1
- package/dist/tui/operator-command-suggestions.d.ts.map +1 -1
- package/dist/tui/operator-command-suggestions.js +23 -2
- package/dist/tui/operator-command-suggestions.js.map +1 -1
- package/dist/tui/operator-text-area.d.ts +9 -2
- package/dist/tui/operator-text-area.d.ts.map +1 -1
- package/dist/tui/operator-text-area.js +5 -2
- package/dist/tui/operator-text-area.js.map +1 -1
- package/dist/tui/operator-text-area.test-double.d.ts +1 -1
- package/dist/tui/operator-text-area.test-double.d.ts.map +1 -1
- package/dist/tui/operator-text-area.test-double.js +87 -6
- package/dist/tui/operator-text-area.test-double.js.map +1 -1
- package/dist/tui/operator-text-area.test-stub.d.ts +5 -1
- package/dist/tui/operator-text-area.test-stub.d.ts.map +1 -1
- package/dist/tui/operator-text-area.test-stub.js +6 -2
- package/dist/tui/operator-text-area.test-stub.js.map +1 -1
- package/dist/tui/operator-text-area.test.js +13 -0
- package/dist/tui/operator-text-area.test.js.map +1 -1
- package/dist/tui/tui-layout-constants.d.ts +3 -5
- package/dist/tui/tui-layout-constants.d.ts.map +1 -1
- package/dist/tui/tui-layout-constants.js +3 -5
- package/dist/tui/tui-layout-constants.js.map +1 -1
- package/dist/tui/worker-display-name.d.ts +2 -0
- package/dist/tui/worker-display-name.d.ts.map +1 -0
- package/dist/tui/worker-display-name.js +4 -0
- package/dist/tui/worker-display-name.js.map +1 -0
- package/package.json +3 -2
|
@@ -6,14 +6,14 @@ import { cleanup, render } from 'ink-testing-library';
|
|
|
6
6
|
import { IssueSessionTui } from './issue-session-tui.js';
|
|
7
7
|
import { createTuiViewModel } from './tui-view-model.js';
|
|
8
8
|
import { buildActivityLogDisplayLines } from './activity-log.js';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveOperatorInputDisplayMode } from './format-operator-context.js';
|
|
10
10
|
import { flushInkStdin, INK_TEST_KEYS } from './ink-test-keys.js';
|
|
11
11
|
import { extractOrchestrationPaneFrameStats, extractTuiPaneFrameStats } from './orchestration-pane-frame.js';
|
|
12
12
|
import { resolveOpenQuestionsPaneLayout } from './open-questions-pane.js';
|
|
13
13
|
import { computeActivityLogLineCapacity, computeActivityPaneHeight, computeInputPaneHeight, computeOrchestrationLogVisibleLineCount, computeOperatorInputCursorX, computeOperatorInputLineIndex, } from './compute-operator-input-cursor-y.js';
|
|
14
|
-
import { getPaneContentWidth
|
|
14
|
+
import { getPaneContentWidth } from './wrap-text-to-width.js';
|
|
15
15
|
import { resolvePaneHeights } from './pane-layout.js';
|
|
16
|
-
import { INPUT_PANE_TITLE, MAIN_PANE_TITLE, OPEN_QUESTIONS_PANE_MIN_HEIGHT,
|
|
16
|
+
import { INPUT_PANE_TITLE, MAIN_PANE_TITLE, OPEN_QUESTIONS_PANE_MIN_HEIGHT, OPERATOR_INPUT_PROMPT, OPERATOR_INPUT_CURSOR_Y_OFFSET, PANE_PADDING_X, ROUND_BORDER_WIDTH, WORKER_PANE_HEIGHT, WORKER_PANE_TITLE, } from './tui-layout-constants.js';
|
|
17
17
|
import { createTuiResizeController, createTuiTerminalSizeStore, TUI_RESIZE_SETTLE_MS, TUI_RESIZE_SHRINK_COALESCE_MS, } from './tui-terminal-size.js';
|
|
18
18
|
class ResizeSource extends EventEmitter {
|
|
19
19
|
columns = 80;
|
|
@@ -21,12 +21,12 @@ class ResizeSource extends EventEmitter {
|
|
|
21
21
|
}
|
|
22
22
|
function findOperatorInputLine(lines) {
|
|
23
23
|
const operatorLineIndices = lines
|
|
24
|
-
.map((line, index) => (line.includes(
|
|
24
|
+
.map((line, index) => (line.includes(OPERATOR_INPUT_PROMPT) ? index : -1))
|
|
25
25
|
.filter((index) => index >= 0);
|
|
26
26
|
const lineIndex = operatorLineIndices.at(-1) ?? -1;
|
|
27
27
|
const line = lines[lineIndex] ?? '';
|
|
28
|
-
const promptIndex = line.lastIndexOf(
|
|
29
|
-
const inputStartX = promptIndex >= 0 ? promptIndex +
|
|
28
|
+
const promptIndex = line.lastIndexOf(OPERATOR_INPUT_PROMPT);
|
|
29
|
+
const inputStartX = promptIndex >= 0 ? promptIndex + OPERATOR_INPUT_PROMPT.length : -1;
|
|
30
30
|
return { lineIndex, inputStartX };
|
|
31
31
|
}
|
|
32
32
|
function expectNoContentOnBorderLines(frame) {
|
|
@@ -34,6 +34,7 @@ function expectNoContentOnBorderLines(frame) {
|
|
|
34
34
|
if (line.startsWith('╭') || line.startsWith('╰') || line.startsWith('┌') || line.startsWith('└')) {
|
|
35
35
|
expect(line).not.toMatch(/\[(?:operator|conductor|implementer|reviewer)\]/);
|
|
36
36
|
expect(line).not.toMatch(/operator>/);
|
|
37
|
+
expect(line).not.toContain(OPERATOR_INPUT_PROMPT);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -96,17 +97,214 @@ describe('IssueSessionTui', () => {
|
|
|
96
97
|
stdin.write('/');
|
|
97
98
|
await flushInkStdin();
|
|
98
99
|
const allCommandsFrame = lastFrame() ?? '';
|
|
100
|
+
expect(allCommandsFrame).toContain(`${OPERATOR_INPUT_PROMPT}/`);
|
|
101
|
+
expect(allCommandsFrame).not.toContain('operator> ');
|
|
99
102
|
expect(allCommandsFrame).toContain('/reconnect — conductor を再接続');
|
|
100
103
|
expect(allCommandsFrame).toContain('/exit — セッションを終了');
|
|
101
104
|
stdin.write('ex');
|
|
102
105
|
await flushInkStdin();
|
|
103
106
|
const filteredFrame = lastFrame() ?? '';
|
|
107
|
+
expect(filteredFrame).toContain(`${OPERATOR_INPUT_PROMPT}/ex`);
|
|
104
108
|
expect(filteredFrame).toContain('/exit — セッションを終了');
|
|
105
109
|
expect(filteredFrame).not.toContain('/reconnect — conductor を再接続');
|
|
106
110
|
stdin.write('\r');
|
|
107
111
|
await flushInkStdin();
|
|
108
112
|
expect(onSubmit).toHaveBeenCalledWith('/ex', undefined);
|
|
109
113
|
});
|
|
114
|
+
it('sizes the input pane using the shortened prompt width', async () => {
|
|
115
|
+
Object.defineProperty(process.stdout, 'columns', {
|
|
116
|
+
configurable: true,
|
|
117
|
+
value: 20,
|
|
118
|
+
});
|
|
119
|
+
const viewModel = createTuiViewModel();
|
|
120
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
121
|
+
stdin.write('1234567890');
|
|
122
|
+
await flushInkStdin();
|
|
123
|
+
const frame = lastFrame() ?? '';
|
|
124
|
+
expect(frame).toContain(`${OPERATOR_INPUT_PROMPT}1234567890`);
|
|
125
|
+
expect(frame).not.toContain('operator> ');
|
|
126
|
+
});
|
|
127
|
+
it('selects slash suggestions with bounded arrows and completes with Tab before Enter submits', async () => {
|
|
128
|
+
const viewModel = createTuiViewModel();
|
|
129
|
+
const onSubmit = vi.fn();
|
|
130
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
131
|
+
stdin.write('/');
|
|
132
|
+
await flushInkStdin();
|
|
133
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
134
|
+
for (const key of [
|
|
135
|
+
INK_TEST_KEYS.downArrow,
|
|
136
|
+
INK_TEST_KEYS.downArrow,
|
|
137
|
+
]) {
|
|
138
|
+
stdin.write(key);
|
|
139
|
+
await flushInkStdin();
|
|
140
|
+
}
|
|
141
|
+
expect(lastFrame() ?? '').toContain('› /close');
|
|
142
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
143
|
+
await flushInkStdin();
|
|
144
|
+
expect(lastFrame() ?? '').toContain('› /close');
|
|
145
|
+
for (const key of [INK_TEST_KEYS.upArrow, INK_TEST_KEYS.upArrow]) {
|
|
146
|
+
stdin.write(key);
|
|
147
|
+
await flushInkStdin();
|
|
148
|
+
}
|
|
149
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
150
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
151
|
+
await flushInkStdin();
|
|
152
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
153
|
+
for (const key of [
|
|
154
|
+
INK_TEST_KEYS.downArrow,
|
|
155
|
+
INK_TEST_KEYS.downArrow,
|
|
156
|
+
INK_TEST_KEYS.tab,
|
|
157
|
+
]) {
|
|
158
|
+
stdin.write(key);
|
|
159
|
+
await flushInkStdin();
|
|
160
|
+
}
|
|
161
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}/close`);
|
|
162
|
+
expect(lastFrame() ?? '').not.toContain('operator> ');
|
|
163
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
164
|
+
stdin.write('\r');
|
|
165
|
+
await flushInkStdin();
|
|
166
|
+
expect(onSubmit).toHaveBeenCalledWith('/close', undefined);
|
|
167
|
+
});
|
|
168
|
+
it.each([
|
|
169
|
+
{
|
|
170
|
+
name: 'choice',
|
|
171
|
+
question: createOpenQuestion({
|
|
172
|
+
id: 'inq-choice',
|
|
173
|
+
question: 'Choose a mode',
|
|
174
|
+
responseType: 'choice',
|
|
175
|
+
choices: [
|
|
176
|
+
{ id: 'safe', label: 'Safe mode' },
|
|
177
|
+
{ id: 'secure', label: 'Secure mode' },
|
|
178
|
+
{ id: 'fast', label: 'Fast mode' },
|
|
179
|
+
],
|
|
180
|
+
}),
|
|
181
|
+
prefix: 's',
|
|
182
|
+
completedValue: 'secure',
|
|
183
|
+
expectedSuggestion: 'safe: Safe mode',
|
|
184
|
+
hasMultipleSuggestions: true,
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'yes_no',
|
|
188
|
+
question: createOpenQuestion({
|
|
189
|
+
id: 'inq-yes-no',
|
|
190
|
+
question: 'Proceed?',
|
|
191
|
+
responseType: 'yes_no',
|
|
192
|
+
}),
|
|
193
|
+
prefix: 'n',
|
|
194
|
+
completedValue: 'no',
|
|
195
|
+
expectedSuggestion: 'no — いいえ',
|
|
196
|
+
hasMultipleSuggestions: false,
|
|
197
|
+
},
|
|
198
|
+
])('completes and submits the selected $name answer in pane mode', async ({ question, prefix, completedValue, expectedSuggestion, hasMultipleSuggestions, }) => {
|
|
199
|
+
const viewModel = createTuiViewModel();
|
|
200
|
+
viewModel.setDisplayState({
|
|
201
|
+
workers: {},
|
|
202
|
+
conductorOutput: null,
|
|
203
|
+
openQuestions: [
|
|
204
|
+
question,
|
|
205
|
+
createOpenQuestion({
|
|
206
|
+
id: 'inq-text',
|
|
207
|
+
question: 'Anything else?',
|
|
208
|
+
}),
|
|
209
|
+
],
|
|
210
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
211
|
+
});
|
|
212
|
+
const onSubmit = vi.fn();
|
|
213
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
214
|
+
stdin.write(prefix);
|
|
215
|
+
await flushInkStdin();
|
|
216
|
+
expect(lastFrame() ?? '').toContain(expectedSuggestion);
|
|
217
|
+
stdin.write(INK_TEST_KEYS.shiftDownArrow);
|
|
218
|
+
await flushInkStdin();
|
|
219
|
+
expect(lastFrame() ?? '').toContain('2/2');
|
|
220
|
+
expect(lastFrame() ?? '').not.toContain(`› ${expectedSuggestion}`);
|
|
221
|
+
stdin.write(INK_TEST_KEYS.shiftUpArrow);
|
|
222
|
+
await flushInkStdin();
|
|
223
|
+
expect(lastFrame() ?? '').toContain('1/2');
|
|
224
|
+
expect(lastFrame() ?? '').toContain(expectedSuggestion);
|
|
225
|
+
if (hasMultipleSuggestions) {
|
|
226
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
227
|
+
await flushInkStdin();
|
|
228
|
+
expect(lastFrame() ?? '').toContain('› secure: Secure mode');
|
|
229
|
+
}
|
|
230
|
+
stdin.write('\r');
|
|
231
|
+
await flushInkStdin();
|
|
232
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
233
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}${completedValue}`);
|
|
234
|
+
expect(lastFrame() ?? '').not.toContain(`› ${completedValue}:`);
|
|
235
|
+
stdin.write(' because it is safer');
|
|
236
|
+
await flushInkStdin();
|
|
237
|
+
stdin.write('\r');
|
|
238
|
+
await flushInkStdin();
|
|
239
|
+
expect(onSubmit).toHaveBeenCalledWith(`${completedValue} because it is safer`, { targetOpenQuestionId: question.id });
|
|
240
|
+
});
|
|
241
|
+
it('prioritizes slash suggestions over answer suggestions in pane mode', async () => {
|
|
242
|
+
const viewModel = createTuiViewModel();
|
|
243
|
+
viewModel.setDisplayState({
|
|
244
|
+
workers: {},
|
|
245
|
+
conductorOutput: null,
|
|
246
|
+
openQuestions: [
|
|
247
|
+
createOpenQuestion({
|
|
248
|
+
id: 'inq-choice',
|
|
249
|
+
question: 'Choose a mode',
|
|
250
|
+
responseType: 'choice',
|
|
251
|
+
choices: [{ id: 'exit', label: 'Exit mode' }],
|
|
252
|
+
}),
|
|
253
|
+
],
|
|
254
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
255
|
+
});
|
|
256
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
257
|
+
stdin.write('/');
|
|
258
|
+
await flushInkStdin();
|
|
259
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
260
|
+
expect(lastFrame() ?? '').not.toContain('› exit: Exit mode');
|
|
261
|
+
});
|
|
262
|
+
it('resets selection to the first matching candidate when the prefix changes', async () => {
|
|
263
|
+
const viewModel = createTuiViewModel();
|
|
264
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
265
|
+
stdin.write('/');
|
|
266
|
+
await flushInkStdin();
|
|
267
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
268
|
+
await flushInkStdin();
|
|
269
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
270
|
+
await flushInkStdin();
|
|
271
|
+
expect(lastFrame() ?? '').toContain('› /close');
|
|
272
|
+
stdin.write('e');
|
|
273
|
+
await flushInkStdin();
|
|
274
|
+
expect(lastFrame() ?? '').toContain('› /exit');
|
|
275
|
+
stdin.write(INK_TEST_KEYS.tab);
|
|
276
|
+
await flushInkStdin();
|
|
277
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}/exit`);
|
|
278
|
+
});
|
|
279
|
+
it('keeps Tab and arrow editing behavior when there is no match or input is multiline', async () => {
|
|
280
|
+
const viewModel = createTuiViewModel();
|
|
281
|
+
const onSubmit = vi.fn();
|
|
282
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
283
|
+
stdin.write('/unknown');
|
|
284
|
+
await flushInkStdin();
|
|
285
|
+
stdin.write(INK_TEST_KEYS.tab);
|
|
286
|
+
await flushInkStdin();
|
|
287
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}/unknown`);
|
|
288
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
289
|
+
stdin.write('\r');
|
|
290
|
+
await flushInkStdin();
|
|
291
|
+
expect(onSubmit).toHaveBeenCalledWith('/unknown', undefined);
|
|
292
|
+
cleanup();
|
|
293
|
+
const secondViewModel = createTuiViewModel();
|
|
294
|
+
const multilineSubmit = vi.fn();
|
|
295
|
+
const multiline = render(_jsx(IssueSessionTui, { viewModel: secondViewModel, onSubmit: multilineSubmit }));
|
|
296
|
+
multiline.stdin.write('\u001B[200~first\nsecond\u001B[201~');
|
|
297
|
+
await flushInkStdin();
|
|
298
|
+
multiline.stdin.write(INK_TEST_KEYS.upArrow);
|
|
299
|
+
await flushInkStdin();
|
|
300
|
+
multiline.stdin.write('X');
|
|
301
|
+
await flushInkStdin();
|
|
302
|
+
expect(multiline.lastFrame() ?? '').toContain('firstX');
|
|
303
|
+
expect(multiline.lastFrame() ?? '').not.toContain('› /');
|
|
304
|
+
multiline.stdin.write('\r');
|
|
305
|
+
await flushInkStdin();
|
|
306
|
+
expect(multilineSubmit).toHaveBeenCalledWith('firstX\nsecond', undefined);
|
|
307
|
+
});
|
|
110
308
|
it('renders four panes with worker, session log, open questions, and input', () => {
|
|
111
309
|
const viewModel = createTuiViewModel();
|
|
112
310
|
viewModel.setDisplayState({
|
|
@@ -139,7 +337,7 @@ describe('IssueSessionTui', () => {
|
|
|
139
337
|
expect(frame).toContain('inq-1');
|
|
140
338
|
expect(frame).toContain('1/1');
|
|
141
339
|
expect(frame).toContain('Shift+↑↓で選択');
|
|
142
|
-
expect(frame).toContain(
|
|
340
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
143
341
|
expect(frame).toContain(INPUT_PANE_TITLE);
|
|
144
342
|
});
|
|
145
343
|
it('shows dispatch hold count in the Workers pane title', () => {
|
|
@@ -164,12 +362,14 @@ describe('IssueSessionTui', () => {
|
|
|
164
362
|
const { lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
165
363
|
expect(lastFrame() ?? '').toContain('conductor dispatch 保留中(1 件)');
|
|
166
364
|
});
|
|
167
|
-
it('
|
|
365
|
+
it('keeps the post-loop input area free of a fixed prompt', () => {
|
|
168
366
|
const viewModel = createTuiViewModel();
|
|
169
367
|
viewModel.setPostLoopWaiting(true);
|
|
170
368
|
const { lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
171
369
|
const frame = lastFrame() ?? '';
|
|
172
|
-
expect(frame).toContain(
|
|
370
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
371
|
+
expect(frame).not.toContain('追加指示');
|
|
372
|
+
expect(frame).not.toContain('/reconnect');
|
|
173
373
|
expect(frame).not.toContain('post-loop 待機中');
|
|
174
374
|
});
|
|
175
375
|
it('shows the issue link in the workers pane header and prompt-only operator input', async () => {
|
|
@@ -188,8 +388,9 @@ describe('IssueSessionTui', () => {
|
|
|
188
388
|
expect(frame).toContain(issueLabel);
|
|
189
389
|
expect(frame).toContain(osc8Open);
|
|
190
390
|
expect(frame).not.toContain(nonCanonicalOsc8Open);
|
|
191
|
-
expect(frame).toContain(
|
|
192
|
-
expect(frame).not.toContain(
|
|
391
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
392
|
+
expect(frame).not.toContain('任意入力');
|
|
393
|
+
expect(frame).not.toContain('/reconnect');
|
|
193
394
|
viewModel.setDisplayState({
|
|
194
395
|
workers: {},
|
|
195
396
|
conductorOutput: null,
|
|
@@ -224,7 +425,9 @@ describe('IssueSessionTui', () => {
|
|
|
224
425
|
expect(lastFrame() ?? '').toContain(issueLabel);
|
|
225
426
|
expect(lastFrame() ?? '').toContain(osc8Open);
|
|
226
427
|
expect(lastFrame() ?? '').not.toContain(nonCanonicalOsc8Open);
|
|
227
|
-
expect(lastFrame() ?? '').toContain(
|
|
428
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
429
|
+
expect(lastFrame() ?? '').not.toContain('追加指示');
|
|
430
|
+
expect(lastFrame() ?? '').not.toContain('/reconnect');
|
|
228
431
|
expect(lastFrame() ?? '').not.toContain('post-loop 待機中');
|
|
229
432
|
viewModel.setShuttingDown(true);
|
|
230
433
|
await flushInkStdin();
|
|
@@ -316,11 +519,9 @@ describe('IssueSessionTui', () => {
|
|
|
316
519
|
await flushInkStdin();
|
|
317
520
|
expectCanonicalIssueUrl();
|
|
318
521
|
const postLoopFrame = (lastFrame() ?? '').replace(/\s+/g, ' ');
|
|
319
|
-
expect(postLoopFrame).toContain(
|
|
320
|
-
expect(postLoopFrame).toContain('
|
|
321
|
-
expect(postLoopFrame).toContain('
|
|
322
|
-
expect(postLoopFrame).toContain('/exit');
|
|
323
|
-
expect(postLoopFrame).toContain('掃除して終了');
|
|
522
|
+
expect(postLoopFrame).toContain(OPERATOR_INPUT_PROMPT);
|
|
523
|
+
expect(postLoopFrame).not.toContain('追加指示');
|
|
524
|
+
expect(postLoopFrame).not.toContain('/reconnect');
|
|
324
525
|
viewModel.setShuttingDown(true);
|
|
325
526
|
await flushInkStdin();
|
|
326
527
|
expectCanonicalIssueUrl();
|
|
@@ -399,14 +600,11 @@ describe('IssueSessionTui', () => {
|
|
|
399
600
|
maxTurns: null,
|
|
400
601
|
openQuestions: [],
|
|
401
602
|
});
|
|
402
|
-
const operatorPrompt =
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
});
|
|
408
|
-
const contextHint = formatOperatorContextHint(viewModel.getSnapshot().operatorContext);
|
|
409
|
-
const hintLineCount = wrapTextToWidth(contextHint, contentWidth).length;
|
|
603
|
+
const operatorPrompt = OPERATOR_INPUT_PROMPT;
|
|
604
|
+
const hintLineCount = resolveOperatorInputDisplayMode({
|
|
605
|
+
openQuestions: viewModel.getSnapshot().operatorContext?.openQuestions ?? [],
|
|
606
|
+
}).hintLines.length;
|
|
607
|
+
expect(hintLineCount).toBe(0);
|
|
410
608
|
const openQuestionsPaneHeight = 0;
|
|
411
609
|
const expectedInputLineIndex = computeOperatorInputLineIndex({
|
|
412
610
|
terminalRows: liveFrameRows,
|
|
@@ -424,7 +622,7 @@ describe('IssueSessionTui', () => {
|
|
|
424
622
|
expect(expectedCursorY).toBe(lineIndex + OPERATOR_INPUT_CURSOR_Y_OFFSET);
|
|
425
623
|
expect(lines).toHaveLength(liveFrameRows);
|
|
426
624
|
});
|
|
427
|
-
it('aligns IME cursor coordinates
|
|
625
|
+
it('aligns IME cursor coordinates without a fixed hint on a narrow terminal', () => {
|
|
428
626
|
const terminalRows = 24;
|
|
429
627
|
const liveFrameRows = terminalRows - 1;
|
|
430
628
|
const terminalColumns = 40;
|
|
@@ -443,14 +641,11 @@ describe('IssueSessionTui', () => {
|
|
|
443
641
|
maxTurns: null,
|
|
444
642
|
openQuestions: [],
|
|
445
643
|
});
|
|
446
|
-
const operatorPrompt =
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
});
|
|
452
|
-
const contextHint = formatOperatorContextHint(viewModel.getSnapshot().operatorContext);
|
|
453
|
-
const hintLineCount = wrapTextToWidth(contextHint, contentWidth).length;
|
|
644
|
+
const operatorPrompt = OPERATOR_INPUT_PROMPT;
|
|
645
|
+
const hintLineCount = resolveOperatorInputDisplayMode({
|
|
646
|
+
openQuestions: viewModel.getSnapshot().operatorContext?.openQuestions ?? [],
|
|
647
|
+
}).hintLines.length;
|
|
648
|
+
expect(hintLineCount).toBe(0);
|
|
454
649
|
const openQuestionsPaneHeight = 0;
|
|
455
650
|
const expectedInputLineIndex = computeOperatorInputLineIndex({
|
|
456
651
|
terminalRows: liveFrameRows,
|
|
@@ -462,7 +657,7 @@ describe('IssueSessionTui', () => {
|
|
|
462
657
|
const { lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
463
658
|
const lines = (lastFrame() ?? '').split('\n');
|
|
464
659
|
const { lineIndex, inputStartX } = findOperatorInputLine(lines);
|
|
465
|
-
expect(hintLineCount).
|
|
660
|
+
expect(hintLineCount).toBe(0);
|
|
466
661
|
expect(lineIndex).toBeGreaterThanOrEqual(0);
|
|
467
662
|
expect(inputStartX).toBe(expectedCursorX);
|
|
468
663
|
expect(lineIndex).toBe(expectedInputLineIndex);
|
|
@@ -482,6 +677,8 @@ describe('IssueSessionTui', () => {
|
|
|
482
677
|
});
|
|
483
678
|
const { lastFrame: thinkingFrame, unmount: unmountThinking } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
484
679
|
expect(thinkingFrame() ?? '').toContain('conductor: thinking');
|
|
680
|
+
expect(thinkingFrame() ?? '').toContain('implementer: idle');
|
|
681
|
+
expect(thinkingFrame() ?? '').not.toContain('implementer (implementer)');
|
|
485
682
|
unmountThinking();
|
|
486
683
|
viewModel.setDisplayState({
|
|
487
684
|
workers: {
|
|
@@ -496,6 +693,33 @@ describe('IssueSessionTui', () => {
|
|
|
496
693
|
expect(idleFrame() ?? '').toContain('conductor: idle');
|
|
497
694
|
expect(idleFrame() ?? '').not.toContain('conductor: thinking');
|
|
498
695
|
});
|
|
696
|
+
it('formats worker names and running activity in the workers pane', () => {
|
|
697
|
+
const viewModel = createTuiViewModel();
|
|
698
|
+
viewModel.setDisplayState({
|
|
699
|
+
workers: {
|
|
700
|
+
conductor: { kind: 'conductor', status: 'running' },
|
|
701
|
+
implementer: {
|
|
702
|
+
kind: 'implementer',
|
|
703
|
+
status: 'running',
|
|
704
|
+
activity: 'calling: Shell',
|
|
705
|
+
},
|
|
706
|
+
impl: {
|
|
707
|
+
kind: 'implementer',
|
|
708
|
+
status: 'running',
|
|
709
|
+
activity: 'editing files',
|
|
710
|
+
},
|
|
711
|
+
},
|
|
712
|
+
conductorOutput: null,
|
|
713
|
+
openQuestions: [],
|
|
714
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
715
|
+
});
|
|
716
|
+
const { lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
717
|
+
const frame = lastFrame() ?? '';
|
|
718
|
+
expect(frame).toContain('conductor: thinking');
|
|
719
|
+
expect(frame).toContain('implementer: running (calling: Shell)');
|
|
720
|
+
expect(frame).not.toContain('implementer (implementer)');
|
|
721
|
+
expect(frame).toContain('impl (implementer): running (editing files)');
|
|
722
|
+
});
|
|
499
723
|
it('does not bleed activity log text onto pane border lines', () => {
|
|
500
724
|
const terminalRows = 24;
|
|
501
725
|
const liveFrameRows = terminalRows - 1;
|
|
@@ -557,7 +781,7 @@ describe('IssueSessionTui', () => {
|
|
|
557
781
|
const frame = lastFrame() ?? '';
|
|
558
782
|
expect(frame.split('\n')).toHaveLength(11);
|
|
559
783
|
expect(Math.max(...frame.split('\n').map((line) => line.trimEnd().length))).toBeLessThanOrEqual(40);
|
|
560
|
-
expect(frame).toContain(
|
|
784
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
561
785
|
terminalSizeStore.dispose();
|
|
562
786
|
});
|
|
563
787
|
it('coalesces staged same-row shrink before redrawing the pane frame', async () => {
|
|
@@ -602,7 +826,7 @@ describe('IssueSessionTui', () => {
|
|
|
602
826
|
expect(finalFrame).not.toBe(initialFrame);
|
|
603
827
|
expect(Math.max(...finalFrame.split('\n').map((line) => line.trimEnd().length))).toBeLessThanOrEqual(60);
|
|
604
828
|
expect(finalFrame).toContain(WORKER_PANE_TITLE);
|
|
605
|
-
expect(finalFrame).toContain(
|
|
829
|
+
expect(finalFrame).toContain(OPERATOR_INPUT_PROMPT);
|
|
606
830
|
}
|
|
607
831
|
finally {
|
|
608
832
|
unsubscribe();
|
|
@@ -632,12 +856,7 @@ describe('IssueSessionTui', () => {
|
|
|
632
856
|
const stats = extractTuiPaneFrameStats(frame, title);
|
|
633
857
|
expect(stats.titleOnBorder).toBe(true);
|
|
634
858
|
}
|
|
635
|
-
const
|
|
636
|
-
columns: 60,
|
|
637
|
-
paddingX: PANE_PADDING_X,
|
|
638
|
-
borderWidth: ROUND_BORDER_WIDTH,
|
|
639
|
-
});
|
|
640
|
-
const hintLineCount = wrapTextToWidth(OPERATOR_INPUT_POST_LOOP_HINT, contentWidth).length;
|
|
859
|
+
const hintLineCount = 0;
|
|
641
860
|
const requestedInputPaneHeight = computeInputPaneHeight({
|
|
642
861
|
hintLineCount,
|
|
643
862
|
inputDisplayLineCount: 1,
|
|
@@ -649,10 +868,10 @@ describe('IssueSessionTui', () => {
|
|
|
649
868
|
inputPaneHeight: requestedInputPaneHeight,
|
|
650
869
|
});
|
|
651
870
|
expect(paneHeights).toEqual({
|
|
652
|
-
workerPaneHeight:
|
|
871
|
+
workerPaneHeight: 5,
|
|
653
872
|
activityPaneHeight: 3,
|
|
654
873
|
openQuestionsPaneHeight: 0,
|
|
655
|
-
inputPaneHeight:
|
|
874
|
+
inputPaneHeight: 3,
|
|
656
875
|
});
|
|
657
876
|
const inputLine = findOperatorInputLine(lines);
|
|
658
877
|
expect(inputLine.lineIndex).toBe(computeOperatorInputLineIndex({
|
|
@@ -667,7 +886,8 @@ describe('IssueSessionTui', () => {
|
|
|
667
886
|
expect(inputLine.lineIndex).toBeLessThan(lines.length);
|
|
668
887
|
expect(inputLine.inputStartX).toBeGreaterThanOrEqual(0);
|
|
669
888
|
expect(frame).toContain('Orchestration');
|
|
670
|
-
expect(frame).toContain(
|
|
889
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
890
|
+
expect(frame).not.toContain('operator> ');
|
|
671
891
|
}
|
|
672
892
|
finally {
|
|
673
893
|
terminalSizeStore.dispose();
|
|
@@ -681,13 +901,16 @@ describe('IssueSessionTui', () => {
|
|
|
681
901
|
expect(frame).toContain('(活動ログなし)');
|
|
682
902
|
expect(frame).not.toContain('Open questions');
|
|
683
903
|
expect(frame).not.toContain('(未回答なし)');
|
|
684
|
-
expect(frame).toContain(
|
|
904
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
905
|
+
expect(frame).not.toContain('任意入力');
|
|
906
|
+
expect(frame).not.toContain('/reconnect');
|
|
685
907
|
});
|
|
686
908
|
it('switches the lower UI between no-question and question modes', async () => {
|
|
687
909
|
const viewModel = createTuiViewModel();
|
|
688
910
|
const { lastFrame } = render(_jsx(IssueSessionTui, { viewModel: viewModel, onSubmit: () => { } }));
|
|
689
911
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
690
|
-
expect(lastFrame() ?? '').toContain(
|
|
912
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
913
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
691
914
|
viewModel.setDisplayState({
|
|
692
915
|
workers: {},
|
|
693
916
|
conductorOutput: null,
|
|
@@ -697,7 +920,7 @@ describe('IssueSessionTui', () => {
|
|
|
697
920
|
await flushInkStdin();
|
|
698
921
|
expect(lastFrame() ?? '').toContain('Open questions');
|
|
699
922
|
expect(lastFrame() ?? '').toContain('inq-1 (1/1) への回答');
|
|
700
|
-
expect(lastFrame() ?? '').not.toContain('任意入力
|
|
923
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
701
924
|
viewModel.setDisplayState({
|
|
702
925
|
workers: {},
|
|
703
926
|
conductorOutput: null,
|
|
@@ -706,7 +929,8 @@ describe('IssueSessionTui', () => {
|
|
|
706
929
|
});
|
|
707
930
|
await flushInkStdin();
|
|
708
931
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
709
|
-
expect(lastFrame() ?? '').toContain(
|
|
932
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
933
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
710
934
|
});
|
|
711
935
|
it.each([
|
|
712
936
|
[
|
|
@@ -740,7 +964,7 @@ describe('IssueSessionTui', () => {
|
|
|
740
964
|
const initialFrame = lastFrame() ?? '';
|
|
741
965
|
expect(initialFrame).toContain('Open questions');
|
|
742
966
|
expect(initialFrame).toContain('inq-resumed-1');
|
|
743
|
-
expect(initialFrame).not.toContain('任意入力
|
|
967
|
+
expect(initialFrame).not.toContain('任意入力');
|
|
744
968
|
if (selectedIndex === 1) {
|
|
745
969
|
stdin.write(INK_TEST_KEYS.shiftDownArrow);
|
|
746
970
|
await flushInkStdin();
|
|
@@ -754,16 +978,18 @@ describe('IssueSessionTui', () => {
|
|
|
754
978
|
});
|
|
755
979
|
if (restoredQuestions.length === 1) {
|
|
756
980
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
757
|
-
expect(lastFrame() ?? '').toContain(
|
|
981
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
982
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
758
983
|
}
|
|
759
984
|
else {
|
|
760
985
|
expect(lastFrame() ?? '').toContain('Open questions');
|
|
761
986
|
expect(lastFrame() ?? '').toContain('inq-resumed-1');
|
|
762
|
-
expect(lastFrame() ?? '').not.toContain('任意入力
|
|
987
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
763
988
|
setOperatorQuestions([]);
|
|
764
989
|
await flushInkStdin();
|
|
765
990
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
766
|
-
expect(lastFrame() ?? '').toContain(
|
|
991
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
992
|
+
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
767
993
|
}
|
|
768
994
|
});
|
|
769
995
|
it('renders all activity log label kinds with distinct markers', () => {
|