@soederpop/luca 0.0.5 → 0.0.7
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/CLAUDE.md +10 -1
- package/bun.lock +1 -1
- package/commands/build-bootstrap.ts +78 -0
- package/commands/build-scaffolds.ts +24 -2
- package/commands/try-all-challenges.ts +543 -0
- package/commands/try-challenge.ts +100 -0
- package/docs/README.md +52 -80
- package/docs/TABLE-OF-CONTENTS.md +82 -51
- package/docs/apis/clients/elevenlabs.md +232 -8
- package/docs/apis/clients/graph.md +59 -8
- package/docs/apis/clients/openai.md +362 -2
- package/docs/apis/clients/rest.md +122 -2
- package/docs/apis/clients/websocket.md +71 -17
- package/docs/apis/features/agi/assistant.md +9 -3
- package/docs/apis/features/agi/assistants-manager.md +2 -2
- package/docs/apis/features/agi/claude-code.md +153 -14
- package/docs/apis/features/agi/conversation-history.md +15 -3
- package/docs/apis/features/agi/conversation.md +133 -20
- package/docs/apis/features/agi/openai-codex.md +90 -12
- package/docs/apis/features/agi/skills-library.md +23 -5
- package/docs/apis/features/node/container-link.md +59 -0
- package/docs/apis/features/node/content-db.md +1 -1
- package/docs/apis/features/node/disk-cache.md +1 -1
- package/docs/apis/features/node/dns.md +1 -0
- package/docs/apis/features/node/docker.md +2 -1
- package/docs/apis/features/node/esbuild.md +4 -3
- package/docs/apis/features/node/file-manager.md +13 -4
- package/docs/apis/features/node/fs.md +726 -171
- package/docs/apis/features/node/git.md +1 -0
- package/docs/apis/features/node/google-auth.md +23 -4
- package/docs/apis/features/node/google-calendar.md +14 -2
- package/docs/apis/features/node/google-docs.md +15 -2
- package/docs/apis/features/node/google-drive.md +21 -3
- package/docs/apis/features/node/google-sheets.md +14 -2
- package/docs/apis/features/node/grep.md +2 -0
- package/docs/apis/features/node/helpers.md +29 -0
- package/docs/apis/features/node/ink.md +2 -2
- package/docs/apis/features/node/networking.md +39 -4
- package/docs/apis/features/node/os.md +28 -0
- package/docs/apis/features/node/postgres.md +26 -4
- package/docs/apis/features/node/proc.md +37 -28
- package/docs/apis/features/node/process-manager.md +33 -5
- package/docs/apis/features/node/repl.md +1 -1
- package/docs/apis/features/node/runpod.md +1 -0
- package/docs/apis/features/node/secure-shell.md +7 -0
- package/docs/apis/features/node/semantic-search.md +12 -5
- package/docs/apis/features/node/sqlite.md +26 -4
- package/docs/apis/features/node/telegram.md +30 -5
- package/docs/apis/features/node/tts.md +17 -2
- package/docs/apis/features/node/ui.md +1 -1
- package/docs/apis/features/node/vault.md +4 -9
- package/docs/apis/features/node/vm.md +3 -12
- package/docs/apis/features/node/window-manager.md +128 -20
- package/docs/apis/features/web/asset-loader.md +13 -1
- package/docs/apis/features/web/container-link.md +59 -0
- package/docs/apis/features/web/esbuild.md +4 -3
- package/docs/apis/features/web/helpers.md +29 -0
- package/docs/apis/features/web/network.md +16 -2
- package/docs/apis/features/web/speech.md +16 -2
- package/docs/apis/features/web/vault.md +4 -9
- package/docs/apis/features/web/vm.md +3 -12
- package/docs/apis/features/web/voice.md +18 -1
- package/docs/apis/servers/express.md +18 -2
- package/docs/apis/servers/mcp.md +29 -4
- package/docs/apis/servers/websocket.md +34 -6
- package/docs/bootstrap/CLAUDE.md +100 -0
- package/docs/bootstrap/SKILL.md +222 -0
- package/docs/bootstrap/templates/about-command.ts +41 -0
- package/docs/bootstrap/templates/docs-models.ts +22 -0
- package/docs/bootstrap/templates/docs-readme.md +43 -0
- package/docs/bootstrap/templates/example-feature.ts +53 -0
- package/docs/bootstrap/templates/health-endpoint.ts +15 -0
- package/docs/bootstrap/templates/luca-cli.ts +25 -0
- package/docs/challenges/caching-proxy.md +16 -0
- package/docs/challenges/content-db-round-trip.md +14 -0
- package/docs/challenges/custom-command.md +9 -0
- package/docs/challenges/file-watcher-pipeline.md +11 -0
- package/docs/challenges/grep-audit-report.md +15 -0
- package/docs/challenges/multi-feature-dashboard.md +14 -0
- package/docs/challenges/process-orchestrator.md +17 -0
- package/docs/challenges/rest-api-server-with-client.md +12 -0
- package/docs/challenges/script-runner-with-vm.md +11 -0
- package/docs/challenges/simple-rest-api.md +15 -0
- package/docs/challenges/websocket-serve-and-client.md +11 -0
- package/docs/challenges/yaml-config-system.md +14 -0
- package/docs/command-system-overhaul.md +94 -0
- package/docs/examples/assistant/CORE.md +18 -0
- package/docs/examples/assistant/hooks.ts +3 -0
- package/docs/examples/assistant/tools.ts +10 -0
- package/docs/examples/window-manager-layouts.md +180 -0
- package/docs/in-memory-fs.md +4 -0
- package/docs/models.ts +13 -10
- package/docs/philosophy.md +4 -3
- package/docs/reports/console-hmr-design.md +170 -0
- package/docs/reports/helper-semantic-search.md +72 -0
- package/docs/scaffolds/client.md +29 -20
- package/docs/scaffolds/command.md +64 -50
- package/docs/scaffolds/endpoint.md +31 -36
- package/docs/scaffolds/feature.md +28 -18
- package/docs/scaffolds/selector.md +91 -0
- package/docs/scaffolds/server.md +18 -9
- package/docs/selectors.md +115 -0
- package/docs/sessions/custom-command/attempt-log-2.md +195 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-1.md +728 -0
- package/docs/sessions/file-watcher-pipeline/attempt-log-2.md +555 -0
- package/docs/sessions/grep-audit-report/attempt-log-1.md +289 -0
- package/docs/sessions/multi-feature-dashboard/attempt-log-2.md +679 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-1.md +1 -0
- package/docs/sessions/rest-api-server-with-client/attempt-log-3.md +920 -0
- package/docs/sessions/simple-rest-api/attempt-log-1.md +593 -0
- package/docs/sessions/websocket-serve-and-client/attempt-log-2.md +995 -0
- package/docs/tutorials/00-bootstrap.md +148 -0
- package/docs/tutorials/07-endpoints.md +7 -7
- package/docs/tutorials/08-commands.md +153 -72
- package/luca.cli.ts +3 -0
- package/package.json +6 -5
- package/public/index.html +1430 -0
- package/scripts/examples/using-ollama.ts +2 -1
- package/scripts/update-introspection-data.ts +2 -2
- package/src/agi/endpoints/experts.ts +1 -1
- package/src/agi/features/assistant.ts +7 -0
- package/src/agi/features/assistants-manager.ts +5 -5
- package/src/agi/features/claude-code.ts +263 -3
- package/src/agi/features/conversation-history.ts +7 -1
- package/src/agi/features/conversation.ts +26 -3
- package/src/agi/features/openai-codex.ts +26 -2
- package/src/agi/features/openapi.ts +6 -1
- package/src/agi/features/skills-library.ts +9 -1
- package/src/bootstrap/generated.ts +540 -0
- package/src/cli/cli.ts +64 -21
- package/src/client.ts +23 -357
- package/src/clients/civitai/index.ts +1 -1
- package/src/clients/client-template.ts +1 -1
- package/src/clients/comfyui/index.ts +13 -2
- package/src/clients/elevenlabs/index.ts +2 -1
- package/src/clients/graph.ts +87 -0
- package/src/clients/openai/index.ts +10 -1
- package/src/clients/rest.ts +207 -0
- package/src/clients/websocket.ts +176 -0
- package/src/command.ts +281 -34
- package/src/commands/bootstrap.ts +181 -0
- package/src/commands/chat.ts +5 -4
- package/src/commands/describe.ts +225 -2
- package/src/commands/help.ts +35 -9
- package/src/commands/index.ts +3 -0
- package/src/commands/introspect.ts +92 -2
- package/src/commands/prompt.ts +5 -6
- package/src/commands/run.ts +33 -10
- package/src/commands/save-api-docs.ts +49 -0
- package/src/commands/scaffold.ts +169 -23
- package/src/commands/select.ts +94 -0
- package/src/commands/serve.ts +10 -1
- package/src/container.ts +15 -0
- package/src/endpoint.ts +19 -0
- package/src/graft.ts +181 -0
- package/src/introspection/generated.agi.ts +12458 -8968
- package/src/introspection/generated.node.ts +10573 -7145
- package/src/introspection/generated.web.ts +1 -1
- package/src/introspection/index.ts +26 -0
- package/src/node/container.ts +6 -7
- package/src/node/features/content-db.ts +49 -2
- package/src/node/features/disk-cache.ts +16 -9
- package/src/node/features/dns.ts +16 -3
- package/src/node/features/docker.ts +16 -4
- package/src/node/features/esbuild.ts +20 -0
- package/src/node/features/file-manager.ts +184 -29
- package/src/node/features/fs.ts +704 -248
- package/src/node/features/git.ts +21 -8
- package/src/node/features/grep.ts +23 -3
- package/src/node/features/helpers.ts +372 -43
- package/src/node/features/networking.ts +39 -4
- package/src/node/features/opener.ts +28 -15
- package/src/node/features/os.ts +76 -0
- package/src/node/features/port-exposer.ts +11 -1
- package/src/node/features/postgres.ts +17 -1
- package/src/node/features/proc.ts +4 -1
- package/src/node/features/python.ts +63 -14
- package/src/node/features/repl.ts +11 -7
- package/src/node/features/runpod.ts +16 -3
- package/src/node/features/secure-shell.ts +27 -2
- package/src/node/features/semantic-search.ts +12 -1
- package/src/node/features/ui.ts +5 -69
- package/src/node/features/vm.ts +17 -0
- package/src/node/features/window-manager.ts +68 -20
- package/src/node.ts +5 -0
- package/src/scaffolds/generated.ts +492 -290
- package/src/scaffolds/template.ts +9 -0
- package/src/schemas/base.ts +46 -5
- package/src/selector.ts +282 -0
- package/src/server.ts +11 -0
- package/src/servers/express.ts +27 -12
- package/src/servers/socket.ts +45 -11
- package/src/web/clients/socket.ts +4 -1
- package/src/web/container.ts +2 -1
- package/src/web/features/network.ts +7 -1
- package/src/web/features/voice-recognition.ts +16 -1
- package/test/clients-servers.test.ts +2 -1
- package/test/command.test.ts +267 -0
- package/test-integration/assistants-manager.test.ts +10 -20
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/1b/b5/c75b28794f00f94c4d609a98978e9420e9b7146d204a7fbf5b0b30477292581705d207c0100dabaac27eef540aaaece3374af75104a93219d4ec8bfb44e7 +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/da/df/1d90ce4e042abeb035a197832c6d6893420a747a056be773eb00e4f745a037d505c8db13dde7d36b36b6b893addbb7df0f5fe9f0c13e665f20056447318b +1 -0
- package/tmp/.cache/luca-disk-cache/content-v2/sha512/ed/04/e1d0c2a58c2db29b3921ca2affb3ea4febe831c53b38ebc21019fb799823aba6ed5b4611873d2cd25d422d49955b852a9c326da0d678899bc1c2c2960901 +1 -0
- package/tmp/.cache/luca-disk-cache/index-v5/00/13/572aa4c9a94f99eda999695d050cdd0ca7fe2d23a50af03234d4c8ce0791 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/75/a9/cb61dc0f0589e8ec10a9aca27b834bc73884c479941042d22a2b22324cd3 +2 -0
- package/tmp/.cache/luca-disk-cache/index-v5/9f/0f/8b1f915ee64cfff7667dd96acd7a5ac0a96aa91a346e19cefd45909a9c9c +2 -0
- package/docs/apis/features/node/launcher-app-command-listener.md +0 -145
- package/docs/examples/launcher-app-command-listener.md +0 -120
- package/docs/tasks/web-container-helper-discovery.md +0 -71
- package/docs/todos.md +0 -1
- package/scripts/test-command-listener.ts +0 -123
- package/src/node/features/launcher-app-command-listener.ts +0 -389
|
@@ -2,7 +2,8 @@ import container from '@soederpop/luca/agi'
|
|
|
2
2
|
|
|
3
3
|
const conversation = container.feature('conversation', {
|
|
4
4
|
local: true,
|
|
5
|
-
model: "
|
|
5
|
+
model: "qwen/qwen3-coder-30b",
|
|
6
|
+
api: "responses"
|
|
6
7
|
})
|
|
7
8
|
|
|
8
9
|
const response = await conversation.ask('What model am I using?')
|
|
@@ -14,7 +14,7 @@ import { NodeContainer } from '../src/node/container.js';
|
|
|
14
14
|
const targets = [
|
|
15
15
|
{
|
|
16
16
|
name: 'node',
|
|
17
|
-
src: ['src/node/features', 'src/servers', 'src/container.ts', 'src/node/container.ts'],
|
|
17
|
+
src: ['src/node/features', 'src/clients', 'src/servers', 'src/container.ts', 'src/node/container.ts'],
|
|
18
18
|
outputPath: 'src/introspection/generated.node.ts',
|
|
19
19
|
},
|
|
20
20
|
{
|
|
@@ -24,7 +24,7 @@ const targets = [
|
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
name: 'agi',
|
|
27
|
-
src: ['src/node/features', 'src/servers', 'src/agi/features', 'src/container.ts', 'src/node/container.ts', 'src/agi/container.server.ts'],
|
|
27
|
+
src: ['src/node/features', 'src/clients', 'src/servers', 'src/agi/features', 'src/container.ts', 'src/node/container.ts', 'src/agi/container.server.ts'],
|
|
28
28
|
outputPath: 'src/introspection/generated.agi.ts',
|
|
29
29
|
},
|
|
30
30
|
];
|
|
@@ -19,7 +19,7 @@ export async function get(_parameters: any, ctx: EndpointContext) {
|
|
|
19
19
|
|
|
20
20
|
for (const relativePath of promptFiles) {
|
|
21
21
|
const name = relativePath.split('/')[1]
|
|
22
|
-
const prompt =
|
|
22
|
+
const prompt = await fs.readFileAsync(container.paths.resolve(relativePath))
|
|
23
23
|
const lines = prompt.split('\n').filter((l: string) => l.trim())
|
|
24
24
|
const title = lines[0]?.replace(/^#+\s*/, '') || name
|
|
25
25
|
const description = lines[1] || ''
|
|
@@ -65,6 +65,8 @@ export const AssistantOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
65
65
|
|
|
66
66
|
maxTokens: z.number().optional().describe('Maximum number of output tokens per completion'),
|
|
67
67
|
|
|
68
|
+
local: z.boolean().default(false).describe('Whether to use our local models for this'),
|
|
69
|
+
|
|
68
70
|
/** History persistence mode: lifecycle (ephemeral), daily (auto-resume per day), persistent (single long-running thread), session (unique per run, resumable) */
|
|
69
71
|
historyMode: z.enum(['lifecycle', 'daily', 'persistent', 'session']).optional().describe('Conversation history persistence mode'),
|
|
70
72
|
})
|
|
@@ -110,6 +112,10 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
110
112
|
return super.container as AGIContainer
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
get name() {
|
|
116
|
+
return this.resolvedFolder.split('/').pop()
|
|
117
|
+
}
|
|
118
|
+
|
|
113
119
|
/** The absolute resolved path to the assistant folder. */
|
|
114
120
|
get resolvedFolder(): string {
|
|
115
121
|
return this.container.paths.resolve(this.options.folder)
|
|
@@ -203,6 +209,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
203
209
|
if (!this._conversation) {
|
|
204
210
|
this._conversation = this.container.feature('conversation', {
|
|
205
211
|
model: this.options.model || 'gpt-5.2',
|
|
212
|
+
local: !!this.options.local,
|
|
206
213
|
tools: this._tools || this.loadTools(),
|
|
207
214
|
...(this.options.maxTokens ? { maxTokens: this.options.maxTokens } : {}),
|
|
208
215
|
history: [
|
|
@@ -96,9 +96,9 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
96
96
|
private _entries: Map<string, AssistantEntry> = new Map()
|
|
97
97
|
private _instances: Map<string, Assistant> = new Map()
|
|
98
98
|
|
|
99
|
-
override afterInitialize() {
|
|
99
|
+
override async afterInitialize() {
|
|
100
100
|
if (this.options.autoDiscover) {
|
|
101
|
-
this.discover()
|
|
101
|
+
await this.discover()
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
@@ -107,13 +107,13 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
107
107
|
* using the fileManager. Each directory containing a CORE.md is
|
|
108
108
|
* treated as an assistant definition.
|
|
109
109
|
*
|
|
110
|
-
* @returns {this} This instance, for chaining
|
|
110
|
+
* @returns {Promise<this>} This instance, for chaining
|
|
111
111
|
*/
|
|
112
|
-
discover(): this {
|
|
112
|
+
async discover(): Promise<this> {
|
|
113
113
|
const { fs, paths } = this.container
|
|
114
114
|
const fileManager = this.container.feature('fileManager') as any
|
|
115
115
|
|
|
116
|
-
fileManager.start()
|
|
116
|
+
await fileManager.start()
|
|
117
117
|
|
|
118
118
|
this._entries.clear()
|
|
119
119
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
4
4
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
5
5
|
import { Feature } from '@soederpop/luca/feature'
|
|
6
6
|
|
|
@@ -136,6 +136,8 @@ export const FileLogLevelSchema = z.enum(['verbose', 'normal', 'minimal']).descr
|
|
|
136
136
|
export type FileLogLevel = z.infer<typeof FileLogLevelSchema>
|
|
137
137
|
|
|
138
138
|
export const ClaudeCodeOptionsSchema = FeatureOptionsSchema.extend({
|
|
139
|
+
/** Claude CLI session ID to resume by default. When set, subsequent run()/start() calls will resume this session unless overridden. */
|
|
140
|
+
session: z.string().optional().describe('Claude CLI session ID to resume by default'),
|
|
139
141
|
/** Path to the claude CLI binary. Defaults to 'claude'. */
|
|
140
142
|
claudePath: z.string().optional().describe('Path to the claude CLI binary'),
|
|
141
143
|
/** Default model to use for sessions. */
|
|
@@ -182,6 +184,21 @@ export const ClaudeCodeOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
182
184
|
skillsFolders: z.array(z.string()).optional().describe('Directories containing Claude Code skills to load into sessions'),
|
|
183
185
|
})
|
|
184
186
|
|
|
187
|
+
export const ClaudeCodeEventsSchema = FeatureEventsSchema.extend({
|
|
188
|
+
'session:start': z.tuple([z.object({ sessionId: z.string(), prompt: z.string() })]).describe('Fired when a new Claude Code session is spawned'),
|
|
189
|
+
'session:init': z.tuple([z.object({ sessionId: z.string(), init: z.any() })]).describe('Fired when the CLI emits its init system event'),
|
|
190
|
+
'session:event': z.tuple([z.object({ sessionId: z.string(), event: z.any() })]).describe('Fired for every parsed JSON event from the CLI stream'),
|
|
191
|
+
'session:stream': z.tuple([z.object({ sessionId: z.string(), streamEvent: z.any() })]).describe('Fired for stream_event type events from the CLI'),
|
|
192
|
+
'session:delta': z.tuple([z.object({ sessionId: z.string(), text: z.string(), role: z.string() })]).describe('Fired for each text delta from an assistant message'),
|
|
193
|
+
'session:message': z.tuple([z.object({ sessionId: z.string(), message: z.any() })]).describe('Fired when a complete assistant message is received'),
|
|
194
|
+
'session:result': z.tuple([z.object({ sessionId: z.string(), result: z.string() })]).describe('Fired when a session completes with a final result'),
|
|
195
|
+
'session:error': z.tuple([z.object({ sessionId: z.string(), error: z.any(), exitCode: z.number().optional() })]).describe('Fired when a session encounters an error'),
|
|
196
|
+
'session:abort': z.tuple([z.object({ sessionId: z.string() })]).describe('Fired when a session is aborted by the user'),
|
|
197
|
+
'session:warning': z.tuple([z.object({ sessionId: z.string(), message: z.string() })]).describe('Fired when the log reader encounters a warning'),
|
|
198
|
+
'session:log-error': z.tuple([z.object({ sessionId: z.string(), error: z.any() })]).describe('Fired when the log reader encounters an error'),
|
|
199
|
+
'session:parse-error': z.tuple([z.object({ sessionId: z.string(), line: z.string() })]).describe('Fired when a JSON line from the CLI cannot be parsed'),
|
|
200
|
+
}).describe('ClaudeCode events')
|
|
201
|
+
|
|
185
202
|
export type ClaudeCodeState = z.infer<typeof ClaudeCodeStateSchema>
|
|
186
203
|
export type ClaudeCodeOptions = z.infer<typeof ClaudeCodeOptionsSchema>
|
|
187
204
|
|
|
@@ -277,6 +294,7 @@ export interface RunOptions {
|
|
|
277
294
|
export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
|
|
278
295
|
static override stateSchema = ClaudeCodeStateSchema
|
|
279
296
|
static override optionsSchema = ClaudeCodeOptionsSchema
|
|
297
|
+
static override eventsSchema = ClaudeCodeEventsSchema
|
|
280
298
|
static override shortcut = 'features.claudeCode' as const
|
|
281
299
|
static override envVars = ['TMPDIR']
|
|
282
300
|
|
|
@@ -296,8 +314,17 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
|
|
|
296
314
|
*
|
|
297
315
|
* @returns {string} The path to the claude binary
|
|
298
316
|
*/
|
|
317
|
+
private _resolvedClaudePath: string | null = null
|
|
318
|
+
|
|
299
319
|
get claudePath(): string {
|
|
300
|
-
|
|
320
|
+
if (this.options.claudePath) return this.options.claudePath
|
|
321
|
+
if (this._resolvedClaudePath) return this._resolvedClaudePath
|
|
322
|
+
try {
|
|
323
|
+
this._resolvedClaudePath = this.container.feature('proc').resolveRealPath('claude')
|
|
324
|
+
} catch {
|
|
325
|
+
this._resolvedClaudePath = 'claude'
|
|
326
|
+
}
|
|
327
|
+
return this._resolvedClaudePath
|
|
301
328
|
}
|
|
302
329
|
|
|
303
330
|
/**
|
|
@@ -511,7 +538,8 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
|
|
|
511
538
|
|
|
512
539
|
if (configPaths.length) args.push('--mcp-config', ...configPaths)
|
|
513
540
|
|
|
514
|
-
|
|
541
|
+
const resumeSessionId = options.resumeSessionId ?? (options.sessionId ? undefined : this.options.session)
|
|
542
|
+
if (resumeSessionId) args.push('--resume', resumeSessionId)
|
|
515
543
|
if (options.continue) args.push('--continue')
|
|
516
544
|
if (options.dangerouslySkipPermissions) args.push('--dangerously-skip-permissions')
|
|
517
545
|
|
|
@@ -1082,6 +1110,238 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
|
|
|
1082
1110
|
}
|
|
1083
1111
|
}
|
|
1084
1112
|
|
|
1113
|
+
/**
|
|
1114
|
+
* The Claude CLI session ID of the most recently initialized session,
|
|
1115
|
+
* or the session set via the `session` option. Useful for resuming later.
|
|
1116
|
+
*
|
|
1117
|
+
* @returns {string | undefined} The Claude CLI session ID
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* ```typescript
|
|
1121
|
+
* const cc = container.feature('claudeCode')
|
|
1122
|
+
* await cc.run('Do something')
|
|
1123
|
+
* console.log(cc.sessionId) // the Claude CLI session ID
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
get sessionId(): string | undefined {
|
|
1127
|
+
// Check if a default session was set via options
|
|
1128
|
+
if (this.options.session) return this.options.session
|
|
1129
|
+
|
|
1130
|
+
// Find the most recently created session that has a Claude CLI sessionId
|
|
1131
|
+
const sessions = Object.values(this.state.current.sessions) as ClaudeSession[]
|
|
1132
|
+
if (sessions.length === 0) return undefined
|
|
1133
|
+
|
|
1134
|
+
// Return the last session's Claude CLI session ID
|
|
1135
|
+
const last = sessions[sessions.length - 1]
|
|
1136
|
+
return last?.sessionId
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Export session history as a readable markdown document.
|
|
1141
|
+
* Reads from a raw JSONL file (Claude CLI session log or this feature's NDJSON log)
|
|
1142
|
+
* so it works independently of in-memory state.
|
|
1143
|
+
*
|
|
1144
|
+
* Can also accept a local session ID to export from in-memory state as a fallback.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {string} [source] - Path to a JSONL file, a local session ID, or omit for the most recent session
|
|
1147
|
+
* @returns {Promise<string>} Markdown-formatted session history
|
|
1148
|
+
*
|
|
1149
|
+
* @example
|
|
1150
|
+
* ```typescript
|
|
1151
|
+
* // From a JSONL file (works without any prior state)
|
|
1152
|
+
* const md = await cc.sessionHistoryToMarkdown('/path/to/session.jsonl')
|
|
1153
|
+
*
|
|
1154
|
+
* // From the most recent in-memory session
|
|
1155
|
+
* const md = await cc.sessionHistoryToMarkdown()
|
|
1156
|
+
*
|
|
1157
|
+
* // From a specific local session ID
|
|
1158
|
+
* const md = await cc.sessionHistoryToMarkdown(localSessionId)
|
|
1159
|
+
* ```
|
|
1160
|
+
*/
|
|
1161
|
+
async sessionHistoryToMarkdown(source?: string): Promise<string> {
|
|
1162
|
+
// If source looks like a file path, read JSONL from disk
|
|
1163
|
+
if (source && (source.includes('/') || source.endsWith('.jsonl'))) {
|
|
1164
|
+
return this.jsonlToMarkdown(source)
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
// Otherwise try to resolve from in-memory state
|
|
1168
|
+
const sessionId = source || this.findLastSessionId()
|
|
1169
|
+
if (!sessionId) throw new Error('No session found. Pass a JSONL file path or run a session first.')
|
|
1170
|
+
|
|
1171
|
+
const session = this.state.current.sessions[sessionId] as ClaudeSession | undefined
|
|
1172
|
+
if (!session) throw new Error(`Session ${sessionId} not found in state. Pass a JSONL file path instead.`)
|
|
1173
|
+
|
|
1174
|
+
return this.sessionToMarkdown(session)
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Find the local ID of the most recent session.
|
|
1179
|
+
*/
|
|
1180
|
+
private findLastSessionId(): string | undefined {
|
|
1181
|
+
const ids = Object.keys(this.state.current.sessions)
|
|
1182
|
+
return ids.length > 0 ? ids[ids.length - 1] : undefined
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Parse a JSONL file and convert its events to markdown.
|
|
1187
|
+
*/
|
|
1188
|
+
private async jsonlToMarkdown(filePath: string): Promise<string> {
|
|
1189
|
+
const fs = this.container.feature('fs')
|
|
1190
|
+
const content = await fs.readFileAsync(filePath, 'utf-8')
|
|
1191
|
+
const lines = content.split('\n').filter((l: string) => l.trim())
|
|
1192
|
+
|
|
1193
|
+
const events: ClaudeEvent[] = []
|
|
1194
|
+
for (const line of lines) {
|
|
1195
|
+
try {
|
|
1196
|
+
const parsed = JSON.parse(line)
|
|
1197
|
+
// Support both raw Claude events and our wrapper format (which has a .data field)
|
|
1198
|
+
events.push(parsed.data ?? parsed)
|
|
1199
|
+
} catch {
|
|
1200
|
+
// skip malformed lines
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
return this.eventsToMarkdown(events, filePath)
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Convert a ClaudeSession (from state) to markdown.
|
|
1209
|
+
*/
|
|
1210
|
+
private sessionToMarkdown(session: ClaudeSession): string {
|
|
1211
|
+
const lines: string[] = []
|
|
1212
|
+
|
|
1213
|
+
lines.push(`# Session: ${session.id}`)
|
|
1214
|
+
if (session.sessionId) lines.push(`**Claude Session ID:** \`${session.sessionId}\``)
|
|
1215
|
+
lines.push(`**Status:** ${session.status}`)
|
|
1216
|
+
if (session.costUsd) lines.push(`**Cost:** $${session.costUsd.toFixed(4)}`)
|
|
1217
|
+
if (session.turns) lines.push(`**Turns:** ${session.turns}`)
|
|
1218
|
+
lines.push('')
|
|
1219
|
+
|
|
1220
|
+
lines.push(`## Prompt`)
|
|
1221
|
+
lines.push('')
|
|
1222
|
+
lines.push(session.prompt)
|
|
1223
|
+
lines.push('')
|
|
1224
|
+
|
|
1225
|
+
if (session.messages.length > 0) {
|
|
1226
|
+
lines.push(`## Conversation`)
|
|
1227
|
+
lines.push('')
|
|
1228
|
+
|
|
1229
|
+
for (const msg of session.messages) {
|
|
1230
|
+
this.renderAssistantMessage(lines, msg)
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (session.result) {
|
|
1235
|
+
lines.push(`## Result`)
|
|
1236
|
+
lines.push('')
|
|
1237
|
+
lines.push(session.result)
|
|
1238
|
+
lines.push('')
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
if (session.error) {
|
|
1242
|
+
lines.push(`## Error`)
|
|
1243
|
+
lines.push('')
|
|
1244
|
+
lines.push(`\`\`\`\n${session.error}\n\`\`\``)
|
|
1245
|
+
lines.push('')
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
return lines.join('\n')
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Convert raw Claude events to markdown.
|
|
1253
|
+
*/
|
|
1254
|
+
private eventsToMarkdown(events: ClaudeEvent[], source: string): string {
|
|
1255
|
+
const lines: string[] = []
|
|
1256
|
+
let sessionId: string | undefined
|
|
1257
|
+
let model: string | undefined
|
|
1258
|
+
let prompt: string | undefined
|
|
1259
|
+
let costUsd: number | undefined
|
|
1260
|
+
let turns: number | undefined
|
|
1261
|
+
let durationMs: number | undefined
|
|
1262
|
+
|
|
1263
|
+
// Extract metadata from init and result events
|
|
1264
|
+
for (const event of events) {
|
|
1265
|
+
if (event.type === 'system' && (event as any).subtype === 'init') {
|
|
1266
|
+
const init = event as ClaudeInitEvent
|
|
1267
|
+
sessionId = init.session_id
|
|
1268
|
+
model = init.model
|
|
1269
|
+
}
|
|
1270
|
+
if (event.type === 'result') {
|
|
1271
|
+
const result = event as ClaudeResultEvent
|
|
1272
|
+
costUsd = result.total_cost_usd
|
|
1273
|
+
turns = result.num_turns
|
|
1274
|
+
durationMs = result.duration_ms
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
lines.push(`# Session History`)
|
|
1279
|
+
lines.push(`**Source:** \`${source}\``)
|
|
1280
|
+
if (sessionId) lines.push(`**Session ID:** \`${sessionId}\``)
|
|
1281
|
+
if (model) lines.push(`**Model:** ${model}`)
|
|
1282
|
+
if (costUsd != null) lines.push(`**Cost:** $${costUsd.toFixed(4)}`)
|
|
1283
|
+
if (turns != null) lines.push(`**Turns:** ${turns}`)
|
|
1284
|
+
if (durationMs != null) lines.push(`**Duration:** ${(durationMs / 1000).toFixed(1)}s`)
|
|
1285
|
+
lines.push('')
|
|
1286
|
+
|
|
1287
|
+
lines.push(`## Conversation`)
|
|
1288
|
+
lines.push('')
|
|
1289
|
+
|
|
1290
|
+
for (const event of events) {
|
|
1291
|
+
if (event.type === 'assistant') {
|
|
1292
|
+
this.renderAssistantMessage(lines, event as ClaudeAssistantMessage)
|
|
1293
|
+
} else if (event.type === 'tool_result') {
|
|
1294
|
+
const tr = event as ClaudeToolResult
|
|
1295
|
+
lines.push(`<details>`)
|
|
1296
|
+
lines.push(`<summary>Tool Result (${tr.tool_use_id})</summary>`)
|
|
1297
|
+
lines.push('')
|
|
1298
|
+
lines.push('```')
|
|
1299
|
+
lines.push(tr.content.length > 2000 ? tr.content.slice(0, 2000) + '\n... (truncated)' : tr.content)
|
|
1300
|
+
lines.push('```')
|
|
1301
|
+
lines.push(`</details>`)
|
|
1302
|
+
lines.push('')
|
|
1303
|
+
} else if (event.type === 'result') {
|
|
1304
|
+
const result = event as ClaudeResultEvent
|
|
1305
|
+
lines.push(`## Result`)
|
|
1306
|
+
lines.push('')
|
|
1307
|
+
if (result.is_error) {
|
|
1308
|
+
lines.push(`**Error:**`)
|
|
1309
|
+
lines.push(`\`\`\`\n${result.result}\n\`\`\``)
|
|
1310
|
+
} else {
|
|
1311
|
+
lines.push(result.result)
|
|
1312
|
+
}
|
|
1313
|
+
lines.push('')
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
return lines.join('\n')
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Render a single assistant message to markdown lines.
|
|
1322
|
+
*/
|
|
1323
|
+
private renderAssistantMessage(lines: string[], msg: ClaudeAssistantMessage): void {
|
|
1324
|
+
lines.push(`### Assistant`)
|
|
1325
|
+
if (msg.message?.usage) {
|
|
1326
|
+
const u = msg.message.usage
|
|
1327
|
+
lines.push(`*${u.input_tokens} in / ${u.output_tokens} out tokens*`)
|
|
1328
|
+
}
|
|
1329
|
+
lines.push('')
|
|
1330
|
+
|
|
1331
|
+
for (const block of msg.message?.content || []) {
|
|
1332
|
+
if (block.type === 'text') {
|
|
1333
|
+
lines.push(block.text)
|
|
1334
|
+
lines.push('')
|
|
1335
|
+
} else if (block.type === 'tool_use') {
|
|
1336
|
+
lines.push(`**Tool Use:** \`${block.name}\``)
|
|
1337
|
+
lines.push('```json')
|
|
1338
|
+
lines.push(JSON.stringify(block.input, null, 2))
|
|
1339
|
+
lines.push('```')
|
|
1340
|
+
lines.push('')
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1085
1345
|
/**
|
|
1086
1346
|
* Clean up any temp MCP config files created during sessions.
|
|
1087
1347
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
4
4
|
import { Feature } from '@soederpop/luca/feature'
|
|
5
5
|
import { NodeContainer, type DiskCache, type NodeFeatures } from '@soederpop/luca/node/container'
|
|
@@ -48,6 +48,11 @@ export const ConversationHistoryStateSchema = FeatureStateSchema.extend({
|
|
|
48
48
|
lastSaved: z.string().optional().describe('ISO timestamp of the last save operation'),
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
+
export const ConversationHistoryEventsSchema = FeatureEventsSchema.extend({
|
|
52
|
+
saved: z.tuple([z.string().describe('The conversation ID that was saved')]).describe('Fired after a conversation record is persisted'),
|
|
53
|
+
deleted: z.tuple([z.string().describe('The conversation ID that was deleted')]).describe('Fired after a conversation record is deleted'),
|
|
54
|
+
}).describe('ConversationHistory events')
|
|
55
|
+
|
|
51
56
|
export type ConversationHistoryOptions = z.infer<typeof ConversationHistoryOptionsSchema>
|
|
52
57
|
export type ConversationHistoryState = z.infer<typeof ConversationHistoryStateSchema>
|
|
53
58
|
|
|
@@ -76,6 +81,7 @@ export type ConversationHistoryState = z.infer<typeof ConversationHistoryStateSc
|
|
|
76
81
|
export class ConversationHistory extends Feature<ConversationHistoryState, ConversationHistoryOptions> {
|
|
77
82
|
static override stateSchema = ConversationHistoryStateSchema
|
|
78
83
|
static override optionsSchema = ConversationHistoryOptionsSchema
|
|
84
|
+
static override eventsSchema = ConversationHistoryEventsSchema
|
|
79
85
|
static override shortcut = 'features.conversationHistory' as const
|
|
80
86
|
|
|
81
87
|
static { Feature.register(this, 'conversationHistory') }
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
4
4
|
import { Feature } from '@soederpop/luca/feature'
|
|
5
5
|
import type { OpenAIClient } from '../../clients/openai';
|
|
@@ -96,6 +96,28 @@ export const ConversationStateSchema = FeatureStateSchema.extend({
|
|
|
96
96
|
contextWindow: z.number().describe('The context window size for the current model'),
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
+
export const ConversationEventsSchema = FeatureEventsSchema.extend({
|
|
100
|
+
userMessage: z.tuple([z.any().describe('The user message content (string or ContentPart[])')]).describe('Fired when a user message is added to the conversation'),
|
|
101
|
+
turnStart: z.tuple([z.object({ turn: z.number(), isFollowUp: z.boolean() })]).describe('Fired at the start of each completion turn'),
|
|
102
|
+
turnEnd: z.tuple([z.object({ turn: z.number(), hasToolCalls: z.boolean() })]).describe('Fired at the end of each completion turn'),
|
|
103
|
+
toolCallsStart: z.tuple([z.any().describe('Array of tool call objects from the model')]).describe('Fired when the model begins a batch of tool calls'),
|
|
104
|
+
toolCall: z.tuple([z.string().describe('Tool name'), z.any().describe('Parsed arguments object')]).describe('Fired before invoking a single tool handler'),
|
|
105
|
+
toolResult: z.tuple([z.string().describe('Tool name'), z.string().describe('Serialized result')]).describe('Fired after a tool handler returns successfully'),
|
|
106
|
+
toolError: z.tuple([z.string().describe('Tool name'), z.any().describe('Error object or message')]).describe('Fired when a tool handler throws or the tool is unknown'),
|
|
107
|
+
toolCallsEnd: z.tuple([]).describe('Fired after all tool calls in a turn have been executed'),
|
|
108
|
+
chunk: z.tuple([z.string().describe('Text delta from the stream')]).describe('Fired for each streaming text delta'),
|
|
109
|
+
preview: z.tuple([z.string().describe('Accumulated text so far')]).describe('Fired after each chunk with the full accumulated text'),
|
|
110
|
+
response: z.tuple([z.string().describe('Final accumulated response text')]).describe('Fired when the final text response is produced'),
|
|
111
|
+
responseCompleted: z.tuple([z.any().describe('The completed OpenAI Response object')]).describe('Fired when the Responses API stream completes'),
|
|
112
|
+
rawEvent: z.tuple([z.any().describe('Raw stream event from the API')]).describe('Fired for every raw event from the Responses API stream'),
|
|
113
|
+
mcpEvent: z.tuple([z.any().describe('MCP-related stream event')]).describe('Fired for MCP-related events from the Responses API'),
|
|
114
|
+
summarizeStart: z.tuple([]).describe('Fired before generating a conversation summary'),
|
|
115
|
+
summarizeEnd: z.tuple([z.string().describe('The generated summary text')]).describe('Fired after the summary is generated'),
|
|
116
|
+
compactStart: z.tuple([z.object({ messageCount: z.number(), keepRecent: z.number() })]).describe('Fired before compacting the conversation history'),
|
|
117
|
+
compactEnd: z.tuple([z.object({ summary: z.string(), removedCount: z.number(), estimatedTokens: z.number(), compactionCount: z.number() })]).describe('Fired after compaction completes'),
|
|
118
|
+
autoCompactTriggered: z.tuple([z.object({ estimated: z.number(), limit: z.number(), contextWindow: z.number() })]).describe('Fired when auto-compact kicks in because tokens exceeded the threshold'),
|
|
119
|
+
}).describe('Conversation events')
|
|
120
|
+
|
|
99
121
|
export type ConversationOptions = z.infer<typeof ConversationOptionsSchema>
|
|
100
122
|
export type ConversationState = z.infer<typeof ConversationStateSchema>
|
|
101
123
|
|
|
@@ -122,6 +144,7 @@ export type AskOptions = {
|
|
|
122
144
|
export class Conversation extends Feature<ConversationState, ConversationOptions> {
|
|
123
145
|
static override stateSchema = ConversationStateSchema
|
|
124
146
|
static override optionsSchema = ConversationOptionsSchema
|
|
147
|
+
static override eventsSchema = ConversationEventsSchema
|
|
125
148
|
static override shortcut = 'features.conversation' as const
|
|
126
149
|
|
|
127
150
|
static { Feature.register(this, 'conversation') }
|
|
@@ -438,11 +461,11 @@ export class Conversation extends Feature<ConversationState, ConversationOptions
|
|
|
438
461
|
let baseURL = this.options.clientOptions?.baseURL ? this.options.clientOptions.baseURL : undefined
|
|
439
462
|
|
|
440
463
|
if (this.options.local) {
|
|
441
|
-
baseURL = "http://localhost:
|
|
464
|
+
baseURL = "http://localhost:1234/v1"
|
|
442
465
|
}
|
|
443
466
|
|
|
444
467
|
return (this.container as any).client('openai', {
|
|
445
|
-
defaultModel: this.options.model || (this.options.local ?
|
|
468
|
+
defaultModel: this.options.model || (this.options.local ? this.options.model || "qwen/qwen3-coder-30b" : "gpt-5"),
|
|
446
469
|
...this.options.clientOptions,
|
|
447
470
|
...(baseURL ? { baseURL } : {}),
|
|
448
471
|
}) as OpenAIClient
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import { z } from 'zod'
|
|
3
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
4
4
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
5
5
|
import { Feature } from '@soederpop/luca/feature'
|
|
6
6
|
|
|
@@ -96,6 +96,20 @@ export const OpenAICodexOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
96
96
|
fullStdout: z.boolean().optional().describe('Do not truncate stdout/stderr from command outputs'),
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
+
export const OpenAICodexEventsSchema = FeatureEventsSchema.extend({
|
|
100
|
+
'session:start': z.tuple([z.object({ sessionId: z.string(), prompt: z.string() })]).describe('Fired when a new Codex session is spawned'),
|
|
101
|
+
'session:event': z.tuple([z.object({ sessionId: z.string(), event: z.any() })]).describe('Fired for every parsed JSON event from the Codex CLI stream'),
|
|
102
|
+
'session:delta': z.tuple([z.object({ sessionId: z.string(), text: z.string(), role: z.string() })]).describe('Fired for each text delta from an agent message'),
|
|
103
|
+
'session:message': z.tuple([z.object({ sessionId: z.string(), message: z.any() })]).describe('Fired when a complete agent message is received'),
|
|
104
|
+
'session:exec': z.tuple([z.object({ sessionId: z.string(), exec: z.any() })]).describe('Fired when a command execution item completes'),
|
|
105
|
+
'session:exec-start': z.tuple([z.object({ sessionId: z.string(), command: z.string() })]).describe('Fired when a command execution item starts'),
|
|
106
|
+
'session:reasoning': z.tuple([z.object({ sessionId: z.string(), text: z.string() })]).describe('Fired when a reasoning item is received'),
|
|
107
|
+
'session:result': z.tuple([z.object({ sessionId: z.string(), result: z.string() })]).describe('Fired when a session completes with a final result'),
|
|
108
|
+
'session:error': z.tuple([z.object({ sessionId: z.string(), error: z.any(), exitCode: z.number().optional() })]).describe('Fired when a session encounters an error'),
|
|
109
|
+
'session:abort': z.tuple([z.object({ sessionId: z.string() })]).describe('Fired when a session is aborted by the user'),
|
|
110
|
+
'session:parse-error': z.tuple([z.object({ sessionId: z.string(), line: z.string() })]).describe('Fired when a JSON line from the CLI cannot be parsed'),
|
|
111
|
+
}).describe('OpenAICodex events')
|
|
112
|
+
|
|
99
113
|
export type OpenAICodexState = z.infer<typeof OpenAICodexStateSchema>
|
|
100
114
|
export type OpenAICodexOptions = z.infer<typeof OpenAICodexOptionsSchema>
|
|
101
115
|
|
|
@@ -146,6 +160,7 @@ export interface CodexRunOptions {
|
|
|
146
160
|
export class OpenAICodex extends Feature<OpenAICodexState, OpenAICodexOptions> {
|
|
147
161
|
static override stateSchema = OpenAICodexStateSchema
|
|
148
162
|
static override optionsSchema = OpenAICodexOptionsSchema
|
|
163
|
+
static override eventsSchema = OpenAICodexEventsSchema
|
|
149
164
|
static override shortcut = 'features.openaiCodex' as const
|
|
150
165
|
|
|
151
166
|
static { Feature.register(this, 'openaiCodex') }
|
|
@@ -159,9 +174,18 @@ export class OpenAICodex extends Feature<OpenAICodexState, OpenAICodexOptions> {
|
|
|
159
174
|
}
|
|
160
175
|
}
|
|
161
176
|
|
|
177
|
+
private _resolvedCodexPath: string | null = null
|
|
178
|
+
|
|
162
179
|
/** @returns The path to the codex CLI binary, falling back to 'codex' on the PATH. */
|
|
163
180
|
get codexPath(): string {
|
|
164
|
-
|
|
181
|
+
if (this.options.codexPath) return this.options.codexPath
|
|
182
|
+
if (this._resolvedCodexPath) return this._resolvedCodexPath
|
|
183
|
+
try {
|
|
184
|
+
this._resolvedCodexPath = this.container.feature('proc').resolveRealPath('codex')
|
|
185
|
+
} catch {
|
|
186
|
+
this._resolvedCodexPath = 'codex'
|
|
187
|
+
}
|
|
188
|
+
return this._resolvedCodexPath
|
|
165
189
|
}
|
|
166
190
|
|
|
167
191
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Feature } from '../../feature.js'
|
|
2
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { z } from 'zod'
|
|
4
4
|
import { camelCase } from 'lodash-es'
|
|
5
5
|
|
|
@@ -20,6 +20,10 @@ export const OpenAPIOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
20
20
|
url: z.string().optional().describe('URL to the OpenAPI/Swagger spec or the API server base URL')
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
+
export const OpenAPIEventsSchema = FeatureEventsSchema.extend({
|
|
24
|
+
loaded: z.tuple([z.any().describe('The parsed OpenAPI spec object')]).describe('Fired after the spec is fetched and parsed'),
|
|
25
|
+
}).describe('OpenAPI events')
|
|
26
|
+
|
|
23
27
|
export type OpenAPIOptions = z.infer<typeof OpenAPIOptionsSchema>
|
|
24
28
|
export type OpenAPIState = z.infer<typeof OpenAPIStateSchema>
|
|
25
29
|
|
|
@@ -102,6 +106,7 @@ export class OpenAPI extends Feature<OpenAPIState, OpenAPIOptions> {
|
|
|
102
106
|
static override description = 'Load and inspect OpenAPI specs, convert endpoints to OpenAI tool/function definitions'
|
|
103
107
|
static override stateSchema = OpenAPIStateSchema
|
|
104
108
|
static override optionsSchema = OpenAPIOptionsSchema
|
|
109
|
+
static override eventsSchema = OpenAPIEventsSchema
|
|
105
110
|
|
|
106
111
|
static { Feature.register(this, 'openapi') }
|
|
107
112
|
|
|
@@ -4,7 +4,7 @@ import os from 'os'
|
|
|
4
4
|
import fs from 'fs/promises'
|
|
5
5
|
import yaml from 'js-yaml'
|
|
6
6
|
import { kebabCase } from 'lodash-es'
|
|
7
|
-
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
7
|
+
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
8
8
|
import { type AvailableFeatures, Feature } from '@soederpop/luca/feature'
|
|
9
9
|
import { Collection, defineModel } from 'contentbase'
|
|
10
10
|
import type { ConversationTool } from './conversation'
|
|
@@ -61,6 +61,13 @@ export const SkillsLibraryOptionsSchema = FeatureOptionsSchema.extend({
|
|
|
61
61
|
userSkillsPath: z.string().optional().describe('Path to user-level global skills directory'),
|
|
62
62
|
})
|
|
63
63
|
|
|
64
|
+
export const SkillsLibraryEventsSchema = FeatureEventsSchema.extend({
|
|
65
|
+
loaded: z.tuple([]).describe('Fired after both project and user skill collections are loaded'),
|
|
66
|
+
skillCreated: z.tuple([z.any().describe('The created SkillEntry object')]).describe('Fired after a new skill is written to disk'),
|
|
67
|
+
skillUpdated: z.tuple([z.any().describe('The updated SkillEntry object')]).describe('Fired after an existing skill is updated'),
|
|
68
|
+
skillRemoved: z.tuple([z.string().describe('The name of the removed skill')]).describe('Fired after a skill is deleted'),
|
|
69
|
+
}).describe('SkillsLibrary events')
|
|
70
|
+
|
|
64
71
|
export type SkillsLibraryState = z.infer<typeof SkillsLibraryStateSchema>
|
|
65
72
|
export type SkillsLibraryOptions = z.infer<typeof SkillsLibraryOptionsSchema>
|
|
66
73
|
|
|
@@ -91,6 +98,7 @@ export type SkillsLibraryOptions = z.infer<typeof SkillsLibraryOptionsSchema>
|
|
|
91
98
|
export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOptions> {
|
|
92
99
|
static override stateSchema = SkillsLibraryStateSchema
|
|
93
100
|
static override optionsSchema = SkillsLibraryOptionsSchema
|
|
101
|
+
static override eventsSchema = SkillsLibraryEventsSchema
|
|
94
102
|
static override shortcut = 'features.skillsLibrary' as const
|
|
95
103
|
|
|
96
104
|
static { Feature.register(this, 'skillsLibrary') }
|