@soederpop/luca 0.0.23 → 0.0.25
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/AGENTS.md +1 -1
- package/CLAUDE.md +6 -1
- package/assistants/codingAssistant/hooks.ts +0 -1
- package/assistants/lucaExpert/CORE.md +37 -0
- package/assistants/lucaExpert/hooks.ts +9 -0
- package/assistants/lucaExpert/tools.ts +177 -0
- package/commands/build-bootstrap.ts +41 -1
- package/docs/TABLE-OF-CONTENTS.md +0 -1
- package/docs/apis/clients/rest.md +5 -5
- package/docs/apis/features/agi/assistant.md +1 -1
- package/docs/apis/features/agi/conversation-history.md +6 -7
- package/docs/apis/features/agi/conversation.md +1 -1
- package/docs/apis/features/agi/semantic-search.md +1 -1
- package/docs/bootstrap/CLAUDE.md +1 -1
- package/docs/bootstrap/SKILL.md +7 -3
- package/docs/bootstrap/templates/luca-cli.ts +5 -0
- package/docs/mcp/readme.md +1 -1
- package/docs/tutorials/00-bootstrap.md +18 -0
- package/package.json +2 -2
- package/scripts/stamp-build.sh +12 -0
- package/scripts/test-docs-reader.ts +10 -0
- package/src/agi/container.server.ts +8 -5
- package/src/agi/features/assistant.ts +208 -55
- package/src/agi/features/assistants-manager.ts +138 -66
- package/src/agi/features/conversation.ts +46 -14
- package/src/agi/features/docs-reader.ts +142 -0
- package/src/agi/features/openapi.ts +1 -1
- package/src/agi/features/skills-library.ts +257 -313
- package/src/bootstrap/generated.ts +8163 -6
- package/src/cli/build-info.ts +4 -0
- package/src/cli/cli.ts +2 -1
- package/src/commands/bootstrap.ts +16 -1
- package/src/commands/eval.ts +6 -1
- package/src/commands/sandbox-mcp.ts +17 -7
- package/src/helper.ts +56 -2
- package/src/introspection/generated.agi.ts +2409 -1608
- package/src/introspection/generated.node.ts +902 -594
- package/src/introspection/generated.web.ts +1 -1
- package/src/node/container.ts +1 -1
- package/src/node/features/content-db.ts +251 -13
- package/src/node/features/git.ts +90 -0
- package/src/node/features/grep.ts +1 -1
- package/src/node/features/proc.ts +1 -0
- package/src/node/features/tts.ts +1 -1
- package/src/node/features/vm.ts +48 -0
- package/src/scaffolds/generated.ts +2 -2
- package/assistants/architect/CORE.md +0 -3
- package/assistants/architect/hooks.ts +0 -3
- package/assistants/architect/tools.ts +0 -10
- package/docs/apis/features/agi/skills-library.md +0 -234
- package/docs/reports/assistant-bugs.md +0 -38
- package/docs/reports/attach-pattern-usage.md +0 -18
- package/docs/reports/code-audit-results.md +0 -391
- package/docs/reports/console-hmr-design.md +0 -170
- package/docs/reports/helper-semantic-search.md +0 -72
- package/docs/reports/introspection-audit-tasks.md +0 -378
- package/docs/reports/luca-mcp-improvements.md +0 -128
- package/test-integration/skills-library.test.ts +0 -157
package/src/cli/cli.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @ts-ignore — bun resolves JSON imports at bundle time
|
|
3
3
|
import pkg from '../../package.json'
|
|
4
|
+
import { BUILD_SHA, BUILD_BRANCH, BUILD_DATE } from './build-info'
|
|
4
5
|
|
|
5
6
|
// Fast-path flags that don't need the container
|
|
6
7
|
const args = process.argv.slice(2)
|
|
7
8
|
if (args.includes('--version') || args.includes('-v')) {
|
|
8
|
-
console.log(`luca v${pkg.version}`)
|
|
9
|
+
console.log(`luca v${pkg.version} (${BUILD_BRANCH}@${BUILD_SHA}) built ${BUILD_DATE}`)
|
|
9
10
|
console.log(` npm: https://www.npmjs.com/package/@soederpop/luca`)
|
|
10
11
|
console.log(` git: https://github.com/soederpop/luca`)
|
|
11
12
|
process.exit(0)
|
|
@@ -2,7 +2,7 @@ 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 { bootstrapFiles, bootstrapTemplates } from '../bootstrap/generated.js'
|
|
5
|
+
import { bootstrapFiles, bootstrapTemplates, bootstrapExamples, bootstrapTutorials } from '../bootstrap/generated.js'
|
|
6
6
|
import { apiDocs } from './save-api-docs.js'
|
|
7
7
|
import { generateScaffold } from '../scaffolds/template.js'
|
|
8
8
|
|
|
@@ -52,6 +52,21 @@ async function bootstrap(options: z.infer<typeof argsSchema>, context: Container
|
|
|
52
52
|
const apiDocsPath = container.paths.resolve(skillDir, 'references', 'api-docs')
|
|
53
53
|
await apiDocs({ _: [], outputPath: apiDocsPath }, context)
|
|
54
54
|
|
|
55
|
+
// ── 3b. examples and tutorials ─────────────────────────────────
|
|
56
|
+
const examplesDir = container.paths.resolve(skillDir, 'references', 'examples')
|
|
57
|
+
await fs.ensureFolder(examplesDir)
|
|
58
|
+
for (const [filename, content] of Object.entries(bootstrapExamples)) {
|
|
59
|
+
await fs.writeFileAsync(container.paths.resolve(examplesDir, filename), content)
|
|
60
|
+
}
|
|
61
|
+
ui.print.cyan(` Writing ${Object.keys(bootstrapExamples).length} example docs...`)
|
|
62
|
+
|
|
63
|
+
const tutorialsDir = container.paths.resolve(skillDir, 'references', 'tutorials')
|
|
64
|
+
await fs.ensureFolder(tutorialsDir)
|
|
65
|
+
for (const [filename, content] of Object.entries(bootstrapTutorials)) {
|
|
66
|
+
await fs.writeFileAsync(container.paths.resolve(tutorialsDir, filename), content)
|
|
67
|
+
}
|
|
68
|
+
ui.print.cyan(` Writing ${Object.keys(bootstrapTutorials).length} tutorial docs...`)
|
|
69
|
+
|
|
55
70
|
// ── 4. docs/ folder ────────────────────────────────────────────
|
|
56
71
|
await fs.ensureFolder(mkPath('docs'))
|
|
57
72
|
await writeFile(fs, ui, mkPath('docs', 'models.ts'), bootstrapTemplates['docs-models'] || '', 'docs/models.ts')
|
package/src/commands/eval.ts
CHANGED
|
@@ -24,7 +24,12 @@ export default async function evalCommand(options: z.infer<typeof argsSchema>, c
|
|
|
24
24
|
|
|
25
25
|
const args = container.argv._ as string[]
|
|
26
26
|
// args[0] is "eval", the rest is the code snippet
|
|
27
|
-
|
|
27
|
+
let code = args.slice(1).join(' ')
|
|
28
|
+
|
|
29
|
+
// Read from stdin if no inline code was provided
|
|
30
|
+
if (!code.trim()) {
|
|
31
|
+
code = await Bun.stdin.text()
|
|
32
|
+
}
|
|
28
33
|
|
|
29
34
|
if (!code.trim()) {
|
|
30
35
|
console.error('Usage: luca eval "<code>" [--json]')
|
|
@@ -42,11 +42,7 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
|
|
|
42
42
|
const vmFeature = container.feature('vm')
|
|
43
43
|
const sandboxContext = vmFeature.createContext({
|
|
44
44
|
container,
|
|
45
|
-
console
|
|
46
|
-
log: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
47
|
-
error: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
48
|
-
warn: (...args: any[]) => args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' '),
|
|
49
|
-
},
|
|
45
|
+
console,
|
|
50
46
|
setTimeout,
|
|
51
47
|
setInterval,
|
|
52
48
|
clearTimeout,
|
|
@@ -154,8 +150,20 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
|
|
|
154
150
|
}),
|
|
155
151
|
handler: async (args) => {
|
|
156
152
|
try {
|
|
157
|
-
const result = await vmFeature.
|
|
153
|
+
const { result, console: calls } = await vmFeature.runCaptured(args.code, sandboxContext)
|
|
154
|
+
|
|
155
|
+
const content: Array<{ type: 'text', text: string }> = []
|
|
158
156
|
|
|
157
|
+
// Include captured console output if any
|
|
158
|
+
if (calls.length > 0) {
|
|
159
|
+
const consoleLines = calls.map(c => {
|
|
160
|
+
const prefix = c.method === 'log' ? '' : `[${c.method}] `
|
|
161
|
+
return prefix + c.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')
|
|
162
|
+
})
|
|
163
|
+
content.push({ type: 'text' as const, text: consoleLines.join('\n') })
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Include the result
|
|
159
167
|
let text: string
|
|
160
168
|
if (result === undefined) {
|
|
161
169
|
text = 'undefined'
|
|
@@ -173,7 +181,9 @@ export default async function mcpSandbox(options: z.infer<typeof argsSchema>, co
|
|
|
173
181
|
text = String(result)
|
|
174
182
|
}
|
|
175
183
|
|
|
176
|
-
|
|
184
|
+
content.push({ type: 'text' as const, text })
|
|
185
|
+
|
|
186
|
+
return { content }
|
|
177
187
|
} catch (error: any) {
|
|
178
188
|
return {
|
|
179
189
|
content: [{ type: 'text' as const, text: `Error: ${error.message}\n\n${error.stack || ''}` }],
|
package/src/helper.ts
CHANGED
|
@@ -35,10 +35,12 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
35
35
|
static stateSchema: z.ZodType = HelperStateSchema
|
|
36
36
|
static optionsSchema: z.ZodType = HelperOptionsSchema
|
|
37
37
|
static eventsSchema: z.ZodType = HelperEventsSchema
|
|
38
|
+
static tools: Record<string, { schema: z.ZodType, handler?: Function }> = {}
|
|
38
39
|
|
|
39
40
|
protected readonly _context: ContainerContext
|
|
40
41
|
protected readonly _events = new Bus<E>()
|
|
41
42
|
protected readonly _options: K
|
|
43
|
+
protected readonly _instanceTools: Record<string, { schema: z.ZodType, handler?: Function }> = {}
|
|
42
44
|
|
|
43
45
|
readonly state: State<T>
|
|
44
46
|
|
|
@@ -125,7 +127,7 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
125
127
|
this._context = context;
|
|
126
128
|
this.state = new State<T>({ initialState: this.initialState });
|
|
127
129
|
|
|
128
|
-
this.hide('_context', '_state', '_options', '_events', 'uuid')
|
|
130
|
+
this.hide('_context', '_state', '_options', '_events', '_instanceTools', 'uuid')
|
|
129
131
|
|
|
130
132
|
this.state.observe(() => {
|
|
131
133
|
(this as any).emit('stateChange', this.state.current)
|
|
@@ -170,7 +172,7 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
170
172
|
return this
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
/**
|
|
175
|
+
/**
|
|
174
176
|
* python / lodash style get method, which will get a value from the container using dot notation
|
|
175
177
|
* and will return a default value if the value is not found.
|
|
176
178
|
*/
|
|
@@ -178,6 +180,58 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
178
180
|
return get(this, key, defaultValue)
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Register a tool on this instance at runtime. Instance tools take precedence
|
|
185
|
+
* over class-level static tools in toTools().
|
|
186
|
+
*/
|
|
187
|
+
tool(name: string, options: { schema: z.ZodType, handler?: Function }): this {
|
|
188
|
+
this._instanceTools[name] = options
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Collect all tools from the inheritance chain and instance, returning
|
|
194
|
+
* { schemas, handlers } with matching keys. Walks the prototype chain
|
|
195
|
+
* so subclass tools override parent tools. Instance tools win over all.
|
|
196
|
+
*
|
|
197
|
+
* If a tool has no explicit handler but this instance has a method with
|
|
198
|
+
* the same name, a handler is auto-generated that delegates to that method.
|
|
199
|
+
*/
|
|
200
|
+
toTools(): { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> } {
|
|
201
|
+
// Walk the prototype chain collecting static tools (parent-first, child overwrites)
|
|
202
|
+
const merged: Record<string, { schema: z.ZodType, handler?: Function }> = {}
|
|
203
|
+
const chain: Function[] = []
|
|
204
|
+
|
|
205
|
+
let current = this.constructor as any
|
|
206
|
+
while (current && current !== Object) {
|
|
207
|
+
if (Object.hasOwn(current, 'tools') && current.tools) {
|
|
208
|
+
chain.unshift(current)
|
|
209
|
+
}
|
|
210
|
+
current = Object.getPrototypeOf(current)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const ctor of chain) {
|
|
214
|
+
Object.assign(merged, (ctor as any).tools)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Instance tools win over static
|
|
218
|
+
Object.assign(merged, this._instanceTools)
|
|
219
|
+
|
|
220
|
+
const schemas: Record<string, z.ZodType> = {}
|
|
221
|
+
const handlers: Record<string, Function> = {}
|
|
222
|
+
|
|
223
|
+
for (const [name, entry] of Object.entries(merged)) {
|
|
224
|
+
schemas[name] = entry.schema
|
|
225
|
+
if (entry.handler) {
|
|
226
|
+
handlers[name] = (args: any) => entry.handler!(args, this)
|
|
227
|
+
} else if (typeof (this as any)[name] === 'function') {
|
|
228
|
+
handlers[name] = (args: any) => (this as any)[name](args)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { schemas, handlers }
|
|
233
|
+
}
|
|
234
|
+
|
|
181
235
|
/**
|
|
182
236
|
* The options passed to the helper when it was created.
|
|
183
237
|
*/
|