@roj-ai/cli 0.0.2

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 (49) hide show
  1. package/dist/client.d.ts +8 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +28 -0
  4. package/dist/commands/answer.d.ts +3 -0
  5. package/dist/commands/answer.d.ts.map +1 -0
  6. package/dist/commands/answer.js +15 -0
  7. package/dist/commands/debug-format.d.ts +12 -0
  8. package/dist/commands/debug-format.d.ts.map +1 -0
  9. package/dist/commands/debug-format.js +262 -0
  10. package/dist/commands/debug.d.ts +14 -0
  11. package/dist/commands/debug.d.ts.map +1 -0
  12. package/dist/commands/debug.js +187 -0
  13. package/dist/commands/messages.d.ts +4 -0
  14. package/dist/commands/messages.d.ts.map +1 -0
  15. package/dist/commands/messages.js +77 -0
  16. package/dist/commands/presets.d.ts +3 -0
  17. package/dist/commands/presets.d.ts.map +1 -0
  18. package/dist/commands/presets.js +14 -0
  19. package/dist/commands/sessions.d.ts +6 -0
  20. package/dist/commands/sessions.d.ts.map +1 -0
  21. package/dist/commands/sessions.js +50 -0
  22. package/dist/main.d.ts +2 -0
  23. package/dist/main.d.ts.map +1 -0
  24. package/dist/main.js +277 -0
  25. package/dist/repl/formatter.d.ts +16 -0
  26. package/dist/repl/formatter.d.ts.map +1 -0
  27. package/dist/repl/formatter.js +72 -0
  28. package/dist/repl/repl.d.ts +3 -0
  29. package/dist/repl/repl.d.ts.map +1 -0
  30. package/dist/repl/repl.js +212 -0
  31. package/dist/repl/session-picker.d.ts +6 -0
  32. package/dist/repl/session-picker.d.ts.map +1 -0
  33. package/dist/repl/session-picker.js +60 -0
  34. package/dist/unwrap.d.ts +4 -0
  35. package/dist/unwrap.d.ts.map +1 -0
  36. package/dist/unwrap.js +6 -0
  37. package/package.json +27 -0
  38. package/src/client.ts +41 -0
  39. package/src/commands/answer.ts +27 -0
  40. package/src/commands/debug-format.ts +299 -0
  41. package/src/commands/debug.ts +347 -0
  42. package/src/commands/messages.ts +92 -0
  43. package/src/commands/presets.ts +22 -0
  44. package/src/commands/sessions.ts +73 -0
  45. package/src/main.ts +319 -0
  46. package/src/repl/formatter.ts +84 -0
  47. package/src/repl/repl.ts +246 -0
  48. package/src/repl/session-picker.ts +81 -0
  49. package/src/unwrap.ts +8 -0
package/src/main.ts ADDED
@@ -0,0 +1,319 @@
1
+ import { SessionId } from '@roj-ai/shared'
2
+ import { RpcError } from '@roj-ai/shared/rpc'
3
+ import { createCliClient } from './client.js'
4
+ import { answerCommand } from './commands/answer.js'
5
+ import {
6
+ agentCommand,
7
+ agentsCommand,
8
+ debugSendCommand,
9
+ eventsCommand,
10
+ llmCallCommand,
11
+ llmCallsCommand,
12
+ mailboxCommand,
13
+ metricsCommand,
14
+ presetAgentsCommand,
15
+ spawnAgentCommand,
16
+ timelineCommand,
17
+ } from './commands/debug.js'
18
+ import { messagesCommand, sendCommand } from './commands/messages.js'
19
+ import { presetsCommand } from './commands/presets.js'
20
+ import { sessionCloseCommand, sessionCreateCommand, sessionGetCommand, sessionsListCommand } from './commands/sessions.js'
21
+ import { startRepl } from './repl/repl.js'
22
+
23
+ const USAGE = `Usage:
24
+ roj-cli [options] Start REPL
25
+ roj-cli <command> [args] [options] Non-interactive
26
+
27
+ Options:
28
+ --url <url> Server URL (default: http://localhost:2486, env: ROJ_URL)
29
+ --json Output as JSON (non-interactive only)
30
+
31
+ Commands:
32
+ presets List available presets
33
+ sessions [--status active|closed|errored] List sessions
34
+ session-create <presetId> Create new session
35
+ session-get <sessionId> Get session info
36
+ session-close <sessionId> Close session
37
+ messages <sessionId> Get messages
38
+ send <sessionId> <message> [--wait] Send message
39
+ answer <sessionId> <questionId> <answer> Answer agent question
40
+
41
+ Debug commands:
42
+ events <sessionId> [--type <type>] [--agent <id>] [--limit N] Show domain events
43
+ agents <sessionId> Show agent tree
44
+ agent <sessionId> <agentId> Show agent detail
45
+ mailbox <sessionId> Show global mailbox
46
+ timeline <sessionId> Show execution timeline
47
+ metrics <sessionId> Show session metrics
48
+ llm-calls <sessionId> [--limit N] List LLM calls
49
+ llm-call <sessionId> <callId> Show LLM call detail
50
+ preset-agents <sessionId> Show spawnable agents
51
+ debug-send <sessionId> <agentId> <message> [--from <sender>] Send debug message
52
+ spawn-agent <sessionId> <defName> <parentId> [--message <msg>] Spawn agent manually`
53
+
54
+ interface ParsedArgs {
55
+ url: string
56
+ json: boolean
57
+ command: string | null
58
+ positional: string[]
59
+ flags: Record<string, string | true>
60
+ }
61
+
62
+ function parseArgs(argv: string[]): ParsedArgs {
63
+ // Skip bun and script path
64
+ const args = argv.slice(2)
65
+
66
+ let url = process.env.ROJ_URL ?? 'http://localhost:2486'
67
+ let json = false
68
+ const positional: string[] = []
69
+ const flags: Record<string, string | true> = {}
70
+
71
+ for (let i = 0; i < args.length; i++) {
72
+ const arg = args[i]
73
+ if (arg === '--url') {
74
+ url = args[++i]
75
+ } else if (arg === '--json') {
76
+ json = true
77
+ } else if (arg === '--help' || arg === '-h') {
78
+ console.log(USAGE)
79
+ process.exit(0)
80
+ } else if (arg.startsWith('--')) {
81
+ const key = arg.slice(2)
82
+ const next = args[i + 1]
83
+ if (next && !next.startsWith('--')) {
84
+ flags[key] = next
85
+ i++
86
+ } else {
87
+ flags[key] = true
88
+ }
89
+ } else {
90
+ positional.push(arg)
91
+ }
92
+ }
93
+
94
+ return {
95
+ url,
96
+ json,
97
+ command: positional[0] ?? null,
98
+ positional: positional.slice(1),
99
+ flags,
100
+ }
101
+ }
102
+
103
+ async function main(): Promise<void> {
104
+ const parsed = parseArgs(process.argv)
105
+ const client = createCliClient(parsed.url)
106
+
107
+ // REPL mode if no command
108
+ if (!parsed.command) {
109
+ await startRepl(client, parsed.url)
110
+ return
111
+ }
112
+
113
+ switch (parsed.command) {
114
+ case 'presets':
115
+ await presetsCommand(client, parsed.json)
116
+ break
117
+
118
+ case 'sessions': {
119
+ const status = parsed.flags.status as 'active' | 'closed' | 'errored' | undefined
120
+ await sessionsListCommand(client, typeof status === 'string' ? status : undefined, parsed.json)
121
+ break
122
+ }
123
+
124
+ case 'session-create': {
125
+ const presetId = parsed.positional[0]
126
+ if (!presetId) {
127
+ console.error('Usage: session-create <presetId>')
128
+ process.exit(1)
129
+ }
130
+ await sessionCreateCommand(client, presetId, parsed.json)
131
+ break
132
+ }
133
+
134
+ case 'session-get': {
135
+ const sessionId = parsed.positional[0]
136
+ if (!sessionId) {
137
+ console.error('Usage: session-get <sessionId>')
138
+ process.exit(1)
139
+ }
140
+ await sessionGetCommand(client, sessionId, parsed.json)
141
+ break
142
+ }
143
+
144
+ case 'session-close': {
145
+ const sessionId = parsed.positional[0]
146
+ if (!sessionId) {
147
+ console.error('Usage: session-close <sessionId>')
148
+ process.exit(1)
149
+ }
150
+ await sessionCloseCommand(client, sessionId, parsed.json)
151
+ break
152
+ }
153
+
154
+ case 'messages': {
155
+ const sessionId = parsed.positional[0]
156
+ if (!sessionId) {
157
+ console.error('Usage: messages <sessionId>')
158
+ process.exit(1)
159
+ }
160
+ await messagesCommand(client, sessionId, parsed.json)
161
+ break
162
+ }
163
+
164
+ case 'send': {
165
+ const sessionId = parsed.positional[0]
166
+ const message = parsed.positional.slice(1).join(' ')
167
+ if (!sessionId || !message) {
168
+ console.error('Usage: send <sessionId> <message> [--wait]')
169
+ process.exit(1)
170
+ }
171
+ const wait = parsed.flags.wait === true
172
+ await sendCommand(client, parsed.url, sessionId, message, wait, parsed.json)
173
+ break
174
+ }
175
+
176
+ case 'answer': {
177
+ const sessionId = parsed.positional[0]
178
+ const questionId = parsed.positional[1]
179
+ const answer = parsed.positional.slice(2).join(' ')
180
+ if (!sessionId || !questionId || !answer) {
181
+ console.error('Usage: answer <sessionId> <questionId> <answer>')
182
+ process.exit(1)
183
+ }
184
+ await answerCommand(client, sessionId, questionId, answer, parsed.json)
185
+ break
186
+ }
187
+
188
+ // Debug commands
189
+ case 'events': {
190
+ const sessionId = parsed.positional[0]
191
+ if (!sessionId) {
192
+ console.error('Usage: events <sessionId> [--type <type>] [--agent <agentId>] [--limit N]')
193
+ process.exit(1)
194
+ }
195
+ await eventsCommand(client, sessionId, parsed.flags, parsed.json)
196
+ break
197
+ }
198
+
199
+ case 'agents': {
200
+ const sessionId = parsed.positional[0]
201
+ if (!sessionId) {
202
+ console.error('Usage: agents <sessionId>')
203
+ process.exit(1)
204
+ }
205
+ await agentsCommand(client, sessionId, parsed.json)
206
+ break
207
+ }
208
+
209
+ case 'agent': {
210
+ const sessionId = parsed.positional[0]
211
+ const agentId = parsed.positional[1]
212
+ if (!sessionId || !agentId) {
213
+ console.error('Usage: agent <sessionId> <agentId>')
214
+ process.exit(1)
215
+ }
216
+ await agentCommand(client, sessionId, agentId, parsed.json)
217
+ break
218
+ }
219
+
220
+ case 'mailbox': {
221
+ const sessionId = parsed.positional[0]
222
+ if (!sessionId) {
223
+ console.error('Usage: mailbox <sessionId>')
224
+ process.exit(1)
225
+ }
226
+ await mailboxCommand(client, sessionId, parsed.json)
227
+ break
228
+ }
229
+
230
+ case 'timeline': {
231
+ const sessionId = parsed.positional[0]
232
+ if (!sessionId) {
233
+ console.error('Usage: timeline <sessionId>')
234
+ process.exit(1)
235
+ }
236
+ await timelineCommand(client, sessionId, parsed.json)
237
+ break
238
+ }
239
+
240
+ case 'metrics': {
241
+ const sessionId = parsed.positional[0]
242
+ if (!sessionId) {
243
+ console.error('Usage: metrics <sessionId>')
244
+ process.exit(1)
245
+ }
246
+ await metricsCommand(client, sessionId, parsed.json)
247
+ break
248
+ }
249
+
250
+ case 'llm-calls': {
251
+ const sessionId = parsed.positional[0]
252
+ if (!sessionId) {
253
+ console.error('Usage: llm-calls <sessionId> [--limit N]')
254
+ process.exit(1)
255
+ }
256
+ await llmCallsCommand(client, sessionId, parsed.flags, parsed.json)
257
+ break
258
+ }
259
+
260
+ case 'llm-call': {
261
+ const sessionId = parsed.positional[0]
262
+ const callId = parsed.positional[1]
263
+ if (!sessionId || !callId) {
264
+ console.error('Usage: llm-call <sessionId> <callId>')
265
+ process.exit(1)
266
+ }
267
+ await llmCallCommand(client, sessionId, callId, parsed.json)
268
+ break
269
+ }
270
+
271
+ case 'preset-agents': {
272
+ const sessionId = parsed.positional[0]
273
+ if (!sessionId) {
274
+ console.error('Usage: preset-agents <sessionId>')
275
+ process.exit(1)
276
+ }
277
+ await presetAgentsCommand(client, SessionId(sessionId), parsed.json)
278
+ break
279
+ }
280
+
281
+ case 'debug-send': {
282
+ const sessionId = parsed.positional[0]
283
+ const agentId = parsed.positional[1]
284
+ const message = parsed.positional.slice(2).join(' ')
285
+ if (!sessionId || !agentId || !message) {
286
+ console.error('Usage: debug-send <sessionId> <agentId> <message> [--from <sender>]')
287
+ process.exit(1)
288
+ }
289
+ await debugSendCommand(client, sessionId, agentId, message, parsed.flags, parsed.json)
290
+ break
291
+ }
292
+
293
+ case 'spawn-agent': {
294
+ const sessionId = parsed.positional[0]
295
+ const definitionName = parsed.positional[1]
296
+ const parentId = parsed.positional[2]
297
+ if (!sessionId || !definitionName || !parentId) {
298
+ console.error('Usage: spawn-agent <sessionId> <definitionName> <parentId> [--message <msg>]')
299
+ process.exit(1)
300
+ }
301
+ await spawnAgentCommand(client, sessionId, definitionName, parentId, parsed.flags, parsed.json)
302
+ break
303
+ }
304
+
305
+ default:
306
+ console.error(`Unknown command: ${parsed.command}`)
307
+ console.error(USAGE)
308
+ process.exit(1)
309
+ }
310
+ }
311
+
312
+ main().catch((error) => {
313
+ if (error instanceof RpcError) {
314
+ console.error(`RPC Error [${error.error.type}]: ${error.error.message}`)
315
+ } else {
316
+ console.error(error instanceof Error ? error.message : String(error))
317
+ }
318
+ process.exit(1)
319
+ })
@@ -0,0 +1,84 @@
1
+ import type { AskUserInputTypeSchema } from '@roj-ai/sdk'
2
+ import type { ChatMessage } from '@roj-ai/shared/rpc'
3
+
4
+ // ANSI color codes
5
+ const RESET = '\x1b[0m'
6
+ const BOLD = '\x1b[1m'
7
+ const DIM = '\x1b[2m'
8
+ const CYAN = '\x1b[36m'
9
+ const GREEN = '\x1b[32m'
10
+ const YELLOW = '\x1b[33m'
11
+ const RED = '\x1b[31m'
12
+ const MAGENTA = '\x1b[35m'
13
+
14
+ export function formatAgentMessage(content: string): string {
15
+ return `${CYAN}${BOLD}Agent:${RESET} ${content}`
16
+ }
17
+
18
+ export function formatUserMessage(content: string): string {
19
+ return `${GREEN}${BOLD}You:${RESET} ${content}`
20
+ }
21
+
22
+ export function formatStatus(status: string): string {
23
+ return `${DIM}[${status}]${RESET}`
24
+ }
25
+
26
+ export function formatError(message: string): string {
27
+ return `${RED}Error: ${message}${RESET}`
28
+ }
29
+
30
+ export function formatQuestion(question: string, inputType: AskUserInputTypeSchema): string {
31
+ const lines = [`${YELLOW}${BOLD}Question:${RESET} ${question}`]
32
+
33
+ if (inputType.type === 'single_choice' || inputType.type === 'multi_choice') {
34
+ for (let i = 0; i < inputType.options.length; i++) {
35
+ const opt = inputType.options[i]
36
+ const desc = opt.description ? ` - ${opt.description}` : ''
37
+ lines.push(` ${MAGENTA}${i + 1})${RESET} ${opt.label}${DIM}${desc}${RESET}`)
38
+ }
39
+ if (inputType.type === 'single_choice') {
40
+ lines.push(`${DIM}Enter number to select:${RESET}`)
41
+ } else {
42
+ lines.push(`${DIM}Enter numbers separated by commas:${RESET}`)
43
+ }
44
+ } else if (inputType.type === 'confirm') {
45
+ const confirmLabel = inputType.confirmLabel ?? 'yes'
46
+ const cancelLabel = inputType.cancelLabel ?? 'no'
47
+ lines.push(`${DIM}(${confirmLabel}/${cancelLabel}):${RESET}`)
48
+ } else if (inputType.type === 'rating') {
49
+ lines.push(`${DIM}Enter a number from ${inputType.min} to ${inputType.max}:${RESET}`)
50
+ }
51
+
52
+ return lines.join('\n')
53
+ }
54
+
55
+ export function formatChatMessage(msg: ChatMessage): string {
56
+ switch (msg.type) {
57
+ case 'user_message':
58
+ return formatUserMessage(msg.content)
59
+ case 'agent_message':
60
+ return formatAgentMessage(msg.content)
61
+ case 'ask_user': {
62
+ const answered = msg.answered ? `${DIM} (answered: ${JSON.stringify(msg.answer)})${RESET}` : ''
63
+ return formatQuestion(msg.question, msg.inputType) + answered
64
+ }
65
+ }
66
+ }
67
+
68
+ export function formatSessionInfo(session: { sessionId: string; presetId: string; status: string; createdAt: number }): string {
69
+ const date = new Date(session.createdAt).toLocaleString()
70
+ return `${BOLD}${session.sessionId}${RESET} ${DIM}[${session.presetId}]${RESET} ${session.status} ${DIM}${date}${RESET}`
71
+ }
72
+
73
+ export function formatTable(headers: string[], rows: string[][]): string {
74
+ const colWidths = headers.map((h, i) => {
75
+ const maxDataWidth = rows.reduce((max, row) => Math.max(max, (row[i] ?? '').length), 0)
76
+ return Math.max(h.length, maxDataWidth)
77
+ })
78
+
79
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ')
80
+ const separator = colWidths.map(w => '─'.repeat(w)).join('──')
81
+ const dataLines = rows.map(row => row.map((cell, i) => (cell ?? '').padEnd(colWidths[i])).join(' '))
82
+
83
+ return [headerLine, separator, ...dataLines].join('\n')
84
+ }
@@ -0,0 +1,246 @@
1
+ import type { ChatMessage, SessionId } from '@roj-ai/sdk'
2
+ import type { AskUserInputTypeSchema } from '@roj-ai/sdk'
3
+ import { AgentId, ChatMessageId } from '@roj-ai/shared'
4
+
5
+ function isChatMessage(msg: unknown): msg is ChatMessage {
6
+ return typeof msg === 'object' && msg !== null && 'type' in msg && 'timestamp' in msg
7
+ }
8
+ import type { RpcClient } from '@roj-ai/shared/rpc'
9
+ import * as readline from 'node:readline'
10
+ import { connectWebSocket, type NotificationHandlers, type WsConnection } from '../client.js'
11
+ import { unwrap } from '../unwrap.js'
12
+ import { formatAgentMessage, formatChatMessage, formatError, formatQuestion, formatStatus } from './formatter.js'
13
+ import { createNewSession, pickSession } from './session-picker.js'
14
+
15
+ const DIM = '\x1b[2m'
16
+ const RESET = '\x1b[0m'
17
+
18
+ export async function startRepl(client: RpcClient, baseUrl: string): Promise<void> {
19
+ const rl = readline.createInterface({
20
+ input: process.stdin,
21
+ output: process.stdout,
22
+ })
23
+
24
+ console.log(`${DIM}Connected to ${baseUrl}${RESET}`)
25
+ console.log(`${DIM}Commands: /quit, /sessions, /new, /messages, /help${RESET}`)
26
+
27
+ let sessionId = await pickSession(client, rl)
28
+ let entryAgentId = await resolveEntryAgentId(client, sessionId)
29
+ await loadAndDisplayMessages(client, sessionId)
30
+
31
+ let pendingQuestion: { questionId: string; inputType: AskUserInputTypeSchema } | null = null
32
+
33
+ const handlers: NotificationHandlers = {
34
+ agentMessage: (payload) => {
35
+ if (typeof payload !== 'object' || payload === null) return
36
+ const input = payload as Record<string, unknown>
37
+ if (typeof input.content !== 'string') return
38
+ readline.clearLine(process.stdout, 0)
39
+ readline.cursorTo(process.stdout, 0)
40
+ console.log(formatAgentMessage(input.content))
41
+ rl.prompt()
42
+ },
43
+ askUser: (payload) => {
44
+ if (typeof payload !== 'object' || payload === null) return
45
+ const input = payload as Record<string, unknown>
46
+ if (typeof input.questionId !== 'string' || typeof input.question !== 'string') return
47
+ readline.clearLine(process.stdout, 0)
48
+ readline.cursorTo(process.stdout, 0)
49
+ console.log(formatQuestion(input.question, input.inputType as AskUserInputTypeSchema))
50
+ pendingQuestion = { questionId: input.questionId, inputType: input.inputType as AskUserInputTypeSchema }
51
+ rl.prompt()
52
+ },
53
+ agentStatus: (payload) => {
54
+ if (typeof payload !== 'object' || payload === null) return
55
+ const input = payload as Record<string, unknown>
56
+ if (typeof input.status !== 'string') return
57
+ const statusLabels: Record<string, string> = {
58
+ idle: 'Agent idle',
59
+ thinking: 'Agent thinking...',
60
+ responding: 'Agent responding...',
61
+ waiting_for_user: 'Waiting for your input',
62
+ error: 'Agent error',
63
+ }
64
+ readline.clearLine(process.stdout, 0)
65
+ readline.cursorTo(process.stdout, 0)
66
+ console.log(formatStatus(statusLabels[input.status] ?? input.status))
67
+ rl.prompt()
68
+ },
69
+ error: (payload) => {
70
+ if (typeof payload !== 'object' || payload === null) return
71
+ const input = payload as Record<string, unknown>
72
+ if (typeof input.message !== 'string') return
73
+ readline.clearLine(process.stdout, 0)
74
+ readline.cursorTo(process.stdout, 0)
75
+ console.log(formatError(input.message))
76
+ rl.prompt()
77
+ },
78
+ }
79
+
80
+ let connection = connectWebSocket(baseUrl, sessionId, handlers)
81
+ await connection.connect()
82
+
83
+ rl.setPrompt('> ')
84
+ rl.prompt()
85
+
86
+ rl.on('line', async (line) => {
87
+ const input = line.trim()
88
+ if (!input) {
89
+ rl.prompt()
90
+ return
91
+ }
92
+
93
+ if (input.startsWith('/')) {
94
+ const handled = await handleSlashCommand(input, client, baseUrl, rl, sessionId, connection)
95
+ if (handled === 'quit') {
96
+ await connection.disconnect()
97
+ rl.close()
98
+ return
99
+ }
100
+ if (handled !== false) {
101
+ // Session switch
102
+ await connection.disconnect()
103
+ sessionId = handled
104
+ entryAgentId = await resolveEntryAgentId(client, sessionId)
105
+ connection = connectWebSocket(baseUrl, sessionId, handlers)
106
+ await connection.connect()
107
+ await loadAndDisplayMessages(client, sessionId)
108
+ }
109
+ rl.prompt()
110
+ return
111
+ }
112
+
113
+ // Handle pending question answer
114
+ if (pendingQuestion && entryAgentId) {
115
+ const answer = parseAnswer(input, pendingQuestion.inputType)
116
+ unwrap(
117
+ await client.call('user-chat.answerQuestion', {
118
+ sessionId,
119
+ agentId: AgentId(entryAgentId),
120
+ questionId: ChatMessageId(pendingQuestion.questionId),
121
+ answer,
122
+ }),
123
+ )
124
+ pendingQuestion = null
125
+ rl.prompt()
126
+ return
127
+ }
128
+
129
+ // Send message
130
+ if (entryAgentId) {
131
+ unwrap(await client.call('user-chat.sendMessage', { sessionId, agentId: AgentId(entryAgentId), content: input }))
132
+ }
133
+ rl.prompt()
134
+ })
135
+
136
+ rl.on('close', async () => {
137
+ await connection.disconnect()
138
+ process.exit(0)
139
+ })
140
+ }
141
+
142
+ async function resolveEntryAgentId(client: RpcClient, sessionId: SessionId): Promise<string | null> {
143
+ const session = unwrap(await client.call('sessions.get', { sessionId }))
144
+ return session.entryAgentId
145
+ }
146
+
147
+ async function loadAndDisplayMessages(client: RpcClient, sessionId: SessionId): Promise<void> {
148
+ const { messages: rawMessages } = unwrap(await client.call('user-chat.getMessages', { sessionId }))
149
+ const messages = rawMessages.filter(isChatMessage)
150
+ if (messages.length > 0) {
151
+ console.log()
152
+ console.log(`${DIM}--- Message history ---${RESET}`)
153
+ for (const msg of messages) {
154
+ console.log(formatChatMessage(msg))
155
+ console.log()
156
+ }
157
+ console.log(`${DIM}--- End of history ---${RESET}`)
158
+ console.log()
159
+ }
160
+ }
161
+
162
+ type SlashResult = 'quit' | SessionId | false
163
+
164
+ async function handleSlashCommand(
165
+ input: string,
166
+ client: RpcClient,
167
+ _baseUrl: string,
168
+ rl: readline.Interface,
169
+ currentSessionId: SessionId,
170
+ _connection: WsConnection,
171
+ ): Promise<SlashResult> {
172
+ const [cmd] = input.split(/\s+/)
173
+
174
+ switch (cmd) {
175
+ case '/quit':
176
+ case '/exit':
177
+ case '/q':
178
+ return 'quit'
179
+
180
+ case '/sessions': {
181
+ const newSessionId = await pickSession(client, rl)
182
+ if (newSessionId !== currentSessionId) {
183
+ return newSessionId
184
+ }
185
+ return false
186
+ }
187
+
188
+ case '/new': {
189
+ const newSessionId = await createNewSession(client, rl)
190
+ return newSessionId
191
+ }
192
+
193
+ case '/messages': {
194
+ await loadAndDisplayMessages(client, currentSessionId)
195
+ return false
196
+ }
197
+
198
+ case '/help':
199
+ console.log(`${DIM}Commands:${RESET}`)
200
+ console.log(` /quit, /exit, /q - Exit the REPL`)
201
+ console.log(` /sessions - Switch to another session`)
202
+ console.log(` /new - Create a new session`)
203
+ console.log(` /messages - Show message history`)
204
+ console.log(` /help - Show this help`)
205
+ return false
206
+
207
+ default:
208
+ console.log(`Unknown command: ${cmd}. Type /help for available commands.`)
209
+ return false
210
+ }
211
+ }
212
+
213
+ function parseAnswer(input: string, inputType: AskUserInputTypeSchema): unknown {
214
+ switch (inputType.type) {
215
+ case 'text':
216
+ return input
217
+
218
+ case 'single_choice': {
219
+ const index = parseInt(input, 10) - 1
220
+ if (index >= 0 && index < inputType.options.length) {
221
+ return inputType.options[index].value
222
+ }
223
+ return input
224
+ }
225
+
226
+ case 'multi_choice': {
227
+ const indices = input.split(',').map(s => parseInt(s.trim(), 10) - 1)
228
+ return indices
229
+ .filter(i => i >= 0 && i < inputType.options.length)
230
+ .map(i => inputType.options[i].value)
231
+ }
232
+
233
+ case 'confirm': {
234
+ const lower = input.toLowerCase()
235
+ return lower === 'y' || lower === 'yes' || lower === '1' || lower === 'true'
236
+ }
237
+
238
+ case 'rating': {
239
+ const num = parseInt(input, 10)
240
+ if (num >= inputType.min && num <= inputType.max) {
241
+ return num
242
+ }
243
+ return parseInt(input, 10)
244
+ }
245
+ }
246
+ }