@robota-sdk/agent-transport 3.0.0-beta.73 → 3.0.0-beta.75

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 (170) 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-CT2ibQnr.cjs} +4 -3
  5. package/dist/node/{headless-C6tj35h3.js → headless-mRYilLfC.js} +4 -3
  6. package/dist/node/headless-mRYilLfC.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-BNccqSpv.d.ts} +13 -3
  14. package/dist/node/index-BNccqSpv.d.ts.map +1 -0
  15. package/dist/node/{index-TMAlNHuM.d.ts → index-BUhHIf7X.d.ts} +13 -3
  16. package/dist/node/index-BUhHIf7X.d.ts.map +1 -0
  17. package/dist/node/{index-C9LWCL4l.d.ts → index-BnAGE-u9.d.ts} +2 -3
  18. package/dist/node/index-BnAGE-u9.d.ts.map +1 -0
  19. package/dist/node/{index-COWvtBa2.d.ts → index-BrQ4gGw0.d.ts} +3 -3
  20. package/dist/node/index-BrQ4gGw0.d.ts.map +1 -0
  21. package/dist/node/{index-27HV5PJB.d.ts → index-CYl7ksS6.d.ts} +18 -3
  22. package/dist/node/index-CYl7ksS6.d.ts.map +1 -0
  23. package/dist/node/{index-X2Zg8FEY.d.ts → index-CoeBF21y.d.ts} +3 -3
  24. package/dist/node/index-CoeBF21y.d.ts.map +1 -0
  25. package/dist/node/{index-BRgV_MPB.d.ts → index-DHt-2VQ-.d.ts} +2 -3
  26. package/dist/node/index-DHt-2VQ-.d.ts.map +1 -0
  27. package/dist/node/{index-nBlMTFkZ.d.ts → index-DMwKN5Le.d.ts} +2 -3
  28. package/dist/node/index-DMwKN5Le.d.ts.map +1 -0
  29. package/dist/node/{index-BRchlFBE.d.ts → index-E8Gx4-lc.d.ts} +18 -3
  30. package/dist/node/index-E8Gx4-lc.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-DIdvTeiT.js → tui-CcH5EsQh.js} +4 -4
  54. package/dist/node/tui-CcH5EsQh.js.map +1 -0
  55. package/dist/node/tui-DznRbcku.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 +30 -2
  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 +25 -11
  85. package/src/tui/BackgroundTaskPanel.tsx +1 -1
  86. package/src/tui/ExecutionWorkspaceDetailPane.tsx +1 -1
  87. package/src/tui/ExecutionWorkspaceSwitcher.tsx +1 -1
  88. package/src/tui/InputArea.tsx +2 -1
  89. package/src/tui/InteractivePrompt.tsx +2 -2
  90. package/src/tui/PluginTUI.tsx +1 -1
  91. package/src/tui/SessionPicker.tsx +1 -1
  92. package/src/tui/SessionStatusBar.tsx +4 -1
  93. package/src/tui/SlashAutocomplete.tsx +1 -1
  94. package/src/tui/StatusBar.tsx +27 -0
  95. package/src/tui/StreamingIndicator.tsx +1 -1
  96. package/src/tui/TransportTUI.tsx +1 -1
  97. package/src/tui/TuiInteractionChannel.ts +72 -38
  98. package/src/tui/UsageSummaryEntry.tsx +1 -1
  99. package/src/tui/__tests__/PluginTUI.test.tsx +1 -1
  100. package/src/tui/__tests__/SlashAutocomplete.test.tsx +1 -1
  101. package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +1 -1
  102. package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +5 -2
  103. package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +1 -1
  104. package/src/tui/__tests__/background-task-panel.test.tsx +1 -1
  105. package/src/tui/__tests__/background-task-row-format.test.ts +1 -1
  106. package/src/tui/__tests__/channel-factory-integration.test.ts +138 -0
  107. package/src/tui/__tests__/execution-workspace-switcher.test.tsx +1 -1
  108. package/src/tui/__tests__/execution-workspace-view-model.test.ts +1 -1
  109. package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +1 -1
  110. package/src/tui/__tests__/input-area-flow.test.ts +1 -1
  111. package/src/tui/__tests__/pty/pty-driver.ts +135 -0
  112. package/src/tui/__tests__/pty/tui-pty.ptytest.ts +61 -0
  113. package/src/tui/__tests__/render-channel-options.test.ts +32 -0
  114. package/src/tui/__tests__/session-init-poller.test.ts +102 -0
  115. package/src/tui/__tests__/session-switch-channel.test.tsx +307 -0
  116. package/src/tui/__tests__/slash-routing-effects.test.ts +4 -1
  117. package/src/tui/__tests__/status-activity.test.ts +3 -3
  118. package/src/tui/__tests__/status-bar.test.tsx +25 -5
  119. package/src/tui/__tests__/tui-channel-init-failure.test.ts +57 -0
  120. package/src/tui/__tests__/tui-state-manager.test.ts +1 -1
  121. package/src/tui/background-task-row-format.ts +1 -1
  122. package/src/tui/execution-workspace-view-model.ts +1 -1
  123. package/src/tui/flows/input-area-flow.ts +1 -1
  124. package/src/tui/flows/permission-prompt-flow.ts +1 -1
  125. package/src/tui/flows/session-init-poller.ts +77 -0
  126. package/src/tui/hooks/command-effect-handler.ts +4 -1
  127. package/src/tui/hooks/command-effect-queue.ts +1 -1
  128. package/src/tui/hooks/side-effects-types.ts +2 -2
  129. package/src/tui/hooks/useAutocomplete.ts +3 -2
  130. package/src/tui/hooks/usePluginCallbacks.ts +1 -1
  131. package/src/tui/hooks/usePluginScreenData.ts +1 -1
  132. package/src/tui/hooks/useSideEffects.ts +1 -1
  133. package/src/tui/hooks/useSlashRouting.ts +3 -3
  134. package/src/tui/hooks/useStatusLineSettings.ts +1 -1
  135. package/src/tui/hooks/useTuiChannel.ts +3 -3
  136. package/src/tui/plugin-tui-handlers.ts +1 -1
  137. package/src/tui/render.tsx +50 -25
  138. package/src/tui/status-activity.ts +2 -2
  139. package/src/tui/tui-cli-adapter.ts +3 -3
  140. package/src/tui/tui-state-manager.ts +2 -2
  141. package/src/tui/tui-transport.ts +4 -2
  142. package/src/ws/__tests__/ws-handler.test.ts +6 -4
  143. package/src/ws/__tests__/ws-transport.test.ts +1 -1
  144. package/src/ws/ws-background-messages.ts +1 -1
  145. package/src/ws/ws-handler.ts +4 -4
  146. package/src/ws/ws-protocol.ts +6 -4
  147. package/src/ws/ws-transport-configurable.ts +4 -2
  148. package/src/ws/ws-transport.ts +1 -2
  149. package/dist/node/headless-C6tj35h3.js.map +0 -1
  150. package/dist/node/http-Br10Ps8m.js.map +0 -1
  151. package/dist/node/http-Da6Kw4oy.cjs +0 -1
  152. package/dist/node/index-27HV5PJB.d.ts.map +0 -1
  153. package/dist/node/index-BRchlFBE.d.ts.map +0 -1
  154. package/dist/node/index-BRgV_MPB.d.ts.map +0 -1
  155. package/dist/node/index-BVNhOeeU.d.ts.map +0 -1
  156. package/dist/node/index-C5KNEBO9.d.ts.map +0 -1
  157. package/dist/node/index-C9LWCL4l.d.ts.map +0 -1
  158. package/dist/node/index-COWvtBa2.d.ts.map +0 -1
  159. package/dist/node/index-TMAlNHuM.d.ts.map +0 -1
  160. package/dist/node/index-X2Zg8FEY.d.ts.map +0 -1
  161. package/dist/node/index-nBlMTFkZ.d.ts.map +0 -1
  162. package/dist/node/mcp-BAujHOMr.js.map +0 -1
  163. package/dist/node/mcp-Bl8jUfev.cjs +0 -1
  164. package/dist/node/tui-D30s8S5f.cjs +0 -24
  165. package/dist/node/tui-DIdvTeiT.js.map +0 -1
  166. package/dist/node/ws-BWel8nzl.js.map +0 -1
  167. package/dist/node/ws-tCjj2gPu.cjs +0 -1
  168. package/src/tui/InkTerminal.ts +0 -42
  169. package/src/tui/hooks/use-interactive-session-init.ts +0 -91
  170. package/src/tui/hooks/usePermissionQueue.ts +0 -52
@@ -8,7 +8,7 @@ import React from 'react';
8
8
 
9
9
  import ListPicker from './ListPicker.js';
10
10
 
11
- import type { IResumableSessionSummary } from '@robota-sdk/agent-framework';
11
+ import type { IResumableSessionSummary } from '@robota-sdk/agent-interface-transport';
12
12
 
13
13
  const SESSION_ID_DISPLAY_LENGTH = 8;
14
14
  const SESSION_PREVIEW_DISPLAY_LENGTH = 60;
@@ -4,7 +4,7 @@ import StatusBar from './StatusBar.js';
4
4
  import { useTuiCliAdapter } from './tui-cli-adapter-context.js';
5
5
 
6
6
  import type { TPermissionMode } 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
  interface IProps {
10
10
  cwd: string;
@@ -20,6 +20,7 @@ interface IProps {
20
20
  sessionName?: string;
21
21
  settings: IStatusLineCommandSettings;
22
22
  activeAgentLabel?: string;
23
+ activePresetId?: string;
23
24
  gitRefreshToken?: number;
24
25
  }
25
26
 
@@ -37,6 +38,7 @@ export default function SessionStatusBar({
37
38
  sessionName,
38
39
  settings,
39
40
  activeAgentLabel,
41
+ activePresetId,
40
42
  gitRefreshToken,
41
43
  }: IProps): React.ReactElement | null {
42
44
  const cliAdapter = useTuiCliAdapter();
@@ -65,6 +67,7 @@ export default function SessionStatusBar({
65
67
  gitBranch={gitBranch}
66
68
  showGitBranch={settings.gitBranch}
67
69
  activeAgentLabel={activeAgentLabel}
70
+ activePresetId={activePresetId}
68
71
  />
69
72
  );
70
73
  }
@@ -1,7 +1,7 @@
1
1
  import { Box, Text, useStdout } from 'ink';
2
2
  import React, { useState, useEffect } from 'react';
3
3
 
4
- import type { ICommand } from '@robota-sdk/agent-framework';
4
+ import type { ICommand } from '@robota-sdk/agent-interface-transport';
5
5
 
6
6
  interface IProps {
7
7
  /** Filtered list of commands to display */
@@ -26,6 +26,7 @@ interface IProps {
26
26
  gitBranch?: string;
27
27
  showGitBranch?: boolean;
28
28
  activeAgentLabel?: string;
29
+ activePresetId?: string;
29
30
  }
30
31
 
31
32
  interface IStatusLeftProps {
@@ -42,6 +43,7 @@ interface IStatusLeftProps {
42
43
  sessionName?: string;
43
44
  gitBranch?: string;
44
45
  showGitBranch: boolean;
46
+ activePresetId?: string;
45
47
  }
46
48
 
47
49
  /** Return the color for the context percentage indicator */
@@ -106,6 +108,21 @@ function shouldShowPermissionMode(permissionMode: TPermissionMode): boolean {
106
108
  return permissionMode !== 'default';
107
109
  }
108
110
 
111
+ function PresetText({ activePresetId }: { activePresetId: string }): React.ReactElement {
112
+ return (
113
+ <>
114
+ <Text color="cyan" bold>
115
+ Preset:
116
+ </Text>{' '}
117
+ <Text>{activePresetId}</Text>
118
+ </>
119
+ );
120
+ }
121
+
122
+ function shouldShowActivePreset(activePresetId: string | undefined): activePresetId is string {
123
+ return activePresetId !== undefined && activePresetId !== 'default';
124
+ }
125
+
109
126
  function ProviderText({
110
127
  modelName,
111
128
  providerDisplayName,
@@ -127,6 +144,8 @@ function StatusLeft(props: IStatusLeftProps): React.ReactElement {
127
144
  const shouldShowGitBranch =
128
145
  props.showGitBranch && props.gitBranch !== undefined && props.gitBranch.length > 0;
129
146
  const showPermissionMode = shouldShowPermissionMode(props.permissionMode);
147
+ const activePresetId = props.activePresetId;
148
+ const showActivePreset = shouldShowActivePreset(activePresetId);
130
149
  return (
131
150
  <Text>
132
151
  <StatusActivityText
@@ -141,6 +160,12 @@ function StatusLeft(props: IStatusLeftProps): React.ReactElement {
141
160
  <ModeText permissionMode={props.permissionMode} />
142
161
  </>
143
162
  )}
163
+ {showActivePreset && (
164
+ <>
165
+ {' | '}
166
+ <PresetText activePresetId={activePresetId} />
167
+ </>
168
+ )}
144
169
  {props.sessionName && (
145
170
  <>
146
171
  {' | '}
@@ -181,6 +206,7 @@ export default function StatusBar({
181
206
  gitBranch,
182
207
  showGitBranch = true,
183
208
  activeAgentLabel,
209
+ activePresetId,
184
210
  }: IProps): React.ReactElement {
185
211
  return (
186
212
  <Box paddingLeft={1} paddingRight={1} justifyContent="space-between">
@@ -198,6 +224,7 @@ export default function StatusBar({
198
224
  sessionName={sessionName}
199
225
  gitBranch={gitBranch}
200
226
  showGitBranch={showGitBranch}
227
+ activePresetId={activePresetId}
201
228
  />
202
229
  {activeAgentLabel !== undefined && (
203
230
  <Text color="yellow" bold>
@@ -9,7 +9,7 @@ import React from 'react';
9
9
  import { renderMarkdown } from './render-markdown.js';
10
10
  import ToolDiffBlock from './ToolDiffBlock.js';
11
11
 
12
- import type { IToolState } from '@robota-sdk/agent-framework';
12
+ import type { IToolState } from '@robota-sdk/agent-interface-transport';
13
13
 
14
14
  function getToolStyle(t: IToolState): {
15
15
  color: string;
@@ -7,8 +7,8 @@
7
7
  import { Box, Text, useInput } from 'ink';
8
8
  import React, { useState, useCallback } from 'react';
9
9
 
10
- import type { IInteractiveSession } from '@robota-sdk/agent-framework';
11
10
  import type {
11
+ IInteractiveSession,
12
12
  ITransportEntry,
13
13
  ITransportRegistryView,
14
14
  } from '@robota-sdk/agent-interface-transport';
@@ -12,37 +12,40 @@ import {
12
12
  } from '@robota-sdk/agent-core';
13
13
  import { InteractiveSession, CommandRegistry } from '@robota-sdk/agent-framework';
14
14
 
15
+ import { createSessionInitPoller } from './flows/session-init-poller.js';
15
16
  import { CommandEffectQueue, type ICommandEffectQueue } from './hooks/command-effect-queue.js';
16
17
  import { applySystemCommandResult } from './hooks/useSlashRouting.js';
17
18
  import { generateSessionName } from './session-naming.js';
18
19
  import { TuiStateManager } from './tui-state-manager.js';
19
20
 
21
+ import type { ISessionInitPoller, TSessionInitFailure } from './flows/session-init-poller.js';
20
22
  import type { IPermissionRequest } from './types.js';
21
23
  import type { IAIProvider, TPermissionMode, TSessionEndReason } from '@robota-sdk/agent-core';
22
24
  import type { TToolArgs } from '@robota-sdk/agent-core';
23
- import type { IInteractionChannel } from '@robota-sdk/agent-framework';
24
- import type {
25
- InteractionEvent,
26
- IActionRequest,
27
- IActionResponse,
28
- ICommandInfo,
29
- } from '@robota-sdk/agent-framework';
30
25
  import type {
31
26
  IBackgroundTaskRunner,
32
27
  ICommandHostAdapters,
33
28
  ICommandModule,
34
- IInteractiveSession,
35
- IInteractiveSessionStore,
36
29
  TSubagentRunnerFactory,
37
- IExecutionWorkspaceEvent,
38
- IExecutionDetailPage,
39
- IExecutionResult,
40
30
  TShellExecFn,
41
31
  } from '@robota-sdk/agent-framework';
42
- import type { TPermissionResultValue } from '@robota-sdk/agent-framework';
43
- import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
32
+ import type {
33
+ IActionRequest,
34
+ IActionResponse,
35
+ ICommandInfo,
36
+ IExecutionDetailPage,
37
+ IExecutionResult,
38
+ IExecutionWorkspaceEvent,
39
+ IInteractionChannel,
40
+ IInteractiveSession,
41
+ IInteractiveSessionStore,
42
+ ITransportRegistryView,
43
+ InteractionEvent,
44
+ TPermissionResultValue,
45
+ } from '@robota-sdk/agent-interface-transport';
44
46
 
45
47
  const SESSION_INIT_POLL_MS = 200;
48
+ const SESSION_INIT_TIMEOUT_MS = 15000;
46
49
 
47
50
  export interface ITuiInteractionChannelOptions {
48
51
  cwd: string;
@@ -63,10 +66,18 @@ export interface ITuiInteractionChannelOptions {
63
66
  language?: string;
64
67
  reloadPluginCommandSource?: (registry: CommandRegistry) => void;
65
68
  agentName?: string;
69
+ /** Active preset id selected at startup (PRESET-011 runtime state). Defaults to 'default'. */
70
+ activePresetId?: string;
71
+ /** Preset persona block composed as a `source: 'persona'` system-prompt section (priority 5). */
72
+ persona?: string;
66
73
  systemPrompt?: string;
67
74
  appendSystemPrompt?: string;
68
75
  allowedTools?: string[];
69
76
  deniedTools?: string[];
77
+ /** Preset execution capability: activate agent runtime + subagent/background dispatch. */
78
+ enableParallelSubagents?: boolean;
79
+ /** Preset execution capability: run a post-task self-verification step. */
80
+ selfVerification?: boolean;
70
81
  }
71
82
 
72
83
  export class TuiInteractionChannel implements IInteractionChannel {
@@ -92,7 +103,7 @@ export class TuiInteractionChannel implements IInteractionChannel {
92
103
 
93
104
  private autoNameTriggered = false;
94
105
  private sessionStarted = false;
95
- private initCheckInterval: ReturnType<typeof setInterval> | null = null;
106
+ private initPoller: ISessionInitPoller | null = null;
96
107
  private permissionQueue: Array<{
97
108
  toolName: string;
98
109
  toolArgs: TToolArgs;
@@ -133,10 +144,14 @@ export class TuiInteractionChannel implements IInteractionChannel {
133
144
  shellExec: opts.shellExec,
134
145
  language: opts.language,
135
146
  agentName: opts.agentName,
147
+ activePresetId: opts.activePresetId,
148
+ persona: opts.persona,
136
149
  systemPrompt: opts.systemPrompt,
137
150
  appendSystemPrompt: opts.appendSystemPrompt,
138
151
  allowedTools: opts.allowedTools,
139
152
  deniedTools: opts.deniedTools,
153
+ enableParallelSubagents: opts.enableParallelSubagents,
154
+ selfVerification: opts.selfVerification,
140
155
  });
141
156
  }
142
157
 
@@ -369,6 +384,9 @@ export class TuiInteractionChannel implements IInteractionChannel {
369
384
  const onSkillActivation = (): void => {
370
385
  manager.syncHistory(session.getFullHistory());
371
386
  };
387
+ const onMemoryEvent = (): void => {
388
+ manager.syncHistory(session.getFullHistory());
389
+ };
372
390
  const onExecutionWorkspaceEvent = (event: IExecutionWorkspaceEvent): void => {
373
391
  manager.syncExecutionWorkspaceSnapshot(event.snapshot);
374
392
  };
@@ -384,6 +402,7 @@ export class TuiInteractionChannel implements IInteractionChannel {
384
402
  session.on('context_update', manager.onContextUpdate);
385
403
  session.on('compact', onCompact);
386
404
  session.on('skill_activation', onSkillActivation);
405
+ session.on('memory_event', onMemoryEvent);
387
406
  session.on('execution_workspace_event', onExecutionWorkspaceEvent);
388
407
  }
389
408
 
@@ -413,36 +432,51 @@ export class TuiInteractionChannel implements IInteractionChannel {
413
432
  }
414
433
 
415
434
  private startInitCheck(): void {
416
- this.initCheckInterval = setInterval(() => {
417
- this.runInitCheck();
418
- }, SESSION_INIT_POLL_MS);
435
+ this.initPoller = createSessionInitPoller({
436
+ check: () => this.runInitCheck(),
437
+ intervalMs: SESSION_INIT_POLL_MS,
438
+ timeoutMs: SESSION_INIT_TIMEOUT_MS,
439
+ onReady: () => undefined,
440
+ onFailure: (failure) => this.onInitFailure(failure),
441
+ });
442
+ this.initPoller.start();
419
443
  }
420
444
 
445
+ /** Throws while the session is not ready; the init poller classifies the error. */
421
446
  private runInitCheck(): void {
422
- try {
423
- const ctx = this.interactiveSession.getContextState();
424
- this.stateManager.setContextState({
425
- percentage: ctx.usedPercentage,
426
- usedTokens: ctx.usedTokens,
427
- maxTokens: ctx.maxTokens,
428
- });
429
- const restored = this.interactiveSession.getFullHistory();
430
- if (restored.length > 0) {
431
- this.stateManager.syncHistory(restored);
432
- }
433
- this.syncExecutionWorkspace();
434
- this.stopInitCheck();
435
- } catch {
436
- // allow-fallback: session initializes asynchronously; poll until ready
437
- /* Not yet initialized */
447
+ const ctx = this.interactiveSession.getContextState();
448
+ this.stateManager.setContextState({
449
+ percentage: ctx.usedPercentage,
450
+ usedTokens: ctx.usedTokens,
451
+ maxTokens: ctx.maxTokens,
452
+ });
453
+ const restored = this.interactiveSession.getFullHistory();
454
+ if (restored.length > 0) {
455
+ this.stateManager.syncHistory(restored);
438
456
  }
457
+ this.syncExecutionWorkspace();
458
+ }
459
+
460
+ private onInitFailure(failure: TSessionInitFailure): void {
461
+ const message =
462
+ failure.kind === 'timeout'
463
+ ? `Session initialization timed out after ${SESSION_INIT_TIMEOUT_MS / 1000}s${
464
+ failure.lastError ? ` (last error: ${failure.lastError.message})` : ''
465
+ }`
466
+ : `Session initialization failed: ${failure.error.message}`;
467
+ this.stateManager.onError();
468
+ this.stateManager.addEntry({
469
+ id: `session-init-error-${Date.now()}`,
470
+ timestamp: new Date(),
471
+ category: 'event',
472
+ type: 'session-init-error',
473
+ data: { message },
474
+ });
439
475
  }
440
476
 
441
477
  private stopInitCheck(): void {
442
- if (this.initCheckInterval !== null) {
443
- clearInterval(this.initCheckInterval);
444
- this.initCheckInterval = null;
445
- }
478
+ this.initPoller?.stop();
479
+ this.initPoller = null;
446
480
  }
447
481
 
448
482
  private syncExecutionWorkspace(): void {
@@ -3,7 +3,7 @@ import { Box, Text } from 'ink';
3
3
  import React from 'react';
4
4
 
5
5
  import type { IHistoryEntry } from '@robota-sdk/agent-core';
6
- import type { IUsageSnapshot } from '@robota-sdk/agent-framework';
6
+ import type { IUsageSnapshot } from '@robota-sdk/agent-interface-transport';
7
7
 
8
8
  const TOKEN_COMPACT_THRESHOLD = 1000;
9
9
 
@@ -3,7 +3,7 @@ import React from 'react';
3
3
  import { render } from 'ink-testing-library';
4
4
  import { describe, it, expect, vi } from 'vitest';
5
5
  import PluginTUI from '../PluginTUI.js';
6
- import type { ICommandPluginAdapter } from '@robota-sdk/agent-framework';
6
+ import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-transport';
7
7
 
8
8
  function mockCallbacks(): ICommandPluginAdapter {
9
9
  return {
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { render } from 'ink-testing-library';
3
3
  import { describe, it, expect } from 'vitest';
4
4
  import SlashAutocomplete from '../SlashAutocomplete.js';
5
- import type { ICommand } from '@robota-sdk/agent-framework';
5
+ import type { ICommand } from '@robota-sdk/agent-interface-transport';
6
6
 
7
7
  // ink-testing-library fixes stdout.columns = 100
8
8
  // outer box chrome = 4 → rowWidth = 96 in tests
@@ -68,7 +68,7 @@ import {
68
68
  import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
69
69
 
70
70
  import type { IAIProvider, IHistoryEntry } from '@robota-sdk/agent-core';
71
- import type { IExecutionResult } from '@robota-sdk/agent-framework';
71
+ import type { IExecutionResult } from '@robota-sdk/agent-interface-transport';
72
72
 
73
73
  // ── Helpers ───────────────────────────────────────────────────────────────────
74
74
 
@@ -53,8 +53,11 @@ vi.mock('@robota-sdk/agent-framework', async () => {
53
53
  import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
54
54
 
55
55
  import type { IAIProvider } from '@robota-sdk/agent-core';
56
- import type { IExecutionResult, IInteractiveSession } from '@robota-sdk/agent-framework';
57
- import type { ITransportRegistryView } from '@robota-sdk/agent-interface-transport';
56
+ import type {
57
+ IExecutionResult,
58
+ IInteractiveSession,
59
+ ITransportRegistryView,
60
+ } from '@robota-sdk/agent-interface-transport';
58
61
 
59
62
  // ── Helpers ───────────────────────────────────────────────────────────────────
60
63
 
@@ -29,7 +29,7 @@ vi.mock('@robota-sdk/agent-framework', async () => {
29
29
  import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
30
30
 
31
31
  import type { IAIProvider } from '@robota-sdk/agent-core';
32
- import type { IActionRequest } from '@robota-sdk/agent-framework';
32
+ import type { IActionRequest } from '@robota-sdk/agent-interface-transport';
33
33
 
34
34
  function makeChannel(): TuiInteractionChannel {
35
35
  return new TuiInteractionChannel({
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { describe, expect, it } from 'vitest';
3
3
  import { render } from 'ink-testing-library';
4
- import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-framework';
4
+ import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-transport';
5
5
  import BackgroundTaskPanel from '../BackgroundTaskPanel.js';
6
6
 
7
7
  function makeEntry(overrides: Partial<IExecutionWorkspaceEntry>): IExecutionWorkspaceEntry {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-framework';
2
+ import type { IExecutionWorkspaceEntry } from '@robota-sdk/agent-interface-transport';
3
3
  import { formatBackgroundTaskRow } from '../background-task-row-format.js';
4
4
 
5
5
  function makeEntry(overrides: Partial<IExecutionWorkspaceEntry>): IExecutionWorkspaceEntry {
@@ -0,0 +1,138 @@
1
+ /**
2
+ * CLI-B11 TC-02: real-store channel factory integration.
3
+ *
4
+ * The official CI equivalent of real-resume-verify-v3.mjs: build the channel
5
+ * exactly the way render.tsx does (toChannelOptions + TuiInteractionChannel)
6
+ * over a REAL project session store with a persisted conversation, and assert
7
+ * the restored model context is non-empty. No store/session mocks.
8
+ */
9
+
10
+ import { mkdtempSync, rmSync } from 'node:fs';
11
+ import { tmpdir } from 'node:os';
12
+ import { join } from 'node:path';
13
+
14
+ import { createProjectSessionStore } from '@robota-sdk/agent-framework';
15
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
16
+
17
+ import { createScriptedProvider } from '../../testing/scripted-provider.js';
18
+ import { toChannelOptions, type IRenderOptions } from '../render.js';
19
+ import { TuiInteractionChannel } from '../TuiInteractionChannel.js';
20
+
21
+ import type { ITuiCliAdapter } from '../tui-cli-adapter.js';
22
+ import type { TUniversalMessage } from '@robota-sdk/agent-core';
23
+ import type { IInteractiveSessionStore } from '@robota-sdk/agent-interface-transport';
24
+
25
+ const RESTORE_DEADLINE_MS = 10_000;
26
+ const POLL_MS = 50;
27
+
28
+ function fakeCliAdapter(settingsPath: string): ITuiCliAdapter {
29
+ return {
30
+ getUserSettingsPath: () => settingsPath,
31
+ readSettings: () => ({}),
32
+ writeSettings: vi.fn(),
33
+ deleteSettings: vi.fn().mockReturnValue(false),
34
+ applyStatusLineSettings: vi.fn(),
35
+ reloadPluginCommandSource: vi.fn(),
36
+ applyActiveModelChange: vi.fn().mockReturnValue({ applied: true }),
37
+ getGitBranch: vi.fn().mockReturnValue(undefined),
38
+ getProviderDisplayName: vi.fn((type: string) => type),
39
+ };
40
+ }
41
+
42
+ function persistConversation(store: IInteractiveSessionStore, id: string, cwd: string): void {
43
+ const messages: TUniversalMessage[] = [
44
+ { role: 'user', content: 'Remember the number 42.' } as TUniversalMessage,
45
+ { role: 'assistant', content: 'Noted: 42.' } as TUniversalMessage,
46
+ { role: 'user', content: 'And the city is Busan.' } as TUniversalMessage,
47
+ { role: 'assistant', content: 'Noted: Busan.' } as TUniversalMessage,
48
+ ];
49
+ store.save({
50
+ id,
51
+ cwd,
52
+ createdAt: '2026-06-13T00:00:00.000Z',
53
+ updatedAt: '2026-06-13T00:00:00.000Z',
54
+ messages,
55
+ });
56
+ }
57
+
58
+ describe('channel factory restores persisted context (CLI-B11 TC-02)', () => {
59
+ let cwd: string;
60
+ let channel: TuiInteractionChannel | undefined;
61
+
62
+ beforeEach(() => {
63
+ cwd = mkdtempSync(join(tmpdir(), 'robota-b11-int-'));
64
+ });
65
+
66
+ afterEach(async () => {
67
+ await channel?.stop();
68
+ channel = undefined;
69
+ rmSync(cwd, { recursive: true, force: true });
70
+ });
71
+
72
+ it('createChannel(sessionId) over a real FileSessionStore yields usedTokens > 0', async () => {
73
+ const store = createProjectSessionStore(cwd);
74
+ const sessionId = 'b11-restore-session';
75
+ persistConversation(store, sessionId, cwd);
76
+
77
+ // Exactly the render.tsx factory: toChannelOptions(options, resumeSessionId).
78
+ const scripted = createScriptedProvider([{ text: 'unused in this test' }]);
79
+ const options: IRenderOptions = {
80
+ cwd,
81
+ provider: scripted.provider,
82
+ sessionStore: store,
83
+ cliAdapter: fakeCliAdapter(join(cwd, 'settings.json')),
84
+ };
85
+ channel = new TuiInteractionChannel(toChannelOptions(options, sessionId));
86
+ await channel.start();
87
+
88
+ // Restoration is asynchronous (pendingRestoreMessages inject after init).
89
+ const deadline = Date.now() + RESTORE_DEADLINE_MS;
90
+ let usedTokens = 0;
91
+ while (Date.now() < deadline) {
92
+ try {
93
+ // allow-fallback: session init is asynchronous; poll until it is ready
94
+ usedTokens = channel.getSession().getContextState().usedTokens;
95
+ if (usedTokens > 0) break;
96
+ } catch {
97
+ // allow-fallback: session init is asynchronous; poll until it is ready
98
+ }
99
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
100
+ }
101
+
102
+ // The persisted messages were injected into the model context — the exact
103
+ // signal that was 0 in the 2026-05-31 bug. (getFullHistory() is the display
104
+ // log restored from record.history, which this record intentionally omits.)
105
+ expect(usedTokens).toBeGreaterThan(0);
106
+ });
107
+
108
+ it('a channel created WITHOUT resumeSessionId starts with an empty context (control)', async () => {
109
+ const store = createProjectSessionStore(cwd);
110
+ persistConversation(store, 'b11-other-session', cwd);
111
+
112
+ const scripted = createScriptedProvider([{ text: 'unused' }]);
113
+ const options: IRenderOptions = {
114
+ cwd,
115
+ provider: scripted.provider,
116
+ sessionStore: store,
117
+ cliAdapter: fakeCliAdapter(join(cwd, 'settings.json')),
118
+ };
119
+ channel = new TuiInteractionChannel(toChannelOptions(options, undefined));
120
+ await channel.start();
121
+
122
+ const deadline = Date.now() + RESTORE_DEADLINE_MS;
123
+ let ready = false;
124
+ while (Date.now() < deadline && !ready) {
125
+ try {
126
+ // allow-fallback: session init is asynchronous; poll until it is ready
127
+ channel.getSession().getContextState();
128
+ ready = true;
129
+ } catch {
130
+ // allow-fallback: session init is asynchronous; poll until it is ready
131
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
132
+ }
133
+ }
134
+
135
+ expect(ready).toBe(true);
136
+ expect(channel.getSession().getFullHistory()).toHaveLength(0);
137
+ });
138
+ });
@@ -4,7 +4,7 @@ import { render } from 'ink-testing-library';
4
4
  import type {
5
5
  IExecutionWorkspaceEntry,
6
6
  IExecutionWorkspaceSnapshot,
7
- } from '@robota-sdk/agent-framework';
7
+ } from '@robota-sdk/agent-interface-transport';
8
8
  import ExecutionWorkspaceSwitcher from '../ExecutionWorkspaceSwitcher.js';
9
9
  import ExecutionWorkspaceDetailPane from '../ExecutionWorkspaceDetailPane.js';
10
10
 
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
  import type {
3
3
  IExecutionWorkspaceEntry,
4
4
  IExecutionWorkspaceSnapshot,
5
- } from '@robota-sdk/agent-framework';
5
+ } from '@robota-sdk/agent-interface-transport';
6
6
  import {
7
7
  countActiveBackgroundWorkspaceEntries,
8
8
  formatExecutionDetailRecord,
@@ -15,7 +15,7 @@ import React from 'react';
15
15
  import InteractivePrompt from '../../InteractivePrompt.js';
16
16
 
17
17
  import type { IAIProvider, IProviderDefinition } from '@robota-sdk/agent-core';
18
- import type { TCommandInteractionPrompt as TInteractivePrompt } from '@robota-sdk/agent-framework';
18
+ import type { TCommandInteractionPrompt as TInteractivePrompt } from '@robota-sdk/agent-interface-transport';
19
19
 
20
20
  const openaiDefaults = {
21
21
  apiKey: '$ENV:OPENAI_API_KEY',
@@ -13,7 +13,7 @@ import {
13
13
  resolveTabCompletion,
14
14
  shouldSubmitInput,
15
15
  } from '../flows/input-area-flow.js';
16
- import type { ICommand } from '@robota-sdk/agent-framework';
16
+ import type { ICommand } from '@robota-sdk/agent-interface-transport';
17
17
  import {
18
18
  createAssistantMessage,
19
19
  createSystemMessage,