@soederpop/luca 0.1.0 → 0.1.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.
@@ -1,7 +1,7 @@
1
1
  import { setBuildTimeData, setContainerBuildTimeData } from './index.js';
2
2
 
3
3
  // Auto-generated introspection registry data
4
- // Generated at: 2026-03-28T05:59:16.310Z
4
+ // Generated at: 2026-03-30T06:52:54.962Z
5
5
 
6
6
  setBuildTimeData('features.containerLink', {
7
7
  "id": "features.containerLink",
@@ -294,7 +294,10 @@ import f292 from "figlet/importable-fonts/Whimsy.js";
294
294
  import f293 from "figlet/importable-fonts/Wow.js";
295
295
  import f294 from "figlet/importable-fonts/miniwi.js";
296
296
 
297
- import figlet from "figlet/lib/figlet.js";
297
+ // Use the browser (core) subpath — filesystem-free, works in compiled binaries.
298
+ // figlet 1.11.0 replaced lib/ with dist/ and added an exports map; "./browser" is the stable subpath.
299
+ // @ts-ignore — figlet/browser exists at runtime via the package exports map
300
+ import figlet from "figlet/browser";
298
301
 
299
302
  // Register all fonts with figlet (filesystem-free, works in compiled binaries)
300
303
  figlet.parseFont("1Row", f0);
@@ -18,7 +18,9 @@ export interface TransformResult {
18
18
  * so the code can run in a vm context that provides `require`.
19
19
  */
20
20
  function esmToCjs(code: string): string {
21
- return code
21
+ const exportedNames: string[] = []
22
+
23
+ let result = code
22
24
  // import Foo, { bar, baz } from 'x' → const Foo = require('x').default ?? require('x'); const { bar, baz } = require('x')
23
25
  .replace(/^import\s+(\w+)\s*,\s*\{([^}]+)\}\s+from\s+(['"][^'"]+['"])\s*;?$/gm,
24
26
  'const $1 = require($3).default ?? require($3); const {$2} = require($3);')
@@ -39,14 +41,37 @@ function esmToCjs(code: string): string {
39
41
  // export { a, b } from 'x' → Object.assign(module.exports, require('x')) (re-exports)
40
42
  .replace(/^export\s+\{[^}]*\}\s+from\s+(['"][^'"]+['"])\s*;?$/gm,
41
43
  'Object.assign(module.exports, require($1));')
42
- // export { ... } → strip (vars already in scope)
43
- .replace(/^export\s+\{[^}]*\}\s*;?$/gm, '')
44
- // export const/let/var → const/let/var
45
- .replace(/^export\s+(const|let|var)\s+/gm, '$1 ')
46
- // export function → function (keep declaration, strip export keyword)
47
- .replace(/^export\s+(function|class)\s+/gm, '$1 ')
48
- // export async function async function
49
- .replace(/^export\s+(async\s+function)\s+/gm, '$1 ')
44
+ // export { a, b as c } → exports.a = a; exports.c = b;
45
+ .replace(/^export\s+\{([^}]*)\}\s*;?$/gm, (_match, body: string) => {
46
+ return body.split(',').map(s => {
47
+ const parts = s.trim().split(/\s+as\s+/)
48
+ const local = parts[0].trim()
49
+ const exported = (parts[1] || parts[0]).trim()
50
+ return local ? `exports['${exported}'] = ${local};` : ''
51
+ }).filter(Boolean).join(' ')
52
+ })
53
+ // export const/let/var NAME → const/let/var NAME (track for deferred export)
54
+ .replace(/^export\s+(const|let|var)\s+(\w+)/gm, (_match, decl: string, name: string) => {
55
+ exportedNames.push(name)
56
+ return `${decl} ${name}`
57
+ })
58
+ // export function NAME / export class NAME → function/class NAME (track for deferred export)
59
+ .replace(/^export\s+(function|class)\s+(\w+)/gm, (_match, type: string, name: string) => {
60
+ exportedNames.push(name)
61
+ return `${type} ${name}`
62
+ })
63
+ // export async function NAME → async function NAME (track for deferred export)
64
+ .replace(/^export\s+(async\s+function)\s+(\w+)/gm, (_match, type: string, name: string) => {
65
+ exportedNames.push(name)
66
+ return `${type} ${name}`
67
+ })
68
+
69
+ // Append exports for all tracked named exports
70
+ if (exportedNames.length > 0) {
71
+ result += '\n' + exportedNames.map(n => `exports['${n}'] = ${n};`).join('\n')
72
+ }
73
+
74
+ return result
50
75
  }
51
76
 
52
77
  /**
@@ -392,10 +392,11 @@ export class VM<
392
392
  const raw = fs.readFile(filePath)
393
393
  const { code } = this.container.feature('transpiler').transformSync(raw, { format: 'cjs' })
394
394
 
395
+ const sharedExports = {}
395
396
  const { context } = this.performSync(code, {
396
397
  require: this.createRequireFor(filePath),
397
- exports: {},
398
- module: { exports: {} },
398
+ exports: sharedExports,
399
+ module: { exports: sharedExports },
399
400
  console,
400
401
  setTimeout,
401
402
  setInterval,
@@ -1,5 +1,5 @@
1
1
  // Auto-generated Python bridge script
2
- // Generated at: 2026-03-28T05:59:19.077Z
2
+ // Generated at: 2026-03-30T06:52:57.841Z
3
3
  // Source: src/python/bridge.py
4
4
  //
5
5
  // Do not edit manually. Run: luca build-python-bridge
@@ -1,5 +1,5 @@
1
1
  // Auto-generated scaffold and MCP readme content
2
- // Generated at: 2026-03-28T05:59:17.268Z
2
+ // Generated at: 2026-03-30T06:52:56.013Z
3
3
  // Source: docs/scaffolds/*.md, docs/examples/assistant/, and docs/mcp/readme.md
4
4
  //
5
5
  // Do not edit manually. Run: luca build-scaffolds
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { AGIContainer } from '../src/agi/container.server'
3
+
4
+ describe('Assistant', () => {
5
+ let container: AGIContainer
6
+
7
+ beforeEach(() => {
8
+ container = new AGIContainer()
9
+ })
10
+
11
+ describe('codingAssistant', () => {
12
+ it('loads a non-empty system prompt from CORE.md', () => {
13
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
14
+ expect(assistant.systemPrompt.length).toBeGreaterThan(0)
15
+ expect(assistant.systemPrompt).toContain('coding assistant')
16
+ })
17
+
18
+ it('loads tools from tools.ts via the VM', () => {
19
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
20
+ const tools = assistant.availableTools
21
+ expect(tools).toContain('rg')
22
+ expect(tools).toContain('ls')
23
+ expect(tools).toContain('cat')
24
+ expect(tools).toContain('pwd')
25
+ expect(tools.length).toBeGreaterThan(0)
26
+ })
27
+
28
+ it('tools have descriptions and parameter schemas', () => {
29
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
30
+ const { rg, ls, cat } = assistant.tools
31
+ expect(rg.description.length).toBeGreaterThan(0)
32
+ expect(rg.parameters.type).toBe('object')
33
+ expect(rg.parameters.properties).toHaveProperty('args')
34
+ expect(ls.parameters.properties).toHaveProperty('args')
35
+ expect(cat.parameters.properties).toHaveProperty('args')
36
+ })
37
+
38
+ it('loads hooks from hooks.ts via the VM', () => {
39
+ const assistant = container.feature('assistant', { folder: 'assistants/codingAssistant' })
40
+ const hooks = assistant.state.get('hooks') as Record<string, Function>
41
+ expect(hooks).toBeDefined()
42
+ expect(typeof hooks.started).toBe('function')
43
+ })
44
+
45
+ it('hooks fire when the assistant starts', async () => {
46
+ const assistant = container.feature('assistant', {
47
+ folder: 'assistants/codingAssistant',
48
+ local: true,
49
+ model: 'qwen/qwen3-8b',
50
+ })
51
+
52
+ // bindHooksToEvents emits 'hookFired' with the event name each time a hook runs
53
+ const fired: string[] = []
54
+ assistant.on('hookFired', (eventName: string) => { fired.push(eventName) })
55
+
56
+ await assistant.start()
57
+ expect(fired).toContain('started')
58
+ })
59
+
60
+ it('tools are wired into the conversation after start', async () => {
61
+ const assistant = container.feature('assistant', {
62
+ folder: 'assistants/codingAssistant',
63
+ local: true,
64
+ model: 'qwen/qwen3-8b',
65
+ })
66
+ await assistant.start()
67
+ const convTools = assistant.conversation.tools
68
+ expect(Object.keys(convTools)).toContain('rg')
69
+ expect(Object.keys(convTools)).toContain('ls')
70
+ })
71
+ })
72
+ })
@@ -0,0 +1,213 @@
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { NodeContainer } from '../src/node/container'
3
+
4
+ /**
5
+ * Tests for the vm.loadModule pipeline: TypeScript source → esmToCjs → performSync → exports.
6
+ *
7
+ * These tests exercise the transpiler+VM execution path in isolation by running
8
+ * TypeScript code strings directly through transpiler.transformSync + vm.performSync,
9
+ * exactly as loadModule does internally.
10
+ */
11
+
12
+ function runModule(c: NodeContainer, ts: string, ctx: Record<string, any> = {}): Record<string, any> {
13
+ const transpiler = c.feature('transpiler')
14
+ const vm = c.feature('vm')
15
+ const { code } = transpiler.transformSync(ts, { format: 'cjs' })
16
+ const sharedExports = {}
17
+ const { context } = vm.performSync(code, {
18
+ require: (id: string) => require(id),
19
+ exports: sharedExports,
20
+ module: { exports: sharedExports },
21
+ console,
22
+ ...ctx,
23
+ })
24
+ return context.module?.exports || context.exports || {}
25
+ }
26
+
27
+ describe('vm.loadModule pipeline', () => {
28
+ describe('export const / let / var', () => {
29
+ it('exports a const', () => {
30
+ const c = new NodeContainer()
31
+ const exports = runModule(c, `export const x = 42`)
32
+ expect(exports.x).toBe(42)
33
+ })
34
+
35
+ it('exports a let', () => {
36
+ const c = new NodeContainer()
37
+ const exports = runModule(c, `export let name = 'hello'`)
38
+ expect(exports.name).toBe('hello')
39
+ })
40
+
41
+ it('exports multiple consts', () => {
42
+ const c = new NodeContainer()
43
+ const exports = runModule(c, `
44
+ export const a = 1
45
+ export const b = 2
46
+ export const c = 3
47
+ `)
48
+ expect(exports.a).toBe(1)
49
+ expect(exports.b).toBe(2)
50
+ expect(exports.c).toBe(3)
51
+ })
52
+
53
+ it('exports a const object', () => {
54
+ const c = new NodeContainer()
55
+ const exports = runModule(c, `
56
+ export const schemas = { rg: 'search', ls: 'list' }
57
+ `)
58
+ expect(exports.schemas).toEqual({ rg: 'search', ls: 'list' })
59
+ })
60
+ })
61
+
62
+ describe('export function', () => {
63
+ it('exports a named function', () => {
64
+ const c = new NodeContainer()
65
+ const exports = runModule(c, `
66
+ export function greet(name: string): string {
67
+ return 'hello ' + name
68
+ }
69
+ `)
70
+ expect(typeof exports.greet).toBe('function')
71
+ expect(exports.greet('world')).toBe('hello world')
72
+ })
73
+
74
+ it('exports an async function', () => {
75
+ const c = new NodeContainer()
76
+ const exports = runModule(c, `
77
+ export async function fetchData(): Promise<string> {
78
+ return 'data'
79
+ }
80
+ `)
81
+ expect(typeof exports.fetchData).toBe('function')
82
+ })
83
+
84
+ it('exports multiple functions', () => {
85
+ const c = new NodeContainer()
86
+ const exports = runModule(c, `
87
+ export function add(a: number, b: number) { return a + b }
88
+ export function multiply(a: number, b: number) { return a * b }
89
+ `)
90
+ expect(exports.add(2, 3)).toBe(5)
91
+ expect(exports.multiply(2, 3)).toBe(6)
92
+ })
93
+ })
94
+
95
+ describe('export { ... }', () => {
96
+ it('exports from a named export block', () => {
97
+ const c = new NodeContainer()
98
+ const exports = runModule(c, `
99
+ const foo = 'foo'
100
+ const bar = 42
101
+ export { foo, bar }
102
+ `)
103
+ expect(exports.foo).toBe('foo')
104
+ expect(exports.bar).toBe(42)
105
+ })
106
+
107
+ it('exports with renaming', () => {
108
+ const c = new NodeContainer()
109
+ const exports = runModule(c, `
110
+ const internal = 'value'
111
+ export { internal as external }
112
+ `)
113
+ expect(exports.external).toBe('value')
114
+ expect(exports.internal).toBeUndefined()
115
+ })
116
+ })
117
+
118
+ describe('export default', () => {
119
+ it('exports a default value', () => {
120
+ const c = new NodeContainer()
121
+ const exports = runModule(c, `export default 99`)
122
+ expect(exports.default).toBe(99)
123
+ })
124
+
125
+ it('exports a default object', () => {
126
+ const c = new NodeContainer()
127
+ const exports = runModule(c, `export default { key: 'value' }`)
128
+ expect(exports.default).toEqual({ key: 'value' })
129
+ })
130
+ })
131
+
132
+ describe('TypeScript features', () => {
133
+ it('strips type annotations', () => {
134
+ const c = new NodeContainer()
135
+ const exports = runModule(c, `
136
+ export function identity<T>(value: T): T {
137
+ return value
138
+ }
139
+ `)
140
+ expect(exports.identity('test')).toBe('test')
141
+ expect(exports.identity(42)).toBe(42)
142
+ })
143
+
144
+ it('strips import type statements', () => {
145
+ const c = new NodeContainer()
146
+ // import type should be stripped entirely — no runtime require
147
+ const exports = runModule(c, `
148
+ import type { SomeType } from 'some-module'
149
+ export const x = 1
150
+ `)
151
+ expect(exports.x).toBe(1)
152
+ })
153
+
154
+ it('strips declare global blocks', () => {
155
+ const c = new NodeContainer()
156
+ const exports = runModule(c, `
157
+ declare global {
158
+ var container: any
159
+ }
160
+ export const answer = 42
161
+ `)
162
+ expect(exports.answer).toBe(42)
163
+ })
164
+ })
165
+
166
+ describe('context injection', () => {
167
+ it('injected context variables are accessible in module code', () => {
168
+ const c = new NodeContainer()
169
+ const exports = runModule(c, `
170
+ export function getGreeting(): string {
171
+ return greeting + ' world'
172
+ }
173
+ `, { greeting: 'hello' })
174
+ expect(exports.getGreeting()).toBe('hello world')
175
+ })
176
+
177
+ it('injected functions are callable from exported functions', () => {
178
+ const c = new NodeContainer()
179
+ const log: string[] = []
180
+ const exports = runModule(c, `
181
+ export function run() {
182
+ record('called')
183
+ }
184
+ `, { record: (s: string) => log.push(s) })
185
+ exports.run()
186
+ expect(log).toEqual(['called'])
187
+ })
188
+ })
189
+
190
+ describe('mixed exports (tools.ts pattern)', () => {
191
+ it('handles the schemas + named functions pattern used by assistant tools', () => {
192
+ const c = new NodeContainer()
193
+ const exports = runModule(c, `
194
+ export const schemas = {
195
+ echo: { description: 'echo a value', properties: { text: {} } }
196
+ }
197
+
198
+ export function echo({ text }: { text: string }): string {
199
+ return text
200
+ }
201
+
202
+ export async function asyncOp(): Promise<string> {
203
+ return 'done'
204
+ }
205
+ `)
206
+ expect(exports.schemas).toBeDefined()
207
+ expect(exports.schemas.echo.description).toBe('echo a value')
208
+ expect(typeof exports.echo).toBe('function')
209
+ expect(exports.echo({ text: 'hi' })).toBe('hi')
210
+ expect(typeof exports.asyncOp).toBe('function')
211
+ })
212
+ })
213
+ })