@robota-sdk/agent-ui-terminal 3.0.0-beta.86 → 3.0.0-beta.88

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +68 -14
  2. package/README.md +2 -2
  3. package/dist/node/index.d.ts +8 -2
  4. package/dist/node/index.d.ts.map +1 -1
  5. package/dist/node/index.js +14 -14
  6. package/dist/node/index.js.map +1 -1
  7. package/package.json +19 -20
  8. package/src/SupervisedSessionView.tsx +12 -2
  9. package/src/__tests__/SupervisedSessionView.test.tsx +9 -5
  10. package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +1 -1
  11. package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +1 -1
  12. package/src/__tests__/UpdateNotice.test.tsx +2 -2
  13. package/src/__tests__/attached-app-session-switch.test.tsx +1 -1
  14. package/src/__tests__/attention-bell-and-marks.test.ts +5 -5
  15. package/src/__tests__/channel-factory-integration.test.ts +3 -3
  16. package/src/__tests__/checkpoint-store-per-channel.test.ts +1 -1
  17. package/src/__tests__/cjk-fallback-render.test.tsx +2 -2
  18. package/src/__tests__/command-handoff-pty-e2e.test.ts +2 -2
  19. package/src/__tests__/command-result-handler.test.ts +3 -3
  20. package/src/__tests__/fork-attach.test.tsx +3 -3
  21. package/src/__tests__/helpers/terminal-profiles.ts +4 -4
  22. package/src/__tests__/humanize-tool-name.test.ts +1 -1
  23. package/src/__tests__/org-policy-reaches-the-channel.test.ts +5 -5
  24. package/src/__tests__/product-display-name.test.tsx +5 -5
  25. package/src/__tests__/pty/ask-user-question.ptytest.ts +2 -2
  26. package/src/__tests__/pty/background-work-switcher.ptytest.ts +3 -3
  27. package/src/__tests__/pty/behavior-2437-git.ptytest.ts +9 -9
  28. package/src/__tests__/pty/flag-tui.ptytest.ts +2 -2
  29. package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +53 -24
  30. package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +59 -14
  31. package/src/__tests__/pty/ime-cursor.ptytest.ts +4 -4
  32. package/src/__tests__/pty/isolated-home.ts +37 -4
  33. package/src/__tests__/pty/org-policy.ptytest.ts +3 -3
  34. package/src/__tests__/pty/provider-setup.ptytest.ts +2 -2
  35. package/src/__tests__/pty/pty-driver.ts +8 -6
  36. package/src/__tests__/pty/replay-conversation.ptytest.ts +2 -2
  37. package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +2 -2
  38. package/src/__tests__/pty/screen-006-no-color.ptytest.ts +4 -4
  39. package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +3 -3
  40. package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +2 -2
  41. package/src/__tests__/pty/screen-2002-themes.ptytest.ts +3 -3
  42. package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +3 -3
  43. package/src/__tests__/pty/screen-reader-mode.ptytest.ts +16 -16
  44. package/src/__tests__/pty/session-attach.ptytest.ts +66 -28
  45. package/src/__tests__/pty/settings-screen.ptytest.ts +2 -2
  46. package/src/__tests__/pty/spawn-pty.ts +3 -3
  47. package/src/__tests__/pty/terminal-handoff.ptytest.ts +2 -2
  48. package/src/__tests__/pty/trust-prompt.ptytest.ts +5 -5
  49. package/src/__tests__/pty/tui-pty.ptytest.ts +2 -2
  50. package/src/__tests__/screen-reader-labels.test.ts +1 -1
  51. package/src/__tests__/screen-reader-pacing.test.ts +3 -3
  52. package/src/__tests__/screen-reader-render-options.test.ts +3 -3
  53. package/src/__tests__/session-switch-channel.test.tsx +1 -1
  54. package/src/__tests__/terminal-capabilities.test.ts +9 -9
  55. package/src/__tests__/terminal-handoff-pty-e2e.test.ts +1 -1
  56. package/src/__tests__/tui-channel-init-failure.test.ts +1 -1
  57. package/src/__tests__/tui-state-manager.test.ts +2 -2
  58. package/src/__tests__/wire-tui-channel.test.ts +2 -2
  59. package/src/keybindings/__tests__/keybinding-registry.test.ts +2 -2
  60. package/src/keybindings/__tests__/node-keybindings-source.test.ts +8 -1
  61. package/src/keybindings/node-keybindings-source.ts +6 -6
  62. package/src/render.tsx +7 -1
  63. package/src/terminal-capabilities.ts +1 -1
  64. package/src/tui-channel-options.ts +2 -0
  65. package/src/tui-session-options.ts +4 -0
  66. package/src/utils/__tests__/tool-call-extractor.test.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-ui-terminal",
3
- "version": "3.0.0-beta.86",
3
+ "version": "3.0.0-beta.88",
4
4
  "description": "Terminal UI package (React + Ink) for the Robota SDK",
5
5
  "type": "module",
6
6
  "main": "dist/node/index.js",
@@ -23,15 +23,6 @@
23
23
  },
24
24
  "./package.json": "./package.json"
25
25
  },
26
- "repository": {
27
- "type": "git",
28
- "url": "https://github.com/woojubb/robota.git",
29
- "directory": "packages/agent-ui-terminal"
30
- },
31
- "homepage": "https://docs.robota.io/en/packages/agent-ui-terminal/",
32
- "bugs": {
33
- "url": "https://github.com/woojubb/robota/issues"
34
- },
35
26
  "engines": {
36
27
  "node": ">=22.12.0"
37
28
  },
@@ -47,15 +38,15 @@
47
38
  "marked-terminal": "^7.3.0",
48
39
  "react": "19.2.8",
49
40
  "string-width": "^8.2.2",
50
- "@robota-sdk/agent-core": "3.0.0-beta.86",
51
- "@robota-sdk/agent-framework": "3.0.0-beta.86",
52
- "@robota-sdk/agent-interface-analytics": "3.0.0-beta.86",
53
- "@robota-sdk/agent-interface-command": "3.0.0-beta.86",
54
- "@robota-sdk/agent-interface-execution": "3.0.0-beta.86",
55
- "@robota-sdk/agent-interface-session": "3.0.0-beta.86",
56
- "@robota-sdk/agent-interface-transport": "3.0.0-beta.86",
57
- "@robota-sdk/agent-interface-tui": "3.0.0-beta.86",
58
- "@robota-sdk/agent-transport": "3.0.0-beta.86"
41
+ "@robota-sdk/agent-core": "3.0.0-beta.88",
42
+ "@robota-sdk/agent-framework": "3.0.0-beta.88",
43
+ "@robota-sdk/agent-interface-analytics": "3.0.0-beta.88",
44
+ "@robota-sdk/agent-interface-command": "3.0.0-beta.88",
45
+ "@robota-sdk/agent-interface-execution": "3.0.0-beta.88",
46
+ "@robota-sdk/agent-interface-session": "3.0.0-beta.88",
47
+ "@robota-sdk/agent-interface-transport": "3.0.0-beta.88",
48
+ "@robota-sdk/agent-interface-tui": "3.0.0-beta.88",
49
+ "@robota-sdk/agent-transport": "3.0.0-beta.88"
59
50
  },
60
51
  "devDependencies": {
61
52
  "@homebridge/node-pty-prebuilt-multiarch": "^0.14.1",
@@ -66,7 +57,7 @@
66
57
  "tsx": "^4.23.1",
67
58
  "typescript": "^6.0.3",
68
59
  "vitest": "^3.2.6",
69
- "@robota-sdk/agent-command": "3.0.0-beta.86"
60
+ "@robota-sdk/agent-command": "3.0.0-beta.88"
70
61
  },
71
62
  "license": "AGPL-3.0-only OR LicenseRef-Commercial",
72
63
  "publishConfig": {
@@ -75,6 +66,14 @@
75
66
  "volta": {
76
67
  "extends": "../../package.json"
77
68
  },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "https://github.com/woojubb/robota"
72
+ },
73
+ "bugs": {
74
+ "url": "https://github.com/woojubb/robota/issues"
75
+ },
76
+ "homepage": "https://robota.io/",
78
77
  "scripts": {
79
78
  "build": "rimraf dist && tsdown",
80
79
  "build:js": "pnpm run build",
@@ -71,6 +71,8 @@ export interface ISupervisedSessionViewProps {
71
71
  readonly onStart?: (choice?: TSupervisedStartTrustChoice) => Promise<string>;
72
72
  /** The question to ask when the folder a new session starts in is not trusted yet; none otherwise. */
73
73
  readonly startTrustQuestion?: () => Promise<ISupervisedStartTrustQuestion | undefined>;
74
+ /** Product-owned command shown when a long trust question is shortened. */
75
+ readonly trustStatusCommand?: string;
74
76
  readonly onOpenPr?: (id: string, url: string, generation: string) => Promise<void>;
75
77
  readonly filteredByCwd?: boolean;
76
78
  readonly filteredByName?: boolean;
@@ -89,10 +91,13 @@ const TRUST_SUMMARY =
89
91
  "Trusting it loads the project's own settings, hooks, plugins, skills and MCP servers:";
90
92
 
91
93
  /** `lines` in at most `room` lines, the last one saying how many more there are. */
92
- function fitLines(lines: readonly string[], room: number): readonly string[] {
94
+ function fitLines(lines: readonly string[], room: number, trustStatusCommand?: string): readonly string[] {
93
95
  if (lines.length <= room) return lines;
94
96
  const shown = Math.max(1, room - 1);
95
- return [...lines.slice(0, shown), `… ${lines.length - shown} more — see robota trust status`];
97
+ const hint = trustStatusCommand === undefined
98
+ ? 'check workspace trust in the terminal'
99
+ : `see ${trustStatusCommand}`;
100
+ return [...lines.slice(0, shown), `… ${lines.length - shown} more — ${hint}`];
96
101
  }
97
102
  type TGroup = (typeof GROUP_ORDER)[number];
98
103
  type TDisplayLine =
@@ -227,6 +232,7 @@ export default function SupervisedSessionView({
227
232
  onStop,
228
233
  onStart,
229
234
  startTrustQuestion,
235
+ trustStatusCommand,
230
236
  onOpenPr,
231
237
  onAttach,
232
238
  filteredByCwd = false,
@@ -651,6 +657,7 @@ export default function SupervisedSessionView({
651
657
  : fitLines(
652
658
  [`Not trusted: ${trustQuestion.folder}`, TRUST_SUMMARY, ...trustQuestion.loads],
653
659
  screenReader ? Number.POSITIVE_INFINITY : viewport,
660
+ trustStatusCommand,
654
661
  );
655
662
  const chromeWrap = screenReader ? {} : { wrap: 'truncate-end' as const };
656
663
  // Attach and peek are offered only for a row this terminal could actually attach to.
@@ -829,6 +836,9 @@ export async function renderSupervisedSessionView(
829
836
  });
830
837
  }
831
838
  let attach: ISupervisedAttachRequest | undefined;
839
+ // The attached App pauses stdin when it unmounts. A view reopened after detach must resume it
840
+ // before Ink subscribes again, otherwise the view paints but ignores q/Escape and other keys.
841
+ process.stdin.resume();
832
842
  const instance = render(
833
843
  <ScreenReaderProvider enabled={options.screenReader}>
834
844
  <SupervisedSessionView
@@ -28,7 +28,7 @@ const THIRD: ISupervisedViewRow = {
28
28
 
29
29
  const QUESTION = {
30
30
  folder: '/work/repo',
31
- loads: ['Project sources (metadata only; content not read):', ' [file] .robota/settings.json — settings'],
31
+ loads: ['Project sources (metadata only; content not read):', ' [file] state/settings.json — settings'],
32
32
  };
33
33
 
34
34
  describe('supervised session view', () => {
@@ -403,7 +403,7 @@ describe('supervised session view', () => {
403
403
  await vi.waitFor(() => expect(view.lastFrame()).toContain('r Start Restricted'));
404
404
  // It names the folder and what trusting it would load.
405
405
  expect(view.lastFrame()).toContain('Not trusted: /work/repo');
406
- expect(view.lastFrame()).toContain('.robota/settings.json');
406
+ expect(view.lastFrame()).toContain('state/settings.json');
407
407
  expect(start).not.toHaveBeenCalled();
408
408
  // n cancels: nothing starts.
409
409
  view.stdin.write('n');
@@ -423,13 +423,16 @@ describe('supervised session view', () => {
423
423
  }
424
424
  });
425
425
 
426
- it('fits a long trust question in a 24-row terminal, the folder and the choices whole', async () => {
426
+ it.each(['alpha-agent', 'beta-agent'] as const)(
427
+ 'shows the configured trust-status command when fitting a long question for %s',
428
+ async (cliName) => {
427
429
  const loads = Array.from({ length: 32 }, (_, index) => ` [absent] source-${index} — a source`);
428
430
  const view = render(
429
431
  <SupervisedSessionView
430
432
  loadRows={async () => [FIRST]}
431
433
  onStart={async () => SECOND.id}
432
434
  startTrustQuestion={async () => ({ folder: '/work/repo', loads })}
435
+ trustStatusCommand={`${cliName} trust status`}
433
436
  />,
434
437
  );
435
438
  try {
@@ -441,12 +444,13 @@ describe('supervised session view', () => {
441
444
  expect(lines.length).toBeLessThanOrEqual(24);
442
445
  expect(lines).toContain('Not trusted: /work/repo');
443
446
  expect(lines).toContain('y Trust and start / r Start Restricted / n Cancel');
444
- expect(frame).toMatch(/… \d+ more — see robota trust status/);
447
+ expect(frame).toMatch(new RegExp(`… \\d+ more — see ${cliName} trust status`));
445
448
  expect(frame).toContain('source-0 — a source');
446
449
  } finally {
447
450
  view.unmount();
448
451
  }
449
- });
452
+ },
453
+ );
450
454
 
451
455
  it('cancels the trust question on Escape in screen-reader mode, and keeps the view open', async () => {
452
456
  const start = vi.fn(async () => SECOND.id);
@@ -81,7 +81,7 @@ import type { IAIProvider, IHistoryEntry } from '@robota-sdk/agent-core';
81
81
  import type { IExecutionResult } from '@robota-sdk/agent-interface-session';
82
82
 
83
83
  /** The session's working directory: private to this run, never a fixed name under /tmp. */
84
- const SESSION_CWD = mkdtempSync(join(tmpdir(), 'robota-tui-display-'));
84
+ const SESSION_CWD = mkdtempSync(join(tmpdir(), 'agent-test-tui-display-'));
85
85
  afterAll(() => rmSync(SESSION_CWD, { recursive: true, force: true }));
86
86
 
87
87
  // ── Helpers ───────────────────────────────────────────────────────────────────
@@ -131,7 +131,7 @@ function makeMockTransportRegistry(): {
131
131
  }
132
132
 
133
133
  /** The session's working directory: private to this run, never a fixed name under /tmp. */
134
- const SESSION_CWD = mkdtempSync(join(tmpdir(), 'robota-tui-lifecycle-'));
134
+ const SESSION_CWD = mkdtempSync(join(tmpdir(), 'agent-test-tui-lifecycle-'));
135
135
  afterAll(() => rmSync(SESSION_CWD, { recursive: true, force: true }));
136
136
 
137
137
  function makeChannel(opts?: {
@@ -6,10 +6,10 @@ import UpdateNotice from '../UpdateNotice.js';
6
6
  describe('UpdateNotice', () => {
7
7
  it('renders an update notice outside session history', () => {
8
8
  const { lastFrame } = render(
9
- <UpdateNotice message="Robota update available. Run npm install -g '@robota-sdk/agent-cli@latest'." />,
9
+ <UpdateNotice message="Fixture Agent update available. Run npm install -g '@robota-sdk/agent-cli@latest'." />,
10
10
  );
11
11
 
12
- expect(lastFrame()).toContain('Robota update available');
12
+ expect(lastFrame()).toContain('Fixture Agent update available');
13
13
  expect(lastFrame()).toContain('npm install');
14
14
  });
15
15
  });
@@ -79,7 +79,7 @@ describe('the full App attached to a host over the wire', () => {
79
79
  let cwd: string;
80
80
 
81
81
  beforeEach(() => {
82
- cwd = mkdtempSync(join(tmpdir(), 'robota-attached-app-'));
82
+ cwd = mkdtempSync(join(tmpdir(), 'agent-attached-app-'));
83
83
  });
84
84
 
85
85
  afterEach(() => {
@@ -123,22 +123,22 @@ describe('TC-09: the OSC 133 turn marks', () => {
123
123
 
124
124
  describe('supportsTurnMarks — the documented negatives', () => {
125
125
  it('is off in WezTerm, which owns OSC 133 itself', () => {
126
- vi.stubEnv('ROBOTA_TURN_MARKS', '');
126
+ vi.stubEnv('PRODUCT_TURN_MARKS', '');
127
127
  vi.stubEnv('TERM_PROGRAM', 'WezTerm');
128
128
  expect(supportsTurnMarks()).toBe(false);
129
129
  });
130
130
 
131
131
  it('honours the explicit opt-in and the kill switch over everything else', () => {
132
132
  vi.stubEnv('TERM_PROGRAM', 'WezTerm');
133
- vi.stubEnv('ROBOTA_TURN_MARKS', '1');
133
+ vi.stubEnv('PRODUCT_TURN_MARKS', '1');
134
134
  expect(supportsTurnMarks({ override: true })).toBe(true);
135
- vi.stubEnv('ROBOTA_TURN_MARKS', '0');
135
+ vi.stubEnv('PRODUCT_TURN_MARKS', '0');
136
136
  expect(supportsTurnMarks({ override: false })).toBe(false);
137
137
  });
138
138
 
139
- it('ignores an ambient Robota override without a host choice', () => {
139
+ it('ignores an ambient Fixture Agent override without a host choice', () => {
140
140
  vi.stubEnv('TERM_PROGRAM', 'WezTerm');
141
- vi.stubEnv('ROBOTA_TURN_MARKS', '1');
141
+ vi.stubEnv('PRODUCT_TURN_MARKS', '1');
142
142
  expect(supportsTurnMarks()).toBe(false);
143
143
  });
144
144
  });
@@ -72,7 +72,7 @@ describe('channel factory restores persisted context (CLI-B11 TC-02)', () => {
72
72
  let channel: TuiInteractionChannel | undefined;
73
73
 
74
74
  beforeEach(() => {
75
- cwd = realpathSync(mkdtempSync(join(tmpdir(), 'robota-b11-int-')));
75
+ cwd = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-b11-int-')));
76
76
  });
77
77
 
78
78
  afterEach(async () => {
@@ -82,7 +82,7 @@ describe('channel factory restores persisted context (CLI-B11 TC-02)', () => {
82
82
  });
83
83
 
84
84
  it('createChannel(sessionId) over a real FileSessionStore yields usedTokens > 0', async () => {
85
- const store = createNodeHostSessionStore(join(cwd, '.robota', 'sessions'));
85
+ const store = createNodeHostSessionStore(join(cwd, 'state', 'sessions'));
86
86
  const sessionId = 'b11-restore-session';
87
87
  persistConversation(store, sessionId, cwd);
88
88
 
@@ -118,7 +118,7 @@ describe('channel factory restores persisted context (CLI-B11 TC-02)', () => {
118
118
  });
119
119
 
120
120
  it('a channel created WITHOUT resumeSessionId starts with an empty context (control)', async () => {
121
- const store = createNodeHostSessionStore(join(cwd, '.robota', 'sessions'));
121
+ const store = createNodeHostSessionStore(join(cwd, 'state', 'sessions'));
122
122
  persistConversation(store, 'b11-other-session', cwd);
123
123
 
124
124
  const scripted = createScriptedProvider([{ text: 'unused' }]);
@@ -57,7 +57,7 @@ describe('#3231: the in-process channel factory', () => {
57
57
  });
58
58
 
59
59
  it('gives each channel, including a switched-to one, its own checkpoint store', () => {
60
- cwd = realpathSync(mkdtempSync(join(tmpdir(), 'robota-store-per-channel-')));
60
+ cwd = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-store-per-channel-')));
61
61
  const createEditCheckpointStore = vi.fn(() => ({}) as EditCheckpointStore);
62
62
  const options: IRenderOptions = {
63
63
  cwd,
@@ -2,7 +2,7 @@
2
2
  * CLI-062 — fallback-path pinning guard.
3
3
  *
4
4
  * When real-cursor positioning is NOT active (capability off — the default in this test process:
5
- * stdout is not a TTY and ROBOTA_IME_CURSOR is unset), CjkTextInput's rendering must stay
5
+ * stdout is not a TTY and PRODUCT_IME_CURSOR is unset), CjkTextInput's rendering must stay
6
6
  * byte-identical to the pre-CLI-062 behavior (invariant I4: guard fail → today's visuals, drawn
7
7
  * inverse cursor included). The expected strings below are literal captures of the component's
8
8
  * output BEFORE the CLI-062 change (chalk.level forced to 3 so the inverse-cursor bytes are pinned,
@@ -31,7 +31,7 @@ describe('CLI-062 — fallback rendering is byte-identical to pre-change output'
31
31
  });
32
32
 
33
33
  it('sanity: capability is off in this test process (fallback path is the one being pinned)', () => {
34
- expect(process.env['ROBOTA_IME_CURSOR']).toBeUndefined();
34
+ expect(process.env['PRODUCT_IME_CURSOR']).toBeUndefined();
35
35
  expect(Boolean(process.stdout.isTTY)).toBe(false);
36
36
  });
37
37
 
@@ -45,7 +45,7 @@ describe('command handoff PTY E2E', () => {
45
45
  it(
46
46
  '/shell runs a one-shot command on the real terminal and returns its exit code',
47
47
  async () => {
48
- const dir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-shell-pty-')));
48
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-shell-pty-')));
49
49
  tempDirs.push(dir);
50
50
  const outputPath = join(dir, 'result.json');
51
51
  const session = spawnPtyFixture(FIXTURE, {
@@ -77,7 +77,7 @@ describe('command handoff PTY E2E', () => {
77
77
  it(
78
78
  '/editor opens $EDITOR on the real terminal and round-trips the composed text',
79
79
  async () => {
80
- const dir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-editor-pty-')));
80
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-editor-pty-')));
81
81
  tempDirs.push(dir);
82
82
  const outputPath = join(dir, 'result.json');
83
83
  const session = spawnPtyFixture(FIXTURE, {
@@ -26,7 +26,7 @@ import { applySystemCommandResult } from '../hooks/command-result-handler.js';
26
26
  const PLUGIN_SOURCE_NAME = 'plugin';
27
27
 
28
28
  function reloadPluginCommandSource(registry: CommandRegistry): void {
29
- const pluginsDir = join(process.env.HOME ?? homedir(), '.robota', 'plugins');
29
+ const pluginsDir = join(process.env.HOME ?? homedir(), 'state', 'plugins');
30
30
  const loader = new BundlePluginLoader(pluginsDir);
31
31
  try {
32
32
  // allow-fallback: test helper — empty registry on load error is safe
@@ -119,10 +119,10 @@ describe('applySystemCommandResult', () => {
119
119
  });
120
120
 
121
121
  it('reloads plugin command source immediately when requested', () => {
122
- const home = realpathSync(mkdtempSync(join(tmpdir(), 'robota-plugin-reload-')));
122
+ const home = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-plugin-reload-')));
123
123
  const pluginDir = join(
124
124
  home,
125
- '.robota',
125
+ 'state',
126
126
  'plugins',
127
127
  'cache',
128
128
  'community',
@@ -191,16 +191,16 @@ describe('attach refuses rather than stranding the terminal (CLI-1994 TC-09)', (
191
191
  notify: neutral,
192
192
  });
193
193
  expect(neutral.mock.calls[0]?.[0]).toContain(FORK_SESSION_ID);
194
- expect(neutral.mock.calls[0]?.[0]).not.toContain('robota');
194
+ expect(neutral.mock.calls[0]?.[0]).not.toContain('agent');
195
195
 
196
196
  const product = vi.fn();
197
197
  attachToForkedSession(entry, {
198
198
  hasSessionRecord: storeWith([FORK_SESSION_ID]),
199
199
  switchSession: vi.fn(),
200
200
  notify: product,
201
- formatResumeCommand: (id) => `robota --resume ${id}`,
201
+ formatResumeCommand: (id) => `agent --resume ${id}`,
202
202
  });
203
- expect(product.mock.calls[0]?.[0]).toContain(`robota --resume ${FORK_SESSION_ID}`);
203
+ expect(product.mock.calls[0]?.[0]).toContain(`agent --resume ${FORK_SESSION_ID}`);
204
204
  });
205
205
 
206
206
  it('a missing session record is refused, naming the record and the task status', () => {
@@ -2,7 +2,7 @@
2
2
  * The terminal matrix, as data.
3
3
  *
4
4
  * Checking IME cursor placement across terminals was a *manual* matrix ("iTerm2 + Terminal.app
5
- * ±ROBOTA_IME_CURSOR, kitty/WezTerm/Ghostty/Windows Terminal/tmux on real hardware"). This module
5
+ * ±PRODUCT_IME_CURSOR, kitty/WezTerm/Ghostty/Windows Terminal/tmux on real hardware"). This module
6
6
  * converts it into something a machine re-runs: each row is the environment handshake a terminal
7
7
  * emulator hands the program it launches, which is the ONLY channel through which the emulator's
8
8
  * identity reaches our code (`supportsImeCursorPositioning()` reads `TERM_PROGRAM`).
@@ -105,7 +105,7 @@ export const TERMINAL_PROFILES: readonly ITerminalProfile[] = [
105
105
  },
106
106
  ];
107
107
 
108
- /** `ROBOTA_IME_CURSOR` settings the matrix sweeps. `undefined` means the variable is absent. */
108
+ /** `PRODUCT_IME_CURSOR` settings the matrix sweeps. `undefined` means the variable is absent. */
109
109
  export const IME_CURSOR_SETTINGS: readonly (string | undefined)[] = [undefined, '1', '0'];
110
110
 
111
111
  export const REPRESENTATIVE_IME_PTY_CELLS: readonly {
@@ -140,13 +140,13 @@ export function profileEnv(
140
140
  ): Record<string, string> {
141
141
  return {
142
142
  ...profile.env,
143
- ...(override === undefined ? {} : { ROBOTA_IME_CURSOR: override }),
143
+ ...(override === undefined ? {} : { PRODUCT_IME_CURSOR: override }),
144
144
  };
145
145
  }
146
146
 
147
147
  /** Matrix label used in test names. */
148
148
  export function cellLabel(profile: ITerminalProfile, override: string | undefined): string {
149
149
  const setting =
150
- override === undefined ? 'ROBOTA_IME_CURSOR unset' : `ROBOTA_IME_CURSOR=${override}`;
150
+ override === undefined ? 'PRODUCT_IME_CURSOR unset' : `PRODUCT_IME_CURSOR=${override}`;
151
151
  return `${profile.label} / ${setting}`;
152
152
  }
@@ -9,7 +9,7 @@ describe('humanizeToolName (SCREEN-012)', () => {
9
9
  });
10
10
 
11
11
  it('uses a host-selected prefix without changing other tool names', () => {
12
- expect(humanizeToolName('robota_command_agent', 'robota_command_')).toBe('agent');
12
+ expect(humanizeToolName('fixture_agent_command_agent', 'fixture_agent_command_')).toBe('agent');
13
13
  expect(humanizeToolName('acme_command_agent', 'acme_command_')).toBe('agent');
14
14
  expect(humanizeToolName('acme_command_agent')).toBe('acme_command_agent');
15
15
  });
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * `toChannelOptions` builds the channel's options field by field. `orgPolicy` was not among them, so
5
5
  * a policy forwarded from the shell arrived here and was dropped, and `blockedCommands` enforcement
6
- * stayed dead on the plain `robota` path — the most common one — after two rounds of wiring it.
6
+ * stayed dead on the plain `agent` path — the most common one — after two rounds of wiring it.
7
7
  *
8
8
  * The reason it was silent is worth keeping: the shell forwards with
9
9
  * `...(orgPolicy === null ? {} : { orgPolicy })`, and a SPREAD BYPASSES TypeScript's excess-property
@@ -51,13 +51,13 @@ describe('CLI-083: the TUI projection carries the org policy', () => {
51
51
  });
52
52
 
53
53
  it('enforces a blocked command from a policy file in a real TUI-owned session', async () => {
54
- const home = realpathSync(mkdtempSync(join(tmpdir(), 'robota-tui-policy-')));
55
- mkdirSync(join(home, '.robota'));
56
- writeFileSync(join(home, '.robota', 'org-policy.json'), JSON.stringify(POLICY));
54
+ const home = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-tui-policy-')));
55
+ mkdirSync(join(home, 'state'));
56
+ writeFileSync(join(home, 'state', 'org-policy.json'), JSON.stringify(POLICY));
57
57
  vi.stubEnv('HOME', home);
58
58
  let channel: TuiInteractionChannel | undefined;
59
59
  try {
60
- const policy = loadOrgPolicy(join(home, '.robota', 'org-policy.json'));
60
+ const policy = loadOrgPolicy(join(home, 'state', 'org-policy.json'));
61
61
  expect(policy).not.toBeNull();
62
62
  channel = new TuiInteractionChannel(
63
63
  toChannelOptions(renderOptions({ cwd: home, orgPolicy: policy ?? undefined })),
@@ -23,8 +23,8 @@ describe('host-selected terminal display name', () => {
23
23
 
24
24
  expect(history.lastFrame()).toContain('Atlas:');
25
25
  expect(streaming.lastFrame()).toContain('Atlas:');
26
- expect(history.lastFrame()).not.toContain('Robota:');
27
- expect(streaming.lastFrame()).not.toContain('Robota:');
26
+ expect(history.lastFrame()).not.toContain('Fixture Agent:');
27
+ expect(streaming.lastFrame()).not.toContain('Fixture Agent:');
28
28
  });
29
29
 
30
30
  it('uses a neutral name when the host does not choose one', () => {
@@ -34,15 +34,15 @@ describe('host-selected terminal display name', () => {
34
34
 
35
35
  it('renders a projected command using the host-selected prefix', () => {
36
36
  const { lastFrame } = render(
37
- <ProductDisplayNameProvider name="Atlas" modelCommandToolPrefix="robota_command_">
37
+ <ProductDisplayNameProvider name="Atlas" modelCommandToolPrefix="fixture_agent_command_">
38
38
  <StreamingIndicator
39
39
  text=""
40
- activeTools={[{ toolName: 'robota_command_echo', firstArg: '', isRunning: true }]}
40
+ activeTools={[{ toolName: 'fixture_agent_command_echo', firstArg: '', isRunning: true }]}
41
41
  />
42
42
  </ProductDisplayNameProvider>,
43
43
  );
44
44
  expect(lastFrame()).toContain('echo(');
45
- expect(lastFrame()).not.toContain('robota_command_echo');
45
+ expect(lastFrame()).not.toContain('fixture_agent_command_echo');
46
46
  });
47
47
 
48
48
  it('uses the host name in transport restart copy', () => {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * CMD-005 TC-03: a model-issued AskUserQuestion renders as the Ink dialog mid-turn and the picked
3
- * answer returns to the model — end to end against the BUILT robota binary.
3
+ * answer returns to the model — end to end against the BUILT CLI binary.
4
4
  *
5
5
  * Boots with `--session-log` (INFRA-018 replay, no model key): the replayed assistant turn calls the
6
6
  * AskUserQuestion tool; the real TUI renders the CMD-004 PendingActionPrompt while the turn is
@@ -30,7 +30,7 @@ describe('AskUserQuestion dialog through the real binary (CMD-005)', () => {
30
30
  let session: IPtySession | undefined;
31
31
 
32
32
  beforeEach(() => {
33
- projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-ask-pty-')));
33
+ projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-ask-pty-')));
34
34
  writeTuiProviderSettings(projectDir); // dummy provider profile; replay answers every call
35
35
  });
36
36
 
@@ -2,11 +2,11 @@
2
2
  * TEST-010 / SCREEN-013: the background-work drill-in entry point, observed through a real PTY.
3
3
  *
4
4
  * Component tests cover the switcher selection + detail pane in isolation; this asserts the
5
- * App-level wiring end to end against the BUILT robota binary: pressing `Ctrl+B` in the live TUI
5
+ * App-level wiring end to end against the BUILT CLI binary: pressing `Ctrl+B` in the live TUI
6
6
  * opens the execution-workspace switcher (the only way into a background task), and `Esc` returns to
7
7
  * the prompt. The switcher always lists the main thread, so this is assertable without seeding
8
8
  * background work. Runs in the dedicated PTY project (`pnpm --filter @robota-sdk/agent-ui-terminal
9
- * test:pty`) against `packages/agent-cli/bin/robota.cjs`.
9
+ * test:pty`) against `packages/agent-cli/bin/agent.cjs`.
10
10
  */
11
11
 
12
12
  import { mkdtempSync, rmSync, realpathSync } from 'node:fs';
@@ -26,7 +26,7 @@ describe('Background-work drill-in entry point through a real PTY (TEST-010 / SC
26
26
  let session: IPtySession | undefined;
27
27
 
28
28
  beforeEach(() => {
29
- projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-bgwork-')));
29
+ projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-bgwork-')));
30
30
  writeTuiProviderSettings(projectDir);
31
31
  });
32
32
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * BEHAVIOR-2437 TC-07 — `/git status`, `/git diff` and `/git commit` on the BUILT robota binary, in
2
+ * BEHAVIOR-2437 TC-07 — `/git status`, `/git diff` and `/git commit` on the BUILT CLI binary, in
3
3
  * a real PTY, over a throwaway git repository. The four User Execution scenarios of the spec, as a
4
4
  * machine runs them: read path (S1), commit path (S2), nothing staged (S3), headless cancel (S4).
5
5
  *
@@ -15,11 +15,12 @@ import { fileURLToPath } from 'node:url';
15
15
  import { afterEach, beforeEach, describe, expect, it } from 'vitest';
16
16
 
17
17
  import { spawnTui } from './pty-driver.js';
18
+ import { createPtyEnv } from './isolated-home.js';
18
19
 
19
20
  import type { IPtySession } from './pty-driver.js';
20
21
 
21
22
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../..');
22
- const ROBOTA_BIN = join(REPO_ROOT, 'packages/agent-cli/bin/robota.cjs');
23
+ const AGENT_BIN = join(REPO_ROOT, 'packages/agent-cli/bin/agent.cjs');
23
24
 
24
25
  const WAIT_MS = 20_000;
25
26
  const CASE_TIMEOUT_MS = 120_000;
@@ -38,8 +39,7 @@ function gitEnv(home: string): NodeJS.ProcessEnv {
38
39
  const globalConfig = join(home, 'gitconfig');
39
40
  writeFileSync(globalConfig, '[commit]\n\tgpgsign = false\n');
40
41
  return {
41
- PATH: process.env['PATH'] ?? '',
42
- HOME: home,
42
+ ...createPtyEnv({ HOME: home }),
43
43
  GIT_CONFIG_NOSYSTEM: '1',
44
44
  GIT_CONFIG_GLOBAL: globalConfig,
45
45
  };
@@ -56,7 +56,7 @@ function git(fixture: IFixture, ...args: string[]): string {
56
56
 
57
57
  /** The dummy provider profile under the isolated HOME — boot/slash/exit make zero model calls. */
58
58
  function writeProviderSettings(home: string): void {
59
- const dir = join(home, '.robota');
59
+ const dir = join(home, 'state');
60
60
  mkdirSync(dir, { recursive: true });
61
61
  writeFileSync(
62
62
  join(dir, 'settings.json'),
@@ -75,7 +75,7 @@ function writeProviderSettings(home: string): void {
75
75
  * `stage`), `notes.txt` edited and left unstaged, `scratch.log` created untracked.
76
76
  */
77
77
  function makeFixture(stage: boolean): IFixture {
78
- const root = realpathSync(mkdtempSync(join(tmpdir(), 'robota-git-pty-')));
78
+ const root = realpathSync(mkdtempSync(join(tmpdir(), 'agent-git-pty-')));
79
79
  const home = join(root, 'home');
80
80
  const repo = join(root, 'repo');
81
81
  mkdirSync(home, { recursive: true });
@@ -83,7 +83,7 @@ function makeFixture(stage: boolean): IFixture {
83
83
  writeProviderSettings(home);
84
84
  const fixture: IFixture = { root, repo, home, env: gitEnv(home) };
85
85
  git(fixture, 'init', '-q', '-b', 'main');
86
- git(fixture, 'config', 'user.name', 'Robota Scenario');
86
+ git(fixture, 'config', 'user.name', 'Scenario Fixture');
87
87
  git(fixture, 'config', 'user.email', 'scenario@example.com');
88
88
  git(fixture, 'config', 'commit.gpgsign', 'false');
89
89
  writeFileSync(join(repo, 'greeting.txt'), 'hello\n');
@@ -326,7 +326,7 @@ describe('/git through the real binary (BEHAVIOR-2437 TC-07)', () => {
326
326
  () => {
327
327
  fixture = makeFixture(true);
328
328
  const env: NodeJS.ProcessEnv = { ...fixture.env, TERM: 'dumb' };
329
- const trust = spawnSync(process.execPath, [ROBOTA_BIN, 'trust', '--yes'], {
329
+ const trust = spawnSync(process.execPath, [AGENT_BIN, 'trust', '--yes'], {
330
330
  cwd: fixture.repo,
331
331
  env,
332
332
  encoding: 'utf8',
@@ -334,7 +334,7 @@ describe('/git through the real binary (BEHAVIOR-2437 TC-07)', () => {
334
334
  expect(trust.status).toBe(0);
335
335
  const result = spawnSync(
336
336
  process.execPath,
337
- [ROBOTA_BIN, '-p', '/git commit feat: add greeting', '--bare', '--no-session-persistence'],
337
+ [AGENT_BIN, '-p', '/git commit feat: add greeting', '--bare', '--no-session-persistence'],
338
338
  { cwd: fixture.repo, env, encoding: 'utf8', timeout: 60_000 },
339
339
  );
340
340
  const output = `${result.stdout}${result.stderr}`;
@@ -4,7 +4,7 @@
4
4
  * The headless flag matrix (Phase 1) asserts request/output effects; this asserts the *rendered*
5
5
  * effect a flag has on the live TUI. `--permission-mode <mode>` is shown in the status bar whenever it
6
6
  * is not the default — so booting with `plan` / `acceptEdits` must render that mode. Runs in the
7
- * dedicated PTY project against the BUILT robota binary (`pnpm --filter @robota-sdk/agent-cli build`).
7
+ * dedicated PTY project against the BUILT CLI binary (`pnpm --filter @robota-sdk/agent-cli build`).
8
8
  */
9
9
 
10
10
  import { mkdtempSync, rmSync, realpathSync } from 'node:fs';
@@ -22,7 +22,7 @@ describe('CLI flag → TUI rendering through a real PTY (TEST-009 Phase 3)', ()
22
22
  let session: IPtySession | undefined;
23
23
 
24
24
  beforeEach(() => {
25
- projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'robota-flagtui-')));
25
+ projectDir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-flagtui-')));
26
26
  writeTuiProviderSettings(projectDir);
27
27
  });
28
28