talon-agent 1.12.0 → 1.34.0
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/README.md +111 -52
- package/package.json +39 -12
- package/prompts/README.md +112 -0
- package/prompts/base.md +12 -12
- package/prompts/discord.md +10 -47
- package/prompts/heartbeat.md +23 -8
- package/prompts/identity.md +12 -22
- package/prompts/native.md +23 -0
- package/prompts/system/contract-text-or-tools.md +8 -0
- package/prompts/system/contract-text-preferred.md +5 -0
- package/prompts/system/contract-tool-only.md +10 -0
- package/prompts/system/cron.md +3 -0
- package/prompts/system/daily-memory.md +3 -0
- package/prompts/system/goals.md +7 -0
- package/prompts/system/heartbeat-agent.md +44 -0
- package/prompts/system/persistent-memory.md +8 -0
- package/prompts/system/skills.md +9 -0
- package/prompts/system/triggers.md +5 -0
- package/prompts/system/workspace.md +9 -0
- package/prompts/teams.md +4 -14
- package/prompts/telegram.md +17 -89
- package/src/app.ts +278 -0
- package/src/backend/claude-sdk/constants.ts +22 -10
- package/src/backend/claude-sdk/factory.ts +79 -22
- package/src/backend/claude-sdk/handler.ts +431 -160
- package/src/backend/claude-sdk/index.ts +6 -2
- package/src/backend/claude-sdk/mcp-ready.ts +54 -0
- package/src/backend/claude-sdk/model-drift.ts +65 -0
- package/src/backend/claude-sdk/model-provider.ts +12 -6
- package/src/backend/claude-sdk/models/convert.ts +169 -0
- package/src/backend/claude-sdk/models/discovery.ts +203 -0
- package/src/backend/claude-sdk/models/index.ts +18 -0
- package/src/backend/claude-sdk/models/parsing.ts +264 -0
- package/src/backend/claude-sdk/models/static.ts +45 -0
- package/src/backend/claude-sdk/one-shot.ts +19 -13
- package/src/backend/claude-sdk/options.ts +228 -104
- package/src/backend/claude-sdk/state.ts +1 -1
- package/src/backend/claude-sdk/stream.ts +153 -45
- package/src/backend/claude-sdk/warm.ts +12 -5
- package/src/backend/codex/auth.ts +216 -40
- package/src/backend/codex/constants.ts +63 -22
- package/src/backend/codex/discovery.ts +427 -0
- package/src/backend/codex/factory.ts +63 -33
- package/src/backend/codex/handler/events.ts +239 -0
- package/src/backend/codex/handler/index.ts +15 -0
- package/src/backend/codex/handler/message.ts +669 -0
- package/src/backend/codex/handler/state.ts +14 -0
- package/src/backend/codex/handler/usage.ts +68 -0
- package/src/backend/codex/index.ts +2 -2
- package/src/backend/codex/init.ts +92 -11
- package/src/backend/codex/mcp-config.ts +89 -62
- package/src/backend/codex/models.ts +305 -54
- package/src/backend/codex/oauth-incompat.ts +302 -0
- package/src/backend/codex/one-shot.ts +91 -8
- package/src/backend/codex/state.ts +54 -3
- package/src/backend/codex/token-usage.ts +434 -0
- package/src/backend/kilo/factory.ts +60 -35
- package/src/backend/kilo/handler/index.ts +13 -0
- package/src/backend/kilo/handler/message.ts +317 -0
- package/src/backend/kilo/handler/state.ts +14 -0
- package/src/backend/kilo/handler/turn.ts +321 -0
- package/src/backend/kilo/index.ts +5 -5
- package/src/backend/kilo/model-provider.ts +26 -168
- package/src/backend/kilo/models/index.ts +52 -0
- package/src/backend/kilo/one-shot.ts +20 -142
- package/src/backend/kilo/server.ts +14 -21
- package/src/backend/openai-agents/builtins.ts +215 -114
- package/src/backend/openai-agents/constants.ts +41 -15
- package/src/backend/openai-agents/discovery.ts +332 -0
- package/src/backend/openai-agents/factory.ts +66 -28
- package/src/backend/openai-agents/handler/events.ts +170 -0
- package/src/backend/openai-agents/handler/index.ts +13 -0
- package/src/backend/openai-agents/handler/message.ts +527 -0
- package/src/backend/openai-agents/handler/state.ts +13 -0
- package/src/backend/openai-agents/index.ts +22 -4
- package/src/backend/openai-agents/init.ts +51 -88
- package/src/backend/openai-agents/mcp-pool.ts +252 -0
- package/src/backend/openai-agents/models.ts +118 -11
- package/src/backend/openai-agents/session.ts +277 -0
- package/src/backend/openai-agents/state.ts +82 -11
- package/src/backend/opencode/factory.ts +54 -25
- package/src/backend/opencode/handler/index.ts +13 -0
- package/src/backend/opencode/handler/message.ts +325 -0
- package/src/backend/opencode/handler/state.ts +14 -0
- package/src/backend/opencode/handler/turn.ts +304 -0
- package/src/backend/opencode/index.ts +2 -2
- package/src/backend/opencode/model-provider.ts +26 -168
- package/src/backend/opencode/models/index.ts +51 -0
- package/src/backend/opencode/one-shot.ts +20 -137
- package/src/backend/opencode/server.ts +12 -11
- package/src/backend/remote-server/client.ts +13 -6
- package/src/backend/remote-server/events.ts +59 -9
- package/src/backend/remote-server/lifecycle.ts +3 -1
- package/src/backend/remote-server/mcp.ts +52 -19
- package/src/backend/remote-server/model-catalog/catalog.ts +270 -0
- package/src/backend/remote-server/model-catalog/index.ts +100 -0
- package/src/backend/remote-server/model-catalog/presentation.ts +298 -0
- package/src/backend/remote-server/model-catalog/provider.ts +193 -0
- package/src/backend/remote-server/model-catalog/resolve.ts +225 -0
- package/src/backend/remote-server/model-catalog/types.ts +136 -0
- package/src/backend/remote-server/one-shot.ts +225 -0
- package/src/backend/remote-server/session-helpers.ts +3 -4
- package/src/backend/remote-server/state.ts +1 -1
- package/src/backend/shared/delivery-contract.ts +161 -0
- package/src/backend/shared/delivery.ts +49 -0
- package/src/backend/shared/flow-violation.ts +38 -7
- package/src/backend/shared/frontends.ts +66 -0
- package/src/backend/shared/handle-retry.ts +244 -0
- package/src/backend/shared/handler-to-events.ts +186 -0
- package/src/backend/shared/handler-types.ts +57 -0
- package/src/backend/shared/index.ts +51 -5
- package/src/backend/shared/metrics.ts +199 -0
- package/src/backend/shared/model-retry.ts +1 -1
- package/src/backend/shared/prompt-format.ts +69 -0
- package/src/backend/shared/session-name.ts +1 -1
- package/src/backend/shared/stream-state.ts +36 -2
- package/src/backend/shared/system-prompt.ts +200 -23
- package/src/bootstrap.ts +338 -38
- package/src/cli/chat.ts +49 -0
- package/src/cli/config-view.ts +100 -0
- package/src/cli/config.ts +105 -0
- package/src/cli/context.ts +23 -0
- package/src/cli/daemon.ts +95 -0
- package/src/cli/doctor.ts +53 -0
- package/src/cli/index.ts +139 -0
- package/src/cli/logs.ts +60 -0
- package/src/cli/menu.ts +104 -0
- package/src/cli/setup.ts +455 -0
- package/src/cli/status.ts +75 -0
- package/src/cli.ts +20 -1211
- package/src/core/agent-runtime/README.md +173 -0
- package/src/{backend/registry.ts → core/agent-runtime/backend-registry.ts} +15 -6
- package/src/core/agent-runtime/capabilities.ts +330 -0
- package/src/core/agent-runtime/contract-tests.ts +378 -0
- package/src/core/agent-runtime/events.ts +291 -0
- package/src/core/agent-runtime/index.ts +62 -0
- package/src/core/agent-runtime/model-ref.ts +130 -0
- package/src/core/background/cron.ts +469 -0
- package/src/core/{dream.ts → background/dream.ts} +118 -53
- package/src/core/background/heartbeat/agent.ts +333 -0
- package/src/core/background/heartbeat/index.ts +25 -0
- package/src/core/background/heartbeat/scheduler.ts +211 -0
- package/src/core/background/heartbeat/state.ts +153 -0
- package/src/core/background/index.ts +21 -0
- package/src/core/background/isolated-agent.ts +115 -0
- package/src/core/background/job-health.ts +140 -0
- package/src/core/background/job-oneshot.ts +165 -0
- package/src/core/background/job-prompt.ts +54 -0
- package/src/core/{pulse.ts → background/pulse.ts} +6 -5
- package/src/core/background/triggers/command.ts +60 -0
- package/src/core/background/triggers/exit.ts +202 -0
- package/src/core/background/triggers/index.ts +38 -0
- package/src/core/background/triggers/output.ts +142 -0
- package/src/core/background/triggers/resume.ts +127 -0
- package/src/core/background/triggers/spawn.ts +282 -0
- package/src/core/background/triggers/state.ts +52 -0
- package/src/core/constants.ts +57 -28
- package/src/core/daemon/control.ts +229 -0
- package/src/core/daemon/discovery.ts +169 -0
- package/src/core/daemon/pidfile.ts +98 -0
- package/src/core/doctor.ts +459 -0
- package/src/core/engine/backend-controller/holders.ts +17 -0
- package/src/core/engine/backend-controller/index.ts +71 -0
- package/src/core/engine/backend-controller/legacy.ts +111 -0
- package/src/core/engine/backend-controller/pool.ts +296 -0
- package/src/core/engine/backend-controller/rebind.ts +205 -0
- package/src/core/engine/backend-controller/state.ts +150 -0
- package/src/core/engine/backend-controller/types.ts +51 -0
- package/src/core/engine/dispatcher.ts +68 -0
- package/src/core/engine/gateway-actions/cron.ts +518 -0
- package/src/core/engine/gateway-actions/fetch-url.ts +120 -0
- package/src/core/engine/gateway-actions/goals.ts +178 -0
- package/src/core/engine/gateway-actions/history.ts +53 -0
- package/src/core/engine/gateway-actions/index.ts +60 -0
- package/src/core/engine/gateway-actions/models.ts +120 -0
- package/src/core/engine/gateway-actions/plugins.ts +83 -0
- package/src/core/engine/gateway-actions/scripts.ts +127 -0
- package/src/core/engine/gateway-actions/shared.ts +66 -0
- package/src/core/engine/gateway-actions/skills.ts +120 -0
- package/src/core/engine/gateway-actions/triggers.ts +237 -0
- package/src/core/engine/gateway-actions/types.ts +18 -0
- package/src/core/{gateway.ts → engine/gateway.ts} +223 -60
- package/src/core/engine/index.ts +20 -0
- package/src/core/engine/model-audit.ts +121 -0
- package/src/core/errors.ts +68 -6
- package/src/core/mcp-hub/children.ts +254 -0
- package/src/core/mcp-hub/index.ts +335 -0
- package/src/core/mcp-hub/proxy-server.ts +45 -0
- package/src/core/mcp-hub/talon-server.ts +71 -0
- package/src/core/memory/retrieval.ts +90 -0
- package/src/core/models/active-model.ts +441 -0
- package/src/core/{models.ts → models/catalog.ts} +6 -0
- package/src/core/models/index.ts +17 -0
- package/src/core/models/reasoning-levels.ts +57 -0
- package/src/core/plugin/actions.ts +32 -0
- package/src/core/plugin/builtins.ts +162 -0
- package/src/core/plugin/index.ts +37 -0
- package/src/core/plugin/loader.ts +289 -0
- package/src/core/plugin/mcp.ts +86 -0
- package/src/core/plugin/registry.ts +124 -0
- package/src/core/plugin/types.ts +126 -0
- package/src/core/prompt/assemble.ts +273 -0
- package/src/core/prompt/disk-prompts.ts +41 -0
- package/src/core/prompt/embed.ts +107 -0
- package/src/core/prompt/embedded-prompts.ts +85 -0
- package/src/core/prompt/index.ts +30 -0
- package/src/core/prompt/templates.ts +124 -0
- package/src/core/prompt/workspace-listing.ts +91 -0
- package/src/core/scripting/lua-runner.ts +210 -0
- package/src/core/scripts/runner.ts +125 -0
- package/src/core/soul/README.md +110 -0
- package/src/core/soul/RESEARCH.md +98 -0
- package/src/core/soul/associative.ts +98 -0
- package/src/core/soul/centrality.ts +98 -0
- package/src/core/soul/cluster.ts +83 -0
- package/src/core/soul/compiler.ts +207 -0
- package/src/core/soul/consolidate.ts +179 -0
- package/src/core/soul/critic.ts +159 -0
- package/src/core/soul/dag.ts +265 -0
- package/src/core/soul/delta.ts +123 -0
- package/src/core/soul/drift.ts +99 -0
- package/src/core/soul/embedder.ts +129 -0
- package/src/core/soul/emergent-critic.ts +96 -0
- package/src/core/soul/forgetting.ts +131 -0
- package/src/core/soul/governance.ts +93 -0
- package/src/core/soul/hash.ts +97 -0
- package/src/core/soul/hdc.ts +154 -0
- package/src/core/soul/index.ts +140 -0
- package/src/core/soul/kernel.ts +540 -0
- package/src/core/soul/lattice.ts +103 -0
- package/src/core/soul/lens.ts +110 -0
- package/src/core/soul/projector.ts +240 -0
- package/src/core/soul/reflect.ts +170 -0
- package/src/core/soul/reflex.ts +164 -0
- package/src/core/soul/retrieve.ts +146 -0
- package/src/core/soul/salience.ts +142 -0
- package/src/core/soul/service.ts +204 -0
- package/src/core/soul/settings.ts +47 -0
- package/src/core/soul/signals.ts +117 -0
- package/src/core/soul/talon-embedder.ts +80 -0
- package/src/core/soul/taps.ts +199 -0
- package/src/core/soul/types.ts +298 -0
- package/src/core/soul/valence.ts +83 -0
- package/src/core/tools/goals.ts +117 -0
- package/src/core/tools/index.ts +10 -0
- package/src/core/tools/mcp-env.ts +94 -0
- package/src/core/tools/mcp-server.ts +27 -4
- package/src/core/tools/messaging.ts +122 -29
- package/src/core/tools/models.ts +39 -0
- package/src/core/tools/scheduling.ts +133 -14
- package/src/core/tools/schemas.ts +16 -4
- package/src/core/tools/scripts.ts +78 -0
- package/src/core/tools/skills.ts +90 -0
- package/src/core/tools/stickers.ts +1 -1
- package/src/core/tools/triggers.ts +19 -3
- package/src/core/tools/types.ts +6 -6
- package/src/core/types.ts +62 -112
- package/src/core/update/self-update.ts +269 -0
- package/src/core/weaver/index.ts +10 -0
- package/src/core/weaver/loom.ts +125 -0
- package/src/core/weaver/thread-session.ts +51 -0
- package/src/core/weaver/thread.ts +196 -0
- package/src/core/weaver/weaver.ts +357 -0
- package/src/frontend/discord/actions/chat-info.ts +268 -0
- package/src/frontend/discord/actions/index.ts +77 -0
- package/src/frontend/discord/actions/media.ts +190 -0
- package/src/frontend/discord/actions/messaging.ts +277 -0
- package/src/frontend/discord/actions/shared.ts +100 -0
- package/src/frontend/discord/actions/types.ts +33 -0
- package/src/frontend/discord/admin.ts +13 -23
- package/src/frontend/discord/callbacks/autocomplete.ts +55 -0
- package/src/frontend/discord/{callbacks.ts → callbacks/components.ts} +150 -190
- package/src/frontend/discord/callbacks/index.ts +14 -0
- package/src/frontend/discord/callbacks/modals.ts +76 -0
- package/src/frontend/discord/callbacks/shared.ts +22 -0
- package/src/frontend/discord/commands/admin.ts +88 -0
- package/src/frontend/discord/commands/definitions.ts +200 -0
- package/src/frontend/discord/commands/index.ts +19 -0
- package/src/frontend/discord/commands/info.ts +106 -0
- package/src/frontend/discord/commands/router.ts +149 -0
- package/src/frontend/discord/commands/session.ts +89 -0
- package/src/frontend/discord/commands/settings.ts +405 -0
- package/src/frontend/discord/commands/shared.ts +50 -0
- package/src/frontend/discord/formatting.ts +8 -34
- package/src/frontend/discord/handlers/access.ts +248 -0
- package/src/frontend/discord/handlers/context.ts +161 -0
- package/src/frontend/discord/handlers/delivery.ts +94 -0
- package/src/frontend/discord/handlers/index.ts +34 -0
- package/src/frontend/discord/handlers/messages.ts +120 -0
- package/src/frontend/discord/handlers/queue.ts +135 -0
- package/src/frontend/discord/handlers/registry.ts +29 -0
- package/src/frontend/discord/handlers/state.ts +94 -0
- package/src/frontend/discord/helpers.ts +46 -57
- package/src/frontend/discord/index.ts +10 -7
- package/src/frontend/discord/middleware.ts +2 -2
- package/src/frontend/native/actions.ts +133 -0
- package/src/frontend/native/chats.ts +146 -0
- package/src/frontend/native/discovery.ts +50 -0
- package/src/frontend/native/index.ts +1227 -0
- package/src/frontend/native/logs.ts +95 -0
- package/src/frontend/native/protocol.ts +253 -0
- package/src/frontend/native/server.ts +612 -0
- package/src/frontend/native/settings.ts +205 -0
- package/src/frontend/native/turn-meta.ts +43 -0
- package/src/frontend/shared/format.ts +51 -0
- package/src/frontend/shared/reasoning-levels.ts +46 -0
- package/src/frontend/shared/session-status.ts +189 -0
- package/src/frontend/shared/status-context.ts +125 -0
- package/src/frontend/teams/actions.ts +7 -5
- package/src/frontend/teams/formatting.ts +7 -18
- package/src/frontend/teams/graph.ts +2 -1
- package/src/frontend/teams/index.ts +76 -54
- package/src/frontend/telegram/actions/chat-info.ts +323 -0
- package/src/frontend/telegram/actions/index.ts +68 -0
- package/src/frontend/telegram/actions/media.ts +224 -0
- package/src/frontend/telegram/actions/messaging.ts +295 -0
- package/src/frontend/telegram/actions/shared.ts +58 -0
- package/src/frontend/telegram/actions/types.ts +31 -0
- package/src/frontend/telegram/admin.ts +12 -22
- package/src/frontend/telegram/callbacks/effort.ts +68 -0
- package/src/frontend/telegram/callbacks/index.ts +68 -0
- package/src/frontend/telegram/callbacks/model.ts +311 -0
- package/src/frontend/telegram/callbacks/pulse.ts +50 -0
- package/src/frontend/telegram/callbacks/settings.ts +177 -0
- package/src/frontend/telegram/callbacks/shared.ts +71 -0
- package/src/frontend/telegram/commands/admin.ts +226 -0
- package/src/frontend/telegram/commands/definitions.ts +60 -0
- package/src/frontend/telegram/commands/index.ts +39 -0
- package/src/frontend/telegram/commands/info.ts +125 -0
- package/src/frontend/telegram/commands/session.ts +77 -0
- package/src/frontend/telegram/commands/settings.ts +345 -0
- package/src/frontend/telegram/commands/state.ts +34 -0
- package/src/frontend/telegram/formatting.ts +14 -25
- package/src/frontend/telegram/handlers/access.ts +287 -0
- package/src/frontend/telegram/handlers/context.ts +210 -0
- package/src/frontend/telegram/handlers/delivery.ts +236 -0
- package/src/frontend/telegram/handlers/index.ts +39 -0
- package/src/frontend/telegram/handlers/messages.ts +477 -0
- package/src/frontend/telegram/handlers/queue.ts +241 -0
- package/src/frontend/telegram/handlers/state.ts +75 -0
- package/src/frontend/telegram/helpers/diagnostics.ts +188 -0
- package/src/frontend/telegram/helpers/format.ts +41 -0
- package/src/frontend/telegram/helpers/index.ts +13 -0
- package/src/frontend/telegram/{helpers.ts → helpers/menu.ts} +209 -210
- package/src/frontend/telegram/index.ts +24 -27
- package/src/frontend/telegram/middleware.ts +16 -3
- package/src/frontend/telegram/model-callbacks.ts +14 -0
- package/src/frontend/telegram/model-menu.ts +317 -0
- package/src/frontend/telegram/sticker-library.ts +178 -0
- package/src/frontend/telegram/userbot.ts +2 -44
- package/src/frontend/terminal/commands.ts +47 -41
- package/src/frontend/terminal/index.ts +46 -31
- package/src/frontend/terminal/input.ts +5 -0
- package/src/frontend/terminal/renderer.ts +4 -2
- package/src/index.ts +13 -171
- package/src/login.ts +3 -2
- package/src/native/blake3-wasm-bytes.ts +7 -0
- package/src/native/blake3.ts +234 -0
- package/src/native/htmlents-wasm-bytes.ts +7 -0
- package/src/native/htmlents.ts +56 -0
- package/src/native/prelude.d.mts +168 -0
- package/src/native/prelude.mjs +1561 -0
- package/src/native/registry.ts +142 -0
- package/src/native/runtime.ts +164 -0
- package/src/native/scheduler-core/gleam.d.mts +2 -0
- package/src/native/scheduler-core/gleam.mjs +1 -0
- package/src/native/scheduler-core/scheduler_core.d.mts +76 -0
- package/src/native/scheduler-core/scheduler_core.mjs +252 -0
- package/src/native/scheduler-core.ts +124 -0
- package/src/native/sqlguard-wasm-bytes.ts +7 -0
- package/src/native/sqlguard.ts +107 -0
- package/src/native/strsim-wasm-bytes.ts +7 -0
- package/src/native/strsim.ts +98 -0
- package/src/native/textops-wasm-bytes.ts +7 -0
- package/src/native/textops.ts +83 -0
- package/src/native/warden.ts +287 -0
- package/src/plugins/github/index.ts +1 -1
- package/src/plugins/mempalace/index.ts +1 -1
- package/src/plugins/playwright/index.ts +1 -1
- package/src/storage/chat-settings.ts +335 -85
- package/src/storage/cron-store.ts +266 -109
- package/src/storage/daily-log.ts +15 -3
- package/src/storage/db.ts +206 -0
- package/src/storage/goal-store.ts +186 -0
- package/src/storage/history.ts +137 -188
- package/src/storage/kv.ts +52 -0
- package/src/storage/legacy-import.ts +47 -0
- package/src/storage/media-index.ts +118 -61
- package/src/storage/repositories/chat-settings-repo.ts +60 -0
- package/src/storage/repositories/cron-repo.ts +128 -0
- package/src/storage/repositories/goals-repo.ts +122 -0
- package/src/storage/repositories/history-repo.ts +207 -0
- package/src/storage/repositories/kv-repo.ts +25 -0
- package/src/storage/repositories/media-index-repo.ts +147 -0
- package/src/storage/repositories/scripts-repo.ts +81 -0
- package/src/storage/repositories/sessions-repo.ts +129 -0
- package/src/storage/repositories/triggers-repo.ts +158 -0
- package/src/storage/repositories/turn-meta-repo.ts +32 -0
- package/src/storage/scheduled-store.ts +84 -0
- package/src/storage/script-store.ts +198 -0
- package/src/storage/sessions.ts +227 -102
- package/src/storage/skill-store.ts +325 -0
- package/src/storage/sql/README.md +18 -0
- package/src/storage/sql/chat-settings.sql +11 -0
- package/src/storage/sql/cron.sql +40 -0
- package/src/storage/sql/db.sql +11 -0
- package/src/storage/sql/embed.ts +118 -0
- package/src/storage/sql/goals.sql +41 -0
- package/src/storage/sql/history.sql +83 -0
- package/src/storage/sql/kv.sql +11 -0
- package/src/storage/sql/media-index.sql +45 -0
- package/src/storage/sql/schema.sql +233 -0
- package/src/storage/sql/scripts.sql +28 -0
- package/src/storage/sql/sessions.sql +22 -0
- package/src/storage/sql/statements.generated.ts +500 -0
- package/src/storage/sql/triggers.sql +69 -0
- package/src/storage/sql/turn-meta.sql +24 -0
- package/src/storage/sticker-store.ts +141 -0
- package/src/storage/trigger-store.ts +104 -126
- package/src/storage/turn-meta.ts +118 -0
- package/src/util/chat-id.ts +30 -0
- package/src/util/config.ts +313 -195
- package/src/util/fs-path.ts +32 -0
- package/src/util/log.ts +53 -31
- package/src/util/mcp-launcher.ts +351 -33
- package/src/util/paths.ts +45 -17
- package/src/util/tail-file.ts +24 -0
- package/src/util/workspace.ts +161 -10
- package/tsconfig.json +1 -0
- package/src/backend/claude-sdk/models.ts +0 -501
- package/src/backend/codex/handler.ts +0 -595
- package/src/backend/kilo/handler.ts +0 -707
- package/src/backend/kilo/models.ts +0 -762
- package/src/backend/openai-agents/handler.ts +0 -539
- package/src/backend/openai-agents/mcp.ts +0 -139
- package/src/backend/opencode/handler.ts +0 -663
- package/src/backend/opencode/models.ts +0 -741
- package/src/core/cron.ts +0 -184
- package/src/core/dispatcher.ts +0 -144
- package/src/core/gateway-actions.ts +0 -603
- package/src/core/heartbeat.ts +0 -575
- package/src/core/plugin.ts +0 -776
- package/src/core/triggers.ts +0 -640
- package/src/frontend/discord/actions.ts +0 -729
- package/src/frontend/discord/commands.ts +0 -1036
- package/src/frontend/discord/handlers.ts +0 -798
- package/src/frontend/telegram/actions.ts +0 -731
- package/src/frontend/telegram/callbacks.ts +0 -419
- package/src/frontend/telegram/commands.ts +0 -642
- package/src/frontend/telegram/handlers.ts +0 -1352
- package/src/util/mcp-launcher.mjs +0 -140
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned prompt templates — loader + Liquid renderer.
|
|
3
|
+
*
|
|
4
|
+
* Talon's prompt text lives in two places with different ownership:
|
|
5
|
+
*
|
|
6
|
+
* - **User-editable prompts** (`identity.md`, `base.md`/`custom.md`,
|
|
7
|
+
* frontend files like `telegram.md`) are seeded once into
|
|
8
|
+
* `~/.talon/prompts/` and read from there — user edits win, and
|
|
9
|
+
* package updates deliberately never overwrite them. These are
|
|
10
|
+
* rendered with plain `{{name}}` string replacement by their
|
|
11
|
+
* consumers (heartbeat/dream), NOT through Liquid — a user file
|
|
12
|
+
* must never be able to break prompt assembly with a syntax error.
|
|
13
|
+
*
|
|
14
|
+
* - **System templates** (`prompts/system/*.md` — delivery
|
|
15
|
+
* contracts, capability docs, section wrappers) are read straight
|
|
16
|
+
* from the PACKAGE directory and are NOT seeded. They describe
|
|
17
|
+
* runtime behaviour that is versioned with the code (tool names,
|
|
18
|
+
* flow enforcement, trigger limits); a stale seeded copy would
|
|
19
|
+
* silently document a contract the code no longer implements.
|
|
20
|
+
*
|
|
21
|
+
* System templates are [Liquid](https://liquidjs.com) — `{{name}}`
|
|
22
|
+
* output (missing → empty string, matching the legacy renderer),
|
|
23
|
+
* `{% if %}`/`{% else %}` conditionals, and `{% render 'partial' %}`
|
|
24
|
+
* includes resolved against `prompts/system/`. Liquid was chosen over
|
|
25
|
+
* a homegrown DSL so prompt text can be composed (sections,
|
|
26
|
+
* conditionals, partials) inside fewer files instead of one file per
|
|
27
|
+
* fragment — and over JS-in-template engines because templates stay
|
|
28
|
+
* pure prose: no code execution.
|
|
29
|
+
*
|
|
30
|
+
* Templates are parsed once per process and cached; rendering with
|
|
31
|
+
* vars is pure in-memory work.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type { FS } from "liquidjs";
|
|
35
|
+
import { Liquid, type Template } from "liquidjs";
|
|
36
|
+
import { promptAssetExists, readPromptAsset } from "#prompt-assets";
|
|
37
|
+
|
|
38
|
+
// ── Renderer ────────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
export type TemplateVars = Record<string, string | undefined>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Map a Liquid lookup (a bare partial name or `<name>.md`) to its rel path
|
|
44
|
+
* under the package `prompts/system/` directory. System templates live
|
|
45
|
+
* flat there and are referenced by bare name in `{% render %}`.
|
|
46
|
+
*/
|
|
47
|
+
const systemRel = (file: string): string => {
|
|
48
|
+
const base = file.split(/[\\/]/).pop() ?? file;
|
|
49
|
+
return `system/${base.endsWith(".md") ? base : `${base}.md`}`;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A Liquid filesystem backed by the `#prompt-assets` seam instead of the
|
|
54
|
+
* real disk, so `{% render 'partial' %}` includes resolve identically
|
|
55
|
+
* under tsx (reads `prompts/system/*.md` from disk) and a
|
|
56
|
+
* `bun build --compile` binary (reads the same files embedded in the
|
|
57
|
+
* binary). System templates are flat, so resolution is by basename.
|
|
58
|
+
*/
|
|
59
|
+
const liquidFs: FS = {
|
|
60
|
+
sep: "/",
|
|
61
|
+
dirname: () => ".",
|
|
62
|
+
resolve: (_dir, file, ext) => (file.endsWith(ext) ? file : `${file}${ext}`),
|
|
63
|
+
existsSync: (file) => promptAssetExists(systemRel(file)),
|
|
64
|
+
readFileSync: (file) => readPromptAsset(systemRel(file)),
|
|
65
|
+
exists: (file) => Promise.resolve(promptAssetExists(systemRel(file))),
|
|
66
|
+
readFile: (file) => Promise.resolve(readPromptAsset(systemRel(file))),
|
|
67
|
+
contains: () => Promise.resolve(true),
|
|
68
|
+
containsSync: () => true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* One engine instance per process. The custom `fs` lets templates compose
|
|
73
|
+
* via `{% render 'partial-name' %}` against `prompts/system/`. Variables
|
|
74
|
+
* stay lenient (unknown → empty string) — a template must degrade to
|
|
75
|
+
* readable prose when an optional var (e.g. a frontend without
|
|
76
|
+
* reactions) is absent.
|
|
77
|
+
*/
|
|
78
|
+
const liquid = new Liquid({
|
|
79
|
+
fs: liquidFs,
|
|
80
|
+
extname: ".md",
|
|
81
|
+
// JS truthiness, not Shopify's: `""` and `0` are falsy. The legacy
|
|
82
|
+
// renderer treated empty-string vars as "absent", and callers rely
|
|
83
|
+
// on it (`truncated: truncated ? "yes" : undefined`-style flags).
|
|
84
|
+
jsTruthy: true,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Render a raw Liquid template string with `vars`. For one-off
|
|
89
|
+
* strings (tests, dynamic snippets); file templates should go through
|
|
90
|
+
* `loadSystemTemplate`, which caches the parse.
|
|
91
|
+
*/
|
|
92
|
+
export function renderTemplate(template: string, vars: TemplateVars): string {
|
|
93
|
+
return liquid.parseAndRenderSync(template, vars);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Loader ──────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
const parseCache = new Map<string, Template[]>();
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load a system template by name (e.g. `"contract-tool-only"`) and
|
|
102
|
+
* render it with `vars`. Throws if the file is missing — system
|
|
103
|
+
* templates ship with the package, so absence is a packaging bug, not
|
|
104
|
+
* a user-configuration state.
|
|
105
|
+
*/
|
|
106
|
+
export function loadSystemTemplate(
|
|
107
|
+
name: string,
|
|
108
|
+
vars: TemplateVars = {},
|
|
109
|
+
): string {
|
|
110
|
+
const file = `${name}.md`;
|
|
111
|
+
let parsed = parseCache.get(file);
|
|
112
|
+
if (parsed === undefined) {
|
|
113
|
+
// The filepath argument anchors relative {% render %} resolution
|
|
114
|
+
// (resolved against the prompt-asset seam, not the disk).
|
|
115
|
+
parsed = liquid.parse(readPromptAsset(`system/${file}`).trim(), file);
|
|
116
|
+
parseCache.set(file, parsed);
|
|
117
|
+
}
|
|
118
|
+
return liquid.renderSync(parsed, vars) as string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Test seam: drop the parse cache (e.g. after writing fixture templates). */
|
|
122
|
+
export function clearTemplateCache(): void {
|
|
123
|
+
parseCache.clear();
|
|
124
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workspace file listing for the system prompt's dynamic tail.
|
|
3
|
+
*
|
|
4
|
+
* Renders a compact tree of `~/.talon/workspace/` so the model knows
|
|
5
|
+
* what artifacts exist. Volatile by nature (file sizes change as logs
|
|
6
|
+
* grow), so it always belongs to the DYNAMIC prompt part — never the
|
|
7
|
+
* cacheable static prefix.
|
|
8
|
+
*
|
|
9
|
+
* Any directory with more than `COLLAPSE_THRESHOLD` rendered entries
|
|
10
|
+
* collapses to a single `name/ (N files)` summary line, so per-file
|
|
11
|
+
* sizes inside such a directory are never needed. The tree is built
|
|
12
|
+
* lazily: `statSync` is deferred to render time and only ever called
|
|
13
|
+
* for files that actually appear in the output. The eager
|
|
14
|
+
* implementation stat'd every file during the walk — on a workspace
|
|
15
|
+
* with tens of thousands of files (logs, build artifacts, media) that
|
|
16
|
+
* meant one blocking syscall per file on every session start, just to
|
|
17
|
+
* print a handful of summary lines.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readdirSync, statSync } from "node:fs";
|
|
21
|
+
import { resolve } from "node:path";
|
|
22
|
+
|
|
23
|
+
const COLLAPSE_THRESHOLD = 8;
|
|
24
|
+
|
|
25
|
+
// A node contributes `count` rendered lines to its parent (used to
|
|
26
|
+
// decide the collapse) and renders those lines lazily via `render()`.
|
|
27
|
+
type Node = { count: number; render: () => string[] };
|
|
28
|
+
|
|
29
|
+
function buildNode(dir: string, prefix: string): Node {
|
|
30
|
+
const children: Node[] = [];
|
|
31
|
+
try {
|
|
32
|
+
for (const e of readdirSync(dir, { withFileTypes: true })) {
|
|
33
|
+
if (
|
|
34
|
+
e.name.startsWith(".") ||
|
|
35
|
+
e.name === "node_modules" ||
|
|
36
|
+
e.name === "talon.log"
|
|
37
|
+
)
|
|
38
|
+
continue;
|
|
39
|
+
const full = resolve(dir, e.name);
|
|
40
|
+
if (e.isDirectory()) {
|
|
41
|
+
const sub = buildNode(full, `${prefix}${e.name}/`);
|
|
42
|
+
if (sub.count === 0) continue; // empty dir → omitted
|
|
43
|
+
if (sub.count <= COLLAPSE_THRESHOLD) {
|
|
44
|
+
children.push(sub); // expand inline
|
|
45
|
+
} else {
|
|
46
|
+
// Collapse to one summary line — never render (or stat) the
|
|
47
|
+
// files inside.
|
|
48
|
+
const line = `${prefix}${e.name}/ (${sub.count} files)`;
|
|
49
|
+
children.push({ count: 1, render: () => [line] });
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
children.push({
|
|
53
|
+
count: 1,
|
|
54
|
+
render: () => {
|
|
55
|
+
let sz = 0;
|
|
56
|
+
try {
|
|
57
|
+
sz = statSync(full).size;
|
|
58
|
+
} catch {
|
|
59
|
+
/* file vanished between readdir and stat — show 0B */
|
|
60
|
+
}
|
|
61
|
+
return [
|
|
62
|
+
`${prefix}${e.name} (${sz < 1024 ? sz + "B" : (sz / 1024).toFixed(0) + "KB"})`,
|
|
63
|
+
];
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
/* unreadable dir → treat as empty */
|
|
70
|
+
}
|
|
71
|
+
const count = children.reduce((n, c) => n + c.count, 0);
|
|
72
|
+
return { count, render: () => children.flatMap((c) => c.render()) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Render the `## Current Workspace Contents` section, or an empty
|
|
77
|
+
* string when the workspace is missing or empty.
|
|
78
|
+
*/
|
|
79
|
+
export function renderWorkspaceListing(workspaceDir: string): string {
|
|
80
|
+
try {
|
|
81
|
+
const files = buildNode(workspaceDir, "").render();
|
|
82
|
+
if (files.length === 0) return "";
|
|
83
|
+
return (
|
|
84
|
+
"## Current Workspace Contents\n\n" +
|
|
85
|
+
files.map((f) => ` ${f}`).join("\n")
|
|
86
|
+
);
|
|
87
|
+
} catch {
|
|
88
|
+
/* no workspace yet */
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lua trigger runner — executes a trigger script inside a WASM-sandboxed
|
|
3
|
+
* Lua 5.4 VM (wasmoon), speaking the same stdout protocol bash/python/node
|
|
4
|
+
* triggers use (see core/background/triggers.ts).
|
|
5
|
+
*
|
|
6
|
+
* Why a separate process at all: the trigger supervisor's whole contract
|
|
7
|
+
* (kill/respawn, SIGTERM grace, log streaming, TALON_FIRE line scanning)
|
|
8
|
+
* is built around child processes. Lua therefore runs as `talon _lua-run
|
|
9
|
+
* <script>` — the same self-reinvocation shape MCP supervision uses
|
|
10
|
+
* (util/mcp-launcher.ts) — so it works identically for tsx source runs,
|
|
11
|
+
* npm installs, and bun-compiled binaries with no source tree on disk.
|
|
12
|
+
*
|
|
13
|
+
* Sandbox (verified empirically, not aspirational):
|
|
14
|
+
* - wasmoon is Lua 5.4 compiled to WASM under emscripten. The VM's
|
|
15
|
+
* filesystem is MEMFS: `io.open("/etc/hostname")` returns nil, and
|
|
16
|
+
* `os.getenv` sees only emscripten's stub environment — the host
|
|
17
|
+
* process env is invisible. No sockets, no subprocesses.
|
|
18
|
+
* - The full Lua stdlib (string/table/math/os.time/os.clock/io) works
|
|
19
|
+
* INSIDE the VM; io/os just can't reach the host.
|
|
20
|
+
* - The only host surface is the injected `talon` API below.
|
|
21
|
+
*
|
|
22
|
+
* Host API (global `talon` table):
|
|
23
|
+
* talon.fire(text) — emit `TALON_FIRE: <text>` on stdout (newlines in
|
|
24
|
+
* text collapsed to spaces); fires a mid-run wake-up.
|
|
25
|
+
* talon.log(text) — stderr line; lands in the trigger run-log as
|
|
26
|
+
* `[stderr] …`.
|
|
27
|
+
* talon.sleep(ms) — synchronous-looking sleep. Implemented as a JS
|
|
28
|
+
* promise the Lua thread suspends on via wasmoon's
|
|
29
|
+
* `:await()` (the engine yields the coroutine and
|
|
30
|
+
* resumes it when the promise settles).
|
|
31
|
+
*
|
|
32
|
+
* Fire-protocol forgery guard: ALL VM-visible stdout — print(), io.write,
|
|
33
|
+
* io.output():write, anything emscripten emits — funnels through
|
|
34
|
+
* console.log (emscripten's `out` channel). We patch BOTH console.log and
|
|
35
|
+
* process.stdout.write for the run: under node, console.log delegates to
|
|
36
|
+
* the stream's write method, but under bun (compiled binaries) console.log
|
|
37
|
+
* is native and bypasses it entirely — patching only the stream was
|
|
38
|
+
* verified to let a forged print() through in the compiled binary.
|
|
39
|
+
* Everything is line-buffered: any script-originated line starting with
|
|
40
|
+
* `TALON_FIRE:` is escaped with one leading space, which defeats the
|
|
41
|
+
* supervisor's startsWith() check while keeping the text readable in the
|
|
42
|
+
* run-log. Only talon.fire writes through the saved raw stream method, and
|
|
43
|
+
* it terminates any pending partial script line first so a trailing
|
|
44
|
+
* io.write fragment can't glue onto (and break) the protocol line. This is
|
|
45
|
+
* an anti-accident/anti-confusion guard at the stdout choke points —
|
|
46
|
+
* the host sandbox above is what provides actual isolation.
|
|
47
|
+
*
|
|
48
|
+
* Known stdio quirk (verified): `io.write("text")` WITHOUT a trailing
|
|
49
|
+
* newline sits in emscripten's internal TTY buffer and is never flushed —
|
|
50
|
+
* not at chunk end, not at engine close — so it's silently lost, exactly
|
|
51
|
+
* like unflushed C stdio. Scripts should end stdout writes with "\n"
|
|
52
|
+
* (print() always does).
|
|
53
|
+
*
|
|
54
|
+
* Exit codes: 0 — script completed; 1 — Lua error (message + traceback on
|
|
55
|
+
* stderr); 2 — usage error (no script path / unreadable file).
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
import { readFileSync } from "node:fs";
|
|
59
|
+
import { basename } from "node:path";
|
|
60
|
+
|
|
61
|
+
/** Hidden CLI subcommand that turns a Talon process into the Lua runner.
|
|
62
|
+
* Dispatched by src/index.ts and src/cli.ts before the app graph loads. */
|
|
63
|
+
export const LUA_RUN_SUBCOMMAND = "_lua-run";
|
|
64
|
+
|
|
65
|
+
/** Must match FIRE_PREFIX in core/background/triggers.ts. */
|
|
66
|
+
const FIRE_PREFIX = "TALON_FIRE:";
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Run a Lua script file to completion inside a fresh wasmoon engine.
|
|
70
|
+
* Throws on Lua errors (message shaped `<file>:<line>: <msg>` plus
|
|
71
|
+
* traceback). Restores process.stdout.write before returning.
|
|
72
|
+
*/
|
|
73
|
+
export async function runLuaScript(scriptPath: string): Promise<void> {
|
|
74
|
+
const source = readFileSync(scriptPath, "utf-8");
|
|
75
|
+
|
|
76
|
+
const originalWrite = process.stdout.write;
|
|
77
|
+
const originalConsoleLog = console.log;
|
|
78
|
+
const rawWrite = originalWrite.bind(process.stdout);
|
|
79
|
+
|
|
80
|
+
// ── Line-buffered, fire-escaping stdout for everything the VM emits ──
|
|
81
|
+
let pending = "";
|
|
82
|
+
const emitLine = (line: string): void => {
|
|
83
|
+
rawWrite(line.startsWith(FIRE_PREFIX) ? ` ${line}\n` : `${line}\n`);
|
|
84
|
+
};
|
|
85
|
+
const scriptWrite = (text: string): void => {
|
|
86
|
+
pending += text;
|
|
87
|
+
let nl: number;
|
|
88
|
+
while ((nl = pending.indexOf("\n")) !== -1) {
|
|
89
|
+
emitLine(pending.slice(0, nl));
|
|
90
|
+
pending = pending.slice(nl + 1);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const flushPending = (): void => {
|
|
94
|
+
if (pending.length === 0) return;
|
|
95
|
+
emitLine(pending);
|
|
96
|
+
pending = "";
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Both patches MUST be installed before wasmoon is imported: its
|
|
100
|
+
// emscripten glue captures `console.log.bind(console)` at module-eval
|
|
101
|
+
// time. Under node the binding still routes through the stream's write
|
|
102
|
+
// method at call time, but under bun (compiled binaries) console.log is
|
|
103
|
+
// native and a pre-patch binding writes straight to fd 1 — verified to
|
|
104
|
+
// let a forged print() through when the patch came after the import.
|
|
105
|
+
process.stdout.write = ((
|
|
106
|
+
chunk: string | Uint8Array,
|
|
107
|
+
encodingOrCb?: BufferEncoding | ((err?: Error | null) => void),
|
|
108
|
+
cb?: (err?: Error | null) => void,
|
|
109
|
+
): boolean => {
|
|
110
|
+
scriptWrite(
|
|
111
|
+
typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf-8"),
|
|
112
|
+
);
|
|
113
|
+
const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb;
|
|
114
|
+
callback?.();
|
|
115
|
+
return true;
|
|
116
|
+
}) as typeof process.stdout.write;
|
|
117
|
+
|
|
118
|
+
// Emscripten calls console.log with a single already-formatted string
|
|
119
|
+
// per line; joining covers stray multi-arg calls.
|
|
120
|
+
console.log = (...args: unknown[]): void => {
|
|
121
|
+
scriptWrite(`${args.map(String).join(" ")}\n`);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
let lua: import("wasmoon").LuaEngine | undefined;
|
|
125
|
+
try {
|
|
126
|
+
// Lazy import: entrypoints and triggers.ts import this module just for
|
|
127
|
+
// LUA_RUN_SUBCOMMAND — they must not pay for the embedded WASM bundle.
|
|
128
|
+
const { LuaFactory } = await import("wasmoon");
|
|
129
|
+
const factory = new LuaFactory();
|
|
130
|
+
lua = await factory.createEngine();
|
|
131
|
+
|
|
132
|
+
// ── Host API ──
|
|
133
|
+
lua.global.set("__talon_fire", (text: unknown) => {
|
|
134
|
+
flushPending();
|
|
135
|
+
const single = String(text)
|
|
136
|
+
.replace(/[\r\n]+/g, " ")
|
|
137
|
+
.trim();
|
|
138
|
+
rawWrite(`${FIRE_PREFIX} ${single}\n`);
|
|
139
|
+
});
|
|
140
|
+
lua.global.set("__talon_log", (text: unknown) => {
|
|
141
|
+
process.stderr.write(`${String(text)}\n`);
|
|
142
|
+
});
|
|
143
|
+
lua.global.set("__talon_sleep", (ms: unknown) => {
|
|
144
|
+
const delay = Math.max(0, Number(ms) || 0);
|
|
145
|
+
return new Promise<void>((resolve) => setTimeout(resolve, delay));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// The sleep wrapper hides the promise plumbing: scripts just call
|
|
149
|
+
// talon.sleep(ms) and the Lua thread suspends until it resolves.
|
|
150
|
+
await lua.doString(`
|
|
151
|
+
talon = {
|
|
152
|
+
fire = function(text) __talon_fire(tostring(text)) end,
|
|
153
|
+
log = function(text) __talon_log(tostring(text)) end,
|
|
154
|
+
sleep = function(ms) __talon_sleep(ms):await() end,
|
|
155
|
+
}
|
|
156
|
+
`);
|
|
157
|
+
|
|
158
|
+
// Mount under the script's basename so Lua errors read
|
|
159
|
+
// `<name>.lua:<line>: <msg>` instead of `[string "…"]`.
|
|
160
|
+
const mounted = `/${basename(scriptPath)}`;
|
|
161
|
+
await factory.mountFile(mounted, source);
|
|
162
|
+
await lua.doFile(mounted);
|
|
163
|
+
} finally {
|
|
164
|
+
// Close the engine BEFORE restoring the patches: if any
|
|
165
|
+
// wasmoon/emscripten teardown ever flushes buffered VM stdout, it must
|
|
166
|
+
// still hit the sanitizer. (Empirically nothing flushes today — see
|
|
167
|
+
// header — but the safe order costs nothing.)
|
|
168
|
+
lua?.global.close();
|
|
169
|
+
console.log = originalConsoleLog;
|
|
170
|
+
process.stdout.write = originalWrite;
|
|
171
|
+
flushPending();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* `_lua-run` entry: run argv[0] as a Lua script and exit with the
|
|
177
|
+
* protocol's exit code. Never resolves — mirrors runSupervisor() so
|
|
178
|
+
* entrypoints can `await` it and nothing after the dispatch ever runs.
|
|
179
|
+
*/
|
|
180
|
+
export async function runLuaMain(argvTail: string[]): Promise<never> {
|
|
181
|
+
const scriptPath = argvTail[0];
|
|
182
|
+
if (!scriptPath) {
|
|
183
|
+
process.stderr.write("lua-runner: missing script path\n");
|
|
184
|
+
process.exit(2);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let code = 0;
|
|
188
|
+
try {
|
|
189
|
+
await runLuaScript(scriptPath);
|
|
190
|
+
} catch (err) {
|
|
191
|
+
process.stderr.write(
|
|
192
|
+
`${err instanceof Error ? err.message : String(err)}\n`,
|
|
193
|
+
);
|
|
194
|
+
code = 1;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Don't process.exit() straight away: stdout/stderr to a pipe flush
|
|
198
|
+
// asynchronously and an immediate exit can drop the tail of the output
|
|
199
|
+
// (including the final TALON_FIRE line). exitCode is the fallback if a
|
|
200
|
+
// stream never runs its callback.
|
|
201
|
+
process.exitCode = code;
|
|
202
|
+
let remaining = 2;
|
|
203
|
+
const done = (): void => {
|
|
204
|
+
remaining -= 1;
|
|
205
|
+
if (remaining === 0) process.exit(code);
|
|
206
|
+
};
|
|
207
|
+
process.stdout.write("", done);
|
|
208
|
+
process.stderr.write("", done);
|
|
209
|
+
return new Promise<never>(() => {});
|
|
210
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Script runner — run-to-completion execution of saved scripts.
|
|
3
|
+
*
|
|
4
|
+
* Unlike triggers (supervised long-running watchers with a fire
|
|
5
|
+
* protocol), a script invocation is a plain subprocess: spawn the
|
|
6
|
+
* interpreter on the script with the caller's args, capture output,
|
|
7
|
+
* enforce a timeout, return the result to the calling turn. Local
|
|
8
|
+
* execution — no model involvement, no token cost.
|
|
9
|
+
*
|
|
10
|
+
* Security posture matches triggers and the agent's Bash tool: the
|
|
11
|
+
* bot account (its OS user, its workspace) is the boundary, not the
|
|
12
|
+
* script contents.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawn } from "node:child_process";
|
|
16
|
+
import { dirs } from "../../util/paths.js";
|
|
17
|
+
import { log } from "../../util/log.js";
|
|
18
|
+
import { commandForLanguage } from "../background/triggers/index.js";
|
|
19
|
+
import type { Script } from "../../storage/script-store.js";
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_SCRIPT_TIMEOUT_SECONDS = 60;
|
|
22
|
+
export const MAX_SCRIPT_TIMEOUT_SECONDS = 300;
|
|
23
|
+
|
|
24
|
+
/** Cap on captured stdout/stderr — keeps tool results context-friendly. */
|
|
25
|
+
const OUTPUT_CAP_CHARS = 16_000;
|
|
26
|
+
|
|
27
|
+
export type ScriptRunResult = {
|
|
28
|
+
exitCode: number | null;
|
|
29
|
+
stdout: string;
|
|
30
|
+
stderr: string;
|
|
31
|
+
timedOut: boolean;
|
|
32
|
+
durationMs: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function validateScriptTimeout(seconds: number): string | null {
|
|
36
|
+
if (!Number.isFinite(seconds) || seconds <= 0)
|
|
37
|
+
return "Timeout must be a positive number";
|
|
38
|
+
if (seconds > MAX_SCRIPT_TIMEOUT_SECONDS)
|
|
39
|
+
return `Timeout exceeds max (${MAX_SCRIPT_TIMEOUT_SECONDS}s)`;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run a script to completion. Resolves (never rejects) with the
|
|
45
|
+
* captured result; spawn failures surface as `exitCode: null` with
|
|
46
|
+
* the error message in stderr.
|
|
47
|
+
*/
|
|
48
|
+
export function runScript(
|
|
49
|
+
script: Script,
|
|
50
|
+
args: readonly string[] = [],
|
|
51
|
+
timeoutSeconds = DEFAULT_SCRIPT_TIMEOUT_SECONDS,
|
|
52
|
+
): Promise<ScriptRunResult> {
|
|
53
|
+
const command = commandForLanguage(script.language);
|
|
54
|
+
if (!command) {
|
|
55
|
+
return Promise.resolve({
|
|
56
|
+
exitCode: null,
|
|
57
|
+
stdout: "",
|
|
58
|
+
stderr: `No ${script.language} interpreter available on this host`,
|
|
59
|
+
timedOut: false,
|
|
60
|
+
durationMs: 0,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const t0 = Date.now();
|
|
65
|
+
return new Promise((resolvePromise) => {
|
|
66
|
+
let stdout = "";
|
|
67
|
+
let stderr = "";
|
|
68
|
+
let timedOut = false;
|
|
69
|
+
let settled = false;
|
|
70
|
+
|
|
71
|
+
const child = spawn(
|
|
72
|
+
command.cmd,
|
|
73
|
+
[...command.args, script.scriptPath, ...args],
|
|
74
|
+
{
|
|
75
|
+
cwd: dirs.workspace,
|
|
76
|
+
env: process.env,
|
|
77
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
78
|
+
windowsHide: true,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const timer = setTimeout(() => {
|
|
83
|
+
timedOut = true;
|
|
84
|
+
child.kill("SIGTERM");
|
|
85
|
+
// SIGKILL backstop for scripts that trap/ignore SIGTERM.
|
|
86
|
+
setTimeout(() => child.kill("SIGKILL"), 5_000).unref();
|
|
87
|
+
// Settle without waiting for 'close': a grandchild that
|
|
88
|
+
// inherited the stdio pipes (e.g. bash spawning `sleep`) keeps
|
|
89
|
+
// them open after the direct child dies, so 'close' may never
|
|
90
|
+
// fire. Whatever output was captured by now is the result.
|
|
91
|
+
setTimeout(() => settle(null), 1_500).unref();
|
|
92
|
+
}, timeoutSeconds * 1000);
|
|
93
|
+
timer.unref();
|
|
94
|
+
|
|
95
|
+
child.stdout.on("data", (chunk: Buffer) => {
|
|
96
|
+
if (stdout.length < OUTPUT_CAP_CHARS) stdout += chunk.toString("utf-8");
|
|
97
|
+
});
|
|
98
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
99
|
+
if (stderr.length < OUTPUT_CAP_CHARS) stderr += chunk.toString("utf-8");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const settle = (exitCode: number | null, spawnError?: string) => {
|
|
103
|
+
if (settled) return;
|
|
104
|
+
settled = true;
|
|
105
|
+
clearTimeout(timer);
|
|
106
|
+
const durationMs = Date.now() - t0;
|
|
107
|
+
log(
|
|
108
|
+
"scripts",
|
|
109
|
+
`ran "${script.name}" (exit=${exitCode ?? "n/a"}${timedOut ? ", TIMED OUT" : ""}, ${durationMs}ms)`,
|
|
110
|
+
);
|
|
111
|
+
resolvePromise({
|
|
112
|
+
exitCode,
|
|
113
|
+
stdout: stdout.slice(0, OUTPUT_CAP_CHARS),
|
|
114
|
+
stderr: spawnError
|
|
115
|
+
? `${spawnError}\n${stderr}`.slice(0, OUTPUT_CAP_CHARS)
|
|
116
|
+
: stderr.slice(0, OUTPUT_CAP_CHARS),
|
|
117
|
+
timedOut,
|
|
118
|
+
durationMs,
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
child.on("error", (err) => settle(null, err.message));
|
|
123
|
+
child.on("close", (code) => settle(code));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Soul Kernel
|
|
2
|
+
|
|
3
|
+
Talon's compiled identity. Not a markdown prompt, not a JSON blob — a
|
|
4
|
+
**content-addressed Merkle DAG** of typed identity nodes, learned **mechanically**
|
|
5
|
+
from behavioral telemetry and projected into the system prompt by **selection**.
|
|
6
|
+
|
|
7
|
+
> The model is the brain. Talon is the body. The Soul is the persistent identity
|
|
8
|
+
> artifact that tells the brain how to be Talon.
|
|
9
|
+
|
|
10
|
+
## The thesis: the model never writes its own soul
|
|
11
|
+
|
|
12
|
+
There are two jobs — _being the self_ (the reasoning model reads the soul and
|
|
13
|
+
acts) and _writing the self_ (turning experience into identity). If the reasoning
|
|
14
|
+
model does the second, it can rationalize, flatter itself, and confabulate a
|
|
15
|
+
coherent self-story that isn't true. So **writing the self is strictly
|
|
16
|
+
model-free**: continuous, deterministic, evidence-grounded. The reasoning model is
|
|
17
|
+
a _reader_ of the soul, never its author. That is the safety property, not just a
|
|
18
|
+
cost saving.
|
|
19
|
+
|
|
20
|
+
The only neural component anywhere is an optional **local embedder** (for
|
|
21
|
+
clustering/dedup/contradiction-distance) — a frozen fixed function, not an agent.
|
|
22
|
+
Everything else is counters, exponential decay, dot products, and selection.
|
|
23
|
+
|
|
24
|
+
## The substrate
|
|
25
|
+
|
|
26
|
+
A node's identity is the sha-256 of its canonical content. The single most
|
|
27
|
+
important decision is the split between:
|
|
28
|
+
|
|
29
|
+
- **Content** (hashed, immutable) — a node's semantic identity: kind + verbatim
|
|
30
|
+
payload + referenced child hashes. A change of content is a new node.
|
|
31
|
+
- **State** (mutable, keyed by hash) — salience, evidence weight, activation
|
|
32
|
+
counts. Churns every tick; lives _outside_ the Merkle structure.
|
|
33
|
+
|
|
34
|
+
So reinforcing a value a thousand times never churns a single content hash.
|
|
35
|
+
Versioning, provenance, dedup, and partial recompilation fall out structurally;
|
|
36
|
+
learning and decay live in the state layer.
|
|
37
|
+
|
|
38
|
+
## The five structures
|
|
39
|
+
|
|
40
|
+
| Structure | Node kind | What it is |
|
|
41
|
+
| --------- | --------------- | ------------------------------------------------------------------------------------------- |
|
|
42
|
+
| Spine | `spine` | append-only causal narrative — _why_ Talon became Talon |
|
|
43
|
+
| Lattice | `value` + edges | values as evidence clusters; tension/affinity edges self-organize via Hebbian co-activation |
|
|
44
|
+
| Reflexes | `reflex` | `trigger → guard → action` rules the harness _enforces_ |
|
|
45
|
+
| Holograph | `lens` | identity refracted per interlocutor (selection + reweighting) |
|
|
46
|
+
| Critic | (classifiers) | frozen feature-based gates over failure modes |
|
|
47
|
+
|
|
48
|
+
The atoms under all of it are `evidence` nodes — verbatim ground truth. The kernel
|
|
49
|
+
never paraphrases; it only ever quotes.
|
|
50
|
+
|
|
51
|
+
## The model-free pipeline
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
telemetry ──▶ signals ──▶ compiler ──▶ DAG (+ salience/Hebbian) ──▶ projector ──▶ runtime.md
|
|
55
|
+
(harness) (structured) (arithmetic) (content + state) (selection) (injected)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **Signals** (`signals.ts`) — structured harness events (reaction, engagement,
|
|
59
|
+
correction, reflex-fire). Interpretation already happened; no transcript reading.
|
|
60
|
+
- **Compiler** (`compiler.ts`) — routes each signal to pure update rules. Outcome
|
|
61
|
+
signals reinforce/penalize the values that were live in that turn's projection
|
|
62
|
+
and Hebbian-coactivate them; corrections store verbatim evidence + a Spine event.
|
|
63
|
+
- **Salience** (`salience.ts`) — lazy exponential decay, reinforcement, Hebbian
|
|
64
|
+
edges, predictive-coding delta rule. The whole "learning" is here, all arithmetic.
|
|
65
|
+
- **Projector** (`projector.ts`) — selects verbatim evidence, salience-ordered
|
|
66
|
+
under a token budget. Reflexes are never budgeted away. The soul cannot
|
|
67
|
+
hallucinate itself.
|
|
68
|
+
- **Critic** (`critic.ts`) — frozen classifiers for wall-of-text, sycophancy,
|
|
69
|
+
emoji-overload.
|
|
70
|
+
- **Kernel** (`kernel.ts`) — orchestrator: genesis → ingest → commit → project,
|
|
71
|
+
with a git-like commit chain and JSON persistence.
|
|
72
|
+
|
|
73
|
+
## File map
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
types.ts node payloads, edges, activation state, commits, config
|
|
77
|
+
hash.ts canonical serialization + content addressing + Merkle root
|
|
78
|
+
dag.ts the DAG store: integrity, dirty propagation, snapshot/restore
|
|
79
|
+
salience.ts decay / reinforce / coactivate / prediction-error (the math)
|
|
80
|
+
reflex.ts predicate registry + enforcer + the three seed reflexes
|
|
81
|
+
signals.ts structured telemetry types + emoji→valence
|
|
82
|
+
compiler.ts signal → arithmetic update routing (writing the self)
|
|
83
|
+
projector.ts DAG → runtime surface by selection (reading the self)
|
|
84
|
+
critic.ts frozen failure-mode classifiers
|
|
85
|
+
kernel.ts orchestrator: lifecycle, commits, persistence
|
|
86
|
+
index.ts public surface
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Status
|
|
90
|
+
|
|
91
|
+
Implemented and tested (150+ tests, model-free end to end):
|
|
92
|
+
|
|
93
|
+
- **Substrate** — content-addressed Merkle DAG, dirty-tracking, commits, persistence.
|
|
94
|
+
- **Learning** — salience decay/reinforce, Hebbian edges, predictive-coding updates.
|
|
95
|
+
- **Emergence** — value clustering (`crystallize`), self-reorganizing `consolidate`
|
|
96
|
+
("dream") with adaptive thresholds + state migration, reflection into `theme`s.
|
|
97
|
+
- **Embedding** — our own `TalonEmbedder` (model-free) behind a pluggable seam.
|
|
98
|
+
- **Adaptation** — learned valence (meaning from outcomes), FSRS/DSR forgetting,
|
|
99
|
+
ADWIN drift → Spine epochs, emergent failure modes.
|
|
100
|
+
- **Recall** — Generative-Agents context retrieval, PageRank centrality, modern
|
|
101
|
+
Hopfield associative recall, VSA/HDC compositional episodic memory.
|
|
102
|
+
- **Refraction** — relational holograph lens compilation.
|
|
103
|
+
- **Surfaces** — projector (selection), delta stream, mechanical Critic.
|
|
104
|
+
- **Governance** — protected-node approval queue; runtime gate (off by default).
|
|
105
|
+
|
|
106
|
+
See `RESEARCH.md` for the literature behind each mechanism.
|
|
107
|
+
|
|
108
|
+
Remaining: **harness wiring** — telemetry taps (reactions/engagement/corrections →
|
|
109
|
+
signals), prompt injection of the projected surface, and live reflex enforcement.
|
|
110
|
+
This touches the live Talon runtime and is gated behind `TALON_SOUL_ENABLED`.
|