@robota-sdk/agent-transport 3.0.0-beta.78 → 3.0.0-beta.81

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 (111) hide show
  1. package/README.md +84 -26
  2. package/dist/browser/client.d.ts +2 -0
  3. package/dist/browser/client.js +1 -0
  4. package/dist/browser/index.d.ts +520 -0
  5. package/dist/browser/index.d.ts.map +1 -0
  6. package/dist/browser/index.js +2 -0
  7. package/dist/browser/index.js.map +1 -0
  8. package/dist/browser/message-decoders-BB8n-H2X.d.ts +252 -0
  9. package/dist/browser/message-decoders-BB8n-H2X.d.ts.map +1 -0
  10. package/dist/browser/message-decoders-CDmDoZPR.js +2 -0
  11. package/dist/browser/message-decoders-CDmDoZPR.js.map +1 -0
  12. package/dist/node/index.cjs +1 -1
  13. package/dist/node/index.d.cts +766 -0
  14. package/dist/node/index.d.cts.map +1 -0
  15. package/dist/node/index.d.ts +765 -27
  16. package/dist/node/index.d.ts.map +1 -1
  17. package/dist/node/index.js +1 -1
  18. package/dist/node/index.js.map +1 -1
  19. package/dist/node/node.cjs +1 -0
  20. package/dist/node/node.d.cts +109 -0
  21. package/dist/node/node.d.cts.map +1 -0
  22. package/dist/node/node.d.ts +109 -0
  23. package/dist/node/node.d.ts.map +1 -0
  24. package/dist/node/node.js +2 -0
  25. package/dist/node/node.js.map +1 -0
  26. package/package.json +68 -47
  27. package/src/__tests__/access-token-verifier.test.ts +672 -0
  28. package/src/__tests__/admission.test.ts +130 -0
  29. package/src/__tests__/channel-frames.test.ts +214 -0
  30. package/src/__tests__/driver-attribution-capability.test.ts +83 -0
  31. package/src/__tests__/handoff-chunking.test.ts +215 -0
  32. package/src/__tests__/handoff-manifest.test.ts +52 -0
  33. package/src/__tests__/message-decoders.test.ts +249 -0
  34. package/src/__tests__/outbound-delivery.test.ts +558 -0
  35. package/src/__tests__/peer-message-ledger.test.ts +200 -0
  36. package/src/__tests__/personal-usage-report.test.ts +193 -0
  37. package/src/__tests__/resume-buffer.test.ts +93 -0
  38. package/src/__tests__/session-event-delivery.test.ts +101 -0
  39. package/src/__tests__/session-message-handler-e5.test.ts +99 -0
  40. package/src/__tests__/session-message-handler.test.ts +590 -0
  41. package/src/__tests__/session-resume-bridge.test.ts +406 -0
  42. package/src/__tests__/submit-prompt-shape.test.ts +78 -0
  43. package/src/__tests__/usage-report-carrier.test.ts +98 -0
  44. package/src/__tests__/ws-broadcast-events.test.ts +84 -0
  45. package/src/background-messages.ts +169 -0
  46. package/src/channel-frames.ts +182 -0
  47. package/src/client.ts +9 -0
  48. package/src/handoff-chunking.ts +235 -0
  49. package/src/index.ts +112 -3
  50. package/src/message-decoders.ts +229 -0
  51. package/src/message-parser.ts +33 -0
  52. package/src/node/access-token-verifier.ts +363 -0
  53. package/src/node/admission.ts +153 -0
  54. package/src/node/handoff-manifest.ts +77 -0
  55. package/src/node/index.ts +10 -0
  56. package/src/outbound-delivery.ts +231 -0
  57. package/src/peer-message-ledger.ts +193 -0
  58. package/src/protocol-session.ts +28 -0
  59. package/src/resume-buffer.ts +123 -0
  60. package/src/session-events.ts +198 -0
  61. package/src/session-message-handler.ts +282 -0
  62. package/src/session-query-messages.ts +42 -0
  63. package/src/session-resume-bridge.ts +252 -0
  64. package/src/usage-messages.ts +195 -0
  65. package/src/wire-messages.ts +165 -0
  66. package/dist/node/headless/index.cjs +0 -1
  67. package/dist/node/headless/index.d.ts +0 -2
  68. package/dist/node/headless/index.js +0 -1
  69. package/dist/node/headless-DG_MEbf0.js +0 -19
  70. package/dist/node/headless-DG_MEbf0.js.map +0 -1
  71. package/dist/node/headless-xq77JBq0.cjs +0 -18
  72. package/dist/node/index-CrJ2Lhn3.d.ts +0 -96
  73. package/dist/node/index-CrJ2Lhn3.d.ts.map +0 -1
  74. package/dist/node/index-JIvzrtaA.d.ts +0 -52
  75. package/dist/node/index-JIvzrtaA.d.ts.map +0 -1
  76. package/dist/node/index-JTD_xtEQ.d.ts +0 -96
  77. package/dist/node/index-JTD_xtEQ.d.ts.map +0 -1
  78. package/dist/node/index-y_HBOfzO.d.ts +0 -52
  79. package/dist/node/index-y_HBOfzO.d.ts.map +0 -1
  80. package/dist/node/programmatic/index.cjs +0 -1
  81. package/dist/node/programmatic/index.d.ts +0 -2
  82. package/dist/node/programmatic/index.js +0 -1
  83. package/dist/node/programmatic-6wLf68qJ.cjs +0 -1
  84. package/dist/node/programmatic-70dxIXtS.js +0 -2
  85. package/dist/node/programmatic-70dxIXtS.js.map +0 -1
  86. package/dist/node/testing/index.cjs +0 -1
  87. package/dist/node/testing/index.d.ts +0 -2
  88. package/dist/node/testing/index.js +0 -1
  89. package/src/__tests__/programmatic/programmatic-ask-user.test.ts +0 -32
  90. package/src/__tests__/programmatic/programmatic-driver.test.ts +0 -107
  91. package/src/__tests__/programmatic/programmatic-flows.test.ts +0 -140
  92. package/src/__tests__/transport-registry.stop-all.test.ts +0 -53
  93. package/src/headless/HeadlessInteractionChannel.ts +0 -129
  94. package/src/headless/__tests__/headless-channel-options.test.ts +0 -106
  95. package/src/headless/__tests__/headless-goal-runner.test.ts +0 -108
  96. package/src/headless/__tests__/headless-provider-failure.integration.test.ts +0 -143
  97. package/src/headless/__tests__/headless-runner-initialization.test.ts +0 -45
  98. package/src/headless/__tests__/headless-runner.test.ts +0 -508
  99. package/src/headless/__tests__/headless-skill-activation.integration.test.ts +0 -430
  100. package/src/headless/__tests__/headless-transport.test.ts +0 -267
  101. package/src/headless/cli-input.ts +0 -50
  102. package/src/headless/headless-runner.ts +0 -230
  103. package/src/headless/headless-stream-json.ts +0 -148
  104. package/src/headless/headless-transport.ts +0 -43
  105. package/src/headless/index.ts +0 -8
  106. package/src/headless/print-terminal.ts +0 -57
  107. package/src/programmatic/ProgrammaticInteractionChannel.ts +0 -82
  108. package/src/programmatic/createProgrammaticAgent.ts +0 -71
  109. package/src/programmatic/index.ts +0 -5
  110. package/src/testing/index.ts +0 -10
  111. package/src/transport-registry.ts +0 -103
@@ -1,50 +0,0 @@
1
- const PRINTABLE_ASCII_START = 32;
2
-
3
- export const promptInput = (label: string, masked = false): Promise<string> =>
4
- new Promise<string>((resolve, reject) => {
5
- process.stdout.write(label);
6
- let input = '';
7
- const stdin = process.stdin;
8
- const wasRaw = stdin.isRaw;
9
- if (!stdin.isTTY) {
10
- reject(
11
- new Error(
12
- 'Cannot prompt for input: stdin is not a TTY.\n' +
13
- 'Set your API key via environment variable instead:\n' +
14
- ' ANTHROPIC_API_KEY=<key> robota\n' +
15
- ' OPENAI_API_KEY=<key> robota',
16
- ),
17
- );
18
- return;
19
- }
20
- stdin.setRawMode(true);
21
- stdin.resume();
22
- stdin.setEncoding('utf8');
23
- const onData = (data: string): void => {
24
- for (const ch of data) {
25
- if (ch === '\r' || ch === '\n') {
26
- stdin.removeListener('data', onData);
27
- stdin.setRawMode(wasRaw ?? false);
28
- stdin.pause();
29
- process.stdout.write('\n');
30
- resolve(input.trim());
31
- return;
32
- } else if (ch === '\x7f' || ch === '\b') {
33
- if (input.length > 0) {
34
- input = input.slice(0, -1);
35
- process.stdout.write('\b \b');
36
- }
37
- } else if (ch === '\x03') {
38
- stdin.removeListener('data', onData);
39
- stdin.setRawMode(wasRaw ?? false);
40
- stdin.pause();
41
- process.stdout.write('\n');
42
- process.exit(0);
43
- } else if (ch.charCodeAt(0) >= PRINTABLE_ASCII_START) {
44
- input += ch;
45
- process.stdout.write(masked ? '*' : ch);
46
- }
47
- }
48
- };
49
- stdin.on('data', onData);
50
- });
@@ -1,230 +0,0 @@
1
- import { executeSlashCommandIfPresent, subscribeStreamJsonEvents } from './headless-stream-json.js';
2
-
3
- import type {
4
- IExecutionResult,
5
- IGoalEvent,
6
- IInteractiveSession,
7
- } from '@robota-sdk/agent-interface-transport';
8
-
9
- export type TOutputFormat = 'text' | 'json' | 'stream-json';
10
-
11
- /** GOAL-001: options for an autonomous headless goal run. */
12
- export interface IHeadlessGoalOptions {
13
- maxIterations?: number;
14
- }
15
-
16
- export interface IHeadlessRunnerOptions {
17
- session: IInteractiveSession;
18
- outputFormat: TOutputFormat;
19
- }
20
-
21
- /** Exit code for a goal that stopped cleanly without being satisfied (bound/convergence/cancel). */
22
- export const GOAL_NOT_SATISFIED_EXIT_CODE = 2;
23
-
24
- export function createHeadlessRunner(options: IHeadlessRunnerOptions): {
25
- run: (prompt: string) => Promise<number>;
26
- runGoal: (objective: string, goalOptions?: IHeadlessGoalOptions) => Promise<number>;
27
- } {
28
- const { session, outputFormat } = options;
29
- return {
30
- run: (prompt: string): Promise<number> => {
31
- if (outputFormat === 'text') return runTextFormat(session, prompt);
32
- if (outputFormat === 'json') return runJsonFormat(session, prompt);
33
- return runStreamJsonFormat(session, prompt);
34
- },
35
- runGoal: (objective: string, goalOptions: IHeadlessGoalOptions = {}): Promise<number> =>
36
- runGoalFormat(session, objective, goalOptions, outputFormat),
37
- };
38
- }
39
-
40
- /**
41
- * GOAL-001: drive an autonomous goal to completion in headless mode. Streams each turn's response
42
- * for progress, then resolves when the goal stops: exit 0 if satisfied, {@link GOAL_NOT_SATISFIED_EXIT_CODE}
43
- * if it stopped at a bound (max-iterations / no-progress / cancelled), or 1 on a turn error.
44
- */
45
- function runGoalFormat(
46
- session: IInteractiveSession,
47
- objective: string,
48
- goalOptions: IHeadlessGoalOptions,
49
- outputFormat: TOutputFormat,
50
- ): Promise<number> {
51
- return new Promise<number>((resolve) => {
52
- const cleanup = (): void => {
53
- session.off('complete', onComplete);
54
- session.off('error', onError);
55
- session.off('goal_event', onGoal);
56
- };
57
- const onComplete = (result: IExecutionResult): void => {
58
- if (result.response) process.stdout.write(result.response + '\n');
59
- };
60
- const onError = (error: Error): void => {
61
- cleanup();
62
- if (outputFormat === 'text') process.stderr.write(error.message + '\n');
63
- else writeJsonResult(getSessionId(session), '', 'error', error);
64
- resolve(1);
65
- };
66
- const onGoal = (event: IGoalEvent): void => {
67
- if (event.type !== 'goal_stopped') return;
68
- cleanup();
69
- const goal = event.goal;
70
- const satisfied = goal.stopReason === 'satisfied';
71
- const summary = satisfied
72
- ? `Goal satisfied after ${goal.iterations} iteration(s).`
73
- : `Goal stopped: ${goal.stopReason} (after ${goal.iterations} iteration(s)).`;
74
- if (outputFormat === 'text')
75
- (satisfied ? process.stdout : process.stderr).write(summary + '\n');
76
- else writeJsonResult(getSessionId(session), summary, satisfied ? 'success' : 'error');
77
- resolve(satisfied ? 0 : GOAL_NOT_SATISFIED_EXIT_CODE);
78
- };
79
-
80
- session.on('complete', onComplete);
81
- session.on('error', onError);
82
- session.on('goal_event', onGoal);
83
-
84
- void session.setGoal(
85
- objective,
86
- goalOptions.maxIterations ? { maxIterations: goalOptions.maxIterations } : {},
87
- );
88
- });
89
- }
90
-
91
- export function resolveErrorCode(error: Error): string {
92
- const msg = error.message.toLowerCase();
93
- if (msg.includes('api key') || msg.includes('no provider') || msg.includes('provider')) {
94
- return 'config_error';
95
- }
96
- if (msg.includes('tool') || msg.includes('execution')) {
97
- return 'tool_error';
98
- }
99
- return 'api_error';
100
- }
101
-
102
- export function writeJsonResult(
103
- sessionId: string,
104
- result: string,
105
- subtype: 'success' | 'error',
106
- error?: Error,
107
- ): void {
108
- const payload: Record<string, unknown> = {
109
- type: 'result',
110
- result,
111
- session_id: sessionId,
112
- subtype,
113
- };
114
- if (subtype === 'error' && error !== undefined) {
115
- payload['error_code'] = resolveErrorCode(error);
116
- }
117
- const output = JSON.stringify(payload);
118
- process.stdout.write(output + '\n');
119
- }
120
-
121
- export function getSessionId(session: IInteractiveSession): string {
122
- try {
123
- return session.getSession().getSessionId();
124
- } catch {
125
- // allow-fallback: session may not be initialized yet
126
- return '';
127
- }
128
- }
129
-
130
- function runTextFormat(session: IInteractiveSession, prompt: string): Promise<number> {
131
- return new Promise<number>((resolve) => {
132
- const cleanup = (): void => {
133
- session.off('complete', onComplete);
134
- session.off('interrupted', onInterrupted);
135
- session.off('error', onError);
136
- };
137
- const onComplete = (result: IExecutionResult): void => {
138
- cleanup();
139
- process.stdout.write(result.response + '\n');
140
- resolve(0);
141
- };
142
- const onInterrupted = (result: IExecutionResult): void => {
143
- cleanup();
144
- if (result.response) process.stdout.write(result.response + '\n');
145
- resolve(0);
146
- };
147
- const onError = (error: Error): void => {
148
- cleanup();
149
- process.stderr.write(error.message + '\n');
150
- resolve(1);
151
- };
152
-
153
- session.on('complete', onComplete);
154
- session.on('interrupted', onInterrupted);
155
- session.on('error', onError);
156
-
157
- void executeSlashCommandIfPresent(session, prompt).then((cmd) => {
158
- if (cmd.kind === 'command-result') {
159
- cleanup();
160
- process.stdout.write(cmd.result.message + '\n');
161
- resolve(cmd.result.success ? 0 : 1);
162
- return;
163
- }
164
- if (cmd.kind !== 'session-execution') void session.submit(prompt);
165
- });
166
- });
167
- }
168
-
169
- function runJsonFormat(session: IInteractiveSession, prompt: string): Promise<number> {
170
- return new Promise<number>((resolve) => {
171
- const cleanup = (): void => {
172
- session.off('complete', onComplete);
173
- session.off('interrupted', onInterrupted);
174
- session.off('error', onError);
175
- };
176
- const onComplete = (result: IExecutionResult): void => {
177
- cleanup();
178
- writeJsonResult(getSessionId(session), result.response, 'success');
179
- resolve(0);
180
- };
181
- const onInterrupted = (result: IExecutionResult): void => {
182
- cleanup();
183
- writeJsonResult(getSessionId(session), result.response, 'success');
184
- resolve(0);
185
- };
186
- const onError = (error: Error): void => {
187
- cleanup();
188
- writeJsonResult(getSessionId(session), '', 'error', error);
189
- resolve(1);
190
- };
191
-
192
- session.on('complete', onComplete);
193
- session.on('interrupted', onInterrupted);
194
- session.on('error', onError);
195
-
196
- void executeSlashCommandIfPresent(session, prompt).then((cmd) => {
197
- if (cmd.kind === 'command-result') {
198
- cleanup();
199
- writeJsonResult(
200
- getSessionId(session),
201
- cmd.result.message,
202
- cmd.result.success ? 'success' : 'error',
203
- );
204
- resolve(cmd.result.success ? 0 : 1);
205
- return;
206
- }
207
- if (cmd.kind !== 'session-execution') void session.submit(prompt);
208
- });
209
- });
210
- }
211
-
212
- function runStreamJsonFormat(session: IInteractiveSession, prompt: string): Promise<number> {
213
- return new Promise<number>((resolve) => {
214
- const cleanup = subscribeStreamJsonEvents(session, getSessionId, writeJsonResult, resolve);
215
-
216
- void executeSlashCommandIfPresent(session, prompt).then((cmd) => {
217
- if (cmd.kind === 'command-result') {
218
- cleanup();
219
- writeJsonResult(
220
- getSessionId(session),
221
- cmd.result.message,
222
- cmd.result.success ? 'success' : 'error',
223
- );
224
- resolve(cmd.result.success ? 0 : 1);
225
- return;
226
- }
227
- if (cmd.kind !== 'session-execution') void session.submit(prompt);
228
- });
229
- });
230
- }
@@ -1,148 +0,0 @@
1
- import { randomUUID } from 'node:crypto';
2
-
3
- import type {
4
- ICommandResult,
5
- IExecutionResult,
6
- IInteractiveSession,
7
- TBackgroundJobGroupEvent,
8
- } from '@robota-sdk/agent-interface-transport';
9
- import type { TBackgroundTaskEvent } from '@robota-sdk/agent-interface-transport';
10
-
11
- type TSlashCommandExecution =
12
- | { readonly kind: 'not-slash' }
13
- | { readonly kind: 'command-result'; readonly result: ICommandResult }
14
- | { readonly kind: 'session-execution' };
15
-
16
- function parseSlashCommand(prompt: string): { name: string; args: string } | null {
17
- const trimmed = prompt.trimStart();
18
- if (!trimmed.startsWith('/')) return null;
19
- const withoutSlash = trimmed.slice(1);
20
- const [name = '', ...args] = withoutSlash.split(/\s+/);
21
- if (name.length === 0) return null;
22
- return { name, args: args.join(' ') };
23
- }
24
-
25
- export async function executeSlashCommandIfPresent(
26
- session: IInteractiveSession,
27
- prompt: string,
28
- ): Promise<TSlashCommandExecution> {
29
- const command = parseSlashCommand(prompt);
30
- if (!command) return { kind: 'not-slash' };
31
-
32
- const result = await session.executeCommand(command.name, command.args);
33
- if (result) {
34
- if (result.effects?.some((effect) => effect.type === 'session-execution-started')) {
35
- return { kind: 'session-execution' };
36
- }
37
- return { kind: 'command-result', result };
38
- }
39
- return {
40
- kind: 'command-result',
41
- result: { message: `Unknown command "/${command.name}".`, success: false },
42
- };
43
- }
44
-
45
- type TStreamJsonEvent =
46
- | {
47
- type: 'content_block_delta';
48
- delta: { type: 'text_delta'; text: string };
49
- }
50
- | {
51
- type: 'background_task_event';
52
- background_task_event: TBackgroundTaskEvent;
53
- }
54
- | {
55
- type: 'background_job_group_event';
56
- background_job_group_event: TBackgroundJobGroupEvent;
57
- };
58
-
59
- interface IStreamJsonHandlers {
60
- onTextDelta: (text: string) => void;
61
- onBackgroundTaskEvent: (event: TBackgroundTaskEvent) => void;
62
- onBackgroundJobGroupEvent: (event: TBackgroundJobGroupEvent) => void;
63
- onComplete: (result: IExecutionResult) => void;
64
- onInterrupted: (result: IExecutionResult) => void;
65
- onError: (error: Error) => void;
66
- }
67
-
68
- function writeStreamJsonEvent(
69
- session: IInteractiveSession,
70
- getSessionId: (s: IInteractiveSession) => string,
71
- event: TStreamJsonEvent,
72
- ): void {
73
- const output = JSON.stringify({
74
- type: 'stream_event',
75
- event,
76
- session_id: getSessionId(session),
77
- uuid: randomUUID(),
78
- });
79
- process.stdout.write(output + '\n');
80
- }
81
-
82
- export function subscribeStreamJsonEvents(
83
- session: IInteractiveSession,
84
- getSessionId: (s: IInteractiveSession) => string,
85
- writeJsonResult: (
86
- sessionId: string,
87
- result: string,
88
- subtype: 'success' | 'error',
89
- error?: Error,
90
- ) => void,
91
- resolve: (exitCode: number) => void,
92
- ): () => void {
93
- const emit = (event: TStreamJsonEvent): void =>
94
- writeStreamJsonEvent(session, getSessionId, event);
95
-
96
- const onTextDelta = (text: string): void =>
97
- emit({ type: 'content_block_delta', delta: { type: 'text_delta', text } });
98
- const onBackgroundTaskEvent = (event: TBackgroundTaskEvent): void =>
99
- emit({ type: 'background_task_event', background_task_event: event });
100
- const onBackgroundJobGroupEvent = (event: TBackgroundJobGroupEvent): void =>
101
- emit({ type: 'background_job_group_event', background_job_group_event: event });
102
-
103
- const cleanup = (): void =>
104
- unsubscribeStreamJsonEvents(session, {
105
- onTextDelta,
106
- onBackgroundTaskEvent,
107
- onBackgroundJobGroupEvent,
108
- onComplete,
109
- onInterrupted,
110
- onError,
111
- });
112
-
113
- const onComplete = (result: IExecutionResult): void => {
114
- cleanup();
115
- writeJsonResult(getSessionId(session), result.response, 'success');
116
- resolve(0);
117
- };
118
- const onInterrupted = (result: IExecutionResult): void => {
119
- cleanup();
120
- writeJsonResult(getSessionId(session), result.response, 'success');
121
- resolve(0);
122
- };
123
- const onError = (error: Error): void => {
124
- cleanup();
125
- writeJsonResult(getSessionId(session), '', 'error', error);
126
- resolve(1);
127
- };
128
-
129
- session.on('text_delta', onTextDelta);
130
- session.on('background_task_event', onBackgroundTaskEvent);
131
- session.on('background_job_group_event', onBackgroundJobGroupEvent);
132
- session.on('complete', onComplete);
133
- session.on('interrupted', onInterrupted);
134
- session.on('error', onError);
135
- return cleanup;
136
- }
137
-
138
- function unsubscribeStreamJsonEvents(
139
- session: IInteractiveSession,
140
- handlers: IStreamJsonHandlers,
141
- ): void {
142
- session.off('text_delta', handlers.onTextDelta);
143
- session.off('background_task_event', handlers.onBackgroundTaskEvent);
144
- session.off('background_job_group_event', handlers.onBackgroundJobGroupEvent);
145
- session.off('complete', handlers.onComplete);
146
- session.off('interrupted', handlers.onInterrupted);
147
- session.off('error', handlers.onError);
148
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * ITransportAdapter implementation for headless transport.
3
- *
4
- * Wraps createHeadlessRunner into the unified ITransportAdapter interface.
5
- * After start() completes, getExitCode() returns the runner's exit code.
6
- */
7
-
8
- import { createHeadlessRunner } from './headless-runner.js';
9
-
10
- import type { TOutputFormat } from './headless-runner.js';
11
- import type { IInteractiveSession, ITransportAdapter } from '@robota-sdk/agent-interface-transport';
12
-
13
- export interface IHeadlessTransportOptions {
14
- /** Output format: 'text', 'json', or 'stream-json'. */
15
- outputFormat: TOutputFormat;
16
- /** The prompt to execute. */
17
- prompt: string;
18
- }
19
-
20
- export function createHeadlessTransport(
21
- options: IHeadlessTransportOptions,
22
- ): ITransportAdapter<IInteractiveSession> & { getExitCode(): number } {
23
- let session: IInteractiveSession | null = null;
24
- let exitCode = 0;
25
-
26
- return {
27
- name: 'headless',
28
- attach(s: IInteractiveSession) {
29
- session = s;
30
- },
31
- async start() {
32
- if (!session) throw new Error('No session attached. Call attach() first.');
33
- const runner = createHeadlessRunner({ session, outputFormat: options.outputFormat });
34
- exitCode = await runner.run(options.prompt);
35
- },
36
- async stop() {
37
- /* no-op: headless runner completes in start() */
38
- },
39
- getExitCode() {
40
- return exitCode;
41
- },
42
- };
43
- }
@@ -1,8 +0,0 @@
1
- export { PrintTerminal } from './print-terminal.js';
2
- export { promptInput } from './cli-input.js';
3
- export { createHeadlessRunner } from './headless-runner.js';
4
- export type { IHeadlessRunnerOptions, TOutputFormat } from './headless-runner.js';
5
- export { createHeadlessTransport } from './headless-transport.js';
6
- export type { IHeadlessTransportOptions } from './headless-transport.js';
7
- export { HeadlessInteractionChannel } from './HeadlessInteractionChannel.js';
8
- export type { IHeadlessInteractionChannelOptions } from './HeadlessInteractionChannel.js';
@@ -1,57 +0,0 @@
1
- /**
2
- * ITerminalOutput implementation for print mode (-p).
3
- *
4
- * Writes to stdout/stderr directly. The readline-based prompt and select are
5
- * only invoked if the agent triggers a permission-gated tool, which is rare in
6
- * one-shot print mode but must still work correctly.
7
- */
8
-
9
- import * as readline from 'node:readline';
10
-
11
- import type { ITerminalOutput, ISpinner } from '@robota-sdk/agent-core';
12
-
13
- export class PrintTerminal implements ITerminalOutput {
14
- write(text: string): void {
15
- process.stdout.write(text);
16
- }
17
- writeLine(text: string): void {
18
- process.stdout.write(text + '\n');
19
- }
20
- writeMarkdown(md: string): void {
21
- process.stdout.write(md);
22
- }
23
- writeError(text: string): void {
24
- process.stderr.write(text + '\n');
25
- }
26
- prompt(question: string): Promise<string> {
27
- return new Promise<string>((resolve) => {
28
- const rl = readline.createInterface({
29
- input: process.stdin,
30
- output: process.stdout,
31
- terminal: false,
32
- historySize: 0,
33
- });
34
- rl.question(question, (answer) => {
35
- rl.close();
36
- resolve(answer);
37
- });
38
- });
39
- }
40
- async select(options: string[], initialIndex = 0): Promise<number> {
41
- for (let i = 0; i < options.length; i++) {
42
- const marker = i === initialIndex ? '>' : ' ';
43
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}\n`);
44
- }
45
- const answer = await this.prompt(
46
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `,
47
- );
48
- const trimmed = answer.trim().toLowerCase();
49
- if (trimmed === '') return initialIndex;
50
- const num = parseInt(trimmed, 10);
51
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
52
- return initialIndex;
53
- }
54
- spinner(_message: string): ISpinner {
55
- return { stop(): void {}, update(): void {} };
56
- }
57
- }
@@ -1,82 +0,0 @@
1
- /**
2
- * ProgrammaticInteractionChannel — an in-process IInteractionChannel adapter (INFRA-019).
3
- *
4
- * The "programmatic preset" adapter slot reserved by the interaction contract: instead of an Ink TUI
5
- * or a print runner, this channel lets a caller push a message in-process and read back the structured
6
- * `InteractionEvent` stream the framework already emits. It uses the documented one-way `write()`
7
- * protocol consumed by `createInteractiveRuntime` (not the TUI's direct-wiring path).
8
- *
9
- * Production transport adapter — lives in transport core. Tests and automation consume it; it never
10
- * depends on test code.
11
- */
12
-
13
- import type { IActionRequest, TActionResponse } from '@robota-sdk/agent-core';
14
- import type {
15
- IInteractionChannel,
16
- ICommandInfo,
17
- InteractionEvent,
18
- } from '@robota-sdk/agent-interface-transport';
19
-
20
- export class ProgrammaticInteractionChannel implements IInteractionChannel {
21
- /** Full structured event stream pushed by the framework, in order. */
22
- readonly events: InteractionEvent[] = [];
23
-
24
- availableCommands: ICommandInfo[] = [];
25
- busy = false;
26
- started = false;
27
- stopped = false;
28
-
29
- private submitHandler: ((text: string) => Promise<void>) | null = null;
30
- private readonly userActionResponses: TActionResponse[] = [];
31
-
32
- // ── IInteractionChannel ──────────────────────────────────────
33
-
34
- onSubmit(handler: (text: string) => Promise<void>): void {
35
- this.submitHandler = handler;
36
- }
37
-
38
- write(event: InteractionEvent): void {
39
- this.events.push(event);
40
- }
41
-
42
- /**
43
- * CMD-004 unified ask. Resolves from the pre-supplied queue (FIFO); an empty queue resolves
44
- * `{ type: 'cancelled' }` so a programmatic run never blocks on an un-answered question.
45
- */
46
- async askUser(_request: IActionRequest): Promise<TActionResponse> {
47
- return this.userActionResponses.shift() ?? { type: 'cancelled' };
48
- }
49
-
50
- setAvailableCommands(commands: ICommandInfo[]): void {
51
- this.availableCommands = commands;
52
- }
53
-
54
- setBusy(busy: boolean): void {
55
- this.busy = busy;
56
- }
57
-
58
- async start(): Promise<void> {
59
- this.started = true;
60
- }
61
-
62
- async stop(): Promise<void> {
63
- this.stopped = true;
64
- }
65
-
66
- // ── Programmatic driving surface ─────────────────────────────
67
-
68
- /** Push a user submission into the framework (the programmatic "user types and presses enter"). */
69
- async submit(text: string): Promise<void> {
70
- if (!this.submitHandler) {
71
- throw new Error(
72
- 'ProgrammaticInteractionChannel: no submit handler registered — start the runtime first',
73
- );
74
- }
75
- await this.submitHandler(text);
76
- }
77
-
78
- /** Pre-answer the next `askUser` (CMD-004 unified ask). */
79
- queueUserAction(response: TActionResponse): void {
80
- this.userActionResponses.push(response);
81
- }
82
- }
@@ -1,71 +0,0 @@
1
- /**
2
- * createProgrammaticAgent — the in-process implementation of the client-side agent contract
3
- * (`IAgentDriver`, INFRA-020; introduced as the programmatic driver in INFRA-019).
4
- *
5
- * Wraps `createInteractiveRuntime` with a {@link ProgrammaticInteractionChannel} so a caller can drive
6
- * the real agent structurally: `start()`, `send(text)` (awaits the whole turn), then read assistant
7
- * replies / tool calls / errors as data — no terminal, no PTY, no scraping. The observation accessors
8
- * delegate to the shared `read*` helpers in `@robota-sdk/agent-interface-transport`, so the
9
- * filter/derivation logic is not re-implemented here.
10
- */
11
-
12
- import { createInteractiveRuntime } from '@robota-sdk/agent-framework';
13
- import {
14
- readAssistantReplies,
15
- readErrors,
16
- readLastAssistantText,
17
- readToolCalls,
18
- } from '@robota-sdk/agent-interface-transport';
19
-
20
- import { ProgrammaticInteractionChannel } from './ProgrammaticInteractionChannel.js';
21
-
22
- import type { IAIProvider, TActionResponse, TPermissionMode } from '@robota-sdk/agent-core';
23
- import type { ICommandModule, IInteractiveRuntime } from '@robota-sdk/agent-framework';
24
- import type { IAgentDriver, IInteractiveSessionStore } from '@robota-sdk/agent-interface-transport';
25
-
26
- export interface ICreateProgrammaticAgentOptions {
27
- /** Provider that answers the agent loop (e.g. a real provider, or the scripted provider in tests). */
28
- provider: IAIProvider;
29
- /** Working directory for session creation. */
30
- cwd: string;
31
- /** Slash-command modules to register (defaults to none). */
32
- commandModules?: readonly ICommandModule[];
33
- /** Optional session store for persistence. */
34
- sessionStore?: IInteractiveSessionStore;
35
- /** Permission mode for tool execution (e.g. `'bypassPermissions'` for unattended driving). */
36
- permissionMode?: TPermissionMode;
37
- }
38
-
39
- /**
40
- * Construct an in-process {@link IAgentDriver} bound to a real `InteractiveSession`. The returned
41
- * driver's accessors are the shared `read*` helpers applied to the captured event stream.
42
- */
43
- export function createProgrammaticAgent(options: ICreateProgrammaticAgentOptions): IAgentDriver {
44
- const channel = new ProgrammaticInteractionChannel();
45
- const runtime: IInteractiveRuntime = createInteractiveRuntime({
46
- channel,
47
- commandModules: options.commandModules ?? [],
48
- provider: options.provider,
49
- cwd: options.cwd,
50
- sessionStore: options.sessionStore,
51
- permissionMode: options.permissionMode,
52
- });
53
-
54
- let started = false;
55
-
56
- return {
57
- events: channel.events,
58
- start: async (): Promise<void> => {
59
- if (started) return;
60
- started = true;
61
- await runtime.start();
62
- },
63
- send: (text: string): Promise<void> => channel.submit(text),
64
- queueUserAction: (response: TActionResponse): void => channel.queueUserAction(response),
65
- assistantReplies: (): string[] => readAssistantReplies(channel.events),
66
- lastAssistantText: (): string | undefined => readLastAssistantText(channel.events),
67
- toolCalls: () => readToolCalls(channel.events),
68
- errors: (): Error[] => readErrors(channel.events),
69
- stop: (): Promise<void> => runtime.stop(),
70
- };
71
- }