@robota-sdk/agent-transport 3.0.0-beta.72 → 3.0.0-beta.74

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 (171) hide show
  1. package/dist/node/headless/index.cjs +1 -1
  2. package/dist/node/headless/index.d.ts +1 -1
  3. package/dist/node/headless/index.js +1 -1
  4. package/dist/node/{headless-DCtHvyVf.cjs → headless-BeHAOlIM.cjs} +4 -3
  5. package/dist/node/{headless-C6tj35h3.js → headless-D02zUEGh.js} +4 -3
  6. package/dist/node/headless-D02zUEGh.js.map +1 -0
  7. package/dist/node/http/index.cjs +1 -1
  8. package/dist/node/http/index.d.ts +1 -1
  9. package/dist/node/http/index.js +1 -1
  10. package/dist/node/{http-Br10Ps8m.js → http-2Jiuflc1.js} +1 -1
  11. package/dist/node/http-2Jiuflc1.js.map +1 -0
  12. package/dist/node/http-CBAvefLw.cjs +1 -0
  13. package/dist/node/{index-BVNhOeeU.d.ts → index-BQLN_Lc9.d.ts} +5 -3
  14. package/dist/node/index-BQLN_Lc9.d.ts.map +1 -0
  15. package/dist/node/{index-C9LWCL4l.d.ts → index-BnAGE-u9.d.ts} +2 -3
  16. package/dist/node/index-BnAGE-u9.d.ts.map +1 -0
  17. package/dist/node/{index-COWvtBa2.d.ts → index-BrQ4gGw0.d.ts} +3 -3
  18. package/dist/node/index-BrQ4gGw0.d.ts.map +1 -0
  19. package/dist/node/{index-X2Zg8FEY.d.ts → index-CoeBF21y.d.ts} +3 -3
  20. package/dist/node/index-CoeBF21y.d.ts.map +1 -0
  21. package/dist/node/{index-27HV5PJB.d.ts → index-DE3-dHqw.d.ts} +8 -3
  22. package/dist/node/index-DE3-dHqw.d.ts.map +1 -0
  23. package/dist/node/{index-BRgV_MPB.d.ts → index-DHt-2VQ-.d.ts} +2 -3
  24. package/dist/node/index-DHt-2VQ-.d.ts.map +1 -0
  25. package/dist/node/{index-nBlMTFkZ.d.ts → index-DMwKN5Le.d.ts} +2 -3
  26. package/dist/node/index-DMwKN5Le.d.ts.map +1 -0
  27. package/dist/node/{index-TMAlNHuM.d.ts → index-IvYaYY6v.d.ts} +5 -3
  28. package/dist/node/index-IvYaYY6v.d.ts.map +1 -0
  29. package/dist/node/{index-BRchlFBE.d.ts → index-WKTgvhlg.d.ts} +8 -3
  30. package/dist/node/index-WKTgvhlg.d.ts.map +1 -0
  31. package/dist/node/{index-C5KNEBO9.d.ts → index-c0M42fsA.d.ts} +2 -3
  32. package/dist/node/index-c0M42fsA.d.ts.map +1 -0
  33. package/dist/node/index.cjs +1 -1
  34. package/dist/node/index.d.ts +6 -7
  35. package/dist/node/index.d.ts.map +1 -1
  36. package/dist/node/index.js +1 -1
  37. package/dist/node/index.js.map +1 -1
  38. package/dist/node/mcp/index.cjs +1 -1
  39. package/dist/node/mcp/index.d.ts +1 -1
  40. package/dist/node/mcp/index.js +1 -1
  41. package/dist/node/mcp-BOglBJNy.cjs +1 -0
  42. package/dist/node/{mcp-BAujHOMr.js → mcp-D3BBVK7C.js} +1 -1
  43. package/dist/node/mcp-D3BBVK7C.js.map +1 -0
  44. package/dist/node/{chunk-Bmb41Sf3.cjs → rolldown-runtime-CMqjfN_6.cjs} +1 -1
  45. package/dist/node/testing/index.cjs +1 -0
  46. package/dist/node/testing/index.d.ts +21 -0
  47. package/dist/node/testing/index.d.ts.map +1 -0
  48. package/dist/node/testing/index.js +2 -0
  49. package/dist/node/testing/index.js.map +1 -0
  50. package/dist/node/tui/index.cjs +1 -1
  51. package/dist/node/tui/index.d.ts +1 -1
  52. package/dist/node/tui/index.js +1 -1
  53. package/dist/node/{tui-4hA-SMtS.js → tui-Btb1q88j.js} +5 -5
  54. package/dist/node/tui-Btb1q88j.js.map +1 -0
  55. package/dist/node/tui-SbUT7Zlt.cjs +24 -0
  56. package/dist/node/ws/index.cjs +1 -1
  57. package/dist/node/ws/index.d.ts +1 -1
  58. package/dist/node/ws/index.js +1 -1
  59. package/dist/node/{ws-BWel8nzl.js → ws-Dc2RUwVs.js} +1 -1
  60. package/dist/node/ws-Dc2RUwVs.js.map +1 -0
  61. package/dist/node/ws-QNMQn5kg.cjs +1 -0
  62. package/package.json +35 -22
  63. package/src/headless/HeadlessInteractionChannel.ts +9 -1
  64. package/src/headless/__tests__/headless-channel-options.test.ts +106 -0
  65. package/src/headless/__tests__/headless-provider-failure.integration.test.ts +143 -0
  66. package/src/headless/__tests__/headless-runner-initialization.test.ts +1 -1
  67. package/src/headless/__tests__/headless-runner.test.ts +24 -3
  68. package/src/headless/__tests__/headless-transport.test.ts +1 -2
  69. package/src/headless/headless-runner.ts +3 -2
  70. package/src/headless/headless-stream-json.ts +5 -5
  71. package/src/headless/headless-transport.ts +1 -2
  72. package/src/http/__tests__/http-transport.test.ts +1 -1
  73. package/src/http/__tests__/routes.test.ts +1 -1
  74. package/src/http/http-transport.ts +1 -2
  75. package/src/http/routes.ts +1 -1
  76. package/src/mcp/__tests__/mcp-server.test.ts +1 -1
  77. package/src/mcp/__tests__/mcp-transport.test.ts +1 -1
  78. package/src/mcp/mcp-server.ts +1 -1
  79. package/src/mcp/mcp-transport.ts +1 -2
  80. package/src/testing/__tests__/scripted-provider.test.ts +73 -0
  81. package/src/testing/index.ts +7 -0
  82. package/src/testing/scripted-provider.ts +73 -0
  83. package/src/transport-registry.ts +1 -1
  84. package/src/tui/App.tsx +38 -29
  85. package/src/tui/BackgroundTaskPanel.tsx +1 -1
  86. package/src/tui/CjkTextInput.tsx +4 -8
  87. package/src/tui/ExecutionWorkspaceDetailPane.tsx +1 -1
  88. package/src/tui/ExecutionWorkspaceSwitcher.tsx +1 -1
  89. package/src/tui/InputArea.tsx +15 -7
  90. package/src/tui/InteractivePrompt.tsx +2 -2
  91. package/src/tui/PluginTUI.tsx +1 -1
  92. package/src/tui/SessionPicker.tsx +1 -1
  93. package/src/tui/SessionStatusBar.tsx +1 -1
  94. package/src/tui/SlashAutocomplete.tsx +1 -1
  95. package/src/tui/StatusBar.tsx +1 -7
  96. package/src/tui/StreamingIndicator.tsx +1 -1
  97. package/src/tui/TransportTUI.tsx +1 -1
  98. package/src/tui/TuiInteractionChannel.ts +60 -38
  99. package/src/tui/UsageSummaryEntry.tsx +1 -1
  100. package/src/tui/__tests__/PluginTUI.test.tsx +1 -1
  101. package/src/tui/__tests__/SlashAutocomplete.test.tsx +1 -1
  102. package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +1 -1
  103. package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +5 -2
  104. package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +1 -1
  105. package/src/tui/__tests__/background-task-panel.test.tsx +1 -1
  106. package/src/tui/__tests__/background-task-row-format.test.ts +1 -1
  107. package/src/tui/__tests__/channel-factory-integration.test.ts +138 -0
  108. package/src/tui/__tests__/execution-workspace-switcher.test.tsx +1 -1
  109. package/src/tui/__tests__/execution-workspace-view-model.test.ts +1 -1
  110. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +1 -1
  111. package/src/tui/__tests__/input-area-flow.test.ts +1 -1
  112. package/src/tui/__tests__/pty/pty-driver.ts +135 -0
  113. package/src/tui/__tests__/pty/tui-pty.ptytest.ts +61 -0
  114. package/src/tui/__tests__/render-channel-options.test.ts +32 -0
  115. package/src/tui/__tests__/session-init-poller.test.ts +102 -0
  116. package/src/tui/__tests__/session-switch-channel.test.tsx +307 -0
  117. package/src/tui/__tests__/slash-routing-effects.test.ts +4 -1
  118. package/src/tui/__tests__/status-activity.test.ts +3 -3
  119. package/src/tui/__tests__/status-bar.test.tsx +7 -6
  120. package/src/tui/__tests__/tui-channel-init-failure.test.ts +57 -0
  121. package/src/tui/__tests__/tui-state-manager.test.ts +1 -1
  122. package/src/tui/background-task-row-format.ts +1 -1
  123. package/src/tui/execution-workspace-view-model.ts +1 -1
  124. package/src/tui/flows/input-area-flow.ts +1 -1
  125. package/src/tui/flows/permission-prompt-flow.ts +1 -1
  126. package/src/tui/flows/session-init-poller.ts +77 -0
  127. package/src/tui/hooks/command-effect-handler.ts +4 -1
  128. package/src/tui/hooks/command-effect-queue.ts +1 -1
  129. package/src/tui/hooks/side-effects-types.ts +2 -2
  130. package/src/tui/hooks/useAutocomplete.ts +3 -2
  131. package/src/tui/hooks/usePluginCallbacks.ts +1 -1
  132. package/src/tui/hooks/usePluginScreenData.ts +1 -1
  133. package/src/tui/hooks/useSideEffects.ts +1 -1
  134. package/src/tui/hooks/useSlashRouting.ts +3 -3
  135. package/src/tui/hooks/useStatusLineSettings.ts +1 -1
  136. package/src/tui/hooks/useTuiChannel.ts +3 -3
  137. package/src/tui/plugin-tui-handlers.ts +1 -1
  138. package/src/tui/render.tsx +38 -25
  139. package/src/tui/status-activity.ts +2 -2
  140. package/src/tui/tui-cli-adapter.ts +3 -3
  141. package/src/tui/tui-state-manager.ts +2 -2
  142. package/src/tui/tui-transport.ts +4 -2
  143. package/src/ws/__tests__/ws-handler.test.ts +6 -4
  144. package/src/ws/__tests__/ws-transport.test.ts +1 -1
  145. package/src/ws/ws-background-messages.ts +1 -1
  146. package/src/ws/ws-handler.ts +4 -4
  147. package/src/ws/ws-protocol.ts +6 -4
  148. package/src/ws/ws-transport-configurable.ts +4 -2
  149. package/src/ws/ws-transport.ts +1 -2
  150. package/dist/node/headless-C6tj35h3.js.map +0 -1
  151. package/dist/node/http-Br10Ps8m.js.map +0 -1
  152. package/dist/node/http-Da6Kw4oy.cjs +0 -1
  153. package/dist/node/index-27HV5PJB.d.ts.map +0 -1
  154. package/dist/node/index-BRchlFBE.d.ts.map +0 -1
  155. package/dist/node/index-BRgV_MPB.d.ts.map +0 -1
  156. package/dist/node/index-BVNhOeeU.d.ts.map +0 -1
  157. package/dist/node/index-C5KNEBO9.d.ts.map +0 -1
  158. package/dist/node/index-C9LWCL4l.d.ts.map +0 -1
  159. package/dist/node/index-COWvtBa2.d.ts.map +0 -1
  160. package/dist/node/index-TMAlNHuM.d.ts.map +0 -1
  161. package/dist/node/index-X2Zg8FEY.d.ts.map +0 -1
  162. package/dist/node/index-nBlMTFkZ.d.ts.map +0 -1
  163. package/dist/node/mcp-BAujHOMr.js.map +0 -1
  164. package/dist/node/mcp-Bl8jUfev.cjs +0 -1
  165. package/dist/node/tui-4hA-SMtS.js.map +0 -1
  166. package/dist/node/tui-CcLmEJ1r.cjs +0 -24
  167. package/dist/node/ws-BWel8nzl.js.map +0 -1
  168. package/dist/node/ws-tCjj2gPu.cjs +0 -1
  169. package/src/tui/InkTerminal.ts +0 -42
  170. package/src/tui/hooks/use-interactive-session-init.ts +0 -91
  171. package/src/tui/hooks/usePermissionQueue.ts +0 -52
@@ -109,18 +109,19 @@ describe('StatusBar', () => {
109
109
  );
110
110
  const frame = lastFrame()!;
111
111
  expect(frame).not.toContain('Activity:');
112
- expect(frame).toContain('Tools x2');
112
+ expect(frame).toContain('Tools (2)');
113
+ expect(frame).not.toContain('Tools x2');
113
114
  expect(frame).toContain('queued');
114
115
  expect(frame).not.toContain('thinking...');
115
- expect(frame.indexOf('Tools x2')).toBeLessThan(frame.indexOf('test-model'));
116
+ expect(frame.indexOf('Tools (2)')).toBeLessThan(frame.indexOf('test-model'));
116
117
  expect(frame).not.toContain('Thinking...');
117
118
  });
118
119
 
119
120
  it('shows background activity when no foreground execution is active', () => {
120
121
  const { lastFrame } = render(<StatusBar {...baseProps} activeBackgroundTaskCount={3} />);
121
122
  const frame = lastFrame()!;
122
- expect(frame).toContain('Background x3');
123
- expect(frame.indexOf('Background x3')).toBeLessThan(frame.indexOf('test-model'));
123
+ expect(frame).toContain('Background (3)');
124
+ expect(frame.indexOf('Background (3)')).toBeLessThan(frame.indexOf('test-model'));
124
125
  });
125
126
 
126
127
  it('keeps the activity segment compact for narrow terminals', () => {
@@ -134,10 +135,10 @@ describe('StatusBar', () => {
134
135
  />,
135
136
  );
136
137
  const frame = lastFrame()!;
137
- const firstLine = frame.split('\n')[1] ?? '';
138
+ const firstLine = frame.split('\n')[0] ?? '';
138
139
  const activityEnd = firstLine.indexOf('test-model');
139
140
  const activitySegment = firstLine.slice(0, activityEnd);
140
- expect(activitySegment).toContain('Tools x12');
141
+ expect(activitySegment).toContain('Tools (12)');
141
142
  expect(activitySegment.length).toBeLessThanOrEqual(40);
142
143
  });
143
144
 
@@ -0,0 +1,57 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import type { IAIProvider } from '@robota-sdk/agent-core';
3
+
4
+ vi.mock('@robota-sdk/agent-framework', async (importOriginal) => {
5
+ const mod = await importOriginal<typeof import('@robota-sdk/agent-framework')>();
6
+
7
+ class FakeInteractiveSession {
8
+ on(): void {}
9
+ off(): void {}
10
+ getFullHistory(): unknown[] {
11
+ return [];
12
+ }
13
+ getContextState(): never {
14
+ throw new Error('ENOENT: session store unreadable');
15
+ }
16
+ getName(): string | undefined {
17
+ return undefined;
18
+ }
19
+ getSession(): { getSessionId: () => string } {
20
+ return { getSessionId: () => 'test-session' };
21
+ }
22
+ async shutdown(): Promise<void> {}
23
+ }
24
+
25
+ return { ...mod, InteractiveSession: FakeInteractiveSession };
26
+ });
27
+
28
+ import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
29
+
30
+ describe('TuiInteractionChannel init failure surfacing', () => {
31
+ beforeEach(() => {
32
+ vi.useFakeTimers();
33
+ });
34
+
35
+ afterEach(() => {
36
+ vi.useRealTimers();
37
+ });
38
+
39
+ it('TC-05: a real init error records a session-init-error entry and sets error state', async () => {
40
+ const channel = new TuiInteractionChannel({
41
+ cwd: '/tmp/project',
42
+ provider: {} as IAIProvider,
43
+ });
44
+ await channel.start();
45
+
46
+ vi.advanceTimersByTime(400);
47
+
48
+ const entries = channel.stateManager.history;
49
+ const initError = entries.find((e) => e.type === 'session-init-error');
50
+ expect(initError).toBeDefined();
51
+ const message = (initError?.data as { message?: string } | undefined)?.message ?? '';
52
+ expect(message).toContain('Session initialization failed');
53
+ expect(message).toContain('ENOENT');
54
+
55
+ await channel.stop();
56
+ });
57
+ });
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { describe, it, expect, vi } from 'vitest';
7
7
  import { TuiStateManager } from '../tui-state-manager.js';
8
- import type { IToolState, IExecutionResult } from '@robota-sdk/agent-framework';
8
+ import type { IExecutionResult, IToolState } from '@robota-sdk/agent-interface-transport';
9
9
 
10
10
  function makeResult(overrides?: Partial<IExecutionResult>): IExecutionResult {
11
11
  return {
@@ -1,6 +1,6 @@
1
1
  import { formatExecutionWorkspaceEntryRow } from './execution-workspace-view-model.js';
2
2
 
3
- import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-framework';
3
+ import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-transport';
4
4
 
5
5
  export interface IBackgroundTaskRow {
6
6
  connector: '├' | '└';
@@ -3,7 +3,7 @@ import type {
3
3
  IExecutionWorkspaceEntry,
4
4
  IExecutionWorkspaceSnapshot,
5
5
  TExecutionWorkspaceStatus,
6
- } from '@robota-sdk/agent-framework';
6
+ } from '@robota-sdk/agent-interface-transport';
7
7
 
8
8
  const ACTIVE_STATUSES: readonly TExecutionWorkspaceStatus[] = [
9
9
  'active',
@@ -1,7 +1,7 @@
1
1
  import { isSlashCommand, tokeniseSlashCommand } from '@robota-sdk/agent-framework';
2
2
 
3
3
  import type { IHistoryEntry, TUniversalValue } from '@robota-sdk/agent-core';
4
- import type { ICommand } from '@robota-sdk/agent-framework';
4
+ import type { ICommand } from '@robota-sdk/agent-interface-transport';
5
5
 
6
6
  export interface IAutocompleteInputKey {
7
7
  upArrow?: boolean;
@@ -67,7 +67,7 @@ export function applyPermissionPromptInput(
67
67
  };
68
68
  }
69
69
 
70
- export function getPermissionDecision(index: number): TPermissionPromptDecision {
70
+ function getPermissionDecision(index: number): TPermissionPromptDecision {
71
71
  if (index === 0) return true;
72
72
  if (index === 1) return 'allow-session';
73
73
  if (index === 2) return 'allow-project';
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Bounded polling helper for asynchronous session initialization.
3
+ *
4
+ * Distinguishes benign "not yet initialized" errors (retried until a timeout)
5
+ * from real failures (surfaced immediately), so the TUI can show an error
6
+ * instead of spinning forever.
7
+ */
8
+
9
+ export type TSessionInitFailure =
10
+ | { kind: 'timeout'; lastError: Error | undefined }
11
+ | { kind: 'error'; error: Error };
12
+
13
+ export interface ISessionInitPollerOptions {
14
+ /** Throws while the session is not ready; returns normally when ready. */
15
+ check: () => void;
16
+ intervalMs: number;
17
+ timeoutMs: number;
18
+ /** Returns true when an error means "keep polling". Defaults to /not initialized/i. */
19
+ isBenignError?: (error: Error) => boolean;
20
+ onReady: () => void;
21
+ onFailure: (failure: TSessionInitFailure) => void;
22
+ }
23
+
24
+ export interface ISessionInitPoller {
25
+ start(): void;
26
+ stop(): void;
27
+ }
28
+
29
+ function defaultIsBenignError(error: Error): boolean {
30
+ return /not initialized/i.test(error.message);
31
+ }
32
+
33
+ export function createSessionInitPoller(options: ISessionInitPollerOptions): ISessionInitPoller {
34
+ const isBenign = options.isBenignError ?? defaultIsBenignError;
35
+ let timer: ReturnType<typeof setInterval> | null = null;
36
+ let elapsedMs = 0;
37
+ let lastBenignError: Error | undefined;
38
+
39
+ function stop(): void {
40
+ if (timer !== null) {
41
+ clearInterval(timer);
42
+ timer = null;
43
+ }
44
+ }
45
+
46
+ function tick(): void {
47
+ elapsedMs += options.intervalMs;
48
+ try {
49
+ options.check();
50
+ } catch (raw) {
51
+ // allow-fallback: poller's purpose is to classify and route this error, never to swallow it
52
+ const error = raw instanceof Error ? raw : new Error(String(raw));
53
+ if (!isBenign(error)) {
54
+ stop();
55
+ options.onFailure({ kind: 'error', error });
56
+ return;
57
+ }
58
+ lastBenignError = error;
59
+ if (elapsedMs >= options.timeoutMs) {
60
+ stop();
61
+ options.onFailure({ kind: 'timeout', lastError: lastBenignError });
62
+ }
63
+ return;
64
+ }
65
+ stop();
66
+ options.onReady();
67
+ }
68
+
69
+ return {
70
+ start(): void {
71
+ if (timer !== null) return;
72
+ elapsedMs = 0;
73
+ timer = setInterval(tick, options.intervalMs);
74
+ },
75
+ stop,
76
+ };
77
+ }
@@ -3,7 +3,10 @@ import { isStatusLineCommandSettingsPatch } from '@robota-sdk/agent-framework';
3
3
 
4
4
  import type { ITuiCliAdapter } from '../tui-cli-adapter.js';
5
5
  import type { IHistoryEntry, TSessionEndReason } from '@robota-sdk/agent-core';
6
- import type { TCommandEffect, TStatusLineCommandSettingsPatch } from '@robota-sdk/agent-framework';
6
+ import type {
7
+ TCommandEffect,
8
+ TStatusLineCommandSettingsPatch,
9
+ } from '@robota-sdk/agent-interface-transport';
7
10
 
8
11
  export interface ICommandEffectHandlerDeps {
9
12
  addEntry: (entry: IHistoryEntry) => void;
@@ -1,4 +1,4 @@
1
- import type { ICommandInteraction, TCommandEffect } from '@robota-sdk/agent-framework';
1
+ import type { ICommandInteraction, TCommandEffect } from '@robota-sdk/agent-interface-transport';
2
2
 
3
3
  export type TQueuedCommandState =
4
4
  | {
@@ -2,9 +2,9 @@ import type { ICommandEffectQueue } from './command-effect-queue.js';
2
2
  import type { IHistoryEntry } from '@robota-sdk/agent-core';
3
3
  import type { InteractiveSession } from '@robota-sdk/agent-framework';
4
4
  import type {
5
- TCommandInteractionPrompt as TInteractivePrompt,
6
5
  IStatusLineCommandSettings as TStatusLineSettings,
7
- } from '@robota-sdk/agent-framework';
6
+ TCommandInteractionPrompt as TInteractivePrompt,
7
+ } from '@robota-sdk/agent-interface-transport';
8
8
 
9
9
  export type { TInteractivePrompt, TStatusLineSettings };
10
10
 
@@ -5,10 +5,11 @@
5
5
 
6
6
  import React, { useState, useMemo } from 'react';
7
7
 
8
- import type { CommandRegistry, ICommand } from '@robota-sdk/agent-framework';
8
+ import type { CommandRegistry } from '@robota-sdk/agent-framework';
9
+ import type { ICommand } from '@robota-sdk/agent-interface-transport';
9
10
 
10
11
  /** Parse input to determine autocomplete state */
11
- export function parseSlashInput(value: string): {
12
+ function parseSlashInput(value: string): {
12
13
  isSlash: boolean;
13
14
  parentCommand: string;
14
15
  filter: string;
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { useMemo } from 'react';
10
10
 
11
- import type { ICommandPluginAdapter } from '@robota-sdk/agent-framework';
11
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-transport';
12
12
 
13
13
  function createNoOpPluginAdapter(): ICommandPluginAdapter {
14
14
  return {
@@ -6,7 +6,7 @@
6
6
  import { useState, useEffect } from 'react';
7
7
 
8
8
  import type { IMenuSelectItem } from '../MenuSelect.js';
9
- import type { ICommandPluginAdapter } from '@robota-sdk/agent-framework';
9
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-transport';
10
10
 
11
11
  export function usePluginScreenData(
12
12
  screen: string,
@@ -8,7 +8,7 @@ import { useTuiCliAdapter } from '../tui-cli-adapter-context.js';
8
8
  import type { IUseSideEffectsOptions, IUseSideEffectsResult } from './side-effects-types.js';
9
9
  import type { TInteractivePrompt } from './side-effects-types.js';
10
10
  import type { TSessionEndReason } from '@robota-sdk/agent-core';
11
- import type { ICommandInteraction, ICommandResult } from '@robota-sdk/agent-framework';
11
+ import type { ICommandInteraction, ICommandResult } from '@robota-sdk/agent-interface-transport';
12
12
 
13
13
  const EXIT_DELAY_MS = 500;
14
14
 
@@ -8,12 +8,12 @@ import { useCallback } from 'react';
8
8
 
9
9
  import type { TuiStateManager } from '../tui-state-manager.js';
10
10
  import type { ICommandEffectQueue } from './command-effect-queue.js';
11
+ import type { CommandRegistry } from '@robota-sdk/agent-framework';
11
12
  import type {
12
- IInteractiveSession,
13
- CommandRegistry,
14
13
  ICommandResult,
14
+ IInteractiveSession,
15
15
  TCommandEffect,
16
- } from '@robota-sdk/agent-framework';
16
+ } from '@robota-sdk/agent-interface-transport';
17
17
 
18
18
  export function useSlashRouting(
19
19
  interactiveSession: IInteractiveSession,
@@ -4,7 +4,7 @@ import { useState } from 'react';
4
4
  import { useTuiCliAdapter } from '../tui-cli-adapter-context.js';
5
5
 
6
6
  import type { TUniversalValue } from '@robota-sdk/agent-core';
7
- import type { IStatusLineCommandSettings } from '@robota-sdk/agent-framework';
7
+ import type { IStatusLineCommandSettings } from '@robota-sdk/agent-interface-transport';
8
8
 
9
9
  function readStatusLineSettings(
10
10
  settings: Record<string, TUniversalValue>,
@@ -13,10 +13,10 @@ import type { IPermissionRequest } from '../types.js';
13
13
  import type { IHistoryEntry, TSessionEndReason } from '@robota-sdk/agent-core';
14
14
  import type { InteractiveSession, CommandRegistry } from '@robota-sdk/agent-framework';
15
15
  import type {
16
- IToolState,
17
- IExecutionWorkspaceSnapshot,
18
16
  IExecutionDetailPage,
19
- } from '@robota-sdk/agent-framework';
17
+ IExecutionWorkspaceSnapshot,
18
+ IToolState,
19
+ } from '@robota-sdk/agent-interface-transport';
20
20
 
21
21
  export interface IInteractiveSessionState {
22
22
  interactiveSession: InteractiveSession;
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import type { IMenuSelectItem } from './MenuSelect.js';
7
- import type { ICommandPluginAdapter } from '@robota-sdk/agent-framework';
7
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-transport';
8
8
 
9
9
  interface IConfirmState {
10
10
  message: string;
@@ -15,13 +15,15 @@ import type {
15
15
  IBackgroundTaskRunner,
16
16
  ICommandHostAdapters,
17
17
  ICommandModule,
18
- IInteractiveSession,
19
- IInteractiveSessionStore,
20
18
  TSubagentRunnerFactory,
21
19
  TShellExecFn,
22
20
  CommandRegistry,
23
21
  } from '@robota-sdk/agent-framework';
24
- import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
22
+ import type {
23
+ IInteractiveSession,
24
+ IInteractiveSessionStore,
25
+ ITransportRegistryView,
26
+ } from '@robota-sdk/agent-interface-transport';
25
27
 
26
28
  export interface IRenderOptions {
27
29
  cwd: string;
@@ -32,6 +34,8 @@ export interface IRenderOptions {
32
34
  language?: string;
33
35
  permissionMode?: TPermissionMode;
34
36
  maxTurns?: number;
37
+ allowedTools?: string[];
38
+ deniedTools?: string[];
35
39
  version?: string;
36
40
  sessionStore?: IInteractiveSessionStore;
37
41
  resumeSessionId?: string;
@@ -50,6 +54,34 @@ export interface IRenderOptions {
50
54
  agentName?: string;
51
55
  }
52
56
 
57
+ /** Map render options to TuiInteractionChannel constructor options. */
58
+ export function toChannelOptions(
59
+ options: IRenderOptions,
60
+ resumeSessionId?: string,
61
+ ): ConstructorParameters<typeof TuiInteractionChannel>[0] {
62
+ return {
63
+ cwd: options.cwd,
64
+ provider: options.provider,
65
+ permissionMode: options.permissionMode,
66
+ maxTurns: options.maxTurns,
67
+ allowedTools: options.allowedTools,
68
+ deniedTools: options.deniedTools,
69
+ sessionStore: options.sessionStore,
70
+ resumeSessionId,
71
+ forkSession: options.forkSession,
72
+ sessionName: options.sessionName,
73
+ backgroundTaskRunners: options.backgroundTaskRunners,
74
+ subagentRunnerFactory: options.subagentRunnerFactory,
75
+ commandModules: options.commandModules,
76
+ commandHostAdapters: options.commandHostAdapters,
77
+ shellExec: options.shellExec,
78
+ transportRegistry: options.transportRegistry,
79
+ language: options.language,
80
+ reloadPluginCommandSource: options.reloadPluginCommandSource,
81
+ agentName: options.agentName,
82
+ };
83
+ }
84
+
53
85
  export async function renderApp(options: IRenderOptions): Promise<void> {
54
86
  process.on('unhandledRejection', (reason) => {
55
87
  process.stderr.write(`\n[UNHANDLED REJECTION] ${reason}\n`);
@@ -58,33 +90,14 @@ export async function renderApp(options: IRenderOptions): Promise<void> {
58
90
  }
59
91
  });
60
92
 
93
+ // Single-owner lifecycle (CLI-B12): render.tsx supplies only the factory;
94
+ // App creates, replaces, and stops channels exclusively through React state.
61
95
  const createChannel = (resumeSessionId?: string): TuiInteractionChannel =>
62
- new TuiInteractionChannel({
63
- cwd: options.cwd,
64
- provider: options.provider,
65
- permissionMode: options.permissionMode,
66
- maxTurns: options.maxTurns,
67
- sessionStore: options.sessionStore,
68
- resumeSessionId,
69
- forkSession: options.forkSession,
70
- sessionName: options.sessionName,
71
- backgroundTaskRunners: options.backgroundTaskRunners,
72
- subagentRunnerFactory: options.subagentRunnerFactory,
73
- commandModules: options.commandModules,
74
- commandHostAdapters: options.commandHostAdapters,
75
- shellExec: options.shellExec,
76
- transportRegistry: options.transportRegistry,
77
- language: options.language,
78
- reloadPluginCommandSource: options.reloadPluginCommandSource,
79
- agentName: options.agentName,
80
- });
81
-
82
- const channel = createChannel(options.resumeSessionId);
96
+ new TuiInteractionChannel(toChannelOptions(options, resumeSessionId));
83
97
 
84
98
  const instance = render(
85
99
  <App
86
100
  cwd={options.cwd}
87
- channel={channel}
88
101
  createChannel={createChannel}
89
102
  providerOverride={options.providerOverride}
90
103
  providerType={options.providerType}
@@ -30,7 +30,7 @@ function getPrimaryActivity(
30
30
  if (input.activeToolCount > NO_ACTIVE_ITEMS) {
31
31
  return {
32
32
  kind: 'tools',
33
- label: `Tools x${input.activeToolCount}`,
33
+ label: `Tools (${input.activeToolCount})`,
34
34
  color: 'cyan',
35
35
  };
36
36
  }
@@ -44,7 +44,7 @@ function getPrimaryActivity(
44
44
  if (input.activeBackgroundTaskCount > NO_ACTIVE_ITEMS) {
45
45
  return {
46
46
  kind: 'background',
47
- label: `Background x${input.activeBackgroundTaskCount}`,
47
+ label: `Background (${input.activeBackgroundTaskCount})`,
48
48
  color: 'cyan',
49
49
  };
50
50
  }
@@ -1,9 +1,9 @@
1
1
  import type { TUniversalValue } from '@robota-sdk/agent-core';
2
+ import type { CommandRegistry } from '@robota-sdk/agent-framework';
2
3
  import type {
3
- TStatusLineCommandSettingsPatch,
4
4
  IStatusLineCommandSettings,
5
- CommandRegistry,
6
- } from '@robota-sdk/agent-framework';
5
+ TStatusLineCommandSettingsPatch,
6
+ } from '@robota-sdk/agent-interface-transport';
7
7
 
8
8
  export interface ITuiCliAdapter {
9
9
  getUserSettingsPath(): string;
@@ -10,10 +10,10 @@
10
10
 
11
11
  import type { IContextWindowState, IHistoryEntry } from '@robota-sdk/agent-core';
12
12
  import type {
13
- IToolState,
14
13
  IExecutionResult,
15
14
  IExecutionWorkspaceSnapshot,
16
- } from '@robota-sdk/agent-framework';
15
+ IToolState,
16
+ } from '@robota-sdk/agent-interface-transport';
17
17
 
18
18
  /** Max messages kept in rendering state */
19
19
  const MAX_RENDERED_MESSAGES = 100;
@@ -1,8 +1,10 @@
1
1
  import { renderApp, type IRenderOptions } from './render.js';
2
2
 
3
3
  import type { TUniversalValue } from '@robota-sdk/agent-core';
4
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
5
- import type { IConfigurableTransport } from '@robota-sdk/agent-interface-transport';
4
+ import type {
5
+ IConfigurableTransport,
6
+ IInteractiveSession,
7
+ } from '@robota-sdk/agent-interface-transport';
6
8
 
7
9
  export class TuiTransport implements IConfigurableTransport<IInteractiveSession> {
8
10
  readonly name = 'tui';
@@ -6,15 +6,17 @@ import { describe, it, expect, vi } from 'vitest';
6
6
  import { createWsHandler } from '../ws-handler.js';
7
7
  import type { TServerMessage } from '../ws-protocol.js';
8
8
  import type {
9
- IBackgroundTaskLogPage,
10
- IBackgroundTaskState,
9
+ IBackgroundJobGroupState,
11
10
  IExecutionWorkspaceEvent,
12
11
  IExecutionWorkspaceSnapshot,
13
12
  IInteractiveSession,
14
- IBackgroundJobGroupState,
15
13
  TBackgroundJobGroupEvent,
16
- TBackgroundTaskEvent,
17
14
  TExecutionWorkspaceUpdateCause,
15
+ } from '@robota-sdk/agent-interface-transport';
16
+ import type {
17
+ IBackgroundTaskLogPage,
18
+ IBackgroundTaskState,
19
+ TBackgroundTaskEvent,
18
20
  } from '@robota-sdk/agent-framework';
19
21
 
20
22
  const backgroundTask: IBackgroundTaskState = {
@@ -1,6 +1,6 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { createWsTransport } from '../ws-transport.js';
3
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
3
+ import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
4
4
 
5
5
  function createMockSession(): IInteractiveSession {
6
6
  return {
@@ -1,5 +1,5 @@
1
1
  import type { TBackgroundControlAction, TClientMessage, TServerMessage } from './ws-protocol.js';
2
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
2
+ import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
3
3
 
4
4
  export function handleBackgroundQueryMessage(
5
5
  session: IInteractiveSession,
@@ -14,14 +14,14 @@ import {
14
14
  } from './ws-background-messages.js';
15
15
 
16
16
  import type { TClientMessage, TServerMessage } from './ws-protocol.js';
17
+ import type { TBackgroundTaskEvent } from '@robota-sdk/agent-framework';
17
18
  import type {
18
- IInteractiveSession,
19
19
  IExecutionResult,
20
20
  IExecutionWorkspaceEvent,
21
- TBackgroundJobGroupEvent,
22
- TBackgroundTaskEvent,
21
+ IInteractiveSession,
23
22
  IToolState,
24
- } from '@robota-sdk/agent-framework';
23
+ TBackgroundJobGroupEvent,
24
+ } from '@robota-sdk/agent-interface-transport';
25
25
 
26
26
  export interface IWsHandlerOptions {
27
27
  /** IInteractiveSession to expose. */
@@ -1,18 +1,20 @@
1
1
  import type {
2
2
  InteractiveSession,
3
- ICommandResult,
4
- IBackgroundJobGroupState,
5
3
  IBackgroundTaskInput,
6
4
  IBackgroundTaskListFilter,
7
5
  IBackgroundTaskLogCursor,
8
6
  IBackgroundTaskLogPage,
9
7
  IBackgroundTaskState,
8
+ TBackgroundTaskEvent,
9
+ } from '@robota-sdk/agent-framework';
10
+ import type {
11
+ IBackgroundJobGroupState,
12
+ ICommandResult,
10
13
  IExecutionResult,
11
14
  IExecutionWorkspaceSnapshot,
12
15
  IToolState,
13
16
  TBackgroundJobGroupEvent,
14
- TBackgroundTaskEvent,
15
- } from '@robota-sdk/agent-framework';
17
+ } from '@robota-sdk/agent-interface-transport';
16
18
 
17
19
  export type TBackgroundControlAction = 'cancel' | 'close' | 'send';
18
20
 
@@ -11,8 +11,10 @@ import { createWsHandler } from './ws-handler.js';
11
11
 
12
12
  import type { TServerMessage } from './ws-protocol.js';
13
13
  import type { TUniversalValue } from '@robota-sdk/agent-core';
14
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
15
- import type { IConfigurableTransport } from '@robota-sdk/agent-interface-transport';
14
+ import type {
15
+ IConfigurableTransport,
16
+ IInteractiveSession,
17
+ } from '@robota-sdk/agent-interface-transport';
16
18
 
17
19
  const DEFAULT_PORT = 7070;
18
20
  const DEFAULT_MAX_RETRIES = 20;
@@ -8,8 +8,7 @@
8
8
  import { createWsHandler } from './ws-handler.js';
9
9
 
10
10
  import type { TServerMessage } from './ws-protocol.js';
11
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
12
- import type { ITransportAdapter } from '@robota-sdk/agent-interface-transport';
11
+ import type { IInteractiveSession, ITransportAdapter } from '@robota-sdk/agent-interface-transport';
13
12
 
14
13
  export interface IWsTransportOptions {
15
14
  /** Send a JSON message to the connected WebSocket client. */