@soederpop/luca 0.0.29 → 0.0.31
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/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 +120 -3
- package/src/agi/features/browser-use.ts +623 -0
- package/src/bootstrap/generated.ts +236 -308
- package/src/cli/build-info.ts +2 -2
- package/src/clients/rest.ts +7 -7
- package/src/command.ts +20 -1
- package/src/commands/chat.ts +22 -0
- package/src/commands/describe.ts +67 -2
- package/src/commands/prompt.ts +23 -3
- package/src/commands/serve.ts +27 -0
- package/src/container.ts +411 -113
- package/src/endpoint.ts +6 -0
- package/src/helper.ts +226 -5
- package/src/introspection/generated.agi.ts +16089 -10021
- package/src/introspection/generated.node.ts +5102 -2077
- 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 +15 -15
- package/src/node/features/fs.ts +23 -22
- package/src/node/features/git.ts +10 -10
- package/src/node/features/helpers.ts +5 -2
- 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 +24 -6
- package/src/servers/mcp.ts +4 -4
- package/src/servers/socket.ts +6 -6
- 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/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
|
@@ -73,6 +73,31 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
73
73
|
return presentIntrospectionJSONAsMarkdown(introspection, depth, section)
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Returns the introspection data formatted as a TypeScript interface declaration.
|
|
78
|
+
* Useful for AI agents that reason better with structured type information,
|
|
79
|
+
* or for generating `.d.ts` files that accurately describe a helper's public API.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* console.log(container.feature('fs').inspectAsType())
|
|
84
|
+
* // interface FS {
|
|
85
|
+
* // readonly cwd: string;
|
|
86
|
+
* // readFile(path: string): Promise<string>;
|
|
87
|
+
* // ...
|
|
88
|
+
* // }
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
static inspectAsType(section?: IntrospectionSection) : string {
|
|
92
|
+
return this.introspectAsType(section)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static introspectAsType(section?: IntrospectionSection) : string {
|
|
96
|
+
const introspection = this.introspect()
|
|
97
|
+
if (!introspection) return ''
|
|
98
|
+
return presentIntrospectionAsTypeScript(introspection, section)
|
|
99
|
+
}
|
|
100
|
+
|
|
76
101
|
|
|
77
102
|
/**
|
|
78
103
|
* All Helpers can be introspect()ed and, assuming the introspection data has been loaded into the registry,
|
|
@@ -90,8 +115,8 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
90
115
|
|
|
91
116
|
/** Alias for introspect */
|
|
92
117
|
inspect(section?: IntrospectionSection) : HelperIntrospection | undefined {
|
|
93
|
-
return this.introspect()
|
|
94
|
-
}
|
|
118
|
+
return this.introspect(section)
|
|
119
|
+
}
|
|
95
120
|
|
|
96
121
|
/**
|
|
97
122
|
* Returns the introspection data formatted as a markdown string.
|
|
@@ -110,7 +135,23 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
110
135
|
inspectAsText(sectionOrDepth?: IntrospectionSection | number, startHeadingDepth?: number) : string {
|
|
111
136
|
return this.introspectAsText(sectionOrDepth, startHeadingDepth)
|
|
112
137
|
}
|
|
113
|
-
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Returns the introspection data formatted as a TypeScript interface declaration.
|
|
141
|
+
* Useful for AI agents that reason better with structured type information,
|
|
142
|
+
* or for generating `.d.ts` files that accurately describe a helper's public API.
|
|
143
|
+
*/
|
|
144
|
+
introspectAsType(section?: IntrospectionSection) : string {
|
|
145
|
+
const introspection = this.introspect()
|
|
146
|
+
if (!introspection) return ''
|
|
147
|
+
return presentIntrospectionAsTypeScript(introspection, section)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Alias for introspectAsType */
|
|
151
|
+
inspectAsType(section?: IntrospectionSection) : string {
|
|
152
|
+
return this.introspectAsType(section)
|
|
153
|
+
}
|
|
154
|
+
|
|
114
155
|
constructor(options: K, context: ContainerContext) {
|
|
115
156
|
const optionSchema = (this.constructor as any).optionsSchema
|
|
116
157
|
if (optionSchema && typeof optionSchema.safeParse === 'function') {
|
|
@@ -226,7 +267,7 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
226
267
|
*/
|
|
227
268
|
toTools(): { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> } {
|
|
228
269
|
// Walk the prototype chain collecting static tools (parent-first, child overwrites)
|
|
229
|
-
const merged: Record<string, { schema: z.ZodType, handler?: Function }> = {}
|
|
270
|
+
const merged: Record<string, { schema: z.ZodType, description?: string, handler?: Function }> = {}
|
|
230
271
|
const chain: Function[] = []
|
|
231
272
|
|
|
232
273
|
let current = this.constructor as any
|
|
@@ -248,7 +289,11 @@ export abstract class Helper<T extends HelperState = HelperState, K extends Help
|
|
|
248
289
|
const handlers: Record<string, Function> = {}
|
|
249
290
|
|
|
250
291
|
for (const [name, entry] of Object.entries(merged)) {
|
|
251
|
-
|
|
292
|
+
// If the tool entry has a description but the schema doesn't, attach it
|
|
293
|
+
// so addTool() picks it up from jsonSchema.description.
|
|
294
|
+
schemas[name] = entry.description && !entry.schema.description
|
|
295
|
+
? entry.schema.describe(entry.description)
|
|
296
|
+
: entry.schema
|
|
252
297
|
if (entry.handler) {
|
|
253
298
|
handlers[name] = (args: any) => entry.handler!(args, this)
|
|
254
299
|
} else if (typeof (this as any)[name] === 'function') {
|
|
@@ -589,6 +634,7 @@ function normalizeLang(lang: string): string {
|
|
|
589
634
|
}
|
|
590
635
|
|
|
591
636
|
export { presentIntrospectionJSONAsMarkdown as presentIntrospectionAsMarkdown }
|
|
637
|
+
export { presentIntrospectionAsTypeScript, renderTypeScriptParams, normalizeTypeString, isGenericObjectType }
|
|
592
638
|
|
|
593
639
|
function presentIntrospectionJSONAsMarkdown(introspection: HelperIntrospection, startHeadingDepth: number = 1, section?: IntrospectionSection) {
|
|
594
640
|
const sections: string[] = []
|
|
@@ -622,3 +668,178 @@ function presentIntrospectionJSONAsMarkdown(introspection: HelperIntrospection,
|
|
|
622
668
|
|
|
623
669
|
return sections.join('\n\n')
|
|
624
670
|
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Renders introspection data as a TypeScript interface declaration.
|
|
674
|
+
* Produces a valid interface string that describes the helper's public API —
|
|
675
|
+
* getters, methods, state shape, options shape, and event listener signatures.
|
|
676
|
+
*/
|
|
677
|
+
function presentIntrospectionAsTypeScript(introspection: HelperIntrospection, section?: IntrospectionSection): string {
|
|
678
|
+
const interfaceName = introspection.className || introspection.id.split('.').pop() || 'Unknown'
|
|
679
|
+
const members: string[] = []
|
|
680
|
+
|
|
681
|
+
const shouldRender = (s: IntrospectionSection) => !section || section === s
|
|
682
|
+
|
|
683
|
+
// Getters
|
|
684
|
+
if (shouldRender('getters') && introspection.getters && Object.keys(introspection.getters).length > 0) {
|
|
685
|
+
for (const [name, info] of Object.entries(introspection.getters)) {
|
|
686
|
+
const returnType = normalizeTypeString(info.returns || 'any')
|
|
687
|
+
if (info.description) {
|
|
688
|
+
members.push(` /** ${info.description} */`)
|
|
689
|
+
}
|
|
690
|
+
members.push(` readonly ${name}: ${returnType};`)
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Methods
|
|
695
|
+
if (shouldRender('methods') && introspection.methods && Object.keys(introspection.methods).length > 0) {
|
|
696
|
+
if (members.length > 0) members.push('')
|
|
697
|
+
for (const [name, info] of Object.entries(introspection.methods)) {
|
|
698
|
+
if (info.description) {
|
|
699
|
+
members.push(` /** ${info.description} */`)
|
|
700
|
+
}
|
|
701
|
+
const params = renderTypeScriptParams(info)
|
|
702
|
+
const returnType = normalizeTypeString(info.returns || 'void')
|
|
703
|
+
members.push(` ${name}(${params}): ${returnType};`)
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// State — render as the observable State<T> object, not just currentState
|
|
708
|
+
if (shouldRender('state') && introspection.state && Object.keys(introspection.state).length > 0) {
|
|
709
|
+
if (members.length > 0) members.push('')
|
|
710
|
+
const stateMembers = Object.entries(introspection.state)
|
|
711
|
+
.map(([name, info]) => {
|
|
712
|
+
const comment = info.description ? ` /** ${info.description} */\n` : ''
|
|
713
|
+
return `${comment} ${name}: ${normalizeTypeString(info.type || 'any')};`
|
|
714
|
+
})
|
|
715
|
+
.join('\n')
|
|
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(` };`)
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Options
|
|
746
|
+
if (shouldRender('options') && introspection.options && Object.keys(introspection.options).length > 0) {
|
|
747
|
+
if (members.length > 0) members.push('')
|
|
748
|
+
const optionMembers = Object.entries(introspection.options)
|
|
749
|
+
.map(([name, info]) => {
|
|
750
|
+
const comment = info.description ? ` /** ${info.description} */\n` : ''
|
|
751
|
+
return `${comment} ${name}?: ${normalizeTypeString(info.type || 'any')};`
|
|
752
|
+
})
|
|
753
|
+
.join('\n')
|
|
754
|
+
members.push(` options: {\n${optionMembers}\n };`)
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Events — rendered as on() overloads
|
|
758
|
+
if (shouldRender('events') && introspection.events && Object.keys(introspection.events).length > 0) {
|
|
759
|
+
if (members.length > 0) members.push('')
|
|
760
|
+
for (const [eventName, eventInfo] of Object.entries(introspection.events)) {
|
|
761
|
+
const args = Object.entries(eventInfo.arguments || {})
|
|
762
|
+
const listenerParams = args.length > 0
|
|
763
|
+
? args.map(([argName, argInfo]) => `${argName}: ${normalizeTypeString(argInfo.type || 'any')}`).join(', ')
|
|
764
|
+
: ''
|
|
765
|
+
if (eventInfo.description) {
|
|
766
|
+
members.push(` /** ${eventInfo.description} */`)
|
|
767
|
+
}
|
|
768
|
+
members.push(` on(event: '${eventName}', listener: (${listenerParams}) => void): this;`)
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const description = introspection.description
|
|
773
|
+
? `/**\n * ${introspection.description.split('\n').join('\n * ')}\n */\n`
|
|
774
|
+
: ''
|
|
775
|
+
|
|
776
|
+
const mainInterface = `${description}interface ${interfaceName} {\n${members.join('\n')}\n}`
|
|
777
|
+
|
|
778
|
+
// Emit referenced type declarations above the main interface
|
|
779
|
+
const types = introspection.types
|
|
780
|
+
if (types && Object.keys(types).length > 0) {
|
|
781
|
+
const typeDeclarations = Object.entries(types).map(([typeName, typeInfo]) => {
|
|
782
|
+
const typeDesc = typeInfo.description
|
|
783
|
+
? `/**\n * ${typeInfo.description.split('\n').join('\n * ')}\n */\n`
|
|
784
|
+
: ''
|
|
785
|
+
const props = Object.entries(typeInfo.properties)
|
|
786
|
+
.map(([propName, propInfo]) => {
|
|
787
|
+
const comment = propInfo.description ? ` /** ${propInfo.description} */\n` : ''
|
|
788
|
+
const opt = propInfo.optional ? '?' : ''
|
|
789
|
+
return `${comment} ${propName}${opt}: ${normalizeTypeString(propInfo.type || 'any')};`
|
|
790
|
+
})
|
|
791
|
+
.join('\n')
|
|
792
|
+
return `${typeDesc}interface ${typeName} {\n${props}\n}`
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
return typeDeclarations.join('\n\n') + '\n\n' + mainInterface
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return mainInterface
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Build a TypeScript parameter list string from method introspection */
|
|
802
|
+
function renderTypeScriptParams(method: { parameters: Record<string, { type: string, description?: string, properties?: Record<string, { type: string, description?: string }> }>, required: string[] }): string {
|
|
803
|
+
const entries = Object.entries(method.parameters || {})
|
|
804
|
+
if (entries.length === 0) return ''
|
|
805
|
+
|
|
806
|
+
return entries.map(([name, info]) => {
|
|
807
|
+
const isRequired = (method.required || []).includes(name)
|
|
808
|
+
let type = normalizeTypeString(info.type || 'any')
|
|
809
|
+
|
|
810
|
+
// If the parameter has expanded sub-properties but a generic type string,
|
|
811
|
+
// render an inline object type from those properties instead
|
|
812
|
+
if (info.properties && Object.keys(info.properties).length > 0 && isGenericObjectType(type)) {
|
|
813
|
+
const props = Object.entries(info.properties)
|
|
814
|
+
.map(([propName, propInfo]) => `${propName}?: ${normalizeTypeString(propInfo.type || 'any')}`)
|
|
815
|
+
.join('; ')
|
|
816
|
+
type = `{ ${props} }`
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
return `${name}${isRequired ? '' : '?'}: ${type}`
|
|
820
|
+
}).join(', ')
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/** Returns true if the type string looks like a generic object type that sub-properties would refine */
|
|
824
|
+
function isGenericObjectType(type: string): boolean {
|
|
825
|
+
const lower = type.toLowerCase()
|
|
826
|
+
return lower === 'object' || lower === 'any' || lower === 'record<string, any>' || lower === '{}'
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/** Clean up type strings from introspection data for use in interface declarations */
|
|
830
|
+
function normalizeTypeString(type: string): string {
|
|
831
|
+
if (!type) return 'any'
|
|
832
|
+
// The AST scanner sometimes wraps types in quotes
|
|
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
|
|
845
|
+
}
|