@soederpop/luca 0.0.23 → 0.0.26
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 +210 -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 +166 -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/command.ts +75 -0
- package/src/commands/bootstrap.ts +16 -1
- package/src/commands/describe.ts +29 -1089
- package/src/commands/eval.ts +6 -1
- package/src/commands/sandbox-mcp.ts +17 -7
- package/src/container-describer.ts +1098 -0
- package/src/container.ts +11 -0
- package/src/helper.ts +56 -2
- package/src/introspection/generated.agi.ts +1684 -799
- package/src/introspection/generated.node.ts +964 -572
- package/src/introspection/generated.web.ts +9 -1
- package/src/node/container.ts +1 -1
- package/src/node/features/content-db.ts +268 -13
- package/src/node/features/fs.ts +18 -0
- 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/src/server.ts +40 -0
- package/src/servers/express.ts +2 -0
- package/src/servers/mcp.ts +1 -0
- package/src/servers/socket.ts +2 -0
- 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
|
@@ -38,15 +38,26 @@ export const AssistantStateSchema = FeatureStateSchema.extend({
|
|
|
38
38
|
docsFolder: z.string().describe('The resolved docs folder'),
|
|
39
39
|
conversationId: z.string().optional().describe('The active conversation persistence ID'),
|
|
40
40
|
threadId: z.string().optional().describe('The active thread ID'),
|
|
41
|
+
systemPrompt: z.string().describe('The loaded system prompt text'),
|
|
42
|
+
meta: z.record(z.string(), z.any()).describe('Parsed YAML frontmatter from CORE.md'),
|
|
43
|
+
tools: z.record(z.string(), z.any()).describe('Registered tool implementations'),
|
|
44
|
+
hooks: z.record(z.string(), z.any()).describe('Loaded event hook functions'),
|
|
45
|
+
resumeThreadId: z.string().optional().describe('Thread ID override for resume'),
|
|
46
|
+
pendingPlugins: z.array(z.any()).describe('Pending async plugin promises'),
|
|
47
|
+
conversation: z.any().nullable().describe('The active Conversation feature instance'),
|
|
48
|
+
subagents: z.record(z.string(), z.any()).describe('Cached subagent instances'),
|
|
41
49
|
})
|
|
42
50
|
|
|
43
51
|
export const AssistantOptionsSchema = FeatureOptionsSchema.extend({
|
|
44
|
-
/** The folder containing the assistant definition (CORE.md, tools.ts, hooks.ts) */
|
|
45
|
-
folder: z.string().describe('The folder containing the assistant definition'),
|
|
52
|
+
/** The folder containing the assistant definition (CORE.md, tools.ts, hooks.ts). Optional for runtime-created assistants. */
|
|
53
|
+
folder: z.string().default('.').describe('The folder containing the assistant definition. Defaults to cwd for runtime-created assistants.'),
|
|
46
54
|
|
|
47
55
|
/** If the docs folder is different from folder/docs */
|
|
48
56
|
docsFolder: z.string().optional().describe('The folder containing the assistant documentation'),
|
|
49
57
|
|
|
58
|
+
/** Provide a complete system prompt directly, bypassing CORE.md. Useful for runtime-created assistants. */
|
|
59
|
+
systemPrompt: z.string().optional().describe('Provide a complete system prompt directly, bypassing CORE.md'),
|
|
60
|
+
|
|
50
61
|
/** Text to prepend to the system prompt from CORE.md */
|
|
51
62
|
prependPrompt: z.string().optional().describe('Text to prepend to the system prompt'),
|
|
52
63
|
|
|
@@ -108,6 +119,14 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
108
119
|
conversationCount: 0,
|
|
109
120
|
lastResponse: '',
|
|
110
121
|
folder: this.resolvedFolder,
|
|
122
|
+
systemPrompt: '',
|
|
123
|
+
meta: {},
|
|
124
|
+
tools: {},
|
|
125
|
+
hooks: {},
|
|
126
|
+
resumeThreadId: undefined,
|
|
127
|
+
pendingPlugins: [],
|
|
128
|
+
conversation: null,
|
|
129
|
+
subagents: {},
|
|
111
130
|
}
|
|
112
131
|
}
|
|
113
132
|
|
|
@@ -177,15 +196,6 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
177
196
|
return this.container.feature('contentDb', { rootPath: this.resolvedDocsFolder })
|
|
178
197
|
}
|
|
179
198
|
|
|
180
|
-
private _conversation?: Conversation
|
|
181
|
-
private _resumeThreadId?: string
|
|
182
|
-
|
|
183
|
-
// Using `declare` to prevent class field initializers from overwriting
|
|
184
|
-
// values set during afterInitialize() (called from the base constructor).
|
|
185
|
-
declare private _tools: Record<string, ConversationTool>
|
|
186
|
-
declare private _hooks: Record<string, (...args: any[]) => any>
|
|
187
|
-
declare private _systemPrompt: string
|
|
188
|
-
declare private _pendingPlugins: Promise<void>[]
|
|
189
199
|
|
|
190
200
|
/**
|
|
191
201
|
* Called immediately after the assistant is constructed. Synchronously loads
|
|
@@ -193,14 +203,14 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
193
203
|
* so every emitted event automatically invokes its corresponding hook.
|
|
194
204
|
*/
|
|
195
205
|
override afterInitialize() {
|
|
196
|
-
this.
|
|
206
|
+
this.state.set('pendingPlugins', [])
|
|
197
207
|
|
|
198
208
|
// Load system prompt synchronously
|
|
199
|
-
this.
|
|
209
|
+
this.state.set('systemPrompt', this.loadSystemPrompt())
|
|
200
210
|
|
|
201
211
|
// Load tools and hooks synchronously via vm.performSync
|
|
202
|
-
this.
|
|
203
|
-
this.
|
|
212
|
+
this.state.set('tools', this.loadTools())
|
|
213
|
+
this.state.set('hooks', this.loadHooks())
|
|
204
214
|
|
|
205
215
|
// Bind hooks to events BEFORE emitting created so the created hook fires
|
|
206
216
|
this.bindHooksToEvents()
|
|
@@ -209,18 +219,25 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
209
219
|
}
|
|
210
220
|
|
|
211
221
|
get conversation(): Conversation {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
let conv = this.state.get('conversation') as Conversation | null
|
|
223
|
+
if (!conv) {
|
|
224
|
+
conv = this.container.feature('conversation', {
|
|
225
|
+
model: this.options.model || 'gpt-5.4',
|
|
215
226
|
local: !!this.options.local,
|
|
216
|
-
tools: this.
|
|
227
|
+
tools: this.tools,
|
|
228
|
+
api: 'chat',
|
|
217
229
|
...(this.options.maxTokens ? { maxTokens: this.options.maxTokens } : {}),
|
|
218
230
|
history: [
|
|
219
|
-
{ role: 'system', content: this.
|
|
231
|
+
{ role: 'system', content: this.systemPrompt || this.loadSystemPrompt() },
|
|
220
232
|
],
|
|
221
233
|
})
|
|
234
|
+
this.state.set('conversation', conv)
|
|
222
235
|
}
|
|
223
|
-
return
|
|
236
|
+
return conv
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
get availableTools() {
|
|
240
|
+
return Object.keys(this.tools)
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
get messages() {
|
|
@@ -234,36 +251,57 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
234
251
|
|
|
235
252
|
/** The current system prompt text. */
|
|
236
253
|
get systemPrompt(): string {
|
|
237
|
-
return this.
|
|
254
|
+
return this.state.get('systemPrompt') || ''
|
|
238
255
|
}
|
|
239
256
|
|
|
240
257
|
/** The tools registered with this assistant. */
|
|
241
258
|
get tools(): Record<string, ConversationTool> {
|
|
242
|
-
return this.
|
|
259
|
+
return (this.state.get('tools') || {}) as Record<string, ConversationTool>
|
|
243
260
|
}
|
|
244
261
|
|
|
245
262
|
/**
|
|
246
|
-
* Apply a setup function to this assistant.
|
|
247
|
-
* assistant instance and can configure tools, hooks, event listeners, etc.
|
|
263
|
+
* Apply a setup function or a Helper instance to this assistant.
|
|
248
264
|
*
|
|
249
|
-
*
|
|
265
|
+
* When passed a function, it receives the assistant and can configure
|
|
266
|
+
* tools, hooks, event listeners, etc.
|
|
267
|
+
*
|
|
268
|
+
* When passed a Helper instance that exposes tools via toTools(),
|
|
269
|
+
* those tools are automatically added to this assistant.
|
|
270
|
+
*
|
|
271
|
+
* @param fnOrHelper - Setup function or Helper instance
|
|
250
272
|
* @returns this, for chaining
|
|
251
273
|
*
|
|
252
274
|
* @example
|
|
253
275
|
* ```typescript
|
|
254
276
|
* assistant
|
|
255
277
|
* .use(setupLogging)
|
|
256
|
-
* .use(
|
|
278
|
+
* .use(container.feature('git'))
|
|
257
279
|
* ```
|
|
258
280
|
*/
|
|
259
|
-
use(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
use(fnOrHelper: ((assistant: this) => void | Promise<void>) | { toTools: () => { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> } } | { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> }): this {
|
|
282
|
+
if (typeof fnOrHelper === 'function') {
|
|
283
|
+
const result = fnOrHelper(this)
|
|
284
|
+
if (result && typeof (result as any).then === 'function') {
|
|
285
|
+
const pending = this.state.get('pendingPlugins') as Promise<void>[]
|
|
286
|
+
this.state.set('pendingPlugins', [...pending, result as Promise<void>])
|
|
287
|
+
}
|
|
288
|
+
} else if (fnOrHelper && typeof (fnOrHelper as any).toTools === 'function') {
|
|
289
|
+
this._registerTools((fnOrHelper as any).toTools())
|
|
290
|
+
} else if (fnOrHelper && 'schemas' in fnOrHelper && 'handlers' in fnOrHelper) {
|
|
291
|
+
this._registerTools(fnOrHelper as { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> })
|
|
263
292
|
}
|
|
264
293
|
return this
|
|
265
294
|
}
|
|
266
295
|
|
|
296
|
+
/** Register tools from a `{ schemas, handlers }` object. */
|
|
297
|
+
private _registerTools({ schemas, handlers }: { schemas: Record<string, z.ZodType>, handlers: Record<string, Function> }) {
|
|
298
|
+
for (const name of Object.keys(schemas)) {
|
|
299
|
+
if (typeof handlers[name] === 'function') {
|
|
300
|
+
this.addTool(name, handlers[name] as any, schemas[name])
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
267
305
|
/**
|
|
268
306
|
* Add a tool to this assistant. The tool name is derived from the
|
|
269
307
|
* handler's function name.
|
|
@@ -279,29 +317,37 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
279
317
|
* }, z.object({ city: z.string() }).describe('Get weather for a city'))
|
|
280
318
|
* ```
|
|
281
319
|
*/
|
|
282
|
-
addTool(handler: (...args: any[]) => any, schema?: z.ZodType): this {
|
|
283
|
-
const name = handler.name
|
|
320
|
+
addTool(name: string, handler: (...args: any[]) => any, schema?: z.ZodType): this {
|
|
284
321
|
if (!name) throw new Error('addTool handler must be a named function')
|
|
285
322
|
|
|
323
|
+
const current = { ...this.tools }
|
|
324
|
+
|
|
286
325
|
if (schema) {
|
|
287
326
|
const jsonSchema = (schema as any).toJSONSchema() as Record<string, any>
|
|
288
|
-
|
|
327
|
+
// OpenAI requires `required` to list ALL property keys — optional params
|
|
328
|
+
// must still appear in `required` but use a default value in the schema.
|
|
329
|
+
const properties = jsonSchema.properties || {}
|
|
330
|
+
const required = Object.keys(properties)
|
|
331
|
+
current[name] = {
|
|
289
332
|
handler: handler as ConversationTool['handler'],
|
|
290
333
|
description: jsonSchema.description || name,
|
|
291
334
|
parameters: {
|
|
292
335
|
type: jsonSchema.type || 'object',
|
|
293
|
-
properties
|
|
294
|
-
|
|
336
|
+
properties,
|
|
337
|
+
required,
|
|
295
338
|
},
|
|
296
339
|
}
|
|
297
340
|
} else {
|
|
298
|
-
|
|
341
|
+
current[name] = {
|
|
299
342
|
handler: handler as ConversationTool['handler'],
|
|
300
343
|
description: name,
|
|
301
344
|
parameters: { type: 'object', properties: {} },
|
|
302
345
|
}
|
|
303
346
|
}
|
|
304
347
|
|
|
348
|
+
this.state.set('tools', current)
|
|
349
|
+
this.emit('toolsChanged')
|
|
350
|
+
|
|
305
351
|
return this
|
|
306
352
|
}
|
|
307
353
|
|
|
@@ -312,17 +358,22 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
312
358
|
* @returns this, for chaining
|
|
313
359
|
*/
|
|
314
360
|
removeTool(nameOrHandler: string | ((...args: any[]) => any)): this {
|
|
361
|
+
const current = { ...this.tools }
|
|
362
|
+
|
|
315
363
|
if (typeof nameOrHandler === 'string') {
|
|
316
|
-
delete
|
|
364
|
+
delete current[nameOrHandler]
|
|
317
365
|
} else {
|
|
318
|
-
for (const [name, tool] of Object.entries(
|
|
366
|
+
for (const [name, tool] of Object.entries(current)) {
|
|
319
367
|
if (tool.handler === nameOrHandler) {
|
|
320
|
-
delete
|
|
368
|
+
delete current[name]
|
|
321
369
|
break
|
|
322
370
|
}
|
|
323
371
|
}
|
|
324
372
|
}
|
|
325
373
|
|
|
374
|
+
this.state.set('tools', current)
|
|
375
|
+
this.emit('toolsChanged')
|
|
376
|
+
|
|
326
377
|
return this
|
|
327
378
|
}
|
|
328
379
|
|
|
@@ -384,17 +435,38 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
384
435
|
return this
|
|
385
436
|
}
|
|
386
437
|
|
|
438
|
+
/**
|
|
439
|
+
* Parsed YAML frontmatter from CORE.md, or empty object if none.
|
|
440
|
+
*/
|
|
441
|
+
get meta(): Record<string, any> {
|
|
442
|
+
return (this.state.get('meta') || {}) as Record<string, any>
|
|
443
|
+
}
|
|
444
|
+
|
|
387
445
|
/**
|
|
388
446
|
* Load the system prompt from CORE.md, applying any prepend/append options.
|
|
447
|
+
* YAML frontmatter (between --- fences) is stripped from the prompt and
|
|
448
|
+
* stored in `_meta`.
|
|
389
449
|
*
|
|
390
450
|
* @returns {string} The assembled system prompt
|
|
391
451
|
*/
|
|
392
452
|
loadSystemPrompt(): string {
|
|
393
453
|
const { fs } = this.container
|
|
394
454
|
let prompt = ''
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
455
|
+
this.state.set('meta', {})
|
|
456
|
+
|
|
457
|
+
if (this.options.systemPrompt) {
|
|
458
|
+
prompt = this.options.systemPrompt
|
|
459
|
+
} else if (fs.exists(this.corePromptPath)) {
|
|
460
|
+
const raw = fs.readFile(this.corePromptPath).toString()
|
|
461
|
+
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/)
|
|
462
|
+
|
|
463
|
+
if (fmMatch) {
|
|
464
|
+
const yaml = this.container.feature('yaml')
|
|
465
|
+
this.state.set('meta', yaml.parse(fmMatch[1]!) ?? {})
|
|
466
|
+
prompt = raw.slice(fmMatch[0].length)
|
|
467
|
+
} else {
|
|
468
|
+
prompt = raw
|
|
469
|
+
}
|
|
398
470
|
}
|
|
399
471
|
|
|
400
472
|
if (this.options.prependPrompt) {
|
|
@@ -426,6 +498,19 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
426
498
|
*/
|
|
427
499
|
loadTools(): Record<string, ConversationTool> {
|
|
428
500
|
const tools: Record<string, ConversationTool> = {}
|
|
501
|
+
|
|
502
|
+
// Skip loading if no tools file exists (runtime-created assistants)
|
|
503
|
+
if (!this.container.fs.exists(this.toolsModulePath)) {
|
|
504
|
+
return this.mergeOptionTools(tools)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Ensure virtual modules (zod, @soederpop/luca, etc.) are seeded so tools
|
|
508
|
+
// files outside the project tree can resolve them through the VM
|
|
509
|
+
if (this.container.features.has('helpers')) {
|
|
510
|
+
const helpers = this.container.feature('helpers') as any
|
|
511
|
+
helpers.seedVirtualModules()
|
|
512
|
+
}
|
|
513
|
+
|
|
429
514
|
const vm = this.container.feature('vm')
|
|
430
515
|
|
|
431
516
|
let moduleExports: Record<string, any>
|
|
@@ -441,7 +526,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
441
526
|
console.error(`Failed to load tools from ${this.toolsModulePath}`)
|
|
442
527
|
console.error(`There may be a syntax error in this file. Please check it.`)
|
|
443
528
|
console.error(err.message || err)
|
|
444
|
-
return tools
|
|
529
|
+
return this.mergeOptionTools(tools)
|
|
445
530
|
}
|
|
446
531
|
|
|
447
532
|
if (Object.keys(moduleExports).length) {
|
|
@@ -472,7 +557,14 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
472
557
|
}
|
|
473
558
|
}
|
|
474
559
|
|
|
475
|
-
|
|
560
|
+
return this.mergeOptionTools(tools)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Merge tools provided via constructor options into the tool map.
|
|
565
|
+
* This allows runtime-created assistants to define tools entirely via options.
|
|
566
|
+
*/
|
|
567
|
+
private mergeOptionTools(tools: Record<string, ConversationTool>): Record<string, ConversationTool> {
|
|
476
568
|
if (this.options.tools) {
|
|
477
569
|
const optionSchemas = this.options.schemas || {}
|
|
478
570
|
|
|
@@ -513,6 +605,12 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
513
605
|
*/
|
|
514
606
|
loadHooks(): Record<string, (...args: any[]) => any> {
|
|
515
607
|
const hooks: Record<string, (...args: any[]) => any> = {}
|
|
608
|
+
|
|
609
|
+
// Skip loading if no hooks file exists (runtime-created assistants)
|
|
610
|
+
if (!this.container.fs.exists(this.hooksModulePath)) {
|
|
611
|
+
return hooks
|
|
612
|
+
}
|
|
613
|
+
|
|
516
614
|
const vm = this.container.feature('vm')
|
|
517
615
|
|
|
518
616
|
let moduleExports: Record<string, any>
|
|
@@ -626,7 +724,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
626
724
|
* @returns this, for chaining
|
|
627
725
|
*/
|
|
628
726
|
resumeThread(threadId: string): this {
|
|
629
|
-
this.
|
|
727
|
+
this.state.set('resumeThreadId', threadId)
|
|
630
728
|
return this
|
|
631
729
|
}
|
|
632
730
|
|
|
@@ -659,7 +757,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
659
757
|
const mode = this.options.historyMode || 'lifecycle'
|
|
660
758
|
if (mode === 'lifecycle') return
|
|
661
759
|
|
|
662
|
-
const threadId = this.
|
|
760
|
+
const threadId = (this.state.get('resumeThreadId') as string | undefined) || this.buildThreadId(mode)
|
|
663
761
|
this.state.set('threadId', threadId)
|
|
664
762
|
|
|
665
763
|
const existing = await this.conversationHistory.findByThread(threadId)
|
|
@@ -670,7 +768,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
670
768
|
|
|
671
769
|
// Swap in fresh system prompt if it changed
|
|
672
770
|
if (messages.length > 0 && (messages[0]!.role === 'system' || messages[0]!.role === 'developer')) {
|
|
673
|
-
messages[0] = { role: messages[0]!.role, content: this.
|
|
771
|
+
messages[0] = { role: messages[0]!.role, content: this.systemPrompt }
|
|
674
772
|
}
|
|
675
773
|
|
|
676
774
|
this.conversation.state.set('id', existing.id)
|
|
@@ -699,7 +797,8 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
699
797
|
|
|
700
798
|
private bindHooksToEvents() {
|
|
701
799
|
const assistant = this
|
|
702
|
-
|
|
800
|
+
const hooks = (this.state.get('hooks') || {}) as Record<string, (...args: any[]) => any>
|
|
801
|
+
for (const [eventName, hookFn] of Object.entries(hooks)) {
|
|
703
802
|
if (Assistant.lifecycleHooks.has(eventName)) continue
|
|
704
803
|
this.on(eventName as any, (...args: any[]) => {
|
|
705
804
|
this.emit('hookFired', eventName)
|
|
@@ -720,17 +819,19 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
720
819
|
if (this.isStarted) return this
|
|
721
820
|
|
|
722
821
|
// Wait for any async .use() plugins to finish before starting
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
822
|
+
const pending = this.state.get('pendingPlugins') as Promise<void>[]
|
|
823
|
+
if (pending.length) {
|
|
824
|
+
await Promise.all(pending)
|
|
825
|
+
this.state.set('pendingPlugins', [])
|
|
726
826
|
}
|
|
727
827
|
|
|
728
828
|
// Allow hooks.ts to export a formatSystemPrompt(assistant, prompt) => string
|
|
729
829
|
// that transforms the system prompt before the conversation is created.
|
|
730
|
-
|
|
731
|
-
|
|
830
|
+
const hooks = (this.state.get('hooks') || {}) as Record<string, (...args: any[]) => any>
|
|
831
|
+
if (hooks.formatSystemPrompt) {
|
|
832
|
+
const result = await hooks.formatSystemPrompt(this, this.systemPrompt)
|
|
732
833
|
if (typeof result === 'string') {
|
|
733
|
-
this.
|
|
834
|
+
this.state.set('systemPrompt', result)
|
|
734
835
|
}
|
|
735
836
|
}
|
|
736
837
|
|
|
@@ -758,6 +859,13 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
758
859
|
if (mode === 'daily' || mode === 'persistent') {
|
|
759
860
|
(this.conversation.options as any).autoCompact = true
|
|
760
861
|
}
|
|
862
|
+
|
|
863
|
+
this.on('toolsChanged', () => {
|
|
864
|
+
const conv = this.state.get('conversation') as Conversation | null
|
|
865
|
+
if (conv) {
|
|
866
|
+
conv.updateTools(this.tools)
|
|
867
|
+
}
|
|
868
|
+
})
|
|
761
869
|
|
|
762
870
|
this.state.set('started', true)
|
|
763
871
|
this.emit('started')
|
|
@@ -818,6 +926,53 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
818
926
|
|
|
819
927
|
return this.conversation.save(opts)
|
|
820
928
|
}
|
|
929
|
+
|
|
930
|
+
// -- Subagent API --
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Names of assistants available as subagents, discovered via the assistantsManager.
|
|
934
|
+
*
|
|
935
|
+
* @returns {string[]} Available assistant names
|
|
936
|
+
*/
|
|
937
|
+
get availableSubagents(): string[] {
|
|
938
|
+
try {
|
|
939
|
+
const manager = this.container.feature('assistantsManager')
|
|
940
|
+
return manager.available
|
|
941
|
+
} catch {
|
|
942
|
+
return []
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Get or create a subagent assistant. Uses the assistantsManager to discover
|
|
948
|
+
* and create the assistant, then caches the instance for reuse across tool calls.
|
|
949
|
+
*
|
|
950
|
+
* @param id - The assistant name (e.g. 'codingAssistant')
|
|
951
|
+
* @param options - Additional options to pass to the assistant constructor
|
|
952
|
+
* @returns {Promise<Assistant>} The subagent assistant instance, started and ready
|
|
953
|
+
*
|
|
954
|
+
* @example
|
|
955
|
+
* ```typescript
|
|
956
|
+
* const researcher = await assistant.subagent('codingAssistant')
|
|
957
|
+
* const answer = await researcher.ask('Find all usages of container.feature("fs")')
|
|
958
|
+
* ```
|
|
959
|
+
*/
|
|
960
|
+
async subagent(id: string, options: Record<string, any> = {}): Promise<Assistant> {
|
|
961
|
+
const subagents = (this.state.get('subagents') || {}) as Record<string, Assistant>
|
|
962
|
+
if (subagents[id]) return subagents[id]
|
|
963
|
+
|
|
964
|
+
const manager = this.container.feature('assistantsManager')
|
|
965
|
+
|
|
966
|
+
if (!manager.state.get('discovered')) {
|
|
967
|
+
await manager.discover()
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
const instance = manager.create(id, options)
|
|
971
|
+
await instance.start()
|
|
972
|
+
|
|
973
|
+
this.state.set('subagents', { ...subagents, [id]: instance })
|
|
974
|
+
return instance
|
|
975
|
+
}
|
|
821
976
|
}
|
|
822
977
|
|
|
823
978
|
export default Assistant
|