@soederpop/luca 0.0.30 → 0.0.32
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/package.json +1 -1
- package/src/agi/features/assistant.ts +59 -2
- package/src/bootstrap/generated.ts +1 -1
- package/src/cli/build-info.ts +2 -2
- package/src/command.ts +20 -1
- package/src/commands/serve.ts +27 -0
- package/src/endpoint.ts +6 -0
- package/src/helper.ts +42 -5
- package/src/introspection/generated.agi.ts +69 -1
- package/src/introspection/generated.node.ts +47 -1
- package/src/introspection/generated.web.ts +1 -1
- package/src/node/features/helpers.ts +5 -2
- package/src/scaffolds/generated.ts +1 -1
- package/src/servers/express.ts +18 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soederpop/luca",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"website": "https://luca.soederpop.com",
|
|
5
5
|
"description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
|
|
6
6
|
"author": "jon soeder aka the people's champ <jon@soederpop.com>",
|
|
@@ -29,6 +29,7 @@ export const AssistantEventsSchema = FeatureEventsSchema.extend({
|
|
|
29
29
|
toolResult: z.tuple([z.string().describe('Tool name'), z.any().describe('Result value')]).describe('Emitted when a tool returns a result'),
|
|
30
30
|
toolError: z.tuple([z.string().describe('Tool name'), z.any().describe('Error')]).describe('Emitted when a tool call fails'),
|
|
31
31
|
hookFired: z.tuple([z.string().describe('Hook/event name')]).describe('Emitted when a hook function is called'),
|
|
32
|
+
reloaded: z.tuple([]).describe('Emitted after tools, hooks, and system prompt are reloaded from disk'),
|
|
32
33
|
systemPromptExtensionsChanged: z.tuple([]).describe('Emitted when system prompt extensions are added or removed'),
|
|
33
34
|
})
|
|
34
35
|
|
|
@@ -461,6 +462,8 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
461
462
|
*/
|
|
462
463
|
addTool(name: string, handler: (...args: any[]) => any, schema?: z.ZodType): this {
|
|
463
464
|
if (!name) throw new Error('addTool handler must be a named function')
|
|
465
|
+
if (!this._runtimeToolNames) this._runtimeToolNames = new Set()
|
|
466
|
+
this._runtimeToolNames.add(name)
|
|
464
467
|
|
|
465
468
|
const current = { ...this.tools }
|
|
466
469
|
|
|
@@ -504,10 +507,12 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
504
507
|
|
|
505
508
|
if (typeof nameOrHandler === 'string') {
|
|
506
509
|
delete current[nameOrHandler]
|
|
510
|
+
this._runtimeToolNames?.delete(nameOrHandler)
|
|
507
511
|
} else {
|
|
508
512
|
for (const [name, tool] of Object.entries(current)) {
|
|
509
513
|
if (tool.handler === nameOrHandler) {
|
|
510
514
|
delete current[name]
|
|
515
|
+
this._runtimeToolNames?.delete(name)
|
|
511
516
|
break
|
|
512
517
|
}
|
|
513
518
|
}
|
|
@@ -936,17 +941,69 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
936
941
|
*/
|
|
937
942
|
/** Hook names that are called directly during lifecycle, not bound as event listeners. */
|
|
938
943
|
private static lifecycleHooks = new Set(['formatSystemPrompt'])
|
|
944
|
+
/** Stored references to bound hook listeners so they can be unbound on reload. Lazily initialized because afterInitialize runs before field initializers. */
|
|
945
|
+
private _boundHookListeners!: Array<{ event: string; listener: (...args: any[]) => void }>
|
|
946
|
+
/** Tool names added at runtime via addTool()/use(), so reload() can preserve them. */
|
|
947
|
+
private _runtimeToolNames!: Set<string>
|
|
939
948
|
|
|
940
949
|
private bindHooksToEvents() {
|
|
950
|
+
if (!this._boundHookListeners) this._boundHookListeners = []
|
|
941
951
|
const assistant = this
|
|
942
952
|
const hooks = (this.state.get('hooks') || {}) as Record<string, (...args: any[]) => any>
|
|
943
953
|
for (const [eventName, hookFn] of Object.entries(hooks)) {
|
|
944
954
|
if (Assistant.lifecycleHooks.has(eventName)) continue
|
|
945
|
-
|
|
955
|
+
const listener = (...args: any[]) => {
|
|
946
956
|
this.emit('hookFired', eventName)
|
|
947
957
|
hookFn(assistant, ...args)
|
|
948
|
-
}
|
|
958
|
+
}
|
|
959
|
+
this._boundHookListeners.push({ event: eventName, listener })
|
|
960
|
+
this.on(eventName as any, listener)
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
private unbindHooksFromEvents() {
|
|
965
|
+
if (!this._boundHookListeners) return
|
|
966
|
+
for (const { event, listener } of this._boundHookListeners) {
|
|
967
|
+
this.off(event as any, listener)
|
|
949
968
|
}
|
|
969
|
+
this._boundHookListeners = []
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Reload tools, hooks, and system prompt from disk. Useful during development
|
|
974
|
+
* or when tool/hook files have been modified and you want the assistant to
|
|
975
|
+
* pick up changes without restarting.
|
|
976
|
+
*
|
|
977
|
+
* @returns this, for chaining
|
|
978
|
+
*/
|
|
979
|
+
reload(): this {
|
|
980
|
+
// Unbind existing hook listeners
|
|
981
|
+
this.unbindHooksFromEvents()
|
|
982
|
+
|
|
983
|
+
// Snapshot runtime-added tools before reloading from disk
|
|
984
|
+
const runtimeTools: Record<string, ConversationTool> = {}
|
|
985
|
+
if (this._runtimeToolNames?.size) {
|
|
986
|
+
const current = this.tools
|
|
987
|
+
for (const name of this._runtimeToolNames) {
|
|
988
|
+
if (current[name]) runtimeTools[name] = current[name]
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// Reload system prompt from disk
|
|
993
|
+
this.state.set('systemPrompt', this.loadSystemPrompt())
|
|
994
|
+
|
|
995
|
+
// Reload tools from disk (merges with option tools), then restore runtime tools
|
|
996
|
+
const diskTools = this.loadTools()
|
|
997
|
+
this.state.set('tools', { ...diskTools, ...runtimeTools })
|
|
998
|
+
this.emit('toolsChanged')
|
|
999
|
+
|
|
1000
|
+
// Reload hooks from disk and rebind
|
|
1001
|
+
this.state.set('hooks', this.loadHooks())
|
|
1002
|
+
this.bindHooksToEvents()
|
|
1003
|
+
|
|
1004
|
+
this.emit('reloaded')
|
|
1005
|
+
|
|
1006
|
+
return this
|
|
950
1007
|
}
|
|
951
1008
|
|
|
952
1009
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated bootstrap content
|
|
2
|
-
// Generated at: 2026-03-
|
|
2
|
+
// Generated at: 2026-03-24T09:09:11.574Z
|
|
3
3
|
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
|
|
4
4
|
//
|
|
5
5
|
// Do not edit manually. Run: luca build-bootstrap
|
package/src/cli/build-info.ts
CHANGED
package/src/command.ts
CHANGED
|
@@ -109,7 +109,26 @@ export class Command<
|
|
|
109
109
|
const named = this._normalizeInput(raw, dispatchSource)
|
|
110
110
|
|
|
111
111
|
// Validate against argsSchema
|
|
112
|
-
|
|
112
|
+
let parsed: any
|
|
113
|
+
try {
|
|
114
|
+
parsed = Cls.argsSchema.parse(named)
|
|
115
|
+
} catch (err: any) {
|
|
116
|
+
if (err?.name === 'ZodError' && dispatchSource === 'cli') {
|
|
117
|
+
const ui = (this.container as any).feature('ui')
|
|
118
|
+
const cmdName = Cls.shortcut?.replace('commands.', '') || 'unknown'
|
|
119
|
+
const issues = err.issues || []
|
|
120
|
+
|
|
121
|
+
ui.print.red(`\n Error: Invalid options for "${cmdName}"\n`)
|
|
122
|
+
for (const issue of issues) {
|
|
123
|
+
const path = issue.path?.length ? issue.path.join('.') : 'input'
|
|
124
|
+
ui.print(` ${ui.colors.yellow('→')} ${ui.colors.bold(path)}: ${issue.message}`)
|
|
125
|
+
}
|
|
126
|
+
ui.print('')
|
|
127
|
+
ui.print.dim(` Run ${ui.colors.cyan(`luca ${cmdName} --help`)} for usage info.\n`)
|
|
128
|
+
process.exit(1)
|
|
129
|
+
}
|
|
130
|
+
throw err
|
|
131
|
+
}
|
|
113
132
|
|
|
114
133
|
// For headless dispatch, capture stdout/stderr
|
|
115
134
|
if (dispatchSource !== 'cli') {
|
package/src/commands/serve.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const argsSchema = CommandOptionsSchema.extend({
|
|
|
19
19
|
force: z.boolean().default(false).describe('Kill any process currently using the target port'),
|
|
20
20
|
anyPort: z.boolean().default(false).describe('Find an available port starting above 3000'),
|
|
21
21
|
open: z.boolean().default(true).describe('Open the server URL in Google Chrome'),
|
|
22
|
+
watch: z.boolean().default(false).describe('Watch endpoint files and hot-reload handlers on change'),
|
|
22
23
|
})
|
|
23
24
|
|
|
24
25
|
export default async function serve(options: z.infer<typeof argsSchema>, context: ContainerContext) {
|
|
@@ -157,6 +158,32 @@ export default async function serve(options: z.infer<typeof argsSchema>, context
|
|
|
157
158
|
}
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
if (options.watch && endpointsDir) {
|
|
162
|
+
const fm = container.feature('fileManager')
|
|
163
|
+
await fm.watch({ paths: [endpointsDir] })
|
|
164
|
+
|
|
165
|
+
fm.on('file:change', async ({ type, path: filePath }: { type: string; path: string }) => {
|
|
166
|
+
if (!filePath.endsWith('.ts')) return
|
|
167
|
+
|
|
168
|
+
if (type === 'change') {
|
|
169
|
+
try {
|
|
170
|
+
const ep = await expressServer.reloadEndpoint(filePath)
|
|
171
|
+
if (ep) {
|
|
172
|
+
console.log(`[watch] Reloaded ${ep.methods.map((m: string) => m.toUpperCase()).join(',')} ${ep.path}`)
|
|
173
|
+
}
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
console.error(`[watch] Failed to reload ${filePath}: ${err.message}`)
|
|
176
|
+
}
|
|
177
|
+
} else if (type === 'add') {
|
|
178
|
+
console.log(`[watch] New file detected: ${filePath} (restart to mount)`)
|
|
179
|
+
} else if (type === 'delete') {
|
|
180
|
+
console.log(`[watch] File deleted: ${filePath} (restart to unmount)`)
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
console.log(`\n[watch] Watching ${endpointsDir} for changes`)
|
|
185
|
+
}
|
|
186
|
+
|
|
160
187
|
console.log()
|
|
161
188
|
}
|
|
162
189
|
|
package/src/endpoint.ts
CHANGED
|
@@ -197,6 +197,12 @@ export class Endpoint<
|
|
|
197
197
|
|
|
198
198
|
async reload(): Promise<this> {
|
|
199
199
|
this._module = null
|
|
200
|
+
if (this.options.filePath) {
|
|
201
|
+
const helpers = this.container.feature('helpers') as any
|
|
202
|
+
const mod = await helpers.loadModuleExports(this.options.filePath, { cacheBust: true })
|
|
203
|
+
const endpointModule: EndpointModule = mod.default || mod
|
|
204
|
+
return this.load(endpointModule)
|
|
205
|
+
}
|
|
200
206
|
return this.load()
|
|
201
207
|
}
|
|
202
208
|
|
package/src/helper.ts
CHANGED
|
@@ -704,16 +704,42 @@ function presentIntrospectionAsTypeScript(introspection: HelperIntrospection, se
|
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
|
|
707
|
-
// State
|
|
707
|
+
// State — render as the observable State<T> object, not just currentState
|
|
708
708
|
if (shouldRender('state') && introspection.state && Object.keys(introspection.state).length > 0) {
|
|
709
709
|
if (members.length > 0) members.push('')
|
|
710
710
|
const stateMembers = Object.entries(introspection.state)
|
|
711
711
|
.map(([name, info]) => {
|
|
712
|
-
const comment = info.description ? `
|
|
713
|
-
return `${comment}
|
|
712
|
+
const comment = info.description ? ` /** ${info.description} */\n` : ''
|
|
713
|
+
return `${comment} ${name}: ${normalizeTypeString(info.type || 'any')};`
|
|
714
714
|
})
|
|
715
715
|
.join('\n')
|
|
716
|
-
|
|
716
|
+
const stateShape = `{\n${stateMembers}\n }`
|
|
717
|
+
members.push(` state: {`)
|
|
718
|
+
members.push(` /** The current version number, incremented on each change */`)
|
|
719
|
+
members.push(` readonly version: number;`)
|
|
720
|
+
members.push(` /** Get the value of a state key */`)
|
|
721
|
+
members.push(` get<K extends keyof T>(key: K): T[K] | undefined;`)
|
|
722
|
+
members.push(` /** Set a state key to a new value, notifying observers */`)
|
|
723
|
+
members.push(` set<K extends keyof T>(key: K, value: T[K]): this;`)
|
|
724
|
+
members.push(` /** Delete a state key, notifying observers */`)
|
|
725
|
+
members.push(` delete<K extends keyof T>(key: K): this;`)
|
|
726
|
+
members.push(` /** Check if a state key exists */`)
|
|
727
|
+
members.push(` has<K extends keyof T>(key: K): boolean;`)
|
|
728
|
+
members.push(` /** Get all state keys */`)
|
|
729
|
+
members.push(` keys(): string[];`)
|
|
730
|
+
members.push(` /** Get the current state snapshot */`)
|
|
731
|
+
members.push(` readonly current: ${stateShape};`)
|
|
732
|
+
members.push(` /** Get all entries as [key, value] pairs */`)
|
|
733
|
+
members.push(` entries(): [string, any][];`)
|
|
734
|
+
members.push(` /** Get all state values */`)
|
|
735
|
+
members.push(` values(): any[];`)
|
|
736
|
+
members.push(` /** Register an observer callback for state changes. Returns an unsubscribe function. */`)
|
|
737
|
+
members.push(` observe(callback: (changeType: 'add' | 'update' | 'delete', key: string, value?: any) => void): () => void;`)
|
|
738
|
+
members.push(` /** Merge partial state, notifying observers for each changed key */`)
|
|
739
|
+
members.push(` setState(value: Partial<${stateShape}> | ((current: ${stateShape}, state: this) => Partial<${stateShape}>)): void;`)
|
|
740
|
+
members.push(` /** Clear all state, notifying observers */`)
|
|
741
|
+
members.push(` clear(): void;`)
|
|
742
|
+
members.push(` };`)
|
|
717
743
|
}
|
|
718
744
|
|
|
719
745
|
// Options
|
|
@@ -804,5 +830,16 @@ function isGenericObjectType(type: string): boolean {
|
|
|
804
830
|
function normalizeTypeString(type: string): string {
|
|
805
831
|
if (!type) return 'any'
|
|
806
832
|
// The AST scanner sometimes wraps types in quotes
|
|
807
|
-
|
|
833
|
+
type = type.replace(/^["']|["']$/g, '')
|
|
834
|
+
// Convert internal ReturnType<typeof this.container.feature<'name'>> to a clean import reference
|
|
835
|
+
// e.g. ReturnType<typeof this.container.feature<'proc'>> → import('luca').Proc
|
|
836
|
+
type = type.replace(
|
|
837
|
+
/ReturnType<typeof this\.container\.(feature|client|server)<'([^']+)'>>/g,
|
|
838
|
+
(_match, _kind, name) => {
|
|
839
|
+
// Convert shortcut name to PascalCase class name
|
|
840
|
+
const className = name.replace(/(^|[-_])(\w)/g, (_: string, _sep: string, ch: string) => ch.toUpperCase())
|
|
841
|
+
return `import('@soederpop/luca').${className}`
|
|
842
|
+
}
|
|
843
|
+
)
|
|
844
|
+
return type
|
|
808
845
|
}
|
|
@@ -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-
|
|
4
|
+
// Generated at: 2026-03-24T09:09:09.989Z
|
|
5
5
|
|
|
6
6
|
setBuildTimeData('features.googleDocs', {
|
|
7
7
|
"id": "features.googleDocs",
|
|
@@ -10426,6 +10426,16 @@ setBuildTimeData('features.helpers', {
|
|
|
10426
10426
|
"absPath": {
|
|
10427
10427
|
"type": "string",
|
|
10428
10428
|
"description": "Absolute path to the module file"
|
|
10429
|
+
},
|
|
10430
|
+
"options": {
|
|
10431
|
+
"type": "{ cacheBust?: boolean }",
|
|
10432
|
+
"description": "Optional settings",
|
|
10433
|
+
"properties": {
|
|
10434
|
+
"cacheBust": {
|
|
10435
|
+
"type": "any",
|
|
10436
|
+
"description": "When true, appends a timestamp query to bypass the native import cache (useful for hot reload)"
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10429
10439
|
}
|
|
10430
10440
|
},
|
|
10431
10441
|
"required": [
|
|
@@ -12749,6 +12759,19 @@ setBuildTimeData('servers.express', {
|
|
|
12749
12759
|
],
|
|
12750
12760
|
"returns": "Promise<this>"
|
|
12751
12761
|
},
|
|
12762
|
+
"reloadEndpoint": {
|
|
12763
|
+
"description": "Reload a mounted endpoint by its file path. Re-reads the module through the helpers VM loader so the next request picks up the new handlers.",
|
|
12764
|
+
"parameters": {
|
|
12765
|
+
"filePath": {
|
|
12766
|
+
"type": "string",
|
|
12767
|
+
"description": "Absolute path to the endpoint file"
|
|
12768
|
+
}
|
|
12769
|
+
},
|
|
12770
|
+
"required": [
|
|
12771
|
+
"filePath"
|
|
12772
|
+
],
|
|
12773
|
+
"returns": "Promise<Endpoint | null>"
|
|
12774
|
+
},
|
|
12752
12775
|
"useEndpointModules": {
|
|
12753
12776
|
"description": "",
|
|
12754
12777
|
"parameters": {
|
|
@@ -14941,6 +14964,12 @@ setBuildTimeData('features.assistant', {
|
|
|
14941
14964
|
"required": [],
|
|
14942
14965
|
"returns": "Promise<number>"
|
|
14943
14966
|
},
|
|
14967
|
+
"reload": {
|
|
14968
|
+
"description": "Reload tools, hooks, and system prompt from disk. Useful during development or when tool/hook files have been modified and you want the assistant to pick up changes without restarting.",
|
|
14969
|
+
"parameters": {},
|
|
14970
|
+
"required": [],
|
|
14971
|
+
"returns": "this"
|
|
14972
|
+
},
|
|
14944
14973
|
"start": {
|
|
14945
14974
|
"description": "Start the assistant by creating the conversation and wiring up events. The system prompt, tools, and hooks are already loaded synchronously during initialization.",
|
|
14946
14975
|
"parameters": {},
|
|
@@ -15128,6 +15157,11 @@ setBuildTimeData('features.assistant', {
|
|
|
15128
15157
|
"description": "Event emitted by Assistant",
|
|
15129
15158
|
"arguments": {}
|
|
15130
15159
|
},
|
|
15160
|
+
"reloaded": {
|
|
15161
|
+
"name": "reloaded",
|
|
15162
|
+
"description": "Event emitted by Assistant",
|
|
15163
|
+
"arguments": {}
|
|
15164
|
+
},
|
|
15131
15165
|
"turnStart": {
|
|
15132
15166
|
"name": "turnStart",
|
|
15133
15167
|
"description": "Event emitted by Assistant",
|
|
@@ -27880,6 +27914,16 @@ export const introspectionData = [
|
|
|
27880
27914
|
"absPath": {
|
|
27881
27915
|
"type": "string",
|
|
27882
27916
|
"description": "Absolute path to the module file"
|
|
27917
|
+
},
|
|
27918
|
+
"options": {
|
|
27919
|
+
"type": "{ cacheBust?: boolean }",
|
|
27920
|
+
"description": "Optional settings",
|
|
27921
|
+
"properties": {
|
|
27922
|
+
"cacheBust": {
|
|
27923
|
+
"type": "any",
|
|
27924
|
+
"description": "When true, appends a timestamp query to bypass the native import cache (useful for hot reload)"
|
|
27925
|
+
}
|
|
27926
|
+
}
|
|
27883
27927
|
}
|
|
27884
27928
|
},
|
|
27885
27929
|
"required": [
|
|
@@ -30192,6 +30236,19 @@ export const introspectionData = [
|
|
|
30192
30236
|
],
|
|
30193
30237
|
"returns": "Promise<this>"
|
|
30194
30238
|
},
|
|
30239
|
+
"reloadEndpoint": {
|
|
30240
|
+
"description": "Reload a mounted endpoint by its file path. Re-reads the module through the helpers VM loader so the next request picks up the new handlers.",
|
|
30241
|
+
"parameters": {
|
|
30242
|
+
"filePath": {
|
|
30243
|
+
"type": "string",
|
|
30244
|
+
"description": "Absolute path to the endpoint file"
|
|
30245
|
+
}
|
|
30246
|
+
},
|
|
30247
|
+
"required": [
|
|
30248
|
+
"filePath"
|
|
30249
|
+
],
|
|
30250
|
+
"returns": "Promise<Endpoint | null>"
|
|
30251
|
+
},
|
|
30195
30252
|
"useEndpointModules": {
|
|
30196
30253
|
"description": "",
|
|
30197
30254
|
"parameters": {
|
|
@@ -32376,6 +32433,12 @@ export const introspectionData = [
|
|
|
32376
32433
|
"required": [],
|
|
32377
32434
|
"returns": "Promise<number>"
|
|
32378
32435
|
},
|
|
32436
|
+
"reload": {
|
|
32437
|
+
"description": "Reload tools, hooks, and system prompt from disk. Useful during development or when tool/hook files have been modified and you want the assistant to pick up changes without restarting.",
|
|
32438
|
+
"parameters": {},
|
|
32439
|
+
"required": [],
|
|
32440
|
+
"returns": "this"
|
|
32441
|
+
},
|
|
32379
32442
|
"start": {
|
|
32380
32443
|
"description": "Start the assistant by creating the conversation and wiring up events. The system prompt, tools, and hooks are already loaded synchronously during initialization.",
|
|
32381
32444
|
"parameters": {},
|
|
@@ -32563,6 +32626,11 @@ export const introspectionData = [
|
|
|
32563
32626
|
"description": "Event emitted by Assistant",
|
|
32564
32627
|
"arguments": {}
|
|
32565
32628
|
},
|
|
32629
|
+
"reloaded": {
|
|
32630
|
+
"name": "reloaded",
|
|
32631
|
+
"description": "Event emitted by Assistant",
|
|
32632
|
+
"arguments": {}
|
|
32633
|
+
},
|
|
32566
32634
|
"turnStart": {
|
|
32567
32635
|
"name": "turnStart",
|
|
32568
32636
|
"description": "Event emitted by Assistant",
|
|
@@ -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-
|
|
4
|
+
// Generated at: 2026-03-24T09:09:09.885Z
|
|
5
5
|
|
|
6
6
|
setBuildTimeData('features.googleDocs', {
|
|
7
7
|
"id": "features.googleDocs",
|
|
@@ -10426,6 +10426,16 @@ setBuildTimeData('features.helpers', {
|
|
|
10426
10426
|
"absPath": {
|
|
10427
10427
|
"type": "string",
|
|
10428
10428
|
"description": "Absolute path to the module file"
|
|
10429
|
+
},
|
|
10430
|
+
"options": {
|
|
10431
|
+
"type": "{ cacheBust?: boolean }",
|
|
10432
|
+
"description": "Optional settings",
|
|
10433
|
+
"properties": {
|
|
10434
|
+
"cacheBust": {
|
|
10435
|
+
"type": "any",
|
|
10436
|
+
"description": "When true, appends a timestamp query to bypass the native import cache (useful for hot reload)"
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10429
10439
|
}
|
|
10430
10440
|
},
|
|
10431
10441
|
"required": [
|
|
@@ -12749,6 +12759,19 @@ setBuildTimeData('servers.express', {
|
|
|
12749
12759
|
],
|
|
12750
12760
|
"returns": "Promise<this>"
|
|
12751
12761
|
},
|
|
12762
|
+
"reloadEndpoint": {
|
|
12763
|
+
"description": "Reload a mounted endpoint by its file path. Re-reads the module through the helpers VM loader so the next request picks up the new handlers.",
|
|
12764
|
+
"parameters": {
|
|
12765
|
+
"filePath": {
|
|
12766
|
+
"type": "string",
|
|
12767
|
+
"description": "Absolute path to the endpoint file"
|
|
12768
|
+
}
|
|
12769
|
+
},
|
|
12770
|
+
"required": [
|
|
12771
|
+
"filePath"
|
|
12772
|
+
],
|
|
12773
|
+
"returns": "Promise<Endpoint | null>"
|
|
12774
|
+
},
|
|
12752
12775
|
"useEndpointModules": {
|
|
12753
12776
|
"description": "",
|
|
12754
12777
|
"parameters": {
|
|
@@ -23768,6 +23791,16 @@ export const introspectionData = [
|
|
|
23768
23791
|
"absPath": {
|
|
23769
23792
|
"type": "string",
|
|
23770
23793
|
"description": "Absolute path to the module file"
|
|
23794
|
+
},
|
|
23795
|
+
"options": {
|
|
23796
|
+
"type": "{ cacheBust?: boolean }",
|
|
23797
|
+
"description": "Optional settings",
|
|
23798
|
+
"properties": {
|
|
23799
|
+
"cacheBust": {
|
|
23800
|
+
"type": "any",
|
|
23801
|
+
"description": "When true, appends a timestamp query to bypass the native import cache (useful for hot reload)"
|
|
23802
|
+
}
|
|
23803
|
+
}
|
|
23771
23804
|
}
|
|
23772
23805
|
},
|
|
23773
23806
|
"required": [
|
|
@@ -26080,6 +26113,19 @@ export const introspectionData = [
|
|
|
26080
26113
|
],
|
|
26081
26114
|
"returns": "Promise<this>"
|
|
26082
26115
|
},
|
|
26116
|
+
"reloadEndpoint": {
|
|
26117
|
+
"description": "Reload a mounted endpoint by its file path. Re-reads the module through the helpers VM loader so the next request picks up the new handlers.",
|
|
26118
|
+
"parameters": {
|
|
26119
|
+
"filePath": {
|
|
26120
|
+
"type": "string",
|
|
26121
|
+
"description": "Absolute path to the endpoint file"
|
|
26122
|
+
}
|
|
26123
|
+
},
|
|
26124
|
+
"required": [
|
|
26125
|
+
"filePath"
|
|
26126
|
+
],
|
|
26127
|
+
"returns": "Promise<Endpoint | null>"
|
|
26128
|
+
},
|
|
26083
26129
|
"useEndpointModules": {
|
|
26084
26130
|
"description": "",
|
|
26085
26131
|
"parameters": {
|
|
@@ -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-
|
|
4
|
+
// Generated at: 2026-03-24T09:09:09.897Z
|
|
5
5
|
|
|
6
6
|
setBuildTimeData('features.containerLink', {
|
|
7
7
|
"id": "features.containerLink",
|
|
@@ -408,11 +408,14 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
|
408
408
|
* Uses the same `useNativeImport` check as discovery to decide the loading strategy.
|
|
409
409
|
*
|
|
410
410
|
* @param absPath - Absolute path to the module file
|
|
411
|
+
* @param options - Optional settings
|
|
412
|
+
* @param options.cacheBust - When true, appends a timestamp query to bypass the native import cache (useful for hot reload)
|
|
411
413
|
* @returns The module's exports
|
|
412
414
|
*/
|
|
413
|
-
async loadModuleExports(absPath: string): Promise<Record<string, any>> {
|
|
415
|
+
async loadModuleExports(absPath: string, options?: { cacheBust?: boolean }): Promise<Record<string, any>> {
|
|
414
416
|
if (this.useNativeImport) {
|
|
415
|
-
const
|
|
417
|
+
const importPath = options?.cacheBust ? `${absPath}?t=${Date.now()}` : absPath
|
|
418
|
+
const mod = await import(importPath)
|
|
416
419
|
return mod
|
|
417
420
|
}
|
|
418
421
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated scaffold and MCP readme content
|
|
2
|
-
// Generated at: 2026-03-
|
|
2
|
+
// Generated at: 2026-03-24T09:09:10.786Z
|
|
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
|
package/src/servers/express.ts
CHANGED
|
@@ -211,6 +211,24 @@ export class ExpressServer<T extends ServerState = ServerState, K extends Expres
|
|
|
211
211
|
return this
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Reload a mounted endpoint by its file path. Re-reads the module through
|
|
216
|
+
* the helpers VM loader so the next request picks up the new handlers.
|
|
217
|
+
*
|
|
218
|
+
* @param filePath - Absolute path to the endpoint file
|
|
219
|
+
* @returns The reloaded Endpoint, or null if no mounted endpoint matches
|
|
220
|
+
*/
|
|
221
|
+
async reloadEndpoint(filePath: string): Promise<Endpoint | null> {
|
|
222
|
+
const endpoint = this._mountedEndpoints.find(ep => (ep.options as any).filePath === filePath)
|
|
223
|
+
if (!endpoint) return null
|
|
224
|
+
|
|
225
|
+
const helpers = this.container.feature('helpers') as any
|
|
226
|
+
const mod = await helpers.loadModuleExports(filePath, { cacheBust: true })
|
|
227
|
+
const endpointModule: EndpointModule = mod.default || mod
|
|
228
|
+
await endpoint.load(endpointModule)
|
|
229
|
+
return endpoint
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
async useEndpointModules(modules: EndpointModule[]): Promise<this> {
|
|
215
233
|
for (const mod of modules) {
|
|
216
234
|
try {
|