@robota-sdk/agent-ui-terminal 3.0.0-beta.85 → 3.0.0-beta.87
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 +68 -14
- package/README.md +2 -2
- package/dist/node/index.d.ts +8 -2
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +14 -14
- package/dist/node/index.js.map +1 -1
- package/package.json +19 -20
- package/src/SupervisedSessionView.tsx +12 -2
- package/src/__tests__/SupervisedSessionView.test.tsx +9 -5
- package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +1 -1
- package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +1 -1
- package/src/__tests__/UpdateNotice.test.tsx +2 -2
- package/src/__tests__/attached-app-session-switch.test.tsx +1 -1
- package/src/__tests__/attention-bell-and-marks.test.ts +5 -5
- package/src/__tests__/channel-factory-integration.test.ts +3 -3
- package/src/__tests__/checkpoint-store-per-channel.test.ts +1 -1
- package/src/__tests__/cjk-fallback-render.test.tsx +2 -2
- package/src/__tests__/command-handoff-pty-e2e.test.ts +2 -2
- package/src/__tests__/command-result-handler.test.ts +3 -3
- package/src/__tests__/fork-attach.test.tsx +3 -3
- package/src/__tests__/helpers/terminal-profiles.ts +4 -4
- package/src/__tests__/humanize-tool-name.test.ts +1 -1
- package/src/__tests__/org-policy-reaches-the-channel.test.ts +5 -5
- package/src/__tests__/product-display-name.test.tsx +5 -5
- package/src/__tests__/pty/ask-user-question.ptytest.ts +2 -2
- package/src/__tests__/pty/background-work-switcher.ptytest.ts +3 -3
- package/src/__tests__/pty/behavior-2437-git.ptytest.ts +9 -9
- package/src/__tests__/pty/flag-tui.ptytest.ts +2 -2
- package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +53 -24
- package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +59 -14
- package/src/__tests__/pty/ime-cursor.ptytest.ts +4 -4
- package/src/__tests__/pty/isolated-home.ts +37 -4
- package/src/__tests__/pty/org-policy.ptytest.ts +3 -3
- package/src/__tests__/pty/provider-setup.ptytest.ts +2 -2
- package/src/__tests__/pty/pty-driver.ts +8 -6
- package/src/__tests__/pty/replay-conversation.ptytest.ts +2 -2
- package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +2 -2
- package/src/__tests__/pty/screen-006-no-color.ptytest.ts +4 -4
- package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +3 -3
- package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +2 -2
- package/src/__tests__/pty/screen-2002-themes.ptytest.ts +3 -3
- package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +3 -3
- package/src/__tests__/pty/screen-reader-mode.ptytest.ts +16 -16
- package/src/__tests__/pty/session-attach.ptytest.ts +66 -28
- package/src/__tests__/pty/settings-screen.ptytest.ts +2 -2
- package/src/__tests__/pty/spawn-pty.ts +3 -3
- package/src/__tests__/pty/terminal-handoff.ptytest.ts +2 -2
- package/src/__tests__/pty/trust-prompt.ptytest.ts +5 -5
- package/src/__tests__/pty/tui-pty.ptytest.ts +2 -2
- package/src/__tests__/screen-reader-labels.test.ts +1 -1
- package/src/__tests__/screen-reader-pacing.test.ts +3 -3
- package/src/__tests__/screen-reader-render-options.test.ts +3 -3
- package/src/__tests__/session-switch-channel.test.tsx +1 -1
- package/src/__tests__/terminal-capabilities.test.ts +9 -9
- package/src/__tests__/terminal-handoff-pty-e2e.test.ts +1 -1
- package/src/__tests__/tui-channel-init-failure.test.ts +1 -1
- package/src/__tests__/tui-state-manager.test.ts +2 -2
- package/src/__tests__/wire-tui-channel.test.ts +2 -2
- package/src/keybindings/__tests__/keybinding-registry.test.ts +2 -2
- package/src/keybindings/__tests__/node-keybindings-source.test.ts +8 -1
- package/src/keybindings/node-keybindings-source.ts +6 -6
- package/src/render.tsx +7 -1
- package/src/terminal-capabilities.ts +1 -1
- package/src/tui-channel-options.ts +2 -0
- package/src/tui-session-options.ts +4 -0
- package/src/utils/__tests__/tool-call-extractor.test.ts +1 -1
|
@@ -37,7 +37,7 @@ vi.mock('@robota-sdk/agent-framework', async (importOriginal) => {
|
|
|
37
37
|
import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
|
|
38
38
|
|
|
39
39
|
/** The session's working directory: private to this run, never a fixed name under /tmp. */
|
|
40
|
-
const PROJECT_DIR = mkdtempSync(join(tmpdir(), '
|
|
40
|
+
const PROJECT_DIR = mkdtempSync(join(tmpdir(), 'agent-test-tui-init-failure-'));
|
|
41
41
|
afterAll(() => rmSync(PROJECT_DIR, { recursive: true, force: true }));
|
|
42
42
|
|
|
43
43
|
describe('TuiInteractionChannel init failure surfacing', () => {
|
|
@@ -346,7 +346,7 @@ describe('TuiStateManager', () => {
|
|
|
346
346
|
expect(mgr.executionWorkspaceSnapshot?.selectedEntryId).toBe('task:agent_1');
|
|
347
347
|
});
|
|
348
348
|
|
|
349
|
-
// ── Display order: Tool →
|
|
349
|
+
// ── Display order: Tool → Fixture Agent ──────────────────────────────
|
|
350
350
|
|
|
351
351
|
it('streaming state is cleared on complete (tools moved to messages)', () => {
|
|
352
352
|
const mgr = new TuiStateManager();
|
|
@@ -432,7 +432,7 @@ describe('TuiStateManager', () => {
|
|
|
432
432
|
expect(mgr.history[2]!.type).toBe('assistant');
|
|
433
433
|
});
|
|
434
434
|
|
|
435
|
-
it('after abort: streaming cleared, messages synced with tool →
|
|
435
|
+
it('after abort: streaming cleared, messages synced with tool → agent → system', () => {
|
|
436
436
|
const mgr = new TuiStateManager();
|
|
437
437
|
|
|
438
438
|
mgr.onThinking(true);
|
|
@@ -327,13 +327,13 @@ describe('WireTuiChannel', () => {
|
|
|
327
327
|
});
|
|
328
328
|
const pending = channel.getSnapshot().pendingUserAction;
|
|
329
329
|
expect(pending).toMatchObject({ title: 'Name?' });
|
|
330
|
-
channel.resolveUserAction(pending!, { type: 'answer', values: [], text: '
|
|
330
|
+
channel.resolveUserAction(pending!, { type: 'answer', values: [], text: 'Fixture Agent' });
|
|
331
331
|
await flush();
|
|
332
332
|
expect(link.sent).toEqual([
|
|
333
333
|
{
|
|
334
334
|
type: 'ask-response',
|
|
335
335
|
id: 'a1',
|
|
336
|
-
response: { type: 'answer', values: [], text: '
|
|
336
|
+
response: { type: 'answer', values: [], text: 'Fixture Agent' },
|
|
337
337
|
},
|
|
338
338
|
]);
|
|
339
339
|
await channel.stop();
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
resolveKeybindingInput,
|
|
12
12
|
} from '../keybinding-registry.js';
|
|
13
13
|
|
|
14
|
-
const SOURCE = '/tmp/
|
|
14
|
+
const SOURCE = '/tmp/agent-test-keybindings.json';
|
|
15
15
|
|
|
16
16
|
function parse(document: unknown) {
|
|
17
17
|
return parseKeybindingsDocument(JSON.stringify(document), SOURCE);
|
|
@@ -217,7 +217,7 @@ describe('contextual keybinding registry', () => {
|
|
|
217
217
|
$defs: Record<string, { properties?: Record<string, unknown> }>;
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
expect(schema.$id).toBe('https://docs.robota.io
|
|
220
|
+
expect(schema.$id).toBe('https://docs.robota.io//schemas/keybindings.schema.json');
|
|
221
221
|
expect(Object.keys(schema.properties.bindings.properties)).toEqual(
|
|
222
222
|
Object.keys(KEYBINDING_ACTIONS),
|
|
223
223
|
);
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
const roots: string[] = [];
|
|
12
12
|
|
|
13
13
|
async function temporaryRoot(): Promise<string> {
|
|
14
|
-
const root = await mkdtemp(join(tmpdir(), '
|
|
14
|
+
const root = await mkdtemp(join(tmpdir(), 'agent-test-keybindings-'));
|
|
15
15
|
roots.push(root);
|
|
16
16
|
return root;
|
|
17
17
|
}
|
|
@@ -34,6 +34,13 @@ afterEach(async () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
describe('Node keybindings source', () => {
|
|
37
|
+
it('omits the schema when the host has no documentation URL', async () => {
|
|
38
|
+
const root = await temporaryRoot();
|
|
39
|
+
const source = createNodeKeybindingsSource({ filePath: join(root, 'keys.json') });
|
|
40
|
+
expect(JSON.parse(await readFile(await source.ensureFile(), 'utf8'))).toEqual({ version: 1, bindings: {} });
|
|
41
|
+
source.dispose();
|
|
42
|
+
});
|
|
43
|
+
|
|
37
44
|
it('uses only the host-selected file and schema when creating the sparse document', async () => {
|
|
38
45
|
const root = await temporaryRoot();
|
|
39
46
|
const filePath = join(root, 'host-config', 'keys.json');
|
|
@@ -25,15 +25,15 @@ export interface IKeybindingsSource extends IKeybindingsFilePort {
|
|
|
25
25
|
|
|
26
26
|
export interface INodeKeybindingsSourceOptions {
|
|
27
27
|
readonly filePath: string;
|
|
28
|
-
readonly schemaUrl
|
|
28
|
+
readonly schemaUrl?: string;
|
|
29
29
|
readonly onDiagnostic?: (diagnostic: IKeybindingDiagnostic) => void;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
function defaultDocument(schemaUrl: string) {
|
|
33
|
-
return { $schema: schemaUrl, version: 1 as const, bindings: {} };
|
|
32
|
+
function defaultDocument(schemaUrl: string | undefined) {
|
|
33
|
+
return { ...(schemaUrl === undefined ? {} : { $schema: schemaUrl }), version: 1 as const, bindings: {} };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function defaultSnapshot(filePath: string, schemaUrl: string): IKeybindingSnapshot {
|
|
36
|
+
function defaultSnapshot(filePath: string, schemaUrl: string | undefined): IKeybindingSnapshot {
|
|
37
37
|
const parsed = parseKeybindingsDocument(JSON.stringify(defaultDocument(schemaUrl)), filePath);
|
|
38
38
|
if (!parsed.ok) throw new Error(parsed.diagnostic.message);
|
|
39
39
|
return parsed.snapshot;
|
|
@@ -41,7 +41,7 @@ function defaultSnapshot(filePath: string, schemaUrl: string): IKeybindingSnapsh
|
|
|
41
41
|
|
|
42
42
|
class NodeKeybindingsSource implements IKeybindingsSource {
|
|
43
43
|
readonly filePath: string;
|
|
44
|
-
readonly #schemaUrl: string;
|
|
44
|
+
readonly #schemaUrl: string | undefined;
|
|
45
45
|
readonly #onDiagnostic: ((diagnostic: IKeybindingDiagnostic) => void) | undefined;
|
|
46
46
|
readonly #listeners = new Set<(snapshot: IKeybindingSnapshot) => void>();
|
|
47
47
|
#snapshot: IKeybindingSnapshot;
|
|
@@ -52,7 +52,7 @@ class NodeKeybindingsSource implements IKeybindingsSource {
|
|
|
52
52
|
|
|
53
53
|
constructor(options: INodeKeybindingsSourceOptions) {
|
|
54
54
|
if (options.filePath.trim() === '') throw new Error('Keybindings file path must not be empty.');
|
|
55
|
-
if (options.schemaUrl
|
|
55
|
+
if (options.schemaUrl?.trim() === '') throw new Error('Keybindings schema URL must not be empty.');
|
|
56
56
|
this.filePath = options.filePath;
|
|
57
57
|
this.#schemaUrl = options.schemaUrl;
|
|
58
58
|
this.#onDiagnostic = options.onDiagnostic;
|
package/src/render.tsx
CHANGED
|
@@ -101,9 +101,10 @@ export interface IRenderOptions {
|
|
|
101
101
|
contributionSources?: readonly IContributionSource[];
|
|
102
102
|
skillRoots?: readonly ISkillRootDescriptor[];
|
|
103
103
|
userSettingsSources?: readonly INodeHostSettingsSource[];
|
|
104
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
104
105
|
/**
|
|
105
106
|
* CLI-083 (issue #2287) — the org policy, forwarded to the session so `blockedCommands` is
|
|
106
|
-
* enforced on the
|
|
107
|
+
* enforced on the direct CLI path as well as under `--serve`.
|
|
107
108
|
*
|
|
108
109
|
* DECLARED, not merely spread through. The shell forwards this with
|
|
109
110
|
* `...(orgPolicy === null ? {} : { orgPolicy })`, and a spread bypasses TypeScript's
|
|
@@ -186,6 +187,7 @@ export interface IRenderOptions {
|
|
|
186
187
|
defaultTools?: readonly IToolWithEventService[];
|
|
187
188
|
/** The sandbox the shell tools run under, so the session can let a confined command skip the prompt. */
|
|
188
189
|
sandboxClient?: ICreateSessionOptions['sandboxClient'];
|
|
190
|
+
toolExecutionPolicy?: ICreateSessionOptions['toolExecutionPolicy'];
|
|
189
191
|
commandModules?: readonly ICommandModule[];
|
|
190
192
|
commandHostAdapters?: ICommandHostAdapters;
|
|
191
193
|
shellExec?: TShellExecFn;
|
|
@@ -305,6 +307,7 @@ export function toChannelOptions(
|
|
|
305
307
|
...(options.userSettingsSources !== undefined
|
|
306
308
|
? { userSettingsSources: options.userSettingsSources }
|
|
307
309
|
: {}),
|
|
310
|
+
...(options.environment !== undefined ? { environment: options.environment } : {}),
|
|
308
311
|
...(options.orgPolicy !== undefined ? { orgPolicy: options.orgPolicy } : {}),
|
|
309
312
|
...(options.externalEventVerifierFactory !== undefined
|
|
310
313
|
? { externalEventVerifierFactory: options.externalEventVerifierFactory }
|
|
@@ -351,6 +354,9 @@ export function toChannelOptions(
|
|
|
351
354
|
: {}),
|
|
352
355
|
...(options.additionalTools !== undefined ? { additionalTools: options.additionalTools } : {}),
|
|
353
356
|
...(options.defaultTools !== undefined ? { defaultTools: options.defaultTools } : {}),
|
|
357
|
+
...(options.toolExecutionPolicy !== undefined
|
|
358
|
+
? { toolExecutionPolicy: options.toolExecutionPolicy }
|
|
359
|
+
: {}),
|
|
354
360
|
...(options.sandboxClient !== undefined ? { sandboxClient: options.sandboxClient } : {}),
|
|
355
361
|
commandModules: options.commandModules,
|
|
356
362
|
commandHostAdapters: options.commandHostAdapters,
|
|
@@ -41,7 +41,7 @@ export function supportsImeCursorPositioning(
|
|
|
41
41
|
* CLI-2004 — may this process write OSC 133 shell-integration marks at agent-turn boundaries?
|
|
42
42
|
*
|
|
43
43
|
* The marks are otherwise harmless: every emulator surveyed discards an unknown OSC sequence, so
|
|
44
|
-
* this gate carries DOCUMENTED negatives, not a capability probe.
|
|
44
|
+
* this gate carries DOCUMENTED negatives, not a capability probe. The support table (the TUI
|
|
45
45
|
* package's docs/SPEC.md) is the user-facing half of the same list.
|
|
46
46
|
*
|
|
47
47
|
* Precedence:
|
|
@@ -80,6 +80,7 @@ export interface ITuiInteractionChannelOptions {
|
|
|
80
80
|
contributionSources?: ICreateSessionOptions['contributionSources'];
|
|
81
81
|
skillRoots?: ICreateSessionOptions['skillRoots'];
|
|
82
82
|
userSettingsSources?: readonly INodeHostSettingsSource[];
|
|
83
|
+
environment?: Readonly<Record<string, string | undefined>>;
|
|
83
84
|
/**
|
|
84
85
|
* Explicit authority- and permission-backed edit checkpoint capability for this channel's one
|
|
85
86
|
* session. Never shared with another channel's session: a store holds one turn in progress.
|
|
@@ -131,6 +132,7 @@ export interface ITuiInteractionChannelOptions {
|
|
|
131
132
|
defaultTools?: readonly IToolWithEventService[];
|
|
132
133
|
/** The sandbox the shell tools run under, so the session can let a confined command skip the prompt. */
|
|
133
134
|
sandboxClient?: ICreateSessionOptions['sandboxClient'];
|
|
135
|
+
toolExecutionPolicy?: ICreateSessionOptions['toolExecutionPolicy'];
|
|
134
136
|
commandModules?: readonly ICommandModule[];
|
|
135
137
|
commandHostAdapters?: ICommandHostAdapters;
|
|
136
138
|
shellExec?: TShellExecFn;
|
|
@@ -52,6 +52,7 @@ export function buildTuiSessionOptions(
|
|
|
52
52
|
...(opts.userSettingsSources !== undefined
|
|
53
53
|
? { userSettingsSources: opts.userSettingsSources }
|
|
54
54
|
: {}),
|
|
55
|
+
...(opts.environment !== undefined ? { environment: opts.environment } : {}),
|
|
55
56
|
...(opts.editCheckpointStore !== undefined
|
|
56
57
|
? { editCheckpointStore: opts.editCheckpointStore }
|
|
57
58
|
: {}),
|
|
@@ -98,6 +99,9 @@ export function buildTuiSessionOptions(
|
|
|
98
99
|
...(opts.pluginDirectories !== undefined ? { pluginDirectories: opts.pluginDirectories } : {}),
|
|
99
100
|
...(opts.additionalTools !== undefined ? { additionalTools: opts.additionalTools } : {}),
|
|
100
101
|
...(opts.defaultTools !== undefined ? { defaultTools: opts.defaultTools } : {}),
|
|
102
|
+
...(opts.toolExecutionPolicy !== undefined
|
|
103
|
+
? { toolExecutionPolicy: opts.toolExecutionPolicy }
|
|
104
|
+
: {}),
|
|
101
105
|
...(opts.sandboxClient !== undefined ? { sandboxClient: opts.sandboxClient } : {}),
|
|
102
106
|
commandModules: opts.commandModules,
|
|
103
107
|
commandHostAdapters: opts.commandHostAdapters,
|
|
@@ -65,7 +65,7 @@ describe('extractToolCalls', () => {
|
|
|
65
65
|
|
|
66
66
|
it('truncates long argument values with middle ellipsis', () => {
|
|
67
67
|
const longPath =
|
|
68
|
-
'/Users/
|
|
68
|
+
'/Users/example/robota/workspace/packages/agent-sdk/src/plugins/very-long-directory-name/file.ts';
|
|
69
69
|
const history = [
|
|
70
70
|
{
|
|
71
71
|
role: 'assistant',
|