@robota-sdk/agent-transport 3.0.0-beta.75 → 3.0.0-beta.77
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -21
- package/README.md +17 -14
- package/dist/node/headless/index.cjs +1 -1
- package/dist/node/headless/index.d.ts +1 -1
- package/dist/node/headless/index.js +1 -1
- package/dist/node/{headless-mRYilLfC.js → headless-DG_MEbf0.js} +7 -4
- package/dist/node/headless-DG_MEbf0.js.map +1 -0
- package/dist/node/headless-xq77JBq0.cjs +18 -0
- package/dist/node/{index-E8Gx4-lc.d.ts → index-CrJ2Lhn3.d.ts} +16 -3
- package/dist/node/index-CrJ2Lhn3.d.ts.map +1 -0
- package/dist/node/index-JIvzrtaA.d.ts +52 -0
- package/dist/node/index-JIvzrtaA.d.ts.map +1 -0
- package/dist/node/{index-CYl7ksS6.d.ts → index-JTD_xtEQ.d.ts} +15 -2
- package/dist/node/index-JTD_xtEQ.d.ts.map +1 -0
- package/dist/node/index-y_HBOfzO.d.ts +52 -0
- package/dist/node/index-y_HBOfzO.d.ts.map +1 -0
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.ts +10 -9
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/node/index.js.map +1 -1
- package/dist/node/programmatic/index.cjs +1 -0
- package/dist/node/programmatic/index.d.ts +2 -0
- package/dist/node/programmatic/index.js +1 -0
- package/dist/node/programmatic-6wLf68qJ.cjs +1 -0
- package/dist/node/programmatic-70dxIXtS.js +2 -0
- package/dist/node/programmatic-70dxIXtS.js.map +1 -0
- package/dist/node/testing/index.cjs +1 -1
- package/dist/node/testing/index.d.ts +2 -21
- package/dist/node/testing/index.js +1 -2
- package/package.json +15 -71
- package/src/__tests__/programmatic/programmatic-ask-user.test.ts +32 -0
- package/src/__tests__/programmatic/programmatic-driver.test.ts +107 -0
- package/src/__tests__/programmatic/programmatic-flows.test.ts +140 -0
- package/src/__tests__/transport-registry.stop-all.test.ts +53 -0
- package/src/headless/HeadlessInteractionChannel.ts +22 -5
- package/src/headless/__tests__/headless-goal-runner.test.ts +108 -0
- package/src/headless/__tests__/headless-runner.test.ts +2 -1
- package/src/headless/headless-runner.ts +67 -1
- package/src/headless/headless-stream-json.ts +1 -1
- package/src/headless/print-terminal.ts +1 -0
- package/src/index.ts +2 -5
- package/src/programmatic/ProgrammaticInteractionChannel.ts +82 -0
- package/src/programmatic/createProgrammaticAgent.ts +71 -0
- package/src/programmatic/index.ts +5 -0
- package/src/testing/index.ts +7 -4
- package/src/transport-registry.ts +15 -12
- package/dist/node/headless-CT2ibQnr.cjs +0 -15
- package/dist/node/headless-mRYilLfC.js.map +0 -1
- package/dist/node/http/index.cjs +0 -1
- package/dist/node/http/index.d.ts +0 -2
- package/dist/node/http/index.js +0 -1
- package/dist/node/http-2Jiuflc1.js +0 -2
- package/dist/node/http-2Jiuflc1.js.map +0 -1
- package/dist/node/http-CBAvefLw.cjs +0 -1
- package/dist/node/index-BNccqSpv.d.ts +0 -86
- package/dist/node/index-BNccqSpv.d.ts.map +0 -1
- package/dist/node/index-BUhHIf7X.d.ts +0 -86
- package/dist/node/index-BUhHIf7X.d.ts.map +0 -1
- package/dist/node/index-BnAGE-u9.d.ts +0 -33
- package/dist/node/index-BnAGE-u9.d.ts.map +0 -1
- package/dist/node/index-BrQ4gGw0.d.ts +0 -213
- package/dist/node/index-BrQ4gGw0.d.ts.map +0 -1
- package/dist/node/index-CYl7ksS6.d.ts.map +0 -1
- package/dist/node/index-CoeBF21y.d.ts +0 -213
- package/dist/node/index-CoeBF21y.d.ts.map +0 -1
- package/dist/node/index-DHt-2VQ-.d.ts +0 -46
- package/dist/node/index-DHt-2VQ-.d.ts.map +0 -1
- package/dist/node/index-DMwKN5Le.d.ts +0 -33
- package/dist/node/index-DMwKN5Le.d.ts.map +0 -1
- package/dist/node/index-E8Gx4-lc.d.ts.map +0 -1
- package/dist/node/index-c0M42fsA.d.ts +0 -46
- package/dist/node/index-c0M42fsA.d.ts.map +0 -1
- package/dist/node/mcp/index.cjs +0 -1
- package/dist/node/mcp/index.d.ts +0 -2
- package/dist/node/mcp/index.js +0 -1
- package/dist/node/mcp-BOglBJNy.cjs +0 -1
- package/dist/node/mcp-D3BBVK7C.js +0 -2
- package/dist/node/mcp-D3BBVK7C.js.map +0 -1
- package/dist/node/rolldown-runtime-CMqjfN_6.cjs +0 -1
- package/dist/node/testing/index.d.ts.map +0 -1
- package/dist/node/testing/index.js.map +0 -1
- package/dist/node/tui/index.cjs +0 -1
- package/dist/node/tui/index.d.ts +0 -2
- package/dist/node/tui/index.js +0 -1
- package/dist/node/tui-CcH5EsQh.js +0 -25
- package/dist/node/tui-CcH5EsQh.js.map +0 -1
- package/dist/node/tui-DznRbcku.cjs +0 -24
- package/dist/node/ws/index.cjs +0 -1
- package/dist/node/ws/index.d.ts +0 -2
- package/dist/node/ws/index.js +0 -1
- package/dist/node/ws-Dc2RUwVs.js +0 -2
- package/dist/node/ws-Dc2RUwVs.js.map +0 -1
- package/dist/node/ws-QNMQn5kg.cjs +0 -1
- package/src/http/__tests__/http-transport.test.ts +0 -55
- package/src/http/__tests__/routes.test.ts +0 -168
- package/src/http/http-transport.ts +0 -41
- package/src/http/index.ts +0 -4
- package/src/http/routes.ts +0 -152
- package/src/mcp/__tests__/mcp-server.test.ts +0 -66
- package/src/mcp/__tests__/mcp-transport.test.ts +0 -46
- package/src/mcp/index.ts +0 -4
- package/src/mcp/mcp-server.ts +0 -163
- package/src/mcp/mcp-transport.ts +0 -48
- package/src/testing/__tests__/scripted-provider.test.ts +0 -73
- package/src/testing/scripted-provider.ts +0 -73
- package/src/tui/App.tsx +0 -491
- package/src/tui/BackgroundTaskPanel.tsx +0 -36
- package/src/tui/CjkTextInput.tsx +0 -199
- package/src/tui/ConfirmPrompt.tsx +0 -70
- package/src/tui/ContextWarningBanner.tsx +0 -34
- package/src/tui/ExecutionWorkspaceDetailPane.tsx +0 -64
- package/src/tui/ExecutionWorkspaceSwitcher.tsx +0 -187
- package/src/tui/InputArea.tsx +0 -310
- package/src/tui/InteractivePrompt.tsx +0 -59
- package/src/tui/ListPicker.tsx +0 -95
- package/src/tui/MenuSelect.tsx +0 -104
- package/src/tui/MessageList.tsx +0 -284
- package/src/tui/PermissionPrompt.tsx +0 -86
- package/src/tui/PluginTUI.tsx +0 -258
- package/src/tui/SessionPicker.tsx +0 -68
- package/src/tui/SessionStatusBar.tsx +0 -73
- package/src/tui/SlashAutocomplete.tsx +0 -110
- package/src/tui/StatusBar.tsx +0 -236
- package/src/tui/StreamingIndicator.tsx +0 -93
- package/src/tui/TextPrompt.tsx +0 -81
- package/src/tui/ToolCommandOutput.tsx +0 -39
- package/src/tui/ToolDiffBlock.tsx +0 -32
- package/src/tui/TransportTUI.tsx +0 -117
- package/src/tui/TuiInteractionChannel.ts +0 -495
- package/src/tui/UpdateNotice.tsx +0 -14
- package/src/tui/UsageSummaryEntry.tsx +0 -39
- package/src/tui/WaveText.tsx +0 -44
- package/src/tui/__tests__/InteractivePrompt.test.tsx +0 -82
- package/src/tui/__tests__/ListPicker.test.tsx +0 -159
- package/src/tui/__tests__/MenuSelect.test.tsx +0 -103
- package/src/tui/__tests__/PluginTUI.test.tsx +0 -167
- package/src/tui/__tests__/SlashAutocomplete.test.tsx +0 -140
- package/src/tui/__tests__/TextPrompt.test.tsx +0 -98
- package/src/tui/__tests__/TuiInteractionChannel.display-contract.test.ts +0 -239
- package/src/tui/__tests__/TuiInteractionChannel.lifecycle.test.ts +0 -297
- package/src/tui/__tests__/TuiInteractionChannel.requestAction.test.ts +0 -124
- package/src/tui/__tests__/UpdateNotice.test.tsx +0 -15
- package/src/tui/__tests__/abort-after-permission.test.tsx +0 -169
- package/src/tui/__tests__/abort-streaming-e2e.test.tsx +0 -183
- package/src/tui/__tests__/background-task-panel.test.tsx +0 -53
- package/src/tui/__tests__/background-task-row-format.test.ts +0 -59
- package/src/tui/__tests__/channel-factory-integration.test.ts +0 -138
- package/src/tui/__tests__/cjk-text-input-flow.test.ts +0 -109
- package/src/tui/__tests__/cjk-text-input.test.ts +0 -191
- package/src/tui/__tests__/command-effect-handler.test.ts +0 -127
- package/src/tui/__tests__/command-output-summary.test.ts +0 -95
- package/src/tui/__tests__/compact-event-bridge.test.ts +0 -20
- package/src/tui/__tests__/confirm-permission-flow.test.ts +0 -130
- package/src/tui/__tests__/confirm-prompt.test.tsx +0 -87
- package/src/tui/__tests__/execution-workspace-switcher.test.tsx +0 -110
- package/src/tui/__tests__/execution-workspace-view-model.test.ts +0 -93
- package/src/tui/__tests__/fixtures/provider-setup-prompt-driver.tsx +0 -125
- package/src/tui/__tests__/input-area-flow.test.ts +0 -164
- package/src/tui/__tests__/message-list-rendering.test.tsx +0 -353
- package/src/tui/__tests__/prompt-queue.test.tsx +0 -255
- package/src/tui/__tests__/provider-setup-pty-e2e.test.ts +0 -233
- package/src/tui/__tests__/pty/pty-driver.ts +0 -135
- package/src/tui/__tests__/pty/tui-pty.ptytest.ts +0 -61
- package/src/tui/__tests__/render-channel-options.test.ts +0 -32
- package/src/tui/__tests__/render-markdown.test.ts +0 -72
- package/src/tui/__tests__/selection-flow.test.ts +0 -61
- package/src/tui/__tests__/session-init-poller.test.ts +0 -102
- package/src/tui/__tests__/session-naming.test.ts +0 -64
- package/src/tui/__tests__/session-switch-channel.test.tsx +0 -307
- package/src/tui/__tests__/slash-routing-effects.test.ts +0 -228
- package/src/tui/__tests__/status-activity.test.ts +0 -71
- package/src/tui/__tests__/status-bar.test.tsx +0 -177
- package/src/tui/__tests__/streaming-indicator.test.tsx +0 -137
- package/src/tui/__tests__/text-prompt-flow.test.ts +0 -77
- package/src/tui/__tests__/tui-channel-init-failure.test.ts +0 -57
- package/src/tui/__tests__/tui-state-manager.test.ts +0 -401
- package/src/tui/background-task-row-format.ts +0 -53
- package/src/tui/command-interaction.ts +0 -9
- package/src/tui/command-output-summary.ts +0 -122
- package/src/tui/create-default-tui-cli-adapter.ts +0 -41
- package/src/tui/execution-workspace-view-model.ts +0 -123
- package/src/tui/flows/cjk-text-input-flow.ts +0 -285
- package/src/tui/flows/confirm-prompt-flow.ts +0 -45
- package/src/tui/flows/input-area-flow.ts +0 -189
- package/src/tui/flows/permission-prompt-flow.ts +0 -85
- package/src/tui/flows/selection-flow.ts +0 -126
- package/src/tui/flows/session-init-poller.ts +0 -77
- package/src/tui/flows/text-prompt-flow.ts +0 -98
- package/src/tui/hooks/command-effect-handler.ts +0 -97
- package/src/tui/hooks/command-effect-queue.ts +0 -39
- package/src/tui/hooks/side-effects-types.ts +0 -35
- package/src/tui/hooks/useAutocomplete.ts +0 -87
- package/src/tui/hooks/usePluginCallbacks.ts +0 -31
- package/src/tui/hooks/usePluginScreenData.ts +0 -85
- package/src/tui/hooks/useSideEffects.ts +0 -175
- package/src/tui/hooks/useSlashRouting.ts +0 -118
- package/src/tui/hooks/useStatusLineSettings.ts +0 -37
- package/src/tui/hooks/useTuiChannel.ts +0 -95
- package/src/tui/index.ts +0 -14
- package/src/tui/interactions/CommandConfirm.tsx +0 -36
- package/src/tui/interactions/CommandPicker.tsx +0 -77
- package/src/tui/interactions/__tests__/CommandConfirm.test.tsx +0 -124
- package/src/tui/interactions/__tests__/CommandPicker.test.tsx +0 -138
- package/src/tui/plugin-tui-handlers.ts +0 -163
- package/src/tui/render-markdown.ts +0 -130
- package/src/tui/render.tsx +0 -129
- package/src/tui/session-naming.ts +0 -33
- package/src/tui/status-activity.ts +0 -63
- package/src/tui/tui-cli-adapter-context.tsx +0 -13
- package/src/tui/tui-cli-adapter.ts +0 -25
- package/src/tui/tui-state-manager.ts +0 -226
- package/src/tui/tui-transport.ts +0 -35
- package/src/tui/types.ts +0 -15
- package/src/tui/utils/__tests__/edit-diff.test.ts +0 -426
- package/src/tui/utils/__tests__/paste-detection.test.ts +0 -116
- package/src/tui/utils/__tests__/paste-labels.test.ts +0 -46
- package/src/tui/utils/__tests__/tool-call-extractor.test.ts +0 -227
- package/src/tui/utils/__tests__/tool-diff-summary.test.ts +0 -104
- package/src/tui/utils/edit-diff.ts +0 -153
- package/src/tui/utils/paste-labels.ts +0 -9
- package/src/tui/utils/tool-call-extractor.ts +0 -92
- package/src/tui/utils/tool-diff-summary.ts +0 -75
- package/src/ws/__tests__/ws-handler.test.ts +0 -409
- package/src/ws/__tests__/ws-transport.test.ts +0 -53
- package/src/ws/index.ts +0 -13
- package/src/ws/ws-background-messages.ts +0 -170
- package/src/ws/ws-handler.ts +0 -280
- package/src/ws/ws-protocol.ts +0 -78
- package/src/ws/ws-transport-configurable.ts +0 -128
- package/src/ws/ws-transport.ts +0 -42
package/src/http/routes.ts
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP transport adapter — exposes IInteractiveSession over REST API.
|
|
3
|
-
*
|
|
4
|
-
* Built on Hono for Cloudflare Workers + Node.js + AWS Lambda compatibility.
|
|
5
|
-
* Each endpoint maps 1:1 to an IInteractiveSession API method.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Hono } from 'hono';
|
|
9
|
-
import { streamSSE } from 'hono/streaming';
|
|
10
|
-
|
|
11
|
-
import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
|
|
12
|
-
import type { Context } from 'hono';
|
|
13
|
-
|
|
14
|
-
/** Callback that resolves an IInteractiveSession from the request context. */
|
|
15
|
-
export type TSessionFactory = (c: Context) => IInteractiveSession | Promise<IInteractiveSession>;
|
|
16
|
-
|
|
17
|
-
export interface IAgentRoutesOptions {
|
|
18
|
-
/** Resolve an IInteractiveSession per request (e.g., by auth token, session ID). */
|
|
19
|
-
sessionFactory: TSessionFactory;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Create a Hono router with all agent HTTP endpoints.
|
|
24
|
-
*
|
|
25
|
-
* Usage:
|
|
26
|
-
* ```typescript
|
|
27
|
-
* const routes = createAgentRoutes({ sessionFactory });
|
|
28
|
-
* app.route('/agent', routes); // mount on existing app
|
|
29
|
-
* export default routes; // or use standalone (CF Workers)
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export function createAgentRoutes(options: IAgentRoutesOptions): Hono {
|
|
33
|
-
const { sessionFactory } = options;
|
|
34
|
-
const app = new Hono();
|
|
35
|
-
|
|
36
|
-
// POST /submit — execute prompt, stream events via SSE
|
|
37
|
-
app.post('/submit', async (c) => {
|
|
38
|
-
const session = await sessionFactory(c);
|
|
39
|
-
const body = await c.req.json<{ prompt: string }>();
|
|
40
|
-
|
|
41
|
-
if (!body.prompt || typeof body.prompt !== 'string') {
|
|
42
|
-
return c.json({ error: 'prompt is required' }, 400);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return streamSSE(c, async (stream) => {
|
|
46
|
-
const cleanup: Array<() => void> = [];
|
|
47
|
-
|
|
48
|
-
const subscribe = <T>(event: string, handler: (data: T) => void): void => {
|
|
49
|
-
session.on(event as 'text_delta', handler as () => void);
|
|
50
|
-
cleanup.push(() => session.off(event as 'text_delta', handler as () => void));
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
let completed = false;
|
|
54
|
-
const done = new Promise<void>((resolve) => {
|
|
55
|
-
subscribe('text_delta', (delta: string) => {
|
|
56
|
-
stream.writeSSE({ event: 'text_delta', data: JSON.stringify({ delta }) });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
subscribe('tool_start', (state) => {
|
|
60
|
-
stream.writeSSE({ event: 'tool_start', data: JSON.stringify(state) });
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
subscribe('tool_end', (state) => {
|
|
64
|
-
stream.writeSSE({ event: 'tool_end', data: JSON.stringify(state) });
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
subscribe('thinking', (isThinking: boolean) => {
|
|
68
|
-
stream.writeSSE({ event: 'thinking', data: JSON.stringify({ isThinking }) });
|
|
69
|
-
if (!isThinking && completed) {
|
|
70
|
-
resolve();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
subscribe('complete', (result) => {
|
|
75
|
-
completed = true;
|
|
76
|
-
stream.writeSSE({ event: 'complete', data: JSON.stringify(result) });
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
subscribe('interrupted', (result) => {
|
|
80
|
-
completed = true;
|
|
81
|
-
stream.writeSSE({ event: 'interrupted', data: JSON.stringify(result) });
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
subscribe('error', (error: Error) => {
|
|
85
|
-
completed = true;
|
|
86
|
-
stream.writeSSE({ event: 'error', data: JSON.stringify({ message: error.message }) });
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await session.submit(body.prompt);
|
|
91
|
-
await done;
|
|
92
|
-
|
|
93
|
-
for (const fn of cleanup) fn();
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// POST /command — execute system command
|
|
98
|
-
app.post('/command', async (c) => {
|
|
99
|
-
const session = await sessionFactory(c);
|
|
100
|
-
const body = await c.req.json<{ name: string; args?: string }>();
|
|
101
|
-
|
|
102
|
-
if (!body.name || typeof body.name !== 'string') {
|
|
103
|
-
return c.json({ error: 'name is required' }, 400);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const result = await session.executeCommand(body.name, body.args ?? '');
|
|
107
|
-
if (!result) {
|
|
108
|
-
return c.json({ error: `Unknown command: ${body.name}` }, 404);
|
|
109
|
-
}
|
|
110
|
-
return c.json(result);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
// POST /abort — abort current execution
|
|
114
|
-
app.post('/abort', async (c) => {
|
|
115
|
-
const session = await sessionFactory(c);
|
|
116
|
-
session.abort();
|
|
117
|
-
return c.json({ ok: true });
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// POST /cancel-queue — cancel queued prompt
|
|
121
|
-
app.post('/cancel-queue', async (c) => {
|
|
122
|
-
const session = await sessionFactory(c);
|
|
123
|
-
session.cancelQueue();
|
|
124
|
-
return c.json({ ok: true });
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// GET /messages — get message history
|
|
128
|
-
app.get('/messages', async (c) => {
|
|
129
|
-
const session = await sessionFactory(c);
|
|
130
|
-
return c.json(session.getMessages());
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// GET /context — get context window state
|
|
134
|
-
app.get('/context', async (c) => {
|
|
135
|
-
const session = await sessionFactory(c);
|
|
136
|
-
return c.json(session.getContextState());
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// GET /executing — check if currently executing
|
|
140
|
-
app.get('/executing', async (c) => {
|
|
141
|
-
const session = await sessionFactory(c);
|
|
142
|
-
return c.json({ executing: session.isExecuting() });
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// GET /pending — get pending queued prompt
|
|
146
|
-
app.get('/pending', async (c) => {
|
|
147
|
-
const session = await sessionFactory(c);
|
|
148
|
-
return c.json({ pending: session.getPendingPrompt() });
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
return app;
|
|
152
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for MCP transport adapter.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
6
|
-
import { createAgentMcpServer } from '../mcp-server.js';
|
|
7
|
-
import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
|
|
8
|
-
|
|
9
|
-
function createMockSession(commands?: Array<{ name: string; description: string }>) {
|
|
10
|
-
return {
|
|
11
|
-
submit: vi.fn(),
|
|
12
|
-
abort: vi.fn(),
|
|
13
|
-
cancelQueue: vi.fn(),
|
|
14
|
-
getMessages: vi.fn().mockReturnValue([]),
|
|
15
|
-
getContextState: vi
|
|
16
|
-
.fn()
|
|
17
|
-
.mockReturnValue({ usedTokens: 0, maxTokens: 200000, usedPercentage: 0 }),
|
|
18
|
-
isExecuting: vi.fn().mockReturnValue(false),
|
|
19
|
-
getPendingPrompt: vi.fn().mockReturnValue(null),
|
|
20
|
-
executeCommand: vi.fn().mockResolvedValue({ message: 'done', success: true }),
|
|
21
|
-
listCommands: vi.fn().mockReturnValue(
|
|
22
|
-
commands ?? [
|
|
23
|
-
{ name: 'clear', description: 'Clear history' },
|
|
24
|
-
{ name: 'mode', description: 'Permission mode' },
|
|
25
|
-
],
|
|
26
|
-
),
|
|
27
|
-
on: vi.fn(),
|
|
28
|
-
off: vi.fn(),
|
|
29
|
-
} as unknown as IInteractiveSession;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
describe('createAgentMcpServer', () => {
|
|
33
|
-
it('creates a server instance', () => {
|
|
34
|
-
const server = createAgentMcpServer({
|
|
35
|
-
name: 'test-agent',
|
|
36
|
-
version: '1.0.0',
|
|
37
|
-
session: createMockSession(),
|
|
38
|
-
});
|
|
39
|
-
expect(server).toBeDefined();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('exposeCommands=false does not call listCommands', () => {
|
|
43
|
-
const session = createMockSession();
|
|
44
|
-
createAgentMcpServer({
|
|
45
|
-
name: 'test',
|
|
46
|
-
version: '1.0.0',
|
|
47
|
-
session,
|
|
48
|
-
exposeCommands: false,
|
|
49
|
-
});
|
|
50
|
-
expect(session.listCommands).not.toHaveBeenCalled();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('exposeCommands=true calls session.listCommands()', () => {
|
|
54
|
-
const session = createMockSession([
|
|
55
|
-
{ name: 'clear', description: 'Clear' },
|
|
56
|
-
{ name: 'help', description: 'Help' },
|
|
57
|
-
]);
|
|
58
|
-
createAgentMcpServer({
|
|
59
|
-
name: 'test',
|
|
60
|
-
version: '1.0.0',
|
|
61
|
-
session,
|
|
62
|
-
exposeCommands: true,
|
|
63
|
-
});
|
|
64
|
-
expect(session.listCommands).toHaveBeenCalled();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { createMcpTransport } from '../mcp-transport.js';
|
|
3
|
-
import type { IInteractiveSession } from '@robota-sdk/agent-interface-transport';
|
|
4
|
-
|
|
5
|
-
function createMockSession(): IInteractiveSession {
|
|
6
|
-
return {
|
|
7
|
-
submit: vi.fn(),
|
|
8
|
-
abort: vi.fn(),
|
|
9
|
-
cancelQueue: vi.fn(),
|
|
10
|
-
getMessages: vi.fn().mockReturnValue([]),
|
|
11
|
-
getContextState: vi
|
|
12
|
-
.fn()
|
|
13
|
-
.mockReturnValue({ usedPercentage: 0, usedTokens: 0, maxTokens: 200000 }),
|
|
14
|
-
isExecuting: vi.fn().mockReturnValue(false),
|
|
15
|
-
getPendingPrompt: vi.fn().mockReturnValue(null),
|
|
16
|
-
executeCommand: vi.fn().mockResolvedValue({ message: 'ok', success: true }),
|
|
17
|
-
listCommands: vi.fn().mockReturnValue([]),
|
|
18
|
-
on: vi.fn(),
|
|
19
|
-
off: vi.fn(),
|
|
20
|
-
} as unknown as IInteractiveSession;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe('createMcpTransport', () => {
|
|
24
|
-
it('returns an adapter with name "mcp"', () => {
|
|
25
|
-
const transport = createMcpTransport({ name: 'test', version: '1.0.0' });
|
|
26
|
-
expect(transport.name).toBe('mcp');
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('throws if start() is called without attach()', async () => {
|
|
30
|
-
const transport = createMcpTransport({ name: 'test', version: '1.0.0' });
|
|
31
|
-
await expect(transport.start()).rejects.toThrow('No session attached');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('throws if getServer() is called before start()', () => {
|
|
35
|
-
const transport = createMcpTransport({ name: 'test', version: '1.0.0' });
|
|
36
|
-
expect(() => transport.getServer()).toThrow('Transport not started');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('creates an MCP server after attach + start', async () => {
|
|
40
|
-
const transport = createMcpTransport({ name: 'test', version: '1.0.0' });
|
|
41
|
-
transport.attach(createMockSession() as never);
|
|
42
|
-
await transport.start();
|
|
43
|
-
const server = transport.getServer();
|
|
44
|
-
expect(server).toBeDefined();
|
|
45
|
-
});
|
|
46
|
-
});
|
package/src/mcp/index.ts
DELETED
package/src/mcp/mcp-server.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP transport adapter — exposes IInteractiveSession as an MCP server.
|
|
3
|
-
*
|
|
4
|
-
* Uses the low-level MCP Server class to avoid TypeScript depth issues
|
|
5
|
-
* with McpServer.registerTool() generics. Registers tools/list and
|
|
6
|
-
* tools/call handlers directly.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
10
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
-
|
|
12
|
-
import type { IExecutionResult, IInteractiveSession } from '@robota-sdk/agent-interface-transport';
|
|
13
|
-
|
|
14
|
-
export interface IAgentMcpOptions {
|
|
15
|
-
/** Name for the MCP server. */
|
|
16
|
-
name: string;
|
|
17
|
-
/** Version string. */
|
|
18
|
-
version: string;
|
|
19
|
-
/** IInteractiveSession to expose. */
|
|
20
|
-
session: IInteractiveSession;
|
|
21
|
-
/** If true, register each system command as a separate MCP tool. Default: true. */
|
|
22
|
-
exposeCommands?: boolean;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Create an MCP server that exposes IInteractiveSession over Model Context Protocol.
|
|
27
|
-
*
|
|
28
|
-
* Usage:
|
|
29
|
-
* ```typescript
|
|
30
|
-
* const server = createAgentMcpServer({
|
|
31
|
-
* name: 'robota-agent',
|
|
32
|
-
* version: '1.0.0',
|
|
33
|
-
* session: interactiveSession,
|
|
34
|
-
* });
|
|
35
|
-
*
|
|
36
|
-
* import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
37
|
-
* await server.connect(new StdioServerTransport());
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export function createAgentMcpServer(options: IAgentMcpOptions): Server {
|
|
41
|
-
const { name, version, session, exposeCommands = true } = options;
|
|
42
|
-
|
|
43
|
-
const server = new Server({ name, version }, { capabilities: { tools: {} } });
|
|
44
|
-
|
|
45
|
-
// Build tool definitions
|
|
46
|
-
const tools: Array<{
|
|
47
|
-
name: string;
|
|
48
|
-
description: string;
|
|
49
|
-
inputSchema: Record<string, unknown>;
|
|
50
|
-
}> = [
|
|
51
|
-
{
|
|
52
|
-
name: 'submit',
|
|
53
|
-
description: 'Submit a prompt to the AI agent and wait for the response',
|
|
54
|
-
inputSchema: {
|
|
55
|
-
type: 'object',
|
|
56
|
-
properties: {
|
|
57
|
-
prompt: { type: 'string', description: 'The prompt to send to the agent' },
|
|
58
|
-
},
|
|
59
|
-
required: ['prompt'],
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
];
|
|
63
|
-
|
|
64
|
-
if (exposeCommands) {
|
|
65
|
-
for (const cmd of session.listCommands()) {
|
|
66
|
-
tools.push({
|
|
67
|
-
name: `command_${cmd.name}`,
|
|
68
|
-
description: cmd.description,
|
|
69
|
-
inputSchema: {
|
|
70
|
-
type: 'object',
|
|
71
|
-
properties: {
|
|
72
|
-
args: { type: 'string', description: 'Command arguments' },
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// tools/list handler
|
|
80
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
81
|
-
tools,
|
|
82
|
-
}));
|
|
83
|
-
|
|
84
|
-
// tools/call handler
|
|
85
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
86
|
-
const { name: toolName, arguments: toolArgs } = request.params;
|
|
87
|
-
|
|
88
|
-
if (toolName === 'submit') {
|
|
89
|
-
const prompt = (toolArgs as Record<string, string>)?.prompt;
|
|
90
|
-
if (!prompt) {
|
|
91
|
-
return {
|
|
92
|
-
content: [{ type: 'text', text: 'Error: prompt is required' }],
|
|
93
|
-
isError: true,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
const result = await waitForCompletion(session, prompt);
|
|
97
|
-
return {
|
|
98
|
-
content: [{ type: 'text', text: result.response }],
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// System commands: command_<name>
|
|
103
|
-
if (toolName.startsWith('command_')) {
|
|
104
|
-
const cmdName = toolName.slice('command_'.length);
|
|
105
|
-
const args = (toolArgs as Record<string, string>)?.args ?? '';
|
|
106
|
-
const result = await session.executeCommand(cmdName, args);
|
|
107
|
-
return {
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: 'text',
|
|
111
|
-
text: result?.message ?? `Unknown command: ${cmdName}`,
|
|
112
|
-
},
|
|
113
|
-
],
|
|
114
|
-
isError: !result,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
content: [{ type: 'text', text: `Unknown tool: ${toolName}` }],
|
|
120
|
-
isError: true,
|
|
121
|
-
};
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
return server;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Submit a prompt and wait for the complete/interrupted/error event.
|
|
129
|
-
*/
|
|
130
|
-
function waitForCompletion(
|
|
131
|
-
session: IInteractiveSession,
|
|
132
|
-
prompt: string,
|
|
133
|
-
): Promise<IExecutionResult> {
|
|
134
|
-
return new Promise((resolve, reject) => {
|
|
135
|
-
const onComplete = (result: IExecutionResult): void => {
|
|
136
|
-
cleanup();
|
|
137
|
-
resolve(result);
|
|
138
|
-
};
|
|
139
|
-
const onInterrupted = (result: IExecutionResult): void => {
|
|
140
|
-
cleanup();
|
|
141
|
-
resolve(result);
|
|
142
|
-
};
|
|
143
|
-
const onError = (error: Error): void => {
|
|
144
|
-
cleanup();
|
|
145
|
-
reject(error);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const cleanup = (): void => {
|
|
149
|
-
session.off('complete', onComplete);
|
|
150
|
-
session.off('interrupted', onInterrupted);
|
|
151
|
-
session.off('error', onError);
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
session.on('complete', onComplete);
|
|
155
|
-
session.on('interrupted', onInterrupted);
|
|
156
|
-
session.on('error', onError);
|
|
157
|
-
|
|
158
|
-
session.submit(prompt).catch((err) => {
|
|
159
|
-
cleanup();
|
|
160
|
-
reject(err instanceof Error ? err : new Error(String(err)));
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
}
|
package/src/mcp/mcp-transport.ts
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ITransportAdapter implementation for MCP transport.
|
|
3
|
-
*
|
|
4
|
-
* Wraps createAgentMcpServer into the unified ITransportAdapter interface
|
|
5
|
-
* while exposing the underlying MCP Server via getServer().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createAgentMcpServer } from './mcp-server.js';
|
|
9
|
-
|
|
10
|
-
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
-
import type { IInteractiveSession, ITransportAdapter } from '@robota-sdk/agent-interface-transport';
|
|
12
|
-
|
|
13
|
-
export interface IMcpTransportOptions {
|
|
14
|
-
/** Name for the MCP server. */
|
|
15
|
-
name: string;
|
|
16
|
-
/** Version string. */
|
|
17
|
-
version: string;
|
|
18
|
-
/** If true, register each system command as a separate MCP tool. Default: true. */
|
|
19
|
-
exposeCommands?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function createMcpTransport(
|
|
23
|
-
options: IMcpTransportOptions,
|
|
24
|
-
): ITransportAdapter<IInteractiveSession> & { getServer(): Server } {
|
|
25
|
-
let session: IInteractiveSession | null = null;
|
|
26
|
-
let server: Server | null = null;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
name: 'mcp',
|
|
30
|
-
attach(s: IInteractiveSession) {
|
|
31
|
-
session = s;
|
|
32
|
-
},
|
|
33
|
-
async start() {
|
|
34
|
-
if (!session) throw new Error('No session attached. Call attach() first.');
|
|
35
|
-
server = createAgentMcpServer({ ...options, session });
|
|
36
|
-
},
|
|
37
|
-
async stop() {
|
|
38
|
-
if (server) {
|
|
39
|
-
await server.close();
|
|
40
|
-
server = null;
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
getServer() {
|
|
44
|
-
if (!server) throw new Error('Transport not started. Call start() first.');
|
|
45
|
-
return server;
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scripted provider fixture tests (CLI-074 TC-01).
|
|
3
|
-
*
|
|
4
|
-
* The fixture must replay declared turns in order, record every request's
|
|
5
|
-
* message array for assertions, and fail fast on script exhaustion — never
|
|
6
|
-
* silently improvise a response.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, expect, it } from 'vitest';
|
|
10
|
-
|
|
11
|
-
import { createScriptedProvider } from '../scripted-provider.js';
|
|
12
|
-
|
|
13
|
-
describe('createScriptedProvider (CLI-074)', () => {
|
|
14
|
-
it('TC-01: replays text turns in order and records requests', async () => {
|
|
15
|
-
const { provider, requests } = createScriptedProvider([
|
|
16
|
-
{ text: 'first answer' },
|
|
17
|
-
{ text: 'second answer' },
|
|
18
|
-
]);
|
|
19
|
-
|
|
20
|
-
const first = await provider.chat([
|
|
21
|
-
{ id: 'u1', role: 'user', content: 'hi', state: 'complete', timestamp: new Date() },
|
|
22
|
-
]);
|
|
23
|
-
const second = await provider.chat([
|
|
24
|
-
{ id: 'u2', role: 'user', content: 'again', state: 'complete', timestamp: new Date() },
|
|
25
|
-
]);
|
|
26
|
-
|
|
27
|
-
expect(first.role).toBe('assistant');
|
|
28
|
-
expect(first.content).toBe('first answer');
|
|
29
|
-
expect(second.content).toBe('second answer');
|
|
30
|
-
expect(requests).toHaveLength(2);
|
|
31
|
-
expect(requests[0]?.map((message) => message.content)).toContain('hi');
|
|
32
|
-
expect(requests[1]?.map((message) => message.content)).toContain('again');
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('TC-01: replays tool_use turns as assistant toolCalls', async () => {
|
|
36
|
-
const { provider } = createScriptedProvider([
|
|
37
|
-
{ toolCalls: [{ name: 'Read', args: { file_path: '/tmp/a.txt' } }] },
|
|
38
|
-
{ text: 'done reading' },
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
const turn = await provider.chat([
|
|
42
|
-
{ id: 'u1', role: 'user', content: 'read it', state: 'complete', timestamp: new Date() },
|
|
43
|
-
]);
|
|
44
|
-
|
|
45
|
-
expect(turn.role).toBe('assistant');
|
|
46
|
-
if (turn.role !== 'assistant') throw new Error('unreachable');
|
|
47
|
-
expect(turn.content).toBeNull();
|
|
48
|
-
expect(turn.toolCalls).toHaveLength(1);
|
|
49
|
-
expect(turn.toolCalls?.[0]?.function.name).toBe('Read');
|
|
50
|
-
expect(JSON.parse(turn.toolCalls?.[0]?.function.arguments ?? '{}')).toEqual({
|
|
51
|
-
file_path: '/tmp/a.txt',
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('TC-01: throws on script exhaustion instead of improvising', async () => {
|
|
56
|
-
const { provider } = createScriptedProvider([{ text: 'only turn' }]);
|
|
57
|
-
|
|
58
|
-
await provider.chat([
|
|
59
|
-
{ id: 'u1', role: 'user', content: 'one', state: 'complete', timestamp: new Date() },
|
|
60
|
-
]);
|
|
61
|
-
|
|
62
|
-
await expect(
|
|
63
|
-
provider.chat([
|
|
64
|
-
{ id: 'u2', role: 'user', content: 'two', state: 'complete', timestamp: new Date() },
|
|
65
|
-
]),
|
|
66
|
-
).rejects.toThrow(/script exhausted/i);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('supportsTools is true and validateConfig passes (agent-loop prerequisites)', () => {
|
|
70
|
-
const { provider } = createScriptedProvider([{ text: 'x' }]);
|
|
71
|
-
expect(provider.supportsTools()).toBe(true);
|
|
72
|
-
});
|
|
73
|
-
});
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deterministic scripted provider for E2E tests (CLI-074).
|
|
3
|
-
*
|
|
4
|
-
* Replays a declared sequence of assistant turns through the REAL agent loop —
|
|
5
|
-
* tool execution, permission gate, session persistence, and output transports all
|
|
6
|
-
* run unmocked. Test-only: exported via the `@robota-sdk/agent-transport/testing`
|
|
7
|
-
* subpath and never imported by runtime code.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { IAIProvider, IRawProviderResponse, TUniversalMessage } from '@robota-sdk/agent-core';
|
|
11
|
-
|
|
12
|
-
/** One scripted assistant turn: plain text or tool invocations. */
|
|
13
|
-
export type TScriptedTurn =
|
|
14
|
-
| { text: string }
|
|
15
|
-
| { toolCalls: ReadonlyArray<{ name: string; args: Record<string, unknown> }> };
|
|
16
|
-
|
|
17
|
-
export interface IScriptedProvider {
|
|
18
|
-
provider: IAIProvider;
|
|
19
|
-
/** Message arrays of every chat() call, in order, for request assertions. */
|
|
20
|
-
requests: TUniversalMessage[][];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createScriptedProvider(turns: readonly TScriptedTurn[]): IScriptedProvider {
|
|
24
|
-
const requests: TUniversalMessage[][] = [];
|
|
25
|
-
let cursor = 0;
|
|
26
|
-
|
|
27
|
-
const provider: IAIProvider = {
|
|
28
|
-
name: 'scripted-test-provider',
|
|
29
|
-
version: 'test',
|
|
30
|
-
async chat(messages: TUniversalMessage[]): Promise<TUniversalMessage> {
|
|
31
|
-
requests.push([...messages]);
|
|
32
|
-
const turn = turns[cursor];
|
|
33
|
-
if (turn === undefined) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
`Scripted provider: script exhausted at call ${cursor + 1} (script declares ${turns.length} turn(s)) — extend the script instead of relying on improvised responses`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
cursor += 1;
|
|
39
|
-
if ('text' in turn) {
|
|
40
|
-
return {
|
|
41
|
-
id: `scripted-${cursor}`,
|
|
42
|
-
role: 'assistant',
|
|
43
|
-
content: turn.text,
|
|
44
|
-
state: 'complete',
|
|
45
|
-
timestamp: new Date(),
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
return {
|
|
49
|
-
id: `scripted-${cursor}`,
|
|
50
|
-
role: 'assistant',
|
|
51
|
-
content: null,
|
|
52
|
-
state: 'complete',
|
|
53
|
-
timestamp: new Date(),
|
|
54
|
-
toolCalls: turn.toolCalls.map((call, index) => ({
|
|
55
|
-
id: `scripted-call-${cursor}-${index}`,
|
|
56
|
-
type: 'function' as const,
|
|
57
|
-
function: { name: call.name, arguments: JSON.stringify(call.args) },
|
|
58
|
-
})),
|
|
59
|
-
};
|
|
60
|
-
},
|
|
61
|
-
async generateResponse(): Promise<IRawProviderResponse> {
|
|
62
|
-
return { content: 'scripted provider does not implement raw responses' };
|
|
63
|
-
},
|
|
64
|
-
supportsTools(): boolean {
|
|
65
|
-
return true;
|
|
66
|
-
},
|
|
67
|
-
validateConfig(): boolean {
|
|
68
|
-
return true;
|
|
69
|
-
},
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
return { provider, requests };
|
|
73
|
-
}
|