@soederpop/luca 0.1.2 → 0.2.1
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/.github/workflows/release.yaml +167 -0
- package/CLAUDE.md +2 -0
- package/README.md +3 -0
- package/assistants/codingAssistant/ABOUT.md +3 -0
- package/assistants/codingAssistant/CORE.md +22 -17
- package/assistants/codingAssistant/hooks.ts +17 -4
- package/assistants/codingAssistant/tools.ts +1 -106
- package/assistants/inkbot/ABOUT.md +5 -0
- package/assistants/inkbot/CORE.md +71 -0
- package/assistants/inkbot/hooks.ts +14 -0
- package/assistants/inkbot/tools.ts +47 -0
- package/bun.lock +20 -4
- package/commands/inkbot.ts +353 -0
- package/commands/release.ts +75 -181
- package/dist/agi/container.server.d.ts +63 -0
- package/dist/agi/container.server.d.ts.map +1 -0
- package/dist/agi/endpoints/ask.d.ts +20 -0
- package/dist/agi/endpoints/ask.d.ts.map +1 -0
- package/dist/agi/endpoints/conversations/[id].d.ts +27 -0
- package/dist/agi/endpoints/conversations/[id].d.ts.map +1 -0
- package/dist/agi/endpoints/conversations.d.ts +18 -0
- package/dist/agi/endpoints/conversations.d.ts.map +1 -0
- package/dist/agi/endpoints/experts.d.ts +8 -0
- package/dist/agi/endpoints/experts.d.ts.map +1 -0
- package/dist/agi/feature.d.ts +9 -0
- package/dist/agi/feature.d.ts.map +1 -0
- package/dist/agi/features/assistant.d.ts +509 -0
- package/dist/agi/features/assistant.d.ts.map +1 -0
- package/dist/agi/features/assistants-manager.d.ts +236 -0
- package/dist/agi/features/assistants-manager.d.ts.map +1 -0
- package/dist/agi/features/autonomous-assistant.d.ts +281 -0
- package/dist/agi/features/autonomous-assistant.d.ts.map +1 -0
- package/dist/agi/features/browser-use.d.ts +479 -0
- package/dist/agi/features/browser-use.d.ts.map +1 -0
- package/dist/agi/features/claude-code.d.ts +824 -0
- package/dist/agi/features/claude-code.d.ts.map +1 -0
- package/dist/agi/features/conversation-history.d.ts +245 -0
- package/dist/agi/features/conversation-history.d.ts.map +1 -0
- package/dist/agi/features/conversation.d.ts +464 -0
- package/dist/agi/features/conversation.d.ts.map +1 -0
- package/dist/agi/features/docs-reader.d.ts +72 -0
- package/dist/agi/features/docs-reader.d.ts.map +1 -0
- package/dist/agi/features/file-tools.d.ts +110 -0
- package/dist/agi/features/file-tools.d.ts.map +1 -0
- package/dist/agi/features/luca-coder.d.ts +323 -0
- package/dist/agi/features/luca-coder.d.ts.map +1 -0
- package/dist/agi/features/openai-codex.d.ts +381 -0
- package/dist/agi/features/openai-codex.d.ts.map +1 -0
- package/dist/agi/features/openapi.d.ts +200 -0
- package/dist/agi/features/openapi.d.ts.map +1 -0
- package/dist/agi/features/skills-library.d.ts +167 -0
- package/dist/agi/features/skills-library.d.ts.map +1 -0
- package/dist/agi/index.d.ts +5 -0
- package/dist/agi/index.d.ts.map +1 -0
- package/dist/agi/lib/interceptor-chain.d.ts +44 -0
- package/dist/agi/lib/interceptor-chain.d.ts.map +1 -0
- package/dist/agi/lib/token-counter.d.ts +13 -0
- package/dist/agi/lib/token-counter.d.ts.map +1 -0
- package/dist/bootstrap/generated.d.ts +5 -0
- package/dist/bootstrap/generated.d.ts.map +1 -0
- package/dist/browser.d.ts +12 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/bus.d.ts +29 -0
- package/dist/bus.d.ts.map +1 -0
- package/dist/cli/build-info.d.ts +4 -0
- package/dist/cli/build-info.d.ts.map +1 -0
- package/dist/cli/cli.d.ts +3 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/client.d.ts +60 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/clients/civitai/index.d.ts +472 -0
- package/dist/clients/civitai/index.d.ts.map +1 -0
- package/dist/clients/client-template.d.ts +30 -0
- package/dist/clients/client-template.d.ts.map +1 -0
- package/dist/clients/comfyui/index.d.ts +281 -0
- package/dist/clients/comfyui/index.d.ts.map +1 -0
- package/dist/clients/elevenlabs/index.d.ts +197 -0
- package/dist/clients/elevenlabs/index.d.ts.map +1 -0
- package/dist/clients/graph.d.ts +64 -0
- package/dist/clients/graph.d.ts.map +1 -0
- package/dist/clients/openai/index.d.ts +247 -0
- package/dist/clients/openai/index.d.ts.map +1 -0
- package/dist/clients/rest.d.ts +92 -0
- package/dist/clients/rest.d.ts.map +1 -0
- package/dist/clients/supabase/index.d.ts +176 -0
- package/dist/clients/supabase/index.d.ts.map +1 -0
- package/dist/clients/websocket.d.ts +127 -0
- package/dist/clients/websocket.d.ts.map +1 -0
- package/dist/command.d.ts +163 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/commands/bootstrap.d.ts +20 -0
- package/dist/commands/bootstrap.d.ts.map +1 -0
- package/dist/commands/chat.d.ts +37 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/code.d.ts +28 -0
- package/dist/commands/code.d.ts.map +1 -0
- package/dist/commands/console.d.ts +22 -0
- package/dist/commands/console.d.ts.map +1 -0
- package/dist/commands/describe.d.ts +50 -0
- package/dist/commands/describe.d.ts.map +1 -0
- package/dist/commands/eval.d.ts +23 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/help.d.ts +25 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/introspect.d.ts +24 -0
- package/dist/commands/introspect.d.ts.map +1 -0
- package/dist/commands/mcp.d.ts +35 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/prompt.d.ts +38 -0
- package/dist/commands/prompt.d.ts.map +1 -0
- package/dist/commands/run.d.ts +24 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/sandbox-mcp.d.ts +34 -0
- package/dist/commands/sandbox-mcp.d.ts.map +1 -0
- package/dist/commands/save-api-docs.d.ts +21 -0
- package/dist/commands/save-api-docs.d.ts.map +1 -0
- package/dist/commands/scaffold.d.ts +24 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/select.d.ts +22 -0
- package/dist/commands/select.d.ts.map +1 -0
- package/dist/commands/serve.d.ts +29 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/container-describer.d.ts +144 -0
- package/dist/container-describer.d.ts.map +1 -0
- package/dist/container.d.ts +451 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/endpoint.d.ts +113 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/feature.d.ts +47 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/graft.d.ts +29 -0
- package/dist/graft.d.ts.map +1 -0
- package/dist/hash-object.d.ts +8 -0
- package/dist/hash-object.d.ts.map +1 -0
- package/dist/helper.d.ts +209 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/introspection/generated.node.d.ts +44623 -0
- package/dist/introspection/generated.node.d.ts.map +1 -0
- package/dist/introspection/generated.web.d.ts +1412 -0
- package/dist/introspection/generated.web.d.ts.map +1 -0
- package/dist/introspection/index.d.ts +156 -0
- package/dist/introspection/index.d.ts.map +1 -0
- package/dist/introspection/scan.d.ts +147 -0
- package/dist/introspection/scan.d.ts.map +1 -0
- package/dist/node/container.d.ts +256 -0
- package/dist/node/container.d.ts.map +1 -0
- package/dist/node/feature.d.ts +9 -0
- package/dist/node/feature.d.ts.map +1 -0
- package/dist/node/features/container-link.d.ts +213 -0
- package/dist/node/features/container-link.d.ts.map +1 -0
- package/dist/node/features/content-db.d.ts +354 -0
- package/dist/node/features/content-db.d.ts.map +1 -0
- package/dist/node/features/disk-cache.d.ts +236 -0
- package/dist/node/features/disk-cache.d.ts.map +1 -0
- package/dist/node/features/dns.d.ts +511 -0
- package/dist/node/features/dns.d.ts.map +1 -0
- package/dist/node/features/docker.d.ts +485 -0
- package/dist/node/features/docker.d.ts.map +1 -0
- package/dist/node/features/downloader.d.ts +73 -0
- package/dist/node/features/downloader.d.ts.map +1 -0
- package/dist/node/features/figlet-fonts.d.ts +4 -0
- package/dist/node/features/figlet-fonts.d.ts.map +1 -0
- package/dist/node/features/file-manager.d.ts +177 -0
- package/dist/node/features/file-manager.d.ts.map +1 -0
- package/dist/node/features/fs.d.ts +635 -0
- package/dist/node/features/fs.d.ts.map +1 -0
- package/dist/node/features/git.d.ts +329 -0
- package/dist/node/features/git.d.ts.map +1 -0
- package/dist/node/features/google-auth.d.ts +200 -0
- package/dist/node/features/google-auth.d.ts.map +1 -0
- package/dist/node/features/google-calendar.d.ts +194 -0
- package/dist/node/features/google-calendar.d.ts.map +1 -0
- package/dist/node/features/google-docs.d.ts +138 -0
- package/dist/node/features/google-docs.d.ts.map +1 -0
- package/dist/node/features/google-drive.d.ts +202 -0
- package/dist/node/features/google-drive.d.ts.map +1 -0
- package/dist/node/features/google-mail.d.ts +221 -0
- package/dist/node/features/google-mail.d.ts.map +1 -0
- package/dist/node/features/google-sheets.d.ts +157 -0
- package/dist/node/features/google-sheets.d.ts.map +1 -0
- package/dist/node/features/grep.d.ts +207 -0
- package/dist/node/features/grep.d.ts.map +1 -0
- package/dist/node/features/helpers.d.ts +236 -0
- package/dist/node/features/helpers.d.ts.map +1 -0
- package/dist/node/features/ink.d.ts +332 -0
- package/dist/node/features/ink.d.ts.map +1 -0
- package/dist/node/features/ipc-socket.d.ts +298 -0
- package/dist/node/features/ipc-socket.d.ts.map +1 -0
- package/dist/node/features/json-tree.d.ts +140 -0
- package/dist/node/features/json-tree.d.ts.map +1 -0
- package/dist/node/features/networking.d.ts +373 -0
- package/dist/node/features/networking.d.ts.map +1 -0
- package/dist/node/features/nlp.d.ts +125 -0
- package/dist/node/features/nlp.d.ts.map +1 -0
- package/dist/node/features/opener.d.ts +93 -0
- package/dist/node/features/opener.d.ts.map +1 -0
- package/dist/node/features/os.d.ts +168 -0
- package/dist/node/features/os.d.ts.map +1 -0
- package/dist/node/features/package-finder.d.ts +419 -0
- package/dist/node/features/package-finder.d.ts.map +1 -0
- package/dist/node/features/postgres.d.ts +173 -0
- package/dist/node/features/postgres.d.ts.map +1 -0
- package/dist/node/features/proc.d.ts +285 -0
- package/dist/node/features/proc.d.ts.map +1 -0
- package/dist/node/features/process-manager.d.ts +427 -0
- package/dist/node/features/process-manager.d.ts.map +1 -0
- package/dist/node/features/python.d.ts +477 -0
- package/dist/node/features/python.d.ts.map +1 -0
- package/dist/node/features/redis.d.ts +247 -0
- package/dist/node/features/redis.d.ts.map +1 -0
- package/dist/node/features/repl.d.ts +84 -0
- package/dist/node/features/repl.d.ts.map +1 -0
- package/dist/node/features/runpod.d.ts +527 -0
- package/dist/node/features/runpod.d.ts.map +1 -0
- package/dist/node/features/secure-shell.d.ts +145 -0
- package/dist/node/features/secure-shell.d.ts.map +1 -0
- package/dist/node/features/semantic-search.d.ts +207 -0
- package/dist/node/features/semantic-search.d.ts.map +1 -0
- package/dist/node/features/sqlite.d.ts +180 -0
- package/dist/node/features/sqlite.d.ts.map +1 -0
- package/dist/node/features/telegram.d.ts +173 -0
- package/dist/node/features/telegram.d.ts.map +1 -0
- package/dist/node/features/transpiler.d.ts +51 -0
- package/dist/node/features/transpiler.d.ts.map +1 -0
- package/dist/node/features/tts.d.ts +108 -0
- package/dist/node/features/tts.d.ts.map +1 -0
- package/dist/node/features/ui.d.ts +562 -0
- package/dist/node/features/ui.d.ts.map +1 -0
- package/dist/node/features/vault.d.ts +90 -0
- package/dist/node/features/vault.d.ts.map +1 -0
- package/dist/node/features/vm.d.ts +285 -0
- package/dist/node/features/vm.d.ts.map +1 -0
- package/dist/node/features/yaml-tree.d.ts +118 -0
- package/dist/node/features/yaml-tree.d.ts.map +1 -0
- package/dist/node/features/yaml.d.ts +127 -0
- package/dist/node/features/yaml.d.ts.map +1 -0
- package/dist/node.d.ts +67 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/python/generated.d.ts +2 -0
- package/dist/python/generated.d.ts.map +1 -0
- package/dist/react/index.d.ts +36 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/registry.d.ts +97 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/scaffolds/generated.d.ts +13 -0
- package/dist/scaffolds/generated.d.ts.map +1 -0
- package/dist/scaffolds/template.d.ts +11 -0
- package/dist/scaffolds/template.d.ts.map +1 -0
- package/dist/schemas/base.d.ts +254 -0
- package/dist/schemas/base.d.ts.map +1 -0
- package/dist/selector.d.ts +130 -0
- package/dist/selector.d.ts.map +1 -0
- package/dist/server.d.ts +89 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/servers/express.d.ts +104 -0
- package/dist/servers/express.d.ts.map +1 -0
- package/dist/servers/mcp.d.ts +201 -0
- package/dist/servers/mcp.d.ts.map +1 -0
- package/dist/servers/socket.d.ts +121 -0
- package/dist/servers/socket.d.ts.map +1 -0
- package/dist/state.d.ts +24 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/web/clients/socket.d.ts +37 -0
- package/dist/web/clients/socket.d.ts.map +1 -0
- package/dist/web/container.d.ts +55 -0
- package/dist/web/container.d.ts.map +1 -0
- package/dist/web/extension.d.ts +4 -0
- package/dist/web/extension.d.ts.map +1 -0
- package/dist/web/feature.d.ts +8 -0
- package/dist/web/feature.d.ts.map +1 -0
- package/dist/web/features/asset-loader.d.ts +35 -0
- package/dist/web/features/asset-loader.d.ts.map +1 -0
- package/dist/web/features/container-link.d.ts +167 -0
- package/dist/web/features/container-link.d.ts.map +1 -0
- package/dist/web/features/esbuild.d.ts +51 -0
- package/dist/web/features/esbuild.d.ts.map +1 -0
- package/dist/web/features/helpers.d.ts +140 -0
- package/dist/web/features/helpers.d.ts.map +1 -0
- package/dist/web/features/network.d.ts +69 -0
- package/dist/web/features/network.d.ts.map +1 -0
- package/dist/web/features/speech.d.ts +71 -0
- package/dist/web/features/speech.d.ts.map +1 -0
- package/dist/web/features/vault.d.ts +62 -0
- package/dist/web/features/vault.d.ts.map +1 -0
- package/dist/web/features/vm.d.ts +48 -0
- package/dist/web/features/vm.d.ts.map +1 -0
- package/dist/web/features/voice-recognition.d.ts +96 -0
- package/dist/web/features/voice-recognition.d.ts.map +1 -0
- package/dist/web/shims/isomorphic-vm.d.ts +22 -0
- package/dist/web/shims/isomorphic-vm.d.ts.map +1 -0
- package/docs/apis/features/agi/assistant.md +1 -0
- package/docs/apis/features/agi/assistants-manager.md +62 -2
- package/docs/apis/features/agi/auto-assistant.md +11 -109
- package/docs/apis/features/agi/claude-code.md +138 -0
- package/docs/apis/features/agi/conversation.md +60 -31
- package/docs/apis/features/agi/luca-coder.md +407 -0
- package/docs/apis/features/agi/openapi.md +2 -2
- package/docs/apis/features/agi/skills-library.md +12 -0
- package/docs/apis/features/node/python.md +81 -11
- package/docs/apis/features/node/transpiler.md +74 -0
- package/docs/apis/features/web/esbuild.md +0 -6
- package/docs/apis/servers/mcp.md +2 -2
- package/docs/examples/entity.md +124 -0
- package/docs/ideas/assistant-factory-pattern.md +142 -0
- package/package.json +74 -21
- package/src/agi/container.server.ts +10 -0
- package/src/agi/feature.ts +13 -0
- package/src/agi/features/agent-memory.ts +694 -0
- package/src/agi/features/assistant.ts +37 -26
- package/src/agi/features/assistants-manager.ts +95 -5
- package/src/agi/features/autonomous-assistant.ts +1 -5
- package/src/agi/features/browser-use.ts +32 -2
- package/src/agi/features/claude-code.ts +165 -1
- package/src/agi/features/coding-tools.ts +175 -0
- package/src/agi/features/conversation-history.ts +2 -6
- package/src/agi/features/conversation.ts +95 -3
- package/src/agi/features/docs-reader.ts +2 -1
- package/src/agi/features/file-tools.ts +35 -28
- package/src/agi/features/luca-coder.ts +1 -5
- package/src/agi/features/openai-codex.ts +1 -1
- package/src/agi/features/openapi.ts +3 -3
- package/src/agi/features/skills-library.ts +111 -13
- package/src/agi/lib/interceptor-chain.ts +10 -0
- package/src/agi/lib/token-counter.ts +1 -1
- package/src/bootstrap/generated.ts +126 -1
- package/src/bus.ts +27 -5
- package/src/cli/build-info.ts +2 -2
- package/src/client.ts +2 -2
- package/src/clients/elevenlabs/index.ts +5 -0
- package/src/clients/voicebox/index.ts +300 -0
- package/src/commands/bootstrap.ts +2 -1
- package/src/commands/chat.ts +1 -0
- package/src/commands/code.ts +4 -2
- package/src/commands/prompt.ts +34 -34
- package/src/commands/sandbox-mcp.ts +69 -163
- package/src/commands/save-api-docs.ts +10 -8
- package/src/commands/select.ts +8 -3
- package/src/container-describer.ts +70 -84
- package/src/container.ts +93 -3
- package/src/endpoint.ts +1 -1
- package/src/entity.ts +173 -0
- package/src/feature.ts +3 -3
- package/src/helper.ts +8 -4
- package/src/introspection/generated.agi.ts +3012 -1356
- package/src/introspection/generated.node.ts +179 -33
- package/src/introspection/generated.web.ts +95 -3
- package/src/introspection/scan.ts +1 -1
- package/src/node/container.ts +1 -1
- package/src/node/features/content-db.ts +57 -30
- package/src/node/features/file-manager.ts +10 -9
- package/src/node/features/git.ts +5 -5
- package/src/node/features/helpers.ts +1 -1
- package/src/node/features/json-tree.ts +1 -1
- package/src/node/features/os.ts +3 -3
- package/src/node/features/package-finder.ts +1 -1
- package/src/node/features/process-manager.ts +51 -18
- package/src/node/features/python.ts +3 -3
- package/src/node/features/redis.ts +1 -1
- package/src/node/features/repl.ts +2 -2
- package/src/node/features/transpiler.ts +2 -2
- package/src/node/features/ui.ts +1 -1
- package/src/node/features/vm.ts +3 -3
- package/src/node/features/yaml-tree.ts +1 -1
- package/src/node.ts +1 -0
- package/src/python/generated.ts +1 -1
- package/src/scaffolds/generated.ts +1 -1
- package/src/selector.ts +74 -4
- package/src/server.ts +2 -2
- package/src/servers/mcp.ts +6 -6
- package/src/web/features/helpers.ts +1 -1
- package/src/web/features/network.ts +1 -0
- package/test/assistant.test.ts +14 -5
- package/test/conversation.test.ts +220 -0
- package/test-integration/memory.test.ts +204 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +1 -1
- package/scripts/examples/telegram-ink-ui.ts +0 -302
- package/scripts/examples/using-openai-codex.ts +0 -23
- package/scripts/examples/vm-loading-esm-modules.ts +0 -16
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
4
|
-
import { Feature } from '
|
|
4
|
+
import { Feature } from '../feature.js'
|
|
5
5
|
import type { Conversation, ConversationTool, ContentPart, AskOptions, Message } from './conversation'
|
|
6
|
-
import type { AGIContainer } from '../container.server.js'
|
|
7
6
|
import type { ContentDb } from '@soederpop/luca/node'
|
|
8
7
|
import type { ConversationHistory, ConversationMeta } from './conversation-history'
|
|
9
8
|
import hashObject from '../../hash-object.js'
|
|
@@ -152,6 +151,10 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
152
151
|
* @returns this, for chaining
|
|
153
152
|
*/
|
|
154
153
|
intercept<K extends InterceptorPoint>(point: K, fn: InterceptorFn<InterceptorPoints[K]>): this {
|
|
154
|
+
if (!(point in this.interceptors)) {
|
|
155
|
+
const available = Object.keys(this.interceptors).join(', ')
|
|
156
|
+
throw new Error(`Unknown intercept point "${point}". Available points: ${available}`)
|
|
157
|
+
}
|
|
155
158
|
this.interceptors[point].add(fn as any)
|
|
156
159
|
return this
|
|
157
160
|
}
|
|
@@ -176,12 +179,9 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
176
179
|
}
|
|
177
180
|
}
|
|
178
181
|
|
|
179
|
-
override get container(): AGIContainer {
|
|
180
|
-
return super.container as AGIContainer
|
|
181
|
-
}
|
|
182
182
|
|
|
183
183
|
get name() {
|
|
184
|
-
return this.resolvedFolder.split('/').pop()
|
|
184
|
+
return this.options.name || this.resolvedFolder.split('/').pop()
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/** The absolute resolved path to the assistant folder. */
|
|
@@ -213,11 +213,11 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
213
213
|
get voiceConfig(): Record<string, any> | undefined {
|
|
214
214
|
if (!this.hasVoice) return undefined
|
|
215
215
|
const yaml = this.container.feature('yaml')
|
|
216
|
-
return yaml.parse(this.container.fs.readFile(this.paths.resolve('voice.yaml')))
|
|
216
|
+
return yaml.parse(String(this.container.fs.readFile(this.paths.resolve('voice.yaml'))))
|
|
217
217
|
}
|
|
218
218
|
|
|
219
219
|
get resolvedDocsFolder() {
|
|
220
|
-
const { docsFolder = this.
|
|
220
|
+
const { docsFolder = this.effectiveOptions.docsFolder || 'docs' } = this.state.current
|
|
221
221
|
|
|
222
222
|
if (this.container.fs.exists(docsFolder)) {
|
|
223
223
|
return this.container.paths.resolve(docsFolder)
|
|
@@ -261,24 +261,24 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
261
261
|
// Bind hooks to events BEFORE emitting created so the created hook fires
|
|
262
262
|
this.bindHooksToEvents()
|
|
263
263
|
|
|
264
|
-
this.emit('created')
|
|
264
|
+
setTimeout(() => this.emit('created'), 1)
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
get conversation(): Conversation {
|
|
268
268
|
let conv = this.state.get('conversation') as Conversation | null
|
|
269
269
|
if (!conv) {
|
|
270
270
|
conv = this.container.feature('conversation', {
|
|
271
|
-
model: this.
|
|
272
|
-
local: !!this.
|
|
271
|
+
model: this.effectiveOptions.model || 'gpt-5.4',
|
|
272
|
+
local: !!this.effectiveOptions.local,
|
|
273
273
|
tools: this.tools,
|
|
274
274
|
api: 'chat',
|
|
275
|
-
...(this.
|
|
276
|
-
...(this.
|
|
277
|
-
...(this.
|
|
278
|
-
...(this.
|
|
279
|
-
...(this.
|
|
280
|
-
...(this.
|
|
281
|
-
...(this.
|
|
275
|
+
...(this.effectiveOptions.maxTokens ? { maxTokens: this.effectiveOptions.maxTokens } : {}),
|
|
276
|
+
...(this.effectiveOptions.temperature != null ? { temperature: this.effectiveOptions.temperature } : {}),
|
|
277
|
+
...(this.effectiveOptions.topP != null ? { topP: this.effectiveOptions.topP } : {}),
|
|
278
|
+
...(this.effectiveOptions.topK != null ? { topK: this.effectiveOptions.topK } : {}),
|
|
279
|
+
...(this.effectiveOptions.frequencyPenalty != null ? { frequencyPenalty: this.effectiveOptions.frequencyPenalty } : {}),
|
|
280
|
+
...(this.effectiveOptions.presencePenalty != null ? { presencePenalty: this.effectiveOptions.presencePenalty } : {}),
|
|
281
|
+
...(this.effectiveOptions.stop ? { stop: this.effectiveOptions.stop } : {}),
|
|
282
282
|
history: [
|
|
283
283
|
{ role: 'system', content: this.effectiveSystemPrompt },
|
|
284
284
|
],
|
|
@@ -372,7 +372,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
372
372
|
* allowTools is applied first (strict allowlist), then forbidTools removes from whatever remains.
|
|
373
373
|
*/
|
|
374
374
|
private applyToolFilters(tools: Record<string, ConversationTool>): Record<string, ConversationTool> {
|
|
375
|
-
const { allowTools, forbidTools, toolNames } = this.
|
|
375
|
+
const { allowTools, forbidTools, toolNames } = this.effectiveOptions
|
|
376
376
|
if (!allowTools && !forbidTools && !toolNames) return tools
|
|
377
377
|
|
|
378
378
|
let names = Object.keys(tools)
|
|
@@ -395,7 +395,8 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
395
395
|
|
|
396
396
|
const result: Record<string, ConversationTool> = {}
|
|
397
397
|
for (const n of names) {
|
|
398
|
-
|
|
398
|
+
const tool = tools[n]
|
|
399
|
+
if (tool) result[n] = tool
|
|
399
400
|
}
|
|
400
401
|
return result
|
|
401
402
|
}
|
|
@@ -607,6 +608,15 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
607
608
|
return (this.state.get('meta') || {}) as Record<string, any>
|
|
608
609
|
}
|
|
609
610
|
|
|
611
|
+
/**
|
|
612
|
+
* Merged options where CORE.md frontmatter provides defaults and
|
|
613
|
+
* constructor options take precedence. Prefer this over `this.options`
|
|
614
|
+
* anywhere model parameters or runtime config is consumed.
|
|
615
|
+
*/
|
|
616
|
+
get effectiveOptions(): AssistantOptions & Record<string, any> {
|
|
617
|
+
return { ...this.meta, ...this.options }
|
|
618
|
+
}
|
|
619
|
+
|
|
610
620
|
/**
|
|
611
621
|
* Load the system prompt from CORE.md, applying any prepend/append options.
|
|
612
622
|
* YAML frontmatter (between --- fences) is stripped from the prompt and
|
|
@@ -831,8 +841,9 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
831
841
|
return `${stamp} ${content}`
|
|
832
842
|
}
|
|
833
843
|
|
|
834
|
-
|
|
835
|
-
|
|
844
|
+
const firstPart = content[0]
|
|
845
|
+
if (firstPart && firstPart.type === 'text') {
|
|
846
|
+
return [{ type: 'text' as const, text: `${stamp} ${firstPart.text}` }, ...content.slice(1)]
|
|
836
847
|
}
|
|
837
848
|
|
|
838
849
|
return [{ type: 'text' as const, text: stamp }, ...content]
|
|
@@ -919,7 +930,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
919
930
|
* Called from start() for non-lifecycle modes.
|
|
920
931
|
*/
|
|
921
932
|
private async loadConversationHistory(): Promise<void> {
|
|
922
|
-
const mode = this.
|
|
933
|
+
const mode = this.effectiveOptions.historyMode || 'lifecycle'
|
|
923
934
|
if (mode === 'lifecycle') return
|
|
924
935
|
|
|
925
936
|
const threadId = (this.state.get('resumeThreadId') as string | undefined) || this.buildThreadId(mode)
|
|
@@ -1104,7 +1115,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
1104
1115
|
await this.loadConversationHistory()
|
|
1105
1116
|
|
|
1106
1117
|
// Enable autoCompact for modes that accumulate history
|
|
1107
|
-
const mode = this.
|
|
1118
|
+
const mode = this.effectiveOptions.historyMode || 'lifecycle'
|
|
1108
1119
|
if (mode === 'daily' || mode === 'persistent') {
|
|
1109
1120
|
(this.conversation.options as any).autoCompact = true
|
|
1110
1121
|
}
|
|
@@ -1146,7 +1157,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
1146
1157
|
const count = (this.state.get('conversationCount') || 0) + 1
|
|
1147
1158
|
this.state.set('conversationCount', count)
|
|
1148
1159
|
|
|
1149
|
-
if (this.
|
|
1160
|
+
if (this.effectiveOptions.injectTimestamps) {
|
|
1150
1161
|
question = this.prependTimestamp(question)
|
|
1151
1162
|
}
|
|
1152
1163
|
|
|
@@ -1169,7 +1180,7 @@ export class Assistant extends Feature<AssistantState, AssistantOptions> {
|
|
|
1169
1180
|
}
|
|
1170
1181
|
|
|
1171
1182
|
// Auto-save for non-lifecycle modes
|
|
1172
|
-
if (this.
|
|
1183
|
+
if (this.effectiveOptions.historyMode !== 'lifecycle' && this.state.get('threadId')) {
|
|
1173
1184
|
await this.conversation.save({ thread: this.state.get('threadId') })
|
|
1174
1185
|
}
|
|
1175
1186
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
4
|
-
import { Feature } from '
|
|
5
|
-
import type { AGIContainer } from '../container.server.js'
|
|
4
|
+
import { Feature } from '../feature.js'
|
|
6
5
|
import type { Assistant } from './assistant.js'
|
|
6
|
+
import type { InterceptorFn, InterceptorPoint, InterceptorPoints } from '../lib/interceptor-chain.js'
|
|
7
7
|
|
|
8
8
|
declare module '@soederpop/luca/feature' {
|
|
9
9
|
interface AvailableFeatures {
|
|
@@ -27,6 +27,10 @@ export interface AssistantEntry {
|
|
|
27
27
|
hasHooks: boolean
|
|
28
28
|
/** Whether a voice.yaml configuration file exists. */
|
|
29
29
|
hasVoice: boolean
|
|
30
|
+
/** Contents of ABOUT.md if present, undefined otherwise. */
|
|
31
|
+
about?: string
|
|
32
|
+
/** Frontmatter metadata parsed from CORE.md. */
|
|
33
|
+
meta?: Record<string, any>
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export const AssistantsManagerEventsSchema = FeatureEventsSchema.extend({
|
|
@@ -47,6 +51,7 @@ export const AssistantsManagerStateSchema = FeatureStateSchema.extend({
|
|
|
47
51
|
entries: z.record(z.string(), z.any()).describe('Discovered assistant entries keyed by name'),
|
|
48
52
|
instances: z.record(z.string(), z.any()).describe('Active assistant instances keyed by name'),
|
|
49
53
|
factories: z.record(z.string(), z.any()).describe('Registered factory functions keyed by name'),
|
|
54
|
+
extraFolders: z.array(z.string()).describe('Additional folders to scan during discovery'),
|
|
50
55
|
})
|
|
51
56
|
|
|
52
57
|
export const AssistantsManagerOptionsSchema = FeatureOptionsSchema.extend({})
|
|
@@ -91,12 +96,10 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
91
96
|
entries: {},
|
|
92
97
|
instances: {},
|
|
93
98
|
factories: {},
|
|
99
|
+
extraFolders: [],
|
|
94
100
|
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
override get container(): AGIContainer {
|
|
98
|
-
return super.container as AGIContainer
|
|
99
|
-
}
|
|
100
103
|
|
|
101
104
|
/** Discovered assistant entries keyed by name. */
|
|
102
105
|
get entries(): Record<string, AssistantEntry> {
|
|
@@ -113,12 +116,60 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
113
116
|
return (this.state.get('factories') || {}) as Record<string, (options: Record<string, any>) => Assistant>
|
|
114
117
|
}
|
|
115
118
|
|
|
119
|
+
/** Interceptor registrations to be applied to every assistant this manager creates. */
|
|
120
|
+
private _interceptors: Array<{ point: InterceptorPoint; fn: InterceptorFn<any> }> = []
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Registers a pipeline interceptor that is applied to every assistant created by this manager.
|
|
124
|
+
* Interceptors are applied at the given interception point on each assistant at creation time.
|
|
125
|
+
* This mirrors the per-assistant `assistant.intercept(point, fn)` API, but scopes it globally
|
|
126
|
+
* across all assistants managed here — useful for cross-cutting concerns like logging, tracing,
|
|
127
|
+
* or policy enforcement.
|
|
128
|
+
*
|
|
129
|
+
* @param {InterceptorPoint} point - The interception point (beforeAsk, beforeTurn, beforeToolCall, afterToolCall, beforeResponse)
|
|
130
|
+
* @param {InterceptorFn<InterceptorPoints[K]>} fn - Middleware function receiving (ctx, next)
|
|
131
|
+
* @returns {this} This instance, for chaining
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* manager.intercept('beforeAsk', async (ctx, next) => {
|
|
136
|
+
* console.log(`[${ctx.assistant.name}] asking: ${ctx.message}`)
|
|
137
|
+
* await next()
|
|
138
|
+
* })
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
intercept<K extends InterceptorPoint>(point: K, fn: InterceptorFn<InterceptorPoints[K]>): this {
|
|
142
|
+
this._interceptors.push({ point, fn })
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
116
146
|
/**
|
|
117
147
|
* Discovers assistants by listing subdirectories in ~/.luca/assistants/
|
|
118
148
|
* and cwd/assistants/. Each subdirectory containing a CORE.md is an assistant.
|
|
119
149
|
*
|
|
120
150
|
* @returns {Promise<this>} This instance, for chaining
|
|
121
151
|
*/
|
|
152
|
+
/**
|
|
153
|
+
* Registers an additional folder to scan during assistant discovery and
|
|
154
|
+
* immediately triggers a new discovery pass.
|
|
155
|
+
*
|
|
156
|
+
* @param {string} folderPath - Absolute path to a folder containing assistant subdirectories
|
|
157
|
+
* @returns {Promise<this>} This instance, for chaining
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```typescript
|
|
161
|
+
* await manager.addDiscoveryFolder('/path/to/more/assistants')
|
|
162
|
+
* console.log(manager.available) // includes assistants from the new folder
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
async addDiscoveryFolder(folderPath: string): Promise<this> {
|
|
166
|
+
const current = this.state.get('extraFolders') as string[]
|
|
167
|
+
if (!current.includes(folderPath)) {
|
|
168
|
+
this.state.set('extraFolders', [...current, folderPath])
|
|
169
|
+
}
|
|
170
|
+
return this.discover()
|
|
171
|
+
}
|
|
172
|
+
|
|
122
173
|
async discover(): Promise<this> {
|
|
123
174
|
const { fs, paths, os } = this.container
|
|
124
175
|
|
|
@@ -127,6 +178,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
127
178
|
const locations = [
|
|
128
179
|
`${os.homedir}/.luca/assistants`,
|
|
129
180
|
paths.resolve('assistants'),
|
|
181
|
+
...(this.state.get('extraFolders') as string[]),
|
|
130
182
|
]
|
|
131
183
|
|
|
132
184
|
for (const location of locations) {
|
|
@@ -143,6 +195,25 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
143
195
|
|
|
144
196
|
// Don't overwrite earlier entries (home takes precedence for same name)
|
|
145
197
|
if (!discovered[entry]) {
|
|
198
|
+
const hasAbout = fs.exists(`${folder}/ABOUT.md`)
|
|
199
|
+
let about: string | undefined
|
|
200
|
+
let meta: Record<string, any> | undefined
|
|
201
|
+
|
|
202
|
+
if (hasAbout) {
|
|
203
|
+
about = fs.readFileSync(`${folder}/ABOUT.md`, 'utf8')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const coreContent = fs.readFileSync(`${folder}/CORE.md`, 'utf8')
|
|
208
|
+
const fmMatch = coreContent.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
209
|
+
if (fmMatch) {
|
|
210
|
+
const yaml = this.container.feature('yaml')
|
|
211
|
+
meta = yaml.parse(fmMatch[1])
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// CORE.md exists but couldn't be parsed — skip meta
|
|
215
|
+
}
|
|
216
|
+
|
|
146
217
|
discovered[entry] = {
|
|
147
218
|
name: entry,
|
|
148
219
|
folder,
|
|
@@ -150,6 +221,8 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
150
221
|
hasTools: fs.exists(`${folder}/tools.ts`),
|
|
151
222
|
hasHooks: fs.exists(`${folder}/hooks.ts`),
|
|
152
223
|
hasVoice: fs.exists(`${folder}/voice.yaml`),
|
|
224
|
+
...(about != null && { about }),
|
|
225
|
+
...(meta != null && { meta }),
|
|
153
226
|
}
|
|
154
227
|
}
|
|
155
228
|
}
|
|
@@ -262,6 +335,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
262
335
|
const factory = this.factories[name]
|
|
263
336
|
if (factory) {
|
|
264
337
|
const instance = factory(options)
|
|
338
|
+
this._bindAssistant(instance)
|
|
265
339
|
const updated = { ...this.instances, [name]: instance }
|
|
266
340
|
this.state.setState({ instances: updated, activeCount: Object.keys(updated).length })
|
|
267
341
|
this.emit('assistantCreated', name, instance)
|
|
@@ -281,6 +355,7 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
281
355
|
...options,
|
|
282
356
|
})
|
|
283
357
|
|
|
358
|
+
this._bindAssistant(instance)
|
|
284
359
|
const updated = { ...this.instances, [name]: instance }
|
|
285
360
|
this.state.setState({ instances: updated, activeCount: Object.keys(updated).length })
|
|
286
361
|
this.emit('assistantCreated', name, instance)
|
|
@@ -288,6 +363,21 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
288
363
|
return instance
|
|
289
364
|
}
|
|
290
365
|
|
|
366
|
+
/**
|
|
367
|
+
* Wires an assistant into the manager: bridges all assistant events up to the manager
|
|
368
|
+
* as `assistantEvent:<eventName>` with (assistant, ...originalArgs), and applies any
|
|
369
|
+
* globally registered interceptors.
|
|
370
|
+
*/
|
|
371
|
+
private _bindAssistant(instance: Assistant): void {
|
|
372
|
+
instance.on('*', (event: string, ...args: any[]) => {
|
|
373
|
+
this.emit(`assistantEvent:${event}` as any, instance, ...args)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
for (const { point, fn } of this._interceptors) {
|
|
377
|
+
instance.intercept(point, fn)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
291
381
|
/**
|
|
292
382
|
* Returns a previously created assistant instance by name.
|
|
293
383
|
*
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
-
import { Feature } from '
|
|
4
|
-
import type { AGIContainer } from '../container.server.js'
|
|
3
|
+
import { Feature } from '../feature.js'
|
|
5
4
|
import type { Assistant } from './assistant.js'
|
|
6
5
|
import type { ToolCallCtx } from '../lib/interceptor-chain.js'
|
|
7
6
|
|
|
@@ -158,9 +157,6 @@ export class AutonomousAssistant extends Feature<AutonomousAssistantState, Auton
|
|
|
158
157
|
}
|
|
159
158
|
}
|
|
160
159
|
|
|
161
|
-
override get container(): AGIContainer {
|
|
162
|
-
return super.container as AGIContainer
|
|
163
|
-
}
|
|
164
160
|
|
|
165
161
|
/** The inner assistant. Throws if not started. */
|
|
166
162
|
get assistant(): Assistant {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
|
-
import { Feature } from '
|
|
3
|
+
import { Feature } from '../feature.js'
|
|
4
|
+
import type { Helper } from '../../helper.js'
|
|
4
5
|
|
|
5
6
|
declare module '@soederpop/luca/feature' {
|
|
6
7
|
interface AvailableFeatures {
|
|
@@ -61,7 +62,7 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
|
|
|
61
62
|
static override optionsSchema = BrowserUseOptionsSchema
|
|
62
63
|
static override eventsSchema = BrowserUseEventsSchema
|
|
63
64
|
|
|
64
|
-
static tools = {
|
|
65
|
+
static override tools = {
|
|
65
66
|
browserOpen: {
|
|
66
67
|
description: 'Navigate the browser to a URL. Call this first to open a page before any interaction.',
|
|
67
68
|
schema: z.object({
|
|
@@ -223,6 +224,35 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
|
|
|
223
224
|
|
|
224
225
|
static { Feature.register(this, 'browserUse') }
|
|
225
226
|
|
|
227
|
+
/**
|
|
228
|
+
* When an assistant uses browserUse, inject system prompt guidance
|
|
229
|
+
* about the browser interaction loop.
|
|
230
|
+
*/
|
|
231
|
+
override setupToolsConsumer(consumer: Helper) {
|
|
232
|
+
if (typeof (consumer as any).addSystemPromptExtension === 'function') {
|
|
233
|
+
(consumer as any).addSystemPromptExtension('browserUse', [
|
|
234
|
+
'## Browser Automation',
|
|
235
|
+
'',
|
|
236
|
+
'**The core loop:** `browserOpen` → `browserGetState` → interact → `browserGetState` again.',
|
|
237
|
+
'',
|
|
238
|
+
'`browserGetState` is your eyes. It returns all interactive elements with index numbers. You MUST call it:',
|
|
239
|
+
'- After every `browserOpen` or navigation',
|
|
240
|
+
'- After any action that changes the page (click, submit, scroll)',
|
|
241
|
+
'- Before any interaction — to get fresh element indices',
|
|
242
|
+
'',
|
|
243
|
+
'Element indices change whenever the page updates. Never reuse indices from a previous `browserGetState` call after the page has changed.',
|
|
244
|
+
'',
|
|
245
|
+
'**Interacting with elements:** Use `browserInput` (click + type) for form fields. Use `browserClick` for buttons and links. Use `browserSelect` for dropdowns. All require an element index from `browserGetState`.',
|
|
246
|
+
'',
|
|
247
|
+
'**When things load asynchronously:** Use `browserWaitForSelector` or `browserWaitForText` after actions that trigger page updates (form submissions, AJAX). Then call `browserGetState` to see the updated page.',
|
|
248
|
+
'',
|
|
249
|
+
'**Debugging:** If an interaction doesn\'t work, take a `browserScreenshot` to see the actual page state. Check `browserGetState` to see what elements are available.',
|
|
250
|
+
'',
|
|
251
|
+
'**Cleanup:** Call `browserClose` when you\'re done to free resources.',
|
|
252
|
+
].join('\n'))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
226
256
|
override async afterInitialize() {
|
|
227
257
|
if (this.options.session) this.state.set('session', this.options.session)
|
|
228
258
|
if (this.options.headed) this.state.set('headed', true)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
4
4
|
import { type AvailableFeatures } from '@soederpop/luca/feature'
|
|
5
|
-
import { Feature } from '
|
|
5
|
+
import { Feature } from '../feature.js'
|
|
6
6
|
|
|
7
7
|
declare module '@soederpop/luca/feature' {
|
|
8
8
|
interface AvailableFeatures {
|
|
@@ -1349,6 +1349,170 @@ export class ClaudeCode extends Feature<ClaudeCodeState, ClaudeCodeOptions> {
|
|
|
1349
1349
|
}
|
|
1350
1350
|
}
|
|
1351
1351
|
|
|
1352
|
+
/**
|
|
1353
|
+
* List all Claude Code processes currently registered in ~/.claude/sessions/.
|
|
1354
|
+
* Returns each session's metadata along with whether the process is still alive.
|
|
1355
|
+
*
|
|
1356
|
+
* @returns {Promise<Array<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string; alive: boolean }>>}
|
|
1357
|
+
*
|
|
1358
|
+
* @example
|
|
1359
|
+
* const sessions = await cc.listProcessSessions()
|
|
1360
|
+
* for (const s of sessions) {
|
|
1361
|
+
* console.log(`[${s.alive ? 'LIVE' : 'dead'}] PID ${s.pid} in ${s.cwd}`)
|
|
1362
|
+
* }
|
|
1363
|
+
*/
|
|
1364
|
+
async listProcessSessions(): Promise<Array<{
|
|
1365
|
+
pid: number
|
|
1366
|
+
sessionId: string
|
|
1367
|
+
cwd: string
|
|
1368
|
+
startedAt: number
|
|
1369
|
+
kind: string
|
|
1370
|
+
entrypoint: string
|
|
1371
|
+
alive: boolean
|
|
1372
|
+
}>> {
|
|
1373
|
+
const fs = this.container.feature('fs')
|
|
1374
|
+
const proc = this.container.feature('proc')
|
|
1375
|
+
const home = process.env.HOME ?? '/tmp'
|
|
1376
|
+
const sessionsDir = `${home}/.claude/sessions`
|
|
1377
|
+
|
|
1378
|
+
let files: string[]
|
|
1379
|
+
try {
|
|
1380
|
+
files = await fs.readdir(sessionsDir)
|
|
1381
|
+
} catch {
|
|
1382
|
+
return []
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
const jsonFiles = files.filter((f: string) => f.endsWith('.json'))
|
|
1386
|
+
|
|
1387
|
+
const results = await Promise.all(jsonFiles.map(async (file: string) => {
|
|
1388
|
+
try {
|
|
1389
|
+
const raw = await fs.readFile(`${sessionsDir}/${file}`, 'utf8')
|
|
1390
|
+
const data = JSON.parse(raw)
|
|
1391
|
+
let alive = false
|
|
1392
|
+
try {
|
|
1393
|
+
await proc.exec(`kill -0 ${data.pid}`)
|
|
1394
|
+
alive = true
|
|
1395
|
+
} catch {
|
|
1396
|
+
alive = false
|
|
1397
|
+
}
|
|
1398
|
+
return { ...data, alive }
|
|
1399
|
+
} catch {
|
|
1400
|
+
return null
|
|
1401
|
+
}
|
|
1402
|
+
}))
|
|
1403
|
+
|
|
1404
|
+
return results.filter(Boolean)
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* Read a single process session by PID from ~/.claude/sessions/<pid>.json.
|
|
1409
|
+
*
|
|
1410
|
+
* @param {number} pid - The process ID
|
|
1411
|
+
* @returns {Promise<{ pid: number; sessionId: string; cwd: string; startedAt: number; kind: string; entrypoint: string } | null>}
|
|
1412
|
+
*
|
|
1413
|
+
* @example
|
|
1414
|
+
* const session = await cc.getProcessSession(12345)
|
|
1415
|
+
* console.log(session?.cwd)
|
|
1416
|
+
*/
|
|
1417
|
+
async getProcessSession(pid: number): Promise<{
|
|
1418
|
+
pid: number
|
|
1419
|
+
sessionId: string
|
|
1420
|
+
cwd: string
|
|
1421
|
+
startedAt: number
|
|
1422
|
+
kind: string
|
|
1423
|
+
entrypoint: string
|
|
1424
|
+
} | null> {
|
|
1425
|
+
const fs = this.container.feature('fs')
|
|
1426
|
+
const home = process.env.HOME ?? '/tmp'
|
|
1427
|
+
try {
|
|
1428
|
+
const raw = await fs.readFile(`${home}/.claude/sessions/${pid}.json`, 'utf8')
|
|
1429
|
+
return JSON.parse(raw)
|
|
1430
|
+
} catch {
|
|
1431
|
+
return null
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Read the conversation history for a Claude Code session from its JSONL file in
|
|
1437
|
+
* ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl.
|
|
1438
|
+
*
|
|
1439
|
+
* Returns an array of parsed message objects (user, assistant, tool_use, tool_result).
|
|
1440
|
+
*
|
|
1441
|
+
* @param {string} sessionId - The Claude CLI session ID (from listProcessSessions or getProcessSession)
|
|
1442
|
+
* @param {string} cwd - The working directory of the session (used to locate the project folder)
|
|
1443
|
+
* @returns {Promise<any[]>} Array of parsed JSONL records
|
|
1444
|
+
*
|
|
1445
|
+
* @example
|
|
1446
|
+
* const sessions = await cc.listProcessSessions()
|
|
1447
|
+
* const s = sessions[0]
|
|
1448
|
+
* const history = await cc.getConversationHistory(s.sessionId, s.cwd)
|
|
1449
|
+
* console.log(history.length, 'turns')
|
|
1450
|
+
*/
|
|
1451
|
+
async getConversationHistory(sessionId: string, cwd?: string): Promise<any[]> {
|
|
1452
|
+
const fs = this.container.feature('fs')
|
|
1453
|
+
const home = process.env.HOME ?? '/tmp'
|
|
1454
|
+
const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
|
|
1455
|
+
const encodedCwd = resolvedCwd.replace(/\//g, '-').replace(/@/g, '-')
|
|
1456
|
+
const filePath = `${home}/.claude/projects/${encodedCwd}/${sessionId}.jsonl`
|
|
1457
|
+
try {
|
|
1458
|
+
const raw = await fs.readFile(filePath, 'utf8')
|
|
1459
|
+
return raw
|
|
1460
|
+
.split('\n')
|
|
1461
|
+
.filter((line: string) => line.trim())
|
|
1462
|
+
.map((line: string) => JSON.parse(line))
|
|
1463
|
+
} catch {
|
|
1464
|
+
return []
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* List all conversation sessions stored for a given working directory.
|
|
1470
|
+
* Reads ~/.claude/projects/<encoded-cwd>/ and returns metadata for each .jsonl file.
|
|
1471
|
+
*
|
|
1472
|
+
* @param {string} cwd - The working directory path to look up
|
|
1473
|
+
* @returns {Promise<Array<{ sessionId: string; filePath: string; messageCount: number }>>}
|
|
1474
|
+
*
|
|
1475
|
+
* @example
|
|
1476
|
+
* const sessions = await cc.listSessionsForCwd('/Users/me/my-project')
|
|
1477
|
+
* for (const s of sessions) {
|
|
1478
|
+
* console.log(s.sessionId, s.messageCount, 'messages')
|
|
1479
|
+
* }
|
|
1480
|
+
*/
|
|
1481
|
+
async listSessionsForCwd(cwd?: string): Promise<Array<{
|
|
1482
|
+
sessionId: string
|
|
1483
|
+
filePath: string
|
|
1484
|
+
messageCount: number
|
|
1485
|
+
}>> {
|
|
1486
|
+
const fs = this.container.feature('fs')
|
|
1487
|
+
const home = process.env.HOME ?? '/tmp'
|
|
1488
|
+
const resolvedCwd = cwd ?? this.options.cwd ?? (this.container as any).cwd
|
|
1489
|
+
const encodedCwd = resolvedCwd.replace(/\//g, '-').replace(/@/g, '-')
|
|
1490
|
+
const projectDir = `${home}/.claude/projects/${encodedCwd}`
|
|
1491
|
+
|
|
1492
|
+
let files: string[]
|
|
1493
|
+
try {
|
|
1494
|
+
files = await fs.readdir(projectDir)
|
|
1495
|
+
} catch {
|
|
1496
|
+
return []
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const jsonlFiles = files.filter((f: string) => f.endsWith('.jsonl'))
|
|
1500
|
+
|
|
1501
|
+
const results = await Promise.all(jsonlFiles.map(async (file: string) => {
|
|
1502
|
+
const sessionId = file.replace(/\.jsonl$/, '')
|
|
1503
|
+
const filePath = `${projectDir}/${file}`
|
|
1504
|
+
try {
|
|
1505
|
+
const raw = await fs.readFile(filePath, 'utf8')
|
|
1506
|
+
const messageCount = raw.split('\n').filter((l: string) => l.trim()).length
|
|
1507
|
+
return { sessionId, filePath, messageCount }
|
|
1508
|
+
} catch {
|
|
1509
|
+
return { sessionId, filePath, messageCount: 0 }
|
|
1510
|
+
}
|
|
1511
|
+
}))
|
|
1512
|
+
|
|
1513
|
+
return results
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1352
1516
|
/**
|
|
1353
1517
|
* Clean up any temp MCP config files created during sessions.
|
|
1354
1518
|
*/
|