@soederpop/luca 0.2.2 → 0.2.3
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/assistants/codingAssistant/ABOUT.md +3 -1
- package/assistants/codingAssistant/CORE.md +2 -4
- package/assistants/codingAssistant/hooks.ts +9 -10
- package/assistants/codingAssistant/tools.ts +9 -0
- package/assistants/inkbot/ABOUT.md +13 -2
- package/assistants/inkbot/CORE.md +278 -39
- package/assistants/inkbot/hooks.ts +0 -8
- package/assistants/inkbot/tools.ts +24 -18
- package/assistants/researcher/ABOUT.md +5 -0
- package/assistants/researcher/CORE.md +46 -0
- package/assistants/researcher/hooks.ts +16 -0
- package/assistants/researcher/tools.ts +237 -0
- package/commands/inkbot.ts +526 -194
- package/docs/examples/assistant-hooks-reference.ts +171 -0
- package/package.json +1 -1
- package/public/slides-ai-native.html +902 -0
- package/public/slides-intro.html +974 -0
- package/src/agi/features/assistant.ts +432 -62
- package/src/agi/features/conversation.ts +170 -10
- package/src/bootstrap/generated.ts +1 -1
- package/src/cli/build-info.ts +2 -2
- package/src/helper.ts +12 -3
- package/src/introspection/generated.agi.ts +1105 -873
- package/src/introspection/generated.node.ts +757 -757
- package/src/introspection/generated.web.ts +1 -1
- package/src/python/generated.ts +1 -1
- package/src/scaffolds/generated.ts +1 -1
- package/test/assistant-hooks.test.ts +306 -0
- package/test/assistant.test.ts +1 -1
- package/test/fork-and-research.test.ts +450 -0
- package/SPEC.md +0 -304
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example hooks.ts — showcases the assistant hook system.
|
|
3
|
+
*
|
|
4
|
+
* Every named export is a hook. Hooks are awaited via triggerHook(),
|
|
5
|
+
* so async work completes BEFORE the assistant proceeds.
|
|
6
|
+
*
|
|
7
|
+
* Available hooks:
|
|
8
|
+
*
|
|
9
|
+
* Lifecycle:
|
|
10
|
+
* created() — after prompt/tools/hooks load (before start)
|
|
11
|
+
* beforeStart() — before conversation wiring, blocks start()
|
|
12
|
+
* started() — conversation is ready, wire up tools here
|
|
13
|
+
* afterStart() — everything is live, blocks start() until done
|
|
14
|
+
* formatSystemPrompt(a, prompt) => string — rewrite the system prompt
|
|
15
|
+
*
|
|
16
|
+
* Ask flow:
|
|
17
|
+
* beforeInitialAsk(a, question, options) — first ask() only
|
|
18
|
+
* beforeAsk(a, question, options) => string? — every ask(), return rewrites question
|
|
19
|
+
* answered(a, result) — after response, before ask() returns
|
|
20
|
+
*
|
|
21
|
+
* Tool execution:
|
|
22
|
+
* beforeToolCall(a, ctx) — inspect/rewrite args, set ctx.skip to bypass
|
|
23
|
+
* afterToolCall(a, ctx) — inspect/rewrite result after execution
|
|
24
|
+
*
|
|
25
|
+
* Forwarded from conversation (awaited before the bus event):
|
|
26
|
+
* turnStart(a, info) turnEnd(a, info) chunk(a, delta)
|
|
27
|
+
* preview(a, text) response(a, text) toolCall(a, name, args)
|
|
28
|
+
* toolResult(a, name, result) toolError(a, name, error)
|
|
29
|
+
*
|
|
30
|
+
* The first argument is always the assistant instance.
|
|
31
|
+
* `assistant` and `container` are also available as globals (injected by the VM).
|
|
32
|
+
*/
|
|
33
|
+
import type { Assistant, AGIContainer } from '@soederpop/luca/agi'
|
|
34
|
+
|
|
35
|
+
declare global {
|
|
36
|
+
var assistant: Assistant
|
|
37
|
+
var container: AGIContainer
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Lifecycle ──────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Runs before the conversation is created. Good for async setup
|
|
44
|
+
* that needs to finish before the assistant is usable.
|
|
45
|
+
*/
|
|
46
|
+
export async function beforeStart() {
|
|
47
|
+
console.log(`[hooks] preparing ${assistant.assistantName}...`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Conversation is wired — register tools, plugins, extensions.
|
|
52
|
+
* This is the most common hook: equivalent to the old bus-based pattern.
|
|
53
|
+
*/
|
|
54
|
+
export function started() {
|
|
55
|
+
// Give the assistant shell tools
|
|
56
|
+
assistant.use(container.feature('codingTools'))
|
|
57
|
+
|
|
58
|
+
// Add write operations from fileTools
|
|
59
|
+
const fileTools = container.feature('fileTools')
|
|
60
|
+
assistant.use(fileTools.toTools({ only: ['editFile', 'writeFile'] }))
|
|
61
|
+
fileTools.setupToolsConsumer(assistant)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Everything is live. Good for loading state that depends on
|
|
66
|
+
* the conversation being fully initialized. Blocks start().
|
|
67
|
+
*/
|
|
68
|
+
export async function afterStart() {
|
|
69
|
+
// e.g. load a knowledge base into the system prompt
|
|
70
|
+
const fs = container.feature('fs')
|
|
71
|
+
const notesPath = assistant.paths.resolve('notes.md')
|
|
72
|
+
|
|
73
|
+
if (await fs.exists(notesPath)) {
|
|
74
|
+
const notes = await fs.readFile(notesPath)
|
|
75
|
+
assistant.addSystemPromptExtension('notes', `\n## Your Notes\n${notes}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Rewrite the system prompt before the conversation is created.
|
|
81
|
+
* Return the new prompt string.
|
|
82
|
+
*/
|
|
83
|
+
export async function formatSystemPrompt(_assistant: Assistant, prompt: string) {
|
|
84
|
+
const today = new Date().toLocaleDateString('en-US', {
|
|
85
|
+
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
|
86
|
+
})
|
|
87
|
+
return `${prompt}\n\nToday is ${today}.`
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Ask Flow ───────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Runs only on the very first ask(). Good for one-time greetings
|
|
94
|
+
* or loading context that depends on the first user message.
|
|
95
|
+
*/
|
|
96
|
+
export async function beforeInitialAsk(_assistant: Assistant, question: string) {
|
|
97
|
+
console.log(`[hooks] first message from user: "${question}"`)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Runs before every ask(). Return a string to rewrite the question.
|
|
102
|
+
* Useful for injecting context the model should see but the user
|
|
103
|
+
* shouldn't have to type every time.
|
|
104
|
+
*/
|
|
105
|
+
export async function beforeAsk(_assistant: Assistant, question: string) {
|
|
106
|
+
// Example: inject recent git context into every question
|
|
107
|
+
const proc = container.feature('proc')
|
|
108
|
+
const { stdout } = await proc.exec('git log --oneline -5 2>/dev/null || true')
|
|
109
|
+
|
|
110
|
+
if (stdout.trim()) {
|
|
111
|
+
return `${question}\n\n<context>\nRecent commits:\n${stdout.trim()}\n</context>`
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Fires after the model responds, before ask() returns.
|
|
117
|
+
* Good for logging, analytics, or auto-saving.
|
|
118
|
+
*/
|
|
119
|
+
export async function answered(_assistant: Assistant, result: string) {
|
|
120
|
+
console.log(`[hooks] response length: ${result.length} chars`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ─── Tool Execution ─────────────────────────────────────────────
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Fires before every tool call. The ctx object is mutable:
|
|
127
|
+
* ctx.name — tool name
|
|
128
|
+
* ctx.args — arguments (rewrite to modify)
|
|
129
|
+
* ctx.skip — set true to bypass execution
|
|
130
|
+
* ctx.result — set when skipping to provide a result
|
|
131
|
+
*/
|
|
132
|
+
export async function beforeToolCall(_assistant: Assistant, ctx: any) {
|
|
133
|
+
console.log(`[hooks] tool call: ${ctx.name}(${JSON.stringify(ctx.args)})`)
|
|
134
|
+
|
|
135
|
+
// Example: block dangerous commands
|
|
136
|
+
if (ctx.name === 'runCommand' && ctx.args.command?.includes('rm -rf')) {
|
|
137
|
+
ctx.skip = true
|
|
138
|
+
ctx.result = JSON.stringify({ error: 'Blocked: destructive command not allowed' })
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Fires after every tool call. The ctx object contains:
|
|
144
|
+
* ctx.name — tool name
|
|
145
|
+
* ctx.args — original arguments
|
|
146
|
+
* ctx.result — the result string (rewrite to modify what the model sees)
|
|
147
|
+
* ctx.error — error object if the tool threw
|
|
148
|
+
*/
|
|
149
|
+
export async function afterToolCall(_assistant: Assistant, ctx: any) {
|
|
150
|
+
if (ctx.error) {
|
|
151
|
+
console.error(`[hooks] tool ${ctx.name} failed:`, ctx.error.message)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Example: truncate huge tool outputs so they don't blow the context
|
|
155
|
+
if (ctx.result && ctx.result.length > 10000) {
|
|
156
|
+
const truncated = ctx.result.slice(0, 10000)
|
|
157
|
+
ctx.result = `${truncated}\n\n... (truncated from ${ctx.result.length} chars)`
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ─── Forwarded Conversation Events ──────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Fires at the start of each completion turn. turn > 1 means
|
|
165
|
+
* the model is continuing after tool calls.
|
|
166
|
+
*/
|
|
167
|
+
export function turnStart(_assistant: Assistant, info: { turn: number; isFollowUp: boolean }) {
|
|
168
|
+
if (info.isFollowUp) {
|
|
169
|
+
console.log(`[hooks] follow-up turn ${info.turn}`)
|
|
170
|
+
}
|
|
171
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soederpop/luca",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
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>",
|