@agents-ensemble/cli 0.13.0 → 0.14.2
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 +9 -3
- package/config.example.yaml +3 -3
- package/dist/format-session-summary.d.ts.map +1 -1
- package/dist/format-session-summary.js +51 -45
- package/dist/format-session-summary.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/session-log-lines.js +1 -1
- package/dist/session-log-lines.js.map +1 -1
- package/dist/tui/activity-log.d.ts +1 -1
- package/dist/tui/activity-log.d.ts.map +1 -1
- package/dist/tui/activity-log.js +16 -2
- package/dist/tui/activity-log.js.map +1 -1
- package/dist/tui/create-issue-session-tui-host.d.ts.map +1 -1
- package/dist/tui/create-issue-session-tui-host.js +1 -1
- package/dist/tui/create-issue-session-tui-host.js.map +1 -1
- package/dist/tui/format-operator-context.d.ts.map +1 -1
- package/dist/tui/format-operator-context.js +3 -3
- package/dist/tui/format-operator-context.js.map +1 -1
- package/dist/tui/ink-test-keys.d.ts +4 -0
- package/dist/tui/ink-test-keys.d.ts.map +1 -1
- package/dist/tui/ink-test-keys.js +4 -0
- package/dist/tui/ink-test-keys.js.map +1 -1
- package/dist/tui/issue-session-tui-stream-cursor.test.js +3 -3
- 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 +133 -15
- package/dist/tui/issue-session-tui-stream.js.map +1 -1
- package/dist/tui/issue-session-tui-stream.test.js +369 -13
- package/dist/tui/issue-session-tui-stream.test.js.map +1 -1
- package/dist/tui/issue-session-tui.d.ts +3 -2
- package/dist/tui/issue-session-tui.d.ts.map +1 -1
- package/dist/tui/issue-session-tui.js +205 -21
- package/dist/tui/issue-session-tui.js.map +1 -1
- package/dist/tui/issue-session-tui.test.js +553 -37
- 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/open-questions-pane.d.ts +19 -0
- package/dist/tui/open-questions-pane.d.ts.map +1 -1
- package/dist/tui/open-questions-pane.js +35 -34
- package/dist/tui/open-questions-pane.js.map +1 -1
- package/dist/tui/operator-command-suggestions.d.ts +15 -0
- package/dist/tui/operator-command-suggestions.d.ts.map +1 -1
- package/dist/tui/operator-input-history.d.ts +13 -0
- package/dist/tui/operator-input-history.d.ts.map +1 -0
- package/dist/tui/operator-input-history.js +51 -0
- package/dist/tui/operator-input-history.js.map +1 -0
- package/dist/tui/operator-input-layout.d.ts +2 -0
- package/dist/tui/operator-input-layout.d.ts.map +1 -1
- package/dist/tui/operator-input-layout.js +73 -0
- package/dist/tui/operator-input-layout.js.map +1 -1
- package/dist/tui/operator-text-area.d.ts +6 -4
- package/dist/tui/operator-text-area.d.ts.map +1 -1
- package/dist/tui/operator-text-area.js +17 -5
- 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 +15 -14
- package/dist/tui/operator-text-area.test-double.js.map +1 -1
- package/dist/tui/operator-text-area.test.js +17 -3
- package/dist/tui/operator-text-area.test.js.map +1 -1
- package/dist/tui/pane-layout.d.ts +2 -0
- package/dist/tui/pane-layout.d.ts.map +1 -1
- package/dist/tui/pane-layout.js +7 -3
- package/dist/tui/pane-layout.js.map +1 -1
- package/dist/tui/stream-layout.d.ts +3 -3
- package/dist/tui/stream-layout.d.ts.map +1 -1
- package/dist/tui/stream-layout.js +6 -6
- package/dist/tui/stream-layout.js.map +1 -1
- package/dist/tui/tui-layout-constants.d.ts +4 -2
- package/dist/tui/tui-layout-constants.d.ts.map +1 -1
- package/dist/tui/tui-layout-constants.js +4 -2
- package/dist/tui/tui-layout-constants.js.map +1 -1
- package/dist/tui/tui-view-model.d.ts.map +1 -1
- package/dist/tui/tui-view-model.js +2 -8
- package/dist/tui/tui-view-model.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/docs/config.md +92 -16
- package/docs/settings.md +3 -3
- package/package.json +3 -3
|
@@ -8,7 +8,9 @@ import { render as renderInk } from 'ink';
|
|
|
8
8
|
import { cleanup, render } from 'ink-testing-library';
|
|
9
9
|
import { IssueSessionTuiStream } from './issue-session-tui-stream.js';
|
|
10
10
|
import { createTuiViewModel } from './tui-view-model.js';
|
|
11
|
+
import { extractTuiPaneFrameStats } from './orchestration-pane-frame.js';
|
|
11
12
|
import { flushInkStdin, INK_TEST_KEYS } from './ink-test-keys.js';
|
|
13
|
+
import { OPERATOR_INPUT_PROMPT } from './tui-layout-constants.js';
|
|
12
14
|
import { createTuiResizeController, createTuiTerminalSizeStore, TUI_RESIZE_SETTLE_MS, TUI_RESIZE_SHRINK_COALESCE_MS, } from './tui-terminal-size.js';
|
|
13
15
|
import { createTuiResizeRecovery, TUI_FULL_REDRAW_SEQUENCE, } from './tui-resize-recovery.js';
|
|
14
16
|
class ResizeSource extends EventEmitter {
|
|
@@ -48,6 +50,7 @@ function createOpenQuestion(overrides) {
|
|
|
48
50
|
}
|
|
49
51
|
const ISSUE_URL = 'https://github.com/otolab/agents-ensemble/issues/261';
|
|
50
52
|
const NON_CANONICAL_ISSUE_URL = 'http://github.com/otolab/agents-ensemble/issues/261/';
|
|
53
|
+
const LONG_QUESTION_CONTEXT = Array.from({ length: 40 }, (_, index) => `context-line-${String(index).padStart(2, '0')} ${'wrapped segment '.repeat(6)}`).join('\n');
|
|
51
54
|
describe('IssueSessionTuiStream', () => {
|
|
52
55
|
beforeEach(() => {
|
|
53
56
|
Object.defineProperty(process.stdout, 'rows', {
|
|
@@ -77,6 +80,153 @@ describe('IssueSessionTuiStream', () => {
|
|
|
77
80
|
await flushInkStdin();
|
|
78
81
|
expect(onSubmit).toHaveBeenCalledWith('/ex', undefined);
|
|
79
82
|
});
|
|
83
|
+
it('navigates submitted history at visual boundaries and restores an edited draft', async () => {
|
|
84
|
+
const viewModel = createTuiViewModel();
|
|
85
|
+
const onSubmit = vi.fn();
|
|
86
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
87
|
+
stdin.write('A');
|
|
88
|
+
await flushInkStdin();
|
|
89
|
+
stdin.write('\r');
|
|
90
|
+
await flushInkStdin();
|
|
91
|
+
expect(onSubmit).toHaveBeenCalledWith('A', undefined);
|
|
92
|
+
stdin.write('\u001B[200~line1\nline2\u001B[201~');
|
|
93
|
+
await flushInkStdin();
|
|
94
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
95
|
+
await flushInkStdin();
|
|
96
|
+
stdin.write('X');
|
|
97
|
+
await flushInkStdin();
|
|
98
|
+
stdin.write(INK_TEST_KEYS.home);
|
|
99
|
+
await flushInkStdin();
|
|
100
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
101
|
+
await flushInkStdin();
|
|
102
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}A`);
|
|
103
|
+
stdin.write("'");
|
|
104
|
+
await flushInkStdin();
|
|
105
|
+
stdin.write('\r');
|
|
106
|
+
await flushInkStdin();
|
|
107
|
+
expect(onSubmit).toHaveBeenLastCalledWith("A'", undefined);
|
|
108
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
109
|
+
await flushInkStdin();
|
|
110
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}line1X`);
|
|
111
|
+
expect(lastFrame() ?? '').toContain('line2');
|
|
112
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
113
|
+
await flushInkStdin();
|
|
114
|
+
stdin.write(INK_TEST_KEYS.home);
|
|
115
|
+
await flushInkStdin();
|
|
116
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
117
|
+
await flushInkStdin();
|
|
118
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}A'`);
|
|
119
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
120
|
+
await flushInkStdin();
|
|
121
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}A`);
|
|
122
|
+
});
|
|
123
|
+
it('keeps an Open Question answer completion after a historical submit', async () => {
|
|
124
|
+
const viewModel = createTuiViewModel();
|
|
125
|
+
const question = createOpenQuestion({
|
|
126
|
+
id: 'inq-yes-no',
|
|
127
|
+
question: 'Proceed?',
|
|
128
|
+
responseType: 'yes_no',
|
|
129
|
+
});
|
|
130
|
+
viewModel.setDisplayState({
|
|
131
|
+
workers: {},
|
|
132
|
+
conductorOutput: null,
|
|
133
|
+
openQuestions: [question],
|
|
134
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
135
|
+
});
|
|
136
|
+
const onSubmit = vi.fn();
|
|
137
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
138
|
+
stdin.write('previous input');
|
|
139
|
+
await flushInkStdin();
|
|
140
|
+
stdin.write('\r');
|
|
141
|
+
await flushInkStdin();
|
|
142
|
+
stdin.write('unsent draft');
|
|
143
|
+
await flushInkStdin();
|
|
144
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
145
|
+
await flushInkStdin();
|
|
146
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}previous input`);
|
|
147
|
+
stdin.write(' edited');
|
|
148
|
+
await flushInkStdin();
|
|
149
|
+
stdin.write('\r');
|
|
150
|
+
await flushInkStdin();
|
|
151
|
+
expect(onSubmit).toHaveBeenLastCalledWith('previous input edited', {
|
|
152
|
+
targetOpenQuestionId: question.id,
|
|
153
|
+
});
|
|
154
|
+
stdin.write('\r');
|
|
155
|
+
await flushInkStdin();
|
|
156
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}yes`);
|
|
157
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
158
|
+
await flushInkStdin();
|
|
159
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}yes`);
|
|
160
|
+
expect(lastFrame() ?? '').not.toContain(`${OPERATOR_INPUT_PROMPT}unsent draft`);
|
|
161
|
+
stdin.write('\r');
|
|
162
|
+
await flushInkStdin();
|
|
163
|
+
expect(onSubmit).toHaveBeenLastCalledWith('yes', {
|
|
164
|
+
targetOpenQuestionId: question.id,
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
it('moves within a soft-wrapped input and enters history only at its visual boundaries', async () => {
|
|
168
|
+
Object.defineProperty(process.stdout, 'columns', {
|
|
169
|
+
configurable: true,
|
|
170
|
+
value: 44,
|
|
171
|
+
});
|
|
172
|
+
const viewModel = createTuiViewModel();
|
|
173
|
+
const onSubmit = vi.fn();
|
|
174
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
175
|
+
const olderEntry = 'b'.repeat(90);
|
|
176
|
+
stdin.write(olderEntry);
|
|
177
|
+
await flushInkStdin();
|
|
178
|
+
stdin.write('\r');
|
|
179
|
+
await flushInkStdin();
|
|
180
|
+
const wrappedDraft = 'a'.repeat(90);
|
|
181
|
+
stdin.write(wrappedDraft);
|
|
182
|
+
await flushInkStdin();
|
|
183
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
184
|
+
await flushInkStdin();
|
|
185
|
+
stdin.write('X');
|
|
186
|
+
await flushInkStdin();
|
|
187
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
188
|
+
await flushInkStdin();
|
|
189
|
+
stdin.write('Y');
|
|
190
|
+
await flushInkStdin();
|
|
191
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
192
|
+
await flushInkStdin();
|
|
193
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
194
|
+
await flushInkStdin();
|
|
195
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
196
|
+
await flushInkStdin();
|
|
197
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}${olderEntry.slice(0, 10)}`);
|
|
198
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
199
|
+
await flushInkStdin();
|
|
200
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
201
|
+
await flushInkStdin();
|
|
202
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
203
|
+
await flushInkStdin();
|
|
204
|
+
stdin.write('\r');
|
|
205
|
+
await flushInkStdin();
|
|
206
|
+
const editedWrappedDraft = onSubmit.mock.calls.at(-1)?.[0];
|
|
207
|
+
expect(editedWrappedDraft).toHaveLength(wrappedDraft.length + 2);
|
|
208
|
+
expect(editedWrappedDraft.indexOf('Y') - editedWrappedDraft.indexOf('X')).toBeGreaterThan(1);
|
|
209
|
+
expect(editedWrappedDraft.replace(/[XY]/g, '')).toBe(wrappedDraft);
|
|
210
|
+
stdin.write('pending draft');
|
|
211
|
+
await flushInkStdin();
|
|
212
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
213
|
+
await flushInkStdin();
|
|
214
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
215
|
+
await flushInkStdin();
|
|
216
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}pending draft`);
|
|
217
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
218
|
+
await flushInkStdin();
|
|
219
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
220
|
+
await flushInkStdin();
|
|
221
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
222
|
+
await flushInkStdin();
|
|
223
|
+
stdin.write(INK_TEST_KEYS.upArrow);
|
|
224
|
+
await flushInkStdin();
|
|
225
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}${olderEntry.slice(0, 10)}`);
|
|
226
|
+
stdin.write('\r');
|
|
227
|
+
await flushInkStdin();
|
|
228
|
+
expect(onSubmit).toHaveBeenLastCalledWith(olderEntry, undefined);
|
|
229
|
+
});
|
|
80
230
|
it('selects a slash suggestion and Tab completes it without submitting', async () => {
|
|
81
231
|
const viewModel = createTuiViewModel();
|
|
82
232
|
const onSubmit = vi.fn();
|
|
@@ -86,17 +236,116 @@ describe('IssueSessionTuiStream', () => {
|
|
|
86
236
|
expect(lastFrame() ?? '').toContain('› /close');
|
|
87
237
|
stdin.write(INK_TEST_KEYS.tab);
|
|
88
238
|
await flushInkStdin();
|
|
89
|
-
expect(lastFrame() ?? '').toContain(
|
|
239
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}/close`);
|
|
240
|
+
expect(lastFrame() ?? '').not.toContain('operator> ');
|
|
90
241
|
expect(onSubmit).not.toHaveBeenCalled();
|
|
91
242
|
stdin.write('\r');
|
|
92
243
|
await flushInkStdin();
|
|
93
244
|
expect(onSubmit).toHaveBeenCalledWith('/close', undefined);
|
|
94
245
|
});
|
|
246
|
+
it.each([
|
|
247
|
+
{
|
|
248
|
+
name: 'choice',
|
|
249
|
+
question: createOpenQuestion({
|
|
250
|
+
id: 'inq-choice',
|
|
251
|
+
question: 'Choose a mode',
|
|
252
|
+
responseType: 'choice',
|
|
253
|
+
choices: [
|
|
254
|
+
{ id: 'safe', label: 'Safe mode' },
|
|
255
|
+
{ id: 'secure', label: 'Secure mode' },
|
|
256
|
+
{ id: 'fast', label: 'Fast mode' },
|
|
257
|
+
],
|
|
258
|
+
}),
|
|
259
|
+
prefix: 's',
|
|
260
|
+
completedValue: 'secure',
|
|
261
|
+
expectedSuggestion: 'safe: Safe mode',
|
|
262
|
+
hasMultipleSuggestions: true,
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'yes_no',
|
|
266
|
+
question: createOpenQuestion({
|
|
267
|
+
id: 'inq-yes-no',
|
|
268
|
+
question: 'Proceed?',
|
|
269
|
+
responseType: 'yes_no',
|
|
270
|
+
}),
|
|
271
|
+
prefix: 'n',
|
|
272
|
+
completedValue: 'no',
|
|
273
|
+
expectedSuggestion: 'no — いいえ',
|
|
274
|
+
hasMultipleSuggestions: false,
|
|
275
|
+
},
|
|
276
|
+
])('completes and submits the selected $name answer in stream mode', async ({ question, prefix, completedValue, expectedSuggestion, hasMultipleSuggestions, }) => {
|
|
277
|
+
const viewModel = createTuiViewModel();
|
|
278
|
+
viewModel.setDisplayState({
|
|
279
|
+
workers: {},
|
|
280
|
+
conductorOutput: null,
|
|
281
|
+
openQuestions: [
|
|
282
|
+
question,
|
|
283
|
+
createOpenQuestion({
|
|
284
|
+
id: 'inq-text',
|
|
285
|
+
question: 'Anything else?',
|
|
286
|
+
}),
|
|
287
|
+
],
|
|
288
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
289
|
+
});
|
|
290
|
+
const onSubmit = vi.fn();
|
|
291
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: onSubmit }));
|
|
292
|
+
stdin.write(prefix);
|
|
293
|
+
await flushInkStdin();
|
|
294
|
+
expect(lastFrame() ?? '').toContain(expectedSuggestion);
|
|
295
|
+
stdin.write(INK_TEST_KEYS.shiftDownArrow);
|
|
296
|
+
await flushInkStdin();
|
|
297
|
+
expect(lastFrame() ?? '').toContain('2/2');
|
|
298
|
+
expect(lastFrame() ?? '').not.toContain(`› ${expectedSuggestion}`);
|
|
299
|
+
stdin.write(INK_TEST_KEYS.shiftUpArrow);
|
|
300
|
+
await flushInkStdin();
|
|
301
|
+
expect(lastFrame() ?? '').toContain('1/2');
|
|
302
|
+
expect(lastFrame() ?? '').toContain(expectedSuggestion);
|
|
303
|
+
if (hasMultipleSuggestions) {
|
|
304
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
305
|
+
await flushInkStdin();
|
|
306
|
+
expect(lastFrame() ?? '').toContain('› secure: Secure mode');
|
|
307
|
+
}
|
|
308
|
+
stdin.write('\r');
|
|
309
|
+
await flushInkStdin();
|
|
310
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
311
|
+
expect(lastFrame() ?? '').toContain(`${OPERATOR_INPUT_PROMPT}${completedValue}`);
|
|
312
|
+
expect(lastFrame() ?? '').not.toContain(`› ${completedValue}:`);
|
|
313
|
+
stdin.write(' because it is safer');
|
|
314
|
+
await flushInkStdin();
|
|
315
|
+
stdin.write('\r');
|
|
316
|
+
await flushInkStdin();
|
|
317
|
+
expect(onSubmit).toHaveBeenCalledWith(`${completedValue} because it is safer`, { targetOpenQuestionId: question.id });
|
|
318
|
+
});
|
|
319
|
+
it('prioritizes slash suggestions over answer suggestions in stream mode', async () => {
|
|
320
|
+
const viewModel = createTuiViewModel();
|
|
321
|
+
viewModel.setDisplayState({
|
|
322
|
+
workers: {},
|
|
323
|
+
conductorOutput: null,
|
|
324
|
+
openQuestions: [
|
|
325
|
+
createOpenQuestion({
|
|
326
|
+
id: 'inq-choice',
|
|
327
|
+
question: 'Choose a mode',
|
|
328
|
+
responseType: 'choice',
|
|
329
|
+
choices: [{ id: 'exit', label: 'Exit mode' }],
|
|
330
|
+
}),
|
|
331
|
+
],
|
|
332
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
333
|
+
});
|
|
334
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: () => { } }));
|
|
335
|
+
stdin.write('/');
|
|
336
|
+
await flushInkStdin();
|
|
337
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
338
|
+
expect(lastFrame() ?? '').not.toContain('› exit: Exit mode');
|
|
339
|
+
});
|
|
95
340
|
it('renders activity as unframed static output and keeps the lower panes framed', () => {
|
|
96
341
|
const viewModel = createTuiViewModel();
|
|
97
342
|
viewModel.setDisplayState({
|
|
98
343
|
workers: {
|
|
99
|
-
implementer: {
|
|
344
|
+
implementer: {
|
|
345
|
+
kind: 'implementer',
|
|
346
|
+
status: 'running',
|
|
347
|
+
activity: 'calling: Shell',
|
|
348
|
+
},
|
|
100
349
|
},
|
|
101
350
|
conductorOutput: null,
|
|
102
351
|
openQuestions: [createOpenQuestion({ id: 'inq-1', question: 'Approve?' })],
|
|
@@ -109,6 +358,8 @@ describe('IssueSessionTuiStream', () => {
|
|
|
109
358
|
expect(frame).toContain('[operator] operator ping');
|
|
110
359
|
expect(frame).toContain('[conductor] conductor says hi');
|
|
111
360
|
expect(frame).toContain('Workers');
|
|
361
|
+
expect(frame).toContain('implementer: running (calling: Shell)');
|
|
362
|
+
expect(frame).not.toContain('implementer (implementer)');
|
|
112
363
|
expect(frame).toContain('Open questions');
|
|
113
364
|
expect(frame).toContain('Operator input');
|
|
114
365
|
expect(frame).not.toContain('Orchestration');
|
|
@@ -117,6 +368,27 @@ describe('IssueSessionTuiStream', () => {
|
|
|
117
368
|
expect(frame).toContain('inq-1 (1/1) への回答');
|
|
118
369
|
expect(frame).not.toContain('任意入力');
|
|
119
370
|
});
|
|
371
|
+
it('sizes the stream Workers pane for conductor and each worker row', () => {
|
|
372
|
+
const viewModel = createTuiViewModel();
|
|
373
|
+
viewModel.setDisplayState({
|
|
374
|
+
workers: {
|
|
375
|
+
conductor: { kind: 'conductor', status: 'idle' },
|
|
376
|
+
implementer: { kind: 'implementer', status: 'running' },
|
|
377
|
+
reviewer: { kind: 'reviewer', status: 'idle' },
|
|
378
|
+
},
|
|
379
|
+
conductorOutput: null,
|
|
380
|
+
openQuestions: [],
|
|
381
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
382
|
+
});
|
|
383
|
+
const { lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: () => { } }));
|
|
384
|
+
const frame = lastFrame() ?? '';
|
|
385
|
+
const stats = extractTuiPaneFrameStats(frame, 'Workers');
|
|
386
|
+
expect(stats.contentRows).toHaveLength(3);
|
|
387
|
+
expect(stats.blankRows).toHaveLength(0);
|
|
388
|
+
expect(frame).toContain('conductor: idle');
|
|
389
|
+
expect(frame).toContain('implementer: running');
|
|
390
|
+
expect(frame).toContain('reviewer: idle');
|
|
391
|
+
});
|
|
120
392
|
it('keeps operator line and paragraph breaks in stream activity output', () => {
|
|
121
393
|
const viewModel = createTuiViewModel({ activityLogWindowSize: null });
|
|
122
394
|
viewModel.appendActivityLog('operator', 'first line\n\nsecond line');
|
|
@@ -225,7 +497,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
225
497
|
expect(frame).not.toContain('Open questions');
|
|
226
498
|
expect(frame).not.toContain('(未回答なし)');
|
|
227
499
|
expect(workersIndex).toBeGreaterThan(operatorInputIndex);
|
|
228
|
-
expect(frame).toContain(
|
|
500
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
229
501
|
expect(frame).not.toContain('追加指示');
|
|
230
502
|
expect(frame).not.toContain('/reconnect');
|
|
231
503
|
expect(frame).toContain('otolab/agents-ensemble#261');
|
|
@@ -309,7 +581,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
309
581
|
await flushInkStdin();
|
|
310
582
|
expectCanonicalIssueUrl();
|
|
311
583
|
const postLoopFrame = (lastFrame() ?? '').replace(/\s+/g, ' ');
|
|
312
|
-
expect(postLoopFrame).toContain(
|
|
584
|
+
expect(postLoopFrame).toContain(OPERATOR_INPUT_PROMPT);
|
|
313
585
|
expect(postLoopFrame).not.toContain('追加指示');
|
|
314
586
|
expect(postLoopFrame).not.toContain('/reconnect');
|
|
315
587
|
viewModel.setShuttingDown(true);
|
|
@@ -394,7 +666,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
394
666
|
viewModel.setPostLoopWaiting(true);
|
|
395
667
|
await flushInkStdin();
|
|
396
668
|
expectCanonicalIssueLink();
|
|
397
|
-
expect(lastFrame() ?? '').toContain(
|
|
669
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
398
670
|
expect(lastFrame() ?? '').not.toContain('追加指示');
|
|
399
671
|
expect(lastFrame() ?? '').not.toContain('/reconnect');
|
|
400
672
|
viewModel.setShuttingDown(true);
|
|
@@ -406,7 +678,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
406
678
|
const viewModel = createTuiViewModel();
|
|
407
679
|
const { lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, issueUrl: "https://github.com/otolab/agents-ensemble/issues/261", issueLinkMode: "label", onSubmit: () => { } }));
|
|
408
680
|
const frame = lastFrame() ?? '';
|
|
409
|
-
expect(frame).toContain(
|
|
681
|
+
expect(frame).toContain(OPERATOR_INPUT_PROMPT);
|
|
410
682
|
expect(frame).not.toContain('任意入力');
|
|
411
683
|
expect(frame).not.toContain('/reconnect');
|
|
412
684
|
expect(frame).not.toContain('自律ターン');
|
|
@@ -416,7 +688,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
416
688
|
const onSubmit = vi.fn();
|
|
417
689
|
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, issueUrl: "https://github.com/otolab/agents-ensemble/issues/263", issueLinkMode: "label", onSubmit: onSubmit }));
|
|
418
690
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
419
|
-
expect(lastFrame() ?? '').toContain(
|
|
691
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
420
692
|
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
421
693
|
expect(lastFrame() ?? '').not.toContain('/reconnect');
|
|
422
694
|
expect(lastFrame() ?? '').toContain('otolab/agents-ensemble#263');
|
|
@@ -427,16 +699,33 @@ describe('IssueSessionTuiStream', () => {
|
|
|
427
699
|
await flushInkStdin();
|
|
428
700
|
expect(onSubmit).toHaveBeenCalledWith('follow-up', undefined);
|
|
429
701
|
});
|
|
430
|
-
it('
|
|
702
|
+
it('separates changing activity labels in static output and groups same-label entries', async () => {
|
|
431
703
|
const viewModel = createTuiViewModel();
|
|
432
704
|
viewModel.appendActivityLog('harness', 'first');
|
|
433
705
|
const { lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: () => { } }));
|
|
434
706
|
expect(lastFrame() ?? '').toContain('[harness] first');
|
|
435
|
-
viewModel.appendActivityLog('
|
|
707
|
+
viewModel.appendActivityLog('harness', 'second');
|
|
708
|
+
viewModel.appendActivityLog('observation', 'third');
|
|
709
|
+
viewModel.appendActivityLog('operator', 'fourth');
|
|
710
|
+
viewModel.appendActivityLog('conductor', 'fifth');
|
|
436
711
|
await flushInkStdin();
|
|
437
712
|
const frame = lastFrame() ?? '';
|
|
713
|
+
const lines = frame.split('\n');
|
|
714
|
+
const harnessFirst = lines.findIndex((line) => line.includes('[harness] first'));
|
|
715
|
+
const harnessSecond = lines.findIndex((line) => line.includes('[harness] second'));
|
|
716
|
+
const observation = lines.findIndex((line) => line.includes('[observation] third'));
|
|
717
|
+
const operator = lines.findIndex((line) => line.includes('[operator] fourth'));
|
|
718
|
+
const conductor = lines.findIndex((line) => line.includes('[conductor] fifth'));
|
|
719
|
+
expect(harnessFirst).toBeGreaterThanOrEqual(0);
|
|
720
|
+
expect(harnessSecond).toBe(harnessFirst + 1);
|
|
721
|
+
expect(observation).toBe(harnessSecond + 2);
|
|
722
|
+
expect(operator).toBe(observation + 2);
|
|
723
|
+
expect(conductor).toBe(operator + 2);
|
|
438
724
|
expect(frame).toContain('[harness] first');
|
|
439
|
-
expect(frame).toContain('[
|
|
725
|
+
expect(frame).toContain('[harness] second');
|
|
726
|
+
expect(frame).toContain('[observation] third');
|
|
727
|
+
expect(frame).toContain('[operator] fourth');
|
|
728
|
+
expect(frame).toContain('[conductor] fifth');
|
|
440
729
|
});
|
|
441
730
|
it('holds new activity while detached and flushes the pending suffix once on End', async () => {
|
|
442
731
|
const viewModel = createTuiViewModel({ activityLogWindowSize: null });
|
|
@@ -545,7 +834,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
545
834
|
expect(countHistory(finalFrame)).toBe(1);
|
|
546
835
|
expect(Math.max(...finalFrame.split('\n').map((line) => line.trimEnd().length))).toBeLessThanOrEqual(60);
|
|
547
836
|
expect(finalFrame).toContain('Workers');
|
|
548
|
-
expect(finalFrame).toContain(
|
|
837
|
+
expect(finalFrame).toContain(OPERATOR_INPUT_PROMPT);
|
|
549
838
|
}
|
|
550
839
|
finally {
|
|
551
840
|
unsubscribe();
|
|
@@ -738,7 +1027,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
738
1027
|
});
|
|
739
1028
|
if (restoredQuestions.length === 1) {
|
|
740
1029
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
741
|
-
expect(lastFrame() ?? '').toContain(
|
|
1030
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
742
1031
|
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
743
1032
|
}
|
|
744
1033
|
else {
|
|
@@ -748,7 +1037,7 @@ describe('IssueSessionTuiStream', () => {
|
|
|
748
1037
|
setOperatorQuestions([]);
|
|
749
1038
|
await flushInkStdin();
|
|
750
1039
|
expect(lastFrame() ?? '').not.toContain('Open questions');
|
|
751
|
-
expect(lastFrame() ?? '').toContain(
|
|
1040
|
+
expect(lastFrame() ?? '').toContain(OPERATOR_INPUT_PROMPT);
|
|
752
1041
|
expect(lastFrame() ?? '').not.toContain('任意入力');
|
|
753
1042
|
}
|
|
754
1043
|
});
|
|
@@ -772,5 +1061,72 @@ describe('IssueSessionTuiStream', () => {
|
|
|
772
1061
|
expect(lastFrame() ?? '').toContain('2/2');
|
|
773
1062
|
expect(lastFrame() ?? '').toContain('▸ inq-2');
|
|
774
1063
|
});
|
|
1064
|
+
it('scrolls the selected full context from top to bottom without taking suggestion arrows', async () => {
|
|
1065
|
+
const viewModel = createTuiViewModel();
|
|
1066
|
+
viewModel.setDisplayState({
|
|
1067
|
+
workers: {},
|
|
1068
|
+
conductorOutput: null,
|
|
1069
|
+
openQuestions: [
|
|
1070
|
+
createOpenQuestion({
|
|
1071
|
+
id: 'inq-long',
|
|
1072
|
+
question: 'Choose a mode',
|
|
1073
|
+
responseType: 'choice',
|
|
1074
|
+
context: LONG_QUESTION_CONTEXT,
|
|
1075
|
+
choices: [
|
|
1076
|
+
{ id: 'safe', label: 'Safe mode' },
|
|
1077
|
+
{ id: 'secure', label: 'Secure mode' },
|
|
1078
|
+
],
|
|
1079
|
+
}),
|
|
1080
|
+
createOpenQuestion({
|
|
1081
|
+
id: 'inq-next',
|
|
1082
|
+
question: 'Anything else?',
|
|
1083
|
+
}),
|
|
1084
|
+
],
|
|
1085
|
+
dispatchHold: { hold: false, heldEventCount: 0 },
|
|
1086
|
+
});
|
|
1087
|
+
const { stdin, lastFrame } = render(_jsx(IssueSessionTuiStream, { viewModel: viewModel, onSubmit: () => { } }));
|
|
1088
|
+
const initialFrame = lastFrame() ?? '';
|
|
1089
|
+
expect(initialFrame).toContain('context-line-00');
|
|
1090
|
+
expect(initialFrame).not.toContain('context-line-39');
|
|
1091
|
+
expect(initialFrame).toContain('Shift+PgUp/PgDnで本文スクロール');
|
|
1092
|
+
expect(initialFrame).toContain('で選択) · Shift+PgUp/PgDn');
|
|
1093
|
+
expect(initialFrame).toContain('› safe: Safe mode');
|
|
1094
|
+
stdin.write(INK_TEST_KEYS.shiftPageDown.repeat(2));
|
|
1095
|
+
await flushInkStdin();
|
|
1096
|
+
const scrolledFrame = lastFrame() ?? '';
|
|
1097
|
+
expect(scrolledFrame).not.toContain('context-line-00');
|
|
1098
|
+
expect(scrolledFrame).toContain('context-line-01');
|
|
1099
|
+
expect(scrolledFrame).toContain('› safe: Safe mode');
|
|
1100
|
+
stdin.write(INK_TEST_KEYS.downArrow);
|
|
1101
|
+
await flushInkStdin();
|
|
1102
|
+
expect(lastFrame() ?? '').toContain('› secure: Secure mode');
|
|
1103
|
+
expect(lastFrame() ?? '').toContain('context-line-01');
|
|
1104
|
+
stdin.write('/');
|
|
1105
|
+
await flushInkStdin();
|
|
1106
|
+
expect(lastFrame() ?? '').toContain('› /reconnect');
|
|
1107
|
+
stdin.write(INK_TEST_KEYS.shiftPageDown.repeat(200));
|
|
1108
|
+
await flushInkStdin();
|
|
1109
|
+
const bottomFrame = lastFrame() ?? '';
|
|
1110
|
+
expect(bottomFrame).toContain('context-line-39');
|
|
1111
|
+
expect(bottomFrame).not.toContain('context-line-00');
|
|
1112
|
+
expect(bottomFrame).toContain('› /reconnect');
|
|
1113
|
+
stdin.write(INK_TEST_KEYS.shiftDownArrow);
|
|
1114
|
+
await flushInkStdin();
|
|
1115
|
+
expect(lastFrame() ?? '').toContain('2/2');
|
|
1116
|
+
stdin.write(INK_TEST_KEYS.shiftUpArrow);
|
|
1117
|
+
await flushInkStdin();
|
|
1118
|
+
const resetFrame = lastFrame() ?? '';
|
|
1119
|
+
expect(resetFrame).toContain('1/2');
|
|
1120
|
+
expect(resetFrame).toContain('context-line-00');
|
|
1121
|
+
expect(resetFrame).not.toContain('context-line-39');
|
|
1122
|
+
stdin.write(INK_TEST_KEYS.shiftPageDown.repeat(200));
|
|
1123
|
+
await flushInkStdin();
|
|
1124
|
+
expect(lastFrame() ?? '').toContain('context-line-39');
|
|
1125
|
+
stdin.write(INK_TEST_KEYS.shiftPageUp.repeat(200));
|
|
1126
|
+
await flushInkStdin();
|
|
1127
|
+
const topFrame = lastFrame() ?? '';
|
|
1128
|
+
expect(topFrame).toContain('context-line-00');
|
|
1129
|
+
expect(topFrame).not.toContain('context-line-39');
|
|
1130
|
+
});
|
|
775
1131
|
});
|
|
776
1132
|
//# sourceMappingURL=issue-session-tui-stream.test.js.map
|