@soederpop/luca 0.0.28 → 0.0.30

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 (51) hide show
  1. package/commands/try-all-challenges.ts +1 -1
  2. package/docs/TABLE-OF-CONTENTS.md +0 -3
  3. package/docs/examples/structured-output-with-assistants.md +144 -0
  4. package/docs/tutorials/20-browser-esm.md +234 -0
  5. package/package.json +1 -1
  6. package/src/agi/container.server.ts +4 -0
  7. package/src/agi/features/assistant.ts +132 -2
  8. package/src/agi/features/browser-use.ts +623 -0
  9. package/src/agi/features/conversation.ts +135 -45
  10. package/src/agi/lib/interceptor-chain.ts +79 -0
  11. package/src/bootstrap/generated.ts +381 -308
  12. package/src/cli/build-info.ts +2 -2
  13. package/src/clients/rest.ts +7 -7
  14. package/src/commands/chat.ts +22 -0
  15. package/src/commands/describe.ts +67 -2
  16. package/src/commands/prompt.ts +23 -3
  17. package/src/container.ts +411 -113
  18. package/src/helper.ts +189 -5
  19. package/src/introspection/generated.agi.ts +17664 -11568
  20. package/src/introspection/generated.node.ts +4891 -1860
  21. package/src/introspection/generated.web.ts +379 -291
  22. package/src/introspection/index.ts +7 -0
  23. package/src/introspection/scan.ts +224 -7
  24. package/src/node/container.ts +31 -10
  25. package/src/node/features/content-db.ts +7 -7
  26. package/src/node/features/disk-cache.ts +11 -11
  27. package/src/node/features/esbuild.ts +3 -3
  28. package/src/node/features/file-manager.ts +37 -16
  29. package/src/node/features/fs.ts +64 -25
  30. package/src/node/features/git.ts +10 -10
  31. package/src/node/features/helpers.ts +25 -18
  32. package/src/node/features/ink.ts +13 -13
  33. package/src/node/features/ipc-socket.ts +8 -8
  34. package/src/node/features/networking.ts +3 -3
  35. package/src/node/features/os.ts +7 -7
  36. package/src/node/features/package-finder.ts +15 -15
  37. package/src/node/features/proc.ts +1 -1
  38. package/src/node/features/ui.ts +13 -13
  39. package/src/node/features/vm.ts +4 -4
  40. package/src/scaffolds/generated.ts +1 -1
  41. package/src/servers/express.ts +6 -6
  42. package/src/servers/mcp.ts +4 -4
  43. package/src/servers/socket.ts +6 -6
  44. package/test/interceptor-chain.test.ts +61 -0
  45. package/docs/apis/features/node/window-manager.md +0 -445
  46. package/docs/examples/window-manager-layouts.md +0 -180
  47. package/docs/examples/window-manager.md +0 -125
  48. package/docs/window-manager-fix.md +0 -249
  49. package/scripts/test-window-manager-lifecycle.ts +0 -86
  50. package/scripts/test-window-manager.ts +0 -43
  51. package/src/node/features/window-manager.ts +0 -1603
@@ -65,7 +65,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
65
65
 
66
66
  _wss?: BaseServer
67
67
 
68
- get wss() {
68
+ get wss(): BaseServer {
69
69
  if (this._wss) {
70
70
  return this._wss
71
71
  }
@@ -78,7 +78,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
78
78
  connections : Set<any> = new Set()
79
79
  _pending = new Map<string, PendingRequest>()
80
80
 
81
- async broadcast(message: any) {
81
+ async broadcast(message: any): Promise<this> {
82
82
  for(const ws of this.connections) {
83
83
  await ws.send(JSON.stringify(message))
84
84
  }
@@ -86,7 +86,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
86
86
  return this
87
87
  }
88
88
 
89
- async send(ws: any, message: any) {
89
+ async send(ws: any, message: any): Promise<this> {
90
90
  await ws.send(JSON.stringify(message))
91
91
  return this
92
92
  }
@@ -158,7 +158,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
158
158
  *
159
159
  * @param options - Optional runtime overrides for port and host
160
160
  */
161
- override async start(options?: StartOptions) {
161
+ override async start(options?: StartOptions): Promise<this> {
162
162
  if (this.isListening) {
163
163
  return this
164
164
  }
@@ -209,7 +209,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
209
209
  return this
210
210
  }
211
211
 
212
- override async stop() {
212
+ override async stop(): Promise<this> {
213
213
  if (this.isStopped) {
214
214
  return this
215
215
  }
@@ -250,7 +250,7 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
250
250
  }
251
251
 
252
252
  /** The port this server will bind to. Defaults to 8081 if not set via constructor options or start(). */
253
- override get port() {
253
+ override get port(): number {
254
254
  return this.state.get('port') || this.options.port || 8081
255
255
  }
256
256
  }
@@ -0,0 +1,61 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { InterceptorChain } from '../src/agi/lib/interceptor-chain'
3
+
4
+ describe('InterceptorChain', () => {
5
+ it('runs interceptors in order then the final', async () => {
6
+ const chain = new InterceptorChain<{ log: string[] }>()
7
+ chain.add(async (ctx, next) => { ctx.log.push('a'); await next() })
8
+ chain.add(async (ctx, next) => { ctx.log.push('b'); await next() })
9
+
10
+ const ctx = { log: [] as string[] }
11
+ await chain.run(ctx, async () => { ctx.log.push('final') })
12
+
13
+ expect(ctx.log).toEqual(['a', 'b', 'final'])
14
+ })
15
+
16
+ it('skips the final when an interceptor does not call next', async () => {
17
+ const chain = new InterceptorChain<{ log: string[] }>()
18
+ chain.add(async (ctx, _next) => { ctx.log.push('blocker') })
19
+ chain.add(async (ctx, next) => { ctx.log.push('never'); await next() })
20
+
21
+ const ctx = { log: [] as string[] }
22
+ await chain.run(ctx, async () => { ctx.log.push('final') })
23
+
24
+ expect(ctx.log).toEqual(['blocker'])
25
+ })
26
+
27
+ it('allows interceptors to mutate ctx before and after next', async () => {
28
+ const chain = new InterceptorChain<{ value: number }>()
29
+ chain.add(async (ctx, next) => {
30
+ ctx.value *= 2
31
+ await next()
32
+ ctx.value += 100
33
+ })
34
+
35
+ const ctx = { value: 5 }
36
+ await chain.run(ctx, async () => { ctx.value += 1 })
37
+
38
+ expect(ctx.value).toBe(111) // (5*2)=10, final +1=11, after +100=111
39
+ })
40
+
41
+ it('reports hasInterceptors and size', () => {
42
+ const chain = new InterceptorChain<{}>()
43
+ expect(chain.hasInterceptors).toBe(false)
44
+ expect(chain.size).toBe(0)
45
+
46
+ const fn = async (_ctx: {}, next: () => Promise<void>) => { await next() }
47
+ chain.add(fn)
48
+ expect(chain.hasInterceptors).toBe(true)
49
+ expect(chain.size).toBe(1)
50
+
51
+ chain.remove(fn)
52
+ expect(chain.hasInterceptors).toBe(false)
53
+ })
54
+
55
+ it('runs just the final when no interceptors are registered', async () => {
56
+ const chain = new InterceptorChain<{ ran: boolean }>()
57
+ const ctx = { ran: false }
58
+ await chain.run(ctx, async () => { ctx.ran = true })
59
+ expect(ctx.ran).toBe(true)
60
+ })
61
+ })
@@ -1,445 +0,0 @@
1
- # WindowManager (features.windowManager)
2
-
3
- WindowManager Feature — Native window control via LucaVoiceLauncher Acts as an IPC server that the native macOS launcher app connects to. Communicates over a Unix domain socket using NDJSON (newline-delimited JSON). **Protocol:** - Bun listens on a Unix domain socket; the native app connects as a client - Window dispatch commands are sent as NDJSON with a `window` field - The app executes window commands and sends back `windowAck` messages - Any non-windowAck message from the app is emitted as a `message` event - Other features can use `send()` to write arbitrary NDJSON to the app **Capabilities:** - Spawn native browser windows with configurable chrome - Navigate, focus, close, and eval JavaScript in windows - Automatic socket file cleanup and fallback paths
4
-
5
- ## Usage
6
-
7
- ```ts
8
- container.feature('windowManager', {
9
- // Path to the Unix domain socket the server listens on
10
- socketPath,
11
- // Automatically start listening when the feature is enabled
12
- autoListen,
13
- // Per-request timeout in milliseconds for window operations
14
- requestTimeoutMs,
15
- })
16
- ```
17
-
18
- ## Options (Zod v4 schema)
19
-
20
- | Property | Type | Description |
21
- |----------|------|-------------|
22
- | `socketPath` | `string` | Path to the Unix domain socket the server listens on |
23
- | `autoListen` | `boolean` | Automatically start listening when the feature is enabled |
24
- | `requestTimeoutMs` | `number` | Per-request timeout in milliseconds for window operations |
25
-
26
- ## Methods
27
-
28
- ### enable
29
-
30
- **Parameters:**
31
-
32
- | Name | Type | Required | Description |
33
- |------|------|----------|-------------|
34
- | `options` | `any` | | Parameter options |
35
-
36
- **Returns:** `Promise<this>`
37
-
38
-
39
-
40
- ### listen
41
-
42
- Start listening on the Unix domain socket for the native app to connect. Fire-and-forget — binds the socket and returns immediately. Sits quietly until the native app connects; does nothing visible if it never does.
43
-
44
- **Parameters:**
45
-
46
- | Name | Type | Required | Description |
47
- |------|------|----------|-------------|
48
- | `socketPath` | `string` | | Override the configured socket path |
49
-
50
- **Returns:** `this`
51
-
52
-
53
-
54
- ### stop
55
-
56
- Stop the IPC server and clean up all connections. Rejects any pending window operation requests.
57
-
58
- **Returns:** `Promise<this>`
59
-
60
-
61
-
62
- ### spawn
63
-
64
- Spawn a new native browser window. Sends a window dispatch to the app and waits for the ack.
65
-
66
- **Parameters:**
67
-
68
- | Name | Type | Required | Description |
69
- |------|------|----------|-------------|
70
- | `opts` | `SpawnOptions` | | Window configuration (url, dimensions, chrome options) |
71
-
72
- `SpawnOptions` properties:
73
-
74
- | Property | Type | Description |
75
- |----------|------|-------------|
76
- | `url` | `string` | |
77
- | `width` | `DimensionValue` | |
78
- | `height` | `DimensionValue` | |
79
- | `x` | `DimensionValue` | |
80
- | `y` | `DimensionValue` | |
81
- | `alwaysOnTop` | `boolean` | |
82
- | `window` | `{
83
- decorations?: 'normal' | 'hiddenTitleBar' | 'none'
84
- transparent?: boolean
85
- shadow?: boolean
86
- alwaysOnTop?: boolean
87
- opacity?: number
88
- clickThrough?: boolean
89
- }` | |
90
-
91
- **Returns:** `Promise<WindowHandle>`
92
-
93
-
94
-
95
- ### spawnTTY
96
-
97
- Spawn a native terminal window running a command. The terminal is read-only — stdout/stderr are rendered with ANSI support. Closing the window terminates the process.
98
-
99
- **Parameters:**
100
-
101
- | Name | Type | Required | Description |
102
- |------|------|----------|-------------|
103
- | `opts` | `SpawnTTYOptions` | ✓ | Terminal configuration (command, args, cwd, dimensions, etc.) |
104
-
105
- `SpawnTTYOptions` properties:
106
-
107
- | Property | Type | Description |
108
- |----------|------|-------------|
109
- | `command` | `string` | Executable name or path (required). |
110
- | `args` | `string[]` | Arguments passed after the command. |
111
- | `cwd` | `string` | Working directory for the process. |
112
- | `env` | `Record<string, string>` | Environment variable overrides. |
113
- | `cols` | `number` | Initial terminal columns. |
114
- | `rows` | `number` | Initial terminal rows. |
115
- | `title` | `string` | Window title. |
116
- | `width` | `DimensionValue` | Window width in points. |
117
- | `height` | `DimensionValue` | Window height in points. |
118
- | `x` | `DimensionValue` | Window x position. |
119
- | `y` | `DimensionValue` | Window y position. |
120
- | `window` | `SpawnOptions['window']` | Chrome options (decorations, alwaysOnTop, etc.) |
121
-
122
- **Returns:** `Promise<WindowHandle>`
123
-
124
-
125
-
126
- ### focus
127
-
128
- Bring a window to the front.
129
-
130
- **Parameters:**
131
-
132
- | Name | Type | Required | Description |
133
- |------|------|----------|-------------|
134
- | `windowId` | `string` | | The window ID. If omitted, the app uses the most recent window. |
135
-
136
- **Returns:** `Promise<WindowAckResult>`
137
-
138
-
139
-
140
- ### close
141
-
142
- Close a window.
143
-
144
- **Parameters:**
145
-
146
- | Name | Type | Required | Description |
147
- |------|------|----------|-------------|
148
- | `windowId` | `string` | | The window ID. If omitted, the app closes the most recent window. |
149
-
150
- **Returns:** `Promise<WindowAckResult>`
151
-
152
-
153
-
154
- ### navigate
155
-
156
- Navigate a window to a new URL.
157
-
158
- **Parameters:**
159
-
160
- | Name | Type | Required | Description |
161
- |------|------|----------|-------------|
162
- | `windowId` | `string` | ✓ | The window ID |
163
- | `url` | `string` | ✓ | The URL to navigate to |
164
-
165
- **Returns:** `Promise<WindowAckResult>`
166
-
167
-
168
-
169
- ### eval
170
-
171
- Evaluate JavaScript in a window's web view.
172
-
173
- **Parameters:**
174
-
175
- | Name | Type | Required | Description |
176
- |------|------|----------|-------------|
177
- | `windowId` | `string` | ✓ | The window ID |
178
- | `code` | `string` | ✓ | JavaScript code to evaluate |
179
- | `opts` | `{ timeoutMs?: number; returnJson?: boolean }` | | timeoutMs (default 5000), returnJson (default true) |
180
-
181
- **Returns:** `Promise<WindowAckResult>`
182
-
183
-
184
-
185
- ### screengrab
186
-
187
- Capture a PNG screenshot from a window.
188
-
189
- **Parameters:**
190
-
191
- | Name | Type | Required | Description |
192
- |------|------|----------|-------------|
193
- | `opts` | `WindowScreenGrabOptions` | ✓ | Window target and output path |
194
-
195
- `WindowScreenGrabOptions` properties:
196
-
197
- | Property | Type | Description |
198
- |----------|------|-------------|
199
- | `windowId` | `string` | Window ID. If omitted, the launcher uses the most recent window. |
200
- | `path` | `string` | Output file path for the PNG image. |
201
-
202
- **Returns:** `Promise<WindowAckResult>`
203
-
204
-
205
-
206
- ### video
207
-
208
- Record a video from a window to disk.
209
-
210
- **Parameters:**
211
-
212
- | Name | Type | Required | Description |
213
- |------|------|----------|-------------|
214
- | `opts` | `WindowVideoOptions` | ✓ | Window target, output path, and optional duration |
215
-
216
- `WindowVideoOptions` properties:
217
-
218
- | Property | Type | Description |
219
- |----------|------|-------------|
220
- | `windowId` | `string` | Window ID. If omitted, the launcher uses the most recent window. |
221
- | `path` | `string` | Output file path for the video file. |
222
- | `durationMs` | `number` | Recording duration in milliseconds. |
223
-
224
- **Returns:** `Promise<WindowAckResult>`
225
-
226
-
227
-
228
- ### window
229
-
230
- Get a WindowHandle for chainable operations on a specific window. Returns the tracked handle if one exists, otherwise creates a new one.
231
-
232
- **Parameters:**
233
-
234
- | Name | Type | Required | Description |
235
- |------|------|----------|-------------|
236
- | `windowId` | `string` | ✓ | The window ID |
237
-
238
- **Returns:** `WindowHandle`
239
-
240
-
241
-
242
- ### spawnLayout
243
-
244
- Spawn multiple windows in parallel from a layout configuration. Returns handles in the same order as the config entries.
245
-
246
- **Parameters:**
247
-
248
- | Name | Type | Required | Description |
249
- |------|------|----------|-------------|
250
- | `config` | `LayoutEntry[]` | ✓ | Array of layout entries (window or tty) |
251
-
252
- **Returns:** `Promise<WindowHandle[]>`
253
-
254
- ```ts
255
- const handles = await wm.spawnLayout([
256
- { type: 'window', url: 'https://google.com', width: 800, height: 600 },
257
- { type: 'tty', command: 'htop' },
258
- { url: 'https://github.com' }, // defaults to window
259
- ])
260
- ```
261
-
262
-
263
-
264
- ### spawnLayouts
265
-
266
- Spawn multiple layouts sequentially. Each layout's windows spawn in parallel, but the next layout waits for the previous one to fully complete.
267
-
268
- **Parameters:**
269
-
270
- | Name | Type | Required | Description |
271
- |------|------|----------|-------------|
272
- | `configs` | `LayoutEntry[][]` | ✓ | Array of layout configurations |
273
-
274
- **Returns:** `Promise<WindowHandle[][]>`
275
-
276
- ```ts
277
- const [firstBatch, secondBatch] = await wm.spawnLayouts([
278
- [{ url: 'https://google.com' }, { url: 'https://github.com' }],
279
- [{ type: 'tty', command: 'htop' }],
280
- ])
281
- ```
282
-
283
-
284
-
285
- ### send
286
-
287
- Write an NDJSON message to the connected app client. Public so other features can send arbitrary protocol messages over the same socket.
288
-
289
- **Parameters:**
290
-
291
- | Name | Type | Required | Description |
292
- |------|------|----------|-------------|
293
- | `msg` | `Record<string, any>` | ✓ | The message object to send (will be JSON-serialized + newline) |
294
-
295
- **Returns:** `boolean`
296
-
297
-
298
-
299
- ## Getters
300
-
301
- | Property | Type | Description |
302
- |----------|------|-------------|
303
- | `isListening` | `boolean` | Whether the IPC server is currently listening. |
304
- | `isClientConnected` | `boolean` | Whether the native app client is currently connected. |
305
-
306
- ## Events (Zod v4 schema)
307
-
308
- ### listening
309
-
310
- Emitted when the IPC server starts listening
311
-
312
-
313
-
314
- ### clientConnected
315
-
316
- Emitted when the native app connects
317
-
318
- **Event Arguments:**
319
-
320
- | Name | Type | Description |
321
- |------|------|-------------|
322
- | `arg0` | `any` | The client socket |
323
-
324
-
325
-
326
- ### clientDisconnected
327
-
328
- Emitted when the native app disconnects
329
-
330
-
331
-
332
- ### windowAck
333
-
334
- Emitted when a window ack is received from the app
335
-
336
- **Event Arguments:**
337
-
338
- | Name | Type | Description |
339
- |------|------|-------------|
340
- | `arg0` | `any` | The window ack payload |
341
-
342
-
343
-
344
- ### windowClosed
345
-
346
- Emitted when the native app reports a window closed event
347
-
348
- **Event Arguments:**
349
-
350
- | Name | Type | Description |
351
- |------|------|-------------|
352
- | `arg0` | `any` | Window lifecycle payload emitted when a window closes |
353
-
354
-
355
-
356
- ### terminalExited
357
-
358
- Emitted when the native app reports a terminal process exit event
359
-
360
- **Event Arguments:**
361
-
362
- | Name | Type | Description |
363
- |------|------|-------------|
364
- | `arg0` | `any` | Terminal lifecycle payload emitted when a terminal process exits |
365
-
366
-
367
-
368
- ### message
369
-
370
- Emitted for any incoming message that is not a windowAck
371
-
372
- **Event Arguments:**
373
-
374
- | Name | Type | Description |
375
- |------|------|-------------|
376
- | `arg0` | `any` | The parsed message object |
377
-
378
-
379
-
380
- ### error
381
-
382
- Emitted on error
383
-
384
- **Event Arguments:**
385
-
386
- | Name | Type | Description |
387
- |------|------|-------------|
388
- | `arg0` | `any` | The error |
389
-
390
-
391
-
392
- ## State (Zod v4 schema)
393
-
394
- | Property | Type | Description |
395
- |----------|------|-------------|
396
- | `enabled` | `boolean` | Whether this feature is currently enabled |
397
- | `listening` | `boolean` | Whether the IPC server is listening |
398
- | `clientConnected` | `boolean` | Whether the native launcher app is connected |
399
- | `socketPath` | `string` | The socket path in use |
400
- | `windowCount` | `number` | Number of tracked windows |
401
- | `lastError` | `string` | Last error message |
402
-
403
- ## Examples
404
-
405
- **features.windowManager**
406
-
407
- ```ts
408
- const wm = container.feature('windowManager', { enable: true, autoListen: true })
409
-
410
- const handle = await wm.spawn({ url: 'https://google.com', width: 800, height: 600 })
411
- handle.on('close', (msg) => console.log('window closed'))
412
- await handle.navigate('https://news.ycombinator.com')
413
- const title = await handle.eval('document.title')
414
- await handle.close()
415
-
416
- // Other features can listen for non-window messages
417
- wm.on('message', (msg) => console.log('App says:', msg))
418
-
419
- // Other features can write raw NDJSON to the app
420
- wm.send({ id: 'abc', status: 'processing', speech: 'Working on it' })
421
- ```
422
-
423
-
424
-
425
- **spawnLayout**
426
-
427
- ```ts
428
- const handles = await wm.spawnLayout([
429
- { type: 'window', url: 'https://google.com', width: 800, height: 600 },
430
- { type: 'tty', command: 'htop' },
431
- { url: 'https://github.com' }, // defaults to window
432
- ])
433
- ```
434
-
435
-
436
-
437
- **spawnLayouts**
438
-
439
- ```ts
440
- const [firstBatch, secondBatch] = await wm.spawnLayouts([
441
- [{ url: 'https://google.com' }, { url: 'https://github.com' }],
442
- [{ type: 'tty', command: 'htop' }],
443
- ])
444
- ```
445
-