@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.
Files changed (50) hide show
  1. package/commands/try-all-challenges.ts +1 -1
  2. package/docs/TABLE-OF-CONTENTS.md +0 -3
  3. package/docs/tutorials/20-browser-esm.md +234 -0
  4. package/package.json +1 -1
  5. package/src/agi/container.server.ts +4 -0
  6. package/src/agi/features/assistant.ts +120 -3
  7. package/src/agi/features/browser-use.ts +623 -0
  8. package/src/bootstrap/generated.ts +236 -308
  9. package/src/cli/build-info.ts +2 -2
  10. package/src/clients/rest.ts +7 -7
  11. package/src/command.ts +20 -1
  12. package/src/commands/chat.ts +22 -0
  13. package/src/commands/describe.ts +67 -2
  14. package/src/commands/prompt.ts +23 -3
  15. package/src/commands/serve.ts +27 -0
  16. package/src/container.ts +411 -113
  17. package/src/endpoint.ts +6 -0
  18. package/src/helper.ts +226 -5
  19. package/src/introspection/generated.agi.ts +16089 -10021
  20. package/src/introspection/generated.node.ts +5102 -2077
  21. package/src/introspection/generated.web.ts +379 -291
  22. package/src/introspection/index.ts +7 -0
  23. package/src/introspection/scan.ts +224 -7
  24. package/src/node/container.ts +31 -10
  25. package/src/node/features/content-db.ts +7 -7
  26. package/src/node/features/disk-cache.ts +11 -11
  27. package/src/node/features/esbuild.ts +3 -3
  28. package/src/node/features/file-manager.ts +15 -15
  29. package/src/node/features/fs.ts +23 -22
  30. package/src/node/features/git.ts +10 -10
  31. package/src/node/features/helpers.ts +5 -2
  32. package/src/node/features/ink.ts +13 -13
  33. package/src/node/features/ipc-socket.ts +8 -8
  34. package/src/node/features/networking.ts +3 -3
  35. package/src/node/features/os.ts +7 -7
  36. package/src/node/features/package-finder.ts +15 -15
  37. package/src/node/features/proc.ts +1 -1
  38. package/src/node/features/ui.ts +13 -13
  39. package/src/node/features/vm.ts +4 -4
  40. package/src/scaffolds/generated.ts +1 -1
  41. package/src/servers/express.ts +24 -6
  42. package/src/servers/mcp.ts +4 -4
  43. package/src/servers/socket.ts +6 -6
  44. package/docs/apis/features/node/window-manager.md +0 -445
  45. package/docs/examples/window-manager-layouts.md +0 -180
  46. package/docs/examples/window-manager.md +0 -125
  47. package/docs/window-manager-fix.md +0 -249
  48. package/scripts/test-window-manager-lifecycle.ts +0 -86
  49. package/scripts/test-window-manager.ts +0 -43
  50. 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
- schemas[name] = entry.schema
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
+ }