@robota-sdk/agent-transport 3.0.0-beta.74 → 3.0.0-beta.76
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/README.md +10 -10
- 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-OnpVk4-k.cjs +15 -0
- package/dist/node/{headless-D02zUEGh.js → headless-mRYilLfC.js} +2 -2
- package/dist/node/{headless-D02zUEGh.js.map → headless-mRYilLfC.js.map} +1 -1
- package/dist/node/{index-DE3-dHqw.d.ts → index-CYl7ksS6.d.ts} +12 -2
- package/dist/node/{index-DE3-dHqw.d.ts.map → index-CYl7ksS6.d.ts.map} +1 -1
- package/dist/node/{index-WKTgvhlg.d.ts → index-E8Gx4-lc.d.ts} +12 -2
- package/dist/node/{index-WKTgvhlg.d.ts.map → index-E8Gx4-lc.d.ts.map} +1 -1
- package/dist/node/index.cjs +1 -1
- package/dist/node/index.d.ts +2 -7
- 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/package.json +7 -75
- package/src/headless/HeadlessInteractionChannel.ts +21 -1
- package/src/index.ts +1 -5
- package/src/transport-registry.ts +0 -9
- package/dist/node/headless-BeHAOlIM.cjs +0 -15
- 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-BQLN_Lc9.d.ts +0 -78
- package/dist/node/index-BQLN_Lc9.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-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-IvYaYY6v.d.ts +0 -78
- package/dist/node/index-IvYaYY6v.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/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-Btb1q88j.js +0 -25
- package/dist/node/tui-Btb1q88j.js.map +0 -1
- package/dist/node/tui-SbUT7Zlt.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/tui/App.tsx +0 -488
- 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 -70
- package/src/tui/SlashAutocomplete.tsx +0 -110
- package/src/tui/StatusBar.tsx +0 -209
- 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 -483
- 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 -158
- 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 -117
- 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
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render } from 'ink-testing-library';
|
|
3
|
-
import { describe, it, expect } from 'vitest';
|
|
4
|
-
|
|
5
|
-
import CommandConfirm from '../CommandConfirm.js';
|
|
6
|
-
|
|
7
|
-
import type { ITuiConfirmInteraction } from '../../command-interaction.js';
|
|
8
|
-
|
|
9
|
-
const delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
|
|
10
|
-
|
|
11
|
-
function makeInteraction(message: string): ITuiConfirmInteraction {
|
|
12
|
-
return { onMissingArgs: 'confirm', message };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('CommandConfirm', () => {
|
|
16
|
-
it('renders the message text', () => {
|
|
17
|
-
const { lastFrame } = render(
|
|
18
|
-
<CommandConfirm
|
|
19
|
-
commandName="exit"
|
|
20
|
-
interaction={makeInteraction('Exit the session?')}
|
|
21
|
-
onConfirm={() => {}}
|
|
22
|
-
onCancel={() => {}}
|
|
23
|
-
/>,
|
|
24
|
-
);
|
|
25
|
-
expect(lastFrame()).toContain('Exit the session?');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('calls onConfirm when y is pressed', () => {
|
|
29
|
-
let confirmed = false;
|
|
30
|
-
const { stdin } = render(
|
|
31
|
-
<CommandConfirm
|
|
32
|
-
commandName="exit"
|
|
33
|
-
interaction={makeInteraction('Exit?')}
|
|
34
|
-
onConfirm={() => {
|
|
35
|
-
confirmed = true;
|
|
36
|
-
}}
|
|
37
|
-
onCancel={() => {}}
|
|
38
|
-
/>,
|
|
39
|
-
);
|
|
40
|
-
stdin.write('y');
|
|
41
|
-
expect(confirmed).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('calls onConfirm when Y is pressed', () => {
|
|
45
|
-
let confirmed = false;
|
|
46
|
-
const { stdin } = render(
|
|
47
|
-
<CommandConfirm
|
|
48
|
-
commandName="exit"
|
|
49
|
-
interaction={makeInteraction('Exit?')}
|
|
50
|
-
onConfirm={() => {
|
|
51
|
-
confirmed = true;
|
|
52
|
-
}}
|
|
53
|
-
onCancel={() => {}}
|
|
54
|
-
/>,
|
|
55
|
-
);
|
|
56
|
-
stdin.write('Y');
|
|
57
|
-
expect(confirmed).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('calls onConfirm on Enter', () => {
|
|
61
|
-
let confirmed = false;
|
|
62
|
-
const { stdin } = render(
|
|
63
|
-
<CommandConfirm
|
|
64
|
-
commandName="exit"
|
|
65
|
-
interaction={makeInteraction('Exit?')}
|
|
66
|
-
onConfirm={() => {
|
|
67
|
-
confirmed = true;
|
|
68
|
-
}}
|
|
69
|
-
onCancel={() => {}}
|
|
70
|
-
/>,
|
|
71
|
-
);
|
|
72
|
-
stdin.write('\r');
|
|
73
|
-
expect(confirmed).toBe(true);
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('calls onCancel when n is pressed', () => {
|
|
77
|
-
let cancelled = false;
|
|
78
|
-
const { stdin } = render(
|
|
79
|
-
<CommandConfirm
|
|
80
|
-
commandName="exit"
|
|
81
|
-
interaction={makeInteraction('Exit?')}
|
|
82
|
-
onConfirm={() => {}}
|
|
83
|
-
onCancel={() => {
|
|
84
|
-
cancelled = true;
|
|
85
|
-
}}
|
|
86
|
-
/>,
|
|
87
|
-
);
|
|
88
|
-
stdin.write('n');
|
|
89
|
-
expect(cancelled).toBe(true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('calls onCancel when N is pressed', () => {
|
|
93
|
-
let cancelled = false;
|
|
94
|
-
const { stdin } = render(
|
|
95
|
-
<CommandConfirm
|
|
96
|
-
commandName="exit"
|
|
97
|
-
interaction={makeInteraction('Exit?')}
|
|
98
|
-
onConfirm={() => {}}
|
|
99
|
-
onCancel={() => {
|
|
100
|
-
cancelled = true;
|
|
101
|
-
}}
|
|
102
|
-
/>,
|
|
103
|
-
);
|
|
104
|
-
stdin.write('N');
|
|
105
|
-
expect(cancelled).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('calls onCancel on Escape', async () => {
|
|
109
|
-
let cancelled = false;
|
|
110
|
-
const { stdin } = render(
|
|
111
|
-
<CommandConfirm
|
|
112
|
-
commandName="exit"
|
|
113
|
-
interaction={makeInteraction('Exit?')}
|
|
114
|
-
onConfirm={() => {}}
|
|
115
|
-
onCancel={() => {
|
|
116
|
-
cancelled = true;
|
|
117
|
-
}}
|
|
118
|
-
/>,
|
|
119
|
-
);
|
|
120
|
-
stdin.write('\x1b');
|
|
121
|
-
await delay(50);
|
|
122
|
-
expect(cancelled).toBe(true);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render } from 'ink-testing-library';
|
|
3
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
-
|
|
5
|
-
import CommandPicker from '../CommandPicker.js';
|
|
6
|
-
|
|
7
|
-
import type { ITuiPickerInteraction, ITuiPickerItem } from '../../command-interaction.js';
|
|
8
|
-
|
|
9
|
-
const delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));
|
|
10
|
-
|
|
11
|
-
function makeInteraction(items: ITuiPickerItem[]): ITuiPickerInteraction {
|
|
12
|
-
return {
|
|
13
|
-
onMissingArgs: 'picker',
|
|
14
|
-
getItems: () => items,
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const ITEMS: ITuiPickerItem[] = [
|
|
19
|
-
{ label: 'plan', value: 'plan', description: 'Plan mode' },
|
|
20
|
-
{ label: 'default', value: 'default', description: 'Default mode' },
|
|
21
|
-
{ label: 'auto', value: 'auto' },
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
describe('CommandPicker', () => {
|
|
25
|
-
it('renders all item labels', () => {
|
|
26
|
-
const { lastFrame } = render(
|
|
27
|
-
<CommandPicker
|
|
28
|
-
commandName="mode"
|
|
29
|
-
interaction={makeInteraction(ITEMS)}
|
|
30
|
-
onSelect={() => {}}
|
|
31
|
-
onCancel={() => {}}
|
|
32
|
-
/>,
|
|
33
|
-
);
|
|
34
|
-
const frame = lastFrame()!;
|
|
35
|
-
expect(frame).toContain('plan');
|
|
36
|
-
expect(frame).toContain('default');
|
|
37
|
-
expect(frame).toContain('auto');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('highlights first item by default', () => {
|
|
41
|
-
const { lastFrame } = render(
|
|
42
|
-
<CommandPicker
|
|
43
|
-
commandName="mode"
|
|
44
|
-
interaction={makeInteraction(ITEMS)}
|
|
45
|
-
onSelect={() => {}}
|
|
46
|
-
onCancel={() => {}}
|
|
47
|
-
/>,
|
|
48
|
-
);
|
|
49
|
-
const frame = lastFrame()!;
|
|
50
|
-
expect(frame).toContain('▸ plan');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('calls onSelect with first item on Enter', () => {
|
|
54
|
-
const onSelect = vi.fn();
|
|
55
|
-
const { stdin } = render(
|
|
56
|
-
<CommandPicker
|
|
57
|
-
commandName="mode"
|
|
58
|
-
interaction={makeInteraction(ITEMS)}
|
|
59
|
-
onSelect={onSelect}
|
|
60
|
-
onCancel={() => {}}
|
|
61
|
-
/>,
|
|
62
|
-
);
|
|
63
|
-
stdin.write('\r');
|
|
64
|
-
expect(onSelect).toHaveBeenCalledWith(ITEMS[0]);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('moves highlight down on arrow-down then selects on Enter', async () => {
|
|
68
|
-
let selected: ITuiPickerItem | undefined;
|
|
69
|
-
const { stdin } = render(
|
|
70
|
-
<CommandPicker
|
|
71
|
-
commandName="mode"
|
|
72
|
-
interaction={makeInteraction(ITEMS)}
|
|
73
|
-
onSelect={(item) => {
|
|
74
|
-
selected = item;
|
|
75
|
-
}}
|
|
76
|
-
onCancel={() => {}}
|
|
77
|
-
/>,
|
|
78
|
-
);
|
|
79
|
-
stdin.write('\x1B[B'); // ↓
|
|
80
|
-
await delay(50);
|
|
81
|
-
stdin.write('\r');
|
|
82
|
-
expect(selected).toEqual(ITEMS[1]);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('calls onCancel on Escape', async () => {
|
|
86
|
-
let cancelled = false;
|
|
87
|
-
const { stdin } = render(
|
|
88
|
-
<CommandPicker
|
|
89
|
-
commandName="mode"
|
|
90
|
-
interaction={makeInteraction(ITEMS)}
|
|
91
|
-
onSelect={() => {}}
|
|
92
|
-
onCancel={() => {
|
|
93
|
-
cancelled = true;
|
|
94
|
-
}}
|
|
95
|
-
/>,
|
|
96
|
-
);
|
|
97
|
-
stdin.write('\x1b');
|
|
98
|
-
await delay(50);
|
|
99
|
-
expect(cancelled).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('calls onCancel on q', () => {
|
|
103
|
-
const onCancel = vi.fn();
|
|
104
|
-
const { stdin } = render(
|
|
105
|
-
<CommandPicker
|
|
106
|
-
commandName="mode"
|
|
107
|
-
interaction={makeInteraction(ITEMS)}
|
|
108
|
-
onSelect={() => {}}
|
|
109
|
-
onCancel={onCancel}
|
|
110
|
-
/>,
|
|
111
|
-
);
|
|
112
|
-
stdin.write('q');
|
|
113
|
-
expect(onCancel).toHaveBeenCalled();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('wraps highlight from last to first on arrow-down at bottom', async () => {
|
|
117
|
-
let selected: ITuiPickerItem | undefined;
|
|
118
|
-
const { stdin } = render(
|
|
119
|
-
<CommandPicker
|
|
120
|
-
commandName="mode"
|
|
121
|
-
interaction={makeInteraction(ITEMS)}
|
|
122
|
-
onSelect={(item) => {
|
|
123
|
-
selected = item;
|
|
124
|
-
}}
|
|
125
|
-
onCancel={() => {}}
|
|
126
|
-
/>,
|
|
127
|
-
);
|
|
128
|
-
// Navigate to last item (index 2)
|
|
129
|
-
stdin.write('\x1B[B'); // ↓ to index 1
|
|
130
|
-
await delay(50);
|
|
131
|
-
stdin.write('\x1B[B'); // ↓ to index 2
|
|
132
|
-
await delay(50);
|
|
133
|
-
stdin.write('\x1B[B'); // ↓ wraps to index 0
|
|
134
|
-
await delay(50);
|
|
135
|
-
stdin.write('\r');
|
|
136
|
-
expect(selected).toEqual(ITEMS[0]);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Screen-specific selection handlers for PluginTUI.
|
|
3
|
-
* Extracted to keep PluginTUI.tsx under 300 lines.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { IMenuSelectItem } from './MenuSelect.js';
|
|
7
|
-
import type { ICommandPluginAdapter } from '@robota-sdk/agent-interface-transport';
|
|
8
|
-
|
|
9
|
-
interface IConfirmState {
|
|
10
|
-
message: string;
|
|
11
|
-
onConfirm: () => void;
|
|
12
|
-
onCancel: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface IMenuContext {
|
|
16
|
-
marketplace?: string;
|
|
17
|
-
pluginId?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface INavActions {
|
|
21
|
-
push: (state: { screen: string; context?: IMenuContext }) => void;
|
|
22
|
-
pop: () => void;
|
|
23
|
-
popN: (n: number) => void;
|
|
24
|
-
notify: (content: string) => void;
|
|
25
|
-
setConfirm: (state: IConfirmState | undefined) => void;
|
|
26
|
-
refresh: () => void;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function handleMainSelect(value: string, nav: Pick<INavActions, 'push'>): void {
|
|
30
|
-
if (value === 'marketplace') {
|
|
31
|
-
nav.push({ screen: 'marketplace-list' });
|
|
32
|
-
} else if (value === 'installed') {
|
|
33
|
-
nav.push({ screen: 'installed-list' });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function handleMarketplaceListSelect(value: string, nav: Pick<INavActions, 'push'>): void {
|
|
38
|
-
if (value === '__add__') {
|
|
39
|
-
nav.push({ screen: 'marketplace-add' });
|
|
40
|
-
} else {
|
|
41
|
-
nav.push({ screen: 'marketplace-action', context: { marketplace: value } });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function handleMarketplaceActionSelect(
|
|
46
|
-
value: string,
|
|
47
|
-
marketplace: string,
|
|
48
|
-
callbacks: ICommandPluginAdapter,
|
|
49
|
-
nav: Pick<INavActions, 'push' | 'pop' | 'popN' | 'notify' | 'setConfirm'>,
|
|
50
|
-
): void {
|
|
51
|
-
if (value === 'browse') {
|
|
52
|
-
nav.push({ screen: 'marketplace-browse', context: { marketplace } });
|
|
53
|
-
} else if (value === 'update') {
|
|
54
|
-
callbacks
|
|
55
|
-
.marketplaceUpdate(marketplace)
|
|
56
|
-
.then(() => {
|
|
57
|
-
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
58
|
-
nav.pop();
|
|
59
|
-
})
|
|
60
|
-
.catch((err) => {
|
|
61
|
-
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
62
|
-
});
|
|
63
|
-
} else if (value === 'remove') {
|
|
64
|
-
nav.setConfirm({
|
|
65
|
-
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
66
|
-
onConfirm: () => {
|
|
67
|
-
nav.setConfirm(undefined);
|
|
68
|
-
callbacks
|
|
69
|
-
.marketplaceRemove(marketplace)
|
|
70
|
-
.then(() => {
|
|
71
|
-
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
72
|
-
nav.popN(2);
|
|
73
|
-
})
|
|
74
|
-
.catch((err) => {
|
|
75
|
-
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
76
|
-
});
|
|
77
|
-
},
|
|
78
|
-
onCancel: () => nav.setConfirm(undefined),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function handleMarketplaceBrowseSelect(
|
|
84
|
-
value: string,
|
|
85
|
-
marketplace: string,
|
|
86
|
-
items: IMenuSelectItem[],
|
|
87
|
-
nav: Pick<INavActions, 'push'>,
|
|
88
|
-
): void {
|
|
89
|
-
const fullId = `${value}@${marketplace}`;
|
|
90
|
-
const item = items.find((i) => i.value === value);
|
|
91
|
-
if (item?.hint === 'installed') {
|
|
92
|
-
nav.push({ screen: 'installed-action', context: { pluginId: fullId } });
|
|
93
|
-
} else {
|
|
94
|
-
nav.push({ screen: 'marketplace-install-scope', context: { marketplace, pluginId: fullId } });
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function handleInstallScopeSelect(
|
|
99
|
-
value: string,
|
|
100
|
-
pluginId: string,
|
|
101
|
-
callbacks: ICommandPluginAdapter,
|
|
102
|
-
nav: Pick<INavActions, 'popN' | 'notify'>,
|
|
103
|
-
): void {
|
|
104
|
-
const scope = value as 'user' | 'project';
|
|
105
|
-
callbacks
|
|
106
|
-
.install(pluginId, scope)
|
|
107
|
-
.then(() => {
|
|
108
|
-
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
109
|
-
nav.popN(2);
|
|
110
|
-
})
|
|
111
|
-
.catch((err) => {
|
|
112
|
-
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function handleInstalledListSelect(
|
|
117
|
-
value: string,
|
|
118
|
-
callbacks: ICommandPluginAdapter,
|
|
119
|
-
nav: Pick<INavActions, 'notify' | 'setConfirm' | 'refresh'>,
|
|
120
|
-
): void {
|
|
121
|
-
nav.setConfirm({
|
|
122
|
-
message: `Uninstall plugin "${value}"?`,
|
|
123
|
-
onConfirm: () => {
|
|
124
|
-
nav.setConfirm(undefined);
|
|
125
|
-
callbacks
|
|
126
|
-
.uninstall(value)
|
|
127
|
-
.then(() => {
|
|
128
|
-
nav.notify(`Uninstalled plugin "${value}".`);
|
|
129
|
-
nav.refresh();
|
|
130
|
-
})
|
|
131
|
-
.catch((err) => {
|
|
132
|
-
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
-
});
|
|
134
|
-
},
|
|
135
|
-
onCancel: () => nav.setConfirm(undefined),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function handleInstalledActionSelect(
|
|
140
|
-
value: string,
|
|
141
|
-
pluginId: string,
|
|
142
|
-
callbacks: ICommandPluginAdapter,
|
|
143
|
-
nav: Pick<INavActions, 'popN' | 'notify' | 'setConfirm'>,
|
|
144
|
-
): void {
|
|
145
|
-
if (value === 'uninstall') {
|
|
146
|
-
nav.setConfirm({
|
|
147
|
-
message: `Uninstall plugin "${pluginId}"?`,
|
|
148
|
-
onConfirm: () => {
|
|
149
|
-
nav.setConfirm(undefined);
|
|
150
|
-
callbacks
|
|
151
|
-
.uninstall(pluginId)
|
|
152
|
-
.then(() => {
|
|
153
|
-
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
154
|
-
nav.popN(2);
|
|
155
|
-
})
|
|
156
|
-
.catch((err) => {
|
|
157
|
-
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
158
|
-
});
|
|
159
|
-
},
|
|
160
|
-
onCancel: () => nav.setConfirm(undefined),
|
|
161
|
-
});
|
|
162
|
-
}
|
|
163
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { marked } from 'marked';
|
|
2
|
-
// @ts-expect-error — marked-terminal has no type declarations
|
|
3
|
-
import TerminalRenderer from 'marked-terminal';
|
|
4
|
-
|
|
5
|
-
import type { Renderer } from 'marked';
|
|
6
|
-
|
|
7
|
-
const ANSI_LIGHT_RED = '\u001b[38;5;210m';
|
|
8
|
-
const ANSI_LIGHT_GREEN = '\u001b[38;5;120m';
|
|
9
|
-
const ANSI_CYAN = '\u001b[36m';
|
|
10
|
-
const ANSI_DIM = '\u001b[2m';
|
|
11
|
-
const ANSI_DARK_RED_BACKGROUND = '\u001b[48;5;52m';
|
|
12
|
-
const ANSI_DARK_GREEN_BACKGROUND = '\u001b[48;5;22m';
|
|
13
|
-
const ANSI_RESET = '\u001b[0m';
|
|
14
|
-
const CODE_BLOCK_INDENT = ' ';
|
|
15
|
-
const ZERO_COLOR = '0';
|
|
16
|
-
|
|
17
|
-
interface IRenderMarkdownOptions {
|
|
18
|
-
color?: boolean;
|
|
19
|
-
codeBlockWidth?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface ITerminalRendererOptions {
|
|
23
|
-
code?: (text: string) => string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface IHighlightOptions {
|
|
27
|
-
ignoreIllegals?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type TTerminalRendererConstructor = new (
|
|
31
|
-
options?: ITerminalRendererOptions,
|
|
32
|
-
highlightOptions?: IHighlightOptions,
|
|
33
|
-
) => Renderer;
|
|
34
|
-
|
|
35
|
-
const TerminalRendererConstructor = TerminalRenderer as TTerminalRendererConstructor;
|
|
36
|
-
|
|
37
|
-
function shouldUseColor(option: boolean | undefined): boolean {
|
|
38
|
-
if (option !== undefined) {
|
|
39
|
-
return option;
|
|
40
|
-
}
|
|
41
|
-
if (process.env.NO_COLOR || process.env.FORCE_COLOR === ZERO_COLOR) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
if (process.env.FORCE_COLOR) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
return Boolean(process.stdout.isTTY);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function isDiffLanguage(language: string | undefined): boolean {
|
|
51
|
-
return language?.trim().toLowerCase() === 'diff';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function styleAddedOrRemovedDiffRow(line: string, rowWidth: number, color: boolean): string {
|
|
55
|
-
const row = `${CODE_BLOCK_INDENT}${line}`.padEnd(rowWidth);
|
|
56
|
-
if (!color) {
|
|
57
|
-
return row.trimEnd();
|
|
58
|
-
}
|
|
59
|
-
if (line.startsWith('+')) {
|
|
60
|
-
return `${ANSI_DARK_GREEN_BACKGROUND}${ANSI_LIGHT_GREEN}${row}${ANSI_RESET}`;
|
|
61
|
-
}
|
|
62
|
-
if (line.startsWith('-')) {
|
|
63
|
-
return `${ANSI_DARK_RED_BACKGROUND}${ANSI_LIGHT_RED}${row}${ANSI_RESET}`;
|
|
64
|
-
}
|
|
65
|
-
return row.trimEnd();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function colorizeDiffLine(line: string, color: boolean, rowWidth: number): string {
|
|
69
|
-
if (line.startsWith('+') || line.startsWith('-')) {
|
|
70
|
-
return styleAddedOrRemovedDiffRow(line, rowWidth, color);
|
|
71
|
-
}
|
|
72
|
-
const row = `${CODE_BLOCK_INDENT}${line}`;
|
|
73
|
-
if (!color) {
|
|
74
|
-
return row;
|
|
75
|
-
}
|
|
76
|
-
if (line.startsWith('@@')) {
|
|
77
|
-
return `${ANSI_CYAN}${row}${ANSI_RESET}`;
|
|
78
|
-
}
|
|
79
|
-
if (line.startsWith('diff ') || line.startsWith('index ')) {
|
|
80
|
-
return `${ANSI_DIM}${row}${ANSI_RESET}`;
|
|
81
|
-
}
|
|
82
|
-
return row;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function resolveDiffRowWidth(lines: readonly string[], requestedWidth: number | undefined): number {
|
|
86
|
-
const minimumWidth = lines.reduce(
|
|
87
|
-
(maxWidth, line) => Math.max(maxWidth, CODE_BLOCK_INDENT.length + line.length),
|
|
88
|
-
0,
|
|
89
|
-
);
|
|
90
|
-
if (requestedWidth === undefined) {
|
|
91
|
-
return minimumWidth;
|
|
92
|
-
}
|
|
93
|
-
return Math.max(minimumWidth, requestedWidth);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function renderDiffCodeBlock(
|
|
97
|
-
code: string,
|
|
98
|
-
color: boolean,
|
|
99
|
-
codeBlockWidth: number | undefined,
|
|
100
|
-
): string {
|
|
101
|
-
const lines = code.split('\n');
|
|
102
|
-
const rowWidth = resolveDiffRowWidth(lines, codeBlockWidth);
|
|
103
|
-
const body = lines.map((line) => colorizeDiffLine(line, color, rowWidth)).join('\n');
|
|
104
|
-
return `${body}\n\n`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function createTerminalRenderer(color: boolean, codeBlockWidth: number | undefined): Renderer {
|
|
108
|
-
const renderer = new TerminalRendererConstructor(undefined, { ignoreIllegals: true });
|
|
109
|
-
const renderCode = renderer.code.bind(renderer);
|
|
110
|
-
|
|
111
|
-
renderer.code = (code: string, language: string | undefined, escaped: boolean): string => {
|
|
112
|
-
if (isDiffLanguage(language)) {
|
|
113
|
-
return renderDiffCodeBlock(code, color, codeBlockWidth);
|
|
114
|
-
}
|
|
115
|
-
return renderCode(code, language, escaped);
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
return renderer;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Render markdown to a terminal-formatted string with colors, bold, etc.
|
|
123
|
-
* Returns the rendered string (may include ANSI escape codes).
|
|
124
|
-
*/
|
|
125
|
-
export function renderMarkdown(md: string, options: IRenderMarkdownOptions = {}): string {
|
|
126
|
-
const result = marked.parse(md, {
|
|
127
|
-
renderer: createTerminalRenderer(shouldUseColor(options.color), options.codeBlockWidth),
|
|
128
|
-
});
|
|
129
|
-
return typeof result === 'string' ? result.trimEnd() : md;
|
|
130
|
-
}
|
package/src/tui/render.tsx
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Ink render entry point.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { render } from 'ink';
|
|
6
|
-
import React from 'react';
|
|
7
|
-
|
|
8
|
-
import App from './App.js';
|
|
9
|
-
import { TuiInteractionChannel } from './TuiInteractionChannel.js';
|
|
10
|
-
|
|
11
|
-
import type { ITuiCliAdapter } from './tui-cli-adapter.js';
|
|
12
|
-
import type { IAIProvider } from '@robota-sdk/agent-core';
|
|
13
|
-
import type { TPermissionMode } from '@robota-sdk/agent-core';
|
|
14
|
-
import type {
|
|
15
|
-
IBackgroundTaskRunner,
|
|
16
|
-
ICommandHostAdapters,
|
|
17
|
-
ICommandModule,
|
|
18
|
-
TSubagentRunnerFactory,
|
|
19
|
-
TShellExecFn,
|
|
20
|
-
CommandRegistry,
|
|
21
|
-
} from '@robota-sdk/agent-framework';
|
|
22
|
-
import type {
|
|
23
|
-
IInteractiveSession,
|
|
24
|
-
IInteractiveSessionStore,
|
|
25
|
-
ITransportRegistryView,
|
|
26
|
-
} from '@robota-sdk/agent-interface-transport';
|
|
27
|
-
|
|
28
|
-
export interface IRenderOptions {
|
|
29
|
-
cwd: string;
|
|
30
|
-
provider: IAIProvider;
|
|
31
|
-
providerOverride?: string | undefined;
|
|
32
|
-
providerType?: string | undefined;
|
|
33
|
-
modelId?: string;
|
|
34
|
-
language?: string;
|
|
35
|
-
permissionMode?: TPermissionMode;
|
|
36
|
-
maxTurns?: number;
|
|
37
|
-
allowedTools?: string[];
|
|
38
|
-
deniedTools?: string[];
|
|
39
|
-
version?: string;
|
|
40
|
-
sessionStore?: IInteractiveSessionStore;
|
|
41
|
-
resumeSessionId?: string;
|
|
42
|
-
showSessionPickerOnStart?: boolean;
|
|
43
|
-
forkSession?: boolean;
|
|
44
|
-
sessionName?: string;
|
|
45
|
-
backgroundTaskRunners?: IBackgroundTaskRunner[];
|
|
46
|
-
subagentRunnerFactory?: TSubagentRunnerFactory;
|
|
47
|
-
commandModules?: readonly ICommandModule[];
|
|
48
|
-
commandHostAdapters?: ICommandHostAdapters;
|
|
49
|
-
shellExec?: TShellExecFn;
|
|
50
|
-
startupUpdateNotice?: Promise<string | undefined>;
|
|
51
|
-
transportRegistry?: ITransportRegistryView<IInteractiveSession>;
|
|
52
|
-
cliAdapter: ITuiCliAdapter;
|
|
53
|
-
reloadPluginCommandSource?: (registry: CommandRegistry) => void;
|
|
54
|
-
agentName?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Map render options to TuiInteractionChannel constructor options. */
|
|
58
|
-
export function toChannelOptions(
|
|
59
|
-
options: IRenderOptions,
|
|
60
|
-
resumeSessionId?: string,
|
|
61
|
-
): ConstructorParameters<typeof TuiInteractionChannel>[0] {
|
|
62
|
-
return {
|
|
63
|
-
cwd: options.cwd,
|
|
64
|
-
provider: options.provider,
|
|
65
|
-
permissionMode: options.permissionMode,
|
|
66
|
-
maxTurns: options.maxTurns,
|
|
67
|
-
allowedTools: options.allowedTools,
|
|
68
|
-
deniedTools: options.deniedTools,
|
|
69
|
-
sessionStore: options.sessionStore,
|
|
70
|
-
resumeSessionId,
|
|
71
|
-
forkSession: options.forkSession,
|
|
72
|
-
sessionName: options.sessionName,
|
|
73
|
-
backgroundTaskRunners: options.backgroundTaskRunners,
|
|
74
|
-
subagentRunnerFactory: options.subagentRunnerFactory,
|
|
75
|
-
commandModules: options.commandModules,
|
|
76
|
-
commandHostAdapters: options.commandHostAdapters,
|
|
77
|
-
shellExec: options.shellExec,
|
|
78
|
-
transportRegistry: options.transportRegistry,
|
|
79
|
-
language: options.language,
|
|
80
|
-
reloadPluginCommandSource: options.reloadPluginCommandSource,
|
|
81
|
-
agentName: options.agentName,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function renderApp(options: IRenderOptions): Promise<void> {
|
|
86
|
-
process.on('unhandledRejection', (reason) => {
|
|
87
|
-
process.stderr.write(`\n[UNHANDLED REJECTION] ${reason}\n`);
|
|
88
|
-
if (reason instanceof Error) {
|
|
89
|
-
process.stderr.write(`${reason.stack}\n`);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
// Single-owner lifecycle (CLI-B12): render.tsx supplies only the factory;
|
|
94
|
-
// App creates, replaces, and stops channels exclusively through React state.
|
|
95
|
-
const createChannel = (resumeSessionId?: string): TuiInteractionChannel =>
|
|
96
|
-
new TuiInteractionChannel(toChannelOptions(options, resumeSessionId));
|
|
97
|
-
|
|
98
|
-
const instance = render(
|
|
99
|
-
<App
|
|
100
|
-
cwd={options.cwd}
|
|
101
|
-
createChannel={createChannel}
|
|
102
|
-
providerOverride={options.providerOverride}
|
|
103
|
-
providerType={options.providerType}
|
|
104
|
-
modelId={options.modelId}
|
|
105
|
-
permissionMode={options.permissionMode}
|
|
106
|
-
version={options.version}
|
|
107
|
-
sessionStore={options.sessionStore}
|
|
108
|
-
resumeSessionId={options.resumeSessionId}
|
|
109
|
-
showSessionPickerOnStart={options.showSessionPickerOnStart}
|
|
110
|
-
startupUpdateNotice={options.startupUpdateNotice}
|
|
111
|
-
transportRegistry={options.transportRegistry}
|
|
112
|
-
cliAdapter={options.cliAdapter}
|
|
113
|
-
/>,
|
|
114
|
-
{ exitOnCtrlC: false },
|
|
115
|
-
);
|
|
116
|
-
await instance.waitUntilExit();
|
|
117
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { createSystemMessage, createUserMessage } from '@robota-sdk/agent-core';
|
|
2
|
-
|
|
3
|
-
import type { IAIProvider } from '@robota-sdk/agent-core';
|
|
4
|
-
|
|
5
|
-
const SYSTEM_PROMPT =
|
|
6
|
-
'You generate short session titles. Respond with ONLY a 3-5 word lowercase-hyphenated title (e.g. refactor-auth-middleware). No explanation, no punctuation, no quotes.';
|
|
7
|
-
|
|
8
|
-
const MAX_FIRST_MESSAGE_CHARS = 200;
|
|
9
|
-
|
|
10
|
-
function sanitizeName(raw: string): string {
|
|
11
|
-
return raw
|
|
12
|
-
.toLowerCase()
|
|
13
|
-
.replace(/[^a-z0-9\s-]/g, '')
|
|
14
|
-
.trim()
|
|
15
|
-
.replace(/\s+/g, '-')
|
|
16
|
-
.replace(/-+/g, '-')
|
|
17
|
-
.slice(0, 60);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export async function generateSessionName(
|
|
21
|
-
provider: IAIProvider,
|
|
22
|
-
firstMessage: string,
|
|
23
|
-
): Promise<string> {
|
|
24
|
-
const truncated = firstMessage.slice(0, MAX_FIRST_MESSAGE_CHARS);
|
|
25
|
-
const response = await provider.chat(
|
|
26
|
-
[createSystemMessage(SYSTEM_PROMPT), createUserMessage(truncated)],
|
|
27
|
-
{ maxTokens: 20 },
|
|
28
|
-
);
|
|
29
|
-
const raw = typeof response.content === 'string' ? response.content : '';
|
|
30
|
-
const name = sanitizeName(raw);
|
|
31
|
-
if (!name || name.length < 3) return sanitizeName(firstMessage);
|
|
32
|
-
return name;
|
|
33
|
-
}
|