@soederpop/luca 0.2.1 → 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.
Files changed (35) hide show
  1. package/.github/workflows/release.yaml +2 -0
  2. package/CNAME +1 -0
  3. package/assistants/codingAssistant/ABOUT.md +3 -1
  4. package/assistants/codingAssistant/CORE.md +2 -4
  5. package/assistants/codingAssistant/hooks.ts +9 -10
  6. package/assistants/codingAssistant/tools.ts +9 -0
  7. package/assistants/inkbot/ABOUT.md +13 -2
  8. package/assistants/inkbot/CORE.md +278 -39
  9. package/assistants/inkbot/hooks.ts +0 -8
  10. package/assistants/inkbot/tools.ts +24 -18
  11. package/assistants/researcher/ABOUT.md +5 -0
  12. package/assistants/researcher/CORE.md +46 -0
  13. package/assistants/researcher/hooks.ts +16 -0
  14. package/assistants/researcher/tools.ts +237 -0
  15. package/commands/inkbot.ts +526 -194
  16. package/docs/CNAME +1 -0
  17. package/docs/examples/assistant-hooks-reference.ts +171 -0
  18. package/index.html +1430 -0
  19. package/package.json +1 -1
  20. package/public/slides-ai-native.html +902 -0
  21. package/public/slides-intro.html +974 -0
  22. package/src/agi/features/assistant.ts +432 -62
  23. package/src/agi/features/conversation.ts +170 -10
  24. package/src/bootstrap/generated.ts +1 -1
  25. package/src/cli/build-info.ts +2 -2
  26. package/src/helper.ts +12 -3
  27. package/src/introspection/generated.agi.ts +1663 -644
  28. package/src/introspection/generated.node.ts +1637 -870
  29. package/src/introspection/generated.web.ts +1 -1
  30. package/src/python/generated.ts +1 -1
  31. package/src/scaffolds/generated.ts +1 -1
  32. package/test/assistant-hooks.test.ts +306 -0
  33. package/test/assistant.test.ts +1 -1
  34. package/test/fork-and-research.test.ts +450 -0
  35. package/SPEC.md +0 -304
package/docs/CNAME ADDED
@@ -0,0 +1 @@
1
+ luca-js.soederpop.com
@@ -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
+ }