@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.
- package/commands/try-all-challenges.ts +1 -1
- package/docs/TABLE-OF-CONTENTS.md +0 -3
- package/docs/examples/structured-output-with-assistants.md +144 -0
- package/docs/tutorials/20-browser-esm.md +234 -0
- package/package.json +1 -1
- package/src/agi/container.server.ts +4 -0
- package/src/agi/features/assistant.ts +132 -2
- package/src/agi/features/browser-use.ts +623 -0
- package/src/agi/features/conversation.ts +135 -45
- package/src/agi/lib/interceptor-chain.ts +79 -0
- package/src/bootstrap/generated.ts +381 -308
- package/src/cli/build-info.ts +2 -2
- package/src/clients/rest.ts +7 -7
- package/src/commands/chat.ts +22 -0
- package/src/commands/describe.ts +67 -2
- package/src/commands/prompt.ts +23 -3
- package/src/container.ts +411 -113
- package/src/helper.ts +189 -5
- package/src/introspection/generated.agi.ts +17664 -11568
- package/src/introspection/generated.node.ts +4891 -1860
- package/src/introspection/generated.web.ts +379 -291
- package/src/introspection/index.ts +7 -0
- package/src/introspection/scan.ts +224 -7
- package/src/node/container.ts +31 -10
- package/src/node/features/content-db.ts +7 -7
- package/src/node/features/disk-cache.ts +11 -11
- package/src/node/features/esbuild.ts +3 -3
- package/src/node/features/file-manager.ts +37 -16
- package/src/node/features/fs.ts +64 -25
- package/src/node/features/git.ts +10 -10
- package/src/node/features/helpers.ts +25 -18
- package/src/node/features/ink.ts +13 -13
- package/src/node/features/ipc-socket.ts +8 -8
- package/src/node/features/networking.ts +3 -3
- package/src/node/features/os.ts +7 -7
- package/src/node/features/package-finder.ts +15 -15
- package/src/node/features/proc.ts +1 -1
- package/src/node/features/ui.ts +13 -13
- package/src/node/features/vm.ts +4 -4
- package/src/scaffolds/generated.ts +1 -1
- package/src/servers/express.ts +6 -6
- package/src/servers/mcp.ts +4 -4
- package/src/servers/socket.ts +6 -6
- package/test/interceptor-chain.test.ts +61 -0
- package/docs/apis/features/node/window-manager.md +0 -445
- package/docs/examples/window-manager-layouts.md +0 -180
- package/docs/examples/window-manager.md +0 -125
- package/docs/window-manager-fix.md +0 -249
- package/scripts/test-window-manager-lifecycle.ts +0 -86
- package/scripts/test-window-manager.ts +0 -43
- package/src/node/features/window-manager.ts +0 -1603
package/src/cli/build-info.ts
CHANGED
package/src/clients/rest.ts
CHANGED
|
@@ -50,7 +50,7 @@ export class RestClient<
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
async beforeRequest() {
|
|
53
|
+
async beforeRequest(): Promise<void> {
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/** Whether JSON content-type headers should be set automatically. */
|
|
@@ -71,7 +71,7 @@ export class RestClient<
|
|
|
71
71
|
* @param options - Additional axios request config
|
|
72
72
|
* @returns Parsed response body
|
|
73
73
|
*/
|
|
74
|
-
async patch(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
74
|
+
async patch(url: string, data: any = {}, options: AxiosRequestConfig = {}): Promise<any> {
|
|
75
75
|
await this.beforeRequest();
|
|
76
76
|
return this.axios({
|
|
77
77
|
...options,
|
|
@@ -98,7 +98,7 @@ export class RestClient<
|
|
|
98
98
|
* @param options - Additional axios request config
|
|
99
99
|
* @returns Parsed response body
|
|
100
100
|
*/
|
|
101
|
-
async put(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
101
|
+
async put(url: string, data: any = {}, options: AxiosRequestConfig = {}): Promise<any> {
|
|
102
102
|
await this.beforeRequest();
|
|
103
103
|
return this.axios({
|
|
104
104
|
...options,
|
|
@@ -125,7 +125,7 @@ export class RestClient<
|
|
|
125
125
|
* @param options - Additional axios request config
|
|
126
126
|
* @returns Parsed response body
|
|
127
127
|
*/
|
|
128
|
-
async post(url: string, data: any = {}, options: AxiosRequestConfig = {}) {
|
|
128
|
+
async post(url: string, data: any = {}, options: AxiosRequestConfig = {}): Promise<any> {
|
|
129
129
|
await this.beforeRequest();
|
|
130
130
|
return this.axios({
|
|
131
131
|
...options,
|
|
@@ -152,7 +152,7 @@ export class RestClient<
|
|
|
152
152
|
* @param options - Additional axios request config
|
|
153
153
|
* @returns Parsed response body
|
|
154
154
|
*/
|
|
155
|
-
async delete(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
|
|
155
|
+
async delete(url: string, params: any = {}, options: AxiosRequestConfig = {}): Promise<any> {
|
|
156
156
|
await this.beforeRequest();
|
|
157
157
|
return this.axios({
|
|
158
158
|
...options,
|
|
@@ -179,7 +179,7 @@ export class RestClient<
|
|
|
179
179
|
* @param options - Additional axios request config
|
|
180
180
|
* @returns Parsed response body
|
|
181
181
|
*/
|
|
182
|
-
async get(url: string, params: any = {}, options: AxiosRequestConfig = {}) {
|
|
182
|
+
async get(url: string, params: any = {}, options: AxiosRequestConfig = {}): Promise<any> {
|
|
183
183
|
await this.beforeRequest()
|
|
184
184
|
return this.axios({
|
|
185
185
|
...options,
|
|
@@ -198,7 +198,7 @@ export class RestClient<
|
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
/** Handle an axios error by emitting 'failure' and returning the error as JSON. */
|
|
201
|
-
async handleError(error: AxiosError) {
|
|
201
|
+
async handleError(error: AxiosError): Promise<object> {
|
|
202
202
|
this.emit('failure', error)
|
|
203
203
|
return error.toJSON();
|
|
204
204
|
}
|
package/src/commands/chat.ts
CHANGED
|
@@ -20,6 +20,9 @@ export const argsSchema = CommandOptionsSchema.extend({
|
|
|
20
20
|
clear: z.boolean().optional().describe('Clear the conversation history for the resolved history mode and exit'),
|
|
21
21
|
prependPrompt: z.string().optional().describe('Text or path to a markdown file to prepend to the system prompt'),
|
|
22
22
|
appendPrompt: z.string().optional().describe('Text or path to a markdown file to append to the system prompt'),
|
|
23
|
+
use: z.union([z.string(), z.array(z.string())]).optional().describe('Feature(s) to inject into the assistant via .use(). Supports options: --use "contentDb:rootPath=/tmp;lazy=true"'),
|
|
24
|
+
forbidTool: z.union([z.string(), z.array(z.string())]).optional().describe('Tool name patterns to exclude (supports * glob). Can be specified multiple times.'),
|
|
25
|
+
allowTool: z.union([z.string(), z.array(z.string())]).optional().describe('Tool name patterns to allow (strict allowlist, supports * glob). Can be specified multiple times.'),
|
|
23
26
|
})
|
|
24
27
|
|
|
25
28
|
export default async function chat(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
@@ -72,6 +75,8 @@ export default async function chat(options: z.infer<typeof argsSchema>, context:
|
|
|
72
75
|
const createOptions: Record<string, any> = { historyMode, injectTimestamps: true }
|
|
73
76
|
if (options.model) createOptions.model = options.model
|
|
74
77
|
if (options.local) createOptions.local = options.local
|
|
78
|
+
if (options.forbidTool) createOptions.forbidTools = Array.isArray(options.forbidTool) ? options.forbidTool : [options.forbidTool]
|
|
79
|
+
if (options.allowTool) createOptions.allowTools = Array.isArray(options.allowTool) ? options.allowTool : [options.allowTool]
|
|
75
80
|
|
|
76
81
|
// Resolve --prepend-prompt / --append-prompt: if it's an existing file, read it; if it ends in .md but doesn't exist, error
|
|
77
82
|
const fs = container.feature('fs')
|
|
@@ -176,6 +181,23 @@ export default async function chat(options: z.infer<typeof argsSchema>, context:
|
|
|
176
181
|
process.stdout.write('\n')
|
|
177
182
|
})
|
|
178
183
|
|
|
184
|
+
// --use: inject features into the assistant
|
|
185
|
+
if (options.use) {
|
|
186
|
+
const items = Array.isArray(options.use) ? options.use : [options.use]
|
|
187
|
+
for (const item of items) {
|
|
188
|
+
const [namepart, optStr] = item.split(':')
|
|
189
|
+
const featureOpts: Record<string, any> = { enable: true }
|
|
190
|
+
if (optStr) {
|
|
191
|
+
for (const pair of optStr.split(';')) {
|
|
192
|
+
const [k, v] = pair.split('=')
|
|
193
|
+
if (k) featureOpts[k.trim()] = v?.trim() === 'true' ? true : v?.trim() === 'false' ? false : v?.trim()
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const feature = container.feature(namepart.trim(), featureOpts)
|
|
197
|
+
assistant.use(feature)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
179
201
|
// Start the assistant (loads history if applicable)
|
|
180
202
|
await assistant.start()
|
|
181
203
|
|
package/src/commands/describe.ts
CHANGED
|
@@ -2,11 +2,12 @@ import { z } from 'zod'
|
|
|
2
2
|
import { commands } from '../command.js'
|
|
3
3
|
import { CommandOptionsSchema } from '../schemas/base.js'
|
|
4
4
|
import type { ContainerContext } from '../container.js'
|
|
5
|
-
import type { HelperIntrospection } from '../introspection/index.js'
|
|
5
|
+
import type { HelperIntrospection, IntrospectionSection } from '../introspection/index.js'
|
|
6
6
|
import { __INTROSPECTION__, __BROWSER_INTROSPECTION__ } from '../introspection/index.js'
|
|
7
7
|
import { features } from '../feature.js'
|
|
8
8
|
import { ContainerDescriber } from '../container-describer.js'
|
|
9
9
|
import type { BrowserFeatureData } from '../container-describer.js'
|
|
10
|
+
import { presentIntrospectionAsTypeScript } from '../helper.js'
|
|
10
11
|
|
|
11
12
|
declare module '../command.js' {
|
|
12
13
|
interface AvailableCommands {
|
|
@@ -16,6 +17,8 @@ declare module '../command.js' {
|
|
|
16
17
|
|
|
17
18
|
export const argsSchema = CommandOptionsSchema.extend({
|
|
18
19
|
json: z.boolean().default(false).describe('Output introspection data as JSON instead of markdown'),
|
|
20
|
+
typescript: z.boolean().default(false).describe('Output introspection data as TypeScript interface declarations'),
|
|
21
|
+
ts: z.boolean().default(false).describe('Alias for --typescript'),
|
|
19
22
|
pretty: z.boolean().default(false).describe('Render markdown with terminal styling via ui.markdown'),
|
|
20
23
|
title: z.boolean().default(true).describe('Include the title header in the output (use --no-title to omit)'),
|
|
21
24
|
// Clean section flags (can be combined: --description --usage)
|
|
@@ -156,7 +159,12 @@ export default async function describe(options: z.infer<typeof argsSchema>, cont
|
|
|
156
159
|
platform: options.platform as any,
|
|
157
160
|
})
|
|
158
161
|
|
|
159
|
-
|
|
162
|
+
const wantsTypeScript = options.typescript || options.ts
|
|
163
|
+
|
|
164
|
+
if (wantsTypeScript) {
|
|
165
|
+
const output = renderResultAsTypeScript(result, targets, describer, sections)
|
|
166
|
+
console.log(output)
|
|
167
|
+
} else if (options.json) {
|
|
160
168
|
console.log(JSON.stringify(result.json, null, 2))
|
|
161
169
|
} else if (options.pretty) {
|
|
162
170
|
const ui = container.feature('ui')
|
|
@@ -166,6 +174,63 @@ export default async function describe(options: z.infer<typeof argsSchema>, cont
|
|
|
166
174
|
}
|
|
167
175
|
}
|
|
168
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Renders the describe result as TypeScript interface declarations.
|
|
179
|
+
* Handles single helpers, arrays of helpers (registry describes), and the container.
|
|
180
|
+
*/
|
|
181
|
+
function renderResultAsTypeScript(result: { json: any; text: string }, targets: string[], describer: ContainerDescriber, sections: (IntrospectionSection | 'description')[]): string {
|
|
182
|
+
const json = result.json
|
|
183
|
+
const section = sections.length === 1 && sections[0] !== 'description' ? sections[0] as IntrospectionSection : undefined
|
|
184
|
+
|
|
185
|
+
// Single helper introspection object (has shortcut = full data, or id = filtered data)
|
|
186
|
+
if (json && (json.shortcut || json.id)) {
|
|
187
|
+
// If sections were applied, the JSON is partial — get full data and pass section to renderer
|
|
188
|
+
const fullData = json.shortcut ? json : __INTROSPECTION__.get(json.id) || json
|
|
189
|
+
return presentIntrospectionAsTypeScript(fullData, section)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Container introspection (has className, registries, factories)
|
|
193
|
+
if (json && json.className && json.registries) {
|
|
194
|
+
const container = (describer as any).container
|
|
195
|
+
return container.inspectAsType()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Array of results (e.g. from registry describe or multiple targets)
|
|
199
|
+
if (Array.isArray(json)) {
|
|
200
|
+
const interfaces = json
|
|
201
|
+
.filter((item: any) => item && (item.shortcut || item.id))
|
|
202
|
+
.map((item: any) => {
|
|
203
|
+
const fullData = item.shortcut ? item : __INTROSPECTION__.get(item.id) || item
|
|
204
|
+
return presentIntrospectionAsTypeScript(fullData, section)
|
|
205
|
+
})
|
|
206
|
+
return interfaces.join('\n\n')
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Object keyed by helper id (registry describe format — has _shared and per-helper summaries)
|
|
210
|
+
if (json && typeof json === 'object' && !json._helper && json._shared) {
|
|
211
|
+
const ids = Object.keys(json).filter(k => k !== '_shared')
|
|
212
|
+
const interfaces = ids
|
|
213
|
+
.map((id: string) => {
|
|
214
|
+
// Try qualified key first (e.g. "clients.rest"), then scan the introspection map
|
|
215
|
+
for (const prefix of ['features', 'clients', 'servers', 'commands', 'endpoints']) {
|
|
216
|
+
const data = __INTROSPECTION__.get(`${prefix}.${id}`)
|
|
217
|
+
if (data) return presentIntrospectionAsTypeScript(data, section)
|
|
218
|
+
}
|
|
219
|
+
return null
|
|
220
|
+
})
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
if (interfaces.length > 0) return interfaces.join('\n\n')
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Member-level result (has _helper and _type) — render the full helper interface
|
|
226
|
+
if (json && json._helper) {
|
|
227
|
+
const fullData = __INTROSPECTION__.get(json._helper) || __INTROSPECTION__.get(`features.${json._helper}`)
|
|
228
|
+
if (fullData) return presentIntrospectionAsTypeScript(fullData)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return result.text
|
|
232
|
+
}
|
|
233
|
+
|
|
169
234
|
commands.registerHandler('describe', {
|
|
170
235
|
description: 'Describe the container, registries, or individual helpers',
|
|
171
236
|
argsSchema,
|
package/src/commands/prompt.ts
CHANGED
|
@@ -12,7 +12,8 @@ declare module '../command.js' {
|
|
|
12
12
|
|
|
13
13
|
export const argsSchema = CommandOptionsSchema.extend({
|
|
14
14
|
model: z.string().optional().describe('Override the LLM model (assistant mode only)'),
|
|
15
|
-
'
|
|
15
|
+
'include-frontmatter': z.boolean().default(false).describe('Keep YAML frontmatter in the prompt instead of stripping it before sending to the agent.'),
|
|
16
|
+
'skip-eval': z.boolean().default(false).describe('Skip execution of fenced code blocks in the prompt file'),
|
|
16
17
|
'permission-mode': z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).default('acceptEdits').describe('Permission mode for CLI agents (default: acceptEdits)'),
|
|
17
18
|
'in-folder': z.string().optional().describe('Run the CLI agent in this directory (resolved via container.paths)'),
|
|
18
19
|
'out-file': z.string().optional().describe('Save session output as a markdown file'),
|
|
@@ -790,12 +791,31 @@ async function preparePrompt(
|
|
|
790
791
|
}
|
|
791
792
|
|
|
792
793
|
let promptContent: string
|
|
793
|
-
if (options['
|
|
794
|
-
|
|
794
|
+
if (options['skip-eval']) {
|
|
795
|
+
// Strip frontmatter but don't execute code blocks
|
|
796
|
+
if (content.startsWith('---')) {
|
|
797
|
+
const fmEnd = content.indexOf('\n---', 3)
|
|
798
|
+
if (fmEnd !== -1) {
|
|
799
|
+
promptContent = content.slice(fmEnd + 4).trimStart()
|
|
800
|
+
} else {
|
|
801
|
+
promptContent = content
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
promptContent = content
|
|
805
|
+
}
|
|
795
806
|
} else {
|
|
796
807
|
promptContent = await executePromptFile(resolvedPath, container, resolvedInputs)
|
|
797
808
|
}
|
|
798
809
|
|
|
810
|
+
// Re-attach frontmatter if requested
|
|
811
|
+
if (options['include-frontmatter'] && content.startsWith('---')) {
|
|
812
|
+
const fmEnd = content.indexOf('\n---', 3)
|
|
813
|
+
if (fmEnd !== -1) {
|
|
814
|
+
const frontmatter = content.slice(0, fmEnd + 4)
|
|
815
|
+
promptContent = frontmatter + '\n' + promptContent
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
|
|
799
819
|
// Substitute {{key}} placeholders with resolved input values
|
|
800
820
|
if (Object.keys(resolvedInputs).length) {
|
|
801
821
|
promptContent = substituteInputs(promptContent, resolvedInputs)
|