@robota-sdk/agent-ui-terminal 3.0.0-beta.82 → 3.0.0-beta.83
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/CHANGELOG.md +858 -0
- package/README.md +5 -1
- package/dist/node/index.d.ts +189 -54
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +21 -21
- package/dist/node/index.js.map +1 -1
- package/package.json +16 -21
- package/src/App.tsx +14 -3
- package/src/AppPresentation.tsx +1 -1
- package/src/PermissionPrompt.tsx +50 -17
- package/src/SessionPicker.tsx +27 -7
- package/src/SessionStatusBar.tsx +6 -2
- package/src/StatusBar.tsx +24 -0
- package/src/SupervisedSessionView.tsx +479 -157
- package/src/TuiInteractionChannel.ts +8 -53
- package/src/__tests__/SupervisedSessionView-render.test.tsx +28 -0
- package/src/__tests__/SupervisedSessionView.test.tsx +453 -82
- package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +32 -22
- package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +10 -2
- package/src/__tests__/attached-app-session-switch.test.tsx +317 -0
- package/src/__tests__/checkpoint-store-per-channel.test.ts +81 -0
- package/src/__tests__/command-handoff-pty-e2e.test.ts +2 -2
- package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +3 -1
- package/src/__tests__/fixtures/command-handoff-driver.tsx +8 -5
- package/src/__tests__/grant-history-reaches-the-session.test.ts +31 -0
- package/src/__tests__/numbered-selection-chunk.test.tsx +41 -0
- package/src/__tests__/palette-consistency.test.ts +0 -1
- package/src/__tests__/permission-prompt-arming.test.tsx +153 -0
- package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +2 -1
- package/src/__tests__/pty/screen-006-no-color.ptytest.ts +2 -0
- package/src/__tests__/pty/session-attach.ptytest.ts +144 -0
- package/src/__tests__/render-channel-options.test.ts +6 -6
- package/src/__tests__/sandbox-reaches-the-session.test.ts +37 -0
- package/src/__tests__/screen-reader-menus.test.tsx +1 -1
- package/src/__tests__/session-picker.test.tsx +93 -0
- package/src/__tests__/status-bar.test.tsx +8 -0
- package/src/__tests__/tui-channel-init-failure.test.ts +10 -2
- package/src/__tests__/wire-tui-channel.test.ts +1445 -0
- package/src/__tests__/wire-tui-client-commands.test.ts +243 -0
- package/src/app-view-model.ts +6 -2
- package/src/attached-session-connection.ts +14 -0
- package/src/hooks/__tests__/use-app-detach-key.test.tsx +72 -0
- package/src/hooks/__tests__/use-app-loop-escape.test.tsx +13 -0
- package/src/hooks/useAppController.ts +6 -2
- package/src/hooks/useAppInputBindings.ts +20 -2
- package/src/hooks/useAppInteractionState.ts +3 -0
- package/src/hooks/useAppOverlays.ts +20 -4
- package/src/hooks/useNumberedSelection.ts +19 -2
- package/src/hooks/useTuiChannel.ts +13 -1
- package/src/index.ts +17 -3
- package/src/render.tsx +218 -27
- package/src/short-session-id.ts +9 -0
- package/src/slash-command-input.ts +11 -0
- package/src/tui-app-channel-port.ts +29 -1
- package/src/tui-channel-options.ts +11 -2
- package/src/tui-session-event-projector.ts +0 -2
- package/src/tui-session-events.ts +6 -0
- package/src/tui-session-options.ts +9 -0
- package/src/tui-state-manager.ts +17 -3
- package/src/waiting-loop-stop-notice.ts +23 -0
- package/src/wire-history-sync.ts +159 -0
- package/src/wire-tui-channel.ts +962 -0
- package/src/wire-tui-client-commands.ts +142 -0
- package/src/wire-tui-projection.ts +136 -0
- package/dist/node/index.cjs +0 -32
- package/dist/node/index.d.cts +0 -1066
- package/dist/node/index.d.cts.map +0 -1
|
@@ -6,16 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { createSystemMessage, messageToHistoryEntry } from '@robota-sdk/agent-core';
|
|
9
|
-
import {
|
|
10
|
-
CommandRegistry,
|
|
11
|
-
buildRuntimeSession,
|
|
12
|
-
generateSessionName,
|
|
13
|
-
} from '@robota-sdk/agent-framework';
|
|
9
|
+
import { CommandRegistry, buildRuntimeSession } from '@robota-sdk/agent-framework';
|
|
14
10
|
|
|
15
11
|
import { AttentionCoordinator } from './attention/attention-coordinator.js';
|
|
16
12
|
import { MS_PER_SECOND } from './attention/time-units.js';
|
|
17
13
|
import { createSessionInitPoller } from './flows/session-init-poller.js';
|
|
18
14
|
import { applySystemCommandResult } from './hooks/command-result-handler.js';
|
|
15
|
+
import { parseSlashCommandInput } from './slash-command-input.js';
|
|
19
16
|
import {
|
|
20
17
|
TuiChannelLifecycleCoordinator,
|
|
21
18
|
TuiChannelStartRollbackError,
|
|
@@ -24,6 +21,7 @@ import { TuiPermissionQueue, TuiUserActionQueue } from './tui-interaction-queues
|
|
|
24
21
|
import { TuiSessionEventProjector } from './tui-session-event-projector.js';
|
|
25
22
|
import { buildTuiSessionOptions } from './tui-session-options.js';
|
|
26
23
|
import { TuiStateManager } from './tui-state-manager.js';
|
|
24
|
+
import { WAITING_LOOP_STOP_REASON, waitingLoopStopNotice } from './waiting-loop-stop-notice.js';
|
|
27
25
|
|
|
28
26
|
import type { IAttentionSource } from './attention/attention-tracker.js';
|
|
29
27
|
import type { ISessionInitPoller, TSessionInitFailure } from './flows/session-init-poller.js';
|
|
@@ -78,7 +76,6 @@ export class TuiInteractionChannel implements ITuiAppChannelPort {
|
|
|
78
76
|
availableCommands: ICommandInfo[] = [];
|
|
79
77
|
sessionName: string | undefined;
|
|
80
78
|
|
|
81
|
-
private autoNameTriggered = false;
|
|
82
79
|
private initPoller: ISessionInitPoller | null = null;
|
|
83
80
|
|
|
84
81
|
/** TERM-002: the App registers its Ink suspend/resume hooks into this controller. */
|
|
@@ -103,7 +100,6 @@ export class TuiInteractionChannel implements ITuiAppChannelPort {
|
|
|
103
100
|
session: this.interactiveSession,
|
|
104
101
|
manager: this.stateManager,
|
|
105
102
|
...(this.attention ? { attention: this.attention } : {}),
|
|
106
|
-
onUserMessage: (content) => this.handleAutoNaming(content),
|
|
107
103
|
requestPermission: (toolName, toolArgs, id, canPersistProjectPermission, requestedByPeer) =>
|
|
108
104
|
this.permissions.enqueue(
|
|
109
105
|
toolName,
|
|
@@ -324,34 +320,11 @@ export class TuiInteractionChannel implements ITuiAppChannelPort {
|
|
|
324
320
|
this.stateManager.setPendingPrompt(null);
|
|
325
321
|
}
|
|
326
322
|
|
|
323
|
+
/** Esc on an idle prompt: the session decides which waiting loop, if any, stops. */
|
|
327
324
|
async stopWaitingSelfPacedLoop(): Promise<void> {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (waiting.length === 0) return;
|
|
332
|
-
if (waiting.length > 1) {
|
|
333
|
-
this.addEntry(
|
|
334
|
-
messageToHistoryEntry(
|
|
335
|
-
createSystemMessage(
|
|
336
|
-
'Several self-paced loops are waiting. Use /loop list and /loop stop <id> to choose one.',
|
|
337
|
-
),
|
|
338
|
-
),
|
|
339
|
-
);
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
const loopId = waiting[0]!.loopId;
|
|
343
|
-
try {
|
|
344
|
-
await this.interactiveSession.stopSelfPacedLoop(loopId, 'Loop stopped by Esc');
|
|
345
|
-
this.addEntry(messageToHistoryEntry(createSystemMessage(`Loop ${loopId} stopped by Esc.`)));
|
|
346
|
-
} catch (error) {
|
|
347
|
-
this.addEntry(
|
|
348
|
-
messageToHistoryEntry(
|
|
349
|
-
createSystemMessage(
|
|
350
|
-
`Could not stop loop ${loopId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
351
|
-
),
|
|
352
|
-
),
|
|
353
|
-
);
|
|
354
|
-
}
|
|
325
|
+
const outcome = await this.interactiveSession.stopWaitingSelfPacedLoop(WAITING_LOOP_STOP_REASON);
|
|
326
|
+
const notice = waitingLoopStopNotice(outcome);
|
|
327
|
+
if (notice !== undefined) this.addEntry(messageToHistoryEntry(createSystemMessage(notice)));
|
|
355
328
|
}
|
|
356
329
|
|
|
357
330
|
async shutdown(options?: { reason?: TSessionEndReason; timeoutMs?: number }): Promise<void> {
|
|
@@ -407,9 +380,7 @@ export class TuiInteractionChannel implements ITuiAppChannelPort {
|
|
|
407
380
|
}
|
|
408
381
|
|
|
409
382
|
private async handleSlashCommand(input: string): Promise<void> {
|
|
410
|
-
const
|
|
411
|
-
const cmd = parts[0]?.toLowerCase() ?? '';
|
|
412
|
-
const args = parts.slice(1).join(' ');
|
|
383
|
+
const { name: cmd, args } = parseSlashCommandInput(input);
|
|
413
384
|
|
|
414
385
|
const result = await this.interactiveSession.executeCommand(cmd, args);
|
|
415
386
|
if (result) {
|
|
@@ -463,22 +434,6 @@ export class TuiInteractionChannel implements ITuiAppChannelPort {
|
|
|
463
434
|
this.permissions.cancelAll();
|
|
464
435
|
}
|
|
465
436
|
|
|
466
|
-
private handleAutoNaming(content: string): void {
|
|
467
|
-
if (this.autoNameTriggered) return;
|
|
468
|
-
if (this.opts.sessionName || this.interactiveSession.getName()) return;
|
|
469
|
-
this.autoNameTriggered = true;
|
|
470
|
-
generateSessionName(this.opts.provider, content)
|
|
471
|
-
.then((name) => {
|
|
472
|
-
this.interactiveSession.setName(name);
|
|
473
|
-
this.sessionName = name;
|
|
474
|
-
this.opts.onAutoNamed?.(name);
|
|
475
|
-
this.onChange?.();
|
|
476
|
-
})
|
|
477
|
-
.catch(() => {
|
|
478
|
-
this.autoNameTriggered = false;
|
|
479
|
-
});
|
|
480
|
-
}
|
|
481
|
-
|
|
482
437
|
private syncRestoredHistory(): void {
|
|
483
438
|
if (this.stateManager.history.length === 0) {
|
|
484
439
|
const restored = this.interactiveSession.getFullHistory();
|
|
@@ -21,4 +21,32 @@ describe('production supervised view renderer', () => {
|
|
|
21
21
|
expect(wrapper.props.children.props.onOpenPr).toBe(onOpenPr);
|
|
22
22
|
expect(wrapper.props.children.props.filteredByPr).toBe(true);
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
it('returns the attach the user confirmed so the host can run it and come back', async () => {
|
|
26
|
+
vi.mocked(render).mockClear();
|
|
27
|
+
let exitView: () => void = () => undefined;
|
|
28
|
+
vi.mocked(render).mockImplementationOnce(() => ({
|
|
29
|
+
waitUntilExit: () => new Promise<void>((resolve) => { exitView = resolve; }),
|
|
30
|
+
unmount: () => undefined,
|
|
31
|
+
}) as never);
|
|
32
|
+
const ended = renderSupervisedSessionView({ loadRows: async () => [], screenReader: false });
|
|
33
|
+
const wrapper = vi.mocked(render).mock.calls[0]?.[0] as React.ReactElement<{
|
|
34
|
+
children: React.ReactElement<{ onAttach?: (request: unknown) => void }>;
|
|
35
|
+
}>;
|
|
36
|
+
wrapper.props.children.props.onAttach?.({ id: 'x', generation: 'g', mode: 'observe', groupByDirectory: true });
|
|
37
|
+
exitView();
|
|
38
|
+
expect(await ended).toEqual({ kind: 'attach', id: 'x', generation: 'g', mode: 'observe', groupByDirectory: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('prints the screen-reader line only when asked to announce', async () => {
|
|
42
|
+
const out = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
43
|
+
try {
|
|
44
|
+
await renderSupervisedSessionView({ loadRows: async () => [], screenReader: true, screenReaderChannel: 'flag', announce: false });
|
|
45
|
+
expect(out.mock.calls.map(([text]) => String(text)).join('')).not.toContain('Screen reader mode');
|
|
46
|
+
await renderSupervisedSessionView({ loadRows: async () => [], screenReader: true, screenReaderChannel: 'flag' });
|
|
47
|
+
expect(out.mock.calls.map(([text]) => String(text)).join('')).toContain('Screen reader mode: on via flag');
|
|
48
|
+
} finally {
|
|
49
|
+
out.mockRestore();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
24
52
|
});
|